OSDN Git Service

99ad4bddbd11c6d6cbaadb9406854e504ba97c37
[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.Generic;
33 using System.ComponentModel;
34 using System.Diagnostics;
35 using System.Drawing;
36 using System.IO;
37 using System.Linq;
38 using System.Media;
39 using System.Net;
40 using System.Net.Http;
41 using System.Reflection;
42 using System.Text;
43 using System.Text.RegularExpressions;
44 using System.Threading;
45 using System.Threading.Tasks;
46 using System.Windows.Forms;
47 using OpenTween.Api;
48 using OpenTween.Connection;
49 using OpenTween.OpenTweenCustomControl;
50 using OpenTween.Thumbnail;
51
52 namespace OpenTween
53 {
54     public partial class TweenMain : OTBaseForm
55     {
56         //各種設定
57         private Size _mySize;           //画面サイズ
58         private Point _myLoc;           //画面位置
59         private int _mySpDis;           //区切り位置
60         private int _mySpDis2;          //発言欄区切り位置
61         private int _mySpDis3;          //プレビュー区切り位置
62         private int _iconSz;            //アイコンサイズ(現在は16、24、48の3種類。将来直接数字指定可能とする 注:24x24の場合に26と指定しているのはMSゴシック系フォントのための仕様)
63         private bool _iconCol;          //1列表示の時true(48サイズのとき)
64
65         //雑多なフラグ類
66         private bool _initial;         //true:起動時処理中
67         private bool _initialLayout = true;
68         private bool _ignoreConfigSave;         //true:起動時処理中
69         private bool _tabDrag;           //タブドラッグ中フラグ(DoDragDropを実行するかの判定用)
70         private TabPage _beforeSelectedTab; //タブが削除されたときに前回選択されていたときのタブを選択する為に保持
71         private Point _tabMouseDownPoint;
72         private string _rclickTabName;      //右クリックしたタブの名前(Tabコントロール機能不足対応)
73         private readonly object _syncObject = new object();    //ロック用
74
75         private const string detailHtmlFormatHeaderMono = 
76             "<html><head><meta http-equiv=\"X-UA-Compatible\" content=\"IE=8\">"
77             + "<style type=\"text/css\"><!-- "
78             + "body, p, pre {margin: 0;} "
79             + "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%);} "
80             + "a:link, a:visited, a:active, a:hover {color:rgb(%LINK_COLOR%); } "
81             + "img.emoji {width: 1em; height: 1em; margin: 0 .05em 0 .1em; vertical-align: -0.1em; border: none;} "
82             + ".quote-tweet {border: 1px solid #ccc; margin: 1em; padding: 0.5em;} "
83             + ".quote-tweet-link {color: inherit !important; text-decoration: none;}"
84             + "--></style>"
85             + "</head><body><pre>";
86         private const string detailHtmlFormatFooterMono = "</pre></body></html>";
87         private const string detailHtmlFormatHeaderColor = 
88             "<html><head><meta http-equiv=\"X-UA-Compatible\" content=\"IE=8\">"
89             + "<style type=\"text/css\"><!-- "
90             + "body, p, pre {margin: 0;} "
91             + "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%);} "
92             + "a:link, a:visited, a:active, a:hover {color:rgb(%LINK_COLOR%); } "
93             + "img.emoji {width: 1em; height: 1em; margin: 0 .05em 0 .1em; vertical-align: -0.1em; border: none;} "
94             + ".quote-tweet {border: 1px solid #ccc; margin: 1em; padding: 0.5em;} "
95             + ".quote-tweet-link {color: inherit !important; text-decoration: none;}"
96             + "--></style>"
97             + "</head><body><p>";
98         private const string detailHtmlFormatFooterColor = "</p></body></html>";
99         private string detailHtmlFormatHeader;
100         private string detailHtmlFormatFooter;
101
102         private bool _myStatusError = false;
103         private bool _myStatusOnline = false;
104         private bool soundfileListup = false;
105         private FormWindowState _formWindowState = FormWindowState.Normal; // フォームの状態保存用 通知領域からアイコンをクリックして復帰した際に使用する
106
107         //設定ファイル関連
108         //private SettingToConfig _cfg; //旧
109         private SettingLocal _cfgLocal;
110         private SettingCommon _cfgCommon;
111         private bool _modifySettingLocal = false;
112         private bool _modifySettingCommon = false;
113         private bool _modifySettingAtId = false;
114
115         //twitter解析部
116         private Twitter tw = new Twitter();
117
118         //Growl呼び出し部
119         private GrowlHelper gh = new GrowlHelper(Application.ProductName);
120
121         //サブ画面インスタンス
122         private SearchWordDialog SearchDialog = new SearchWordDialog();     //検索画面インスタンス
123         private OpenURL UrlDialog = new OpenURL();
124         public AtIdSupplement AtIdSupl;     //@id補助
125         public AtIdSupplement HashSupl;    //Hashtag補助
126         public HashtagManage HashMgr;
127         private EventViewerDialog evtDialog;
128
129         //表示フォント、色、アイコン
130         private Font _fntUnread;            //未読用フォント
131         private Color _clUnread;            //未読用文字色
132         private Font _fntReaded;            //既読用フォント
133         private Color _clReaded;            //既読用文字色
134         private Color _clFav;               //Fav用文字色
135         private Color _clOWL;               //片思い用文字色
136         private Color _clRetweet;               //Retweet用文字色
137         private Color _clHighLight = Color.FromKnownColor(KnownColor.HighlightText);         //選択中の行用文字色
138         private Font _fntDetail;            //発言詳細部用フォント
139         private Color _clDetail;              //発言詳細部用色
140         private Color _clDetailLink;          //発言詳細部用リンク文字色
141         private Color _clDetailBackcolor;     //発言詳細部用背景色
142         private Color _clSelf;              //自分の発言用背景色
143         private Color _clAtSelf;            //自分宛返信用背景色
144         private Color _clTarget;            //選択発言者の他の発言用背景色
145         private Color _clAtTarget;          //選択発言中の返信先用背景色
146         private Color _clAtFromTarget;      //選択発言者への返信発言用背景色
147         private Color _clAtTo;              //選択発言の唯一@先
148         private Color _clListBackcolor;       //リスト部通常発言背景色
149         private Color _clInputBackcolor;      //入力欄背景色
150         private Color _clInputFont;           //入力欄文字色
151         private Font _fntInputFont;           //入力欄フォント
152         private ImageCache IconCache;        //アイコン画像リスト
153         private Icon NIconAt;               //At.ico             タスクトレイアイコン:通常時
154         private Icon NIconAtRed;            //AtRed.ico          タスクトレイアイコン:通信エラー時
155         private Icon NIconAtSmoke;          //AtSmoke.ico        タスクトレイアイコン:オフライン時
156         private Icon[] NIconRefresh = new Icon[4];       //Refresh.ico        タスクトレイアイコン:更新中(アニメーション用に4種類を保持するリスト)
157         private Icon TabIcon;               //Tab.ico            未読のあるタブ用アイコン
158         private Icon MainIcon;              //Main.ico           画面左上のアイコン
159         private Icon ReplyIcon;               //5g
160         private Icon ReplyIconBlink;          //6g
161
162         private ImageList _listViewImageList = new ImageList();    //ListViewItemの高さ変更用
163
164         private PostClass _anchorPost;
165         private bool _anchorFlag;        //true:関連発言移動中(関連移動以外のオペレーションをするとfalseへ。trueだとリスト背景色をアンカー発言選択中として描画)
166
167         private List<PostingStatus> _history = new List<PostingStatus>();   //発言履歴
168         private int _hisIdx;                  //発言履歴カレントインデックス
169
170         //発言投稿時のAPI引数(発言編集時に設定。手書きreplyでは設定されない)
171         private Tuple<long, string> inReplyTo = null; // リプライ先のステータスID・スクリーン名
172
173         //時速表示用
174         private List<DateTime> _postTimestamps = new List<DateTime>();
175         private List<DateTime> _favTimestamps = new List<DateTime>();
176         private Dictionary<DateTime, int> _tlTimestamps = new Dictionary<DateTime, int>();
177         private int _tlCount;
178
179         // 以下DrawItem関連
180         private SolidBrush _brsHighLight = new SolidBrush(Color.FromKnownColor(KnownColor.Highlight));
181         private SolidBrush _brsBackColorMine;
182         private SolidBrush _brsBackColorAt;
183         private SolidBrush _brsBackColorYou;
184         private SolidBrush _brsBackColorAtYou;
185         private SolidBrush _brsBackColorAtFromTarget;
186         private SolidBrush _brsBackColorAtTo;
187         private SolidBrush _brsBackColorNone;
188         private SolidBrush _brsDeactiveSelection = new SolidBrush(Color.FromKnownColor(KnownColor.ButtonFace)); //Listにフォーカスないときの選択行の背景色
189         private StringFormat sfTab = new StringFormat();
190
191         //////////////////////////////////////////////////////////////////////////////////////////////////////////
192         private TabInformations _statuses;
193
194         // ListViewItem のキャッシュ関連
195         private int _itemCacheIndex;
196         private ListViewItem[] _itemCache;
197         private PostClass[] _postCache;
198         private ReaderWriterLockSlim itemCacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
199
200         private TabPage _curTab;
201         private int _curItemIndex;
202         private DetailsListView _curList;
203         private PostClass _curPost;
204         private bool _isColumnChanged = false;
205
206         private const int MAX_WORKER_THREADS = 20;
207         private SemaphoreSlim workerSemaphore = new SemaphoreSlim(MAX_WORKER_THREADS);
208         private CancellationTokenSource workerCts = new CancellationTokenSource();
209
210         private int UnreadCounter = -1;
211         private int UnreadAtCounter = -1;
212
213         private string[] ColumnOrgText = new string[9];
214         private string[] ColumnText = new string[9];
215
216         private bool _DoFavRetweetFlags = false;
217         private bool osResumed = false;
218
219         //////////////////////////////////////////////////////////////////////////////////////////////////////////
220         private string _postBrowserStatusText = "";
221
222         private bool _colorize = false;
223
224         private System.Timers.Timer TimerTimeline = new System.Timers.Timer();
225
226         private ImageListViewItem displayItem;
227
228         private string recommendedStatusFooter;
229
230         //URL短縮のUndo用
231         private struct urlUndo
232         {
233             public string Before;
234             public string After;
235         }
236
237         private List<urlUndo> urlUndoBuffer = null;
238
239         private struct ReplyChain
240         {
241             public long OriginalId;
242             public long InReplyToId;
243             public TabPage OriginalTab;
244
245             public ReplyChain(long originalId, long inReplyToId, TabPage originalTab)
246             {
247                 this.OriginalId = originalId;
248                 this.InReplyToId = inReplyToId;
249                 this.OriginalTab = originalTab;
250             }
251         }
252
253         private Stack<ReplyChain> replyChains; //[, ]でのリプライ移動の履歴
254         private Stack<Tuple<TabPage, PostClass>> selectPostChains = new Stack<Tuple<TabPage, PostClass>>(); //ポスト選択履歴
255
256         //検索処理タイプ
257         private enum SEARCHTYPE
258         {
259             DialogSearch,
260             NextSearch,
261             PrevSearch,
262         }
263
264         private class PostingStatus
265         {
266             public string status = "";
267             public long? inReplyToId = null;
268             public string inReplyToName = null;
269             public string imageService = "";      //画像投稿サービス名
270             public IMediaItem[] mediaItems = null;
271             public PostingStatus()
272             {
273             }
274             public PostingStatus(string status, long? replyToId, string replyToName)
275             {
276                 this.status = status;
277                 this.inReplyToId = replyToId;
278                 this.inReplyToName = replyToName;
279             }
280         }
281
282         private void TweenMain_Activated(object sender, EventArgs e)
283         {
284             //画面がアクティブになったら、発言欄の背景色戻す
285             if (StatusText.Focused)
286             {
287                 this.StatusText_Enter(this.StatusText, System.EventArgs.Empty);
288             }
289         }
290
291         private bool disposed = false;
292
293         /// <summary>
294         /// 使用中のリソースをすべてクリーンアップします。
295         /// </summary>
296         /// <param name="disposing">マネージ リソースが破棄される場合 true、破棄されない場合は false です。</param>
297         protected override void Dispose(bool disposing)
298         {
299             base.Dispose(disposing);
300
301             if (this.disposed)
302                 return;
303
304             if (disposing)
305             {
306                 this.components?.Dispose();
307
308                 //後始末
309                 SearchDialog.Dispose();
310                 UrlDialog.Dispose();
311                 NIconAt?.Dispose();
312                 NIconAtRed?.Dispose();
313                 NIconAtSmoke?.Dispose();
314                 foreach (var iconRefresh in this.NIconRefresh)
315                 {
316                     iconRefresh?.Dispose();
317                 }
318                 TabIcon?.Dispose();
319                 MainIcon?.Dispose();
320                 ReplyIcon?.Dispose();
321                 ReplyIconBlink?.Dispose();
322                 _listViewImageList.Dispose();
323                 _brsHighLight.Dispose();
324                 _brsBackColorMine?.Dispose();
325                 _brsBackColorAt?.Dispose();
326                 _brsBackColorYou?.Dispose();
327                 _brsBackColorAtYou?.Dispose();
328                 _brsBackColorAtFromTarget?.Dispose();
329                 _brsBackColorAtTo?.Dispose();
330                 _brsBackColorNone?.Dispose();
331                 _brsDeactiveSelection?.Dispose();
332                 //sf.Dispose();
333                 sfTab.Dispose();
334
335                 this.workerCts.Cancel();
336
337                 if (IconCache != null)
338                 {
339                     this.IconCache.CancelAsync();
340                     this.IconCache.Dispose();
341                 }
342
343                 this.thumbnailTokenSource?.Dispose();
344
345                 this.itemCacheLock.Dispose();
346                 this.tw.Dispose();
347                 this._hookGlobalHotkey.Dispose();
348             }
349
350             // 終了時にRemoveHandlerしておかないとメモリリークする
351             // http://msdn.microsoft.com/ja-jp/library/microsoft.win32.systemevents.powermodechanged.aspx
352             Microsoft.Win32.SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
353
354             this.disposed = true;
355         }
356
357         private void LoadIcons()
358         {
359             // Icons フォルダ以下のアイコンを読み込み(着せ替えアイコン対応)
360             var iconsDir = Path.Combine(Application.StartupPath, "Icons");
361
362             // ウィンドウ左上のアイコン
363             var iconMain = this.LoadIcon(Path.Combine(iconsDir, "MIcon.ico"));
364
365             // タブ見出し未読表示アイコン
366             var iconTab = this.LoadIcon(Path.Combine(iconsDir, "Tab.ico"));
367
368             // タスクトレイ: 通常時アイコン
369             var iconAt = this.LoadIcon(Path.Combine(iconsDir, "At.ico"));
370
371             // タスクトレイ: エラー時アイコン
372             var iconAtRed = this.LoadIcon(Path.Combine(iconsDir, "AtRed.ico"));
373
374             // タスクトレイ: オフライン時アイコン
375             var iconAtSmoke = this.LoadIcon(Path.Combine(iconsDir, "AtSmoke.ico"));
376
377             // タスクトレイ: Reply通知アイコン (最大2枚でアニメーション可能)
378             var iconReply = this.LoadIcon(Path.Combine(iconsDir, "Reply.ico"));
379             var iconReplyBlink = this.LoadIcon(Path.Combine(iconsDir, "ReplyBlink.ico"));
380
381             // タスクトレイ: 更新中アイコン (最大4枚でアニメーション可能)
382             var iconRefresh1 = this.LoadIcon(Path.Combine(iconsDir, "Refresh.ico"));
383             var iconRefresh2 = this.LoadIcon(Path.Combine(iconsDir, "Refresh2.ico"));
384             var iconRefresh3 = this.LoadIcon(Path.Combine(iconsDir, "Refresh3.ico"));
385             var iconRefresh4 = this.LoadIcon(Path.Combine(iconsDir, "Refresh4.ico"));
386
387             // 読み込んだアイコンを設定 (不足するアイコンはデフォルトのものを設定)
388
389             this.MainIcon = iconMain ?? Properties.Resources.MIcon;
390             this.TabIcon = iconTab ?? Properties.Resources.TabIcon;
391             this.NIconAt = iconAt ?? iconMain ?? Properties.Resources.At;
392             this.NIconAtRed = iconAtRed ?? Properties.Resources.AtRed;
393             this.NIconAtSmoke = iconAtSmoke ?? Properties.Resources.AtSmoke;
394
395             if (iconReply != null && iconReplyBlink != null)
396             {
397                 this.ReplyIcon = iconReply;
398                 this.ReplyIconBlink = iconReplyBlink;
399             }
400             else
401             {
402                 this.ReplyIcon = iconReply ?? iconReplyBlink ?? Properties.Resources.Reply;
403                 this.ReplyIconBlink = this.NIconAt;
404             }
405
406             if (iconRefresh1 == null)
407             {
408                 this.NIconRefresh = new[] {
409                     Properties.Resources.Refresh, Properties.Resources.Refresh2,
410                     Properties.Resources.Refresh3, Properties.Resources.Refresh4,
411                 };
412             }
413             else if (iconRefresh2 == null)
414             {
415                 this.NIconRefresh = new[] { iconRefresh1 };
416             }
417             else if (iconRefresh3 == null)
418             {
419                 this.NIconRefresh = new[] { iconRefresh1, iconRefresh2 };
420             }
421             else if (iconRefresh4 == null)
422             {
423                 this.NIconRefresh = new[] { iconRefresh1, iconRefresh2, iconRefresh3 };
424             }
425             else // iconRefresh1 から iconRefresh4 まで全て揃っている
426             {
427                 this.NIconRefresh = new[] { iconRefresh1, iconRefresh2, iconRefresh3, iconRefresh4 };
428             }
429         }
430
431         private Icon LoadIcon(string filePath)
432         {
433             if (!File.Exists(filePath))
434                 return null;
435
436             try
437             {
438                 return new Icon(filePath);
439             }
440             catch (Exception)
441             {
442                 return null;
443             }
444         }
445
446         private void InitColumns(ListView list, bool startup)
447         {
448             this.InitColumnText();
449
450             ColumnHeader[] columns;
451             if (this._iconCol)
452             {
453                 columns = new[]
454                 {
455                     new ColumnHeader { Text = this.ColumnText[0], Width = 48 }, // アイコン
456                     new ColumnHeader { Text = this.ColumnText[2], Width = 300 },  // 本文
457                 };
458
459                 if (startup)
460                 {
461                     var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / this._cfgLocal.ScaleDimension.Width;
462
463                     columns[0].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width1);
464                     columns[1].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width3);
465                     columns[0].DisplayIndex = 0;
466                     columns[1].DisplayIndex = 1;
467                 }
468                 else
469                 {
470                     var idx = 0;
471                     foreach (var curListColumn in this._curList.Columns.Cast<ColumnHeader>())
472                     {
473                         columns[idx].Width = curListColumn.Width;
474                         columns[idx].DisplayIndex = curListColumn.DisplayIndex;
475                         idx++;
476                     }
477                 }
478             }
479             else
480             {
481                 columns = new[]
482                 {
483                     new ColumnHeader { Text = this.ColumnText[0], Width = 48 }, // アイコン
484                     new ColumnHeader { Text = this.ColumnText[1], Width = 80 }, // ニックネーム
485                     new ColumnHeader { Text = this.ColumnText[2], Width = 300 }, // 本文
486                     new ColumnHeader { Text = this.ColumnText[3], Width = 50 }, // 日付
487                     new ColumnHeader { Text = this.ColumnText[4], Width = 50 }, // ユーザID
488                     new ColumnHeader { Text = this.ColumnText[5], Width = 16 }, // 未読
489                     new ColumnHeader { Text = this.ColumnText[6], Width = 16 }, // マーク&プロテクト
490                     new ColumnHeader { Text = this.ColumnText[7], Width = 50 }, // ソース
491                 };
492
493                 if (startup)
494                 {
495                     var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / this._cfgLocal.ScaleDimension.Width;
496
497                     columns[0].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width1);
498                     columns[1].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width2);
499                     columns[2].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width3);
500                     columns[3].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width4);
501                     columns[4].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width5);
502                     columns[5].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width6);
503                     columns[6].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width7);
504                     columns[7].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width8);
505
506                     var displayIndex = new[] {
507                         this._cfgLocal.DisplayIndex1, this._cfgLocal.DisplayIndex2,
508                         this._cfgLocal.DisplayIndex3, this._cfgLocal.DisplayIndex4,
509                         this._cfgLocal.DisplayIndex5, this._cfgLocal.DisplayIndex6,
510                         this._cfgLocal.DisplayIndex7, this._cfgLocal.DisplayIndex8
511                     };
512
513                     foreach (var i in Enumerable.Range(0, displayIndex.Length))
514                     {
515                         columns[i].DisplayIndex = displayIndex[i];
516                     }
517                 }
518                 else
519                 {
520                     var idx = 0;
521                     foreach (var curListColumn in this._curList.Columns.Cast<ColumnHeader>())
522                     {
523                         columns[idx].Width = curListColumn.Width;
524                         columns[idx].DisplayIndex = curListColumn.DisplayIndex;
525                         idx++;
526                     }
527                 }
528             }
529
530             list.Columns.AddRange(columns);
531         }
532
533         private void InitColumnText()
534         {
535             ColumnText[0] = "";
536             ColumnText[1] = Properties.Resources.AddNewTabText2;
537             ColumnText[2] = Properties.Resources.AddNewTabText3;
538             ColumnText[3] = Properties.Resources.AddNewTabText4_2;
539             ColumnText[4] = Properties.Resources.AddNewTabText5;
540             ColumnText[5] = "";
541             ColumnText[6] = "";
542             ColumnText[7] = "Source";
543
544             ColumnOrgText[0] = "";
545             ColumnOrgText[1] = Properties.Resources.AddNewTabText2;
546             ColumnOrgText[2] = Properties.Resources.AddNewTabText3;
547             ColumnOrgText[3] = Properties.Resources.AddNewTabText4_2;
548             ColumnOrgText[4] = Properties.Resources.AddNewTabText5;
549             ColumnOrgText[5] = "";
550             ColumnOrgText[6] = "";
551             ColumnOrgText[7] = "Source";
552
553             int c = 0;
554             switch (_statuses.SortMode)
555             {
556                 case ComparerMode.Nickname:  //ニックネーム
557                     c = 1;
558                     break;
559                 case ComparerMode.Data:  //本文
560                     c = 2;
561                     break;
562                 case ComparerMode.Id:  //時刻=発言Id
563                     c = 3;
564                     break;
565                 case ComparerMode.Name:  //名前
566                     c = 4;
567                     break;
568                 case ComparerMode.Source:  //Source
569                     c = 7;
570                     break;
571             }
572
573             if (_iconCol)
574             {
575                 if (_statuses.SortOrder == SortOrder.Descending)
576                 {
577                     // U+25BE BLACK DOWN-POINTING SMALL TRIANGLE
578                     ColumnText[2] = ColumnOrgText[2] + "▾";
579                 }
580                 else
581                 {
582                     // U+25B4 BLACK UP-POINTING SMALL TRIANGLE
583                     ColumnText[2] = ColumnOrgText[2] + "▴";
584                 }
585             }
586             else
587             {
588                 if (_statuses.SortOrder == SortOrder.Descending)
589                 {
590                     // U+25BE BLACK DOWN-POINTING SMALL TRIANGLE
591                     ColumnText[c] = ColumnOrgText[c] + "▾";
592                 }
593                 else
594                 {
595                     // U+25B4 BLACK UP-POINTING SMALL TRIANGLE
596                     ColumnText[c] = ColumnOrgText[c] + "▴";
597                 }
598             }
599         }
600
601         private void InitializeTraceFrag()
602         {
603 #if DEBUG
604             TraceOutToolStripMenuItem.Checked = true;
605             MyCommon.TraceFlag = true;
606 #endif
607             if (!MyCommon.FileVersion.EndsWith("0"))
608             {
609                 TraceOutToolStripMenuItem.Checked = true;
610                 MyCommon.TraceFlag = true;
611             }
612         }
613
614         private void TweenMain_Load(object sender, EventArgs e)
615         {
616             _ignoreConfigSave = true;
617             this.Visible = false;
618
619             if (MyApplication.StartupOptions.ContainsKey("d"))
620                 MyCommon.TraceFlag = true;
621
622             InitializeTraceFrag();
623
624             //Win32Api.SetProxy(HttpConnection.ProxyType.Specified, "127.0.0.1", 8080, "user", "pass")
625
626             new InternetSecurityManager(PostBrowser);
627             this.PostBrowser.AllowWebBrowserDrop = false;  // COMException を回避するため、ActiveX の初期化が終わってから設定する
628
629             MyCommon.TwitterApiInfo.AccessLimitUpdated += TwitterApiStatus_AccessLimitUpdated;
630             Microsoft.Win32.SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
631
632             Regex.CacheSize = 100;
633
634             //発言保持クラス
635             _statuses = TabInformations.GetInstance();
636
637             //アイコン設定
638             LoadIcons();
639             this.Icon = MainIcon;              //メインフォーム(TweenMain)
640             NotifyIcon1.Icon = NIconAt;      //タスクトレイ
641             TabImage.Images.Add(TabIcon);    //タブ見出し
642
643             //<<<<<<<<<設定関連>>>>>>>>>
644             ////設定読み出し
645             LoadConfig();
646
647             // 現在の DPI と設定保存時の DPI との比を取得する
648             var configScaleFactor = this._cfgLocal.GetConfigScaleFactor(this.CurrentAutoScaleDimensions);
649
650             // UIフォント設定
651             var fontUIGlobal = this._cfgLocal.FontUIGlobal;
652             if (fontUIGlobal != null)
653             {
654                 OTBaseForm.GlobalFont = fontUIGlobal;
655                 this.Font = fontUIGlobal;
656             }
657
658             //不正値チェック
659             if (!MyApplication.StartupOptions.ContainsKey("nolimit"))
660             {
661                 if (this._cfgCommon.TimelinePeriod < 15 && this._cfgCommon.TimelinePeriod > 0)
662                     this._cfgCommon.TimelinePeriod = 15;
663
664                 if (this._cfgCommon.ReplyPeriod < 15 && this._cfgCommon.ReplyPeriod > 0)
665                     this._cfgCommon.ReplyPeriod = 15;
666
667                 if (this._cfgCommon.DMPeriod < 15 && this._cfgCommon.DMPeriod > 0)
668                     this._cfgCommon.DMPeriod = 15;
669
670                 if (this._cfgCommon.PubSearchPeriod < 30 && this._cfgCommon.PubSearchPeriod > 0)
671                     this._cfgCommon.PubSearchPeriod = 30;
672
673                 if (this._cfgCommon.UserTimelinePeriod < 15 && this._cfgCommon.UserTimelinePeriod > 0)
674                     this._cfgCommon.UserTimelinePeriod = 15;
675
676                 if (this._cfgCommon.ListsPeriod < 15 && this._cfgCommon.ListsPeriod > 0)
677                     this._cfgCommon.ListsPeriod = 15;
678             }
679
680             if (!Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.Timeline, this._cfgCommon.CountApi))
681                 this._cfgCommon.CountApi = 60;
682             if (!Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.Reply, this._cfgCommon.CountApiReply))
683                 this._cfgCommon.CountApiReply = 40;
684
685             if (this._cfgCommon.MoreCountApi != 0 && !Twitter.VerifyMoreApiResultCount(this._cfgCommon.MoreCountApi))
686                 this._cfgCommon.MoreCountApi = 200;
687             if (this._cfgCommon.FirstCountApi != 0 && !Twitter.VerifyFirstApiResultCount(this._cfgCommon.FirstCountApi))
688                 this._cfgCommon.FirstCountApi = 100;
689
690             if (this._cfgCommon.FavoritesCountApi != 0 && !Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.Favorites, this._cfgCommon.FavoritesCountApi))
691                 this._cfgCommon.FavoritesCountApi = 40;
692             if (this._cfgCommon.ListCountApi != 0 && !Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.List, this._cfgCommon.ListCountApi))
693                 this._cfgCommon.ListCountApi = 100;
694             if (this._cfgCommon.SearchCountApi != 0 && !Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.PublicSearch, this._cfgCommon.SearchCountApi))
695                 this._cfgCommon.SearchCountApi = 100;
696             if (this._cfgCommon.UserTimelineCountApi != 0 && !Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.UserTimeline, this._cfgCommon.UserTimelineCountApi))
697                 this._cfgCommon.UserTimelineCountApi = 20;
698
699             //廃止サービスが選択されていた場合ux.nuへ読み替え
700             if (this._cfgCommon.AutoShortUrlFirst < 0)
701                 this._cfgCommon.AutoShortUrlFirst = MyCommon.UrlConverter.Uxnu;
702
703             HttpTwitter.TwitterUrl = this._cfgCommon.TwitterUrl;
704
705             //認証関連
706             if (string.IsNullOrEmpty(this._cfgCommon.Token)) this._cfgCommon.UserName = "";
707             tw.Initialize(this._cfgCommon.Token, this._cfgCommon.TokenSecret, this._cfgCommon.UserName, this._cfgCommon.UserId);
708
709             _initial = true;
710
711             Networking.Initialize();
712
713             bool saveRequired = false;
714             bool firstRun = false;
715
716             //ユーザー名、パスワードが未設定なら設定画面を表示(初回起動時など)
717             if (string.IsNullOrEmpty(tw.Username))
718             {
719                 saveRequired = true;
720                 firstRun = true;
721
722                 //設定せずにキャンセルされたか、設定されたが依然ユーザー名が未設定ならプログラム終了
723                 if (ShowSettingDialog(showTaskbarIcon: true) != DialogResult.OK ||
724                     string.IsNullOrEmpty(tw.Username))
725                 {
726                     Application.Exit();  //強制終了
727                     return;
728                 }
729             }
730
731             //Twitter用通信クラス初期化
732             Networking.DefaultTimeout = TimeSpan.FromSeconds(this._cfgCommon.DefaultTimeOut);
733             Networking.SetWebProxy(this._cfgLocal.ProxyType,
734                 this._cfgLocal.ProxyAddress, this._cfgLocal.ProxyPort,
735                 this._cfgLocal.ProxyUser, this._cfgLocal.ProxyPassword);
736             Networking.ForceIPv4 = this._cfgCommon.ForceIPv4;
737
738             HttpTwitter.TwitterUrl = this._cfgCommon.TwitterUrl;
739             tw.RestrictFavCheck = this._cfgCommon.RestrictFavCheck;
740             tw.ReadOwnPost = this._cfgCommon.ReadOwnPost;
741             tw.TrackWord = this._cfgCommon.TrackWord;
742             TrackToolStripMenuItem.Checked = !String.IsNullOrEmpty(tw.TrackWord);
743             tw.AllAtReply = this._cfgCommon.AllAtReply;
744             AllrepliesToolStripMenuItem.Checked = tw.AllAtReply;
745             ShortUrl.Instance.DisableExpanding = !this._cfgCommon.TinyUrlResolve;
746             ShortUrl.Instance.BitlyId = this._cfgCommon.BilyUser;
747             ShortUrl.Instance.BitlyKey = this._cfgCommon.BitlyPwd;
748
749             //サムネイル関連の初期化
750             //プロキシ設定等の通信まわりの初期化が済んでから処理する
751             ThumbnailGenerator.InitializeGenerator();
752
753             var imgazyobizinet = ThumbnailGenerator.ImgAzyobuziNetInstance;
754             imgazyobizinet.Enabled = this._cfgCommon.EnableImgAzyobuziNet;
755             imgazyobizinet.DisabledInDM = this._cfgCommon.ImgAzyobuziNetDisabledInDM;
756
757             Thumbnail.Services.TonTwitterCom.InitializeOAuthToken = x =>
758                 x.Initialize(ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
759                     this.tw.AccessToken, this.tw.AccessTokenSecret, "", "");
760
761             //画像投稿サービス
762             ImageSelector.Initialize(tw, this.tw.Configuration, _cfgCommon.UseImageServiceName, _cfgCommon.UseImageService);
763
764             //ハッシュタグ/@id関連
765             AtIdSupl = new AtIdSupplement(SettingAtIdList.Load().AtIdList, "@");
766             HashSupl = new AtIdSupplement(_cfgCommon.HashTags, "#");
767             HashMgr = new HashtagManage(HashSupl,
768                                     _cfgCommon.HashTags.ToArray(),
769                                     _cfgCommon.HashSelected,
770                                     _cfgCommon.HashIsPermanent,
771                                     _cfgCommon.HashIsHead,
772                                     _cfgCommon.HashIsNotAddToAtReply);
773             if (!string.IsNullOrEmpty(HashMgr.UseHash) && HashMgr.IsPermanent) HashStripSplitButton.Text = HashMgr.UseHash;
774
775             //アイコンリスト作成
776             this.IconCache = new ImageCache();
777
778             //フォント&文字色&背景色保持
779             _fntUnread = this._cfgLocal.FontUnread;
780             _clUnread = this._cfgLocal.ColorUnread;
781             _fntReaded = this._cfgLocal.FontRead;
782             _clReaded = this._cfgLocal.ColorRead;
783             _clFav = this._cfgLocal.ColorFav;
784             _clOWL = this._cfgLocal.ColorOWL;
785             _clRetweet = this._cfgLocal.ColorRetweet;
786             _fntDetail = this._cfgLocal.FontDetail;
787             _clDetail = this._cfgLocal.ColorDetail;
788             _clDetailLink = this._cfgLocal.ColorDetailLink;
789             _clDetailBackcolor = this._cfgLocal.ColorDetailBackcolor;
790             _clSelf = this._cfgLocal.ColorSelf;
791             _clAtSelf = this._cfgLocal.ColorAtSelf;
792             _clTarget = this._cfgLocal.ColorTarget;
793             _clAtTarget = this._cfgLocal.ColorAtTarget;
794             _clAtFromTarget = this._cfgLocal.ColorAtFromTarget;
795             _clAtTo = this._cfgLocal.ColorAtTo;
796             _clListBackcolor = this._cfgLocal.ColorListBackcolor;
797             _clInputBackcolor = this._cfgLocal.ColorInputBackcolor;
798             _clInputFont = this._cfgLocal.ColorInputFont;
799             _fntInputFont = this._cfgLocal.FontInputFont;
800
801             _brsBackColorMine = new SolidBrush(_clSelf);
802             _brsBackColorAt = new SolidBrush(_clAtSelf);
803             _brsBackColorYou = new SolidBrush(_clTarget);
804             _brsBackColorAtYou = new SolidBrush(_clAtTarget);
805             _brsBackColorAtFromTarget = new SolidBrush(_clAtFromTarget);
806             _brsBackColorAtTo = new SolidBrush(_clAtTo);
807             //_brsBackColorNone = new SolidBrush(Color.FromKnownColor(KnownColor.Window));
808             _brsBackColorNone = new SolidBrush(_clListBackcolor);
809
810             // StringFormatオブジェクトへの事前設定
811             //sf.Alignment = StringAlignment.Near;             // Textを近くへ配置(左から右の場合は左寄せ)
812             //sf.LineAlignment = StringAlignment.Near;         // Textを近くへ配置(上寄せ)
813             //sf.FormatFlags = StringFormatFlags.LineLimit;    // 
814             sfTab.Alignment = StringAlignment.Center;
815             sfTab.LineAlignment = StringAlignment.Center;
816
817             InitDetailHtmlFormat();
818
819             //Regex statregex = new Regex("^0*");
820             this.recommendedStatusFooter = " [TWNv" + Regex.Replace(MyCommon.FileVersion.Replace(".", ""), "^0*", "") + "]";
821
822             _history.Add(new PostingStatus());
823             _hisIdx = 0;
824             this.inReplyTo = null;
825
826             //各種ダイアログ設定
827             SearchDialog.Owner = this;
828             UrlDialog.Owner = this;
829
830             //新着バルーン通知のチェック状態設定
831             NewPostPopMenuItem.Checked = _cfgCommon.NewAllPop;
832             this.NotifyFileMenuItem.Checked = NewPostPopMenuItem.Checked;
833
834             //新着取得時のリストスクロールをするか。trueならスクロールしない
835             ListLockMenuItem.Checked = _cfgCommon.ListLock;
836             this.LockListFileMenuItem.Checked = _cfgCommon.ListLock;
837             //サウンド再生(タブ別設定より優先)
838             this.PlaySoundMenuItem.Checked = this._cfgCommon.PlaySound;
839             this.PlaySoundFileMenuItem.Checked = this._cfgCommon.PlaySound;
840
841             this.IdeographicSpaceToSpaceToolStripMenuItem.Checked = _cfgCommon.WideSpaceConvert;
842             this.ToolStripFocusLockMenuItem.Checked = _cfgCommon.FocusLockToStatusText;
843
844             //ウィンドウ設定
845             this.ClientSize = ScaleBy(configScaleFactor, _cfgLocal.FormSize);
846             _mySize = this.ClientSize; // サイズ保持(最小化・最大化されたまま終了した場合の対応用)
847             _myLoc = _cfgLocal.FormLocation;
848             //タイトルバー領域
849             if (this.WindowState != FormWindowState.Minimized)
850             {
851                 this.DesktopLocation = _cfgLocal.FormLocation;
852                 Rectangle tbarRect = new Rectangle(this.Location, new Size(_mySize.Width, SystemInformation.CaptionHeight));
853                 bool outOfScreen = true;
854                 if (Screen.AllScreens.Length == 1)    //ハングするとの報告
855                 {
856                     foreach (Screen scr in Screen.AllScreens)
857                     {
858                         if (!Rectangle.Intersect(tbarRect, scr.Bounds).IsEmpty)
859                         {
860                             outOfScreen = false;
861                             break;
862                         }
863                     }
864                     if (outOfScreen)
865                     {
866                         this.DesktopLocation = new Point(0, 0);
867                         _myLoc = this.DesktopLocation;
868                     }
869                 }
870             }
871             this.TopMost = this._cfgCommon.AlwaysTop;
872             _mySpDis = ScaleBy(configScaleFactor.Height, _cfgLocal.SplitterDistance);
873             _mySpDis2 = ScaleBy(configScaleFactor.Height, _cfgLocal.StatusTextHeight);
874             if (_cfgLocal.PreviewDistance == -1)
875             {
876                 _mySpDis3 = _mySize.Width - ScaleBy(this.CurrentScaleFactor.Width, 150);
877                 if (_mySpDis3 < 1) _mySpDis3 = ScaleBy(this.CurrentScaleFactor.Width, 50);
878                 _cfgLocal.PreviewDistance = _mySpDis3;
879             }
880             else
881             {
882                 _mySpDis3 = ScaleBy(configScaleFactor.Width, _cfgLocal.PreviewDistance);
883             }
884             MultiLineMenuItem.Checked = _cfgLocal.StatusMultiline;
885             //this.Tween_ClientSizeChanged(this, null);
886             this.PlaySoundMenuItem.Checked = this._cfgCommon.PlaySound;
887             this.PlaySoundFileMenuItem.Checked = this._cfgCommon.PlaySound;
888             //入力欄
889             StatusText.Font = _fntInputFont;
890             StatusText.ForeColor = _clInputFont;
891
892             // SplitContainer2.Panel2MinSize を一行表示の入力欄の高さに合わせる (MS UI Gothic 12pt (96dpi) の場合は 19px)
893             this.StatusText.Multiline = false; // _cfgLocal.StatusMultiline の設定は後で反映される
894             this.SplitContainer2.Panel2MinSize = this.StatusText.Height;
895
896             // NameLabel のフォントを OTBaseForm.GlobalFont に変更
897             this.NameLabel.Font = this.ReplaceToGlobalFont(this.NameLabel.Font);
898
899             // 必要であれば、発言一覧と発言詳細部・入力欄の上下を入れ替える
900             SplitContainer1.IsPanelInverted = !this._cfgCommon.StatusAreaAtBottom;
901
902             //全新着通知のチェック状態により、Reply&DMの新着通知有効無効切り替え(タブ別設定にするため削除予定)
903             if (this._cfgCommon.UnreadManage == false)
904             {
905                 ReadedStripMenuItem.Enabled = false;
906                 UnreadStripMenuItem.Enabled = false;
907             }
908
909             //発言詳細部の初期化
910             NameLabel.Text = "";
911             DateTimeLabel.Text = "";
912             SourceLinkLabel.Text = "";
913
914             //リンク先URL表示部の初期化(画面左下)
915             StatusLabelUrl.Text = "";
916             //状態表示部の初期化(画面右下)
917             StatusLabel.Text = "";
918             StatusLabel.AutoToolTip = false;
919             StatusLabel.ToolTipText = "";
920             //文字カウンタ初期化
921             lblLen.Text = GetRestStatusCount(true, false).ToString();
922
923             this.JumpReadOpMenuItem.ShortcutKeyDisplayString = "Space";
924             CopySTOTMenuItem.ShortcutKeyDisplayString = "Ctrl+C";
925             CopyURLMenuItem.ShortcutKeyDisplayString = "Ctrl+Shift+C";
926             CopyUserIdStripMenuItem.ShortcutKeyDisplayString = "Shift+Alt+C";
927
928             ////////////////////////////////////////////////////////////////////////////////
929             _statuses.SortOrder = (SortOrder)_cfgCommon.SortOrder;
930             var mode = ComparerMode.Id;
931             switch (_cfgCommon.SortColumn)
932             {
933                 case 0:    //0:アイコン,5:未読マーク,6:プロテクト・フィルターマーク
934                 case 5:
935                 case 6:
936                     //ソートしない
937                     mode = ComparerMode.Id;  //Idソートに読み替え
938                     break;
939                 case 1:  //ニックネーム
940                     mode = ComparerMode.Nickname;
941                     break;
942                 case 2:  //本文
943                     mode = ComparerMode.Data;
944                     break;
945                 case 3:  //時刻=発言Id
946                     mode = ComparerMode.Id;
947                     break;
948                 case 4:  //名前
949                     mode = ComparerMode.Name;
950                     break;
951                 case 7:  //Source
952                     mode = ComparerMode.Source;
953                     break;
954             }
955             _statuses.SortMode = mode;
956             ////////////////////////////////////////////////////////////////////////////////
957
958             ApplyListViewIconSize(this._cfgCommon.IconSize);
959
960             //<<<<<<<<タブ関連>>>>>>>
961             // タブの位置を調整する
962             SetTabAlignment();
963
964             //デフォルトタブの存在チェック、ない場合には追加
965             if (_statuses.GetTabByType(MyCommon.TabUsageType.Home) == null)
966             {
967                 TabClass tab;
968                 if (!_statuses.Tabs.TryGetValue(MyCommon.DEFAULTTAB.RECENT, out tab))
969                 {
970                     _statuses.AddTab(MyCommon.DEFAULTTAB.RECENT, MyCommon.TabUsageType.Home, null);
971                 }
972                 else
973                 {
974                     tab.TabType = MyCommon.TabUsageType.Home;
975                 }
976             }
977             if (_statuses.GetTabByType(MyCommon.TabUsageType.Mentions) == null)
978             {
979                 TabClass tab;
980                 if (!_statuses.Tabs.TryGetValue(MyCommon.DEFAULTTAB.REPLY, out tab))
981                 {
982                     _statuses.AddTab(MyCommon.DEFAULTTAB.REPLY, MyCommon.TabUsageType.Mentions, null);
983                 }
984                 else
985                 {
986                     tab.TabType = MyCommon.TabUsageType.Mentions;
987                 }
988             }
989             if (_statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage) == null)
990             {
991                 TabClass tab;
992                 if (!_statuses.Tabs.TryGetValue(MyCommon.DEFAULTTAB.DM, out tab))
993                 {
994                     _statuses.AddTab(MyCommon.DEFAULTTAB.DM, MyCommon.TabUsageType.DirectMessage, null);
995                 }
996                 else
997                 {
998                     tab.TabType = MyCommon.TabUsageType.DirectMessage;
999                 }
1000             }
1001             if (_statuses.GetTabByType(MyCommon.TabUsageType.Favorites) == null)
1002             {
1003                 TabClass tab;
1004                 if (!_statuses.Tabs.TryGetValue(MyCommon.DEFAULTTAB.FAV, out tab))
1005                 {
1006                     _statuses.AddTab(MyCommon.DEFAULTTAB.FAV, MyCommon.TabUsageType.Favorites, null);
1007                 }
1008                 else
1009                 {
1010                     tab.TabType = MyCommon.TabUsageType.Favorites;
1011                 }
1012             }
1013             if (_statuses.GetTabByType(MyCommon.TabUsageType.Mute) == null)
1014             {
1015                 TabClass tab;
1016                 if (!_statuses.Tabs.TryGetValue(MyCommon.DEFAULTTAB.MUTE, out tab))
1017                 {
1018                     _statuses.AddTab(MyCommon.DEFAULTTAB.MUTE, MyCommon.TabUsageType.Mute, null);
1019                 }
1020                 else
1021                 {
1022                     tab.TabType = MyCommon.TabUsageType.Mute;
1023                 }
1024             }
1025
1026             foreach (var tab in _statuses.Tabs.Values)
1027             {
1028                 // ミュートタブは表示しない
1029                 if (tab.TabType == MyCommon.TabUsageType.Mute)
1030                     continue;
1031
1032                 if (tab.TabType == MyCommon.TabUsageType.Undefined)
1033                 {
1034                     tab.TabType = MyCommon.TabUsageType.UserDefined;
1035                 }
1036                 if (!AddNewTab(tab.TabName, true, tab.TabType, tab.ListInfo))
1037                     throw new TabException(Properties.Resources.TweenMain_LoadText1);
1038             }
1039
1040             _curTab = ListTab.SelectedTab;
1041             _curItemIndex = -1;
1042             _curList = (DetailsListView)_curTab.Tag;
1043
1044             if (this._cfgCommon.TabIconDisp)
1045             {
1046                 ListTab.DrawMode = TabDrawMode.Normal;
1047             }
1048             else
1049             {
1050                 ListTab.DrawMode = TabDrawMode.OwnerDrawFixed;
1051                 ListTab.DrawItem += ListTab_DrawItem;
1052                 ListTab.ImageList = null;
1053             }
1054
1055             if (this._cfgCommon.HotkeyEnabled)
1056             {
1057                 //////グローバルホットキーの登録
1058                 HookGlobalHotkey.ModKeys modKey = HookGlobalHotkey.ModKeys.None;
1059                 if ((this._cfgCommon.HotkeyModifier & Keys.Alt) == Keys.Alt)
1060                     modKey |= HookGlobalHotkey.ModKeys.Alt;
1061                 if ((this._cfgCommon.HotkeyModifier & Keys.Control) == Keys.Control)
1062                     modKey |= HookGlobalHotkey.ModKeys.Ctrl;
1063                 if ((this._cfgCommon.HotkeyModifier & Keys.Shift) == Keys.Shift)
1064                     modKey |= HookGlobalHotkey.ModKeys.Shift;
1065                 if ((this._cfgCommon.HotkeyModifier & Keys.LWin) == Keys.LWin)
1066                     modKey |= HookGlobalHotkey.ModKeys.Win;
1067
1068                 _hookGlobalHotkey.RegisterOriginalHotkey(this._cfgCommon.HotkeyKey, this._cfgCommon.HotkeyValue, modKey);
1069             }
1070
1071             if (this._cfgCommon.IsUseNotifyGrowl)
1072                 gh.RegisterGrowl();
1073
1074             StatusLabel.Text = Properties.Resources.Form1_LoadText1;       //画面右下の状態表示を変更
1075
1076             SetMainWindowTitle();
1077             SetNotifyIconText();
1078
1079             if (!this._cfgCommon.MinimizeToTray || this.WindowState != FormWindowState.Minimized)
1080             {
1081                 this.Visible = true;
1082             }
1083
1084             //タイマー設定
1085             TimerTimeline.AutoReset = true;
1086             TimerTimeline.SynchronizingObject = this;
1087             //Recent取得間隔
1088             TimerTimeline.Interval = 1000;
1089             TimerTimeline.Enabled = true;
1090             //更新中アイコンアニメーション間隔
1091             TimerRefreshIcon.Interval = 200;
1092             TimerRefreshIcon.Enabled = true;
1093
1094             _ignoreConfigSave = false;
1095             this.TweenMain_Resize(null, null);
1096             if (saveRequired) SaveConfigsAll(false);
1097
1098             if (tw.UserId == 0)
1099                 tw.VerifyCredentials();
1100
1101             foreach (var ua in this._cfgCommon.UserAccounts)
1102             {
1103                 if (ua.UserId == 0 && ua.Username.ToLower() == tw.Username.ToLower())
1104                 {
1105                     ua.UserId = tw.UserId;
1106                     break;
1107                 }
1108             }
1109
1110             if (firstRun)
1111             {
1112                 // 初回起動時だけ右下のメニューを目立たせる
1113                 HashStripSplitButton.ShowDropDown();
1114             }
1115         }
1116
1117         private void InitDetailHtmlFormat()
1118         {
1119             if (this._cfgCommon.IsMonospace)
1120             {
1121                 detailHtmlFormatHeader = detailHtmlFormatHeaderMono;
1122                 detailHtmlFormatFooter = detailHtmlFormatFooterMono;
1123             }
1124             else
1125             {
1126                 detailHtmlFormatHeader = detailHtmlFormatHeaderColor;
1127                 detailHtmlFormatFooter = detailHtmlFormatFooterColor;
1128             }
1129
1130             detailHtmlFormatHeader = detailHtmlFormatHeader
1131                     .Replace("%FONT_FAMILY%", _fntDetail.Name)
1132                     .Replace("%FONT_SIZE%", _fntDetail.Size.ToString())
1133                     .Replace("%FONT_COLOR%", _clDetail.R.ToString() + "," + _clDetail.G.ToString() + "," + _clDetail.B.ToString())
1134                     .Replace("%LINK_COLOR%", _clDetailLink.R.ToString() + "," + _clDetailLink.G.ToString() + "," + _clDetailLink.B.ToString())
1135                     .Replace("%BG_COLOR%", _clDetailBackcolor.R.ToString() + "," + _clDetailBackcolor.G.ToString() + "," + _clDetailBackcolor.B.ToString());
1136         }
1137
1138         private void ListTab_DrawItem(object sender, DrawItemEventArgs e)
1139         {
1140             string txt;
1141             try
1142             {
1143                 txt = ListTab.TabPages[e.Index].Text;
1144             }
1145             catch (Exception)
1146             {
1147                 return;
1148             }
1149
1150             e.Graphics.FillRectangle(System.Drawing.SystemBrushes.Control, e.Bounds);
1151             if (e.State == DrawItemState.Selected)
1152             {
1153                 e.DrawFocusRectangle();
1154             }
1155             Brush fore;
1156             try
1157             {
1158                 if (_statuses.Tabs[txt].UnreadCount > 0)
1159                     fore = Brushes.Red;
1160                 else
1161                     fore = System.Drawing.SystemBrushes.ControlText;
1162             }
1163             catch (Exception)
1164             {
1165                 fore = System.Drawing.SystemBrushes.ControlText;
1166             }
1167             e.Graphics.DrawString(txt, e.Font, fore, e.Bounds, sfTab);
1168         }
1169
1170         private void LoadConfig()
1171         {
1172             _cfgCommon = SettingCommon.Load();
1173             SettingCommon.Instance = this._cfgCommon;
1174             if (_cfgCommon.UserAccounts == null || _cfgCommon.UserAccounts.Count == 0)
1175             {
1176                 _cfgCommon.UserAccounts = new List<UserAccount>();
1177                 if (!string.IsNullOrEmpty(_cfgCommon.UserName))
1178                 {
1179                     UserAccount account = new UserAccount();
1180                     account.Username = _cfgCommon.UserName;
1181                     account.UserId = _cfgCommon.UserId;
1182                     account.Token = _cfgCommon.Token;
1183                     account.TokenSecret = _cfgCommon.TokenSecret;
1184
1185                     _cfgCommon.UserAccounts.Add(account);
1186                 }
1187             }
1188
1189             _cfgLocal = SettingLocal.Load();
1190
1191             // v1.2.4 以前の設定には ScaleDimension の項目がないため、現在の DPI と同じとして扱う
1192             if (_cfgLocal.ScaleDimension.IsEmpty)
1193                 _cfgLocal.ScaleDimension = this.CurrentAutoScaleDimensions;
1194
1195             List<TabClass> tabs = SettingTabs.Load().Tabs;
1196             foreach (TabClass tb in tabs)
1197             {
1198                 try
1199                 {
1200                     tb.FilterModified = false;
1201                     _statuses.Tabs.Add(tb.TabName, tb);
1202                 }
1203                 catch (Exception)
1204                 {
1205                     tb.TabName = _statuses.GetUniqueTabName();
1206                     _statuses.Tabs.Add(tb.TabName, tb);
1207                 }
1208             }
1209             if (_statuses.Tabs.Count == 0)
1210             {
1211                 _statuses.AddTab(MyCommon.DEFAULTTAB.RECENT, MyCommon.TabUsageType.Home, null);
1212                 _statuses.AddTab(MyCommon.DEFAULTTAB.REPLY, MyCommon.TabUsageType.Mentions, null);
1213                 _statuses.AddTab(MyCommon.DEFAULTTAB.DM, MyCommon.TabUsageType.DirectMessage, null);
1214                 _statuses.AddTab(MyCommon.DEFAULTTAB.FAV, MyCommon.TabUsageType.Favorites, null);
1215             }
1216         }
1217
1218         private void TimerInterval_Changed(object sender, IntervalChangedEventArgs e) //Handles SettingDialog.IntervalChanged
1219         {
1220             if (!TimerTimeline.Enabled) return;
1221             ResetTimers = e;
1222         }
1223
1224         private IntervalChangedEventArgs ResetTimers = IntervalChangedEventArgs.ResetAll;
1225
1226         private static int homeCounter = 0;
1227         private static int mentionCounter = 0;
1228         private static int dmCounter = 0;
1229         private static int pubSearchCounter = 0;
1230         private static int userTimelineCounter = 0;
1231         private static int listsCounter = 0;
1232         private static int usCounter = 0;
1233         private static int ResumeWait = 0;
1234         private static int refreshFollowers = 0;
1235
1236         private async void TimerTimeline_Elapsed(object sender, EventArgs e)
1237         {
1238             if (homeCounter > 0) Interlocked.Decrement(ref homeCounter);
1239             if (mentionCounter > 0) Interlocked.Decrement(ref mentionCounter);
1240             if (dmCounter > 0) Interlocked.Decrement(ref dmCounter);
1241             if (pubSearchCounter > 0) Interlocked.Decrement(ref pubSearchCounter);
1242             if (userTimelineCounter > 0) Interlocked.Decrement(ref userTimelineCounter);
1243             if (listsCounter > 0) Interlocked.Decrement(ref listsCounter);
1244             if (usCounter > 0) Interlocked.Decrement(ref usCounter);
1245             Interlocked.Increment(ref refreshFollowers);
1246
1247             var refreshTasks = new List<Task>();
1248
1249             ////タイマー初期化
1250             if (ResetTimers.Timeline || homeCounter <= 0 && this._cfgCommon.TimelinePeriod > 0)
1251             {
1252                 Interlocked.Exchange(ref homeCounter, this._cfgCommon.TimelinePeriod);
1253                 if (!tw.IsUserstreamDataReceived && !ResetTimers.Timeline)
1254                     refreshTasks.Add(this.GetHomeTimelineAsync());
1255                 ResetTimers.Timeline = false;
1256             }
1257             if (ResetTimers.Reply || mentionCounter <= 0 && this._cfgCommon.ReplyPeriod > 0)
1258             {
1259                 Interlocked.Exchange(ref mentionCounter, this._cfgCommon.ReplyPeriod);
1260                 if (!tw.IsUserstreamDataReceived && !ResetTimers.Reply)
1261                     refreshTasks.Add(this.GetReplyAsync());
1262                 ResetTimers.Reply = false;
1263             }
1264             if (ResetTimers.DirectMessage || dmCounter <= 0 && this._cfgCommon.DMPeriod > 0)
1265             {
1266                 Interlocked.Exchange(ref dmCounter, this._cfgCommon.DMPeriod);
1267                 if (!tw.IsUserstreamDataReceived && !ResetTimers.DirectMessage)
1268                     refreshTasks.Add(this.GetDirectMessagesAsync());
1269                 ResetTimers.DirectMessage = false;
1270             }
1271             if (ResetTimers.PublicSearch || pubSearchCounter <= 0 && this._cfgCommon.PubSearchPeriod > 0)
1272             {
1273                 Interlocked.Exchange(ref pubSearchCounter, this._cfgCommon.PubSearchPeriod);
1274                 if (!ResetTimers.PublicSearch)
1275                     refreshTasks.Add(this.GetPublicSearchAllAsync());
1276                 ResetTimers.PublicSearch = false;
1277             }
1278             if (ResetTimers.UserTimeline || userTimelineCounter <= 0 && this._cfgCommon.UserTimelinePeriod > 0)
1279             {
1280                 Interlocked.Exchange(ref userTimelineCounter, this._cfgCommon.UserTimelinePeriod);
1281                 if (!ResetTimers.UserTimeline)
1282                     refreshTasks.Add(this.GetUserTimelineAllAsync());
1283                 ResetTimers.UserTimeline = false;
1284             }
1285             if (ResetTimers.Lists || listsCounter <= 0 && this._cfgCommon.ListsPeriod > 0)
1286             {
1287                 Interlocked.Exchange(ref listsCounter, this._cfgCommon.ListsPeriod);
1288                 if (!ResetTimers.Lists)
1289                     refreshTasks.Add(this.GetListTimelineAllAsync());
1290                 ResetTimers.Lists = false;
1291             }
1292             if (ResetTimers.UserStream || usCounter <= 0 && this._cfgCommon.UserstreamPeriod > 0)
1293             {
1294                 Interlocked.Exchange(ref usCounter, this._cfgCommon.UserstreamPeriod);
1295                 if (this._isActiveUserstream)
1296                 {
1297                     refreshTasks.Add(this.RefreshTasktrayIcon(true));
1298                     this.RefreshTimeline(true);
1299                 }
1300                 ResetTimers.UserStream = false;
1301             }
1302             if (refreshFollowers > 6 * 3600)
1303             {
1304                 Interlocked.Exchange(ref refreshFollowers, 0);
1305                 refreshTasks.AddRange(new[]
1306                 {
1307                     this.doGetFollowersMenu(),
1308                     this.RefreshNoRetweetIdsAsync(),
1309                     this.RefreshTwitterConfigurationAsync(),
1310                 });
1311             }
1312             if (osResumed)
1313             {
1314                 Interlocked.Increment(ref ResumeWait);
1315                 if (ResumeWait > 30)
1316                 {
1317                     osResumed = false;
1318                     Interlocked.Exchange(ref ResumeWait, 0);
1319                     refreshTasks.AddRange(new[]
1320                     {
1321                         this.GetHomeTimelineAsync(),
1322                         this.GetReplyAsync(),
1323                         this.GetDirectMessagesAsync(),
1324                         this.GetPublicSearchAllAsync(),
1325                         this.GetUserTimelineAllAsync(),
1326                         this.GetListTimelineAllAsync(),
1327                         this.doGetFollowersMenu(),
1328                         this.RefreshTwitterConfigurationAsync(),
1329                     });
1330                 }
1331             }
1332
1333             await Task.WhenAll(refreshTasks);
1334         }
1335
1336         private void RefreshTimeline(bool isUserStream)
1337         {
1338             //スクロール制御準備
1339             int smode = -1;    //-1:制御しない,-2:最新へ,その他:topitem使用
1340             long topId = GetScrollPos(ref smode);
1341             int befCnt = _curList.VirtualListSize;
1342
1343             //現在の選択状態を退避
1344             var selId = new Dictionary<string, long[]>();
1345             var focusedId = new Dictionary<string, Tuple<long, long>>();
1346             SaveSelectedStatus(selId, focusedId);
1347
1348             //mentionsの更新前件数を保持
1349             int dmCount = _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage).AllCount;
1350
1351             //更新確定
1352             PostClass[] notifyPosts = null;
1353             string soundFile = "";
1354             int addCount = 0;
1355             bool isMention = false;
1356             bool isDelete = false;
1357             addCount = _statuses.SubmitUpdate(ref soundFile, ref notifyPosts, ref isMention, ref isDelete, isUserStream);
1358
1359             if (MyCommon._endingFlag) return;
1360
1361             //リストに反映&選択状態復元
1362             try
1363             {
1364                 foreach (TabPage tab in ListTab.TabPages)
1365                 {
1366                     DetailsListView lst = (DetailsListView)tab.Tag;
1367                     TabClass tabInfo = _statuses.Tabs[tab.Text];
1368                     using (ControlTransaction.Update(lst))
1369                     {
1370                         if (isDelete || lst.VirtualListSize != tabInfo.AllCount)
1371                         {
1372                             if (lst.Equals(_curList))
1373                             {
1374                                 this.PurgeListViewItemCache();
1375                             }
1376                             try
1377                             {
1378                                 lst.VirtualListSize = tabInfo.AllCount; //リスト件数更新
1379                             }
1380                             catch (Exception)
1381                             {
1382                                 //アイコン描画不具合あり?
1383                             }
1384
1385                             // status_id から ListView 上のインデックスに変換
1386                             var selectedIndices = selId[tab.Text] != null
1387                                 ? tabInfo.IndexOf(selId[tab.Text]).Where(x => x != -1).ToArray()
1388                                 : null;
1389                             var focusedIndex = tabInfo.IndexOf(focusedId[tab.Text].Item1);
1390                             var selectionMarkIndex = tabInfo.IndexOf(focusedId[tab.Text].Item2);
1391
1392                             this.SelectListItem(lst, selectedIndices, focusedIndex, selectionMarkIndex);
1393                         }
1394                     }
1395                     if (tabInfo.UnreadCount > 0)
1396                         if (this._cfgCommon.TabIconDisp)
1397                             if (tab.ImageIndex == -1) tab.ImageIndex = 0; //タブアイコン
1398                 }
1399                 if (!this._cfgCommon.TabIconDisp) ListTab.Refresh();
1400             }
1401             catch (Exception)
1402             {
1403                 //ex.Data["Msg"] = "Ref1, UseAPI=" + SettingDialog.UseAPI.ToString();
1404                 //throw;
1405             }
1406
1407             //スクロール制御後処理
1408             if (smode != -1)
1409             {
1410                 try
1411                 {
1412                     if (befCnt != _curList.VirtualListSize)
1413                     {
1414                         switch (smode)
1415                         {
1416                             case -3:
1417                                 //最上行
1418                                 if (_curList.VirtualListSize > 0) _curList.EnsureVisible(0);
1419                                 break;
1420                             case -2:
1421                                 //最下行へ
1422                                 if (_curList.VirtualListSize > 0) _curList.EnsureVisible(_curList.VirtualListSize - 1);
1423                                 break;
1424                             case -1:
1425                                 //制御しない
1426                                 break;
1427                             default:
1428                                 //表示位置キープ
1429                                 if (_curList.VirtualListSize > 0 && _statuses.Tabs[_curTab.Text].IndexOf(topId) > -1)
1430                                 {
1431                                     _curList.EnsureVisible(_curList.VirtualListSize - 1);
1432                                     _curList.EnsureVisible(_statuses.Tabs[_curTab.Text].IndexOf(topId));
1433                                 }
1434                                 break;
1435                         }
1436                     }
1437                 }
1438                 catch (Exception ex)
1439                 {
1440                     ex.Data["Msg"] = "Ref2";
1441                     throw;
1442                 }
1443             }
1444
1445             //新着通知
1446             NotifyNewPosts(notifyPosts,
1447                            soundFile,
1448                            addCount,
1449                            isMention || dmCount != _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage).AllCount);
1450
1451             SetMainWindowTitle();
1452             if (!StatusLabelUrl.Text.StartsWith("http")) SetStatusLabelUrl();
1453
1454             HashSupl.AddRangeItem(tw.GetHashList());
1455
1456         }
1457
1458         private long GetScrollPos(ref int smode)
1459         {
1460             long topId = -1;
1461             if (_curList != null && _curTab != null && _curList.VirtualListSize > 0)
1462             {
1463                 if (_statuses.SortMode == ComparerMode.Id)
1464                 {
1465                     if (_statuses.SortOrder == SortOrder.Ascending)
1466                     {
1467                         //Id昇順
1468                         if (ListLockMenuItem.Checked)
1469                         {
1470                             //制御しない
1471                             smode = -1;
1472                             ////現在表示位置へ強制スクロール
1473                             //if (_curList.TopItem != null) topId = _statuses.GetId(_curTab.Text, _curList.TopItem.Index);
1474                             //smode = 0;
1475                         }
1476                         else
1477                         {
1478                             //最下行が表示されていたら、最下行へ強制スクロール。最下行が表示されていなかったら制御しない
1479                             ListViewItem _item;
1480                             _item = _curList.GetItemAt(0, _curList.ClientSize.Height - 1);   //一番下
1481                             if (_item == null) _item = _curList.Items[_curList.VirtualListSize - 1];
1482                             if (_item.Index == _curList.VirtualListSize - 1)
1483                             {
1484                                 smode = -2;
1485                             }
1486                             else
1487                             {
1488                                 smode = -1;
1489                                 //if (_curList.TopItem != null) topId = _statuses.GetId(_curTab.Text, _curList.TopItem.Index);
1490                                 //smode = 0;
1491                             }
1492                         }
1493                     }
1494                     else
1495                     {
1496                         //Id降順
1497                         if (ListLockMenuItem.Checked)
1498                         {
1499                             //現在表示位置へ強制スクロール
1500                             if (_curList.TopItem != null) topId = _statuses.Tabs[_curTab.Text].GetId(_curList.TopItem.Index);
1501                             smode = 0;
1502                         }
1503                         else
1504                         {
1505                             //最上行が表示されていたら、制御しない。最上行が表示されていなかったら、現在表示位置へ強制スクロール
1506                             ListViewItem _item;
1507
1508                             _item = _curList.GetItemAt(0, 10);     //一番上
1509                             if (_item == null) _item = _curList.Items[0];
1510                             if (_item.Index == 0)
1511                             {
1512                                 smode = -3;  //最上行
1513                             }
1514                             else
1515                             {
1516                                 if (_curList.TopItem != null) topId = _statuses.Tabs[_curTab.Text].GetId(_curList.TopItem.Index);
1517                                 smode = 0;
1518                             }
1519                         }
1520                     }
1521                 }
1522                 else
1523                 {
1524                     //現在表示位置へ強制スクロール
1525                     if (_curList.TopItem != null) topId = _statuses.Tabs[_curTab.Text].GetId(_curList.TopItem.Index);
1526                     smode = 0;
1527                 }
1528             }
1529             else
1530             {
1531                 smode = -1;
1532             }
1533             return topId;
1534         }
1535
1536         private void SaveSelectedStatus(Dictionary<string, long[]> selId, Dictionary<string, Tuple<long, long>> focusedIdDict)
1537         {
1538             if (MyCommon._endingFlag) return;
1539             foreach (TabPage tab in ListTab.TabPages)
1540             {
1541                 var lst = (DetailsListView)tab.Tag;
1542                 var tabInfo = _statuses.Tabs[tab.Text];
1543                 if (lst.SelectedIndices.Count > 0 && lst.SelectedIndices.Count < 61)
1544                 {
1545                     selId.Add(tab.Text, tabInfo.GetId(lst.SelectedIndices));
1546                 }
1547                 else
1548                 {
1549                     selId.Add(tab.Text, null);
1550                 }
1551
1552                 var focusedItem = lst.FocusedItem;
1553                 var focusedId = focusedItem != null ? tabInfo.GetId(focusedItem.Index) : -2;
1554
1555                 var selectionMarkIndex = lst.SelectionMark;
1556                 var selectionMarkId = selectionMarkIndex != -1 ? tabInfo.GetId(selectionMarkIndex) : -2;
1557
1558                 focusedIdDict[tab.Text] = Tuple.Create(focusedId, selectionMarkId);
1559             }
1560
1561         }
1562
1563         private bool BalloonRequired()
1564         {
1565             Twitter.FormattedEvent ev = new Twitter.FormattedEvent();
1566             ev.Eventtype = MyCommon.EVENTTYPE.None;
1567
1568             return BalloonRequired(ev);
1569         }
1570
1571         private bool IsEventNotifyAsEventType(MyCommon.EVENTTYPE type)
1572         {
1573             return this._cfgCommon.EventNotifyEnabled && (type & this._cfgCommon.EventNotifyFlag) != 0 || type == MyCommon.EVENTTYPE.None;
1574         }
1575
1576         private bool IsMyEventNotityAsEventType(Twitter.FormattedEvent ev)
1577         {
1578             return (ev.Eventtype & this._cfgCommon.IsMyEventNotifyFlag) != 0 ? true : !ev.IsMe;
1579         }
1580
1581         private bool BalloonRequired(Twitter.FormattedEvent ev)
1582         {
1583             if ((
1584                 IsEventNotifyAsEventType(ev.Eventtype) && IsMyEventNotityAsEventType(ev) &&
1585                 (NewPostPopMenuItem.Checked || (this._cfgCommon.ForceEventNotify && ev.Eventtype != MyCommon.EVENTTYPE.None)) &&
1586                 !_initial &&
1587                 (
1588                     (
1589                         this._cfgCommon.LimitBalloon &&
1590                         (
1591                             this.WindowState == FormWindowState.Minimized ||
1592                             !this.Visible ||
1593                             Form.ActiveForm == null
1594                             )
1595                         ) ||
1596                     !this._cfgCommon.LimitBalloon
1597                     )
1598                 ) &&
1599                 !NativeMethods.IsScreenSaverRunning())
1600             {
1601                 return true;
1602             }
1603             else
1604             {
1605                 return false;
1606             }
1607         }
1608
1609         private void NotifyNewPosts(PostClass[] notifyPosts, string soundFile, int addCount, bool newMentions)
1610         {
1611             if (notifyPosts != null &&
1612                 notifyPosts.Length > 0 &&
1613                 this._cfgCommon.ReadOwnPost &&
1614                 notifyPosts.All((post) => { return post.UserId == tw.UserId || post.ScreenName == tw.Username; }))
1615             {
1616                 return;
1617             }
1618
1619             //新着通知
1620             if (BalloonRequired())
1621             {
1622                 if (notifyPosts != null && notifyPosts.Length > 0)
1623                 {
1624                     //Growlは一個ずつばらして通知。ただし、3ポスト以上あるときはまとめる
1625                     if (this._cfgCommon.IsUseNotifyGrowl)
1626                     {
1627                         StringBuilder sb = new StringBuilder();
1628                         bool reply = false;
1629                         bool dm = false;
1630
1631                         foreach (PostClass post in notifyPosts)
1632                         {
1633                             if (!(notifyPosts.Length > 3))
1634                             {
1635                                 sb.Clear();
1636                                 reply = false;
1637                                 dm = false;
1638                             }
1639                             if (post.IsReply && !post.IsExcludeReply) reply = true;
1640                             if (post.IsDm) dm = true;
1641                             if (sb.Length > 0) sb.Append(System.Environment.NewLine);
1642                             switch (this._cfgCommon.NameBalloon)
1643                             {
1644                                 case MyCommon.NameBalloonEnum.UserID:
1645                                     sb.Append(post.ScreenName).Append(" : ");
1646                                     break;
1647                                 case MyCommon.NameBalloonEnum.NickName:
1648                                     sb.Append(post.Nickname).Append(" : ");
1649                                     break;
1650                             }
1651                             sb.Append(post.TextFromApi);
1652                             if (notifyPosts.Length > 3)
1653                             {
1654                                 if (notifyPosts.Last() != post) continue;
1655                             }
1656
1657                             StringBuilder title = new StringBuilder();
1658                             GrowlHelper.NotifyType nt;
1659                             if (this._cfgCommon.DispUsername)
1660                             {
1661                                 title.Append(tw.Username);
1662                                 title.Append(" - ");
1663                             }
1664                             else
1665                             {
1666                                 //title.Clear();
1667                             }
1668                             if (dm)
1669                             {
1670                                 //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Warning;
1671                                 //NotifyIcon1.BalloonTipTitle += Application.ProductName + " [DM] " + Properties.Resources.RefreshDirectMessageText1 + " " + addCount.ToString() + Properties.Resources.RefreshDirectMessageText2;
1672                                 title.Append(Application.ProductName);
1673                                 title.Append(" [DM] ");
1674                                 title.Append(Properties.Resources.RefreshDirectMessageText1);
1675                                 title.Append(" ");
1676                                 title.Append(addCount);
1677                                 title.Append(Properties.Resources.RefreshDirectMessageText2);
1678                                 nt = GrowlHelper.NotifyType.DirectMessage;
1679                             }
1680                             else if (reply)
1681                             {
1682                                 //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Warning;
1683                                 //NotifyIcon1.BalloonTipTitle += Application.ProductName + " [Reply!] " + Properties.Resources.RefreshTimelineText1 + " " + addCount.ToString() + Properties.Resources.RefreshTimelineText2;
1684                                 title.Append(Application.ProductName);
1685                                 title.Append(" [Reply!] ");
1686                                 title.Append(Properties.Resources.RefreshTimelineText1);
1687                                 title.Append(" ");
1688                                 title.Append(addCount);
1689                                 title.Append(Properties.Resources.RefreshTimelineText2);
1690                                 nt = GrowlHelper.NotifyType.Reply;
1691                             }
1692                             else
1693                             {
1694                                 //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
1695                                 //NotifyIcon1.BalloonTipTitle += Application.ProductName + " " + Properties.Resources.RefreshTimelineText1 + " " + addCount.ToString() + Properties.Resources.RefreshTimelineText2;
1696                                 title.Append(Application.ProductName);
1697                                 title.Append(" ");
1698                                 title.Append(Properties.Resources.RefreshTimelineText1);
1699                                 title.Append(" ");
1700                                 title.Append(addCount);
1701                                 title.Append(Properties.Resources.RefreshTimelineText2);
1702                                 nt = GrowlHelper.NotifyType.Notify;
1703                             }
1704                             string bText = sb.ToString();
1705                             if (string.IsNullOrEmpty(bText)) return;
1706
1707                             var image = this.IconCache.TryGetFromCache(post.ImageUrl);
1708                             gh.Notify(nt, post.StatusId.ToString(), title.ToString(), bText, image == null ? null : image.Image, post.ImageUrl);
1709                         }
1710                     }
1711                     else
1712                     {
1713                         StringBuilder sb = new StringBuilder();
1714                         bool reply = false;
1715                         bool dm = false;
1716                         foreach (PostClass post in notifyPosts)
1717                         {
1718                             if (post.IsReply && !post.IsExcludeReply) reply = true;
1719                             if (post.IsDm) dm = true;
1720                             if (sb.Length > 0) sb.Append(System.Environment.NewLine);
1721                             switch (this._cfgCommon.NameBalloon)
1722                             {
1723                                 case MyCommon.NameBalloonEnum.UserID:
1724                                     sb.Append(post.ScreenName).Append(" : ");
1725                                     break;
1726                                 case MyCommon.NameBalloonEnum.NickName:
1727                                     sb.Append(post.Nickname).Append(" : ");
1728                                     break;
1729                             }
1730                             sb.Append(post.TextFromApi);
1731
1732                         }
1733                         //if (SettingDialog.DispUsername) { NotifyIcon1.BalloonTipTitle = tw.Username + " - "; } else { NotifyIcon1.BalloonTipTitle = ""; }
1734                         StringBuilder title = new StringBuilder();
1735                         ToolTipIcon ntIcon;
1736                         if (this._cfgCommon.DispUsername)
1737                         {
1738                             title.Append(tw.Username);
1739                             title.Append(" - ");
1740                         }
1741                         else
1742                         {
1743                             //title.Clear();
1744                         }
1745                         if (dm)
1746                         {
1747                             //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Warning;
1748                             //NotifyIcon1.BalloonTipTitle += Application.ProductName + " [DM] " + Properties.Resources.RefreshDirectMessageText1 + " " + addCount.ToString() + Properties.Resources.RefreshDirectMessageText2;
1749                             ntIcon = ToolTipIcon.Warning;
1750                             title.Append(Application.ProductName);
1751                             title.Append(" [DM] ");
1752                             title.Append(Properties.Resources.RefreshDirectMessageText1);
1753                             title.Append(" ");
1754                             title.Append(addCount);
1755                             title.Append(Properties.Resources.RefreshDirectMessageText2);
1756                         }
1757                         else if (reply)
1758                         {
1759                             //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Warning;
1760                             //NotifyIcon1.BalloonTipTitle += Application.ProductName + " [Reply!] " + Properties.Resources.RefreshTimelineText1 + " " + addCount.ToString() + Properties.Resources.RefreshTimelineText2;
1761                             ntIcon = ToolTipIcon.Warning;
1762                             title.Append(Application.ProductName);
1763                             title.Append(" [Reply!] ");
1764                             title.Append(Properties.Resources.RefreshTimelineText1);
1765                             title.Append(" ");
1766                             title.Append(addCount);
1767                             title.Append(Properties.Resources.RefreshTimelineText2);
1768                         }
1769                         else
1770                         {
1771                             //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
1772                             //NotifyIcon1.BalloonTipTitle += Application.ProductName + " " + Properties.Resources.RefreshTimelineText1 + " " + addCount.ToString() + Properties.Resources.RefreshTimelineText2;
1773                             ntIcon = ToolTipIcon.Info;
1774                             title.Append(Application.ProductName);
1775                             title.Append(" ");
1776                             title.Append(Properties.Resources.RefreshTimelineText1);
1777                             title.Append(" ");
1778                             title.Append(addCount);
1779                             title.Append(Properties.Resources.RefreshTimelineText2);
1780                         }
1781                         string bText = sb.ToString();
1782                         if (string.IsNullOrEmpty(bText)) return;
1783                         //NotifyIcon1.BalloonTipText = sb.ToString();
1784                         //NotifyIcon1.ShowBalloonTip(500);
1785                         NotifyIcon1.BalloonTipTitle = title.ToString();
1786                         NotifyIcon1.BalloonTipText = bText;
1787                         NotifyIcon1.BalloonTipIcon = ntIcon;
1788                         NotifyIcon1.ShowBalloonTip(500);
1789                     }
1790                 }
1791             }
1792
1793             //サウンド再生
1794             if (!_initial && this._cfgCommon.PlaySound && !string.IsNullOrEmpty(soundFile))
1795             {
1796                 try
1797                 {
1798                     string dir = Application.StartupPath;
1799                     if (Directory.Exists(Path.Combine(dir, "Sounds")))
1800                     {
1801                         dir = Path.Combine(dir, "Sounds");
1802                     }
1803                     using (SoundPlayer player = new SoundPlayer(Path.Combine(dir, soundFile)))
1804                     {
1805                         player.Play();
1806                     }
1807                 }
1808                 catch (Exception)
1809                 {
1810                 }
1811             }
1812
1813             //mentions新着時に画面ブリンク
1814             if (!_initial && this._cfgCommon.BlinkNewMentions && newMentions && Form.ActiveForm == null)
1815             {
1816                 NativeMethods.FlashMyWindow(this.Handle, NativeMethods.FlashSpecification.FlashTray, 3);
1817             }
1818         }
1819
1820         private void MyList_SelectedIndexChanged(object sender, EventArgs e)
1821         {
1822             if (_curList == null || !_curList.Equals(sender) || _curList.SelectedIndices.Count != 1) return;
1823
1824             _curItemIndex = _curList.SelectedIndices[0];
1825             if (_curItemIndex > _curList.VirtualListSize - 1) return;
1826
1827             try
1828             {
1829                 _curPost = GetCurTabPost(_curItemIndex);
1830             }
1831             catch (ArgumentException)
1832             {
1833                 return;
1834             }
1835
1836             this.PushSelectPostChain();
1837
1838             this._statuses.SetReadAllTab(_curPost.StatusId, read: true);
1839             //キャッシュの書き換え
1840             ChangeCacheStyleRead(true, _curItemIndex);   //既読へ(フォント、文字色)
1841
1842             ColorizeList();
1843             _colorize = true;
1844         }
1845
1846         private void ChangeCacheStyleRead(bool Read, int Index)
1847         {
1848             var tabInfo = _statuses.Tabs[_curTab.Text];
1849             //Read:true=既読 false=未読
1850             //未読管理していなかったら既読として扱う
1851             if (!tabInfo.UnreadManage ||
1852                !this._cfgCommon.UnreadManage) Read = true;
1853
1854             //対象の特定
1855             ListViewItem itm = null;
1856             PostClass post = null;
1857
1858             this.TryGetListViewItemCache(Index, out itm, out post);
1859
1860             // キャッシュに含まれていないアイテムは対象外
1861             if (itm == null)
1862                 return;
1863
1864             ChangeItemStyleRead(Read, itm, post, ((DetailsListView)_curTab.Tag));
1865         }
1866
1867         private void ChangeItemStyleRead(bool Read, ListViewItem Item, PostClass Post, DetailsListView DList)
1868         {
1869             Font fnt;
1870             //フォント
1871             if (Read)
1872             {
1873                 fnt = _fntReaded;
1874                 Item.SubItems[5].Text = "";
1875             }
1876             else
1877             {
1878                 fnt = _fntUnread;
1879                 Item.SubItems[5].Text = "★";
1880             }
1881             //文字色
1882             Color cl;
1883             if (Post.IsFav)
1884                 cl = _clFav;
1885             else if (Post.RetweetedId != null)
1886                 cl = _clRetweet;
1887             else if (Post.IsOwl && (Post.IsDm || this._cfgCommon.OneWayLove))
1888                 cl = _clOWL;
1889             else if (Read || !this._cfgCommon.UseUnreadStyle)
1890                 cl = _clReaded;
1891             else
1892                 cl = _clUnread;
1893
1894             if (DList == null || Item.Index == -1)
1895             {
1896                 Item.ForeColor = cl;
1897                 if (this._cfgCommon.UseUnreadStyle)
1898                     Item.Font = fnt;
1899             }
1900             else
1901             {
1902                 DList.Update();
1903                 if (this._cfgCommon.UseUnreadStyle)
1904                     DList.ChangeItemFontAndColor(Item.Index, cl, fnt);
1905                 else
1906                     DList.ChangeItemForeColor(Item.Index, cl);
1907                 //if (_itemCache != null) DList.RedrawItems(_itemCacheIndex, _itemCacheIndex + _itemCache.Length - 1, false);
1908             }
1909         }
1910
1911         private void ColorizeList()
1912         {
1913             //Index:更新対象のListviewItem.Index。Colorを返す。
1914             //-1は全キャッシュ。Colorは返さない(ダミーを戻す)
1915             PostClass _post;
1916             if (_anchorFlag)
1917                 _post = _anchorPost;
1918             else
1919                 _post = _curPost;
1920
1921             if (_post == null) return;
1922
1923             var itemColors = new Color[] { };
1924             int itemIndex = -1;
1925
1926             this.itemCacheLock.EnterReadLock();
1927             try
1928             {
1929                 if (this._itemCache == null) return;
1930
1931                 var query = 
1932                     from i in Enumerable.Range(0, this._itemCache.Length)
1933                     select this.JudgeColor(_post, this._postCache[i]);
1934                 
1935                 itemColors = query.ToArray();
1936                 itemIndex = _itemCacheIndex;
1937             }
1938             finally { this.itemCacheLock.ExitReadLock(); }
1939
1940             if (itemIndex < 0) return;
1941
1942             foreach (var backColor in itemColors)
1943             {
1944                 // この処理中に MyList_CacheVirtualItems が呼ばれることがあるため、
1945                 // 同一スレッド内での二重ロックを避けるためにロックの外で実行する必要がある
1946                 _curList.ChangeItemBackColor(itemIndex++, backColor);
1947             }
1948         }
1949
1950         private void ColorizeList(ListViewItem Item, int Index)
1951         {
1952             //Index:更新対象のListviewItem.Index。Colorを返す。
1953             //-1は全キャッシュ。Colorは返さない(ダミーを戻す)
1954             PostClass _post;
1955             if (_anchorFlag)
1956                 _post = _anchorPost;
1957             else
1958                 _post = _curPost;
1959
1960             PostClass tPost = GetCurTabPost(Index);
1961
1962             if (_post == null) return;
1963
1964             if (Item.Index == -1)
1965                 Item.BackColor = JudgeColor(_post, tPost);
1966             else
1967                 _curList.ChangeItemBackColor(Item.Index, JudgeColor(_post, tPost));
1968         }
1969
1970         private Color JudgeColor(PostClass BasePost, PostClass TargetPost)
1971         {
1972             Color cl;
1973             if (TargetPost.StatusId == BasePost.InReplyToStatusId)
1974                 //@先
1975                 cl = _clAtTo;
1976             else if (TargetPost.IsMe)
1977                 //自分=発言者
1978                 cl = _clSelf;
1979             else if (TargetPost.IsReply)
1980                 //自分宛返信
1981                 cl = _clAtSelf;
1982             else if (BasePost.ReplyToList.Contains(TargetPost.ScreenName.ToLower()))
1983                 //返信先
1984                 cl = _clAtFromTarget;
1985             else if (TargetPost.ReplyToList.Contains(BasePost.ScreenName.ToLower()))
1986                 //その人への返信
1987                 cl = _clAtTarget;
1988             else if (TargetPost.ScreenName.Equals(BasePost.ScreenName, StringComparison.OrdinalIgnoreCase))
1989                 //発言者
1990                 cl = _clTarget;
1991             else
1992                 //その他
1993                 cl = _clListBackcolor;
1994
1995             return cl;
1996         }
1997
1998         private async void PostButton_Click(object sender, EventArgs e)
1999         {
2000             if (StatusText.Text.Trim().Length == 0)
2001             {
2002                 if (!ImageSelector.Enabled)
2003                 {
2004                     this.DoRefresh();
2005                     return;
2006                 }
2007             }
2008
2009             if (this.ExistCurrentPost && StatusText.Text.Trim() == string.Format("RT @{0}: {1}", _curPost.ScreenName, _curPost.TextFromApi))
2010             {
2011                 DialogResult rtResult = MessageBox.Show(string.Format(Properties.Resources.PostButton_Click1, Environment.NewLine),
2012                                                                "Retweet",
2013                                                                MessageBoxButtons.YesNoCancel,
2014                                                                MessageBoxIcon.Question);
2015                 switch (rtResult)
2016                 {
2017                     case DialogResult.Yes:
2018                         StatusText.Text = "";
2019                         await this.doReTweetOfficial(false);
2020                         return;
2021                     case DialogResult.Cancel:
2022                         return;
2023                 }
2024             }
2025
2026             var inReplyToStatusId = this.inReplyTo?.Item1;
2027             var inReplyToScreenName = this.inReplyTo?.Item2;
2028             _history[_history.Count - 1] = new PostingStatus(StatusText.Text, inReplyToStatusId, inReplyToScreenName);
2029
2030             if (this._cfgCommon.Nicoms)
2031             {
2032                 StatusText.SelectionStart = StatusText.Text.Length;
2033                 await UrlConvertAsync(MyCommon.UrlConverter.Nicoms);
2034             }
2035             //if (SettingDialog.UrlConvertAuto)
2036             //{
2037             //    StatusText.SelectionStart = StatusText.Text.Length;
2038             //    UrlConvertAutoToolStripMenuItem_Click(null, null);
2039             //}
2040             //else if (SettingDialog.Nicoms)
2041             //{
2042             //    StatusText.SelectionStart = StatusText.Text.Length;
2043             //    UrlConvert(UrlConverter.Nicoms);
2044             //}
2045             StatusText.SelectionStart = StatusText.Text.Length;
2046             CheckReplyTo(StatusText.Text);
2047
2048             //整形によって増加する文字数を取得
2049             int adjustCount = 0;
2050             string tmpStatus = StatusText.Text.Trim();
2051             if (ToolStripMenuItemApiCommandEvasion.Checked)
2052             {
2053                 // APIコマンド回避
2054                 if (Regex.IsMatch(tmpStatus,
2055                     @"^[+\-\[\]\s\\.,*/(){}^~|='&%$#""<>?]*(get|g|fav|follow|f|on|off|stop|quit|leave|l|whois|w|nudge|n|stats|invite|track|untrack|tracks|tracking|\*)([+\-\[\]\s\\.,*/(){}^~|='&%$#""<>?]+|$)",
2056                     RegexOptions.IgnoreCase)
2057                    && tmpStatus.EndsWith(" .") == false) adjustCount += 2;
2058             }
2059
2060             if (ToolStripMenuItemUrlMultibyteSplit.Checked)
2061             {
2062                 // URLと全角文字の切り離し
2063                 adjustCount += Regex.Matches(tmpStatus, @"https?:\/\/[-_.!~*'()a-zA-Z0-9;\/?:\@&=+\$,%#^]+").Count;
2064             }
2065
2066             bool isCutOff = false;
2067             bool isRemoveFooter = MyCommon.IsKeyDown(Keys.Shift);
2068             if (StatusText.Multiline && !this._cfgCommon.PostCtrlEnter)
2069             {
2070                 //複数行でEnter投稿の場合、Ctrlも押されていたらフッタ付加しない
2071                 isRemoveFooter = MyCommon.IsKeyDown(Keys.Control);
2072             }
2073             if (this._cfgCommon.PostShiftEnter)
2074             {
2075                 isRemoveFooter = MyCommon.IsKeyDown(Keys.Control);
2076             }
2077             if (!isRemoveFooter && (StatusText.Text.Contains("RT @") || StatusText.Text.Contains("QT @")))
2078             {
2079                 isRemoveFooter = true;
2080             }
2081             if (GetRestStatusCount(false, !isRemoveFooter) - adjustCount < 0)
2082             {
2083                 if (MessageBox.Show(Properties.Resources.PostLengthOverMessage1, Properties.Resources.PostLengthOverMessage2, MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.OK)
2084                 {
2085                     isCutOff = true;
2086                     //if (!SettingDialog.UrlConvertAuto) UrlConvertAutoToolStripMenuItem_Click(null, null);
2087                     if (GetRestStatusCount(false, !isRemoveFooter) - adjustCount < 0)
2088                     {
2089                         isRemoveFooter = true;
2090                     }
2091                 }
2092                 else
2093                 {
2094                     return;
2095                 }
2096             }
2097
2098             string footer = "";
2099             string header = "";
2100             if (StatusText.Text.StartsWith("D ") || StatusText.Text.StartsWith("d "))
2101             {
2102                 //DM時は何もつけない
2103                 footer = "";
2104             }
2105             else
2106             {
2107                 //ハッシュタグ
2108                 if (HashMgr.IsNotAddToAtReply)
2109                 {
2110                     if (!string.IsNullOrEmpty(HashMgr.UseHash) && this.inReplyTo == null)
2111                     {
2112                         if (HashMgr.IsHead)
2113                             header = HashMgr.UseHash + " ";
2114                         else
2115                             footer = " " + HashMgr.UseHash;
2116                     }
2117                 }
2118                 else
2119                 {
2120                     if (!string.IsNullOrEmpty(HashMgr.UseHash))
2121                     {
2122                         if (HashMgr.IsHead)
2123                             header = HashMgr.UseHash + " ";
2124                         else
2125                             footer = " " + HashMgr.UseHash;
2126                     }
2127                 }
2128                 if (!isRemoveFooter)
2129                 {
2130                     if (this._cfgLocal.UseRecommendStatus)
2131                     {
2132                         // 推奨ステータスを使用する
2133                         footer += this.recommendedStatusFooter;
2134                     }
2135                     else if (!string.IsNullOrEmpty(this._cfgLocal.StatusText))
2136                     {
2137                         // テキストボックスに入力されている文字列を使用する
2138                         footer += " " + this._cfgLocal.StatusText.Trim();
2139                     }
2140                 }
2141             }
2142
2143             var status = new PostingStatus();
2144             status.status = header + StatusText.Text + footer;
2145
2146             if (ToolStripMenuItemApiCommandEvasion.Checked)
2147             {
2148                 // APIコマンド回避
2149                 if (Regex.IsMatch(status.status,
2150                     @"^[+\-\[\]\s\\.,*/(){}^~|='&%$#""<>?]*(get|g|fav|follow|f|on|off|stop|quit|leave|l|whois|w|nudge|n|stats|invite|track|untrack|tracks|tracking|\*)([+\-\[\]\s\\.,*/(){}^~|='&%$#""<>?]+|$)",
2151                     RegexOptions.IgnoreCase)
2152                    && status.status.EndsWith(" .") == false) status.status += " .";
2153             }
2154
2155             if (ToolStripMenuItemUrlMultibyteSplit.Checked)
2156             {
2157                 // URLと全角文字の切り離し
2158                 Match mc2 = Regex.Match(status.status, @"https?:\/\/[-_.!~*'()a-zA-Z0-9;\/?:\@&=+\$,%#^]+");
2159                 if (mc2.Success) status.status = Regex.Replace(status.status, @"https?:\/\/[-_.!~*'()a-zA-Z0-9;\/?:\@&=+\$,%#^]+", "$& ");
2160             }
2161
2162             if (IdeographicSpaceToSpaceToolStripMenuItem.Checked)
2163             {
2164                 // 文中の全角スペースを半角スペース1個にする
2165                 status.status = status.status.Replace(" ", " ");
2166             }
2167
2168             if (isCutOff && status.status.Length > 140)
2169             {
2170                 status.status = status.status.Substring(0, 140);
2171                 string AtId = @"(@|@)[a-z0-9_/]+$";
2172                 string HashTag = @"(^|[^0-9A-Z&\/\?]+)(#|#)([0-9A-Z_]*[A-Z_]+)$";
2173                 string Url = @"https?:\/\/[a-z0-9!\*'\(\);:&=\+\$\/%#\[\]\-_\.,~?]+$"; //簡易判定
2174                 string pattern = string.Format("({0})|({1})|({2})", AtId, HashTag, Url);
2175                 Match mc = Regex.Match(status.status, pattern, RegexOptions.IgnoreCase);
2176                 if (mc.Success)
2177                 {
2178                     //さらに@ID、ハッシュタグ、URLと推測される文字列をカットする
2179                     status.status = status.status.Substring(0, 140 - mc.Value.Length);
2180                 }
2181                 if (MessageBox.Show(status.status, "Post or Cancel?", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel) return;
2182             }
2183
2184             status.inReplyToId = this.inReplyTo?.Item1;
2185             status.inReplyToName = this.inReplyTo?.Item2;
2186             if (ImageSelector.Visible)
2187             {
2188                 //画像投稿
2189                 if (!ImageSelector.TryGetSelectedMedia(out status.imageService, out status.mediaItems))
2190                     return;
2191             }
2192
2193             this.inReplyTo = null;
2194             StatusText.Text = "";
2195             _history.Add(new PostingStatus());
2196             _hisIdx = _history.Count - 1;
2197             if (!ToolStripFocusLockMenuItem.Checked)
2198                 ((Control)ListTab.SelectedTab.Tag).Focus();
2199             urlUndoBuffer = null;
2200             UrlUndoToolStripMenuItem.Enabled = false;  //Undoをできないように設定
2201
2202             //Google検索(試験実装)
2203             if (StatusText.Text.StartsWith("Google:", StringComparison.OrdinalIgnoreCase) && StatusText.Text.Trim().Length > 7)
2204             {
2205                 string tmp = string.Format(Properties.Resources.SearchItem2Url, Uri.EscapeDataString(StatusText.Text.Substring(7)));
2206                 await this.OpenUriInBrowserAsync(tmp);
2207             }
2208
2209             await this.PostMessageAsync(status);
2210         }
2211
2212         private void EndToolStripMenuItem_Click(object sender, EventArgs e)
2213         {
2214             MyCommon._endingFlag = true;
2215             this.Close();
2216         }
2217
2218         private void TweenMain_FormClosing(object sender, FormClosingEventArgs e)
2219         {
2220             if (!this._cfgCommon.CloseToExit && e.CloseReason == CloseReason.UserClosing && MyCommon._endingFlag == false)
2221             {
2222                 //_endingFlag=false:フォームの×ボタン
2223                 e.Cancel = true;
2224                 this.Visible = false;
2225             }
2226             else
2227             {
2228                 _hookGlobalHotkey.UnregisterAllOriginalHotkey();
2229                 _ignoreConfigSave = true;
2230                 MyCommon._endingFlag = true;
2231                 TimerTimeline.Enabled = false;
2232                 TimerRefreshIcon.Enabled = false;
2233             }
2234         }
2235
2236         private void NotifyIcon1_BalloonTipClicked(object sender, EventArgs e)
2237         {
2238             this.Visible = true;
2239             if (this.WindowState == FormWindowState.Minimized)
2240             {
2241                 this.WindowState = FormWindowState.Normal;
2242             }
2243             this.Activate();
2244             this.BringToFront();
2245         }
2246
2247         private static int errorCount = 0;
2248
2249         private static bool CheckAccountValid()
2250         {
2251             if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid)
2252             {
2253                 errorCount += 1;
2254                 if (errorCount > 5)
2255                 {
2256                     errorCount = 0;
2257                     Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
2258                     return true;
2259                 }
2260                 return false;
2261             }
2262             errorCount = 0;
2263             return true;
2264         }
2265
2266         private Task GetHomeTimelineAsync()
2267         {
2268             return this.GetHomeTimelineAsync(loadMore: false);
2269         }
2270
2271         private async Task GetHomeTimelineAsync(bool loadMore)
2272         {
2273             await this.workerSemaphore.WaitAsync();
2274
2275             try
2276             {
2277                 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2278
2279                 await this.GetHomeTimelineAsyncInternal(progress, this.workerCts.Token, loadMore);
2280             }
2281             catch (WebApiException ex)
2282             {
2283                 this._myStatusError = true;
2284                 this.StatusLabel.Text = ex.Message;
2285             }
2286             finally
2287             {
2288                 this.workerSemaphore.Release();
2289             }
2290         }
2291
2292         private async Task GetHomeTimelineAsyncInternal(IProgress<string> p, CancellationToken ct, bool loadMore)
2293         {
2294             if (ct.IsCancellationRequested)
2295                 return;
2296
2297             if (!CheckAccountValid())
2298                 throw new WebApiException("Auth error. Check your account");
2299
2300             bool read;
2301             if (!this._cfgCommon.UnreadManage)
2302                 read = true;
2303             else
2304                 read = this._initial && this._cfgCommon.Read;
2305
2306             p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText5 +
2307                 (loadMore ? "-1" : "1") +
2308                 Properties.Resources.GetTimelineWorker_RunWorkerCompletedText6);
2309
2310             await Task.Run(() =>
2311             {
2312                 this.tw.GetTimelineApi(read, MyCommon.WORKERTYPE.Timeline, loadMore, this._initial);
2313
2314                 // 新着時未読クリア
2315                 if (this._cfgCommon.ReadOldPosts)
2316                     this._statuses.SetReadHomeTab();
2317
2318                 var addCount = this._statuses.DistributePosts();
2319
2320                 if (!this._initial)
2321                 {
2322                     lock (this._syncObject)
2323                     {
2324                         var tm = DateTime.Now;
2325                         if (this._tlTimestamps.ContainsKey(tm))
2326                             this._tlTimestamps[tm] += addCount;
2327                         else
2328                             this._tlTimestamps[tm] = addCount;
2329
2330                         var removeKeys = new List<DateTime>();
2331                         var oneHour = DateTime.Now - TimeSpan.FromHours(1);
2332
2333                         this._tlCount = 0;
2334                         foreach (var pair in this._tlTimestamps)
2335                         {
2336                             if (pair.Key < oneHour)
2337                                 removeKeys.Add(pair.Key);
2338                             else
2339                                 this._tlCount += pair.Value;
2340                         }
2341
2342                         foreach (var key in removeKeys)
2343                             this._tlTimestamps.Remove(key);
2344                     }
2345                 }
2346             });
2347
2348             if (ct.IsCancellationRequested)
2349                 return;
2350
2351             p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText1);
2352
2353             this.RefreshTimeline(false);
2354         }
2355
2356         private Task GetReplyAsync()
2357         {
2358             return this.GetReplyAsync(loadMore: false);
2359         }
2360
2361         private async Task GetReplyAsync(bool loadMore)
2362         {
2363             await this.workerSemaphore.WaitAsync();
2364
2365             try
2366             {
2367                 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2368
2369                 await this.GetReplyAsyncInternal(progress, this.workerCts.Token, loadMore);
2370             }
2371             catch (WebApiException ex)
2372             {
2373                 this._myStatusError = true;
2374                 this.StatusLabel.Text = ex.Message;
2375             }
2376             finally
2377             {
2378                 this.workerSemaphore.Release();
2379             }
2380         }
2381
2382         private async Task GetReplyAsyncInternal(IProgress<string> p, CancellationToken ct, bool loadMore)
2383         {
2384             if (ct.IsCancellationRequested)
2385                 return;
2386
2387             if (!CheckAccountValid())
2388                 throw new WebApiException("Auth error. Check your account");
2389
2390             bool read;
2391             if (!this._cfgCommon.UnreadManage)
2392                 read = true;
2393             else
2394                 read = this._initial && this._cfgCommon.Read;
2395
2396             p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText4 +
2397                 (loadMore ? "-1" : "1") +
2398                 Properties.Resources.GetTimelineWorker_RunWorkerCompletedText6);
2399
2400             await Task.Run(() =>
2401             {
2402                 this.tw.GetTimelineApi(read, MyCommon.WORKERTYPE.Reply, loadMore, this._initial);
2403
2404                 this._statuses.DistributePosts();
2405             });
2406
2407             if (ct.IsCancellationRequested)
2408                 return;
2409
2410             p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText9);
2411
2412             this.RefreshTimeline(false);
2413         }
2414
2415         private Task GetDirectMessagesAsync()
2416         {
2417             return this.GetDirectMessagesAsync(loadMore: false);
2418         }
2419
2420         private async Task GetDirectMessagesAsync(bool loadMore)
2421         {
2422             await this.workerSemaphore.WaitAsync();
2423
2424             try
2425             {
2426                 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2427
2428                 await this.GetDirectMessagesAsyncInternal(progress, this.workerCts.Token, loadMore);
2429             }
2430             catch (WebApiException ex)
2431             {
2432                 this._myStatusError = true;
2433                 this.StatusLabel.Text = ex.Message;
2434             }
2435             finally
2436             {
2437                 this.workerSemaphore.Release();
2438             }
2439         }
2440
2441         private async Task GetDirectMessagesAsyncInternal(IProgress<string> p, CancellationToken ct, bool loadMore)
2442         {
2443             if (ct.IsCancellationRequested)
2444                 return;
2445
2446             if (!CheckAccountValid())
2447                 throw new WebApiException("Auth error. Check your account");
2448
2449             bool read;
2450             if (!this._cfgCommon.UnreadManage)
2451                 read = true;
2452             else
2453                 read = this._initial && this._cfgCommon.Read;
2454
2455             p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText8 +
2456                 (loadMore ? "-1" : "1") +
2457                 Properties.Resources.GetTimelineWorker_RunWorkerCompletedText6);
2458
2459             await Task.Run(() =>
2460             {
2461                 this.tw.GetDirectMessageApi(read, MyCommon.WORKERTYPE.DirectMessegeRcv, loadMore);
2462                 this.tw.GetDirectMessageApi(read, MyCommon.WORKERTYPE.DirectMessegeSnt, loadMore);
2463
2464                 this._statuses.DistributePosts();
2465             });
2466
2467             if (ct.IsCancellationRequested)
2468                 return;
2469
2470             p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText11);
2471
2472             this.RefreshTimeline(false);
2473         }
2474
2475         private Task GetFavoritesAsync()
2476         {
2477             return this.GetFavoritesAsync(loadMore: false);
2478         }
2479
2480         private async Task GetFavoritesAsync(bool loadMore)
2481         {
2482             await this.workerSemaphore.WaitAsync();
2483
2484             try
2485             {
2486                 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2487
2488                 await this.GetFavoritesAsyncInternal(progress, this.workerCts.Token, loadMore);
2489             }
2490             catch (WebApiException ex)
2491             {
2492                 this._myStatusError = true;
2493                 this.StatusLabel.Text = ex.Message;
2494             }
2495             finally
2496             {
2497                 this.workerSemaphore.Release();
2498             }
2499         }
2500
2501         private async Task GetFavoritesAsyncInternal(IProgress<string> p, CancellationToken ct, bool loadMore)
2502         {
2503             if (ct.IsCancellationRequested)
2504                 return;
2505
2506             if (!CheckAccountValid())
2507                 throw new WebApiException("Auth error. Check your account");
2508
2509             bool read;
2510             if (!this._cfgCommon.UnreadManage)
2511                 read = true;
2512             else
2513                 read = this._initial && this._cfgCommon.Read;
2514
2515             p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText19);
2516
2517             await Task.Run(() =>
2518             {
2519                 this.tw.GetFavoritesApi(read, loadMore);
2520
2521                 this._statuses.DistributePosts();
2522             });
2523
2524             if (ct.IsCancellationRequested)
2525                 return;
2526
2527             p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText20);
2528
2529             this.RefreshTimeline(false);
2530         }
2531
2532         private Task GetPublicSearchAllAsync()
2533         {
2534             return this.GetPublicSearchAsync(null, loadMore: false);
2535         }
2536
2537         private Task GetPublicSearchAsync(TabClass tab)
2538         {
2539             return this.GetPublicSearchAsync(tab, loadMore: false);
2540         }
2541
2542         private async Task GetPublicSearchAsync(TabClass tab, bool loadMore)
2543         {
2544             await this.workerSemaphore.WaitAsync();
2545
2546             try
2547             {
2548                 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2549
2550                 var tabs = tab != null
2551                     ? new[] { tab }.AsEnumerable()
2552                     : this._statuses.GetTabsByType(MyCommon.TabUsageType.PublicSearch);
2553
2554                 await this.GetPublicSearchAsyncInternal(progress, this.workerCts.Token, tabs, loadMore);
2555             }
2556             catch (WebApiException ex)
2557             {
2558                 this._myStatusError = true;
2559                 this.StatusLabel.Text = ex.Message;
2560             }
2561             finally
2562             {
2563                 this.workerSemaphore.Release();
2564             }
2565         }
2566
2567         private async Task GetPublicSearchAsyncInternal(IProgress<string> p, CancellationToken ct, IEnumerable<TabClass> tabs, bool loadMore)
2568         {
2569             if (ct.IsCancellationRequested)
2570                 return;
2571
2572             if (!CheckAccountValid())
2573                 throw new WebApiException("Auth error. Check your account");
2574
2575             bool read;
2576             if (!this._cfgCommon.UnreadManage)
2577                 read = true;
2578             else
2579                 read = this._initial && this._cfgCommon.Read;
2580
2581             p.Report("Search refreshing...");
2582
2583             await Task.Run(() =>
2584             {
2585                 foreach (var tab in tabs)
2586                 {
2587                     if (string.IsNullOrEmpty(tab.SearchWords))
2588                         continue;
2589
2590                     this.tw.GetSearch(read, tab, false);
2591
2592                     if (loadMore)
2593                         this.tw.GetSearch(read, tab, true);
2594                 }
2595
2596                 this._statuses.DistributePosts();
2597             });
2598
2599             if (ct.IsCancellationRequested)
2600                 return;
2601
2602             p.Report("Search refreshed");
2603
2604             this.RefreshTimeline(false);
2605         }
2606
2607         private Task GetUserTimelineAllAsync()
2608         {
2609             return this.GetUserTimelineAsync(null, loadMore: false);
2610         }
2611
2612         private Task GetUserTimelineAsync(TabClass tab)
2613         {
2614             return this.GetUserTimelineAsync(tab, loadMore: false);
2615         }
2616
2617         private async Task GetUserTimelineAsync(TabClass tab, bool loadMore)
2618         {
2619             await this.workerSemaphore.WaitAsync();
2620
2621             try
2622             {
2623                 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2624
2625                 var tabs = tab != null
2626                     ? new[] { tab }.AsEnumerable()
2627                     : this._statuses.GetTabsByType(MyCommon.TabUsageType.UserTimeline);
2628
2629                 await this.GetUserTimelineAsyncInternal(progress, this.workerCts.Token, tabs, loadMore);
2630             }
2631             catch (WebApiException ex)
2632             {
2633                 this._myStatusError = true;
2634                 this.StatusLabel.Text = ex.Message;
2635             }
2636             finally
2637             {
2638                 this.workerSemaphore.Release();
2639             }
2640         }
2641
2642         private async Task GetUserTimelineAsyncInternal(IProgress<string> p, CancellationToken ct, IEnumerable<TabClass> tabs, bool loadMore)
2643         {
2644             if (ct.IsCancellationRequested)
2645                 return;
2646
2647             if (!CheckAccountValid())
2648                 throw new WebApiException("Auth error. Check your account");
2649
2650             bool read;
2651             if (!this._cfgCommon.UnreadManage)
2652                 read = true;
2653             else
2654                 read = this._initial && this._cfgCommon.Read;
2655
2656             p.Report("UserTimeline refreshing...");
2657
2658             await Task.Run(() =>
2659             {
2660                 foreach (var tab in tabs)
2661                 {
2662                     if (string.IsNullOrEmpty(tab.User))
2663                         continue;
2664
2665                     this.tw.GetUserTimelineApi(read, tab.User, tab, loadMore);
2666                 }
2667
2668                 this._statuses.DistributePosts();
2669             });
2670
2671             if (ct.IsCancellationRequested)
2672                 return;
2673
2674             p.Report("UserTimeline refreshed");
2675
2676             this.RefreshTimeline(false);
2677         }
2678
2679         private Task GetListTimelineAllAsync()
2680         {
2681             return this.GetListTimelineAsync(null, loadMore: false);
2682         }
2683
2684         private Task GetListTimelineAsync(TabClass tab)
2685         {
2686             return this.GetListTimelineAsync(tab, loadMore: false);
2687         }
2688
2689         private async Task GetListTimelineAsync(TabClass tab, bool loadMore)
2690         {
2691             await this.workerSemaphore.WaitAsync();
2692
2693             try
2694             {
2695                 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2696
2697                 var tabs = tab != null
2698                     ? new[] { tab }.AsEnumerable()
2699                     : this._statuses.GetTabsByType(MyCommon.TabUsageType.Lists);
2700
2701                 await this.GetListTimelineAsyncInternal(progress, this.workerCts.Token, tabs, loadMore);
2702             }
2703             catch (WebApiException ex)
2704             {
2705                 this._myStatusError = true;
2706                 this.StatusLabel.Text = ex.Message;
2707             }
2708             finally
2709             {
2710                 this.workerSemaphore.Release();
2711             }
2712         }
2713
2714         private async Task GetListTimelineAsyncInternal(IProgress<string> p, CancellationToken ct, IEnumerable<TabClass> tabs, bool loadMore)
2715         {
2716             if (ct.IsCancellationRequested)
2717                 return;
2718
2719             if (!CheckAccountValid())
2720                 throw new WebApiException("Auth error. Check your account");
2721
2722             bool read;
2723             if (!this._cfgCommon.UnreadManage)
2724                 read = true;
2725             else
2726                 read = this._initial && this._cfgCommon.Read;
2727
2728             p.Report("List refreshing...");
2729
2730             await Task.Run(() =>
2731             {
2732                 foreach (var tab in tabs)
2733                 {
2734                     if (tab.ListInfo == null || tab.ListInfo.Id == 0)
2735                         continue;
2736
2737                     this.tw.GetListStatus(read, tab, loadMore, this._initial);
2738                 }
2739
2740                 this._statuses.DistributePosts();
2741             });
2742
2743             if (ct.IsCancellationRequested)
2744                 return;
2745
2746             p.Report("List refreshed");
2747
2748             this.RefreshTimeline(false);
2749         }
2750
2751         private async Task GetRelatedTweetsAsync(TabClass tab)
2752         {
2753             await this.workerSemaphore.WaitAsync();
2754
2755             try
2756             {
2757                 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2758
2759                 await this.GetRelatedTweetsAsyncInternal(progress, this.workerCts.Token, tab);
2760             }
2761             catch (WebApiException ex)
2762             {
2763                 this._myStatusError = true;
2764                 this.StatusLabel.Text = ex.Message;
2765             }
2766             finally
2767             {
2768                 this.workerSemaphore.Release();
2769             }
2770         }
2771
2772         private async Task GetRelatedTweetsAsyncInternal(IProgress<string> p, CancellationToken ct, TabClass tab)
2773         {
2774             if (ct.IsCancellationRequested)
2775                 return;
2776
2777             if (!CheckAccountValid())
2778                 throw new WebApiException("Auth error. Check your account");
2779
2780             bool read;
2781             if (!this._cfgCommon.UnreadManage)
2782                 read = true;
2783             else
2784                 read = this._initial && this._cfgCommon.Read;
2785
2786             p.Report("Related refreshing...");
2787
2788             await Task.Run(() =>
2789             {
2790                 this.tw.GetRelatedResult(read, tab);
2791
2792                 this._statuses.DistributePosts();
2793             });
2794
2795             if (ct.IsCancellationRequested)
2796                 return;
2797
2798             p.Report("Related refreshed");
2799
2800             this.RefreshTimeline(false);
2801
2802             var tabPage = this.ListTab.TabPages.Cast<TabPage>()
2803                 .FirstOrDefault(x => x.Text == tab.TabName);
2804
2805             if (tabPage != null)
2806             {
2807                 // TODO: 非同期更新中にタブが閉じられている場合を厳密に考慮したい
2808
2809                 var listView = (DetailsListView)tabPage.Tag;
2810                 var index = tab.IndexOf(tab.RelationTargetPost.RetweetedId ?? tab.RelationTargetPost.StatusId);
2811
2812                 if (index != -1 && index < listView.Items.Count)
2813                 {
2814                     listView.SelectedIndices.Add(index);
2815                     listView.Items[index].Focused = true;
2816                 }
2817             }
2818         }
2819
2820         private async Task FavAddAsync(IReadOnlyList<long> statusIds, TabClass tab)
2821         {
2822             await this.workerSemaphore.WaitAsync();
2823
2824             try
2825             {
2826                 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2827
2828                 await this.FavAddAsyncInternal(progress, this.workerCts.Token, statusIds, tab);
2829             }
2830             catch (WebApiException ex)
2831             {
2832                 this._myStatusError = true;
2833                 this.StatusLabel.Text = ex.Message;
2834             }
2835             finally
2836             {
2837                 this.workerSemaphore.Release();
2838             }
2839         }
2840
2841         private async Task FavAddAsyncInternal(IProgress<string> p, CancellationToken ct, IReadOnlyList<long> statusIds, TabClass tab)
2842         {
2843             if (ct.IsCancellationRequested)
2844                 return;
2845
2846             if (!CheckAccountValid())
2847                 throw new WebApiException("Auth error. Check your account");
2848
2849             var successIds = new List<long>();
2850
2851             await Task.Run(() =>
2852             {
2853                 //スレッド処理はしない
2854                 var allCount = 0;
2855                 var failedCount = 0;
2856
2857                 foreach (var statusId in statusIds)
2858                 {
2859                     allCount++;
2860
2861                     p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText15 +
2862                         allCount + "/" + statusIds.Count +
2863                         Properties.Resources.GetTimelineWorker_RunWorkerCompletedText16 +
2864                         failedCount);
2865
2866                     var post = tab.Posts[statusId];
2867
2868                     if (post.IsFav)
2869                         continue;
2870
2871                     try
2872                     {
2873                         this.tw.PostFavAdd(post.RetweetedId ?? post.StatusId);
2874                     }
2875                     catch (WebApiException)
2876                     {
2877                         failedCount++;
2878                         continue;
2879                     }
2880
2881                     successIds.Add(statusId);
2882                     post.IsFav = true; // リスト再描画必要
2883
2884                     this._favTimestamps.Add(DateTime.Now);
2885
2886                     // TLでも取得済みならfav反映
2887                     if (this._statuses.ContainsKey(statusId))
2888                     {
2889                         var postTl = this._statuses[statusId];
2890                         postTl.IsFav = true;
2891
2892                         var favTab = this._statuses.GetTabByType(MyCommon.TabUsageType.Favorites);
2893                         favTab.Add(statusId, postTl.IsRead, false);
2894                     }
2895
2896                     // 検索,リスト,UserTimeline,Relatedの各タブに反映
2897                     foreach (var tb in this._statuses.GetTabsInnerStorageType())
2898                     {
2899                         if (tb.Contains(statusId))
2900                             tb.Posts[statusId].IsFav = true;
2901                     }
2902                 }
2903
2904                 // 時速表示用
2905                 var oneHour = DateTime.Now - TimeSpan.FromHours(1);
2906                 foreach (var i in MyCommon.CountDown(this._favTimestamps.Count - 1, 0))
2907                 {
2908                     if (this._favTimestamps[i] < oneHour)
2909                         this._favTimestamps.RemoveAt(i);
2910                 }
2911
2912                 this._statuses.DistributePosts();
2913             });
2914
2915             if (ct.IsCancellationRequested)
2916                 return;
2917
2918             this.RefreshTimeline(false);
2919
2920             if (this._curList != null && this._curTab != null && this._curTab.Text == tab.TabName)
2921             {
2922                 using (ControlTransaction.Update(this._curList))
2923                 {
2924                     foreach (var statusId in successIds)
2925                     {
2926                         var idx = tab.IndexOf(statusId);
2927                         if (idx == -1)
2928                             continue;
2929
2930                         var post = tab.Posts[statusId];
2931                         this.ChangeCacheStyleRead(post.IsRead, idx);
2932                     }
2933                 }
2934
2935                 if (successIds.Contains(this._curPost.StatusId))
2936                     await this.DispSelectedPost(true); // 選択アイテム再表示
2937             }
2938         }
2939
2940         private async Task FavRemoveAsync(IReadOnlyList<long> statusIds, TabClass tab)
2941         {
2942             await this.workerSemaphore.WaitAsync();
2943
2944             try
2945             {
2946                 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2947
2948                 await this.FavRemoveAsyncInternal(progress, this.workerCts.Token, statusIds, tab);
2949             }
2950             catch (WebApiException ex)
2951             {
2952                 this._myStatusError = true;
2953                 this.StatusLabel.Text = ex.Message;
2954             }
2955             finally
2956             {
2957                 this.workerSemaphore.Release();
2958             }
2959         }
2960
2961         private async Task FavRemoveAsyncInternal(IProgress<string> p, CancellationToken ct, IReadOnlyList<long> statusIds, TabClass tab)
2962         {
2963             if (ct.IsCancellationRequested)
2964                 return;
2965
2966             if (!CheckAccountValid())
2967                 throw new WebApiException("Auth error. Check your account");
2968
2969             var successIds = new List<long>();
2970
2971             await Task.Run(() =>
2972             {
2973                 //スレッド処理はしない
2974                 var allCount = 0;
2975                 var failedCount = 0;
2976                 foreach (var statusId in statusIds)
2977                 {
2978                     allCount++;
2979
2980                     var post = tab.Posts[statusId];
2981
2982                     p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText17 +
2983                         allCount + "/" + statusIds.Count +
2984                         Properties.Resources.GetTimelineWorker_RunWorkerCompletedText18 +
2985                         failedCount);
2986
2987                     if (!post.IsFav)
2988                         continue;
2989
2990                     try
2991                     {
2992                         this.tw.PostFavRemove(post.RetweetedId ?? post.StatusId);
2993                     }
2994                     catch (WebApiException)
2995                     {
2996                         failedCount++;
2997                         continue;
2998                     }
2999
3000                     successIds.Add(statusId);
3001                     post.IsFav = false; // リスト再描画必要
3002
3003                     if (this._statuses.ContainsKey(statusId))
3004                     {
3005                         this._statuses[statusId].IsFav = false;
3006                     }
3007
3008                     // 検索,リスト,UserTimeline,Relatedの各タブに反映
3009                     foreach (var tb in this._statuses.GetTabsInnerStorageType())
3010                     {
3011                         if (tb.Contains(statusId))
3012                             tb.Posts[statusId].IsFav = false;
3013                     }
3014                 }
3015             });
3016
3017             if (ct.IsCancellationRequested)
3018                 return;
3019
3020             this.RemovePostFromFavTab(successIds.ToArray());
3021
3022             this.RefreshTimeline(false);
3023
3024             if (this._curList != null && this._curTab != null && this._curTab.Text == tab.TabName)
3025             {
3026                 if (tab.TabType == MyCommon.TabUsageType.Favorites)
3027                 {
3028                     // 色変えは不要
3029                 }
3030                 else
3031                 {
3032                     using (ControlTransaction.Update(this._curList))
3033                     {
3034                         foreach (var statusId in successIds)
3035                         {
3036                             var idx = tab.IndexOf(statusId);
3037                             if (idx == -1)
3038                                 continue;
3039
3040                             var post = tab.Posts[statusId];
3041                             this.ChangeCacheStyleRead(post.IsRead, idx);
3042                         }
3043                     }
3044
3045                     if (successIds.Contains(this._curPost.StatusId))
3046                         await this.DispSelectedPost(true); // 選択アイテム再表示
3047                 }
3048             }
3049         }
3050
3051         private async Task PostMessageAsync(PostingStatus status)
3052         {
3053             await this.workerSemaphore.WaitAsync();
3054
3055             try
3056             {
3057                 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
3058
3059                 await this.PostMessageAsyncInternal(progress, this.workerCts.Token, status);
3060             }
3061             catch (WebApiException ex)
3062             {
3063                 this._myStatusError = true;
3064                 this.StatusLabel.Text = ex.Message;
3065             }
3066             finally
3067             {
3068                 this.workerSemaphore.Release();
3069             }
3070         }
3071
3072         private async Task PostMessageAsyncInternal(IProgress<string> p, CancellationToken ct, PostingStatus status)
3073         {
3074             if (ct.IsCancellationRequested)
3075                 return;
3076
3077             if (!CheckAccountValid())
3078                 throw new WebApiException("Auth error. Check your account");
3079
3080             p.Report("Posting...");
3081
3082             var errMsg = "";
3083
3084             try
3085             {
3086                 await Task.Run(async () =>
3087                 {
3088                     if (status.mediaItems == null || status.mediaItems.Length == 0)
3089                     {
3090                         this.tw.PostStatus(status.status, status.inReplyToId);
3091                     }
3092                     else
3093                     {
3094                         var service = ImageSelector.GetService(status.imageService);
3095                         await service.PostStatusAsync(status.status, status.inReplyToId, status.mediaItems)
3096                             .ConfigureAwait(false);
3097                     }
3098                 });
3099
3100                 p.Report(Properties.Resources.PostWorker_RunWorkerCompletedText4);
3101             }
3102             catch (WebApiException ex)
3103             {
3104                 // 処理は中断せずエラーの表示のみ行う
3105                 errMsg = ex.Message;
3106                 p.Report(errMsg);
3107                 this._myStatusError = true;
3108             }
3109             finally
3110             {
3111                 // 使い終わった MediaItem は破棄する
3112                 if (status.mediaItems != null)
3113                 {
3114                     foreach (var disposableItem in status.mediaItems.OfType<IDisposable>())
3115                     {
3116                         disposableItem.Dispose();
3117                     }
3118                 }
3119             }
3120
3121             if (ct.IsCancellationRequested)
3122                 return;
3123
3124             if (!string.IsNullOrEmpty(errMsg) &&
3125                 !errMsg.StartsWith("OK:", StringComparison.Ordinal) &&
3126                 !errMsg.StartsWith("Warn:", StringComparison.Ordinal))
3127             {
3128                 var ret = MessageBox.Show(
3129                     string.Format(
3130                         "{0}   --->   [ " + errMsg + " ]" + Environment.NewLine +
3131                         "\"" + status.status + "\"" + Environment.NewLine +
3132                         "{1}",
3133                         Properties.Resources.StatusUpdateFailed1,
3134                         Properties.Resources.StatusUpdateFailed2),
3135                     "Failed to update status",
3136                     MessageBoxButtons.RetryCancel,
3137                     MessageBoxIcon.Question);
3138
3139                 if (ret == DialogResult.Retry)
3140                 {
3141                     await this.PostMessageAsync(status);
3142                 }
3143                 else
3144                 {
3145                     // 連投モードのときだけEnterイベントが起きないので強制的に背景色を戻す
3146                     if (this.ToolStripFocusLockMenuItem.Checked)
3147                         this.StatusText_Enter(this.StatusText, EventArgs.Empty);
3148                 }
3149                 return;
3150             }
3151
3152             this._postTimestamps.Add(DateTime.Now);
3153
3154             var oneHour = DateTime.Now - TimeSpan.FromHours(1);
3155             foreach (var i in MyCommon.CountDown(this._postTimestamps.Count - 1, 0))
3156             {
3157                 if (this._postTimestamps[i] < oneHour)
3158                     this._postTimestamps.RemoveAt(i);
3159             }
3160
3161             if (!this.HashMgr.IsPermanent && !string.IsNullOrEmpty(this.HashMgr.UseHash))
3162             {
3163                 this.HashMgr.ClearHashtag();
3164                 this.HashStripSplitButton.Text = "#[-]";
3165                 this.HashToggleMenuItem.Checked = false;
3166                 this.HashToggleToolStripMenuItem.Checked = false;
3167             }
3168
3169             this.SetMainWindowTitle();
3170
3171             if (this._cfgCommon.PostAndGet)
3172             {
3173                 if (this._isActiveUserstream)
3174                     this.RefreshTimeline(true);
3175                 else
3176                     await this.GetHomeTimelineAsync();
3177             }
3178         }
3179
3180         private async Task RetweetAsync(IReadOnlyList<long> statusIds)
3181         {
3182             await this.workerSemaphore.WaitAsync();
3183
3184             try
3185             {
3186                 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
3187
3188                 await this.RetweetAsyncInternal(progress, this.workerCts.Token, statusIds);
3189             }
3190             catch (WebApiException ex)
3191             {
3192                 this._myStatusError = true;
3193                 this.StatusLabel.Text = ex.Message;
3194             }
3195             finally
3196             {
3197                 this.workerSemaphore.Release();
3198             }
3199         }
3200
3201         private async Task RetweetAsyncInternal(IProgress<string> p, CancellationToken ct, IReadOnlyList<long> statusIds)
3202         {
3203             if (ct.IsCancellationRequested)
3204                 return;
3205
3206             if (!CheckAccountValid())
3207                 throw new WebApiException("Auth error. Check your account");
3208
3209             bool read;
3210             if (!this._cfgCommon.UnreadManage)
3211                 read = true;
3212             else
3213                 read = this._initial && this._cfgCommon.Read;
3214
3215             p.Report("Posting...");
3216
3217             await Task.Run(() =>
3218             {
3219                 foreach (var statusId in statusIds)
3220                 {
3221                     this.tw.PostRetweet(statusId, read);
3222                 }
3223             });
3224
3225             if (ct.IsCancellationRequested)
3226                 return;
3227
3228             p.Report(Properties.Resources.PostWorker_RunWorkerCompletedText4);
3229
3230             this._postTimestamps.Add(DateTime.Now);
3231
3232             var oneHour = DateTime.Now - TimeSpan.FromHours(1);
3233             foreach (var i in MyCommon.CountDown(this._postTimestamps.Count - 1, 0))
3234             {
3235                 if (this._postTimestamps[i] < oneHour)
3236                     this._postTimestamps.RemoveAt(i);
3237             }
3238
3239             if (this._cfgCommon.PostAndGet && !this._isActiveUserstream)
3240                 await this.GetHomeTimelineAsync();
3241         }
3242
3243         private async Task RefreshFollowerIdsAsync()
3244         {
3245             await this.workerSemaphore.WaitAsync();
3246             try
3247             {
3248                 this.StatusLabel.Text = Properties.Resources.UpdateFollowersMenuItem1_ClickText1;
3249
3250                 await Task.Run(() => tw.RefreshFollowerIds());
3251
3252                 this.StatusLabel.Text = Properties.Resources.UpdateFollowersMenuItem1_ClickText3;
3253
3254                 this.RefreshTimeline(false);
3255                 this.PurgeListViewItemCache();
3256                 this._curList?.Refresh();
3257             }
3258             catch (WebApiException ex)
3259             {
3260                 this.StatusLabel.Text = ex.Message;
3261             }
3262             finally
3263             {
3264                 this.workerSemaphore.Release();
3265             }
3266         }
3267
3268         private async Task RefreshNoRetweetIdsAsync()
3269         {
3270             await this.workerSemaphore.WaitAsync();
3271             try
3272             {
3273                 await Task.Run(() => tw.RefreshNoRetweetIds());
3274
3275                 this.StatusLabel.Text = "NoRetweetIds refreshed";
3276             }
3277             catch (WebApiException ex)
3278             {
3279                 this.StatusLabel.Text = ex.Message;
3280             }
3281             finally
3282             {
3283                 this.workerSemaphore.Release();
3284             }
3285         }
3286
3287         private async Task RefreshBlockIdsAsync()
3288         {
3289             await this.workerSemaphore.WaitAsync();
3290             try
3291             {
3292                 this.StatusLabel.Text = Properties.Resources.UpdateBlockUserText1;
3293
3294                 await Task.Run(() => tw.RefreshBlockIds());
3295
3296                 this.StatusLabel.Text = Properties.Resources.UpdateBlockUserText3;
3297             }
3298             catch (WebApiException ex)
3299             {
3300                 this.StatusLabel.Text = ex.Message;
3301             }
3302             finally
3303             {
3304                 this.workerSemaphore.Release();
3305             }
3306         }
3307
3308         private async Task RefreshTwitterConfigurationAsync()
3309         {
3310             await this.workerSemaphore.WaitAsync();
3311             try
3312             {
3313                 await Task.Run(() => tw.RefreshConfiguration());
3314
3315                 if (this.tw.Configuration.PhotoSizeLimit != 0)
3316                 {
3317                     foreach (var service in this.ImageSelector.GetServices())
3318                     {
3319                         service.UpdateTwitterConfiguration(this.tw.Configuration);
3320                     }
3321                 }
3322
3323                 this.PurgeListViewItemCache();
3324
3325                 this._curList?.Refresh();
3326             }
3327             catch (WebApiException ex)
3328             {
3329                 this.StatusLabel.Text = ex.Message;
3330             }
3331             finally
3332             {
3333                 this.workerSemaphore.Release();
3334             }
3335         }
3336
3337         private async Task RefreshMuteUserIdsAsync()
3338         {
3339             this.StatusLabel.Text = Properties.Resources.UpdateMuteUserIds_Start;
3340
3341             try
3342             {
3343                 await tw.RefreshMuteUserIdsAsync();
3344             }
3345             catch (WebApiException ex)
3346             {
3347                 this.StatusLabel.Text = string.Format(Properties.Resources.UpdateMuteUserIds_Error, ex.Message);
3348                 return;
3349             }
3350
3351             this.StatusLabel.Text = Properties.Resources.UpdateMuteUserIds_Finish;
3352         }
3353
3354         private void RemovePostFromFavTab(Int64[] ids)
3355         {
3356             var favTab = this._statuses.GetTabByType(MyCommon.TabUsageType.Favorites);
3357             string favTabName = favTab.TabName;
3358             int fidx = 0;
3359             if (_curTab.Text.Equals(favTabName))
3360             {
3361                 fidx = _curList.FocusedItem?.Index ?? _curList.TopItem?.Index ?? 0;
3362             }
3363
3364             foreach (long i in ids)
3365             {
3366                 try
3367                 {
3368                     _statuses.RemoveFavPost(i);
3369                 }
3370                 catch (Exception)
3371                 {
3372                     continue;
3373                 }
3374             }
3375             if (_curTab != null && _curTab.Text.Equals(favTabName))
3376             {
3377                 this.PurgeListViewItemCache();
3378                 _curPost = null;
3379                 //_curItemIndex = -1;
3380             }
3381             foreach (TabPage tp in ListTab.TabPages)
3382             {
3383                 if (tp.Text == favTabName)
3384                 {
3385                     ((DetailsListView)tp.Tag).VirtualListSize = favTab.AllCount;
3386                     break;
3387                 }
3388             }
3389             if (_curTab.Text.Equals(favTabName))
3390             {
3391                 do
3392                 {
3393                     _curList.SelectedIndices.Clear();
3394                 }
3395                 while (_curList.SelectedIndices.Count > 0);
3396
3397                 if (favTab.AllCount > 0)
3398                 {
3399                     if (favTab.AllCount - 1 > fidx && fidx > -1)
3400                     {
3401                         _curList.SelectedIndices.Add(fidx);
3402                     }
3403                     else
3404                     {
3405                         _curList.SelectedIndices.Add(favTab.AllCount - 1);
3406                     }
3407                     if (_curList.SelectedIndices.Count > 0)
3408                     {
3409                         _curList.EnsureVisible(_curList.SelectedIndices[0]);
3410                         _curList.FocusedItem = _curList.Items[_curList.SelectedIndices[0]];
3411                     }
3412                 }
3413             }
3414         }
3415
3416         private void NotifyIcon1_MouseClick(object sender, MouseEventArgs e)
3417         {
3418             if (e.Button == MouseButtons.Left)
3419             {
3420                 this.Visible = true;
3421                 if (this.WindowState == FormWindowState.Minimized)
3422                 {
3423                     this.WindowState = _formWindowState;
3424                 }
3425                 this.Activate();
3426                 this.BringToFront();
3427             }
3428         }
3429
3430         private async void MyList_MouseDoubleClick(object sender, MouseEventArgs e)
3431         {
3432             switch (this._cfgCommon.ListDoubleClickAction)
3433             {
3434                 case 0:
3435                     MakeReplyOrDirectStatus();
3436                     break;
3437                 case 1:
3438                     await this.FavoriteChange(true);
3439                     break;
3440                 case 2:
3441                     if (_curPost != null)
3442                         await this.ShowUserStatus(_curPost.ScreenName, false);
3443                     break;
3444                 case 3:
3445                     ShowUserTimeline();
3446                     break;
3447                 case 4:
3448                     ShowRelatedStatusesMenuItem_Click(null, null);
3449                     break;
3450                 case 5:
3451                     MoveToHomeToolStripMenuItem_Click(null, null);
3452                     break;
3453                 case 6:
3454                     StatusOpenMenuItem_Click(null, null);
3455                     break;
3456                 case 7:
3457                     //動作なし
3458                     break;
3459             }
3460         }
3461
3462         private async void FavAddToolStripMenuItem_Click(object sender, EventArgs e)
3463         {
3464             await this.FavoriteChange(true);
3465         }
3466
3467         private async void FavRemoveToolStripMenuItem_Click(object sender, EventArgs e)
3468         {
3469             await this.FavoriteChange(false);
3470         }
3471
3472
3473         private async void FavoriteRetweetMenuItem_Click(object sender, EventArgs e)
3474         {
3475             await this.FavoritesRetweetOfficial();
3476         }
3477
3478         private async void FavoriteRetweetUnofficialMenuItem_Click(object sender, EventArgs e)
3479         {
3480             await this.FavoritesRetweetUnofficial();
3481         }
3482
3483         private async Task FavoriteChange(bool FavAdd , bool multiFavoriteChangeDialogEnable = true)
3484         {
3485             TabClass tab;
3486             if (!this._statuses.Tabs.TryGetValue(this._curTab.Text, out tab))
3487                 return;
3488
3489             //trueでFavAdd,falseでFavRemove
3490             if (tab.TabType == MyCommon.TabUsageType.DirectMessage || _curList.SelectedIndices.Count == 0
3491                 || !this.ExistCurrentPost) return;
3492
3493             // 誤ふぁぼ・故意によるふぁぼ爆対策 (変更しないこと)
3494             // https://support.twitter.com/articles/76915#favoriting
3495             const int MultiSelectFavLimit = 1;
3496
3497             //複数fav確認msg
3498             if (_curList.SelectedIndices.Count > MultiSelectFavLimit && FavAdd)
3499             {
3500                 MessageBox.Show(string.Format(Properties.Resources.FavoriteLimitCountText, MultiSelectFavLimit));
3501                 _DoFavRetweetFlags = false;
3502                 return;
3503             }
3504             else if (multiFavoriteChangeDialogEnable && _curList.SelectedIndices.Count > 1)
3505             {
3506                 if (FavAdd)
3507                 {
3508                     string QuestionText = Properties.Resources.FavAddToolStripMenuItem_ClickText1;
3509                     if (_DoFavRetweetFlags) QuestionText = Properties.Resources.FavoriteRetweetQuestionText3;
3510                     if (MessageBox.Show(QuestionText, Properties.Resources.FavAddToolStripMenuItem_ClickText2,
3511                                        MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
3512                     {
3513                         _DoFavRetweetFlags = false;
3514                         return;
3515                     }
3516                 }
3517                 else
3518                 {
3519                     if (MessageBox.Show(Properties.Resources.FavRemoveToolStripMenuItem_ClickText1, Properties.Resources.FavRemoveToolStripMenuItem_ClickText2,
3520                                     MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
3521                     {
3522                         return;
3523                     }
3524                 }
3525             }
3526
3527             var statusIds = new List<long>();
3528             foreach (int idx in _curList.SelectedIndices)
3529             {
3530                 PostClass post = GetCurTabPost(idx);
3531                 if (FavAdd)
3532                 {
3533                     if (!post.IsFav)
3534                         statusIds.Add(post.StatusId);
3535                 }
3536                 else
3537                 {
3538                     if (post.IsFav)
3539                         statusIds.Add(post.StatusId);
3540                 }
3541             }
3542             if (statusIds.Count == 0)
3543             {
3544                 if (FavAdd)
3545                     StatusLabel.Text = Properties.Resources.FavAddToolStripMenuItem_ClickText4;
3546                 else
3547                     StatusLabel.Text = Properties.Resources.FavRemoveToolStripMenuItem_ClickText4;
3548
3549                 return;
3550             }
3551
3552             if (FavAdd)
3553                 await this.FavAddAsync(statusIds, tab);
3554             else
3555                 await this.FavRemoveAsync(statusIds, tab);
3556         }
3557
3558         private PostClass GetCurTabPost(int Index)
3559         {
3560             this.itemCacheLock.EnterReadLock();
3561             try
3562             {
3563                 if (_postCache != null && Index >= _itemCacheIndex && Index < _itemCacheIndex + _postCache.Length)
3564                     return _postCache[Index - _itemCacheIndex];
3565             }
3566             finally { this.itemCacheLock.ExitReadLock(); }
3567
3568             return _statuses.Tabs[_curTab.Text][Index];
3569         }
3570
3571
3572         private async void MoveToHomeToolStripMenuItem_Click(object sender, EventArgs e)
3573         {
3574             if (_curList.SelectedIndices.Count > 0)
3575                 await this.OpenUriInBrowserAsync(MyCommon.TwitterUrl + GetCurTabPost(_curList.SelectedIndices[0]).ScreenName);
3576             else if (_curList.SelectedIndices.Count == 0)
3577                 await this.OpenUriInBrowserAsync(MyCommon.TwitterUrl);
3578         }
3579
3580         private async void MoveToFavToolStripMenuItem_Click(object sender, EventArgs e)
3581         {
3582             if (_curList.SelectedIndices.Count > 0)
3583                 await this.OpenUriInBrowserAsync(MyCommon.TwitterUrl + "#!/" + GetCurTabPost(_curList.SelectedIndices[0]).ScreenName + "/favorites");
3584         }
3585
3586         private void TweenMain_ClientSizeChanged(object sender, EventArgs e)
3587         {
3588             if ((!_initialLayout) && this.Visible)
3589             {
3590                 if (this.WindowState == FormWindowState.Normal)
3591                 {
3592                     _mySize = this.ClientSize;
3593                     _mySpDis = this.SplitContainer1.SplitterDistance;
3594                     _mySpDis3 = this.SplitContainer3.SplitterDistance;
3595                     if (StatusText.Multiline) _mySpDis2 = this.StatusText.Height;
3596                     _modifySettingLocal = true;
3597                 }
3598             }
3599         }
3600
3601         private void MyList_ColumnClick(object sender, ColumnClickEventArgs e)
3602         {
3603             var comparerMode = this.GetComparerModeByColumnIndex(e.Column);
3604             if (comparerMode == null)
3605                 return;
3606
3607             this.SetSortColumn(comparerMode.Value);
3608         }
3609
3610         /// <summary>
3611         /// 列インデックスからソートを行う ComparerMode を求める
3612         /// </summary>
3613         /// <param name="columnIndex">ソートを行うカラムのインデックス (表示上の順序とは異なる)</param>
3614         /// <returns>ソートを行う ComparerMode。null であればソートを行わない</returns>
3615         private ComparerMode? GetComparerModeByColumnIndex(int columnIndex)
3616         {
3617             if (this._iconCol)
3618                 return ComparerMode.Id;
3619
3620             switch (columnIndex)
3621             {
3622                 case 1: // ニックネーム
3623                     return ComparerMode.Nickname;
3624                 case 2: // 本文
3625                     return ComparerMode.Data;
3626                 case 3: // 時刻=発言Id
3627                     return ComparerMode.Id;
3628                 case 4: // 名前
3629                     return ComparerMode.Name;
3630                 case 7: // Source
3631                     return ComparerMode.Source;
3632                 default:
3633                     // 0:アイコン, 5:未読マーク, 6:プロテクト・フィルターマーク
3634                     return null;
3635             }
3636         }
3637
3638         /// <summary>
3639         /// 発言一覧の指定した位置の列でソートする
3640         /// </summary>
3641         /// <param name="columnIndex">ソートする列の位置 (表示上の順序で指定)</param>
3642         private void SetSortColumnByDisplayIndex(int columnIndex)
3643         {
3644             // 表示上の列の位置から ColumnHeader を求める
3645             var col = this._curList.Columns.Cast<ColumnHeader>()
3646                 .Where(x => x.DisplayIndex == columnIndex)
3647                 .FirstOrDefault();
3648
3649             if (col == null)
3650                 return;
3651
3652             var comparerMode = this.GetComparerModeByColumnIndex(col.Index);
3653             if (comparerMode == null)
3654                 return;
3655
3656             this.SetSortColumn(comparerMode.Value);
3657         }
3658
3659         /// <summary>
3660         /// 発言一覧の最後列の項目でソートする
3661         /// </summary>
3662         private void SetSortLastColumn()
3663         {
3664             // 表示上の最後列にある ColumnHeader を求める
3665             var col = this._curList.Columns.Cast<ColumnHeader>()
3666                 .OrderByDescending(x => x.DisplayIndex)
3667                 .First();
3668
3669             var comparerMode = this.GetComparerModeByColumnIndex(col.Index);
3670             if (comparerMode == null)
3671                 return;
3672
3673             this.SetSortColumn(comparerMode.Value);
3674         }
3675
3676         /// <summary>
3677         /// 発言一覧を指定された ComparerMode に基づいてソートする
3678         /// </summary>
3679         private void SetSortColumn(ComparerMode sortColumn)
3680         {
3681             if (this._cfgCommon.SortOrderLock)
3682                 return;
3683
3684             this._statuses.ToggleSortOrder(sortColumn);
3685             this.InitColumnText();
3686
3687             var list = this._curList;
3688             if (_iconCol)
3689             {
3690                 list.Columns[0].Text = this.ColumnText[0];
3691                 list.Columns[1].Text = this.ColumnText[2];
3692             }
3693             else
3694             {
3695                 for (var i = 0; i <= 7; i++)
3696                 {
3697                     list.Columns[i].Text = this.ColumnText[i];
3698                 }
3699             }
3700
3701             this.PurgeListViewItemCache();
3702
3703             var tab = this._statuses.Tabs[this._curTab.Text];
3704             if (tab.AllCount > 0 && this._curPost != null)
3705             {
3706                 var idx = tab.IndexOf(this._curPost.StatusId);
3707                 if (idx > -1)
3708                 {
3709                     this.SelectListItem(list, idx);
3710                     list.EnsureVisible(idx);
3711                 }
3712             }
3713             list.Refresh();
3714
3715             this._modifySettingCommon = true;
3716         }
3717
3718         private void TweenMain_LocationChanged(object sender, EventArgs e)
3719         {
3720             if (this.WindowState == FormWindowState.Normal && !_initialLayout)
3721             {
3722                 _myLoc = this.DesktopLocation;
3723                 _modifySettingLocal = true;
3724             }
3725         }
3726
3727         private void ContextMenuOperate_Opening(object sender, CancelEventArgs e)
3728         {
3729             if (ListTab.SelectedTab == null) return;
3730             if (_statuses == null || _statuses.Tabs == null || !_statuses.Tabs.ContainsKey(ListTab.SelectedTab.Text)) return;
3731             if (!this.ExistCurrentPost)
3732             {
3733                 ReplyStripMenuItem.Enabled = false;
3734                 ReplyAllStripMenuItem.Enabled = false;
3735                 DMStripMenuItem.Enabled = false;
3736                 ShowProfileMenuItem.Enabled = false;
3737                 ShowUserTimelineContextMenuItem.Enabled = false;
3738                 ListManageUserContextToolStripMenuItem2.Enabled = false;
3739                 MoveToFavToolStripMenuItem.Enabled = false;
3740                 TabMenuItem.Enabled = false;
3741                 IDRuleMenuItem.Enabled = false;
3742                 SourceRuleMenuItem.Enabled = false;
3743                 ReadedStripMenuItem.Enabled = false;
3744                 UnreadStripMenuItem.Enabled = false;
3745             }
3746             else
3747             {
3748                 ShowProfileMenuItem.Enabled = true;
3749                 ListManageUserContextToolStripMenuItem2.Enabled = true;
3750                 ReplyStripMenuItem.Enabled = true;
3751                 ReplyAllStripMenuItem.Enabled = true;
3752                 DMStripMenuItem.Enabled = true;
3753                 ShowUserTimelineContextMenuItem.Enabled = true;
3754                 MoveToFavToolStripMenuItem.Enabled = true;
3755                 TabMenuItem.Enabled = true;
3756                 IDRuleMenuItem.Enabled = true;
3757                 SourceRuleMenuItem.Enabled = true;
3758                 ReadedStripMenuItem.Enabled = true;
3759                 UnreadStripMenuItem.Enabled = true;
3760             }
3761             if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType == MyCommon.TabUsageType.DirectMessage || !this.ExistCurrentPost || _curPost.IsDm)
3762             {
3763                 FavAddToolStripMenuItem.Enabled = false;
3764                 FavRemoveToolStripMenuItem.Enabled = false;
3765                 StatusOpenMenuItem.Enabled = false;
3766                 FavorareMenuItem.Enabled = false;
3767                 ShowRelatedStatusesMenuItem.Enabled = false;
3768
3769                 ReTweetStripMenuItem.Enabled = false;
3770                 ReTweetUnofficialStripMenuItem.Enabled = false;
3771                 QuoteStripMenuItem.Enabled = false;
3772                 FavoriteRetweetContextMenu.Enabled = false;
3773                 FavoriteRetweetUnofficialContextMenu.Enabled = false;
3774             }
3775             else
3776             {
3777                 FavAddToolStripMenuItem.Enabled = true;
3778                 FavRemoveToolStripMenuItem.Enabled = true;
3779                 StatusOpenMenuItem.Enabled = true;
3780                 FavorareMenuItem.Enabled = true;
3781                 ShowRelatedStatusesMenuItem.Enabled = true;  //PublicSearchの時問題出るかも
3782
3783                 if (_curPost.IsMe)
3784                 {
3785                     ReTweetStripMenuItem.Enabled = false;  //公式RTは無効に
3786                     ReTweetUnofficialStripMenuItem.Enabled = true;
3787                     QuoteStripMenuItem.Enabled = true;
3788                     FavoriteRetweetContextMenu.Enabled = false;  //公式RTは無効に
3789                     FavoriteRetweetUnofficialContextMenu.Enabled = true;
3790                 }
3791                 else
3792                 {
3793                     if (_curPost.IsProtect)
3794                     {
3795                         ReTweetStripMenuItem.Enabled = false;
3796                         ReTweetUnofficialStripMenuItem.Enabled = false;
3797                         QuoteStripMenuItem.Enabled = false;
3798                         FavoriteRetweetContextMenu.Enabled = false;
3799                         FavoriteRetweetUnofficialContextMenu.Enabled = false;
3800                     }
3801                     else
3802                     {
3803                         ReTweetStripMenuItem.Enabled = true;
3804                         ReTweetUnofficialStripMenuItem.Enabled = true;
3805                         QuoteStripMenuItem.Enabled = true;
3806                         FavoriteRetweetContextMenu.Enabled = true;
3807                         FavoriteRetweetUnofficialContextMenu.Enabled = true;
3808                     }
3809                 }
3810             }
3811             //if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType != MyCommon.TabUsageType.Favorites)
3812             //{
3813             //    RefreshMoreStripMenuItem.Enabled = true;
3814             //}
3815             //else
3816             //{
3817             //    RefreshMoreStripMenuItem.Enabled = false;
3818             //}
3819             if (!this.ExistCurrentPost
3820                 || _curPost.InReplyToStatusId == null)
3821             {
3822                 RepliedStatusOpenMenuItem.Enabled = false;
3823             }
3824             else
3825             {
3826                 RepliedStatusOpenMenuItem.Enabled = true;
3827             }
3828             if (!this.ExistCurrentPost || string.IsNullOrEmpty(_curPost.RetweetedBy))
3829             {
3830                 MoveToRTHomeMenuItem.Enabled = false;
3831             }
3832             else
3833             {
3834                 MoveToRTHomeMenuItem.Enabled = true;
3835             }
3836
3837             if (this.ExistCurrentPost)
3838             {
3839                 this.DeleteStripMenuItem.Enabled = this._curPost.CanDeleteBy(this.tw.UserId);
3840                 if (this._curPost.RetweetedByUserId == this.tw.UserId)
3841                     this.DeleteStripMenuItem.Text = Properties.Resources.DeleteMenuText2;
3842                 else
3843                     this.DeleteStripMenuItem.Text = Properties.Resources.DeleteMenuText1;
3844             }
3845         }
3846
3847         private void ReplyStripMenuItem_Click(object sender, EventArgs e)
3848         {
3849             MakeReplyOrDirectStatus(false, true);
3850         }
3851
3852         private void DMStripMenuItem_Click(object sender, EventArgs e)
3853         {
3854             MakeReplyOrDirectStatus(false, false);
3855         }
3856
3857         private void doStatusDelete()
3858         {
3859             if (this._curTab == null || this._curList == null)
3860                 return;
3861
3862             if (this._curList.SelectedIndices.Count == 0)
3863                 return;
3864
3865             var posts = this._curList.SelectedIndices.Cast<int>()
3866                 .Select(x => this.GetCurTabPost(x))
3867                 .ToArray();
3868
3869             // 選択されたツイートの中に削除可能なものが一つでもあるか
3870             if (!posts.Any(x => x.CanDeleteBy(this.tw.UserId)))
3871                 return;
3872
3873             var ret = MessageBox.Show(this,
3874                 string.Format(Properties.Resources.DeleteStripMenuItem_ClickText1, Environment.NewLine),
3875                 Properties.Resources.DeleteStripMenuItem_ClickText2,
3876                 MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
3877
3878             if (ret != DialogResult.OK)
3879                 return;
3880
3881             var focusedIndex = this._curList.FocusedItem?.Index ?? this._curList.TopItem?.Index ?? 0;
3882
3883             using (ControlTransaction.Cursor(this, Cursors.WaitCursor))
3884             {
3885                 Exception lastException = null;
3886                 foreach (var post in posts)
3887                 {
3888                     if (!post.CanDeleteBy(this.tw.UserId))
3889                         continue;
3890
3891                     try
3892                     {
3893                         if (post.IsDm)
3894                         {
3895                             this.tw.RemoveDirectMessage(post.StatusId, post);
3896                         }
3897                         else
3898                         {
3899                             if (post.RetweetedId != null && post.UserId == this.tw.UserId)
3900                                 // 他人に RT された自分のツイート
3901                                 this.tw.RemoveStatus(post.RetweetedId.Value);
3902                             else
3903                                 // 自分のツイート or 自分が RT したツイート
3904                                 this.tw.RemoveStatus(post.StatusId);
3905                         }
3906                     }
3907                     catch (WebApiException ex)
3908                     {
3909                         lastException = ex;
3910                         continue;
3911                     }
3912
3913                     this._statuses.RemovePost(post.StatusId);
3914                 }
3915
3916                 if (lastException == null)
3917                     this.StatusLabel.Text = Properties.Resources.DeleteStripMenuItem_ClickText4; // 成功
3918                 else
3919                     this.StatusLabel.Text = Properties.Resources.DeleteStripMenuItem_ClickText3; // 失敗
3920
3921                 this.PurgeListViewItemCache();
3922                 this._curPost = null;
3923                 this._curItemIndex = -1;
3924
3925                 foreach (var tabPage in this.ListTab.TabPages.Cast<TabPage>())
3926                 {
3927                     var listView = (DetailsListView)tabPage.Tag;
3928                     var tab = this._statuses.Tabs[tabPage.Text];
3929
3930                     using (ControlTransaction.Update(listView))
3931                     {
3932                         listView.VirtualListSize = tab.AllCount;
3933
3934                         if (tabPage == this._curTab)
3935                         {
3936                             listView.SelectedIndices.Clear();
3937
3938                             if (tab.AllCount != 0)
3939                             {
3940                                 int selectedIndex;
3941                                 if (tab.AllCount - 1 > focusedIndex && focusedIndex > -1)
3942                                     selectedIndex = focusedIndex;
3943                                 else
3944                                     selectedIndex = tab.AllCount - 1;
3945
3946                                 listView.SelectedIndices.Add(selectedIndex);
3947                                 listView.EnsureVisible(selectedIndex);
3948                                 listView.FocusedItem = listView.Items[selectedIndex];
3949                             }
3950                         }
3951                     }
3952
3953                     if (this._cfgCommon.TabIconDisp && tab.UnreadCount == 0)
3954                     {
3955                         if (tabPage.ImageIndex == 0)
3956                             tabPage.ImageIndex = -1; // タブアイコン
3957                     }
3958                 }
3959
3960                 if (!this._cfgCommon.TabIconDisp)
3961                     this.ListTab.Refresh();
3962             }
3963         }
3964
3965         private void DeleteStripMenuItem_Click(object sender, EventArgs e)
3966         {
3967             doStatusDelete();
3968         }
3969
3970         private void ReadedStripMenuItem_Click(object sender, EventArgs e)
3971         {
3972             using (ControlTransaction.Update(this._curList))
3973             {
3974                 foreach (int idx in _curList.SelectedIndices)
3975                 {
3976                     var post = this._statuses.Tabs[this._curTab.Text][idx];
3977                     this._statuses.SetReadAllTab(post.StatusId, read: true);
3978                     ChangeCacheStyleRead(true, idx);
3979                 }
3980                 ColorizeList();
3981             }
3982             foreach (TabPage tb in ListTab.TabPages)
3983             {
3984                 if (_statuses.Tabs[tb.Text].UnreadCount == 0)
3985                 {
3986                     if (this._cfgCommon.TabIconDisp)
3987                     {
3988                         if (tb.ImageIndex == 0) tb.ImageIndex = -1; //タブアイコン
3989                     }
3990                 }
3991             }
3992             if (!this._cfgCommon.TabIconDisp) ListTab.Refresh();
3993         }
3994
3995         private void UnreadStripMenuItem_Click(object sender, EventArgs e)
3996         {
3997             using (ControlTransaction.Update(this._curList))
3998             {
3999                 foreach (int idx in _curList.SelectedIndices)
4000                 {
4001                     var post = this._statuses.Tabs[this._curTab.Text][idx];
4002                     this._statuses.SetReadAllTab(post.StatusId, read: false);
4003                     ChangeCacheStyleRead(false, idx);
4004                 }
4005                 ColorizeList();
4006             }
4007             foreach (TabPage tb in ListTab.TabPages)
4008             {
4009                 if (_statuses.Tabs[tb.Text].UnreadCount > 0)
4010                 {
4011                     if (this._cfgCommon.TabIconDisp)
4012                     {
4013                         if (tb.ImageIndex == -1) tb.ImageIndex = 0; //タブアイコン
4014                     }
4015                 }
4016             }
4017             if (!this._cfgCommon.TabIconDisp) ListTab.Refresh();
4018         }
4019
4020         private void RefreshStripMenuItem_Click(object sender, EventArgs e)
4021         {
4022             this.DoRefresh();
4023         }
4024
4025         private void DoRefresh()
4026         {
4027             if (_curTab != null)
4028             {
4029                 TabClass tab;
4030                 if (!this._statuses.Tabs.TryGetValue(this._curTab.Text, out tab))
4031                     return;
4032
4033                 switch (_statuses.Tabs[_curTab.Text].TabType)
4034                 {
4035                     case MyCommon.TabUsageType.Mentions:
4036                         this.GetReplyAsync();
4037                         break;
4038                     case MyCommon.TabUsageType.DirectMessage:
4039                         this.GetDirectMessagesAsync();
4040                         break;
4041                     case MyCommon.TabUsageType.Favorites:
4042                         this.GetFavoritesAsync();
4043                         break;
4044                     //case MyCommon.TabUsageType.Profile:
4045                         //// TODO
4046                     case MyCommon.TabUsageType.PublicSearch:
4047                         //// TODO
4048                         if (string.IsNullOrEmpty(tab.SearchWords)) return;
4049                         this.GetPublicSearchAsync(tab);
4050                         break;
4051                     case MyCommon.TabUsageType.UserTimeline:
4052                         this.GetUserTimelineAsync(tab);
4053                         break;
4054                     case MyCommon.TabUsageType.Lists:
4055                         //// TODO
4056                         if (tab.ListInfo == null || tab.ListInfo.Id == 0) return;
4057                         this.GetListTimelineAsync(tab);
4058                         break;
4059                     default:
4060                         this.GetHomeTimelineAsync();
4061                         break;
4062                 }
4063             }
4064             else
4065             {
4066                 this.GetHomeTimelineAsync();
4067             }
4068         }
4069
4070         private async Task DoRefreshMore()
4071         {
4072             //ページ指定をマイナス1に
4073             if (_curTab != null)
4074             {
4075                 TabClass tab;
4076                 if (!this._statuses.Tabs.TryGetValue(this._curTab.Text, out tab))
4077                     return;
4078
4079                 switch (_statuses.Tabs[_curTab.Text].TabType)
4080                 {
4081                     case MyCommon.TabUsageType.Mentions:
4082                         await this.GetReplyAsync(loadMore: true);
4083                         break;
4084                     case MyCommon.TabUsageType.DirectMessage:
4085                         await this.GetDirectMessagesAsync(loadMore: true);
4086                         break;
4087                     case MyCommon.TabUsageType.Favorites:
4088                         await this.GetFavoritesAsync(loadMore: true);
4089                         break;
4090                     case MyCommon.TabUsageType.Profile:
4091                         //// TODO
4092                         break;
4093                     case MyCommon.TabUsageType.PublicSearch:
4094                         // TODO
4095                         if (string.IsNullOrEmpty(tab.SearchWords)) return;
4096                         await this.GetPublicSearchAsync(tab, loadMore: true);
4097                         break;
4098                     case MyCommon.TabUsageType.UserTimeline:
4099                         await this.GetUserTimelineAsync(tab, loadMore: true);
4100                         break;
4101                     case MyCommon.TabUsageType.Lists:
4102                         //// TODO
4103                         if (tab.ListInfo == null || tab.ListInfo.Id == 0) return;
4104                         await this.GetListTimelineAsync(tab, loadMore: true);
4105                         break;
4106                     default:
4107                         await this.GetHomeTimelineAsync(loadMore: true);
4108                         break;
4109                 }
4110             }
4111             else
4112             {
4113                 await this.GetHomeTimelineAsync(loadMore: true);
4114             }
4115         }
4116
4117         private DialogResult ShowSettingDialog(bool showTaskbarIcon = false)
4118         {
4119             DialogResult result = DialogResult.Abort;
4120
4121             using (var settingDialog = new AppendSettingDialog())
4122             {
4123                 settingDialog.Icon = this.MainIcon;
4124                 settingDialog.Owner = this;
4125                 settingDialog.ShowInTaskbar = showTaskbarIcon;
4126                 settingDialog.IntervalChanged += this.TimerInterval_Changed;
4127
4128                 settingDialog.tw = this.tw;
4129                 settingDialog.LoadConfig(this._cfgCommon, this._cfgLocal);
4130
4131                 try
4132                 {
4133                     result = settingDialog.ShowDialog(this);
4134                 }
4135                 catch (Exception)
4136                 {
4137                     return DialogResult.Abort;
4138                 }
4139
4140                 if (result == DialogResult.OK)
4141                 {
4142                     lock (_syncObject)
4143                     {
4144                         settingDialog.SaveConfig(this._cfgCommon, this._cfgLocal);
4145                     }
4146                 }
4147             }
4148
4149             return result;
4150         }
4151
4152         private async void SettingStripMenuItem_Click(object sender, EventArgs e)
4153         {
4154             // 設定画面表示前のユーザー情報
4155             var oldUser = new { tw.AccessToken, tw.AccessTokenSecret, tw.Username, tw.UserId };
4156
4157             var oldIconSz = this._cfgCommon.IconSize;
4158
4159             if (ShowSettingDialog() == DialogResult.OK)
4160             {
4161                 lock (_syncObject)
4162                 {
4163                     tw.RestrictFavCheck = this._cfgCommon.RestrictFavCheck;
4164                     tw.ReadOwnPost = this._cfgCommon.ReadOwnPost;
4165                     ShortUrl.Instance.DisableExpanding = !this._cfgCommon.TinyUrlResolve;
4166                     ShortUrl.Instance.BitlyId = this._cfgCommon.BilyUser;
4167                     ShortUrl.Instance.BitlyKey = this._cfgCommon.BitlyPwd;
4168                     HttpTwitter.TwitterUrl = _cfgCommon.TwitterUrl;
4169
4170                     Networking.DefaultTimeout = TimeSpan.FromSeconds(this._cfgCommon.DefaultTimeOut);
4171                     Networking.SetWebProxy(this._cfgLocal.ProxyType,
4172                         this._cfgLocal.ProxyAddress, this._cfgLocal.ProxyPort,
4173                         this._cfgLocal.ProxyUser, this._cfgLocal.ProxyPassword);
4174                     Networking.ForceIPv4 = this._cfgCommon.ForceIPv4;
4175
4176                     ImageSelector.Reset(tw, this.tw.Configuration);
4177
4178                     try
4179                     {
4180                         if (this._cfgCommon.TabIconDisp)
4181                         {
4182                             ListTab.DrawItem -= ListTab_DrawItem;
4183                             ListTab.DrawMode = TabDrawMode.Normal;
4184                             ListTab.ImageList = this.TabImage;
4185                         }
4186                         else
4187                         {
4188                             ListTab.DrawItem -= ListTab_DrawItem;
4189                             ListTab.DrawItem += ListTab_DrawItem;
4190                             ListTab.DrawMode = TabDrawMode.OwnerDrawFixed;
4191                             ListTab.ImageList = null;
4192                         }
4193                     }
4194                     catch (Exception ex)
4195                     {
4196                         ex.Data["Instance"] = "ListTab(TabIconDisp)";
4197                         ex.Data["IsTerminatePermission"] = false;
4198                         throw;
4199                     }
4200
4201                     try
4202                     {
4203                         if (!this._cfgCommon.UnreadManage)
4204                         {
4205                             ReadedStripMenuItem.Enabled = false;
4206                             UnreadStripMenuItem.Enabled = false;
4207                             if (this._cfgCommon.TabIconDisp)
4208                             {
4209                                 foreach (TabPage myTab in ListTab.TabPages)
4210                                 {
4211                                     myTab.ImageIndex = -1;
4212                                 }
4213                             }
4214                         }
4215                         else
4216                         {
4217                             ReadedStripMenuItem.Enabled = true;
4218                             UnreadStripMenuItem.Enabled = true;
4219                         }
4220                     }
4221                     catch (Exception ex)
4222                     {
4223                         ex.Data["Instance"] = "ListTab(UnreadManage)";
4224                         ex.Data["IsTerminatePermission"] = false;
4225                         throw;
4226                     }
4227
4228                     // タブの表示位置の決定
4229                     SetTabAlignment();
4230
4231                     SplitContainer1.IsPanelInverted = !this._cfgCommon.StatusAreaAtBottom;
4232
4233                     var imgazyobizinet = ThumbnailGenerator.ImgAzyobuziNetInstance;
4234                     imgazyobizinet.Enabled = this._cfgCommon.EnableImgAzyobuziNet;
4235                     imgazyobizinet.DisabledInDM = this._cfgCommon.ImgAzyobuziNetDisabledInDM;
4236
4237                     this.PlaySoundMenuItem.Checked = this._cfgCommon.PlaySound;
4238                     this.PlaySoundFileMenuItem.Checked = this._cfgCommon.PlaySound;
4239                     _fntUnread = this._cfgLocal.FontUnread;
4240                     _clUnread = this._cfgLocal.ColorUnread;
4241                     _fntReaded = this._cfgLocal.FontRead;
4242                     _clReaded = this._cfgLocal.ColorRead;
4243                     _clFav = this._cfgLocal.ColorFav;
4244                     _clOWL = this._cfgLocal.ColorOWL;
4245                     _clRetweet = this._cfgLocal.ColorRetweet;
4246                     _fntDetail = this._cfgLocal.FontDetail;
4247                     _clDetail = this._cfgLocal.ColorDetail;
4248                     _clDetailLink = this._cfgLocal.ColorDetailLink;
4249                     _clDetailBackcolor = this._cfgLocal.ColorDetailBackcolor;
4250                     _clSelf = this._cfgLocal.ColorSelf;
4251                     _clAtSelf = this._cfgLocal.ColorAtSelf;
4252                     _clTarget = this._cfgLocal.ColorTarget;
4253                     _clAtTarget = this._cfgLocal.ColorAtTarget;
4254                     _clAtFromTarget = this._cfgLocal.ColorAtFromTarget;
4255                     _clAtTo = this._cfgLocal.ColorAtTo;
4256                     _clListBackcolor = this._cfgLocal.ColorListBackcolor;
4257                     _clInputBackcolor = this._cfgLocal.ColorInputBackcolor;
4258                     _clInputFont = this._cfgLocal.ColorInputFont;
4259                     _fntInputFont = this._cfgLocal.FontInputFont;
4260                     _brsBackColorMine.Dispose();
4261                     _brsBackColorAt.Dispose();
4262                     _brsBackColorYou.Dispose();
4263                     _brsBackColorAtYou.Dispose();
4264                     _brsBackColorAtFromTarget.Dispose();
4265                     _brsBackColorAtTo.Dispose();
4266                     _brsBackColorNone.Dispose();
4267                     _brsBackColorMine = new SolidBrush(_clSelf);
4268                     _brsBackColorAt = new SolidBrush(_clAtSelf);
4269                     _brsBackColorYou = new SolidBrush(_clTarget);
4270                     _brsBackColorAtYou = new SolidBrush(_clAtTarget);
4271                     _brsBackColorAtFromTarget = new SolidBrush(_clAtFromTarget);
4272                     _brsBackColorAtTo = new SolidBrush(_clAtTo);
4273                     _brsBackColorNone = new SolidBrush(_clListBackcolor);
4274
4275                     try
4276                     {
4277                         if (StatusText.Focused) StatusText.BackColor = _clInputBackcolor;
4278                         StatusText.Font = _fntInputFont;
4279                         StatusText.ForeColor = _clInputFont;
4280                     }
4281                     catch (Exception ex)
4282                     {
4283                         MessageBox.Show(ex.Message);
4284                     }
4285
4286                     try
4287                     {
4288                         InitDetailHtmlFormat();
4289                     }
4290                     catch (Exception ex)
4291                     {
4292                         ex.Data["Instance"] = "Font";
4293                         ex.Data["IsTerminatePermission"] = false;
4294                         throw;
4295                     }
4296
4297                     try
4298                     {
4299                         foreach (TabPage tb in ListTab.TabPages)
4300                         {
4301                             if (this._cfgCommon.TabIconDisp)
4302                             {
4303                                 if (_statuses.Tabs[tb.Text].UnreadCount == 0)
4304                                     tb.ImageIndex = -1;
4305                                 else
4306                                     tb.ImageIndex = 0;
4307                             }
4308                         }
4309                     }
4310                     catch (Exception ex)
4311                     {
4312                         ex.Data["Instance"] = "ListTab(TabIconDisp no2)";
4313                         ex.Data["IsTerminatePermission"] = false;
4314                         throw;
4315                     }
4316
4317                     try
4318                     {
4319                         var oldIconCol = _iconCol;
4320
4321                         if (this._cfgCommon.IconSize != oldIconSz)
4322                             ApplyListViewIconSize(this._cfgCommon.IconSize);
4323
4324                         foreach (TabPage tp in ListTab.TabPages)
4325                         {
4326                             DetailsListView lst = (DetailsListView)tp.Tag;
4327
4328                             using (ControlTransaction.Update(lst))
4329                             {
4330                                 lst.GridLines = this._cfgCommon.ShowGrid;
4331                                 lst.Font = _fntReaded;
4332                                 lst.BackColor = _clListBackcolor;
4333
4334                                 if (_iconCol != oldIconCol)
4335                                     ResetColumns(lst);
4336                             }
4337                         }
4338                     }
4339                     catch (Exception ex)
4340                     {
4341                         ex.Data["Instance"] = "ListView(IconSize)";
4342                         ex.Data["IsTerminatePermission"] = false;
4343                         throw;
4344                     }
4345
4346                     SetMainWindowTitle();
4347                     SetNotifyIconText();
4348
4349                     this.PurgeListViewItemCache();
4350                     _curList?.Refresh();
4351                     ListTab.Refresh();
4352
4353                     _hookGlobalHotkey.UnregisterAllOriginalHotkey();
4354                     if (this._cfgCommon.HotkeyEnabled)
4355                     {
4356                         ///グローバルホットキーの登録。設定で変更可能にするかも
4357                         HookGlobalHotkey.ModKeys modKey = HookGlobalHotkey.ModKeys.None;
4358                         if ((this._cfgCommon.HotkeyModifier & Keys.Alt) == Keys.Alt)
4359                             modKey |= HookGlobalHotkey.ModKeys.Alt;
4360                         if ((this._cfgCommon.HotkeyModifier & Keys.Control) == Keys.Control)
4361                             modKey |= HookGlobalHotkey.ModKeys.Ctrl;
4362                         if ((this._cfgCommon.HotkeyModifier & Keys.Shift) == Keys.Shift)
4363                             modKey |=  HookGlobalHotkey.ModKeys.Shift;
4364                         if ((this._cfgCommon.HotkeyModifier & Keys.LWin) == Keys.LWin)
4365                             modKey |= HookGlobalHotkey.ModKeys.Win;
4366
4367                         _hookGlobalHotkey.RegisterOriginalHotkey(this._cfgCommon.HotkeyKey, this._cfgCommon.HotkeyValue, modKey);
4368                     }
4369
4370                     if (this._cfgCommon.IsUseNotifyGrowl) gh.RegisterGrowl();
4371                     try
4372                     {
4373                         StatusText_TextChanged(null, null);
4374                     }
4375                     catch (Exception)
4376                     {
4377                     }
4378                 }
4379             }
4380             else
4381             {
4382                 // キャンセル時は Twitter クラスの認証情報を画面表示前の状態に戻す
4383                 this.tw.Initialize(oldUser.AccessToken, oldUser.AccessTokenSecret, oldUser.Username, oldUser.UserId);
4384             }
4385
4386             Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
4387
4388             this.TopMost = this._cfgCommon.AlwaysTop;
4389             SaveConfigsAll(false);
4390
4391             if (tw.Username != oldUser.Username)
4392                 await this.doGetFollowersMenu();
4393         }
4394
4395         /// <summary>
4396         /// タブの表示位置を設定する
4397         /// </summary>
4398         private void SetTabAlignment()
4399         {
4400             var newAlignment = this._cfgCommon.ViewTabBottom ? TabAlignment.Bottom : TabAlignment.Top;
4401             if (ListTab.Alignment == newAlignment) return;
4402
4403             //現在の選択状態を退避
4404             var selId = new Dictionary<string, long[]>();
4405             var focusedId = new Dictionary<string, Tuple<long, long>>();
4406             SaveSelectedStatus(selId, focusedId);
4407
4408             ListTab.Alignment = newAlignment;
4409
4410             //選択状態を復帰
4411             foreach (TabPage tab in ListTab.TabPages)
4412             {
4413                 DetailsListView lst = (DetailsListView)tab.Tag;
4414                 TabClass tabInfo = _statuses.Tabs[tab.Text];
4415                 using (ControlTransaction.Update(lst))
4416                 {
4417                     // status_id から ListView 上のインデックスに変換
4418                     var selectedIndices = selId[tab.Text] != null
4419                         ? tabInfo.IndexOf(selId[tab.Text]).Where(x => x != -1).ToArray()
4420                         : null;
4421                     var focusedIndex = tabInfo.IndexOf(focusedId[tab.Text].Item1);
4422                     var selectionMarkIndex = tabInfo.IndexOf(focusedId[tab.Text].Item2);
4423
4424                     this.SelectListItem(lst, selectedIndices, focusedIndex, selectionMarkIndex);
4425                 }
4426             }
4427         }
4428
4429         private void ApplyListViewIconSize(MyCommon.IconSizes iconSz)
4430         {
4431             // アイコンサイズの再設定
4432             _iconCol = false;
4433             switch (iconSz)
4434             {
4435                 case MyCommon.IconSizes.IconNone:
4436                     _iconSz = 0;
4437                     break;
4438                 case MyCommon.IconSizes.Icon16:
4439                     _iconSz = 16;
4440                     break;
4441                 case MyCommon.IconSizes.Icon24:
4442                     _iconSz = 26;
4443                     break;
4444                 case MyCommon.IconSizes.Icon48:
4445                     _iconSz = 48;
4446                     break;
4447                 case MyCommon.IconSizes.Icon48_2:
4448                     _iconSz = 48;
4449                     _iconCol = true;
4450                     break;
4451             }
4452
4453             if (_iconSz > 0)
4454             {
4455                 // ディスプレイの DPI 設定を考慮したサイズを設定する
4456                 _listViewImageList.ImageSize = new Size(
4457                     1,
4458                     (int)Math.Ceiling(this._iconSz * this.CurrentScaleFactor.Height));
4459             }
4460             else
4461             {
4462                 _listViewImageList.ImageSize = new Size(1, 1);
4463             }
4464         }
4465
4466         private void ResetColumns(DetailsListView list)
4467         {
4468             using (ControlTransaction.Update(list))
4469             using (ControlTransaction.Layout(list, false))
4470             {
4471                 // カラムヘッダの再設定
4472                 list.ColumnClick -= MyList_ColumnClick;
4473                 list.DrawColumnHeader -= MyList_DrawColumnHeader;
4474                 list.ColumnReordered -= MyList_ColumnReordered;
4475                 list.ColumnWidthChanged -= MyList_ColumnWidthChanged;
4476
4477                 var cols = list.Columns.Cast<ColumnHeader>().ToList();
4478                 list.Columns.Clear();
4479                 cols.ForEach(col => col.Dispose());
4480                 cols.Clear();
4481
4482                 InitColumns(list, true);
4483
4484                 list.ColumnClick += MyList_ColumnClick;
4485                 list.DrawColumnHeader += MyList_DrawColumnHeader;
4486                 list.ColumnReordered += MyList_ColumnReordered;
4487                 list.ColumnWidthChanged += MyList_ColumnWidthChanged;
4488             }
4489         }
4490
4491         private async void PostBrowser_Navigated(object sender, WebBrowserNavigatedEventArgs e)
4492         {
4493             if (e.Url.AbsoluteUri != "about:blank")
4494             {
4495                 await this.DispSelectedPost();
4496                 await this.OpenUriInBrowserAsync(e.Url.OriginalString);
4497             }
4498         }
4499
4500         private async void PostBrowser_Navigating(object sender, WebBrowserNavigatingEventArgs e)
4501         {
4502             if (e.Url.Scheme == "data")
4503             {
4504                 StatusLabelUrl.Text = PostBrowser.StatusText.Replace("&", "&&");
4505             }
4506             else if (e.Url.AbsoluteUri != "about:blank")
4507             {
4508                 e.Cancel = true;
4509                 // Ctrlを押しながらリンクを開いた場合は、設定と逆の動作をするフラグを true としておく
4510                 await this.OpenUriAsync( e.Url, MyCommon.IsKeyDown( Keys.Control ) );
4511             }
4512         }
4513
4514         public void AddNewTabForSearch(string searchWord)
4515         {
4516             //同一検索条件のタブが既に存在すれば、そのタブアクティブにして終了
4517             foreach (TabClass tb in _statuses.GetTabsByType(MyCommon.TabUsageType.PublicSearch))
4518             {
4519                 if (tb.SearchWords == searchWord && string.IsNullOrEmpty(tb.SearchLang))
4520                 {
4521                     foreach (TabPage tp in ListTab.TabPages)
4522                     {
4523                         if (tb.TabName == tp.Text)
4524                         {
4525                             ListTab.SelectedTab = tp;
4526                             return;
4527                         }
4528                     }
4529                 }
4530             }
4531             //ユニークなタブ名生成
4532             string tabName = searchWord;
4533             for (int i = 0; i <= 100; i++)
4534             {
4535                 if (_statuses.ContainsTab(tabName))
4536                     tabName += "_";
4537                 else
4538                     break;
4539             }
4540             //タブ追加
4541             _statuses.AddTab(tabName, MyCommon.TabUsageType.PublicSearch, null);
4542             AddNewTab(tabName, false, MyCommon.TabUsageType.PublicSearch);
4543             //追加したタブをアクティブに
4544             ListTab.SelectedIndex = ListTab.TabPages.Count - 1;
4545             //検索条件の設定
4546             ComboBox cmb = (ComboBox)ListTab.SelectedTab.Controls["panelSearch"].Controls["comboSearch"];
4547             cmb.Items.Add(searchWord);
4548             cmb.Text = searchWord;
4549             SaveConfigsTabs();
4550             //検索実行
4551             this.SearchButton_Click(ListTab.SelectedTab.Controls["panelSearch"].Controls["comboSearch"], null);
4552         }
4553
4554         private void ShowUserTimeline()
4555         {
4556             if (!this.ExistCurrentPost) return;
4557             AddNewTabForUserTimeline(_curPost.ScreenName);
4558         }
4559
4560         private void SearchComboBox_KeyDown(object sender, KeyEventArgs e)
4561         {
4562             if (e.KeyCode == Keys.Escape)
4563             {
4564                 TabPage relTp = ListTab.SelectedTab;
4565                 RemoveSpecifiedTab(relTp.Text, false);
4566                 SaveConfigsTabs();
4567                 e.SuppressKeyPress = true;
4568             }
4569         }
4570
4571         public void AddNewTabForUserTimeline(string user)
4572         {
4573             //同一検索条件のタブが既に存在すれば、そのタブアクティブにして終了
4574             foreach (TabClass tb in _statuses.GetTabsByType(MyCommon.TabUsageType.UserTimeline))
4575             {
4576                 if (tb.User == user)
4577                 {
4578                     foreach (TabPage tp in ListTab.TabPages)
4579                     {
4580                         if (tb.TabName == tp.Text)
4581                         {
4582                             ListTab.SelectedTab = tp;
4583                             return;
4584                         }
4585                     }
4586                 }
4587             }
4588             //ユニークなタブ名生成
4589             string tabName = "user:" + user;
4590             while (_statuses.ContainsTab(tabName))
4591             {
4592                 tabName += "_";
4593             }
4594             //タブ追加
4595             _statuses.AddTab(tabName, MyCommon.TabUsageType.UserTimeline, null);
4596             var tab = this._statuses.Tabs[tabName];
4597             tab.User = user;
4598             AddNewTab(tabName, false, MyCommon.TabUsageType.UserTimeline);
4599             //追加したタブをアクティブに
4600             ListTab.SelectedIndex = ListTab.TabPages.Count - 1;
4601             SaveConfigsTabs();
4602             //検索実行
4603             this.GetUserTimelineAsync(tab);
4604         }
4605
4606         public bool AddNewTab(string tabName, bool startup, MyCommon.TabUsageType tabType, ListElement listInfo = null)
4607         {
4608             //重複チェック
4609             foreach (TabPage tb in ListTab.TabPages)
4610             {
4611                 if (tb.Text == tabName) return false;
4612             }
4613
4614             //新規タブ名チェック
4615             if (tabName == Properties.Resources.AddNewTabText1) return false;
4616
4617             //タブタイプ重複チェック
4618             if (!startup)
4619             {
4620                 if (tabType == MyCommon.TabUsageType.DirectMessage ||
4621                    tabType == MyCommon.TabUsageType.Favorites ||
4622                    tabType == MyCommon.TabUsageType.Home ||
4623                    tabType == MyCommon.TabUsageType.Mentions ||
4624                    tabType == MyCommon.TabUsageType.Related)
4625                 {
4626                     if (_statuses.GetTabByType(tabType) != null) return false;
4627                 }
4628             }
4629
4630             var _tabPage = new TabPage();
4631             var _listCustom = new DetailsListView();
4632
4633             int cnt = ListTab.TabPages.Count;
4634
4635             ///ToDo:Create and set controls follow tabtypes
4636
4637             using (ControlTransaction.Update(_listCustom))
4638             using (ControlTransaction.Layout(this.SplitContainer1.Panel1, false))
4639             using (ControlTransaction.Layout(this.SplitContainer1.Panel2, false))
4640             using (ControlTransaction.Layout(this.SplitContainer1, false))
4641             using (ControlTransaction.Layout(this.ListTab, false))
4642             using (ControlTransaction.Layout(this))
4643             using (ControlTransaction.Layout(_tabPage, false))
4644             {
4645                 _tabPage.Controls.Add(_listCustom);
4646
4647                 /// UserTimeline関連
4648                 if (tabType == MyCommon.TabUsageType.UserTimeline || tabType == MyCommon.TabUsageType.Lists)
4649                 {
4650                     var label = new Label();
4651                     label.Dock = DockStyle.Top;
4652                     label.Name = "labelUser";
4653                     if (tabType == MyCommon.TabUsageType.Lists)
4654                     {
4655                         label.Text = listInfo.ToString();
4656                     }
4657                     else
4658                     {
4659                         label.Text = _statuses.Tabs[tabName].User + "'s Timeline";
4660                     }
4661                     label.TextAlign = ContentAlignment.MiddleLeft;
4662                     using (ComboBox tmpComboBox = new ComboBox())
4663                     {
4664                         label.Height = tmpComboBox.Height;
4665                     }
4666                     _tabPage.Controls.Add(label);
4667                 }
4668                 /// 検索関連の準備
4669                 else if (tabType == MyCommon.TabUsageType.PublicSearch)
4670                 {
4671                     var pnl = new Panel();
4672
4673                     var lbl = new Label();
4674                     var cmb = new ComboBox();
4675                     var btn = new Button();
4676                     var cmbLang = new ComboBox();
4677
4678                     using (ControlTransaction.Layout(pnl, false))
4679                     {
4680                         pnl.Controls.Add(cmb);
4681                         pnl.Controls.Add(cmbLang);
4682                         pnl.Controls.Add(btn);
4683                         pnl.Controls.Add(lbl);
4684                         pnl.Name = "panelSearch";
4685                         pnl.Dock = DockStyle.Top;
4686                         pnl.Height = cmb.Height;
4687                         pnl.Enter += SearchControls_Enter;
4688                         pnl.Leave += SearchControls_Leave;
4689
4690                         cmb.Text = "";
4691                         cmb.Anchor = AnchorStyles.Left | AnchorStyles.Right;
4692                         cmb.Dock = DockStyle.Fill;
4693                         cmb.Name = "comboSearch";
4694                         cmb.DropDownStyle = ComboBoxStyle.DropDown;
4695                         cmb.ImeMode = ImeMode.NoControl;
4696                         cmb.TabStop = false;
4697                         cmb.AutoCompleteMode = AutoCompleteMode.None;
4698                         cmb.KeyDown += SearchComboBox_KeyDown;
4699
4700                         cmbLang.Text = "";
4701                         cmbLang.Anchor = AnchorStyles.Left | AnchorStyles.Right;
4702                         cmbLang.Dock = DockStyle.Right;
4703                         cmbLang.Width = 50;
4704                         cmbLang.Name = "comboLang";
4705                         cmbLang.DropDownStyle = ComboBoxStyle.DropDownList;
4706                         cmbLang.TabStop = false;
4707                         cmbLang.Items.Add("");
4708                         cmbLang.Items.Add("ja");
4709                         cmbLang.Items.Add("en");
4710                         cmbLang.Items.Add("ar");
4711                         cmbLang.Items.Add("da");
4712                         cmbLang.Items.Add("nl");
4713                         cmbLang.Items.Add("fa");
4714                         cmbLang.Items.Add("fi");
4715                         cmbLang.Items.Add("fr");
4716                         cmbLang.Items.Add("de");
4717                         cmbLang.Items.Add("hu");
4718                         cmbLang.Items.Add("is");
4719                         cmbLang.Items.Add("it");
4720                         cmbLang.Items.Add("no");
4721                         cmbLang.Items.Add("pl");
4722                         cmbLang.Items.Add("pt");
4723                         cmbLang.Items.Add("ru");
4724                         cmbLang.Items.Add("es");
4725                         cmbLang.Items.Add("sv");
4726                         cmbLang.Items.Add("th");
4727
4728                         lbl.Text = "Search(C-S-f)";
4729                         lbl.Name = "label1";
4730                         lbl.Dock = DockStyle.Left;
4731                         lbl.Width = 90;
4732                         lbl.Height = cmb.Height;
4733                         lbl.TextAlign = ContentAlignment.MiddleLeft;
4734
4735                         btn.Text = "Search";
4736                         btn.Name = "buttonSearch";
4737                         btn.UseVisualStyleBackColor = true;
4738                         btn.Dock = DockStyle.Right;
4739                         btn.TabStop = false;
4740                         btn.Click += SearchButton_Click;
4741
4742                         TabClass tab;
4743                         if (_statuses.Tabs.TryGetValue(tabName, out tab))
4744                         {
4745                             if (!string.IsNullOrEmpty(tab.SearchWords))
4746                             {
4747                                 cmb.Items.Add(tab.SearchWords);
4748                                 cmb.Text = tab.SearchWords;
4749                             }
4750
4751                             cmbLang.Text = tab.SearchLang;
4752                         }
4753
4754                         _tabPage.Controls.Add(pnl);
4755                     }
4756                 }
4757
4758                 _tabPage.Tag = _listCustom;
4759                 this.ListTab.Controls.Add(_tabPage);
4760
4761                 _tabPage.Location = new Point(4, 4);
4762                 _tabPage.Name = "CTab" + cnt.ToString();
4763                 _tabPage.Size = new Size(380, 260);
4764                 _tabPage.TabIndex = 2 + cnt;
4765                 _tabPage.Text = tabName;
4766                 _tabPage.UseVisualStyleBackColor = true;
4767
4768                 _listCustom.AllowColumnReorder = true;
4769                 _listCustom.ContextMenuStrip = this.ContextMenuOperate;
4770                 _listCustom.ColumnHeaderContextMenuStrip = this.ContextMenuColumnHeader;
4771                 _listCustom.Dock = DockStyle.Fill;
4772                 _listCustom.FullRowSelect = true;
4773                 _listCustom.HideSelection = false;
4774                 _listCustom.Location = new Point(0, 0);
4775                 _listCustom.Margin = new Padding(0);
4776                 _listCustom.Name = "CList" + Environment.TickCount.ToString();
4777                 _listCustom.ShowItemToolTips = true;
4778                 _listCustom.Size = new Size(380, 260);
4779                 _listCustom.UseCompatibleStateImageBehavior = false;
4780                 _listCustom.View = View.Details;
4781                 _listCustom.OwnerDraw = true;
4782                 _listCustom.VirtualMode = true;
4783                 _listCustom.Font = _fntReaded;
4784                 _listCustom.BackColor = _clListBackcolor;
4785
4786                 _listCustom.GridLines = this._cfgCommon.ShowGrid;
4787                 _listCustom.AllowDrop = true;
4788
4789                 _listCustom.SmallImageList = _listViewImageList;
4790
4791                 InitColumns(_listCustom, startup);
4792
4793                 _listCustom.SelectedIndexChanged += MyList_SelectedIndexChanged;
4794                 _listCustom.MouseDoubleClick += MyList_MouseDoubleClick;
4795                 _listCustom.ColumnClick += MyList_ColumnClick;
4796                 _listCustom.DrawColumnHeader += MyList_DrawColumnHeader;
4797                 _listCustom.DragDrop += TweenMain_DragDrop;
4798                 _listCustom.DragEnter += TweenMain_DragEnter;
4799                 _listCustom.DragOver += TweenMain_DragOver;
4800                 _listCustom.DrawItem += MyList_DrawItem;
4801                 _listCustom.MouseClick += MyList_MouseClick;
4802                 _listCustom.ColumnReordered += MyList_ColumnReordered;
4803                 _listCustom.ColumnWidthChanged += MyList_ColumnWidthChanged;
4804                 _listCustom.CacheVirtualItems += MyList_CacheVirtualItems;
4805                 _listCustom.RetrieveVirtualItem += MyList_RetrieveVirtualItem;
4806                 _listCustom.DrawSubItem += MyList_DrawSubItem;
4807                 _listCustom.HScrolled += MyList_HScrolled;
4808             }
4809
4810             return true;
4811         }
4812
4813         public bool RemoveSpecifiedTab(string TabName, bool confirm)
4814         {
4815             var tabInfo = _statuses.GetTabByName(TabName);
4816             if (tabInfo.IsDefaultTabType || tabInfo.Protected) return false;
4817
4818             if (confirm)
4819             {
4820                 string tmp = string.Format(Properties.Resources.RemoveSpecifiedTabText1, Environment.NewLine);
4821                 if (MessageBox.Show(tmp, TabName + " " + Properties.Resources.RemoveSpecifiedTabText2,
4822                                  MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Cancel)
4823                 {
4824                     return false;
4825                 }
4826             }
4827
4828             var _tabPage = ListTab.TabPages.Cast<TabPage>().FirstOrDefault(tp => tp.Text == TabName);
4829             if (_tabPage == null) return false;
4830
4831             SetListProperty();   //他のタブに列幅等を反映
4832
4833             //オブジェクトインスタンスの削除
4834             DetailsListView _listCustom = (DetailsListView)_tabPage.Tag;
4835             _tabPage.Tag = null;
4836
4837             using (ControlTransaction.Layout(this.SplitContainer1.Panel1, false))
4838             using (ControlTransaction.Layout(this.SplitContainer1.Panel2, false))
4839             using (ControlTransaction.Layout(this.SplitContainer1, false))
4840             using (ControlTransaction.Layout(this.ListTab, false))
4841             using (ControlTransaction.Layout(this))
4842             using (ControlTransaction.Layout(_tabPage, false))
4843             {
4844                 if (this.ListTab.SelectedTab == _tabPage)
4845                 {
4846                     this.ListTab.SelectTab((this._beforeSelectedTab != null && this.ListTab.TabPages.Contains(this._beforeSelectedTab)) ? this._beforeSelectedTab : this.ListTab.TabPages[0]);
4847                     this._beforeSelectedTab = null;
4848                 }
4849                 this.ListTab.Controls.Remove(_tabPage);
4850
4851                 // 後付けのコントロールを破棄
4852                 if (tabInfo.TabType == MyCommon.TabUsageType.UserTimeline || tabInfo.TabType == MyCommon.TabUsageType.Lists)
4853                 {
4854                     using (Control label = _tabPage.Controls["labelUser"])
4855                     {
4856                         _tabPage.Controls.Remove(label);
4857                     }
4858                 }
4859                 else if (tabInfo.TabType == MyCommon.TabUsageType.PublicSearch)
4860                 {
4861                     using (Control pnl = _tabPage.Controls["panelSearch"])
4862                     {
4863                         pnl.Enter -= SearchControls_Enter;
4864                         pnl.Leave -= SearchControls_Leave;
4865                         _tabPage.Controls.Remove(pnl);
4866
4867                         foreach (Control ctrl in pnl.Controls)
4868                         {
4869                             if (ctrl.Name == "buttonSearch")
4870                             {
4871                                 ctrl.Click -= SearchButton_Click;
4872                             }
4873                             else if (ctrl.Name == "comboSearch")
4874                             {
4875                                 ctrl.KeyDown -= SearchComboBox_KeyDown;
4876                             }
4877                             pnl.Controls.Remove(ctrl);
4878                             ctrl.Dispose();
4879                         }
4880                     }
4881                 }
4882
4883                 _tabPage.Controls.Remove(_listCustom);
4884
4885                 _listCustom.SelectedIndexChanged -= MyList_SelectedIndexChanged;
4886                 _listCustom.MouseDoubleClick -= MyList_MouseDoubleClick;
4887                 _listCustom.ColumnClick -= MyList_ColumnClick;
4888                 _listCustom.DrawColumnHeader -= MyList_DrawColumnHeader;
4889                 _listCustom.DragDrop -= TweenMain_DragDrop;
4890                 _listCustom.DragEnter -= TweenMain_DragEnter;
4891                 _listCustom.DragOver -= TweenMain_DragOver;
4892                 _listCustom.DrawItem -= MyList_DrawItem;
4893                 _listCustom.MouseClick -= MyList_MouseClick;
4894                 _listCustom.ColumnReordered -= MyList_ColumnReordered;
4895                 _listCustom.ColumnWidthChanged -= MyList_ColumnWidthChanged;
4896                 _listCustom.CacheVirtualItems -= MyList_CacheVirtualItems;
4897                 _listCustom.RetrieveVirtualItem -= MyList_RetrieveVirtualItem;
4898                 _listCustom.DrawSubItem -= MyList_DrawSubItem;
4899                 _listCustom.HScrolled -= MyList_HScrolled;
4900
4901                 var cols = _listCustom.Columns.Cast<ColumnHeader>().ToList<ColumnHeader>();
4902                 _listCustom.Columns.Clear();
4903                 cols.ForEach(col => col.Dispose());
4904                 cols.Clear();
4905
4906                 _listCustom.ContextMenuStrip = null;
4907                 _listCustom.ColumnHeaderContextMenuStrip = null;
4908                 _listCustom.Font = null;
4909
4910                 _listCustom.SmallImageList = null;
4911                 _listCustom.ListViewItemSorter = null;
4912
4913                 //キャッシュのクリア
4914                 if (_curTab.Equals(_tabPage))
4915                 {
4916                     _curTab = null;
4917                     _curItemIndex = -1;
4918                     _curList = null;
4919                     _curPost = null;
4920                 }
4921                 this.PurgeListViewItemCache();
4922             }
4923
4924             _tabPage.Dispose();
4925             _listCustom.Dispose();
4926             _statuses.RemoveTab(TabName);
4927
4928             foreach (TabPage tp in ListTab.TabPages)
4929             {
4930                 DetailsListView lst = (DetailsListView)tp.Tag;
4931                 var count = _statuses.Tabs[tp.Text].AllCount;
4932                 if (lst.VirtualListSize != count)
4933                 {
4934                     lst.VirtualListSize = count;
4935                 }
4936             }
4937
4938             return true;
4939         }
4940
4941         private void ListTab_Deselected(object sender, TabControlEventArgs e)
4942         {
4943             this.PurgeListViewItemCache();
4944             _beforeSelectedTab = e.TabPage;
4945         }
4946
4947         private void ListTab_MouseMove(object sender, MouseEventArgs e)
4948         {
4949             //タブのD&D
4950
4951             if (!this._cfgCommon.TabMouseLock && e.Button == MouseButtons.Left && _tabDrag)
4952             {
4953                 string tn = "";
4954                 Rectangle dragEnableRectangle = new Rectangle((int)(_tabMouseDownPoint.X - (SystemInformation.DragSize.Width / 2)), (int)(_tabMouseDownPoint.Y - (SystemInformation.DragSize.Height / 2)), SystemInformation.DragSize.Width, SystemInformation.DragSize.Height);
4955                 if (!dragEnableRectangle.Contains(e.Location))
4956                 {
4957                     //タブが多段の場合にはMouseDownの前の段階で選択されたタブの段が変わっているので、このタイミングでカーソルの位置からタブを判定出来ない。
4958                     tn = ListTab.SelectedTab.Text;
4959                 }
4960
4961                 if (string.IsNullOrEmpty(tn)) return;
4962
4963                 foreach (TabPage tb in ListTab.TabPages)
4964                 {
4965                     if (tb.Text == tn)
4966                     {
4967                         ListTab.DoDragDrop(tb, DragDropEffects.All);
4968                         break;
4969                     }
4970                 }
4971             }
4972             else
4973             {
4974                 _tabDrag = false;
4975             }
4976
4977             Point cpos = new Point(e.X, e.Y);
4978             for (int i = 0; i < ListTab.TabPages.Count; i++)
4979             {
4980                 Rectangle rect = ListTab.GetTabRect(i);
4981                 if (rect.Left <= cpos.X & cpos.X <= rect.Right &
4982                    rect.Top <= cpos.Y & cpos.Y <= rect.Bottom)
4983                 {
4984                     _rclickTabName = ListTab.TabPages[i].Text;
4985                     break;
4986                 }
4987             }
4988         }
4989
4990         private async void ListTab_SelectedIndexChanged(object sender, EventArgs e)
4991         {
4992             //_curList.Refresh();
4993             SetMainWindowTitle();
4994             SetStatusLabelUrl();
4995             SetApiStatusLabel();
4996             if (ListTab.Focused || ((Control)ListTab.SelectedTab.Tag).Focused) this.Tag = ListTab.Tag;
4997             TabMenuControl(ListTab.SelectedTab.Text);
4998             this.PushSelectPostChain();
4999             await DispSelectedPost();
5000         }
5001
5002         private void SetListProperty()
5003         {
5004             //削除などで見つからない場合は処理せず
5005             if (_curList == null) return;
5006             if (!_isColumnChanged) return;
5007
5008             int[] dispOrder = new int[_curList.Columns.Count];
5009             for (int i = 0; i < _curList.Columns.Count; i++)
5010             {
5011                 for (int j = 0; j < _curList.Columns.Count; j++)
5012                 {
5013                     if (_curList.Columns[j].DisplayIndex == i)
5014                     {
5015                         dispOrder[i] = j;
5016                         break;
5017                     }
5018                 }
5019             }
5020
5021             //列幅、列並びを他のタブに設定
5022             foreach (TabPage tb in ListTab.TabPages)
5023             {
5024                 if (!tb.Equals(_curTab))
5025                 {
5026                     if (tb.Tag != null && tb.Controls.Count > 0)
5027                     {
5028                         DetailsListView lst = (DetailsListView)tb.Tag;
5029                         for (int i = 0; i < lst.Columns.Count; i++)
5030                         {
5031                             lst.Columns[dispOrder[i]].DisplayIndex = i;
5032                             lst.Columns[i].Width = _curList.Columns[i].Width;
5033                         }
5034                     }
5035                 }
5036             }
5037
5038             _isColumnChanged = false;
5039         }
5040
5041         private void PostBrowser_StatusTextChanged(object sender, EventArgs e)
5042         {
5043             try
5044             {
5045                 if (PostBrowser.StatusText.StartsWith("http") || PostBrowser.StatusText.StartsWith("ftp")
5046                         || PostBrowser.StatusText.StartsWith("data"))
5047                 {
5048                     StatusLabelUrl.Text = PostBrowser.StatusText.Replace("&", "&&");
5049                 }
5050                 if (string.IsNullOrEmpty(PostBrowser.StatusText))
5051                 {
5052                     SetStatusLabelUrl();
5053                 }
5054             }
5055             catch (Exception)
5056             {
5057             }
5058         }
5059
5060         private void StatusText_KeyPress(object sender, KeyPressEventArgs e)
5061         {
5062             if (e.KeyChar == '@')
5063             {
5064                 if (!this._cfgCommon.UseAtIdSupplement) return;
5065                 //@マーク
5066                 int cnt = AtIdSupl.ItemCount;
5067                 ShowSuplDialog(StatusText, AtIdSupl);
5068                 if (cnt != AtIdSupl.ItemCount) _modifySettingAtId = true;
5069                 e.Handled = true;
5070             }
5071             else if (e.KeyChar == '#')
5072             {
5073                 if (!this._cfgCommon.UseHashSupplement) return;
5074                 ShowSuplDialog(StatusText, HashSupl);
5075                 e.Handled = true;
5076             }
5077         }
5078
5079         public void ShowSuplDialog(TextBox owner, AtIdSupplement dialog)
5080         {
5081             ShowSuplDialog(owner, dialog, 0, "");
5082         }
5083
5084         public void ShowSuplDialog(TextBox owner, AtIdSupplement dialog, int offset)
5085         {
5086             ShowSuplDialog(owner, dialog, offset, "");
5087         }
5088
5089         public void ShowSuplDialog(TextBox owner, AtIdSupplement dialog, int offset, string startswith)
5090         {
5091             dialog.StartsWith = startswith;
5092             if (dialog.Visible)
5093             {
5094                 dialog.Focus();
5095             }
5096             else
5097             {
5098                 dialog.ShowDialog();
5099             }
5100             this.TopMost = this._cfgCommon.AlwaysTop;
5101             int selStart = owner.SelectionStart;
5102             string fHalf = "";
5103             string eHalf = "";
5104             if (dialog.DialogResult == DialogResult.OK)
5105             {
5106                 if (!string.IsNullOrEmpty(dialog.inputText))
5107                 {
5108                     if (selStart > 0)
5109                     {
5110                         fHalf = owner.Text.Substring(0, selStart - offset);
5111                     }
5112                     if (selStart < owner.Text.Length)
5113                     {
5114                         eHalf = owner.Text.Substring(selStart);
5115                     }
5116                     owner.Text = fHalf + dialog.inputText + eHalf;
5117                     owner.SelectionStart = selStart + dialog.inputText.Length;
5118                 }
5119             }
5120             else
5121             {
5122                 if (selStart > 0)
5123                 {
5124                     fHalf = owner.Text.Substring(0, selStart);
5125                 }
5126                 if (selStart < owner.Text.Length)
5127                 {
5128                     eHalf = owner.Text.Substring(selStart);
5129                 }
5130                 owner.Text = fHalf + eHalf;
5131                 if (selStart > 0)
5132                 {
5133                     owner.SelectionStart = selStart;
5134                 }
5135             }
5136             owner.Focus();
5137         }
5138
5139         private void StatusText_KeyUp(object sender, KeyEventArgs e)
5140         {
5141             //スペースキーで未読ジャンプ
5142             if (!e.Alt && !e.Control && !e.Shift)
5143             {
5144                 if (e.KeyCode == Keys.Space || e.KeyCode == Keys.ProcessKey)
5145                 {
5146                     bool isSpace = false;
5147                     foreach (char c in StatusText.Text.ToCharArray())
5148                     {
5149                         if (c == ' ' || c == ' ')
5150                         {
5151                             isSpace = true;
5152                         }
5153                         else
5154                         {
5155                             isSpace = false;
5156                             break;
5157                         }
5158                     }
5159                     if (isSpace)
5160                     {
5161                         e.Handled = true;
5162                         StatusText.Text = "";
5163                         JumpUnreadMenuItem_Click(null, null);
5164                     }
5165                 }
5166             }
5167             this.StatusText_TextChanged(null, null);
5168         }
5169
5170         private void StatusText_TextChanged(object sender, EventArgs e)
5171         {
5172             //文字数カウント
5173             int pLen = GetRestStatusCount(true, false);
5174             lblLen.Text = pLen.ToString();
5175             if (pLen < 0)
5176             {
5177                 StatusText.ForeColor = Color.Red;
5178             }
5179             else
5180             {
5181                 StatusText.ForeColor = _clInputFont;
5182             }
5183             if (string.IsNullOrEmpty(StatusText.Text))
5184             {
5185                 this.inReplyTo = null;
5186             }
5187         }
5188
5189         private int GetRestStatusCount(bool isAuto, bool isAddFooter)
5190         {
5191             //文字数カウント
5192             var statusText = this.StatusText.Text;
5193             statusText = statusText.Replace("\r\n", "\n");
5194
5195             int pLen = 140 - statusText.Length;
5196             if (this.NotifyIcon1 == null || !this.NotifyIcon1.Visible) return pLen;
5197             if ((isAuto && !MyCommon.IsKeyDown(Keys.Control) && this._cfgCommon.PostShiftEnter) ||
5198                 (isAuto && !MyCommon.IsKeyDown(Keys.Shift) && !this._cfgCommon.PostShiftEnter) ||
5199                 (!isAuto && isAddFooter))
5200             {
5201                 if (this._cfgLocal.UseRecommendStatus)
5202                     pLen -= this.recommendedStatusFooter.Length;
5203                 else if (this._cfgLocal.StatusText.Length > 0)
5204                     pLen -= this._cfgLocal.StatusText.Length + 1;
5205             }
5206             if (!string.IsNullOrEmpty(HashMgr.UseHash))
5207             {
5208                 pLen -= HashMgr.UseHash.Length + 1;
5209             }
5210             //foreach (Match m in Regex.Matches(statusText, "https?:\/\/[-_.!~*//()a-zA-Z0-9;\/?:\@&=+\$,%#^]+"))
5211             //{
5212             //    pLen += m.Length - SettingDialog.TwitterConfiguration.ShortUrlLength;
5213             //}
5214             foreach (Match m in Regex.Matches(statusText, Twitter.rgUrl, RegexOptions.IgnoreCase))
5215             {
5216                 string before = m.Result("${before}");
5217                 string url = m.Result("${url}");
5218                 string protocol = m.Result("${protocol}");
5219                 string domain = m.Result("${domain}");
5220                 string path = m.Result("${path}");
5221                 if (protocol.Length == 0)
5222                 {
5223                     if (Regex.IsMatch(before, Twitter.url_invalid_without_protocol_preceding_chars))
5224                     {
5225                         continue;
5226                     }
5227
5228                     bool last_url_invalid_match = false;
5229                     string lasturl = null;
5230                     foreach (Match mm in Regex.Matches(domain, Twitter.url_valid_ascii_domain, RegexOptions.IgnoreCase))
5231                     {
5232                         lasturl = mm.ToString();
5233                         last_url_invalid_match = Regex.IsMatch(lasturl, Twitter.url_invalid_short_domain, RegexOptions.IgnoreCase);
5234                         if (!last_url_invalid_match)
5235                         {
5236                             pLen += lasturl.Length - this.tw.Configuration.ShortUrlLength;
5237                         }
5238                     }
5239
5240                     if (path.Length != 0)
5241                     {
5242                         if (last_url_invalid_match)
5243                         {
5244                             pLen += lasturl.Length - this.tw.Configuration.ShortUrlLength;
5245                         }
5246                         pLen += path.Length;
5247                     }
5248                 }
5249                 else
5250                 {
5251                     int shortUrlLength = protocol == "https://"
5252                         ? this.tw.Configuration.ShortUrlLengthHttps
5253                         : this.tw.Configuration.ShortUrlLength;
5254
5255                     pLen += url.Length - shortUrlLength;
5256                 }
5257                 
5258                 //if (m.Result("${url}").Length > SettingDialog.TwitterConfiguration.ShortUrlLength)
5259                 //{
5260                 //    pLen += m.Result("${url}").Length - SettingDialog.TwitterConfiguration.ShortUrlLength;
5261                 //}
5262             }
5263             if (ImageSelector.Visible && !string.IsNullOrEmpty(ImageSelector.ServiceName))
5264             {
5265                 pLen -= this.tw.Configuration.CharactersReservedPerMedia;
5266             }
5267             return pLen;
5268         }
5269
5270         private void MyList_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e)
5271         {
5272             this.itemCacheLock.EnterUpgradeableReadLock();
5273             try
5274             {
5275                 if (_curList.Equals(sender))
5276                 {
5277                     if (_itemCache != null &&
5278                        e.StartIndex >= _itemCacheIndex &&
5279                        e.EndIndex < _itemCacheIndex + _itemCache.Length)
5280                     {
5281                         //If the newly requested cache is a subset of the old cache, 
5282                         //no need to rebuild everything, so do nothing.
5283                         return;
5284                     }
5285
5286                     //Now we need to rebuild the cache.
5287                     CreateCache(e.StartIndex, e.EndIndex);
5288                 }
5289             }
5290             finally { this.itemCacheLock.ExitUpgradeableReadLock(); }
5291         }
5292
5293         private void MyList_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
5294         {
5295             ListViewItem item = null;
5296             PostClass cacheItemPost = null;
5297
5298             if (_curList.Equals(sender))
5299                 this.TryGetListViewItemCache(e.ItemIndex, out item, out cacheItemPost);
5300
5301             if (item == null)
5302             {
5303                 //A cache miss, so create a new ListViewItem and pass it back.
5304                 TabPage tb = (TabPage)((DetailsListView)sender).Parent;
5305                 try
5306                 {
5307                     item = this.CreateItem(tb, _statuses.Tabs[tb.Text][e.ItemIndex], e.ItemIndex);
5308                 }
5309                 catch (Exception)
5310                 {
5311                     //不正な要求に対する間に合わせの応答
5312                     string[] sitem = {"", "", "", "", "", "", "", ""};
5313                     item = new ImageListViewItem(sitem);
5314                 }
5315             }
5316
5317             e.Item = item;
5318         }
5319
5320         private void CreateCache(int StartIndex, int EndIndex)
5321         {
5322             this.itemCacheLock.EnterWriteLock();
5323             try
5324             {
5325                 var tabInfo = _statuses.Tabs[_curTab.Text];
5326
5327                 //キャッシュ要求(要求範囲±30を作成)
5328                 StartIndex -= 30;
5329                 if (StartIndex < 0) StartIndex = 0;
5330                 EndIndex += 30;
5331                 if (EndIndex >= tabInfo.AllCount) EndIndex = tabInfo.AllCount - 1;
5332                 _postCache = tabInfo[StartIndex, EndIndex]; //配列で取得
5333                 _itemCacheIndex = StartIndex;
5334
5335                 _itemCache = new ListViewItem[0] {};
5336                 Array.Resize(ref _itemCache, _postCache.Length);
5337
5338                 for (int i = 0; i < _postCache.Length; i++)
5339                 {
5340                     _itemCache[i] = CreateItem(_curTab, _postCache[i], StartIndex + i);
5341                 }
5342             }
5343             catch (Exception)
5344             {
5345                 //キャッシュ要求が実データとずれるため(イベントの遅延?)
5346                 _postCache = null;
5347                 _itemCacheIndex = -1;
5348                 _itemCache = null;
5349             }
5350             finally { this.itemCacheLock.ExitWriteLock(); }
5351         }
5352
5353         /// <summary>
5354         /// DetailsListView のための ListViewItem のキャッシュを消去する
5355         /// </summary>
5356         private void PurgeListViewItemCache()
5357         {
5358             this.itemCacheLock.EnterWriteLock();
5359             try
5360             {
5361                 this._itemCache = null;
5362                 this._itemCacheIndex = -1;
5363                 this._postCache = null;
5364             }
5365             finally { this.itemCacheLock.ExitWriteLock(); }
5366         }
5367
5368         private bool TryGetListViewItemCache(int index, out ListViewItem item, out PostClass post)
5369         {
5370             this.itemCacheLock.EnterReadLock();
5371             try
5372             {
5373                 if (this._itemCache != null && index >= this._itemCacheIndex && index < this._itemCacheIndex + this._itemCache.Length)
5374                 {
5375                     item = this._itemCache[index - _itemCacheIndex];
5376                     post = this._postCache[index - _itemCacheIndex];
5377                     return true;
5378                 }
5379             }
5380             finally { this.itemCacheLock.ExitReadLock(); }
5381
5382             item = null;
5383             post = null;
5384             return false;
5385         }
5386
5387         private ListViewItem CreateItem(TabPage Tab, PostClass Post, int Index)
5388         {
5389             StringBuilder mk = new StringBuilder();
5390             //if (Post.IsDeleted) mk.Append("×");
5391             //if (Post.IsMark) mk.Append("♪");
5392             //if (Post.IsProtect) mk.Append("Ю");
5393             //if (Post.InReplyToStatusId != null) mk.Append("⇒");
5394             if (Post.FavoritedCount > 0) mk.Append("+" + Post.FavoritedCount.ToString());
5395             ImageListViewItem itm;
5396             if (Post.RetweetedId == null)
5397             {
5398                 string[] sitem= {"",
5399                                  Post.Nickname,
5400                                  Post.IsDeleted ? "(DELETED)" : Post.TextSingleLine,
5401                                  Post.CreatedAt.ToString(this._cfgCommon.DateTimeFormat),
5402                                  Post.ScreenName,
5403                                  "",
5404                                  mk.ToString(),
5405                                  Post.Source};
5406                 itm = new ImageListViewItem(sitem, this.IconCache, Post.ImageUrl);
5407             }
5408             else
5409             {
5410                 string[] sitem = {"",
5411                                   Post.Nickname,
5412                                   Post.IsDeleted ? "(DELETED)" : Post.TextSingleLine,
5413                                   Post.CreatedAt.ToString(this._cfgCommon.DateTimeFormat),
5414                                   Post.ScreenName + Environment.NewLine + "(RT:" + Post.RetweetedBy + ")",
5415                                   "",
5416                                   mk.ToString(),
5417                                   Post.Source};
5418                 itm = new ImageListViewItem(sitem, this.IconCache, Post.ImageUrl);
5419             }
5420             itm.StateIndex = Post.StateIndex;
5421
5422             bool read = Post.IsRead;
5423             //未読管理していなかったら既読として扱う
5424             if (!_statuses.Tabs[Tab.Text].UnreadManage || !this._cfgCommon.UnreadManage) read = true;
5425             ChangeItemStyleRead(read, itm, Post, null);
5426             if (Tab.Equals(_curTab)) ColorizeList(itm, Index);
5427             return itm;
5428         }
5429
5430         /// <summary>
5431         /// 全てのタブの振り分けルールを反映し直します
5432         /// </summary>
5433         private void ApplyPostFilters()
5434         {
5435             using (ControlTransaction.Cursor(this, Cursors.WaitCursor))
5436             {
5437                 this.PurgeListViewItemCache();
5438                 this._curPost = null;
5439                 this._curItemIndex = -1;
5440                 this._statuses.FilterAll();
5441
5442                 foreach (TabPage tabPage in this.ListTab.TabPages)
5443                 {
5444                     var tab = this._statuses.Tabs[tabPage.Text];
5445
5446                     var listview = (DetailsListView)tabPage.Tag;
5447                     using (ControlTransaction.Update(listview))
5448                     {
5449                         listview.VirtualListSize = tab.AllCount;
5450                     }
5451
5452                     if (this._cfgCommon.TabIconDisp)
5453                     {
5454                         if (tab.UnreadCount > 0)
5455                             tabPage.ImageIndex = 0;
5456                         else
5457                             tabPage.ImageIndex = -1;
5458                     }
5459                 }
5460
5461                 if (!this._cfgCommon.TabIconDisp)
5462                     this.ListTab.Refresh();
5463
5464                 SetMainWindowTitle();
5465                 SetStatusLabelUrl();
5466             }
5467         }
5468
5469         private void MyList_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
5470         {
5471             e.DrawDefault = true;
5472         }
5473
5474         private void MyList_HScrolled(object sender, EventArgs e)
5475         {
5476             DetailsListView listView = (DetailsListView)sender;
5477             listView.Refresh();
5478         }
5479
5480         private void MyList_DrawItem(object sender, DrawListViewItemEventArgs e)
5481         {
5482             if (e.State == 0) return;
5483             e.DrawDefault = false;
5484
5485             SolidBrush brs2 = null;
5486             if (!e.Item.Selected)     //e.ItemStateでうまく判定できない???
5487             {
5488                 if (e.Item.BackColor == _clSelf)
5489                     brs2 = _brsBackColorMine;
5490                 else if (e.Item.BackColor == _clAtSelf)
5491                     brs2 = _brsBackColorAt;
5492                 else if (e.Item.BackColor == _clTarget)
5493                     brs2 = _brsBackColorYou;
5494                 else if (e.Item.BackColor == _clAtTarget)
5495                     brs2 = _brsBackColorAtYou;
5496                 else if (e.Item.BackColor == _clAtFromTarget)
5497                     brs2 = _brsBackColorAtFromTarget;
5498                 else if (e.Item.BackColor == _clAtTo)
5499                     brs2 = _brsBackColorAtTo;
5500                 else
5501                     brs2 = _brsBackColorNone;
5502             }
5503             else
5504             {
5505                 //選択中の行
5506                 if (((Control)sender).Focused)
5507                     brs2 = _brsHighLight;
5508                 else
5509                     brs2 = _brsDeactiveSelection;
5510             }
5511             e.Graphics.FillRectangle(brs2, e.Bounds);
5512             e.DrawFocusRectangle();
5513             this.DrawListViewItemIcon(e);
5514         }
5515
5516         private void MyList_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
5517         {
5518             if (e.ItemState == 0) return;
5519
5520             if (e.ColumnIndex > 0)
5521             {
5522                 //アイコン以外の列
5523                 RectangleF rct = e.Bounds;
5524                 rct.Width = e.Header.Width;
5525                 int fontHeight = e.Item.Font.Height;
5526                 if (_iconCol)
5527                 {
5528                     rct.Y += fontHeight;
5529                     rct.Height -= fontHeight;
5530                 }
5531
5532                 int heightDiff;
5533                 int drawLineCount = Math.Max(1, Math.DivRem((int)rct.Height, fontHeight, out heightDiff));
5534
5535                 //if (heightDiff > fontHeight * 0.7)
5536                 //{
5537                 //    rct.Height += fontHeight;
5538                 //    drawLineCount += 1;
5539                 //}
5540
5541                 //フォントの高さの半分を足してるのは保険。無くてもいいかも。
5542                 if (!_iconCol && drawLineCount <= 1)
5543                 {
5544                     //rct.Inflate(0, heightDiff / -2);
5545                     //rct.Height += fontHeight / 2;
5546                 }
5547                 else if (heightDiff < fontHeight * 0.7)
5548                 {
5549                     //最終行が70%以上欠けていたら、最終行は表示しない
5550                     //rct.Height = (float)((fontHeight * drawLineCount) + (fontHeight / 2));
5551                     rct.Height = (fontHeight * drawLineCount) - 1;
5552                 }
5553                 else
5554                 {
5555                     drawLineCount += 1;
5556                 }
5557
5558                 //if (!_iconCol && drawLineCount > 1)
5559                 //{
5560                 //    rct.Y += fontHeight * 0.2;
5561                 //    if (heightDiff >= fontHeight * 0.8) rct.Height -= fontHeight * 0.2;
5562                 //}
5563
5564                 if (rct.Width > 0)
5565                 {
5566                     Color color = (!e.Item.Selected) ? e.Item.ForeColor :   //選択されていない行
5567                         (((Control)sender).Focused) ? _clHighLight :        //選択中の行
5568                         _clUnread;
5569
5570                     if (_iconCol)
5571                     {
5572                         Rectangle rctB = e.Bounds;
5573                         rctB.Width = e.Header.Width;
5574                         rctB.Height = fontHeight;
5575
5576                         using (Font fnt = new Font(e.Item.Font, FontStyle.Bold))
5577                         {
5578                             TextRenderer.DrawText(e.Graphics,
5579                                                     e.Item.SubItems[2].Text,
5580                                                     e.Item.Font,
5581                                                     Rectangle.Round(rct),
5582                                                     color,
5583                                                     TextFormatFlags.WordBreak |
5584                                                     TextFormatFlags.EndEllipsis |
5585                                                     TextFormatFlags.GlyphOverhangPadding |
5586                                                     TextFormatFlags.NoPrefix);
5587                             TextRenderer.DrawText(e.Graphics,
5588                                                     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 + "]",
5589                                                     fnt,
5590                                                     rctB,
5591                                                     color,
5592                                                     TextFormatFlags.SingleLine |
5593                                                     TextFormatFlags.EndEllipsis |
5594                                                     TextFormatFlags.GlyphOverhangPadding |
5595                                                     TextFormatFlags.NoPrefix);
5596                         }
5597                     }
5598                     else if (drawLineCount == 1)
5599                     {
5600                         TextRenderer.DrawText(e.Graphics,
5601                                                 e.SubItem.Text,
5602                                                 e.Item.Font,
5603                                                 Rectangle.Round(rct),
5604                                                 color,
5605                                                 TextFormatFlags.SingleLine |
5606                                                 TextFormatFlags.EndEllipsis |
5607                                                 TextFormatFlags.GlyphOverhangPadding |
5608                                                 TextFormatFlags.NoPrefix |
5609                                                 TextFormatFlags.VerticalCenter);
5610                     }
5611                     else
5612                     {
5613                         TextRenderer.DrawText(e.Graphics,
5614                                                 e.SubItem.Text,
5615                                                 e.Item.Font,
5616                                                 Rectangle.Round(rct),
5617                                                 color,
5618                                                 TextFormatFlags.WordBreak |
5619                                                 TextFormatFlags.EndEllipsis |
5620                                                 TextFormatFlags.GlyphOverhangPadding |
5621                                                 TextFormatFlags.NoPrefix);
5622                     }
5623                     //if (e.ColumnIndex == 6) this.DrawListViewItemStateIcon(e, rct);
5624                 }
5625             }
5626         }
5627
5628         private void DrawListViewItemIcon(DrawListViewItemEventArgs e)
5629         {
5630             if (_iconSz == 0) return;
5631
5632             ImageListViewItem item = (ImageListViewItem)e.Item;
5633
5634             //e.Bounds.Leftが常に0を指すから自前で計算
5635             Rectangle itemRect = item.Bounds;
5636             var col0 = e.Item.ListView.Columns[0];
5637             itemRect.Width = col0.Width;
5638
5639             if (col0.DisplayIndex > 0)
5640             {
5641                 foreach (ColumnHeader clm in e.Item.ListView.Columns)
5642                 {
5643                     if (clm.DisplayIndex < col0.DisplayIndex)
5644                         itemRect.X += clm.Width;
5645                 }
5646             }
5647
5648             // ディスプレイの DPI 設定を考慮したアイコンサイズ
5649             var realIconSize = new SizeF(this._iconSz * this.CurrentScaleFactor.Width, this._iconSz * this.CurrentScaleFactor.Height).ToSize();
5650             var realStateSize = new SizeF(16 * this.CurrentScaleFactor.Width, 16 * this.CurrentScaleFactor.Height).ToSize();
5651
5652             Rectangle iconRect;
5653             var img = item.Image;
5654             if (img != null)
5655             {
5656                 iconRect = Rectangle.Intersect(new Rectangle(e.Item.GetBounds(ItemBoundsPortion.Icon).Location, realIconSize), itemRect);
5657                 iconRect.Offset(0, Math.Max(0, (itemRect.Height - realIconSize.Height) / 2));
5658
5659                 if (iconRect.Width > 0)
5660                 {
5661                     e.Graphics.FillRectangle(Brushes.White, iconRect);
5662                     e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
5663                     try
5664                     {
5665                         e.Graphics.DrawImage(img.Image, iconRect);
5666                     }
5667                     catch (ArgumentException)
5668                     {
5669                         item.RefreshImageAsync();
5670                     }
5671                 }
5672             }
5673             else
5674             {
5675                 iconRect = Rectangle.Intersect(new Rectangle(e.Item.GetBounds(ItemBoundsPortion.Icon).Location, new Size(1, 1)), itemRect);
5676                 //iconRect.Offset(0, Math.Max(0, (itemRect.Height - realIconSize.Height) / 2));
5677
5678                 item.GetImageAsync();
5679             }
5680
5681             if (item.StateIndex > -1)
5682             {
5683                 Rectangle stateRect = Rectangle.Intersect(new Rectangle(new Point(iconRect.X + realIconSize.Width + 2, iconRect.Y), realStateSize), itemRect);
5684                 if (stateRect.Width > 0)
5685                 {
5686                     //e.Graphics.FillRectangle(Brushes.White, stateRect);
5687                     //e.Graphics.InterpolationMode = Drawing2D.InterpolationMode.High;
5688                     e.Graphics.DrawImage(this.PostStateImageList.Images[item.StateIndex], stateRect);
5689                 }
5690             }
5691         }
5692
5693         protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
5694         {
5695             base.ScaleControl(factor, specified);
5696
5697             ScaleChildControl(this.TabImage, factor);
5698
5699             var tabpages = this.ListTab.TabPages.Cast<TabPage>();
5700             var listviews = tabpages.Select(x => x.Tag).Cast<ListView>();
5701
5702             foreach (var listview in listviews)
5703             {
5704                 ScaleChildControl(listview, factor);
5705             }
5706         }
5707
5708         //private void DrawListViewItemStateIcon(DrawListViewSubItemEventArgs e, RectangleF rct)
5709         //{
5710         //    ImageListViewItem item = (ImageListViewItem)e.Item;
5711         //    if (item.StateImageIndex > -1)
5712         //    {
5713         //        ////e.Bounds.Leftが常に0を指すから自前で計算
5714         //        //Rectangle itemRect = item.Bounds;
5715         //        //itemRect.Width = e.Item.ListView.Columns[4].Width;
5716
5717         //        //foreach (ColumnHeader clm in e.Item.ListView.Columns)
5718         //        //{
5719         //        //    if (clm.DisplayIndex < e.Item.ListView.Columns[4].DisplayIndex)
5720         //        //    {
5721         //        //        itemRect.X += clm.Width;
5722         //        //    }
5723         //        //}
5724
5725         //        //Rectangle iconRect = Rectangle.Intersect(new Rectangle(e.Item.GetBounds(ItemBoundsPortion.Icon).Location, new Size(_iconSz, _iconSz)), itemRect);
5726         //        //iconRect.Offset(0, Math.Max(0, (itemRect.Height - _iconSz) / 2));
5727
5728         //        if (rct.Width > 0)
5729         //        {
5730         //            RectangleF stateRect = RectangleF.Intersect(rct, new RectangleF(rct.Location, new Size(18, 16)));
5731         //            //e.Graphics.FillRectangle(Brushes.White, rct);
5732         //            //e.Graphics.InterpolationMode = Drawing2D.InterpolationMode.High;
5733         //            e.Graphics.DrawImage(this.PostStateImageList.Images(item.StateImageIndex), stateRect);
5734         //        }
5735         //    }
5736         //}
5737
5738         private void DoTabSearch(string _word,
5739                                  bool CaseSensitive,
5740                                  bool UseRegex,
5741                                  SEARCHTYPE SType)
5742         {
5743             int cidx = 0;
5744             bool fnd = false;
5745             int toIdx;
5746             int stp = 1;
5747
5748             if (_curList.VirtualListSize == 0)
5749             {
5750                 MessageBox.Show(Properties.Resources.DoTabSearchText2, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Information);
5751             }
5752
5753             if (_curList.SelectedIndices.Count > 0)
5754             {
5755                 cidx = _curList.SelectedIndices[0];
5756             }
5757             toIdx = _curList.VirtualListSize;
5758
5759             switch (SType)
5760             {
5761                 case SEARCHTYPE.DialogSearch:    //ダイアログからの検索
5762                     if (_curList.SelectedIndices.Count > 0)
5763                         cidx = _curList.SelectedIndices[0];
5764                     else
5765                         cidx = 0;
5766                     break;
5767                 case SEARCHTYPE.NextSearch:      //次を検索
5768                     if (_curList.SelectedIndices.Count > 0)
5769                     {
5770                         cidx = _curList.SelectedIndices[0] + 1;
5771                         if (cidx > toIdx) cidx = toIdx;
5772                     }
5773                     else
5774                     {
5775                         cidx = 0;
5776                     }
5777                     break;
5778                 case SEARCHTYPE.PrevSearch:      //前を検索
5779                     if (_curList.SelectedIndices.Count > 0)
5780                     {
5781                         cidx = _curList.SelectedIndices[0] - 1;
5782                         if (cidx < 0) cidx = 0;
5783                     }
5784                     else
5785                     {
5786                         cidx = toIdx;
5787                     }
5788                     toIdx = -1;
5789                     stp = -1;
5790                     break;
5791             }
5792
5793             RegexOptions regOpt = RegexOptions.None;
5794             StringComparison fndOpt = StringComparison.Ordinal;
5795             if (!CaseSensitive)
5796             {
5797                 regOpt = RegexOptions.IgnoreCase;
5798                 fndOpt = StringComparison.OrdinalIgnoreCase;
5799             }
5800             try
5801             {
5802     RETRY:
5803                 if (UseRegex)
5804                 {
5805                     // 正規表現検索
5806                     Regex _search;
5807                     try
5808                     {
5809                         _search = new Regex(_word, regOpt);
5810                         for (int idx = cidx; idx != toIdx; idx += stp)
5811                         {
5812                             PostClass post;
5813                             try
5814                             {
5815                                 post = _statuses.Tabs[_curTab.Text][idx];
5816                             }
5817                             catch (Exception)
5818                             {
5819                                 continue;
5820                             }
5821                             if (_search.IsMatch(post.Nickname)
5822                                 || _search.IsMatch(post.TextFromApi)
5823                                 || _search.IsMatch(post.ScreenName))
5824                             {
5825                                 SelectListItem(_curList, idx);
5826                                 _curList.EnsureVisible(idx);
5827                                 return;
5828                             }
5829                         }
5830                     }
5831                     catch (ArgumentException)
5832                     {
5833                         MessageBox.Show(Properties.Resources.DoTabSearchText1, "", MessageBoxButtons.OK, MessageBoxIcon.Error);
5834                         return;
5835                     }
5836                 }
5837                 else
5838                 {
5839                     // 通常検索
5840                     for (int idx = cidx; idx != toIdx; idx += stp)
5841                     {
5842                         PostClass post;
5843                         try
5844                         {
5845                             post = _statuses.Tabs[_curTab.Text][idx];
5846                         }
5847                         catch (Exception)
5848                         {
5849                             continue;
5850                         }
5851                         if (post.Nickname.IndexOf(_word, fndOpt) > -1
5852                             || post.TextFromApi.IndexOf(_word, fndOpt) > -1
5853                             || post.ScreenName.IndexOf(_word, fndOpt) > -1)
5854                         {
5855                             SelectListItem(_curList, idx);
5856                             _curList.EnsureVisible(idx);
5857                             return;
5858                         }
5859                     }
5860                 }
5861
5862                 if (!fnd)
5863                 {
5864                     switch (SType)
5865                     {
5866                         case SEARCHTYPE.DialogSearch:
5867                         case SEARCHTYPE.NextSearch:
5868                             toIdx = cidx;
5869                             cidx = 0;
5870                             break;
5871                         case SEARCHTYPE.PrevSearch:
5872                             toIdx = cidx;
5873                             cidx = _curList.VirtualListSize - 1;
5874                             break;
5875                     }
5876                     fnd = true;
5877                     goto RETRY;
5878                 }
5879             }
5880             catch (ArgumentOutOfRangeException)
5881             {
5882             }
5883             MessageBox.Show(Properties.Resources.DoTabSearchText2, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Information);
5884         }
5885
5886         private void MenuItemSubSearch_Click(object sender, EventArgs e)
5887         {
5888             // 検索メニュー
5889             this.ShowSearchDialog();
5890         }
5891
5892         private void MenuItemSearchNext_Click(object sender, EventArgs e)
5893         {
5894             var previousSearch = this.SearchDialog.ResultOptions;
5895             if (previousSearch == null || previousSearch.Type != SearchWordDialog.SearchType.Timeline)
5896             {
5897                 this.SearchDialog.Reset();
5898                 this.ShowSearchDialog();
5899                 return;
5900             }
5901
5902             // 次を検索
5903             this.DoTabSearch(
5904                 previousSearch.Query,
5905                 previousSearch.CaseSensitive,
5906                 previousSearch.UseRegex,
5907                 SEARCHTYPE.NextSearch);
5908         }
5909
5910         private void MenuItemSearchPrev_Click(object sender, EventArgs e)
5911         {
5912             var previousSearch = this.SearchDialog.ResultOptions;
5913             if (previousSearch == null || previousSearch.Type != SearchWordDialog.SearchType.Timeline)
5914             {
5915                 this.SearchDialog.Reset();
5916                 this.ShowSearchDialog();
5917                 return;
5918             }
5919
5920             // 前を検索
5921             this.DoTabSearch(
5922                 previousSearch.Query,
5923                 previousSearch.CaseSensitive,
5924                 previousSearch.UseRegex,
5925                 SEARCHTYPE.PrevSearch);
5926         }
5927
5928         /// <summary>
5929         /// 検索ダイアログを表示し、検索を実行します
5930         /// </summary>
5931         private void ShowSearchDialog()
5932         {
5933             if (this.SearchDialog.ShowDialog(this) != DialogResult.OK)
5934             {
5935                 this.TopMost = this._cfgCommon.AlwaysTop;
5936                 return;
5937             }
5938             this.TopMost = this._cfgCommon.AlwaysTop;
5939
5940             var searchOptions = this.SearchDialog.ResultOptions;
5941             if (searchOptions.Type == SearchWordDialog.SearchType.Timeline)
5942             {
5943                 if (searchOptions.NewTab)
5944                 {
5945                     var tabName = Properties.Resources.SearchResults_TabName;
5946
5947                     try
5948                     {
5949                         tabName = this._statuses.MakeTabName(tabName);
5950                     }
5951                     catch (TabException ex)
5952                     {
5953                         MessageBox.Show(this, ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
5954                     }
5955
5956                     this.AddNewTab(tabName, false, MyCommon.TabUsageType.SearchResults);
5957                     this._statuses.AddTab(tabName, MyCommon.TabUsageType.SearchResults, null);
5958
5959                     var filter = new PostFilterRule
5960                     {
5961                         FilterBody = new[] { searchOptions.Query },
5962                         UseRegex = searchOptions.UseRegex,
5963                         CaseSensitive = searchOptions.CaseSensitive,
5964                     };
5965
5966                     var targetTab = this._statuses.Tabs[this._curTab.Text];
5967                     var posts = targetTab.Posts.Values
5968                         .Where(x => filter.ExecFilter(x) == MyCommon.HITRESULT.CopyAndMark)
5969                         .Where(x => targetTab.Contains(x.StatusId));
5970
5971                     var resultTab = this._statuses.Tabs[tabName];
5972                     foreach (var post in posts)
5973                     {
5974                         resultTab.AddPostToInnerStorage(post.Clone());
5975                     }
5976
5977                     this._statuses.DistributePosts();
5978                     this.RefreshTimeline(false);
5979
5980                     var tabPage = this.ListTab.TabPages.Cast<TabPage>()
5981                         .First(x => x.Text == tabName);
5982
5983                     this.ListTab.SelectedTab = tabPage;
5984                 }
5985                 else
5986                 {
5987                     this.DoTabSearch(
5988                         searchOptions.Query,
5989                         searchOptions.CaseSensitive,
5990                         searchOptions.UseRegex,
5991                         SEARCHTYPE.DialogSearch);
5992                 }
5993             }
5994             else if (searchOptions.Type == SearchWordDialog.SearchType.Public)
5995             {
5996                 this.AddNewTabForSearch(searchOptions.Query);
5997             }
5998         }
5999
6000         private void AboutMenuItem_Click(object sender, EventArgs e)
6001         {
6002             using (TweenAboutBox about = new TweenAboutBox())
6003             {
6004                 about.ShowDialog(this);
6005             }
6006             this.TopMost = this._cfgCommon.AlwaysTop;
6007         }
6008
6009         private void JumpUnreadMenuItem_Click(object sender, EventArgs e)
6010         {
6011             int bgnIdx = ListTab.TabPages.IndexOf(_curTab);
6012             int idx = -1;
6013             DetailsListView lst = null;
6014
6015             if (ImageSelector.Enabled)
6016                 return;
6017
6018             //現在タブから最終タブまで探索
6019             for (int i = bgnIdx; i < ListTab.TabPages.Count; i++)
6020             {
6021                 //未読Index取得
6022                 idx = _statuses.Tabs[ListTab.TabPages[i].Text].OldestUnreadIndex;
6023                 if (idx > -1)
6024                 {
6025                     ListTab.SelectedIndex = i;
6026                     lst = (DetailsListView)ListTab.TabPages[i].Tag;
6027                     //_curTab = ListTab.TabPages[i];
6028                     break;
6029                 }
6030             }
6031
6032             //未読みつからず&現在タブが先頭ではなかったら、先頭タブから現在タブの手前まで探索
6033             if (idx == -1 && bgnIdx > 0)
6034             {
6035                 for (int i = 0; i < bgnIdx; i++)
6036                 {
6037                     idx = _statuses.Tabs[ListTab.TabPages[i].Text].OldestUnreadIndex;
6038                     if (idx > -1)
6039                     {
6040                         ListTab.SelectedIndex = i;
6041                         lst = (DetailsListView)ListTab.TabPages[i].Tag;
6042                         //_curTab = ListTab.TabPages[i];
6043                         break;
6044                     }
6045                 }
6046             }
6047
6048             //全部調べたが未読見つからず→先頭タブの最新発言へ
6049             if (idx == -1)
6050             {
6051                 ListTab.SelectedIndex = 0;
6052                 lst = (DetailsListView)ListTab.TabPages[0].Tag;
6053                 //_curTab = ListTab.TabPages[0];
6054                 if (_statuses.SortOrder == SortOrder.Ascending)
6055                     idx = lst.VirtualListSize - 1;
6056                 else
6057                     idx = 0;
6058             }
6059
6060             if (lst.VirtualListSize > 0 && idx > -1 && lst.VirtualListSize > idx)
6061             {
6062                 SelectListItem(lst, idx);
6063                 if (_statuses.SortMode == ComparerMode.Id)
6064                 {
6065                     if (_statuses.SortOrder == SortOrder.Ascending && lst.Items[idx].Position.Y > lst.ClientSize.Height - _iconSz - 10 ||
6066                        _statuses.SortOrder == SortOrder.Descending && lst.Items[idx].Position.Y < _iconSz + 10)
6067                     {
6068                         MoveTop();
6069                     }
6070                     else
6071                     {
6072                         lst.EnsureVisible(idx);
6073                     }
6074                 }
6075                 else
6076                 {
6077                     lst.EnsureVisible(idx);
6078                 }
6079             }
6080             lst.Focus();
6081         }
6082
6083         private async void StatusOpenMenuItem_Click(object sender, EventArgs e)
6084         {
6085             if (_curList.SelectedIndices.Count > 0 && _statuses.Tabs[_curTab.Text].TabType != MyCommon.TabUsageType.DirectMessage)
6086             {
6087                 var post = _statuses.Tabs[_curTab.Text][_curList.SelectedIndices[0]];
6088                 await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(post));
6089             }
6090         }
6091
6092         private async void FavorareMenuItem_Click(object sender, EventArgs e)
6093         {
6094             if (_curList.SelectedIndices.Count > 0)
6095             {
6096                 PostClass post = _statuses.Tabs[_curTab.Text][_curList.SelectedIndices[0]];
6097                 await this.OpenUriInBrowserAsync(Properties.Resources.FavstarUrl + "users/" + post.ScreenName + "/recent");
6098             }
6099         }
6100
6101         private async void VerUpMenuItem_Click(object sender, EventArgs e)
6102         {
6103             await this.CheckNewVersion(false);
6104         }
6105
6106         private void RunTweenUp()
6107         {
6108             ProcessStartInfo pinfo = new ProcessStartInfo();
6109             pinfo.UseShellExecute = true;
6110             pinfo.WorkingDirectory = MyCommon.settingPath;
6111             pinfo.FileName = Path.Combine(MyCommon.settingPath, "TweenUp3.exe");
6112             pinfo.Arguments = "\"" + Application.StartupPath + "\"";
6113             try
6114             {
6115                 Process.Start(pinfo);
6116             }
6117             catch (Exception)
6118             {
6119                 MessageBox.Show("Failed to execute TweenUp3.exe.");
6120             }
6121         }
6122
6123         public class VersionInfo
6124         {
6125             public Version Version { get; set; }
6126             public Uri DownloadUri { get; set; }
6127             public string ReleaseNote { get; set; }
6128         }
6129
6130         /// <summary>
6131         /// OpenTween の最新バージョンの情報を取得します
6132         /// </summary>
6133         public async Task<VersionInfo> GetVersionInfoAsync()
6134         {
6135             var versionInfoUrl = new Uri(ApplicationSettings.VersionInfoUrl + "?" +
6136                 DateTime.Now.ToString("yyMMddHHmmss") + Environment.TickCount);
6137
6138             var responseText = await Networking.Http.GetStringAsync(versionInfoUrl)
6139                 .ConfigureAwait(false);
6140
6141             // 改行2つで前後パートを分割(前半がバージョン番号など、後半が詳細テキスト)
6142             var msgPart = responseText.Split(new[] { "\n\n", "\r\n\r\n" }, 2, StringSplitOptions.None);
6143
6144             var msgHeader = msgPart[0].Split(new[] { "\n", "\r\n" }, StringSplitOptions.None);
6145             var msgBody = msgPart.Length == 2 ? msgPart[1] : "";
6146
6147             msgBody = Regex.Replace(msgBody, "(?<!\r)\n", "\r\n"); // LF -> CRLF
6148
6149             return new VersionInfo
6150             {
6151                 Version = Version.Parse(msgHeader[0]),
6152                 DownloadUri = new Uri(msgHeader[1]),
6153                 ReleaseNote = msgBody,
6154             };
6155         }
6156
6157         private async Task CheckNewVersion(bool startup = false)
6158         {
6159             if (ApplicationSettings.VersionInfoUrl == null)
6160                 return; // 更新チェック無効化
6161
6162             try
6163             {
6164                 var versionInfo = await this.GetVersionInfoAsync();
6165
6166                 if (versionInfo.Version <= Version.Parse(MyCommon.FileVersion))
6167                 {
6168                     // 更新不要
6169                     if (!startup)
6170                     {
6171                         var msgtext = string.Format(Properties.Resources.CheckNewVersionText7,
6172                             MyCommon.GetReadableVersion(), MyCommon.GetReadableVersion(versionInfo.Version));
6173                         msgtext = MyCommon.ReplaceAppName(msgtext);
6174
6175                         MessageBox.Show(msgtext,
6176                             MyCommon.ReplaceAppName(Properties.Resources.CheckNewVersionText2),
6177                             MessageBoxButtons.OK, MessageBoxIcon.Information);
6178                     }
6179                     return;
6180                 }
6181
6182                 using (var dialog = new UpdateDialog())
6183                 {
6184                     dialog.SummaryText = string.Format(Properties.Resources.CheckNewVersionText3,
6185                         MyCommon.GetReadableVersion(versionInfo.Version));
6186                     dialog.DetailsText = versionInfo.ReleaseNote;
6187
6188                     if (dialog.ShowDialog(this) == DialogResult.Yes)
6189                     {
6190                         await this.OpenUriInBrowserAsync(versionInfo.DownloadUri.OriginalString);
6191                     }
6192                 }
6193             }
6194             catch (Exception)
6195             {
6196                 this.StatusLabel.Text = Properties.Resources.CheckNewVersionText9;
6197                 if (!startup)
6198                 {
6199                     MessageBox.Show(Properties.Resources.CheckNewVersionText10,
6200                         MyCommon.ReplaceAppName(Properties.Resources.CheckNewVersionText2),
6201                         MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2);
6202                 }
6203             }
6204         }
6205
6206         private async Task Colorize()
6207         {
6208             _colorize = false;
6209             await this.DispSelectedPost();
6210             //件数関連の場合、タイトル即時書き換え
6211             if (this._cfgCommon.DispLatestPost != MyCommon.DispTitleEnum.None &&
6212                this._cfgCommon.DispLatestPost != MyCommon.DispTitleEnum.Post &&
6213                this._cfgCommon.DispLatestPost != MyCommon.DispTitleEnum.Ver &&
6214                this._cfgCommon.DispLatestPost != MyCommon.DispTitleEnum.OwnStatus)
6215             {
6216                 SetMainWindowTitle();
6217             }
6218             if (!StatusLabelUrl.Text.StartsWith("http")) SetStatusLabelUrl();
6219             foreach (TabPage tb in ListTab.TabPages)
6220             {
6221                 if (_statuses.Tabs[tb.Text].UnreadCount == 0)
6222                 {
6223                     if (this._cfgCommon.TabIconDisp)
6224                     {
6225                         if (tb.ImageIndex == 0) tb.ImageIndex = -1;
6226                     }
6227                 }
6228             }
6229             if (!this._cfgCommon.TabIconDisp) ListTab.Refresh();
6230         }
6231
6232         public string createDetailHtml(string orgdata)
6233         {
6234             if (this._cfgLocal.UseTwemoji)
6235                 orgdata = EmojiFormatter.ReplaceEmojiToImg(orgdata);
6236
6237             return detailHtmlFormatHeader + orgdata + detailHtmlFormatFooter;
6238         }
6239
6240         private async void DisplayItemImage_Downloaded(object sender, EventArgs e)
6241         {
6242             if (sender.Equals(displayItem))
6243             {
6244                 this.ClearUserPicture();
6245
6246                 var img = displayItem.Image;
6247                 try
6248                 {
6249                     if (img != null)
6250                         img = await img.CloneAsync();
6251
6252                     UserPicture.Image = img;
6253                 }
6254                 catch (Exception)
6255                 {
6256                     UserPicture.ShowErrorImage();
6257                 }
6258             }
6259         }
6260
6261         private Task DispSelectedPost()
6262         {
6263             return this.DispSelectedPost(false);
6264         }
6265
6266         private PostClass displayPost = new PostClass();
6267
6268         /// <summary>
6269         /// サムネイル表示に使用する CancellationToken の生成元
6270         /// </summary>
6271         private CancellationTokenSource thumbnailTokenSource = null;
6272
6273         private async Task DispSelectedPost(bool forceupdate)
6274         {
6275             if (_curList.SelectedIndices.Count == 0 || _curPost == null)
6276                 return;
6277
6278             var oldDisplayPost = this.displayPost;
6279             this.displayPost = this._curPost;
6280
6281             if (!forceupdate && this._curPost.Equals(oldDisplayPost))
6282                 return;
6283
6284             if (displayItem != null)
6285             {
6286                 displayItem.ImageDownloaded -= this.DisplayItemImage_Downloaded;
6287                 displayItem = null;
6288             }
6289             displayItem = (ImageListViewItem)_curList.Items[_curList.SelectedIndices[0]];
6290             displayItem.ImageDownloaded += this.DisplayItemImage_Downloaded;
6291
6292             using (ControlTransaction.Update(this.TableLayoutPanel1))
6293             {
6294                 SourceLinkLabel.Text = this._curPost.Source;
6295                 SourceLinkLabel.Tag = this._curPost.SourceUri;
6296                 SourceLinkLabel.TabStop = false; // Text を更新すると勝手に true にされる
6297
6298                 string nameText;
6299                 if (_curPost.IsDm)
6300                 {
6301                     if (_curPost.IsOwl)
6302                         nameText = "DM FROM <- ";
6303                     else
6304                         nameText = "DM TO -> ";
6305                 }
6306                 else
6307                 {
6308                     nameText = "";
6309                 }
6310                 nameText += _curPost.ScreenName + "/" + _curPost.Nickname;
6311                 if (_curPost.RetweetedId != null)
6312                     nameText += " (RT:" + _curPost.RetweetedBy + ")";
6313
6314                 NameLabel.Text = nameText;
6315                 NameLabel.Tag = _curPost.ScreenName;
6316
6317                 var nameForeColor = SystemColors.ControlText;
6318                 if (_curPost.IsOwl && (this._cfgCommon.OneWayLove || _curPost.IsDm))
6319                     nameForeColor = this._clOWL;
6320                 if (_curPost.RetweetedId != null)
6321                     nameForeColor = this._clRetweet;
6322                 if (_curPost.IsFav)
6323                     nameForeColor = this._clFav;
6324                 NameLabel.ForeColor = nameForeColor;
6325
6326                 this.ClearUserPicture();
6327
6328                 if (!string.IsNullOrEmpty(_curPost.ImageUrl))
6329                 {
6330                     var image = IconCache.TryGetFromCache(_curPost.ImageUrl);
6331                     try
6332                     {
6333                         UserPicture.Image = image?.Clone();
6334                     }
6335                     catch (Exception)
6336                     {
6337                         UserPicture.ShowErrorImage();
6338                     }
6339                 }
6340
6341                 DateTimeLabel.Text = _curPost.CreatedAt.ToString();
6342             }
6343
6344             if (DumpPostClassToolStripMenuItem.Checked)
6345             {
6346                 StringBuilder sb = new StringBuilder(512);
6347
6348                 sb.Append("-----Start PostClass Dump<br>");
6349                 sb.AppendFormat("TextFromApi           : {0}<br>", _curPost.TextFromApi);
6350                 sb.AppendFormat("(PlainText)    : <xmp>{0}</xmp><br>", _curPost.TextFromApi);
6351                 sb.AppendFormat("StatusId             : {0}<br>", _curPost.StatusId.ToString());
6352                 //sb.AppendFormat("ImageIndex     : {0}<br>", _curPost.ImageIndex.ToString());
6353                 sb.AppendFormat("ImageUrl       : {0}<br>", _curPost.ImageUrl);
6354                 sb.AppendFormat("InReplyToStatusId    : {0}<br>", _curPost.InReplyToStatusId.ToString());
6355                 sb.AppendFormat("InReplyToUser  : {0}<br>", _curPost.InReplyToUser);
6356                 sb.AppendFormat("IsDM           : {0}<br>", _curPost.IsDm.ToString());
6357                 sb.AppendFormat("IsFav          : {0}<br>", _curPost.IsFav.ToString());
6358                 sb.AppendFormat("IsMark         : {0}<br>", _curPost.IsMark.ToString());
6359                 sb.AppendFormat("IsMe           : {0}<br>", _curPost.IsMe.ToString());
6360                 sb.AppendFormat("IsOwl          : {0}<br>", _curPost.IsOwl.ToString());
6361                 sb.AppendFormat("IsProtect      : {0}<br>", _curPost.IsProtect.ToString());
6362                 sb.AppendFormat("IsRead         : {0}<br>", _curPost.IsRead.ToString());
6363                 sb.AppendFormat("IsReply        : {0}<br>", _curPost.IsReply.ToString());
6364
6365                 foreach (string nm in _curPost.ReplyToList)
6366                 {
6367                     sb.AppendFormat("ReplyToList    : {0}<br>", nm);
6368                 }
6369
6370                 sb.AppendFormat("ScreenName           : {0}<br>", _curPost.ScreenName);
6371                 sb.AppendFormat("NickName       : {0}<br>", _curPost.Nickname);
6372                 sb.AppendFormat("Text   : {0}<br>", _curPost.Text);
6373                 sb.AppendFormat("(PlainText)    : <xmp>{0}</xmp><br>", _curPost.Text);
6374                 sb.AppendFormat("CreatedAt          : {0}<br>", _curPost.CreatedAt.ToString());
6375                 sb.AppendFormat("Source         : {0}<br>", _curPost.Source);
6376                 sb.AppendFormat("UserId            : {0}<br>", _curPost.UserId);
6377                 sb.AppendFormat("FilterHit      : {0}<br>", _curPost.FilterHit);
6378                 sb.AppendFormat("RetweetedBy    : {0}<br>", _curPost.RetweetedBy);
6379                 sb.AppendFormat("RetweetedId    : {0}<br>", _curPost.RetweetedId);
6380                 sb.AppendFormat("SearchTabName  : {0}<br>", _curPost.RelTabName);
6381
6382                 sb.AppendFormat("Media.Count    : {0}<br>", _curPost.Media.Count);
6383                 if (_curPost.Media.Count > 0)
6384                 {
6385                     for (int i = 0; i < _curPost.Media.Count; i++)
6386                     {
6387                         var info = _curPost.Media[i];
6388                         sb.AppendFormat("Media[{0}].Url         : {1}<br>", i, info.Url);
6389                         sb.AppendFormat("Media[{0}].VideoUrl    : {1}<br>", i, info.VideoUrl ?? "---");
6390                     }
6391                 }
6392                 sb.Append("-----End PostClass Dump<br>");
6393
6394                 PostBrowser.DocumentText = detailHtmlFormatHeader + sb.ToString() + detailHtmlFormatFooter;
6395                 return;
6396             }
6397
6398             var loadTasks = new List<Task>();
6399
6400             // 同じIDのツイートであれば WebBrowser とサムネイルの更新を行わない
6401             // (同一ツイートの RT は文面が同じであるため同様に更新しない)
6402             if (_curPost.StatusId != oldDisplayPost.StatusId)
6403             {
6404                 using (ControlTransaction.Update(this.PostBrowser))
6405                 {
6406                     this.PostBrowser.DocumentText =
6407                         this.createDetailHtml(_curPost.IsDeleted ? "(DELETED)" : _curPost.Text);
6408
6409                     this.PostBrowser.Document.Window.ScrollTo(0, 0);
6410                 }
6411
6412                 this.SplitContainer3.Panel2Collapsed = true;
6413
6414                 if (this._cfgCommon.PreviewEnable)
6415                 {
6416                     var oldTokenSource = Interlocked.Exchange(ref this.thumbnailTokenSource, new CancellationTokenSource());
6417                     oldTokenSource?.Cancel();
6418
6419                     var token = this.thumbnailTokenSource.Token;
6420                     loadTasks.Add(this.tweetThumbnail1.ShowThumbnailAsync(_curPost, token));
6421                 }
6422
6423                 loadTasks.Add(this.AppendQuoteTweetAsync(this._curPost));
6424             }
6425
6426             try
6427             {
6428                 await Task.WhenAll(loadTasks);
6429             }
6430             catch (OperationCanceledException) { }
6431         }
6432
6433         /// <summary>
6434         /// 発言詳細欄のツイートURLを展開する
6435         /// </summary>
6436         private async Task AppendQuoteTweetAsync(PostClass post)
6437         {
6438             var statusIds = post.QuoteStatusIds;
6439             if (statusIds.Length == 0)
6440                 return;
6441
6442             // 「読み込み中」テキストを表示
6443             var loadingQuoteHtml = statusIds.Select(x => FormatQuoteTweetHtml(x, Properties.Resources.LoadingText));
6444             var body = post.Text + string.Concat(loadingQuoteHtml);
6445
6446             using (ControlTransaction.Update(this.PostBrowser))
6447                 this.PostBrowser.DocumentText = this.createDetailHtml(body);
6448
6449             // 引用ツイートを読み込み
6450             var quoteHtmls = await Task.WhenAll(statusIds.Select(x => this.CreateQuoteTweetHtml(x)));
6451
6452             // 非同期処理中に表示中のツイートが変わっていたらキャンセルされたものと扱う
6453             if (this._curPost != post || this._curPost.IsDeleted)
6454                 return;
6455
6456             body = post.Text + string.Concat(quoteHtmls);
6457
6458             using (ControlTransaction.Update(this.PostBrowser))
6459                 this.PostBrowser.DocumentText = this.createDetailHtml(body);
6460         }
6461
6462         private async Task<string> CreateQuoteTweetHtml(long statusId)
6463         {
6464             PostClass post = this._statuses[statusId];
6465             if (post == null)
6466             {
6467                 try
6468                 {
6469                     post = await Task.Run(() => this.tw.GetStatusApi(false, statusId))
6470                         .ConfigureAwait(false);
6471                 }
6472                 catch (WebApiException ex)
6473                 {
6474                     return FormatQuoteTweetHtml(statusId, WebUtility.HtmlEncode(ex.Message));
6475                 }
6476
6477                 post.IsRead = true;
6478                 if (!this._statuses.AddQuoteTweet(post))
6479                     return FormatQuoteTweetHtml(statusId, "This Tweet is unavailable.");
6480             }
6481
6482             return FormatQuoteTweetHtml(post);
6483         }
6484
6485         internal static string FormatQuoteTweetHtml(PostClass post)
6486         {
6487             var innerHtml = "<p>" + StripLinkTagHtml(post.Text) + "</p>" +
6488                 " &mdash; " + WebUtility.HtmlEncode(post.Nickname) +
6489                 " (@" + WebUtility.HtmlEncode(post.ScreenName) + ") " +
6490                 WebUtility.HtmlEncode(post.CreatedAt.ToString());
6491
6492             return FormatQuoteTweetHtml(post.StatusId, innerHtml);
6493         }
6494
6495         internal static string FormatQuoteTweetHtml(long statusId, string innerHtml)
6496         {
6497             return "<a class=\"quote-tweet-link\" href=\"//opentween/status/" + statusId + "\">" +
6498                 "<blockquote class=\"quote-tweet\">" + innerHtml + "</blockquote>" +
6499                 "</a>";
6500         }
6501
6502         /// <summary>
6503         /// 指定されたHTMLからリンクを除去します
6504         /// </summary>
6505         internal static string StripLinkTagHtml(string html)
6506         {
6507             // a 要素はネストされていない前提の正規表現パターン
6508             return Regex.Replace(html, @"<a[^>]*>(.*?)</a>", "$1");
6509         }
6510
6511         private async void MatomeMenuItem_Click(object sender, EventArgs e)
6512         {
6513             await this.OpenApplicationWebsite();
6514         }
6515
6516         private async Task OpenApplicationWebsite()
6517         {
6518             await this.OpenUriInBrowserAsync(ApplicationSettings.WebsiteUrl);
6519         }
6520
6521         private async void ShortcutKeyListMenuItem_Click(object sender, EventArgs e)
6522         {
6523             await this.OpenUriInBrowserAsync(ApplicationSettings.ShortcutKeyUrl);
6524         }
6525
6526         private async void ListTab_KeyDown(object sender, KeyEventArgs e)
6527         {
6528             if (ListTab.SelectedTab != null)
6529             {
6530                 if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType == MyCommon.TabUsageType.PublicSearch)
6531                 {
6532                     Control pnl = ListTab.SelectedTab.Controls["panelSearch"];
6533                     if (pnl.Controls["comboSearch"].Focused ||
6534                         pnl.Controls["comboLang"].Focused ||
6535                         pnl.Controls["buttonSearch"].Focused) return;
6536                 }
6537
6538                 if (e.Control || e.Shift || e.Alt)
6539                     this._anchorFlag = false;
6540
6541                 Task asyncTask;
6542                 if (CommonKeyDown(e.KeyData, FocusedControl.ListTab, out asyncTask))
6543                 {
6544                     e.Handled = true;
6545                     e.SuppressKeyPress = true;
6546                 }
6547
6548                 if (asyncTask != null)
6549                     await asyncTask;
6550             }
6551         }
6552
6553         private ShortcutCommand[] shortcutCommands = new ShortcutCommand[0];
6554
6555         private void InitializeShortcuts()
6556         {
6557             this.shortcutCommands = new[]
6558             {
6559                 // リストのカーソル移動関係(上下キー、PageUp/Downに該当)
6560                 ShortcutCommand.Create(Keys.J, Keys.Control | Keys.J, Keys.Shift | Keys.J, Keys.Control | Keys.Shift | Keys.J)
6561                     .FocusedOn(FocusedControl.ListTab)
6562                     .Do(() => SendKeys.Send("{DOWN}")),
6563
6564                 ShortcutCommand.Create(Keys.K, Keys.Control | Keys.K, Keys.Shift | Keys.K, Keys.Control | Keys.Shift | Keys.K)
6565                     .FocusedOn(FocusedControl.ListTab)
6566                     .Do(() => SendKeys.Send("{UP}")),
6567
6568                 ShortcutCommand.Create(Keys.F, Keys.Shift | Keys.F)
6569                     .FocusedOn(FocusedControl.ListTab)
6570                     .Do(() => SendKeys.Send("{PGDN}")),
6571
6572                 ShortcutCommand.Create(Keys.B, Keys.Shift | Keys.B)
6573                     .FocusedOn(FocusedControl.ListTab)
6574                     .Do(() => SendKeys.Send("{PGUP}")),
6575
6576                 ShortcutCommand.Create(Keys.F1)
6577                     .Do(() => this.OpenApplicationWebsite()),
6578
6579                 ShortcutCommand.Create(Keys.F3)
6580                     .Do(() => this.MenuItemSearchNext_Click(null, null)),
6581
6582                 ShortcutCommand.Create(Keys.F5)
6583                     .Do(() => this.DoRefresh()),
6584
6585                 ShortcutCommand.Create(Keys.F6)
6586                     .Do(() => this.GetReplyAsync()),
6587
6588                 ShortcutCommand.Create(Keys.F7)
6589                     .Do(() => this.GetDirectMessagesAsync()),
6590
6591                 ShortcutCommand.Create(Keys.Space, Keys.ProcessKey)
6592                     .NotFocusedOn(FocusedControl.StatusText)
6593                     .Do(() => { this._anchorFlag = false; this.JumpUnreadMenuItem_Click(null, null); }),
6594
6595                 ShortcutCommand.Create(Keys.G)
6596                     .NotFocusedOn(FocusedControl.StatusText)
6597                     .Do(() => { this._anchorFlag = false; this.ShowRelatedStatusesMenuItem_Click(null, null); }),
6598
6599                 ShortcutCommand.Create(Keys.Right, Keys.N)
6600                     .FocusedOn(FocusedControl.ListTab)
6601                     .Do(() => this.GoRelPost(forward: true)),
6602
6603                 ShortcutCommand.Create(Keys.Left, Keys.P)
6604                     .FocusedOn(FocusedControl.ListTab)
6605                     .Do(() => this.GoRelPost(forward: false)),
6606
6607                 ShortcutCommand.Create(Keys.OemPeriod)
6608                     .FocusedOn(FocusedControl.ListTab)
6609                     .Do(() => this.GoAnchor()),
6610
6611                 ShortcutCommand.Create(Keys.I)
6612                     .FocusedOn(FocusedControl.ListTab)
6613                     .OnlyWhen(() => this.StatusText.Enabled)
6614                     .Do(() => this.StatusText.Focus()),
6615
6616                 ShortcutCommand.Create(Keys.Enter)
6617                     .FocusedOn(FocusedControl.ListTab)
6618                     .Do(() => this.MakeReplyOrDirectStatus()),
6619
6620                 ShortcutCommand.Create(Keys.R)
6621                     .FocusedOn(FocusedControl.ListTab)
6622                     .Do(() => this.DoRefresh()),
6623
6624                 ShortcutCommand.Create(Keys.L)
6625                     .FocusedOn(FocusedControl.ListTab)
6626                     .Do(() => { this._anchorFlag = false; this.GoPost(forward: true); }),
6627
6628                 ShortcutCommand.Create(Keys.H)
6629                     .FocusedOn(FocusedControl.ListTab)
6630                     .Do(() => { this._anchorFlag = false; this.GoPost(forward: false); }),
6631
6632                 ShortcutCommand.Create(Keys.Z, Keys.Oemcomma)
6633                     .FocusedOn(FocusedControl.ListTab)
6634                     .Do(() => { this._anchorFlag = false; this.MoveTop(); }),
6635
6636                 ShortcutCommand.Create(Keys.S)
6637                     .FocusedOn(FocusedControl.ListTab)
6638                     .Do(() => { this._anchorFlag = false; this.GoNextTab(forward: true); }),
6639
6640                 ShortcutCommand.Create(Keys.A)
6641                     .FocusedOn(FocusedControl.ListTab)
6642                     .Do(() => { this._anchorFlag = false; this.GoNextTab(forward: false); }),
6643
6644                 // ] in_reply_to参照元へ戻る
6645                 ShortcutCommand.Create(Keys.Oem4)
6646                     .FocusedOn(FocusedControl.ListTab)
6647                     .Do(() => { this._anchorFlag = false; return this.GoInReplyToPostTree(); }),
6648
6649                 // [ in_reply_toへジャンプ
6650                 ShortcutCommand.Create(Keys.Oem6)
6651                     .FocusedOn(FocusedControl.ListTab)
6652                     .Do(() => { this._anchorFlag = false; this.GoBackInReplyToPostTree(); }),
6653
6654                 ShortcutCommand.Create(Keys.Escape)
6655                     .FocusedOn(FocusedControl.ListTab)
6656                     .Do(() => {
6657                         this._anchorFlag = false;
6658                         if (ListTab.SelectedTab != null)
6659                         {
6660                             var tabtype = _statuses.Tabs[ListTab.SelectedTab.Text].TabType;
6661                             if (tabtype == MyCommon.TabUsageType.Related || tabtype == MyCommon.TabUsageType.UserTimeline || tabtype == MyCommon.TabUsageType.PublicSearch || tabtype == MyCommon.TabUsageType.SearchResults)
6662                             {
6663                                 var relTp = ListTab.SelectedTab;
6664                                 RemoveSpecifiedTab(relTp.Text, false);
6665                                 SaveConfigsTabs();
6666                             }
6667                         }
6668                     }),
6669
6670                 // PreviewKeyDownEventArgs.IsInputKey を true にしてスクロールを発生させる
6671                 ShortcutCommand.Create(Keys.Up, Keys.Down)
6672                     .FocusedOn(FocusedControl.PostBrowser)
6673                     .Do(() => { }),
6674
6675                 ShortcutCommand.Create(Keys.Control | Keys.R)
6676                     .Do(() => this.MakeReplyOrDirectStatus(isAuto: false, isReply: true)),
6677
6678                 ShortcutCommand.Create(Keys.Control | Keys.D)
6679                     .Do(() => this.doStatusDelete()),
6680
6681                 ShortcutCommand.Create(Keys.Control | Keys.M)
6682                     .Do(() => this.MakeReplyOrDirectStatus(isAuto: false, isReply: false)),
6683
6684                 ShortcutCommand.Create(Keys.Control | Keys.S)
6685                     .Do(() => this.FavoriteChange(FavAdd: true)),
6686
6687                 ShortcutCommand.Create(Keys.Control | Keys.I)
6688                     .Do(() => this.doRepliedStatusOpen()),
6689
6690                 ShortcutCommand.Create(Keys.Control | Keys.Q)
6691                     .Do(() => this.doQuoteOfficial()),
6692
6693                 ShortcutCommand.Create(Keys.Control | Keys.B)
6694                     .Do(() => this.ReadedStripMenuItem_Click(null, null)),
6695
6696                 ShortcutCommand.Create(Keys.Control | Keys.T)
6697                     .Do(() => this.HashManageMenuItem_Click(null, null)),
6698
6699                 ShortcutCommand.Create(Keys.Control | Keys.L)
6700                     .Do(() => this.UrlConvertAutoToolStripMenuItem_Click(null, null)),
6701
6702                 ShortcutCommand.Create(Keys.Control | Keys.Y)
6703                     .NotFocusedOn(FocusedControl.PostBrowser)
6704                     .Do(() => this.MultiLineMenuItem_Click(null, null)),
6705
6706                 ShortcutCommand.Create(Keys.Control | Keys.F)
6707                     .Do(() => this.MenuItemSubSearch_Click(null, null)),
6708
6709                 ShortcutCommand.Create(Keys.Control | Keys.U)
6710                     .Do(() => this.ShowUserTimeline()),
6711
6712                 ShortcutCommand.Create(Keys.Control | Keys.H)
6713                     .Do(() => this.MoveToHomeToolStripMenuItem_Click(null, null)),
6714
6715                 ShortcutCommand.Create(Keys.Control | Keys.G)
6716                     .Do(() => this.MoveToFavToolStripMenuItem_Click(null, null)),
6717
6718                 ShortcutCommand.Create(Keys.Control | Keys.O)
6719                     .Do(() => this.StatusOpenMenuItem_Click(null, null)),
6720
6721                 ShortcutCommand.Create(Keys.Control | Keys.E)
6722                     .Do(() => this.OpenURLMenuItem_Click(null, null)),
6723
6724                 ShortcutCommand.Create(Keys.Control | Keys.Home, Keys.Control | Keys.End)
6725                     .FocusedOn(FocusedControl.ListTab)
6726                     .Do(() => this._colorize = true, preventDefault: false),
6727
6728                 ShortcutCommand.Create(Keys.Control | Keys.N)
6729                     .FocusedOn(FocusedControl.ListTab)
6730                     .Do(() => this.GoNextTab(forward: true)),
6731
6732                 ShortcutCommand.Create(Keys.Control | Keys.P)
6733                     .FocusedOn(FocusedControl.ListTab)
6734                     .Do(() => this.GoNextTab(forward: false)),
6735
6736                 ShortcutCommand.Create(Keys.Control | Keys.C)
6737                     .FocusedOn(FocusedControl.ListTab)
6738                     .Do(() => this.CopyStot()),
6739
6740                 ShortcutCommand.Create(Keys.Control | Keys.C)
6741                     .FocusedOn(FocusedControl.ListTab)
6742                     .Do(() => this.CopyStot()),
6743
6744                 // タブダイレクト選択(Ctrl+1~8,Ctrl+9)
6745                 ShortcutCommand.Create(Keys.Control | Keys.D1)
6746                     .FocusedOn(FocusedControl.ListTab)
6747                     .OnlyWhen(() => this.ListTab.TabPages.Count >= 1)
6748                     .Do(() => this.ListTab.SelectedIndex = 0),
6749
6750                 ShortcutCommand.Create(Keys.Control | Keys.D2)
6751                     .FocusedOn(FocusedControl.ListTab)
6752                     .OnlyWhen(() => this.ListTab.TabPages.Count >= 2)
6753                     .Do(() => this.ListTab.SelectedIndex = 1),
6754
6755                 ShortcutCommand.Create(Keys.Control | Keys.D3)
6756                     .FocusedOn(FocusedControl.ListTab)
6757                     .OnlyWhen(() => this.ListTab.TabPages.Count >= 3)
6758                     .Do(() => this.ListTab.SelectedIndex = 2),
6759
6760                 ShortcutCommand.Create(Keys.Control | Keys.D4)
6761                     .FocusedOn(FocusedControl.ListTab)
6762                     .OnlyWhen(() => this.ListTab.TabPages.Count >= 4)
6763                     .Do(() => this.ListTab.SelectedIndex = 3),
6764
6765                 ShortcutCommand.Create(Keys.Control | Keys.D5)
6766                     .FocusedOn(FocusedControl.ListTab)
6767                     .OnlyWhen(() => this.ListTab.TabPages.Count >= 5)
6768                     .Do(() => this.ListTab.SelectedIndex = 4),
6769
6770                 ShortcutCommand.Create(Keys.Control | Keys.D6)
6771                     .FocusedOn(FocusedControl.ListTab)
6772                     .OnlyWhen(() => this.ListTab.TabPages.Count >= 6)
6773                     .Do(() => this.ListTab.SelectedIndex = 5),
6774
6775                 ShortcutCommand.Create(Keys.Control | Keys.D7)
6776                     .FocusedOn(FocusedControl.ListTab)
6777                     .OnlyWhen(() => this.ListTab.TabPages.Count >= 7)
6778                     .Do(() => this.ListTab.SelectedIndex = 6),
6779
6780                 ShortcutCommand.Create(Keys.Control | Keys.D8)
6781                     .FocusedOn(FocusedControl.ListTab)
6782                     .OnlyWhen(() => this.ListTab.TabPages.Count >= 8)
6783                     .Do(() => this.ListTab.SelectedIndex = 7),
6784
6785                 ShortcutCommand.Create(Keys.Control | Keys.D9)
6786                     .FocusedOn(FocusedControl.ListTab)
6787                     .Do(() => this.ListTab.SelectedIndex = this.ListTab.TabPages.Count - 1),
6788
6789                 ShortcutCommand.Create(Keys.Control | Keys.A)
6790                     .FocusedOn(FocusedControl.StatusText)
6791                     .Do(() => this.StatusText.SelectAll()),
6792
6793                 ShortcutCommand.Create(Keys.Control | Keys.V)
6794                     .FocusedOn(FocusedControl.StatusText)
6795                     .Do(() => this.ProcClipboardFromStatusTextWhenCtrlPlusV()),
6796
6797                 ShortcutCommand.Create(Keys.Control | Keys.Up)
6798                     .FocusedOn(FocusedControl.StatusText)
6799                     .Do(() => {
6800                         if (!string.IsNullOrWhiteSpace(StatusText.Text))
6801                         {
6802                             var inReplyToStatusId = this.inReplyTo?.Item1;
6803                             var inReplyToScreenName = this.inReplyTo?.Item2;
6804                             _history[_hisIdx] = new PostingStatus(StatusText.Text, inReplyToStatusId, inReplyToScreenName);
6805                         }
6806                         _hisIdx -= 1;
6807                         if (_hisIdx < 0) _hisIdx = 0;
6808
6809                         var historyItem = this._history[this._hisIdx];
6810                         StatusText.Text = historyItem.status;
6811                         StatusText.SelectionStart = StatusText.Text.Length;
6812                         if (historyItem.inReplyToId != null)
6813                             this.inReplyTo = Tuple.Create(historyItem.inReplyToId.Value, historyItem.inReplyToName);
6814                         else
6815                             this.inReplyTo = null;
6816                     }),
6817
6818                 ShortcutCommand.Create(Keys.Control | Keys.Down)
6819                     .FocusedOn(FocusedControl.StatusText)
6820                     .Do(() => {
6821                         if (!string.IsNullOrWhiteSpace(StatusText.Text))
6822                         {
6823                             var inReplyToStatusId = this.inReplyTo?.Item1;
6824                             var inReplyToScreenName = this.inReplyTo?.Item2;
6825                             _history[_hisIdx] = new PostingStatus(StatusText.Text, inReplyToStatusId, inReplyToScreenName);
6826                         }
6827                         _hisIdx += 1;
6828                         if (_hisIdx > _history.Count - 1) _hisIdx = _history.Count - 1;
6829
6830                         var historyItem = this._history[this._hisIdx];
6831                         StatusText.Text = historyItem.status;
6832                         StatusText.SelectionStart = StatusText.Text.Length;
6833                         if (historyItem.inReplyToId != null)
6834                             this.inReplyTo = Tuple.Create(historyItem.inReplyToId.Value, historyItem.inReplyToName);
6835                         else
6836                             this.inReplyTo = null;
6837                     }),
6838
6839                 ShortcutCommand.Create(Keys.Control | Keys.PageUp, Keys.Control | Keys.P)
6840                     .FocusedOn(FocusedControl.StatusText)
6841                     .Do(() => {
6842                         if (ListTab.SelectedIndex == 0)
6843                         {
6844                             ListTab.SelectedIndex = ListTab.TabCount - 1;
6845                         }
6846                         else
6847                         {
6848                             ListTab.SelectedIndex -= 1;
6849                         }
6850                         StatusText.Focus();
6851                     }),
6852
6853                 ShortcutCommand.Create(Keys.Control | Keys.PageDown, Keys.Control | Keys.N)
6854                     .FocusedOn(FocusedControl.StatusText)
6855                     .Do(() => {
6856                         if (ListTab.SelectedIndex == ListTab.TabCount - 1)
6857                         {
6858                             ListTab.SelectedIndex = 0;
6859                         }
6860                         else
6861                         {
6862                             ListTab.SelectedIndex += 1;
6863                         }
6864                         StatusText.Focus();
6865                     }),
6866
6867                 ShortcutCommand.Create(Keys.Control | Keys.Y)
6868                     .FocusedOn(FocusedControl.PostBrowser)
6869                     .Do(() => {
6870                         MultiLineMenuItem.Checked = !MultiLineMenuItem.Checked;
6871                         MultiLineMenuItem_Click(null, null);
6872                     }),
6873
6874                 ShortcutCommand.Create(Keys.Shift | Keys.F3)
6875                     .Do(() => this.MenuItemSearchPrev_Click(null, null)),
6876
6877                 ShortcutCommand.Create(Keys.Shift | Keys.F5)
6878                     .Do(() => this.DoRefreshMore()),
6879
6880                 ShortcutCommand.Create(Keys.Shift | Keys.F6)
6881                     .Do(() => this.GetReplyAsync(loadMore: true)),
6882
6883                 ShortcutCommand.Create(Keys.Shift | Keys.F7)
6884                     .Do(() => this.GetDirectMessagesAsync(loadMore: true)),
6885
6886                 ShortcutCommand.Create(Keys.Shift | Keys.R)
6887                     .NotFocusedOn(FocusedControl.StatusText)
6888                     .Do(() => this.DoRefreshMore()),
6889
6890                 ShortcutCommand.Create(Keys.Shift | Keys.H)
6891                     .FocusedOn(FocusedControl.ListTab)
6892                     .Do(() => this.GoTopEnd(GoTop: true)),
6893
6894                 ShortcutCommand.Create(Keys.Shift | Keys.L)
6895                     .FocusedOn(FocusedControl.ListTab)
6896                     .Do(() => this.GoTopEnd(GoTop: false)),
6897
6898                 ShortcutCommand.Create(Keys.Shift | Keys.M)
6899                     .FocusedOn(FocusedControl.ListTab)
6900                     .Do(() => this.GoMiddle()),
6901
6902                 ShortcutCommand.Create(Keys.Shift | Keys.G)
6903                     .FocusedOn(FocusedControl.ListTab)
6904                     .Do(() => this.GoLast()),
6905
6906                 ShortcutCommand.Create(Keys.Shift | Keys.Z)
6907                     .FocusedOn(FocusedControl.ListTab)
6908                     .Do(() => this.MoveMiddle()),
6909
6910                 ShortcutCommand.Create(Keys.Shift | Keys.Oem4)
6911                     .FocusedOn(FocusedControl.ListTab)
6912                     .Do(() => this.GoBackInReplyToPostTree(parallel: true, isForward: false)),
6913
6914                 ShortcutCommand.Create(Keys.Shift | Keys.Oem6)
6915                     .FocusedOn(FocusedControl.ListTab)
6916                     .Do(() => this.GoBackInReplyToPostTree(parallel: true, isForward: true)),
6917
6918                 // お気に入り前後ジャンプ(SHIFT+N←/P→)
6919                 ShortcutCommand.Create(Keys.Shift | Keys.Right, Keys.Shift | Keys.N)
6920                     .FocusedOn(FocusedControl.ListTab)
6921                     .Do(() => this.GoFav(forward: true)),
6922
6923                 // お気に入り前後ジャンプ(SHIFT+N←/P→)
6924                 ShortcutCommand.Create(Keys.Shift | Keys.Left, Keys.Shift | Keys.P)
6925                     .FocusedOn(FocusedControl.ListTab)
6926                     .Do(() => this.GoFav(forward: false)),
6927
6928                 ShortcutCommand.Create(Keys.Shift | Keys.Space)
6929                     .FocusedOn(FocusedControl.ListTab)
6930                     .Do(() => this.GoBackSelectPostChain()),
6931
6932                 ShortcutCommand.Create(Keys.Alt | Keys.R)
6933                     .Do(() => this.doReTweetOfficial(isConfirm: true)),
6934
6935                 ShortcutCommand.Create(Keys.Alt | Keys.P)
6936                     .OnlyWhen(() => this._curPost != null)
6937                     .Do(() => this.doShowUserStatus(_curPost.ScreenName, ShowInputDialog: false)),
6938
6939                 ShortcutCommand.Create(Keys.Alt | Keys.Up)
6940                     .Do(() => this.ScrollDownPostBrowser(forward: false)),
6941
6942                 ShortcutCommand.Create(Keys.Alt | Keys.Down)
6943                     .Do(() => this.ScrollDownPostBrowser(forward: true)),
6944
6945                 ShortcutCommand.Create(Keys.Alt | Keys.PageUp)
6946                     .Do(() => this.PageDownPostBrowser(forward: false)),
6947
6948                 ShortcutCommand.Create(Keys.Alt | Keys.PageDown)
6949                     .Do(() => this.PageDownPostBrowser(forward: true)),
6950
6951                 // 別タブの同じ書き込みへ(ALT+←/→)
6952                 ShortcutCommand.Create(Keys.Alt | Keys.Right)
6953                     .FocusedOn(FocusedControl.ListTab)
6954                     .Do(() => this.GoSamePostToAnotherTab(left: false)),
6955
6956                 ShortcutCommand.Create(Keys.Alt | Keys.Left)
6957                     .FocusedOn(FocusedControl.ListTab)
6958                     .Do(() => this.GoSamePostToAnotherTab(left: true)),
6959
6960                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.R)
6961                     .Do(() => this.MakeReplyOrDirectStatus(isAuto: false, isReply: true, isAll: true)),
6962
6963                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.C)
6964                     .Do(() => this.CopyIdUri()),
6965
6966                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.F)
6967                     .OnlyWhen(() => this.ListTab.SelectedTab != null &&
6968                         this._statuses.Tabs[this.ListTab.SelectedTab.Text].TabType == MyCommon.TabUsageType.PublicSearch)
6969                     .Do(() => this.ListTab.SelectedTab.Controls["panelSearch"].Controls["comboSearch"].Focus()),
6970
6971                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.S)
6972                     .Do(() => this.FavoriteChange(FavAdd: false)),
6973
6974                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.B)
6975                     .Do(() => this.UnreadStripMenuItem_Click(null, null)),
6976
6977                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.T)
6978                     .Do(() => this.HashToggleMenuItem_Click(null, null)),
6979
6980                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.P)
6981                     .Do(() => this.ImageSelectMenuItem_Click(null, null)),
6982
6983                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.H)
6984                     .Do(() => this.doMoveToRTHome()),
6985
6986                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.O)
6987                     .Do(() => this.FavorareMenuItem_Click(null, null)),
6988
6989                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.Up)
6990                     .FocusedOn(FocusedControl.StatusText)
6991                     .Do(() => {
6992                         if (_curList != null && _curList.VirtualListSize != 0 &&
6993                                     _curList.SelectedIndices.Count > 0 && _curList.SelectedIndices[0] > 0)
6994                         {
6995                             var idx = _curList.SelectedIndices[0] - 1;
6996                             SelectListItem(_curList, idx);
6997                             _curList.EnsureVisible(idx);
6998                         }
6999                     }),
7000
7001                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.Down)
7002                     .FocusedOn(FocusedControl.StatusText)
7003                     .Do(() => {
7004                         if (_curList != null && _curList.VirtualListSize != 0 && _curList.SelectedIndices.Count > 0
7005                                     && _curList.SelectedIndices[0] < _curList.VirtualListSize - 1)
7006                         {
7007                             var idx = _curList.SelectedIndices[0] + 1;
7008                             SelectListItem(_curList, idx);
7009                             _curList.EnsureVisible(idx);
7010                         }
7011                     }),
7012
7013                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.Space)
7014                     .FocusedOn(FocusedControl.StatusText)
7015                     .Do(() => {
7016                         if (StatusText.SelectionStart > 0)
7017                         {
7018                             int endidx = StatusText.SelectionStart - 1;
7019                             string startstr = "";
7020                             for (int i = StatusText.SelectionStart - 1; i >= 0; i--)
7021                             {
7022                                 char c = StatusText.Text[i];
7023                                 if (Char.IsLetterOrDigit(c) || c == '_')
7024                                 {
7025                                     continue;
7026                                 }
7027                                 if (c == '@')
7028                                 {
7029                                     startstr = StatusText.Text.Substring(i + 1, endidx - i);
7030                                     int cnt = AtIdSupl.ItemCount;
7031                                     ShowSuplDialog(StatusText, AtIdSupl, startstr.Length + 1, startstr);
7032                                     if (AtIdSupl.ItemCount != cnt) _modifySettingAtId = true;
7033                                 }
7034                                 else if (c == '#')
7035                                 {
7036                                     startstr = StatusText.Text.Substring(i + 1, endidx - i);
7037                                     ShowSuplDialog(StatusText, HashSupl, startstr.Length + 1, startstr);
7038                                 }
7039                                 else
7040                                 {
7041                                     break;
7042                                 }
7043                             }
7044                         }
7045                     }),
7046
7047                 // ソートダイレクト選択(Ctrl+Shift+1~8,Ctrl+Shift+9)
7048                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D1)
7049                     .FocusedOn(FocusedControl.ListTab)
7050                     .Do(() => this.SetSortColumnByDisplayIndex(0)),
7051
7052                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D2)
7053                     .FocusedOn(FocusedControl.ListTab)
7054                     .Do(() => this.SetSortColumnByDisplayIndex(1)),
7055
7056                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D3)
7057                     .FocusedOn(FocusedControl.ListTab)
7058                     .Do(() => this.SetSortColumnByDisplayIndex(2)),
7059
7060                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D4)
7061                     .FocusedOn(FocusedControl.ListTab)
7062                     .Do(() => this.SetSortColumnByDisplayIndex(3)),
7063
7064                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D5)
7065                     .FocusedOn(FocusedControl.ListTab)
7066                     .Do(() => this.SetSortColumnByDisplayIndex(4)),
7067
7068                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D6)
7069                     .FocusedOn(FocusedControl.ListTab)
7070                     .Do(() => this.SetSortColumnByDisplayIndex(5)),
7071
7072                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D7)
7073                     .FocusedOn(FocusedControl.ListTab)
7074                     .Do(() => this.SetSortColumnByDisplayIndex(6)),
7075
7076                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D8)
7077                     .FocusedOn(FocusedControl.ListTab)
7078                     .Do(() => this.SetSortColumnByDisplayIndex(7)),
7079
7080                 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D9)
7081                     .FocusedOn(FocusedControl.ListTab)
7082                     .Do(() => this.SetSortLastColumn()),
7083
7084                 ShortcutCommand.Create(Keys.Control | Keys.Alt | Keys.S)
7085                     .Do(() => this.FavoritesRetweetOfficial()),
7086
7087                 ShortcutCommand.Create(Keys.Control | Keys.Alt | Keys.R)
7088                     .Do(() => this.FavoritesRetweetUnofficial()),
7089
7090                 ShortcutCommand.Create(Keys.Control | Keys.Alt | Keys.H)
7091                     .Do(() => this.OpenUserAppointUrl()),
7092
7093                 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.R)
7094                     .FocusedOn(FocusedControl.PostBrowser)
7095                     .Do(() => this.doReTweetUnofficial()),
7096
7097                 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.C)
7098                     .FocusedOn(FocusedControl.PostBrowser)
7099                     .Do(() => this.CopyUserId()),
7100
7101                 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.T)
7102                     .OnlyWhen(() => this.ExistCurrentPost)
7103                     .Do(() => this.doTranslation(_curPost.TextFromApi)),
7104
7105                 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.R)
7106                     .Do(() => this.doReTweetUnofficial()),
7107
7108                 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.C)
7109                     .Do(() => this.CopyUserId()),
7110
7111                 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.Up)
7112                     .Do(() => this.tweetThumbnail1.ScrollUp()),
7113
7114                 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.Down)
7115                     .Do(() => this.tweetThumbnail1.ScrollDown()),
7116
7117                 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.Enter)
7118                     .FocusedOn(FocusedControl.ListTab)
7119                     .OnlyWhen(() => !this.SplitContainer3.Panel2Collapsed)
7120                     .Do(() => this.OpenThumbnailPicture(this.tweetThumbnail1.Thumbnail)),
7121             };
7122         }
7123
7124         private bool CommonKeyDown(Keys keyData, FocusedControl focusedOn, out Task asyncTask)
7125         {
7126             // Task を返す非同期処理があれば asyncTask に代入する
7127             asyncTask = null;
7128
7129             // ShortcutCommand に対応しているコマンドはここで処理される
7130             foreach (var command in this.shortcutCommands)
7131             {
7132                 if (command.IsMatch(keyData, focusedOn))
7133                 {
7134                     asyncTask = command.RunCommand();
7135                     return command.PreventDefault;
7136                 }
7137             }
7138
7139             return false;
7140         }
7141
7142         private void ScrollDownPostBrowser(bool forward)
7143         {
7144             var doc = PostBrowser.Document;
7145             if (doc == null) return;
7146
7147             var tags = doc.GetElementsByTagName("html");
7148             if (tags.Count > 0)
7149             {
7150                 if (forward)
7151                     tags[0].ScrollTop += this._fntDetail.Height;
7152                 else
7153                     tags[0].ScrollTop -= this._fntDetail.Height;
7154             }
7155         }
7156
7157         private void PageDownPostBrowser(bool forward)
7158         {
7159             var doc = PostBrowser.Document;
7160             if (doc == null) return;
7161
7162             var tags = doc.GetElementsByTagName("html");
7163             if (tags.Count > 0)
7164             {
7165                 if (forward)
7166                     tags[0].ScrollTop += PostBrowser.ClientRectangle.Height - this._fntDetail.Height;
7167                 else
7168                     tags[0].ScrollTop -= PostBrowser.ClientRectangle.Height - this._fntDetail.Height;
7169             }
7170         }
7171
7172         private void GoNextTab(bool forward)
7173         {
7174             int idx = ListTab.SelectedIndex;
7175             if (forward)
7176             {
7177                 idx += 1;
7178                 if (idx > ListTab.TabPages.Count - 1) idx = 0;
7179             }
7180             else
7181             {
7182                 idx -= 1;
7183                 if (idx < 0) idx = ListTab.TabPages.Count - 1;
7184             }
7185             ListTab.SelectedIndex = idx;
7186         }
7187
7188         private void CopyStot()
7189         {
7190             string clstr = "";
7191             StringBuilder sb = new StringBuilder();
7192             bool IsProtected = false;
7193             bool isDm = false;
7194             if (this._curTab != null && this._statuses.GetTabByName(this._curTab.Text) != null) isDm = this._statuses.GetTabByName(this._curTab.Text).TabType == MyCommon.TabUsageType.DirectMessage;
7195             foreach (int idx in _curList.SelectedIndices)
7196             {
7197                 PostClass post = _statuses.Tabs[_curTab.Text][idx];
7198                 if (post.IsProtect)
7199                 {
7200                     IsProtected = true;
7201                     continue;
7202                 }
7203                 if (post.IsDeleted) continue;
7204                 if (!isDm)
7205                 {
7206                     if (post.RetweetedId != null)
7207                         sb.AppendFormat("{0}:{1} [https://twitter.com/{0}/status/{2}]{3}", post.ScreenName, post.TextSingleLine, post.RetweetedId, Environment.NewLine);
7208                     else
7209                         sb.AppendFormat("{0}:{1} [https://twitter.com/{0}/status/{2}]{3}", post.ScreenName, post.TextSingleLine, post.StatusId, Environment.NewLine);
7210                 }
7211                 else
7212                 {
7213                     sb.AppendFormat("{0}:{1} [{2}]{3}", post.ScreenName, post.TextSingleLine, post.StatusId, Environment.NewLine);
7214                 }
7215             }
7216             if (IsProtected)
7217             {
7218                 MessageBox.Show(Properties.Resources.CopyStotText1);
7219             }
7220             if (sb.Length > 0)
7221             {
7222                 clstr = sb.ToString();
7223                 try
7224                 {
7225                     Clipboard.SetDataObject(clstr, false, 5, 100);
7226                 }
7227                 catch (Exception ex)
7228                 {
7229                     MessageBox.Show(ex.Message);
7230                 }
7231             }
7232         }
7233
7234         private void CopyIdUri()
7235         {
7236             string clstr = "";
7237             StringBuilder sb = new StringBuilder();
7238             if (this._curTab == null) return;
7239             if (this._statuses.GetTabByName(this._curTab.Text) == null) return;
7240             if (this._statuses.GetTabByName(this._curTab.Text).TabType == MyCommon.TabUsageType.DirectMessage) return;
7241             foreach (int idx in _curList.SelectedIndices)
7242             {
7243                 var post = _statuses.Tabs[_curTab.Text][idx];
7244                 sb.Append(MyCommon.GetStatusUrl(post));
7245                 sb.Append(Environment.NewLine);
7246             }
7247             if (sb.Length > 0)
7248             {
7249                 clstr = sb.ToString();
7250                 try
7251                 {
7252                     Clipboard.SetDataObject(clstr, false, 5, 100);
7253                 }
7254                 catch (Exception ex)
7255                 {
7256                     MessageBox.Show(ex.Message);
7257                 }
7258             }
7259         }
7260
7261         private void GoFav(bool forward)
7262         {
7263             if (_curList.VirtualListSize == 0) return;
7264             int fIdx = 0;
7265             int toIdx = 0;
7266             int stp = 1;
7267
7268             if (forward)
7269             {
7270                 if (_curList.SelectedIndices.Count == 0)
7271                 {
7272                     fIdx = 0;
7273                 }
7274                 else
7275                 {
7276                     fIdx = _curList.SelectedIndices[0] + 1;
7277                     if (fIdx > _curList.VirtualListSize - 1) return;
7278                 }
7279                 toIdx = _curList.VirtualListSize;
7280                 stp = 1;
7281             }
7282             else
7283             {
7284                 if (_curList.SelectedIndices.Count == 0)
7285                 {
7286                     fIdx = _curList.VirtualListSize - 1;
7287                 }
7288                 else
7289                 {
7290                     fIdx = _curList.SelectedIndices[0] - 1;
7291                     if (fIdx < 0) return;
7292                 }
7293                 toIdx = -1;
7294                 stp = -1;
7295             }
7296
7297             for (int idx = fIdx; idx != toIdx; idx += stp)
7298             {
7299                 if (_statuses.Tabs[_curTab.Text][idx].IsFav)
7300                 {
7301                     SelectListItem(_curList, idx);
7302                     _curList.EnsureVisible(idx);
7303                     break;
7304                 }
7305             }
7306         }
7307
7308         private void GoSamePostToAnotherTab(bool left)
7309         {
7310             if (_curList.VirtualListSize == 0) return;
7311             int fIdx = 0;
7312             int toIdx = 0;
7313             int stp = 1;
7314             long targetId = 0;
7315
7316             if (_statuses.Tabs[_curTab.Text].TabType == MyCommon.TabUsageType.DirectMessage) return; // Directタブは対象外(見つかるはずがない)
7317             if (_curList.SelectedIndices.Count == 0) return; //未選択も処理しない
7318
7319             targetId = GetCurTabPost(_curList.SelectedIndices[0]).StatusId;
7320
7321             if (left)
7322             {
7323                 // 左のタブへ
7324                 if (ListTab.SelectedIndex == 0)
7325                 {
7326                     return;
7327                 }
7328                 else
7329                 {
7330                     fIdx = ListTab.SelectedIndex - 1;
7331                 }
7332                 toIdx = -1;
7333                 stp = -1;
7334             }
7335             else
7336             {
7337                 // 右のタブへ
7338                 if (ListTab.SelectedIndex == ListTab.TabCount - 1)
7339                 {
7340                     return;
7341                 }
7342                 else
7343                 {
7344                     fIdx = ListTab.SelectedIndex + 1;
7345                 }
7346                 toIdx = ListTab.TabCount;
7347                 stp = 1;
7348             }
7349
7350             bool found = false;
7351             for (int tabidx = fIdx; tabidx != toIdx; tabidx += stp)
7352             {
7353                 if (_statuses.Tabs[ListTab.TabPages[tabidx].Text].TabType == MyCommon.TabUsageType.DirectMessage) continue; // Directタブは対象外
7354                 for (int idx = 0; idx < ((DetailsListView)ListTab.TabPages[tabidx].Tag).VirtualListSize; idx++)
7355                 {
7356                     if (_statuses.Tabs[ListTab.TabPages[tabidx].Text][idx].StatusId == targetId)
7357                     {
7358                         ListTab.SelectedIndex = tabidx;
7359                         SelectListItem(_curList, idx);
7360                         _curList.EnsureVisible(idx);
7361                         found = true;
7362                         break;
7363                     }
7364                 }
7365                 if (found) break;
7366             }
7367         }
7368
7369         private void GoPost(bool forward)
7370         {
7371             if (_curList.SelectedIndices.Count == 0 || _curPost == null) return;
7372             int fIdx = 0;
7373             int toIdx = 0;
7374             int stp = 1;
7375
7376             if (forward)
7377             {
7378                 fIdx = _curList.SelectedIndices[0] + 1;
7379                 if (fIdx > _curList.VirtualListSize - 1) return;
7380                 toIdx = _curList.VirtualListSize;
7381                 stp = 1;
7382             }
7383             else
7384             {
7385                 fIdx = _curList.SelectedIndices[0] - 1;
7386                 if (fIdx < 0) return;
7387                 toIdx = -1;
7388                 stp = -1;
7389             }
7390
7391             string name = "";
7392             if (_curPost.RetweetedId == null)
7393             {
7394                 name = _curPost.ScreenName;
7395             }
7396             else
7397             {
7398                 name = _curPost.RetweetedBy;
7399             }
7400             for (int idx = fIdx; idx != toIdx; idx += stp)
7401             {
7402                 if (_statuses.Tabs[_curTab.Text][idx].RetweetedId == null)
7403                 {
7404                     if (_statuses.Tabs[_curTab.Text][idx].ScreenName == name)
7405                     {
7406                         SelectListItem(_curList, idx);
7407                         _curList.EnsureVisible(idx);
7408                         break;
7409                     }
7410                 }
7411                 else
7412                 {
7413                     if (_statuses.Tabs[_curTab.Text][idx].RetweetedBy == name)
7414                     {
7415                         SelectListItem(_curList, idx);
7416                         _curList.EnsureVisible(idx);
7417                         break;
7418                     }
7419                 }
7420             }
7421         }
7422
7423         private void GoRelPost(bool forward)
7424         {
7425             if (_curList.SelectedIndices.Count == 0) return;
7426
7427             int fIdx = 0;
7428             int toIdx = 0;
7429             int stp = 1;
7430             if (forward)
7431             {
7432                 fIdx = _curList.SelectedIndices[0] + 1;
7433                 if (fIdx > _curList.VirtualListSize - 1) return;
7434                 toIdx = _curList.VirtualListSize;
7435                 stp = 1;
7436             }
7437             else
7438             {
7439                 fIdx = _curList.SelectedIndices[0] - 1;
7440                 if (fIdx < 0) return;
7441                 toIdx = -1;
7442                 stp = -1;
7443             }
7444
7445             if (!_anchorFlag)
7446             {
7447                 if (_curPost == null) return;
7448                 _anchorPost = _curPost;
7449                 _anchorFlag = true;
7450             }
7451             else
7452             {
7453                 if (_anchorPost == null) return;
7454             }
7455
7456             for (int idx = fIdx; idx != toIdx; idx += stp)
7457             {
7458                 PostClass post = _statuses.Tabs[_curTab.Text][idx];
7459                 if (post.ScreenName == _anchorPost.ScreenName ||
7460                     post.RetweetedBy == _anchorPost.ScreenName ||
7461                     post.ScreenName == _anchorPost.RetweetedBy ||
7462                     (!string.IsNullOrEmpty(post.RetweetedBy) && post.RetweetedBy == _anchorPost.RetweetedBy) ||
7463                     _anchorPost.ReplyToList.Contains(post.ScreenName.ToLower()) ||
7464                     _anchorPost.ReplyToList.Contains(post.RetweetedBy.ToLower()) ||
7465                     post.ReplyToList.Contains(_anchorPost.ScreenName.ToLower()) ||
7466                     post.ReplyToList.Contains(_anchorPost.RetweetedBy.ToLower()))
7467                 {
7468                     SelectListItem(_curList, idx);
7469                     _curList.EnsureVisible(idx);
7470                     break;
7471                 }
7472             }
7473         }
7474
7475         private void GoAnchor()
7476         {
7477             if (_anchorPost == null) return;
7478             int idx = _statuses.Tabs[_curTab.Text].IndexOf(_anchorPost.StatusId);
7479             if (idx == -1) return;
7480
7481             SelectListItem(_curList, idx);
7482             _curList.EnsureVisible(idx);
7483         }
7484
7485         private void GoTopEnd(bool GoTop)
7486         {
7487             if (_curList.VirtualListSize == 0)
7488                 return;
7489
7490             ListViewItem _item;
7491             int idx;
7492
7493             if (GoTop)
7494             {
7495                 _item = _curList.GetItemAt(0, 25);
7496                 if (_item == null)
7497                     idx = 0;
7498                 else
7499                     idx = _item.Index;
7500             }
7501             else
7502             {
7503                 _item = _curList.GetItemAt(0, _curList.ClientSize.Height - 1);
7504                 if (_item == null)
7505                     idx = _curList.VirtualListSize - 1;
7506                 else
7507                     idx = _item.Index;
7508             }
7509             SelectListItem(_curList, idx);
7510         }
7511
7512         private void GoMiddle()
7513         {
7514             if (_curList.VirtualListSize == 0)
7515                 return;
7516
7517             ListViewItem _item;
7518             int idx1;
7519             int idx2;
7520             int idx3;
7521
7522             _item = _curList.GetItemAt(0, 0);
7523             if (_item == null)
7524             {
7525                 idx1 = 0;
7526             }
7527             else
7528             {
7529                 idx1 = _item.Index;
7530             }
7531
7532             _item = _curList.GetItemAt(0, _curList.ClientSize.Height - 1);
7533             if (_item == null)
7534             {
7535                 idx2 = _curList.VirtualListSize - 1;
7536             }
7537             else
7538             {
7539                 idx2 = _item.Index;
7540             }
7541             idx3 = (idx1 + idx2) / 2;
7542
7543             SelectListItem(_curList, idx3);
7544         }
7545
7546         private void GoLast()
7547         {
7548             if (_curList.VirtualListSize == 0) return;
7549
7550             if (_statuses.SortOrder == SortOrder.Ascending)
7551             {
7552                 SelectListItem(_curList, _curList.VirtualListSize - 1);
7553                 _curList.EnsureVisible(_curList.VirtualListSize - 1);
7554             }
7555             else
7556             {
7557                 SelectListItem(_curList, 0);
7558                 _curList.EnsureVisible(0);
7559             }
7560         }
7561
7562         private void MoveTop()
7563         {
7564             if (_curList.SelectedIndices.Count == 0) return;
7565             int idx = _curList.SelectedIndices[0];
7566             if (_statuses.SortOrder == SortOrder.Ascending)
7567             {
7568                 _curList.EnsureVisible(_curList.VirtualListSize - 1);
7569             }
7570             else
7571             {
7572                 _curList.EnsureVisible(0);
7573             }
7574             _curList.EnsureVisible(idx);
7575         }
7576
7577         private async Task GoInReplyToPostTree()
7578         {
7579             if (_curPost == null) return;
7580
7581             TabClass curTabClass = _statuses.Tabs[_curTab.Text];
7582
7583             if (curTabClass.TabType == MyCommon.TabUsageType.PublicSearch && _curPost.InReplyToStatusId == null && _curPost.TextFromApi.Contains("@"))
7584             {
7585                 try
7586                 {
7587                     var post = tw.GetStatusApi(false, _curPost.StatusId);
7588
7589                     _curPost.InReplyToStatusId = post.InReplyToStatusId;
7590                     _curPost.InReplyToUser = post.InReplyToUser;
7591                     _curPost.IsReply = post.IsReply;
7592                     this.PurgeListViewItemCache();
7593                     _curList.RedrawItems(_curItemIndex, _curItemIndex, false);
7594                 }
7595                 catch (WebApiException ex)
7596                 {
7597                     this.StatusLabel.Text = ex.Message;
7598                 }
7599             }
7600
7601             if (!(this.ExistCurrentPost && _curPost.InReplyToUser != null && _curPost.InReplyToStatusId != null)) return;
7602
7603             if (replyChains == null || (replyChains.Count > 0 && replyChains.Peek().InReplyToId != _curPost.StatusId))
7604             {
7605                 replyChains = new Stack<ReplyChain>();
7606             }
7607             replyChains.Push(new ReplyChain(_curPost.StatusId, _curPost.InReplyToStatusId.Value, _curTab));
7608
7609             int inReplyToIndex;
7610             string inReplyToTabName;
7611             long inReplyToId = _curPost.InReplyToStatusId.Value;
7612             string inReplyToUser = _curPost.InReplyToUser;
7613             //Dictionary<long, PostClass> curTabPosts = curTabClass.Posts;
7614
7615             var inReplyToPosts = from tab in _statuses.Tabs.Values
7616                                  orderby tab != curTabClass
7617                                  from post in tab.Posts.Values
7618                                  where post.StatusId == inReplyToId
7619                                  let index = tab.IndexOf(post.StatusId)
7620                                  where index != -1
7621                                  select new {Tab = tab, Index = index};
7622
7623             var inReplyPost = inReplyToPosts.FirstOrDefault();
7624             if (inReplyPost == null)
7625             {
7626                 PostClass post;
7627                 try
7628                 {
7629                     post = tw.GetStatusApi(false, _curPost.InReplyToStatusId.Value);
7630                 }
7631                 catch (WebApiException ex)
7632                 {
7633                     this.StatusLabel.Text = ex.Message;
7634                     await this.OpenUriInBrowserAsync("https://twitter.com/" + inReplyToUser + "/statuses/" + inReplyToId.ToString());
7635                     return;
7636                 }
7637
7638                 post.IsRead = true;
7639                 _statuses.AddPost(post);
7640                 _statuses.DistributePosts();
7641                 //_statuses.SubmitUpdate(null, null, null, false);
7642                 this.RefreshTimeline(false);
7643
7644                 inReplyPost = inReplyToPosts.FirstOrDefault();
7645                 if (inReplyPost == null)
7646                 {
7647                     await this.OpenUriInBrowserAsync("https://twitter.com/" + inReplyToUser + "/statuses/" + inReplyToId.ToString());
7648                     return;
7649                 }
7650                 inReplyToTabName = inReplyPost.Tab.TabName;
7651                 inReplyToIndex = inReplyPost.Index;
7652             }
7653             inReplyToTabName = inReplyPost.Tab.TabName;
7654             inReplyToIndex = inReplyPost.Index;
7655
7656             TabPage tabPage = this.ListTab.TabPages.Cast<TabPage>().First((tp) => { return tp.Text == inReplyToTabName; });
7657             DetailsListView listView = (DetailsListView)tabPage.Tag;
7658
7659             if (_curTab != tabPage)
7660             {
7661                 this.ListTab.SelectTab(tabPage);
7662             }
7663
7664             this.SelectListItem(listView, inReplyToIndex);
7665             listView.EnsureVisible(inReplyToIndex);
7666         }
7667
7668         private void GoBackInReplyToPostTree(bool parallel = false, bool isForward = true)
7669         {
7670             if (_curPost == null) return;
7671
7672             TabClass curTabClass = _statuses.Tabs[_curTab.Text];
7673             //Dictionary<long, PostClass> curTabPosts = curTabClass.Posts;
7674
7675             if (parallel)
7676             {
7677                 if (_curPost.InReplyToStatusId != null)
7678                 {
7679                     var posts = from t in _statuses.Tabs
7680                                 from p in t.Value.Posts
7681                                 where p.Value.StatusId != _curPost.StatusId && p.Value.InReplyToStatusId == _curPost.InReplyToStatusId
7682                                 let indexOf = t.Value.IndexOf(p.Value.StatusId)
7683                                 where indexOf > -1
7684                                 orderby isForward ? indexOf : indexOf * -1
7685                                 orderby t.Value != curTabClass
7686                                 select new {Tab = t.Value, Post = p.Value, Index = indexOf};
7687                     try
7688                     {
7689                         var postList = posts.ToList();
7690                         for (int i = postList.Count - 1; i >= 0; i--)
7691                         {
7692                             int index = i;
7693                             if (postList.FindIndex((pst) => { return pst.Post.StatusId == postList[index].Post.StatusId; }) != index)
7694                             {
7695                                 postList.RemoveAt(index);
7696                             }
7697                         }
7698                         var post = postList.FirstOrDefault((pst) => { return pst.Tab == curTabClass && isForward ? pst.Index > _curItemIndex : pst.Index < _curItemIndex; });
7699                         if (post == null) post = postList.FirstOrDefault((pst) => { return pst.Tab != curTabClass; });
7700                         if (post == null) post = postList.First();
7701                         this.ListTab.SelectTab(this.ListTab.TabPages.Cast<TabPage>().First((tp) => { return tp.Text == post.Tab.TabName; }));
7702                         DetailsListView listView = (DetailsListView)this.ListTab.SelectedTab.Tag;
7703                         SelectListItem(listView, post.Index);
7704                         listView.EnsureVisible(post.Index);
7705                     }
7706                     catch (InvalidOperationException)
7707                     {
7708                         return;
7709                     }
7710                 }
7711             }
7712             else
7713             {
7714                 if (replyChains == null || replyChains.Count < 1)
7715                 {
7716                     var posts = from t in _statuses.Tabs
7717                                 from p in t.Value.Posts
7718                                 where p.Value.InReplyToStatusId == _curPost.StatusId
7719                                 let indexOf = t.Value.IndexOf(p.Value.StatusId)
7720                                 where indexOf > -1
7721                                 orderby indexOf
7722                                 orderby t.Value != curTabClass
7723                                 select new {Tab = t.Value, Index = indexOf};
7724                     try
7725                     {
7726                         var post = posts.First();
7727                         this.ListTab.SelectTab(this.ListTab.TabPages.Cast<TabPage>().First((tp) => { return tp.Text == post.Tab.TabName; }));
7728                         DetailsListView listView = (DetailsListView)this.ListTab.SelectedTab.Tag;
7729                         SelectListItem(listView, post.Index);
7730                         listView.EnsureVisible(post.Index);
7731                     }
7732                     catch (InvalidOperationException)
7733                     {
7734                         return;
7735                     }
7736                 }
7737                 else
7738                 {
7739                     ReplyChain chainHead = replyChains.Pop();
7740                     if (chainHead.InReplyToId == _curPost.StatusId)
7741                     {
7742                         int idx = _statuses.Tabs[chainHead.OriginalTab.Text].IndexOf(chainHead.OriginalId);
7743                         if (idx == -1)
7744                         {
7745                             replyChains = null;
7746                         }
7747                         else
7748                         {
7749                             try
7750                             {
7751                                 ListTab.SelectTab(chainHead.OriginalTab);
7752                             }
7753                             catch (Exception)
7754                             {
7755                                 replyChains = null;
7756                             }
7757                             SelectListItem(_curList, idx);
7758                             _curList.EnsureVisible(idx);
7759                         }
7760                     }
7761                     else
7762                     {
7763                         replyChains = null;
7764                         this.GoBackInReplyToPostTree(parallel);
7765                     }
7766                 }
7767             }
7768         }
7769
7770         private void GoBackSelectPostChain()
7771         {
7772             if (this.selectPostChains.Count > 1)
7773             {
7774                 var idx = -1;
7775                 TabPage tp = null;
7776
7777                 do
7778                 {
7779                     try
7780                     {
7781                         this.selectPostChains.Pop();
7782                         var tabPostPair = this.selectPostChains.Peek();
7783
7784                         if (!this.ListTab.TabPages.Contains(tabPostPair.Item1)) continue;  //該当タブが存在しないので無視
7785
7786                         if (tabPostPair.Item2 != null)
7787                         {
7788                             idx = this._statuses.Tabs[tabPostPair.Item1.Text].IndexOf(tabPostPair.Item2.StatusId);
7789                             if (idx == -1) continue;  //該当ポストが存在しないので無視
7790                         }
7791
7792                         tp = tabPostPair.Item1;
7793
7794                         this.selectPostChains.Pop();
7795                     }
7796                     catch (InvalidOperationException)
7797                     {
7798                     }
7799
7800                     break;
7801                 }
7802                 while (this.selectPostChains.Count > 1);
7803
7804                 if (tp == null)
7805                 {
7806                     //状態がおかしいので処理を中断
7807                     //履歴が残り1つであればクリアしておく
7808                     if (this.selectPostChains.Count == 1)
7809                         this.selectPostChains.Clear();
7810                     return;
7811                 }
7812
7813                 DetailsListView lst = (DetailsListView)tp.Tag;
7814                 this.ListTab.SelectedTab = tp;
7815                 if (idx > -1)
7816                 {
7817                     SelectListItem(lst, idx);
7818                     lst.EnsureVisible(idx);
7819                 }
7820                 lst.Focus();
7821             }
7822         }
7823
7824         private void PushSelectPostChain()
7825         {
7826             int count = this.selectPostChains.Count;
7827             if (count > 0)
7828             {
7829                 var p = this.selectPostChains.Peek();
7830                 if (p.Item1 == this._curTab)
7831                 {
7832                     if (p.Item2 == this._curPost) return;  //最新の履歴と同一
7833                     if (p.Item2 == null) this.selectPostChains.Pop();  //置き換えるため削除
7834                 }
7835             }
7836             if (count >= 2500) TrimPostChain();
7837             this.selectPostChains.Push(Tuple.Create(this._curTab, this._curPost));
7838         }
7839
7840         private void TrimPostChain()
7841         {
7842             if (this.selectPostChains.Count <= 2000) return;
7843             var p = new Stack<Tuple<TabPage, PostClass>>(2000);
7844             for (int i = 0; i < 2000; i++)
7845             {
7846                 p.Push(this.selectPostChains.Pop());
7847             }
7848             this.selectPostChains.Clear();
7849             for (int i = 0; i < 2000; i++)
7850             {
7851                 this.selectPostChains.Push(p.Pop());
7852             }
7853         }
7854
7855         private bool GoStatus(long statusId)
7856         {
7857             if (statusId == 0) return false;
7858             for (int tabidx = 0; tabidx < ListTab.TabCount; tabidx++)
7859             {
7860                 if (_statuses.Tabs[ListTab.TabPages[tabidx].Text].TabType != MyCommon.TabUsageType.DirectMessage && _statuses.Tabs[ListTab.TabPages[tabidx].Text].Contains(statusId))
7861                 {
7862                     int idx = _statuses.Tabs[ListTab.TabPages[tabidx].Text].IndexOf(statusId);
7863                     ListTab.SelectedIndex = tabidx;
7864                     SelectListItem(_curList, idx);
7865                     _curList.EnsureVisible(idx);
7866                     return true;
7867                 }
7868             }
7869             return false;
7870         }
7871
7872         private bool GoDirectMessage(long statusId)
7873         {
7874             if (statusId == 0) return false;
7875             for (int tabidx = 0; tabidx < ListTab.TabCount; tabidx++)
7876             {
7877                 if (_statuses.Tabs[ListTab.TabPages[tabidx].Text].TabType == MyCommon.TabUsageType.DirectMessage && _statuses.Tabs[ListTab.TabPages[tabidx].Text].Contains(statusId))
7878                 {
7879                     int idx = _statuses.Tabs[ListTab.TabPages[tabidx].Text].IndexOf(statusId);
7880                     ListTab.SelectedIndex = tabidx;
7881                     SelectListItem(_curList, idx);
7882                     _curList.EnsureVisible(idx);
7883                     return true;
7884                 }
7885             }
7886             return false;
7887         }
7888
7889         private void MyList_MouseClick(object sender, MouseEventArgs e)
7890         {
7891             _anchorFlag = false;
7892         }
7893
7894         private void StatusText_Enter(object sender, EventArgs e)
7895         {
7896             // フォーカスの戻り先を StatusText に設定
7897             this.Tag = StatusText;
7898             StatusText.BackColor = _clInputBackcolor;
7899         }
7900
7901         public Color InputBackColor
7902         {
7903             get { return _clInputBackcolor; }
7904             set { _clInputBackcolor = value; }
7905         }
7906
7907         private void StatusText_Leave(object sender, EventArgs e)
7908         {
7909             // フォーカスがメニューに遷移しないならばフォーカスはタブに移ることを期待
7910             if (ListTab.SelectedTab != null && MenuStrip1.Tag == null) this.Tag = ListTab.SelectedTab.Tag;
7911             StatusText.BackColor = Color.FromKnownColor(KnownColor.Window);
7912         }
7913
7914         private async void StatusText_KeyDown(object sender, KeyEventArgs e)
7915         {
7916             Task asyncTask;
7917             if (CommonKeyDown(e.KeyData, FocusedControl.StatusText, out asyncTask))
7918             {
7919                 e.Handled = true;
7920                 e.SuppressKeyPress = true;
7921             }
7922
7923             this.StatusText_TextChanged(null, null);
7924
7925             if (asyncTask != null)
7926                 await asyncTask;
7927         }
7928
7929         private void SaveConfigsAll(bool ifModified)
7930         {
7931             if (!ifModified)
7932             {
7933                 SaveConfigsCommon();
7934                 SaveConfigsLocal();
7935                 SaveConfigsTabs();
7936                 SaveConfigsAtId();
7937             }
7938             else
7939             {
7940                 if (_modifySettingCommon) SaveConfigsCommon();
7941                 if (_modifySettingLocal) SaveConfigsLocal();
7942                 if (_modifySettingAtId) SaveConfigsAtId();
7943             }
7944         }
7945
7946         private void SaveConfigsAtId()
7947         {
7948             if (_ignoreConfigSave || !this._cfgCommon.UseAtIdSupplement && AtIdSupl == null) return;
7949
7950             _modifySettingAtId = false;
7951             SettingAtIdList cfgAtId = new SettingAtIdList(AtIdSupl.GetItemList());
7952             cfgAtId.Save();
7953         }
7954
7955         private void SaveConfigsCommon()
7956         {
7957             if (_ignoreConfigSave) return;
7958
7959             _modifySettingCommon = false;
7960             lock (_syncObject)
7961             {
7962                 _cfgCommon.UserName = tw.Username;
7963                 _cfgCommon.UserId = tw.UserId;
7964                 _cfgCommon.Password = tw.Password;
7965                 _cfgCommon.Token = tw.AccessToken;
7966                 _cfgCommon.TokenSecret = tw.AccessTokenSecret;
7967
7968                 if (IdeographicSpaceToSpaceToolStripMenuItem != null &&
7969                    IdeographicSpaceToSpaceToolStripMenuItem.IsDisposed == false)
7970                 {
7971                     _cfgCommon.WideSpaceConvert = this.IdeographicSpaceToSpaceToolStripMenuItem.Checked;
7972                 }
7973
7974                 _cfgCommon.SortOrder = (int)_statuses.SortOrder;
7975                 switch (_statuses.SortMode)
7976                 {
7977                     case ComparerMode.Nickname:  //ニックネーム
7978                         _cfgCommon.SortColumn = 1;
7979                         break;
7980                     case ComparerMode.Data:  //本文
7981                         _cfgCommon.SortColumn = 2;
7982                         break;
7983                     case ComparerMode.Id:  //時刻=発言Id
7984                         _cfgCommon.SortColumn = 3;
7985                         break;
7986                     case ComparerMode.Name:  //名前
7987                         _cfgCommon.SortColumn = 4;
7988                         break;
7989                     case ComparerMode.Source:  //Source
7990                         _cfgCommon.SortColumn = 7;
7991                         break;
7992                 }
7993
7994                 _cfgCommon.HashTags = HashMgr.HashHistories;
7995                 if (HashMgr.IsPermanent)
7996                 {
7997                     _cfgCommon.HashSelected = HashMgr.UseHash;
7998                 }
7999                 else
8000                 {
8001                     _cfgCommon.HashSelected = "";
8002                 }
8003                 _cfgCommon.HashIsHead = HashMgr.IsHead;
8004                 _cfgCommon.HashIsPermanent = HashMgr.IsPermanent;
8005                 _cfgCommon.HashIsNotAddToAtReply = HashMgr.IsNotAddToAtReply;
8006                 if (ToolStripFocusLockMenuItem != null &&
8007                         ToolStripFocusLockMenuItem.IsDisposed == false)
8008                 {
8009                     _cfgCommon.FocusLockToStatusText = this.ToolStripFocusLockMenuItem.Checked;
8010                 }
8011                 _cfgCommon.TrackWord = tw.TrackWord;
8012                 _cfgCommon.AllAtReply = tw.AllAtReply;
8013                 _cfgCommon.UseImageService = ImageSelector.ServiceIndex;
8014                 _cfgCommon.UseImageServiceName = ImageSelector.ServiceName;
8015
8016                 _cfgCommon.Save();
8017             }
8018         }
8019
8020         private void SaveConfigsLocal()
8021         {
8022             if (_ignoreConfigSave) return;
8023             lock (_syncObject)
8024             {
8025                 _modifySettingLocal = false;
8026                 _cfgLocal.ScaleDimension = this.CurrentAutoScaleDimensions;
8027                 _cfgLocal.FormSize = _mySize;
8028                 _cfgLocal.FormLocation = _myLoc;
8029                 _cfgLocal.SplitterDistance = _mySpDis;
8030                 _cfgLocal.PreviewDistance = _mySpDis3;
8031                 _cfgLocal.StatusMultiline = StatusText.Multiline;
8032                 _cfgLocal.StatusTextHeight = _mySpDis2;
8033
8034                 _cfgLocal.FontUnread = _fntUnread;
8035                 _cfgLocal.ColorUnread = _clUnread;
8036                 _cfgLocal.FontRead = _fntReaded;
8037                 _cfgLocal.ColorRead = _clReaded;
8038                 _cfgLocal.FontDetail = _fntDetail;
8039                 _cfgLocal.ColorDetail = _clDetail;
8040                 _cfgLocal.ColorDetailBackcolor = _clDetailBackcolor;
8041                 _cfgLocal.ColorDetailLink = _clDetailLink;
8042                 _cfgLocal.ColorFav = _clFav;
8043                 _cfgLocal.ColorOWL = _clOWL;
8044                 _cfgLocal.ColorRetweet = _clRetweet;
8045                 _cfgLocal.ColorSelf = _clSelf;
8046                 _cfgLocal.ColorAtSelf = _clAtSelf;
8047                 _cfgLocal.ColorTarget = _clTarget;
8048                 _cfgLocal.ColorAtTarget = _clAtTarget;
8049                 _cfgLocal.ColorAtFromTarget = _clAtFromTarget;
8050                 _cfgLocal.ColorAtTo = _clAtTo;
8051                 _cfgLocal.ColorListBackcolor = _clListBackcolor;
8052                 _cfgLocal.ColorInputBackcolor = _clInputBackcolor;
8053                 _cfgLocal.ColorInputFont = _clInputFont;
8054                 _cfgLocal.FontInputFont = _fntInputFont;
8055
8056                 if (_ignoreConfigSave) return;
8057                 _cfgLocal.Save();
8058             }
8059         }
8060
8061         private void SaveConfigsTabs()
8062         {
8063             SettingTabs tabSetting = new SettingTabs();
8064             for (int i = 0; i < ListTab.TabPages.Count; i++)
8065             {
8066                 var tab = _statuses.Tabs[ListTab.TabPages[i].Text];
8067                 if (tab.TabType != MyCommon.TabUsageType.Related && tab.TabType != MyCommon.TabUsageType.SearchResults)
8068                     tabSetting.Tabs.Add(tab);
8069             }
8070             tabSetting.Tabs.Add(this._statuses.GetTabByType(MyCommon.TabUsageType.Mute));
8071             tabSetting.Save();
8072         }
8073
8074         private async void OpenURLFileMenuItem_Click(object sender, EventArgs e)
8075         {
8076             string inputText;
8077             var ret = InputDialog.Show(this, Properties.Resources.OpenURL_InputText, Properties.Resources.OpenURL_Caption, out inputText);
8078             if (ret != DialogResult.OK)
8079                 return;
8080
8081             var match = Twitter.StatusUrlRegex.Match(inputText);
8082             if (!match.Success)
8083             {
8084                 MessageBox.Show(this, Properties.Resources.OpenURL_InvalidFormat,
8085                     Properties.Resources.OpenURL_Caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
8086                 return;
8087             }
8088
8089             try
8090             {
8091                 var statusId = long.Parse(match.Groups["StatusId"].Value);
8092                 await this.OpenRelatedTab(statusId);
8093             }
8094             catch (TabException ex)
8095             {
8096                 MessageBox.Show(this, ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
8097             }
8098         }
8099
8100         private void SaveLogMenuItem_Click(object sender, EventArgs e)
8101         {
8102             DialogResult rslt = MessageBox.Show(string.Format(Properties.Resources.SaveLogMenuItem_ClickText1, Environment.NewLine),
8103                     Properties.Resources.SaveLogMenuItem_ClickText2,
8104                     MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
8105             if (rslt == DialogResult.Cancel) return;
8106
8107             SaveFileDialog1.FileName = MyCommon.GetAssemblyName() + "Posts" + DateTime.Now.ToString("yyMMdd-HHmmss") + ".tsv";
8108             SaveFileDialog1.InitialDirectory = Application.ExecutablePath;
8109             SaveFileDialog1.Filter = Properties.Resources.SaveLogMenuItem_ClickText3;
8110             SaveFileDialog1.FilterIndex = 0;
8111             SaveFileDialog1.Title = Properties.Resources.SaveLogMenuItem_ClickText4;
8112             SaveFileDialog1.RestoreDirectory = true;
8113
8114             if (SaveFileDialog1.ShowDialog() == DialogResult.OK)
8115             {
8116                 if (!SaveFileDialog1.ValidateNames) return;
8117                 using (StreamWriter sw = new StreamWriter(SaveFileDialog1.FileName, false, Encoding.UTF8))
8118                 {
8119                     if (rslt == DialogResult.Yes)
8120                     {
8121                         //All
8122                         for (int idx = 0; idx < _curList.VirtualListSize; idx++)
8123                         {
8124                             PostClass post = _statuses.Tabs[_curTab.Text][idx];
8125                             string protect = "";
8126                             if (post.IsProtect) protect = "Protect";
8127                             sw.WriteLine(post.Nickname + "\t" +
8128                                      "\"" + post.TextFromApi.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
8129                                      post.CreatedAt.ToString() + "\t" +
8130                                      post.ScreenName + "\t" +
8131                                      post.StatusId.ToString() + "\t" +
8132                                      post.ImageUrl + "\t" +
8133                                      "\"" + post.Text.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
8134                                      protect);
8135                         }
8136                     }
8137                     else
8138                     {
8139                         foreach (int idx in _curList.SelectedIndices)
8140                         {
8141                             PostClass post = _statuses.Tabs[_curTab.Text][idx];
8142                             string protect = "";
8143                             if (post.IsProtect) protect = "Protect";
8144                             sw.WriteLine(post.Nickname + "\t" +
8145                                      "\"" + post.TextFromApi.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
8146                                      post.CreatedAt.ToString() + "\t" +
8147                                      post.ScreenName + "\t" +
8148                                      post.StatusId.ToString() + "\t" +
8149                                      post.ImageUrl + "\t" +
8150                                      "\"" + post.Text.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
8151                                      protect);
8152                         }
8153                     }
8154                 }
8155             }
8156             this.TopMost = this._cfgCommon.AlwaysTop;
8157         }
8158
8159         private async void PostBrowser_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
8160         {
8161             Task asyncTask;
8162             bool KeyRes = CommonKeyDown(e.KeyData, FocusedControl.PostBrowser, out asyncTask);
8163             if (KeyRes)
8164             {
8165                 e.IsInputKey = true;
8166             }
8167             else
8168             {
8169                 if (Enum.IsDefined(typeof(Shortcut), (Shortcut)e.KeyData))
8170                 {
8171                     var shortcut = (Shortcut)e.KeyData;
8172                     switch (shortcut)
8173                     {
8174                         case Shortcut.CtrlA:
8175                         case Shortcut.CtrlC:
8176                         case Shortcut.CtrlIns:
8177                             // 既定の動作を有効にする
8178                             break;
8179                         default:
8180                             // その他のショートカットキーは無効にする
8181                             e.IsInputKey = true;
8182                             break;
8183                     }
8184                 }
8185             }
8186
8187             if (asyncTask != null)
8188                 await asyncTask;
8189         }
8190         public bool TabRename(ref string tabName)
8191         {
8192             //タブ名変更
8193             string newTabText = null;
8194             using (InputTabName inputName = new InputTabName())
8195             {
8196                 inputName.TabName = tabName;
8197                 inputName.ShowDialog();
8198                 if (inputName.DialogResult == DialogResult.Cancel) return false;
8199                 newTabText = inputName.TabName;
8200             }
8201             this.TopMost = this._cfgCommon.AlwaysTop;
8202             if (!string.IsNullOrEmpty(newTabText))
8203             {
8204                 //新タブ名存在チェック
8205                 for (int i = 0; i < ListTab.TabCount; i++)
8206                 {
8207                     if (ListTab.TabPages[i].Text == newTabText)
8208                     {
8209                         string tmp = string.Format(Properties.Resources.Tabs_DoubleClickText1, newTabText);
8210                         MessageBox.Show(tmp, Properties.Resources.Tabs_DoubleClickText2, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
8211                         return false;
8212                     }
8213                 }
8214                 //タブ名を変更
8215                 for (int i = 0; i < ListTab.TabCount; i++)
8216                 {
8217                     if (ListTab.TabPages[i].Text == tabName)
8218                     {
8219                         ListTab.TabPages[i].Text = newTabText;
8220                         break;
8221                     }
8222                 }
8223                 _statuses.RenameTab(tabName, newTabText);
8224
8225                 SaveConfigsCommon();
8226                 SaveConfigsTabs();
8227                 _rclickTabName = newTabText;
8228                 tabName = newTabText;
8229                 return true;
8230             }
8231             else
8232             {
8233                 return false;
8234             }
8235         }
8236
8237         private void ListTab_MouseClick(object sender, MouseEventArgs e)
8238         {
8239             if (e.Button == MouseButtons.Middle)
8240             {
8241                 for (int i = 0; i < this.ListTab.TabPages.Count; i++)
8242                 {
8243                     if (this.ListTab.GetTabRect(i).Contains(e.Location))
8244                     {
8245                         this.RemoveSpecifiedTab(this.ListTab.TabPages[i].Text, true);
8246                         this.SaveConfigsTabs();
8247                         break;
8248                     }
8249                 }
8250             }
8251         }
8252
8253         private void ListTab_DoubleClick(object sender, MouseEventArgs e)
8254         {
8255             string tn = ListTab.SelectedTab.Text;
8256             TabRename(ref tn);
8257         }
8258
8259         private void ListTab_MouseDown(object sender, MouseEventArgs e)
8260         {
8261             if (this._cfgCommon.TabMouseLock) return;
8262             Point cpos = new Point(e.X, e.Y);
8263             if (e.Button == MouseButtons.Left)
8264             {
8265                 for (int i = 0; i < ListTab.TabPages.Count; i++)
8266                 {
8267                     if (this.ListTab.GetTabRect(i).Contains(e.Location))
8268                     {
8269                         _tabDrag = true;
8270                         _tabMouseDownPoint = e.Location;
8271                         break;
8272                     }
8273                 }
8274             }
8275             else
8276             {
8277                 _tabDrag = false;
8278             }
8279         }
8280
8281         private void ListTab_DragEnter(object sender, DragEventArgs e)
8282         {
8283             if (e.Data.GetDataPresent(typeof(TabPage)))
8284                 e.Effect = DragDropEffects.Move;
8285             else
8286                 e.Effect = DragDropEffects.None;
8287         }
8288
8289         private void ListTab_DragDrop(object sender, DragEventArgs e)
8290         {
8291             if (!e.Data.GetDataPresent(typeof(TabPage))) return;
8292
8293             _tabDrag = false;
8294             string tn = "";
8295             bool bef = false;
8296             Point cpos = new Point(e.X, e.Y);
8297             Point spos = ListTab.PointToClient(cpos);
8298             int i;
8299             for (i = 0; i < ListTab.TabPages.Count; i++)
8300             {
8301                 Rectangle rect = ListTab.GetTabRect(i);
8302                 if (rect.Left <= spos.X && spos.X <= rect.Right &&
8303                     rect.Top <= spos.Y && spos.Y <= rect.Bottom)
8304                 {
8305                     tn = ListTab.TabPages[i].Text;
8306                     if (spos.X <= (rect.Left + rect.Right) / 2)
8307                         bef = true;
8308                     else
8309                         bef = false;
8310
8311                     break;
8312                 }
8313             }
8314
8315             //タブのないところにドロップ->最後尾へ移動
8316             if (string.IsNullOrEmpty(tn))
8317             {
8318                 tn = ListTab.TabPages[ListTab.TabPages.Count - 1].Text;
8319                 bef = false;
8320                 i = ListTab.TabPages.Count - 1;
8321             }
8322
8323             TabPage tp = (TabPage)e.Data.GetData(typeof(TabPage));
8324             if (tp.Text == tn) return;
8325
8326             ReOrderTab(tp.Text, tn, bef);
8327         }
8328
8329         public void ReOrderTab(string targetTabText, string baseTabText, bool isBeforeBaseTab)
8330         {
8331             var baseIndex = this.GetTabPageIndex(baseTabText);
8332             if (baseIndex == -1)
8333                 return;
8334
8335             var targetIndex = this.GetTabPageIndex(targetTabText);
8336             if (targetIndex == -1)
8337                 return;
8338
8339             using (ControlTransaction.Layout(this.ListTab))
8340             {
8341                 var mTp = this.ListTab.TabPages[targetIndex];
8342                 this.ListTab.TabPages.Remove(mTp);
8343
8344                 if (targetIndex < baseIndex)
8345                     baseIndex--;
8346
8347                 if (isBeforeBaseTab)
8348                     ListTab.TabPages.Insert(baseIndex, mTp);
8349                 else
8350                     ListTab.TabPages.Insert(baseIndex + 1, mTp);
8351             }
8352
8353             SaveConfigsTabs();
8354         }
8355
8356         private void MakeReplyOrDirectStatus(bool isAuto = true, bool isReply = true, bool isAll = false)
8357         {
8358             //isAuto:true=先頭に挿入、false=カーソル位置に挿入
8359             //isReply:true=@,false=DM
8360             if (!StatusText.Enabled) return;
8361             if (_curList == null) return;
8362             if (_curTab == null) return;
8363             if (!this.ExistCurrentPost) return;
8364
8365             // 複数あてリプライはReplyではなく通常ポスト
8366             //↑仕様変更で全部リプライ扱いでOK(先頭ドット付加しない)
8367             //090403暫定でドットを付加しないようにだけ修正。単独と複数の処理は統合できると思われる。
8368             //090513 all @ replies 廃止の仕様変更によりドット付加に戻し(syo68k)
8369
8370             if (_curList.SelectedIndices.Count > 0)
8371             {
8372                 // アイテムが1件以上選択されている
8373                 if (_curList.SelectedIndices.Count == 1 && !isAll && this.ExistCurrentPost)
8374                 {
8375                     // 単独ユーザー宛リプライまたはDM
8376                     if ((_statuses.Tabs[ListTab.SelectedTab.Text].TabType == MyCommon.TabUsageType.DirectMessage && isAuto) || (!isAuto && !isReply))
8377                     {
8378                         // ダイレクトメッセージ
8379                         StatusText.Text = "D " + _curPost.ScreenName + " " + StatusText.Text;
8380                         StatusText.SelectionStart = StatusText.Text.Length;
8381                         StatusText.Focus();
8382                         this.inReplyTo = null;
8383                         return;
8384                     }
8385                     if (string.IsNullOrEmpty(StatusText.Text))
8386                     {
8387                         //空の場合
8388
8389                         // ステータステキストが入力されていない場合先頭に@ユーザー名を追加する
8390                         StatusText.Text = "@" + _curPost.ScreenName + " ";
8391
8392                         var inReplyToStatusId = this._curPost.RetweetedId ?? this._curPost.StatusId;
8393                         var inReplyToScreenName = this._curPost.ScreenName;
8394                         this.inReplyTo = Tuple.Create(inReplyToStatusId, inReplyToScreenName);
8395                     }
8396                     else
8397                     {
8398                         //何か入力済の場合
8399
8400                         if (isAuto)
8401                         {
8402                             //1件選んでEnter or DoubleClick
8403                             if (StatusText.Text.Contains("@" + _curPost.ScreenName + " "))
8404                             {
8405                                 if (this.inReplyTo?.Item2 == _curPost.ScreenName)
8406                                 {
8407                                     //返信先書き換え
8408                                     var inReplyToStatusId = this._curPost.RetweetedId ?? this._curPost.StatusId;
8409                                     var inReplyToScreenName = this._curPost.ScreenName;
8410                                     this.inReplyTo = Tuple.Create(inReplyToStatusId, inReplyToScreenName);
8411                                 }
8412                                 return;
8413                             }
8414                             if (!StatusText.Text.StartsWith("@"))
8415                             {
8416                                 //文頭@以外
8417                                 if (StatusText.Text.StartsWith(". "))
8418                                 {
8419                                     // 複数リプライ
8420                                     StatusText.Text = StatusText.Text.Insert(2, "@" + _curPost.ScreenName + " ");
8421                                     this.inReplyTo = null;
8422                                 }
8423                                 else
8424                                 {
8425                                     // 単独リプライ
8426                                     StatusText.Text = "@" + _curPost.ScreenName + " " + StatusText.Text;
8427                                     var inReplyToStatusId = this._curPost.RetweetedId ?? this._curPost.StatusId;
8428                                     var inReplyToScreenName = this._curPost.ScreenName;
8429                                     this.inReplyTo = Tuple.Create(inReplyToStatusId, inReplyToScreenName);
8430                                 }
8431                             }
8432                             else
8433                             {
8434                                 //文頭@
8435                                 // 複数リプライ
8436                                 StatusText.Text = ". @" + _curPost.ScreenName + " " + StatusText.Text;
8437                                 //StatusText.Text = "@" + _curPost.ScreenName + " " + StatusText.Text;
8438                                 this.inReplyTo = null;
8439                             }
8440                         }
8441                         else
8442                         {
8443                             //1件選んでCtrl-Rの場合(返信先操作せず)
8444                             int sidx = StatusText.SelectionStart;
8445                             string id = "@" + _curPost.ScreenName + " ";
8446                             if (sidx > 0)
8447                             {
8448                                 if (StatusText.Text.Substring(sidx - 1, 1) != " ")
8449                                 {
8450                                     id = " " + id;
8451                                 }
8452                             }
8453                             StatusText.Text = StatusText.Text.Insert(sidx, id);
8454                             sidx += id.Length;
8455                             //if (StatusText.Text.StartsWith("@"))
8456                             //{
8457                             //    //複数リプライ
8458                             //    StatusText.Text = ". " + StatusText.Text.Insert(sidx, " @" + _curPost.ScreenName + " ");
8459                             //    sidx += 5 + _curPost.ScreenName.Length;
8460                             //}
8461                             //else
8462                             //{
8463                             //    // 複数リプライ
8464                             //    StatusText.Text = StatusText.Text.Insert(sidx, " @" + _curPost.ScreenName + " ");
8465                             //    sidx += 3 + _curPost.ScreenName.Length;
8466                             //}
8467                             StatusText.SelectionStart = sidx;
8468                             StatusText.Focus();
8469                             //_reply_to_id = 0;
8470                             //_reply_to_name = null;
8471                             return;
8472                         }
8473                     }
8474                 }
8475                 else
8476                 {
8477                     // 複数リプライ
8478                     if (!isAuto && !isReply) return;
8479
8480                     //C-S-rか、複数の宛先を選択中にEnter/DoubleClick/C-r/C-S-r
8481
8482                     if (isAuto)
8483                     {
8484                         //Enter or DoubleClick
8485
8486                         string sTxt = StatusText.Text;
8487                         if (!sTxt.StartsWith(". "))
8488                         {
8489                             sTxt = ". " + sTxt;
8490                             this.inReplyTo = null;
8491                         }
8492                         for (int cnt = 0; cnt < _curList.SelectedIndices.Count; cnt++)
8493                         {
8494                             PostClass post = _statuses.Tabs[_curTab.Text][_curList.SelectedIndices[cnt]];
8495                             if (!sTxt.Contains("@" + post.ScreenName + " "))
8496                             {
8497                                 sTxt = sTxt.Insert(2, "@" + post.ScreenName + " ");
8498                                 //sTxt = "@" + post.ScreenName + " " + sTxt;
8499                             }
8500                         }
8501                         StatusText.Text = sTxt;
8502                     }
8503                     else
8504                     {
8505                         //C-S-r or C-r
8506                         if (_curList.SelectedIndices.Count > 1)
8507                         {
8508                             //複数ポスト選択
8509
8510                             string ids = "";
8511                             int sidx = StatusText.SelectionStart;
8512                             for (int cnt = 0; cnt < _curList.SelectedIndices.Count; cnt++)
8513                             {
8514                                 PostClass post = _statuses.Tabs[_curTab.Text][_curList.SelectedIndices[cnt]];
8515                                 if (!ids.Contains("@" + post.ScreenName + " ") &&
8516                                     !post.ScreenName.Equals(tw.Username, StringComparison.CurrentCultureIgnoreCase))
8517                                 {
8518                                     ids += "@" + post.ScreenName + " ";
8519                                 }
8520                                 if (isAll)
8521                                 {
8522                                     foreach (string nm in post.ReplyToList)
8523                                     {
8524                                         if (!ids.Contains("@" + nm + " ") &&
8525                                             !nm.Equals(tw.Username, StringComparison.CurrentCultureIgnoreCase))
8526                                         {
8527                                             Match m = Regex.Match(post.TextFromApi, "[@@](?<id>" + nm + ")([^a-zA-Z0-9]|$)", RegexOptions.IgnoreCase);
8528                                             if (m.Success)
8529                                                 ids += "@" + m.Result("${id}") + " ";
8530                                             else
8531                                                 ids += "@" + nm + " ";
8532                                         }
8533                                     }
8534                                 }
8535                             }
8536                             if (ids.Length == 0) return;
8537                             if (!StatusText.Text.StartsWith(". "))
8538                             {
8539                                 StatusText.Text = ". " + StatusText.Text;
8540                                 sidx += 2;
8541                                 this.inReplyTo = null;
8542                             }
8543                             if (sidx > 0)
8544                             {
8545                                 if (StatusText.Text.Substring(sidx - 1, 1) != " ")
8546                                 {
8547                                     ids = " " + ids;
8548                                 }
8549                             }
8550                             StatusText.Text = StatusText.Text.Insert(sidx, ids);
8551                             sidx += ids.Length;
8552                             //if (StatusText.Text.StartsWith("@"))
8553                             //{
8554                             //    StatusText.Text = ". " + StatusText.Text.Insert(sidx, ids);
8555                             //    sidx += 2 + ids.Length;
8556                             //}
8557                             //else
8558                             //{
8559                             //    StatusText.Text = StatusText.Text.Insert(sidx, ids);
8560                             //    sidx += 1 + ids.Length;
8561                             //}
8562                             StatusText.SelectionStart = sidx;
8563                             StatusText.Focus();
8564                             return;
8565                         }
8566                         else
8567                         {
8568                             //1件のみ選択のC-S-r(返信元付加する可能性あり)
8569
8570                             string ids = "";
8571                             int sidx = StatusText.SelectionStart;
8572                             PostClass post = _curPost;
8573                             if (!ids.Contains("@" + post.ScreenName + " ") &&
8574                                 !post.ScreenName.Equals(tw.Username, StringComparison.CurrentCultureIgnoreCase))
8575                             {
8576                                 ids += "@" + post.ScreenName + " ";
8577                             }
8578                             foreach (string nm in post.ReplyToList)
8579                             {
8580                                 if (!ids.Contains("@" + nm + " ") &&
8581                                     !nm.Equals(tw.Username, StringComparison.CurrentCultureIgnoreCase))
8582                                 {
8583                                     Match m = Regex.Match(post.TextFromApi, "[@@](?<id>" + nm + ")([^a-zA-Z0-9]|$)", RegexOptions.IgnoreCase);
8584                                     if (m.Success)
8585                                         ids += "@" + m.Result("${id}") + " ";
8586                                     else
8587                                         ids += "@" + nm + " ";
8588                                 }
8589                             }
8590                             if (!string.IsNullOrEmpty(post.RetweetedBy))
8591                             {
8592                                 if (!ids.Contains("@" + post.RetweetedBy + " ") &&
8593                                    !post.RetweetedBy.Equals(tw.Username, StringComparison.CurrentCultureIgnoreCase))
8594                                 {
8595                                     ids += "@" + post.RetweetedBy + " ";
8596                                 }
8597                             }
8598                             if (ids.Length == 0) return;
8599                             if (string.IsNullOrEmpty(StatusText.Text))
8600                             {
8601                                 //未入力の場合のみ返信先付加
8602                                 StatusText.Text = ids;
8603                                 StatusText.SelectionStart = ids.Length;
8604                                 StatusText.Focus();
8605
8606                                 var inReplyToStatusId = this._curPost.RetweetedId ?? this._curPost.StatusId;
8607                                 var inReplyToScreenName = this._curPost.ScreenName;
8608                                 this.inReplyTo = Tuple.Create(inReplyToStatusId, inReplyToScreenName);
8609                                 return;
8610                             }
8611
8612                             if (sidx > 0)
8613                             {
8614                                 if (StatusText.Text.Substring(sidx - 1, 1) != " ")
8615                                 {
8616                                     ids = " " + ids;
8617                                 }
8618                             }
8619                             StatusText.Text = StatusText.Text.Insert(sidx, ids);
8620                             sidx += ids.Length;
8621                             StatusText.SelectionStart = sidx;
8622                             StatusText.Focus();
8623                             return;
8624                         }
8625                     }
8626                 }
8627                 StatusText.SelectionStart = StatusText.Text.Length;
8628                 StatusText.Focus();
8629             }
8630         }
8631
8632         private void ListTab_MouseUp(object sender, MouseEventArgs e)
8633         {
8634             _tabDrag = false;
8635         }
8636
8637         private static int iconCnt = 0;
8638         private static int blinkCnt = 0;
8639         private static bool blink = false;
8640         private static bool idle = false;
8641
8642         private async Task RefreshTasktrayIcon(bool forceRefresh)
8643         {
8644             if (_colorize)
8645                 await this.Colorize();
8646
8647             if (!TimerRefreshIcon.Enabled) return;
8648             //Static usCheckCnt As int = 0
8649
8650             //Static iconDlListTopItem As ListViewItem = null
8651
8652             if (forceRefresh) idle = false;
8653
8654             //if (((ListView)ListTab.SelectedTab.Tag).TopItem == iconDlListTopItem)
8655             //    ((ImageDictionary)this.TIconDic).PauseGetImage = false;
8656             //else
8657             //    ((ImageDictionary)this.TIconDic).PauseGetImage = true;
8658             //
8659             //iconDlListTopItem = ((ListView)ListTab.SelectedTab.Tag).TopItem;
8660
8661             iconCnt += 1;
8662             blinkCnt += 1;
8663             //usCheckCnt += 1;
8664
8665             //if (usCheckCnt > 300)    //1min
8666             //{
8667             //    usCheckCnt = 0;
8668             //    if (!this.IsReceivedUserStream)
8669             //    {
8670             //        TraceOut("ReconnectUserStream");
8671             //        tw.ReconnectUserStream();
8672             //    }
8673             //}
8674
8675             var busy = this.workerSemaphore.CurrentCount != MAX_WORKER_THREADS;
8676
8677             if (iconCnt >= this.NIconRefresh.Length)
8678             {
8679                 iconCnt = 0;
8680             }
8681             if (blinkCnt > 10)
8682             {
8683                 blinkCnt = 0;
8684                 //未保存の変更を保存
8685                 SaveConfigsAll(true);
8686             }
8687
8688             if (busy)
8689             {
8690                 NotifyIcon1.Icon = NIconRefresh[iconCnt];
8691                 idle = false;
8692                 _myStatusError = false;
8693                 return;
8694             }
8695
8696             TabClass tb = _statuses.GetTabByType(MyCommon.TabUsageType.Mentions);
8697             if (this._cfgCommon.ReplyIconState != MyCommon.REPLY_ICONSTATE.None && tb != null && tb.UnreadCount > 0)
8698             {
8699                 if (blinkCnt > 0) return;
8700                 blink = !blink;
8701                 if (blink || this._cfgCommon.ReplyIconState == MyCommon.REPLY_ICONSTATE.StaticIcon)
8702                 {
8703                     NotifyIcon1.Icon = ReplyIcon;
8704                 }
8705                 else
8706                 {
8707                     NotifyIcon1.Icon = ReplyIconBlink;
8708                 }
8709                 idle = false;
8710                 return;
8711             }
8712
8713             if (idle) return;
8714             idle = true;
8715             //優先度:エラー→オフライン→アイドル
8716             //エラーは更新アイコンでクリアされる
8717             if (_myStatusError)
8718             {
8719                 NotifyIcon1.Icon = NIconAtRed;
8720                 return;
8721             }
8722             if (_myStatusOnline)
8723             {
8724                 NotifyIcon1.Icon = NIconAt;
8725             }
8726             else
8727             {
8728                 NotifyIcon1.Icon = NIconAtSmoke;
8729             }
8730         }
8731
8732         private async void TimerRefreshIcon_Tick(object sender, EventArgs e)
8733         {
8734             //200ms
8735             await this.RefreshTasktrayIcon(false);
8736         }
8737
8738         private void ContextMenuTabProperty_Opening(object sender, CancelEventArgs e)
8739         {
8740             //右クリックの場合はタブ名が設定済。アプリケーションキーの場合は現在のタブを対象とする
8741             if (string.IsNullOrEmpty(_rclickTabName) || sender != ContextMenuTabProperty)
8742             {
8743                 if (ListTab != null && ListTab.SelectedTab != null)
8744                     _rclickTabName = ListTab.SelectedTab.Text;
8745                 else
8746                     return;
8747             }
8748
8749             if (_statuses == null) return;
8750             if (_statuses.Tabs == null) return;
8751
8752             TabClass tb = _statuses.Tabs[_rclickTabName];
8753             if (tb == null) return;
8754
8755             NotifyDispMenuItem.Checked = tb.Notify;
8756             this.NotifyTbMenuItem.Checked = tb.Notify;
8757
8758             soundfileListup = true;
8759             SoundFileComboBox.Items.Clear();
8760             this.SoundFileTbComboBox.Items.Clear();
8761             SoundFileComboBox.Items.Add("");
8762             this.SoundFileTbComboBox.Items.Add("");
8763             DirectoryInfo oDir = new DirectoryInfo(Application.StartupPath + Path.DirectorySeparatorChar);
8764             if (Directory.Exists(Path.Combine(Application.StartupPath, "Sounds")))
8765             {
8766                 oDir = oDir.GetDirectories("Sounds")[0];
8767             }
8768             foreach (FileInfo oFile in oDir.GetFiles("*.wav"))
8769             {
8770                 SoundFileComboBox.Items.Add(oFile.Name);
8771                 this.SoundFileTbComboBox.Items.Add(oFile.Name);
8772             }
8773             int idx = SoundFileComboBox.Items.IndexOf(tb.SoundFile);
8774             if (idx == -1) idx = 0;
8775             SoundFileComboBox.SelectedIndex = idx;
8776             this.SoundFileTbComboBox.SelectedIndex = idx;
8777             soundfileListup = false;
8778             UreadManageMenuItem.Checked = tb.UnreadManage;
8779             this.UnreadMngTbMenuItem.Checked = tb.UnreadManage;
8780
8781             TabMenuControl(_rclickTabName);
8782         }
8783
8784         private void TabMenuControl(string tabName)
8785         {
8786             var tabInfo = _statuses.GetTabByName(tabName);
8787
8788             this.FilterEditMenuItem.Enabled = true;
8789             this.EditRuleTbMenuItem.Enabled = true;
8790
8791             if (tabInfo.IsDefaultTabType)
8792             {
8793                 this.ProtectTabMenuItem.Enabled = false;
8794                 this.ProtectTbMenuItem.Enabled = false;
8795             }
8796             else
8797             {
8798                 this.ProtectTabMenuItem.Enabled = true;
8799                 this.ProtectTbMenuItem.Enabled = true;
8800             }
8801
8802             if (tabInfo.IsDefaultTabType || tabInfo.Protected)
8803             {
8804                 this.ProtectTabMenuItem.Checked = true;
8805                 this.ProtectTbMenuItem.Checked = true;
8806                 this.DeleteTabMenuItem.Enabled = false;
8807                 this.DeleteTbMenuItem.Enabled = false;
8808             }
8809             else
8810             {
8811                 this.ProtectTabMenuItem.Checked = false;
8812                 this.ProtectTbMenuItem.Checked = false;
8813                 this.DeleteTabMenuItem.Enabled = true;
8814                 this.DeleteTbMenuItem.Enabled = true;
8815             }
8816         }
8817
8818         private void ProtectTabMenuItem_Click(object sender, EventArgs e)
8819         {
8820             var checkState = ((ToolStripMenuItem)sender).Checked;
8821
8822             // チェック状態を同期
8823             this.ProtectTbMenuItem.Checked = checkState;
8824             this.ProtectTabMenuItem.Checked = checkState;
8825
8826             // ロック中はタブの削除を無効化
8827             this.DeleteTabMenuItem.Enabled = !checkState;
8828             this.DeleteTbMenuItem.Enabled = !checkState;
8829
8830             if (string.IsNullOrEmpty(_rclickTabName)) return;
8831             _statuses.Tabs[_rclickTabName].Protected = checkState;
8832
8833             SaveConfigsTabs();
8834         }
8835
8836         private void UreadManageMenuItem_Click(object sender, EventArgs e)
8837         {
8838             UreadManageMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
8839             this.UnreadMngTbMenuItem.Checked = UreadManageMenuItem.Checked;
8840
8841             if (string.IsNullOrEmpty(_rclickTabName)) return;
8842             ChangeTabUnreadManage(_rclickTabName, UreadManageMenuItem.Checked);
8843
8844             SaveConfigsTabs();
8845         }
8846
8847         public void ChangeTabUnreadManage(string tabName, bool isManage)
8848         {
8849             var idx = this.GetTabPageIndex(tabName);
8850             if (idx == -1)
8851                 return;
8852
8853             _statuses.Tabs[tabName].UnreadManage = isManage;
8854             if (this._cfgCommon.TabIconDisp)
8855             {
8856                 if (_statuses.Tabs[tabName].UnreadCount > 0)
8857                     ListTab.TabPages[idx].ImageIndex = 0;
8858                 else
8859                     ListTab.TabPages[idx].ImageIndex = -1;
8860             }
8861
8862             if (_curTab.Text == tabName)
8863             {
8864                 this.PurgeListViewItemCache();
8865                 _curList.Refresh();
8866             }
8867
8868             SetMainWindowTitle();
8869             SetStatusLabelUrl();
8870             if (!this._cfgCommon.TabIconDisp) ListTab.Refresh();
8871         }
8872
8873         private void NotifyDispMenuItem_Click(object sender, EventArgs e)
8874         {
8875             NotifyDispMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
8876             this.NotifyTbMenuItem.Checked = NotifyDispMenuItem.Checked;
8877
8878             if (string.IsNullOrEmpty(_rclickTabName)) return;
8879
8880             _statuses.Tabs[_rclickTabName].Notify = NotifyDispMenuItem.Checked;
8881
8882             SaveConfigsTabs();
8883         }
8884
8885         private void SoundFileComboBox_SelectedIndexChanged(object sender, EventArgs e)
8886         {
8887             if (soundfileListup || string.IsNullOrEmpty(_rclickTabName)) return;
8888
8889             _statuses.Tabs[_rclickTabName].SoundFile = (string)((ToolStripComboBox)sender).SelectedItem;
8890
8891             SaveConfigsTabs();
8892         }
8893
8894         private void DeleteTabMenuItem_Click(object sender, EventArgs e)
8895         {
8896             if (string.IsNullOrEmpty(_rclickTabName) || sender == this.DeleteTbMenuItem) _rclickTabName = ListTab.SelectedTab.Text;
8897
8898             RemoveSpecifiedTab(_rclickTabName, true);
8899             SaveConfigsTabs();
8900         }
8901
8902         private void FilterEditMenuItem_Click(object sender, EventArgs e)
8903         {
8904             if (string.IsNullOrEmpty(_rclickTabName)) _rclickTabName = _statuses.GetTabByType(MyCommon.TabUsageType.Home).TabName;
8905
8906             using (var fltDialog = new FilterDialog())
8907             {
8908                 fltDialog.Owner = this;
8909                 fltDialog.SetCurrent(_rclickTabName);
8910                 fltDialog.ShowDialog(this);
8911             }
8912             this.TopMost = this._cfgCommon.AlwaysTop;
8913
8914             this.ApplyPostFilters();
8915             SaveConfigsTabs();
8916         }
8917
8918         private void AddTabMenuItem_Click(object sender, EventArgs e)
8919         {
8920             string tabName = null;
8921             MyCommon.TabUsageType tabUsage;
8922             using (InputTabName inputName = new InputTabName())
8923             {
8924                 inputName.TabName = _statuses.GetUniqueTabName();
8925                 inputName.IsShowUsage = true;
8926                 inputName.ShowDialog();
8927                 if (inputName.DialogResult == DialogResult.Cancel) return;
8928                 tabName = inputName.TabName;
8929                 tabUsage = inputName.Usage;
8930             }
8931             this.TopMost = this._cfgCommon.AlwaysTop;
8932             if (!string.IsNullOrEmpty(tabName))
8933             {
8934                 //List対応
8935                 ListElement list = null;
8936                 if (tabUsage == MyCommon.TabUsageType.Lists)
8937                 {
8938                     using (ListAvailable listAvail = new ListAvailable())
8939                     {
8940                         if (listAvail.ShowDialog(this) == DialogResult.Cancel) return;
8941                         if (listAvail.SelectedList == null) return;
8942                         list = listAvail.SelectedList;
8943                     }
8944                 }
8945                 if (!_statuses.AddTab(tabName, tabUsage, list) || !AddNewTab(tabName, false, tabUsage, list))
8946                 {
8947                     string tmp = string.Format(Properties.Resources.AddTabMenuItem_ClickText1, tabName);
8948                     MessageBox.Show(tmp, Properties.Resources.AddTabMenuItem_ClickText2, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
8949                 }
8950                 else
8951                 {
8952                     //成功
8953                     SaveConfigsTabs();
8954                     if (tabUsage == MyCommon.TabUsageType.PublicSearch)
8955                     {
8956                         ListTab.SelectedIndex = ListTab.TabPages.Count - 1;
8957                         ListTab.SelectedTab.Controls["panelSearch"].Controls["comboSearch"].Focus();
8958                     }
8959                     if (tabUsage == MyCommon.TabUsageType.Lists)
8960                     {
8961                         ListTab.SelectedIndex = ListTab.TabPages.Count - 1;
8962                         var tab = this._statuses.Tabs[this._curTab.Text];
8963                         this.GetListTimelineAsync(tab);
8964                     }
8965                 }
8966             }
8967         }
8968
8969         private void TabMenuItem_Click(object sender, EventArgs e)
8970         {
8971             using (var fltDialog = new FilterDialog())
8972             {
8973                 fltDialog.Owner = this;
8974
8975                 //選択発言を元にフィルタ追加
8976                 foreach (int idx in _curList.SelectedIndices)
8977                 {
8978                     string tabName;
8979                     //タブ選択(or追加)
8980                     if (!SelectTab(out tabName)) return;
8981
8982                     fltDialog.SetCurrent(tabName);
8983                     if (_statuses.Tabs[_curTab.Text][idx].RetweetedId == null)
8984                     {
8985                         fltDialog.AddNewFilter(_statuses.Tabs[_curTab.Text][idx].ScreenName, _statuses.Tabs[_curTab.Text][idx].TextFromApi);
8986                     }
8987                     else
8988                     {
8989                         fltDialog.AddNewFilter(_statuses.Tabs[_curTab.Text][idx].RetweetedBy, _statuses.Tabs[_curTab.Text][idx].TextFromApi);
8990                     }
8991                     fltDialog.ShowDialog(this);
8992                     this.TopMost = this._cfgCommon.AlwaysTop;
8993                 }
8994             }
8995
8996             this.ApplyPostFilters();
8997             SaveConfigsTabs();
8998             if (this.ListTab.SelectedTab != null &&
8999                 ((DetailsListView)this.ListTab.SelectedTab.Tag).SelectedIndices.Count > 0)
9000             {
9001                 _curPost = _statuses.Tabs[this.ListTab.SelectedTab.Text][((DetailsListView)this.ListTab.SelectedTab.Tag).SelectedIndices[0]];
9002             }
9003         }
9004
9005         protected override bool ProcessDialogKey(Keys keyData)
9006         {
9007             //TextBox1でEnterを押してもビープ音が鳴らないようにする
9008             if ((keyData & Keys.KeyCode) == Keys.Enter)
9009             {
9010                 if (StatusText.Focused)
9011                 {
9012                     bool _NewLine = false;
9013                     bool _Post = false;
9014
9015                     if (this._cfgCommon.PostCtrlEnter) //Ctrl+Enter投稿時
9016                     {
9017                         if (StatusText.Multiline)
9018                         {
9019                             if ((keyData & Keys.Shift) == Keys.Shift && (keyData & Keys.Control) != Keys.Control) _NewLine = true;
9020
9021                             if ((keyData & Keys.Control) == Keys.Control) _Post = true;
9022                         }
9023                         else
9024                         {
9025                             if (((keyData & Keys.Control) == Keys.Control)) _Post = true;
9026                         }
9027
9028                     }
9029                     else if (this._cfgCommon.PostShiftEnter) //SHift+Enter投稿時
9030                     {
9031                         if (StatusText.Multiline)
9032                         {
9033                             if ((keyData & Keys.Control) == Keys.Control && (keyData & Keys.Shift) != Keys.Shift) _NewLine = true;
9034
9035                             if ((keyData & Keys.Shift) == Keys.Shift) _Post = true;
9036                         }
9037                         else
9038                         {
9039                             if (((keyData & Keys.Shift) == Keys.Shift)) _Post = true;
9040                         }
9041
9042                     }
9043                     else //Enter投稿時
9044                     {
9045                         if (StatusText.Multiline)
9046                         {
9047                             if ((keyData & Keys.Shift) == Keys.Shift && (keyData & Keys.Control) != Keys.Control) _NewLine = true;
9048
9049                             if (((keyData & Keys.Control) != Keys.Control && (keyData & Keys.Shift) != Keys.Shift) ||
9050                                 ((keyData & Keys.Control) == Keys.Control && (keyData & Keys.Shift) == Keys.Shift)) _Post = true;
9051                         }
9052                         else
9053                         {
9054                             if (((keyData & Keys.Shift) == Keys.Shift) ||
9055                                 (((keyData & Keys.Control) != Keys.Control) &&
9056                                 ((keyData & Keys.Shift) != Keys.Shift))) _Post = true;
9057                         }
9058                     }
9059
9060                     if (_NewLine)
9061                     {
9062                         int pos1 = StatusText.SelectionStart;
9063                         if (StatusText.SelectionLength > 0)
9064                         {
9065                             StatusText.Text = StatusText.Text.Remove(pos1, StatusText.SelectionLength);  //選択状態文字列削除
9066                         }
9067                         StatusText.Text = StatusText.Text.Insert(pos1, Environment.NewLine);  //改行挿入
9068                         StatusText.SelectionStart = pos1 + Environment.NewLine.Length;    //カーソルを改行の次の文字へ移動
9069                         return true;
9070                     }
9071                     else if (_Post)
9072                     {
9073                         PostButton_Click(null, null);
9074                         return true;
9075                     }
9076                 }
9077                 else if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType == MyCommon.TabUsageType.PublicSearch &&
9078                          (ListTab.SelectedTab.Controls["panelSearch"].Controls["comboSearch"].Focused ||
9079                          ListTab.SelectedTab.Controls["panelSearch"].Controls["comboLang"].Focused))
9080                 {
9081                     this.SearchButton_Click(ListTab.SelectedTab.Controls["panelSearch"].Controls["comboSearch"], null);
9082                     return true;
9083                 }
9084             }
9085
9086             return base.ProcessDialogKey(keyData);
9087         }
9088
9089         private void ReplyAllStripMenuItem_Click(object sender, EventArgs e)
9090         {
9091             MakeReplyOrDirectStatus(false, true, true);
9092         }
9093
9094         private void IDRuleMenuItem_Click(object sender, EventArgs e)
9095         {
9096             string tabName;
9097
9098             //未選択なら処理終了
9099             if (_curList.SelectedIndices.Count == 0) return;
9100
9101             //タブ選択(or追加)
9102             if (!SelectTab(out tabName)) return;
9103
9104             var tab = this._statuses.Tabs[tabName];
9105
9106             bool mv;
9107             bool mk;
9108             if (tab.TabType != MyCommon.TabUsageType.Mute)
9109             {
9110                 this.MoveOrCopy(out mv, out mk);
9111             }
9112             else
9113             {
9114                 // ミュートタブでは常に MoveMatches を true にする
9115                 mv = true;
9116                 mk = false;
9117             }
9118
9119             List<string> ids = new List<string>();
9120             foreach (int idx in _curList.SelectedIndices)
9121             {
9122                 PostClass post = _statuses.Tabs[_curTab.Text][idx];
9123                 if (!ids.Contains(post.ScreenName))
9124                 {
9125                     PostFilterRule fc = new PostFilterRule();
9126                     ids.Add(post.ScreenName);
9127                     if (post.RetweetedId == null)
9128                     {
9129                         fc.FilterName = post.ScreenName;
9130                     }
9131                     else
9132                     {
9133                         fc.FilterName = post.RetweetedBy;
9134                     }
9135                     fc.UseNameField = true;
9136                     fc.MoveMatches = mv;
9137                     fc.MarkMatches = mk;
9138                     fc.UseRegex = false;
9139                     fc.FilterByUrl = false;
9140                     tab.AddFilter(fc);
9141                 }
9142             }
9143             if (ids.Count != 0)
9144             {
9145                 List<string> atids = new List<string>();
9146                 foreach (string id in ids)
9147                 {
9148                     atids.Add("@" + id);
9149                 }
9150                 int cnt = AtIdSupl.ItemCount;
9151                 AtIdSupl.AddRangeItem(atids.ToArray());
9152                 if (AtIdSupl.ItemCount != cnt) _modifySettingAtId = true;
9153             }
9154
9155             this.ApplyPostFilters();
9156             SaveConfigsTabs();
9157         }
9158
9159         private void SourceRuleMenuItem_Click(object sender, EventArgs e)
9160         {
9161             if (this._curList.SelectedIndices.Count == 0)
9162                 return;
9163
9164             // タブ選択ダイアログを表示(or追加)
9165             string tabName;
9166             if (!this.SelectTab(out tabName))
9167                 return;
9168
9169             var currentTab = this._statuses.Tabs[this._curTab.Text];
9170             var filterTab = this._statuses.Tabs[tabName];
9171
9172             bool mv;
9173             bool mk;
9174             if (filterTab.TabType != MyCommon.TabUsageType.Mute)
9175             {
9176                 // フィルタ動作選択ダイアログを表示(移動/コピー, マーク有無)
9177                 this.MoveOrCopy(out mv, out mk);
9178             }
9179             else
9180             {
9181                 // ミュートタブでは常に MoveMatches を true にする
9182                 mv = true;
9183                 mk = false;
9184             }
9185
9186             // 振り分けルールに追加するSource
9187             var sources = new HashSet<string>();
9188
9189             foreach (var idx in this._curList.SelectedIndices.Cast<int>())
9190             {
9191                 var post = currentTab[idx];
9192                 var filterSource = post.Source;
9193
9194                 if (sources.Add(filterSource))
9195                 {
9196                     var filter = new PostFilterRule
9197                     {
9198                         FilterSource = filterSource,
9199                         MoveMatches = mv,
9200                         MarkMatches = mk,
9201                         UseRegex = false,
9202                         FilterByUrl = false,
9203                     };
9204                     filterTab.AddFilter(filter);
9205                 }
9206             }
9207
9208             this.ApplyPostFilters();
9209             this.SaveConfigsTabs();
9210         }
9211
9212         private bool SelectTab(out string tabName)
9213         {
9214             do
9215             {
9216                 tabName = null;
9217
9218                 //振り分け先タブ選択
9219                 using (var dialog = new TabsDialog(_statuses))
9220                 {
9221                     if (dialog.ShowDialog(this) == DialogResult.Cancel) return false;
9222
9223                     var selectedTab = dialog.SelectedTab;
9224                     tabName = selectedTab == null ? null : selectedTab.TabName;
9225                 }
9226
9227                 ListTab.SelectedTab.Focus();
9228                 //新規タブを選択→タブ作成
9229                 if (tabName == null)
9230                 {
9231                     using (InputTabName inputName = new InputTabName())
9232                     {
9233                         inputName.TabName = _statuses.GetUniqueTabName();
9234                         inputName.ShowDialog();
9235                         if (inputName.DialogResult == DialogResult.Cancel) return false;
9236                         tabName = inputName.TabName;
9237                     }
9238                     this.TopMost = this._cfgCommon.AlwaysTop;
9239                     if (!string.IsNullOrEmpty(tabName))
9240                     {
9241                         if (!_statuses.AddTab(tabName, MyCommon.TabUsageType.UserDefined, null) || !AddNewTab(tabName, false, MyCommon.TabUsageType.UserDefined))
9242                         {
9243                             string tmp = string.Format(Properties.Resources.IDRuleMenuItem_ClickText2, tabName);
9244                             MessageBox.Show(tmp, Properties.Resources.IDRuleMenuItem_ClickText3, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
9245                             //もう一度タブ名入力
9246                         }
9247                         else
9248                         {
9249                             return true;
9250                         }
9251                     }
9252                 }
9253                 else
9254                 {
9255                     //既存タブを選択
9256                     return true;
9257                 }
9258             }
9259             while (true);
9260         }
9261
9262         private void MoveOrCopy(out bool move, out bool mark)
9263         {
9264             {
9265                 //移動するか?
9266                 string _tmp = string.Format(Properties.Resources.IDRuleMenuItem_ClickText4, Environment.NewLine);
9267                 if (MessageBox.Show(_tmp, Properties.Resources.IDRuleMenuItem_ClickText5, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
9268                     move = false;
9269                 else
9270                     move = true;
9271             }
9272             if (!move)
9273             {
9274                 //マークするか?
9275                 string _tmp = string.Format(Properties.Resources.IDRuleMenuItem_ClickText6, Environment.NewLine);
9276                 if (MessageBox.Show(_tmp, Properties.Resources.IDRuleMenuItem_ClickText7, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
9277                     mark = true;
9278                 else
9279                     mark = false;
9280             }
9281             else
9282             {
9283                 mark = false;
9284             }
9285         }
9286         private void CopySTOTMenuItem_Click(object sender, EventArgs e)
9287         {
9288             this.CopyStot();
9289         }
9290
9291         private void CopyURLMenuItem_Click(object sender, EventArgs e)
9292         {
9293             this.CopyIdUri();
9294         }
9295
9296         private void SelectAllMenuItem_Click(object sender, EventArgs e)
9297         {
9298             if (StatusText.Focused)
9299             {
9300                 // 発言欄でのCtrl+A
9301                 StatusText.SelectAll();
9302             }
9303             else
9304             {
9305                 // ListView上でのCtrl+A
9306                 NativeMethods.SelectAllItems(this._curList);
9307             }
9308         }
9309
9310         private void MoveMiddle()
9311         {
9312             ListViewItem _item;
9313             int idx1;
9314             int idx2;
9315
9316             if (_curList.SelectedIndices.Count == 0) return;
9317
9318             int idx = _curList.SelectedIndices[0];
9319
9320             _item = _curList.GetItemAt(0, 25);
9321             if (_item == null)
9322                 idx1 = 0;
9323             else
9324                 idx1 = _item.Index;
9325
9326             _item = _curList.GetItemAt(0, _curList.ClientSize.Height - 1);
9327             if (_item == null)
9328                 idx2 = _curList.VirtualListSize - 1;
9329             else
9330                 idx2 = _item.Index;
9331
9332             idx -= Math.Abs(idx1 - idx2) / 2;
9333             if (idx < 0) idx = 0;
9334
9335             _curList.EnsureVisible(_curList.VirtualListSize - 1);
9336             _curList.EnsureVisible(idx);
9337         }
9338
9339         private async void OpenURLMenuItem_Click(object sender, EventArgs e)
9340         {
9341             var linkElements = this.PostBrowser.Document.Links.Cast<HtmlElement>()
9342                 .Where(x => x.GetAttribute("className") != "tweet-quote-link") // 引用ツイートで追加されたリンクを除く
9343                 .ToArray();
9344
9345             if (linkElements.Length > 0)
9346             {
9347                 UrlDialog.ClearUrl();
9348
9349                 string openUrlStr = "";
9350
9351                 if (linkElements.Length == 1)
9352                 {
9353                     // ツイートに含まれる URL が 1 つのみの場合
9354                     //   => OpenURL ダイアログを表示せずにリンクを開く
9355
9356                     string urlStr = "";
9357                     try
9358                     {
9359                         urlStr = MyCommon.IDNEncode(linkElements[0].GetAttribute("href"));
9360                     }
9361                     catch (ArgumentException)
9362                     {
9363                         //変なHTML?
9364                         return;
9365                     }
9366                     catch (Exception)
9367                     {
9368                         return;
9369                     }
9370                     if (string.IsNullOrEmpty(urlStr)) return;
9371                     openUrlStr = MyCommon.urlEncodeMultibyteChar(urlStr);
9372
9373                     // Ctrl+E で呼ばれた場合を考慮し isReverseSettings の判定を行わない
9374                     await this.OpenUriAsync(new Uri(openUrlStr));
9375                 }
9376                 else
9377                 {
9378                     // ツイートに含まれる URL が複数ある場合
9379                     //   => OpenURL を表示しユーザーが選択したリンクを開く
9380
9381                     foreach (var linkElm in linkElements)
9382                     {
9383                         string urlStr = "";
9384                         string linkText = "";
9385                         string href = "";
9386                         try
9387                         {
9388                             urlStr = linkElm.GetAttribute("title");
9389                             href = MyCommon.IDNEncode(linkElm.GetAttribute("href"));
9390                             if (string.IsNullOrEmpty(urlStr)) urlStr = href;
9391                             linkText = linkElm.InnerText;
9392                         }
9393                         catch (ArgumentException)
9394                         {
9395                             //変なHTML?
9396                             return;
9397                         }
9398                         catch (Exception)
9399                         {
9400                             return;
9401                         }
9402                         if (string.IsNullOrEmpty(urlStr)) continue;
9403                         UrlDialog.AddUrl(new OpenUrlItem(linkText, MyCommon.urlEncodeMultibyteChar(urlStr), href));
9404                     }
9405                     try
9406                     {
9407                         if (UrlDialog.ShowDialog() == DialogResult.OK)
9408                         {
9409                             openUrlStr = UrlDialog.SelectedUrl;
9410
9411                             // Ctrlを押しながらリンクを開いた場合は、設定と逆の動作をするフラグを true としておく
9412                             await this.OpenUriAsync(new Uri(openUrlStr), MyCommon.IsKeyDown(Keys.Control));
9413                         }
9414                     }
9415                     catch (Exception)
9416                     {
9417                         return;
9418                     }
9419                     this.TopMost = this._cfgCommon.AlwaysTop;
9420                 }
9421             }
9422         }
9423
9424         private void ClearTabMenuItem_Click(object sender, EventArgs e)
9425         {
9426             if (string.IsNullOrEmpty(_rclickTabName)) return;
9427             ClearTab(_rclickTabName, true);
9428         }
9429
9430         private void ClearTab(string tabName, bool showWarning)
9431         {
9432             if (showWarning)
9433             {
9434                 string tmp = string.Format(Properties.Resources.ClearTabMenuItem_ClickText1, Environment.NewLine);
9435                 if (MessageBox.Show(tmp, tabName + " " + Properties.Resources.ClearTabMenuItem_ClickText2, MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
9436                 {
9437                     return;
9438                 }
9439             }
9440
9441             _statuses.ClearTabIds(tabName);
9442             if (ListTab.SelectedTab.Text == tabName)
9443             {
9444                 _anchorPost = null;
9445                 _anchorFlag = false;
9446                 this.PurgeListViewItemCache();
9447                 _curItemIndex = -1;
9448                 _curPost = null;
9449             }
9450             foreach (TabPage tb in ListTab.TabPages)
9451             {
9452                 if (tb.Text == tabName)
9453                 {
9454                     tb.ImageIndex = -1;
9455                     ((DetailsListView)tb.Tag).VirtualListSize = 0;
9456                     break;
9457                 }
9458             }
9459             if (!this._cfgCommon.TabIconDisp) ListTab.Refresh();
9460
9461             SetMainWindowTitle();
9462             SetStatusLabelUrl();
9463         }
9464
9465         private static long followers = 0;
9466
9467         private void SetMainWindowTitle()
9468         {
9469             //メインウインドウタイトルの書き換え
9470             StringBuilder ttl = new StringBuilder(256);
9471             int ur = 0;
9472             int al = 0;
9473             if (this._cfgCommon.DispLatestPost != MyCommon.DispTitleEnum.None &&
9474                 this._cfgCommon.DispLatestPost != MyCommon.DispTitleEnum.Post &&
9475                 this._cfgCommon.DispLatestPost != MyCommon.DispTitleEnum.Ver &&
9476                 this._cfgCommon.DispLatestPost != MyCommon.DispTitleEnum.OwnStatus)
9477             {
9478                 foreach (var tab in _statuses.Tabs.Values)
9479                 {
9480                     ur += tab.UnreadCount;
9481                     al += tab.AllCount;
9482                 }
9483             }
9484
9485             if (this._cfgCommon.DispUsername) ttl.Append(tw.Username).Append(" - ");
9486             ttl.Append(Application.ProductName);
9487             ttl.Append("  ");
9488             switch (this._cfgCommon.DispLatestPost)
9489             {
9490                 case MyCommon.DispTitleEnum.Ver:
9491                     ttl.Append("Ver:").Append(MyCommon.GetReadableVersion());
9492                     break;
9493                 case MyCommon.DispTitleEnum.Post:
9494                     if (_history != null && _history.Count > 1)
9495                         ttl.Append(_history[_history.Count - 2].status.Replace("\r\n", " "));
9496                     break;
9497                 case MyCommon.DispTitleEnum.UnreadRepCount:
9498                     ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText1, _statuses.GetTabByType(MyCommon.TabUsageType.Mentions).UnreadCount + _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage).UnreadCount);
9499                     break;
9500                 case MyCommon.DispTitleEnum.UnreadAllCount:
9501                     ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText2, ur);
9502                     break;
9503                 case MyCommon.DispTitleEnum.UnreadAllRepCount:
9504                     ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText3, ur, _statuses.GetTabByType(MyCommon.TabUsageType.Mentions).UnreadCount + _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage).UnreadCount);
9505                     break;
9506                 case MyCommon.DispTitleEnum.UnreadCountAllCount:
9507                     ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText4, ur, al);
9508                     break;
9509                 case MyCommon.DispTitleEnum.OwnStatus:
9510                     if (followers == 0 && tw.FollowersCount > 0) followers = tw.FollowersCount;
9511                     ttl.AppendFormat(Properties.Resources.OwnStatusTitle, tw.StatusesCount, tw.FriendsCount, tw.FollowersCount, tw.FollowersCount - followers);
9512                     break;
9513             }
9514
9515             try
9516             {
9517                 this.Text = ttl.ToString();
9518             }
9519             catch (AccessViolationException)
9520             {
9521                 //原因不明。ポスト内容に依存か?たまーに発生するが再現せず。
9522             }
9523         }
9524
9525         private string GetStatusLabelText()
9526         {
9527             //ステータス欄にカウント表示
9528             //タブ未読数/タブ発言数 全未読数/総発言数 (未読@+未読DM数)
9529             if (_statuses == null) return "";
9530             TabClass tbRep = _statuses.GetTabByType(MyCommon.TabUsageType.Mentions);
9531             TabClass tbDm = _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage);
9532             if (tbRep == null || tbDm == null) return "";
9533             int urat = tbRep.UnreadCount + tbDm.UnreadCount;
9534             int ur = 0;
9535             int al = 0;
9536             int tur = 0;
9537             int tal = 0;
9538             StringBuilder slbl = new StringBuilder(256);
9539             try
9540             {
9541                 foreach (var tab in _statuses.Tabs.Values)
9542                 {
9543                     ur += tab.UnreadCount;
9544                     al += tab.AllCount;
9545                     if (_curTab != null && tab.TabName.Equals(_curTab.Text))
9546                     {
9547                         tur = tab.UnreadCount;
9548                         tal = tab.AllCount;
9549                     }
9550                 }
9551             }
9552             catch (Exception)
9553             {
9554                 return "";
9555             }
9556
9557             UnreadCounter = ur;
9558             UnreadAtCounter = urat;
9559
9560             slbl.AppendFormat(Properties.Resources.SetStatusLabelText1, tur, tal, ur, al, urat, _postTimestamps.Count, _favTimestamps.Count, _tlCount);
9561             if (this._cfgCommon.TimelinePeriod == 0)
9562             {
9563                 slbl.Append(Properties.Resources.SetStatusLabelText2);
9564             }
9565             else
9566             {
9567                 slbl.Append(this._cfgCommon.TimelinePeriod + Properties.Resources.SetStatusLabelText3);
9568             }
9569             return slbl.ToString();
9570         }
9571
9572         private void TwitterApiStatus_AccessLimitUpdated(object sender, EventArgs e)
9573         {
9574             try
9575             {
9576                 if (this.InvokeRequired && !this.IsDisposed)
9577                 {
9578                     this.Invoke((MethodInvoker)(() => this.TwitterApiStatus_AccessLimitUpdated(sender, e)));
9579                 }
9580                 else
9581                 {
9582                     var endpointName = (e as TwitterApiStatus.AccessLimitUpdatedEventArgs).EndpointName;
9583                     SetApiStatusLabel(endpointName);
9584                 }
9585             }
9586             catch (ObjectDisposedException)
9587             {
9588                 return;
9589             }
9590             catch (InvalidOperationException)
9591             {
9592                 return;
9593             }
9594         }
9595
9596         private void SetApiStatusLabel(string endpointName = null)
9597         {
9598             if (_curTab == null)
9599             {
9600                 this.toolStripApiGauge.ApiEndpoint = null;
9601             }
9602             else
9603             {
9604                 var tabType = _statuses.Tabs[_curTab.Text].TabType;
9605
9606                 if (endpointName == null)
9607                 {
9608                     // 表示中のタブに応じて更新
9609                     switch (tabType)
9610                     {
9611                         case MyCommon.TabUsageType.Home:
9612                         case MyCommon.TabUsageType.UserDefined:
9613                             endpointName = "/statuses/home_timeline";
9614                             break;
9615
9616                         case MyCommon.TabUsageType.Mentions:
9617                             endpointName = "/statuses/mentions_timeline";
9618                             break;
9619
9620                         case MyCommon.TabUsageType.Favorites:
9621                             endpointName = "/favorites/list";
9622                             break;
9623
9624                         case MyCommon.TabUsageType.DirectMessage:
9625                             endpointName = "/direct_messages";
9626                             break;
9627
9628                         case MyCommon.TabUsageType.UserTimeline:
9629                             endpointName = "/statuses/user_timeline";
9630                             break;
9631
9632                         case MyCommon.TabUsageType.Lists:
9633                             endpointName = "/lists/statuses";
9634                             break;
9635
9636                         case MyCommon.TabUsageType.PublicSearch:
9637                             endpointName = "/search/tweets";
9638                             break;
9639
9640                         case MyCommon.TabUsageType.Related:
9641                             endpointName = "/statuses/show/:id";
9642                             break;
9643
9644                         default:
9645                             break;
9646                     }
9647
9648                     this.toolStripApiGauge.ApiEndpoint = endpointName;
9649                 }
9650                 else
9651                 {
9652                     // 表示中のタブに関連する endpoint であれば更新
9653                     var update = false;
9654
9655                     switch (endpointName)
9656                     {
9657                         case "/statuses/home_timeline":
9658                             update = tabType == MyCommon.TabUsageType.Home ||
9659                                      tabType == MyCommon.TabUsageType.UserDefined;
9660                             break;
9661
9662                         case "/statuses/mentions_timeline":
9663                             update = tabType == MyCommon.TabUsageType.Mentions;
9664                             break;
9665
9666                         case "/favorites/list":
9667                             update = tabType == MyCommon.TabUsageType.Favorites;
9668                             break;
9669
9670                         case "/direct_messages:":
9671                             update = tabType == MyCommon.TabUsageType.DirectMessage;
9672                             break;
9673
9674                         case "/statuses/user_timeline":
9675                             update = tabType == MyCommon.TabUsageType.UserTimeline;
9676                             break;
9677
9678                         case "/lists/statuses":
9679                             update = tabType == MyCommon.TabUsageType.Lists;
9680                             break;
9681
9682                         case "/search/tweets":
9683                             update = tabType == MyCommon.TabUsageType.PublicSearch;
9684                             break;
9685
9686                         case "/statuses/show/:id":
9687                             update = tabType == MyCommon.TabUsageType.Related;
9688                             break;
9689
9690                         default:
9691                             break;
9692                     }
9693
9694                     if (update)
9695                     {
9696                         this.toolStripApiGauge.ApiEndpoint = endpointName;
9697                     }
9698                 }
9699             }
9700         }
9701
9702         private void SetStatusLabelUrl()
9703         {
9704             StatusLabelUrl.Text = GetStatusLabelText();
9705         }
9706
9707         public void SetStatusLabel(string text)
9708         {
9709             StatusLabel.Text = text;
9710         }
9711
9712         private static StringBuilder ur = new StringBuilder(64);
9713
9714         private void SetNotifyIconText()
9715         {
9716             // タスクトレイアイコンのツールチップテキスト書き換え
9717             // Tween [未読/@]
9718             ur.Remove(0, ur.Length);
9719             if (this._cfgCommon.DispUsername)
9720             {
9721                 ur.Append(tw.Username);
9722                 ur.Append(" - ");
9723             }
9724             ur.Append(Application.ProductName);
9725 #if DEBUG
9726             ur.Append("(Debug Build)");
9727 #endif
9728             if (UnreadCounter != -1 && UnreadAtCounter != -1)
9729             {
9730                 ur.Append(" [");
9731                 ur.Append(UnreadCounter);
9732                 ur.Append("/@");
9733                 ur.Append(UnreadAtCounter);
9734                 ur.Append("]");
9735             }
9736             NotifyIcon1.Text = ur.ToString();
9737         }
9738
9739         internal void CheckReplyTo(string StatusText)
9740         {
9741             MatchCollection m;
9742             //ハッシュタグの保存
9743             m = Regex.Matches(StatusText, Twitter.HASHTAG, RegexOptions.IgnoreCase);
9744             string hstr = "";
9745             foreach (Match hm in m)
9746             {
9747                 if (!hstr.Contains("#" + hm.Result("$3") + " "))
9748                 {
9749                     hstr += "#" + hm.Result("$3") + " ";
9750                     HashSupl.AddItem("#" + hm.Result("$3"));
9751                 }
9752             }
9753             if (!string.IsNullOrEmpty(HashMgr.UseHash) && !hstr.Contains(HashMgr.UseHash + " "))
9754             {
9755                 hstr += HashMgr.UseHash;
9756             }
9757             if (!string.IsNullOrEmpty(hstr)) HashMgr.AddHashToHistory(hstr.Trim(), false);
9758
9759             // 本当にリプライ先指定すべきかどうかの判定
9760             m = Regex.Matches(StatusText, "(^|[ -/:-@[-^`{-~])(?<id>@[a-zA-Z0-9_]+)");
9761
9762             if (this._cfgCommon.UseAtIdSupplement)
9763             {
9764                 int bCnt = AtIdSupl.ItemCount;
9765                 foreach (Match mid in m)
9766                 {
9767                     AtIdSupl.AddItem(mid.Result("${id}"));
9768                 }
9769                 if (bCnt != AtIdSupl.ItemCount) _modifySettingAtId = true;
9770             }
9771
9772             // リプライ先ステータスIDの指定がない場合は指定しない
9773             if (this.inReplyTo == null)
9774                 return;
9775
9776             // 通常Reply
9777             // 次の条件を満たす場合に in_reply_to_status_id 指定
9778             // 1. Twitterによりリンクと判定される @idが文中に1つ含まれる (2009/5/28 リンク化される@IDのみカウントするように修正)
9779             // 2. リプライ先ステータスIDが設定されている(リストをダブルクリックで返信している)
9780             // 3. 文中に含まれた@idがリプライ先のポスト者のIDと一致する
9781
9782             if (m != null)
9783             {
9784                 var inReplyToScreenName = this.inReplyTo.Item2;
9785                 if (StatusText.StartsWith("@"))
9786                 {
9787                     if (StatusText.StartsWith("@" + inReplyToScreenName)) return;
9788                 }
9789                 else
9790                 {
9791                     foreach (Match mid in m)
9792                     {
9793                         if (StatusText.Contains("RT " + mid.Result("${id}") + ":") && mid.Result("${id}") == "@" + inReplyToScreenName) return;
9794                     }
9795                 }
9796             }
9797
9798             this.inReplyTo = null;
9799         }
9800
9801         private void TweenMain_Resize(object sender, EventArgs e)
9802         {
9803             if (!_initialLayout && this._cfgCommon.MinimizeToTray && WindowState == FormWindowState.Minimized)
9804             {
9805                 this.Visible = false;
9806             }
9807             if (_initialLayout && _cfgLocal != null && this.WindowState == FormWindowState.Normal && this.Visible)
9808             {
9809                 // 現在の DPI と設定保存時の DPI との比を取得する
9810                 var configScaleFactor = this._cfgLocal.GetConfigScaleFactor(this.CurrentAutoScaleDimensions);
9811
9812                 this.ClientSize = ScaleBy(configScaleFactor, _cfgLocal.FormSize);
9813                 //_mySize = this.ClientSize;                     //サイズ保持(最小化・最大化されたまま終了した場合の対応用)
9814                 this.DesktopLocation = _cfgLocal.FormLocation;
9815                 //_myLoc = this.DesktopLocation;                        //位置保持(最小化・最大化されたまま終了した場合の対応用)
9816
9817                 // Splitterの位置設定
9818                 var splitterDistance = ScaleBy(configScaleFactor.Height, _cfgLocal.SplitterDistance);
9819                 if (splitterDistance > this.SplitContainer1.Panel1MinSize &&
9820                     splitterDistance < this.SplitContainer1.Height - this.SplitContainer1.Panel2MinSize - this.SplitContainer1.SplitterWidth)
9821                 {
9822                     this.SplitContainer1.SplitterDistance = splitterDistance;
9823                 }
9824
9825                 //発言欄複数行
9826                 StatusText.Multiline = _cfgLocal.StatusMultiline;
9827                 if (StatusText.Multiline)
9828                 {
9829                     var statusTextHeight = ScaleBy(configScaleFactor.Height, _cfgLocal.StatusTextHeight);
9830                     int dis = SplitContainer2.Height - statusTextHeight - SplitContainer2.SplitterWidth;
9831                     if (dis > SplitContainer2.Panel1MinSize && dis < SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth)
9832                     {
9833                         SplitContainer2.SplitterDistance = SplitContainer2.Height - statusTextHeight - SplitContainer2.SplitterWidth;
9834                     }
9835                     StatusText.Height = statusTextHeight;
9836                 }
9837                 else
9838                 {
9839                     if (SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth > 0)
9840                     {
9841                         SplitContainer2.SplitterDistance = SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth;
9842                     }
9843                 }
9844
9845                 var previewDistance = ScaleBy(configScaleFactor.Width, _cfgLocal.PreviewDistance);
9846                 if (previewDistance > this.SplitContainer3.Panel1MinSize && previewDistance < this.SplitContainer3.Width - this.SplitContainer3.Panel2MinSize - this.SplitContainer3.SplitterWidth)
9847                 {
9848                     this.SplitContainer3.SplitterDistance = previewDistance;
9849                 }
9850                 _initialLayout = false;
9851             }
9852             if (this.WindowState != FormWindowState.Minimized)
9853             {
9854                 _formWindowState = this.WindowState;
9855             }
9856         }
9857
9858         private void PlaySoundMenuItem_CheckedChanged(object sender, EventArgs e)
9859         {
9860             PlaySoundMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
9861             this.PlaySoundFileMenuItem.Checked = PlaySoundMenuItem.Checked;
9862             if (PlaySoundMenuItem.Checked)
9863             {
9864                 this._cfgCommon.PlaySound = true;
9865             }
9866             else
9867             {
9868                 this._cfgCommon.PlaySound = false;
9869             }
9870             _modifySettingCommon = true;
9871         }
9872
9873         private void SplitContainer1_SplitterMoved(object sender, SplitterEventArgs e)
9874         {
9875             if (this.WindowState == FormWindowState.Normal && !_initialLayout)
9876             {
9877                 _mySpDis = SplitContainer1.SplitterDistance;
9878                 if (StatusText.Multiline) _mySpDis2 = StatusText.Height;
9879                 _modifySettingLocal = true;
9880             }
9881         }
9882
9883         private async Task doRepliedStatusOpen()
9884         {
9885             if (this.ExistCurrentPost && _curPost.InReplyToUser != null && _curPost.InReplyToStatusId != null)
9886             {
9887                 if (MyCommon.IsKeyDown(Keys.Shift))
9888                 {
9889                     await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(_curPost.InReplyToUser, _curPost.InReplyToStatusId.Value));
9890                     return;
9891                 }
9892                 if (_statuses.ContainsKey(_curPost.InReplyToStatusId.Value))
9893                 {
9894                     PostClass repPost = _statuses[_curPost.InReplyToStatusId.Value];
9895                     MessageBox.Show(repPost.ScreenName + " / " + repPost.Nickname + "   (" + repPost.CreatedAt.ToString() + ")" + Environment.NewLine + repPost.TextFromApi);
9896                 }
9897                 else
9898                 {
9899                     foreach (TabClass tb in _statuses.GetTabsByType(MyCommon.TabUsageType.Lists | MyCommon.TabUsageType.PublicSearch))
9900                     {
9901                         if (tb == null || !tb.Contains(_curPost.InReplyToStatusId.Value)) break;
9902                         PostClass repPost = _statuses[_curPost.InReplyToStatusId.Value];
9903                         MessageBox.Show(repPost.ScreenName + " / " + repPost.Nickname + "   (" + repPost.CreatedAt.ToString() + ")" + Environment.NewLine + repPost.TextFromApi);
9904                         return;
9905                     }
9906                     await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(_curPost.InReplyToUser, _curPost.InReplyToStatusId.Value));
9907                 }
9908             }
9909         }
9910
9911         private async void RepliedStatusOpenMenuItem_Click(object sender, EventArgs e)
9912         {
9913             await this.doRepliedStatusOpen();
9914         }
9915
9916         /// <summary>
9917         /// UserPicture.Image に設定されている画像を破棄します。
9918         /// </summary>
9919         private void ClearUserPicture()
9920         {
9921             if (this.UserPicture.Image != null)
9922             {
9923                 var oldImage = this.UserPicture.Image;
9924                 this.UserPicture.Image = null;
9925                 oldImage.Dispose();
9926             }
9927         }
9928
9929         private void ContextMenuUserPicture_Opening(object sender, CancelEventArgs e)
9930         {
9931             //発言詳細のアイコン右クリック時のメニュー制御
9932             if (_curList.SelectedIndices.Count > 0 && _curPost != null)
9933             {
9934                 string name = _curPost.ImageUrl;
9935                 if (name != null && name.Length > 0)
9936                 {
9937                     int idx = name.LastIndexOf('/');
9938                     if (idx != -1)
9939                     {
9940                         name = Path.GetFileName(name.Substring(idx));
9941                         if (name.Contains("_normal.") || name.EndsWith("_normal"))
9942                         {
9943                             name = name.Replace("_normal", "");
9944                             this.IconNameToolStripMenuItem.Text = name;
9945                             this.IconNameToolStripMenuItem.Enabled = true;
9946                         }
9947                         else
9948                         {
9949                             this.IconNameToolStripMenuItem.Enabled = false;
9950                             this.IconNameToolStripMenuItem.Text = Properties.Resources.ContextMenuStrip3_OpeningText1;
9951                         }
9952                     }
9953                     else
9954                     {
9955                         this.IconNameToolStripMenuItem.Enabled = false;
9956                         this.IconNameToolStripMenuItem.Text = Properties.Resources.ContextMenuStrip3_OpeningText1;
9957                     }
9958
9959                     this.ReloadIconToolStripMenuItem.Enabled = true;
9960
9961                     if (this.IconCache.TryGetFromCache(_curPost.ImageUrl) != null)
9962                     {
9963                         this.SaveIconPictureToolStripMenuItem.Enabled = true;
9964                     }
9965                     else
9966                     {
9967                         this.SaveIconPictureToolStripMenuItem.Enabled = false;
9968                     }
9969                 }
9970                 else
9971                 {
9972                     this.IconNameToolStripMenuItem.Enabled = false;
9973                     this.ReloadIconToolStripMenuItem.Enabled = false;
9974                     this.SaveIconPictureToolStripMenuItem.Enabled = false;
9975                     this.IconNameToolStripMenuItem.Text = Properties.Resources.ContextMenuStrip3_OpeningText1;
9976                 }
9977             }
9978             else
9979             {
9980                 this.IconNameToolStripMenuItem.Enabled = false;
9981                 this.ReloadIconToolStripMenuItem.Enabled = false;
9982                 this.SaveIconPictureToolStripMenuItem.Enabled = false;
9983                 this.IconNameToolStripMenuItem.Text = Properties.Resources.ContextMenuStrip3_OpeningText2;
9984             }
9985             if (NameLabel.Tag != null)
9986             {
9987                 string id = (string)NameLabel.Tag;
9988                 if (id == tw.Username)
9989                 {
9990                     FollowToolStripMenuItem.Enabled = false;
9991                     UnFollowToolStripMenuItem.Enabled = false;
9992                     ShowFriendShipToolStripMenuItem.Enabled = false;
9993                     ShowUserStatusToolStripMenuItem.Enabled = true;
9994                     SearchPostsDetailNameToolStripMenuItem.Enabled = true;
9995                     SearchAtPostsDetailNameToolStripMenuItem.Enabled = false;
9996                     ListManageUserContextToolStripMenuItem3.Enabled = true;
9997                 }
9998                 else
9999                 {
10000                     FollowToolStripMenuItem.Enabled = true;
10001                     UnFollowToolStripMenuItem.Enabled = true;
10002                     ShowFriendShipToolStripMenuItem.Enabled = true;
10003                     ShowUserStatusToolStripMenuItem.Enabled = true;
10004                     SearchPostsDetailNameToolStripMenuItem.Enabled = true;
10005                     SearchAtPostsDetailNameToolStripMenuItem.Enabled = true;
10006                     ListManageUserContextToolStripMenuItem3.Enabled = true;
10007                 }
10008             }
10009             else
10010             {
10011                 FollowToolStripMenuItem.Enabled = false;
10012                 UnFollowToolStripMenuItem.Enabled = false;
10013                 ShowFriendShipToolStripMenuItem.Enabled = false;
10014                 ShowUserStatusToolStripMenuItem.Enabled = false;
10015                 SearchPostsDetailNameToolStripMenuItem.Enabled = false;
10016                 SearchAtPostsDetailNameToolStripMenuItem.Enabled = false;
10017                 ListManageUserContextToolStripMenuItem3.Enabled = false;
10018             }
10019         }
10020
10021         private async void IconNameToolStripMenuItem_Click(object sender, EventArgs e)
10022         {
10023             if (_curPost == null) return;
10024             string name = _curPost.ImageUrl;
10025             await this.OpenUriInBrowserAsync(name.Remove(name.LastIndexOf("_normal"), 7)); // "_normal".Length
10026         }
10027
10028         private async void ReloadIconToolStripMenuItem_Click(object sender, EventArgs e)
10029         {
10030             if (this._curPost == null) return;
10031
10032             await this.UserPicture.SetImageFromTask(async () =>
10033             {
10034                 var imageUrl = this._curPost.ImageUrl;
10035
10036                 var image = await this.IconCache.DownloadImageAsync(imageUrl, force: true)
10037                     .ConfigureAwait(false);
10038
10039                 return await image.CloneAsync()
10040                     .ConfigureAwait(false);
10041             });
10042         }
10043
10044         private void SaveOriginalSizeIconPictureToolStripMenuItem_Click(object sender, EventArgs e)
10045         {
10046             if (_curPost == null) return;
10047             string name = _curPost.ImageUrl;
10048             name = Path.GetFileNameWithoutExtension(name.Substring(name.LastIndexOf('/')));
10049
10050             this.SaveFileDialog1.FileName = name.Substring(0, name.Length - 8); // "_normal".Length + 1
10051
10052             if (this.SaveFileDialog1.ShowDialog() == DialogResult.OK)
10053             {
10054                 // STUB
10055             }
10056         }
10057
10058         private void SaveIconPictureToolStripMenuItem_Click(object sender, EventArgs e)
10059         {
10060             if (_curPost == null) return;
10061             string name = _curPost.ImageUrl;
10062
10063             this.SaveFileDialog1.FileName = name.Substring(name.LastIndexOf('/') + 1);
10064
10065             if (this.SaveFileDialog1.ShowDialog() == DialogResult.OK)
10066             {
10067                 try
10068                 {
10069                     using (Image orgBmp = new Bitmap(IconCache.TryGetFromCache(name).Image))
10070                     {
10071                         using (Bitmap bmp2 = new Bitmap(orgBmp.Size.Width, orgBmp.Size.Height))
10072                         {
10073                             using (Graphics g = Graphics.FromImage(bmp2))
10074                             {
10075                                 g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
10076                                 g.DrawImage(orgBmp, 0, 0, orgBmp.Size.Width, orgBmp.Size.Height);
10077                             }
10078                             bmp2.Save(this.SaveFileDialog1.FileName);
10079                         }
10080                     }
10081                 }
10082                 catch (Exception)
10083                 {
10084                     //処理中にキャッシュアウトする可能性あり
10085                 }
10086             }
10087         }
10088
10089         private void SplitContainer2_Panel2_Resize(object sender, EventArgs e)
10090         {
10091             this.StatusText.Multiline = this.SplitContainer2.Panel2.Height > this.SplitContainer2.Panel2MinSize + 2;
10092             MultiLineMenuItem.Checked = this.StatusText.Multiline;
10093             _modifySettingLocal = true;
10094         }
10095
10096         private void StatusText_MultilineChanged(object sender, EventArgs e)
10097         {
10098             if (this.StatusText.Multiline)
10099                 this.StatusText.ScrollBars = ScrollBars.Vertical;
10100             else
10101                 this.StatusText.ScrollBars = ScrollBars.None;
10102
10103             _modifySettingLocal = true;
10104         }
10105
10106         private void MultiLineMenuItem_Click(object sender, EventArgs e)
10107         {
10108             //発言欄複数行
10109             StatusText.Multiline = MultiLineMenuItem.Checked;
10110             _cfgLocal.StatusMultiline = MultiLineMenuItem.Checked;
10111             if (MultiLineMenuItem.Checked)
10112             {
10113                 if (SplitContainer2.Height - _mySpDis2 - SplitContainer2.SplitterWidth < 0)
10114                     SplitContainer2.SplitterDistance = 0;
10115                 else
10116                     SplitContainer2.SplitterDistance = SplitContainer2.Height - _mySpDis2 - SplitContainer2.SplitterWidth;
10117             }
10118             else
10119             {
10120                 SplitContainer2.SplitterDistance = SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth;
10121             }
10122             _modifySettingLocal = true;
10123         }
10124
10125         private async Task<bool> UrlConvertAsync(MyCommon.UrlConverter Converter_Type)
10126         {
10127             //t.coで投稿時自動短縮する場合は、外部サービスでの短縮禁止
10128             //if (SettingDialog.UrlConvertAuto && SettingDialog.ShortenTco) return;
10129
10130             //Converter_Type=Nicomsの場合は、nicovideoのみ短縮する
10131             //参考資料 RFC3986 Uniform Resource Identifier (URI): Generic Syntax
10132             //Appendix A.  Collected ABNF for URI
10133             //http://www.ietf.org/rfc/rfc3986.txt
10134
10135             string result = "";
10136
10137             const string nico = @"^https?://[a-z]+\.(nicovideo|niconicommons|nicolive)\.jp/[a-z]+/[a-z0-9]+$";
10138
10139             if (StatusText.SelectionLength > 0)
10140             {
10141                 string tmp = StatusText.SelectedText;
10142                 // httpから始まらない場合、ExcludeStringで指定された文字列で始まる場合は対象としない
10143                 if (tmp.StartsWith("http"))
10144                 {
10145                     // 文字列が選択されている場合はその文字列について処理
10146
10147                     //nico.ms使用、nicovideoにマッチしたら変換
10148                     if (this._cfgCommon.Nicoms && Regex.IsMatch(tmp, nico))
10149                     {
10150                         result = nicoms.Shorten(tmp);
10151                     }
10152                     else if (Converter_Type != MyCommon.UrlConverter.Nicoms)
10153                     {
10154                         //短縮URL変換 日本語を含むかもしれないのでURLエンコードする
10155                         try
10156                         {
10157                             var srcUri = new Uri(MyCommon.urlEncodeMultibyteChar(tmp));
10158                             var resultUri = await ShortUrl.Instance.ShortenUrlAsync(Converter_Type, srcUri);
10159                             result = resultUri.AbsoluteUri;
10160                         }
10161                         catch (WebApiException e)
10162                         {
10163                             this.StatusLabel.Text = Converter_Type + ":" + e.Message;
10164                             return false;
10165                         }
10166                         catch (UriFormatException e)
10167                         {
10168                             this.StatusLabel.Text = Converter_Type + ":" + e.Message;
10169                             return false;
10170                         }
10171                     }
10172                     else
10173                     {
10174                         return true;
10175                     }
10176
10177                     if (!string.IsNullOrEmpty(result))
10178                     {
10179                         urlUndo undotmp = new urlUndo();
10180
10181                         StatusText.Select(StatusText.Text.IndexOf(tmp, StringComparison.Ordinal), tmp.Length);
10182                         StatusText.SelectedText = result;
10183
10184                         //undoバッファにセット
10185                         undotmp.Before = tmp;
10186                         undotmp.After = result;
10187
10188                         if (urlUndoBuffer == null)
10189                         {
10190                             urlUndoBuffer = new List<urlUndo>();
10191                             UrlUndoToolStripMenuItem.Enabled = true;
10192                         }
10193
10194                         urlUndoBuffer.Add(undotmp);
10195                     }
10196                 }
10197             }
10198             else
10199             {
10200                 const string url = @"(?<before>(?:[^\""':!=]|^|\:))" +
10201                                    @"(?<url>(?<protocol>https?://)" +
10202                                    @"(?<domain>(?:[\.-]|[^\p{P}\s])+\.[a-z]{2,}(?::[0-9]+)?)" +
10203                                    @"(?<path>/[a-z0-9!*//();:&=+$/%#\-_.,~@]*[a-z0-9)=#/]?)?" +
10204                                    @"(?<query>\?[a-z0-9!*//();:&=+$/%#\-_.,~@?]*[a-z0-9_&=#/])?)";
10205                 // 正規表現にマッチしたURL文字列をtinyurl化
10206                 foreach (Match mt in Regex.Matches(StatusText.Text, url, RegexOptions.IgnoreCase))
10207                 {
10208                     if (StatusText.Text.IndexOf(mt.Result("${url}"), StringComparison.Ordinal) == -1) continue;
10209                     string tmp = mt.Result("${url}");
10210                     if (tmp.StartsWith("w", StringComparison.OrdinalIgnoreCase)) tmp = "http://" + tmp;
10211                     urlUndo undotmp = new urlUndo();
10212
10213                     //選んだURLを選択(?)
10214                     StatusText.Select(StatusText.Text.IndexOf(mt.Result("${url}"), StringComparison.Ordinal), mt.Result("${url}").Length);
10215
10216                     //nico.ms使用、nicovideoにマッチしたら変換
10217                     if (this._cfgCommon.Nicoms && Regex.IsMatch(tmp, nico))
10218                     {
10219                         result = nicoms.Shorten(tmp);
10220                     }
10221                     else if (Converter_Type != MyCommon.UrlConverter.Nicoms)
10222                     {
10223                         //短縮URL変換 日本語を含むかもしれないのでURLエンコードする
10224                         try
10225                         {
10226                             var srcUri = new Uri(MyCommon.urlEncodeMultibyteChar(tmp));
10227                             var resultUri = await ShortUrl.Instance.ShortenUrlAsync(Converter_Type, srcUri);
10228                             result = resultUri.AbsoluteUri;
10229                         }
10230                         catch (HttpRequestException e)
10231                         {
10232                             // 例外のメッセージが「Response status code does not indicate success: 500 (Internal Server Error).」
10233                             // のように長いので「:」が含まれていればそれ以降のみを抽出する
10234                             var message = e.Message.Split(new[] { ':' }, count: 2).Last();
10235
10236                             this.StatusLabel.Text = Converter_Type + ":" + message;
10237                             continue;
10238                         }
10239                         catch (WebApiException e)
10240                         {
10241                             this.StatusLabel.Text = Converter_Type + ":" + e.Message;
10242                             continue;
10243                         }
10244                         catch (UriFormatException e)
10245                         {
10246                             this.StatusLabel.Text = Converter_Type + ":" + e.Message;
10247                             continue;
10248                         }
10249                     }
10250                     else
10251                     {
10252                         continue;
10253                     }
10254
10255                     if (!string.IsNullOrEmpty(result))
10256                     {
10257                         StatusText.Select(StatusText.Text.IndexOf(mt.Result("${url}"), StringComparison.Ordinal), mt.Result("${url}").Length);
10258                         StatusText.SelectedText = result;
10259                         //undoバッファにセット
10260                         undotmp.Before = mt.Result("${url}");
10261                         undotmp.After = result;
10262
10263                         if (urlUndoBuffer == null)
10264                         {
10265                             urlUndoBuffer = new List<urlUndo>();
10266                             UrlUndoToolStripMenuItem.Enabled = true;
10267                         }
10268
10269                         urlUndoBuffer.Add(undotmp);
10270                     }
10271                 }
10272             }
10273
10274             return true;
10275         }
10276
10277         private void doUrlUndo()
10278         {
10279             if (urlUndoBuffer != null)
10280             {
10281                 string tmp = StatusText.Text;
10282                 foreach (urlUndo data in urlUndoBuffer)
10283                 {
10284                     tmp = tmp.Replace(data.After, data.Before);
10285                 }
10286                 StatusText.Text = tmp;
10287                 urlUndoBuffer = null;
10288                 UrlUndoToolStripMenuItem.Enabled = false;
10289                 StatusText.SelectionStart = 0;
10290                 StatusText.SelectionLength = 0;
10291             }
10292         }
10293
10294         private async void TinyURLToolStripMenuItem_Click(object sender, EventArgs e)
10295         {
10296             await UrlConvertAsync(MyCommon.UrlConverter.TinyUrl);
10297         }
10298
10299         private async void IsgdToolStripMenuItem_Click(object sender, EventArgs e)
10300         {
10301             await UrlConvertAsync(MyCommon.UrlConverter.Isgd);
10302         }
10303
10304         private async void TwurlnlToolStripMenuItem_Click(object sender, EventArgs e)
10305         {
10306             await UrlConvertAsync(MyCommon.UrlConverter.Twurl);
10307         }
10308
10309         private async void UxnuMenuItem_Click(object sender, EventArgs e)
10310         {
10311             await UrlConvertAsync(MyCommon.UrlConverter.Uxnu);
10312         }
10313
10314         private async void UrlConvertAutoToolStripMenuItem_Click(object sender, EventArgs e)
10315         {
10316             if (!await UrlConvertAsync(this._cfgCommon.AutoShortUrlFirst))
10317             {
10318                 MyCommon.UrlConverter svc = this._cfgCommon.AutoShortUrlFirst;
10319                 Random rnd = new Random();
10320                 // 前回使用した短縮URLサービス以外を選択する
10321                 do
10322                 {
10323                     svc = (MyCommon.UrlConverter)rnd.Next(System.Enum.GetNames(typeof(MyCommon.UrlConverter)).Length);
10324                 }
10325                 while (svc == this._cfgCommon.AutoShortUrlFirst || svc == MyCommon.UrlConverter.Nicoms || svc == MyCommon.UrlConverter.Unu);
10326                 await UrlConvertAsync(svc);
10327             }
10328         }
10329
10330         private void UrlUndoToolStripMenuItem_Click(object sender, EventArgs e)
10331         {
10332             doUrlUndo();
10333         }
10334
10335         private void NewPostPopMenuItem_CheckStateChanged(object sender, EventArgs e)
10336         {
10337             this.NotifyFileMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
10338             this.NewPostPopMenuItem.Checked = this.NotifyFileMenuItem.Checked;
10339             _cfgCommon.NewAllPop = NewPostPopMenuItem.Checked;
10340             _modifySettingCommon = true;
10341         }
10342
10343         private void ListLockMenuItem_CheckStateChanged(object sender, EventArgs e)
10344         {
10345             ListLockMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
10346             this.LockListFileMenuItem.Checked = ListLockMenuItem.Checked;
10347             _cfgCommon.ListLock = ListLockMenuItem.Checked;
10348             _modifySettingCommon = true;
10349         }
10350
10351         private void MenuStrip1_MenuActivate(object sender, EventArgs e)
10352         {
10353             // フォーカスがメニューに移る (MenuStrip1.Tag フラグを立てる)
10354             MenuStrip1.Tag = new Object();
10355             MenuStrip1.Select(); // StatusText がフォーカスを持っている場合 Leave が発生
10356         }
10357
10358         private void MenuStrip1_MenuDeactivate(object sender, EventArgs e)
10359         {
10360             if (this.Tag != null) // 設定された戻り先へ遷移
10361             {
10362                 if (this.Tag == this.ListTab.SelectedTab)
10363                     ((Control)this.ListTab.SelectedTab.Tag).Select();
10364                 else
10365                     ((Control)this.Tag).Select();
10366             }
10367             else // 戻り先が指定されていない (初期状態) 場合はタブに遷移
10368             {
10369                 if (ListTab.SelectedIndex > -1 && ListTab.SelectedTab.HasChildren)
10370                 {
10371                     this.Tag = ListTab.SelectedTab.Tag;
10372                     ((Control)this.Tag).Select();
10373                 }
10374             }
10375             // フォーカスがメニューに遷移したかどうかを表すフラグを降ろす
10376             MenuStrip1.Tag = null;
10377         }
10378
10379         private void MyList_ColumnReordered(object sender, ColumnReorderedEventArgs e)
10380         {
10381             DetailsListView lst = (DetailsListView)sender;
10382             if (_cfgLocal == null) return;
10383
10384             if (_iconCol)
10385             {
10386                 _cfgLocal.Width1 = lst.Columns[0].Width;
10387                 _cfgLocal.Width3 = lst.Columns[1].Width;
10388             }
10389             else
10390             {
10391                 int[] darr = new int[lst.Columns.Count];
10392                 for (int i = 0; i < lst.Columns.Count; i++)
10393                 {
10394                     darr[lst.Columns[i].DisplayIndex] = i;
10395                 }
10396                 MyCommon.MoveArrayItem(darr, e.OldDisplayIndex, e.NewDisplayIndex);
10397
10398                 for (int i = 0; i < lst.Columns.Count; i++)
10399                 {
10400                     switch (darr[i])
10401                     {
10402                         case 0:
10403                             _cfgLocal.DisplayIndex1 = i;
10404                             break;
10405                         case 1:
10406                             _cfgLocal.DisplayIndex2 = i;
10407                             break;
10408                         case 2:
10409                             _cfgLocal.DisplayIndex3 = i;
10410                             break;
10411                         case 3:
10412                             _cfgLocal.DisplayIndex4 = i;
10413                             break;
10414                         case 4:
10415                             _cfgLocal.DisplayIndex5 = i;
10416                             break;
10417                         case 5:
10418                             _cfgLocal.DisplayIndex6 = i;
10419                             break;
10420                         case 6:
10421                             _cfgLocal.DisplayIndex7 = i;
10422                             break;
10423                         case 7:
10424                             _cfgLocal.DisplayIndex8 = i;
10425                             break;
10426                     }
10427                 }
10428                 _cfgLocal.Width1 = lst.Columns[0].Width;
10429                 _cfgLocal.Width2 = lst.Columns[1].Width;
10430                 _cfgLocal.Width3 = lst.Columns[2].Width;
10431                 _cfgLocal.Width4 = lst.Columns[3].Width;
10432                 _cfgLocal.Width5 = lst.Columns[4].Width;
10433                 _cfgLocal.Width6 = lst.Columns[5].Width;
10434                 _cfgLocal.Width7 = lst.Columns[6].Width;
10435                 _cfgLocal.Width8 = lst.Columns[7].Width;
10436             }
10437             _modifySettingLocal = true;
10438             _isColumnChanged = true;
10439         }
10440
10441         private void MyList_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e)
10442         {
10443             DetailsListView lst = (DetailsListView)sender;
10444             if (_cfgLocal == null) return;
10445             if (_iconCol)
10446             {
10447                 if (_cfgLocal.Width1 != lst.Columns[0].Width)
10448                 {
10449                     _cfgLocal.Width1 = lst.Columns[0].Width;
10450                     _modifySettingLocal = true;
10451                     _isColumnChanged = true;
10452                 }
10453                 if (_cfgLocal.Width3 != lst.Columns[1].Width)
10454                 {
10455                     _cfgLocal.Width3 = lst.Columns[1].Width;
10456                     _modifySettingLocal = true;
10457                     _isColumnChanged = true;
10458                 }
10459             }
10460             else
10461             {
10462                 if (_cfgLocal.Width1 != lst.Columns[0].Width)
10463                 {
10464                     _cfgLocal.Width1 = lst.Columns[0].Width;
10465                     _modifySettingLocal = true;
10466                     _isColumnChanged = true;
10467                 }
10468                 if (_cfgLocal.Width2 != lst.Columns[1].Width)
10469                 {
10470                     _cfgLocal.Width2 = lst.Columns[1].Width;
10471                     _modifySettingLocal = true;
10472                     _isColumnChanged = true;
10473                 }
10474                 if (_cfgLocal.Width3 != lst.Columns[2].Width)
10475                 {
10476                     _cfgLocal.Width3 = lst.Columns[2].Width;
10477                     _modifySettingLocal = true;
10478                     _isColumnChanged = true;
10479                 }
10480                 if (_cfgLocal.Width4 != lst.Columns[3].Width)
10481                 {
10482                     _cfgLocal.Width4 = lst.Columns[3].Width;
10483                     _modifySettingLocal = true;
10484                     _isColumnChanged = true;
10485                 }
10486                 if (_cfgLocal.Width5 != lst.Columns[4].Width)
10487                 {
10488                     _cfgLocal.Width5 = lst.Columns[4].Width;
10489                     _modifySettingLocal = true;
10490                     _isColumnChanged = true;
10491                 }
10492                 if (_cfgLocal.Width6 != lst.Columns[5].Width)
10493                 {
10494                     _cfgLocal.Width6 = lst.Columns[5].Width;
10495                     _modifySettingLocal = true;
10496                     _isColumnChanged = true;
10497                 }
10498                 if (_cfgLocal.Width7 != lst.Columns[6].Width)
10499                 {
10500                     _cfgLocal.Width7 = lst.Columns[6].Width;
10501                     _modifySettingLocal = true;
10502                     _isColumnChanged = true;
10503                 }
10504                 if (_cfgLocal.Width8 != lst.Columns[7].Width)
10505                 {
10506                     _cfgLocal.Width8 = lst.Columns[7].Width;
10507                     _modifySettingLocal = true;
10508                     _isColumnChanged = true;
10509                 }
10510             }
10511             // 非表示の時にColumnChangedが呼ばれた場合はForm初期化処理中なので保存しない
10512             //if (changed)
10513             //{
10514             //    SaveConfigsLocal();
10515             //}
10516         }
10517
10518         private void SelectionCopyContextMenuItem_Click(object sender, EventArgs e)
10519         {
10520             //発言詳細で「選択文字列をコピー」
10521             string _selText = this.PostBrowser.GetSelectedText();
10522             try
10523             {
10524                 Clipboard.SetDataObject(_selText, false, 5, 100);
10525             }
10526             catch (Exception ex)
10527             {
10528                 MessageBox.Show(ex.Message);
10529             }
10530         }
10531
10532         private async Task doSearchToolStrip(string url)
10533         {
10534             //発言詳細で「選択文字列で検索」(選択文字列取得)
10535             string _selText = this.PostBrowser.GetSelectedText();
10536
10537             if (_selText != null)
10538             {
10539                 if (url == Properties.Resources.SearchItem4Url)
10540                 {
10541                     //公式検索
10542                     AddNewTabForSearch(_selText);
10543                     return;
10544                 }
10545
10546                 string tmp = string.Format(url, Uri.EscapeDataString(_selText));
10547                 await this.OpenUriInBrowserAsync(tmp);
10548             }
10549         }
10550
10551         private void SelectionAllContextMenuItem_Click(object sender, EventArgs e)
10552         {
10553             //発言詳細ですべて選択
10554             PostBrowser.Document.ExecCommand("SelectAll", false, null);
10555         }
10556
10557         private async void SearchWikipediaContextMenuItem_Click(object sender, EventArgs e)
10558         {
10559             await this.doSearchToolStrip(Properties.Resources.SearchItem1Url);
10560         }
10561
10562         private async void SearchGoogleContextMenuItem_Click(object sender, EventArgs e)
10563         {
10564             await this.doSearchToolStrip(Properties.Resources.SearchItem2Url);
10565         }
10566
10567         private async void SearchPublicSearchContextMenuItem_Click(object sender, EventArgs e)
10568         {
10569             await this.doSearchToolStrip(Properties.Resources.SearchItem4Url);
10570         }
10571
10572         private void UrlCopyContextMenuItem_Click(object sender, EventArgs e)
10573         {
10574             try
10575             {
10576                 MatchCollection mc = Regex.Matches(this.PostBrowser.DocumentText, @"<a[^>]*href=""(?<url>" + this._postBrowserStatusText.Replace(".", @"\.") + @")""[^>]*title=""(?<title>https?://[^""]+)""", RegexOptions.IgnoreCase);
10577                 foreach (Match m in mc)
10578                 {
10579                     if (m.Groups["url"].Value == this._postBrowserStatusText)
10580                     {
10581                         Clipboard.SetDataObject(m.Groups["title"].Value, false, 5, 100);
10582                         break;
10583                     }
10584                 }
10585                 if (mc.Count == 0)
10586                 {
10587                     Clipboard.SetDataObject(this._postBrowserStatusText, false, 5, 100);
10588                 }
10589                 //Clipboard.SetDataObject(this._postBrowserStatusText, false, 5, 100);
10590             }
10591             catch (Exception ex)
10592             {
10593                 MessageBox.Show(ex.Message);
10594             }
10595         }
10596
10597         private void ContextMenuPostBrowser_Opening(object ender, CancelEventArgs e)
10598         {
10599             // URLコピーの項目の表示/非表示
10600             if (PostBrowser.StatusText.StartsWith("http"))
10601             {
10602                 this._postBrowserStatusText = PostBrowser.StatusText;
10603                 string name = GetUserId();
10604                 UrlCopyContextMenuItem.Enabled = true;
10605                 if (name != null)
10606                 {
10607                     FollowContextMenuItem.Enabled = true;
10608                     RemoveContextMenuItem.Enabled = true;
10609                     FriendshipContextMenuItem.Enabled = true;
10610                     ShowUserStatusContextMenuItem.Enabled = true;
10611                     SearchPostsDetailToolStripMenuItem.Enabled = true;
10612                     IdFilterAddMenuItem.Enabled = true;
10613                     ListManageUserContextToolStripMenuItem.Enabled = true;
10614                     SearchAtPostsDetailToolStripMenuItem.Enabled = true;
10615                 }
10616                 else
10617                 {
10618                     FollowContextMenuItem.Enabled = false;
10619                     RemoveContextMenuItem.Enabled = false;
10620                     FriendshipContextMenuItem.Enabled = false;
10621                     ShowUserStatusContextMenuItem.Enabled = false;
10622                     SearchPostsDetailToolStripMenuItem.Enabled = false;
10623                     IdFilterAddMenuItem.Enabled = false;
10624                     ListManageUserContextToolStripMenuItem.Enabled = false;
10625                     SearchAtPostsDetailToolStripMenuItem.Enabled = false;
10626                 }
10627
10628                 if (Regex.IsMatch(this._postBrowserStatusText, @"^https?://twitter.com/search\?q=%23"))
10629                     UseHashtagMenuItem.Enabled = true;
10630                 else
10631                     UseHashtagMenuItem.Enabled = false;
10632             }
10633             else
10634             {
10635                 this._postBrowserStatusText = "";
10636                 UrlCopyContextMenuItem.Enabled = false;
10637                 FollowContextMenuItem.Enabled = false;
10638                 RemoveContextMenuItem.Enabled = false;
10639                 FriendshipContextMenuItem.Enabled = false;
10640                 ShowUserStatusContextMenuItem.Enabled = false;
10641                 SearchPostsDetailToolStripMenuItem.Enabled = false;
10642                 SearchAtPostsDetailToolStripMenuItem.Enabled = false;
10643                 UseHashtagMenuItem.Enabled = false;
10644                 IdFilterAddMenuItem.Enabled = false;
10645                 ListManageUserContextToolStripMenuItem.Enabled = false;
10646             }
10647             // 文字列選択されていないときは選択文字列関係の項目を非表示に
10648             string _selText = this.PostBrowser.GetSelectedText();
10649             if (_selText == null)
10650             {
10651                 SelectionSearchContextMenuItem.Enabled = false;
10652                 SelectionCopyContextMenuItem.Enabled = false;
10653                 SelectionTranslationToolStripMenuItem.Enabled = false;
10654             }
10655             else
10656             {
10657                 SelectionSearchContextMenuItem.Enabled = true;
10658                 SelectionCopyContextMenuItem.Enabled = true;
10659                 SelectionTranslationToolStripMenuItem.Enabled = true;
10660             }
10661             //発言内に自分以外のユーザーが含まれてればフォロー状態全表示を有効に
10662             MatchCollection ma = Regex.Matches(this.PostBrowser.DocumentText, @"href=""https?://twitter.com/(#!/)?(?<ScreenName>[a-zA-Z0-9_]+)(/status(es)?/[0-9]+)?""");
10663             bool fAllFlag = false;
10664             foreach (Match mu in ma)
10665             {
10666                 if (mu.Result("${ScreenName}").ToLower() != tw.Username.ToLower())
10667                 {
10668                     fAllFlag = true;
10669                     break;
10670                 }
10671             }
10672             this.FriendshipAllMenuItem.Enabled = fAllFlag;
10673
10674             if (_curPost == null)
10675                 TranslationToolStripMenuItem.Enabled = false;
10676             else
10677                 TranslationToolStripMenuItem.Enabled = true;
10678
10679             e.Cancel = false;
10680         }
10681
10682         private void CurrentTabToolStripMenuItem_Click(object sender, EventArgs e)
10683         {
10684             //発言詳細の選択文字列で現在のタブを検索
10685             string _selText = this.PostBrowser.GetSelectedText();
10686
10687             if (_selText != null)
10688             {
10689                 var searchOptions = new SearchWordDialog.SearchOptions(
10690                     SearchWordDialog.SearchType.Timeline,
10691                     _selText,
10692                     newTab: false,
10693                     caseSensitive: false,
10694                     useRegex: false);
10695
10696                 this.SearchDialog.ResultOptions = searchOptions;
10697
10698                 this.DoTabSearch(
10699                     searchOptions.Query,
10700                     searchOptions.CaseSensitive,
10701                     searchOptions.UseRegex,
10702                     SEARCHTYPE.NextSearch);
10703             }
10704         }
10705
10706         private void SplitContainer2_SplitterMoved(object sender, SplitterEventArgs e)
10707         {
10708             if (StatusText.Multiline) _mySpDis2 = StatusText.Height;
10709             _modifySettingLocal = true;
10710         }
10711
10712         private void TweenMain_DragDrop(object sender, DragEventArgs e)
10713         {
10714             if (e.Data.GetDataPresent(DataFormats.FileDrop))
10715             {
10716                 if (!e.Data.GetDataPresent(DataFormats.Html, false))  // WebBrowserコントロールからの絵文字画像Drag&Dropは弾く
10717                 {
10718                     SelectMedia_DragDrop(e);
10719                 }
10720             }
10721             else if (e.Data.GetDataPresent("UniformResourceLocatorW"))
10722             {
10723                 var url = GetUrlFromDataObject(e.Data);
10724
10725                 string appendText;
10726                 if (url.Item2 == null)
10727                     appendText = url.Item1;
10728                 else
10729                     appendText = url.Item2 + " " + url.Item1;
10730
10731                 if (this.StatusText.TextLength == 0)
10732                     this.StatusText.Text = appendText;
10733                 else
10734                     this.StatusText.Text += " " + appendText;
10735             }
10736             else if (e.Data.GetDataPresent(DataFormats.UnicodeText))
10737             {
10738                 var text = (string)e.Data.GetData(DataFormats.UnicodeText);
10739                 if (text != null)
10740                     this.StatusText.Text += text;
10741             }
10742             else if (e.Data.GetDataPresent(DataFormats.StringFormat))
10743             {
10744                 string data = (string)e.Data.GetData(DataFormats.StringFormat, true);
10745                 if (data != null) StatusText.Text += data;
10746             }
10747         }
10748
10749         /// <summary>
10750         /// IDataObject から URL とタイトルの対を取得します
10751         /// </summary>
10752         /// <remarks>
10753         /// タイトルのみ取得できなかった場合は Value2 が null のタプルを返すことがあります。
10754         /// </remarks>
10755         /// <exception cref="ArgumentException">不正なフォーマットが入力された場合</exception>
10756         /// <exception cref="NotSupportedException">サポートされていないデータが入力された場合</exception>
10757         internal static Tuple<string, string> GetUrlFromDataObject(IDataObject data)
10758         {
10759             if (data.GetDataPresent("text/x-moz-url"))
10760             {
10761                 // Firefox, Google Chrome で利用可能
10762                 // 参照: https://developer.mozilla.org/ja/docs/DragDrop/Recommended_Drag_Types
10763
10764                 using (var stream = (MemoryStream)data.GetData("text/x-moz-url"))
10765                 {
10766                     var lines = Encoding.Unicode.GetString(stream.ToArray()).TrimEnd('\0').Split('\n');
10767                     if (lines.Length < 2)
10768                         throw new ArgumentException("不正な text/x-moz-url フォーマットです", nameof(data));
10769
10770                     return new Tuple<string, string>(lines[0], lines[1]);
10771                 }
10772             }
10773             else if (data.GetDataPresent("IESiteModeToUrl"))
10774             {
10775                 // Internet Exproler 用
10776                 // 保護モードが有効なデフォルトの IE では DragDrop イベントが発火しないため使えない
10777
10778                 using (var stream = (MemoryStream)data.GetData("IESiteModeToUrl"))
10779                 {
10780                     var lines = Encoding.Unicode.GetString(stream.ToArray()).TrimEnd('\0').Split('\0');
10781                     if (lines.Length < 2)
10782                         throw new ArgumentException("不正な IESiteModeToUrl フォーマットです", nameof(data));
10783
10784                     return new Tuple<string, string>(lines[0], lines[1]);
10785                 }
10786             }
10787             else if (data.GetDataPresent("UniformResourceLocatorW"))
10788             {
10789                 // それ以外のブラウザ向け
10790
10791                 using (var stream = (MemoryStream)data.GetData("UniformResourceLocatorW"))
10792                 {
10793                     var url = Encoding.Unicode.GetString(stream.ToArray()).TrimEnd('\0');
10794                     return new Tuple<string, string>(url, null);
10795                 }
10796             }
10797
10798             throw new NotSupportedException("サポートされていないデータ形式です: " + data.GetFormats()[0]);
10799         }
10800
10801         private void TweenMain_DragEnter(object sender, DragEventArgs e)
10802         {
10803             if (e.Data.GetDataPresent(DataFormats.FileDrop))
10804             {
10805                 if (!e.Data.GetDataPresent(DataFormats.Html, false))  // WebBrowserコントロールからの絵文字画像Drag&Dropは弾く
10806                 {
10807                     SelectMedia_DragEnter(e);
10808                     return;
10809                 }
10810             }
10811             else if (e.Data.GetDataPresent("UniformResourceLocatorW"))
10812             {
10813                 e.Effect = DragDropEffects.Copy;
10814                 return;
10815             }
10816             else if (e.Data.GetDataPresent(DataFormats.UnicodeText))
10817             {
10818                 e.Effect = DragDropEffects.Copy;
10819                 return;
10820             }
10821             else if (e.Data.GetDataPresent(DataFormats.StringFormat))
10822             {
10823                 e.Effect = DragDropEffects.Copy;
10824                 return;
10825             }
10826
10827             e.Effect = DragDropEffects.None;
10828         }
10829
10830         private void TweenMain_DragOver(object sender, DragEventArgs e)
10831         {
10832         }
10833
10834         public bool IsNetworkAvailable()
10835         {
10836             bool nw = true;
10837             nw = MyCommon.IsNetworkAvailable();
10838             _myStatusOnline = nw;
10839             return nw;
10840         }
10841
10842         public async Task OpenUriAsync(Uri uri, bool isReverseSettings = false)
10843         {
10844             var uriStr = uri.AbsoluteUri;
10845
10846             // OpenTween 内部で使用する URL
10847             if (uri.Authority == "opentween")
10848             {
10849                 await this.OpenInternalUriAsync(uri);
10850                 return;
10851             }
10852
10853             // ハッシュタグを含む Twitter 検索
10854             if (uri.Host == "twitter.com" && uri.AbsolutePath == "/search" && uri.Query.Contains("q=%23"))
10855             {
10856                 // ハッシュタグの場合は、タブで開く
10857                 var unescapedQuery = Uri.UnescapeDataString(uri.Query);
10858                 var pos = unescapedQuery.IndexOf('#');
10859                 if (pos == -1) return;
10860
10861                 var hash = unescapedQuery.Substring(pos);
10862                 this.HashSupl.AddItem(hash);
10863                 this.HashMgr.AddHashToHistory(hash.Trim(), false);
10864                 this.AddNewTabForSearch(hash);
10865                 return;
10866             }
10867
10868             // ユーザープロフィールURL
10869             // フラグが立っている場合は設定と逆の動作をする
10870             if( this._cfgCommon.OpenUserTimeline && !isReverseSettings ||
10871                 !this._cfgCommon.OpenUserTimeline && isReverseSettings )
10872             {
10873                 var userUriMatch = Regex.Match(uriStr, "^https?://twitter.com/(#!/)?(?<ScreenName>[a-zA-Z0-9_]+)$");
10874                 if (userUriMatch.Success)
10875                 {
10876                     var screenName = userUriMatch.Groups["ScreenName"].Value;
10877                     if (this.IsTwitterId(screenName))
10878                     {
10879                         this.AddNewTabForUserTimeline(screenName);
10880                         return;
10881                     }
10882                 }
10883             }
10884
10885             // どのパターンにも該当しないURL
10886             await this.OpenUriInBrowserAsync(uriStr);
10887         }
10888
10889         /// <summary>
10890         /// OpenTween 内部の機能を呼び出すための URL を開きます
10891         /// </summary>
10892         private async Task OpenInternalUriAsync(Uri uri)
10893         {
10894             // ツイートを開く (//opentween/status/:status_id)
10895             var match = Regex.Match(uri.AbsolutePath, @"^/status/(\d+)$");
10896             if (match.Success)
10897             {
10898                 var statusId = long.Parse(match.Groups[1].Value);
10899                 await this.OpenRelatedTab(statusId);
10900                 return;
10901             }
10902         }
10903
10904         public Task OpenUriInBrowserAsync(string UriString)
10905         {
10906             return Task.Run(() =>
10907             {
10908                 string myPath = UriString;
10909
10910                 try
10911                 {
10912                     var configBrowserPath = this._cfgLocal.BrowserPath;
10913                     if (!string.IsNullOrEmpty(configBrowserPath))
10914                     {
10915                         if (configBrowserPath.StartsWith("\"") && configBrowserPath.Length > 2 && configBrowserPath.IndexOf("\"", 2) > -1)
10916                         {
10917                             int sep = configBrowserPath.IndexOf("\"", 2);
10918                             string browserPath = configBrowserPath.Substring(1, sep - 1);
10919                             string arg = "";
10920                             if (sep < configBrowserPath.Length - 1)
10921                             {
10922                                 arg = configBrowserPath.Substring(sep + 1);
10923                             }
10924                             myPath = arg + " " + myPath;
10925                             System.Diagnostics.Process.Start(browserPath, myPath);
10926                         }
10927                         else
10928                         {
10929                             System.Diagnostics.Process.Start(configBrowserPath, myPath);
10930                         }
10931                     }
10932                     else
10933                     {
10934                         System.Diagnostics.Process.Start(myPath);
10935                     }
10936                 }
10937                 catch (Exception)
10938                 {
10939                     //MessageBox.Show("ブラウザの起動に失敗、またはタイムアウトしました。" + ex.ToString());
10940                 }
10941             });
10942         }
10943
10944         private void ListTabSelect(TabPage _tab)
10945         {
10946             SetListProperty();
10947
10948             this.PurgeListViewItemCache();
10949
10950             _curTab = _tab;
10951             _curList = (DetailsListView)_tab.Tag;
10952             if (_curList.SelectedIndices.Count > 0)
10953             {
10954                 _curItemIndex = _curList.SelectedIndices[0];
10955                 _curPost = GetCurTabPost(_curItemIndex);
10956             }
10957             else
10958             {
10959                 _curItemIndex = -1;
10960                 _curPost = null;
10961             }
10962
10963             _anchorPost = null;
10964             _anchorFlag = false;
10965
10966             if (_iconCol)
10967             {
10968                 ((DetailsListView)_tab.Tag).Columns[1].Text = ColumnText[2];
10969             }
10970             else
10971             {
10972                 for (int i = 0; i < _curList.Columns.Count; i++)
10973                 {
10974                     ((DetailsListView)_tab.Tag).Columns[i].Text = ColumnText[i];
10975                 }
10976             }
10977         }
10978
10979         private void ListTab_Selecting(object sender, TabControlCancelEventArgs e)
10980         {
10981             ListTabSelect(e.TabPage);
10982         }
10983
10984         private void SelectListItem(DetailsListView LView, int Index)
10985         {
10986             //単一
10987             Rectangle bnd = new Rectangle();
10988             bool flg = false;
10989             var item = LView.FocusedItem;
10990             if (item != null)
10991             {
10992                 bnd = item.Bounds;
10993                 flg = true;
10994             }
10995
10996             do
10997             {
10998                 LView.SelectedIndices.Clear();
10999             }
11000             while (LView.SelectedIndices.Count > 0);
11001             item = LView.Items[Index];
11002             item.Selected = true;
11003             item.Focused = true;
11004
11005             if (flg) LView.Invalidate(bnd);
11006         }
11007
11008         private void SelectListItem(DetailsListView LView , int[] Index, int focusedIndex, int selectionMarkIndex)
11009         {
11010             //複数
11011             Rectangle bnd = new Rectangle();
11012             bool flg = false;
11013             var item = LView.FocusedItem;
11014             if (item != null)
11015             {
11016                 bnd = item.Bounds;
11017                 flg = true;
11018             }
11019
11020             if (Index != null)
11021             {
11022                 do
11023                 {
11024                     LView.SelectedIndices.Clear();
11025                 }
11026                 while (LView.SelectedIndices.Count > 0);
11027                 LView.SelectItems(Index);
11028             }
11029             if (selectionMarkIndex > -1 && LView.VirtualListSize > selectionMarkIndex)
11030             {
11031                 LView.SelectionMark = selectionMarkIndex;
11032             }
11033             if (focusedIndex > -1 && LView.VirtualListSize > focusedIndex)
11034             {
11035                 LView.Items[focusedIndex].Focused = true;
11036             }
11037             else if (Index != null && Index.Length != 0)
11038             {
11039                 LView.Items[Index.Last()].Focused = true;
11040             }
11041
11042             if (flg) LView.Invalidate(bnd);
11043         }
11044
11045         private void StartUserStream()
11046         {
11047             tw.NewPostFromStream += tw_NewPostFromStream;
11048             tw.UserStreamStarted += tw_UserStreamStarted;
11049             tw.UserStreamStopped += tw_UserStreamStopped;
11050             tw.PostDeleted += tw_PostDeleted;
11051             tw.UserStreamEventReceived += tw_UserStreamEventArrived;
11052
11053             MenuItemUserStream.Text = "&UserStream ■";
11054             MenuItemUserStream.Enabled = true;
11055             StopToolStripMenuItem.Text = "&Start";
11056             StopToolStripMenuItem.Enabled = true;
11057             if (this._cfgCommon.UserstreamStartup) tw.StartUserStream();
11058         }
11059
11060         private async void TweenMain_Shown(object sender, EventArgs e)
11061         {
11062             try
11063             {
11064                 using (ControlTransaction.Update(this.PostBrowser))
11065                 {
11066                     PostBrowser.Url = new Uri("about:blank");
11067                     PostBrowser.DocumentText = "";       //発言詳細部初期化
11068                 }
11069             }
11070             catch (Exception)
11071             {
11072             }
11073
11074             NotifyIcon1.Visible = true;
11075
11076             if (this.IsNetworkAvailable())
11077             {
11078                 StartUserStream();
11079
11080                 var loadTasks = new List<Task>
11081                 {
11082                     this.RefreshMuteUserIdsAsync(),
11083                     this.RefreshBlockIdsAsync(),
11084                     this.RefreshNoRetweetIdsAsync(),
11085                     this.RefreshTwitterConfigurationAsync(),
11086                     this.GetHomeTimelineAsync(),
11087                     this.GetReplyAsync(),
11088                     this.GetDirectMessagesAsync(),
11089                     this.GetPublicSearchAllAsync(),
11090                     this.GetUserTimelineAllAsync(),
11091                     this.GetListTimelineAllAsync(),
11092                 };
11093
11094                 if (this._cfgCommon.StartupFollowers)
11095                     loadTasks.Add(this.RefreshFollowerIdsAsync());
11096
11097                 if (this._cfgCommon.GetFav)
11098                     loadTasks.Add(this.GetFavoritesAsync());
11099
11100                 var allTasks = Task.WhenAll(loadTasks);
11101
11102                 var i = 0;
11103                 while (true)
11104                 {
11105                     var timeout = Task.Delay(5000);
11106                     if (await Task.WhenAny(allTasks, timeout) != timeout)
11107                         break;
11108
11109                     i += 1;
11110                     if (i > 24) break; // 120秒間初期処理が終了しなかったら強制的に打ち切る
11111
11112                     if (MyCommon._endingFlag)
11113                         return;
11114                 }
11115
11116                 if (MyCommon._endingFlag) return;
11117
11118                 if (ApplicationSettings.VersionInfoUrl != null)
11119                 {
11120                     //バージョンチェック(引数:起動時チェックの場合はtrue・・・チェック結果のメッセージを表示しない)
11121                     if (this._cfgCommon.StartupVersion)
11122                         await this.CheckNewVersion(true);
11123                 }
11124                 else
11125                 {
11126                     // ApplicationSetting.cs の設定により更新チェックが無効化されている場合
11127                     this.VerUpMenuItem.Enabled = false;
11128                     this.VerUpMenuItem.Available = false;
11129                     this.ToolStripSeparator16.Available = false; // VerUpMenuItem の一つ上にあるセパレータ
11130                 }
11131
11132                 // 権限チェック read/write権限(xAuthで取得したトークン)の場合は再認証を促す
11133                 if (MyCommon.TwitterApiInfo.AccessLevel == TwitterApiAccessLevel.ReadWrite)
11134                 {
11135                     MessageBox.Show(Properties.Resources.ReAuthorizeText);
11136                     SettingStripMenuItem_Click(null, null);
11137                 }
11138
11139                 // 取得失敗の場合は再試行する
11140                 var reloadTasks = new List<Task>();
11141
11142                 if (!tw.GetFollowersSuccess && this._cfgCommon.StartupFollowers)
11143                     reloadTasks.Add(this.RefreshFollowerIdsAsync());
11144
11145                 if (!tw.GetNoRetweetSuccess)
11146                     reloadTasks.Add(this.RefreshNoRetweetIdsAsync());
11147
11148                 if (this.tw.Configuration.PhotoSizeLimit == 0)
11149                     reloadTasks.Add(this.RefreshTwitterConfigurationAsync());
11150
11151                 await Task.WhenAll(reloadTasks);
11152             }
11153
11154             _initial = false;
11155
11156             TimerTimeline.Enabled = true;
11157         }
11158
11159         private async Task doGetFollowersMenu()
11160         {
11161             await this.RefreshFollowerIdsAsync();
11162             await this.DispSelectedPost(true);
11163         }
11164
11165         private async void GetFollowersAllToolStripMenuItem_Click(object sender, EventArgs e)
11166         {
11167             await this.doGetFollowersMenu();
11168         }
11169
11170         private void ReTweetUnofficialStripMenuItem_Click(object sender, EventArgs e)
11171         {
11172             doReTweetUnofficial();
11173         }
11174
11175         private async Task doReTweetOfficial(bool isConfirm)
11176         {
11177             //公式RT
11178             if (this.ExistCurrentPost)
11179             {
11180                 if (_curPost.IsProtect)
11181                 {
11182                     MessageBox.Show("Protected.");
11183                     _DoFavRetweetFlags = false;
11184                     return;
11185                 }
11186                 if (_curList.SelectedIndices.Count > 15)
11187                 {
11188                     MessageBox.Show(Properties.Resources.RetweetLimitText);
11189                     _DoFavRetweetFlags = false;
11190                     return;
11191                 }
11192                 else if (_curList.SelectedIndices.Count > 1)
11193                 {
11194                     string QuestionText = Properties.Resources.RetweetQuestion2;
11195                     if (_DoFavRetweetFlags) QuestionText = Properties.Resources.FavoriteRetweetQuestionText1;
11196                     switch (MessageBox.Show(QuestionText, "Retweet", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question))
11197                     {
11198                         case DialogResult.Cancel:
11199                         case DialogResult.No:
11200                             _DoFavRetweetFlags = false;
11201                             return;
11202                     }
11203                 }
11204                 else
11205                 {
11206                     if (_curPost.IsDm || _curPost.IsMe)
11207                     {
11208                         _DoFavRetweetFlags = false;
11209                         return;
11210                     }
11211                     if (!this._cfgCommon.RetweetNoConfirm)
11212                     {
11213                         string Questiontext = Properties.Resources.RetweetQuestion1;
11214                         if (_DoFavRetweetFlags) Questiontext = Properties.Resources.FavoritesRetweetQuestionText2;
11215                         if (isConfirm && MessageBox.Show(Questiontext, "Retweet", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
11216                         {
11217                             _DoFavRetweetFlags = false;
11218                             return;
11219                         }
11220                     }
11221                 }
11222
11223                 var statusIds = new List<long>();
11224                 foreach (int idx in _curList.SelectedIndices)
11225                 {
11226                     PostClass post = GetCurTabPost(idx);
11227                     if (!post.IsMe && !post.IsProtect && !post.IsDm)
11228                         statusIds.Add(post.StatusId);
11229                 }
11230
11231                 await this.RetweetAsync(statusIds);
11232             }
11233         }
11234
11235         private async void ReTweetStripMenuItem_Click(object sender, EventArgs e)
11236         {
11237             await this.doReTweetOfficial(true);
11238         }
11239
11240         private async Task FavoritesRetweetOfficial()
11241         {
11242             if (!this.ExistCurrentPost) return;
11243             _DoFavRetweetFlags = true;
11244             var retweetTask = this.doReTweetOfficial(true);
11245             if (_DoFavRetweetFlags)
11246             {
11247                 _DoFavRetweetFlags = false;
11248                 var favoriteTask = this.FavoriteChange(true, false);
11249
11250                 await Task.WhenAll(retweetTask, favoriteTask);
11251             }
11252             else
11253             {
11254                 await retweetTask;
11255             }
11256         }
11257
11258         private async Task FavoritesRetweetUnofficial()
11259         {
11260             if (this.ExistCurrentPost && !_curPost.IsDm)
11261             {
11262                 _DoFavRetweetFlags = true;
11263                 var favoriteTask = this.FavoriteChange(true);
11264                 if (!_curPost.IsProtect && _DoFavRetweetFlags)
11265                 {
11266                     _DoFavRetweetFlags = false;
11267                     doReTweetUnofficial();
11268                 }
11269
11270                 await favoriteTask;
11271             }
11272         }
11273
11274         /// <summary>
11275         /// TweetFormatterクラスによって整形された状態のHTMLを、非公式RT用に元のツイートに復元します
11276         /// </summary>
11277         /// <param name="statusHtml">TweetFormatterによって整形された状態のHTML</param>
11278         /// <param name="multiline">trueであればBRタグを改行に、falseであればスペースに変換します</param>
11279         /// <returns>復元されたツイート本文</returns>
11280         internal static string CreateRetweetUnofficial(string statusHtml, bool multiline)
11281         {
11282             // TweetFormatterクラスによって整形された状態のHTMLを元のツイートに復元します
11283
11284             // 通常の URL
11285             statusHtml = Regex.Replace(statusHtml, "<a href=\"(?<href>.+?)\" title=\"(?<title>.+?)\">(?<text>.+?)</a>", "${title}");
11286             // メンション
11287             statusHtml = Regex.Replace(statusHtml, "<a class=\"mention\" href=\"(?<href>.+?)\">(?<text>.+?)</a>", "${text}");
11288             // ハッシュタグ
11289             statusHtml = Regex.Replace(statusHtml, "<a class=\"hashtag\" href=\"(?<href>.+?)\">(?<text>.+?)</a>", "${text}");
11290
11291             // <br> 除去
11292             if (multiline)
11293                 statusHtml = statusHtml.Replace("<br>", Environment.NewLine);
11294             else
11295                 statusHtml = statusHtml.Replace("<br>", " ");
11296
11297             // &nbsp; は本来であれば U+00A0 (NON-BREAK SPACE) に置換すべきですが、
11298             // 現状では半角スペースの代用として &nbsp; を使用しているため U+0020 に置換します
11299             statusHtml = statusHtml.Replace("&nbsp;", " ");
11300
11301             return WebUtility.HtmlDecode(statusHtml);
11302         }
11303
11304         private async void DumpPostClassToolStripMenuItem_Click(object sender, EventArgs e)
11305         {
11306             if (_curPost != null)
11307                 await this.DispSelectedPost(true);
11308         }
11309
11310         private void MenuItemHelp_DropDownOpening(object sender, EventArgs e)
11311         {
11312             if (MyCommon.DebugBuild || MyCommon.IsKeyDown(Keys.CapsLock, Keys.Control, Keys.Shift))
11313                 DebugModeToolStripMenuItem.Visible = true;
11314             else
11315                 DebugModeToolStripMenuItem.Visible = false;
11316         }
11317
11318         private void ToolStripMenuItemUrlAutoShorten_CheckedChanged(object sender, EventArgs e)
11319         {
11320             this._cfgCommon.UrlConvertAuto = ToolStripMenuItemUrlAutoShorten.Checked;
11321         }
11322
11323         private void ContextMenuPostMode_Opening(object sender, CancelEventArgs e)
11324         {
11325             ToolStripMenuItemUrlAutoShorten.Checked = this._cfgCommon.UrlConvertAuto;
11326         }
11327
11328         private void TraceOutToolStripMenuItem_Click(object sender, EventArgs e)
11329         {
11330             if (TraceOutToolStripMenuItem.Checked)
11331                 MyCommon.TraceFlag = true;
11332             else
11333                 MyCommon.TraceFlag = false;
11334         }
11335
11336         private void TweenMain_Deactivate(object sender, EventArgs e)
11337         {
11338             //画面が非アクティブになったら、発言欄の背景色をデフォルトへ
11339             this.StatusText_Leave(StatusText, System.EventArgs.Empty);
11340         }
11341
11342         private void TabRenameMenuItem_Click(object sender, EventArgs e)
11343         {
11344             if (string.IsNullOrEmpty(_rclickTabName)) return;
11345             TabRename(ref _rclickTabName);
11346         }
11347
11348         private async void BitlyToolStripMenuItem_Click(object sender, EventArgs e)
11349         {
11350             await UrlConvertAsync(MyCommon.UrlConverter.Bitly);
11351         }
11352
11353         private async void JmpToolStripMenuItem_Click(object sender, EventArgs e)
11354         {
11355             await UrlConvertAsync(MyCommon.UrlConverter.Jmp);
11356         }
11357
11358         private async void ApiUsageInfoMenuItem_Click(object sender, EventArgs e)
11359         {
11360             TwitterApiStatus apiStatus;
11361
11362             using (var dialog = new WaitingDialog(Properties.Resources.ApiInfo6))
11363             {
11364                 var cancellationToken = dialog.EnableCancellation();
11365
11366                 try
11367                 {
11368                     var task = Task.Run(() => this.tw.GetInfoApi());
11369                     apiStatus = await dialog.WaitForAsync(this, task);
11370                 }
11371                 catch (WebApiException)
11372                 {
11373                     apiStatus = null;
11374                 }
11375
11376                 if (cancellationToken.IsCancellationRequested)
11377                     return;
11378
11379                 if (apiStatus == null)
11380                 {
11381                     MessageBox.Show(Properties.Resources.ApiInfo5, Properties.Resources.ApiInfo4, MessageBoxButtons.OK, MessageBoxIcon.Information);
11382                     return;
11383                 }
11384             }
11385
11386             using (var apiDlg = new ApiInfoDialog())
11387             {
11388                 apiDlg.ShowDialog(this);
11389             }
11390         }
11391
11392         private async void FollowCommandMenuItem_Click(object sender, EventArgs e)
11393         {
11394             var id = _curPost?.ScreenName ?? "";
11395
11396             await this.FollowCommand(id);
11397         }
11398
11399         private async Task FollowCommand(string id)
11400         {
11401             using (var inputName = new InputTabName())
11402             {
11403                 inputName.FormTitle = "Follow";
11404                 inputName.FormDescription = Properties.Resources.FRMessage1;
11405                 inputName.TabName = id;
11406
11407                 if (inputName.ShowDialog(this) != DialogResult.OK)
11408                     return;
11409                 if (string.IsNullOrWhiteSpace(inputName.TabName))
11410                     return;
11411
11412                 id = inputName.TabName.Trim();
11413             }
11414
11415             using (var dialog = new WaitingDialog(Properties.Resources.FollowCommandText1))
11416             {
11417                 try
11418                 {
11419                     var task = Task.Run(() => this.tw.PostFollowCommand(id));
11420                     await dialog.WaitForAsync(this, task);
11421                 }
11422                 catch (WebApiException ex)
11423                 {
11424                     MessageBox.Show(Properties.Resources.FRMessage2 + ex.Message);
11425                     return;
11426                 }
11427             }
11428
11429             MessageBox.Show(Properties.Resources.FRMessage3);
11430         }
11431
11432         private async void RemoveCommandMenuItem_Click(object sender, EventArgs e)
11433         {
11434             var id = _curPost?.ScreenName ?? "";
11435
11436             await this.RemoveCommand(id, false);
11437         }
11438
11439         private async Task RemoveCommand(string id, bool skipInput)
11440         {
11441             if (!skipInput)
11442             {
11443                 using (var inputName = new InputTabName())
11444                 {
11445                     inputName.FormTitle = "Unfollow";
11446                     inputName.FormDescription = Properties.Resources.FRMessage1;
11447                     inputName.TabName = id;
11448
11449                     if (inputName.ShowDialog(this) != DialogResult.OK)
11450                         return;
11451                     if (string.IsNullOrWhiteSpace(inputName.TabName))
11452                         return;
11453
11454                     id = inputName.TabName.Trim();
11455                 }
11456             }
11457
11458             using (var dialog = new WaitingDialog(Properties.Resources.RemoveCommandText1))
11459             {
11460                 try
11461                 {
11462                     var task = Task.Run(() => this.tw.PostRemoveCommand(id));
11463                     await dialog.WaitForAsync(this, task);
11464                 }
11465                 catch (WebApiException ex)
11466                 {
11467                     MessageBox.Show(Properties.Resources.FRMessage2 + ex.Message);
11468                     return;
11469                 }
11470             }
11471
11472             MessageBox.Show(Properties.Resources.FRMessage3);
11473         }
11474
11475         private async void FriendshipMenuItem_Click(object sender, EventArgs e)
11476         {
11477             var id = _curPost?.ScreenName ?? "";
11478
11479             await this.ShowFriendship(id);
11480         }
11481
11482         private async Task ShowFriendship(string id)
11483         {
11484             using (var inputName = new InputTabName())
11485             {
11486                 inputName.FormTitle = "Show Friendships";
11487                 inputName.FormDescription = Properties.Resources.FRMessage1;
11488                 inputName.TabName = id;
11489
11490                 if (inputName.ShowDialog(this) != DialogResult.OK)
11491                     return;
11492                 if (string.IsNullOrWhiteSpace(inputName.TabName))
11493                     return;
11494
11495                 id = inputName.TabName.Trim();
11496             }
11497
11498             bool isFollowing, isFollowed;
11499
11500             using (var dialog = new WaitingDialog(Properties.Resources.ShowFriendshipText1))
11501             {
11502                 var cancellationToken = dialog.EnableCancellation();
11503
11504                 try
11505                 {
11506                     var task = Task.Run(() => this.tw.GetFriendshipInfo(id));
11507                     var friendship = await dialog.WaitForAsync(this, task);
11508
11509                     isFollowing = friendship.Relationship.Source.Following;
11510                     isFollowed = friendship.Relationship.Source.FollowedBy;
11511                 }
11512                 catch (WebApiException ex)
11513                 {
11514                     if (!cancellationToken.IsCancellationRequested)
11515                         MessageBox.Show(ex.Message);
11516                     return;
11517                 }
11518
11519                 if (cancellationToken.IsCancellationRequested)
11520                     return;
11521             }
11522
11523             string result = "";
11524             if (isFollowing)
11525             {
11526                 result = Properties.Resources.GetFriendshipInfo1 + System.Environment.NewLine;
11527             }
11528             else
11529             {
11530                 result = Properties.Resources.GetFriendshipInfo2 + System.Environment.NewLine;
11531             }
11532             if (isFollowed)
11533             {
11534                 result += Properties.Resources.GetFriendshipInfo3;
11535             }
11536             else
11537             {
11538                 result += Properties.Resources.GetFriendshipInfo4;
11539             }
11540             result = id + Properties.Resources.GetFriendshipInfo5 + System.Environment.NewLine + result;
11541             MessageBox.Show(result);
11542         }
11543
11544         private async Task ShowFriendship(string[] ids)
11545         {
11546             foreach (string id in ids)
11547             {
11548                 bool isFollowing, isFollowed;
11549
11550                 using (var dialog = new WaitingDialog(Properties.Resources.ShowFriendshipText1))
11551                 {
11552                     var cancellationToken = dialog.EnableCancellation();
11553
11554                     try
11555                     {
11556                         var task = Task.Run(() => this.tw.GetFriendshipInfo(id));
11557                         var friendship = await dialog.WaitForAsync(this, task);
11558
11559                         isFollowing = friendship.Relationship.Source.Following;
11560                         isFollowed = friendship.Relationship.Source.FollowedBy;
11561                     }
11562                     catch (WebApiException ex)
11563                     {
11564                         if (!cancellationToken.IsCancellationRequested)
11565                             MessageBox.Show(ex.Message);
11566                         return;
11567                     }
11568
11569                     if (cancellationToken.IsCancellationRequested)
11570                         return;
11571                 }
11572
11573                 string result = "";
11574                 string ff = "";
11575
11576                 ff = "  ";
11577                 if (isFollowing)
11578                 {
11579                     ff += Properties.Resources.GetFriendshipInfo1;
11580                 }
11581                 else
11582                 {
11583                     ff += Properties.Resources.GetFriendshipInfo2;
11584                 }
11585
11586                 ff += System.Environment.NewLine + "  ";
11587                 if (isFollowed)
11588                 {
11589                     ff += Properties.Resources.GetFriendshipInfo3;
11590                 }
11591                 else
11592                 {
11593                     ff += Properties.Resources.GetFriendshipInfo4;
11594                 }
11595                 result += id + Properties.Resources.GetFriendshipInfo5 + System.Environment.NewLine + ff;
11596                 if (isFollowing)
11597                 {
11598                     if (MessageBox.Show(
11599                         Properties.Resources.GetFriendshipInfo7 + System.Environment.NewLine + result, Properties.Resources.GetFriendshipInfo8,
11600                         MessageBoxButtons.YesNo,
11601                         MessageBoxIcon.Question,
11602                         MessageBoxDefaultButton.Button2) == DialogResult.Yes)
11603                     {
11604                         await this.RemoveCommand(id, true);
11605                     }
11606                 }
11607                 else
11608                 {
11609                     MessageBox.Show(result);
11610                 }
11611             }
11612         }
11613
11614         private async void OwnStatusMenuItem_Click(object sender, EventArgs e)
11615         {
11616             await this.doShowUserStatus(tw.Username, false);
11617             //if (!string.IsNullOrEmpty(tw.UserInfoXml))
11618             //{
11619             //    doShowUserStatus(tw.Username, false);
11620             //}
11621             //else
11622             //{
11623             //    MessageBox.Show(Properties.Resources.ShowYourProfileText1, "Your status", MessageBoxButtons.OK, MessageBoxIcon.Information);
11624             //    return;
11625             //}
11626         }
11627
11628         // TwitterIDでない固定文字列を調べる(文字列検証のみ 実際に取得はしない)
11629         // URLから切り出した文字列を渡す
11630
11631         public bool IsTwitterId(string name)
11632         {
11633             if (this.tw.Configuration.NonUsernamePaths == null || this.tw.Configuration.NonUsernamePaths.Length == 0)
11634                 return !Regex.Match(name, @"^(about|jobs|tos|privacy|who_to_follow|download|messages)$", RegexOptions.IgnoreCase).Success;
11635             else
11636                 return !this.tw.Configuration.NonUsernamePaths.Contains(name.ToLower());
11637         }
11638
11639         private string GetUserId()
11640         {
11641             Match m = Regex.Match(this._postBrowserStatusText, @"^https?://twitter.com/(#!/)?(?<ScreenName>[a-zA-Z0-9_]+)(/status(es)?/[0-9]+)?$");
11642             if (m.Success && IsTwitterId(m.Result("${ScreenName}")))
11643                 return m.Result("${ScreenName}");
11644             else
11645                 return null;
11646         }
11647
11648         private async void FollowContextMenuItem_Click(object sender, EventArgs e)
11649         {
11650             string name = GetUserId();
11651             if (name != null)
11652                 await this.FollowCommand(name);
11653         }
11654
11655         private async void RemoveContextMenuItem_Click(object sender, EventArgs e)
11656         {
11657             string name = GetUserId();
11658             if (name != null)
11659                 await this.RemoveCommand(name, false);
11660         }
11661
11662         private async void FriendshipContextMenuItem_Click(object sender, EventArgs e)
11663         {
11664             string name = GetUserId();
11665             if (name != null)
11666                 await this.ShowFriendship(name);
11667         }
11668
11669         private async void FriendshipAllMenuItem_Click(object sender, EventArgs e)
11670         {
11671             MatchCollection ma = Regex.Matches(this.PostBrowser.DocumentText, @"href=""https?://twitter.com/(#!/)?(?<ScreenName>[a-zA-Z0-9_]+)(/status(es)?/[0-9]+)?""");
11672             List<string> ids = new List<string>();
11673             foreach (Match mu in ma)
11674             {
11675                 if (mu.Result("${ScreenName}").ToLower() != tw.Username.ToLower())
11676                 {
11677                     ids.Add(mu.Result("${ScreenName}"));
11678                 }
11679             }
11680
11681             await this.ShowFriendship(ids.ToArray());
11682         }
11683
11684         private async void ShowUserStatusContextMenuItem_Click(object sender, EventArgs e)
11685         {
11686             string name = GetUserId();
11687             if (name != null)
11688                 await this.ShowUserStatus(name);
11689         }
11690
11691         private void SearchPostsDetailToolStripMenuItem_Click(object sender, EventArgs e)
11692         {
11693             string name = GetUserId();
11694             if (name != null) AddNewTabForUserTimeline(name);
11695         }
11696
11697         private void SearchAtPostsDetailToolStripMenuItem_Click(object sender, EventArgs e)
11698         {
11699             string name = GetUserId();
11700             if (name != null) AddNewTabForSearch("@" + name);
11701         }
11702
11703         private void IdeographicSpaceToSpaceToolStripMenuItem_Click(object sender, EventArgs e)
11704         {
11705             _modifySettingCommon = true;
11706         }
11707
11708         private void ToolStripFocusLockMenuItem_CheckedChanged(object sender, EventArgs e)
11709         {
11710             _modifySettingCommon = true;
11711         }
11712
11713         private void doQuoteOfficial()
11714         {
11715             if (this.ExistCurrentPost)
11716             {
11717                 if (_curPost.IsDm ||
11718                     !StatusText.Enabled) return;
11719
11720                 if (_curPost.IsProtect)
11721                 {
11722                     MessageBox.Show("Protected.");
11723                     return;
11724                 }
11725
11726                 StatusText.Text = " " + MyCommon.GetStatusUrl(_curPost);
11727
11728                 this.inReplyTo = null;
11729
11730                 StatusText.SelectionStart = 0;
11731                 StatusText.Focus();
11732             }
11733         }
11734
11735         private void doReTweetUnofficial()
11736         {
11737             //RT @id:内容
11738             if (this.ExistCurrentPost)
11739             {
11740                 if (_curPost.IsDm || !StatusText.Enabled)
11741                     return;
11742
11743                 if (_curPost.IsProtect)
11744                 {
11745                     MessageBox.Show("Protected.");
11746                     return;
11747                 }
11748                 string rtdata = _curPost.Text;
11749                 rtdata = CreateRetweetUnofficial(rtdata, this.StatusText.Multiline);
11750
11751                 StatusText.Text = " RT @" + _curPost.ScreenName + ": " + rtdata;
11752
11753                 // 投稿時に in_reply_to_status_id を付加する
11754                 var inReplyToStatusId = this._curPost.RetweetedId ?? this._curPost.StatusId;
11755                 var inReplyToScreenName = this._curPost.ScreenName;
11756                 this.inReplyTo = Tuple.Create(inReplyToStatusId, inReplyToScreenName);
11757
11758                 StatusText.SelectionStart = 0;
11759                 StatusText.Focus();
11760             }
11761         }
11762
11763         private void QuoteStripMenuItem_Click(object sender, EventArgs e) // Handles QuoteStripMenuItem.Click, QtOpMenuItem.Click
11764         {
11765             doQuoteOfficial();
11766         }
11767
11768         private void SearchButton_Click(object sender, EventArgs e)
11769         {
11770             //公式検索
11771             Control pnl = ((Control)sender).Parent;
11772             if (pnl == null) return;
11773             string tbName = pnl.Parent.Text;
11774             TabClass tb = _statuses.Tabs[tbName];
11775             ComboBox cmb = (ComboBox)pnl.Controls["comboSearch"];
11776             ComboBox cmbLang = (ComboBox)pnl.Controls["comboLang"];
11777             cmb.Text = cmb.Text.Trim();
11778             // 検索式演算子 OR についてのみ大文字しか認識しないので強制的に大文字とする
11779             bool Quote = false;
11780             StringBuilder buf = new StringBuilder();
11781             char[] c = cmb.Text.ToCharArray();
11782             for (int cnt = 0; cnt < cmb.Text.Length; cnt++)
11783             {
11784                 if (cnt > cmb.Text.Length - 4)
11785                 {
11786                     buf.Append(cmb.Text.Substring(cnt));
11787                     break;
11788                 }
11789                 if (c[cnt] == '"')
11790                 {
11791                     Quote = !Quote;
11792                 }
11793                 else
11794                 {
11795                     if (!Quote && cmb.Text.Substring(cnt, 4).Equals(" or ", StringComparison.OrdinalIgnoreCase))
11796                     {
11797                         buf.Append(" OR ");
11798                         cnt += 3;
11799                         continue;
11800                     }
11801                 }
11802                 buf.Append(c[cnt]);
11803             }
11804             cmb.Text = buf.ToString();
11805
11806             var listView = (DetailsListView)pnl.Parent.Tag;
11807
11808             tb.SearchWords = cmb.Text;
11809             tb.SearchLang = cmbLang.Text;
11810             if (string.IsNullOrEmpty(cmb.Text))
11811             {
11812                 listView.Focus();
11813                 SaveConfigsTabs();
11814                 return;
11815             }
11816             if (tb.IsSearchQueryChanged)
11817             {
11818                 int idx = cmb.Items.IndexOf(tb.SearchWords);
11819                 if (idx > -1) cmb.Items.RemoveAt(idx);
11820                 cmb.Items.Insert(0, tb.SearchWords);
11821                 cmb.Text = tb.SearchWords;
11822                 cmb.SelectAll();
11823                 this.PurgeListViewItemCache();
11824                 listView.VirtualListSize = 0;
11825                 _statuses.ClearTabIds(tbName);
11826                 SaveConfigsTabs();   //検索条件の保存
11827             }
11828
11829             this.GetPublicSearchAsync(tb);
11830             listView.Focus();
11831         }
11832
11833         private async void RefreshMoreStripMenuItem_Click(object sender, EventArgs e)
11834         {
11835             //もっと前を取得
11836             await this.DoRefreshMore();
11837         }
11838
11839         /// <summary>
11840         /// 指定されたタブのListTabにおける位置を返します
11841         /// </summary>
11842         /// <remarks>
11843         /// 非表示のタブについて -1 が返ることを常に考慮して下さい
11844         /// </remarks>
11845         public int GetTabPageIndex(string tabName)
11846         {
11847             var index = 0;
11848             foreach (var tabPage in this.ListTab.TabPages.Cast<TabPage>())
11849             {
11850                 if (tabPage.Text == tabName)
11851                     return index;
11852
11853                 index++;
11854             }
11855
11856             return -1;
11857         }
11858
11859         private void UndoRemoveTabMenuItem_Click(object sender, EventArgs e)
11860         {
11861             if (_statuses.RemovedTab.Count == 0)
11862             {
11863                 MessageBox.Show("There isn't removed tab.", "Undo", MessageBoxButtons.OK, MessageBoxIcon.Information);
11864                 return;
11865             }
11866             else
11867             {
11868                 DetailsListView listView = null;
11869
11870                 TabClass tb = _statuses.RemovedTab.Pop();
11871                 if (tb.TabType == MyCommon.TabUsageType.Related)
11872                 {
11873                     var relatedTab = _statuses.GetTabByType(MyCommon.TabUsageType.Related);
11874                     if (relatedTab != null)
11875                     {
11876                         // 関連発言なら既存のタブを置き換える
11877                         tb.TabName = relatedTab.TabName;
11878                         this.ClearTab(tb.TabName, false);
11879                         _statuses.Tabs[tb.TabName] = tb;
11880
11881                         for (int i = 0; i < ListTab.TabPages.Count; i++)
11882                         {
11883                             var tabPage = ListTab.TabPages[i];
11884                             if (tb.TabName == tabPage.Text)
11885                             {
11886                                 listView = (DetailsListView)tabPage.Tag;
11887                                 ListTab.SelectedIndex = i;
11888                                 break;
11889                             }
11890                         }
11891                     }
11892                     else
11893                     {
11894                         const string TabName = "Related Tweets";
11895                         string renamed = TabName;
11896                         for (int i = 2; i <= 100; i++)
11897                         {
11898                             if (!_statuses.ContainsTab(renamed)) break;
11899                             renamed = TabName + i.ToString();
11900                         }
11901                         tb.TabName = renamed;
11902                         AddNewTab(renamed, false, tb.TabType, tb.ListInfo);
11903                         _statuses.Tabs.Add(renamed, tb);  // 後に
11904
11905                         var tabPage = ListTab.TabPages[ListTab.TabPages.Count - 1];
11906                         listView = (DetailsListView)tabPage.Tag;
11907                         ListTab.SelectedIndex = ListTab.TabPages.Count - 1;
11908                     }
11909                 }
11910                 else
11911                 {
11912                     string renamed = tb.TabName;
11913                     for (int i = 1; i < int.MaxValue; i++)
11914                     {
11915                         if (!_statuses.ContainsTab(renamed)) break;
11916                         renamed = tb.TabName + "(" + i.ToString() + ")";
11917                     }
11918                     tb.TabName = renamed;
11919                     _statuses.Tabs.Add(renamed, tb);  // 先に
11920                     AddNewTab(renamed, false, tb.TabType, tb.ListInfo);
11921
11922                     var tabPage = ListTab.TabPages[ListTab.TabPages.Count - 1];
11923                     listView = (DetailsListView)tabPage.Tag;
11924                     ListTab.SelectedIndex = ListTab.TabPages.Count - 1;
11925                 }
11926                 SaveConfigsTabs();
11927
11928                 if (listView != null)
11929                 {
11930                     using (ControlTransaction.Update(listView))
11931                     {
11932                         listView.VirtualListSize = tb.AllCount;
11933                     }
11934                 }
11935             }
11936         }
11937
11938         private async Task doMoveToRTHome()
11939         {
11940             if (_curList.SelectedIndices.Count > 0)
11941             {
11942                 PostClass post = GetCurTabPost(_curList.SelectedIndices[0]);
11943                 if (post.RetweetedId != null)
11944                 {
11945                     await this.OpenUriInBrowserAsync("https://twitter.com/" + GetCurTabPost(_curList.SelectedIndices[0]).RetweetedBy);
11946                 }
11947             }
11948         }
11949
11950         private async void MoveToRTHomeMenuItem_Click(object sender, EventArgs e)
11951         {
11952             await this.doMoveToRTHome();
11953         }
11954
11955         private void IdFilterAddMenuItem_Click(object sender, EventArgs e)
11956         {
11957             string name = GetUserId();
11958             if (name != null)
11959             {
11960                 string tabName;
11961
11962                 //未選択なら処理終了
11963                 if (_curList.SelectedIndices.Count == 0) return;
11964
11965                 //タブ選択(or追加)
11966                 if (!SelectTab(out tabName)) return;
11967
11968                 var tab = this._statuses.Tabs[tabName];
11969
11970                 bool mv;
11971                 bool mk;
11972                 if (tab.TabType != MyCommon.TabUsageType.Mute)
11973                 {
11974                     this.MoveOrCopy(out mv, out mk);
11975                 }
11976                 else
11977                 {
11978                     // ミュートタブでは常に MoveMatches を true にする
11979                     mv = true;
11980                     mk = false;
11981                 }
11982
11983                 PostFilterRule fc = new PostFilterRule();
11984                 fc.FilterName = name;
11985                 fc.UseNameField = true;
11986                 fc.MoveMatches = mv;
11987                 fc.MarkMatches = mk;
11988                 fc.UseRegex = false;
11989                 fc.FilterByUrl = false;
11990                 tab.AddFilter(fc);
11991
11992                 this.ApplyPostFilters();
11993                 SaveConfigsTabs();
11994             }
11995         }
11996
11997         private void ListManageUserContextToolStripMenuItem_Click(object sender, EventArgs e)
11998         {
11999             string user;
12000
12001             ToolStripMenuItem menuItem = (ToolStripMenuItem)sender;
12002
12003             if (menuItem.Owner == this.ContextMenuPostBrowser)
12004             {
12005                 user = GetUserId();
12006                 if (user == null) return;
12007             }
12008             else if (this._curPost != null)
12009             {
12010                 user = this._curPost.ScreenName;
12011             }
12012             else
12013             {
12014                 return;
12015             }
12016
12017             if (TabInformations.GetInstance().SubscribableLists.Count == 0)
12018             {
12019                 try
12020                 {
12021                     this.tw.GetListsApi();
12022                 }
12023                 catch (WebApiException ex)
12024                 {
12025                     MessageBox.Show("Failed to get lists. (" + ex.Message + ")");
12026                     return;
12027                 }
12028             }
12029
12030             using (MyLists listSelectForm = new MyLists(user, this.tw))
12031             {
12032                 listSelectForm.ShowDialog(this);
12033             }
12034         }
12035
12036         private void SearchControls_Enter(object sender, EventArgs e)
12037         {
12038             Control pnl = (Control)sender;
12039             foreach (Control ctl in pnl.Controls)
12040             {
12041                 ctl.TabStop = true;
12042             }
12043         }
12044
12045         private void SearchControls_Leave(object sender, EventArgs e)
12046         {
12047             Control pnl = (Control)sender;
12048             foreach (Control ctl in pnl.Controls)
12049             {
12050                 ctl.TabStop = false;
12051             }
12052         }
12053
12054         private void PublicSearchQueryMenuItem_Click(object sender, EventArgs e)
12055         {
12056             if (ListTab.SelectedTab != null)
12057             {
12058                 if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType != MyCommon.TabUsageType.PublicSearch) return;
12059                 ListTab.SelectedTab.Controls["panelSearch"].Controls["comboSearch"].Focus();
12060             }
12061         }
12062
12063         private void UseHashtagMenuItem_Click(object sender, EventArgs e)
12064         {
12065             Match m = Regex.Match(this._postBrowserStatusText, @"^https?://twitter.com/search\?q=%23(?<hash>.+)$");
12066             if (m.Success)
12067             {
12068                 HashMgr.SetPermanentHash("#" + Uri.UnescapeDataString(m.Result("${hash}")));
12069                 HashStripSplitButton.Text = HashMgr.UseHash;
12070                 HashToggleMenuItem.Checked = true;
12071                 HashToggleToolStripMenuItem.Checked = true;
12072                 //使用ハッシュタグとして設定
12073                 _modifySettingCommon = true;
12074             }
12075         }
12076
12077         private void StatusLabel_DoubleClick(object sender, EventArgs e)
12078         {
12079             MessageBox.Show(StatusLabel.TextHistory, "Logs", MessageBoxButtons.OK, MessageBoxIcon.None);
12080         }
12081
12082         private void HashManageMenuItem_Click(object sender, EventArgs e)
12083         {
12084             DialogResult rslt = DialogResult.Cancel;
12085             try
12086             {
12087                 rslt = HashMgr.ShowDialog();
12088             }
12089             catch (Exception)
12090             {
12091                 return;
12092             }
12093             this.TopMost = this._cfgCommon.AlwaysTop;
12094             if (rslt == DialogResult.Cancel) return;
12095             if (!string.IsNullOrEmpty(HashMgr.UseHash))
12096             {
12097                 HashStripSplitButton.Text = HashMgr.UseHash;
12098                 HashToggleMenuItem.Checked = true;
12099                 HashToggleToolStripMenuItem.Checked = true;
12100             }
12101             else
12102             {
12103                 HashStripSplitButton.Text = "#[-]";
12104                 HashToggleMenuItem.Checked = false;
12105                 HashToggleToolStripMenuItem.Checked = false;
12106             }
12107             //if (HashMgr.IsInsert && HashMgr.UseHash != "")
12108             //{
12109             //    int sidx = StatusText.SelectionStart;
12110             //    string hash = HashMgr.UseHash + " ";
12111             //    if (sidx > 0)
12112             //    {
12113             //        if (StatusText.Text.Substring(sidx - 1, 1) != " ")
12114             //            hash = " " + hash;
12115             //    }
12116             //    StatusText.Text = StatusText.Text.Insert(sidx, hash);
12117             //    sidx += hash.Length;
12118             //    StatusText.SelectionStart = sidx;
12119             //    StatusText.Focus();
12120             //}
12121             _modifySettingCommon = true;
12122             this.StatusText_TextChanged(null, null);
12123         }
12124
12125         private void HashToggleMenuItem_Click(object sender, EventArgs e)
12126         {
12127             HashMgr.ToggleHash();
12128             if (!string.IsNullOrEmpty(HashMgr.UseHash))
12129             {
12130                 HashStripSplitButton.Text = HashMgr.UseHash;
12131                 HashToggleMenuItem.Checked = true;
12132                 HashToggleToolStripMenuItem.Checked = true;
12133             }
12134             else
12135             {
12136                 HashStripSplitButton.Text = "#[-]";
12137                 HashToggleMenuItem.Checked = false;
12138                 HashToggleToolStripMenuItem.Checked = false;
12139             }
12140             _modifySettingCommon = true;
12141             this.StatusText_TextChanged(null, null);
12142         }
12143
12144         private void HashStripSplitButton_ButtonClick(object sender, EventArgs e)
12145         {
12146             HashToggleMenuItem_Click(null, null);
12147         }
12148
12149         private void MenuItemOperate_DropDownOpening(object sender, EventArgs e)
12150         {
12151             if (ListTab.SelectedTab == null) return;
12152             if (_statuses == null || _statuses.Tabs == null || !_statuses.Tabs.ContainsKey(ListTab.SelectedTab.Text)) return;
12153             if (!this.ExistCurrentPost)
12154             {
12155                 this.ReplyOpMenuItem.Enabled = false;
12156                 this.ReplyAllOpMenuItem.Enabled = false;
12157                 this.DmOpMenuItem.Enabled = false;
12158                 this.ShowProfMenuItem.Enabled = false;
12159                 this.ShowUserTimelineToolStripMenuItem.Enabled = false;
12160                 this.ListManageMenuItem.Enabled = false;
12161                 this.OpenFavOpMenuItem.Enabled = false;
12162                 this.CreateTabRuleOpMenuItem.Enabled = false;
12163                 this.CreateIdRuleOpMenuItem.Enabled = false;
12164                 this.CreateSourceRuleOpMenuItem.Enabled = false;
12165                 this.ReadOpMenuItem.Enabled = false;
12166                 this.UnreadOpMenuItem.Enabled = false;
12167             }
12168             else
12169             {
12170                 this.ReplyOpMenuItem.Enabled = true;
12171                 this.ReplyAllOpMenuItem.Enabled = true;
12172                 this.DmOpMenuItem.Enabled = true;
12173                 this.ShowProfMenuItem.Enabled = true;
12174                 this.ShowUserTimelineToolStripMenuItem.Enabled = true;
12175                 this.ListManageMenuItem.Enabled = true;
12176                 this.OpenFavOpMenuItem.Enabled = true;
12177                 this.CreateTabRuleOpMenuItem.Enabled = true;
12178                 this.CreateIdRuleOpMenuItem.Enabled = true;
12179                 this.CreateSourceRuleOpMenuItem.Enabled = true;
12180                 this.ReadOpMenuItem.Enabled = true;
12181                 this.UnreadOpMenuItem.Enabled = true;
12182             }
12183
12184             if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType == MyCommon.TabUsageType.DirectMessage || !this.ExistCurrentPost || _curPost.IsDm)
12185             {
12186                 this.FavOpMenuItem.Enabled = false;
12187                 this.UnFavOpMenuItem.Enabled = false;
12188                 this.OpenStatusOpMenuItem.Enabled = false;
12189                 this.OpenFavotterOpMenuItem.Enabled = false;
12190                 this.ShowRelatedStatusesMenuItem2.Enabled = false;
12191                 this.RtOpMenuItem.Enabled = false;
12192                 this.RtUnOpMenuItem.Enabled = false;
12193                 this.QtOpMenuItem.Enabled = false;
12194                 this.FavoriteRetweetMenuItem.Enabled = false;
12195                 this.FavoriteRetweetUnofficialMenuItem.Enabled = false;
12196             }
12197             else
12198             {
12199                 this.FavOpMenuItem.Enabled = true;
12200                 this.UnFavOpMenuItem.Enabled = true;
12201                 this.OpenStatusOpMenuItem.Enabled = true;
12202                 this.OpenFavotterOpMenuItem.Enabled = true;
12203                 this.ShowRelatedStatusesMenuItem2.Enabled = true;  //PublicSearchの時問題出るかも
12204
12205                 if (_curPost.IsMe)
12206                 {
12207                     this.RtOpMenuItem.Enabled = false;  //公式RTは無効に
12208                     this.RtUnOpMenuItem.Enabled = true;
12209                     this.QtOpMenuItem.Enabled = true;
12210                     this.FavoriteRetweetMenuItem.Enabled = false;  //公式RTは無効に
12211                     this.FavoriteRetweetUnofficialMenuItem.Enabled = true;
12212                 }
12213                 else
12214                 {
12215                     if (_curPost.IsProtect)
12216                     {
12217                         this.RtOpMenuItem.Enabled = false;
12218                         this.RtUnOpMenuItem.Enabled = false;
12219                         this.QtOpMenuItem.Enabled = false;
12220                         this.FavoriteRetweetMenuItem.Enabled = false;
12221                         this.FavoriteRetweetUnofficialMenuItem.Enabled = false;
12222                     }
12223                     else
12224                     {
12225                         this.RtOpMenuItem.Enabled = true;
12226                         this.RtUnOpMenuItem.Enabled = true;
12227                         this.QtOpMenuItem.Enabled = true;
12228                         this.FavoriteRetweetMenuItem.Enabled = true;
12229                         this.FavoriteRetweetUnofficialMenuItem.Enabled = true;
12230                     }
12231                 }
12232             }
12233
12234             if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType != MyCommon.TabUsageType.Favorites)
12235             {
12236                 this.RefreshPrevOpMenuItem.Enabled = true;
12237             }
12238             else
12239             {
12240                 this.RefreshPrevOpMenuItem.Enabled = false;
12241             }
12242             if (!this.ExistCurrentPost
12243                 || _curPost.InReplyToStatusId == null)
12244             {
12245                 OpenRepSourceOpMenuItem.Enabled = false;
12246             }
12247             else
12248             {
12249                 OpenRepSourceOpMenuItem.Enabled = true;
12250             }
12251             if (!this.ExistCurrentPost || string.IsNullOrEmpty(_curPost.RetweetedBy))
12252             {
12253                 OpenRterHomeMenuItem.Enabled = false;
12254             }
12255             else
12256             {
12257                 OpenRterHomeMenuItem.Enabled = true;
12258             }
12259
12260             if (this.ExistCurrentPost)
12261             {
12262                 this.DelOpMenuItem.Enabled = this._curPost.CanDeleteBy(this.tw.UserId);
12263             }
12264         }
12265
12266         private void MenuItemTab_DropDownOpening(object sender, EventArgs e)
12267         {
12268             ContextMenuTabProperty_Opening(sender, null);
12269         }
12270
12271         public Twitter TwitterInstance
12272         {
12273             get { return tw; }
12274         }
12275
12276         private void SplitContainer3_SplitterMoved(object sender, SplitterEventArgs e)
12277         {
12278             if (this.WindowState == FormWindowState.Normal && !_initialLayout)
12279             {
12280                 _mySpDis3 = SplitContainer3.SplitterDistance;
12281                 _modifySettingLocal = true;
12282             }
12283         }
12284
12285         private void MenuItemEdit_DropDownOpening(object sender, EventArgs e)
12286         {
12287             if (_statuses.RemovedTab.Count == 0)
12288             {
12289                 UndoRemoveTabMenuItem.Enabled = false;
12290             }
12291             else
12292             {
12293                 UndoRemoveTabMenuItem.Enabled = true;
12294             }
12295             if (ListTab.SelectedTab != null)
12296             {
12297                 if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType == MyCommon.TabUsageType.PublicSearch)
12298                     PublicSearchQueryMenuItem.Enabled = true;
12299                 else
12300                     PublicSearchQueryMenuItem.Enabled = false;
12301             }
12302             else
12303             {
12304                 PublicSearchQueryMenuItem.Enabled = false;
12305             }
12306             if (!this.ExistCurrentPost)
12307             {
12308                 this.CopySTOTMenuItem.Enabled = false;
12309                 this.CopyURLMenuItem.Enabled = false;
12310                 this.CopyUserIdStripMenuItem.Enabled = false;
12311             }
12312             else
12313             {
12314                 this.CopySTOTMenuItem.Enabled = true;
12315                 this.CopyURLMenuItem.Enabled = true;
12316                 this.CopyUserIdStripMenuItem.Enabled = true;
12317                 if (_curPost.IsDm) this.CopyURLMenuItem.Enabled = false;
12318                 if (_curPost.IsProtect) this.CopySTOTMenuItem.Enabled = false;
12319             }
12320         }
12321
12322         private void NotifyIcon1_MouseMove(object sender, MouseEventArgs e)
12323         {
12324             SetNotifyIconText();
12325         }
12326
12327         private async void UserStatusToolStripMenuItem_Click(object sender, EventArgs e)
12328         {
12329             var id = _curPost?.ScreenName ?? "";
12330
12331             await this.ShowUserStatus(id);
12332         }
12333
12334         private async Task doShowUserStatus(string id, bool ShowInputDialog)
12335         {
12336             TwitterUser user = null;
12337
12338             if (ShowInputDialog)
12339             {
12340                 using (var inputName = new InputTabName())
12341                 {
12342                     inputName.FormTitle = "Show UserStatus";
12343                     inputName.FormDescription = Properties.Resources.FRMessage1;
12344                     inputName.TabName = id;
12345
12346                     if (inputName.ShowDialog(this) != DialogResult.OK)
12347                         return;
12348                     if (string.IsNullOrWhiteSpace(inputName.TabName))
12349                         return;
12350
12351                     id = inputName.TabName.Trim();
12352                 }
12353             }
12354
12355             using (var dialog = new WaitingDialog(Properties.Resources.doShowUserStatusText1))
12356             {
12357                 var cancellationToken = dialog.EnableCancellation();
12358
12359                 try
12360                 {
12361                     var task = Task.Run(() => this.tw.GetUserInfo(id));
12362                     user = await dialog.WaitForAsync(this, task);
12363                 }
12364                 catch (WebApiException ex)
12365                 {
12366                     if (!cancellationToken.IsCancellationRequested)
12367                         MessageBox.Show(ex.Message);
12368                     return;
12369                 }
12370
12371                 if (cancellationToken.IsCancellationRequested)
12372                     return;
12373             }
12374
12375             await this.doShowUserStatus(user);
12376         }
12377
12378         private async Task doShowUserStatus(TwitterUser user)
12379         {
12380             using (var userDialog = new UserInfoDialog(this, this.tw))
12381             {
12382                 var showUserTask = userDialog.ShowUserAsync(user);
12383                 userDialog.ShowDialog(this);
12384
12385                 this.Activate();
12386                 this.BringToFront();
12387
12388                 // ユーザー情報の表示が完了するまで userDialog を破棄しない
12389                 await showUserTask;
12390             }
12391         }
12392
12393         private Task ShowUserStatus(string id, bool ShowInputDialog)
12394         {
12395             return this.doShowUserStatus(id, ShowInputDialog);
12396         }
12397
12398         private Task ShowUserStatus(string id)
12399         {
12400             return this.doShowUserStatus(id, true);
12401         }
12402
12403         private async void FollowToolStripMenuItem_Click(object sender, EventArgs e)
12404         {
12405             if (NameLabel.Tag != null)
12406             {
12407                 string id = (string)NameLabel.Tag;
12408                 if (id != tw.Username)
12409                 {
12410                     await this.FollowCommand(id);
12411                 }
12412             }
12413         }
12414
12415         private async void UnFollowToolStripMenuItem_Click(object sender, EventArgs e)
12416         {
12417             if (NameLabel.Tag != null)
12418             {
12419                 string id = (string)NameLabel.Tag;
12420                 if (id != tw.Username)
12421                 {
12422                     await this.RemoveCommand(id, false);
12423                 }
12424             }
12425         }
12426
12427         private async void ShowFriendShipToolStripMenuItem_Click(object sender, EventArgs e)
12428         {
12429             if (NameLabel.Tag != null)
12430             {
12431                 string id = (string)NameLabel.Tag;
12432                 if (id != tw.Username)
12433                 {
12434                     await this.ShowFriendship(id);
12435                 }
12436             }
12437         }
12438
12439         private async void ShowUserStatusToolStripMenuItem_Click(object sender, EventArgs e)
12440         {
12441             if (NameLabel.Tag != null)
12442             {
12443                 string id = (string)NameLabel.Tag;
12444                 await this.ShowUserStatus(id, false);
12445             }
12446         }
12447
12448         private void SearchPostsDetailNameToolStripMenuItem_Click(object sender, EventArgs e)
12449         {
12450             if (NameLabel.Tag != null)
12451             {
12452                 string id = (string)NameLabel.Tag;
12453                 AddNewTabForUserTimeline(id);
12454             }
12455         }
12456
12457         private void SearchAtPostsDetailNameToolStripMenuItem_Click(object sender, EventArgs e)
12458         {
12459             if (NameLabel.Tag != null)
12460             {
12461                 string id = (string)NameLabel.Tag;
12462                 AddNewTabForSearch("@" + id);
12463             }
12464         }
12465
12466         private async void ShowProfileMenuItem_Click(object sender, EventArgs e)
12467         {
12468             if (_curPost != null)
12469             {
12470                 await this.ShowUserStatus(_curPost.ScreenName, false);
12471             }
12472         }
12473
12474         private async void RtCountMenuItem_Click(object sender, EventArgs e)
12475         {
12476             if (!this.ExistCurrentPost)
12477                 return;
12478
12479             var statusId = this._curPost.RetweetedId ?? this._curPost.StatusId;
12480             int retweetCount = 0;
12481
12482             using (var dialog = new WaitingDialog(Properties.Resources.RtCountMenuItem_ClickText1))
12483             {
12484                 var cancellationToken = dialog.EnableCancellation();
12485
12486                 try
12487                 {
12488                     var task = Task.Run(() => this.tw.GetStatus_Retweeted_Count(statusId));
12489                     retweetCount = await dialog.WaitForAsync(this, task);
12490                 }
12491                 catch (WebApiException ex)
12492                 {
12493                     if (!cancellationToken.IsCancellationRequested)
12494                         MessageBox.Show(Properties.Resources.RtCountText2 + Environment.NewLine + ex.Message);
12495                     return;
12496                 }
12497
12498                 if (cancellationToken.IsCancellationRequested)
12499                     return;
12500             }
12501
12502             MessageBox.Show(retweetCount + Properties.Resources.RtCountText1);
12503         }
12504
12505         private HookGlobalHotkey _hookGlobalHotkey;
12506         public TweenMain()
12507         {
12508             _hookGlobalHotkey = new HookGlobalHotkey(this);
12509
12510             // この呼び出しは、Windows フォーム デザイナで必要です。
12511             InitializeComponent();
12512
12513             // InitializeComponent() 呼び出しの後で初期化を追加します。
12514
12515             if (!this.DesignMode)
12516             {
12517                 // デザイナでの編集時にレイアウトが縦方向に数pxずれる問題の対策
12518                 this.StatusText.Dock = DockStyle.Fill;
12519             }
12520
12521             this.TimerTimeline.Elapsed += this.TimerTimeline_Elapsed;
12522             this._hookGlobalHotkey.HotkeyPressed += _hookGlobalHotkey_HotkeyPressed;
12523             this.gh.NotifyClicked += GrowlHelper_Callback;
12524
12525             // メイリオフォント指定時にタブの最小幅が広くなる問題の対策
12526             this.ListTab.HandleCreated += (s, e) => NativeMethods.SetMinTabWidth((TabControl)s, 40);
12527
12528             this.ImageSelector.Visible = false;
12529             this.ImageSelector.Enabled = false;
12530             this.ImageSelector.FilePickDialog = OpenFileDialog1;
12531
12532             this.ReplaceAppName();
12533             this.InitializeShortcuts();
12534         }
12535
12536         private void _hookGlobalHotkey_HotkeyPressed(object sender, KeyEventArgs e)
12537         {
12538             if ((this.WindowState == FormWindowState.Normal || this.WindowState == FormWindowState.Maximized) && this.Visible && Form.ActiveForm == this)
12539             {
12540                 //アイコン化
12541                 this.Visible = false;
12542             }
12543             else if (Form.ActiveForm == null)
12544             {
12545                 this.Visible = true;
12546                 if (this.WindowState == FormWindowState.Minimized) this.WindowState = FormWindowState.Normal;
12547                 this.Activate();
12548                 this.BringToFront();
12549                 this.StatusText.Focus();
12550             }
12551         }
12552
12553         private void UserPicture_MouseEnter(object sender, EventArgs e)
12554         {
12555             this.UserPicture.Cursor = Cursors.Hand;
12556         }
12557
12558         private void UserPicture_MouseLeave(object sender, EventArgs e)
12559         {
12560             this.UserPicture.Cursor = Cursors.Default;
12561         }
12562
12563         private async void UserPicture_DoubleClick(object sender, EventArgs e)
12564         {
12565             if (NameLabel.Tag != null)
12566             {
12567                 await this.OpenUriInBrowserAsync(MyCommon.TwitterUrl + NameLabel.Tag.ToString());
12568             }
12569         }
12570
12571         private void SplitContainer2_MouseDoubleClick(object sender, MouseEventArgs e)
12572         {
12573             this.MultiLineMenuItem.PerformClick();
12574         }
12575
12576         public PostClass CurPost
12577         {
12578             get { return _curPost; }
12579         }
12580
12581 #region "画像投稿"
12582         private void ImageSelectMenuItem_Click(object sender, EventArgs e)
12583         {
12584             if (ImageSelector.Visible)
12585                 ImageSelector.EndSelection();
12586             else
12587                 ImageSelector.BeginSelection();
12588         }
12589
12590         private void SelectMedia_DragEnter(DragEventArgs e)
12591         {
12592             if (ImageSelector.HasUploadableService(((string[])e.Data.GetData(DataFormats.FileDrop, false))[0], true))
12593             {
12594                 e.Effect = DragDropEffects.Copy;
12595                 return;
12596             }
12597             e.Effect = DragDropEffects.None;
12598         }
12599
12600         private void SelectMedia_DragDrop(DragEventArgs e)
12601         {
12602             this.Activate();
12603             this.BringToFront();
12604             ImageSelector.BeginSelection((string[])e.Data.GetData(DataFormats.FileDrop, false));
12605             StatusText.Focus();
12606         }
12607
12608         private void ImageSelector_BeginSelecting(object sender, EventArgs e)
12609         {
12610             TimelinePanel.Visible = false;
12611             TimelinePanel.Enabled = false;
12612         }
12613
12614         private void ImageSelector_EndSelecting(object sender, EventArgs e)
12615         {
12616             TimelinePanel.Visible = true;
12617             TimelinePanel.Enabled = true;
12618             ((DetailsListView)ListTab.SelectedTab.Tag).Focus();
12619         }
12620
12621         private void ImageSelector_FilePickDialogOpening(object sender, EventArgs e)
12622         {
12623             this.AllowDrop = false;
12624         }
12625
12626         private void ImageSelector_FilePickDialogClosed(object sender, EventArgs e)
12627         {
12628             this.AllowDrop = true;
12629         }
12630
12631         private void ImageSelector_SelectedServiceChanged(object sender, EventArgs e)
12632         {
12633             if (ImageSelector.Visible)
12634             {
12635                 _modifySettingCommon = true;
12636                 SaveConfigsAll(true);
12637
12638                 if (ImageSelector.ServiceName.Equals("Twitter"))
12639                     this.StatusText_TextChanged(null, null);
12640             }
12641         }
12642
12643         private void ImageSelector_VisibleChanged(object sender, EventArgs e)
12644         {
12645             this.StatusText_TextChanged(null, null);
12646         }
12647
12648         /// <summary>
12649         /// StatusTextでCtrl+Vが押下された時の処理
12650         /// </summary>
12651         private void ProcClipboardFromStatusTextWhenCtrlPlusV()
12652         {
12653             if (Clipboard.ContainsText())
12654             {
12655                 // clipboardにテキストがある場合は貼り付け処理
12656                 this.StatusText.Paste(Clipboard.GetText());
12657             }
12658             else if (Clipboard.ContainsImage())
12659             {
12660                 // 画像があるので投稿処理を行う
12661                 if (MessageBox.Show(Properties.Resources.PostPictureConfirm3,
12662                                    Properties.Resources.PostPictureWarn4,
12663                                    MessageBoxButtons.OKCancel,
12664                                    MessageBoxIcon.Question,
12665                                    MessageBoxDefaultButton.Button2)
12666                                == DialogResult.OK)
12667                 {
12668                     // clipboardから画像を取得
12669                     using (var image = Clipboard.GetImage())
12670                     {
12671                         this.ImageSelector.BeginSelection(image);
12672                     }
12673                 }
12674             }
12675         }
12676 #endregion
12677
12678         private void ListManageToolStripMenuItem_Click(object sender, EventArgs e)
12679         {
12680             using (ListManage form = new ListManage(tw))
12681             {
12682                 form.ShowDialog(this);
12683             }
12684         }
12685
12686         public bool ModifySettingCommon
12687         {
12688             set { _modifySettingCommon = value; }
12689         }
12690
12691         public bool ModifySettingLocal
12692         {
12693             set { _modifySettingLocal = value; }
12694         }
12695
12696         public bool ModifySettingAtId
12697         {
12698             set { _modifySettingAtId = value; }
12699         }
12700
12701         private async void SourceLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
12702         {
12703             var sourceUri = (Uri)this.SourceLinkLabel.Tag;
12704             if (sourceUri != null && e.Button == MouseButtons.Left)
12705             {
12706                 await this.OpenUriInBrowserAsync(sourceUri.AbsoluteUri);
12707             }
12708         }
12709
12710         private void SourceLinkLabel_MouseEnter(object sender, EventArgs e)
12711         {
12712             var sourceUri = (Uri)this.SourceLinkLabel.Tag;
12713             if (sourceUri != null)
12714             {
12715                 StatusLabelUrl.Text = MyCommon.ConvertToReadableUrl(sourceUri.AbsoluteUri);
12716             }
12717         }
12718
12719         private void SourceLinkLabel_MouseLeave(object sender, EventArgs e)
12720         {
12721             SetStatusLabelUrl();
12722         }
12723
12724         private void MenuItemCommand_DropDownOpening(object sender, EventArgs e)
12725         {
12726             if (this.ExistCurrentPost && !_curPost.IsDm)
12727                 RtCountMenuItem.Enabled = true;
12728             else
12729                 RtCountMenuItem.Enabled = false;
12730
12731             //if (SettingDialog.UrlConvertAuto && SettingDialog.ShortenTco)
12732             //    TinyUrlConvertToolStripMenuItem.Enabled = false;
12733             //else
12734             //    TinyUrlConvertToolStripMenuItem.Enabled = true;
12735         }
12736
12737         private void CopyUserIdStripMenuItem_Click(object sender, EventArgs e)
12738         {
12739             CopyUserId();
12740         }
12741
12742         private void CopyUserId()
12743         {
12744             if (_curPost == null) return;
12745             string clstr = _curPost.ScreenName;
12746             try
12747             {
12748                 Clipboard.SetDataObject(clstr, false, 5, 100);
12749             }
12750             catch (Exception ex)
12751             {
12752                 MessageBox.Show(ex.Message);
12753             }
12754         }
12755
12756         private async void ShowRelatedStatusesMenuItem_Click(object sender, EventArgs e)
12757         {
12758             if (this.ExistCurrentPost && !_curPost.IsDm)
12759             {
12760                 try
12761                 {
12762                     await this.OpenRelatedTab(this._curPost);
12763                 }
12764                 catch (TabException ex)
12765                 {
12766                     MessageBox.Show(this, ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
12767                 }
12768             }
12769         }
12770
12771         /// <summary>
12772         /// 指定されたツイートに対する関連発言タブを開きます
12773         /// </summary>
12774         /// <param name="statusId">表示するツイートのID</param>
12775         /// <exception cref="TabException">名前の重複が多すぎてタブを作成できない場合</exception>
12776         private async Task OpenRelatedTab(long statusId)
12777         {
12778             var post = this._statuses[statusId];
12779             if (post == null)
12780             {
12781                 try
12782                 {
12783                     post = await Task.Run(() => this.tw.GetStatusApi(false, statusId));
12784                 }
12785                 catch (WebApiException ex)
12786                 {
12787                     this.StatusLabel.Text = ex.Message;
12788                     return;
12789                 }
12790             }
12791
12792             await this.OpenRelatedTab(post);
12793         }
12794
12795         /// <summary>
12796         /// 指定されたツイートに対する関連発言タブを開きます
12797         /// </summary>
12798         /// <param name="post">表示する対象となるツイート</param>
12799         /// <exception cref="TabException">名前の重複が多すぎてタブを作成できない場合</exception>
12800         private async Task OpenRelatedTab(PostClass post)
12801         {
12802             var tabRelated = this._statuses.GetTabByType(MyCommon.TabUsageType.Related);
12803             string tabName;
12804
12805             if (tabRelated == null)
12806             {
12807                 tabName = this._statuses.MakeTabName("Related Tweets");
12808
12809                 this.AddNewTab(tabName, false, MyCommon.TabUsageType.Related);
12810                 this._statuses.AddTab(tabName, MyCommon.TabUsageType.Related, null);
12811
12812                 tabRelated = this._statuses.GetTabByType(MyCommon.TabUsageType.Related);
12813                 tabRelated.UnreadManage = false;
12814                 tabRelated.Notify = false;
12815             }
12816             else
12817             {
12818                 tabName = tabRelated.TabName;
12819             }
12820
12821             tabRelated.RelationTargetPost = post;
12822             this.ClearTab(tabName, false);
12823
12824             for (int i = 0; i < this.ListTab.TabPages.Count; i++)
12825             {
12826                 var tabPage = this.ListTab.TabPages[i];
12827                 if (tabName == tabPage.Text)
12828                 {
12829                     this.ListTab.SelectedIndex = i;
12830                     break;
12831                 }
12832             }
12833
12834             await this.GetRelatedTweetsAsync(tabRelated);
12835         }
12836
12837         private void CacheInfoMenuItem_Click(object sender, EventArgs e)
12838         {
12839             StringBuilder buf = new StringBuilder();
12840             //buf.AppendFormat("キャッシュメモリ容量         : {0}bytes({1}MB)" + Environment.NewLine, IconCache.CacheMemoryLimit, ((ImageDictionary)IconCache).CacheMemoryLimit / 1048576);
12841             //buf.AppendFormat("物理メモリ使用割合           : {0}%" + Environment.NewLine, IconCache.PhysicalMemoryLimit);
12842             buf.AppendFormat("キャッシュエントリ保持数     : {0}" + Environment.NewLine, IconCache.CacheCount);
12843             buf.AppendFormat("キャッシュエントリ破棄数     : {0}" + Environment.NewLine, IconCache.CacheRemoveCount);
12844             MessageBox.Show(buf.ToString(), "アイコンキャッシュ使用状況");
12845         }
12846
12847         private void tw_UserIdChanged()
12848         {
12849             this._modifySettingCommon = true;
12850         }
12851
12852 #region "Userstream"
12853         private bool _isActiveUserstream = false;
12854
12855         private void tw_PostDeleted(object sender, PostDeletedEventArgs e)
12856         {
12857             try
12858             {
12859                 if (InvokeRequired && !IsDisposed)
12860                 {
12861                     Invoke((Action) (async () =>
12862                            {
12863                                _statuses.RemovePostReserve(e.StatusId);
12864                                if (_curTab != null && _statuses.Tabs[_curTab.Text].Contains(e.StatusId))
12865                                {
12866                                    this.PurgeListViewItemCache();
12867                                    ((DetailsListView)_curTab.Tag).Update();
12868                                    if (_curPost != null && _curPost.StatusId == e.StatusId)
12869                                        await this.DispSelectedPost(true);
12870                                }
12871                            }));
12872                     return;
12873                 }
12874             }
12875             catch (ObjectDisposedException)
12876             {
12877                 return;
12878             }
12879             catch (InvalidOperationException)
12880             {
12881                 return;
12882             }
12883         }
12884
12885         private void tw_NewPostFromStream(object sender, EventArgs e)
12886         {
12887             if (this._cfgCommon.ReadOldPosts)
12888             {
12889                 _statuses.SetReadHomeTab(); //新着時未読クリア
12890             }
12891
12892             int rsltAddCount = _statuses.DistributePosts();
12893             lock (_syncObject)
12894             {
12895                 DateTime tm = DateTime.Now;
12896                 if (_tlTimestamps.ContainsKey(tm))
12897                 {
12898                     _tlTimestamps[tm] += rsltAddCount;
12899                 }
12900                 else
12901                 {
12902                     _tlTimestamps.Add(tm, rsltAddCount);
12903                 }
12904                 DateTime oneHour = DateTime.Now.Subtract(new TimeSpan(1, 0, 0));
12905                 List<DateTime> keys = new List<DateTime>();
12906                 _tlCount = 0;
12907                 foreach (DateTime key in _tlTimestamps.Keys)
12908                 {
12909                     if (key.CompareTo(oneHour) < 0)
12910                         keys.Add(key);
12911                     else
12912                         _tlCount += _tlTimestamps[key];
12913                 }
12914                 foreach (DateTime key in keys)
12915                 {
12916                     _tlTimestamps.Remove(key);
12917                 }
12918                 keys.Clear();
12919
12920                 //Static DateTime before = Now;
12921                 //if (before.Subtract(Now).Seconds > -5) return;
12922                 //before = Now;
12923             }
12924
12925             if (this._cfgCommon.UserstreamPeriod > 0) return;
12926
12927             try
12928             {
12929                 if (InvokeRequired && !IsDisposed)
12930                 {
12931                     Invoke((Action)(async () =>
12932                     {
12933                         await this.RefreshTasktrayIcon(true);
12934                         this.RefreshTimeline(true);
12935                     }));
12936                     return;
12937                 }
12938             }
12939             catch (ObjectDisposedException)
12940             {
12941                 return;
12942             }
12943             catch (InvalidOperationException)
12944             {
12945                 return;
12946             }
12947         }
12948
12949         private void tw_UserStreamStarted(object sender, EventArgs e)
12950         {
12951             this._isActiveUserstream = true;
12952             try
12953             {
12954                 if (InvokeRequired && !IsDisposed)
12955                 {
12956                     Invoke((Action)(() => this.tw_UserStreamStarted(sender, e)));
12957                     return;
12958                 }
12959             }
12960             catch (ObjectDisposedException)
12961             {
12962                 return;
12963             }
12964             catch (InvalidOperationException)
12965             {
12966                 return;
12967             }
12968
12969             MenuItemUserStream.Text = "&UserStream ▶";
12970             MenuItemUserStream.Enabled = true;
12971             StopToolStripMenuItem.Text = "&Stop";
12972             StopToolStripMenuItem.Enabled = true;
12973
12974             StatusLabel.Text = "UserStream Started.";
12975         }
12976
12977         private void tw_UserStreamStopped(object sender, EventArgs e)
12978         {
12979             this._isActiveUserstream = false;
12980             try
12981             {
12982                 if (InvokeRequired && !IsDisposed)
12983                 {
12984                     Invoke((Action)(() => this.tw_UserStreamStopped(sender, e)));
12985                     return;
12986                 }
12987             }
12988             catch (ObjectDisposedException)
12989             {
12990                 return;
12991             }
12992             catch (InvalidOperationException)
12993             {
12994                 return;
12995             }
12996
12997             MenuItemUserStream.Text = "&UserStream ■";
12998             MenuItemUserStream.Enabled = true;
12999             StopToolStripMenuItem.Text = "&Start";
13000             StopToolStripMenuItem.Enabled = true;
13001
13002             StatusLabel.Text = "UserStream Stopped.";
13003         }
13004
13005         private void tw_UserStreamEventArrived(object sender, UserStreamEventReceivedEventArgs e)
13006         {
13007             try
13008             {
13009                 if (InvokeRequired && !IsDisposed)
13010                 {
13011                     Invoke((Action)(() => this.tw_UserStreamEventArrived(sender, e)));
13012                     return;
13013                 }
13014             }
13015             catch (ObjectDisposedException)
13016             {
13017                 return;
13018             }
13019             catch (InvalidOperationException)
13020             {
13021                 return;
13022             }
13023             var ev = e.EventData;
13024             StatusLabel.Text = "Event: " + ev.Event;
13025             //if (ev.Event == "favorite")
13026             //{
13027             //    NotifyFavorite(ev);
13028             //}
13029             NotifyEvent(ev);
13030             if (ev.Event == "favorite" || ev.Event == "unfavorite")
13031             {
13032                 if (_curTab != null && _statuses.Tabs[_curTab.Text].Contains(ev.Id))
13033                 {
13034                     this.PurgeListViewItemCache();
13035                     ((DetailsListView)_curTab.Tag).Update();
13036                 }
13037                 if (ev.Event == "unfavorite" && ev.Username.ToLower().Equals(tw.Username.ToLower()))
13038                 {
13039                     RemovePostFromFavTab(new long[] {ev.Id});
13040                 }
13041             }
13042         }
13043
13044         private void NotifyEvent(Twitter.FormattedEvent ev)
13045         {
13046             //新着通知 
13047             if (BalloonRequired(ev))
13048             {
13049                 NotifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
13050                 //if (SettingDialog.DispUsername) NotifyIcon1.BalloonTipTitle = tw.Username + " - "; else NotifyIcon1.BalloonTipTitle = "";
13051                 //NotifyIcon1.BalloonTipTitle += Application.ProductName + " [" + ev.Event.ToUpper() + "] by " + ((string)(!string.IsNullOrEmpty(ev.Username) ? ev.Username : ""), string);
13052                 StringBuilder title = new StringBuilder();
13053                 if (this._cfgCommon.DispUsername)
13054                 {
13055                     title.Append(tw.Username);
13056                     title.Append(" - ");
13057                 }
13058                 else
13059                 {
13060                     //title.Clear();
13061                 }
13062                 title.Append(Application.ProductName);
13063                 title.Append(" [");
13064                 title.Append(ev.Event.ToUpper());
13065                 title.Append("] by ");
13066                 if (!string.IsNullOrEmpty(ev.Username))
13067                 {
13068                     title.Append(ev.Username.ToString());
13069                 }
13070                 else
13071                 {
13072                     //title.Append("");
13073                 }
13074                 string text;
13075                 if (!string.IsNullOrEmpty(ev.Target))
13076                 {
13077                     //NotifyIcon1.BalloonTipText = ev.Target;
13078                     text = ev.Target;
13079                 }
13080                 else
13081                 {
13082                     //NotifyIcon1.BalloonTipText = " ";
13083                     text = " ";
13084                 }
13085                 //NotifyIcon1.ShowBalloonTip(500);
13086                 if (this._cfgCommon.IsUseNotifyGrowl)
13087                 {
13088                     gh.Notify(GrowlHelper.NotifyType.UserStreamEvent,
13089                               ev.Id.ToString(), title.ToString(), text);
13090                 }
13091                 else
13092                 {
13093                     NotifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
13094                     NotifyIcon1.BalloonTipTitle = title.ToString();
13095                     NotifyIcon1.BalloonTipText = text;
13096                     NotifyIcon1.ShowBalloonTip(500);
13097                 }
13098             }
13099
13100             //サウンド再生
13101             string snd = this._cfgCommon.EventSoundFile;
13102             if (!_initial && this._cfgCommon.PlaySound && !string.IsNullOrEmpty(snd))
13103             {
13104                 if ((ev.Eventtype & this._cfgCommon.EventNotifyFlag) != 0 && IsMyEventNotityAsEventType(ev))
13105                 {
13106                     try
13107                     {
13108                         string dir = Application.StartupPath;
13109                         if (Directory.Exists(Path.Combine(dir, "Sounds")))
13110                         {
13111                             dir = Path.Combine(dir, "Sounds");
13112                         }
13113                         using (SoundPlayer player = new SoundPlayer(Path.Combine(dir, snd)))
13114                         {
13115                             player.Play();
13116                         }
13117                     }
13118                     catch (Exception)
13119                     {
13120                     }
13121                 }
13122             }
13123         }
13124
13125         private void StopToolStripMenuItem_Click(object sender, EventArgs e)
13126         {
13127             MenuItemUserStream.Enabled = false;
13128             if (StopRefreshAllMenuItem.Checked)
13129             {
13130                 StopRefreshAllMenuItem.Checked = false;
13131                 return;
13132             }
13133             if (this._isActiveUserstream)
13134             {
13135                 tw.StopUserStream();
13136             }
13137             else
13138             {
13139                 tw.StartUserStream();
13140             }
13141         }
13142
13143         private static string inputTrack = "";
13144
13145         private void TrackToolStripMenuItem_Click(object sender, EventArgs e)
13146         {
13147             if (TrackToolStripMenuItem.Checked)
13148             {
13149                 using (InputTabName inputForm = new InputTabName())
13150                 {
13151                     inputForm.TabName = inputTrack;
13152                     inputForm.FormTitle = "Input track word";
13153                     inputForm.FormDescription = "Track word";
13154                     if (inputForm.ShowDialog() != DialogResult.OK)
13155                     {
13156                         TrackToolStripMenuItem.Checked = false;
13157                         return;
13158                     }
13159                     inputTrack = inputForm.TabName.Trim();
13160                 }
13161                 if (!inputTrack.Equals(tw.TrackWord))
13162                 {
13163                     tw.TrackWord = inputTrack;
13164                     this._modifySettingCommon = true;
13165                     TrackToolStripMenuItem.Checked = !string.IsNullOrEmpty(inputTrack);
13166                     tw.ReconnectUserStream();
13167                 }
13168             }
13169             else
13170             {
13171                 tw.TrackWord = "";
13172                 tw.ReconnectUserStream();
13173             }
13174             this._modifySettingCommon = true;
13175         }
13176
13177         private void AllrepliesToolStripMenuItem_Click(object sender, EventArgs e)
13178         {
13179             tw.AllAtReply = AllrepliesToolStripMenuItem.Checked;
13180             this._modifySettingCommon = true;
13181             tw.ReconnectUserStream();
13182         }
13183
13184         private void EventViewerMenuItem_Click(object sender, EventArgs e)
13185         {
13186             if (evtDialog == null || evtDialog.IsDisposed)
13187             {
13188                 evtDialog = null;
13189                 evtDialog = new EventViewerDialog();
13190                 evtDialog.Owner = this;
13191                 //親の中央に表示
13192                 Point pos = evtDialog.Location;
13193                 pos.X = Convert.ToInt32(this.Location.X + this.Size.Width / 2 - evtDialog.Size.Width / 2);
13194                 pos.Y = Convert.ToInt32(this.Location.Y + this.Size.Height / 2 - evtDialog.Size.Height / 2);
13195                 evtDialog.Location = pos;
13196             }
13197             evtDialog.EventSource = tw.StoredEvent;
13198             if (!evtDialog.Visible)
13199             {
13200                 evtDialog.Show(this);
13201             }
13202             else
13203             {
13204                 evtDialog.Activate();
13205             }
13206             this.TopMost = this._cfgCommon.AlwaysTop;
13207         }
13208 #endregion
13209
13210         private void TweenRestartMenuItem_Click(object sender, EventArgs e)
13211         {
13212             MyCommon._endingFlag = true;
13213             try
13214             {
13215                 this.Close();
13216                 Application.Restart();
13217             }
13218             catch (Exception)
13219             {
13220                 MessageBox.Show("Failed to restart. Please run " + Application.ProductName + " manually.");
13221             }
13222         }
13223
13224         private async void OpenOwnFavedMenuItem_Click(object sender, EventArgs e)
13225         {
13226             if (!string.IsNullOrEmpty(tw.Username))
13227                 await this.OpenUriInBrowserAsync(Properties.Resources.FavstarUrl + "users/" + tw.Username + "/recent");
13228         }
13229
13230         private async void OpenOwnHomeMenuItem_Click(object sender, EventArgs e)
13231         {
13232             await this.OpenUriInBrowserAsync(MyCommon.TwitterUrl + tw.Username);
13233         }
13234
13235         private async Task doTranslation(string str)
13236         {
13237             if (string.IsNullOrEmpty(str))
13238                 return;
13239
13240             var bing = new Bing();
13241             try
13242             {
13243                 var translatedText = await bing.TranslateAsync(str,
13244                     langFrom: null,
13245                     langTo: this._cfgCommon.TranslateLanguage);
13246
13247                 this.PostBrowser.DocumentText = this.createDetailHtml(translatedText);
13248             }
13249             catch (HttpRequestException e)
13250             {
13251                 this.StatusLabel.Text = "Err:" + e.Message;
13252             }
13253         }
13254
13255         private async void TranslationToolStripMenuItem_Click(object sender, EventArgs e)
13256         {
13257             if (!this.ExistCurrentPost)
13258                 return;
13259
13260             await this.doTranslation(this._curPost.TextFromApi);
13261         }
13262
13263         private async void SelectionTranslationToolStripMenuItem_Click(object sender, EventArgs e)
13264         {
13265             var text = this.PostBrowser.GetSelectedText();
13266             await this.doTranslation(text);
13267         }
13268
13269         private bool ExistCurrentPost
13270         {
13271             get
13272             {
13273                 if (_curPost == null) return false;
13274                 if (_curPost.IsDeleted) return false;
13275                 return true;
13276             }
13277         }
13278
13279         private void ShowUserTimelineToolStripMenuItem_Click(object sender, EventArgs e)
13280         {
13281             ShowUserTimeline();
13282         }
13283
13284         private string GetUserIdFromCurPostOrInput(string caption)
13285         {
13286             var id = _curPost?.ScreenName ?? "";
13287
13288             using (InputTabName inputName = new InputTabName())
13289             {
13290                 inputName.FormTitle = caption;
13291                 inputName.FormDescription = Properties.Resources.FRMessage1;
13292                 inputName.TabName = id;
13293                 if (inputName.ShowDialog() == DialogResult.OK &&
13294                     !string.IsNullOrEmpty(inputName.TabName.Trim()))
13295                 {
13296                     id = inputName.TabName.Trim();
13297                 }
13298                 else
13299                 {
13300                     id = "";
13301                 }
13302             }
13303             return id;
13304         }
13305
13306         private void UserTimelineToolStripMenuItem_Click(object sender, EventArgs e)
13307         {
13308             string id = GetUserIdFromCurPostOrInput("Show UserTimeline");
13309             if (!string.IsNullOrEmpty(id))
13310             {
13311                 AddNewTabForUserTimeline(id);
13312             }
13313         }
13314
13315         private async void UserFavorareToolStripMenuItem_Click(object sender, EventArgs e)
13316         {
13317             string id = GetUserIdFromCurPostOrInput("Show Favstar");
13318             if (!string.IsNullOrEmpty(id))
13319             {
13320                 await this.OpenUriInBrowserAsync(Properties.Resources.FavstarUrl + "users/" + id + "/recent");
13321             }
13322         }
13323
13324         private void SystemEvents_PowerModeChanged(object sender, Microsoft.Win32.PowerModeChangedEventArgs e)
13325         {
13326             if (e.Mode == Microsoft.Win32.PowerModes.Resume) osResumed = true;
13327         }
13328
13329         private void TimelineRefreshEnableChange(bool isEnable)
13330         {
13331             if (isEnable)
13332             {
13333                 tw.StartUserStream();
13334             }
13335             else
13336             {
13337                 tw.StopUserStream();
13338             }
13339             TimerTimeline.Enabled = isEnable;
13340         }
13341
13342         private void StopRefreshAllMenuItem_CheckedChanged(object sender, EventArgs e)
13343         {
13344             TimelineRefreshEnableChange(!StopRefreshAllMenuItem.Checked);
13345         }
13346
13347         private async Task OpenUserAppointUrl()
13348         {
13349             if (this._cfgCommon.UserAppointUrl != null)
13350             {
13351                 if (this._cfgCommon.UserAppointUrl.Contains("{ID}") || this._cfgCommon.UserAppointUrl.Contains("{STATUS}"))
13352                 {
13353                     if (_curPost != null)
13354                     {
13355                         string xUrl = this._cfgCommon.UserAppointUrl;
13356                         xUrl = xUrl.Replace("{ID}", _curPost.ScreenName);
13357
13358                         var statusId = _curPost.RetweetedId ?? _curPost.StatusId;
13359                         xUrl = xUrl.Replace("{STATUS}", statusId.ToString());
13360
13361                         await this.OpenUriInBrowserAsync(xUrl);
13362                     }
13363                 }
13364                 else
13365                 {
13366                     await this.OpenUriInBrowserAsync(this._cfgCommon.UserAppointUrl);
13367                 }
13368             }
13369         }
13370
13371         private async void OpenUserSpecifiedUrlMenuItem_Click(object sender, EventArgs e)
13372         {
13373             await this.OpenUserAppointUrl();
13374         }
13375
13376         private void SourceCopyMenuItem_Click(object sender, EventArgs e)
13377         {
13378             string selText = SourceLinkLabel.Text;
13379             try
13380             {
13381                 Clipboard.SetDataObject(selText, false, 5, 100);
13382             }
13383             catch (Exception ex)
13384             {
13385                 MessageBox.Show(ex.Message);
13386             }
13387         }
13388
13389         private void SourceUrlCopyMenuItem_Click(object sender, EventArgs e)
13390         {
13391             var sourceUri = (Uri)this.SourceLinkLabel.Tag;
13392             try
13393             {
13394                 Clipboard.SetDataObject(sourceUri.AbsoluteUri, false, 5, 100);
13395             }
13396             catch (Exception ex)
13397             {
13398                 MessageBox.Show(ex.Message);
13399             }
13400         }
13401
13402         private void ContextMenuSource_Opening(object sender, CancelEventArgs e)
13403         {
13404             if (_curPost == null || !ExistCurrentPost || _curPost.IsDm)
13405             {
13406                 SourceCopyMenuItem.Enabled = false;
13407                 SourceUrlCopyMenuItem.Enabled = false;
13408             }
13409             else
13410             {
13411                 SourceCopyMenuItem.Enabled = true;
13412                 SourceUrlCopyMenuItem.Enabled = true;
13413             }
13414         }
13415
13416         private void GrowlHelper_Callback(object sender, GrowlHelper.NotifyCallbackEventArgs e)
13417         {
13418             if (Form.ActiveForm == null)
13419             {
13420                 this.BeginInvoke((Action) (() =>
13421                 {
13422                     this.Visible = true;
13423                     if (this.WindowState == FormWindowState.Minimized) this.WindowState = FormWindowState.Normal;
13424                     this.Activate();
13425                     this.BringToFront();
13426                     if (e.NotifyType == GrowlHelper.NotifyType.DirectMessage)
13427                     {
13428                         if (!this.GoDirectMessage(e.StatusId)) this.StatusText.Focus();
13429                     }
13430                     else
13431                     {
13432                         if (!this.GoStatus(e.StatusId)) this.StatusText.Focus();
13433                     }
13434                 }));
13435             }
13436         }
13437
13438         private void ReplaceAppName()
13439         {
13440             MatomeMenuItem.Text = MyCommon.ReplaceAppName(MatomeMenuItem.Text);
13441             AboutMenuItem.Text = MyCommon.ReplaceAppName(AboutMenuItem.Text);
13442         }
13443
13444         private void tweetThumbnail1_ThumbnailLoading(object sender, EventArgs e)
13445         {
13446             this.SplitContainer3.Panel2Collapsed = false;
13447
13448             // PreviewDistance が起動のたびに広がっていく問題の回避策
13449             // FixedPanel が Panel2 に設定された状態で Panel2 を開くと、初回だけ SplitterDistance が再計算されておかしくなるため、
13450             // None で開いた後に設定するようにする
13451             if (this.SplitContainer3.FixedPanel == FixedPanel.None)
13452                 this.SplitContainer3.FixedPanel = FixedPanel.Panel2;
13453         }
13454
13455         private async void tweetThumbnail1_ThumbnailDoubleClick(object sender, ThumbnailDoubleClickEventArgs e)
13456         {
13457             await this.OpenThumbnailPicture(e.Thumbnail);
13458         }
13459
13460         private async void tweetThumbnail1_ThumbnailImageSearchClick(object sender, ThumbnailImageSearchEventArgs e)
13461         {
13462             await this.OpenUriInBrowserAsync(e.ImageUrl);
13463         }
13464
13465         private async Task OpenThumbnailPicture(ThumbnailInfo thumbnail)
13466         {
13467             var url = thumbnail.FullSizeImageUrl ?? thumbnail.ImageUrl;
13468
13469             await this.OpenUriInBrowserAsync(url);
13470         }
13471
13472         private async void TwitterApiStatusToolStripMenuItem_Click(object sender, EventArgs e)
13473         {
13474             await this.OpenUriInBrowserAsync(Twitter.ServiceAvailabilityStatusUrl);
13475         }
13476
13477         private void PostButton_KeyDown(object sender, KeyEventArgs e)
13478         {
13479             if (e.KeyCode == Keys.Space)
13480             {
13481                 this.JumpUnreadMenuItem_Click(null, null);
13482
13483                 e.SuppressKeyPress = true;
13484             }
13485         }
13486
13487         private void ContextMenuColumnHeader_Opening(object sender, CancelEventArgs e)
13488         {
13489             this.IconSizeNoneToolStripMenuItem.Checked = this._cfgCommon.IconSize == MyCommon.IconSizes.IconNone;
13490             this.IconSize16ToolStripMenuItem.Checked = this._cfgCommon.IconSize == MyCommon.IconSizes.Icon16;
13491             this.IconSize24ToolStripMenuItem.Checked = this._cfgCommon.IconSize == MyCommon.IconSizes.Icon24;
13492             this.IconSize48ToolStripMenuItem.Checked = this._cfgCommon.IconSize == MyCommon.IconSizes.Icon48;
13493             this.IconSize48_2ToolStripMenuItem.Checked = this._cfgCommon.IconSize == MyCommon.IconSizes.Icon48_2;
13494
13495             this.LockListSortOrderToolStripMenuItem.Checked = this._cfgCommon.SortOrderLock;
13496         }
13497
13498         private void IconSizeNoneToolStripMenuItem_Click(object sender, EventArgs e)
13499         {
13500             ChangeListViewIconSize(MyCommon.IconSizes.IconNone);
13501         }
13502
13503         private void IconSize16ToolStripMenuItem_Click(object sender, EventArgs e)
13504         {
13505             ChangeListViewIconSize(MyCommon.IconSizes.Icon16);
13506         }
13507
13508         private void IconSize24ToolStripMenuItem_Click(object sender, EventArgs e)
13509         {
13510             ChangeListViewIconSize(MyCommon.IconSizes.Icon24);
13511         }
13512
13513         private void IconSize48ToolStripMenuItem_Click(object sender, EventArgs e)
13514         {
13515             ChangeListViewIconSize(MyCommon.IconSizes.Icon48);
13516         }
13517
13518         private void IconSize48_2ToolStripMenuItem_Click(object sender, EventArgs e)
13519         {
13520             ChangeListViewIconSize(MyCommon.IconSizes.Icon48_2);
13521         }
13522
13523         private void ChangeListViewIconSize(MyCommon.IconSizes iconSize)
13524         {
13525             if (this._cfgCommon.IconSize == iconSize) return;
13526
13527             var oldIconCol = _iconCol;
13528
13529             this._cfgCommon.IconSize = iconSize;
13530             ApplyListViewIconSize(iconSize);
13531
13532             if (_iconCol != oldIconCol)
13533             {
13534                 foreach (TabPage tp in ListTab.TabPages)
13535                 {
13536                     ResetColumns((DetailsListView)tp.Tag);
13537                 }
13538             }
13539
13540             _curList?.Refresh();
13541
13542             _modifySettingCommon = true;
13543         }
13544
13545         private void LockListSortToolStripMenuItem_Click(object sender, EventArgs e)
13546         {
13547             var state = this.LockListSortOrderToolStripMenuItem.Checked;
13548             if (this._cfgCommon.SortOrderLock == state) return;
13549
13550             this._cfgCommon.SortOrderLock = state;
13551
13552             _modifySettingCommon = true;
13553         }
13554     }
13555 }