OSDN Git Service

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