OSDN Git Service

シンプルな型名を使用する (IDE0049)
[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 readonly TwitterApi twitterApi = new TwitterApi();
117         private Twitter tw;
118
119         //Growl呼び出し部
120         private readonly GrowlHelper gh = new GrowlHelper(ApplicationSettings.ApplicationName);
121
122         //サブ画面インスタンス
123         internal SearchWordDialog SearchDialog = new SearchWordDialog();     //検索画面インスタンス
124         private readonly 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 readonly 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 readonly ImageList _listViewImageList = new ImageList();    //ListViewItemの高さ変更用
164
165         private PostClass _anchorPost;
166         private bool _anchorFlag;        //true:関連発言移動中(関連移動以外のオペレーションをするとfalseへ。trueだとリスト背景色をアンカー発言選択中として描画)
167
168         private readonly 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 readonly List<DateTimeUtc> _postTimestamps = new List<DateTimeUtc>();
176         private readonly List<DateTimeUtc> _favTimestamps = new List<DateTimeUtc>();
177
178         // 以下DrawItem関連
179         private readonly 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 readonly SolidBrush _brsDeactiveSelection = new SolidBrush(Color.FromKnownColor(KnownColor.ButtonFace)); //Listにフォーカスないときの選択行の背景色
188         private readonly 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 readonly SemaphoreSlim workerSemaphore = new SemaphoreSlim(MAX_WORKER_THREADS);
253         private readonly CancellationTokenSource workerCts = new CancellationTokenSource();
254         private readonly IProgress<string> workerProgress;
255
256         private int UnreadCounter = -1;
257         private int UnreadAtCounter = -1;
258
259         private readonly string[] ColumnOrgText = new string[9];
260         private readonly 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 readonly 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             var 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             var saveRequired = false;
790             var 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                 var tbarRect = new Rectangle(this._myLoc, new Size(_mySize.Width, SystemInformation.CaptionHeight));
938                 var outOfScreen = true;
939                 if (Screen.AllScreens.Length == 1)    //ハングするとの報告
940                 {
941                     foreach (var 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                 var 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                         var sb = new StringBuilder();
1692                         var reply = false;
1693                         var dm = false;
1694
1695                         foreach (var 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                             var 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                             var 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                         var sb = new StringBuilder();
1769                         var reply = false;
1770                         var dm = false;
1771                         foreach (var 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                         var 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                         var 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                     var dir = Application.StartupPath;
1845                     if (Directory.Exists(Path.Combine(dir, "Sounds")))
1846                     {
1847                         dir = Path.Combine(dir, "Sounds");
1848                     }
1849                     using (var 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                 var 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                 var 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 _, 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             var 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                             var 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                         var 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             var tabName = searchWord;
3868             for (var 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             var 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             var 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             var 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 (var 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                 var 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             var _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 (var label = _tabPage.Controls["labelUser"])
4185                     {
4186                         _tabPage.Controls.Remove(label);
4187                     }
4188                 }
4189                 else if (tabInfo.TabType == MyCommon.TabUsageType.PublicSearch)
4190                 {
4191                     using (var 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                 var tn = "";
4273                 var 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             var 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             var dispOrder = new int[currentListView.Columns.Count];
4326             for (var i = 0; i < currentListView.Columns.Count; i++)
4327             {
4328                 for (var 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                     var lst = (DetailsListView)tb.Tag;
4347                     for (var 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                 var 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             var selStart = owner.SelectionStart;
4397             var fHalf = "";
4398             var 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                     var isSpace = false;
4442                     foreach (var 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             var 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             var _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 _, out _);
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 _))
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             var 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             var 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;
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                 var fontHeight = e.Item.Font.Height;
4920                 if (_iconCol)
4921                 {
4922                     rct.Y += fontHeight;
4923                     rct.Height -= fontHeight;
4924                 }
4925
4926                 var 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                     var color = (!e.Item.Selected) ? e.Item.ForeColor :   //選択されていない行
4960                         (((Control)sender).Focused) ? _clHighLight :        //選択中の行
4961                         _clUnread;
4962
4963                     if (_iconCol)
4964                     {
4965                         var rctB = e.Bounds;
4966                         rctB.Width = e.Header.Width;
4967                         rctB.Height = fontHeight;
4968
4969                         using (var 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             var item = (ImageListViewItem)e.Item;
5035
5036             //e.Bounds.Leftが常に0を指すから自前で計算
5037             var 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                 var 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 (var 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             var 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                 var 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                             var endidx = StatusText.SelectionStart - 1;
6107                             var startstr = "";
6108                             for (var i = StatusText.SelectionStart - 1; i >= 0; i--)
6109                             {
6110                                 var 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                                     var 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             var 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             var sb = new StringBuilder();
6250             var tab = this.CurrentTab;
6251             var IsProtected = false;
6252             var isDm = tab.TabType == MyCommon.TabUsageType.DirectMessage;
6253             foreach (var post in tab.SelectedPosts)
6254             {
6255                 if (post.IsDeleted) continue;
6256                 if (!isDm)
6257                 {
6258                     if (post.RetweetedId != null)
6259                         sb.AppendFormat("{0}:{1} [https://twitter.com/{0}/status/{2}]{3}", post.ScreenName, post.TextSingleLine, post.RetweetedId, Environment.NewLine);
6260                     else
6261                         sb.AppendFormat("{0}:{1} [https://twitter.com/{0}/status/{2}]{3}", post.ScreenName, post.TextSingleLine, post.StatusId, Environment.NewLine);
6262                 }
6263                 else
6264                 {
6265                     sb.AppendFormat("{0}:{1} [{2}]{3}", post.ScreenName, post.TextSingleLine, post.StatusId, Environment.NewLine);
6266                 }
6267             }
6268             if (IsProtected)
6269             {
6270                 MessageBox.Show(Properties.Resources.CopyStotText1);
6271             }
6272             if (sb.Length > 0)
6273             {
6274                 var clstr = sb.ToString();
6275                 try
6276                 {
6277                     Clipboard.SetDataObject(clstr, false, 5, 100);
6278                 }
6279                 catch (Exception ex)
6280                 {
6281                     MessageBox.Show(ex.Message);
6282                 }
6283             }
6284         }
6285
6286         private void CopyIdUri()
6287         {
6288             var tab = this.CurrentTab;
6289             if (tab == null || tab is DirectMessagesTabModel)
6290                 return;
6291
6292             var copyUrls = new List<string>();
6293             foreach (var post in tab.SelectedPosts)
6294                 copyUrls.Add(MyCommon.GetStatusUrl(post));
6295
6296             if (copyUrls.Count == 0)
6297                 return;
6298
6299             try
6300             {
6301                 Clipboard.SetDataObject(string.Join(Environment.NewLine, copyUrls), false, 5, 100);
6302             }
6303             catch (ExternalException ex)
6304             {
6305                 MessageBox.Show(ex.Message);
6306             }
6307         }
6308
6309         private void GoFav(bool forward)
6310         {
6311             var tab = this.CurrentTab;
6312             if (tab.AllCount == 0)
6313                 return;
6314
6315             var selectedIndex = tab.SelectedIndex;
6316
6317             int fIdx;
6318             int toIdx;
6319             int stp;
6320
6321             if (forward)
6322             {
6323                 if (selectedIndex == -1)
6324                 {
6325                     fIdx = 0;
6326                 }
6327                 else
6328                 {
6329                     fIdx = selectedIndex + 1;
6330                     if (fIdx > tab.AllCount - 1)
6331                         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)
6346                         return;
6347                 }
6348                 toIdx = -1;
6349                 stp = -1;
6350             }
6351
6352             for (var idx = fIdx; idx != toIdx; idx += stp)
6353             {
6354                 if (tab[idx].IsFav)
6355                 {
6356                     var listView = this.CurrentListView;
6357                     SelectListItem(listView, idx);
6358                     listView.EnsureVisible(idx);
6359                     break;
6360                 }
6361             }
6362         }
6363
6364         private void GoSamePostToAnotherTab(bool left)
6365         {
6366             var tab = this.CurrentTab;
6367
6368             // Directタブは対象外(見つかるはずがない)
6369             if (tab.TabType == MyCommon.TabUsageType.DirectMessage)
6370                 return;
6371
6372             var selectedStatusId = tab.SelectedStatusId;
6373             if (selectedStatusId == -1)
6374                 return;
6375
6376             int fIdx, toIdx, stp;
6377
6378             if (left)
6379             {
6380                 // 左のタブへ
6381                 if (ListTab.SelectedIndex == 0)
6382                 {
6383                     return;
6384                 }
6385                 else
6386                 {
6387                     fIdx = ListTab.SelectedIndex - 1;
6388                 }
6389                 toIdx = -1;
6390                 stp = -1;
6391             }
6392             else
6393             {
6394                 // 右のタブへ
6395                 if (ListTab.SelectedIndex == ListTab.TabCount - 1)
6396                 {
6397                     return;
6398                 }
6399                 else
6400                 {
6401                     fIdx = ListTab.SelectedIndex + 1;
6402                 }
6403                 toIdx = ListTab.TabCount;
6404                 stp = 1;
6405             }
6406
6407             for (var tabidx = fIdx; tabidx != toIdx; tabidx += stp)
6408             {
6409                 var targetTab = this._statuses.Tabs[tabidx];
6410
6411                 // Directタブは対象外
6412                 if (targetTab.TabType == MyCommon.TabUsageType.DirectMessage)
6413                     continue;
6414
6415                 var foundIndex = targetTab.IndexOf(selectedStatusId);
6416                 if (foundIndex != -1)
6417                 {
6418                     ListTab.SelectedIndex = tabidx;
6419                     var listView = this.CurrentListView;
6420                     SelectListItem(listView, foundIndex);
6421                     listView.EnsureVisible(foundIndex);
6422                     return;
6423                 }
6424             }
6425         }
6426
6427         private void GoPost(bool forward)
6428         {
6429             var tab = this.CurrentTab;
6430             var currentPost = this.CurrentPost;
6431
6432             if (currentPost == null)
6433                 return;
6434
6435             var selectedIndex = tab.SelectedIndex;
6436
6437             int fIdx, toIdx, stp;
6438
6439             if (forward)
6440             {
6441                 fIdx = selectedIndex + 1;
6442                 if (fIdx > tab.AllCount - 1) return;
6443                 toIdx = tab.AllCount;
6444                 stp = 1;
6445             }
6446             else
6447             {
6448                 fIdx = selectedIndex - 1;
6449                 if (fIdx < 0) return;
6450                 toIdx = -1;
6451                 stp = -1;
6452             }
6453
6454             string name;
6455             if (currentPost.RetweetedId == null)
6456             {
6457                 name = currentPost.ScreenName;
6458             }
6459             else
6460             {
6461                 name = currentPost.RetweetedBy;
6462             }
6463             for (var idx = fIdx; idx != toIdx; idx += stp)
6464             {
6465                 var post = tab[idx];
6466                 if (post.RetweetedId == null)
6467                 {
6468                     if (post.ScreenName == name)
6469                     {
6470                         var listView = this.CurrentListView;
6471                         SelectListItem(listView, idx);
6472                         listView.EnsureVisible(idx);
6473                         break;
6474                     }
6475                 }
6476                 else
6477                 {
6478                     if (post.RetweetedBy == name)
6479                     {
6480                         var listView = this.CurrentListView;
6481                         SelectListItem(listView, idx);
6482                         listView.EnsureVisible(idx);
6483                         break;
6484                     }
6485                 }
6486             }
6487         }
6488
6489         private void GoRelPost(bool forward)
6490         {
6491             var tab = this.CurrentTab;
6492             var selectedIndex = tab.SelectedIndex;
6493
6494             if (selectedIndex == -1)
6495                 return;
6496
6497             int fIdx, toIdx, stp;
6498
6499             if (forward)
6500             {
6501                 fIdx = selectedIndex + 1;
6502                 if (fIdx > tab.AllCount - 1) return;
6503                 toIdx = tab.AllCount;
6504                 stp = 1;
6505             }
6506             else
6507             {
6508                 fIdx = selectedIndex - 1;
6509                 if (fIdx < 0) return;
6510                 toIdx = -1;
6511                 stp = -1;
6512             }
6513
6514             if (!_anchorFlag)
6515             {
6516                 var currentPost = this.CurrentPost;
6517                 if (currentPost == null) return;
6518                 _anchorPost = currentPost;
6519                 _anchorFlag = true;
6520             }
6521             else
6522             {
6523                 if (_anchorPost == null) return;
6524             }
6525
6526             for (var idx = fIdx; idx != toIdx; idx += stp)
6527             {
6528                 var post = tab[idx];
6529                 if (post.ScreenName == _anchorPost.ScreenName ||
6530                     post.RetweetedBy == _anchorPost.ScreenName ||
6531                     post.ScreenName == _anchorPost.RetweetedBy ||
6532                     (!string.IsNullOrEmpty(post.RetweetedBy) && post.RetweetedBy == _anchorPost.RetweetedBy) ||
6533                     _anchorPost.ReplyToList.Any(x => x.UserId == post.UserId) ||
6534                     _anchorPost.ReplyToList.Any(x => x.UserId == post.RetweetedByUserId) ||
6535                     post.ReplyToList.Any(x => x.UserId == _anchorPost.UserId) ||
6536                     post.ReplyToList.Any(x => x.UserId == _anchorPost.RetweetedByUserId))
6537                 {
6538                     var listView = this.CurrentListView;
6539                     SelectListItem(listView, idx);
6540                     listView.EnsureVisible(idx);
6541                     break;
6542                 }
6543             }
6544         }
6545
6546         private void GoAnchor()
6547         {
6548             if (_anchorPost == null) return;
6549             var idx = this.CurrentTab.IndexOf(_anchorPost.StatusId);
6550             if (idx == -1) return;
6551
6552             var listView = this.CurrentListView;
6553             SelectListItem(listView, idx);
6554             listView.EnsureVisible(idx);
6555         }
6556
6557         private void GoTopEnd(bool GoTop)
6558         {
6559             var listView = this.CurrentListView;
6560             if (listView.VirtualListSize == 0)
6561                 return;
6562
6563             ListViewItem _item;
6564             int idx;
6565
6566             if (GoTop)
6567             {
6568                 _item = listView.GetItemAt(0, 25);
6569                 if (_item == null)
6570                     idx = 0;
6571                 else
6572                     idx = _item.Index;
6573             }
6574             else
6575             {
6576                 _item = listView.GetItemAt(0, listView.ClientSize.Height - 1);
6577                 if (_item == null)
6578                     idx = listView.VirtualListSize - 1;
6579                 else
6580                     idx = _item.Index;
6581             }
6582             SelectListItem(listView, idx);
6583         }
6584
6585         private void GoMiddle()
6586         {
6587             var listView = this.CurrentListView;
6588             if (listView.VirtualListSize == 0)
6589                 return;
6590
6591             ListViewItem _item;
6592             int idx1;
6593             int idx2;
6594             int idx3;
6595
6596             _item = listView.GetItemAt(0, 0);
6597             if (_item == null)
6598             {
6599                 idx1 = 0;
6600             }
6601             else
6602             {
6603                 idx1 = _item.Index;
6604             }
6605
6606             _item = listView.GetItemAt(0, listView.ClientSize.Height - 1);
6607             if (_item == null)
6608             {
6609                 idx2 = listView.VirtualListSize - 1;
6610             }
6611             else
6612             {
6613                 idx2 = _item.Index;
6614             }
6615             idx3 = (idx1 + idx2) / 2;
6616
6617             SelectListItem(listView, idx3);
6618         }
6619
6620         private void GoLast()
6621         {
6622             var listView = this.CurrentListView;
6623             if (listView.VirtualListSize == 0) return;
6624
6625             if (_statuses.SortOrder == SortOrder.Ascending)
6626             {
6627                 SelectListItem(listView, listView.VirtualListSize - 1);
6628                 listView.EnsureVisible(listView.VirtualListSize - 1);
6629             }
6630             else
6631             {
6632                 SelectListItem(listView, 0);
6633                 listView.EnsureVisible(0);
6634             }
6635         }
6636
6637         private void MoveTop()
6638         {
6639             var listView = this.CurrentListView;
6640             if (listView.SelectedIndices.Count == 0) return;
6641             var idx = listView.SelectedIndices[0];
6642             if (_statuses.SortOrder == SortOrder.Ascending)
6643             {
6644                 listView.EnsureVisible(listView.VirtualListSize - 1);
6645             }
6646             else
6647             {
6648                 listView.EnsureVisible(0);
6649             }
6650             listView.EnsureVisible(idx);
6651         }
6652
6653         private async Task GoInReplyToPostTree()
6654         {
6655             var curTabClass = this.CurrentTab;
6656             var currentPost = this.CurrentPost;
6657
6658             if (currentPost == null)
6659                 return;
6660
6661             if (curTabClass.TabType == MyCommon.TabUsageType.PublicSearch && currentPost.InReplyToStatusId == null && currentPost.TextFromApi.Contains("@"))
6662             {
6663                 try
6664                 {
6665                     var post = await tw.GetStatusApi(false, currentPost.StatusId);
6666
6667                     currentPost.InReplyToStatusId = post.InReplyToStatusId;
6668                     currentPost.InReplyToUser = post.InReplyToUser;
6669                     currentPost.IsReply = post.IsReply;
6670                     this.PurgeListViewItemCache();
6671
6672                     var index = curTabClass.SelectedIndex;
6673                     this.CurrentListView.RedrawItems(index, index, false);
6674                 }
6675                 catch (WebApiException ex)
6676                 {
6677                     this.StatusLabel.Text = $"Err:{ex.Message}(GetStatus)";
6678                 }
6679             }
6680
6681             if (!(this.ExistCurrentPost && currentPost.InReplyToUser != null && currentPost.InReplyToStatusId != null)) return;
6682
6683             if (replyChains == null || (replyChains.Count > 0 && replyChains.Peek().InReplyToId != currentPost.StatusId))
6684             {
6685                 replyChains = new Stack<ReplyChain>();
6686             }
6687             replyChains.Push(new ReplyChain(currentPost.StatusId, currentPost.InReplyToStatusId.Value, curTabClass));
6688
6689             int inReplyToIndex;
6690             string inReplyToTabName;
6691             var inReplyToId = currentPost.InReplyToStatusId.Value;
6692             var inReplyToUser = currentPost.InReplyToUser;
6693
6694             var inReplyToPosts = from tab in _statuses.Tabs
6695                                  orderby tab != curTabClass
6696                                  from post in tab.Posts.Values
6697                                  where post.StatusId == inReplyToId
6698                                  let index = tab.IndexOf(post.StatusId)
6699                                  where index != -1
6700                                  select new {Tab = tab, Index = index};
6701
6702             var inReplyPost = inReplyToPosts.FirstOrDefault();
6703             if (inReplyPost == null)
6704             {
6705                 try
6706                 {
6707                     await Task.Run(async () =>
6708                     {
6709                         var post = await tw.GetStatusApi(false, currentPost.InReplyToStatusId.Value)
6710                             .ConfigureAwait(false);
6711                         post.IsRead = true;
6712
6713                         _statuses.AddPost(post);
6714                         _statuses.DistributePosts();
6715                     });
6716                 }
6717                 catch (WebApiException ex)
6718                 {
6719                     this.StatusLabel.Text = $"Err:{ex.Message}(GetStatus)";
6720                     await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(inReplyToUser, inReplyToId));
6721                     return;
6722                 }
6723
6724                 this.RefreshTimeline();
6725
6726                 inReplyPost = inReplyToPosts.FirstOrDefault();
6727                 if (inReplyPost == null)
6728                 {
6729                     await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(inReplyToUser, inReplyToId));
6730                     return;
6731                 }
6732             }
6733             inReplyToTabName = inReplyPost.Tab.TabName;
6734             inReplyToIndex = inReplyPost.Index;
6735
6736             var tabIndex = this._statuses.Tabs.IndexOf(inReplyToTabName);
6737             var tabPage = this.ListTab.TabPages[tabIndex];
6738             var listView = (DetailsListView)tabPage.Tag;
6739
6740             if (this.CurrentTabName != inReplyToTabName)
6741             {
6742                 this.ListTab.SelectedIndex = tabIndex;
6743             }
6744
6745             this.SelectListItem(listView, inReplyToIndex);
6746             listView.EnsureVisible(inReplyToIndex);
6747         }
6748
6749         private void GoBackInReplyToPostTree(bool parallel = false, bool isForward = true)
6750         {
6751             var curTabClass = this.CurrentTab;
6752             var currentPost = this.CurrentPost;
6753
6754             if (currentPost == null)
6755                 return;
6756
6757             if (parallel)
6758             {
6759                 if (currentPost.InReplyToStatusId != null)
6760                 {
6761                     var posts = from t in _statuses.Tabs
6762                                 from p in t.Posts
6763                                 where p.Value.StatusId != currentPost.StatusId && p.Value.InReplyToStatusId == currentPost.InReplyToStatusId
6764                                 let indexOf = t.IndexOf(p.Value.StatusId)
6765                                 where indexOf > -1
6766                                 orderby isForward ? indexOf : indexOf * -1
6767                                 orderby t != curTabClass
6768                                 select new {Tab = t, Post = p.Value, Index = indexOf};
6769                     try
6770                     {
6771                         var postList = posts.ToList();
6772                         for (var i = postList.Count - 1; i >= 0; i--)
6773                         {
6774                             var index = i;
6775                             if (postList.FindIndex(pst => pst.Post.StatusId == postList[index].Post.StatusId) != index)
6776                             {
6777                                 postList.RemoveAt(index);
6778                             }
6779                         }
6780                         var currentIndex = this.CurrentTab.SelectedIndex;
6781                         var post = postList.FirstOrDefault(pst => pst.Tab == curTabClass && isForward ? pst.Index > currentIndex : pst.Index < currentIndex);
6782                         if (post == null) post = postList.FirstOrDefault(pst => pst.Tab != curTabClass);
6783                         if (post == null) post = postList.First();
6784                         var tabIndex = this._statuses.Tabs.IndexOf(post.Tab);
6785                         this.ListTab.SelectedIndex = tabIndex;
6786                         var listView = this.CurrentListView;
6787                         SelectListItem(listView, post.Index);
6788                         listView.EnsureVisible(post.Index);
6789                     }
6790                     catch (InvalidOperationException)
6791                     {
6792                         return;
6793                     }
6794                 }
6795             }
6796             else
6797             {
6798                 if (replyChains == null || replyChains.Count < 1)
6799                 {
6800                     var posts = from t in _statuses.Tabs
6801                                 from p in t.Posts
6802                                 where p.Value.InReplyToStatusId == currentPost.StatusId
6803                                 let indexOf = t.IndexOf(p.Value.StatusId)
6804                                 where indexOf > -1
6805                                 orderby indexOf
6806                                 orderby t != curTabClass
6807                                 select new {Tab = t, Index = indexOf};
6808                     try
6809                     {
6810                         var post = posts.First();
6811                         var tabIndex = this._statuses.Tabs.IndexOf(post.Tab);
6812                         this.ListTab.SelectedIndex = tabIndex;
6813                         var listView = this.CurrentListView;
6814                         SelectListItem(listView, post.Index);
6815                         listView.EnsureVisible(post.Index);
6816                     }
6817                     catch (InvalidOperationException)
6818                     {
6819                         return;
6820                     }
6821                 }
6822                 else
6823                 {
6824                     var chainHead = replyChains.Pop();
6825                     if (chainHead.InReplyToId == currentPost.StatusId)
6826                     {
6827                         var tab = chainHead.OriginalTab;
6828                         if (!this._statuses.Tabs.Contains(tab))
6829                         {
6830                             replyChains = null;
6831                         }
6832                         else
6833                         {
6834                             var idx = tab.IndexOf(chainHead.OriginalId);
6835                             if (idx == -1)
6836                             {
6837                                 replyChains = null;
6838                             }
6839                             else
6840                             {
6841                                 var tabIndex = this._statuses.Tabs.IndexOf(tab);
6842                                 try
6843                                 {
6844                                     this.ListTab.SelectedIndex = tabIndex;
6845                                 }
6846                                 catch (Exception)
6847                                 {
6848                                     replyChains = null;
6849                                 }
6850                                 var listView = this.CurrentListView;
6851                                 SelectListItem(listView, idx);
6852                                 listView.EnsureVisible(idx);
6853                             }
6854                         }
6855                     }
6856                     else
6857                     {
6858                         replyChains = null;
6859                         this.GoBackInReplyToPostTree(parallel);
6860                     }
6861                 }
6862             }
6863         }
6864
6865         private void GoBackSelectPostChain()
6866         {
6867             if (this.selectPostChains.Count > 1)
6868             {
6869                 var idx = -1;
6870                 TabModel foundTab = null;
6871
6872                 do
6873                 {
6874                     try
6875                     {
6876                         this.selectPostChains.Pop();
6877                         var (tab, post) = this.selectPostChains.Peek();
6878
6879                         if (!this._statuses.Tabs.Contains(tab))
6880                             continue; // 該当タブが存在しないので無視
6881
6882                         if (post != null)
6883                         {
6884                             idx = tab.IndexOf(post.StatusId);
6885                             if (idx == -1) continue;  //該当ポストが存在しないので無視
6886                         }
6887
6888                         foundTab = tab;
6889
6890                         this.selectPostChains.Pop();
6891                     }
6892                     catch (InvalidOperationException)
6893                     {
6894                     }
6895
6896                     break;
6897                 }
6898                 while (this.selectPostChains.Count > 1);
6899
6900                 if (foundTab == null)
6901                 {
6902                     //状態がおかしいので処理を中断
6903                     //履歴が残り1つであればクリアしておく
6904                     if (this.selectPostChains.Count == 1)
6905                         this.selectPostChains.Clear();
6906                     return;
6907                 }
6908
6909                 var tabIndex = this._statuses.Tabs.IndexOf(foundTab);
6910                 var tabPage = this.ListTab.TabPages[tabIndex];
6911                 var lst = (DetailsListView)tabPage.Tag;
6912                 this.ListTab.SelectedIndex = tabIndex;
6913
6914                 if (idx > -1)
6915                 {
6916                     SelectListItem(lst, idx);
6917                     lst.EnsureVisible(idx);
6918                 }
6919                 lst.Focus();
6920             }
6921         }
6922
6923         private void PushSelectPostChain()
6924         {
6925             var currentTab = this.CurrentTab;
6926             var currentPost = this.CurrentPost;
6927
6928             var count = this.selectPostChains.Count;
6929             if (count > 0)
6930             {
6931                 var (tab, post) = this.selectPostChains.Peek();
6932                 if (tab == currentTab)
6933                 {
6934                     if (post == currentPost) return;  //最新の履歴と同一
6935                     if (post == null) this.selectPostChains.Pop();  //置き換えるため削除
6936                 }
6937             }
6938             if (count >= 2500) TrimPostChain();
6939             this.selectPostChains.Push((currentTab, currentPost));
6940         }
6941
6942         private void TrimPostChain()
6943         {
6944             if (this.selectPostChains.Count <= 2000) return;
6945             var p = new Stack<(TabModel, PostClass)>(2000);
6946             for (var i = 0; i < 2000; i++)
6947             {
6948                 p.Push(this.selectPostChains.Pop());
6949             }
6950             this.selectPostChains.Clear();
6951             for (var i = 0; i < 2000; i++)
6952             {
6953                 this.selectPostChains.Push(p.Pop());
6954             }
6955         }
6956
6957         private bool GoStatus(long statusId)
6958         {
6959             if (statusId == 0) return false;
6960
6961             var tab = this._statuses.Tabs
6962                 .Where(x => x.TabType != MyCommon.TabUsageType.DirectMessage)
6963                 .Where(x => x.Contains(statusId))
6964                 .FirstOrDefault();
6965
6966             if (tab == null)
6967                 return false;
6968
6969             var index = tab.IndexOf(statusId);
6970
6971             var tabIndex = this._statuses.Tabs.IndexOf(tab);
6972             this.ListTab.SelectedIndex = tabIndex;
6973
6974             var listView = this.CurrentListView;
6975             this.SelectListItem(listView, index);
6976             listView.EnsureVisible(index);
6977
6978             return true;
6979         }
6980
6981         private bool GoDirectMessage(long statusId)
6982         {
6983             if (statusId == 0) return false;
6984
6985             var tab = this._statuses.GetTabByType<DirectMessagesTabModel>();
6986             var index = tab.IndexOf(statusId);
6987
6988             if (index == -1)
6989                 return false;
6990
6991             var tabIndex = this._statuses.Tabs.IndexOf(tab);
6992             this.ListTab.SelectedIndex = tabIndex;
6993
6994             var listView = this.CurrentListView;
6995             this.SelectListItem(listView, index);
6996             listView.EnsureVisible(index);
6997
6998             return true;
6999         }
7000
7001         private void MyList_MouseClick(object sender, MouseEventArgs e)
7002             => this._anchorFlag = false;
7003
7004         private void StatusText_Enter(object sender, EventArgs e)
7005         {
7006             // フォーカスの戻り先を StatusText に設定
7007             this.Tag = StatusText;
7008             StatusText.BackColor = _clInputBackcolor;
7009         }
7010
7011         public Color InputBackColor
7012         {
7013             get => _clInputBackcolor;
7014             set => _clInputBackcolor = value;
7015         }
7016
7017         private void StatusText_Leave(object sender, EventArgs e)
7018         {
7019             // フォーカスがメニューに遷移しないならばフォーカスはタブに移ることを期待
7020             if (ListTab.SelectedTab != null && MenuStrip1.Tag == null) this.Tag = ListTab.SelectedTab.Tag;
7021             StatusText.BackColor = Color.FromKnownColor(KnownColor.Window);
7022         }
7023
7024         private async void StatusText_KeyDown(object sender, KeyEventArgs e)
7025         {
7026             if (CommonKeyDown(e.KeyData, FocusedControl.StatusText, out var asyncTask))
7027             {
7028                 e.Handled = true;
7029                 e.SuppressKeyPress = true;
7030             }
7031
7032             this.StatusText_TextChanged(null, null);
7033
7034             if (asyncTask != null)
7035                 await asyncTask;
7036         }
7037
7038         private void SaveConfigsAll(bool ifModified)
7039         {
7040             if (!ifModified)
7041             {
7042                 SaveConfigsCommon();
7043                 SaveConfigsLocal();
7044                 SaveConfigsTabs();
7045                 SaveConfigsAtId();
7046             }
7047             else
7048             {
7049                 if (ModifySettingCommon) SaveConfigsCommon();
7050                 if (ModifySettingLocal) SaveConfigsLocal();
7051                 if (ModifySettingAtId) SaveConfigsAtId();
7052             }
7053         }
7054
7055         private void SaveConfigsAtId()
7056         {
7057             if (_ignoreConfigSave || !SettingManager.Common.UseAtIdSupplement && AtIdSupl == null) return;
7058
7059             ModifySettingAtId = false;
7060             SettingManager.AtIdList.AtIdList = this.AtIdSupl.GetItemList();
7061             SettingManager.SaveAtIdList();
7062         }
7063
7064         private void SaveConfigsCommon()
7065         {
7066             if (_ignoreConfigSave) return;
7067
7068             ModifySettingCommon = false;
7069             lock (_syncObject)
7070             {
7071                 SettingManager.Common.UserName = tw.Username;
7072                 SettingManager.Common.UserId = tw.UserId;
7073                 SettingManager.Common.Token = tw.AccessToken;
7074                 SettingManager.Common.TokenSecret = tw.AccessTokenSecret;
7075                 SettingManager.Common.SortOrder = (int)_statuses.SortOrder;
7076                 switch (_statuses.SortMode)
7077                 {
7078                     case ComparerMode.Nickname:  //ニックネーム
7079                         SettingManager.Common.SortColumn = 1;
7080                         break;
7081                     case ComparerMode.Data:  //本文
7082                         SettingManager.Common.SortColumn = 2;
7083                         break;
7084                     case ComparerMode.Id:  //時刻=発言Id
7085                         SettingManager.Common.SortColumn = 3;
7086                         break;
7087                     case ComparerMode.Name:  //名前
7088                         SettingManager.Common.SortColumn = 4;
7089                         break;
7090                     case ComparerMode.Source:  //Source
7091                         SettingManager.Common.SortColumn = 7;
7092                         break;
7093                 }
7094
7095                 SettingManager.Common.HashTags = HashMgr.HashHistories;
7096                 if (HashMgr.IsPermanent)
7097                 {
7098                     SettingManager.Common.HashSelected = HashMgr.UseHash;
7099                 }
7100                 else
7101                 {
7102                     SettingManager.Common.HashSelected = "";
7103                 }
7104                 SettingManager.Common.HashIsHead = HashMgr.IsHead;
7105                 SettingManager.Common.HashIsPermanent = HashMgr.IsPermanent;
7106                 SettingManager.Common.HashIsNotAddToAtReply = HashMgr.IsNotAddToAtReply;
7107                 SettingManager.Common.TrackWord = tw.TrackWord;
7108                 SettingManager.Common.AllAtReply = tw.AllAtReply;
7109                 SettingManager.Common.UseImageService = ImageSelector.ServiceIndex;
7110                 SettingManager.Common.UseImageServiceName = ImageSelector.ServiceName;
7111
7112                 SettingManager.SaveCommon();
7113             }
7114         }
7115
7116         private void SaveConfigsLocal()
7117         {
7118             if (_ignoreConfigSave) return;
7119             lock (_syncObject)
7120             {
7121                 ModifySettingLocal = false;
7122                 SettingManager.Local.ScaleDimension = this.CurrentAutoScaleDimensions;
7123                 SettingManager.Local.FormSize = _mySize;
7124                 SettingManager.Local.FormLocation = _myLoc;
7125                 SettingManager.Local.SplitterDistance = _mySpDis;
7126                 SettingManager.Local.PreviewDistance = _mySpDis3;
7127                 SettingManager.Local.StatusMultiline = StatusText.Multiline;
7128                 SettingManager.Local.StatusTextHeight = _mySpDis2;
7129
7130                 SettingManager.Local.FontUnread = _fntUnread;
7131                 SettingManager.Local.ColorUnread = _clUnread;
7132                 SettingManager.Local.FontRead = _fntReaded;
7133                 SettingManager.Local.ColorRead = _clReaded;
7134                 SettingManager.Local.FontDetail = _fntDetail;
7135                 SettingManager.Local.ColorDetail = _clDetail;
7136                 SettingManager.Local.ColorDetailBackcolor = _clDetailBackcolor;
7137                 SettingManager.Local.ColorDetailLink = _clDetailLink;
7138                 SettingManager.Local.ColorFav = _clFav;
7139                 SettingManager.Local.ColorOWL = _clOWL;
7140                 SettingManager.Local.ColorRetweet = _clRetweet;
7141                 SettingManager.Local.ColorSelf = _clSelf;
7142                 SettingManager.Local.ColorAtSelf = _clAtSelf;
7143                 SettingManager.Local.ColorTarget = _clTarget;
7144                 SettingManager.Local.ColorAtTarget = _clAtTarget;
7145                 SettingManager.Local.ColorAtFromTarget = _clAtFromTarget;
7146                 SettingManager.Local.ColorAtTo = _clAtTo;
7147                 SettingManager.Local.ColorListBackcolor = _clListBackcolor;
7148                 SettingManager.Local.ColorInputBackcolor = _clInputBackcolor;
7149                 SettingManager.Local.ColorInputFont = _clInputFont;
7150                 SettingManager.Local.FontInputFont = _fntInputFont;
7151
7152                 if (_ignoreConfigSave) return;
7153                 SettingManager.SaveLocal();
7154             }
7155         }
7156
7157         private void SaveConfigsTabs()
7158         {
7159             var tabSettingList = new List<SettingTabs.SettingTabItem>();
7160
7161             var tabs = this._statuses.Tabs.Append(this._statuses.MuteTab);
7162
7163             foreach (var tab in tabs)
7164             {
7165                 if (!tab.IsPermanentTabType)
7166                     continue;
7167
7168                 var tabSetting = new SettingTabs.SettingTabItem
7169                 {
7170                     TabName = tab.TabName,
7171                     TabType = tab.TabType,
7172                     UnreadManage = tab.UnreadManage,
7173                     Protected = tab.Protected,
7174                     Notify = tab.Notify,
7175                     SoundFile = tab.SoundFile,
7176                 };
7177
7178                 switch (tab)
7179                 {
7180                     case FilterTabModel filterTab:
7181                         tabSetting.FilterArray = filterTab.FilterArray;
7182                         break;
7183                     case UserTimelineTabModel userTab:
7184                         tabSetting.User = userTab.ScreenName;
7185                         break;
7186                     case PublicSearchTabModel searchTab:
7187                         tabSetting.SearchWords = searchTab.SearchWords;
7188                         tabSetting.SearchLang = searchTab.SearchLang;
7189                         break;
7190                     case ListTimelineTabModel listTab:
7191                         tabSetting.ListInfo = listTab.ListInfo;
7192                         break;
7193                 }
7194
7195                 tabSettingList.Add(tabSetting);
7196             }
7197
7198             SettingManager.Tabs.Tabs = tabSettingList;
7199             SettingManager.SaveTabs();
7200         }
7201
7202         private async void OpenURLFileMenuItem_Click(object sender, EventArgs e)
7203         {
7204             var ret = InputDialog.Show(this, Properties.Resources.OpenURL_InputText, Properties.Resources.OpenURL_Caption, out var inputText);
7205             if (ret != DialogResult.OK)
7206                 return;
7207
7208             var match = Twitter.StatusUrlRegex.Match(inputText);
7209             if (!match.Success)
7210             {
7211                 MessageBox.Show(this, Properties.Resources.OpenURL_InvalidFormat,
7212                     Properties.Resources.OpenURL_Caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
7213                 return;
7214             }
7215
7216             try
7217             {
7218                 var statusId = long.Parse(match.Groups["StatusId"].Value);
7219                 await this.OpenRelatedTab(statusId);
7220             }
7221             catch (TabException ex)
7222             {
7223                 MessageBox.Show(this, ex.Message, ApplicationSettings.ApplicationName, MessageBoxButtons.OK, MessageBoxIcon.Error);
7224             }
7225         }
7226
7227         private void SaveLogMenuItem_Click(object sender, EventArgs e)
7228         {
7229             var tab = this.CurrentTab;
7230
7231             var rslt = MessageBox.Show(string.Format(Properties.Resources.SaveLogMenuItem_ClickText1, Environment.NewLine),
7232                     Properties.Resources.SaveLogMenuItem_ClickText2,
7233                     MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
7234             if (rslt == DialogResult.Cancel) return;
7235
7236             SaveFileDialog1.FileName = $"{ApplicationSettings.AssemblyName}Posts{DateTimeUtc.Now.ToLocalTime():yyMMdd-HHmmss}.tsv";
7237             SaveFileDialog1.InitialDirectory = Application.ExecutablePath;
7238             SaveFileDialog1.Filter = Properties.Resources.SaveLogMenuItem_ClickText3;
7239             SaveFileDialog1.FilterIndex = 0;
7240             SaveFileDialog1.Title = Properties.Resources.SaveLogMenuItem_ClickText4;
7241             SaveFileDialog1.RestoreDirectory = true;
7242
7243             if (SaveFileDialog1.ShowDialog() == DialogResult.OK)
7244             {
7245                 if (!SaveFileDialog1.ValidateNames) return;
7246                 using (var sw = new StreamWriter(SaveFileDialog1.FileName, false, Encoding.UTF8))
7247                 {
7248                     if (rslt == DialogResult.Yes)
7249                     {
7250                         //All
7251                         for (var idx = 0; idx < tab.AllCount; idx++)
7252                         {
7253                             var post = tab[idx];
7254                             var protect = "";
7255                             if (post.IsProtect) protect = "Protect";
7256                             sw.WriteLine(post.Nickname + "\t" +
7257                                      "\"" + post.TextFromApi.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
7258                                      post.CreatedAt.ToLocalTimeString() + "\t" +
7259                                      post.ScreenName + "\t" +
7260                                      post.StatusId + "\t" +
7261                                      post.ImageUrl + "\t" +
7262                                      "\"" + post.Text.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
7263                                      protect);
7264                         }
7265                     }
7266                     else
7267                     {
7268                         foreach (var post in this.CurrentTab.SelectedPosts)
7269                         {
7270                             var protect = "";
7271                             if (post.IsProtect) protect = "Protect";
7272                             sw.WriteLine(post.Nickname + "\t" +
7273                                      "\"" + post.TextFromApi.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
7274                                      post.CreatedAt.ToLocalTimeString() + "\t" +
7275                                      post.ScreenName + "\t" +
7276                                      post.StatusId + "\t" +
7277                                      post.ImageUrl + "\t" +
7278                                      "\"" + post.Text.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
7279                                      protect);
7280                         }
7281                     }
7282                 }
7283             }
7284             this.TopMost = SettingManager.Common.AlwaysTop;
7285         }
7286
7287         public bool TabRename(string origTabName, out string newTabName)
7288         {
7289             //タブ名変更
7290             newTabName = null;
7291             using (var inputName = new InputTabName())
7292             {
7293                 inputName.TabName = origTabName;
7294                 inputName.ShowDialog();
7295                 if (inputName.DialogResult == DialogResult.Cancel) return false;
7296                 newTabName = inputName.TabName;
7297             }
7298             this.TopMost = SettingManager.Common.AlwaysTop;
7299             if (!string.IsNullOrEmpty(newTabName))
7300             {
7301                 //新タブ名存在チェック
7302                 if (this._statuses.ContainsTab(newTabName))
7303                 {
7304                     var tmp = string.Format(Properties.Resources.Tabs_DoubleClickText1, newTabName);
7305                     MessageBox.Show(tmp, Properties.Resources.Tabs_DoubleClickText2, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
7306                     return false;
7307                 }
7308
7309                 var tabIndex = this._statuses.Tabs.IndexOf(origTabName);
7310                 var tabPage = this.ListTab.TabPages[tabIndex];
7311
7312                 // タブ名を変更
7313                 if (tabPage != null)
7314                     tabPage.Text = newTabName;
7315
7316                 _statuses.RenameTab(origTabName, newTabName);
7317
7318                 SaveConfigsCommon();
7319                 SaveConfigsTabs();
7320                 _rclickTabName = newTabName;
7321                 return true;
7322             }
7323             else
7324             {
7325                 return false;
7326             }
7327         }
7328
7329         private void ListTab_MouseClick(object sender, MouseEventArgs e)
7330         {
7331             if (e.Button == MouseButtons.Middle)
7332             {
7333                 foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
7334                 {
7335                     if (this.ListTab.GetTabRect(index).Contains(e.Location))
7336                     {
7337                         this.RemoveSpecifiedTab(tab.TabName, true);
7338                         this.SaveConfigsTabs();
7339                         break;
7340                     }
7341                 }
7342             }
7343         }
7344
7345         private void ListTab_DoubleClick(object sender, MouseEventArgs e)
7346             => this.TabRename(this.CurrentTabName, out _);
7347
7348         private void ListTab_MouseDown(object sender, MouseEventArgs e)
7349         {
7350             if (SettingManager.Common.TabMouseLock) return;
7351             if (e.Button == MouseButtons.Left)
7352             {
7353                 foreach (var i in Enumerable.Range(0, this._statuses.Tabs.Count))
7354                 {
7355                     if (this.ListTab.GetTabRect(i).Contains(e.Location))
7356                     {
7357                         _tabDrag = true;
7358                         _tabMouseDownPoint = e.Location;
7359                         break;
7360                     }
7361                 }
7362             }
7363             else
7364             {
7365                 _tabDrag = false;
7366             }
7367         }
7368
7369         private void ListTab_DragEnter(object sender, DragEventArgs e)
7370         {
7371             if (e.Data.GetDataPresent(typeof(TabPage)))
7372                 e.Effect = DragDropEffects.Move;
7373             else
7374                 e.Effect = DragDropEffects.None;
7375         }
7376
7377         private void ListTab_DragDrop(object sender, DragEventArgs e)
7378         {
7379             if (!e.Data.GetDataPresent(typeof(TabPage))) return;
7380
7381             _tabDrag = false;
7382             var tn = "";
7383             var bef = false;
7384             var cpos = new Point(e.X, e.Y);
7385             var spos = ListTab.PointToClient(cpos);
7386             foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
7387             {
7388                 var rect = ListTab.GetTabRect(index);
7389                 if (rect.Contains(spos))
7390                 {
7391                     tn = tab.TabName;
7392                     if (spos.X <= (rect.Left + rect.Right) / 2)
7393                         bef = true;
7394                     else
7395                         bef = false;
7396
7397                     break;
7398                 }
7399             }
7400
7401             //タブのないところにドロップ->最後尾へ移動
7402             if (string.IsNullOrEmpty(tn))
7403             {
7404                 var lastTab = this._statuses.Tabs.Last();
7405                 tn = lastTab.TabName;
7406                 bef = false;
7407             }
7408
7409             var tp = (TabPage)e.Data.GetData(typeof(TabPage));
7410             if (tp.Text == tn) return;
7411
7412             ReOrderTab(tp.Text, tn, bef);
7413         }
7414
7415         public void ReOrderTab(string targetTabText, string baseTabText, bool isBeforeBaseTab)
7416         {
7417             var baseIndex = this.GetTabPageIndex(baseTabText);
7418             if (baseIndex == -1)
7419                 return;
7420
7421             var targetIndex = this.GetTabPageIndex(targetTabText);
7422             if (targetIndex == -1)
7423                 return;
7424
7425             using (ControlTransaction.Layout(this.ListTab))
7426             {
7427                 var tab = this._statuses.Tabs[targetIndex];
7428                 var tabPage = this.ListTab.TabPages[targetIndex];
7429
7430                 this.ListTab.TabPages.Remove(tabPage);
7431
7432                 if (targetIndex < baseIndex)
7433                     baseIndex--;
7434
7435                 if (!isBeforeBaseTab)
7436                     baseIndex++;
7437
7438                 this._statuses.MoveTab(baseIndex, tab);
7439
7440                 ListTab.TabPages.Insert(baseIndex, tabPage);
7441             }
7442
7443             SaveConfigsTabs();
7444         }
7445
7446         private void MakeReplyOrDirectStatus(bool isAuto = true, bool isReply = true, bool isAll = false)
7447         {
7448             //isAuto:true=先頭に挿入、false=カーソル位置に挿入
7449             //isReply:true=@,false=DM
7450             if (!StatusText.Enabled) return;
7451             if (!this.ExistCurrentPost) return;
7452
7453             var tab = this.CurrentTab;
7454             var selectedPosts = tab.SelectedPosts;
7455
7456             // 複数あてリプライはReplyではなく通常ポスト
7457             //↑仕様変更で全部リプライ扱いでOK(先頭ドット付加しない)
7458             //090403暫定でドットを付加しないようにだけ修正。単独と複数の処理は統合できると思われる。
7459             //090513 all @ replies 廃止の仕様変更によりドット付加に戻し(syo68k)
7460
7461             if (selectedPosts.Length > 0)
7462             {
7463                 // アイテムが1件以上選択されている
7464                 if (selectedPosts.Length == 1 && !isAll && this.ExistCurrentPost)
7465                 {
7466                     var post = selectedPosts.Single();
7467
7468                     // 単独ユーザー宛リプライまたはDM
7469                     if ((tab.TabType == MyCommon.TabUsageType.DirectMessage && isAuto) || (!isAuto && !isReply))
7470                     {
7471                         // ダイレクトメッセージ
7472                         this.inReplyTo = null;
7473                         StatusText.Text = "D " + post.ScreenName + " " + StatusText.Text;
7474                         StatusText.SelectionStart = StatusText.Text.Length;
7475                         StatusText.Focus();
7476                         return;
7477                     }
7478                     if (string.IsNullOrEmpty(StatusText.Text))
7479                     {
7480                         //空の場合
7481                         var inReplyToStatusId = post.RetweetedId ?? post.StatusId;
7482                         var inReplyToScreenName = post.ScreenName;
7483                         this.inReplyTo = (inReplyToStatusId, inReplyToScreenName);
7484
7485                         // ステータステキストが入力されていない場合先頭に@ユーザー名を追加する
7486                         StatusText.Text = "@" + post.ScreenName + " ";
7487                     }
7488                     else
7489                     {
7490                         //何か入力済の場合
7491
7492                         if (isAuto)
7493                         {
7494                             //1件選んでEnter or DoubleClick
7495                             if (StatusText.Text.Contains("@" + post.ScreenName + " "))
7496                             {
7497                                 if (this.inReplyTo?.ScreenName == post.ScreenName)
7498                                 {
7499                                     //返信先書き換え
7500                                     var inReplyToStatusId = post.RetweetedId ?? post.StatusId;
7501                                     var inReplyToScreenName = post.ScreenName;
7502                                     this.inReplyTo = (inReplyToStatusId, inReplyToScreenName);
7503                                 }
7504                                 return;
7505                             }
7506                             if (!StatusText.Text.StartsWith("@", StringComparison.Ordinal))
7507                             {
7508                                 //文頭@以外
7509                                 if (StatusText.Text.StartsWith(". ", StringComparison.Ordinal))
7510                                 {
7511                                     // 複数リプライ
7512                                     this.inReplyTo = null;
7513                                     StatusText.Text = StatusText.Text.Insert(2, "@" + post.ScreenName + " ");
7514                                 }
7515                                 else
7516                                 {
7517                                     // 単独リプライ
7518                                     var inReplyToStatusId = post.RetweetedId ?? post.StatusId;
7519                                     var inReplyToScreenName = post.ScreenName;
7520                                     this.inReplyTo = (inReplyToStatusId, inReplyToScreenName);
7521                                     StatusText.Text = "@" + post.ScreenName + " " + StatusText.Text;
7522                                 }
7523                             }
7524                             else
7525                             {
7526                                 //文頭@
7527                                 // 複数リプライ
7528                                 this.inReplyTo = null;
7529                                 StatusText.Text = ". @" + post.ScreenName + " " + StatusText.Text;
7530                             }
7531                         }
7532                         else
7533                         {
7534                             //1件選んでCtrl-Rの場合(返信先操作せず)
7535                             var sidx = StatusText.SelectionStart;
7536                             var id = "@" + post.ScreenName + " ";
7537                             if (sidx > 0)
7538                             {
7539                                 if (StatusText.Text.Substring(sidx - 1, 1) != " ")
7540                                 {
7541                                     id = " " + id;
7542                                 }
7543                             }
7544                             StatusText.Text = StatusText.Text.Insert(sidx, id);
7545                             sidx += id.Length;
7546                             //if (StatusText.Text.StartsWith("@"))
7547                             //{
7548                             //    //複数リプライ
7549                             //    StatusText.Text = ". " + StatusText.Text.Insert(sidx, " @" + _curPost.ScreenName + " ");
7550                             //    sidx += 5 + _curPost.ScreenName.Length;
7551                             //}
7552                             //else
7553                             //{
7554                             //    // 複数リプライ
7555                             //    StatusText.Text = StatusText.Text.Insert(sidx, " @" + _curPost.ScreenName + " ");
7556                             //    sidx += 3 + _curPost.ScreenName.Length;
7557                             //}
7558                             StatusText.SelectionStart = sidx;
7559                             StatusText.Focus();
7560                             //_reply_to_id = 0;
7561                             //_reply_to_name = null;
7562                             return;
7563                         }
7564                     }
7565                 }
7566                 else
7567                 {
7568                     // 複数リプライ
7569                     if (!isAuto && !isReply) return;
7570
7571                     //C-S-rか、複数の宛先を選択中にEnter/DoubleClick/C-r/C-S-r
7572
7573                     if (isAuto)
7574                     {
7575                         //Enter or DoubleClick
7576
7577                         var sTxt = StatusText.Text;
7578                         if (!sTxt.StartsWith(". ", StringComparison.Ordinal))
7579                         {
7580                             sTxt = ". " + sTxt;
7581                             this.inReplyTo = null;
7582                         }
7583                         foreach (var post in selectedPosts)
7584                         {
7585                             if (!sTxt.Contains("@" + post.ScreenName + " "))
7586                             {
7587                                 sTxt = sTxt.Insert(2, "@" + post.ScreenName + " ");
7588                                 //sTxt = "@" + post.ScreenName + " " + sTxt;
7589                             }
7590                         }
7591                         StatusText.Text = sTxt;
7592                     }
7593                     else
7594                     {
7595                         //C-S-r or C-r
7596
7597                         if (selectedPosts.Length > 1)
7598                         {
7599                             //複数ポスト選択
7600
7601                             var ids = "";
7602                             var sidx = StatusText.SelectionStart;
7603                             foreach (var post in selectedPosts)
7604                             {
7605                                 if (!ids.Contains("@" + post.ScreenName + " ") && post.UserId != tw.UserId)
7606                                 {
7607                                     ids += "@" + post.ScreenName + " ";
7608                                 }
7609                                 if (isAll)
7610                                 {
7611                                     foreach (var (_, screenName) in post.ReplyToList)
7612                                     {
7613                                         if (!ids.Contains("@" + screenName + " ") &&
7614                                             !screenName.Equals(tw.Username, StringComparison.CurrentCultureIgnoreCase))
7615                                         {
7616                                             var m = Regex.Match(post.TextFromApi, "[@@](?<id>" + screenName + ")([^a-zA-Z0-9]|$)", RegexOptions.IgnoreCase);
7617                                             if (m.Success)
7618                                                 ids += "@" + m.Result("${id}") + " ";
7619                                             else
7620                                                 ids += "@" + screenName + " ";
7621                                         }
7622                                     }
7623                                 }
7624                             }
7625                             if (ids.Length == 0) return;
7626                             if (!StatusText.Text.StartsWith(". ", StringComparison.Ordinal))
7627                             {
7628                                 this.inReplyTo = null;
7629                                 StatusText.Text = ". " + StatusText.Text;
7630                                 sidx += 2;
7631                             }
7632                             if (sidx > 0)
7633                             {
7634                                 if (StatusText.Text.Substring(sidx - 1, 1) != " ")
7635                                 {
7636                                     ids = " " + ids;
7637                                 }
7638                             }
7639                             StatusText.Text = StatusText.Text.Insert(sidx, ids);
7640                             sidx += ids.Length;
7641                             //if (StatusText.Text.StartsWith("@"))
7642                             //{
7643                             //    StatusText.Text = ". " + StatusText.Text.Insert(sidx, ids);
7644                             //    sidx += 2 + ids.Length;
7645                             //}
7646                             //else
7647                             //{
7648                             //    StatusText.Text = StatusText.Text.Insert(sidx, ids);
7649                             //    sidx += 1 + ids.Length;
7650                             //}
7651                             StatusText.SelectionStart = sidx;
7652                             StatusText.Focus();
7653                             return;
7654                         }
7655                         else
7656                         {
7657                             //1件のみ選択のC-S-r(返信元付加する可能性あり)
7658
7659                             var ids = "";
7660                             var sidx = StatusText.SelectionStart;
7661                             var post = selectedPosts.Single();
7662                             if (!ids.Contains("@" + post.ScreenName + " ") && post.UserId != tw.UserId)
7663                             {
7664                                 ids += "@" + post.ScreenName + " ";
7665                             }
7666                             foreach (var (_, screenName) in post.ReplyToList)
7667                             {
7668                                 if (!ids.Contains("@" + screenName + " ") &&
7669                                     !screenName.Equals(tw.Username, StringComparison.CurrentCultureIgnoreCase))
7670                                 {
7671                                     var m = Regex.Match(post.TextFromApi, "[@@](?<id>" + screenName + ")([^a-zA-Z0-9]|$)", RegexOptions.IgnoreCase);
7672                                     if (m.Success)
7673                                         ids += "@" + m.Result("${id}") + " ";
7674                                     else
7675                                         ids += "@" + screenName + " ";
7676                                 }
7677                             }
7678                             if (!string.IsNullOrEmpty(post.RetweetedBy))
7679                             {
7680                                 if (!ids.Contains("@" + post.RetweetedBy + " ") && post.RetweetedByUserId != tw.UserId)
7681                                 {
7682                                     ids += "@" + post.RetweetedBy + " ";
7683                                 }
7684                             }
7685                             if (ids.Length == 0) return;
7686                             if (string.IsNullOrEmpty(StatusText.Text))
7687                             {
7688                                 //未入力の場合のみ返信先付加
7689                                 var inReplyToStatusId = post.RetweetedId ?? post.StatusId;
7690                                 var inReplyToScreenName = post.ScreenName;
7691                                 this.inReplyTo = (inReplyToStatusId, inReplyToScreenName);
7692
7693                                 StatusText.Text = ids;
7694                                 StatusText.SelectionStart = ids.Length;
7695                                 StatusText.Focus();
7696                                 return;
7697                             }
7698
7699                             if (sidx > 0)
7700                             {
7701                                 if (StatusText.Text.Substring(sidx - 1, 1) != " ")
7702                                 {
7703                                     ids = " " + ids;
7704                                 }
7705                             }
7706                             StatusText.Text = StatusText.Text.Insert(sidx, ids);
7707                             sidx += ids.Length;
7708                             StatusText.SelectionStart = sidx;
7709                             StatusText.Focus();
7710                             return;
7711                         }
7712                     }
7713                 }
7714                 StatusText.SelectionStart = StatusText.Text.Length;
7715                 StatusText.Focus();
7716             }
7717         }
7718
7719         private void ListTab_MouseUp(object sender, MouseEventArgs e)
7720             => this._tabDrag = false;
7721
7722         private int iconCnt = 0;
7723         private int blinkCnt = 0;
7724         private bool blink = false;
7725
7726         private void RefreshTasktrayIcon()
7727         {
7728             void EnableTasktrayAnimation()
7729                 => this.TimerRefreshIcon.Enabled = true;
7730
7731             void DisableTasktrayAnimation()
7732                 => this.TimerRefreshIcon.Enabled = false;
7733
7734             var busyTasks = this.workerSemaphore.CurrentCount != MAX_WORKER_THREADS;
7735             if (busyTasks)
7736             {
7737                 iconCnt += 1;
7738                 if (iconCnt >= this.NIconRefresh.Length)
7739                     iconCnt = 0;
7740
7741                 NotifyIcon1.Icon = NIconRefresh[iconCnt];
7742                 _myStatusError = false;
7743                 EnableTasktrayAnimation();
7744                 return;
7745             }
7746
7747             var replyIconType = SettingManager.Common.ReplyIconState;
7748             var reply = false;
7749             if (replyIconType != MyCommon.REPLY_ICONSTATE.None)
7750             {
7751                 var replyTab = this._statuses.GetTabByType<MentionsTabModel>();
7752                 if (replyTab != null && replyTab.UnreadCount > 0)
7753                     reply = true;
7754             }
7755
7756             if (replyIconType == MyCommon.REPLY_ICONSTATE.BlinkIcon && reply)
7757             {
7758                 blinkCnt += 1;
7759                 if (blinkCnt > 10)
7760                     blinkCnt = 0;
7761
7762                 if (blinkCnt == 0)
7763                     blink = !blink;
7764
7765                 NotifyIcon1.Icon = blink ? ReplyIconBlink : ReplyIcon;
7766                 EnableTasktrayAnimation();
7767                 return;
7768             }
7769
7770             DisableTasktrayAnimation();
7771
7772             iconCnt = 0;
7773             blinkCnt = 0;
7774             blink = false;
7775
7776             // 優先度:リプライ→エラー→オフライン→アイドル
7777             // エラーは更新アイコンでクリアされる
7778             if (replyIconType == MyCommon.REPLY_ICONSTATE.StaticIcon && reply)
7779                 NotifyIcon1.Icon = ReplyIcon;
7780             else if (_myStatusError)
7781                 NotifyIcon1.Icon = NIconAtRed;
7782             else if (_myStatusOnline)
7783                 NotifyIcon1.Icon = NIconAt;
7784             else
7785                 NotifyIcon1.Icon = NIconAtSmoke;
7786         }
7787
7788         private void TimerRefreshIcon_Tick(object sender, EventArgs e)
7789             => this.RefreshTasktrayIcon(); // 200ms
7790
7791         private void ContextMenuTabProperty_Opening(object sender, CancelEventArgs e)
7792         {
7793             //右クリックの場合はタブ名が設定済。アプリケーションキーの場合は現在のタブを対象とする
7794             if (string.IsNullOrEmpty(_rclickTabName) || sender != ContextMenuTabProperty)
7795                 _rclickTabName = this.CurrentTabName;
7796
7797             if (_statuses == null) return;
7798             if (_statuses.Tabs == null) return;
7799
7800             if (!this._statuses.Tabs.TryGetValue(this._rclickTabName, out var tb))
7801                 return;
7802
7803             NotifyDispMenuItem.Checked = tb.Notify;
7804             this.NotifyTbMenuItem.Checked = tb.Notify;
7805
7806             soundfileListup = true;
7807             SoundFileComboBox.Items.Clear();
7808             this.SoundFileTbComboBox.Items.Clear();
7809             SoundFileComboBox.Items.Add("");
7810             this.SoundFileTbComboBox.Items.Add("");
7811             var oDir = new DirectoryInfo(Application.StartupPath + Path.DirectorySeparatorChar);
7812             if (Directory.Exists(Path.Combine(Application.StartupPath, "Sounds")))
7813             {
7814                 oDir = oDir.GetDirectories("Sounds")[0];
7815             }
7816             foreach (var oFile in oDir.GetFiles("*.wav"))
7817             {
7818                 SoundFileComboBox.Items.Add(oFile.Name);
7819                 this.SoundFileTbComboBox.Items.Add(oFile.Name);
7820             }
7821             var idx = SoundFileComboBox.Items.IndexOf(tb.SoundFile);
7822             if (idx == -1) idx = 0;
7823             SoundFileComboBox.SelectedIndex = idx;
7824             this.SoundFileTbComboBox.SelectedIndex = idx;
7825             soundfileListup = false;
7826             UreadManageMenuItem.Checked = tb.UnreadManage;
7827             this.UnreadMngTbMenuItem.Checked = tb.UnreadManage;
7828
7829             TabMenuControl(_rclickTabName);
7830         }
7831
7832         private void TabMenuControl(string tabName)
7833         {
7834             var tabInfo = _statuses.GetTabByName(tabName);
7835
7836             this.FilterEditMenuItem.Enabled = true;
7837             this.EditRuleTbMenuItem.Enabled = true;
7838
7839             if (tabInfo.IsDefaultTabType)
7840             {
7841                 this.ProtectTabMenuItem.Enabled = false;
7842                 this.ProtectTbMenuItem.Enabled = false;
7843             }
7844             else
7845             {
7846                 this.ProtectTabMenuItem.Enabled = true;
7847                 this.ProtectTbMenuItem.Enabled = true;
7848             }
7849
7850             if (tabInfo.IsDefaultTabType || tabInfo.Protected)
7851             {
7852                 this.ProtectTabMenuItem.Checked = true;
7853                 this.ProtectTbMenuItem.Checked = true;
7854                 this.DeleteTabMenuItem.Enabled = false;
7855                 this.DeleteTbMenuItem.Enabled = false;
7856             }
7857             else
7858             {
7859                 this.ProtectTabMenuItem.Checked = false;
7860                 this.ProtectTbMenuItem.Checked = false;
7861                 this.DeleteTabMenuItem.Enabled = true;
7862                 this.DeleteTbMenuItem.Enabled = true;
7863             }
7864         }
7865
7866         private void ProtectTabMenuItem_Click(object sender, EventArgs e)
7867         {
7868             var checkState = ((ToolStripMenuItem)sender).Checked;
7869
7870             // チェック状態を同期
7871             this.ProtectTbMenuItem.Checked = checkState;
7872             this.ProtectTabMenuItem.Checked = checkState;
7873
7874             // ロック中はタブの削除を無効化
7875             this.DeleteTabMenuItem.Enabled = !checkState;
7876             this.DeleteTbMenuItem.Enabled = !checkState;
7877
7878             if (string.IsNullOrEmpty(_rclickTabName)) return;
7879             _statuses.Tabs[_rclickTabName].Protected = checkState;
7880
7881             SaveConfigsTabs();
7882         }
7883
7884         private void UreadManageMenuItem_Click(object sender, EventArgs e)
7885         {
7886             UreadManageMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
7887             this.UnreadMngTbMenuItem.Checked = UreadManageMenuItem.Checked;
7888
7889             if (string.IsNullOrEmpty(_rclickTabName)) return;
7890             ChangeTabUnreadManage(_rclickTabName, UreadManageMenuItem.Checked);
7891
7892             SaveConfigsTabs();
7893         }
7894
7895         public void ChangeTabUnreadManage(string tabName, bool isManage)
7896         {
7897             var idx = this.GetTabPageIndex(tabName);
7898             if (idx == -1)
7899                 return;
7900
7901             var tab = this._statuses.Tabs[tabName];
7902             tab.UnreadManage = isManage;
7903
7904             if (SettingManager.Common.TabIconDisp)
7905             {
7906                 var tabPage = this.ListTab.TabPages[idx];
7907                 if (tab.UnreadCount > 0)
7908                     tabPage.ImageIndex = 0;
7909                 else
7910                     tabPage.ImageIndex = -1;
7911             }
7912
7913             if (this.CurrentTabName == tabName)
7914             {
7915                 this.PurgeListViewItemCache();
7916                 this.CurrentListView.Refresh();
7917             }
7918
7919             SetMainWindowTitle();
7920             SetStatusLabelUrl();
7921             if (!SettingManager.Common.TabIconDisp) ListTab.Refresh();
7922         }
7923
7924         private void NotifyDispMenuItem_Click(object sender, EventArgs e)
7925         {
7926             NotifyDispMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
7927             this.NotifyTbMenuItem.Checked = NotifyDispMenuItem.Checked;
7928
7929             if (string.IsNullOrEmpty(_rclickTabName)) return;
7930
7931             _statuses.Tabs[_rclickTabName].Notify = NotifyDispMenuItem.Checked;
7932
7933             SaveConfigsTabs();
7934         }
7935
7936         private void SoundFileComboBox_SelectedIndexChanged(object sender, EventArgs e)
7937         {
7938             if (soundfileListup || string.IsNullOrEmpty(_rclickTabName)) return;
7939
7940             _statuses.Tabs[_rclickTabName].SoundFile = (string)((ToolStripComboBox)sender).SelectedItem;
7941
7942             SaveConfigsTabs();
7943         }
7944
7945         private void DeleteTabMenuItem_Click(object sender, EventArgs e)
7946         {
7947             if (string.IsNullOrEmpty(_rclickTabName) || sender == this.DeleteTbMenuItem)
7948                 _rclickTabName = this.CurrentTabName;
7949
7950             RemoveSpecifiedTab(_rclickTabName, true);
7951             SaveConfigsTabs();
7952         }
7953
7954         private void FilterEditMenuItem_Click(object sender, EventArgs e)
7955         {
7956             if (string.IsNullOrEmpty(_rclickTabName)) _rclickTabName = _statuses.GetTabByType(MyCommon.TabUsageType.Home).TabName;
7957
7958             using (var fltDialog = new FilterDialog())
7959             {
7960                 fltDialog.Owner = this;
7961                 fltDialog.SetCurrent(_rclickTabName);
7962                 fltDialog.ShowDialog(this);
7963             }
7964             this.TopMost = SettingManager.Common.AlwaysTop;
7965
7966             this.ApplyPostFilters();
7967             SaveConfigsTabs();
7968         }
7969
7970         private async void AddTabMenuItem_Click(object sender, EventArgs e)
7971         {
7972             string tabName = null;
7973             MyCommon.TabUsageType tabUsage;
7974             using (var inputName = new InputTabName())
7975             {
7976                 inputName.TabName = _statuses.MakeTabName("MyTab");
7977                 inputName.IsShowUsage = true;
7978                 inputName.ShowDialog();
7979                 if (inputName.DialogResult == DialogResult.Cancel) return;
7980                 tabName = inputName.TabName;
7981                 tabUsage = inputName.Usage;
7982             }
7983             this.TopMost = SettingManager.Common.AlwaysTop;
7984             if (!string.IsNullOrEmpty(tabName))
7985             {
7986                 //List対応
7987                 ListElement list = null;
7988                 if (tabUsage == MyCommon.TabUsageType.Lists)
7989                 {
7990                     using (var listAvail = new ListAvailable())
7991                     {
7992                         if (listAvail.ShowDialog(this) == DialogResult.Cancel) return;
7993                         if (listAvail.SelectedList == null) return;
7994                         list = listAvail.SelectedList;
7995                     }
7996                 }
7997
7998                 TabModel tab;
7999                 switch (tabUsage)
8000                 {
8001                     case MyCommon.TabUsageType.UserDefined:
8002                         tab = new FilterTabModel(tabName);
8003                         break;
8004                     case MyCommon.TabUsageType.PublicSearch:
8005                         tab = new PublicSearchTabModel(tabName);
8006                         break;
8007                     case MyCommon.TabUsageType.Lists:
8008                         tab = new ListTimelineTabModel(tabName, list);
8009                         break;
8010                     default:
8011                         return;
8012                 }
8013
8014                 if (!_statuses.AddTab(tab) || !AddNewTab(tab, startup: false))
8015                 {
8016                     var tmp = string.Format(Properties.Resources.AddTabMenuItem_ClickText1, tabName);
8017                     MessageBox.Show(tmp, Properties.Resources.AddTabMenuItem_ClickText2, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
8018                 }
8019                 else
8020                 {
8021                     //成功
8022                     SaveConfigsTabs();
8023
8024                     var tabIndex = this._statuses.Tabs.Count - 1;
8025
8026                     if (tabUsage == MyCommon.TabUsageType.PublicSearch)
8027                     {
8028                         ListTab.SelectedIndex = tabIndex;
8029                         this.CurrentTabPage.Controls["panelSearch"].Controls["comboSearch"].Focus();
8030                     }
8031                     if (tabUsage == MyCommon.TabUsageType.Lists)
8032                     {
8033                         ListTab.SelectedIndex = tabIndex;
8034                         await this.RefreshTabAsync(this.CurrentTab);
8035                     }
8036                 }
8037             }
8038         }
8039
8040         private void TabMenuItem_Click(object sender, EventArgs e)
8041         {
8042             using (var fltDialog = new FilterDialog())
8043             {
8044                 fltDialog.Owner = this;
8045
8046                 //選択発言を元にフィルタ追加
8047                 foreach (var post in this.CurrentTab.SelectedPosts)
8048                 {
8049                     //タブ選択(or追加)
8050                     if (!SelectTab(out var tabName)) return;
8051
8052                     fltDialog.SetCurrent(tabName);
8053
8054                     if (post.RetweetedId == null)
8055                     {
8056                         fltDialog.AddNewFilter(post.ScreenName, post.TextFromApi);
8057                     }
8058                     else
8059                     {
8060                         fltDialog.AddNewFilter(post.RetweetedBy, post.TextFromApi);
8061                     }
8062                     fltDialog.ShowDialog(this);
8063                     this.TopMost = SettingManager.Common.AlwaysTop;
8064                 }
8065             }
8066
8067             this.ApplyPostFilters();
8068             SaveConfigsTabs();
8069         }
8070
8071         protected override bool ProcessDialogKey(Keys keyData)
8072         {
8073             //TextBox1でEnterを押してもビープ音が鳴らないようにする
8074             if ((keyData & Keys.KeyCode) == Keys.Enter)
8075             {
8076                 if (StatusText.Focused)
8077                 {
8078                     var _NewLine = false;
8079                     var _Post = false;
8080
8081                     if (SettingManager.Common.PostCtrlEnter) //Ctrl+Enter投稿時
8082                     {
8083                         if (StatusText.Multiline)
8084                         {
8085                             if ((keyData & Keys.Shift) == Keys.Shift && (keyData & Keys.Control) != Keys.Control) _NewLine = true;
8086
8087                             if ((keyData & Keys.Control) == Keys.Control) _Post = true;
8088                         }
8089                         else
8090                         {
8091                             if (((keyData & Keys.Control) == Keys.Control)) _Post = true;
8092                         }
8093
8094                     }
8095                     else if (SettingManager.Common.PostShiftEnter) //SHift+Enter投稿時
8096                     {
8097                         if (StatusText.Multiline)
8098                         {
8099                             if ((keyData & Keys.Control) == Keys.Control && (keyData & Keys.Shift) != Keys.Shift) _NewLine = true;
8100
8101                             if ((keyData & Keys.Shift) == Keys.Shift) _Post = true;
8102                         }
8103                         else
8104                         {
8105                             if (((keyData & Keys.Shift) == Keys.Shift)) _Post = true;
8106                         }
8107
8108                     }
8109                     else //Enter投稿時
8110                     {
8111                         if (StatusText.Multiline)
8112                         {
8113                             if ((keyData & Keys.Shift) == Keys.Shift && (keyData & Keys.Control) != Keys.Control) _NewLine = true;
8114
8115                             if (((keyData & Keys.Control) != Keys.Control && (keyData & Keys.Shift) != Keys.Shift) ||
8116                                 ((keyData & Keys.Control) == Keys.Control && (keyData & Keys.Shift) == Keys.Shift)) _Post = true;
8117                         }
8118                         else
8119                         {
8120                             if (((keyData & Keys.Shift) == Keys.Shift) ||
8121                                 (((keyData & Keys.Control) != Keys.Control) &&
8122                                 ((keyData & Keys.Shift) != Keys.Shift))) _Post = true;
8123                         }
8124                     }
8125
8126                     if (_NewLine)
8127                     {
8128                         var pos1 = StatusText.SelectionStart;
8129                         if (StatusText.SelectionLength > 0)
8130                         {
8131                             StatusText.Text = StatusText.Text.Remove(pos1, StatusText.SelectionLength);  //選択状態文字列削除
8132                         }
8133                         StatusText.Text = StatusText.Text.Insert(pos1, Environment.NewLine);  //改行挿入
8134                         StatusText.SelectionStart = pos1 + Environment.NewLine.Length;    //カーソルを改行の次の文字へ移動
8135                         return true;
8136                     }
8137                     else if (_Post)
8138                     {
8139                         PostButton_Click(null, null);
8140                         return true;
8141                     }
8142                 }
8143                 else
8144                 {
8145                     var tab = this.CurrentTab;
8146                     if (tab.TabType == MyCommon.TabUsageType.PublicSearch)
8147                     {
8148                         var tabPage = this.CurrentTabPage;
8149                         if (tabPage.Controls["panelSearch"].Controls["comboSearch"].Focused ||
8150                             tabPage.Controls["panelSearch"].Controls["comboLang"].Focused)
8151                         {
8152                             this.SearchButton_Click(tabPage.Controls["panelSearch"].Controls["comboSearch"], null);
8153                             return true;
8154                         }
8155                     }
8156                 }
8157             }
8158
8159             return base.ProcessDialogKey(keyData);
8160         }
8161
8162         private void ReplyAllStripMenuItem_Click(object sender, EventArgs e)
8163             => this.MakeReplyOrDirectStatus(false, true, true);
8164
8165         private void IDRuleMenuItem_Click(object sender, EventArgs e)
8166         {
8167             var tab = this.CurrentTab;
8168             var selectedPosts = tab.SelectedPosts;
8169
8170             // 未選択なら処理終了
8171             if (selectedPosts.Length == 0)
8172                 return;
8173
8174             var screenNameArray = selectedPosts
8175                 .Select(x => x.RetweetedId != null ? x.RetweetedBy : x.ScreenName)
8176                 .ToArray();
8177
8178             this.AddFilterRuleByScreenName(screenNameArray);
8179
8180             if (screenNameArray.Length != 0)
8181             {
8182                 var atids = new List<string>();
8183                 foreach (var screenName in screenNameArray)
8184                 {
8185                     atids.Add("@" + screenName);
8186                 }
8187                 var cnt = AtIdSupl.ItemCount;
8188                 AtIdSupl.AddRangeItem(atids.ToArray());
8189                 if (AtIdSupl.ItemCount != cnt)
8190                     this.MarkSettingAtIdModified();
8191             }
8192         }
8193
8194         private void SourceRuleMenuItem_Click(object sender, EventArgs e)
8195         {
8196             var tab = this.CurrentTab;
8197             var selectedPosts = tab.SelectedPosts;
8198
8199             if (selectedPosts.Length == 0)
8200                 return;
8201
8202             var sourceArray = selectedPosts.Select(x => x.Source).ToArray();
8203
8204             this.AddFilterRuleBySource(sourceArray);
8205         }
8206
8207         public void AddFilterRuleByScreenName(params string[] screenNameArray)
8208         {
8209             //タブ選択(or追加)
8210             if (!SelectTab(out var tabName)) return;
8211
8212             var tab = (FilterTabModel)this._statuses.Tabs[tabName];
8213
8214             bool mv;
8215             bool mk;
8216             if (tab.TabType != MyCommon.TabUsageType.Mute)
8217             {
8218                 this.MoveOrCopy(out mv, out mk);
8219             }
8220             else
8221             {
8222                 // ミュートタブでは常に MoveMatches を true にする
8223                 mv = true;
8224                 mk = false;
8225             }
8226
8227             foreach (var screenName in screenNameArray)
8228             {
8229                 tab.AddFilter(new PostFilterRule
8230                 {
8231                     FilterName = screenName,
8232                     UseNameField = true,
8233                     MoveMatches = mv,
8234                     MarkMatches = mk,
8235                     UseRegex = false,
8236                     FilterByUrl = false,
8237                 });
8238             }
8239
8240             this.ApplyPostFilters();
8241             SaveConfigsTabs();
8242         }
8243
8244         public void AddFilterRuleBySource(params string[] sourceArray)
8245         {
8246             // タブ選択ダイアログを表示(or追加)
8247             if (!this.SelectTab(out var tabName))
8248                 return;
8249
8250             var filterTab = (FilterTabModel)this._statuses.Tabs[tabName];
8251
8252             bool mv;
8253             bool mk;
8254             if (filterTab.TabType != MyCommon.TabUsageType.Mute)
8255             {
8256                 // フィルタ動作選択ダイアログを表示(移動/コピー, マーク有無)
8257                 this.MoveOrCopy(out mv, out mk);
8258             }
8259             else
8260             {
8261                 // ミュートタブでは常に MoveMatches を true にする
8262                 mv = true;
8263                 mk = false;
8264             }
8265
8266             // 振り分けルールに追加するSource
8267             foreach (var source in sourceArray)
8268             {
8269                 filterTab.AddFilter(new PostFilterRule
8270                 {
8271                     FilterSource = source,
8272                     MoveMatches = mv,
8273                     MarkMatches = mk,
8274                     UseRegex = false,
8275                     FilterByUrl = false,
8276                 });
8277             }
8278
8279             this.ApplyPostFilters();
8280             this.SaveConfigsTabs();
8281         }
8282
8283         private bool SelectTab(out string tabName)
8284         {
8285             do
8286             {
8287                 tabName = null;
8288
8289                 //振り分け先タブ選択
8290                 using (var dialog = new TabsDialog(_statuses))
8291                 {
8292                     if (dialog.ShowDialog(this) == DialogResult.Cancel) return false;
8293
8294                     tabName = dialog.SelectedTab?.TabName;
8295                 }
8296
8297                 this.CurrentTabPage.Focus();
8298                 //新規タブを選択→タブ作成
8299                 if (tabName == null)
8300                 {
8301                     using (var inputName = new InputTabName())
8302                     {
8303                         inputName.TabName = _statuses.MakeTabName("MyTab");
8304                         inputName.ShowDialog();
8305                         if (inputName.DialogResult == DialogResult.Cancel) return false;
8306                         tabName = inputName.TabName;
8307                     }
8308                     this.TopMost = SettingManager.Common.AlwaysTop;
8309                     if (!string.IsNullOrEmpty(tabName))
8310                     {
8311                         var tab = new FilterTabModel(tabName);
8312                         if (!_statuses.AddTab(tab) || !AddNewTab(tab, startup: false))
8313                         {
8314                             var tmp = string.Format(Properties.Resources.IDRuleMenuItem_ClickText2, tabName);
8315                             MessageBox.Show(tmp, Properties.Resources.IDRuleMenuItem_ClickText3, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
8316                             //もう一度タブ名入力
8317                         }
8318                         else
8319                         {
8320                             return true;
8321                         }
8322                     }
8323                 }
8324                 else
8325                 {
8326                     //既存タブを選択
8327                     return true;
8328                 }
8329             }
8330             while (true);
8331         }
8332
8333         private void MoveOrCopy(out bool move, out bool mark)
8334         {
8335             {
8336                 //移動するか?
8337                 var _tmp = string.Format(Properties.Resources.IDRuleMenuItem_ClickText4, Environment.NewLine);
8338                 if (MessageBox.Show(_tmp, Properties.Resources.IDRuleMenuItem_ClickText5, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
8339                     move = false;
8340                 else
8341                     move = true;
8342             }
8343             if (!move)
8344             {
8345                 //マークするか?
8346                 var _tmp = string.Format(Properties.Resources.IDRuleMenuItem_ClickText6, Environment.NewLine);
8347                 if (MessageBox.Show(_tmp, Properties.Resources.IDRuleMenuItem_ClickText7, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
8348                     mark = true;
8349                 else
8350                     mark = false;
8351             }
8352             else
8353             {
8354                 mark = false;
8355             }
8356         }
8357
8358         private void CopySTOTMenuItem_Click(object sender, EventArgs e)
8359             => this.CopyStot();
8360
8361         private void CopyURLMenuItem_Click(object sender, EventArgs e)
8362             => this.CopyIdUri();
8363
8364         private void SelectAllMenuItem_Click(object sender, EventArgs e)
8365         {
8366             if (StatusText.Focused)
8367             {
8368                 // 発言欄でのCtrl+A
8369                 StatusText.SelectAll();
8370             }
8371             else
8372             {
8373                 // ListView上でのCtrl+A
8374                 NativeMethods.SelectAllItems(this.CurrentListView);
8375             }
8376         }
8377
8378         private void MoveMiddle()
8379         {
8380             ListViewItem _item;
8381             int idx1;
8382             int idx2;
8383
8384             var listView = this.CurrentListView;
8385             if (listView.SelectedIndices.Count == 0) return;
8386
8387             var idx = listView.SelectedIndices[0];
8388
8389             _item = listView.GetItemAt(0, 25);
8390             if (_item == null)
8391                 idx1 = 0;
8392             else
8393                 idx1 = _item.Index;
8394
8395             _item = listView.GetItemAt(0, listView.ClientSize.Height - 1);
8396             if (_item == null)
8397                 idx2 = listView.VirtualListSize - 1;
8398             else
8399                 idx2 = _item.Index;
8400
8401             idx -= Math.Abs(idx1 - idx2) / 2;
8402             if (idx < 0) idx = 0;
8403
8404             listView.EnsureVisible(listView.VirtualListSize - 1);
8405             listView.EnsureVisible(idx);
8406         }
8407
8408         private async void OpenURLMenuItem_Click(object sender, EventArgs e)
8409         {
8410             var linkElements = this.tweetDetailsView.GetLinkElements();
8411
8412             if (linkElements.Length == 0)
8413                 return;
8414
8415             var links = new List<OpenUrlItem>(linkElements.Length);
8416
8417             foreach (var linkElm in linkElements)
8418             {
8419                 var displayUrl = linkElm.GetAttribute("title");
8420                 var href = linkElm.GetAttribute("href");
8421                 var linkedText = linkElm.InnerText;
8422
8423                 if (string.IsNullOrEmpty(displayUrl))
8424                     displayUrl = href;
8425
8426                 links.Add(new OpenUrlItem(linkedText, displayUrl, href));
8427             }
8428
8429             string selectedUrl;
8430             bool isReverseSettings;
8431
8432             if (links.Count == 1)
8433             {
8434                 // ツイートに含まれる URL が 1 つのみの場合
8435                 //   => OpenURL ダイアログを表示せずにリンクを開く
8436                 selectedUrl = links[0].Href;
8437
8438                 // Ctrl+E で呼ばれた場合を考慮し isReverseSettings の判定を行わない
8439                 isReverseSettings = false;
8440             }
8441             else
8442             {
8443                 // ツイートに含まれる URL が複数ある場合
8444                 //   => OpenURL を表示しユーザーが選択したリンクを開く
8445                 this.UrlDialog.ClearUrl();
8446
8447                 foreach (var link in links)
8448                     this.UrlDialog.AddUrl(link);
8449
8450                 if (this.UrlDialog.ShowDialog(this) != DialogResult.OK)
8451                     return;
8452
8453                 this.TopMost = SettingManager.Common.AlwaysTop;
8454
8455                 selectedUrl = this.UrlDialog.SelectedUrl;
8456
8457                 // Ctrlを押しながらリンクを開いた場合は、設定と逆の動作をするフラグを true としておく
8458                 isReverseSettings = MyCommon.IsKeyDown(Keys.Control);
8459             }
8460
8461             await this.OpenUriAsync(new Uri(selectedUrl), isReverseSettings);
8462         }
8463
8464         private void ClearTabMenuItem_Click(object sender, EventArgs e)
8465         {
8466             if (string.IsNullOrEmpty(_rclickTabName)) return;
8467             ClearTab(_rclickTabName, true);
8468         }
8469
8470         private void ClearTab(string tabName, bool showWarning)
8471         {
8472             if (showWarning)
8473             {
8474                 var tmp = string.Format(Properties.Resources.ClearTabMenuItem_ClickText1, Environment.NewLine);
8475                 if (MessageBox.Show(tmp, tabName + " " + Properties.Resources.ClearTabMenuItem_ClickText2, MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
8476                 {
8477                     return;
8478                 }
8479             }
8480
8481             _statuses.ClearTabIds(tabName);
8482             if (this.CurrentTabName == tabName)
8483             {
8484                 _anchorPost = null;
8485                 _anchorFlag = false;
8486                 this.PurgeListViewItemCache();
8487             }
8488
8489             var tabIndex = this._statuses.Tabs.IndexOf(tabName);
8490             var tabPage = this.ListTab.TabPages[tabIndex];
8491             tabPage.ImageIndex = -1;
8492
8493             var listView = (DetailsListView)tabPage.Tag;
8494             listView.VirtualListSize = 0;
8495
8496             if (!SettingManager.Common.TabIconDisp) ListTab.Refresh();
8497
8498             SetMainWindowTitle();
8499             SetStatusLabelUrl();
8500         }
8501
8502         private static long followers = 0;
8503
8504         private void SetMainWindowTitle()
8505         {
8506             //メインウインドウタイトルの書き換え
8507             var ttl = new StringBuilder(256);
8508             var ur = 0;
8509             var al = 0;
8510             if (SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.None &&
8511                 SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.Post &&
8512                 SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.Ver &&
8513                 SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.OwnStatus)
8514             {
8515                 foreach (var tab in _statuses.Tabs)
8516                 {
8517                     ur += tab.UnreadCount;
8518                     al += tab.AllCount;
8519                 }
8520             }
8521
8522             if (SettingManager.Common.DispUsername) ttl.Append(tw.Username).Append(" - ");
8523             ttl.Append(ApplicationSettings.ApplicationName);
8524             ttl.Append("  ");
8525             switch (SettingManager.Common.DispLatestPost)
8526             {
8527                 case MyCommon.DispTitleEnum.Ver:
8528                     ttl.Append("Ver:").Append(MyCommon.GetReadableVersion());
8529                     break;
8530                 case MyCommon.DispTitleEnum.Post:
8531                     if (_history != null && _history.Count > 1)
8532                         ttl.Append(_history[_history.Count - 2].status.Replace("\r\n", " "));
8533                     break;
8534                 case MyCommon.DispTitleEnum.UnreadRepCount:
8535                     ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText1, _statuses.GetTabByType(MyCommon.TabUsageType.Mentions).UnreadCount + _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage).UnreadCount);
8536                     break;
8537                 case MyCommon.DispTitleEnum.UnreadAllCount:
8538                     ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText2, ur);
8539                     break;
8540                 case MyCommon.DispTitleEnum.UnreadAllRepCount:
8541                     ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText3, ur, _statuses.GetTabByType(MyCommon.TabUsageType.Mentions).UnreadCount + _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage).UnreadCount);
8542                     break;
8543                 case MyCommon.DispTitleEnum.UnreadCountAllCount:
8544                     ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText4, ur, al);
8545                     break;
8546                 case MyCommon.DispTitleEnum.OwnStatus:
8547                     if (followers == 0 && tw.FollowersCount > 0) followers = tw.FollowersCount;
8548                     ttl.AppendFormat(Properties.Resources.OwnStatusTitle, tw.StatusesCount, tw.FriendsCount, tw.FollowersCount, tw.FollowersCount - followers);
8549                     break;
8550             }
8551
8552             try
8553             {
8554                 this.Text = ttl.ToString();
8555             }
8556             catch (AccessViolationException)
8557             {
8558                 //原因不明。ポスト内容に依存か?たまーに発生するが再現せず。
8559             }
8560         }
8561
8562         private string GetStatusLabelText()
8563         {
8564             //ステータス欄にカウント表示
8565             //タブ未読数/タブ発言数 全未読数/総発言数 (未読@+未読DM数)
8566             if (_statuses == null) return "";
8567             var tbRep = _statuses.GetTabByType(MyCommon.TabUsageType.Mentions);
8568             var tbDm = _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage);
8569             if (tbRep == null || tbDm == null) return "";
8570             var urat = tbRep.UnreadCount + tbDm.UnreadCount;
8571             var ur = 0;
8572             var al = 0;
8573             var tur = 0;
8574             var tal = 0;
8575             var slbl = new StringBuilder(256);
8576             try
8577             {
8578                 foreach (var tab in _statuses.Tabs)
8579                 {
8580                     ur += tab.UnreadCount;
8581                     al += tab.AllCount;
8582                     if (tab.TabName == this.CurrentTabName)
8583                     {
8584                         tur = tab.UnreadCount;
8585                         tal = tab.AllCount;
8586                     }
8587                 }
8588             }
8589             catch (Exception)
8590             {
8591                 return "";
8592             }
8593
8594             UnreadCounter = ur;
8595             UnreadAtCounter = urat;
8596
8597             var homeTab = this._statuses.GetTabByType<HomeTabModel>();
8598
8599             slbl.AppendFormat(Properties.Resources.SetStatusLabelText1, tur, tal, ur, al, urat, _postTimestamps.Count, _favTimestamps.Count, homeTab.TweetsPerHour);
8600             if (SettingManager.Common.TimelinePeriod == 0)
8601             {
8602                 slbl.Append(Properties.Resources.SetStatusLabelText2);
8603             }
8604             else
8605             {
8606                 slbl.Append(SettingManager.Common.TimelinePeriod + Properties.Resources.SetStatusLabelText3);
8607             }
8608             return slbl.ToString();
8609         }
8610
8611         private async void TwitterApiStatus_AccessLimitUpdated(object sender, EventArgs e)
8612         {
8613             try
8614             {
8615                 if (this.InvokeRequired && !this.IsDisposed)
8616                 {
8617                     await this.InvokeAsync(() => this.TwitterApiStatus_AccessLimitUpdated(sender, e));
8618                 }
8619                 else
8620                 {
8621                     var endpointName = (e as TwitterApiStatus.AccessLimitUpdatedEventArgs).EndpointName;
8622                     SetApiStatusLabel(endpointName);
8623                 }
8624             }
8625             catch (ObjectDisposedException)
8626             {
8627                 return;
8628             }
8629             catch (InvalidOperationException)
8630             {
8631                 return;
8632             }
8633         }
8634
8635         private void SetApiStatusLabel(string endpointName = null)
8636         {
8637             var tabType = this.CurrentTab.TabType;
8638
8639             if (endpointName == null)
8640             {
8641                 // 表示中のタブに応じて更新
8642                 switch (tabType)
8643                 {
8644                     case MyCommon.TabUsageType.Home:
8645                     case MyCommon.TabUsageType.UserDefined:
8646                         endpointName = "/statuses/home_timeline";
8647                         break;
8648
8649                     case MyCommon.TabUsageType.Mentions:
8650                         endpointName = "/statuses/mentions_timeline";
8651                         break;
8652
8653                     case MyCommon.TabUsageType.Favorites:
8654                         endpointName = "/favorites/list";
8655                         break;
8656
8657                     case MyCommon.TabUsageType.DirectMessage:
8658                         endpointName = "/direct_messages/events/list";
8659                         break;
8660
8661                     case MyCommon.TabUsageType.UserTimeline:
8662                         endpointName = "/statuses/user_timeline";
8663                         break;
8664
8665                     case MyCommon.TabUsageType.Lists:
8666                         endpointName = "/lists/statuses";
8667                         break;
8668
8669                     case MyCommon.TabUsageType.PublicSearch:
8670                         endpointName = "/search/tweets";
8671                         break;
8672
8673                     case MyCommon.TabUsageType.Related:
8674                         endpointName = "/statuses/show/:id";
8675                         break;
8676
8677                     default:
8678                         break;
8679                 }
8680
8681                 this.toolStripApiGauge.ApiEndpoint = endpointName;
8682             }
8683             else
8684             {
8685                 // 表示中のタブに関連する endpoint であれば更新
8686                 var update = false;
8687
8688                 switch (endpointName)
8689                 {
8690                     case "/statuses/home_timeline":
8691                         update = tabType == MyCommon.TabUsageType.Home ||
8692                                  tabType == MyCommon.TabUsageType.UserDefined;
8693                         break;
8694
8695                     case "/statuses/mentions_timeline":
8696                         update = tabType == MyCommon.TabUsageType.Mentions;
8697                         break;
8698
8699                     case "/favorites/list":
8700                         update = tabType == MyCommon.TabUsageType.Favorites;
8701                         break;
8702
8703                     case "/direct_messages/events/list":
8704                         update = tabType == MyCommon.TabUsageType.DirectMessage;
8705                         break;
8706
8707                     case "/statuses/user_timeline":
8708                         update = tabType == MyCommon.TabUsageType.UserTimeline;
8709                         break;
8710
8711                     case "/lists/statuses":
8712                         update = tabType == MyCommon.TabUsageType.Lists;
8713                         break;
8714
8715                     case "/search/tweets":
8716                         update = tabType == MyCommon.TabUsageType.PublicSearch;
8717                         break;
8718
8719                     case "/statuses/show/:id":
8720                         update = tabType == MyCommon.TabUsageType.Related;
8721                         break;
8722
8723                     default:
8724                         break;
8725                 }
8726
8727                 if (update)
8728                 {
8729                     this.toolStripApiGauge.ApiEndpoint = endpointName;
8730                 }
8731             }
8732         }
8733
8734         private void SetStatusLabelUrl()
8735             => this.StatusLabelUrl.Text = this.GetStatusLabelText();
8736
8737         public void SetStatusLabel(string text)
8738             => this.StatusLabel.Text = text;
8739
8740         private void SetNotifyIconText()
8741         {
8742             var ur = new StringBuilder(64);
8743
8744             // タスクトレイアイコンのツールチップテキスト書き換え
8745             // Tween [未読/@]
8746             ur.Remove(0, ur.Length);
8747             if (SettingManager.Common.DispUsername)
8748             {
8749                 ur.Append(tw.Username);
8750                 ur.Append(" - ");
8751             }
8752             ur.Append(ApplicationSettings.ApplicationName);
8753 #if DEBUG
8754             ur.Append("(Debug Build)");
8755 #endif
8756             if (UnreadCounter != -1 && UnreadAtCounter != -1)
8757             {
8758                 ur.Append(" [");
8759                 ur.Append(UnreadCounter);
8760                 ur.Append("/@");
8761                 ur.Append(UnreadAtCounter);
8762                 ur.Append("]");
8763             }
8764             NotifyIcon1.Text = ur.ToString();
8765         }
8766
8767         internal void CheckReplyTo(string StatusText)
8768         {
8769             MatchCollection m;
8770             //ハッシュタグの保存
8771             m = Regex.Matches(StatusText, Twitter.HASHTAG, RegexOptions.IgnoreCase);
8772             var hstr = "";
8773             foreach (Match hm in m)
8774             {
8775                 if (!hstr.Contains("#" + hm.Result("$3") + " "))
8776                 {
8777                     hstr += "#" + hm.Result("$3") + " ";
8778                     HashSupl.AddItem("#" + hm.Result("$3"));
8779                 }
8780             }
8781             if (!string.IsNullOrEmpty(HashMgr.UseHash) && !hstr.Contains(HashMgr.UseHash + " "))
8782             {
8783                 hstr += HashMgr.UseHash;
8784             }
8785             if (!string.IsNullOrEmpty(hstr)) HashMgr.AddHashToHistory(hstr.Trim(), false);
8786
8787             // 本当にリプライ先指定すべきかどうかの判定
8788             m = Regex.Matches(StatusText, "(^|[ -/:-@[-^`{-~])(?<id>@[a-zA-Z0-9_]+)");
8789
8790             if (SettingManager.Common.UseAtIdSupplement)
8791             {
8792                 var bCnt = AtIdSupl.ItemCount;
8793                 foreach (Match mid in m)
8794                 {
8795                     AtIdSupl.AddItem(mid.Result("${id}"));
8796                 }
8797                 if (bCnt != AtIdSupl.ItemCount)
8798                     this.MarkSettingAtIdModified();
8799             }
8800
8801             // リプライ先ステータスIDの指定がない場合は指定しない
8802             if (this.inReplyTo == null)
8803                 return;
8804
8805             // 通常Reply
8806             // 次の条件を満たす場合に in_reply_to_status_id 指定
8807             // 1. Twitterによりリンクと判定される @idが文中に1つ含まれる (2009/5/28 リンク化される@IDのみカウントするように修正)
8808             // 2. リプライ先ステータスIDが設定されている(リストをダブルクリックで返信している)
8809             // 3. 文中に含まれた@idがリプライ先のポスト者のIDと一致する
8810
8811             if (m != null)
8812             {
8813                 var inReplyToScreenName = this.inReplyTo.Value.ScreenName;
8814                 if (StatusText.StartsWith("@", StringComparison.Ordinal))
8815                 {
8816                     if (StatusText.StartsWith("@" + inReplyToScreenName, StringComparison.Ordinal)) return;
8817                 }
8818                 else
8819                 {
8820                     foreach (Match mid in m)
8821                     {
8822                         if (StatusText.Contains("RT " + mid.Result("${id}") + ":") && mid.Result("${id}") == "@" + inReplyToScreenName) return;
8823                     }
8824                 }
8825             }
8826
8827             this.inReplyTo = null;
8828         }
8829
8830         private void TweenMain_Resize(object sender, EventArgs e)
8831         {
8832             if (!_initialLayout && SettingManager.Common.MinimizeToTray && WindowState == FormWindowState.Minimized)
8833             {
8834                 this.Visible = false;
8835             }
8836             if (_initialLayout && SettingManager.Local != null && this.WindowState == FormWindowState.Normal && this.Visible)
8837             {
8838                 // 現在の DPI と設定保存時の DPI との比を取得する
8839                 var configScaleFactor = SettingManager.Local.GetConfigScaleFactor(this.CurrentAutoScaleDimensions);
8840
8841                 this.ClientSize = ScaleBy(configScaleFactor, SettingManager.Local.FormSize);
8842
8843                 // Splitterの位置設定
8844                 var splitterDistance = ScaleBy(configScaleFactor.Height, SettingManager.Local.SplitterDistance);
8845                 if (splitterDistance > this.SplitContainer1.Panel1MinSize &&
8846                     splitterDistance < this.SplitContainer1.Height - this.SplitContainer1.Panel2MinSize - this.SplitContainer1.SplitterWidth)
8847                 {
8848                     this.SplitContainer1.SplitterDistance = splitterDistance;
8849                 }
8850
8851                 //発言欄複数行
8852                 StatusText.Multiline = SettingManager.Local.StatusMultiline;
8853                 if (StatusText.Multiline)
8854                 {
8855                     var statusTextHeight = ScaleBy(configScaleFactor.Height, SettingManager.Local.StatusTextHeight);
8856                     var dis = SplitContainer2.Height - statusTextHeight - SplitContainer2.SplitterWidth;
8857                     if (dis > SplitContainer2.Panel1MinSize && dis < SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth)
8858                     {
8859                         SplitContainer2.SplitterDistance = SplitContainer2.Height - statusTextHeight - SplitContainer2.SplitterWidth;
8860                     }
8861                     StatusText.Height = statusTextHeight;
8862                 }
8863                 else
8864                 {
8865                     if (SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth > 0)
8866                     {
8867                         SplitContainer2.SplitterDistance = SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth;
8868                     }
8869                 }
8870
8871                 var previewDistance = ScaleBy(configScaleFactor.Width, SettingManager.Local.PreviewDistance);
8872                 if (previewDistance > this.SplitContainer3.Panel1MinSize && previewDistance < this.SplitContainer3.Width - this.SplitContainer3.Panel2MinSize - this.SplitContainer3.SplitterWidth)
8873                 {
8874                     this.SplitContainer3.SplitterDistance = previewDistance;
8875                 }
8876
8877                 // Panel2Collapsed は SplitterDistance の設定を終えるまで true にしない
8878                 this.SplitContainer3.Panel2Collapsed = true;
8879
8880                 _initialLayout = false;
8881             }
8882             if (this.WindowState != FormWindowState.Minimized)
8883             {
8884                 _formWindowState = this.WindowState;
8885             }
8886         }
8887
8888         private void PlaySoundMenuItem_CheckedChanged(object sender, EventArgs e)
8889         {
8890             PlaySoundMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
8891             this.PlaySoundFileMenuItem.Checked = PlaySoundMenuItem.Checked;
8892             if (PlaySoundMenuItem.Checked)
8893             {
8894                 SettingManager.Common.PlaySound = true;
8895             }
8896             else
8897             {
8898                 SettingManager.Common.PlaySound = false;
8899             }
8900             this.MarkSettingCommonModified();
8901         }
8902
8903         private void SplitContainer1_SplitterMoved(object sender, SplitterEventArgs e)
8904         {
8905             if (this._initialLayout)
8906                 return;
8907
8908             int splitterDistance;
8909             switch (this.WindowState)
8910             {
8911                 case FormWindowState.Normal:
8912                     splitterDistance = this.SplitContainer1.SplitterDistance;
8913                     break;
8914                 case FormWindowState.Maximized:
8915                     // 最大化時は、通常時のウィンドウサイズに換算した SplitterDistance を算出する
8916                     var normalContainerHeight = this._mySize.Height - this.ToolStripContainer1.TopToolStripPanel.Height - this.ToolStripContainer1.BottomToolStripPanel.Height;
8917                     splitterDistance = this.SplitContainer1.SplitterDistance - (this.SplitContainer1.Height - normalContainerHeight);
8918                     splitterDistance = Math.Min(splitterDistance, normalContainerHeight - this.SplitContainer1.SplitterWidth - this.SplitContainer1.Panel2MinSize);
8919                     break;
8920                 default:
8921                     return;
8922             }
8923
8924             this._mySpDis = splitterDistance;
8925             this.MarkSettingLocalModified();
8926         }
8927
8928         private async Task doRepliedStatusOpen()
8929         {
8930             var currentPost = this.CurrentPost;
8931             if (this.ExistCurrentPost && currentPost.InReplyToUser != null && currentPost.InReplyToStatusId != null)
8932             {
8933                 if (MyCommon.IsKeyDown(Keys.Shift))
8934                 {
8935                     await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(currentPost.InReplyToUser, currentPost.InReplyToStatusId.Value));
8936                     return;
8937                 }
8938                 if (_statuses.ContainsKey(currentPost.InReplyToStatusId.Value))
8939                 {
8940                     var repPost = _statuses[currentPost.InReplyToStatusId.Value];
8941                     MessageBox.Show($"{repPost.ScreenName} / {repPost.Nickname}   ({repPost.CreatedAt.ToLocalTimeString()})" + Environment.NewLine + repPost.TextFromApi);
8942                 }
8943                 else
8944                 {
8945                     foreach (var tb in _statuses.GetTabsByType(MyCommon.TabUsageType.Lists | MyCommon.TabUsageType.PublicSearch))
8946                     {
8947                         if (tb == null || !tb.Contains(currentPost.InReplyToStatusId.Value)) break;
8948                         var repPost = _statuses[currentPost.InReplyToStatusId.Value];
8949                         MessageBox.Show($"{repPost.ScreenName} / {repPost.Nickname}   ({repPost.CreatedAt.ToLocalTimeString()})" + Environment.NewLine + repPost.TextFromApi);
8950                         return;
8951                     }
8952                     await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(currentPost.InReplyToUser, currentPost.InReplyToStatusId.Value));
8953                 }
8954             }
8955         }
8956
8957         private async void RepliedStatusOpenMenuItem_Click(object sender, EventArgs e)
8958             => await this.doRepliedStatusOpen();
8959
8960         private void SplitContainer2_Panel2_Resize(object sender, EventArgs e)
8961         {
8962             if (this._initialLayout)
8963                 return; // SettingLocal の反映が完了するまで multiline の判定を行わない
8964
8965             var multiline = this.SplitContainer2.Panel2.Height > this.SplitContainer2.Panel2MinSize + 2;
8966             if (multiline != this.StatusText.Multiline)
8967             {
8968                 this.StatusText.Multiline = multiline;
8969                 SettingManager.Local.StatusMultiline = multiline;
8970                 this.MarkSettingLocalModified();
8971             }
8972         }
8973
8974         private void StatusText_MultilineChanged(object sender, EventArgs e)
8975         {
8976             if (this.StatusText.Multiline)
8977                 this.StatusText.ScrollBars = ScrollBars.Vertical;
8978             else
8979                 this.StatusText.ScrollBars = ScrollBars.None;
8980
8981             if (!this._initialLayout)
8982                 this.MarkSettingLocalModified();
8983         }
8984
8985         private void MultiLineMenuItem_Click(object sender, EventArgs e)
8986         {
8987             //発言欄複数行
8988             var menuItemChecked = ((ToolStripMenuItem)sender).Checked;
8989             StatusText.Multiline = menuItemChecked;
8990             SettingManager.Local.StatusMultiline = menuItemChecked;
8991             if (menuItemChecked)
8992             {
8993                 if (SplitContainer2.Height - _mySpDis2 - SplitContainer2.SplitterWidth < 0)
8994                     SplitContainer2.SplitterDistance = 0;
8995                 else
8996                     SplitContainer2.SplitterDistance = SplitContainer2.Height - _mySpDis2 - SplitContainer2.SplitterWidth;
8997             }
8998             else
8999             {
9000                 SplitContainer2.SplitterDistance = SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth;
9001             }
9002             this.MarkSettingLocalModified();
9003         }
9004
9005         private async Task<bool> UrlConvertAsync(MyCommon.UrlConverter Converter_Type)
9006         {
9007             if (Converter_Type == MyCommon.UrlConverter.Bitly || Converter_Type == MyCommon.UrlConverter.Jmp)
9008             {
9009                 // OAuth2 アクセストークンまたは API キー (旧方式) のいずれも設定されていなければ短縮しない
9010                 if (string.IsNullOrEmpty(SettingManager.Common.BitlyAccessToken) &&
9011                     (string.IsNullOrEmpty(SettingManager.Common.BilyUser) || string.IsNullOrEmpty(SettingManager.Common.BitlyPwd)))
9012                 {
9013                     MessageBox.Show(this, Properties.Resources.UrlConvert_BitlyAuthRequired, ApplicationSettings.ApplicationName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
9014                     return false;
9015                 }
9016             }
9017
9018             //t.coで投稿時自動短縮する場合は、外部サービスでの短縮禁止
9019             //if (SettingDialog.UrlConvertAuto && SettingDialog.ShortenTco) return;
9020
9021             //Converter_Type=Nicomsの場合は、nicovideoのみ短縮する
9022             //参考資料 RFC3986 Uniform Resource Identifier (URI): Generic Syntax
9023             //Appendix A.  Collected ABNF for URI
9024             //http://www.ietf.org/rfc/rfc3986.txt
9025
9026             const string nico = @"^https?://[a-z]+\.(nicovideo|niconicommons|nicolive)\.jp/[a-z]+/[a-z0-9]+$";
9027
9028             string result;
9029             if (StatusText.SelectionLength > 0)
9030             {
9031                 var 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                         var 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)
9104                         continue;
9105                     var tmp = mt.Result("${url}");
9106                     if (tmp.StartsWith("w", StringComparison.OrdinalIgnoreCase))
9107                         tmp = "http://" + tmp;
9108                     var undotmp = new urlUndo();
9109
9110                     //選んだURLを選択(?)
9111                     StatusText.Select(StatusText.Text.IndexOf(mt.Result("${url}"), StringComparison.Ordinal), mt.Result("${url}").Length);
9112
9113                     //nico.ms使用、nicovideoにマッチしたら変換
9114                     if (SettingManager.Common.Nicoms && Regex.IsMatch(tmp, nico))
9115                     {
9116                         result = nicoms.Shorten(tmp);
9117                     }
9118                     else if (Converter_Type != MyCommon.UrlConverter.Nicoms)
9119                     {
9120                         // 短縮URL変換
9121                         try
9122                         {
9123                             var srcUri = new Uri(tmp);
9124                             var resultUri = await ShortUrl.Instance.ShortenUrlAsync(Converter_Type, srcUri);
9125                             result = resultUri.AbsoluteUri;
9126                         }
9127                         catch (HttpRequestException e)
9128                         {
9129                             // 例外のメッセージが「Response status code does not indicate success: 500 (Internal Server Error).」
9130                             // のように長いので「:」が含まれていればそれ以降のみを抽出する
9131                             var message = e.Message.Split(new[] { ':' }, count: 2).Last();
9132
9133                             this.StatusLabel.Text = Converter_Type + ":" + message;
9134                             continue;
9135                         }
9136                         catch (WebApiException e)
9137                         {
9138                             this.StatusLabel.Text = Converter_Type + ":" + e.Message;
9139                             continue;
9140                         }
9141                         catch (UriFormatException e)
9142                         {
9143                             this.StatusLabel.Text = Converter_Type + ":" + e.Message;
9144                             continue;
9145                         }
9146                     }
9147                     else
9148                     {
9149                         continue;
9150                     }
9151
9152                     if (!string.IsNullOrEmpty(result))
9153                     {
9154                         // 短縮 URL が生成されるまでの間に投稿欄から元の URL が削除されていたら中断する
9155                         var origUrlIndex = this.StatusText.Text.IndexOf(mt.Result("${url}"), StringComparison.Ordinal);
9156                         if (origUrlIndex == -1)
9157                             return false;
9158
9159                         StatusText.Select(origUrlIndex, mt.Result("${url}").Length);
9160                         StatusText.SelectedText = result;
9161                         //undoバッファにセット
9162                         undotmp.Before = mt.Result("${url}");
9163                         undotmp.After = result;
9164
9165                         if (urlUndoBuffer == null)
9166                         {
9167                             urlUndoBuffer = new List<urlUndo>();
9168                             UrlUndoToolStripMenuItem.Enabled = true;
9169                         }
9170
9171                         urlUndoBuffer.Add(undotmp);
9172                     }
9173                 }
9174             }
9175
9176             return true;
9177         }
9178
9179         private void doUrlUndo()
9180         {
9181             if (urlUndoBuffer != null)
9182             {
9183                 var tmp = StatusText.Text;
9184                 foreach (var data in urlUndoBuffer)
9185                 {
9186                     tmp = tmp.Replace(data.After, data.Before);
9187                 }
9188                 StatusText.Text = tmp;
9189                 urlUndoBuffer = null;
9190                 UrlUndoToolStripMenuItem.Enabled = false;
9191                 StatusText.SelectionStart = 0;
9192                 StatusText.SelectionLength = 0;
9193             }
9194         }
9195
9196         private async void TinyURLToolStripMenuItem_Click(object sender, EventArgs e)
9197             => await this.UrlConvertAsync(MyCommon.UrlConverter.TinyUrl);
9198
9199         private async void IsgdToolStripMenuItem_Click(object sender, EventArgs e)
9200             => await this.UrlConvertAsync(MyCommon.UrlConverter.Isgd);
9201
9202         private async void UxnuMenuItem_Click(object sender, EventArgs e)
9203             => await this.UrlConvertAsync(MyCommon.UrlConverter.Uxnu);
9204
9205         private async void UrlConvertAutoToolStripMenuItem_Click(object sender, EventArgs e)
9206         {
9207             if (!await UrlConvertAsync(SettingManager.Common.AutoShortUrlFirst))
9208             {
9209                 var rnd = new Random();
9210
9211                 MyCommon.UrlConverter svc;
9212                 // 前回使用した短縮URLサービス以外を選択する
9213                 do
9214                 {
9215                     svc = (MyCommon.UrlConverter)rnd.Next(System.Enum.GetNames(typeof(MyCommon.UrlConverter)).Length);
9216                 }
9217                 while (svc == SettingManager.Common.AutoShortUrlFirst || svc == MyCommon.UrlConverter.Nicoms || svc == MyCommon.UrlConverter.Unu);
9218                 await UrlConvertAsync(svc);
9219             }
9220         }
9221
9222         private void UrlUndoToolStripMenuItem_Click(object sender, EventArgs e)
9223             => this.doUrlUndo();
9224
9225         private void NewPostPopMenuItem_CheckStateChanged(object sender, EventArgs e)
9226         {
9227             this.NotifyFileMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
9228             this.NewPostPopMenuItem.Checked = this.NotifyFileMenuItem.Checked;
9229             SettingManager.Common.NewAllPop = NewPostPopMenuItem.Checked;
9230             this.MarkSettingCommonModified();
9231         }
9232
9233         private void ListLockMenuItem_CheckStateChanged(object sender, EventArgs e)
9234         {
9235             ListLockMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
9236             this.LockListFileMenuItem.Checked = ListLockMenuItem.Checked;
9237             SettingManager.Common.ListLock = ListLockMenuItem.Checked;
9238             this.MarkSettingCommonModified();
9239         }
9240
9241         private void MenuStrip1_MenuActivate(object sender, EventArgs e)
9242         {
9243             // フォーカスがメニューに移る (MenuStrip1.Tag フラグを立てる)
9244             MenuStrip1.Tag = new object();
9245             MenuStrip1.Select(); // StatusText がフォーカスを持っている場合 Leave が発生
9246         }
9247
9248         private void MenuStrip1_MenuDeactivate(object sender, EventArgs e)
9249         {
9250             var currentTabPage = this.CurrentTabPage;
9251             if (this.Tag != null) // 設定された戻り先へ遷移
9252             {
9253                 if (this.Tag == currentTabPage)
9254                     ((Control)currentTabPage.Tag).Select();
9255                 else
9256                     ((Control)this.Tag).Select();
9257             }
9258             else // 戻り先が指定されていない (初期状態) 場合はタブに遷移
9259             {
9260                 this.Tag = currentTabPage.Tag;
9261                 ((Control)this.Tag).Select();
9262             }
9263             // フォーカスがメニューに遷移したかどうかを表すフラグを降ろす
9264             MenuStrip1.Tag = null;
9265         }
9266
9267         private void MyList_ColumnReordered(object sender, ColumnReorderedEventArgs e)
9268         {
9269             var lst = (DetailsListView)sender;
9270             if (SettingManager.Local == null) return;
9271
9272             if (_iconCol)
9273             {
9274                 SettingManager.Local.Width1 = lst.Columns[0].Width;
9275                 SettingManager.Local.Width3 = lst.Columns[1].Width;
9276             }
9277             else
9278             {
9279                 var darr = new int[lst.Columns.Count];
9280                 for (var i = 0; i < lst.Columns.Count; i++)
9281                 {
9282                     darr[lst.Columns[i].DisplayIndex] = i;
9283                 }
9284                 MyCommon.MoveArrayItem(darr, e.OldDisplayIndex, e.NewDisplayIndex);
9285
9286                 for (var i = 0; i < lst.Columns.Count; i++)
9287                 {
9288                     switch (darr[i])
9289                     {
9290                         case 0:
9291                             SettingManager.Local.DisplayIndex1 = i;
9292                             break;
9293                         case 1:
9294                             SettingManager.Local.DisplayIndex2 = i;
9295                             break;
9296                         case 2:
9297                             SettingManager.Local.DisplayIndex3 = i;
9298                             break;
9299                         case 3:
9300                             SettingManager.Local.DisplayIndex4 = i;
9301                             break;
9302                         case 4:
9303                             SettingManager.Local.DisplayIndex5 = i;
9304                             break;
9305                         case 5:
9306                             SettingManager.Local.DisplayIndex6 = i;
9307                             break;
9308                         case 6:
9309                             SettingManager.Local.DisplayIndex7 = i;
9310                             break;
9311                         case 7:
9312                             SettingManager.Local.DisplayIndex8 = i;
9313                             break;
9314                     }
9315                 }
9316                 SettingManager.Local.Width1 = lst.Columns[0].Width;
9317                 SettingManager.Local.Width2 = lst.Columns[1].Width;
9318                 SettingManager.Local.Width3 = lst.Columns[2].Width;
9319                 SettingManager.Local.Width4 = lst.Columns[3].Width;
9320                 SettingManager.Local.Width5 = lst.Columns[4].Width;
9321                 SettingManager.Local.Width6 = lst.Columns[5].Width;
9322                 SettingManager.Local.Width7 = lst.Columns[6].Width;
9323                 SettingManager.Local.Width8 = lst.Columns[7].Width;
9324             }
9325             this.MarkSettingLocalModified();
9326             _isColumnChanged = true;
9327         }
9328
9329         private void MyList_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e)
9330         {
9331             var lst = (DetailsListView)sender;
9332             if (SettingManager.Local == null) return;
9333
9334             var modified = false;
9335             if (_iconCol)
9336             {
9337                 if (SettingManager.Local.Width1 != lst.Columns[0].Width)
9338                 {
9339                     SettingManager.Local.Width1 = lst.Columns[0].Width;
9340                     modified = true;
9341                 }
9342                 if (SettingManager.Local.Width3 != lst.Columns[1].Width)
9343                 {
9344                     SettingManager.Local.Width3 = lst.Columns[1].Width;
9345                     modified = true;
9346                 }
9347             }
9348             else
9349             {
9350                 if (SettingManager.Local.Width1 != lst.Columns[0].Width)
9351                 {
9352                     SettingManager.Local.Width1 = lst.Columns[0].Width;
9353                     modified = true;
9354                 }
9355                 if (SettingManager.Local.Width2 != lst.Columns[1].Width)
9356                 {
9357                     SettingManager.Local.Width2 = lst.Columns[1].Width;
9358                     modified = true;
9359                 }
9360                 if (SettingManager.Local.Width3 != lst.Columns[2].Width)
9361                 {
9362                     SettingManager.Local.Width3 = lst.Columns[2].Width;
9363                     modified = true;
9364                 }
9365                 if (SettingManager.Local.Width4 != lst.Columns[3].Width)
9366                 {
9367                     SettingManager.Local.Width4 = lst.Columns[3].Width;
9368                     modified = true;
9369                 }
9370                 if (SettingManager.Local.Width5 != lst.Columns[4].Width)
9371                 {
9372                     SettingManager.Local.Width5 = lst.Columns[4].Width;
9373                     modified = true;
9374                 }
9375                 if (SettingManager.Local.Width6 != lst.Columns[5].Width)
9376                 {
9377                     SettingManager.Local.Width6 = lst.Columns[5].Width;
9378                     modified = true;
9379                 }
9380                 if (SettingManager.Local.Width7 != lst.Columns[6].Width)
9381                 {
9382                     SettingManager.Local.Width7 = lst.Columns[6].Width;
9383                     modified = true;
9384                 }
9385                 if (SettingManager.Local.Width8 != lst.Columns[7].Width)
9386                 {
9387                     SettingManager.Local.Width8 = lst.Columns[7].Width;
9388                     modified = true;
9389                 }
9390             }
9391             if (modified)
9392             {
9393                 this.MarkSettingLocalModified();
9394                 this._isColumnChanged = true;
9395             }
9396             // 非表示の時にColumnChangedが呼ばれた場合はForm初期化処理中なので保存しない
9397             //if (changed)
9398             //{
9399             //    SaveConfigsLocal();
9400             //}
9401         }
9402
9403         private void SplitContainer2_SplitterMoved(object sender, SplitterEventArgs e)
9404         {
9405             if (StatusText.Multiline) _mySpDis2 = StatusText.Height;
9406             this.MarkSettingLocalModified();
9407         }
9408
9409         private void TweenMain_DragDrop(object sender, DragEventArgs e)
9410         {
9411             if (e.Data.GetDataPresent(DataFormats.FileDrop))
9412             {
9413                 if (!e.Data.GetDataPresent(DataFormats.Html, false))  // WebBrowserコントロールからの絵文字画像Drag&Dropは弾く
9414                 {
9415                     SelectMedia_DragDrop(e);
9416                 }
9417             }
9418             else if (e.Data.GetDataPresent("UniformResourceLocatorW"))
9419             {
9420                 var (url, title) = GetUrlFromDataObject(e.Data);
9421
9422                 string appendText;
9423                 if (title == null)
9424                     appendText = url;
9425                 else
9426                     appendText = title + " " + url;
9427
9428                 if (this.StatusText.TextLength == 0)
9429                     this.StatusText.Text = appendText;
9430                 else
9431                     this.StatusText.Text += " " + appendText;
9432             }
9433             else if (e.Data.GetDataPresent(DataFormats.UnicodeText))
9434             {
9435                 var text = (string)e.Data.GetData(DataFormats.UnicodeText);
9436                 if (text != null)
9437                     this.StatusText.Text += text;
9438             }
9439             else if (e.Data.GetDataPresent(DataFormats.StringFormat))
9440             {
9441                 var data = (string)e.Data.GetData(DataFormats.StringFormat, true);
9442                 if (data != null) StatusText.Text += data;
9443             }
9444         }
9445
9446         /// <summary>
9447         /// IDataObject から URL とタイトルの対を取得します
9448         /// </summary>
9449         /// <remarks>
9450         /// タイトルのみ取得できなかった場合は Value2 が null のタプルを返すことがあります。
9451         /// </remarks>
9452         /// <exception cref="ArgumentException">不正なフォーマットが入力された場合</exception>
9453         /// <exception cref="NotSupportedException">サポートされていないデータが入力された場合</exception>
9454         internal static (string Url, string Title) GetUrlFromDataObject(IDataObject data)
9455         {
9456             if (data.GetDataPresent("text/x-moz-url"))
9457             {
9458                 // Firefox, Google Chrome で利用可能
9459                 // 参照: https://developer.mozilla.org/ja/docs/DragDrop/Recommended_Drag_Types
9460
9461                 using (var stream = (MemoryStream)data.GetData("text/x-moz-url"))
9462                 {
9463                     var lines = Encoding.Unicode.GetString(stream.ToArray()).TrimEnd('\0').Split('\n');
9464                     if (lines.Length < 2)
9465                         throw new ArgumentException("不正な text/x-moz-url フォーマットです", nameof(data));
9466
9467                     return (lines[0], lines[1]);
9468                 }
9469             }
9470             else if (data.GetDataPresent("IESiteModeToUrl"))
9471             {
9472                 // Internet Exproler 用
9473                 // 保護モードが有効なデフォルトの IE では DragDrop イベントが発火しないため使えない
9474
9475                 using (var stream = (MemoryStream)data.GetData("IESiteModeToUrl"))
9476                 {
9477                     var lines = Encoding.Unicode.GetString(stream.ToArray()).TrimEnd('\0').Split('\0');
9478                     if (lines.Length < 2)
9479                         throw new ArgumentException("不正な IESiteModeToUrl フォーマットです", nameof(data));
9480
9481                     return (lines[0], lines[1]);
9482                 }
9483             }
9484             else if (data.GetDataPresent("UniformResourceLocatorW"))
9485             {
9486                 // それ以外のブラウザ向け
9487
9488                 using (var stream = (MemoryStream)data.GetData("UniformResourceLocatorW"))
9489                 {
9490                     var url = Encoding.Unicode.GetString(stream.ToArray()).TrimEnd('\0');
9491                     return (url, null);
9492                 }
9493             }
9494
9495             throw new NotSupportedException("サポートされていないデータ形式です: " + data.GetFormats()[0]);
9496         }
9497
9498         private void TweenMain_DragEnter(object sender, DragEventArgs e)
9499         {
9500             if (e.Data.GetDataPresent(DataFormats.FileDrop))
9501             {
9502                 if (!e.Data.GetDataPresent(DataFormats.Html, false))  // WebBrowserコントロールからの絵文字画像Drag&Dropは弾く
9503                 {
9504                     SelectMedia_DragEnter(e);
9505                     return;
9506                 }
9507             }
9508             else if (e.Data.GetDataPresent("UniformResourceLocatorW"))
9509             {
9510                 e.Effect = DragDropEffects.Copy;
9511                 return;
9512             }
9513             else if (e.Data.GetDataPresent(DataFormats.UnicodeText))
9514             {
9515                 e.Effect = DragDropEffects.Copy;
9516                 return;
9517             }
9518             else if (e.Data.GetDataPresent(DataFormats.StringFormat))
9519             {
9520                 e.Effect = DragDropEffects.Copy;
9521                 return;
9522             }
9523
9524             e.Effect = DragDropEffects.None;
9525         }
9526
9527         private void TweenMain_DragOver(object sender, DragEventArgs e)
9528         {
9529         }
9530
9531         public bool IsNetworkAvailable()
9532         {
9533             var nw = MyCommon.IsNetworkAvailable();
9534             _myStatusOnline = nw;
9535             return nw;
9536         }
9537
9538         public async Task OpenUriAsync(Uri uri, bool isReverseSettings = false)
9539         {
9540             var uriStr = uri.AbsoluteUri;
9541
9542             // OpenTween 内部で使用する URL
9543             if (uri.Authority == "opentween")
9544             {
9545                 await this.OpenInternalUriAsync(uri);
9546                 return;
9547             }
9548
9549             // ハッシュタグを含む Twitter 検索
9550             if (uri.Host == "twitter.com" && uri.AbsolutePath == "/search" && uri.Query.Contains("q=%23"))
9551             {
9552                 // ハッシュタグの場合は、タブで開く
9553                 var unescapedQuery = Uri.UnescapeDataString(uri.Query);
9554                 var pos = unescapedQuery.IndexOf('#');
9555                 if (pos == -1) return;
9556
9557                 var hash = unescapedQuery.Substring(pos);
9558                 this.HashSupl.AddItem(hash);
9559                 this.HashMgr.AddHashToHistory(hash.Trim(), false);
9560                 this.AddNewTabForSearch(hash);
9561                 return;
9562             }
9563
9564             // ユーザープロフィールURL
9565             // フラグが立っている場合は設定と逆の動作をする
9566             if( SettingManager.Common.OpenUserTimeline && !isReverseSettings ||
9567                 !SettingManager.Common.OpenUserTimeline && isReverseSettings )
9568             {
9569                 var userUriMatch = Regex.Match(uriStr, "^https?://twitter.com/(#!/)?(?<ScreenName>[a-zA-Z0-9_]+)$");
9570                 if (userUriMatch.Success)
9571                 {
9572                     var screenName = userUriMatch.Groups["ScreenName"].Value;
9573                     if (this.IsTwitterId(screenName))
9574                     {
9575                         await this.AddNewTabForUserTimeline(screenName);
9576                         return;
9577                     }
9578                 }
9579             }
9580
9581             // どのパターンにも該当しないURL
9582             await this.OpenUriInBrowserAsync(uriStr);
9583         }
9584
9585         /// <summary>
9586         /// OpenTween 内部の機能を呼び出すための URL を開きます
9587         /// </summary>
9588         private async Task OpenInternalUriAsync(Uri uri)
9589         {
9590             // ツイートを開く (//opentween/status/:status_id)
9591             var match = Regex.Match(uri.AbsolutePath, @"^/status/(\d+)$");
9592             if (match.Success)
9593             {
9594                 var statusId = long.Parse(match.Groups[1].Value);
9595                 await this.OpenRelatedTab(statusId);
9596                 return;
9597             }
9598         }
9599
9600         public Task OpenUriInBrowserAsync(string UriString)
9601         {
9602             return Task.Run(() =>
9603             {
9604                 var myPath = UriString;
9605
9606                 try
9607                 {
9608                     var configBrowserPath = SettingManager.Local.BrowserPath;
9609                     if (!string.IsNullOrEmpty(configBrowserPath))
9610                     {
9611                         if (configBrowserPath.StartsWith("\"", StringComparison.Ordinal) && configBrowserPath.Length > 2 && configBrowserPath.IndexOf("\"", 2, StringComparison.Ordinal) > -1)
9612                         {
9613                             var sep = configBrowserPath.IndexOf("\"", 2, StringComparison.Ordinal);
9614                             var browserPath = configBrowserPath.Substring(1, sep - 1);
9615                             var arg = "";
9616                             if (sep < configBrowserPath.Length - 1)
9617                             {
9618                                 arg = configBrowserPath.Substring(sep + 1);
9619                             }
9620                             myPath = arg + " " + myPath;
9621                             System.Diagnostics.Process.Start(browserPath, myPath);
9622                         }
9623                         else
9624                         {
9625                             System.Diagnostics.Process.Start(configBrowserPath, myPath);
9626                         }
9627                     }
9628                     else
9629                     {
9630                         System.Diagnostics.Process.Start(myPath);
9631                     }
9632                 }
9633                 catch (Exception)
9634                 {
9635                     //MessageBox.Show("ブラウザの起動に失敗、またはタイムアウトしました。" + ex.ToString());
9636                 }
9637             });
9638         }
9639
9640         private void ListTabSelect(TabPage _tab)
9641         {
9642             SetListProperty();
9643
9644             this.PurgeListViewItemCache();
9645
9646             this._statuses.SelectTab(_tab.Text);
9647
9648             var listView = this.CurrentListView;
9649
9650             _anchorPost = null;
9651             _anchorFlag = false;
9652
9653             if (_iconCol)
9654             {
9655                 listView.Columns[1].Text = ColumnText[2];
9656             }
9657             else
9658             {
9659                 for (var i = 0; i < listView.Columns.Count; i++)
9660                 {
9661                     listView.Columns[i].Text = ColumnText[i];
9662                 }
9663             }
9664         }
9665
9666         private void ListTab_Selecting(object sender, TabControlCancelEventArgs e)
9667             => this.ListTabSelect(e.TabPage);
9668
9669         private void SelectListItem(DetailsListView LView, int Index)
9670         {
9671             //単一
9672             var bnd = new Rectangle();
9673             var flg = false;
9674             var item = LView.FocusedItem;
9675             if (item != null)
9676             {
9677                 bnd = item.Bounds;
9678                 flg = true;
9679             }
9680
9681             do
9682             {
9683                 LView.SelectedIndices.Clear();
9684             }
9685             while (LView.SelectedIndices.Count > 0);
9686             item = LView.Items[Index];
9687             item.Selected = true;
9688             item.Focused = true;
9689
9690             if (flg) LView.Invalidate(bnd);
9691         }
9692
9693         private void SelectListItem(DetailsListView LView , int[] Index, int focusedIndex, int selectionMarkIndex)
9694         {
9695             //複数
9696             var bnd = new Rectangle();
9697             var flg = false;
9698             var item = LView.FocusedItem;
9699             if (item != null)
9700             {
9701                 bnd = item.Bounds;
9702                 flg = true;
9703             }
9704
9705             if (Index != null)
9706             {
9707                 do
9708                 {
9709                     LView.SelectedIndices.Clear();
9710                 }
9711                 while (LView.SelectedIndices.Count > 0);
9712                 LView.SelectItems(Index);
9713             }
9714             if (selectionMarkIndex > -1 && LView.VirtualListSize > selectionMarkIndex)
9715             {
9716                 LView.SelectionMark = selectionMarkIndex;
9717             }
9718             if (focusedIndex > -1 && LView.VirtualListSize > focusedIndex)
9719             {
9720                 LView.Items[focusedIndex].Focused = true;
9721             }
9722             else if (Index != null && Index.Length != 0)
9723             {
9724                 LView.Items[Index.Last()].Focused = true;
9725             }
9726
9727             if (flg) LView.Invalidate(bnd);
9728         }
9729
9730         private void StartUserStream()
9731         {
9732             tw.NewPostFromStream += tw_NewPostFromStream;
9733             tw.UserStreamStarted += tw_UserStreamStarted;
9734             tw.UserStreamStopped += tw_UserStreamStopped;
9735             tw.PostDeleted += tw_PostDeleted;
9736             tw.UserStreamEventReceived += tw_UserStreamEventArrived;
9737
9738             this.RefreshUserStreamsMenu();
9739
9740             if (SettingManager.Common.UserstreamStartup)
9741                 tw.StartUserStream();
9742         }
9743
9744         private async void TweenMain_Shown(object sender, EventArgs e)
9745         {
9746             NotifyIcon1.Visible = true;
9747
9748             if (this.IsNetworkAvailable())
9749             {
9750                 StartUserStream();
9751
9752                 var loadTasks = new List<Task>
9753                 {
9754                     this.RefreshMuteUserIdsAsync(),
9755                     this.RefreshBlockIdsAsync(),
9756                     this.RefreshNoRetweetIdsAsync(),
9757                     this.RefreshTwitterConfigurationAsync(),
9758                     this.RefreshTabAsync<HomeTabModel>(),
9759                     this.RefreshTabAsync<MentionsTabModel>(),
9760                     this.RefreshTabAsync<DirectMessagesTabModel>(),
9761                     this.RefreshTabAsync<PublicSearchTabModel>(),
9762                     this.RefreshTabAsync<UserTimelineTabModel>(),
9763                     this.RefreshTabAsync<ListTimelineTabModel>(),
9764                 };
9765
9766                 if (SettingManager.Common.StartupFollowers)
9767                     loadTasks.Add(this.RefreshFollowerIdsAsync());
9768
9769                 if (SettingManager.Common.GetFav)
9770                     loadTasks.Add(this.RefreshTabAsync<FavoritesTabModel>());
9771
9772                 var allTasks = Task.WhenAll(loadTasks);
9773
9774                 var i = 0;
9775                 while (true)
9776                 {
9777                     var timeout = Task.Delay(5000);
9778                     if (await Task.WhenAny(allTasks, timeout) != timeout)
9779                         break;
9780
9781                     i += 1;
9782                     if (i > 24) break; // 120秒間初期処理が終了しなかったら強制的に打ち切る
9783
9784                     if (MyCommon._endingFlag)
9785                         return;
9786                 }
9787
9788                 if (MyCommon._endingFlag) return;
9789
9790                 if (ApplicationSettings.VersionInfoUrl != null)
9791                 {
9792                     //バージョンチェック(引数:起動時チェックの場合はtrue・・・チェック結果のメッセージを表示しない)
9793                     if (SettingManager.Common.StartupVersion)
9794                         await this.CheckNewVersion(true);
9795                 }
9796                 else
9797                 {
9798                     // ApplicationSetting.cs の設定により更新チェックが無効化されている場合
9799                     this.VerUpMenuItem.Enabled = false;
9800                     this.VerUpMenuItem.Available = false;
9801                     this.ToolStripSeparator16.Available = false; // VerUpMenuItem の一つ上にあるセパレータ
9802                 }
9803
9804                 // 権限チェック read/write権限(xAuthで取得したトークン)の場合は再認証を促す
9805                 if (MyCommon.TwitterApiInfo.AccessLevel == TwitterApiAccessLevel.ReadWrite)
9806                 {
9807                     MessageBox.Show(Properties.Resources.ReAuthorizeText);
9808                     SettingStripMenuItem_Click(null, null);
9809                 }
9810
9811                 // 取得失敗の場合は再試行する
9812                 var reloadTasks = new List<Task>();
9813
9814                 if (!tw.GetFollowersSuccess && SettingManager.Common.StartupFollowers)
9815                     reloadTasks.Add(this.RefreshFollowerIdsAsync());
9816
9817                 if (!tw.GetNoRetweetSuccess)
9818                     reloadTasks.Add(this.RefreshNoRetweetIdsAsync());
9819
9820                 if (this.tw.Configuration.PhotoSizeLimit == 0)
9821                     reloadTasks.Add(this.RefreshTwitterConfigurationAsync());
9822
9823                 await Task.WhenAll(reloadTasks);
9824             }
9825
9826             _initial = false;
9827
9828             this.timelineScheduler.Enabled = true;
9829         }
9830
9831         private async Task doGetFollowersMenu()
9832         {
9833             await this.RefreshFollowerIdsAsync();
9834             this.DispSelectedPost(true);
9835         }
9836
9837         private async void GetFollowersAllToolStripMenuItem_Click(object sender, EventArgs e)
9838             => await this.doGetFollowersMenu();
9839
9840         private void ReTweetUnofficialStripMenuItem_Click(object sender, EventArgs e)
9841             => this.doReTweetUnofficial();
9842
9843         private async Task doReTweetOfficial(bool isConfirm)
9844         {
9845             //公式RT
9846             if (this.ExistCurrentPost)
9847             {
9848                 var selectedPosts = this.CurrentTab.SelectedPosts;
9849
9850                 if (selectedPosts.Any(x => !x.CanRetweetBy(this.twitterApi.CurrentUserId)))
9851                 {
9852                     if (selectedPosts.Any(x => x.IsProtect))
9853                         MessageBox.Show("Protected.");
9854
9855                     _DoFavRetweetFlags = false;
9856                     return;
9857                 }
9858
9859                 if (selectedPosts.Length > 15)
9860                 {
9861                     MessageBox.Show(Properties.Resources.RetweetLimitText);
9862                     _DoFavRetweetFlags = false;
9863                     return;
9864                 }
9865                 else if (selectedPosts.Length > 1)
9866                 {
9867                     var QuestionText = Properties.Resources.RetweetQuestion2;
9868                     if (_DoFavRetweetFlags) QuestionText = Properties.Resources.FavoriteRetweetQuestionText1;
9869                     switch (MessageBox.Show(QuestionText, "Retweet", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question))
9870                     {
9871                         case DialogResult.Cancel:
9872                         case DialogResult.No:
9873                             _DoFavRetweetFlags = false;
9874                             return;
9875                     }
9876                 }
9877                 else
9878                 {
9879                     if (!SettingManager.Common.RetweetNoConfirm)
9880                     {
9881                         var Questiontext = Properties.Resources.RetweetQuestion1;
9882                         if (_DoFavRetweetFlags) Questiontext = Properties.Resources.FavoritesRetweetQuestionText2;
9883                         if (isConfirm && MessageBox.Show(Questiontext, "Retweet", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
9884                         {
9885                             _DoFavRetweetFlags = false;
9886                             return;
9887                         }
9888                     }
9889                 }
9890
9891                 var statusIds = selectedPosts.Select(x => x.StatusId).ToList();
9892
9893                 await this.RetweetAsync(statusIds);
9894             }
9895         }
9896
9897         private async void ReTweetStripMenuItem_Click(object sender, EventArgs e)
9898             => await this.doReTweetOfficial(true);
9899
9900         private async Task FavoritesRetweetOfficial()
9901         {
9902             if (!this.ExistCurrentPost) return;
9903             _DoFavRetweetFlags = true;
9904             var retweetTask = this.doReTweetOfficial(true);
9905             if (_DoFavRetweetFlags)
9906             {
9907                 _DoFavRetweetFlags = false;
9908                 var favoriteTask = this.FavoriteChange(true, false);
9909
9910                 await Task.WhenAll(retweetTask, favoriteTask);
9911             }
9912             else
9913             {
9914                 await retweetTask;
9915             }
9916         }
9917
9918         private async Task FavoritesRetweetUnofficial()
9919         {
9920             var post = this.CurrentPost;
9921             if (this.ExistCurrentPost && !post.IsDm)
9922             {
9923                 _DoFavRetweetFlags = true;
9924                 var favoriteTask = this.FavoriteChange(true);
9925                 if (!post.IsProtect && _DoFavRetweetFlags)
9926                 {
9927                     _DoFavRetweetFlags = false;
9928                     doReTweetUnofficial();
9929                 }
9930
9931                 await favoriteTask;
9932             }
9933         }
9934
9935         /// <summary>
9936         /// TweetFormatterクラスによって整形された状態のHTMLを、非公式RT用に元のツイートに復元します
9937         /// </summary>
9938         /// <param name="statusHtml">TweetFormatterによって整形された状態のHTML</param>
9939         /// <param name="multiline">trueであればBRタグを改行に、falseであればスペースに変換します</param>
9940         /// <returns>復元されたツイート本文</returns>
9941         internal static string CreateRetweetUnofficial(string statusHtml, bool multiline)
9942         {
9943             // TweetFormatterクラスによって整形された状態のHTMLを元のツイートに復元します
9944
9945             // 通常の URL
9946             statusHtml = Regex.Replace(statusHtml, "<a href=\"(?<href>.+?)\" title=\"(?<title>.+?)\">(?<text>.+?)</a>", "${title}");
9947             // メンション
9948             statusHtml = Regex.Replace(statusHtml, "<a class=\"mention\" href=\"(?<href>.+?)\">(?<text>.+?)</a>", "${text}");
9949             // ハッシュタグ
9950             statusHtml = Regex.Replace(statusHtml, "<a class=\"hashtag\" href=\"(?<href>.+?)\">(?<text>.+?)</a>", "${text}");
9951             // 絵文字
9952             statusHtml = Regex.Replace(statusHtml, "<img class=\"emoji\" src=\".+?\" alt=\"(?<text>.+?)\" />", "${text}");
9953
9954             // <br> 除去
9955             if (multiline)
9956                 statusHtml = statusHtml.Replace("<br>", Environment.NewLine);
9957             else
9958                 statusHtml = statusHtml.Replace("<br>", " ");
9959
9960             // &nbsp; は本来であれば U+00A0 (NON-BREAK SPACE) に置換すべきですが、
9961             // 現状では半角スペースの代用として &nbsp; を使用しているため U+0020 に置換します
9962             statusHtml = statusHtml.Replace("&nbsp;", " ");
9963
9964             return WebUtility.HtmlDecode(statusHtml);
9965         }
9966
9967         private void DumpPostClassToolStripMenuItem_Click(object sender, EventArgs e)
9968         {
9969             this.tweetDetailsView.DumpPostClass = this.DumpPostClassToolStripMenuItem.Checked;
9970
9971             if (this.CurrentPost != null)
9972                 this.DispSelectedPost(true);
9973         }
9974
9975         private void MenuItemHelp_DropDownOpening(object sender, EventArgs e)
9976         {
9977             if (MyCommon.DebugBuild || MyCommon.IsKeyDown(Keys.CapsLock, Keys.Control, Keys.Shift))
9978                 DebugModeToolStripMenuItem.Visible = true;
9979             else
9980                 DebugModeToolStripMenuItem.Visible = false;
9981         }
9982
9983         private void UrlMultibyteSplitMenuItem_CheckedChanged(object sender, EventArgs e)
9984             => this.urlMultibyteSplit = ((ToolStripMenuItem)sender).Checked;
9985
9986         private void PreventSmsCommandMenuItem_CheckedChanged(object sender, EventArgs e)
9987             => this.preventSmsCommand = ((ToolStripMenuItem)sender).Checked;
9988
9989         private void UrlAutoShortenMenuItem_CheckedChanged(object sender, EventArgs e)
9990             => SettingManager.Common.UrlConvertAuto = ((ToolStripMenuItem)sender).Checked;
9991
9992         private void IdeographicSpaceToSpaceMenuItem_Click(object sender, EventArgs e)
9993         {
9994             SettingManager.Common.WideSpaceConvert = ((ToolStripMenuItem)sender).Checked;
9995             this.MarkSettingCommonModified();
9996         }
9997
9998         private void FocusLockMenuItem_CheckedChanged(object sender, EventArgs e)
9999         {
10000             SettingManager.Common.FocusLockToStatusText = ((ToolStripMenuItem)sender).Checked;
10001             this.MarkSettingCommonModified();
10002         }
10003
10004         private void PostModeMenuItem_DropDownOpening(object sender, EventArgs e)
10005         {
10006             UrlMultibyteSplitMenuItem.Checked = this.urlMultibyteSplit;
10007             PreventSmsCommandMenuItem.Checked = this.preventSmsCommand;
10008             UrlAutoShortenMenuItem.Checked = SettingManager.Common.UrlConvertAuto;
10009             IdeographicSpaceToSpaceMenuItem.Checked = SettingManager.Common.WideSpaceConvert;
10010             MultiLineMenuItem.Checked = SettingManager.Local.StatusMultiline;
10011             FocusLockMenuItem.Checked = SettingManager.Common.FocusLockToStatusText;
10012         }
10013
10014         private void ContextMenuPostMode_Opening(object sender, CancelEventArgs e)
10015         {
10016             UrlMultibyteSplitPullDownMenuItem.Checked = this.urlMultibyteSplit;
10017             PreventSmsCommandPullDownMenuItem.Checked = this.preventSmsCommand;
10018             UrlAutoShortenPullDownMenuItem.Checked = SettingManager.Common.UrlConvertAuto;
10019             IdeographicSpaceToSpacePullDownMenuItem.Checked = SettingManager.Common.WideSpaceConvert;
10020             MultiLinePullDownMenuItem.Checked = SettingManager.Local.StatusMultiline;
10021             FocusLockPullDownMenuItem.Checked = SettingManager.Common.FocusLockToStatusText;
10022         }
10023
10024         private void TraceOutToolStripMenuItem_Click(object sender, EventArgs e)
10025         {
10026             if (TraceOutToolStripMenuItem.Checked)
10027                 MyCommon.TraceFlag = true;
10028             else
10029                 MyCommon.TraceFlag = false;
10030         }
10031
10032         private void TweenMain_Deactivate(object sender, EventArgs e)
10033             => this.StatusText_Leave(StatusText, EventArgs.Empty); // 画面が非アクティブになったら、発言欄の背景色をデフォルトへ
10034
10035         private void TabRenameMenuItem_Click(object sender, EventArgs e)
10036         {
10037             if (string.IsNullOrEmpty(_rclickTabName)) return;
10038
10039             _ = TabRename(_rclickTabName, out _);
10040         }
10041
10042         private async void BitlyToolStripMenuItem_Click(object sender, EventArgs e)
10043             => await this.UrlConvertAsync(MyCommon.UrlConverter.Bitly);
10044
10045         private async void JmpToolStripMenuItem_Click(object sender, EventArgs e)
10046             => await this.UrlConvertAsync(MyCommon.UrlConverter.Jmp);
10047
10048         private async void ApiUsageInfoMenuItem_Click(object sender, EventArgs e)
10049         {
10050             TwitterApiStatus apiStatus;
10051
10052             using (var dialog = new WaitingDialog(Properties.Resources.ApiInfo6))
10053             {
10054                 var cancellationToken = dialog.EnableCancellation();
10055
10056                 try
10057                 {
10058                     var task = this.tw.GetInfoApi();
10059                     apiStatus = await dialog.WaitForAsync(this, task);
10060                 }
10061                 catch (WebApiException)
10062                 {
10063                     apiStatus = null;
10064                 }
10065
10066                 if (cancellationToken.IsCancellationRequested)
10067                     return;
10068
10069                 if (apiStatus == null)
10070                 {
10071                     MessageBox.Show(Properties.Resources.ApiInfo5, Properties.Resources.ApiInfo4, MessageBoxButtons.OK, MessageBoxIcon.Information);
10072                     return;
10073                 }
10074             }
10075
10076             using (var apiDlg = new ApiInfoDialog())
10077             {
10078                 apiDlg.ShowDialog(this);
10079             }
10080         }
10081
10082         private async void FollowCommandMenuItem_Click(object sender, EventArgs e)
10083         {
10084             var id = this.CurrentPost?.ScreenName ?? "";
10085
10086             await this.FollowCommand(id);
10087         }
10088
10089         internal async Task FollowCommand(string id)
10090         {
10091             using (var inputName = new InputTabName())
10092             {
10093                 inputName.FormTitle = "Follow";
10094                 inputName.FormDescription = Properties.Resources.FRMessage1;
10095                 inputName.TabName = id;
10096
10097                 if (inputName.ShowDialog(this) != DialogResult.OK)
10098                     return;
10099                 if (string.IsNullOrWhiteSpace(inputName.TabName))
10100                     return;
10101
10102                 id = inputName.TabName.Trim();
10103             }
10104
10105             using (var dialog = new WaitingDialog(Properties.Resources.FollowCommandText1))
10106             {
10107                 try
10108                 {
10109                     var task = this.twitterApi.FriendshipsCreate(id).IgnoreResponse();
10110                     await dialog.WaitForAsync(this, task);
10111                 }
10112                 catch (WebApiException ex)
10113                 {
10114                     MessageBox.Show(Properties.Resources.FRMessage2 + ex.Message);
10115                     return;
10116                 }
10117             }
10118
10119             MessageBox.Show(Properties.Resources.FRMessage3);
10120         }
10121
10122         private async void RemoveCommandMenuItem_Click(object sender, EventArgs e)
10123         {
10124             var id = this.CurrentPost?.ScreenName ?? "";
10125
10126             await this.RemoveCommand(id, false);
10127         }
10128
10129         internal async Task RemoveCommand(string id, bool skipInput)
10130         {
10131             if (!skipInput)
10132             {
10133                 using (var inputName = new InputTabName())
10134                 {
10135                     inputName.FormTitle = "Unfollow";
10136                     inputName.FormDescription = Properties.Resources.FRMessage1;
10137                     inputName.TabName = id;
10138
10139                     if (inputName.ShowDialog(this) != DialogResult.OK)
10140                         return;
10141                     if (string.IsNullOrWhiteSpace(inputName.TabName))
10142                         return;
10143
10144                     id = inputName.TabName.Trim();
10145                 }
10146             }
10147
10148             using (var dialog = new WaitingDialog(Properties.Resources.RemoveCommandText1))
10149             {
10150                 try
10151                 {
10152                     var task = this.twitterApi.FriendshipsDestroy(id).IgnoreResponse();
10153                     await dialog.WaitForAsync(this, task);
10154                 }
10155                 catch (WebApiException ex)
10156                 {
10157                     MessageBox.Show(Properties.Resources.FRMessage2 + ex.Message);
10158                     return;
10159                 }
10160             }
10161
10162             MessageBox.Show(Properties.Resources.FRMessage3);
10163         }
10164
10165         private async void FriendshipMenuItem_Click(object sender, EventArgs e)
10166         {
10167             var id = this.CurrentPost?.ScreenName ?? "";
10168
10169             await this.ShowFriendship(id);
10170         }
10171
10172         internal async Task ShowFriendship(string id)
10173         {
10174             using (var inputName = new InputTabName())
10175             {
10176                 inputName.FormTitle = "Show Friendships";
10177                 inputName.FormDescription = Properties.Resources.FRMessage1;
10178                 inputName.TabName = id;
10179
10180                 if (inputName.ShowDialog(this) != DialogResult.OK)
10181                     return;
10182                 if (string.IsNullOrWhiteSpace(inputName.TabName))
10183                     return;
10184
10185                 id = inputName.TabName.Trim();
10186             }
10187
10188             bool isFollowing, isFollowed;
10189
10190             using (var dialog = new WaitingDialog(Properties.Resources.ShowFriendshipText1))
10191             {
10192                 var cancellationToken = dialog.EnableCancellation();
10193
10194                 try
10195                 {
10196                     var task = this.twitterApi.FriendshipsShow(this.twitterApi.CurrentScreenName, id);
10197                     var friendship = await dialog.WaitForAsync(this, task);
10198
10199                     isFollowing = friendship.Relationship.Source.Following;
10200                     isFollowed = friendship.Relationship.Source.FollowedBy;
10201                 }
10202                 catch (WebApiException ex)
10203                 {
10204                     if (!cancellationToken.IsCancellationRequested)
10205                         MessageBox.Show($"Err:{ex.Message}(FriendshipsShow)");
10206                     return;
10207                 }
10208
10209                 if (cancellationToken.IsCancellationRequested)
10210                     return;
10211             }
10212
10213             string result;
10214             if (isFollowing)
10215             {
10216                 result = Properties.Resources.GetFriendshipInfo1 + System.Environment.NewLine;
10217             }
10218             else
10219             {
10220                 result = Properties.Resources.GetFriendshipInfo2 + System.Environment.NewLine;
10221             }
10222             if (isFollowed)
10223             {
10224                 result += Properties.Resources.GetFriendshipInfo3;
10225             }
10226             else
10227             {
10228                 result += Properties.Resources.GetFriendshipInfo4;
10229             }
10230             result = id + Properties.Resources.GetFriendshipInfo5 + System.Environment.NewLine + result;
10231             MessageBox.Show(result);
10232         }
10233
10234         internal async Task ShowFriendship(string[] ids)
10235         {
10236             foreach (var id in ids)
10237             {
10238                 bool isFollowing, isFollowed;
10239
10240                 using (var dialog = new WaitingDialog(Properties.Resources.ShowFriendshipText1))
10241                 {
10242                     var cancellationToken = dialog.EnableCancellation();
10243
10244                     try
10245                     {
10246                         var task = this.twitterApi.FriendshipsShow(this.twitterApi.CurrentScreenName, id);
10247                         var friendship = await dialog.WaitForAsync(this, task);
10248
10249                         isFollowing = friendship.Relationship.Source.Following;
10250                         isFollowed = friendship.Relationship.Source.FollowedBy;
10251                     }
10252                     catch (WebApiException ex)
10253                     {
10254                         if (!cancellationToken.IsCancellationRequested)
10255                             MessageBox.Show($"Err:{ex.Message}(FriendshipsShow)");
10256                         return;
10257                     }
10258
10259                     if (cancellationToken.IsCancellationRequested)
10260                         return;
10261                 }
10262
10263                 var result = "";
10264                 var ff = "";
10265
10266                 ff = "  ";
10267                 if (isFollowing)
10268                 {
10269                     ff += Properties.Resources.GetFriendshipInfo1;
10270                 }
10271                 else
10272                 {
10273                     ff += Properties.Resources.GetFriendshipInfo2;
10274                 }
10275
10276                 ff += System.Environment.NewLine + "  ";
10277                 if (isFollowed)
10278                 {
10279                     ff += Properties.Resources.GetFriendshipInfo3;
10280                 }
10281                 else
10282                 {
10283                     ff += Properties.Resources.GetFriendshipInfo4;
10284                 }
10285                 result += id + Properties.Resources.GetFriendshipInfo5 + System.Environment.NewLine + ff;
10286                 if (isFollowing)
10287                 {
10288                     if (MessageBox.Show(
10289                         Properties.Resources.GetFriendshipInfo7 + System.Environment.NewLine + result, Properties.Resources.GetFriendshipInfo8,
10290                         MessageBoxButtons.YesNo,
10291                         MessageBoxIcon.Question,
10292                         MessageBoxDefaultButton.Button2) == DialogResult.Yes)
10293                     {
10294                         await this.RemoveCommand(id, true);
10295                     }
10296                 }
10297                 else
10298                 {
10299                     MessageBox.Show(result);
10300                 }
10301             }
10302         }
10303
10304         private async void OwnStatusMenuItem_Click(object sender, EventArgs e)
10305             => await this.doShowUserStatus(tw.Username, false);
10306
10307         // TwitterIDでない固定文字列を調べる(文字列検証のみ 実際に取得はしない)
10308         // URLから切り出した文字列を渡す
10309
10310         public bool IsTwitterId(string name)
10311         {
10312             if (this.tw.Configuration.NonUsernamePaths == null || this.tw.Configuration.NonUsernamePaths.Length == 0)
10313                 return !Regex.Match(name, @"^(about|jobs|tos|privacy|who_to_follow|download|messages)$", RegexOptions.IgnoreCase).Success;
10314             else
10315                 return !this.tw.Configuration.NonUsernamePaths.Contains(name, StringComparer.InvariantCultureIgnoreCase);
10316         }
10317
10318         private void doQuoteOfficial()
10319         {
10320             if (this.ExistCurrentPost)
10321             {
10322                 var post = this.CurrentPost;
10323                 if (post.IsDm || !StatusText.Enabled)
10324                     return;
10325
10326                 if (post.IsProtect)
10327                 {
10328                     MessageBox.Show("Protected.");
10329                     return;
10330                 }
10331
10332                 var selection = (this.StatusText.SelectionStart, this.StatusText.SelectionLength);
10333
10334                 this.inReplyTo = null;
10335
10336                 StatusText.Text += " " + MyCommon.GetStatusUrl(post);
10337
10338                 (this.StatusText.SelectionStart, this.StatusText.SelectionLength) = selection;
10339                 StatusText.Focus();
10340             }
10341         }
10342
10343         private void doReTweetUnofficial()
10344         {
10345             //RT @id:内容
10346             if (this.ExistCurrentPost)
10347             {
10348                 var post = this.CurrentPost;
10349                 if (post.IsDm || !StatusText.Enabled)
10350                     return;
10351
10352                 if (post.IsProtect)
10353                 {
10354                     MessageBox.Show("Protected.");
10355                     return;
10356                 }
10357                 var rtdata = post.Text;
10358                 rtdata = CreateRetweetUnofficial(rtdata, this.StatusText.Multiline);
10359
10360                 var selection = (this.StatusText.SelectionStart, this.StatusText.SelectionLength);
10361
10362                 // 投稿時に in_reply_to_status_id を付加する
10363                 var inReplyToStatusId = post.RetweetedId ?? post.StatusId;
10364                 var inReplyToScreenName = post.ScreenName;
10365                 this.inReplyTo = (inReplyToStatusId, inReplyToScreenName);
10366
10367                 StatusText.Text += " RT @" + post.ScreenName + ": " + rtdata;
10368
10369                 (this.StatusText.SelectionStart, this.StatusText.SelectionLength) = selection;
10370                 StatusText.Focus();
10371             }
10372         }
10373
10374         private void QuoteStripMenuItem_Click(object sender, EventArgs e)
10375             => this.doQuoteOfficial();
10376
10377         private async void SearchButton_Click(object sender, EventArgs e)
10378         {
10379             //公式検索
10380             var pnl = ((Control)sender).Parent;
10381             if (pnl == null) return;
10382             var tbName = pnl.Parent.Text;
10383             var tb = (PublicSearchTabModel)_statuses.Tabs[tbName];
10384             var cmb = (ComboBox)pnl.Controls["comboSearch"];
10385             var cmbLang = (ComboBox)pnl.Controls["comboLang"];
10386             cmb.Text = cmb.Text.Trim();
10387             // 検索式演算子 OR についてのみ大文字しか認識しないので強制的に大文字とする
10388             var Quote = false;
10389             var buf = new StringBuilder();
10390             var c = cmb.Text.ToCharArray();
10391             for (var cnt = 0; cnt < cmb.Text.Length; cnt++)
10392             {
10393                 if (cnt > cmb.Text.Length - 4)
10394                 {
10395                     buf.Append(cmb.Text.Substring(cnt));
10396                     break;
10397                 }
10398                 if (c[cnt] == '"')
10399                 {
10400                     Quote = !Quote;
10401                 }
10402                 else
10403                 {
10404                     if (!Quote && cmb.Text.Substring(cnt, 4).Equals(" or ", StringComparison.OrdinalIgnoreCase))
10405                     {
10406                         buf.Append(" OR ");
10407                         cnt += 3;
10408                         continue;
10409                     }
10410                 }
10411                 buf.Append(c[cnt]);
10412             }
10413             cmb.Text = buf.ToString();
10414
10415             var listView = (DetailsListView)pnl.Parent.Tag;
10416
10417             var queryChanged = tb.SearchWords != cmb.Text || tb.SearchLang != cmbLang.Text;
10418
10419             tb.SearchWords = cmb.Text;
10420             tb.SearchLang = cmbLang.Text;
10421             if (string.IsNullOrEmpty(cmb.Text))
10422             {
10423                 listView.Focus();
10424                 SaveConfigsTabs();
10425                 return;
10426             }
10427             if (queryChanged)
10428             {
10429                 var idx = cmb.Items.IndexOf(tb.SearchWords);
10430                 if (idx > -1) cmb.Items.RemoveAt(idx);
10431                 cmb.Items.Insert(0, tb.SearchWords);
10432                 cmb.Text = tb.SearchWords;
10433                 cmb.SelectAll();
10434                 this.PurgeListViewItemCache();
10435                 listView.VirtualListSize = 0;
10436                 _statuses.ClearTabIds(tbName);
10437                 SaveConfigsTabs();   //検索条件の保存
10438             }
10439
10440             listView.Focus();
10441             await this.RefreshTabAsync(tb);
10442         }
10443
10444         private async void RefreshMoreStripMenuItem_Click(object sender, EventArgs e)
10445             => await this.DoRefreshMore(); // もっと前を取得
10446
10447         /// <summary>
10448         /// 指定されたタブのListTabにおける位置を返します
10449         /// </summary>
10450         /// <remarks>
10451         /// 非表示のタブについて -1 が返ることを常に考慮して下さい
10452         /// </remarks>
10453         public int GetTabPageIndex(string tabName)
10454             => this._statuses.Tabs.IndexOf(tabName);
10455
10456         private void UndoRemoveTabMenuItem_Click(object sender, EventArgs e)
10457         {
10458             if (_statuses.RemovedTab.Count == 0)
10459             {
10460                 MessageBox.Show("There isn't removed tab.", "Undo", MessageBoxButtons.OK, MessageBoxIcon.Information);
10461                 return;
10462             }
10463             else
10464             {
10465                 var tb = _statuses.RemovedTab.Pop();
10466                 DetailsListView listView;
10467                 if (tb.TabType == MyCommon.TabUsageType.Related)
10468                 {
10469                     var relatedTab = _statuses.GetTabByType(MyCommon.TabUsageType.Related);
10470                     if (relatedTab != null)
10471                     {
10472                         // 関連発言なら既存のタブを置き換える
10473                         tb.TabName = relatedTab.TabName;
10474                         this.ClearTab(tb.TabName, false);
10475
10476                         this._statuses.ReplaceTab(tb);
10477
10478                         var tabIndex = this._statuses.Tabs.IndexOf(tb);
10479                         var tabPage = this.ListTab.TabPages[tabIndex];
10480                         listView = (DetailsListView)tabPage.Tag;
10481                         this.ListTab.SelectedIndex = tabIndex;
10482                     }
10483                     else
10484                     {
10485                         const string TabName = "Related Tweets";
10486                         var renamed = TabName;
10487                         for (var i = 2; i <= 100; i++)
10488                         {
10489                             if (!_statuses.ContainsTab(renamed))
10490                                 break;
10491                             renamed = TabName + i;
10492                         }
10493                         tb.TabName = renamed;
10494
10495                         _statuses.AddTab(tb);
10496                         AddNewTab(tb, startup: false);
10497
10498                         var tabIndex = this._statuses.Tabs.Count - 1;
10499                         var tabPage = this.ListTab.TabPages[tabIndex];
10500
10501                         listView = (DetailsListView)tabPage.Tag;
10502                         this.ListTab.SelectedIndex = tabIndex;
10503                     }
10504                 }
10505                 else
10506                 {
10507                     var renamed = tb.TabName;
10508                     for (var i = 1; i < int.MaxValue; i++)
10509                     {
10510                         if (!_statuses.ContainsTab(renamed))
10511                             break;
10512                         renamed = tb.TabName + "(" + i + ")";
10513                     }
10514                     tb.TabName = renamed;
10515
10516                     _statuses.AddTab(tb);
10517                     AddNewTab(tb, startup: false);
10518
10519                     var tabIndex = this._statuses.Tabs.Count - 1;
10520                     var tabPage = this.ListTab.TabPages[tabIndex];
10521
10522                     listView = (DetailsListView)tabPage.Tag;
10523                     this.ListTab.SelectedIndex = tabIndex;
10524                 }
10525                 SaveConfigsTabs();
10526
10527                 if (listView != null)
10528                 {
10529                     using (ControlTransaction.Update(listView))
10530                     {
10531                         listView.VirtualListSize = tb.AllCount;
10532                     }
10533                 }
10534             }
10535         }
10536
10537         private async Task doMoveToRTHome()
10538         {
10539             var post = this.CurrentPost;
10540             if (post != null && post.RetweetedId != null)
10541                 await this.OpenUriInBrowserAsync("https://twitter.com/" + post.RetweetedBy);
10542         }
10543
10544         private async void MoveToRTHomeMenuItem_Click(object sender, EventArgs e)
10545             => await this.doMoveToRTHome();
10546
10547         private void ListManageUserContextToolStripMenuItem_Click(object sender, EventArgs e)
10548         {
10549             var screenName = this.CurrentPost?.ScreenName;
10550             if (screenName != null)
10551                 this.ListManageUserContext(screenName);
10552         }
10553
10554         public void ListManageUserContext(string screenName)
10555         {
10556             using (var listSelectForm = new MyLists(screenName, this.twitterApi))
10557             {
10558                 listSelectForm.ShowDialog(this);
10559             }
10560         }
10561
10562         private void SearchControls_Enter(object sender, EventArgs e)
10563         {
10564             var pnl = (Control)sender;
10565             foreach (Control ctl in pnl.Controls)
10566             {
10567                 ctl.TabStop = true;
10568             }
10569         }
10570
10571         private void SearchControls_Leave(object sender, EventArgs e)
10572         {
10573             var pnl = (Control)sender;
10574             foreach (Control ctl in pnl.Controls)
10575             {
10576                 ctl.TabStop = false;
10577             }
10578         }
10579
10580         private void PublicSearchQueryMenuItem_Click(object sender, EventArgs e)
10581         {
10582             var tab = this.CurrentTab;
10583             if (tab.TabType != MyCommon.TabUsageType.PublicSearch) return;
10584             this.CurrentTabPage.Controls["panelSearch"].Controls["comboSearch"].Focus();
10585         }
10586
10587         private void StatusLabel_DoubleClick(object sender, EventArgs e)
10588             => MessageBox.Show(StatusLabel.TextHistory, "Logs", MessageBoxButtons.OK, MessageBoxIcon.None);
10589
10590         private void HashManageMenuItem_Click(object sender, EventArgs e)
10591         {
10592             DialogResult rslt;
10593             try
10594             {
10595                 rslt = HashMgr.ShowDialog();
10596             }
10597             catch (Exception)
10598             {
10599                 return;
10600             }
10601             this.TopMost = SettingManager.Common.AlwaysTop;
10602             if (rslt == DialogResult.Cancel) return;
10603             if (!string.IsNullOrEmpty(HashMgr.UseHash))
10604             {
10605                 HashStripSplitButton.Text = HashMgr.UseHash;
10606                 HashTogglePullDownMenuItem.Checked = true;
10607                 HashToggleMenuItem.Checked = true;
10608             }
10609             else
10610             {
10611                 HashStripSplitButton.Text = "#[-]";
10612                 HashTogglePullDownMenuItem.Checked = false;
10613                 HashToggleMenuItem.Checked = false;
10614             }
10615             //if (HashMgr.IsInsert && HashMgr.UseHash != "")
10616             //{
10617             //    int sidx = StatusText.SelectionStart;
10618             //    string hash = HashMgr.UseHash + " ";
10619             //    if (sidx > 0)
10620             //    {
10621             //        if (StatusText.Text.Substring(sidx - 1, 1) != " ")
10622             //            hash = " " + hash;
10623             //    }
10624             //    StatusText.Text = StatusText.Text.Insert(sidx, hash);
10625             //    sidx += hash.Length;
10626             //    StatusText.SelectionStart = sidx;
10627             //    StatusText.Focus();
10628             //}
10629             this.MarkSettingCommonModified();
10630             this.StatusText_TextChanged(null, null);
10631         }
10632
10633         private void HashToggleMenuItem_Click(object sender, EventArgs e)
10634         {
10635             HashMgr.ToggleHash();
10636             if (!string.IsNullOrEmpty(HashMgr.UseHash))
10637             {
10638                 HashStripSplitButton.Text = HashMgr.UseHash;
10639                 HashToggleMenuItem.Checked = true;
10640                 HashTogglePullDownMenuItem.Checked = true;
10641             }
10642             else
10643             {
10644                 HashStripSplitButton.Text = "#[-]";
10645                 HashToggleMenuItem.Checked = false;
10646                 HashTogglePullDownMenuItem.Checked = false;
10647             }
10648             this.MarkSettingCommonModified();
10649             this.StatusText_TextChanged(null, null);
10650         }
10651
10652         private void HashStripSplitButton_ButtonClick(object sender, EventArgs e)
10653             => this.HashToggleMenuItem_Click(null, null);
10654
10655         public void SetPermanentHashtag(string hashtag)
10656         {
10657             HashMgr.SetPermanentHash("#" + hashtag);
10658             HashStripSplitButton.Text = HashMgr.UseHash;
10659             HashTogglePullDownMenuItem.Checked = true;
10660             HashToggleMenuItem.Checked = true;
10661             //使用ハッシュタグとして設定
10662             this.MarkSettingCommonModified();
10663         }
10664
10665         private void MenuItemOperate_DropDownOpening(object sender, EventArgs e)
10666         {
10667             if (!this.ExistCurrentPost)
10668             {
10669                 this.ReplyOpMenuItem.Enabled = false;
10670                 this.ReplyAllOpMenuItem.Enabled = false;
10671                 this.DmOpMenuItem.Enabled = false;
10672                 this.ShowProfMenuItem.Enabled = false;
10673                 this.ShowUserTimelineToolStripMenuItem.Enabled = false;
10674                 this.ListManageMenuItem.Enabled = false;
10675                 this.OpenFavOpMenuItem.Enabled = false;
10676                 this.CreateTabRuleOpMenuItem.Enabled = false;
10677                 this.CreateIdRuleOpMenuItem.Enabled = false;
10678                 this.CreateSourceRuleOpMenuItem.Enabled = false;
10679                 this.ReadOpMenuItem.Enabled = false;
10680                 this.UnreadOpMenuItem.Enabled = false;
10681             }
10682             else
10683             {
10684                 this.ReplyOpMenuItem.Enabled = true;
10685                 this.ReplyAllOpMenuItem.Enabled = true;
10686                 this.DmOpMenuItem.Enabled = true;
10687                 this.ShowProfMenuItem.Enabled = true;
10688                 this.ShowUserTimelineToolStripMenuItem.Enabled = true;
10689                 this.ListManageMenuItem.Enabled = true;
10690                 this.OpenFavOpMenuItem.Enabled = true;
10691                 this.CreateTabRuleOpMenuItem.Enabled = true;
10692                 this.CreateIdRuleOpMenuItem.Enabled = true;
10693                 this.CreateSourceRuleOpMenuItem.Enabled = true;
10694                 this.ReadOpMenuItem.Enabled = true;
10695                 this.UnreadOpMenuItem.Enabled = true;
10696             }
10697
10698             var tab = this.CurrentTab;
10699             var post = this.CurrentPost;
10700             if (tab.TabType == MyCommon.TabUsageType.DirectMessage || !this.ExistCurrentPost || post.IsDm)
10701             {
10702                 this.FavOpMenuItem.Enabled = false;
10703                 this.UnFavOpMenuItem.Enabled = false;
10704                 this.OpenStatusOpMenuItem.Enabled = false;
10705                 this.ShowRelatedStatusesMenuItem2.Enabled = false;
10706                 this.RtOpMenuItem.Enabled = false;
10707                 this.RtUnOpMenuItem.Enabled = false;
10708                 this.QtOpMenuItem.Enabled = false;
10709                 this.FavoriteRetweetMenuItem.Enabled = false;
10710                 this.FavoriteRetweetUnofficialMenuItem.Enabled = false;
10711             }
10712             else
10713             {
10714                 this.FavOpMenuItem.Enabled = true;
10715                 this.UnFavOpMenuItem.Enabled = true;
10716                 this.OpenStatusOpMenuItem.Enabled = true;
10717                 this.ShowRelatedStatusesMenuItem2.Enabled = true;  //PublicSearchの時問題出るかも
10718
10719                 if (!post.CanRetweetBy(this.twitterApi.CurrentUserId))
10720                 {
10721                     this.RtOpMenuItem.Enabled = false;
10722                     this.RtUnOpMenuItem.Enabled = false;
10723                     this.QtOpMenuItem.Enabled = false;
10724                     this.FavoriteRetweetMenuItem.Enabled = false;
10725                     this.FavoriteRetweetUnofficialMenuItem.Enabled = false;
10726                 }
10727                 else
10728                 {
10729                     this.RtOpMenuItem.Enabled = true;
10730                     this.RtUnOpMenuItem.Enabled = true;
10731                     this.QtOpMenuItem.Enabled = true;
10732                     this.FavoriteRetweetMenuItem.Enabled = true;
10733                     this.FavoriteRetweetUnofficialMenuItem.Enabled = true;
10734                 }
10735             }
10736
10737             if (tab.TabType != MyCommon.TabUsageType.Favorites)
10738             {
10739                 this.RefreshPrevOpMenuItem.Enabled = true;
10740             }
10741             else
10742             {
10743                 this.RefreshPrevOpMenuItem.Enabled = false;
10744             }
10745             if (!this.ExistCurrentPost || post.InReplyToStatusId == null)
10746             {
10747                 OpenRepSourceOpMenuItem.Enabled = false;
10748             }
10749             else
10750             {
10751                 OpenRepSourceOpMenuItem.Enabled = true;
10752             }
10753             if (!this.ExistCurrentPost || string.IsNullOrEmpty(post.RetweetedBy))
10754             {
10755                 OpenRterHomeMenuItem.Enabled = false;
10756             }
10757             else
10758             {
10759                 OpenRterHomeMenuItem.Enabled = true;
10760             }
10761
10762             if (this.ExistCurrentPost)
10763             {
10764                 this.DelOpMenuItem.Enabled = post.CanDeleteBy(this.tw.UserId);
10765             }
10766         }
10767
10768         private void MenuItemTab_DropDownOpening(object sender, EventArgs e)
10769             => this.ContextMenuTabProperty_Opening(sender, null);
10770
10771         public Twitter TwitterInstance
10772             => this.tw;
10773
10774         private void SplitContainer3_SplitterMoved(object sender, SplitterEventArgs e)
10775         {
10776             if (this._initialLayout)
10777                 return;
10778
10779             int splitterDistance;
10780             switch (this.WindowState)
10781             {
10782                 case FormWindowState.Normal:
10783                     splitterDistance = this.SplitContainer3.SplitterDistance;
10784                     break;
10785                 case FormWindowState.Maximized:
10786                     // 最大化時は、通常時のウィンドウサイズに換算した SplitterDistance を算出する
10787                     var normalContainerWidth = this._mySize.Width - SystemInformation.Border3DSize.Width * 2;
10788                     splitterDistance = this.SplitContainer3.SplitterDistance - (this.SplitContainer3.Width - normalContainerWidth);
10789                     splitterDistance = Math.Min(splitterDistance, normalContainerWidth - this.SplitContainer3.SplitterWidth - this.SplitContainer3.Panel2MinSize);
10790                     break;
10791                 default:
10792                     return;
10793             }
10794
10795             this._mySpDis3 = splitterDistance;
10796             this.MarkSettingLocalModified();
10797         }
10798
10799         private void MenuItemEdit_DropDownOpening(object sender, EventArgs e)
10800         {
10801             if (_statuses.RemovedTab.Count == 0)
10802             {
10803                 UndoRemoveTabMenuItem.Enabled = false;
10804             }
10805             else
10806             {
10807                 UndoRemoveTabMenuItem.Enabled = true;
10808             }
10809
10810             if (this.CurrentTab.TabType == MyCommon.TabUsageType.PublicSearch)
10811                 PublicSearchQueryMenuItem.Enabled = true;
10812             else
10813                 PublicSearchQueryMenuItem.Enabled = false;
10814
10815             if (!this.ExistCurrentPost)
10816             {
10817                 this.CopySTOTMenuItem.Enabled = false;
10818                 this.CopyURLMenuItem.Enabled = false;
10819                 this.CopyUserIdStripMenuItem.Enabled = false;
10820             }
10821             else
10822             {
10823                 this.CopySTOTMenuItem.Enabled = true;
10824                 this.CopyURLMenuItem.Enabled = true;
10825                 this.CopyUserIdStripMenuItem.Enabled = true;
10826
10827                 var post = this.CurrentPost;
10828                 if (post.IsDm) this.CopyURLMenuItem.Enabled = false;
10829                 if (post.IsProtect) this.CopySTOTMenuItem.Enabled = false;
10830             }
10831         }
10832
10833         private void NotifyIcon1_MouseMove(object sender, MouseEventArgs e)
10834             => this.SetNotifyIconText();
10835
10836         private async void UserStatusToolStripMenuItem_Click(object sender, EventArgs e)
10837             => await this.ShowUserStatus(this.CurrentPost?.ScreenName ?? "");
10838
10839         private async Task doShowUserStatus(string id, bool ShowInputDialog)
10840         {
10841             TwitterUser user = null;
10842
10843             if (ShowInputDialog)
10844             {
10845                 using (var inputName = new InputTabName())
10846                 {
10847                     inputName.FormTitle = "Show UserStatus";
10848                     inputName.FormDescription = Properties.Resources.FRMessage1;
10849                     inputName.TabName = id;
10850
10851                     if (inputName.ShowDialog(this) != DialogResult.OK)
10852                         return;
10853                     if (string.IsNullOrWhiteSpace(inputName.TabName))
10854                         return;
10855
10856                     id = inputName.TabName.Trim();
10857                 }
10858             }
10859
10860             using (var dialog = new WaitingDialog(Properties.Resources.doShowUserStatusText1))
10861             {
10862                 var cancellationToken = dialog.EnableCancellation();
10863
10864                 try
10865                 {
10866                     var task = this.twitterApi.UsersShow(id);
10867                     user = await dialog.WaitForAsync(this, task);
10868                 }
10869                 catch (WebApiException ex)
10870                 {
10871                     if (!cancellationToken.IsCancellationRequested)
10872                         MessageBox.Show($"Err:{ex.Message}(UsersShow)");
10873                     return;
10874                 }
10875
10876                 if (cancellationToken.IsCancellationRequested)
10877                     return;
10878             }
10879
10880             await this.doShowUserStatus(user);
10881         }
10882
10883         private async Task doShowUserStatus(TwitterUser user)
10884         {
10885             using (var userDialog = new UserInfoDialog(this, this.twitterApi))
10886             {
10887                 var showUserTask = userDialog.ShowUserAsync(user);
10888                 userDialog.ShowDialog(this);
10889
10890                 this.Activate();
10891                 this.BringToFront();
10892
10893                 // ユーザー情報の表示が完了するまで userDialog を破棄しない
10894                 await showUserTask;
10895             }
10896         }
10897
10898         internal Task ShowUserStatus(string id, bool ShowInputDialog)
10899             => this.doShowUserStatus(id, ShowInputDialog);
10900
10901         internal Task ShowUserStatus(string id)
10902             => this.doShowUserStatus(id, true);
10903
10904         private async void ShowProfileMenuItem_Click(object sender, EventArgs e)
10905         {
10906             var post = this.CurrentPost;
10907             if (post != null)
10908             {
10909                 await this.ShowUserStatus(post.ScreenName, false);
10910             }
10911         }
10912
10913         private async void RtCountMenuItem_Click(object sender, EventArgs e)
10914         {
10915             if (!this.ExistCurrentPost)
10916                 return;
10917
10918             var post = this.CurrentPost;
10919             var statusId = post.RetweetedId ?? post.StatusId;
10920             TwitterStatus status;
10921
10922             using (var dialog = new WaitingDialog(Properties.Resources.RtCountMenuItem_ClickText1))
10923             {
10924                 var cancellationToken = dialog.EnableCancellation();
10925
10926                 try
10927                 {
10928                     var task = this.twitterApi.StatusesShow(statusId);
10929                     status = await dialog.WaitForAsync(this, task);
10930                 }
10931                 catch (WebApiException ex)
10932                 {
10933                     if (!cancellationToken.IsCancellationRequested)
10934                         MessageBox.Show(Properties.Resources.RtCountText2 + Environment.NewLine + "Err:" + ex.Message);
10935                     return;
10936                 }
10937
10938                 if (cancellationToken.IsCancellationRequested)
10939                     return;
10940             }
10941
10942             MessageBox.Show(status.RetweetCount + Properties.Resources.RtCountText1);
10943         }
10944
10945         private readonly HookGlobalHotkey _hookGlobalHotkey;
10946         public TweenMain()
10947         {
10948             _hookGlobalHotkey = new HookGlobalHotkey(this);
10949
10950             // この呼び出しは、Windows フォーム デザイナで必要です。
10951             InitializeComponent();
10952
10953             // InitializeComponent() 呼び出しの後で初期化を追加します。
10954
10955             if (!this.DesignMode)
10956             {
10957                 // デザイナでの編集時にレイアウトが縦方向に数pxずれる問題の対策
10958                 this.StatusText.Dock = DockStyle.Fill;
10959             }
10960
10961             this.tweetDetailsView.Owner = this;
10962
10963             this._hookGlobalHotkey.HotkeyPressed += _hookGlobalHotkey_HotkeyPressed;
10964             this.gh.NotifyClicked += GrowlHelper_Callback;
10965
10966             // メイリオフォント指定時にタブの最小幅が広くなる問題の対策
10967             this.ListTab.HandleCreated += (s, e) => NativeMethods.SetMinTabWidth((TabControl)s, 40);
10968
10969             this.ImageSelector.Visible = false;
10970             this.ImageSelector.Enabled = false;
10971             this.ImageSelector.FilePickDialog = OpenFileDialog1;
10972
10973             this.workerProgress = new Progress<string>(x => this.StatusLabel.Text = x);
10974
10975             this.ReplaceAppName();
10976             this.InitializeShortcuts();
10977         }
10978
10979         private void _hookGlobalHotkey_HotkeyPressed(object sender, KeyEventArgs e)
10980         {
10981             if ((this.WindowState == FormWindowState.Normal || this.WindowState == FormWindowState.Maximized) && this.Visible && Form.ActiveForm == this)
10982             {
10983                 //アイコン化
10984                 this.Visible = false;
10985             }
10986             else if (Form.ActiveForm == null)
10987             {
10988                 this.Visible = true;
10989                 if (this.WindowState == FormWindowState.Minimized) this.WindowState = FormWindowState.Normal;
10990                 this.Activate();
10991                 this.BringToFront();
10992                 this.StatusText.Focus();
10993             }
10994         }
10995
10996         private void SplitContainer2_MouseDoubleClick(object sender, MouseEventArgs e)
10997             => this.MultiLinePullDownMenuItem.PerformClick();
10998
10999 #region "画像投稿"
11000         private void ImageSelectMenuItem_Click(object sender, EventArgs e)
11001         {
11002             if (ImageSelector.Visible)
11003                 ImageSelector.EndSelection();
11004             else
11005                 ImageSelector.BeginSelection();
11006         }
11007
11008         private void SelectMedia_DragEnter(DragEventArgs e)
11009         {
11010             if (ImageSelector.HasUploadableService(((string[])e.Data.GetData(DataFormats.FileDrop, false))[0], true))
11011             {
11012                 e.Effect = DragDropEffects.Copy;
11013                 return;
11014             }
11015             e.Effect = DragDropEffects.None;
11016         }
11017
11018         private void SelectMedia_DragDrop(DragEventArgs e)
11019         {
11020             this.Activate();
11021             this.BringToFront();
11022             ImageSelector.BeginSelection((string[])e.Data.GetData(DataFormats.FileDrop, false));
11023             StatusText.Focus();
11024         }
11025
11026         private void ImageSelector_BeginSelecting(object sender, EventArgs e)
11027         {
11028             TimelinePanel.Visible = false;
11029             TimelinePanel.Enabled = false;
11030         }
11031
11032         private void ImageSelector_EndSelecting(object sender, EventArgs e)
11033         {
11034             TimelinePanel.Visible = true;
11035             TimelinePanel.Enabled = true;
11036             this.CurrentListView.Focus();
11037         }
11038
11039         private void ImageSelector_FilePickDialogOpening(object sender, EventArgs e)
11040             => this.AllowDrop = false;
11041
11042         private void ImageSelector_FilePickDialogClosed(object sender, EventArgs e)
11043             => this.AllowDrop = true;
11044
11045         private void ImageSelector_SelectedServiceChanged(object sender, EventArgs e)
11046         {
11047             if (ImageSelector.Visible)
11048             {
11049                 this.MarkSettingCommonModified();
11050                 this.StatusText_TextChanged(null, null);
11051             }
11052         }
11053
11054         private void ImageSelector_VisibleChanged(object sender, EventArgs e)
11055             => this.StatusText_TextChanged(null, null);
11056
11057         /// <summary>
11058         /// StatusTextでCtrl+Vが押下された時の処理
11059         /// </summary>
11060         private void ProcClipboardFromStatusTextWhenCtrlPlusV()
11061         {
11062             try
11063             {
11064                 if (Clipboard.ContainsText())
11065                 {
11066                     // clipboardにテキストがある場合は貼り付け処理
11067                     this.StatusText.Paste(Clipboard.GetText());
11068                 }
11069                 else if (Clipboard.ContainsImage())
11070                 {
11071                     // 画像があるので投稿処理を行う
11072                     if (MessageBox.Show(Properties.Resources.PostPictureConfirm3,
11073                                        Properties.Resources.PostPictureWarn4,
11074                                        MessageBoxButtons.OKCancel,
11075                                        MessageBoxIcon.Question,
11076                                        MessageBoxDefaultButton.Button2)
11077                                    == DialogResult.OK)
11078                     {
11079                         // clipboardから画像を取得
11080                         using (var image = Clipboard.GetImage())
11081                         {
11082                             this.ImageSelector.BeginSelection(image);
11083                         }
11084                     }
11085                 }
11086             }
11087             catch (ExternalException ex)
11088             {
11089                 MessageBox.Show(ex.Message);
11090             }
11091         }
11092 #endregion
11093
11094         private void ListManageToolStripMenuItem_Click(object sender, EventArgs e)
11095         {
11096             using (var form = new ListManage(tw))
11097             {
11098                 form.ShowDialog(this);
11099             }
11100         }
11101
11102         private bool ModifySettingCommon { get; set; }
11103         private bool ModifySettingLocal { get; set; }
11104         private bool ModifySettingAtId { get; set; }
11105
11106         private void MenuItemCommand_DropDownOpening(object sender, EventArgs e)
11107         {
11108             var post = this.CurrentPost;
11109             if (this.ExistCurrentPost && !post.IsDm)
11110                 RtCountMenuItem.Enabled = true;
11111             else
11112                 RtCountMenuItem.Enabled = false;
11113
11114             //if (SettingDialog.UrlConvertAuto && SettingDialog.ShortenTco)
11115             //    TinyUrlConvertToolStripMenuItem.Enabled = false;
11116             //else
11117             //    TinyUrlConvertToolStripMenuItem.Enabled = true;
11118         }
11119
11120         private void CopyUserIdStripMenuItem_Click(object sender, EventArgs e)
11121             => this.CopyUserId();
11122
11123         private void CopyUserId()
11124         {
11125             var post = this.CurrentPost;
11126             if (post == null) return;
11127             var clstr = post.ScreenName;
11128             try
11129             {
11130                 Clipboard.SetDataObject(clstr, false, 5, 100);
11131             }
11132             catch (Exception ex)
11133             {
11134                 MessageBox.Show(ex.Message);
11135             }
11136         }
11137
11138         private async void ShowRelatedStatusesMenuItem_Click(object sender, EventArgs e)
11139         {
11140             var post = this.CurrentPost;
11141             if (this.ExistCurrentPost && !post.IsDm)
11142             {
11143                 try
11144                 {
11145                     await this.OpenRelatedTab(post);
11146                 }
11147                 catch (TabException ex)
11148                 {
11149                     MessageBox.Show(this, ex.Message, ApplicationSettings.ApplicationName, MessageBoxButtons.OK, MessageBoxIcon.Error);
11150                 }
11151             }
11152         }
11153
11154         /// <summary>
11155         /// 指定されたツイートに対する関連発言タブを開きます
11156         /// </summary>
11157         /// <param name="statusId">表示するツイートのID</param>
11158         /// <exception cref="TabException">名前の重複が多すぎてタブを作成できない場合</exception>
11159         public async Task OpenRelatedTab(long statusId)
11160         {
11161             var post = this._statuses[statusId];
11162             if (post == null)
11163             {
11164                 try
11165                 {
11166                     post = await this.tw.GetStatusApi(false, statusId);
11167                 }
11168                 catch (WebApiException ex)
11169                 {
11170                     this.StatusLabel.Text = $"Err:{ex.Message}(GetStatus)";
11171                     return;
11172                 }
11173             }
11174
11175             await this.OpenRelatedTab(post);
11176         }
11177
11178         /// <summary>
11179         /// 指定されたツイートに対する関連発言タブを開きます
11180         /// </summary>
11181         /// <param name="post">表示する対象となるツイート</param>
11182         /// <exception cref="TabException">名前の重複が多すぎてタブを作成できない場合</exception>
11183         private async Task OpenRelatedTab(PostClass post)
11184         {
11185             var tabRelated = this._statuses.GetTabByType<RelatedPostsTabModel>();
11186             if (tabRelated != null)
11187             {
11188                 this.RemoveSpecifiedTab(tabRelated.TabName, confirm: false);
11189             }
11190
11191             var tabName = this._statuses.MakeTabName("Related Tweets");
11192
11193             tabRelated = new RelatedPostsTabModel(tabName, post)
11194             {
11195                 UnreadManage = false,
11196                 Notify = false,
11197             };
11198
11199             this._statuses.AddTab(tabRelated);
11200             this.AddNewTab(tabRelated, startup: false);
11201
11202             this.ListTab.SelectedIndex = this._statuses.Tabs.IndexOf(tabName);
11203
11204             await this.RefreshTabAsync(tabRelated);
11205
11206             var tabIndex = this._statuses.Tabs.IndexOf(tabRelated.TabName);
11207
11208             if (tabIndex != -1)
11209             {
11210                 // TODO: 非同期更新中にタブが閉じられている場合を厳密に考慮したい
11211
11212                 var tabPage = this.ListTab.TabPages[tabIndex];
11213                 var listView = (DetailsListView)tabPage.Tag;
11214                 var targetPost = tabRelated.TargetPost;
11215                 var index = tabRelated.IndexOf(targetPost.RetweetedId ?? targetPost.StatusId);
11216
11217                 if (index != -1 && index < listView.Items.Count)
11218                 {
11219                     listView.SelectedIndices.Add(index);
11220                     listView.Items[index].Focused = true;
11221                 }
11222             }
11223         }
11224
11225         private void CacheInfoMenuItem_Click(object sender, EventArgs e)
11226         {
11227             var buf = new StringBuilder();
11228             //buf.AppendFormat("キャッシュメモリ容量         : {0}bytes({1}MB)" + Environment.NewLine, IconCache.CacheMemoryLimit, ((ImageDictionary)IconCache).CacheMemoryLimit / 1048576);
11229             //buf.AppendFormat("物理メモリ使用割合           : {0}%" + Environment.NewLine, IconCache.PhysicalMemoryLimit);
11230             buf.AppendFormat("キャッシュエントリ保持数     : {0}" + Environment.NewLine, IconCache.CacheCount);
11231             buf.AppendFormat("キャッシュエントリ破棄数     : {0}" + Environment.NewLine, IconCache.CacheRemoveCount);
11232             MessageBox.Show(buf.ToString(), "アイコンキャッシュ使用状況");
11233         }
11234
11235 #region "Userstream"
11236         private async void tw_PostDeleted(object sender, PostDeletedEventArgs e)
11237         {
11238             try
11239             {
11240                 if (InvokeRequired && !IsDisposed)
11241                 {
11242                     await this.InvokeAsync(() =>
11243                     {
11244                         this._statuses.RemovePostFromAllTabs(e.StatusId, setIsDeleted: true);
11245                         if (this.CurrentTab.Contains(e.StatusId))
11246                         {
11247                             this.PurgeListViewItemCache();
11248                             this.CurrentListView.Update();
11249                             var post = this.CurrentPost;
11250                             if (post != null && post.StatusId == e.StatusId)
11251                                 this.DispSelectedPost(true);
11252                         }
11253                     });
11254                     return;
11255                 }
11256             }
11257             catch (ObjectDisposedException)
11258             {
11259                 return;
11260             }
11261             catch (InvalidOperationException)
11262             {
11263                 return;
11264             }
11265         }
11266
11267         private void tw_NewPostFromStream(object sender, EventArgs e)
11268         {
11269             if (SettingManager.Common.ReadOldPosts)
11270             {
11271                 _statuses.SetReadHomeTab(); //新着時未読クリア
11272             }
11273
11274             this._statuses.DistributePosts();
11275
11276             this.RefreshThrottlingTimer.Call();
11277         }
11278
11279         private async void tw_UserStreamStarted(object sender, EventArgs e)
11280         {
11281             try
11282             {
11283                 if (InvokeRequired && !IsDisposed)
11284                 {
11285                     await this.InvokeAsync(() => this.tw_UserStreamStarted(sender, e));
11286                     return;
11287                 }
11288             }
11289             catch (ObjectDisposedException)
11290             {
11291                 return;
11292             }
11293             catch (InvalidOperationException)
11294             {
11295                 return;
11296             }
11297
11298             this.RefreshUserStreamsMenu();
11299             this.MenuItemUserStream.Enabled = true;
11300
11301             StatusLabel.Text = "UserStream Started.";
11302         }
11303
11304         private async void tw_UserStreamStopped(object sender, EventArgs e)
11305         {
11306             try
11307             {
11308                 if (InvokeRequired && !IsDisposed)
11309                 {
11310                     await this.InvokeAsync(() => this.tw_UserStreamStopped(sender, e));
11311                     return;
11312                 }
11313             }
11314             catch (ObjectDisposedException)
11315             {
11316                 return;
11317             }
11318             catch (InvalidOperationException)
11319             {
11320                 return;
11321             }
11322
11323             this.RefreshUserStreamsMenu();
11324             this.MenuItemUserStream.Enabled = true;
11325
11326             StatusLabel.Text = "UserStream Stopped.";
11327         }
11328
11329         private void RefreshUserStreamsMenu()
11330         {
11331             if (this.tw.UserStreamActive)
11332             {
11333                 this.MenuItemUserStream.Text = "&UserStream ▶";
11334                 this.StopToolStripMenuItem.Text = "&Stop";
11335             }
11336             else
11337             {
11338                 this.MenuItemUserStream.Text = "&UserStream ■";
11339                 this.StopToolStripMenuItem.Text = "&Start";
11340             }
11341         }
11342
11343         private async void tw_UserStreamEventArrived(object sender, UserStreamEventReceivedEventArgs e)
11344         {
11345             try
11346             {
11347                 if (InvokeRequired && !IsDisposed)
11348                 {
11349                     await this.InvokeAsync(() => this.tw_UserStreamEventArrived(sender, e));
11350                     return;
11351                 }
11352             }
11353             catch (ObjectDisposedException)
11354             {
11355                 return;
11356             }
11357             catch (InvalidOperationException)
11358             {
11359                 return;
11360             }
11361             var ev = e.EventData;
11362             StatusLabel.Text = "Event: " + ev.Event;
11363             //if (ev.Event == "favorite")
11364             //{
11365             //    NotifyFavorite(ev);
11366             //}
11367             NotifyEvent(ev);
11368             if (ev.Event == "favorite" || ev.Event == "unfavorite")
11369             {
11370                 if (this.CurrentTab.Contains(ev.Id))
11371                 {
11372                     this.PurgeListViewItemCache();
11373                     this.CurrentListView.Update();
11374                 }
11375                 if (ev.Event == "unfavorite" && ev.Username.Equals(tw.Username, StringComparison.InvariantCultureIgnoreCase))
11376                 {
11377                     var favTab = this._statuses.GetTabByType(MyCommon.TabUsageType.Favorites);
11378                     favTab.EnqueueRemovePost(ev.Id, setIsDeleted: false);
11379                 }
11380             }
11381         }
11382
11383         private void NotifyEvent(Twitter.FormattedEvent ev)
11384         {
11385             //新着通知 
11386             if (BalloonRequired(ev))
11387             {
11388                 NotifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
11389                 //if (SettingDialog.DispUsername) NotifyIcon1.BalloonTipTitle = tw.Username + " - "; else NotifyIcon1.BalloonTipTitle = "";
11390                 //NotifyIcon1.BalloonTipTitle += Application.ProductName + " [" + ev.Event.ToUpper() + "] by " + ((string)(!string.IsNullOrEmpty(ev.Username) ? ev.Username : ""), string);
11391                 var title = new StringBuilder();
11392                 if (SettingManager.Common.DispUsername)
11393                 {
11394                     title.Append(tw.Username);
11395                     title.Append(" - ");
11396                 }
11397                 else
11398                 {
11399                     //title.Clear();
11400                 }
11401                 title.Append(ApplicationSettings.ApplicationName);
11402                 title.Append(" [");
11403                 title.Append(ev.Event.ToUpper(CultureInfo.CurrentCulture));
11404                 title.Append("] by ");
11405                 if (!string.IsNullOrEmpty(ev.Username))
11406                 {
11407                     title.Append(ev.Username);
11408                 }
11409                 else
11410                 {
11411                     //title.Append("");
11412                 }
11413                 string text;
11414                 if (!string.IsNullOrEmpty(ev.Target))
11415                 {
11416                     //NotifyIcon1.BalloonTipText = ev.Target;
11417                     text = ev.Target;
11418                 }
11419                 else
11420                 {
11421                     //NotifyIcon1.BalloonTipText = " ";
11422                     text = " ";
11423                 }
11424                 //NotifyIcon1.ShowBalloonTip(500);
11425                 if (SettingManager.Common.IsUseNotifyGrowl)
11426                 {
11427                     gh.Notify(GrowlHelper.NotifyType.UserStreamEvent,
11428                               ev.Id.ToString(), title.ToString(), text);
11429                 }
11430                 else
11431                 {
11432                     NotifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
11433                     NotifyIcon1.BalloonTipTitle = title.ToString();
11434                     NotifyIcon1.BalloonTipText = text;
11435                     NotifyIcon1.ShowBalloonTip(500);
11436                 }
11437             }
11438
11439             //サウンド再生
11440             var snd = SettingManager.Common.EventSoundFile;
11441             if (!_initial && SettingManager.Common.PlaySound && !string.IsNullOrEmpty(snd))
11442             {
11443                 if ((ev.Eventtype & SettingManager.Common.EventNotifyFlag) != 0 && IsMyEventNotityAsEventType(ev))
11444                 {
11445                     try
11446                     {
11447                         var dir = Application.StartupPath;
11448                         if (Directory.Exists(Path.Combine(dir, "Sounds")))
11449                         {
11450                             dir = Path.Combine(dir, "Sounds");
11451                         }
11452                         using (var player = new SoundPlayer(Path.Combine(dir, snd)))
11453                         {
11454                             player.Play();
11455                         }
11456                     }
11457                     catch (Exception)
11458                     {
11459                     }
11460                 }
11461             }
11462         }
11463
11464         private void StopToolStripMenuItem_Click(object sender, EventArgs e)
11465         {
11466             MenuItemUserStream.Enabled = false;
11467             if (StopRefreshAllMenuItem.Checked)
11468             {
11469                 StopRefreshAllMenuItem.Checked = false;
11470                 return;
11471             }
11472             if (this.tw.UserStreamActive)
11473             {
11474                 tw.StopUserStream();
11475             }
11476             else
11477             {
11478                 tw.StartUserStream();
11479             }
11480         }
11481
11482         private static string inputTrack = "";
11483
11484         private void TrackToolStripMenuItem_Click(object sender, EventArgs e)
11485         {
11486             if (TrackToolStripMenuItem.Checked)
11487             {
11488                 using (var inputForm = new InputTabName())
11489                 {
11490                     inputForm.TabName = inputTrack;
11491                     inputForm.FormTitle = "Input track word";
11492                     inputForm.FormDescription = "Track word";
11493                     if (inputForm.ShowDialog() != DialogResult.OK)
11494                     {
11495                         TrackToolStripMenuItem.Checked = false;
11496                         return;
11497                     }
11498                     inputTrack = inputForm.TabName.Trim();
11499                 }
11500                 if (!inputTrack.Equals(tw.TrackWord))
11501                 {
11502                     tw.TrackWord = inputTrack;
11503                     this.MarkSettingCommonModified();
11504                     TrackToolStripMenuItem.Checked = !string.IsNullOrEmpty(inputTrack);
11505                     tw.ReconnectUserStream();
11506                 }
11507             }
11508             else
11509             {
11510                 tw.TrackWord = "";
11511                 tw.ReconnectUserStream();
11512             }
11513             this.MarkSettingCommonModified();
11514         }
11515
11516         private void AllrepliesToolStripMenuItem_Click(object sender, EventArgs e)
11517         {
11518             tw.AllAtReply = AllrepliesToolStripMenuItem.Checked;
11519             this.MarkSettingCommonModified();
11520             tw.ReconnectUserStream();
11521         }
11522
11523         private void EventViewerMenuItem_Click(object sender, EventArgs e)
11524         {
11525             if (evtDialog == null || evtDialog.IsDisposed)
11526             {
11527                 this.evtDialog = new EventViewerDialog
11528                 {
11529                     Owner = this,
11530                 };
11531
11532                 //親の中央に表示
11533                 this.evtDialog.Location = new Point
11534                 {
11535                     X = Convert.ToInt32(this.Location.X + this.Size.Width / 2 - evtDialog.Size.Width / 2),
11536                     Y = Convert.ToInt32(this.Location.Y + this.Size.Height / 2 - evtDialog.Size.Height / 2),
11537                 };
11538             }
11539             evtDialog.EventSource = tw.StoredEvent;
11540             if (!evtDialog.Visible)
11541             {
11542                 evtDialog.Show(this);
11543             }
11544             else
11545             {
11546                 evtDialog.Activate();
11547             }
11548             this.TopMost = SettingManager.Common.AlwaysTop;
11549         }
11550 #endregion
11551
11552         private void TweenRestartMenuItem_Click(object sender, EventArgs e)
11553         {
11554             MyCommon._endingFlag = true;
11555             try
11556             {
11557                 this.Close();
11558                 Application.Restart();
11559             }
11560             catch (Exception)
11561             {
11562                 MessageBox.Show("Failed to restart. Please run " + ApplicationSettings.ApplicationName + " manually.");
11563             }
11564         }
11565
11566         private async void OpenOwnHomeMenuItem_Click(object sender, EventArgs e)
11567             => await this.OpenUriInBrowserAsync(MyCommon.TwitterUrl + tw.Username);
11568
11569         private bool ExistCurrentPost
11570         {
11571             get
11572             {
11573                 var post = this.CurrentPost;
11574                 return post != null && !post.IsDeleted;
11575             }
11576         }
11577
11578         private async void ShowUserTimelineToolStripMenuItem_Click(object sender, EventArgs e)
11579             => await this.ShowUserTimeline();
11580
11581         private string GetUserIdFromCurPostOrInput(string caption)
11582         {
11583             var id = this.CurrentPost?.ScreenName ?? "";
11584
11585             using (var inputName = new InputTabName())
11586             {
11587                 inputName.FormTitle = caption;
11588                 inputName.FormDescription = Properties.Resources.FRMessage1;
11589                 inputName.TabName = id;
11590                 if (inputName.ShowDialog() == DialogResult.OK &&
11591                     !string.IsNullOrEmpty(inputName.TabName.Trim()))
11592                 {
11593                     id = inputName.TabName.Trim();
11594                 }
11595                 else
11596                 {
11597                     id = "";
11598                 }
11599             }
11600             return id;
11601         }
11602
11603         private async void UserTimelineToolStripMenuItem_Click(object sender, EventArgs e)
11604         {
11605             var id = GetUserIdFromCurPostOrInput("Show UserTimeline");
11606             if (!string.IsNullOrEmpty(id))
11607             {
11608                 await this.AddNewTabForUserTimeline(id);
11609             }
11610         }
11611
11612         private void SystemEvents_PowerModeChanged(object sender, Microsoft.Win32.PowerModeChangedEventArgs e)
11613         {
11614             if (e.Mode == Microsoft.Win32.PowerModes.Resume)
11615                 this.timelineScheduler.SystemResumed();
11616         }
11617
11618         private void SystemEvents_TimeChanged(object sender, EventArgs e)
11619         {
11620             var prevTimeOffset = TimeZoneInfo.Local.BaseUtcOffset;
11621
11622             TimeZoneInfo.ClearCachedData();
11623
11624             var curTimeOffset = TimeZoneInfo.Local.BaseUtcOffset;
11625
11626             if (curTimeOffset != prevTimeOffset)
11627             {
11628                 // タイムゾーンの変更を反映
11629                 this.PurgeListViewItemCache();
11630                 this.CurrentListView.Refresh();
11631
11632                 this.DispSelectedPost(forceupdate: true);
11633             }
11634         }
11635
11636         private void TimelineRefreshEnableChange(bool isEnable)
11637         {
11638             if (isEnable)
11639             {
11640                 tw.StartUserStream();
11641             }
11642             else
11643             {
11644                 tw.StopUserStream();
11645             }
11646             this.timelineScheduler.Enabled = isEnable;
11647         }
11648
11649         private void StopRefreshAllMenuItem_CheckedChanged(object sender, EventArgs e)
11650             => this.TimelineRefreshEnableChange(!StopRefreshAllMenuItem.Checked);
11651
11652         private async Task OpenUserAppointUrl()
11653         {
11654             if (SettingManager.Common.UserAppointUrl != null)
11655             {
11656                 if (SettingManager.Common.UserAppointUrl.Contains("{ID}") || SettingManager.Common.UserAppointUrl.Contains("{STATUS}"))
11657                 {
11658                     var post = this.CurrentPost;
11659                     if (post != null)
11660                     {
11661                         var xUrl = SettingManager.Common.UserAppointUrl;
11662                         xUrl = xUrl.Replace("{ID}", post.ScreenName);
11663
11664                         var statusId = post.RetweetedId ?? post.StatusId;
11665                         xUrl = xUrl.Replace("{STATUS}", statusId.ToString());
11666
11667                         await this.OpenUriInBrowserAsync(xUrl);
11668                     }
11669                 }
11670                 else
11671                 {
11672                     await this.OpenUriInBrowserAsync(SettingManager.Common.UserAppointUrl);
11673                 }
11674             }
11675         }
11676
11677         private async void OpenUserSpecifiedUrlMenuItem_Click(object sender, EventArgs e)
11678             => await this.OpenUserAppointUrl();
11679
11680         private async void GrowlHelper_Callback(object sender, GrowlHelper.NotifyCallbackEventArgs e)
11681         {
11682             if (Form.ActiveForm == null)
11683             {
11684                 await this.InvokeAsync(() =>
11685                 {
11686                     this.Visible = true;
11687                     if (this.WindowState == FormWindowState.Minimized) this.WindowState = FormWindowState.Normal;
11688                     this.Activate();
11689                     this.BringToFront();
11690                     if (e.NotifyType == GrowlHelper.NotifyType.DirectMessage)
11691                     {
11692                         if (!this.GoDirectMessage(e.StatusId)) this.StatusText.Focus();
11693                     }
11694                     else
11695                     {
11696                         if (!this.GoStatus(e.StatusId)) this.StatusText.Focus();
11697                     }
11698                 });
11699             }
11700         }
11701
11702         private void ReplaceAppName()
11703         {
11704             MatomeMenuItem.Text = MyCommon.ReplaceAppName(MatomeMenuItem.Text);
11705             AboutMenuItem.Text = MyCommon.ReplaceAppName(AboutMenuItem.Text);
11706         }
11707
11708         private void tweetThumbnail1_ThumbnailLoading(object sender, EventArgs e)
11709             => this.SplitContainer3.Panel2Collapsed = false;
11710
11711         private async void tweetThumbnail1_ThumbnailDoubleClick(object sender, ThumbnailDoubleClickEventArgs e)
11712             => await this.OpenThumbnailPicture(e.Thumbnail);
11713
11714         private async void tweetThumbnail1_ThumbnailImageSearchClick(object sender, ThumbnailImageSearchEventArgs e)
11715             => await this.OpenUriInBrowserAsync(e.ImageUrl);
11716
11717         private async Task OpenThumbnailPicture(ThumbnailInfo thumbnail)
11718         {
11719             var url = thumbnail.FullSizeImageUrl ?? thumbnail.MediaPageUrl;
11720
11721             await this.OpenUriInBrowserAsync(url);
11722         }
11723
11724         private async void TwitterApiStatusToolStripMenuItem_Click(object sender, EventArgs e)
11725             => await this.OpenUriInBrowserAsync(Twitter.ServiceAvailabilityStatusUrl);
11726
11727         private void PostButton_KeyDown(object sender, KeyEventArgs e)
11728         {
11729             if (e.KeyCode == Keys.Space)
11730             {
11731                 this.JumpUnreadMenuItem_Click(null, null);
11732
11733                 e.SuppressKeyPress = true;
11734             }
11735         }
11736
11737         private void ContextMenuColumnHeader_Opening(object sender, CancelEventArgs e)
11738         {
11739             this.IconSizeNoneToolStripMenuItem.Checked = SettingManager.Common.IconSize == MyCommon.IconSizes.IconNone;
11740             this.IconSize16ToolStripMenuItem.Checked = SettingManager.Common.IconSize == MyCommon.IconSizes.Icon16;
11741             this.IconSize24ToolStripMenuItem.Checked = SettingManager.Common.IconSize == MyCommon.IconSizes.Icon24;
11742             this.IconSize48ToolStripMenuItem.Checked = SettingManager.Common.IconSize == MyCommon.IconSizes.Icon48;
11743             this.IconSize48_2ToolStripMenuItem.Checked = SettingManager.Common.IconSize == MyCommon.IconSizes.Icon48_2;
11744
11745             this.LockListSortOrderToolStripMenuItem.Checked = SettingManager.Common.SortOrderLock;
11746         }
11747
11748         private void IconSizeNoneToolStripMenuItem_Click(object sender, EventArgs e)
11749             => this.ChangeListViewIconSize(MyCommon.IconSizes.IconNone);
11750
11751         private void IconSize16ToolStripMenuItem_Click(object sender, EventArgs e)
11752             => this.ChangeListViewIconSize(MyCommon.IconSizes.Icon16);
11753
11754         private void IconSize24ToolStripMenuItem_Click(object sender, EventArgs e)
11755             => this.ChangeListViewIconSize(MyCommon.IconSizes.Icon24);
11756
11757         private void IconSize48ToolStripMenuItem_Click(object sender, EventArgs e)
11758             => this.ChangeListViewIconSize(MyCommon.IconSizes.Icon48);
11759
11760         private void IconSize48_2ToolStripMenuItem_Click(object sender, EventArgs e)
11761             => this.ChangeListViewIconSize(MyCommon.IconSizes.Icon48_2);
11762
11763         private void ChangeListViewIconSize(MyCommon.IconSizes iconSize)
11764         {
11765             if (SettingManager.Common.IconSize == iconSize) return;
11766
11767             var oldIconCol = _iconCol;
11768
11769             SettingManager.Common.IconSize = iconSize;
11770             ApplyListViewIconSize(iconSize);
11771
11772             if (_iconCol != oldIconCol)
11773             {
11774                 foreach (TabPage tp in ListTab.TabPages)
11775                 {
11776                     ResetColumns((DetailsListView)tp.Tag);
11777                 }
11778             }
11779
11780             this.CurrentListView.Refresh();
11781             this.MarkSettingCommonModified();
11782         }
11783
11784         private void LockListSortToolStripMenuItem_Click(object sender, EventArgs e)
11785         {
11786             var state = this.LockListSortOrderToolStripMenuItem.Checked;
11787             if (SettingManager.Common.SortOrderLock == state) return;
11788
11789             SettingManager.Common.SortOrderLock = state;
11790             this.MarkSettingCommonModified();
11791         }
11792
11793         private void tweetDetailsView_StatusChanged(object sender, TweetDetailsViewStatusChengedEventArgs e)
11794         {
11795             if (!string.IsNullOrEmpty(e.StatusText))
11796             {
11797                 this.StatusLabelUrl.Text = e.StatusText;
11798             }
11799             else
11800             {
11801                 this.SetStatusLabelUrl();
11802             }
11803         }
11804     }
11805 }