OSDN Git Service

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