OSDN Git Service

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