OSDN Git Service

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