OSDN Git Service

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