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.
10 // This file is part of OpenTween.
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)
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
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.
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)"
32 using System.Collections.Concurrent;
33 using System.Collections.Generic;
34 using System.ComponentModel;
35 using System.Diagnostics;
37 using System.Globalization;
42 using System.Net.Http;
43 using System.Reflection;
44 using System.Runtime.InteropServices;
46 using System.Text.RegularExpressions;
47 using System.Threading;
48 using System.Threading.Tasks;
49 using System.Windows.Forms;
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;
60 public partial class TweenMain : OTBaseForm
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サイズのとき)
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(); //ロック用
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;}"
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;}"
105 + "</head><body><p>";
106 private const string detailHtmlFormatFooterColor = "</p></body></html>";
107 private string detailHtmlFormatHeader;
108 private string detailHtmlFormatFooter;
110 private bool _myStatusError = false;
111 private bool _myStatusOnline = false;
112 private bool soundfileListup = false;
113 private FormWindowState _formWindowState = FormWindowState.Normal; // フォームの状態保存用 通知領域からアイコンをクリックして復帰した際に使用する
116 private TwitterApi twitterApi = new TwitterApi();
120 private GrowlHelper gh = new GrowlHelper(ApplicationSettings.ApplicationName);
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;
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
163 private ImageList _listViewImageList = new ImageList(); //ListViewItemの高さ変更用
165 private PostClass _anchorPost;
166 private bool _anchorFlag; //true:関連発言移動中(関連移動以外のオペレーションをするとfalseへ。trueだとリスト背景色をアンカー発言選択中として描画)
168 private List<StatusTextHistory> _history = new List<StatusTextHistory>(); //発言履歴
169 private int _hisIdx; //発言履歴カレントインデックス
171 //発言投稿時のAPI引数(発言編集時に設定。手書きreplyでは設定されない)
172 private (long StatusId, string ScreenName)? inReplyTo = null; // リプライ先のステータスID・スクリーン名
175 private List<DateTimeUtc> _postTimestamps = new List<DateTimeUtc>();
176 private List<DateTimeUtc> _favTimestamps = new List<DateTimeUtc>();
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();
190 //////////////////////////////////////////////////////////////////////////////////////////////////////////
191 private TabInformations _statuses;
194 /// 現在表示している発言一覧の <see cref="ListView"/> に対するキャッシュ
197 /// キャッシュクリアのために null が代入されることがあるため、
198 /// 使用する場合には <see cref="_listItemCache"/> に対して直接メソッド等を呼び出さずに
199 /// 一旦ローカル変数に代入してから参照すること。
201 private ListViewItemCache _listItemCache = null;
203 internal class ListViewItemCache
205 /// <summary>アイテムをキャッシュする対象の <see cref="ListView"/></summary>
206 public ListView TargetList { get; set; }
208 /// <summary>キャッシュする範囲の開始インデックス</summary>
209 public int StartIndex { get; set; }
211 /// <summary>キャッシュする範囲の終了インデックス</summary>
212 public int EndIndex { get; set; }
214 /// <summary>キャッシュされた <see cref="ListViewItem"/> インスタンス</summary>
215 public ListViewItem[] ListItem { get; set; }
217 /// <summary>キャッシュされた範囲に対応する <see cref="PostClass"/> インスタンス</summary>
218 public PostClass[] Post { get; set; }
220 /// <summary>キャッシュされたアイテムの件数</summary>
222 => this.EndIndex - this.StartIndex + 1;
224 /// <summary>指定されたインデックスがキャッシュの範囲内であるか判定します</summary>
225 /// <returns><paramref name="index"/> がキャッシュの範囲内であれば true、それ以外は false</returns>
226 public bool Contains(int index)
227 => index >= this.StartIndex && index <= this.EndIndex;
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;
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)
238 if (this.Contains(index))
240 item = this.ListItem[index - this.StartIndex];
241 post = this.Post[index - this.StartIndex];
253 private TabPage _curTab;
254 private int _curItemIndex;
255 private DetailsListView _curList;
256 private PostClass _curPost;
257 private bool _isColumnChanged = false;
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;
264 private int UnreadCounter = -1;
265 private int UnreadAtCounter = -1;
267 private string[] ColumnOrgText = new string[9];
268 private string[] ColumnText = new string[9];
270 private bool _DoFavRetweetFlags = false;
271 private bool osResumed = false;
273 //////////////////////////////////////////////////////////////////////////////////////////////////////////
274 private bool _colorize = false;
276 private System.Timers.Timer TimerTimeline = new System.Timers.Timer();
277 private ThrottlingTimer RefreshThrottlingTimer;
279 private string recommendedStatusFooter;
280 private bool urlMultibyteSplit = false;
281 private bool preventSmsCommand = true;
284 private struct urlUndo
286 public string Before;
290 private List<urlUndo> urlUndoBuffer = null;
292 private struct ReplyChain
294 public long OriginalId;
295 public long InReplyToId;
296 public TabPage OriginalTab;
298 public ReplyChain(long originalId, long inReplyToId, TabPage originalTab)
300 this.OriginalId = originalId;
301 this.InReplyToId = inReplyToId;
302 this.OriginalTab = originalTab;
306 private Stack<ReplyChain> replyChains; //[, ]でのリプライ移動の履歴
307 private Stack<(TabPage, PostClass)> selectPostChains = new Stack<(TabPage, PostClass)>(); //ポスト選択履歴
310 internal enum SEARCHTYPE
317 private class StatusTextHistory
319 public string status = "";
320 public (long StatusId, string ScreenName)? inReplyTo = null;
321 public string imageService = ""; //画像投稿サービス名
322 public IMediaItem[] mediaItems = null;
323 public StatusTextHistory()
326 public StatusTextHistory(string status, (long StatusId, string ScreenName)? inReplyTo)
328 this.status = status;
329 this.inReplyTo = inReplyTo;
333 private void TweenMain_Activated(object sender, EventArgs e)
335 //画面がアクティブになったら、発言欄の背景色戻す
336 if (StatusText.Focused)
338 this.StatusText_Enter(this.StatusText, System.EventArgs.Empty);
342 private bool disposed = false;
345 /// 使用中のリソースをすべてクリーンアップします。
347 /// <param name="disposing">マネージ リソースが破棄される場合 true、破棄されない場合は false です。</param>
348 protected override void Dispose(bool disposing)
350 base.Dispose(disposing);
357 this.components?.Dispose();
360 SearchDialog.Dispose();
363 NIconAtRed?.Dispose();
364 NIconAtSmoke?.Dispose();
365 foreach (var iconRefresh in this.NIconRefresh)
367 iconRefresh?.Dispose();
371 ReplyIcon?.Dispose();
372 ReplyIconBlink?.Dispose();
373 _listViewImageList.Dispose();
374 _brsHighLight.Dispose();
375 _brsBackColorMine?.Dispose();
376 _brsBackColorAt?.Dispose();
377 _brsBackColorYou?.Dispose();
378 _brsBackColorAtYou?.Dispose();
379 _brsBackColorAtFromTarget?.Dispose();
380 _brsBackColorAtTo?.Dispose();
381 _brsBackColorNone?.Dispose();
382 _brsDeactiveSelection?.Dispose();
386 this.workerCts.Cancel();
388 if (IconCache != null)
390 this.IconCache.CancelAsync();
391 this.IconCache.Dispose();
394 this.thumbnailTokenSource?.Dispose();
397 this.twitterApi.Dispose();
398 this._hookGlobalHotkey.Dispose();
401 // 終了時にRemoveHandlerしておかないとメモリリークする
402 // http://msdn.microsoft.com/ja-jp/library/microsoft.win32.systemevents.powermodechanged.aspx
403 Microsoft.Win32.SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
404 Microsoft.Win32.SystemEvents.TimeChanged -= SystemEvents_TimeChanged;
406 this.disposed = true;
409 private void LoadIcons()
411 // Icons フォルダ以下のアイコンを読み込み(着せ替えアイコン対応)
412 var iconsDir = Path.Combine(Application.StartupPath, "Icons");
415 var iconMain = this.LoadIcon(Path.Combine(iconsDir, "MIcon.ico"));
418 var iconTab = this.LoadIcon(Path.Combine(iconsDir, "Tab.ico"));
421 var iconAt = this.LoadIcon(Path.Combine(iconsDir, "At.ico"));
424 var iconAtRed = this.LoadIcon(Path.Combine(iconsDir, "AtRed.ico"));
426 // タスクトレイ: オフライン時アイコン
427 var iconAtSmoke = this.LoadIcon(Path.Combine(iconsDir, "AtSmoke.ico"));
429 // タスクトレイ: Reply通知アイコン (最大2枚でアニメーション可能)
430 var iconReply = this.LoadIcon(Path.Combine(iconsDir, "Reply.ico"));
431 var iconReplyBlink = this.LoadIcon(Path.Combine(iconsDir, "ReplyBlink.ico"));
433 // タスクトレイ: 更新中アイコン (最大4枚でアニメーション可能)
434 var iconRefresh1 = this.LoadIcon(Path.Combine(iconsDir, "Refresh.ico"));
435 var iconRefresh2 = this.LoadIcon(Path.Combine(iconsDir, "Refresh2.ico"));
436 var iconRefresh3 = this.LoadIcon(Path.Combine(iconsDir, "Refresh3.ico"));
437 var iconRefresh4 = this.LoadIcon(Path.Combine(iconsDir, "Refresh4.ico"));
439 // 読み込んだアイコンを設定 (不足するアイコンはデフォルトのものを設定)
441 this.MainIcon = iconMain ?? Properties.Resources.MIcon;
442 this.TabIcon = iconTab ?? Properties.Resources.TabIcon;
443 this.NIconAt = iconAt ?? iconMain ?? Properties.Resources.At;
444 this.NIconAtRed = iconAtRed ?? Properties.Resources.AtRed;
445 this.NIconAtSmoke = iconAtSmoke ?? Properties.Resources.AtSmoke;
447 if (iconReply != null && iconReplyBlink != null)
449 this.ReplyIcon = iconReply;
450 this.ReplyIconBlink = iconReplyBlink;
454 this.ReplyIcon = iconReply ?? iconReplyBlink ?? Properties.Resources.Reply;
455 this.ReplyIconBlink = this.NIconAt;
458 if (iconRefresh1 == null)
460 this.NIconRefresh = new[] {
461 Properties.Resources.Refresh, Properties.Resources.Refresh2,
462 Properties.Resources.Refresh3, Properties.Resources.Refresh4,
465 else if (iconRefresh2 == null)
467 this.NIconRefresh = new[] { iconRefresh1 };
469 else if (iconRefresh3 == null)
471 this.NIconRefresh = new[] { iconRefresh1, iconRefresh2 };
473 else if (iconRefresh4 == null)
475 this.NIconRefresh = new[] { iconRefresh1, iconRefresh2, iconRefresh3 };
477 else // iconRefresh1 から iconRefresh4 まで全て揃っている
479 this.NIconRefresh = new[] { iconRefresh1, iconRefresh2, iconRefresh3, iconRefresh4 };
483 private Icon LoadIcon(string filePath)
485 if (!File.Exists(filePath))
490 return new Icon(filePath);
498 private void InitColumns(ListView list, bool startup)
500 this.InitColumnText();
502 ColumnHeader[] columns = null;
509 new ColumnHeader(), // アイコン
510 new ColumnHeader(), // 本文
513 columns[0].Text = this.ColumnText[0];
514 columns[1].Text = this.ColumnText[2];
518 var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / SettingManager.Local.ScaleDimension.Width;
520 columns[0].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width1);
521 columns[1].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width3);
522 columns[0].DisplayIndex = 0;
523 columns[1].DisplayIndex = 1;
528 foreach (var curListColumn in this._curList.Columns.Cast<ColumnHeader>())
530 columns[idx].Width = curListColumn.Width;
531 columns[idx].DisplayIndex = curListColumn.DisplayIndex;
540 new ColumnHeader(), // アイコン
541 new ColumnHeader(), // ニックネーム
542 new ColumnHeader(), // 本文
543 new ColumnHeader(), // 日付
544 new ColumnHeader(), // ユーザID
545 new ColumnHeader(), // 未読
546 new ColumnHeader(), // マーク&プロテクト
547 new ColumnHeader(), // ソース
550 foreach (var i in Enumerable.Range(0, columns.Length))
551 columns[i].Text = this.ColumnText[i];
555 var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / SettingManager.Local.ScaleDimension.Width;
557 columns[0].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width1);
558 columns[1].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width2);
559 columns[2].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width3);
560 columns[3].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width4);
561 columns[4].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width5);
562 columns[5].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width6);
563 columns[6].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width7);
564 columns[7].Width = ScaleBy(widthScaleFactor, SettingManager.Local.Width8);
566 var displayIndex = new[] {
567 SettingManager.Local.DisplayIndex1, SettingManager.Local.DisplayIndex2,
568 SettingManager.Local.DisplayIndex3, SettingManager.Local.DisplayIndex4,
569 SettingManager.Local.DisplayIndex5, SettingManager.Local.DisplayIndex6,
570 SettingManager.Local.DisplayIndex7, SettingManager.Local.DisplayIndex8
573 foreach (var i in Enumerable.Range(0, displayIndex.Length))
575 columns[i].DisplayIndex = displayIndex[i];
581 foreach (var curListColumn in this._curList.Columns.Cast<ColumnHeader>())
583 columns[idx].Width = curListColumn.Width;
584 columns[idx].DisplayIndex = curListColumn.DisplayIndex;
590 list.Columns.AddRange(columns);
598 foreach (var column in columns)
604 private void InitColumnText()
607 ColumnText[1] = Properties.Resources.AddNewTabText2;
608 ColumnText[2] = Properties.Resources.AddNewTabText3;
609 ColumnText[3] = Properties.Resources.AddNewTabText4_2;
610 ColumnText[4] = Properties.Resources.AddNewTabText5;
613 ColumnText[7] = "Source";
615 ColumnOrgText[0] = "";
616 ColumnOrgText[1] = Properties.Resources.AddNewTabText2;
617 ColumnOrgText[2] = Properties.Resources.AddNewTabText3;
618 ColumnOrgText[3] = Properties.Resources.AddNewTabText4_2;
619 ColumnOrgText[4] = Properties.Resources.AddNewTabText5;
620 ColumnOrgText[5] = "";
621 ColumnOrgText[6] = "";
622 ColumnOrgText[7] = "Source";
625 switch (_statuses.SortMode)
627 case ComparerMode.Nickname: //ニックネーム
630 case ComparerMode.Data: //本文
633 case ComparerMode.Id: //時刻=発言Id
636 case ComparerMode.Name: //名前
639 case ComparerMode.Source: //Source
646 if (_statuses.SortOrder == SortOrder.Descending)
648 // U+25BE BLACK DOWN-POINTING SMALL TRIANGLE
649 ColumnText[2] = ColumnOrgText[2] + "▾";
653 // U+25B4 BLACK UP-POINTING SMALL TRIANGLE
654 ColumnText[2] = ColumnOrgText[2] + "▴";
659 if (_statuses.SortOrder == SortOrder.Descending)
661 // U+25BE BLACK DOWN-POINTING SMALL TRIANGLE
662 ColumnText[c] = ColumnOrgText[c] + "▾";
666 // U+25B4 BLACK UP-POINTING SMALL TRIANGLE
667 ColumnText[c] = ColumnOrgText[c] + "▴";
672 private void InitializeTraceFrag()
675 TraceOutToolStripMenuItem.Checked = true;
676 MyCommon.TraceFlag = true;
678 if (!MyCommon.FileVersion.EndsWith("0", StringComparison.Ordinal))
680 TraceOutToolStripMenuItem.Checked = true;
681 MyCommon.TraceFlag = true;
685 private void TweenMain_Load(object sender, EventArgs e)
687 _ignoreConfigSave = true;
688 this.Visible = false;
690 if (MyApplication.StartupOptions.ContainsKey("d"))
691 MyCommon.TraceFlag = true;
693 InitializeTraceFrag();
695 //Win32Api.SetProxy(HttpConnection.ProxyType.Specified, "127.0.0.1", 8080, "user", "pass")
697 MyCommon.TwitterApiInfo.AccessLimitUpdated += TwitterApiStatus_AccessLimitUpdated;
698 Microsoft.Win32.SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
699 Microsoft.Win32.SystemEvents.TimeChanged += SystemEvents_TimeChanged;
701 Regex.CacheSize = 100;
704 _statuses = TabInformations.GetInstance();
708 this.Icon = MainIcon; //メインフォーム(TweenMain)
709 NotifyIcon1.Icon = NIconAt; //タスクトレイ
710 TabImage.Images.Add(TabIcon); //タブ見出し
712 //<<<<<<<<<設定関連>>>>>>>>>
716 // 現在の DPI と設定保存時の DPI との比を取得する
717 var configScaleFactor = SettingManager.Local.GetConfigScaleFactor(this.CurrentAutoScaleDimensions);
720 var fontUIGlobal = SettingManager.Local.FontUIGlobal;
721 if (fontUIGlobal != null)
723 OTBaseForm.GlobalFont = fontUIGlobal;
724 this.Font = fontUIGlobal;
728 if (!MyApplication.StartupOptions.ContainsKey("nolimit"))
730 if (SettingManager.Common.TimelinePeriod < 15 && SettingManager.Common.TimelinePeriod > 0)
731 SettingManager.Common.TimelinePeriod = 15;
733 if (SettingManager.Common.ReplyPeriod < 15 && SettingManager.Common.ReplyPeriod > 0)
734 SettingManager.Common.ReplyPeriod = 15;
736 if (SettingManager.Common.DMPeriod < 15 && SettingManager.Common.DMPeriod > 0)
737 SettingManager.Common.DMPeriod = 15;
739 if (SettingManager.Common.PubSearchPeriod < 30 && SettingManager.Common.PubSearchPeriod > 0)
740 SettingManager.Common.PubSearchPeriod = 30;
742 if (SettingManager.Common.UserTimelinePeriod < 15 && SettingManager.Common.UserTimelinePeriod > 0)
743 SettingManager.Common.UserTimelinePeriod = 15;
745 if (SettingManager.Common.ListsPeriod < 15 && SettingManager.Common.ListsPeriod > 0)
746 SettingManager.Common.ListsPeriod = 15;
749 if (!Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.Timeline, SettingManager.Common.CountApi))
750 SettingManager.Common.CountApi = 60;
751 if (!Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.Reply, SettingManager.Common.CountApiReply))
752 SettingManager.Common.CountApiReply = 40;
754 if (SettingManager.Common.MoreCountApi != 0 && !Twitter.VerifyMoreApiResultCount(SettingManager.Common.MoreCountApi))
755 SettingManager.Common.MoreCountApi = 200;
756 if (SettingManager.Common.FirstCountApi != 0 && !Twitter.VerifyFirstApiResultCount(SettingManager.Common.FirstCountApi))
757 SettingManager.Common.FirstCountApi = 100;
759 if (SettingManager.Common.FavoritesCountApi != 0 && !Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.Favorites, SettingManager.Common.FavoritesCountApi))
760 SettingManager.Common.FavoritesCountApi = 40;
761 if (SettingManager.Common.ListCountApi != 0 && !Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.List, SettingManager.Common.ListCountApi))
762 SettingManager.Common.ListCountApi = 100;
763 if (SettingManager.Common.SearchCountApi != 0 && !Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.PublicSearch, SettingManager.Common.SearchCountApi))
764 SettingManager.Common.SearchCountApi = 100;
765 if (SettingManager.Common.UserTimelineCountApi != 0 && !Twitter.VerifyApiResultCount(MyCommon.WORKERTYPE.UserTimeline, SettingManager.Common.UserTimelineCountApi))
766 SettingManager.Common.UserTimelineCountApi = 20;
768 //廃止サービスが選択されていた場合ux.nuへ読み替え
769 if (SettingManager.Common.AutoShortUrlFirst < 0)
770 SettingManager.Common.AutoShortUrlFirst = MyCommon.UrlConverter.Uxnu;
772 TwitterApiConnection.RestApiHost = SettingManager.Common.TwitterApiHost;
773 this.tw = new Twitter(this.twitterApi);
776 if (string.IsNullOrEmpty(SettingManager.Common.Token)) SettingManager.Common.UserName = "";
777 tw.Initialize(SettingManager.Common.Token, SettingManager.Common.TokenSecret, SettingManager.Common.UserName, SettingManager.Common.UserId);
781 Networking.Initialize();
783 bool saveRequired = false;
784 bool firstRun = false;
786 //ユーザー名、パスワードが未設定なら設定画面を表示(初回起動時など)
787 if (string.IsNullOrEmpty(tw.Username))
792 //設定せずにキャンセルされたか、設定されたが依然ユーザー名が未設定ならプログラム終了
793 if (ShowSettingDialog(showTaskbarIcon: true) != DialogResult.OK ||
794 string.IsNullOrEmpty(tw.Username))
796 Application.Exit(); //強制終了
802 Networking.DefaultTimeout = TimeSpan.FromSeconds(SettingManager.Common.DefaultTimeOut);
803 Networking.UploadImageTimeout = TimeSpan.FromSeconds(SettingManager.Common.UploadImageTimeout);
804 Networking.SetWebProxy(SettingManager.Local.ProxyType,
805 SettingManager.Local.ProxyAddress, SettingManager.Local.ProxyPort,
806 SettingManager.Local.ProxyUser, SettingManager.Local.ProxyPassword);
807 Networking.ForceIPv4 = SettingManager.Common.ForceIPv4;
809 TwitterApiConnection.RestApiHost = SettingManager.Common.TwitterApiHost;
810 tw.RestrictFavCheck = SettingManager.Common.RestrictFavCheck;
811 tw.ReadOwnPost = SettingManager.Common.ReadOwnPost;
812 tw.TrackWord = SettingManager.Common.TrackWord;
813 TrackToolStripMenuItem.Checked = !String.IsNullOrEmpty(tw.TrackWord);
814 tw.AllAtReply = SettingManager.Common.AllAtReply;
815 AllrepliesToolStripMenuItem.Checked = tw.AllAtReply;
816 ShortUrl.Instance.DisableExpanding = !SettingManager.Common.TinyUrlResolve;
817 ShortUrl.Instance.BitlyAccessToken = SettingManager.Common.BitlyAccessToken;
818 ShortUrl.Instance.BitlyId = SettingManager.Common.BilyUser;
819 ShortUrl.Instance.BitlyKey = SettingManager.Common.BitlyPwd;
821 // アクセストークンが有効であるか確認する
822 // ここが Twitter API への最初のアクセスになるようにすること
825 this.tw.VerifyCredentials();
827 catch (WebApiException ex)
829 MessageBox.Show(this, string.Format(Properties.Resources.StartupAuthError_Text, ex.Message),
830 ApplicationSettings.ApplicationName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
834 //プロキシ設定等の通信まわりの初期化が済んでから処理する
835 ThumbnailGenerator.InitializeGenerator();
837 var imgazyobizinet = ThumbnailGenerator.ImgAzyobuziNetInstance;
838 imgazyobizinet.Enabled = SettingManager.Common.EnableImgAzyobuziNet;
839 imgazyobizinet.DisabledInDM = SettingManager.Common.ImgAzyobuziNetDisabledInDM;
841 Thumbnail.Services.TonTwitterCom.GetApiConnection = () => this.twitterApi.Connection;
844 ImageSelector.Initialize(tw, this.tw.Configuration, SettingManager.Common.UseImageServiceName, SettingManager.Common.UseImageService);
847 AtIdSupl = new AtIdSupplement(SettingManager.AtIdList.AtIdList, "@");
848 HashSupl = new AtIdSupplement(SettingManager.Common.HashTags, "#");
849 HashMgr = new HashtagManage(HashSupl,
850 SettingManager.Common.HashTags.ToArray(),
851 SettingManager.Common.HashSelected,
852 SettingManager.Common.HashIsPermanent,
853 SettingManager.Common.HashIsHead,
854 SettingManager.Common.HashIsNotAddToAtReply);
855 if (!string.IsNullOrEmpty(HashMgr.UseHash) && HashMgr.IsPermanent) HashStripSplitButton.Text = HashMgr.UseHash;
858 this.IconCache = new ImageCache();
859 this.tweetDetailsView.IconCache = this.IconCache;
862 _fntUnread = SettingManager.Local.FontUnread;
863 _clUnread = SettingManager.Local.ColorUnread;
864 _fntReaded = SettingManager.Local.FontRead;
865 _clReaded = SettingManager.Local.ColorRead;
866 _clFav = SettingManager.Local.ColorFav;
867 _clOWL = SettingManager.Local.ColorOWL;
868 _clRetweet = SettingManager.Local.ColorRetweet;
869 _fntDetail = SettingManager.Local.FontDetail;
870 _clDetail = SettingManager.Local.ColorDetail;
871 _clDetailLink = SettingManager.Local.ColorDetailLink;
872 _clDetailBackcolor = SettingManager.Local.ColorDetailBackcolor;
873 _clSelf = SettingManager.Local.ColorSelf;
874 _clAtSelf = SettingManager.Local.ColorAtSelf;
875 _clTarget = SettingManager.Local.ColorTarget;
876 _clAtTarget = SettingManager.Local.ColorAtTarget;
877 _clAtFromTarget = SettingManager.Local.ColorAtFromTarget;
878 _clAtTo = SettingManager.Local.ColorAtTo;
879 _clListBackcolor = SettingManager.Local.ColorListBackcolor;
880 _clInputBackcolor = SettingManager.Local.ColorInputBackcolor;
881 _clInputFont = SettingManager.Local.ColorInputFont;
882 _fntInputFont = SettingManager.Local.FontInputFont;
884 _brsBackColorMine = new SolidBrush(_clSelf);
885 _brsBackColorAt = new SolidBrush(_clAtSelf);
886 _brsBackColorYou = new SolidBrush(_clTarget);
887 _brsBackColorAtYou = new SolidBrush(_clAtTarget);
888 _brsBackColorAtFromTarget = new SolidBrush(_clAtFromTarget);
889 _brsBackColorAtTo = new SolidBrush(_clAtTo);
890 //_brsBackColorNone = new SolidBrush(Color.FromKnownColor(KnownColor.Window));
891 _brsBackColorNone = new SolidBrush(_clListBackcolor);
893 // StringFormatオブジェクトへの事前設定
894 //sf.Alignment = StringAlignment.Near; // Textを近くへ配置(左から右の場合は左寄せ)
895 //sf.LineAlignment = StringAlignment.Near; // Textを近くへ配置(上寄せ)
896 //sf.FormatFlags = StringFormatFlags.LineLimit; //
897 sfTab.Alignment = StringAlignment.Center;
898 sfTab.LineAlignment = StringAlignment.Center;
900 InitDetailHtmlFormat();
902 //Regex statregex = new Regex("^0*");
903 this.recommendedStatusFooter = " [TWNv" + Regex.Replace(MyCommon.FileVersion.Replace(".", ""), "^0*", "") + "]";
905 _history.Add(new StatusTextHistory());
907 this.inReplyTo = null;
910 SearchDialog.Owner = this;
911 UrlDialog.Owner = this;
914 NewPostPopMenuItem.Checked = SettingManager.Common.NewAllPop;
915 this.NotifyFileMenuItem.Checked = NewPostPopMenuItem.Checked;
917 //新着取得時のリストスクロールをするか。trueならスクロールしない
918 ListLockMenuItem.Checked = SettingManager.Common.ListLock;
919 this.LockListFileMenuItem.Checked = SettingManager.Common.ListLock;
921 this.PlaySoundMenuItem.Checked = SettingManager.Common.PlaySound;
922 this.PlaySoundFileMenuItem.Checked = SettingManager.Common.PlaySound;
925 this.ClientSize = ScaleBy(configScaleFactor, SettingManager.Local.FormSize);
926 _mySize = this.ClientSize; // サイズ保持(最小化・最大化されたまま終了した場合の対応用)
927 _myLoc = SettingManager.Local.FormLocation;
929 if (this.WindowState != FormWindowState.Minimized)
931 Rectangle tbarRect = new Rectangle(this._myLoc, new Size(_mySize.Width, SystemInformation.CaptionHeight));
932 bool outOfScreen = true;
933 if (Screen.AllScreens.Length == 1) //ハングするとの報告
935 foreach (Screen scr in Screen.AllScreens)
937 if (!Rectangle.Intersect(tbarRect, scr.Bounds).IsEmpty)
945 this._myLoc = new Point(0, 0);
947 this.DesktopLocation = this._myLoc;
949 this.TopMost = SettingManager.Common.AlwaysTop;
950 _mySpDis = ScaleBy(configScaleFactor.Height, SettingManager.Local.SplitterDistance);
951 _mySpDis2 = ScaleBy(configScaleFactor.Height, SettingManager.Local.StatusTextHeight);
952 if (SettingManager.Local.PreviewDistance == -1)
954 _mySpDis3 = _mySize.Width - ScaleBy(this.CurrentScaleFactor.Width, 150);
955 if (_mySpDis3 < 1) _mySpDis3 = ScaleBy(this.CurrentScaleFactor.Width, 50);
956 SettingManager.Local.PreviewDistance = _mySpDis3;
960 _mySpDis3 = ScaleBy(configScaleFactor.Width, SettingManager.Local.PreviewDistance);
962 //this.Tween_ClientSizeChanged(this, null);
963 this.PlaySoundMenuItem.Checked = SettingManager.Common.PlaySound;
964 this.PlaySoundFileMenuItem.Checked = SettingManager.Common.PlaySound;
966 StatusText.Font = _fntInputFont;
967 StatusText.ForeColor = _clInputFont;
969 // SplitContainer2.Panel2MinSize を一行表示の入力欄の高さに合わせる (MS UI Gothic 12pt (96dpi) の場合は 19px)
970 this.StatusText.Multiline = false; // SettingManager.Local.StatusMultiline の設定は後で反映される
971 this.SplitContainer2.Panel2MinSize = this.StatusText.Height;
973 // 必要であれば、発言一覧と発言詳細部・入力欄の上下を入れ替える
974 SplitContainer1.IsPanelInverted = !SettingManager.Common.StatusAreaAtBottom;
976 //全新着通知のチェック状態により、Reply&DMの新着通知有効無効切り替え(タブ別設定にするため削除予定)
977 if (SettingManager.Common.UnreadManage == false)
979 ReadedStripMenuItem.Enabled = false;
980 UnreadStripMenuItem.Enabled = false;
983 //リンク先URL表示部の初期化(画面左下)
984 StatusLabelUrl.Text = "";
986 StatusLabel.Text = "";
987 StatusLabel.AutoToolTip = false;
988 StatusLabel.ToolTipText = "";
990 lblLen.Text = this.GetRestStatusCount(this.FormatStatusTextExtended("")).ToString();
992 this.JumpReadOpMenuItem.ShortcutKeyDisplayString = "Space";
993 CopySTOTMenuItem.ShortcutKeyDisplayString = "Ctrl+C";
994 CopyURLMenuItem.ShortcutKeyDisplayString = "Ctrl+Shift+C";
995 CopyUserIdStripMenuItem.ShortcutKeyDisplayString = "Shift+Alt+C";
997 // SourceLinkLabel のテキストが SplitContainer2.Panel2.AccessibleName にセットされるのを防ぐ
998 // (タブオーダー順で SourceLinkLabel の次にある PostBrowser が TabStop = false となっているため、
999 // さらに次のコントロールである SplitContainer2.Panel2 の AccessibleName がデフォルトで SourceLinkLabel のテキストになってしまう)
1000 this.SplitContainer2.Panel2.AccessibleName = "";
1002 ////////////////////////////////////////////////////////////////////////////////
1003 var sortOrder = (SortOrder)SettingManager.Common.SortOrder;
1004 var mode = ComparerMode.Id;
1005 switch (SettingManager.Common.SortColumn)
1007 case 0: //0:アイコン,5:未読マーク,6:プロテクト・フィルターマーク
1011 mode = ComparerMode.Id; //Idソートに読み替え
1014 mode = ComparerMode.Nickname;
1017 mode = ComparerMode.Data;
1020 mode = ComparerMode.Id;
1023 mode = ComparerMode.Name;
1026 mode = ComparerMode.Source;
1029 _statuses.SetSortMode(mode, sortOrder);
1030 ////////////////////////////////////////////////////////////////////////////////
1032 ApplyListViewIconSize(SettingManager.Common.IconSize);
1034 //<<<<<<<<タブ関連>>>>>>>
1038 //デフォルトタブの存在チェック、ない場合には追加
1039 if (this._statuses.GetTabByType<HomeTabModel>() == null)
1040 this._statuses.AddTab(new HomeTabModel());
1042 if (this._statuses.GetTabByType<MentionsTabModel>() == null)
1043 this._statuses.AddTab(new MentionsTabModel());
1045 if (this._statuses.GetTabByType<DirectMessagesTabModel>() == null)
1046 this._statuses.AddTab(new DirectMessagesTabModel());
1048 if (this._statuses.GetTabByType<FavoritesTabModel>() == null)
1049 this._statuses.AddTab(new FavoritesTabModel());
1051 if (this._statuses.GetTabByType<MuteTabModel>() == null)
1052 this._statuses.AddTab(new MuteTabModel());
1054 foreach (var tab in _statuses.Tabs.Values)
1057 if (tab.TabType == MyCommon.TabUsageType.Mute)
1060 if (!AddNewTab(tab, startup: true))
1061 throw new TabException(Properties.Resources.TweenMain_LoadText1);
1064 _curTab = ListTab.SelectedTab;
1066 _curList = (DetailsListView)_curTab.Tag;
1068 if (SettingManager.Common.TabIconDisp)
1070 ListTab.DrawMode = TabDrawMode.Normal;
1074 ListTab.DrawMode = TabDrawMode.OwnerDrawFixed;
1075 ListTab.DrawItem += ListTab_DrawItem;
1076 ListTab.ImageList = null;
1079 if (SettingManager.Common.HotkeyEnabled)
1082 HookGlobalHotkey.ModKeys modKey = HookGlobalHotkey.ModKeys.None;
1083 if ((SettingManager.Common.HotkeyModifier & Keys.Alt) == Keys.Alt)
1084 modKey |= HookGlobalHotkey.ModKeys.Alt;
1085 if ((SettingManager.Common.HotkeyModifier & Keys.Control) == Keys.Control)
1086 modKey |= HookGlobalHotkey.ModKeys.Ctrl;
1087 if ((SettingManager.Common.HotkeyModifier & Keys.Shift) == Keys.Shift)
1088 modKey |= HookGlobalHotkey.ModKeys.Shift;
1089 if ((SettingManager.Common.HotkeyModifier & Keys.LWin) == Keys.LWin)
1090 modKey |= HookGlobalHotkey.ModKeys.Win;
1092 _hookGlobalHotkey.RegisterOriginalHotkey(SettingManager.Common.HotkeyKey, SettingManager.Common.HotkeyValue, modKey);
1095 if (SettingManager.Common.IsUseNotifyGrowl)
1098 StatusLabel.Text = Properties.Resources.Form1_LoadText1; //画面右下の状態表示を変更
1100 SetMainWindowTitle();
1101 SetNotifyIconText();
1103 if (!SettingManager.Common.MinimizeToTray || this.WindowState != FormWindowState.Minimized)
1105 this.Visible = true;
1110 var streamingRefreshInterval = TimeSpan.FromSeconds(SettingManager.Common.UserstreamPeriod);
1111 this.RefreshThrottlingTimer = new ThrottlingTimer(streamingRefreshInterval,
1112 () => this.InvokeAsync(() => this.RefreshTimeline()));
1114 TimerTimeline.AutoReset = true;
1115 TimerTimeline.SynchronizingObject = this;
1117 TimerTimeline.Interval = 1000;
1118 TimerTimeline.Enabled = true;
1120 TimerRefreshIcon.Interval = 200;
1121 TimerRefreshIcon.Enabled = true;
1123 _ignoreConfigSave = false;
1124 this.TweenMain_Resize(null, null);
1125 if (saveRequired) SaveConfigsAll(false);
1127 foreach (var ua in SettingManager.Common.UserAccounts)
1129 if (ua.UserId == 0 && ua.Username.Equals(tw.Username, StringComparison.InvariantCultureIgnoreCase))
1131 ua.UserId = tw.UserId;
1138 // 初回起動時だけ右下のメニューを目立たせる
1139 HashStripSplitButton.ShowDropDown();
1143 private void InitDetailHtmlFormat()
1145 if (SettingManager.Common.IsMonospace)
1147 detailHtmlFormatHeader = detailHtmlFormatHeaderMono;
1148 detailHtmlFormatFooter = detailHtmlFormatFooterMono;
1152 detailHtmlFormatHeader = detailHtmlFormatHeaderColor;
1153 detailHtmlFormatFooter = detailHtmlFormatFooterColor;
1156 detailHtmlFormatHeader = detailHtmlFormatHeader
1157 .Replace("%FONT_FAMILY%", _fntDetail.Name)
1158 .Replace("%FONT_SIZE%", _fntDetail.Size.ToString())
1159 .Replace("%FONT_COLOR%", $"{_clDetail.R},{_clDetail.G},{_clDetail.B}")
1160 .Replace("%LINK_COLOR%", $"{_clDetailLink.R},{_clDetailLink.G},{_clDetailLink.B}")
1161 .Replace("%BG_COLOR%", $"{_clDetailBackcolor.R},{_clDetailBackcolor.G},{_clDetailBackcolor.B}")
1162 .Replace("%BG_REPLY_COLOR%", $"{_clAtTo.R}, {_clAtTo.G}, {_clAtTo.B}");
1165 private void ListTab_DrawItem(object sender, DrawItemEventArgs e)
1170 txt = ListTab.TabPages[e.Index].Text;
1177 e.Graphics.FillRectangle(System.Drawing.SystemBrushes.Control, e.Bounds);
1178 if (e.State == DrawItemState.Selected)
1180 e.DrawFocusRectangle();
1185 if (_statuses.Tabs[txt].UnreadCount > 0)
1188 fore = System.Drawing.SystemBrushes.ControlText;
1192 fore = System.Drawing.SystemBrushes.ControlText;
1194 e.Graphics.DrawString(txt, e.Font, fore, e.Bounds, sfTab);
1197 private void LoadConfig()
1199 SettingManager.Local = SettingManager.Local;
1201 // v1.2.4 以前の設定には ScaleDimension の項目がないため、現在の DPI と同じとして扱う
1202 if (SettingManager.Local.ScaleDimension.IsEmpty)
1203 SettingManager.Local.ScaleDimension = this.CurrentAutoScaleDimensions;
1205 var tabSettings = SettingManager.Tabs;
1206 foreach (var tabSetting in tabSettings.Tabs)
1209 switch (tabSetting.TabType)
1211 case MyCommon.TabUsageType.Home:
1212 tab = new HomeTabModel(tabSetting.TabName);
1214 case MyCommon.TabUsageType.Mentions:
1215 tab = new MentionsTabModel(tabSetting.TabName);
1217 case MyCommon.TabUsageType.DirectMessage:
1218 tab = new DirectMessagesTabModel(tabSetting.TabName);
1220 case MyCommon.TabUsageType.Favorites:
1221 tab = new FavoritesTabModel(tabSetting.TabName);
1223 case MyCommon.TabUsageType.UserDefined:
1224 tab = new FilterTabModel(tabSetting.TabName);
1226 case MyCommon.TabUsageType.UserTimeline:
1227 tab = new UserTimelineTabModel(tabSetting.TabName, tabSetting.User);
1229 case MyCommon.TabUsageType.PublicSearch:
1230 tab = new PublicSearchTabModel(tabSetting.TabName)
1232 SearchWords = tabSetting.SearchWords,
1233 SearchLang = tabSetting.SearchLang,
1236 case MyCommon.TabUsageType.Lists:
1237 tab = new ListTimelineTabModel(tabSetting.TabName, tabSetting.ListInfo);
1239 case MyCommon.TabUsageType.Mute:
1240 tab = new MuteTabModel(tabSetting.TabName);
1246 tab.UnreadManage = tabSetting.UnreadManage;
1247 tab.Protected = tabSetting.Protected;
1248 tab.Notify = tabSetting.Notify;
1249 tab.SoundFile = tabSetting.SoundFile;
1251 if (tab.IsDistributableTabType)
1253 var filterTab = (FilterTabModel)tab;
1254 filterTab.FilterArray = tabSetting.FilterArray;
1255 filterTab.FilterModified = false;
1258 if (this._statuses.ContainsTab(tab.TabName))
1259 tab.TabName = this._statuses.MakeTabName("MyTab");
1261 this._statuses.AddTab(tab);
1263 if (_statuses.Tabs.Count == 0)
1265 _statuses.AddTab(new HomeTabModel());
1266 _statuses.AddTab(new MentionsTabModel());
1267 _statuses.AddTab(new DirectMessagesTabModel());
1268 _statuses.AddTab(new FavoritesTabModel());
1272 private void TimerInterval_Changed(object sender, IntervalChangedEventArgs e) //Handles SettingDialog.IntervalChanged
1274 if (!TimerTimeline.Enabled) return;
1278 var interval = TimeSpan.FromSeconds(SettingManager.Common.UserstreamPeriod);
1279 var newTimer = new ThrottlingTimer(interval, () => this.InvokeAsync(() => this.RefreshTimeline()));
1280 var oldTimer = Interlocked.Exchange(ref this.RefreshThrottlingTimer, newTimer);
1287 private IntervalChangedEventArgs ResetTimers = IntervalChangedEventArgs.ResetAll;
1289 private static int homeCounter = 0;
1290 private static int mentionCounter = 0;
1291 private static int dmCounter = 0;
1292 private static int pubSearchCounter = 0;
1293 private static int userTimelineCounter = 0;
1294 private static int listsCounter = 0;
1295 private static int ResumeWait = 0;
1296 private static int refreshFollowers = 0;
1298 private async void TimerTimeline_Elapsed(object sender, EventArgs e)
1300 if (homeCounter > 0) Interlocked.Decrement(ref homeCounter);
1301 if (mentionCounter > 0) Interlocked.Decrement(ref mentionCounter);
1302 if (dmCounter > 0) Interlocked.Decrement(ref dmCounter);
1303 if (pubSearchCounter > 0) Interlocked.Decrement(ref pubSearchCounter);
1304 if (userTimelineCounter > 0) Interlocked.Decrement(ref userTimelineCounter);
1305 if (listsCounter > 0) Interlocked.Decrement(ref listsCounter);
1306 Interlocked.Increment(ref refreshFollowers);
1308 var refreshTasks = new List<Task>();
1311 if (ResetTimers.Timeline || homeCounter <= 0 && SettingManager.Common.TimelinePeriod > 0)
1313 Interlocked.Exchange(ref homeCounter, SettingManager.Common.TimelinePeriod);
1314 if (!tw.IsUserstreamDataReceived && !ResetTimers.Timeline)
1315 refreshTasks.Add(this.RefreshTabAsync<HomeTabModel>());
1316 ResetTimers.Timeline = false;
1318 if (ResetTimers.Reply || mentionCounter <= 0 && SettingManager.Common.ReplyPeriod > 0)
1320 Interlocked.Exchange(ref mentionCounter, SettingManager.Common.ReplyPeriod);
1321 if (!tw.IsUserstreamDataReceived && !ResetTimers.Reply)
1322 refreshTasks.Add(this.RefreshTabAsync<MentionsTabModel>());
1323 ResetTimers.Reply = false;
1325 if (ResetTimers.DirectMessage || dmCounter <= 0 && SettingManager.Common.DMPeriod > 0)
1327 Interlocked.Exchange(ref dmCounter, SettingManager.Common.DMPeriod);
1328 if (!tw.IsUserstreamDataReceived && !ResetTimers.DirectMessage)
1329 refreshTasks.Add(this.RefreshTabAsync<DirectMessagesTabModel>());
1330 ResetTimers.DirectMessage = false;
1332 if (ResetTimers.PublicSearch || pubSearchCounter <= 0 && SettingManager.Common.PubSearchPeriod > 0)
1334 Interlocked.Exchange(ref pubSearchCounter, SettingManager.Common.PubSearchPeriod);
1335 if (!ResetTimers.PublicSearch)
1336 refreshTasks.Add(this.RefreshTabAsync<PublicSearchTabModel>());
1337 ResetTimers.PublicSearch = false;
1339 if (ResetTimers.UserTimeline || userTimelineCounter <= 0 && SettingManager.Common.UserTimelinePeriod > 0)
1341 Interlocked.Exchange(ref userTimelineCounter, SettingManager.Common.UserTimelinePeriod);
1342 if (!ResetTimers.UserTimeline)
1343 refreshTasks.Add(this.RefreshTabAsync<UserTimelineTabModel>());
1344 ResetTimers.UserTimeline = false;
1346 if (ResetTimers.Lists || listsCounter <= 0 && SettingManager.Common.ListsPeriod > 0)
1348 Interlocked.Exchange(ref listsCounter, SettingManager.Common.ListsPeriod);
1349 if (!ResetTimers.Lists)
1350 refreshTasks.Add(this.RefreshTabAsync<ListTimelineTabModel>());
1351 ResetTimers.Lists = false;
1353 if (refreshFollowers > 6 * 3600)
1355 Interlocked.Exchange(ref refreshFollowers, 0);
1356 refreshTasks.AddRange(new[]
1358 this.doGetFollowersMenu(),
1359 this.RefreshNoRetweetIdsAsync(),
1360 this.RefreshTwitterConfigurationAsync(),
1365 Interlocked.Increment(ref ResumeWait);
1366 if (ResumeWait > 30)
1369 Interlocked.Exchange(ref ResumeWait, 0);
1370 refreshTasks.AddRange(new[]
1372 this.RefreshTabAsync<HomeTabModel>(),
1373 this.RefreshTabAsync<MentionsTabModel>(),
1374 this.RefreshTabAsync<DirectMessagesTabModel>(),
1375 this.RefreshTabAsync<PublicSearchTabModel>(),
1376 this.RefreshTabAsync<UserTimelineTabModel>(),
1377 this.RefreshTabAsync<ListTimelineTabModel>(),
1378 this.doGetFollowersMenu(),
1379 this.RefreshTwitterConfigurationAsync(),
1384 await Task.WhenAll(refreshTasks);
1387 private void RefreshTimeline()
1389 var curTabModel = this._statuses.Tabs[this._curTab.Text];
1391 // 現在表示中のタブのスクロール位置を退避
1392 var curListScroll = this.SaveListViewScroll(this._curList, curTabModel);
1394 // 各タブのリスト上の選択位置などを退避
1395 var listSelections = this.SaveListViewSelection();
1399 addCount = _statuses.SubmitUpdate(out var soundFile, out var notifyPosts,
1400 out var newMentionOrDm, out var isDelete);
1402 if (MyCommon._endingFlag) return;
1405 foreach (var tabPage in this.ListTab.TabPages.Cast<TabPage>())
1407 var listView = (DetailsListView)tabPage.Tag;
1408 var tabModel = this._statuses.Tabs[tabPage.Text];
1410 if (listView.VirtualListSize != tabModel.AllCount || isDelete)
1412 using (ControlTransaction.Update(listView))
1414 if (listView == this._curList)
1415 this.PurgeListViewItemCache();
1420 listView.VirtualListSize = tabModel.AllCount;
1422 catch (NullReferenceException ex)
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}");
1430 this.RestoreListViewSelection(listView, tabModel, listSelections[tabModel.TabName]);
1437 if (SettingManager.Common.TabIconDisp)
1439 foreach (var tabPage in this.ListTab.TabPages.Cast<TabPage>())
1441 var tabModel = this._statuses.Tabs[tabPage.Text];
1442 if (tabModel.UnreadCount > 0 && tabPage.ImageIndex != 0)
1443 tabPage.ImageIndex = 0; // 未読アイコン
1448 this.ListTab.Refresh();
1453 this.RestoreListViewScroll(this._curList, curTabModel, curListScroll);
1456 NotifyNewPosts(notifyPosts, soundFile, addCount, newMentionOrDm);
1458 SetMainWindowTitle();
1459 if (!StatusLabelUrl.Text.StartsWith("http", StringComparison.Ordinal)) SetStatusLabelUrl();
1461 HashSupl.AddRangeItem(tw.GetHashList());
1465 internal struct ListViewScroll
1467 public ScrollLockMode ScrollLockMode { get; set; }
1468 public long? TopItemStatusId { get; set; }
1471 internal enum ScrollLockMode
1473 /// <summary>固定しない</summary>
1476 /// <summary>最上部に固定する</summary>
1479 /// <summary>最下部に固定する</summary>
1482 /// <summary><see cref="ListViewScroll.TopItemStatusId"/> の位置に固定する</summary>
1487 /// <see cref="ListView"/> のスクロール位置に関する情報を <see cref="ListViewScroll"/> として返します
1489 private ListViewScroll SaveListViewScroll(DetailsListView listView, TabModel tab)
1491 var listScroll = new ListViewScroll
1493 ScrollLockMode = this.GetScrollLockMode(listView),
1496 if (listScroll.ScrollLockMode == ScrollLockMode.FixedToItem)
1498 var topItemIndex = listView.TopItem?.Index ?? -1;
1499 if (topItemIndex != -1 && topItemIndex < tab.AllCount)
1500 listScroll.TopItemStatusId = tab.GetStatusIdAt(topItemIndex);
1506 private ScrollLockMode GetScrollLockMode(DetailsListView listView)
1508 if (this._statuses.SortMode == ComparerMode.Id)
1510 if (this._statuses.SortOrder == SortOrder.Ascending)
1513 if (this.ListLockMenuItem.Checked)
1514 return ScrollLockMode.None;
1516 // 最下行が表示されていたら、最下行へ強制スクロール。最下行が表示されていなかったら制御しない
1519 var bottomItem = listView.GetItemAt(0, listView.ClientSize.Height - 1);
1520 if (bottomItem == null || bottomItem.Index == listView.VirtualListSize - 1)
1521 return ScrollLockMode.FixedToBottom;
1523 return ScrollLockMode.None;
1528 if (this.ListLockMenuItem.Checked)
1529 return ScrollLockMode.FixedToItem;
1531 // 最上行が表示されていたら、制御しない。最上行が表示されていなかったら、現在表示位置へ強制スクロール
1532 var topItem = listView.TopItem;
1533 if (topItem == null || topItem.Index == 0)
1534 return ScrollLockMode.FixedToTop;
1536 return ScrollLockMode.FixedToItem;
1541 return ScrollLockMode.FixedToItem;
1545 internal struct ListViewSelection
1547 public long[] SelectedStatusIds { get; set; }
1548 public long? SelectionMarkStatusId { get; set; }
1549 public long? FocusedStatusId { get; set; }
1553 /// <see cref="ListView"/> の選択状態を <see cref="ListViewSelection"/> として返します
1555 private IReadOnlyDictionary<string, ListViewSelection> SaveListViewSelection()
1557 var listsDict = new Dictionary<string, ListViewSelection>();
1559 foreach (var tabPage in this.ListTab.TabPages.Cast<TabPage>())
1561 var listView = (DetailsListView)tabPage.Tag;
1562 var tab = _statuses.Tabs[tabPage.Text];
1564 listsDict[tab.TabName] = this.SaveListViewSelection(listView, tab);
1571 /// <see cref="ListView"/> の選択状態を <see cref="ListViewSelection"/> として返します
1573 private ListViewSelection SaveListViewSelection(DetailsListView listView, TabModel tab)
1575 if (listView.VirtualListSize == 0)
1577 return new ListViewSelection
1579 SelectedStatusIds = Array.Empty<long>(),
1580 SelectionMarkStatusId = null,
1581 FocusedStatusId = null,
1585 return new ListViewSelection
1587 SelectedStatusIds = this.GetSelectedStatusIds(listView, tab),
1588 FocusedStatusId = this.GetFocusedStatusId(listView, tab),
1589 SelectionMarkStatusId = this.GetSelectionMarkStatusId(listView, tab),
1593 private long[] GetSelectedStatusIds(DetailsListView listView, TabModel tab)
1595 var selectedIndices = listView.SelectedIndices;
1596 if (selectedIndices.Count > 0 && selectedIndices.Count < 61)
1597 return tab.GetStatusIdAt(selectedIndices.Cast<int>());
1602 private long? GetFocusedStatusId(DetailsListView listView, TabModel tab)
1604 var index = listView.FocusedItem?.Index ?? -1;
1606 return index != -1 && index < tab.AllCount ? tab.GetStatusIdAt(index) : (long?)null;
1609 private long? GetSelectionMarkStatusId(DetailsListView listView, TabModel tab)
1611 var index = listView.SelectionMark;
1613 return index != -1 && index < tab.AllCount ? tab.GetStatusIdAt(index) : (long?)null;
1617 /// <see cref="SaveListViewScroll"/> によって保存されたスクロール位置を復元します
1619 private void RestoreListViewScroll(DetailsListView listView, TabModel tab, ListViewScroll listScroll)
1621 if (listView.VirtualListSize == 0)
1624 switch (listScroll.ScrollLockMode)
1626 case ScrollLockMode.FixedToTop:
1627 listView.EnsureVisible(0);
1629 case ScrollLockMode.FixedToBottom:
1630 listView.EnsureVisible(listView.VirtualListSize - 1);
1632 case ScrollLockMode.FixedToItem:
1633 var topIndex = listScroll.TopItemStatusId != null ? tab.IndexOf(listScroll.TopItemStatusId.Value) : -1;
1636 var topItem = listView.Items[topIndex];
1639 listView.TopItem = topItem;
1641 catch (NullReferenceException)
1643 listView.EnsureVisible(listView.VirtualListSize - 1);
1644 listView.EnsureVisible(topIndex);
1648 case ScrollLockMode.None:
1655 /// <see cref="SaveListViewSelection"/> によって保存された選択状態を復元します
1657 private void RestoreListViewSelection(DetailsListView listView, TabModel tab, ListViewSelection listSelection)
1659 // status_id から ListView 上のインデックスに変換
1660 int[] selectedIndices = null;
1661 if (listSelection.SelectedStatusIds != null)
1662 selectedIndices = tab.IndexOf(listSelection.SelectedStatusIds).Where(x => x != -1).ToArray();
1664 var focusedIndex = -1;
1665 if (listSelection.FocusedStatusId != null)
1666 focusedIndex = tab.IndexOf(listSelection.FocusedStatusId.Value);
1668 var selectionMarkIndex = -1;
1669 if (listSelection.SelectionMarkStatusId != null)
1670 selectionMarkIndex = tab.IndexOf(listSelection.SelectionMarkStatusId.Value);
1672 this.SelectListItem(listView, selectedIndices, focusedIndex, selectionMarkIndex);
1675 private bool BalloonRequired()
1677 var ev = new Twitter.FormattedEvent
1679 Eventtype = MyCommon.EVENTTYPE.None,
1682 return BalloonRequired(ev);
1685 private bool IsEventNotifyAsEventType(MyCommon.EVENTTYPE type)
1687 if (type == MyCommon.EVENTTYPE.None)
1690 if (!SettingManager.Common.EventNotifyEnabled)
1693 return SettingManager.Common.EventNotifyFlag.HasFlag(type);
1696 private bool IsMyEventNotityAsEventType(Twitter.FormattedEvent ev)
1701 return SettingManager.Common.IsMyEventNotifyFlag.HasFlag(ev.Eventtype);
1704 private bool BalloonRequired(Twitter.FormattedEvent ev)
1709 if (NativeMethods.IsScreenSaverRunning())
1713 if (!this.NewPostPopMenuItem.Checked)
1715 // 「新着通知が無効でもイベントを通知する」にも該当しない
1716 if (!SettingManager.Common.ForceEventNotify || ev.Eventtype == MyCommon.EVENTTYPE.None)
1720 // 「画面最小化・アイコン時のみバルーンを表示する」が有効
1721 if (SettingManager.Common.LimitBalloon)
1723 if (this.WindowState != FormWindowState.Minimized && this.Visible && Form.ActiveForm != null)
1727 return this.IsEventNotifyAsEventType(ev.Eventtype) && this.IsMyEventNotityAsEventType(ev);
1730 private void NotifyNewPosts(PostClass[] notifyPosts, string soundFile, int addCount, bool newMentions)
1732 if (SettingManager.Common.ReadOwnPost)
1734 if (notifyPosts != null && notifyPosts.Length > 0 && notifyPosts.All(x => x.UserId == tw.UserId))
1739 if (BalloonRequired())
1741 if (notifyPosts != null && notifyPosts.Length > 0)
1743 //Growlは一個ずつばらして通知。ただし、3ポスト以上あるときはまとめる
1744 if (SettingManager.Common.IsUseNotifyGrowl)
1746 StringBuilder sb = new StringBuilder();
1750 foreach (PostClass post in notifyPosts)
1752 if (!(notifyPosts.Length > 3))
1758 if (post.IsReply && !post.IsExcludeReply) reply = true;
1759 if (post.IsDm) dm = true;
1760 if (sb.Length > 0) sb.Append(System.Environment.NewLine);
1761 switch (SettingManager.Common.NameBalloon)
1763 case MyCommon.NameBalloonEnum.UserID:
1764 sb.Append(post.ScreenName).Append(" : ");
1766 case MyCommon.NameBalloonEnum.NickName:
1767 sb.Append(post.Nickname).Append(" : ");
1770 sb.Append(post.TextFromApi);
1771 if (notifyPosts.Length > 3)
1773 if (notifyPosts.Last() != post) continue;
1776 StringBuilder title = new StringBuilder();
1777 GrowlHelper.NotifyType nt;
1778 if (SettingManager.Common.DispUsername)
1780 title.Append(tw.Username);
1781 title.Append(" - ");
1789 //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Warning;
1790 //NotifyIcon1.BalloonTipTitle += Application.ProductName + " [DM] " + Properties.Resources.RefreshDirectMessageText1 + " " + addCount.ToString() + Properties.Resources.RefreshDirectMessageText2;
1791 title.Append(ApplicationSettings.ApplicationName);
1792 title.Append(" [DM] ");
1793 title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
1794 nt = GrowlHelper.NotifyType.DirectMessage;
1798 //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Warning;
1799 //NotifyIcon1.BalloonTipTitle += Application.ProductName + " [Reply!] " + Properties.Resources.RefreshTimelineText1 + " " + addCount.ToString() + Properties.Resources.RefreshTimelineText2;
1800 title.Append(ApplicationSettings.ApplicationName);
1801 title.Append(" [Reply!] ");
1802 title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
1803 nt = GrowlHelper.NotifyType.Reply;
1807 //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
1808 //NotifyIcon1.BalloonTipTitle += Application.ProductName + " " + Properties.Resources.RefreshTimelineText1 + " " + addCount.ToString() + Properties.Resources.RefreshTimelineText2;
1809 title.Append(ApplicationSettings.ApplicationName);
1811 title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
1812 nt = GrowlHelper.NotifyType.Notify;
1814 string bText = sb.ToString();
1815 if (string.IsNullOrEmpty(bText)) return;
1817 var image = this.IconCache.TryGetFromCache(post.ImageUrl);
1818 gh.Notify(nt, post.StatusId.ToString(), title.ToString(), bText, image?.Image, post.ImageUrl);
1823 StringBuilder sb = new StringBuilder();
1826 foreach (PostClass post in notifyPosts)
1828 if (post.IsReply && !post.IsExcludeReply) reply = true;
1829 if (post.IsDm) dm = true;
1830 if (sb.Length > 0) sb.Append(System.Environment.NewLine);
1831 switch (SettingManager.Common.NameBalloon)
1833 case MyCommon.NameBalloonEnum.UserID:
1834 sb.Append(post.ScreenName).Append(" : ");
1836 case MyCommon.NameBalloonEnum.NickName:
1837 sb.Append(post.Nickname).Append(" : ");
1840 sb.Append(post.TextFromApi);
1843 //if (SettingDialog.DispUsername) { NotifyIcon1.BalloonTipTitle = tw.Username + " - "; } else { NotifyIcon1.BalloonTipTitle = ""; }
1844 StringBuilder title = new StringBuilder();
1846 if (SettingManager.Common.DispUsername)
1848 title.Append(tw.Username);
1849 title.Append(" - ");
1857 //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Warning;
1858 //NotifyIcon1.BalloonTipTitle += Application.ProductName + " [DM] " + Properties.Resources.RefreshDirectMessageText1 + " " + addCount.ToString() + Properties.Resources.RefreshDirectMessageText2;
1859 ntIcon = ToolTipIcon.Warning;
1860 title.Append(ApplicationSettings.ApplicationName);
1861 title.Append(" [DM] ");
1862 title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
1866 //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Warning;
1867 //NotifyIcon1.BalloonTipTitle += Application.ProductName + " [Reply!] " + Properties.Resources.RefreshTimelineText1 + " " + addCount.ToString() + Properties.Resources.RefreshTimelineText2;
1868 ntIcon = ToolTipIcon.Warning;
1869 title.Append(ApplicationSettings.ApplicationName);
1870 title.Append(" [Reply!] ");
1871 title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
1875 //NotifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
1876 //NotifyIcon1.BalloonTipTitle += Application.ProductName + " " + Properties.Resources.RefreshTimelineText1 + " " + addCount.ToString() + Properties.Resources.RefreshTimelineText2;
1877 ntIcon = ToolTipIcon.Info;
1878 title.Append(ApplicationSettings.ApplicationName);
1880 title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
1882 string bText = sb.ToString();
1883 if (string.IsNullOrEmpty(bText)) return;
1884 //NotifyIcon1.BalloonTipText = sb.ToString();
1885 //NotifyIcon1.ShowBalloonTip(500);
1886 NotifyIcon1.BalloonTipTitle = title.ToString();
1887 NotifyIcon1.BalloonTipText = bText;
1888 NotifyIcon1.BalloonTipIcon = ntIcon;
1889 NotifyIcon1.ShowBalloonTip(500);
1895 if (!_initial && SettingManager.Common.PlaySound && !string.IsNullOrEmpty(soundFile))
1899 string dir = Application.StartupPath;
1900 if (Directory.Exists(Path.Combine(dir, "Sounds")))
1902 dir = Path.Combine(dir, "Sounds");
1904 using (SoundPlayer player = new SoundPlayer(Path.Combine(dir, soundFile)))
1914 //mentions新着時に画面ブリンク
1915 if (!_initial && SettingManager.Common.BlinkNewMentions && newMentions && Form.ActiveForm == null)
1917 NativeMethods.FlashMyWindow(this.Handle, NativeMethods.FlashSpecification.FlashTray, 3);
1921 private void MyList_SelectedIndexChanged(object sender, EventArgs e)
1923 if (_curList == null || !_curList.Equals(sender) || _curList.SelectedIndices.Count != 1) return;
1925 _curItemIndex = _curList.SelectedIndices[0];
1926 if (_curItemIndex > _curList.VirtualListSize - 1) return;
1930 this._curPost = GetCurTabPost(_curItemIndex);
1932 catch (ArgumentException)
1937 this.PushSelectPostChain();
1939 this._statuses.SetReadAllTab(_curPost.StatusId, read: true);
1941 ChangeCacheStyleRead(true, _curItemIndex); //既読へ(フォント、文字色)
1947 private void ChangeCacheStyleRead(bool Read, int Index)
1949 var tabInfo = _statuses.Tabs[_curTab.Text];
1950 //Read:true=既読 false=未読
1951 //未読管理していなかったら既読として扱う
1952 if (!tabInfo.UnreadManage ||
1953 !SettingManager.Common.UnreadManage) Read = true;
1955 var listCache = this._listItemCache;
1956 if (listCache == null)
1959 // キャッシュに含まれていないアイテムは対象外
1960 if (!listCache.TryGetValue(Index, out var itm, out var post))
1963 ChangeItemStyleRead(Read, itm, post, ((DetailsListView)_curTab.Tag));
1966 private void ChangeItemStyleRead(bool Read, ListViewItem Item, PostClass Post, DetailsListView DList)
1973 Item.SubItems[5].Text = "";
1978 Item.SubItems[5].Text = "★";
1984 else if (Post.RetweetedId != null)
1986 else if (Post.IsOwl && (Post.IsDm || SettingManager.Common.OneWayLove))
1988 else if (Read || !SettingManager.Common.UseUnreadStyle)
1993 if (DList == null || Item.Index == -1)
1995 Item.ForeColor = cl;
1996 if (SettingManager.Common.UseUnreadStyle)
2002 if (SettingManager.Common.UseUnreadStyle)
2003 DList.ChangeItemFontAndColor(Item.Index, cl, fnt);
2005 DList.ChangeItemForeColor(Item.Index, cl);
2006 //if (_itemCache != null) DList.RedrawItems(_itemCacheIndex, _itemCacheIndex + _itemCache.Length - 1, false);
2010 private void ColorizeList()
2012 //Index:更新対象のListviewItem.Index。Colorを返す。
2013 //-1は全キャッシュ。Colorは返さない(ダミーを戻す)
2016 _post = _anchorPost;
2020 if (_post == null) return;
2022 var listCache = this._listItemCache;
2023 if (listCache == null)
2026 var index = listCache.StartIndex;
2027 foreach (var cachedPost in listCache.Post)
2029 var backColor = this.JudgeColor(_post, cachedPost);
2030 this._curList.ChangeItemBackColor(index++, backColor);
2034 private void ColorizeList(ListViewItem Item, int Index)
2036 //Index:更新対象のListviewItem.Index。Colorを返す。
2037 //-1は全キャッシュ。Colorは返さない(ダミーを戻す)
2040 _post = _anchorPost;
2044 PostClass tPost = GetCurTabPost(Index);
2046 if (_post == null) return;
2048 if (Item.Index == -1)
2049 Item.BackColor = JudgeColor(_post, tPost);
2051 _curList.ChangeItemBackColor(Item.Index, JudgeColor(_post, tPost));
2054 private Color JudgeColor(PostClass BasePost, PostClass TargetPost)
2057 if (TargetPost.StatusId == BasePost.InReplyToStatusId)
2060 else if (TargetPost.IsMe)
2063 else if (TargetPost.IsReply)
2066 else if (BasePost.ReplyToList.Any(x => x.UserId == TargetPost.UserId))
2068 cl = _clAtFromTarget;
2069 else if (TargetPost.ReplyToList.Any(x => x.UserId == BasePost.UserId))
2072 else if (TargetPost.ScreenName.Equals(BasePost.ScreenName, StringComparison.OrdinalIgnoreCase))
2077 cl = _clListBackcolor;
2082 private void StatusTextHistoryBack()
2084 if (!string.IsNullOrWhiteSpace(this.StatusText.Text))
2085 this._history[_hisIdx] = new StatusTextHistory(this.StatusText.Text, this.inReplyTo);
2088 if (this._hisIdx < 0)
2091 var historyItem = this._history[this._hisIdx];
2092 this.inReplyTo = historyItem.inReplyTo;
2093 this.StatusText.Text = historyItem.status;
2094 this.StatusText.SelectionStart = this.StatusText.Text.Length;
2097 private void StatusTextHistoryForward()
2099 if (!string.IsNullOrWhiteSpace(this.StatusText.Text))
2100 this._history[this._hisIdx] = new StatusTextHistory(this.StatusText.Text, this.inReplyTo);
2103 if (this._hisIdx > this._history.Count - 1)
2104 this._hisIdx = this._history.Count - 1;
2106 var historyItem = this._history[this._hisIdx];
2107 this.inReplyTo = historyItem.inReplyTo;
2108 this.StatusText.Text = historyItem.status;
2109 this.StatusText.SelectionStart = this.StatusText.Text.Length;
2112 private async void PostButton_Click(object sender, EventArgs e)
2114 if (StatusText.Text.Trim().Length == 0)
2116 if (!ImageSelector.Enabled)
2118 await this.DoRefresh();
2123 if (this.ExistCurrentPost && StatusText.Text.Trim() == string.Format("RT @{0}: {1}", _curPost.ScreenName, _curPost.TextFromApi))
2125 DialogResult rtResult = MessageBox.Show(string.Format(Properties.Resources.PostButton_Click1, Environment.NewLine),
2127 MessageBoxButtons.YesNoCancel,
2128 MessageBoxIcon.Question);
2131 case DialogResult.Yes:
2132 StatusText.Text = "";
2133 await this.doReTweetOfficial(false);
2135 case DialogResult.Cancel:
2140 if (TextContainsOnlyMentions(this.StatusText.Text))
2142 var message = string.Format(Properties.Resources.PostConfirmText, this.StatusText.Text);
2143 var ret = MessageBox.Show(message, ApplicationSettings.ApplicationName, MessageBoxButtons.OKCancel, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2);
2145 if (ret != DialogResult.OK)
2149 _history[_history.Count - 1] = new StatusTextHistory(StatusText.Text, this.inReplyTo);
2151 if (SettingManager.Common.Nicoms)
2153 StatusText.SelectionStart = StatusText.Text.Length;
2154 await UrlConvertAsync(MyCommon.UrlConverter.Nicoms);
2156 //if (SettingDialog.UrlConvertAuto)
2158 // StatusText.SelectionStart = StatusText.Text.Length;
2159 // UrlConvertAutoToolStripMenuItem_Click(null, null);
2161 //else if (SettingDialog.Nicoms)
2163 // StatusText.SelectionStart = StatusText.Text.Length;
2164 // UrlConvert(UrlConverter.Nicoms);
2166 StatusText.SelectionStart = StatusText.Text.Length;
2167 CheckReplyTo(StatusText.Text);
2169 var status = new PostStatusParams();
2171 var statusTextCompat = this.FormatStatusText(this.StatusText.Text);
2172 if (this.GetRestStatusCount(statusTextCompat) >= 0)
2174 // auto_populate_reply_metadata や attachment_url を使用しなくても 140 字以内に
2175 // 収まる場合はこれらのオプションを使用せずに投稿する
2176 status.Text = statusTextCompat;
2177 status.InReplyToStatusId = this.inReplyTo?.StatusId;
2181 status.Text = this.FormatStatusTextExtended(this.StatusText.Text, out var autoPopulatedUserIds, out var attachmentUrl);
2182 status.InReplyToStatusId = this.inReplyTo?.StatusId;
2184 status.AttachmentUrl = attachmentUrl;
2186 // リプライ先がセットされていても autoPopulatedUserIds が空の場合は auto_populate_reply_metadata を有効にしない
2188 var replyToPost = this.inReplyTo != null ? this._statuses[this.inReplyTo.Value.StatusId] : null;
2189 if (replyToPost != null && autoPopulatedUserIds.Length != 0)
2191 status.AutoPopulateReplyMetadata = true;
2193 // ReplyToList のうち autoPopulatedUserIds に含まれていないユーザー ID を抽出
2194 status.ExcludeReplyUserIds = replyToPost.ReplyToList.Select(x => x.UserId).Except(autoPopulatedUserIds)
2199 if (this.GetRestStatusCount(status.Text) < 0)
2201 // 文字数制限を超えているが強制的に投稿するか
2202 var ret = MessageBox.Show(Properties.Resources.PostLengthOverMessage1, Properties.Resources.PostLengthOverMessage2, MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
2203 if (ret != DialogResult.OK)
2207 IMediaUploadService uploadService = null;
2208 IMediaItem[] uploadItems = null;
2209 if (ImageSelector.Visible)
2212 if (!ImageSelector.TryGetSelectedMedia(out var serviceName, out uploadItems))
2215 uploadService = this.ImageSelector.GetService(serviceName);
2218 this.inReplyTo = null;
2219 StatusText.Text = "";
2220 _history.Add(new StatusTextHistory());
2221 _hisIdx = _history.Count - 1;
2222 if (!SettingManager.Common.FocusLockToStatusText)
2223 ((Control)ListTab.SelectedTab.Tag).Focus();
2224 urlUndoBuffer = null;
2225 UrlUndoToolStripMenuItem.Enabled = false; //Undoをできないように設定
2228 if (StatusText.Text.StartsWith("Google:", StringComparison.OrdinalIgnoreCase) && StatusText.Text.Trim().Length > 7)
2230 string tmp = string.Format(Properties.Resources.SearchItem2Url, Uri.EscapeDataString(StatusText.Text.Substring(7)));
2231 await this.OpenUriInBrowserAsync(tmp);
2234 await this.PostMessageAsync(status, uploadService, uploadItems);
2237 private void EndToolStripMenuItem_Click(object sender, EventArgs e)
2239 MyCommon._endingFlag = true;
2243 private void TweenMain_FormClosing(object sender, FormClosingEventArgs e)
2245 if (!SettingManager.Common.CloseToExit && e.CloseReason == CloseReason.UserClosing && MyCommon._endingFlag == false)
2247 //_endingFlag=false:フォームの×ボタン
2249 this.Visible = false;
2253 _hookGlobalHotkey.UnregisterAllOriginalHotkey();
2254 _ignoreConfigSave = true;
2255 MyCommon._endingFlag = true;
2256 TimerTimeline.Enabled = false;
2257 TimerRefreshIcon.Enabled = false;
2261 private void NotifyIcon1_BalloonTipClicked(object sender, EventArgs e)
2263 this.Visible = true;
2264 if (this.WindowState == FormWindowState.Minimized)
2266 this.WindowState = FormWindowState.Normal;
2269 this.BringToFront();
2272 private static int errorCount = 0;
2274 private static bool CheckAccountValid()
2276 if (Twitter.AccountState != MyCommon.ACCOUNT_STATE.Valid)
2282 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
2291 /// <summary>指定された型 <typeparamref name="T"/> に合致する全てのタブを更新します</summary>
2292 private Task RefreshTabAsync<T>() where T : TabModel
2293 => this.RefreshTabAsync<T>(backward: false);
2295 /// <summary>指定された型 <typeparamref name="T"/> に合致する全てのタブを更新します</summary>
2296 private Task RefreshTabAsync<T>(bool backward) where T : TabModel
2299 from tab in this._statuses.GetTabsByType<T>()
2300 select this.RefreshTabAsync(tab, backward);
2302 return Task.WhenAll(loadTasks);
2305 /// <summary>指定されたタブ <paramref name="tab"/> を更新します</summary>
2306 private Task RefreshTabAsync(TabModel tab)
2307 => this.RefreshTabAsync(tab, backward: false);
2309 /// <summary>指定されたタブ <paramref name="tab"/> を更新します</summary>
2310 private async Task RefreshTabAsync(TabModel tab, bool backward)
2312 await this.workerSemaphore.WaitAsync();
2316 await Task.Run(() => tab.RefreshAsync(this.tw, backward, this._initial, this.workerProgress));
2317 this.RefreshTimeline();
2319 catch (WebApiException ex)
2321 this._myStatusError = true;
2326 case HomeTabModel _:
2327 tabType = "GetTimeline";
2329 case MentionsTabModel _:
2330 tabType = "GetTimeline";
2332 case DirectMessagesTabModel _:
2333 tabType = "GetDirectMessage";
2335 case FavoritesTabModel _:
2336 tabType = "GetFavorites";
2338 case PublicSearchTabModel _:
2339 tabType = "GetSearch";
2341 case UserTimelineTabModel _:
2342 tabType = "GetUserTimeline";
2344 case ListTimelineTabModel _:
2345 tabType = "GetListStatus";
2347 case RelatedPostsTabModel _:
2348 tabType = "GetRelatedTweets";
2351 tabType = tab.GetType().Name.Replace("Model", "");
2355 this.StatusLabel.Text = $"Err:{ex.Message}({tabType})";
2359 this.workerSemaphore.Release();
2363 private async Task FavAddAsync(long statusId, TabModel tab)
2365 await this.workerSemaphore.WaitAsync();
2369 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2371 await this.FavAddAsyncInternal(progress, this.workerCts.Token, statusId, tab);
2373 catch (WebApiException ex)
2375 this._myStatusError = true;
2376 this.StatusLabel.Text = $"Err:{ex.Message}(PostFavAdd)";
2380 this.workerSemaphore.Release();
2384 private async Task FavAddAsyncInternal(IProgress<string> p, CancellationToken ct, long statusId, TabModel tab)
2386 if (ct.IsCancellationRequested)
2389 if (!CheckAccountValid())
2390 throw new WebApiException("Auth error. Check your account");
2392 if (!tab.Posts.TryGetValue(statusId, out var post))
2398 await Task.Run(async () =>
2400 p.Report(string.Format(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText15, 0, 1, 0));
2406 await this.twitterApi.FavoritesCreate(post.RetweetedId ?? post.StatusId)
2408 .ConfigureAwait(false);
2410 catch (TwitterApiException ex)
2411 when (ex.ErrorResponse.Errors.All(x => x.Code == TwitterErrorCode.AlreadyFavorited))
2413 // エラーコード 139 のみの場合は成功と見なす
2416 if (SettingManager.Common.RestrictFavCheck)
2418 var status = await this.twitterApi.StatusesShow(post.RetweetedId ?? post.StatusId)
2419 .ConfigureAwait(false);
2421 if (status.Favorited != true)
2422 throw new WebApiException("NG(Restricted?)");
2425 this._favTimestamps.Add(DateTimeUtc.Now);
2428 if (this._statuses.ContainsKey(statusId))
2430 var postTl = this._statuses[statusId];
2431 postTl.IsFav = true;
2433 var favTab = this._statuses.GetTabByType(MyCommon.TabUsageType.Favorites);
2434 favTab.AddPostQueue(postTl);
2437 // 検索,リスト,UserTimeline,Relatedの各タブに反映
2438 foreach (var tb in this._statuses.GetTabsInnerStorageType())
2440 if (tb.Contains(statusId))
2441 tb.Posts[statusId].IsFav = true;
2444 p.Report(string.Format(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText15, 1, 1, 0));
2446 catch (WebApiException)
2448 p.Report(string.Format(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText15, 1, 1, 1));
2453 var oneHour = DateTimeUtc.Now - TimeSpan.FromHours(1);
2454 foreach (var i in MyCommon.CountDown(this._favTimestamps.Count - 1, 0))
2456 if (this._favTimestamps[i] < oneHour)
2457 this._favTimestamps.RemoveAt(i);
2460 this._statuses.DistributePosts();
2463 if (ct.IsCancellationRequested)
2466 this.RefreshTimeline();
2468 if (this._curList != null && this._curTab != null && this._curTab.Text == tab.TabName)
2470 using (ControlTransaction.Update(this._curList))
2472 var idx = tab.IndexOf(statusId);
2474 this.ChangeCacheStyleRead(post.IsRead, idx);
2477 if (statusId == this._curPost.StatusId)
2478 await this.DispSelectedPost(true); // 選択アイテム再表示
2482 private async Task FavRemoveAsync(IReadOnlyList<long> statusIds, TabModel tab)
2484 await this.workerSemaphore.WaitAsync();
2488 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2490 await this.FavRemoveAsyncInternal(progress, this.workerCts.Token, statusIds, tab);
2492 catch (WebApiException ex)
2494 this._myStatusError = true;
2495 this.StatusLabel.Text = $"Err:{ex.Message}(PostFavRemove)";
2499 this.workerSemaphore.Release();
2503 private async Task FavRemoveAsyncInternal(IProgress<string> p, CancellationToken ct, IReadOnlyList<long> statusIds, TabModel tab)
2505 if (ct.IsCancellationRequested)
2508 if (!CheckAccountValid())
2509 throw new WebApiException("Auth error. Check your account");
2511 var successIds = new List<long>();
2513 await Task.Run(async () =>
2517 var failedCount = 0;
2518 foreach (var statusId in statusIds)
2522 var post = tab.Posts[statusId];
2524 p.Report(string.Format(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText17, allCount, statusIds.Count, failedCount));
2531 await this.twitterApi.FavoritesDestroy(post.RetweetedId ?? post.StatusId)
2533 .ConfigureAwait(false);
2535 catch (WebApiException)
2541 successIds.Add(statusId);
2542 post.IsFav = false; // リスト再描画必要
2544 if (this._statuses.ContainsKey(statusId))
2546 this._statuses[statusId].IsFav = false;
2549 // 検索,リスト,UserTimeline,Relatedの各タブに反映
2550 foreach (var tb in this._statuses.GetTabsInnerStorageType())
2552 if (tb.Contains(statusId))
2553 tb.Posts[statusId].IsFav = false;
2558 if (ct.IsCancellationRequested)
2561 var favTab = this._statuses.GetTabByType(MyCommon.TabUsageType.Favorites);
2562 foreach (var statusId in successIds)
2564 // ツイートが削除された訳ではないので IsDeleted はセットしない
2565 favTab.EnqueueRemovePost(statusId, setIsDeleted: false);
2568 this.RefreshTimeline();
2570 if (this._curList != null && this._curTab != null && this._curTab.Text == tab.TabName)
2572 if (tab.TabType == MyCommon.TabUsageType.Favorites)
2578 using (ControlTransaction.Update(this._curList))
2580 foreach (var statusId in successIds)
2582 var idx = tab.IndexOf(statusId);
2586 var post = tab.Posts[statusId];
2587 this.ChangeCacheStyleRead(post.IsRead, idx);
2591 if (successIds.Contains(this._curPost.StatusId))
2592 await this.DispSelectedPost(true); // 選択アイテム再表示
2597 private async Task PostMessageAsync(PostStatusParams postParams, IMediaUploadService uploadService, IMediaItem[] uploadItems)
2599 await this.workerSemaphore.WaitAsync();
2603 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2605 await this.PostMessageAsyncInternal(progress, this.workerCts.Token, postParams, uploadService, uploadItems);
2607 catch (WebApiException ex)
2609 this._myStatusError = true;
2610 this.StatusLabel.Text = $"Err:{ex.Message}(PostMessage)";
2614 this.workerSemaphore.Release();
2618 private async Task PostMessageAsyncInternal(IProgress<string> p, CancellationToken ct, PostStatusParams postParams,
2619 IMediaUploadService uploadService, IMediaItem[] uploadItems)
2621 if (ct.IsCancellationRequested)
2624 if (!CheckAccountValid())
2625 throw new WebApiException("Auth error. Check your account");
2627 p.Report("Posting...");
2629 PostClass post = null;
2634 await Task.Run(async () =>
2636 var postParamsWithMedia = postParams;
2638 if (uploadService != null && uploadItems != null && uploadItems.Length > 0)
2640 postParamsWithMedia = await uploadService.UploadAsync(uploadItems, postParamsWithMedia)
2641 .ConfigureAwait(false);
2644 post = await this.tw.PostStatus(postParamsWithMedia)
2645 .ConfigureAwait(false);
2648 p.Report(Properties.Resources.PostWorker_RunWorkerCompletedText4);
2650 catch (WebApiException ex)
2652 // 処理は中断せずエラーの表示のみ行う
2653 errMsg = $"Err:{ex.Message}(PostMessage)";
2655 this._myStatusError = true;
2657 catch (UnauthorizedAccessException ex)
2659 // アップロード対象のファイルが開けなかった場合など
2660 errMsg = $"Err:{ex.Message}(PostMessage)";
2662 this._myStatusError = true;
2666 // 使い終わった MediaItem は破棄する
2667 if (uploadItems != null)
2669 foreach (var disposableItem in uploadItems.OfType<IDisposable>())
2671 disposableItem.Dispose();
2676 if (ct.IsCancellationRequested)
2679 if (!string.IsNullOrEmpty(errMsg) &&
2680 !errMsg.StartsWith("OK:", StringComparison.Ordinal) &&
2681 !errMsg.StartsWith("Warn:", StringComparison.Ordinal))
2683 var message = string.Format(Properties.Resources.StatusUpdateFailed, errMsg, postParams.Text);
2685 var ret = MessageBox.Show(
2687 "Failed to update status",
2688 MessageBoxButtons.RetryCancel,
2689 MessageBoxIcon.Question);
2691 if (ret == DialogResult.Retry)
2693 await this.PostMessageAsync(postParams, uploadService, uploadItems);
2697 this.StatusTextHistoryBack();
2698 this.StatusText.Focus();
2700 // 連投モードのときだけEnterイベントが起きないので強制的に背景色を戻す
2701 if (SettingManager.Common.FocusLockToStatusText)
2702 this.StatusText_Enter(this.StatusText, EventArgs.Empty);
2707 this._postTimestamps.Add(DateTimeUtc.Now);
2709 var oneHour = DateTimeUtc.Now - TimeSpan.FromHours(1);
2710 foreach (var i in MyCommon.CountDown(this._postTimestamps.Count - 1, 0))
2712 if (this._postTimestamps[i] < oneHour)
2713 this._postTimestamps.RemoveAt(i);
2716 if (!this.HashMgr.IsPermanent && !string.IsNullOrEmpty(this.HashMgr.UseHash))
2718 this.HashMgr.ClearHashtag();
2719 this.HashStripSplitButton.Text = "#[-]";
2720 this.HashTogglePullDownMenuItem.Checked = false;
2721 this.HashToggleMenuItem.Checked = false;
2724 this.SetMainWindowTitle();
2727 if (!this.tw.UserStreamActive)
2729 if (SettingManager.Common.PostAndGet)
2730 await this.RefreshTabAsync<HomeTabModel>();
2735 this._statuses.AddPost(post);
2736 this._statuses.DistributePosts();
2737 this.RefreshTimeline();
2743 private async Task RetweetAsync(IReadOnlyList<long> statusIds)
2745 await this.workerSemaphore.WaitAsync();
2749 var progress = new Progress<string>(x => this.StatusLabel.Text = x);
2751 await this.RetweetAsyncInternal(progress, this.workerCts.Token, statusIds);
2753 catch (WebApiException ex)
2755 this._myStatusError = true;
2756 this.StatusLabel.Text = $"Err:{ex.Message}(PostRetweet)";
2760 this.workerSemaphore.Release();
2764 private async Task RetweetAsyncInternal(IProgress<string> p, CancellationToken ct, IReadOnlyList<long> statusIds)
2766 if (ct.IsCancellationRequested)
2769 if (!CheckAccountValid())
2770 throw new WebApiException("Auth error. Check your account");
2773 if (!SettingManager.Common.UnreadManage)
2776 read = this._initial && SettingManager.Common.Read;
2778 p.Report("Posting...");
2780 var posts = new List<PostClass>();
2782 await Task.Run(async () =>
2784 foreach (var statusId in statusIds)
2786 var post = await this.tw.PostRetweet(statusId, read).ConfigureAwait(false);
2787 if (post != null) posts.Add(post);
2791 if (ct.IsCancellationRequested)
2794 p.Report(Properties.Resources.PostWorker_RunWorkerCompletedText4);
2796 this._postTimestamps.Add(DateTimeUtc.Now);
2798 var oneHour = DateTimeUtc.Now - TimeSpan.FromHours(1);
2799 foreach (var i in MyCommon.CountDown(this._postTimestamps.Count - 1, 0))
2801 if (this._postTimestamps[i] < oneHour)
2802 this._postTimestamps.RemoveAt(i);
2806 if (!this.tw.UserStreamActive)
2808 // 自分のRTはTLの更新では取得できない場合があるので、
2809 // 投稿時取得の有無に関わらず追加しておく
2810 posts.ForEach(post => this._statuses.AddPost(post));
2812 if (SettingManager.Common.PostAndGet)
2813 await this.RefreshTabAsync<HomeTabModel>();
2816 this._statuses.DistributePosts();
2817 this.RefreshTimeline();
2822 private async Task RefreshFollowerIdsAsync()
2824 await this.workerSemaphore.WaitAsync();
2827 this.StatusLabel.Text = Properties.Resources.UpdateFollowersMenuItem1_ClickText1;
2829 await this.tw.RefreshFollowerIds();
2831 this.StatusLabel.Text = Properties.Resources.UpdateFollowersMenuItem1_ClickText3;
2833 this.RefreshTimeline();
2834 this.PurgeListViewItemCache();
2835 this._curList?.Refresh();
2837 catch (WebApiException ex)
2839 this.StatusLabel.Text = $"Err:{ex.Message}(RefreshFollowersIds)";
2843 this.workerSemaphore.Release();
2847 private async Task RefreshNoRetweetIdsAsync()
2849 await this.workerSemaphore.WaitAsync();
2852 await this.tw.RefreshNoRetweetIds();
2854 this.StatusLabel.Text = "NoRetweetIds refreshed";
2856 catch (WebApiException ex)
2858 this.StatusLabel.Text = $"Err:{ex.Message}(RefreshNoRetweetIds)";
2862 this.workerSemaphore.Release();
2866 private async Task RefreshBlockIdsAsync()
2868 await this.workerSemaphore.WaitAsync();
2871 this.StatusLabel.Text = Properties.Resources.UpdateBlockUserText1;
2873 await this.tw.RefreshBlockIds();
2875 this.StatusLabel.Text = Properties.Resources.UpdateBlockUserText3;
2877 catch (WebApiException ex)
2879 this.StatusLabel.Text = $"Err:{ex.Message}(RefreshBlockIds)";
2883 this.workerSemaphore.Release();
2887 private async Task RefreshTwitterConfigurationAsync()
2889 await this.workerSemaphore.WaitAsync();
2892 await this.tw.RefreshConfiguration();
2894 if (this.tw.Configuration.PhotoSizeLimit != 0)
2896 foreach (var service in this.ImageSelector.GetServices())
2898 service.UpdateTwitterConfiguration(this.tw.Configuration);
2902 this.PurgeListViewItemCache();
2904 this._curList?.Refresh();
2906 catch (WebApiException ex)
2908 this.StatusLabel.Text = $"Err:{ex.Message}(RefreshConfiguration)";
2912 this.workerSemaphore.Release();
2916 private async Task RefreshMuteUserIdsAsync()
2918 this.StatusLabel.Text = Properties.Resources.UpdateMuteUserIds_Start;
2922 await tw.RefreshMuteUserIdsAsync();
2924 catch (WebApiException ex)
2926 this.StatusLabel.Text = string.Format(Properties.Resources.UpdateMuteUserIds_Error, ex.Message);
2930 this.StatusLabel.Text = Properties.Resources.UpdateMuteUserIds_Finish;
2933 private void NotifyIcon1_MouseClick(object sender, MouseEventArgs e)
2935 if (e.Button == MouseButtons.Left)
2937 this.Visible = true;
2938 if (this.WindowState == FormWindowState.Minimized)
2940 this.WindowState = _formWindowState;
2943 this.BringToFront();
2947 private async void MyList_MouseDoubleClick(object sender, MouseEventArgs e)
2949 switch (SettingManager.Common.ListDoubleClickAction)
2952 MakeReplyOrDirectStatus();
2955 await this.FavoriteChange(true);
2958 if (_curPost != null)
2959 await this.ShowUserStatus(_curPost.ScreenName, false);
2962 await ShowUserTimeline();
2965 ShowRelatedStatusesMenuItem_Click(null, null);
2968 MoveToHomeToolStripMenuItem_Click(null, null);
2971 StatusOpenMenuItem_Click(null, null);
2979 private async void FavAddToolStripMenuItem_Click(object sender, EventArgs e)
2980 => await this.FavoriteChange(true);
2982 private async void FavRemoveToolStripMenuItem_Click(object sender, EventArgs e)
2983 => await this.FavoriteChange(false);
2986 private async void FavoriteRetweetMenuItem_Click(object sender, EventArgs e)
2987 => await this.FavoritesRetweetOfficial();
2989 private async void FavoriteRetweetUnofficialMenuItem_Click(object sender, EventArgs e)
2990 => await this.FavoritesRetweetUnofficial();
2992 private async Task FavoriteChange(bool FavAdd, bool multiFavoriteChangeDialogEnable = true)
2994 if (!this._statuses.Tabs.TryGetValue(this._curTab.Text, out var tab))
2997 //trueでFavAdd,falseでFavRemove
2998 if (tab.TabType == MyCommon.TabUsageType.DirectMessage || _curList.SelectedIndices.Count == 0
2999 || !this.ExistCurrentPost) return;
3001 if (this._curList.SelectedIndices.Count > 1)
3006 // https://support.twitter.com/articles/76915#favoriting
3007 MessageBox.Show(string.Format(Properties.Resources.FavoriteLimitCountText, 1));
3008 _DoFavRetweetFlags = false;
3013 if (multiFavoriteChangeDialogEnable)
3015 var confirm = MessageBox.Show(Properties.Resources.FavRemoveToolStripMenuItem_ClickText1,
3016 Properties.Resources.FavRemoveToolStripMenuItem_ClickText2,
3017 MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
3019 if (confirm == DialogResult.Cancel)
3027 var selectedPost = this.GetCurTabPost(_curList.SelectedIndices[0]);
3028 if (selectedPost.IsFav)
3030 this.StatusLabel.Text = Properties.Resources.FavAddToolStripMenuItem_ClickText4;
3034 await this.FavAddAsync(selectedPost.StatusId, tab);
3038 var selectedPosts = this._curList.SelectedIndices.Cast<int>()
3039 .Select(x => this.GetCurTabPost(x))
3040 .Where(x => x.IsFav);
3042 var statusIds = selectedPosts.Select(x => x.StatusId).ToArray();
3043 if (statusIds.Length == 0)
3045 this.StatusLabel.Text = Properties.Resources.FavRemoveToolStripMenuItem_ClickText4;
3049 await this.FavRemoveAsync(statusIds, tab);
3053 private PostClass GetCurTabPost(int Index)
3055 var listCache = this._listItemCache;
3056 if (listCache != null)
3058 if (listCache.TryGetValue(Index, out var item, out var post))
3062 return _statuses.Tabs[_curTab.Text][Index];
3065 private async void MoveToHomeToolStripMenuItem_Click(object sender, EventArgs e)
3067 if (_curList.SelectedIndices.Count > 0)
3068 await this.OpenUriInBrowserAsync(MyCommon.TwitterUrl + GetCurTabPost(_curList.SelectedIndices[0]).ScreenName);
3069 else if (_curList.SelectedIndices.Count == 0)
3070 await this.OpenUriInBrowserAsync(MyCommon.TwitterUrl);
3073 private async void MoveToFavToolStripMenuItem_Click(object sender, EventArgs e)
3075 if (_curList.SelectedIndices.Count > 0)
3076 await this.OpenUriInBrowserAsync(MyCommon.TwitterUrl + "#!/" + GetCurTabPost(_curList.SelectedIndices[0]).ScreenName + "/favorites");
3079 private void TweenMain_ClientSizeChanged(object sender, EventArgs e)
3081 if ((!_initialLayout) && this.Visible)
3083 if (this.WindowState == FormWindowState.Normal)
3085 _mySize = this.ClientSize;
3086 _mySpDis = this.SplitContainer1.SplitterDistance;
3087 _mySpDis3 = this.SplitContainer3.SplitterDistance;
3088 if (StatusText.Multiline) _mySpDis2 = this.StatusText.Height;
3089 ModifySettingLocal = true;
3094 private void MyList_ColumnClick(object sender, ColumnClickEventArgs e)
3096 var comparerMode = this.GetComparerModeByColumnIndex(e.Column);
3097 if (comparerMode == null)
3100 this.SetSortColumn(comparerMode.Value);
3104 /// 列インデックスからソートを行う ComparerMode を求める
3106 /// <param name="columnIndex">ソートを行うカラムのインデックス (表示上の順序とは異なる)</param>
3107 /// <returns>ソートを行う ComparerMode。null であればソートを行わない</returns>
3108 private ComparerMode? GetComparerModeByColumnIndex(int columnIndex)
3111 return ComparerMode.Id;
3113 switch (columnIndex)
3116 return ComparerMode.Nickname;
3118 return ComparerMode.Data;
3120 return ComparerMode.Id;
3122 return ComparerMode.Name;
3124 return ComparerMode.Source;
3126 // 0:アイコン, 5:未読マーク, 6:プロテクト・フィルターマーク
3132 /// 発言一覧の指定した位置の列でソートする
3134 /// <param name="columnIndex">ソートする列の位置 (表示上の順序で指定)</param>
3135 private void SetSortColumnByDisplayIndex(int columnIndex)
3137 // 表示上の列の位置から ColumnHeader を求める
3138 var col = this._curList.Columns.Cast<ColumnHeader>()
3139 .FirstOrDefault(x => x.DisplayIndex == columnIndex);
3144 var comparerMode = this.GetComparerModeByColumnIndex(col.Index);
3145 if (comparerMode == null)
3148 this.SetSortColumn(comparerMode.Value);
3152 /// 発言一覧の最後列の項目でソートする
3154 private void SetSortLastColumn()
3156 // 表示上の最後列にある ColumnHeader を求める
3157 var col = this._curList.Columns.Cast<ColumnHeader>()
3158 .OrderByDescending(x => x.DisplayIndex)
3161 var comparerMode = this.GetComparerModeByColumnIndex(col.Index);
3162 if (comparerMode == null)
3165 this.SetSortColumn(comparerMode.Value);
3169 /// 発言一覧を指定された ComparerMode に基づいてソートする
3171 private void SetSortColumn(ComparerMode sortColumn)
3173 if (SettingManager.Common.SortOrderLock)
3176 this._statuses.ToggleSortOrder(sortColumn);
3177 this.InitColumnText();
3179 var list = this._curList;
3182 list.Columns[0].Text = this.ColumnText[0];
3183 list.Columns[1].Text = this.ColumnText[2];
3187 for (var i = 0; i <= 7; i++)
3189 list.Columns[i].Text = this.ColumnText[i];
3193 this.PurgeListViewItemCache();
3195 var tab = this._statuses.Tabs[this._curTab.Text];
3196 if (tab.AllCount > 0 && this._curPost != null)
3198 var idx = tab.IndexOf(this._curPost.StatusId);
3201 this.SelectListItem(list, idx);
3202 list.EnsureVisible(idx);
3207 this.ModifySettingCommon = true;
3210 private void TweenMain_LocationChanged(object sender, EventArgs e)
3212 if (this.WindowState == FormWindowState.Normal && !_initialLayout)
3214 _myLoc = this.DesktopLocation;
3215 ModifySettingLocal = true;
3219 private void ContextMenuOperate_Opening(object sender, CancelEventArgs e)
3221 if (ListTab.SelectedTab == null) return;
3222 if (_statuses == null || _statuses.Tabs == null || !_statuses.Tabs.ContainsKey(ListTab.SelectedTab.Text)) return;
3223 if (!this.ExistCurrentPost)
3225 ReplyStripMenuItem.Enabled = false;
3226 ReplyAllStripMenuItem.Enabled = false;
3227 DMStripMenuItem.Enabled = false;
3228 ShowProfileMenuItem.Enabled = false;
3229 ShowUserTimelineContextMenuItem.Enabled = false;
3230 ListManageUserContextToolStripMenuItem2.Enabled = false;
3231 MoveToFavToolStripMenuItem.Enabled = false;
3232 TabMenuItem.Enabled = false;
3233 IDRuleMenuItem.Enabled = false;
3234 SourceRuleMenuItem.Enabled = false;
3235 ReadedStripMenuItem.Enabled = false;
3236 UnreadStripMenuItem.Enabled = false;
3240 ShowProfileMenuItem.Enabled = true;
3241 ListManageUserContextToolStripMenuItem2.Enabled = true;
3242 ReplyStripMenuItem.Enabled = true;
3243 ReplyAllStripMenuItem.Enabled = true;
3244 DMStripMenuItem.Enabled = true;
3245 ShowUserTimelineContextMenuItem.Enabled = true;
3246 MoveToFavToolStripMenuItem.Enabled = true;
3247 TabMenuItem.Enabled = true;
3248 IDRuleMenuItem.Enabled = true;
3249 SourceRuleMenuItem.Enabled = true;
3250 ReadedStripMenuItem.Enabled = true;
3251 UnreadStripMenuItem.Enabled = true;
3253 if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType == MyCommon.TabUsageType.DirectMessage || !this.ExistCurrentPost || _curPost.IsDm)
3255 FavAddToolStripMenuItem.Enabled = false;
3256 FavRemoveToolStripMenuItem.Enabled = false;
3257 StatusOpenMenuItem.Enabled = false;
3258 ShowRelatedStatusesMenuItem.Enabled = false;
3260 ReTweetStripMenuItem.Enabled = false;
3261 ReTweetUnofficialStripMenuItem.Enabled = false;
3262 QuoteStripMenuItem.Enabled = false;
3263 FavoriteRetweetContextMenu.Enabled = false;
3264 FavoriteRetweetUnofficialContextMenu.Enabled = false;
3268 FavAddToolStripMenuItem.Enabled = true;
3269 FavRemoveToolStripMenuItem.Enabled = true;
3270 StatusOpenMenuItem.Enabled = true;
3271 ShowRelatedStatusesMenuItem.Enabled = true; //PublicSearchの時問題出るかも
3273 if (!_curPost.CanRetweetBy(this.twitterApi.CurrentUserId))
3275 ReTweetStripMenuItem.Enabled = false;
3276 ReTweetUnofficialStripMenuItem.Enabled = false;
3277 QuoteStripMenuItem.Enabled = false;
3278 FavoriteRetweetContextMenu.Enabled = false;
3279 FavoriteRetweetUnofficialContextMenu.Enabled = false;
3283 ReTweetStripMenuItem.Enabled = true;
3284 ReTweetUnofficialStripMenuItem.Enabled = true;
3285 QuoteStripMenuItem.Enabled = true;
3286 FavoriteRetweetContextMenu.Enabled = true;
3287 FavoriteRetweetUnofficialContextMenu.Enabled = true;
3290 //if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType != MyCommon.TabUsageType.Favorites)
3292 // RefreshMoreStripMenuItem.Enabled = true;
3296 // RefreshMoreStripMenuItem.Enabled = false;
3298 if (!this.ExistCurrentPost
3299 || _curPost.InReplyToStatusId == null)
3301 RepliedStatusOpenMenuItem.Enabled = false;
3305 RepliedStatusOpenMenuItem.Enabled = true;
3307 if (!this.ExistCurrentPost || string.IsNullOrEmpty(_curPost.RetweetedBy))
3309 MoveToRTHomeMenuItem.Enabled = false;
3313 MoveToRTHomeMenuItem.Enabled = true;
3316 if (this.ExistCurrentPost)
3318 this.DeleteStripMenuItem.Enabled = this._curPost.CanDeleteBy(this.tw.UserId);
3319 if (this._curPost.RetweetedByUserId == this.tw.UserId)
3320 this.DeleteStripMenuItem.Text = Properties.Resources.DeleteMenuText2;
3322 this.DeleteStripMenuItem.Text = Properties.Resources.DeleteMenuText1;
3326 private void ReplyStripMenuItem_Click(object sender, EventArgs e)
3327 => this.MakeReplyOrDirectStatus(false, true);
3329 private void DMStripMenuItem_Click(object sender, EventArgs e)
3330 => this.MakeReplyOrDirectStatus(false, false);
3332 private async Task doStatusDelete()
3334 if (this._curTab == null || this._curList == null)
3337 if (this._curList.SelectedIndices.Count == 0)
3340 var posts = this._curList.SelectedIndices.Cast<int>()
3341 .Select(x => this.GetCurTabPost(x))
3344 // 選択されたツイートの中に削除可能なものが一つでもあるか
3345 if (!posts.Any(x => x.CanDeleteBy(this.tw.UserId)))
3348 var ret = MessageBox.Show(this,
3349 string.Format(Properties.Resources.DeleteStripMenuItem_ClickText1, Environment.NewLine),
3350 Properties.Resources.DeleteStripMenuItem_ClickText2,
3351 MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
3353 if (ret != DialogResult.OK)
3356 var focusedIndex = this._curList.FocusedItem?.Index ?? this._curList.TopItem?.Index ?? 0;
3358 using (ControlTransaction.Cursor(this, Cursors.WaitCursor))
3360 Exception lastException = null;
3361 foreach (var post in posts)
3363 if (!post.CanDeleteBy(this.tw.UserId))
3370 await this.twitterApi.DirectMessagesEventsDestroy(post.StatusId.ToString(CultureInfo.InvariantCulture));
3374 if (post.RetweetedByUserId == this.tw.UserId)
3376 // 自分が RT したツイート (自分が RT した自分のツイートも含む)
3378 await this.twitterApi.StatusesDestroy(post.StatusId)
3383 if (post.UserId == this.tw.UserId)
3385 if (post.RetweetedId != null)
3386 // 他人に RT された自分のツイート
3387 // => RT 元の自分のツイートを削除
3388 await this.twitterApi.StatusesDestroy(post.RetweetedId.Value)
3393 await this.twitterApi.StatusesDestroy(post.StatusId)
3399 catch (WebApiException ex)
3405 this._statuses.RemovePostFromAllTabs(post.StatusId, setIsDeleted: true);
3408 if (lastException == null)
3409 this.StatusLabel.Text = Properties.Resources.DeleteStripMenuItem_ClickText4; // 成功
3411 this.StatusLabel.Text = Properties.Resources.DeleteStripMenuItem_ClickText3; // 失敗
3413 this.PurgeListViewItemCache();
3414 this._curPost = null;
3415 this._curItemIndex = -1;
3417 foreach (var tabPage in this.ListTab.TabPages.Cast<TabPage>())
3419 var listView = (DetailsListView)tabPage.Tag;
3420 var tab = this._statuses.Tabs[tabPage.Text];
3422 using (ControlTransaction.Update(listView))
3424 listView.VirtualListSize = tab.AllCount;
3426 if (tabPage == this._curTab)
3428 listView.SelectedIndices.Clear();
3430 if (tab.AllCount != 0)
3433 if (tab.AllCount - 1 > focusedIndex && focusedIndex > -1)
3434 selectedIndex = focusedIndex;
3436 selectedIndex = tab.AllCount - 1;
3438 listView.SelectedIndices.Add(selectedIndex);
3439 listView.EnsureVisible(selectedIndex);
3440 listView.FocusedItem = listView.Items[selectedIndex];
3445 if (SettingManager.Common.TabIconDisp && tab.UnreadCount == 0)
3447 if (tabPage.ImageIndex == 0)
3448 tabPage.ImageIndex = -1; // タブアイコン
3452 if (!SettingManager.Common.TabIconDisp)
3453 this.ListTab.Refresh();
3457 private async void DeleteStripMenuItem_Click(object sender, EventArgs e)
3458 => await this.doStatusDelete();
3460 private void ReadedStripMenuItem_Click(object sender, EventArgs e)
3462 using (ControlTransaction.Update(this._curList))
3464 foreach (int idx in _curList.SelectedIndices)
3466 var post = this._statuses.Tabs[this._curTab.Text][idx];
3467 this._statuses.SetReadAllTab(post.StatusId, read: true);
3468 ChangeCacheStyleRead(true, idx);
3472 foreach (TabPage tb in ListTab.TabPages)
3474 if (_statuses.Tabs[tb.Text].UnreadCount == 0)
3476 if (SettingManager.Common.TabIconDisp)
3478 if (tb.ImageIndex == 0) tb.ImageIndex = -1; //タブアイコン
3482 if (!SettingManager.Common.TabIconDisp) ListTab.Refresh();
3485 private void UnreadStripMenuItem_Click(object sender, EventArgs e)
3487 using (ControlTransaction.Update(this._curList))
3489 foreach (int idx in _curList.SelectedIndices)
3491 var post = this._statuses.Tabs[this._curTab.Text][idx];
3492 this._statuses.SetReadAllTab(post.StatusId, read: false);
3493 ChangeCacheStyleRead(false, idx);
3497 foreach (TabPage tb in ListTab.TabPages)
3499 if (_statuses.Tabs[tb.Text].UnreadCount > 0)
3501 if (SettingManager.Common.TabIconDisp)
3503 if (tb.ImageIndex == -1) tb.ImageIndex = 0; //タブアイコン
3507 if (!SettingManager.Common.TabIconDisp) ListTab.Refresh();
3510 private async void RefreshStripMenuItem_Click(object sender, EventArgs e)
3511 => await this.DoRefresh();
3513 private async Task DoRefresh()
3515 if (_curTab != null)
3517 if (!this._statuses.Tabs.TryGetValue(this._curTab.Text, out var tab))
3520 await this.RefreshTabAsync(tab);
3524 await this.RefreshTabAsync<HomeTabModel>();
3528 private async Task DoRefreshMore()
3530 if (_curTab != null)
3532 if (!this._statuses.Tabs.TryGetValue(this._curTab.Text, out var tab))
3535 await this.RefreshTabAsync(tab, backward: true);
3539 await this.RefreshTabAsync<HomeTabModel>(backward: true);
3543 private DialogResult ShowSettingDialog(bool showTaskbarIcon = false)
3545 DialogResult result = DialogResult.Abort;
3547 using (var settingDialog = new AppendSettingDialog())
3549 settingDialog.Icon = this.MainIcon;
3550 settingDialog.Owner = this;
3551 settingDialog.ShowInTaskbar = showTaskbarIcon;
3552 settingDialog.IntervalChanged += this.TimerInterval_Changed;
3554 settingDialog.tw = this.tw;
3555 settingDialog.twitterApi = this.twitterApi;
3557 settingDialog.LoadConfig(SettingManager.Common, SettingManager.Local);
3561 result = settingDialog.ShowDialog(this);
3565 return DialogResult.Abort;
3568 if (result == DialogResult.OK)
3572 settingDialog.SaveConfig(SettingManager.Common, SettingManager.Local);
3580 private async void SettingStripMenuItem_Click(object sender, EventArgs e)
3583 var oldUser = new { tw.AccessToken, tw.AccessTokenSecret, tw.Username, tw.UserId };
3585 var oldIconSz = SettingManager.Common.IconSize;
3587 if (ShowSettingDialog() == DialogResult.OK)
3591 tw.RestrictFavCheck = SettingManager.Common.RestrictFavCheck;
3592 tw.ReadOwnPost = SettingManager.Common.ReadOwnPost;
3593 ShortUrl.Instance.DisableExpanding = !SettingManager.Common.TinyUrlResolve;
3594 ShortUrl.Instance.BitlyAccessToken = SettingManager.Common.BitlyAccessToken;
3595 ShortUrl.Instance.BitlyId = SettingManager.Common.BilyUser;
3596 ShortUrl.Instance.BitlyKey = SettingManager.Common.BitlyPwd;
3597 TwitterApiConnection.RestApiHost = SettingManager.Common.TwitterApiHost;
3599 Networking.DefaultTimeout = TimeSpan.FromSeconds(SettingManager.Common.DefaultTimeOut);
3600 Networking.UploadImageTimeout = TimeSpan.FromSeconds(SettingManager.Common.UploadImageTimeout);
3601 Networking.SetWebProxy(SettingManager.Local.ProxyType,
3602 SettingManager.Local.ProxyAddress, SettingManager.Local.ProxyPort,
3603 SettingManager.Local.ProxyUser, SettingManager.Local.ProxyPassword);
3604 Networking.ForceIPv4 = SettingManager.Common.ForceIPv4;
3606 ImageSelector.Reset(tw, this.tw.Configuration);
3610 if (SettingManager.Common.TabIconDisp)
3612 ListTab.DrawItem -= ListTab_DrawItem;
3613 ListTab.DrawMode = TabDrawMode.Normal;
3614 ListTab.ImageList = this.TabImage;
3618 ListTab.DrawItem -= ListTab_DrawItem;
3619 ListTab.DrawItem += ListTab_DrawItem;
3620 ListTab.DrawMode = TabDrawMode.OwnerDrawFixed;
3621 ListTab.ImageList = null;
3624 catch (Exception ex)
3626 ex.Data["Instance"] = "ListTab(TabIconDisp)";
3627 ex.Data["IsTerminatePermission"] = false;
3633 if (!SettingManager.Common.UnreadManage)
3635 ReadedStripMenuItem.Enabled = false;
3636 UnreadStripMenuItem.Enabled = false;
3637 if (SettingManager.Common.TabIconDisp)
3639 foreach (TabPage myTab in ListTab.TabPages)
3641 myTab.ImageIndex = -1;
3647 ReadedStripMenuItem.Enabled = true;
3648 UnreadStripMenuItem.Enabled = true;
3651 catch (Exception ex)
3653 ex.Data["Instance"] = "ListTab(UnreadManage)";
3654 ex.Data["IsTerminatePermission"] = false;
3661 SplitContainer1.IsPanelInverted = !SettingManager.Common.StatusAreaAtBottom;
3663 var imgazyobizinet = ThumbnailGenerator.ImgAzyobuziNetInstance;
3664 imgazyobizinet.Enabled = SettingManager.Common.EnableImgAzyobuziNet;
3665 imgazyobizinet.DisabledInDM = SettingManager.Common.ImgAzyobuziNetDisabledInDM;
3667 this.PlaySoundMenuItem.Checked = SettingManager.Common.PlaySound;
3668 this.PlaySoundFileMenuItem.Checked = SettingManager.Common.PlaySound;
3669 _fntUnread = SettingManager.Local.FontUnread;
3670 _clUnread = SettingManager.Local.ColorUnread;
3671 _fntReaded = SettingManager.Local.FontRead;
3672 _clReaded = SettingManager.Local.ColorRead;
3673 _clFav = SettingManager.Local.ColorFav;
3674 _clOWL = SettingManager.Local.ColorOWL;
3675 _clRetweet = SettingManager.Local.ColorRetweet;
3676 _fntDetail = SettingManager.Local.FontDetail;
3677 _clDetail = SettingManager.Local.ColorDetail;
3678 _clDetailLink = SettingManager.Local.ColorDetailLink;
3679 _clDetailBackcolor = SettingManager.Local.ColorDetailBackcolor;
3680 _clSelf = SettingManager.Local.ColorSelf;
3681 _clAtSelf = SettingManager.Local.ColorAtSelf;
3682 _clTarget = SettingManager.Local.ColorTarget;
3683 _clAtTarget = SettingManager.Local.ColorAtTarget;
3684 _clAtFromTarget = SettingManager.Local.ColorAtFromTarget;
3685 _clAtTo = SettingManager.Local.ColorAtTo;
3686 _clListBackcolor = SettingManager.Local.ColorListBackcolor;
3687 _clInputBackcolor = SettingManager.Local.ColorInputBackcolor;
3688 _clInputFont = SettingManager.Local.ColorInputFont;
3689 _fntInputFont = SettingManager.Local.FontInputFont;
3690 _brsBackColorMine.Dispose();
3691 _brsBackColorAt.Dispose();
3692 _brsBackColorYou.Dispose();
3693 _brsBackColorAtYou.Dispose();
3694 _brsBackColorAtFromTarget.Dispose();
3695 _brsBackColorAtTo.Dispose();
3696 _brsBackColorNone.Dispose();
3697 _brsBackColorMine = new SolidBrush(_clSelf);
3698 _brsBackColorAt = new SolidBrush(_clAtSelf);
3699 _brsBackColorYou = new SolidBrush(_clTarget);
3700 _brsBackColorAtYou = new SolidBrush(_clAtTarget);
3701 _brsBackColorAtFromTarget = new SolidBrush(_clAtFromTarget);
3702 _brsBackColorAtTo = new SolidBrush(_clAtTo);
3703 _brsBackColorNone = new SolidBrush(_clListBackcolor);
3707 if (StatusText.Focused) StatusText.BackColor = _clInputBackcolor;
3708 StatusText.Font = _fntInputFont;
3709 StatusText.ForeColor = _clInputFont;
3711 catch (Exception ex)
3713 MessageBox.Show(ex.Message);
3718 InitDetailHtmlFormat();
3720 catch (Exception ex)
3722 ex.Data["Instance"] = "Font";
3723 ex.Data["IsTerminatePermission"] = false;
3729 foreach (TabPage tb in ListTab.TabPages)
3731 if (SettingManager.Common.TabIconDisp)
3733 if (_statuses.Tabs[tb.Text].UnreadCount == 0)
3740 catch (Exception ex)
3742 ex.Data["Instance"] = "ListTab(TabIconDisp no2)";
3743 ex.Data["IsTerminatePermission"] = false;
3749 var oldIconCol = _iconCol;
3751 if (SettingManager.Common.IconSize != oldIconSz)
3752 ApplyListViewIconSize(SettingManager.Common.IconSize);
3754 foreach (TabPage tp in ListTab.TabPages)
3756 DetailsListView lst = (DetailsListView)tp.Tag;
3758 using (ControlTransaction.Update(lst))
3760 lst.GridLines = SettingManager.Common.ShowGrid;
3761 lst.Font = _fntReaded;
3762 lst.BackColor = _clListBackcolor;
3764 if (_iconCol != oldIconCol)
3769 catch (Exception ex)
3771 ex.Data["Instance"] = "ListView(IconSize)";
3772 ex.Data["IsTerminatePermission"] = false;
3776 SetMainWindowTitle();
3777 SetNotifyIconText();
3779 this.PurgeListViewItemCache();
3780 _curList?.Refresh();
3783 _hookGlobalHotkey.UnregisterAllOriginalHotkey();
3784 if (SettingManager.Common.HotkeyEnabled)
3786 ///グローバルホットキーの登録。設定で変更可能にするかも
3787 HookGlobalHotkey.ModKeys modKey = HookGlobalHotkey.ModKeys.None;
3788 if ((SettingManager.Common.HotkeyModifier & Keys.Alt) == Keys.Alt)
3789 modKey |= HookGlobalHotkey.ModKeys.Alt;
3790 if ((SettingManager.Common.HotkeyModifier & Keys.Control) == Keys.Control)
3791 modKey |= HookGlobalHotkey.ModKeys.Ctrl;
3792 if ((SettingManager.Common.HotkeyModifier & Keys.Shift) == Keys.Shift)
3793 modKey |= HookGlobalHotkey.ModKeys.Shift;
3794 if ((SettingManager.Common.HotkeyModifier & Keys.LWin) == Keys.LWin)
3795 modKey |= HookGlobalHotkey.ModKeys.Win;
3797 _hookGlobalHotkey.RegisterOriginalHotkey(SettingManager.Common.HotkeyKey, SettingManager.Common.HotkeyValue, modKey);
3800 if (SettingManager.Common.IsUseNotifyGrowl) gh.RegisterGrowl();
3803 StatusText_TextChanged(null, null);
3812 // キャンセル時は Twitter クラスの認証情報を画面表示前の状態に戻す
3813 this.tw.Initialize(oldUser.AccessToken, oldUser.AccessTokenSecret, oldUser.Username, oldUser.UserId);
3816 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
3818 this.TopMost = SettingManager.Common.AlwaysTop;
3819 SaveConfigsAll(false);
3821 if (tw.UserId != oldUser.UserId)
3822 await this.doGetFollowersMenu();
3828 private void SetTabAlignment()
3830 var newAlignment = SettingManager.Common.ViewTabBottom ? TabAlignment.Bottom : TabAlignment.Top;
3831 if (ListTab.Alignment == newAlignment) return;
3833 // 各タブのリスト上の選択位置などを退避
3834 var listSelections = this.SaveListViewSelection();
3836 ListTab.Alignment = newAlignment;
3838 foreach (TabPage tab in ListTab.TabPages)
3840 DetailsListView lst = (DetailsListView)tab.Tag;
3841 TabModel tabInfo = _statuses.Tabs[tab.Text];
3842 using (ControlTransaction.Update(lst))
3845 this.RestoreListViewSelection(lst, tabInfo, listSelections[tabInfo.TabName]);
3850 private void ApplyListViewIconSize(MyCommon.IconSizes iconSz)
3856 case MyCommon.IconSizes.IconNone:
3859 case MyCommon.IconSizes.Icon16:
3862 case MyCommon.IconSizes.Icon24:
3865 case MyCommon.IconSizes.Icon48:
3868 case MyCommon.IconSizes.Icon48_2:
3876 // ディスプレイの DPI 設定を考慮したサイズを設定する
3877 _listViewImageList.ImageSize = new Size(
3879 (int)Math.Ceiling(this._iconSz * this.CurrentScaleFactor.Height));
3883 _listViewImageList.ImageSize = new Size(1, 1);
3887 private void ResetColumns(DetailsListView list)
3889 using (ControlTransaction.Update(list))
3890 using (ControlTransaction.Layout(list, false))
3893 list.ColumnClick -= MyList_ColumnClick;
3894 list.DrawColumnHeader -= MyList_DrawColumnHeader;
3895 list.ColumnReordered -= MyList_ColumnReordered;
3896 list.ColumnWidthChanged -= MyList_ColumnWidthChanged;
3898 var cols = list.Columns.Cast<ColumnHeader>().ToList();
3899 list.Columns.Clear();
3900 cols.ForEach(col => col.Dispose());
3903 InitColumns(list, true);
3905 list.ColumnClick += MyList_ColumnClick;
3906 list.DrawColumnHeader += MyList_DrawColumnHeader;
3907 list.ColumnReordered += MyList_ColumnReordered;
3908 list.ColumnWidthChanged += MyList_ColumnWidthChanged;
3912 public void AddNewTabForSearch(string searchWord)
3914 //同一検索条件のタブが既に存在すれば、そのタブアクティブにして終了
3915 foreach (var tb in _statuses.GetTabsByType<PublicSearchTabModel>())
3917 if (tb.SearchWords == searchWord && string.IsNullOrEmpty(tb.SearchLang))
3919 foreach (TabPage tp in ListTab.TabPages)
3921 if (tb.TabName == tp.Text)
3923 ListTab.SelectedTab = tp;
3930 string tabName = searchWord;
3931 for (int i = 0; i <= 100; i++)
3933 if (_statuses.ContainsTab(tabName))
3939 var tab = new PublicSearchTabModel(tabName);
3940 _statuses.AddTab(tab);
3941 AddNewTab(tab, startup: false);
3943 ListTab.SelectedIndex = ListTab.TabPages.Count - 1;
3945 ComboBox cmb = (ComboBox)ListTab.SelectedTab.Controls["panelSearch"].Controls["comboSearch"];
3946 cmb.Items.Add(searchWord);
3947 cmb.Text = searchWord;
3950 this.SearchButton_Click(ListTab.SelectedTab.Controls["panelSearch"].Controls["comboSearch"], null);
3953 private async Task ShowUserTimeline()
3955 if (!this.ExistCurrentPost) return;
3956 await this.AddNewTabForUserTimeline(_curPost.ScreenName);
3959 private void SearchComboBox_KeyDown(object sender, KeyEventArgs e)
3961 if (e.KeyCode == Keys.Escape)
3963 TabPage relTp = ListTab.SelectedTab;
3964 RemoveSpecifiedTab(relTp.Text, false);
3966 e.SuppressKeyPress = true;
3970 public async Task AddNewTabForUserTimeline(string user)
3972 //同一検索条件のタブが既に存在すれば、そのタブアクティブにして終了
3973 foreach (var tb in _statuses.GetTabsByType<UserTimelineTabModel>())
3975 if (tb.ScreenName == user)
3977 foreach (TabPage tp in ListTab.TabPages)
3979 if (tb.TabName == tp.Text)
3981 ListTab.SelectedTab = tp;
3988 string tabName = "user:" + user;
3989 while (_statuses.ContainsTab(tabName))
3994 var tab = new UserTimelineTabModel(tabName, user);
3995 this._statuses.AddTab(tab);
3996 this.AddNewTab(tab, startup: false);
3998 ListTab.SelectedIndex = ListTab.TabPages.Count - 1;
4001 await this.RefreshTabAsync(tab);
4004 public bool AddNewTab(TabModel tab, bool startup)
4007 foreach (TabPage tb in ListTab.TabPages)
4009 if (tb.Text == tab.TabName) return false;
4013 if (tab.TabName == Properties.Resources.AddNewTabText1) return false;
4015 var _tabPage = new TabPage();
4016 var _listCustom = new DetailsListView();
4018 int cnt = ListTab.TabPages.Count;
4020 ///ToDo:Create and set controls follow tabtypes
4022 using (ControlTransaction.Update(_listCustom))
4023 using (ControlTransaction.Layout(this.SplitContainer1.Panel1, false))
4024 using (ControlTransaction.Layout(this.SplitContainer1.Panel2, false))
4025 using (ControlTransaction.Layout(this.SplitContainer1, false))
4026 using (ControlTransaction.Layout(this.ListTab, false))
4027 using (ControlTransaction.Layout(this))
4028 using (ControlTransaction.Layout(_tabPage, false))
4030 _tabPage.Controls.Add(_listCustom);
4033 var userTab = tab as UserTimelineTabModel;
4034 var listTab = tab as ListTimelineTabModel;
4035 var searchTab = tab as PublicSearchTabModel;
4037 if (userTab != null || listTab != null)
4039 var label = new Label
4041 Dock = DockStyle.Top,
4046 if (listTab != null)
4048 label.Text = listTab.ListInfo.ToString();
4050 else if (userTab != null)
4052 label.Text = userTab.ScreenName + "'s Timeline";
4054 label.TextAlign = ContentAlignment.MiddleLeft;
4055 using (ComboBox tmpComboBox = new ComboBox())
4057 label.Height = tmpComboBox.Height;
4059 _tabPage.Controls.Add(label);
4062 else if (searchTab != null)
4064 var pnl = new Panel();
4066 var lbl = new Label();
4067 var cmb = new ComboBox();
4068 var btn = new Button();
4069 var cmbLang = new ComboBox();
4071 using (ControlTransaction.Layout(pnl, false))
4073 pnl.Controls.Add(cmb);
4074 pnl.Controls.Add(cmbLang);
4075 pnl.Controls.Add(btn);
4076 pnl.Controls.Add(lbl);
4077 pnl.Name = "panelSearch";
4079 pnl.Dock = DockStyle.Top;
4080 pnl.Height = cmb.Height;
4081 pnl.Enter += SearchControls_Enter;
4082 pnl.Leave += SearchControls_Leave;
4085 cmb.Anchor = AnchorStyles.Left | AnchorStyles.Right;
4086 cmb.Dock = DockStyle.Fill;
4087 cmb.Name = "comboSearch";
4088 cmb.DropDownStyle = ComboBoxStyle.DropDown;
4089 cmb.ImeMode = ImeMode.NoControl;
4090 cmb.TabStop = false;
4092 cmb.AutoCompleteMode = AutoCompleteMode.None;
4093 cmb.KeyDown += SearchComboBox_KeyDown;
4096 cmbLang.Anchor = AnchorStyles.Left | AnchorStyles.Right;
4097 cmbLang.Dock = DockStyle.Right;
4099 cmbLang.Name = "comboLang";
4100 cmbLang.DropDownStyle = ComboBoxStyle.DropDownList;
4101 cmbLang.TabStop = false;
4102 cmbLang.TabIndex = 2;
4103 cmbLang.Items.Add("");
4104 cmbLang.Items.Add("ja");
4105 cmbLang.Items.Add("en");
4106 cmbLang.Items.Add("ar");
4107 cmbLang.Items.Add("da");
4108 cmbLang.Items.Add("nl");
4109 cmbLang.Items.Add("fa");
4110 cmbLang.Items.Add("fi");
4111 cmbLang.Items.Add("fr");
4112 cmbLang.Items.Add("de");
4113 cmbLang.Items.Add("hu");
4114 cmbLang.Items.Add("is");
4115 cmbLang.Items.Add("it");
4116 cmbLang.Items.Add("no");
4117 cmbLang.Items.Add("pl");
4118 cmbLang.Items.Add("pt");
4119 cmbLang.Items.Add("ru");
4120 cmbLang.Items.Add("es");
4121 cmbLang.Items.Add("sv");
4122 cmbLang.Items.Add("th");
4124 lbl.Text = "Search(C-S-f)";
4125 lbl.Name = "label1";
4126 lbl.Dock = DockStyle.Left;
4128 lbl.Height = cmb.Height;
4129 lbl.TextAlign = ContentAlignment.MiddleLeft;
4132 btn.Text = "Search";
4133 btn.Name = "buttonSearch";
4134 btn.UseVisualStyleBackColor = true;
4135 btn.Dock = DockStyle.Right;
4136 btn.TabStop = false;
4138 btn.Click += SearchButton_Click;
4140 if (!string.IsNullOrEmpty(searchTab.SearchWords))
4142 cmb.Items.Add(searchTab.SearchWords);
4143 cmb.Text = searchTab.SearchWords;
4146 cmbLang.Text = searchTab.SearchLang;
4148 _tabPage.Controls.Add(pnl);
4152 _tabPage.Tag = _listCustom;
4153 this.ListTab.Controls.Add(_tabPage);
4155 _tabPage.Location = new Point(4, 4);
4156 _tabPage.Name = "CTab" + cnt;
4157 _tabPage.Size = new Size(380, 260);
4158 _tabPage.TabIndex = 2 + cnt;
4159 _tabPage.Text = tab.TabName;
4160 _tabPage.UseVisualStyleBackColor = true;
4161 _tabPage.AccessibleRole = AccessibleRole.PageTab;
4163 _listCustom.AccessibleName = Properties.Resources.AddNewTab_ListView_AccessibleName;
4164 _listCustom.TabIndex = 1;
4165 _listCustom.AllowColumnReorder = true;
4166 _listCustom.ContextMenuStrip = this.ContextMenuOperate;
4167 _listCustom.ColumnHeaderContextMenuStrip = this.ContextMenuColumnHeader;
4168 _listCustom.Dock = DockStyle.Fill;
4169 _listCustom.FullRowSelect = true;
4170 _listCustom.HideSelection = false;
4171 _listCustom.Location = new Point(0, 0);
4172 _listCustom.Margin = new Padding(0);
4173 _listCustom.Name = "CList" + Environment.TickCount;
4174 _listCustom.ShowItemToolTips = true;
4175 _listCustom.Size = new Size(380, 260);
4176 _listCustom.UseCompatibleStateImageBehavior = false;
4177 _listCustom.View = View.Details;
4178 _listCustom.OwnerDraw = true;
4179 _listCustom.VirtualMode = true;
4180 _listCustom.Font = _fntReaded;
4181 _listCustom.BackColor = _clListBackcolor;
4183 _listCustom.GridLines = SettingManager.Common.ShowGrid;
4184 _listCustom.AllowDrop = true;
4186 _listCustom.SmallImageList = _listViewImageList;
4188 InitColumns(_listCustom, startup);
4190 _listCustom.SelectedIndexChanged += MyList_SelectedIndexChanged;
4191 _listCustom.MouseDoubleClick += MyList_MouseDoubleClick;
4192 _listCustom.ColumnClick += MyList_ColumnClick;
4193 _listCustom.DrawColumnHeader += MyList_DrawColumnHeader;
4194 _listCustom.DragDrop += TweenMain_DragDrop;
4195 _listCustom.DragEnter += TweenMain_DragEnter;
4196 _listCustom.DragOver += TweenMain_DragOver;
4197 _listCustom.DrawItem += MyList_DrawItem;
4198 _listCustom.MouseClick += MyList_MouseClick;
4199 _listCustom.ColumnReordered += MyList_ColumnReordered;
4200 _listCustom.ColumnWidthChanged += MyList_ColumnWidthChanged;
4201 _listCustom.CacheVirtualItems += MyList_CacheVirtualItems;
4202 _listCustom.RetrieveVirtualItem += MyList_RetrieveVirtualItem;
4203 _listCustom.DrawSubItem += MyList_DrawSubItem;
4204 _listCustom.HScrolled += MyList_HScrolled;
4210 public bool RemoveSpecifiedTab(string TabName, bool confirm)
4212 var tabInfo = _statuses.GetTabByName(TabName);
4213 if (tabInfo.IsDefaultTabType || tabInfo.Protected) return false;
4217 string tmp = string.Format(Properties.Resources.RemoveSpecifiedTabText1, Environment.NewLine);
4218 if (MessageBox.Show(tmp, TabName + " " + Properties.Resources.RemoveSpecifiedTabText2,
4219 MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Cancel)
4225 var _tabPage = ListTab.TabPages.Cast<TabPage>().FirstOrDefault(tp => tp.Text == TabName);
4226 if (_tabPage == null) return false;
4228 SetListProperty(); //他のタブに列幅等を反映
4231 DetailsListView _listCustom = (DetailsListView)_tabPage.Tag;
4232 _tabPage.Tag = null;
4234 using (ControlTransaction.Layout(this.SplitContainer1.Panel1, false))
4235 using (ControlTransaction.Layout(this.SplitContainer1.Panel2, false))
4236 using (ControlTransaction.Layout(this.SplitContainer1, false))
4237 using (ControlTransaction.Layout(this.ListTab, false))
4238 using (ControlTransaction.Layout(this))
4239 using (ControlTransaction.Layout(_tabPage, false))
4241 if (this.ListTab.SelectedTab == _tabPage)
4243 this.ListTab.SelectTab((this._beforeSelectedTab != null && this.ListTab.TabPages.Contains(this._beforeSelectedTab)) ? this._beforeSelectedTab : this.ListTab.TabPages[0]);
4244 this._beforeSelectedTab = null;
4246 this.ListTab.Controls.Remove(_tabPage);
4249 if (tabInfo.TabType == MyCommon.TabUsageType.UserTimeline || tabInfo.TabType == MyCommon.TabUsageType.Lists)
4251 using (Control label = _tabPage.Controls["labelUser"])
4253 _tabPage.Controls.Remove(label);
4256 else if (tabInfo.TabType == MyCommon.TabUsageType.PublicSearch)
4258 using (Control pnl = _tabPage.Controls["panelSearch"])
4260 pnl.Enter -= SearchControls_Enter;
4261 pnl.Leave -= SearchControls_Leave;
4262 _tabPage.Controls.Remove(pnl);
4264 foreach (Control ctrl in pnl.Controls)
4266 if (ctrl.Name == "buttonSearch")
4268 ctrl.Click -= SearchButton_Click;
4270 else if (ctrl.Name == "comboSearch")
4272 ctrl.KeyDown -= SearchComboBox_KeyDown;
4274 pnl.Controls.Remove(ctrl);
4280 _tabPage.Controls.Remove(_listCustom);
4282 _listCustom.SelectedIndexChanged -= MyList_SelectedIndexChanged;
4283 _listCustom.MouseDoubleClick -= MyList_MouseDoubleClick;
4284 _listCustom.ColumnClick -= MyList_ColumnClick;
4285 _listCustom.DrawColumnHeader -= MyList_DrawColumnHeader;
4286 _listCustom.DragDrop -= TweenMain_DragDrop;
4287 _listCustom.DragEnter -= TweenMain_DragEnter;
4288 _listCustom.DragOver -= TweenMain_DragOver;
4289 _listCustom.DrawItem -= MyList_DrawItem;
4290 _listCustom.MouseClick -= MyList_MouseClick;
4291 _listCustom.ColumnReordered -= MyList_ColumnReordered;
4292 _listCustom.ColumnWidthChanged -= MyList_ColumnWidthChanged;
4293 _listCustom.CacheVirtualItems -= MyList_CacheVirtualItems;
4294 _listCustom.RetrieveVirtualItem -= MyList_RetrieveVirtualItem;
4295 _listCustom.DrawSubItem -= MyList_DrawSubItem;
4296 _listCustom.HScrolled -= MyList_HScrolled;
4298 var cols = _listCustom.Columns.Cast<ColumnHeader>().ToList<ColumnHeader>();
4299 _listCustom.Columns.Clear();
4300 cols.ForEach(col => col.Dispose());
4303 _listCustom.ContextMenuStrip = null;
4304 _listCustom.ColumnHeaderContextMenuStrip = null;
4305 _listCustom.Font = null;
4307 _listCustom.SmallImageList = null;
4308 _listCustom.ListViewItemSorter = null;
4311 if (_curTab.Equals(_tabPage))
4318 this.PurgeListViewItemCache();
4322 _listCustom.Dispose();
4323 _statuses.RemoveTab(TabName);
4325 foreach (TabPage tp in ListTab.TabPages)
4327 DetailsListView lst = (DetailsListView)tp.Tag;
4328 var count = _statuses.Tabs[tp.Text].AllCount;
4329 lst.VirtualListSize = count;
4335 private void ListTab_Deselected(object sender, TabControlEventArgs e)
4337 this.PurgeListViewItemCache();
4338 _beforeSelectedTab = e.TabPage;
4341 private void ListTab_MouseMove(object sender, MouseEventArgs e)
4345 if (!SettingManager.Common.TabMouseLock && e.Button == MouseButtons.Left && _tabDrag)
4348 Rectangle dragEnableRectangle = new Rectangle(_tabMouseDownPoint.X - (SystemInformation.DragSize.Width / 2), _tabMouseDownPoint.Y - (SystemInformation.DragSize.Height / 2), SystemInformation.DragSize.Width, SystemInformation.DragSize.Height);
4349 if (!dragEnableRectangle.Contains(e.Location))
4351 //タブが多段の場合にはMouseDownの前の段階で選択されたタブの段が変わっているので、このタイミングでカーソルの位置からタブを判定出来ない。
4352 tn = ListTab.SelectedTab.Text;
4355 if (string.IsNullOrEmpty(tn)) return;
4357 foreach (TabPage tb in ListTab.TabPages)
4361 ListTab.DoDragDrop(tb, DragDropEffects.All);
4371 Point cpos = new Point(e.X, e.Y);
4372 for (int i = 0; i < ListTab.TabPages.Count; i++)
4374 Rectangle rect = ListTab.GetTabRect(i);
4375 if (rect.Left <= cpos.X & cpos.X <= rect.Right &
4376 rect.Top <= cpos.Y & cpos.Y <= rect.Bottom)
4378 _rclickTabName = ListTab.TabPages[i].Text;
4384 private async void ListTab_SelectedIndexChanged(object sender, EventArgs e)
4386 //_curList.Refresh();
4387 SetMainWindowTitle();
4388 SetStatusLabelUrl();
4389 SetApiStatusLabel();
4390 if (ListTab.Focused || ((Control)ListTab.SelectedTab.Tag).Focused) this.Tag = ListTab.Tag;
4391 TabMenuControl(ListTab.SelectedTab.Text);
4392 this.PushSelectPostChain();
4393 await DispSelectedPost();
4396 private void SetListProperty()
4398 //削除などで見つからない場合は処理せず
4399 if (_curList == null) return;
4400 if (!_isColumnChanged) return;
4402 int[] dispOrder = new int[_curList.Columns.Count];
4403 for (int i = 0; i < _curList.Columns.Count; i++)
4405 for (int j = 0; j < _curList.Columns.Count; j++)
4407 if (_curList.Columns[j].DisplayIndex == i)
4416 foreach (TabPage tb in ListTab.TabPages)
4418 if (!tb.Equals(_curTab))
4420 if (tb.Tag != null && tb.Controls.Count > 0)
4422 DetailsListView lst = (DetailsListView)tb.Tag;
4423 for (int i = 0; i < lst.Columns.Count; i++)
4425 lst.Columns[dispOrder[i]].DisplayIndex = i;
4426 lst.Columns[i].Width = _curList.Columns[i].Width;
4432 _isColumnChanged = false;
4435 private void StatusText_KeyPress(object sender, KeyPressEventArgs e)
4437 if (e.KeyChar == '@')
4439 if (!SettingManager.Common.UseAtIdSupplement) return;
4441 int cnt = AtIdSupl.ItemCount;
4442 ShowSuplDialog(StatusText, AtIdSupl);
4443 if (cnt != AtIdSupl.ItemCount) ModifySettingAtId = true;
4446 else if (e.KeyChar == '#')
4448 if (!SettingManager.Common.UseHashSupplement) return;
4449 ShowSuplDialog(StatusText, HashSupl);
4454 public void ShowSuplDialog(TextBox owner, AtIdSupplement dialog)
4455 => this.ShowSuplDialog(owner, dialog, 0, "");
4457 public void ShowSuplDialog(TextBox owner, AtIdSupplement dialog, int offset)
4458 => this.ShowSuplDialog(owner, dialog, offset, "");
4460 public void ShowSuplDialog(TextBox owner, AtIdSupplement dialog, int offset, string startswith)
4462 dialog.StartsWith = startswith;
4469 dialog.ShowDialog();
4471 this.TopMost = SettingManager.Common.AlwaysTop;
4472 int selStart = owner.SelectionStart;
4475 if (dialog.DialogResult == DialogResult.OK)
4477 if (!string.IsNullOrEmpty(dialog.inputText))
4481 fHalf = owner.Text.Substring(0, selStart - offset);
4483 if (selStart < owner.Text.Length)
4485 eHalf = owner.Text.Substring(selStart);
4487 owner.Text = fHalf + dialog.inputText + eHalf;
4488 owner.SelectionStart = selStart + dialog.inputText.Length;
4495 fHalf = owner.Text.Substring(0, selStart);
4497 if (selStart < owner.Text.Length)
4499 eHalf = owner.Text.Substring(selStart);
4501 owner.Text = fHalf + eHalf;
4504 owner.SelectionStart = selStart;
4510 private void StatusText_KeyUp(object sender, KeyEventArgs e)
4513 if (!e.Alt && !e.Control && !e.Shift)
4515 if (e.KeyCode == Keys.Space || e.KeyCode == Keys.ProcessKey)
4517 bool isSpace = false;
4518 foreach (char c in StatusText.Text)
4520 if (c == ' ' || c == ' ')
4533 StatusText.Text = "";
4534 JumpUnreadMenuItem_Click(null, null);
4538 this.StatusText_TextChanged(null, null);
4541 private void StatusText_TextChanged(object sender, EventArgs e)
4544 int pLen = this.GetRestStatusCount(this.FormatStatusTextExtended(this.StatusText.Text));
4545 lblLen.Text = pLen.ToString();
4548 StatusText.ForeColor = Color.Red;
4552 StatusText.ForeColor = _clInputFont;
4555 this.StatusText.AccessibleDescription = string.Format(Properties.Resources.StatusText_AccessibleDescription, pLen);
4557 if (string.IsNullOrEmpty(StatusText.Text))
4559 this.inReplyTo = null;
4564 /// メンション以外の文字列が含まれていないテキストであるか判定します
4566 internal static bool TextContainsOnlyMentions(string text)
4568 var mentions = TweetExtractor.ExtractMentionEntities(text).OrderBy(x => x.Indices[0]);
4571 foreach (var mention in mentions)
4573 var textPart = text.Substring(startIndex, mention.Indices[0] - startIndex);
4575 if (!string.IsNullOrWhiteSpace(textPart))
4578 startIndex = mention.Indices[1];
4581 var textPartLast = text.Substring(startIndex);
4583 if (!string.IsNullOrWhiteSpace(textPartLast))
4590 /// 投稿時に auto_populate_reply_metadata オプションによって自動で追加されるメンションを除去します
4592 private string RemoveAutoPopuratedMentions(string statusText, out long[] autoPopulatedUserIds)
4594 List<long> _autoPopulatedUserIds = new List<long>();
4596 var replyToPost = this.inReplyTo != null ? this._statuses[this.inReplyTo.Value.StatusId] : null;
4597 if (replyToPost != null)
4599 if (statusText.StartsWith($"@{replyToPost.ScreenName} ", StringComparison.Ordinal))
4601 statusText = statusText.Substring(replyToPost.ScreenName.Length + 2);
4602 _autoPopulatedUserIds.Add(replyToPost.UserId);
4604 foreach (var (userId, screenName) in replyToPost.ReplyToList)
4606 if (statusText.StartsWith($"@{screenName} ", StringComparison.Ordinal))
4608 statusText = statusText.Substring(screenName.Length + 2);
4609 _autoPopulatedUserIds.Add(userId);
4615 autoPopulatedUserIds = _autoPopulatedUserIds.ToArray();
4621 /// attachment_url に指定可能な URL が含まれていれば除去
4623 private string RemoveAttachmentUrl(string statusText, out string attachmentUrl)
4625 attachmentUrl = null;
4627 // attachment_url は media_id と同時に使用できない
4628 if (this.ImageSelector.Visible && this.ImageSelector.SelectedService is TwitterPhoto)
4631 var match = Twitter.AttachmentUrlRegex.Match(statusText);
4635 attachmentUrl = match.Value;
4638 statusText = statusText.Substring(0, match.Index);
4640 // テキストと URL の間にスペースが含まれていれば除去
4641 return statusText.TrimEnd(' ');
4644 private string FormatStatusTextExtended(string statusText)
4645 => this.FormatStatusTextExtended(statusText, out var autoPopulatedUserIds, out var attachmentUrl);
4648 /// <see cref="FormatStatusText"/> に加えて、拡張モードで140字にカウントされない文字列の除去を行います
4650 private string FormatStatusTextExtended(string statusText, out long[] autoPopulatedUserIds, out string attachmentUrl)
4652 statusText = this.RemoveAutoPopuratedMentions(statusText, out autoPopulatedUserIds);
4654 statusText = this.RemoveAttachmentUrl(statusText, out attachmentUrl);
4656 return this.FormatStatusText(statusText);
4660 /// ツイート投稿前のフッター付与などの前処理を行います
4662 private string FormatStatusText(string statusText)
4664 statusText = statusText.Replace("\r\n", "\n");
4666 if (this.urlMultibyteSplit)
4669 statusText = Regex.Replace(statusText, @"https?:\/\/[-_.!~*'()a-zA-Z0-9;\/?:\@&=+\$,%#^]+", "$& ");
4672 if (SettingManager.Common.WideSpaceConvert)
4674 // 文中の全角スペースを半角スペース1個にする
4675 statusText = statusText.Replace(" ", " ");
4678 // DM の場合はこれ以降の処理を行わない
4679 if (statusText.StartsWith("D ", StringComparison.OrdinalIgnoreCase))
4683 if (SettingManager.Common.PostShiftEnter)
4685 disableFooter = MyCommon.IsKeyDown(Keys.Control);
4689 if (this.StatusText.Multiline && !SettingManager.Common.PostCtrlEnter)
4690 disableFooter = MyCommon.IsKeyDown(Keys.Control);
4692 disableFooter = MyCommon.IsKeyDown(Keys.Shift);
4695 if (statusText.Contains("RT @"))
4696 disableFooter = true;
4698 // 自分宛のリプライの場合は先頭の「@screen_name 」の部分を除去する (in_reply_to_status_id は維持される)
4699 if (this.inReplyTo != null && this.inReplyTo.Value.ScreenName == this.tw.Username)
4701 var mentionSelf = $"@{this.tw.Username} ";
4702 if (statusText.StartsWith(mentionSelf, StringComparison.OrdinalIgnoreCase))
4704 if (statusText.Length > mentionSelf.Length || this.GetSelectedImageService() != null)
4705 statusText = statusText.Substring(mentionSelf.Length);
4712 var hashtag = this.HashMgr.UseHash;
4713 if (!string.IsNullOrEmpty(hashtag) && !(this.HashMgr.IsNotAddToAtReply && this.inReplyTo != null))
4716 header = HashMgr.UseHash + " ";
4718 footer = " " + HashMgr.UseHash;
4723 if (SettingManager.Local.UseRecommendStatus)
4726 footer += this.recommendedStatusFooter;
4728 else if (!string.IsNullOrEmpty(SettingManager.Local.StatusText))
4730 // テキストボックスに入力されている文字列を使用する
4731 footer += " " + SettingManager.Local.StatusText.Trim();
4735 statusText = header + statusText + footer;
4737 if (this.preventSmsCommand)
4739 // ツイートが意図せず SMS コマンドとして解釈されることを回避 (D, DM, M のみ)
4740 // 参照: https://support.twitter.com/articles/14020
4742 if (Regex.IsMatch(statusText, @"^[+\-\[\]\s\\.,*/(){}^~|='&%$#""<>?]*(d|dm|m)([+\-\[\]\s\\.,*/(){}^~|='&%$#""<>?]+|$)", RegexOptions.IgnoreCase)
4743 && !Twitter.DMSendTextRegex.IsMatch(statusText))
4745 // U+200B (ZERO WIDTH SPACE) を先頭に加えて回避
4746 statusText = '\u200b' + statusText;
4754 /// 投稿欄に表示する入力可能な文字数を計算します
4756 private int GetRestStatusCount(string statusText)
4758 var remainCount = this.tw.GetTextLengthRemain(statusText);
4760 var uploadService = this.GetSelectedImageService();
4761 if (uploadService != null)
4763 // TODO: ImageSelector で選択中の画像の枚数が mediaCount 引数に渡るようにする
4764 remainCount -= uploadService.GetReservedTextLength(1);
4770 private IMediaUploadService GetSelectedImageService()
4771 => this.ImageSelector.Visible ? this.ImageSelector.SelectedService : null;
4773 private void MyList_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e)
4775 if (sender != this._curList)
4778 var listCache = this._listItemCache;
4779 if (listCache?.TargetList == sender && listCache.IsSupersetOf(e.StartIndex, e.EndIndex))
4781 // If the newly requested cache is a subset of the old cache,
4782 // no need to rebuild everything, so do nothing.
4786 // Now we need to rebuild the cache.
4787 this.CreateCache(e.StartIndex, e.EndIndex);
4790 private void MyList_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
4792 var listCache = this._listItemCache;
4793 if (listCache?.TargetList == sender)
4795 if (listCache.TryGetValue(e.ItemIndex, out var item, out var cacheItemPost))
4802 // A cache miss, so create a new ListViewItem and pass it back.
4803 TabPage tb = (TabPage)((DetailsListView)sender).Parent;
4806 e.Item = this.CreateItem(tb, _statuses.Tabs[tb.Text][e.ItemIndex], e.ItemIndex);
4810 // 不正な要求に対する間に合わせの応答
4811 string[] sitem = {"", "", "", "", "", "", "", ""};
4812 e.Item = new ImageListViewItem(sitem);
4816 private void CreateCache(int startIndex, int endIndex)
4818 var tabInfo = this._statuses.Tabs[this._curTab.Text];
4820 if (tabInfo.AllCount == 0)
4823 // インデックスを 0...(tabInfo.AllCount - 1) の範囲内にする
4824 int FilterRange(int index)
4825 => Math.Max(Math.Min(index, tabInfo.AllCount - 1), 0);
4827 // キャッシュ要求(要求範囲±30を作成)
4828 startIndex = FilterRange(startIndex - 30);
4829 endIndex = FilterRange(endIndex + 30);
4831 var cacheLength = endIndex - startIndex + 1;
4833 var posts = tabInfo[startIndex, endIndex]; //配列で取得
4834 var listItems = Enumerable.Range(0, cacheLength)
4835 .Select(x => this.CreateItem(this._curTab, posts[x], startIndex + x))
4838 var listCache = new ListViewItemCache
4840 TargetList = this._curList,
4841 StartIndex = startIndex,
4842 EndIndex = endIndex,
4844 ListItem = listItems,
4847 Interlocked.Exchange(ref this._listItemCache, listCache);
4851 /// DetailsListView のための ListViewItem のキャッシュを消去する
4853 private void PurgeListViewItemCache()
4854 => Interlocked.Exchange(ref this._listItemCache, null);
4856 private ListViewItem CreateItem(TabPage Tab, PostClass Post, int Index)
4858 StringBuilder mk = new StringBuilder();
4859 //if (Post.IsDeleted) mk.Append("×");
4860 //if (Post.IsMark) mk.Append("♪");
4861 //if (Post.IsProtect) mk.Append("Ю");
4862 //if (Post.InReplyToStatusId != null) mk.Append("⇒");
4863 if (Post.FavoritedCount > 0) mk.Append("+" + Post.FavoritedCount);
4864 ImageListViewItem itm;
4865 if (Post.RetweetedId == null)
4867 string[] sitem= {"",
4869 Post.IsDeleted ? "(DELETED)" : Post.AccessibleText.Replace('\n', ' '),
4870 Post.CreatedAt.ToLocalTimeString(SettingManager.Common.DateTimeFormat),
4875 itm = new ImageListViewItem(sitem, this.IconCache, Post.ImageUrl);
4879 string[] sitem = {"",
4881 Post.IsDeleted ? "(DELETED)" : Post.AccessibleText.Replace('\n', ' '),
4882 Post.CreatedAt.ToLocalTimeString(SettingManager.Common.DateTimeFormat),
4883 Post.ScreenName + Environment.NewLine + "(RT:" + Post.RetweetedBy + ")",
4887 itm = new ImageListViewItem(sitem, this.IconCache, Post.ImageUrl);
4889 itm.StateIndex = Post.StateIndex;
4892 bool read = Post.IsRead;
4893 //未読管理していなかったら既読として扱う
4894 if (!_statuses.Tabs[Tab.Text].UnreadManage || !SettingManager.Common.UnreadManage) read = true;
4895 ChangeItemStyleRead(read, itm, Post, null);
4896 if (Tab.Equals(_curTab)) ColorizeList(itm, Index);
4901 /// 全てのタブの振り分けルールを反映し直します
4903 private void ApplyPostFilters()
4905 using (ControlTransaction.Cursor(this, Cursors.WaitCursor))
4907 this.PurgeListViewItemCache();
4908 this._curPost = null;
4909 this._curItemIndex = -1;
4910 this._statuses.FilterAll();
4912 foreach (TabPage tabPage in this.ListTab.TabPages)
4914 var tab = this._statuses.Tabs[tabPage.Text];
4916 var listview = (DetailsListView)tabPage.Tag;
4917 using (ControlTransaction.Update(listview))
4919 listview.VirtualListSize = tab.AllCount;
4922 if (SettingManager.Common.TabIconDisp)
4924 if (tab.UnreadCount > 0)
4925 tabPage.ImageIndex = 0;
4927 tabPage.ImageIndex = -1;
4931 if (!SettingManager.Common.TabIconDisp)
4932 this.ListTab.Refresh();
4934 SetMainWindowTitle();
4935 SetStatusLabelUrl();
4939 private void MyList_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
4940 => e.DrawDefault = true;
4942 private void MyList_HScrolled(object sender, EventArgs e)
4943 => ((DetailsListView)sender).Refresh();
4945 private void MyList_DrawItem(object sender, DrawListViewItemEventArgs e)
4947 if (e.State == 0) return;
4948 e.DrawDefault = false;
4950 SolidBrush brs2 = null;
4951 if (!e.Item.Selected) //e.ItemStateでうまく判定できない???
4953 if (e.Item.BackColor == _clSelf)
4954 brs2 = _brsBackColorMine;
4955 else if (e.Item.BackColor == _clAtSelf)
4956 brs2 = _brsBackColorAt;
4957 else if (e.Item.BackColor == _clTarget)
4958 brs2 = _brsBackColorYou;
4959 else if (e.Item.BackColor == _clAtTarget)
4960 brs2 = _brsBackColorAtYou;
4961 else if (e.Item.BackColor == _clAtFromTarget)
4962 brs2 = _brsBackColorAtFromTarget;
4963 else if (e.Item.BackColor == _clAtTo)
4964 brs2 = _brsBackColorAtTo;
4966 brs2 = _brsBackColorNone;
4971 if (((Control)sender).Focused)
4972 brs2 = _brsHighLight;
4974 brs2 = _brsDeactiveSelection;
4976 e.Graphics.FillRectangle(brs2, e.Bounds);
4977 e.DrawFocusRectangle();
4978 this.DrawListViewItemIcon(e);
4981 private void MyList_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
4983 if (e.ItemState == 0) return;
4985 if (e.ColumnIndex > 0)
4988 var post = (PostClass)e.Item.Tag;
4990 RectangleF rct = e.Bounds;
4991 rct.Width = e.Header.Width;
4992 int fontHeight = e.Item.Font.Height;
4995 rct.Y += fontHeight;
4996 rct.Height -= fontHeight;
4999 int drawLineCount = Math.Max(1, Math.DivRem((int)rct.Height, fontHeight, out var heightDiff));
5001 //if (heightDiff > fontHeight * 0.7)
5003 // rct.Height += fontHeight;
5004 // drawLineCount += 1;
5007 //フォントの高さの半分を足してるのは保険。無くてもいいかも。
5008 if (!_iconCol && drawLineCount <= 1)
5010 //rct.Inflate(0, heightDiff / -2);
5011 //rct.Height += fontHeight / 2;
5013 else if (heightDiff < fontHeight * 0.7)
5015 //最終行が70%以上欠けていたら、最終行は表示しない
5016 //rct.Height = (float)((fontHeight * drawLineCount) + (fontHeight / 2));
5017 rct.Height = (fontHeight * drawLineCount) - 1;
5024 //if (!_iconCol && drawLineCount > 1)
5026 // rct.Y += fontHeight * 0.2;
5027 // if (heightDiff >= fontHeight * 0.8) rct.Height -= fontHeight * 0.2;
5032 Color color = (!e.Item.Selected) ? e.Item.ForeColor : //選択されていない行
5033 (((Control)sender).Focused) ? _clHighLight : //選択中の行
5038 Rectangle rctB = e.Bounds;
5039 rctB.Width = e.Header.Width;
5040 rctB.Height = fontHeight;
5042 using (Font fnt = new Font(e.Item.Font, FontStyle.Bold))
5044 TextRenderer.DrawText(e.Graphics,
5045 post.IsDeleted ? "(DELETED)" : post.TextSingleLine,
5047 Rectangle.Round(rct),
5049 TextFormatFlags.WordBreak |
5050 TextFormatFlags.EndEllipsis |
5051 TextFormatFlags.GlyphOverhangPadding |
5052 TextFormatFlags.NoPrefix);
5053 TextRenderer.DrawText(e.Graphics,
5054 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 + "]",
5058 TextFormatFlags.SingleLine |
5059 TextFormatFlags.EndEllipsis |
5060 TextFormatFlags.GlyphOverhangPadding |
5061 TextFormatFlags.NoPrefix);
5067 if (e.ColumnIndex != 2)
5068 text = e.SubItem.Text;
5070 text = post.IsDeleted ? "(DELETED)" : post.TextSingleLine;
5072 if (drawLineCount == 1)
5074 TextRenderer.DrawText(e.Graphics,
5077 Rectangle.Round(rct),
5079 TextFormatFlags.SingleLine |
5080 TextFormatFlags.EndEllipsis |
5081 TextFormatFlags.GlyphOverhangPadding |
5082 TextFormatFlags.NoPrefix |
5083 TextFormatFlags.VerticalCenter);
5087 TextRenderer.DrawText(e.Graphics,
5090 Rectangle.Round(rct),
5092 TextFormatFlags.WordBreak |
5093 TextFormatFlags.EndEllipsis |
5094 TextFormatFlags.GlyphOverhangPadding |
5095 TextFormatFlags.NoPrefix);
5098 //if (e.ColumnIndex == 6) this.DrawListViewItemStateIcon(e, rct);
5103 private void DrawListViewItemIcon(DrawListViewItemEventArgs e)
5105 if (_iconSz == 0) return;
5107 ImageListViewItem item = (ImageListViewItem)e.Item;
5109 //e.Bounds.Leftが常に0を指すから自前で計算
5110 Rectangle itemRect = item.Bounds;
5111 var col0 = e.Item.ListView.Columns[0];
5112 itemRect.Width = col0.Width;
5114 if (col0.DisplayIndex > 0)
5116 foreach (ColumnHeader clm in e.Item.ListView.Columns)
5118 if (clm.DisplayIndex < col0.DisplayIndex)
5119 itemRect.X += clm.Width;
5123 // ディスプレイの DPI 設定を考慮したアイコンサイズ
5124 var realIconSize = new SizeF(this._iconSz * this.CurrentScaleFactor.Width, this._iconSz * this.CurrentScaleFactor.Height).ToSize();
5125 var realStateSize = new SizeF(16 * this.CurrentScaleFactor.Width, 16 * this.CurrentScaleFactor.Height).ToSize();
5128 var img = item.Image;
5131 iconRect = Rectangle.Intersect(new Rectangle(e.Item.GetBounds(ItemBoundsPortion.Icon).Location, realIconSize), itemRect);
5132 iconRect.Offset(0, Math.Max(0, (itemRect.Height - realIconSize.Height) / 2));
5134 if (iconRect.Width > 0)
5136 e.Graphics.FillRectangle(Brushes.White, iconRect);
5137 e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
5140 e.Graphics.DrawImage(img.Image, iconRect);
5142 catch (ArgumentException)
5144 item.RefreshImageAsync();
5150 iconRect = Rectangle.Intersect(new Rectangle(e.Item.GetBounds(ItemBoundsPortion.Icon).Location, new Size(1, 1)), itemRect);
5151 //iconRect.Offset(0, Math.Max(0, (itemRect.Height - realIconSize.Height) / 2));
5153 item.GetImageAsync();
5156 if (item.StateIndex > -1)
5158 Rectangle stateRect = Rectangle.Intersect(new Rectangle(new Point(iconRect.X + realIconSize.Width + 2, iconRect.Y), realStateSize), itemRect);
5159 if (stateRect.Width > 0)
5161 //e.Graphics.FillRectangle(Brushes.White, stateRect);
5162 //e.Graphics.InterpolationMode = Drawing2D.InterpolationMode.High;
5163 e.Graphics.DrawImage(this.PostStateImageList.Images[item.StateIndex], stateRect);
5168 protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
5170 base.ScaleControl(factor, specified);
5172 ScaleChildControl(this.TabImage, factor);
5174 var tabpages = this.ListTab.TabPages.Cast<TabPage>();
5175 var listviews = tabpages.Select(x => x.Tag).Cast<ListView>();
5177 foreach (var listview in listviews)
5179 ScaleChildControl(listview, factor);
5183 //private void DrawListViewItemStateIcon(DrawListViewSubItemEventArgs e, RectangleF rct)
5185 // ImageListViewItem item = (ImageListViewItem)e.Item;
5186 // if (item.StateImageIndex > -1)
5188 // ////e.Bounds.Leftが常に0を指すから自前で計算
5189 // //Rectangle itemRect = item.Bounds;
5190 // //itemRect.Width = e.Item.ListView.Columns[4].Width;
5192 // //foreach (ColumnHeader clm in e.Item.ListView.Columns)
5194 // // if (clm.DisplayIndex < e.Item.ListView.Columns[4].DisplayIndex)
5196 // // itemRect.X += clm.Width;
5200 // //Rectangle iconRect = Rectangle.Intersect(new Rectangle(e.Item.GetBounds(ItemBoundsPortion.Icon).Location, new Size(_iconSz, _iconSz)), itemRect);
5201 // //iconRect.Offset(0, Math.Max(0, (itemRect.Height - _iconSz) / 2));
5203 // if (rct.Width > 0)
5205 // RectangleF stateRect = RectangleF.Intersect(rct, new RectangleF(rct.Location, new Size(18, 16)));
5206 // //e.Graphics.FillRectangle(Brushes.White, rct);
5207 // //e.Graphics.InterpolationMode = Drawing2D.InterpolationMode.High;
5208 // e.Graphics.DrawImage(this.PostStateImageList.Images(item.StateImageIndex), stateRect);
5213 internal void DoTabSearch(string searchWord, bool caseSensitive, bool useRegex, SEARCHTYPE searchType)
5215 var tab = this._statuses.Tabs[this._curTab.Text];
5217 if (tab.AllCount == 0)
5219 MessageBox.Show(Properties.Resources.DoTabSearchText2, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Information);
5223 var selectedIndex = this._curList.SelectedIndices.Count != 0 ? this._curList.SelectedIndices[0] : -1;
5228 case SEARCHTYPE.NextSearch: // 次を検索
5229 if (selectedIndex != -1)
5230 startIndex = Math.Min(selectedIndex + 1, tab.AllCount - 1);
5234 case SEARCHTYPE.PrevSearch: // 前を検索
5235 if (selectedIndex != -1)
5236 startIndex = Math.Max(selectedIndex - 1, 0);
5238 startIndex = tab.AllCount - 1;
5240 case SEARCHTYPE.DialogSearch: // ダイアログからの検索
5242 if (selectedIndex != -1)
5243 startIndex = selectedIndex;
5249 Func<string, bool> stringComparer;
5252 stringComparer = this.CreateSearchComparer(searchWord, useRegex, caseSensitive);
5254 catch (ArgumentException)
5256 MessageBox.Show(Properties.Resources.DoTabSearchText1, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Error);
5260 var reverse = searchType == SEARCHTYPE.PrevSearch;
5261 var foundIndex = tab.SearchPostsAll(stringComparer, startIndex, reverse)
5262 .DefaultIfEmpty(-1).First();
5264 if (foundIndex == -1)
5266 MessageBox.Show(Properties.Resources.DoTabSearchText2, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Information);
5270 this.SelectListItem(this._curList, foundIndex);
5271 this._curList.EnsureVisible(foundIndex);
5274 private void MenuItemSubSearch_Click(object sender, EventArgs e)
5275 => this.ShowSearchDialog(); // 検索メニュー
5277 private void MenuItemSearchNext_Click(object sender, EventArgs e)
5279 var previousSearch = this.SearchDialog.ResultOptions;
5280 if (previousSearch == null || previousSearch.Type != SearchWordDialog.SearchType.Timeline)
5282 this.SearchDialog.Reset();
5283 this.ShowSearchDialog();
5289 previousSearch.Query,
5290 previousSearch.CaseSensitive,
5291 previousSearch.UseRegex,
5292 SEARCHTYPE.NextSearch);
5295 private void MenuItemSearchPrev_Click(object sender, EventArgs e)
5297 var previousSearch = this.SearchDialog.ResultOptions;
5298 if (previousSearch == null || previousSearch.Type != SearchWordDialog.SearchType.Timeline)
5300 this.SearchDialog.Reset();
5301 this.ShowSearchDialog();
5307 previousSearch.Query,
5308 previousSearch.CaseSensitive,
5309 previousSearch.UseRegex,
5310 SEARCHTYPE.PrevSearch);
5314 /// 検索ダイアログを表示し、検索を実行します
5316 private void ShowSearchDialog()
5318 if (this.SearchDialog.ShowDialog(this) != DialogResult.OK)
5320 this.TopMost = SettingManager.Common.AlwaysTop;
5323 this.TopMost = SettingManager.Common.AlwaysTop;
5325 var searchOptions = this.SearchDialog.ResultOptions;
5326 if (searchOptions.Type == SearchWordDialog.SearchType.Timeline)
5328 if (searchOptions.NewTab)
5330 var tabName = Properties.Resources.SearchResults_TabName;
5334 tabName = this._statuses.MakeTabName(tabName);
5336 catch (TabException ex)
5338 MessageBox.Show(this, ex.Message, ApplicationSettings.ApplicationName, MessageBoxButtons.OK, MessageBoxIcon.Error);
5341 var resultTab = new LocalSearchTabModel(tabName);
5342 this.AddNewTab(resultTab, startup: false);
5343 this._statuses.AddTab(resultTab);
5345 var targetTab = this._statuses.Tabs[this._curTab.Text];
5347 Func<string, bool> stringComparer;
5350 stringComparer = this.CreateSearchComparer(searchOptions.Query, searchOptions.UseRegex, searchOptions.CaseSensitive);
5352 catch (ArgumentException)
5354 MessageBox.Show(Properties.Resources.DoTabSearchText1, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Error);
5358 var foundIndices = targetTab.SearchPostsAll(stringComparer).ToArray();
5359 if (foundIndices.Length == 0)
5361 MessageBox.Show(Properties.Resources.DoTabSearchText2, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Information);
5365 var foundPosts = foundIndices.Select(x => targetTab[x]);
5366 foreach (var post in foundPosts)
5368 resultTab.AddPostQueue(post);
5371 this._statuses.DistributePosts();
5372 this.RefreshTimeline();
5374 var tabPage = this.ListTab.TabPages.Cast<TabPage>()
5375 .First(x => x.Text == tabName);
5377 this.ListTab.SelectedTab = tabPage;
5382 searchOptions.Query,
5383 searchOptions.CaseSensitive,
5384 searchOptions.UseRegex,
5385 SEARCHTYPE.DialogSearch);
5388 else if (searchOptions.Type == SearchWordDialog.SearchType.Public)
5390 this.AddNewTabForSearch(searchOptions.Query);
5394 /// <summary>発言検索に使用するメソッドを生成します</summary>
5395 /// <exception cref="ArgumentException">
5396 /// <paramref name="useRegex"/> が true かつ、<paramref name="query"/> が不正な正規表現な場合
5398 private Func<string, bool> CreateSearchComparer(string query, bool useRegex, bool caseSensitive)
5402 var regexOption = caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase;
5403 var regex = new Regex(query, regexOption);
5405 return x => regex.IsMatch(x);
5409 var comparisonType = caseSensitive ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase;
5411 return x => x.IndexOf(query, comparisonType) != -1;
5415 private void AboutMenuItem_Click(object sender, EventArgs e)
5417 using (TweenAboutBox about = new TweenAboutBox())
5419 about.ShowDialog(this);
5421 this.TopMost = SettingManager.Common.AlwaysTop;
5424 private void JumpUnreadMenuItem_Click(object sender, EventArgs e)
5426 int bgnIdx = ListTab.TabPages.IndexOf(_curTab);
5428 if (ImageSelector.Enabled)
5431 TabModel foundTab = null;
5434 DetailsListView lst = null;
5437 for (int i = bgnIdx; i < ListTab.TabPages.Count; i++)
5439 var tabPage = this.ListTab.TabPages[i];
5440 var tab = this._statuses.Tabs[tabPage.Text];
5441 var unreadIndex = tab.NextUnreadIndex;
5443 if (unreadIndex != -1)
5445 ListTab.SelectedIndex = i;
5447 foundIndex = unreadIndex;
5448 lst = (DetailsListView)tabPage.Tag;
5453 //未読みつからず&現在タブが先頭ではなかったら、先頭タブから現在タブの手前まで探索
5454 if (foundTab == null && bgnIdx > 0)
5456 for (int i = 0; i < bgnIdx; i++)
5458 var tabPage = this.ListTab.TabPages[i];
5459 var tab = this._statuses.Tabs[tabPage.Text];
5460 var unreadIndex = tab.NextUnreadIndex;
5462 if (unreadIndex != -1)
5464 ListTab.SelectedIndex = i;
5466 foundIndex = unreadIndex;
5467 lst = (DetailsListView)tabPage.Tag;
5473 if (foundTab == null)
5475 //全部調べたが未読見つからず→先頭タブの最新発言へ
5476 ListTab.SelectedIndex = 0;
5477 var tabPage = this.ListTab.TabPages[0];
5478 var tab = this._statuses.Tabs[tabPage.Text];
5480 if (tab.AllCount == 0)
5483 if (_statuses.SortOrder == SortOrder.Ascending)
5484 foundIndex = tab.AllCount - 1;
5488 lst = (DetailsListView)tabPage.Tag;
5491 SelectListItem(lst, foundIndex);
5493 if (_statuses.SortMode == ComparerMode.Id)
5495 if (_statuses.SortOrder == SortOrder.Ascending && lst.Items[foundIndex].Position.Y > lst.ClientSize.Height - _iconSz - 10 ||
5496 _statuses.SortOrder == SortOrder.Descending && lst.Items[foundIndex].Position.Y < _iconSz + 10)
5502 lst.EnsureVisible(foundIndex);
5507 lst.EnsureVisible(foundIndex);
5513 private async void StatusOpenMenuItem_Click(object sender, EventArgs e)
5515 if (_curList.SelectedIndices.Count > 0 && _statuses.Tabs[_curTab.Text].TabType != MyCommon.TabUsageType.DirectMessage)
5517 var post = _statuses.Tabs[_curTab.Text][_curList.SelectedIndices[0]];
5518 await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(post));
5522 private async void VerUpMenuItem_Click(object sender, EventArgs e)
5523 => await this.CheckNewVersion(false);
5525 private void RunTweenUp()
5527 var pinfo = new ProcessStartInfo
5529 UseShellExecute = true,
5530 WorkingDirectory = MyCommon.settingPath,
5531 FileName = Path.Combine(MyCommon.settingPath, "TweenUp3.exe"),
5532 Arguments = "\"" + Application.StartupPath + "\"",
5537 Process.Start(pinfo);
5541 MessageBox.Show("Failed to execute TweenUp3.exe.");
5545 public class VersionInfo
5547 public Version Version { get; set; }
5548 public Uri DownloadUri { get; set; }
5549 public string ReleaseNote { get; set; }
5553 /// OpenTween の最新バージョンの情報を取得します
5555 public async Task<VersionInfo> GetVersionInfoAsync()
5557 var versionInfoUrl = new Uri(ApplicationSettings.VersionInfoUrl + "?" +
5558 DateTimeUtc.Now.ToString("yyMMddHHmmss") + Environment.TickCount);
5560 var responseText = await Networking.Http.GetStringAsync(versionInfoUrl)
5561 .ConfigureAwait(false);
5563 // 改行2つで前後パートを分割(前半がバージョン番号など、後半が詳細テキスト)
5564 var msgPart = responseText.Split(new[] { "\n\n", "\r\n\r\n" }, 2, StringSplitOptions.None);
5566 var msgHeader = msgPart[0].Split(new[] { "\n", "\r\n" }, StringSplitOptions.None);
5567 var msgBody = msgPart.Length == 2 ? msgPart[1] : "";
5569 msgBody = Regex.Replace(msgBody, "(?<!\r)\n", "\r\n"); // LF -> CRLF
5571 return new VersionInfo
5573 Version = Version.Parse(msgHeader[0]),
5574 DownloadUri = new Uri(msgHeader[1]),
5575 ReleaseNote = msgBody,
5579 private async Task CheckNewVersion(bool startup = false)
5581 if (ApplicationSettings.VersionInfoUrl == null)
5582 return; // 更新チェック無効化
5586 var versionInfo = await this.GetVersionInfoAsync();
5588 if (versionInfo.Version <= Version.Parse(MyCommon.FileVersion))
5593 var msgtext = string.Format(Properties.Resources.CheckNewVersionText7,
5594 MyCommon.GetReadableVersion(), MyCommon.GetReadableVersion(versionInfo.Version));
5595 msgtext = MyCommon.ReplaceAppName(msgtext);
5597 MessageBox.Show(msgtext,
5598 MyCommon.ReplaceAppName(Properties.Resources.CheckNewVersionText2),
5599 MessageBoxButtons.OK, MessageBoxIcon.Information);
5604 if (startup && versionInfo.Version <= SettingManager.Common.SkipUpdateVersion)
5607 using (var dialog = new UpdateDialog())
5609 dialog.SummaryText = string.Format(Properties.Resources.CheckNewVersionText3,
5610 MyCommon.GetReadableVersion(versionInfo.Version));
5611 dialog.DetailsText = versionInfo.ReleaseNote;
5613 if (dialog.ShowDialog(this) == DialogResult.Yes)
5615 await this.OpenUriInBrowserAsync(versionInfo.DownloadUri.OriginalString);
5617 else if (dialog.SkipButtonPressed)
5619 SettingManager.Common.SkipUpdateVersion = versionInfo.Version;
5620 this.ModifySettingCommon = true;
5626 this.StatusLabel.Text = Properties.Resources.CheckNewVersionText9;
5629 MessageBox.Show(Properties.Resources.CheckNewVersionText10,
5630 MyCommon.ReplaceAppName(Properties.Resources.CheckNewVersionText2),
5631 MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2);
5636 private async Task Colorize()
5639 await this.DispSelectedPost();
5640 //件数関連の場合、タイトル即時書き換え
5641 if (SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.None &&
5642 SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.Post &&
5643 SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.Ver &&
5644 SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.OwnStatus)
5646 SetMainWindowTitle();
5648 if (!StatusLabelUrl.Text.StartsWith("http", StringComparison.OrdinalIgnoreCase))
5649 SetStatusLabelUrl();
5650 foreach (TabPage tb in ListTab.TabPages)
5652 if (_statuses.Tabs[tb.Text].UnreadCount == 0)
5654 if (SettingManager.Common.TabIconDisp)
5656 if (tb.ImageIndex == 0) tb.ImageIndex = -1;
5660 if (!SettingManager.Common.TabIconDisp) ListTab.Refresh();
5663 public string createDetailHtml(string orgdata)
5664 => detailHtmlFormatHeader + orgdata + detailHtmlFormatFooter;
5666 private Task DispSelectedPost()
5667 => this.DispSelectedPost(false);
5669 private PostClass displayPost = new PostClass();
5672 /// サムネイル表示に使用する CancellationToken の生成元
5674 private CancellationTokenSource thumbnailTokenSource = null;
5676 private async Task DispSelectedPost(bool forceupdate)
5678 if (_curList.SelectedIndices.Count == 0 || _curPost == null)
5681 var oldDisplayPost = this.displayPost;
5682 this.displayPost = this._curPost;
5684 if (!forceupdate && this._curPost.Equals(oldDisplayPost))
5687 var loadTasks = new List<Task>
5689 this.tweetDetailsView.ShowPostDetails(this._curPost),
5692 this.SplitContainer3.Panel2Collapsed = true;
5694 if (SettingManager.Common.PreviewEnable)
5696 var oldTokenSource = Interlocked.Exchange(ref this.thumbnailTokenSource, new CancellationTokenSource());
5697 oldTokenSource?.Cancel();
5699 var token = this.thumbnailTokenSource.Token;
5700 loadTasks.Add(this.tweetThumbnail1.ShowThumbnailAsync(_curPost, token));
5705 await Task.WhenAll(loadTasks);
5707 catch (OperationCanceledException) { }
5710 private async void MatomeMenuItem_Click(object sender, EventArgs e)
5711 => await this.OpenApplicationWebsite();
5713 private async Task OpenApplicationWebsite()
5714 => await this.OpenUriInBrowserAsync(ApplicationSettings.WebsiteUrl);
5716 private async void ShortcutKeyListMenuItem_Click(object sender, EventArgs e)
5717 => await this.OpenUriInBrowserAsync(ApplicationSettings.ShortcutKeyUrl);
5719 private async void ListTab_KeyDown(object sender, KeyEventArgs e)
5721 if (ListTab.SelectedTab != null)
5723 if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType == MyCommon.TabUsageType.PublicSearch)
5725 Control pnl = ListTab.SelectedTab.Controls["panelSearch"];
5726 if (pnl.Controls["comboSearch"].Focused ||
5727 pnl.Controls["comboLang"].Focused ||
5728 pnl.Controls["buttonSearch"].Focused) return;
5731 if (e.Control || e.Shift || e.Alt)
5732 this._anchorFlag = false;
5734 if (CommonKeyDown(e.KeyData, FocusedControl.ListTab, out var asyncTask))
5737 e.SuppressKeyPress = true;
5740 if (asyncTask != null)
5745 private ShortcutCommand[] shortcutCommands = Array.Empty<ShortcutCommand>();
5747 private void InitializeShortcuts()
5749 this.shortcutCommands = new[]
5751 // リストのカーソル移動関係(上下キー、PageUp/Downに該当)
5752 ShortcutCommand.Create(Keys.J, Keys.Control | Keys.J, Keys.Shift | Keys.J, Keys.Control | Keys.Shift | Keys.J)
5753 .FocusedOn(FocusedControl.ListTab)
5754 .Do(() => SendKeys.Send("{DOWN}")),
5756 ShortcutCommand.Create(Keys.K, Keys.Control | Keys.K, Keys.Shift | Keys.K, Keys.Control | Keys.Shift | Keys.K)
5757 .FocusedOn(FocusedControl.ListTab)
5758 .Do(() => SendKeys.Send("{UP}")),
5760 ShortcutCommand.Create(Keys.F, Keys.Shift | Keys.F)
5761 .FocusedOn(FocusedControl.ListTab)
5762 .Do(() => SendKeys.Send("{PGDN}")),
5764 ShortcutCommand.Create(Keys.B, Keys.Shift | Keys.B)
5765 .FocusedOn(FocusedControl.ListTab)
5766 .Do(() => SendKeys.Send("{PGUP}")),
5768 ShortcutCommand.Create(Keys.F1)
5769 .Do(() => this.OpenApplicationWebsite()),
5771 ShortcutCommand.Create(Keys.F3)
5772 .Do(() => this.MenuItemSearchNext_Click(null, null)),
5774 ShortcutCommand.Create(Keys.F5)
5775 .Do(() => this.DoRefresh()),
5777 ShortcutCommand.Create(Keys.F6)
5778 .Do(() => this.RefreshTabAsync<MentionsTabModel>()),
5780 ShortcutCommand.Create(Keys.F7)
5781 .Do(() => this.RefreshTabAsync<DirectMessagesTabModel>()),
5783 ShortcutCommand.Create(Keys.Space, Keys.ProcessKey)
5784 .NotFocusedOn(FocusedControl.StatusText)
5785 .Do(() => { this._anchorFlag = false; this.JumpUnreadMenuItem_Click(null, null); }),
5787 ShortcutCommand.Create(Keys.G)
5788 .NotFocusedOn(FocusedControl.StatusText)
5789 .Do(() => { this._anchorFlag = false; this.ShowRelatedStatusesMenuItem_Click(null, null); }),
5791 ShortcutCommand.Create(Keys.Right, Keys.N)
5792 .FocusedOn(FocusedControl.ListTab)
5793 .Do(() => this.GoRelPost(forward: true)),
5795 ShortcutCommand.Create(Keys.Left, Keys.P)
5796 .FocusedOn(FocusedControl.ListTab)
5797 .Do(() => this.GoRelPost(forward: false)),
5799 ShortcutCommand.Create(Keys.OemPeriod)
5800 .FocusedOn(FocusedControl.ListTab)
5801 .Do(() => this.GoAnchor()),
5803 ShortcutCommand.Create(Keys.I)
5804 .FocusedOn(FocusedControl.ListTab)
5805 .OnlyWhen(() => this.StatusText.Enabled)
5806 .Do(() => this.StatusText.Focus()),
5808 ShortcutCommand.Create(Keys.Enter)
5809 .FocusedOn(FocusedControl.ListTab)
5810 .Do(() => this.MakeReplyOrDirectStatus()),
5812 ShortcutCommand.Create(Keys.R)
5813 .FocusedOn(FocusedControl.ListTab)
5814 .Do(() => this.DoRefresh()),
5816 ShortcutCommand.Create(Keys.L)
5817 .FocusedOn(FocusedControl.ListTab)
5818 .Do(() => { this._anchorFlag = false; this.GoPost(forward: true); }),
5820 ShortcutCommand.Create(Keys.H)
5821 .FocusedOn(FocusedControl.ListTab)
5822 .Do(() => { this._anchorFlag = false; this.GoPost(forward: false); }),
5824 ShortcutCommand.Create(Keys.Z, Keys.Oemcomma)
5825 .FocusedOn(FocusedControl.ListTab)
5826 .Do(() => { this._anchorFlag = false; this.MoveTop(); }),
5828 ShortcutCommand.Create(Keys.S)
5829 .FocusedOn(FocusedControl.ListTab)
5830 .Do(() => { this._anchorFlag = false; this.GoNextTab(forward: true); }),
5832 ShortcutCommand.Create(Keys.A)
5833 .FocusedOn(FocusedControl.ListTab)
5834 .Do(() => { this._anchorFlag = false; this.GoNextTab(forward: false); }),
5836 // ] in_reply_to参照元へ戻る
5837 ShortcutCommand.Create(Keys.Oem4)
5838 .FocusedOn(FocusedControl.ListTab)
5839 .Do(() => { this._anchorFlag = false; return this.GoInReplyToPostTree(); }),
5841 // [ in_reply_toへジャンプ
5842 ShortcutCommand.Create(Keys.Oem6)
5843 .FocusedOn(FocusedControl.ListTab)
5844 .Do(() => { this._anchorFlag = false; this.GoBackInReplyToPostTree(); }),
5846 ShortcutCommand.Create(Keys.Escape)
5847 .FocusedOn(FocusedControl.ListTab)
5849 this._anchorFlag = false;
5850 if (ListTab.SelectedTab != null)
5852 var tabtype = _statuses.Tabs[ListTab.SelectedTab.Text].TabType;
5853 if (tabtype == MyCommon.TabUsageType.Related || tabtype == MyCommon.TabUsageType.UserTimeline || tabtype == MyCommon.TabUsageType.PublicSearch || tabtype == MyCommon.TabUsageType.SearchResults)
5855 var relTp = ListTab.SelectedTab;
5856 RemoveSpecifiedTab(relTp.Text, false);
5862 // 上下キー, PageUp/Downキー, Home/Endキー は既定の動作を残しつつアンカー初期化
5863 ShortcutCommand.Create(Keys.Up, Keys.Down, Keys.PageUp, Keys.PageDown, Keys.Home, Keys.End)
5864 .FocusedOn(FocusedControl.ListTab)
5865 .Do(() => this._anchorFlag = false, preventDefault: false),
5867 // PreviewKeyDownEventArgs.IsInputKey を true にしてスクロールを発生させる
5868 ShortcutCommand.Create(Keys.Up, Keys.Down)
5869 .FocusedOn(FocusedControl.PostBrowser)
5872 ShortcutCommand.Create(Keys.Control | Keys.R)
5873 .Do(() => this.MakeReplyOrDirectStatus(isAuto: false, isReply: true)),
5875 ShortcutCommand.Create(Keys.Control | Keys.D)
5876 .Do(() => this.doStatusDelete()),
5878 ShortcutCommand.Create(Keys.Control | Keys.M)
5879 .Do(() => this.MakeReplyOrDirectStatus(isAuto: false, isReply: false)),
5881 ShortcutCommand.Create(Keys.Control | Keys.S)
5882 .Do(() => this.FavoriteChange(FavAdd: true)),
5884 ShortcutCommand.Create(Keys.Control | Keys.I)
5885 .Do(() => this.doRepliedStatusOpen()),
5887 ShortcutCommand.Create(Keys.Control | Keys.Q)
5888 .Do(() => this.doQuoteOfficial()),
5890 ShortcutCommand.Create(Keys.Control | Keys.B)
5891 .Do(() => this.ReadedStripMenuItem_Click(null, null)),
5893 ShortcutCommand.Create(Keys.Control | Keys.T)
5894 .Do(() => this.HashManageMenuItem_Click(null, null)),
5896 ShortcutCommand.Create(Keys.Control | Keys.L)
5897 .Do(() => this.UrlConvertAutoToolStripMenuItem_Click(null, null)),
5899 ShortcutCommand.Create(Keys.Control | Keys.Y)
5900 .NotFocusedOn(FocusedControl.PostBrowser)
5901 .Do(() => this.MultiLineMenuItem_Click(null, null)),
5903 ShortcutCommand.Create(Keys.Control | Keys.F)
5904 .Do(() => this.MenuItemSubSearch_Click(null, null)),
5906 ShortcutCommand.Create(Keys.Control | Keys.U)
5907 .Do(() => this.ShowUserTimeline()),
5909 ShortcutCommand.Create(Keys.Control | Keys.H)
5910 .Do(() => this.MoveToHomeToolStripMenuItem_Click(null, null)),
5912 ShortcutCommand.Create(Keys.Control | Keys.G)
5913 .Do(() => this.MoveToFavToolStripMenuItem_Click(null, null)),
5915 ShortcutCommand.Create(Keys.Control | Keys.O)
5916 .Do(() => this.StatusOpenMenuItem_Click(null, null)),
5918 ShortcutCommand.Create(Keys.Control | Keys.E)
5919 .Do(() => this.OpenURLMenuItem_Click(null, null)),
5921 ShortcutCommand.Create(Keys.Control | Keys.Home, Keys.Control | Keys.End)
5922 .FocusedOn(FocusedControl.ListTab)
5923 .Do(() => this._colorize = true, preventDefault: false),
5925 ShortcutCommand.Create(Keys.Control | Keys.N)
5926 .FocusedOn(FocusedControl.ListTab)
5927 .Do(() => this.GoNextTab(forward: true)),
5929 ShortcutCommand.Create(Keys.Control | Keys.P)
5930 .FocusedOn(FocusedControl.ListTab)
5931 .Do(() => this.GoNextTab(forward: false)),
5933 ShortcutCommand.Create(Keys.Control | Keys.C, Keys.Control | Keys.Insert)
5934 .FocusedOn(FocusedControl.ListTab)
5935 .Do(() => this.CopyStot()),
5937 // タブダイレクト選択(Ctrl+1~8,Ctrl+9)
5938 ShortcutCommand.Create(Keys.Control | Keys.D1)
5939 .FocusedOn(FocusedControl.ListTab)
5940 .OnlyWhen(() => this.ListTab.TabPages.Count >= 1)
5941 .Do(() => this.ListTab.SelectedIndex = 0),
5943 ShortcutCommand.Create(Keys.Control | Keys.D2)
5944 .FocusedOn(FocusedControl.ListTab)
5945 .OnlyWhen(() => this.ListTab.TabPages.Count >= 2)
5946 .Do(() => this.ListTab.SelectedIndex = 1),
5948 ShortcutCommand.Create(Keys.Control | Keys.D3)
5949 .FocusedOn(FocusedControl.ListTab)
5950 .OnlyWhen(() => this.ListTab.TabPages.Count >= 3)
5951 .Do(() => this.ListTab.SelectedIndex = 2),
5953 ShortcutCommand.Create(Keys.Control | Keys.D4)
5954 .FocusedOn(FocusedControl.ListTab)
5955 .OnlyWhen(() => this.ListTab.TabPages.Count >= 4)
5956 .Do(() => this.ListTab.SelectedIndex = 3),
5958 ShortcutCommand.Create(Keys.Control | Keys.D5)
5959 .FocusedOn(FocusedControl.ListTab)
5960 .OnlyWhen(() => this.ListTab.TabPages.Count >= 5)
5961 .Do(() => this.ListTab.SelectedIndex = 4),
5963 ShortcutCommand.Create(Keys.Control | Keys.D6)
5964 .FocusedOn(FocusedControl.ListTab)
5965 .OnlyWhen(() => this.ListTab.TabPages.Count >= 6)
5966 .Do(() => this.ListTab.SelectedIndex = 5),
5968 ShortcutCommand.Create(Keys.Control | Keys.D7)
5969 .FocusedOn(FocusedControl.ListTab)
5970 .OnlyWhen(() => this.ListTab.TabPages.Count >= 7)
5971 .Do(() => this.ListTab.SelectedIndex = 6),
5973 ShortcutCommand.Create(Keys.Control | Keys.D8)
5974 .FocusedOn(FocusedControl.ListTab)
5975 .OnlyWhen(() => this.ListTab.TabPages.Count >= 8)
5976 .Do(() => this.ListTab.SelectedIndex = 7),
5978 ShortcutCommand.Create(Keys.Control | Keys.D9)
5979 .FocusedOn(FocusedControl.ListTab)
5980 .Do(() => this.ListTab.SelectedIndex = this.ListTab.TabPages.Count - 1),
5982 ShortcutCommand.Create(Keys.Control | Keys.A)
5983 .FocusedOn(FocusedControl.StatusText)
5984 .Do(() => this.StatusText.SelectAll()),
5986 ShortcutCommand.Create(Keys.Control | Keys.V)
5987 .FocusedOn(FocusedControl.StatusText)
5988 .Do(() => this.ProcClipboardFromStatusTextWhenCtrlPlusV()),
5990 ShortcutCommand.Create(Keys.Control | Keys.Up)
5991 .FocusedOn(FocusedControl.StatusText)
5992 .Do(() => this.StatusTextHistoryBack()),
5994 ShortcutCommand.Create(Keys.Control | Keys.Down)
5995 .FocusedOn(FocusedControl.StatusText)
5996 .Do(() => this.StatusTextHistoryForward()),
5998 ShortcutCommand.Create(Keys.Control | Keys.PageUp, Keys.Control | Keys.P)
5999 .FocusedOn(FocusedControl.StatusText)
6001 if (ListTab.SelectedIndex == 0)
6003 ListTab.SelectedIndex = ListTab.TabCount - 1;
6007 ListTab.SelectedIndex -= 1;
6012 ShortcutCommand.Create(Keys.Control | Keys.PageDown, Keys.Control | Keys.N)
6013 .FocusedOn(FocusedControl.StatusText)
6015 if (ListTab.SelectedIndex == ListTab.TabCount - 1)
6017 ListTab.SelectedIndex = 0;
6021 ListTab.SelectedIndex += 1;
6026 ShortcutCommand.Create(Keys.Control | Keys.Y)
6027 .FocusedOn(FocusedControl.PostBrowser)
6029 var multiline = !SettingManager.Local.StatusMultiline;
6030 SettingManager.Local.StatusMultiline = multiline;
6031 MultiLineMenuItem.Checked = multiline;
6032 MultiLineMenuItem_Click(this.MultiLineMenuItem, EventArgs.Empty);
6035 ShortcutCommand.Create(Keys.Shift | Keys.F3)
6036 .Do(() => this.MenuItemSearchPrev_Click(null, null)),
6038 ShortcutCommand.Create(Keys.Shift | Keys.F5)
6039 .Do(() => this.DoRefreshMore()),
6041 ShortcutCommand.Create(Keys.Shift | Keys.F6)
6042 .Do(() => this.RefreshTabAsync<MentionsTabModel>(backward: true)),
6044 ShortcutCommand.Create(Keys.Shift | Keys.F7)
6045 .Do(() => this.RefreshTabAsync<DirectMessagesTabModel>(backward: true)),
6047 ShortcutCommand.Create(Keys.Shift | Keys.R)
6048 .NotFocusedOn(FocusedControl.StatusText)
6049 .Do(() => this.DoRefreshMore()),
6051 ShortcutCommand.Create(Keys.Shift | Keys.H)
6052 .FocusedOn(FocusedControl.ListTab)
6053 .Do(() => this.GoTopEnd(GoTop: true)),
6055 ShortcutCommand.Create(Keys.Shift | Keys.L)
6056 .FocusedOn(FocusedControl.ListTab)
6057 .Do(() => this.GoTopEnd(GoTop: false)),
6059 ShortcutCommand.Create(Keys.Shift | Keys.M)
6060 .FocusedOn(FocusedControl.ListTab)
6061 .Do(() => this.GoMiddle()),
6063 ShortcutCommand.Create(Keys.Shift | Keys.G)
6064 .FocusedOn(FocusedControl.ListTab)
6065 .Do(() => this.GoLast()),
6067 ShortcutCommand.Create(Keys.Shift | Keys.Z)
6068 .FocusedOn(FocusedControl.ListTab)
6069 .Do(() => this.MoveMiddle()),
6071 ShortcutCommand.Create(Keys.Shift | Keys.Oem4)
6072 .FocusedOn(FocusedControl.ListTab)
6073 .Do(() => this.GoBackInReplyToPostTree(parallel: true, isForward: false)),
6075 ShortcutCommand.Create(Keys.Shift | Keys.Oem6)
6076 .FocusedOn(FocusedControl.ListTab)
6077 .Do(() => this.GoBackInReplyToPostTree(parallel: true, isForward: true)),
6079 // お気に入り前後ジャンプ(SHIFT+N←/P→)
6080 ShortcutCommand.Create(Keys.Shift | Keys.Right, Keys.Shift | Keys.N)
6081 .FocusedOn(FocusedControl.ListTab)
6082 .Do(() => this.GoFav(forward: true)),
6084 // お気に入り前後ジャンプ(SHIFT+N←/P→)
6085 ShortcutCommand.Create(Keys.Shift | Keys.Left, Keys.Shift | Keys.P)
6086 .FocusedOn(FocusedControl.ListTab)
6087 .Do(() => this.GoFav(forward: false)),
6089 ShortcutCommand.Create(Keys.Shift | Keys.Space)
6090 .FocusedOn(FocusedControl.ListTab)
6091 .Do(() => this.GoBackSelectPostChain()),
6093 ShortcutCommand.Create(Keys.Alt | Keys.R)
6094 .Do(() => this.doReTweetOfficial(isConfirm: true)),
6096 ShortcutCommand.Create(Keys.Alt | Keys.P)
6097 .OnlyWhen(() => this._curPost != null)
6098 .Do(() => this.doShowUserStatus(_curPost.ScreenName, ShowInputDialog: false)),
6100 ShortcutCommand.Create(Keys.Alt | Keys.Up)
6101 .Do(() => this.tweetDetailsView.ScrollDownPostBrowser(forward: false)),
6103 ShortcutCommand.Create(Keys.Alt | Keys.Down)
6104 .Do(() => this.tweetDetailsView.ScrollDownPostBrowser(forward: true)),
6106 ShortcutCommand.Create(Keys.Alt | Keys.PageUp)
6107 .Do(() => this.tweetDetailsView.PageDownPostBrowser(forward: false)),
6109 ShortcutCommand.Create(Keys.Alt | Keys.PageDown)
6110 .Do(() => this.tweetDetailsView.PageDownPostBrowser(forward: true)),
6112 // 別タブの同じ書き込みへ(ALT+←/→)
6113 ShortcutCommand.Create(Keys.Alt | Keys.Right)
6114 .FocusedOn(FocusedControl.ListTab)
6115 .Do(() => this.GoSamePostToAnotherTab(left: false)),
6117 ShortcutCommand.Create(Keys.Alt | Keys.Left)
6118 .FocusedOn(FocusedControl.ListTab)
6119 .Do(() => this.GoSamePostToAnotherTab(left: true)),
6121 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.R)
6122 .Do(() => this.MakeReplyOrDirectStatus(isAuto: false, isReply: true, isAll: true)),
6124 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.C, Keys.Control | Keys.Shift | Keys.Insert)
6125 .Do(() => this.CopyIdUri()),
6127 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.F)
6128 .OnlyWhen(() => this.ListTab.SelectedTab != null &&
6129 this._statuses.Tabs[this.ListTab.SelectedTab.Text].TabType == MyCommon.TabUsageType.PublicSearch)
6130 .Do(() => this.ListTab.SelectedTab.Controls["panelSearch"].Controls["comboSearch"].Focus()),
6132 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.S)
6133 .Do(() => this.FavoriteChange(FavAdd: false)),
6135 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.B)
6136 .Do(() => this.UnreadStripMenuItem_Click(null, null)),
6138 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.T)
6139 .Do(() => this.HashToggleMenuItem_Click(null, null)),
6141 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.P)
6142 .Do(() => this.ImageSelectMenuItem_Click(null, null)),
6144 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.H)
6145 .Do(() => this.doMoveToRTHome()),
6147 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.Up)
6148 .FocusedOn(FocusedControl.StatusText)
6150 if (_curList != null && _curList.VirtualListSize != 0 &&
6151 _curList.SelectedIndices.Count > 0 && _curList.SelectedIndices[0] > 0)
6153 var idx = _curList.SelectedIndices[0] - 1;
6154 SelectListItem(_curList, idx);
6155 _curList.EnsureVisible(idx);
6159 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.Down)
6160 .FocusedOn(FocusedControl.StatusText)
6162 if (_curList != null && _curList.VirtualListSize != 0 && _curList.SelectedIndices.Count > 0
6163 && _curList.SelectedIndices[0] < _curList.VirtualListSize - 1)
6165 var idx = _curList.SelectedIndices[0] + 1;
6166 SelectListItem(_curList, idx);
6167 _curList.EnsureVisible(idx);
6171 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.Space)
6172 .FocusedOn(FocusedControl.StatusText)
6174 if (StatusText.SelectionStart > 0)
6176 int endidx = StatusText.SelectionStart - 1;
6177 string startstr = "";
6178 for (int i = StatusText.SelectionStart - 1; i >= 0; i--)
6180 char c = StatusText.Text[i];
6181 if (Char.IsLetterOrDigit(c) || c == '_')
6187 startstr = StatusText.Text.Substring(i + 1, endidx - i);
6188 int cnt = AtIdSupl.ItemCount;
6189 ShowSuplDialog(StatusText, AtIdSupl, startstr.Length + 1, startstr);
6190 if (AtIdSupl.ItemCount != cnt) ModifySettingAtId = true;
6194 startstr = StatusText.Text.Substring(i + 1, endidx - i);
6195 ShowSuplDialog(StatusText, HashSupl, startstr.Length + 1, startstr);
6205 // ソートダイレクト選択(Ctrl+Shift+1~8,Ctrl+Shift+9)
6206 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D1)
6207 .FocusedOn(FocusedControl.ListTab)
6208 .Do(() => this.SetSortColumnByDisplayIndex(0)),
6210 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D2)
6211 .FocusedOn(FocusedControl.ListTab)
6212 .Do(() => this.SetSortColumnByDisplayIndex(1)),
6214 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D3)
6215 .FocusedOn(FocusedControl.ListTab)
6216 .Do(() => this.SetSortColumnByDisplayIndex(2)),
6218 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D4)
6219 .FocusedOn(FocusedControl.ListTab)
6220 .Do(() => this.SetSortColumnByDisplayIndex(3)),
6222 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D5)
6223 .FocusedOn(FocusedControl.ListTab)
6224 .Do(() => this.SetSortColumnByDisplayIndex(4)),
6226 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D6)
6227 .FocusedOn(FocusedControl.ListTab)
6228 .Do(() => this.SetSortColumnByDisplayIndex(5)),
6230 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D7)
6231 .FocusedOn(FocusedControl.ListTab)
6232 .Do(() => this.SetSortColumnByDisplayIndex(6)),
6234 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D8)
6235 .FocusedOn(FocusedControl.ListTab)
6236 .Do(() => this.SetSortColumnByDisplayIndex(7)),
6238 ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.D9)
6239 .FocusedOn(FocusedControl.ListTab)
6240 .Do(() => this.SetSortLastColumn()),
6242 ShortcutCommand.Create(Keys.Control | Keys.Alt | Keys.S)
6243 .FocusedOn(FocusedControl.ListTab)
6244 .Do(() => this.FavoritesRetweetOfficial()),
6246 ShortcutCommand.Create(Keys.Control | Keys.Alt | Keys.R)
6247 .FocusedOn(FocusedControl.ListTab)
6248 .Do(() => this.FavoritesRetweetUnofficial()),
6250 ShortcutCommand.Create(Keys.Control | Keys.Alt | Keys.H)
6251 .FocusedOn(FocusedControl.ListTab)
6252 .Do(() => this.OpenUserAppointUrl()),
6254 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.R)
6255 .FocusedOn(FocusedControl.PostBrowser)
6256 .Do(() => this.doReTweetUnofficial()),
6258 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.T)
6259 .OnlyWhen(() => this.ExistCurrentPost)
6260 .Do(() => this.tweetDetailsView.DoTranslation()),
6262 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.R)
6263 .Do(() => this.doReTweetUnofficial()),
6265 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.C, Keys.Alt | Keys.Shift | Keys.Insert)
6266 .Do(() => this.CopyUserId()),
6268 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.Up)
6269 .Do(() => this.tweetThumbnail1.ScrollUp()),
6271 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.Down)
6272 .Do(() => this.tweetThumbnail1.ScrollDown()),
6274 ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.Enter)
6275 .FocusedOn(FocusedControl.ListTab)
6276 .OnlyWhen(() => !this.SplitContainer3.Panel2Collapsed)
6277 .Do(() => this.OpenThumbnailPicture(this.tweetThumbnail1.Thumbnail)),
6281 internal bool CommonKeyDown(Keys keyData, FocusedControl focusedOn, out Task asyncTask)
6283 // Task を返す非同期処理があれば asyncTask に代入する
6286 // ShortcutCommand に対応しているコマンドはここで処理される
6287 foreach (var command in this.shortcutCommands)
6289 if (command.IsMatch(keyData, focusedOn))
6291 asyncTask = command.RunCommand();
6292 return command.PreventDefault;
6299 private void GoNextTab(bool forward)
6301 int idx = ListTab.SelectedIndex;
6305 if (idx > ListTab.TabPages.Count - 1) idx = 0;
6310 if (idx < 0) idx = ListTab.TabPages.Count - 1;
6312 ListTab.SelectedIndex = idx;
6315 private void CopyStot()
6318 StringBuilder sb = new StringBuilder();
6319 bool IsProtected = false;
6321 if (this._curTab != null && this._statuses.GetTabByName(this._curTab.Text) != null) isDm = this._statuses.GetTabByName(this._curTab.Text).TabType == MyCommon.TabUsageType.DirectMessage;
6322 foreach (int idx in _curList.SelectedIndices)
6324 PostClass post = _statuses.Tabs[_curTab.Text][idx];
6325 if (post.IsDeleted) continue;
6328 if (post.RetweetedId != null)
6329 sb.AppendFormat("{0}:{1} [https://twitter.com/{0}/status/{2}]{3}", post.ScreenName, post.TextSingleLine, post.RetweetedId, Environment.NewLine);
6331 sb.AppendFormat("{0}:{1} [https://twitter.com/{0}/status/{2}]{3}", post.ScreenName, post.TextSingleLine, post.StatusId, Environment.NewLine);
6335 sb.AppendFormat("{0}:{1} [{2}]{3}", post.ScreenName, post.TextSingleLine, post.StatusId, Environment.NewLine);
6340 MessageBox.Show(Properties.Resources.CopyStotText1);
6344 clstr = sb.ToString();
6347 Clipboard.SetDataObject(clstr, false, 5, 100);
6349 catch (Exception ex)
6351 MessageBox.Show(ex.Message);
6356 private void CopyIdUri()
6358 if (this._curTab == null)
6361 var tab = this._statuses.GetTabByName(this._curTab.Text);
6362 if (tab == null || tab is DirectMessagesTabModel)
6365 var copyUrls = new List<string>();
6366 foreach (int idx in _curList.SelectedIndices)
6368 var post = tab[idx];
6369 copyUrls.Add(MyCommon.GetStatusUrl(post));
6372 if (copyUrls.Count == 0)
6377 Clipboard.SetDataObject(string.Join(Environment.NewLine, copyUrls), false, 5, 100);
6379 catch (ExternalException ex)
6381 MessageBox.Show(ex.Message);
6385 private void GoFav(bool forward)
6387 if (_curList.VirtualListSize == 0) return;
6394 if (_curList.SelectedIndices.Count == 0)
6400 fIdx = _curList.SelectedIndices[0] + 1;
6401 if (fIdx > _curList.VirtualListSize - 1) return;
6403 toIdx = _curList.VirtualListSize;
6408 if (_curList.SelectedIndices.Count == 0)
6410 fIdx = _curList.VirtualListSize - 1;
6414 fIdx = _curList.SelectedIndices[0] - 1;
6415 if (fIdx < 0) return;
6421 for (int idx = fIdx; idx != toIdx; idx += stp)
6423 if (_statuses.Tabs[_curTab.Text][idx].IsFav)
6425 SelectListItem(_curList, idx);
6426 _curList.EnsureVisible(idx);
6432 private void GoSamePostToAnotherTab(bool left)
6434 if (this._curList.SelectedIndices.Count == 0)
6437 var tab = this._statuses.Tabs[this._curTab.Text];
6439 // Directタブは対象外(見つかるはずがない)
6440 if (tab.TabType == MyCommon.TabUsageType.DirectMessage)
6443 var selectedIndex = this._curList.SelectedIndices[0];
6444 var selectedStatusId = tab.GetStatusIdAt(selectedIndex);
6446 int fIdx, toIdx, stp;
6451 if (ListTab.SelectedIndex == 0)
6457 fIdx = ListTab.SelectedIndex - 1;
6465 if (ListTab.SelectedIndex == ListTab.TabCount - 1)
6471 fIdx = ListTab.SelectedIndex + 1;
6473 toIdx = ListTab.TabCount;
6477 for (int tabidx = fIdx; tabidx != toIdx; tabidx += stp)
6479 var targetTab = this._statuses.Tabs[this.ListTab.TabPages[tabidx].Text];
6482 if (targetTab.TabType == MyCommon.TabUsageType.DirectMessage)
6485 var foundIndex = targetTab.IndexOf(selectedStatusId);
6486 if (foundIndex != -1)
6488 ListTab.SelectedIndex = tabidx;
6489 SelectListItem(_curList, foundIndex);
6490 _curList.EnsureVisible(foundIndex);
6496 private void GoPost(bool forward)
6498 if (_curList.SelectedIndices.Count == 0 || _curPost == null)
6501 var tab = this._statuses.Tabs[this._curTab.Text];
6502 var selectedIndex = this._curList.SelectedIndices[0];
6504 int fIdx, toIdx, stp;
6508 fIdx = selectedIndex + 1;
6509 if (fIdx > tab.AllCount - 1) return;
6510 toIdx = tab.AllCount;
6515 fIdx = selectedIndex - 1;
6516 if (fIdx < 0) return;
6522 if (_curPost.RetweetedId == null)
6524 name = _curPost.ScreenName;
6528 name = _curPost.RetweetedBy;
6530 for (int idx = fIdx; idx != toIdx; idx += stp)
6532 var post = tab[idx];
6533 if (post.RetweetedId == null)
6535 if (post.ScreenName == name)
6537 SelectListItem(_curList, idx);
6538 _curList.EnsureVisible(idx);
6544 if (post.RetweetedBy == name)
6546 SelectListItem(_curList, idx);
6547 _curList.EnsureVisible(idx);
6554 private void GoRelPost(bool forward)
6556 if (this._curList.SelectedIndices.Count == 0)
6559 var tab = this._statuses.Tabs[this._curTab.Text];
6560 var selectedIndex = this._curList.SelectedIndices[0];
6562 int fIdx, toIdx, stp;
6566 fIdx = selectedIndex + 1;
6567 if (fIdx > tab.AllCount - 1) return;
6568 toIdx = tab.AllCount;
6573 fIdx = selectedIndex - 1;
6574 if (fIdx < 0) return;
6581 if (_curPost == null) return;
6582 _anchorPost = _curPost;
6587 if (_anchorPost == null) return;
6590 for (int idx = fIdx; idx != toIdx; idx += stp)
6592 var post = tab[idx];
6593 if (post.ScreenName == _anchorPost.ScreenName ||
6594 post.RetweetedBy == _anchorPost.ScreenName ||
6595 post.ScreenName == _anchorPost.RetweetedBy ||
6596 (!string.IsNullOrEmpty(post.RetweetedBy) && post.RetweetedBy == _anchorPost.RetweetedBy) ||
6597 _anchorPost.ReplyToList.Any(x => x.UserId == post.UserId) ||
6598 _anchorPost.ReplyToList.Any(x => x.UserId == post.RetweetedByUserId) ||
6599 post.ReplyToList.Any(x => x.UserId == _anchorPost.UserId) ||
6600 post.ReplyToList.Any(x => x.UserId == _anchorPost.RetweetedByUserId))
6602 SelectListItem(_curList, idx);
6603 _curList.EnsureVisible(idx);
6609 private void GoAnchor()
6611 if (_anchorPost == null) return;
6612 int idx = _statuses.Tabs[_curTab.Text].IndexOf(_anchorPost.StatusId);
6613 if (idx == -1) return;
6615 SelectListItem(_curList, idx);
6616 _curList.EnsureVisible(idx);
6619 private void GoTopEnd(bool GoTop)
6621 if (_curList.VirtualListSize == 0)
6629 _item = _curList.GetItemAt(0, 25);
6637 _item = _curList.GetItemAt(0, _curList.ClientSize.Height - 1);
6639 idx = _curList.VirtualListSize - 1;
6643 SelectListItem(_curList, idx);
6646 private void GoMiddle()
6648 if (_curList.VirtualListSize == 0)
6656 _item = _curList.GetItemAt(0, 0);
6666 _item = _curList.GetItemAt(0, _curList.ClientSize.Height - 1);
6669 idx2 = _curList.VirtualListSize - 1;
6675 idx3 = (idx1 + idx2) / 2;
6677 SelectListItem(_curList, idx3);
6680 private void GoLast()
6682 if (_curList.VirtualListSize == 0) return;
6684 if (_statuses.SortOrder == SortOrder.Ascending)
6686 SelectListItem(_curList, _curList.VirtualListSize - 1);
6687 _curList.EnsureVisible(_curList.VirtualListSize - 1);
6691 SelectListItem(_curList, 0);
6692 _curList.EnsureVisible(0);
6696 private void MoveTop()
6698 if (_curList.SelectedIndices.Count == 0) return;
6699 int idx = _curList.SelectedIndices[0];
6700 if (_statuses.SortOrder == SortOrder.Ascending)
6702 _curList.EnsureVisible(_curList.VirtualListSize - 1);
6706 _curList.EnsureVisible(0);
6708 _curList.EnsureVisible(idx);
6711 private async Task GoInReplyToPostTree()
6713 if (_curPost == null) return;
6715 TabModel curTabClass = _statuses.Tabs[_curTab.Text];
6717 if (curTabClass.TabType == MyCommon.TabUsageType.PublicSearch && _curPost.InReplyToStatusId == null && _curPost.TextFromApi.Contains("@"))
6721 var post = await tw.GetStatusApi(false, _curPost.StatusId);
6723 _curPost.InReplyToStatusId = post.InReplyToStatusId;
6724 _curPost.InReplyToUser = post.InReplyToUser;
6725 _curPost.IsReply = post.IsReply;
6726 this.PurgeListViewItemCache();
6727 _curList.RedrawItems(_curItemIndex, _curItemIndex, false);
6729 catch (WebApiException ex)
6731 this.StatusLabel.Text = $"Err:{ex.Message}(GetStatus)";
6735 if (!(this.ExistCurrentPost && _curPost.InReplyToUser != null && _curPost.InReplyToStatusId != null)) return;
6737 if (replyChains == null || (replyChains.Count > 0 && replyChains.Peek().InReplyToId != _curPost.StatusId))
6739 replyChains = new Stack<ReplyChain>();
6741 replyChains.Push(new ReplyChain(_curPost.StatusId, _curPost.InReplyToStatusId.Value, _curTab));
6744 string inReplyToTabName;
6745 long inReplyToId = _curPost.InReplyToStatusId.Value;
6746 string inReplyToUser = _curPost.InReplyToUser;
6747 //Dictionary<long, PostClass> curTabPosts = curTabClass.Posts;
6749 var inReplyToPosts = from tab in _statuses.Tabs.Values
6750 orderby tab != curTabClass
6751 from post in tab.Posts.Values
6752 where post.StatusId == inReplyToId
6753 let index = tab.IndexOf(post.StatusId)
6755 select new {Tab = tab, Index = index};
6757 var inReplyPost = inReplyToPosts.FirstOrDefault();
6758 if (inReplyPost == null)
6762 await Task.Run(async () =>
6764 var post = await tw.GetStatusApi(false, _curPost.InReplyToStatusId.Value)
6765 .ConfigureAwait(false);
6768 _statuses.AddPost(post);
6769 _statuses.DistributePosts();
6772 catch (WebApiException ex)
6774 this.StatusLabel.Text = $"Err:{ex.Message}(GetStatus)";
6775 await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(inReplyToUser, inReplyToId));
6779 this.RefreshTimeline();
6781 inReplyPost = inReplyToPosts.FirstOrDefault();
6782 if (inReplyPost == null)
6784 await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(inReplyToUser, inReplyToId));
6788 inReplyToTabName = inReplyPost.Tab.TabName;
6789 inReplyToIndex = inReplyPost.Index;
6791 TabPage tabPage = this.ListTab.TabPages.Cast<TabPage>().First((tp) => { return tp.Text == inReplyToTabName; });
6792 DetailsListView listView = (DetailsListView)tabPage.Tag;
6794 if (_curTab != tabPage)
6796 this.ListTab.SelectTab(tabPage);
6799 this.SelectListItem(listView, inReplyToIndex);
6800 listView.EnsureVisible(inReplyToIndex);
6803 private void GoBackInReplyToPostTree(bool parallel = false, bool isForward = true)
6805 if (_curPost == null) return;
6807 TabModel curTabClass = _statuses.Tabs[_curTab.Text];
6808 //Dictionary<long, PostClass> curTabPosts = curTabClass.Posts;
6812 if (_curPost.InReplyToStatusId != null)
6814 var posts = from t in _statuses.Tabs
6815 from p in t.Value.Posts
6816 where p.Value.StatusId != _curPost.StatusId && p.Value.InReplyToStatusId == _curPost.InReplyToStatusId
6817 let indexOf = t.Value.IndexOf(p.Value.StatusId)
6819 orderby isForward ? indexOf : indexOf * -1
6820 orderby t.Value != curTabClass
6821 select new {Tab = t.Value, Post = p.Value, Index = indexOf};
6824 var postList = posts.ToList();
6825 for (int i = postList.Count - 1; i >= 0; i--)
6828 if (postList.FindIndex((pst) => { return pst.Post.StatusId == postList[index].Post.StatusId; }) != index)
6830 postList.RemoveAt(index);
6833 var post = postList.FirstOrDefault((pst) => { return pst.Tab == curTabClass && isForward ? pst.Index > _curItemIndex : pst.Index < _curItemIndex; });
6834 if (post == null) post = postList.FirstOrDefault((pst) => { return pst.Tab != curTabClass; });
6835 if (post == null) post = postList.First();
6836 this.ListTab.SelectTab(this.ListTab.TabPages.Cast<TabPage>().First((tp) => { return tp.Text == post.Tab.TabName; }));
6837 DetailsListView listView = (DetailsListView)this.ListTab.SelectedTab.Tag;
6838 SelectListItem(listView, post.Index);
6839 listView.EnsureVisible(post.Index);
6841 catch (InvalidOperationException)
6849 if (replyChains == null || replyChains.Count < 1)
6851 var posts = from t in _statuses.Tabs
6852 from p in t.Value.Posts
6853 where p.Value.InReplyToStatusId == _curPost.StatusId
6854 let indexOf = t.Value.IndexOf(p.Value.StatusId)
6857 orderby t.Value != curTabClass
6858 select new {Tab = t.Value, Index = indexOf};
6861 var post = posts.First();
6862 this.ListTab.SelectTab(this.ListTab.TabPages.Cast<TabPage>().First((tp) => { return tp.Text == post.Tab.TabName; }));
6863 DetailsListView listView = (DetailsListView)this.ListTab.SelectedTab.Tag;
6864 SelectListItem(listView, post.Index);
6865 listView.EnsureVisible(post.Index);
6867 catch (InvalidOperationException)
6874 ReplyChain chainHead = replyChains.Pop();
6875 if (chainHead.InReplyToId == _curPost.StatusId)
6877 int idx = _statuses.Tabs[chainHead.OriginalTab.Text].IndexOf(chainHead.OriginalId);
6886 ListTab.SelectTab(chainHead.OriginalTab);
6892 SelectListItem(_curList, idx);
6893 _curList.EnsureVisible(idx);
6899 this.GoBackInReplyToPostTree(parallel);
6905 private void GoBackSelectPostChain()
6907 if (this.selectPostChains.Count > 1)
6916 this.selectPostChains.Pop();
6917 var (tabPage, post) = this.selectPostChains.Peek();
6919 if (!this.ListTab.TabPages.Contains(tabPage)) continue; //該当タブが存在しないので無視
6923 idx = this._statuses.Tabs[tabPage.Text].IndexOf(post.StatusId);
6924 if (idx == -1) continue; //該当ポストが存在しないので無視
6929 this.selectPostChains.Pop();
6931 catch (InvalidOperationException)
6937 while (this.selectPostChains.Count > 1);
6942 //履歴が残り1つであればクリアしておく
6943 if (this.selectPostChains.Count == 1)
6944 this.selectPostChains.Clear();
6948 DetailsListView lst = (DetailsListView)tp.Tag;
6949 this.ListTab.SelectedTab = tp;
6952 SelectListItem(lst, idx);
6953 lst.EnsureVisible(idx);
6959 private void PushSelectPostChain()
6961 int count = this.selectPostChains.Count;
6964 var (tabPage, post) = this.selectPostChains.Peek();
6965 if (tabPage == this._curTab)
6967 if (post == this._curPost) return; //最新の履歴と同一
6968 if (post == null) this.selectPostChains.Pop(); //置き換えるため削除
6971 if (count >= 2500) TrimPostChain();
6972 this.selectPostChains.Push((this._curTab, this._curPost));
6975 private void TrimPostChain()
6977 if (this.selectPostChains.Count <= 2000) return;
6978 var p = new Stack<(TabPage, PostClass)>(2000);
6979 for (int i = 0; i < 2000; i++)
6981 p.Push(this.selectPostChains.Pop());
6983 this.selectPostChains.Clear();
6984 for (int i = 0; i < 2000; i++)
6986 this.selectPostChains.Push(p.Pop());
6990 private bool GoStatus(long statusId)
6992 if (statusId == 0) return false;
6993 for (int tabidx = 0; tabidx < ListTab.TabCount; tabidx++)
6995 if (_statuses.Tabs[ListTab.TabPages[tabidx].Text].TabType != MyCommon.TabUsageType.DirectMessage && _statuses.Tabs[ListTab.TabPages[tabidx].Text].Contains(statusId))
6997 int idx = _statuses.Tabs[ListTab.TabPages[tabidx].Text].IndexOf(statusId);
6998 ListTab.SelectedIndex = tabidx;
6999 SelectListItem(_curList, idx);
7000 _curList.EnsureVisible(idx);
7007 private bool GoDirectMessage(long statusId)
7009 if (statusId == 0) return false;
7010 for (int tabidx = 0; tabidx < ListTab.TabCount; tabidx++)
7012 if (_statuses.Tabs[ListTab.TabPages[tabidx].Text].TabType == MyCommon.TabUsageType.DirectMessage && _statuses.Tabs[ListTab.TabPages[tabidx].Text].Contains(statusId))
7014 int idx = _statuses.Tabs[ListTab.TabPages[tabidx].Text].IndexOf(statusId);
7015 ListTab.SelectedIndex = tabidx;
7016 SelectListItem(_curList, idx);
7017 _curList.EnsureVisible(idx);
7024 private void MyList_MouseClick(object sender, MouseEventArgs e)
7025 => this._anchorFlag = false;
7027 private void StatusText_Enter(object sender, EventArgs e)
7029 // フォーカスの戻り先を StatusText に設定
7030 this.Tag = StatusText;
7031 StatusText.BackColor = _clInputBackcolor;
7034 public Color InputBackColor
7036 get => _clInputBackcolor;
7037 set => _clInputBackcolor = value;
7040 private void StatusText_Leave(object sender, EventArgs e)
7042 // フォーカスがメニューに遷移しないならばフォーカスはタブに移ることを期待
7043 if (ListTab.SelectedTab != null && MenuStrip1.Tag == null) this.Tag = ListTab.SelectedTab.Tag;
7044 StatusText.BackColor = Color.FromKnownColor(KnownColor.Window);
7047 private async void StatusText_KeyDown(object sender, KeyEventArgs e)
7049 if (CommonKeyDown(e.KeyData, FocusedControl.StatusText, out var asyncTask))
7052 e.SuppressKeyPress = true;
7055 this.StatusText_TextChanged(null, null);
7057 if (asyncTask != null)
7061 private void SaveConfigsAll(bool ifModified)
7065 SaveConfigsCommon();
7072 if (ModifySettingCommon) SaveConfigsCommon();
7073 if (ModifySettingLocal) SaveConfigsLocal();
7074 if (ModifySettingAtId) SaveConfigsAtId();
7078 private void SaveConfigsAtId()
7080 if (_ignoreConfigSave || !SettingManager.Common.UseAtIdSupplement && AtIdSupl == null) return;
7082 ModifySettingAtId = false;
7083 SettingManager.AtIdList.AtIdList = this.AtIdSupl.GetItemList();
7084 SettingManager.SaveAtIdList();
7087 private void SaveConfigsCommon()
7089 if (_ignoreConfigSave) return;
7091 ModifySettingCommon = false;
7094 SettingManager.Common.UserName = tw.Username;
7095 SettingManager.Common.UserId = tw.UserId;
7096 SettingManager.Common.Token = tw.AccessToken;
7097 SettingManager.Common.TokenSecret = tw.AccessTokenSecret;
7098 SettingManager.Common.SortOrder = (int)_statuses.SortOrder;
7099 switch (_statuses.SortMode)
7101 case ComparerMode.Nickname: //ニックネーム
7102 SettingManager.Common.SortColumn = 1;
7104 case ComparerMode.Data: //本文
7105 SettingManager.Common.SortColumn = 2;
7107 case ComparerMode.Id: //時刻=発言Id
7108 SettingManager.Common.SortColumn = 3;
7110 case ComparerMode.Name: //名前
7111 SettingManager.Common.SortColumn = 4;
7113 case ComparerMode.Source: //Source
7114 SettingManager.Common.SortColumn = 7;
7118 SettingManager.Common.HashTags = HashMgr.HashHistories;
7119 if (HashMgr.IsPermanent)
7121 SettingManager.Common.HashSelected = HashMgr.UseHash;
7125 SettingManager.Common.HashSelected = "";
7127 SettingManager.Common.HashIsHead = HashMgr.IsHead;
7128 SettingManager.Common.HashIsPermanent = HashMgr.IsPermanent;
7129 SettingManager.Common.HashIsNotAddToAtReply = HashMgr.IsNotAddToAtReply;
7130 SettingManager.Common.TrackWord = tw.TrackWord;
7131 SettingManager.Common.AllAtReply = tw.AllAtReply;
7132 SettingManager.Common.UseImageService = ImageSelector.ServiceIndex;
7133 SettingManager.Common.UseImageServiceName = ImageSelector.ServiceName;
7135 SettingManager.SaveCommon();
7139 private void SaveConfigsLocal()
7141 if (_ignoreConfigSave) return;
7144 ModifySettingLocal = false;
7145 SettingManager.Local.ScaleDimension = this.CurrentAutoScaleDimensions;
7146 SettingManager.Local.FormSize = _mySize;
7147 SettingManager.Local.FormLocation = _myLoc;
7148 SettingManager.Local.SplitterDistance = _mySpDis;
7149 SettingManager.Local.PreviewDistance = _mySpDis3;
7150 SettingManager.Local.StatusMultiline = StatusText.Multiline;
7151 SettingManager.Local.StatusTextHeight = _mySpDis2;
7153 SettingManager.Local.FontUnread = _fntUnread;
7154 SettingManager.Local.ColorUnread = _clUnread;
7155 SettingManager.Local.FontRead = _fntReaded;
7156 SettingManager.Local.ColorRead = _clReaded;
7157 SettingManager.Local.FontDetail = _fntDetail;
7158 SettingManager.Local.ColorDetail = _clDetail;
7159 SettingManager.Local.ColorDetailBackcolor = _clDetailBackcolor;
7160 SettingManager.Local.ColorDetailLink = _clDetailLink;
7161 SettingManager.Local.ColorFav = _clFav;
7162 SettingManager.Local.ColorOWL = _clOWL;
7163 SettingManager.Local.ColorRetweet = _clRetweet;
7164 SettingManager.Local.ColorSelf = _clSelf;
7165 SettingManager.Local.ColorAtSelf = _clAtSelf;
7166 SettingManager.Local.ColorTarget = _clTarget;
7167 SettingManager.Local.ColorAtTarget = _clAtTarget;
7168 SettingManager.Local.ColorAtFromTarget = _clAtFromTarget;
7169 SettingManager.Local.ColorAtTo = _clAtTo;
7170 SettingManager.Local.ColorListBackcolor = _clListBackcolor;
7171 SettingManager.Local.ColorInputBackcolor = _clInputBackcolor;
7172 SettingManager.Local.ColorInputFont = _clInputFont;
7173 SettingManager.Local.FontInputFont = _fntInputFont;
7175 if (_ignoreConfigSave) return;
7176 SettingManager.SaveLocal();
7180 private void SaveConfigsTabs()
7182 var tabSettingList = new List<SettingTabs.SettingTabItem>();
7184 var tabs = this.ListTab.TabPages.Cast<TabPage>()
7185 .Select(x => this._statuses.Tabs[x.Text])
7186 .Append(this._statuses.GetTabByType(MyCommon.TabUsageType.Mute));
7188 foreach (var tab in tabs)
7190 if (!tab.IsPermanentTabType)
7193 var tabSetting = new SettingTabs.SettingTabItem
7195 TabName = tab.TabName,
7196 TabType = tab.TabType,
7197 UnreadManage = tab.UnreadManage,
7198 Protected = tab.Protected,
7199 Notify = tab.Notify,
7200 SoundFile = tab.SoundFile,
7205 case FilterTabModel filterTab:
7206 tabSetting.FilterArray = filterTab.FilterArray;
7208 case UserTimelineTabModel userTab:
7209 tabSetting.User = userTab.ScreenName;
7211 case PublicSearchTabModel searchTab:
7212 tabSetting.SearchWords = searchTab.SearchWords;
7213 tabSetting.SearchLang = searchTab.SearchLang;
7215 case ListTimelineTabModel listTab:
7216 tabSetting.ListInfo = listTab.ListInfo;
7220 tabSettingList.Add(tabSetting);
7223 SettingManager.Tabs.Tabs = tabSettingList;
7224 SettingManager.SaveTabs();
7227 private async void OpenURLFileMenuItem_Click(object sender, EventArgs e)
7229 var ret = InputDialog.Show(this, Properties.Resources.OpenURL_InputText, Properties.Resources.OpenURL_Caption, out var inputText);
7230 if (ret != DialogResult.OK)
7233 var match = Twitter.StatusUrlRegex.Match(inputText);
7236 MessageBox.Show(this, Properties.Resources.OpenURL_InvalidFormat,
7237 Properties.Resources.OpenURL_Caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
7243 var statusId = long.Parse(match.Groups["StatusId"].Value);
7244 await this.OpenRelatedTab(statusId);
7246 catch (TabException ex)
7248 MessageBox.Show(this, ex.Message, ApplicationSettings.ApplicationName, MessageBoxButtons.OK, MessageBoxIcon.Error);
7252 private void SaveLogMenuItem_Click(object sender, EventArgs e)
7254 DialogResult rslt = MessageBox.Show(string.Format(Properties.Resources.SaveLogMenuItem_ClickText1, Environment.NewLine),
7255 Properties.Resources.SaveLogMenuItem_ClickText2,
7256 MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
7257 if (rslt == DialogResult.Cancel) return;
7259 SaveFileDialog1.FileName = $"{ApplicationSettings.AssemblyName}Posts{DateTimeUtc.Now.ToLocalTime():yyMMdd-HHmmss}.tsv";
7260 SaveFileDialog1.InitialDirectory = Application.ExecutablePath;
7261 SaveFileDialog1.Filter = Properties.Resources.SaveLogMenuItem_ClickText3;
7262 SaveFileDialog1.FilterIndex = 0;
7263 SaveFileDialog1.Title = Properties.Resources.SaveLogMenuItem_ClickText4;
7264 SaveFileDialog1.RestoreDirectory = true;
7266 if (SaveFileDialog1.ShowDialog() == DialogResult.OK)
7268 if (!SaveFileDialog1.ValidateNames) return;
7269 using (StreamWriter sw = new StreamWriter(SaveFileDialog1.FileName, false, Encoding.UTF8))
7271 if (rslt == DialogResult.Yes)
7274 for (int idx = 0; idx < _curList.VirtualListSize; idx++)
7276 PostClass post = _statuses.Tabs[_curTab.Text][idx];
7277 string protect = "";
7278 if (post.IsProtect) protect = "Protect";
7279 sw.WriteLine(post.Nickname + "\t" +
7280 "\"" + post.TextFromApi.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
7281 post.CreatedAt.ToLocalTimeString() + "\t" +
7282 post.ScreenName + "\t" +
7283 post.StatusId + "\t" +
7284 post.ImageUrl + "\t" +
7285 "\"" + post.Text.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
7291 foreach (int idx in _curList.SelectedIndices)
7293 PostClass post = _statuses.Tabs[_curTab.Text][idx];
7294 string protect = "";
7295 if (post.IsProtect) protect = "Protect";
7296 sw.WriteLine(post.Nickname + "\t" +
7297 "\"" + post.TextFromApi.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
7298 post.CreatedAt.ToLocalTimeString() + "\t" +
7299 post.ScreenName + "\t" +
7300 post.StatusId + "\t" +
7301 post.ImageUrl + "\t" +
7302 "\"" + post.Text.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
7308 this.TopMost = SettingManager.Common.AlwaysTop;
7311 public bool TabRename(string origTabName, out string newTabName)
7315 using (InputTabName inputName = new InputTabName())
7317 inputName.TabName = origTabName;
7318 inputName.ShowDialog();
7319 if (inputName.DialogResult == DialogResult.Cancel) return false;
7320 newTabName = inputName.TabName;
7322 this.TopMost = SettingManager.Common.AlwaysTop;
7323 if (!string.IsNullOrEmpty(newTabName))
7326 for (int i = 0; i < ListTab.TabCount; i++)
7328 if (ListTab.TabPages[i].Text == newTabName)
7330 string tmp = string.Format(Properties.Resources.Tabs_DoubleClickText1, newTabName);
7331 MessageBox.Show(tmp, Properties.Resources.Tabs_DoubleClickText2, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
7336 var tabPage = this.ListTab.TabPages.Cast<TabPage>()
7337 .FirstOrDefault(x => x.Text == origTabName);
7340 if (tabPage != null)
7341 tabPage.Text = newTabName;
7343 _statuses.RenameTab(origTabName, newTabName);
7345 SaveConfigsCommon();
7347 _rclickTabName = newTabName;
7356 private void ListTab_MouseClick(object sender, MouseEventArgs e)
7358 if (e.Button == MouseButtons.Middle)
7360 for (int i = 0; i < this.ListTab.TabPages.Count; i++)
7362 if (this.ListTab.GetTabRect(i).Contains(e.Location))
7364 this.RemoveSpecifiedTab(this.ListTab.TabPages[i].Text, true);
7365 this.SaveConfigsTabs();
7372 private void ListTab_DoubleClick(object sender, MouseEventArgs e)
7373 => this.TabRename(this.ListTab.SelectedTab.Text, out var _);
7375 private void ListTab_MouseDown(object sender, MouseEventArgs e)
7377 if (SettingManager.Common.TabMouseLock) return;
7378 if (e.Button == MouseButtons.Left)
7380 for (int i = 0; i < ListTab.TabPages.Count; i++)
7382 if (this.ListTab.GetTabRect(i).Contains(e.Location))
7385 _tabMouseDownPoint = e.Location;
7396 private void ListTab_DragEnter(object sender, DragEventArgs e)
7398 if (e.Data.GetDataPresent(typeof(TabPage)))
7399 e.Effect = DragDropEffects.Move;
7401 e.Effect = DragDropEffects.None;
7404 private void ListTab_DragDrop(object sender, DragEventArgs e)
7406 if (!e.Data.GetDataPresent(typeof(TabPage))) return;
7411 Point cpos = new Point(e.X, e.Y);
7412 Point spos = ListTab.PointToClient(cpos);
7414 for (i = 0; i < ListTab.TabPages.Count; i++)
7416 Rectangle rect = ListTab.GetTabRect(i);
7417 if (rect.Left <= spos.X && spos.X <= rect.Right &&
7418 rect.Top <= spos.Y && spos.Y <= rect.Bottom)
7420 tn = ListTab.TabPages[i].Text;
7421 if (spos.X <= (rect.Left + rect.Right) / 2)
7430 //タブのないところにドロップ->最後尾へ移動
7431 if (string.IsNullOrEmpty(tn))
7433 tn = ListTab.TabPages[ListTab.TabPages.Count - 1].Text;
7435 i = ListTab.TabPages.Count - 1;
7438 TabPage tp = (TabPage)e.Data.GetData(typeof(TabPage));
7439 if (tp.Text == tn) return;
7441 ReOrderTab(tp.Text, tn, bef);
7444 public void ReOrderTab(string targetTabText, string baseTabText, bool isBeforeBaseTab)
7446 var baseIndex = this.GetTabPageIndex(baseTabText);
7447 if (baseIndex == -1)
7450 var targetIndex = this.GetTabPageIndex(targetTabText);
7451 if (targetIndex == -1)
7454 using (ControlTransaction.Layout(this.ListTab))
7456 var mTp = this.ListTab.TabPages[targetIndex];
7457 this.ListTab.TabPages.Remove(mTp);
7459 if (targetIndex < baseIndex)
7462 if (isBeforeBaseTab)
7463 ListTab.TabPages.Insert(baseIndex, mTp);
7465 ListTab.TabPages.Insert(baseIndex + 1, mTp);
7471 private void MakeReplyOrDirectStatus(bool isAuto = true, bool isReply = true, bool isAll = false)
7473 //isAuto:true=先頭に挿入、false=カーソル位置に挿入
7474 //isReply:true=@,false=DM
7475 if (!StatusText.Enabled) return;
7476 if (_curList == null) return;
7477 if (_curTab == null) return;
7478 if (!this.ExistCurrentPost) return;
7480 // 複数あてリプライはReplyではなく通常ポスト
7481 //↑仕様変更で全部リプライ扱いでOK(先頭ドット付加しない)
7482 //090403暫定でドットを付加しないようにだけ修正。単独と複数の処理は統合できると思われる。
7483 //090513 all @ replies 廃止の仕様変更によりドット付加に戻し(syo68k)
7485 if (_curList.SelectedIndices.Count > 0)
7488 if (_curList.SelectedIndices.Count == 1 && !isAll && this.ExistCurrentPost)
7491 if ((_statuses.Tabs[ListTab.SelectedTab.Text].TabType == MyCommon.TabUsageType.DirectMessage && isAuto) || (!isAuto && !isReply))
7494 this.inReplyTo = null;
7495 StatusText.Text = "D " + _curPost.ScreenName + " " + StatusText.Text;
7496 StatusText.SelectionStart = StatusText.Text.Length;
7500 if (string.IsNullOrEmpty(StatusText.Text))
7503 var inReplyToStatusId = this._curPost.RetweetedId ?? this._curPost.StatusId;
7504 var inReplyToScreenName = this._curPost.ScreenName;
7505 this.inReplyTo = (inReplyToStatusId, inReplyToScreenName);
7507 // ステータステキストが入力されていない場合先頭に@ユーザー名を追加する
7508 StatusText.Text = "@" + _curPost.ScreenName + " ";
7516 //1件選んでEnter or DoubleClick
7517 if (StatusText.Text.Contains("@" + _curPost.ScreenName + " "))
7519 if (this.inReplyTo?.ScreenName == _curPost.ScreenName)
7522 var inReplyToStatusId = this._curPost.RetweetedId ?? this._curPost.StatusId;
7523 var inReplyToScreenName = this._curPost.ScreenName;
7524 this.inReplyTo = (inReplyToStatusId, inReplyToScreenName);
7528 if (!StatusText.Text.StartsWith("@", StringComparison.Ordinal))
7531 if (StatusText.Text.StartsWith(". ", StringComparison.Ordinal))
7534 this.inReplyTo = null;
7535 StatusText.Text = StatusText.Text.Insert(2, "@" + _curPost.ScreenName + " ");
7540 var inReplyToStatusId = this._curPost.RetweetedId ?? this._curPost.StatusId;
7541 var inReplyToScreenName = this._curPost.ScreenName;
7542 this.inReplyTo = (inReplyToStatusId, inReplyToScreenName);
7543 StatusText.Text = "@" + _curPost.ScreenName + " " + StatusText.Text;
7550 this.inReplyTo = null;
7551 StatusText.Text = ". @" + _curPost.ScreenName + " " + StatusText.Text;
7552 //StatusText.Text = "@" + _curPost.ScreenName + " " + StatusText.Text;
7557 //1件選んでCtrl-Rの場合(返信先操作せず)
7558 int sidx = StatusText.SelectionStart;
7559 string id = "@" + _curPost.ScreenName + " ";
7562 if (StatusText.Text.Substring(sidx - 1, 1) != " ")
7567 StatusText.Text = StatusText.Text.Insert(sidx, id);
7569 //if (StatusText.Text.StartsWith("@"))
7572 // StatusText.Text = ". " + StatusText.Text.Insert(sidx, " @" + _curPost.ScreenName + " ");
7573 // sidx += 5 + _curPost.ScreenName.Length;
7578 // StatusText.Text = StatusText.Text.Insert(sidx, " @" + _curPost.ScreenName + " ");
7579 // sidx += 3 + _curPost.ScreenName.Length;
7581 StatusText.SelectionStart = sidx;
7584 //_reply_to_name = null;
7592 if (!isAuto && !isReply) return;
7594 //C-S-rか、複数の宛先を選択中にEnter/DoubleClick/C-r/C-S-r
7598 //Enter or DoubleClick
7600 string sTxt = StatusText.Text;
7601 if (!sTxt.StartsWith(". ", StringComparison.Ordinal))
7604 this.inReplyTo = null;
7606 for (int cnt = 0; cnt < _curList.SelectedIndices.Count; cnt++)
7608 PostClass post = _statuses.Tabs[_curTab.Text][_curList.SelectedIndices[cnt]];
7609 if (!sTxt.Contains("@" + post.ScreenName + " "))
7611 sTxt = sTxt.Insert(2, "@" + post.ScreenName + " ");
7612 //sTxt = "@" + post.ScreenName + " " + sTxt;
7615 StatusText.Text = sTxt;
7620 if (_curList.SelectedIndices.Count > 1)
7625 int sidx = StatusText.SelectionStart;
7626 for (int cnt = 0; cnt < _curList.SelectedIndices.Count; cnt++)
7628 PostClass post = _statuses.Tabs[_curTab.Text][_curList.SelectedIndices[cnt]];
7629 if (!ids.Contains("@" + post.ScreenName + " ") && post.UserId != tw.UserId)
7631 ids += "@" + post.ScreenName + " ";
7635 foreach (var (_, screenName) in post.ReplyToList)
7637 if (!ids.Contains("@" + screenName + " ") &&
7638 !screenName.Equals(tw.Username, StringComparison.CurrentCultureIgnoreCase))
7640 Match m = Regex.Match(post.TextFromApi, "[@@](?<id>" + screenName + ")([^a-zA-Z0-9]|$)", RegexOptions.IgnoreCase);
7642 ids += "@" + m.Result("${id}") + " ";
7644 ids += "@" + screenName + " ";
7649 if (ids.Length == 0) return;
7650 if (!StatusText.Text.StartsWith(". ", StringComparison.Ordinal))
7652 this.inReplyTo = null;
7653 StatusText.Text = ". " + StatusText.Text;
7658 if (StatusText.Text.Substring(sidx - 1, 1) != " ")
7663 StatusText.Text = StatusText.Text.Insert(sidx, ids);
7665 //if (StatusText.Text.StartsWith("@"))
7667 // StatusText.Text = ". " + StatusText.Text.Insert(sidx, ids);
7668 // sidx += 2 + ids.Length;
7672 // StatusText.Text = StatusText.Text.Insert(sidx, ids);
7673 // sidx += 1 + ids.Length;
7675 StatusText.SelectionStart = sidx;
7681 //1件のみ選択のC-S-r(返信元付加する可能性あり)
7684 int sidx = StatusText.SelectionStart;
7685 PostClass post = _curPost;
7686 if (!ids.Contains("@" + post.ScreenName + " ") && post.UserId != tw.UserId)
7688 ids += "@" + post.ScreenName + " ";
7690 foreach (var (_, screenName) in post.ReplyToList)
7692 if (!ids.Contains("@" + screenName + " ") &&
7693 !screenName.Equals(tw.Username, StringComparison.CurrentCultureIgnoreCase))
7695 Match m = Regex.Match(post.TextFromApi, "[@@](?<id>" + screenName + ")([^a-zA-Z0-9]|$)", RegexOptions.IgnoreCase);
7697 ids += "@" + m.Result("${id}") + " ";
7699 ids += "@" + screenName + " ";
7702 if (!string.IsNullOrEmpty(post.RetweetedBy))
7704 if (!ids.Contains("@" + post.RetweetedBy + " ") && post.RetweetedByUserId != tw.UserId)
7706 ids += "@" + post.RetweetedBy + " ";
7709 if (ids.Length == 0) return;
7710 if (string.IsNullOrEmpty(StatusText.Text))
7713 var inReplyToStatusId = this._curPost.RetweetedId ?? this._curPost.StatusId;
7714 var inReplyToScreenName = this._curPost.ScreenName;
7715 this.inReplyTo = (inReplyToStatusId, inReplyToScreenName);
7717 StatusText.Text = ids;
7718 StatusText.SelectionStart = ids.Length;
7725 if (StatusText.Text.Substring(sidx - 1, 1) != " ")
7730 StatusText.Text = StatusText.Text.Insert(sidx, ids);
7732 StatusText.SelectionStart = sidx;
7738 StatusText.SelectionStart = StatusText.Text.Length;
7743 private void ListTab_MouseUp(object sender, MouseEventArgs e)
7744 => this._tabDrag = false;
7746 private static int iconCnt = 0;
7747 private static int blinkCnt = 0;
7748 private static bool blink = false;
7749 private static bool idle = false;
7751 private async Task RefreshTasktrayIcon()
7754 await this.Colorize();
7756 if (!TimerRefreshIcon.Enabled) return;
7757 //Static usCheckCnt As int = 0
7759 //Static iconDlListTopItem As ListViewItem = null
7761 //if (((ListView)ListTab.SelectedTab.Tag).TopItem == iconDlListTopItem)
7762 // ((ImageDictionary)this.TIconDic).PauseGetImage = false;
7764 // ((ImageDictionary)this.TIconDic).PauseGetImage = true;
7766 //iconDlListTopItem = ((ListView)ListTab.SelectedTab.Tag).TopItem;
7772 //if (usCheckCnt > 300) //1min
7775 // if (!this.IsReceivedUserStream)
7777 // TraceOut("ReconnectUserStream");
7778 // tw.ReconnectUserStream();
7782 var busy = this.workerSemaphore.CurrentCount != MAX_WORKER_THREADS;
7784 if (iconCnt >= this.NIconRefresh.Length)
7792 SaveConfigsAll(true);
7797 NotifyIcon1.Icon = NIconRefresh[iconCnt];
7799 _myStatusError = false;
7803 TabModel tb = _statuses.GetTabByType(MyCommon.TabUsageType.Mentions);
7804 if (SettingManager.Common.ReplyIconState != MyCommon.REPLY_ICONSTATE.None && tb != null && tb.UnreadCount > 0)
7806 if (blinkCnt > 0) return;
7808 if (blink || SettingManager.Common.ReplyIconState == MyCommon.REPLY_ICONSTATE.StaticIcon)
7810 NotifyIcon1.Icon = ReplyIcon;
7814 NotifyIcon1.Icon = ReplyIconBlink;
7822 //優先度:エラー→オフライン→アイドル
7826 NotifyIcon1.Icon = NIconAtRed;
7829 if (_myStatusOnline)
7831 NotifyIcon1.Icon = NIconAt;
7835 NotifyIcon1.Icon = NIconAtSmoke;
7839 private async void TimerRefreshIcon_Tick(object sender, EventArgs e)
7840 => await this.RefreshTasktrayIcon(); // 200ms
7842 private void ContextMenuTabProperty_Opening(object sender, CancelEventArgs e)
7844 //右クリックの場合はタブ名が設定済。アプリケーションキーの場合は現在のタブを対象とする
7845 if (string.IsNullOrEmpty(_rclickTabName) || sender != ContextMenuTabProperty)
7847 if (ListTab != null && ListTab.SelectedTab != null)
7848 _rclickTabName = ListTab.SelectedTab.Text;
7853 if (_statuses == null) return;
7854 if (_statuses.Tabs == null) return;
7856 if (!this._statuses.Tabs.TryGetValue(this._rclickTabName, out var tb))
7859 NotifyDispMenuItem.Checked = tb.Notify;
7860 this.NotifyTbMenuItem.Checked = tb.Notify;
7862 soundfileListup = true;
7863 SoundFileComboBox.Items.Clear();
7864 this.SoundFileTbComboBox.Items.Clear();
7865 SoundFileComboBox.Items.Add("");
7866 this.SoundFileTbComboBox.Items.Add("");
7867 DirectoryInfo oDir = new DirectoryInfo(Application.StartupPath + Path.DirectorySeparatorChar);
7868 if (Directory.Exists(Path.Combine(Application.StartupPath, "Sounds")))
7870 oDir = oDir.GetDirectories("Sounds")[0];
7872 foreach (FileInfo oFile in oDir.GetFiles("*.wav"))
7874 SoundFileComboBox.Items.Add(oFile.Name);
7875 this.SoundFileTbComboBox.Items.Add(oFile.Name);
7877 int idx = SoundFileComboBox.Items.IndexOf(tb.SoundFile);
7878 if (idx == -1) idx = 0;
7879 SoundFileComboBox.SelectedIndex = idx;
7880 this.SoundFileTbComboBox.SelectedIndex = idx;
7881 soundfileListup = false;
7882 UreadManageMenuItem.Checked = tb.UnreadManage;
7883 this.UnreadMngTbMenuItem.Checked = tb.UnreadManage;
7885 TabMenuControl(_rclickTabName);
7888 private void TabMenuControl(string tabName)
7890 var tabInfo = _statuses.GetTabByName(tabName);
7892 this.FilterEditMenuItem.Enabled = true;
7893 this.EditRuleTbMenuItem.Enabled = true;
7895 if (tabInfo.IsDefaultTabType)
7897 this.ProtectTabMenuItem.Enabled = false;
7898 this.ProtectTbMenuItem.Enabled = false;
7902 this.ProtectTabMenuItem.Enabled = true;
7903 this.ProtectTbMenuItem.Enabled = true;
7906 if (tabInfo.IsDefaultTabType || tabInfo.Protected)
7908 this.ProtectTabMenuItem.Checked = true;
7909 this.ProtectTbMenuItem.Checked = true;
7910 this.DeleteTabMenuItem.Enabled = false;
7911 this.DeleteTbMenuItem.Enabled = false;
7915 this.ProtectTabMenuItem.Checked = false;
7916 this.ProtectTbMenuItem.Checked = false;
7917 this.DeleteTabMenuItem.Enabled = true;
7918 this.DeleteTbMenuItem.Enabled = true;
7922 private void ProtectTabMenuItem_Click(object sender, EventArgs e)
7924 var checkState = ((ToolStripMenuItem)sender).Checked;
7927 this.ProtectTbMenuItem.Checked = checkState;
7928 this.ProtectTabMenuItem.Checked = checkState;
7931 this.DeleteTabMenuItem.Enabled = !checkState;
7932 this.DeleteTbMenuItem.Enabled = !checkState;
7934 if (string.IsNullOrEmpty(_rclickTabName)) return;
7935 _statuses.Tabs[_rclickTabName].Protected = checkState;
7940 private void UreadManageMenuItem_Click(object sender, EventArgs e)
7942 UreadManageMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
7943 this.UnreadMngTbMenuItem.Checked = UreadManageMenuItem.Checked;
7945 if (string.IsNullOrEmpty(_rclickTabName)) return;
7946 ChangeTabUnreadManage(_rclickTabName, UreadManageMenuItem.Checked);
7951 public void ChangeTabUnreadManage(string tabName, bool isManage)
7953 var idx = this.GetTabPageIndex(tabName);
7957 _statuses.Tabs[tabName].UnreadManage = isManage;
7958 if (SettingManager.Common.TabIconDisp)
7960 if (_statuses.Tabs[tabName].UnreadCount > 0)
7961 ListTab.TabPages[idx].ImageIndex = 0;
7963 ListTab.TabPages[idx].ImageIndex = -1;
7966 if (_curTab.Text == tabName)
7968 this.PurgeListViewItemCache();
7972 SetMainWindowTitle();
7973 SetStatusLabelUrl();
7974 if (!SettingManager.Common.TabIconDisp) ListTab.Refresh();
7977 private void NotifyDispMenuItem_Click(object sender, EventArgs e)
7979 NotifyDispMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
7980 this.NotifyTbMenuItem.Checked = NotifyDispMenuItem.Checked;
7982 if (string.IsNullOrEmpty(_rclickTabName)) return;
7984 _statuses.Tabs[_rclickTabName].Notify = NotifyDispMenuItem.Checked;
7989 private void SoundFileComboBox_SelectedIndexChanged(object sender, EventArgs e)
7991 if (soundfileListup || string.IsNullOrEmpty(_rclickTabName)) return;
7993 _statuses.Tabs[_rclickTabName].SoundFile = (string)((ToolStripComboBox)sender).SelectedItem;
7998 private void DeleteTabMenuItem_Click(object sender, EventArgs e)
8000 if (string.IsNullOrEmpty(_rclickTabName) || sender == this.DeleteTbMenuItem) _rclickTabName = ListTab.SelectedTab.Text;
8002 RemoveSpecifiedTab(_rclickTabName, true);
8006 private void FilterEditMenuItem_Click(object sender, EventArgs e)
8008 if (string.IsNullOrEmpty(_rclickTabName)) _rclickTabName = _statuses.GetTabByType(MyCommon.TabUsageType.Home).TabName;
8010 using (var fltDialog = new FilterDialog())
8012 fltDialog.Owner = this;
8013 fltDialog.SetCurrent(_rclickTabName);
8014 fltDialog.ShowDialog(this);
8016 this.TopMost = SettingManager.Common.AlwaysTop;
8018 this.ApplyPostFilters();
8022 private async void AddTabMenuItem_Click(object sender, EventArgs e)
8024 string tabName = null;
8025 MyCommon.TabUsageType tabUsage;
8026 using (InputTabName inputName = new InputTabName())
8028 inputName.TabName = _statuses.MakeTabName("MyTab");
8029 inputName.IsShowUsage = true;
8030 inputName.ShowDialog();
8031 if (inputName.DialogResult == DialogResult.Cancel) return;
8032 tabName = inputName.TabName;
8033 tabUsage = inputName.Usage;
8035 this.TopMost = SettingManager.Common.AlwaysTop;
8036 if (!string.IsNullOrEmpty(tabName))
8039 ListElement list = null;
8040 if (tabUsage == MyCommon.TabUsageType.Lists)
8042 using (ListAvailable listAvail = new ListAvailable())
8044 if (listAvail.ShowDialog(this) == DialogResult.Cancel) return;
8045 if (listAvail.SelectedList == null) return;
8046 list = listAvail.SelectedList;
8053 case MyCommon.TabUsageType.UserDefined:
8054 tab = new FilterTabModel(tabName);
8056 case MyCommon.TabUsageType.PublicSearch:
8057 tab = new PublicSearchTabModel(tabName);
8059 case MyCommon.TabUsageType.Lists:
8060 tab = new ListTimelineTabModel(tabName, list);
8066 if (!_statuses.AddTab(tab) || !AddNewTab(tab, startup: false))
8068 string tmp = string.Format(Properties.Resources.AddTabMenuItem_ClickText1, tabName);
8069 MessageBox.Show(tmp, Properties.Resources.AddTabMenuItem_ClickText2, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
8075 if (tabUsage == MyCommon.TabUsageType.PublicSearch)
8077 ListTab.SelectedIndex = ListTab.TabPages.Count - 1;
8078 ListTab.SelectedTab.Controls["panelSearch"].Controls["comboSearch"].Focus();
8080 if (tabUsage == MyCommon.TabUsageType.Lists)
8082 ListTab.SelectedIndex = ListTab.TabPages.Count - 1;
8083 var listTab = this._statuses.Tabs[this._curTab.Text];
8084 await this.RefreshTabAsync(listTab);
8090 private void TabMenuItem_Click(object sender, EventArgs e)
8092 using (var fltDialog = new FilterDialog())
8094 fltDialog.Owner = this;
8097 foreach (int idx in _curList.SelectedIndices)
8100 if (!SelectTab(out var tabName)) return;
8102 fltDialog.SetCurrent(tabName);
8103 if (_statuses.Tabs[_curTab.Text][idx].RetweetedId == null)
8105 fltDialog.AddNewFilter(_statuses.Tabs[_curTab.Text][idx].ScreenName, _statuses.Tabs[_curTab.Text][idx].TextFromApi);
8109 fltDialog.AddNewFilter(_statuses.Tabs[_curTab.Text][idx].RetweetedBy, _statuses.Tabs[_curTab.Text][idx].TextFromApi);
8111 fltDialog.ShowDialog(this);
8112 this.TopMost = SettingManager.Common.AlwaysTop;
8116 this.ApplyPostFilters();
8118 if (this.ListTab.SelectedTab != null &&
8119 ((DetailsListView)this.ListTab.SelectedTab.Tag).SelectedIndices.Count > 0)
8121 _curPost = _statuses.Tabs[this.ListTab.SelectedTab.Text][((DetailsListView)this.ListTab.SelectedTab.Tag).SelectedIndices[0]];
8125 protected override bool ProcessDialogKey(Keys keyData)
8127 //TextBox1でEnterを押してもビープ音が鳴らないようにする
8128 if ((keyData & Keys.KeyCode) == Keys.Enter)
8130 if (StatusText.Focused)
8132 bool _NewLine = false;
8135 if (SettingManager.Common.PostCtrlEnter) //Ctrl+Enter投稿時
8137 if (StatusText.Multiline)
8139 if ((keyData & Keys.Shift) == Keys.Shift && (keyData & Keys.Control) != Keys.Control) _NewLine = true;
8141 if ((keyData & Keys.Control) == Keys.Control) _Post = true;
8145 if (((keyData & Keys.Control) == Keys.Control)) _Post = true;
8149 else if (SettingManager.Common.PostShiftEnter) //SHift+Enter投稿時
8151 if (StatusText.Multiline)
8153 if ((keyData & Keys.Control) == Keys.Control && (keyData & Keys.Shift) != Keys.Shift) _NewLine = true;
8155 if ((keyData & Keys.Shift) == Keys.Shift) _Post = true;
8159 if (((keyData & Keys.Shift) == Keys.Shift)) _Post = true;
8165 if (StatusText.Multiline)
8167 if ((keyData & Keys.Shift) == Keys.Shift && (keyData & Keys.Control) != Keys.Control) _NewLine = true;
8169 if (((keyData & Keys.Control) != Keys.Control && (keyData & Keys.Shift) != Keys.Shift) ||
8170 ((keyData & Keys.Control) == Keys.Control && (keyData & Keys.Shift) == Keys.Shift)) _Post = true;
8174 if (((keyData & Keys.Shift) == Keys.Shift) ||
8175 (((keyData & Keys.Control) != Keys.Control) &&
8176 ((keyData & Keys.Shift) != Keys.Shift))) _Post = true;
8182 int pos1 = StatusText.SelectionStart;
8183 if (StatusText.SelectionLength > 0)
8185 StatusText.Text = StatusText.Text.Remove(pos1, StatusText.SelectionLength); //選択状態文字列削除
8187 StatusText.Text = StatusText.Text.Insert(pos1, Environment.NewLine); //改行挿入
8188 StatusText.SelectionStart = pos1 + Environment.NewLine.Length; //カーソルを改行の次の文字へ移動
8193 PostButton_Click(null, null);
8197 else if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType == MyCommon.TabUsageType.PublicSearch &&
8198 (ListTab.SelectedTab.Controls["panelSearch"].Controls["comboSearch"].Focused ||
8199 ListTab.SelectedTab.Controls["panelSearch"].Controls["comboLang"].Focused))
8201 this.SearchButton_Click(ListTab.SelectedTab.Controls["panelSearch"].Controls["comboSearch"], null);
8206 return base.ProcessDialogKey(keyData);
8209 private void ReplyAllStripMenuItem_Click(object sender, EventArgs e)
8210 => this.MakeReplyOrDirectStatus(false, true, true);
8212 private void IDRuleMenuItem_Click(object sender, EventArgs e)
8215 if (_curList.SelectedIndices.Count == 0) return;
8217 var tab = this._statuses.Tabs[this._curTab.Text];
8218 var screenNameArray = this._curList.SelectedIndices.Cast<int>()
8219 .Select(x => tab[x])
8220 .Select(x => x.RetweetedId != null ? x.RetweetedBy : x.ScreenName)
8223 this.AddFilterRuleByScreenName(screenNameArray);
8225 if (screenNameArray.Length != 0)
8227 List<string> atids = new List<string>();
8228 foreach (var screenName in screenNameArray)
8230 atids.Add("@" + screenName);
8232 int cnt = AtIdSupl.ItemCount;
8233 AtIdSupl.AddRangeItem(atids.ToArray());
8234 if (AtIdSupl.ItemCount != cnt) ModifySettingAtId = true;
8238 private void SourceRuleMenuItem_Click(object sender, EventArgs e)
8240 if (this._curList.SelectedIndices.Count == 0)
8243 var tab = this._statuses.Tabs[this._curTab.Text];
8244 var sourceArray = this._curList.SelectedIndices.Cast<int>()
8245 .Select(x => tab[x].Source).ToArray();
8247 this.AddFilterRuleBySource(sourceArray);
8250 public void AddFilterRuleByScreenName(params string[] screenNameArray)
8253 if (!SelectTab(out var tabName)) return;
8255 var tab = (FilterTabModel)this._statuses.Tabs[tabName];
8259 if (tab.TabType != MyCommon.TabUsageType.Mute)
8261 this.MoveOrCopy(out mv, out mk);
8265 // ミュートタブでは常に MoveMatches を true にする
8270 foreach (var screenName in screenNameArray)
8272 tab.AddFilter(new PostFilterRule
8274 FilterName = screenName,
8275 UseNameField = true,
8279 FilterByUrl = false,
8283 this.ApplyPostFilters();
8287 public void AddFilterRuleBySource(params string[] sourceArray)
8289 // タブ選択ダイアログを表示(or追加)
8290 if (!this.SelectTab(out var tabName))
8293 var filterTab = (FilterTabModel)this._statuses.Tabs[tabName];
8297 if (filterTab.TabType != MyCommon.TabUsageType.Mute)
8299 // フィルタ動作選択ダイアログを表示(移動/コピー, マーク有無)
8300 this.MoveOrCopy(out mv, out mk);
8304 // ミュートタブでは常に MoveMatches を true にする
8309 // 振り分けルールに追加するSource
8310 foreach (var source in sourceArray)
8312 filterTab.AddFilter(new PostFilterRule
8314 FilterSource = source,
8318 FilterByUrl = false,
8322 this.ApplyPostFilters();
8323 this.SaveConfigsTabs();
8326 private bool SelectTab(out string tabName)
8333 using (var dialog = new TabsDialog(_statuses))
8335 if (dialog.ShowDialog(this) == DialogResult.Cancel) return false;
8337 tabName = dialog.SelectedTab?.TabName;
8340 ListTab.SelectedTab.Focus();
8342 if (tabName == null)
8344 using (InputTabName inputName = new InputTabName())
8346 inputName.TabName = _statuses.MakeTabName("MyTab");
8347 inputName.ShowDialog();
8348 if (inputName.DialogResult == DialogResult.Cancel) return false;
8349 tabName = inputName.TabName;
8351 this.TopMost = SettingManager.Common.AlwaysTop;
8352 if (!string.IsNullOrEmpty(tabName))
8354 var tab = new FilterTabModel(tabName);
8355 if (!_statuses.AddTab(tab) || !AddNewTab(tab, startup: false))
8357 string tmp = string.Format(Properties.Resources.IDRuleMenuItem_ClickText2, tabName);
8358 MessageBox.Show(tmp, Properties.Resources.IDRuleMenuItem_ClickText3, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
8376 private void MoveOrCopy(out bool move, out bool mark)
8380 string _tmp = string.Format(Properties.Resources.IDRuleMenuItem_ClickText4, Environment.NewLine);
8381 if (MessageBox.Show(_tmp, Properties.Resources.IDRuleMenuItem_ClickText5, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
8389 string _tmp = string.Format(Properties.Resources.IDRuleMenuItem_ClickText6, Environment.NewLine);
8390 if (MessageBox.Show(_tmp, Properties.Resources.IDRuleMenuItem_ClickText7, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
8401 private void CopySTOTMenuItem_Click(object sender, EventArgs e)
8404 private void CopyURLMenuItem_Click(object sender, EventArgs e)
8405 => this.CopyIdUri();
8407 private void SelectAllMenuItem_Click(object sender, EventArgs e)
8409 if (StatusText.Focused)
8412 StatusText.SelectAll();
8416 // ListView上でのCtrl+A
8417 NativeMethods.SelectAllItems(this._curList);
8421 private void MoveMiddle()
8427 if (_curList.SelectedIndices.Count == 0) return;
8429 int idx = _curList.SelectedIndices[0];
8431 _item = _curList.GetItemAt(0, 25);
8437 _item = _curList.GetItemAt(0, _curList.ClientSize.Height - 1);
8439 idx2 = _curList.VirtualListSize - 1;
8443 idx -= Math.Abs(idx1 - idx2) / 2;
8444 if (idx < 0) idx = 0;
8446 _curList.EnsureVisible(_curList.VirtualListSize - 1);
8447 _curList.EnsureVisible(idx);
8450 private async void OpenURLMenuItem_Click(object sender, EventArgs e)
8452 var linkElements = this.tweetDetailsView.GetLinkElements();
8454 if (linkElements.Length > 0)
8456 UrlDialog.ClearUrl();
8458 string openUrlStr = "";
8460 if (linkElements.Length == 1)
8462 // ツイートに含まれる URL が 1 つのみの場合
8463 // => OpenURL ダイアログを表示せずにリンクを開く
8468 urlStr = MyCommon.IDNEncode(linkElements[0].GetAttribute("href"));
8470 catch (ArgumentException)
8479 if (string.IsNullOrEmpty(urlStr)) return;
8480 openUrlStr = MyCommon.urlEncodeMultibyteChar(urlStr);
8482 // Ctrl+E で呼ばれた場合を考慮し isReverseSettings の判定を行わない
8483 await this.OpenUriAsync(new Uri(openUrlStr));
8487 // ツイートに含まれる URL が複数ある場合
8488 // => OpenURL を表示しユーザーが選択したリンクを開く
8490 foreach (var linkElm in linkElements)
8493 string linkText = "";
8497 urlStr = linkElm.GetAttribute("title");
8498 href = MyCommon.IDNEncode(linkElm.GetAttribute("href"));
8499 if (string.IsNullOrEmpty(urlStr)) urlStr = href;
8500 linkText = linkElm.InnerText;
8502 catch (ArgumentException)
8511 if (string.IsNullOrEmpty(urlStr)) continue;
8512 UrlDialog.AddUrl(new OpenUrlItem(linkText, MyCommon.urlEncodeMultibyteChar(urlStr), href));
8516 if (UrlDialog.ShowDialog() == DialogResult.OK)
8518 openUrlStr = UrlDialog.SelectedUrl;
8520 // Ctrlを押しながらリンクを開いた場合は、設定と逆の動作をするフラグを true としておく
8521 await this.OpenUriAsync(new Uri(openUrlStr), MyCommon.IsKeyDown(Keys.Control));
8528 this.TopMost = SettingManager.Common.AlwaysTop;
8533 private void ClearTabMenuItem_Click(object sender, EventArgs e)
8535 if (string.IsNullOrEmpty(_rclickTabName)) return;
8536 ClearTab(_rclickTabName, true);
8539 private void ClearTab(string tabName, bool showWarning)
8543 string tmp = string.Format(Properties.Resources.ClearTabMenuItem_ClickText1, Environment.NewLine);
8544 if (MessageBox.Show(tmp, tabName + " " + Properties.Resources.ClearTabMenuItem_ClickText2, MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
8550 _statuses.ClearTabIds(tabName);
8551 if (ListTab.SelectedTab.Text == tabName)
8554 _anchorFlag = false;
8555 this.PurgeListViewItemCache();
8559 foreach (TabPage tb in ListTab.TabPages)
8561 if (tb.Text == tabName)
8563 ((DetailsListView)tb.Tag).VirtualListSize = 0;
8568 if (!SettingManager.Common.TabIconDisp) ListTab.Refresh();
8570 SetMainWindowTitle();
8571 SetStatusLabelUrl();
8574 private static long followers = 0;
8576 private void SetMainWindowTitle()
8579 StringBuilder ttl = new StringBuilder(256);
8582 if (SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.None &&
8583 SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.Post &&
8584 SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.Ver &&
8585 SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.OwnStatus)
8587 foreach (var tab in _statuses.Tabs.Values)
8589 ur += tab.UnreadCount;
8594 if (SettingManager.Common.DispUsername) ttl.Append(tw.Username).Append(" - ");
8595 ttl.Append(ApplicationSettings.ApplicationName);
8597 switch (SettingManager.Common.DispLatestPost)
8599 case MyCommon.DispTitleEnum.Ver:
8600 ttl.Append("Ver:").Append(MyCommon.GetReadableVersion());
8602 case MyCommon.DispTitleEnum.Post:
8603 if (_history != null && _history.Count > 1)
8604 ttl.Append(_history[_history.Count - 2].status.Replace("\r\n", " "));
8606 case MyCommon.DispTitleEnum.UnreadRepCount:
8607 ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText1, _statuses.GetTabByType(MyCommon.TabUsageType.Mentions).UnreadCount + _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage).UnreadCount);
8609 case MyCommon.DispTitleEnum.UnreadAllCount:
8610 ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText2, ur);
8612 case MyCommon.DispTitleEnum.UnreadAllRepCount:
8613 ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText3, ur, _statuses.GetTabByType(MyCommon.TabUsageType.Mentions).UnreadCount + _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage).UnreadCount);
8615 case MyCommon.DispTitleEnum.UnreadCountAllCount:
8616 ttl.AppendFormat(Properties.Resources.SetMainWindowTitleText4, ur, al);
8618 case MyCommon.DispTitleEnum.OwnStatus:
8619 if (followers == 0 && tw.FollowersCount > 0) followers = tw.FollowersCount;
8620 ttl.AppendFormat(Properties.Resources.OwnStatusTitle, tw.StatusesCount, tw.FriendsCount, tw.FollowersCount, tw.FollowersCount - followers);
8626 this.Text = ttl.ToString();
8628 catch (AccessViolationException)
8630 //原因不明。ポスト内容に依存か?たまーに発生するが再現せず。
8634 private string GetStatusLabelText()
8637 //タブ未読数/タブ発言数 全未読数/総発言数 (未読@+未読DM数)
8638 if (_statuses == null) return "";
8639 TabModel tbRep = _statuses.GetTabByType(MyCommon.TabUsageType.Mentions);
8640 TabModel tbDm = _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage);
8641 if (tbRep == null || tbDm == null) return "";
8642 int urat = tbRep.UnreadCount + tbDm.UnreadCount;
8647 StringBuilder slbl = new StringBuilder(256);
8650 foreach (var tab in _statuses.Tabs.Values)
8652 ur += tab.UnreadCount;
8654 if (_curTab != null && tab.TabName.Equals(_curTab.Text))
8656 tur = tab.UnreadCount;
8667 UnreadAtCounter = urat;
8669 var homeTab = this._statuses.GetTabByType<HomeTabModel>();
8671 slbl.AppendFormat(Properties.Resources.SetStatusLabelText1, tur, tal, ur, al, urat, _postTimestamps.Count, _favTimestamps.Count, homeTab.TweetsPerHour);
8672 if (SettingManager.Common.TimelinePeriod == 0)
8674 slbl.Append(Properties.Resources.SetStatusLabelText2);
8678 slbl.Append(SettingManager.Common.TimelinePeriod + Properties.Resources.SetStatusLabelText3);
8680 return slbl.ToString();
8683 private async void TwitterApiStatus_AccessLimitUpdated(object sender, EventArgs e)
8687 if (this.InvokeRequired && !this.IsDisposed)
8689 await this.InvokeAsync(() => this.TwitterApiStatus_AccessLimitUpdated(sender, e));
8693 var endpointName = (e as TwitterApiStatus.AccessLimitUpdatedEventArgs).EndpointName;
8694 SetApiStatusLabel(endpointName);
8697 catch (ObjectDisposedException)
8701 catch (InvalidOperationException)
8707 private void SetApiStatusLabel(string endpointName = null)
8709 if (_curTab == null)
8711 this.toolStripApiGauge.ApiEndpoint = null;
8715 var tabType = _statuses.Tabs[_curTab.Text].TabType;
8717 if (endpointName == null)
8722 case MyCommon.TabUsageType.Home:
8723 case MyCommon.TabUsageType.UserDefined:
8724 endpointName = "/statuses/home_timeline";
8727 case MyCommon.TabUsageType.Mentions:
8728 endpointName = "/statuses/mentions_timeline";
8731 case MyCommon.TabUsageType.Favorites:
8732 endpointName = "/favorites/list";
8735 case MyCommon.TabUsageType.DirectMessage:
8736 endpointName = "/direct_messages/events/list";
8739 case MyCommon.TabUsageType.UserTimeline:
8740 endpointName = "/statuses/user_timeline";
8743 case MyCommon.TabUsageType.Lists:
8744 endpointName = "/lists/statuses";
8747 case MyCommon.TabUsageType.PublicSearch:
8748 endpointName = "/search/tweets";
8751 case MyCommon.TabUsageType.Related:
8752 endpointName = "/statuses/show/:id";
8759 this.toolStripApiGauge.ApiEndpoint = endpointName;
8763 // 表示中のタブに関連する endpoint であれば更新
8766 switch (endpointName)
8768 case "/statuses/home_timeline":
8769 update = tabType == MyCommon.TabUsageType.Home ||
8770 tabType == MyCommon.TabUsageType.UserDefined;
8773 case "/statuses/mentions_timeline":
8774 update = tabType == MyCommon.TabUsageType.Mentions;
8777 case "/favorites/list":
8778 update = tabType == MyCommon.TabUsageType.Favorites;
8781 case "/direct_messages/events/list":
8782 update = tabType == MyCommon.TabUsageType.DirectMessage;
8785 case "/statuses/user_timeline":
8786 update = tabType == MyCommon.TabUsageType.UserTimeline;
8789 case "/lists/statuses":
8790 update = tabType == MyCommon.TabUsageType.Lists;
8793 case "/search/tweets":
8794 update = tabType == MyCommon.TabUsageType.PublicSearch;
8797 case "/statuses/show/:id":
8798 update = tabType == MyCommon.TabUsageType.Related;
8807 this.toolStripApiGauge.ApiEndpoint = endpointName;
8813 private void SetStatusLabelUrl()
8814 => this.StatusLabelUrl.Text = this.GetStatusLabelText();
8816 public void SetStatusLabel(string text)
8817 => this.StatusLabel.Text = text;
8819 private void SetNotifyIconText()
8821 var ur = new StringBuilder(64);
8823 // タスクトレイアイコンのツールチップテキスト書き換え
8825 ur.Remove(0, ur.Length);
8826 if (SettingManager.Common.DispUsername)
8828 ur.Append(tw.Username);
8831 ur.Append(ApplicationSettings.ApplicationName);
8833 ur.Append("(Debug Build)");
8835 if (UnreadCounter != -1 && UnreadAtCounter != -1)
8838 ur.Append(UnreadCounter);
8840 ur.Append(UnreadAtCounter);
8843 NotifyIcon1.Text = ur.ToString();
8846 internal void CheckReplyTo(string StatusText)
8850 m = Regex.Matches(StatusText, Twitter.HASHTAG, RegexOptions.IgnoreCase);
8852 foreach (Match hm in m)
8854 if (!hstr.Contains("#" + hm.Result("$3") + " "))
8856 hstr += "#" + hm.Result("$3") + " ";
8857 HashSupl.AddItem("#" + hm.Result("$3"));
8860 if (!string.IsNullOrEmpty(HashMgr.UseHash) && !hstr.Contains(HashMgr.UseHash + " "))
8862 hstr += HashMgr.UseHash;
8864 if (!string.IsNullOrEmpty(hstr)) HashMgr.AddHashToHistory(hstr.Trim(), false);
8866 // 本当にリプライ先指定すべきかどうかの判定
8867 m = Regex.Matches(StatusText, "(^|[ -/:-@[-^`{-~])(?<id>@[a-zA-Z0-9_]+)");
8869 if (SettingManager.Common.UseAtIdSupplement)
8871 int bCnt = AtIdSupl.ItemCount;
8872 foreach (Match mid in m)
8874 AtIdSupl.AddItem(mid.Result("${id}"));
8876 if (bCnt != AtIdSupl.ItemCount) ModifySettingAtId = true;
8879 // リプライ先ステータスIDの指定がない場合は指定しない
8880 if (this.inReplyTo == null)
8884 // 次の条件を満たす場合に in_reply_to_status_id 指定
8885 // 1. Twitterによりリンクと判定される @idが文中に1つ含まれる (2009/5/28 リンク化される@IDのみカウントするように修正)
8886 // 2. リプライ先ステータスIDが設定されている(リストをダブルクリックで返信している)
8887 // 3. 文中に含まれた@idがリプライ先のポスト者のIDと一致する
8891 var inReplyToScreenName = this.inReplyTo.Value.ScreenName;
8892 if (StatusText.StartsWith("@", StringComparison.Ordinal))
8894 if (StatusText.StartsWith("@" + inReplyToScreenName, StringComparison.Ordinal)) return;
8898 foreach (Match mid in m)
8900 if (StatusText.Contains("RT " + mid.Result("${id}") + ":") && mid.Result("${id}") == "@" + inReplyToScreenName) return;
8905 this.inReplyTo = null;
8908 private void TweenMain_Resize(object sender, EventArgs e)
8910 if (!_initialLayout && SettingManager.Common.MinimizeToTray && WindowState == FormWindowState.Minimized)
8912 this.Visible = false;
8914 if (_initialLayout && SettingManager.Local != null && this.WindowState == FormWindowState.Normal && this.Visible)
8916 // 現在の DPI と設定保存時の DPI との比を取得する
8917 var configScaleFactor = SettingManager.Local.GetConfigScaleFactor(this.CurrentAutoScaleDimensions);
8919 this.ClientSize = ScaleBy(configScaleFactor, SettingManager.Local.FormSize);
8922 var splitterDistance = ScaleBy(configScaleFactor.Height, SettingManager.Local.SplitterDistance);
8923 if (splitterDistance > this.SplitContainer1.Panel1MinSize &&
8924 splitterDistance < this.SplitContainer1.Height - this.SplitContainer1.Panel2MinSize - this.SplitContainer1.SplitterWidth)
8926 this.SplitContainer1.SplitterDistance = splitterDistance;
8930 StatusText.Multiline = SettingManager.Local.StatusMultiline;
8931 if (StatusText.Multiline)
8933 var statusTextHeight = ScaleBy(configScaleFactor.Height, SettingManager.Local.StatusTextHeight);
8934 int dis = SplitContainer2.Height - statusTextHeight - SplitContainer2.SplitterWidth;
8935 if (dis > SplitContainer2.Panel1MinSize && dis < SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth)
8937 SplitContainer2.SplitterDistance = SplitContainer2.Height - statusTextHeight - SplitContainer2.SplitterWidth;
8939 StatusText.Height = statusTextHeight;
8943 if (SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth > 0)
8945 SplitContainer2.SplitterDistance = SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth;
8949 var previewDistance = ScaleBy(configScaleFactor.Width, SettingManager.Local.PreviewDistance);
8950 if (previewDistance > this.SplitContainer3.Panel1MinSize && previewDistance < this.SplitContainer3.Width - this.SplitContainer3.Panel2MinSize - this.SplitContainer3.SplitterWidth)
8952 this.SplitContainer3.SplitterDistance = previewDistance;
8955 // Panel2Collapsed は SplitterDistance の設定を終えるまで true にしない
8956 this.SplitContainer3.Panel2Collapsed = true;
8958 _initialLayout = false;
8960 if (this.WindowState != FormWindowState.Minimized)
8962 _formWindowState = this.WindowState;
8966 private void PlaySoundMenuItem_CheckedChanged(object sender, EventArgs e)
8968 PlaySoundMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
8969 this.PlaySoundFileMenuItem.Checked = PlaySoundMenuItem.Checked;
8970 if (PlaySoundMenuItem.Checked)
8972 SettingManager.Common.PlaySound = true;
8976 SettingManager.Common.PlaySound = false;
8978 ModifySettingCommon = true;
8981 private void SplitContainer1_SplitterMoved(object sender, SplitterEventArgs e)
8983 if (this._initialLayout)
8986 int splitterDistance;
8987 switch (this.WindowState)
8989 case FormWindowState.Normal:
8990 splitterDistance = this.SplitContainer1.SplitterDistance;
8992 case FormWindowState.Maximized:
8993 // 最大化時は、通常時のウィンドウサイズに換算した SplitterDistance を算出する
8994 var normalContainerHeight = this._mySize.Height - this.ToolStripContainer1.TopToolStripPanel.Height - this.ToolStripContainer1.BottomToolStripPanel.Height;
8995 splitterDistance = this.SplitContainer1.SplitterDistance - (this.SplitContainer1.Height - normalContainerHeight);
8996 splitterDistance = Math.Min(splitterDistance, normalContainerHeight - this.SplitContainer1.SplitterWidth - this.SplitContainer1.Panel2MinSize);
9002 this._mySpDis = splitterDistance;
9003 this.ModifySettingLocal = true;
9006 private async Task doRepliedStatusOpen()
9008 if (this.ExistCurrentPost && _curPost.InReplyToUser != null && _curPost.InReplyToStatusId != null)
9010 if (MyCommon.IsKeyDown(Keys.Shift))
9012 await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(_curPost.InReplyToUser, _curPost.InReplyToStatusId.Value));
9015 if (_statuses.ContainsKey(_curPost.InReplyToStatusId.Value))
9017 PostClass repPost = _statuses[_curPost.InReplyToStatusId.Value];
9018 MessageBox.Show($"{repPost.ScreenName} / {repPost.Nickname} ({repPost.CreatedAt.ToLocalTimeString()})" + Environment.NewLine + repPost.TextFromApi);
9022 foreach (TabModel tb in _statuses.GetTabsByType(MyCommon.TabUsageType.Lists | MyCommon.TabUsageType.PublicSearch))
9024 if (tb == null || !tb.Contains(_curPost.InReplyToStatusId.Value)) break;
9025 PostClass repPost = _statuses[_curPost.InReplyToStatusId.Value];
9026 MessageBox.Show($"{repPost.ScreenName} / {repPost.Nickname} ({repPost.CreatedAt.ToLocalTimeString()})" + Environment.NewLine + repPost.TextFromApi);
9029 await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(_curPost.InReplyToUser, _curPost.InReplyToStatusId.Value));
9034 private async void RepliedStatusOpenMenuItem_Click(object sender, EventArgs e)
9035 => await this.doRepliedStatusOpen();
9037 private void SplitContainer2_Panel2_Resize(object sender, EventArgs e)
9039 if (this._initialLayout)
9040 return; // SettingLocal の反映が完了するまで multiline の判定を行わない
9042 var multiline = this.SplitContainer2.Panel2.Height > this.SplitContainer2.Panel2MinSize + 2;
9043 if (multiline != this.StatusText.Multiline)
9045 this.StatusText.Multiline = multiline;
9046 SettingManager.Local.StatusMultiline = multiline;
9047 ModifySettingLocal = true;
9051 private void StatusText_MultilineChanged(object sender, EventArgs e)
9053 if (this.StatusText.Multiline)
9054 this.StatusText.ScrollBars = ScrollBars.Vertical;
9056 this.StatusText.ScrollBars = ScrollBars.None;
9058 ModifySettingLocal = true;
9061 private void MultiLineMenuItem_Click(object sender, EventArgs e)
9064 var menuItemChecked = ((ToolStripMenuItem)sender).Checked;
9065 StatusText.Multiline = menuItemChecked;
9066 SettingManager.Local.StatusMultiline = menuItemChecked;
9067 if (menuItemChecked)
9069 if (SplitContainer2.Height - _mySpDis2 - SplitContainer2.SplitterWidth < 0)
9070 SplitContainer2.SplitterDistance = 0;
9072 SplitContainer2.SplitterDistance = SplitContainer2.Height - _mySpDis2 - SplitContainer2.SplitterWidth;
9076 SplitContainer2.SplitterDistance = SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth;
9078 ModifySettingLocal = true;
9081 private async Task<bool> UrlConvertAsync(MyCommon.UrlConverter Converter_Type)
9083 if (Converter_Type == MyCommon.UrlConverter.Bitly || Converter_Type == MyCommon.UrlConverter.Jmp)
9085 // OAuth2 アクセストークンまたは API キー (旧方式) のいずれも設定されていなければ短縮しない
9086 if (string.IsNullOrEmpty(SettingManager.Common.BitlyAccessToken) &&
9087 (string.IsNullOrEmpty(SettingManager.Common.BilyUser) || string.IsNullOrEmpty(SettingManager.Common.BitlyPwd)))
9089 MessageBox.Show(this, Properties.Resources.UrlConvert_BitlyAuthRequired, ApplicationSettings.ApplicationName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
9094 //t.coで投稿時自動短縮する場合は、外部サービスでの短縮禁止
9095 //if (SettingDialog.UrlConvertAuto && SettingDialog.ShortenTco) return;
9097 //Converter_Type=Nicomsの場合は、nicovideoのみ短縮する
9098 //参考資料 RFC3986 Uniform Resource Identifier (URI): Generic Syntax
9099 //Appendix A. Collected ABNF for URI
9100 //http://www.ietf.org/rfc/rfc3986.txt
9104 const string nico = @"^https?://[a-z]+\.(nicovideo|niconicommons|nicolive)\.jp/[a-z]+/[a-z0-9]+$";
9106 if (StatusText.SelectionLength > 0)
9108 string tmp = StatusText.SelectedText;
9109 // httpから始まらない場合、ExcludeStringで指定された文字列で始まる場合は対象としない
9110 if (tmp.StartsWith("http", StringComparison.OrdinalIgnoreCase))
9112 // 文字列が選択されている場合はその文字列について処理
9114 //nico.ms使用、nicovideoにマッチしたら変換
9115 if (SettingManager.Common.Nicoms && Regex.IsMatch(tmp, nico))
9117 result = nicoms.Shorten(tmp);
9119 else if (Converter_Type != MyCommon.UrlConverter.Nicoms)
9121 //短縮URL変換 日本語を含むかもしれないのでURLエンコードする
9124 var srcUri = new Uri(MyCommon.urlEncodeMultibyteChar(tmp));
9125 var resultUri = await ShortUrl.Instance.ShortenUrlAsync(Converter_Type, srcUri);
9126 result = resultUri.AbsoluteUri;
9128 catch (WebApiException e)
9130 this.StatusLabel.Text = Converter_Type + ":" + e.Message;
9133 catch (UriFormatException e)
9135 this.StatusLabel.Text = Converter_Type + ":" + e.Message;
9144 if (!string.IsNullOrEmpty(result))
9146 urlUndo undotmp = new urlUndo();
9148 // 短縮 URL が生成されるまでの間に投稿欄から元の URL が削除されていたら中断する
9149 var origUrlIndex = this.StatusText.Text.IndexOf(tmp, StringComparison.Ordinal);
9150 if (origUrlIndex == -1)
9153 StatusText.Select(origUrlIndex, tmp.Length);
9154 StatusText.SelectedText = result;
9157 undotmp.Before = tmp;
9158 undotmp.After = result;
9160 if (urlUndoBuffer == null)
9162 urlUndoBuffer = new List<urlUndo>();
9163 UrlUndoToolStripMenuItem.Enabled = true;
9166 urlUndoBuffer.Add(undotmp);
9172 const string url = @"(?<before>(?:[^\""':!=]|^|\:))" +
9173 @"(?<url>(?<protocol>https?://)" +
9174 @"(?<domain>(?:[\.-]|[^\p{P}\s])+\.[a-z]{2,}(?::[0-9]+)?)" +
9175 @"(?<path>/[a-z0-9!*//();:&=+$/%#\-_.,~@]*[a-z0-9)=#/]?)?" +
9176 @"(?<query>\?[a-z0-9!*//();:&=+$/%#\-_.,~@?]*[a-z0-9_&=#/])?)";
9177 // 正規表現にマッチしたURL文字列をtinyurl化
9178 foreach (Match mt in Regex.Matches(StatusText.Text, url, RegexOptions.IgnoreCase))
9180 if (StatusText.Text.IndexOf(mt.Result("${url}"), StringComparison.Ordinal) == -1) continue;
9181 string tmp = mt.Result("${url}");
9182 if (tmp.StartsWith("w", StringComparison.OrdinalIgnoreCase)) tmp = "http://" + tmp;
9183 urlUndo undotmp = new urlUndo();
9186 StatusText.Select(StatusText.Text.IndexOf(mt.Result("${url}"), StringComparison.Ordinal), mt.Result("${url}").Length);
9188 //nico.ms使用、nicovideoにマッチしたら変換
9189 if (SettingManager.Common.Nicoms && Regex.IsMatch(tmp, nico))
9191 result = nicoms.Shorten(tmp);
9193 else if (Converter_Type != MyCommon.UrlConverter.Nicoms)
9195 //短縮URL変換 日本語を含むかもしれないのでURLエンコードする
9198 var srcUri = new Uri(MyCommon.urlEncodeMultibyteChar(tmp));
9199 var resultUri = await ShortUrl.Instance.ShortenUrlAsync(Converter_Type, srcUri);
9200 result = resultUri.AbsoluteUri;
9202 catch (HttpRequestException e)
9204 // 例外のメッセージが「Response status code does not indicate success: 500 (Internal Server Error).」
9205 // のように長いので「:」が含まれていればそれ以降のみを抽出する
9206 var message = e.Message.Split(new[] { ':' }, count: 2).Last();
9208 this.StatusLabel.Text = Converter_Type + ":" + message;
9211 catch (WebApiException e)
9213 this.StatusLabel.Text = Converter_Type + ":" + e.Message;
9216 catch (UriFormatException e)
9218 this.StatusLabel.Text = Converter_Type + ":" + e.Message;
9227 if (!string.IsNullOrEmpty(result))
9229 // 短縮 URL が生成されるまでの間に投稿欄から元の URL が削除されていたら中断する
9230 var origUrlIndex = this.StatusText.Text.IndexOf(mt.Result("${url}"), StringComparison.Ordinal);
9231 if (origUrlIndex == -1)
9234 StatusText.Select(origUrlIndex, mt.Result("${url}").Length);
9235 StatusText.SelectedText = result;
9237 undotmp.Before = mt.Result("${url}");
9238 undotmp.After = result;
9240 if (urlUndoBuffer == null)
9242 urlUndoBuffer = new List<urlUndo>();
9243 UrlUndoToolStripMenuItem.Enabled = true;
9246 urlUndoBuffer.Add(undotmp);
9254 private void doUrlUndo()
9256 if (urlUndoBuffer != null)
9258 string tmp = StatusText.Text;
9259 foreach (urlUndo data in urlUndoBuffer)
9261 tmp = tmp.Replace(data.After, data.Before);
9263 StatusText.Text = tmp;
9264 urlUndoBuffer = null;
9265 UrlUndoToolStripMenuItem.Enabled = false;
9266 StatusText.SelectionStart = 0;
9267 StatusText.SelectionLength = 0;
9271 private async void TinyURLToolStripMenuItem_Click(object sender, EventArgs e)
9272 => await this.UrlConvertAsync(MyCommon.UrlConverter.TinyUrl);
9274 private async void IsgdToolStripMenuItem_Click(object sender, EventArgs e)
9275 => await this.UrlConvertAsync(MyCommon.UrlConverter.Isgd);
9277 private async void UxnuMenuItem_Click(object sender, EventArgs e)
9278 => await this.UrlConvertAsync(MyCommon.UrlConverter.Uxnu);
9280 private async void UrlConvertAutoToolStripMenuItem_Click(object sender, EventArgs e)
9282 if (!await UrlConvertAsync(SettingManager.Common.AutoShortUrlFirst))
9284 MyCommon.UrlConverter svc = SettingManager.Common.AutoShortUrlFirst;
9285 Random rnd = new Random();
9286 // 前回使用した短縮URLサービス以外を選択する
9289 svc = (MyCommon.UrlConverter)rnd.Next(System.Enum.GetNames(typeof(MyCommon.UrlConverter)).Length);
9291 while (svc == SettingManager.Common.AutoShortUrlFirst || svc == MyCommon.UrlConverter.Nicoms || svc == MyCommon.UrlConverter.Unu);
9292 await UrlConvertAsync(svc);
9296 private void UrlUndoToolStripMenuItem_Click(object sender, EventArgs e)
9297 => this.doUrlUndo();
9299 private void NewPostPopMenuItem_CheckStateChanged(object sender, EventArgs e)
9301 this.NotifyFileMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
9302 this.NewPostPopMenuItem.Checked = this.NotifyFileMenuItem.Checked;
9303 SettingManager.Common.NewAllPop = NewPostPopMenuItem.Checked;
9304 ModifySettingCommon = true;
9307 private void ListLockMenuItem_CheckStateChanged(object sender, EventArgs e)
9309 ListLockMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
9310 this.LockListFileMenuItem.Checked = ListLockMenuItem.Checked;
9311 SettingManager.Common.ListLock = ListLockMenuItem.Checked;
9312 ModifySettingCommon = true;
9315 private void MenuStrip1_MenuActivate(object sender, EventArgs e)
9317 // フォーカスがメニューに移る (MenuStrip1.Tag フラグを立てる)
9318 MenuStrip1.Tag = new Object();
9319 MenuStrip1.Select(); // StatusText がフォーカスを持っている場合 Leave が発生
9322 private void MenuStrip1_MenuDeactivate(object sender, EventArgs e)
9324 if (this.Tag != null) // 設定された戻り先へ遷移
9326 if (this.Tag == this.ListTab.SelectedTab)
9327 ((Control)this.ListTab.SelectedTab.Tag).Select();
9329 ((Control)this.Tag).Select();
9331 else // 戻り先が指定されていない (初期状態) 場合はタブに遷移
9333 if (ListTab.SelectedIndex > -1 && ListTab.SelectedTab.HasChildren)
9335 this.Tag = ListTab.SelectedTab.Tag;
9336 ((Control)this.Tag).Select();
9339 // フォーカスがメニューに遷移したかどうかを表すフラグを降ろす
9340 MenuStrip1.Tag = null;
9343 private void MyList_ColumnReordered(object sender, ColumnReorderedEventArgs e)
9345 DetailsListView lst = (DetailsListView)sender;
9346 if (SettingManager.Local == null) return;
9350 SettingManager.Local.Width1 = lst.Columns[0].Width;
9351 SettingManager.Local.Width3 = lst.Columns[1].Width;
9355 int[] darr = new int[lst.Columns.Count];
9356 for (int i = 0; i < lst.Columns.Count; i++)
9358 darr[lst.Columns[i].DisplayIndex] = i;
9360 MyCommon.MoveArrayItem(darr, e.OldDisplayIndex, e.NewDisplayIndex);
9362 for (int i = 0; i < lst.Columns.Count; i++)
9367 SettingManager.Local.DisplayIndex1 = i;
9370 SettingManager.Local.DisplayIndex2 = i;
9373 SettingManager.Local.DisplayIndex3 = i;
9376 SettingManager.Local.DisplayIndex4 = i;
9379 SettingManager.Local.DisplayIndex5 = i;
9382 SettingManager.Local.DisplayIndex6 = i;
9385 SettingManager.Local.DisplayIndex7 = i;
9388 SettingManager.Local.DisplayIndex8 = i;
9392 SettingManager.Local.Width1 = lst.Columns[0].Width;
9393 SettingManager.Local.Width2 = lst.Columns[1].Width;
9394 SettingManager.Local.Width3 = lst.Columns[2].Width;
9395 SettingManager.Local.Width4 = lst.Columns[3].Width;
9396 SettingManager.Local.Width5 = lst.Columns[4].Width;
9397 SettingManager.Local.Width6 = lst.Columns[5].Width;
9398 SettingManager.Local.Width7 = lst.Columns[6].Width;
9399 SettingManager.Local.Width8 = lst.Columns[7].Width;
9401 ModifySettingLocal = true;
9402 _isColumnChanged = true;
9405 private void MyList_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e)
9407 DetailsListView lst = (DetailsListView)sender;
9408 if (SettingManager.Local == null) return;
9411 if (SettingManager.Local.Width1 != lst.Columns[0].Width)
9413 SettingManager.Local.Width1 = lst.Columns[0].Width;
9414 ModifySettingLocal = true;
9415 _isColumnChanged = true;
9417 if (SettingManager.Local.Width3 != lst.Columns[1].Width)
9419 SettingManager.Local.Width3 = lst.Columns[1].Width;
9420 ModifySettingLocal = true;
9421 _isColumnChanged = true;
9426 if (SettingManager.Local.Width1 != lst.Columns[0].Width)
9428 SettingManager.Local.Width1 = lst.Columns[0].Width;
9429 ModifySettingLocal = true;
9430 _isColumnChanged = true;
9432 if (SettingManager.Local.Width2 != lst.Columns[1].Width)
9434 SettingManager.Local.Width2 = lst.Columns[1].Width;
9435 ModifySettingLocal = true;
9436 _isColumnChanged = true;
9438 if (SettingManager.Local.Width3 != lst.Columns[2].Width)
9440 SettingManager.Local.Width3 = lst.Columns[2].Width;
9441 ModifySettingLocal = true;
9442 _isColumnChanged = true;
9444 if (SettingManager.Local.Width4 != lst.Columns[3].Width)
9446 SettingManager.Local.Width4 = lst.Columns[3].Width;
9447 ModifySettingLocal = true;
9448 _isColumnChanged = true;
9450 if (SettingManager.Local.Width5 != lst.Columns[4].Width)
9452 SettingManager.Local.Width5 = lst.Columns[4].Width;
9453 ModifySettingLocal = true;
9454 _isColumnChanged = true;
9456 if (SettingManager.Local.Width6 != lst.Columns[5].Width)
9458 SettingManager.Local.Width6 = lst.Columns[5].Width;
9459 ModifySettingLocal = true;
9460 _isColumnChanged = true;
9462 if (SettingManager.Local.Width7 != lst.Columns[6].Width)
9464 SettingManager.Local.Width7 = lst.Columns[6].Width;
9465 ModifySettingLocal = true;
9466 _isColumnChanged = true;
9468 if (SettingManager.Local.Width8 != lst.Columns[7].Width)
9470 SettingManager.Local.Width8 = lst.Columns[7].Width;
9471 ModifySettingLocal = true;
9472 _isColumnChanged = true;
9475 // 非表示の時にColumnChangedが呼ばれた場合はForm初期化処理中なので保存しない
9478 // SaveConfigsLocal();
9482 private void SplitContainer2_SplitterMoved(object sender, SplitterEventArgs e)
9484 if (StatusText.Multiline) _mySpDis2 = StatusText.Height;
9485 ModifySettingLocal = true;
9488 private void TweenMain_DragDrop(object sender, DragEventArgs e)
9490 if (e.Data.GetDataPresent(DataFormats.FileDrop))
9492 if (!e.Data.GetDataPresent(DataFormats.Html, false)) // WebBrowserコントロールからの絵文字画像Drag&Dropは弾く
9494 SelectMedia_DragDrop(e);
9497 else if (e.Data.GetDataPresent("UniformResourceLocatorW"))
9499 var (url, title) = GetUrlFromDataObject(e.Data);
9505 appendText = title + " " + url;
9507 if (this.StatusText.TextLength == 0)
9508 this.StatusText.Text = appendText;
9510 this.StatusText.Text += " " + appendText;
9512 else if (e.Data.GetDataPresent(DataFormats.UnicodeText))
9514 var text = (string)e.Data.GetData(DataFormats.UnicodeText);
9516 this.StatusText.Text += text;
9518 else if (e.Data.GetDataPresent(DataFormats.StringFormat))
9520 string data = (string)e.Data.GetData(DataFormats.StringFormat, true);
9521 if (data != null) StatusText.Text += data;
9526 /// IDataObject から URL とタイトルの対を取得します
9529 /// タイトルのみ取得できなかった場合は Value2 が null のタプルを返すことがあります。
9531 /// <exception cref="ArgumentException">不正なフォーマットが入力された場合</exception>
9532 /// <exception cref="NotSupportedException">サポートされていないデータが入力された場合</exception>
9533 internal static (string Url, string Title) GetUrlFromDataObject(IDataObject data)
9535 if (data.GetDataPresent("text/x-moz-url"))
9537 // Firefox, Google Chrome で利用可能
9538 // 参照: https://developer.mozilla.org/ja/docs/DragDrop/Recommended_Drag_Types
9540 using (var stream = (MemoryStream)data.GetData("text/x-moz-url"))
9542 var lines = Encoding.Unicode.GetString(stream.ToArray()).TrimEnd('\0').Split('\n');
9543 if (lines.Length < 2)
9544 throw new ArgumentException("不正な text/x-moz-url フォーマットです", nameof(data));
9546 return (lines[0], lines[1]);
9549 else if (data.GetDataPresent("IESiteModeToUrl"))
9551 // Internet Exproler 用
9552 // 保護モードが有効なデフォルトの IE では DragDrop イベントが発火しないため使えない
9554 using (var stream = (MemoryStream)data.GetData("IESiteModeToUrl"))
9556 var lines = Encoding.Unicode.GetString(stream.ToArray()).TrimEnd('\0').Split('\0');
9557 if (lines.Length < 2)
9558 throw new ArgumentException("不正な IESiteModeToUrl フォーマットです", nameof(data));
9560 return (lines[0], lines[1]);
9563 else if (data.GetDataPresent("UniformResourceLocatorW"))
9567 using (var stream = (MemoryStream)data.GetData("UniformResourceLocatorW"))
9569 var url = Encoding.Unicode.GetString(stream.ToArray()).TrimEnd('\0');
9574 throw new NotSupportedException("サポートされていないデータ形式です: " + data.GetFormats()[0]);
9577 private void TweenMain_DragEnter(object sender, DragEventArgs e)
9579 if (e.Data.GetDataPresent(DataFormats.FileDrop))
9581 if (!e.Data.GetDataPresent(DataFormats.Html, false)) // WebBrowserコントロールからの絵文字画像Drag&Dropは弾く
9583 SelectMedia_DragEnter(e);
9587 else if (e.Data.GetDataPresent("UniformResourceLocatorW"))
9589 e.Effect = DragDropEffects.Copy;
9592 else if (e.Data.GetDataPresent(DataFormats.UnicodeText))
9594 e.Effect = DragDropEffects.Copy;
9597 else if (e.Data.GetDataPresent(DataFormats.StringFormat))
9599 e.Effect = DragDropEffects.Copy;
9603 e.Effect = DragDropEffects.None;
9606 private void TweenMain_DragOver(object sender, DragEventArgs e)
9610 public bool IsNetworkAvailable()
9613 nw = MyCommon.IsNetworkAvailable();
9614 _myStatusOnline = nw;
9618 public async Task OpenUriAsync(Uri uri, bool isReverseSettings = false)
9620 var uriStr = uri.AbsoluteUri;
9622 // OpenTween 内部で使用する URL
9623 if (uri.Authority == "opentween")
9625 await this.OpenInternalUriAsync(uri);
9629 // ハッシュタグを含む Twitter 検索
9630 if (uri.Host == "twitter.com" && uri.AbsolutePath == "/search" && uri.Query.Contains("q=%23"))
9633 var unescapedQuery = Uri.UnescapeDataString(uri.Query);
9634 var pos = unescapedQuery.IndexOf('#');
9635 if (pos == -1) return;
9637 var hash = unescapedQuery.Substring(pos);
9638 this.HashSupl.AddItem(hash);
9639 this.HashMgr.AddHashToHistory(hash.Trim(), false);
9640 this.AddNewTabForSearch(hash);
9645 // フラグが立っている場合は設定と逆の動作をする
9646 if( SettingManager.Common.OpenUserTimeline && !isReverseSettings ||
9647 !SettingManager.Common.OpenUserTimeline && isReverseSettings )
9649 var userUriMatch = Regex.Match(uriStr, "^https?://twitter.com/(#!/)?(?<ScreenName>[a-zA-Z0-9_]+)$");
9650 if (userUriMatch.Success)
9652 var screenName = userUriMatch.Groups["ScreenName"].Value;
9653 if (this.IsTwitterId(screenName))
9655 await this.AddNewTabForUserTimeline(screenName);
9662 await this.OpenUriInBrowserAsync(uriStr);
9666 /// OpenTween 内部の機能を呼び出すための URL を開きます
9668 private async Task OpenInternalUriAsync(Uri uri)
9670 // ツイートを開く (//opentween/status/:status_id)
9671 var match = Regex.Match(uri.AbsolutePath, @"^/status/(\d+)$");
9674 var statusId = long.Parse(match.Groups[1].Value);
9675 await this.OpenRelatedTab(statusId);
9680 public Task OpenUriInBrowserAsync(string UriString)
9682 return Task.Run(() =>
9684 string myPath = UriString;
9688 var configBrowserPath = SettingManager.Local.BrowserPath;
9689 if (!string.IsNullOrEmpty(configBrowserPath))
9691 if (configBrowserPath.StartsWith("\"", StringComparison.Ordinal) && configBrowserPath.Length > 2 && configBrowserPath.IndexOf("\"", 2, StringComparison.Ordinal) > -1)
9693 int sep = configBrowserPath.IndexOf("\"", 2, StringComparison.Ordinal);
9694 string browserPath = configBrowserPath.Substring(1, sep - 1);
9696 if (sep < configBrowserPath.Length - 1)
9698 arg = configBrowserPath.Substring(sep + 1);
9700 myPath = arg + " " + myPath;
9701 System.Diagnostics.Process.Start(browserPath, myPath);
9705 System.Diagnostics.Process.Start(configBrowserPath, myPath);
9710 System.Diagnostics.Process.Start(myPath);
9715 //MessageBox.Show("ブラウザの起動に失敗、またはタイムアウトしました。" + ex.ToString());
9720 private void ListTabSelect(TabPage _tab)
9724 this.PurgeListViewItemCache();
9727 _curList = (DetailsListView)_tab.Tag;
9729 if (_curList.SelectedIndices.Count > 0)
9731 _curItemIndex = _curList.SelectedIndices[0];
9732 _curPost = GetCurTabPost(_curItemIndex);
9741 _anchorFlag = false;
9745 ((DetailsListView)_tab.Tag).Columns[1].Text = ColumnText[2];
9749 for (int i = 0; i < _curList.Columns.Count; i++)
9751 ((DetailsListView)_tab.Tag).Columns[i].Text = ColumnText[i];
9756 private void ListTab_Selecting(object sender, TabControlCancelEventArgs e)
9757 => this.ListTabSelect(e.TabPage);
9759 private void SelectListItem(DetailsListView LView, int Index)
9762 Rectangle bnd = new Rectangle();
9764 var item = LView.FocusedItem;
9773 LView.SelectedIndices.Clear();
9775 while (LView.SelectedIndices.Count > 0);
9776 item = LView.Items[Index];
9777 item.Selected = true;
9778 item.Focused = true;
9780 if (flg) LView.Invalidate(bnd);
9783 private void SelectListItem(DetailsListView LView , int[] Index, int focusedIndex, int selectionMarkIndex)
9786 Rectangle bnd = new Rectangle();
9788 var item = LView.FocusedItem;
9799 LView.SelectedIndices.Clear();
9801 while (LView.SelectedIndices.Count > 0);
9802 LView.SelectItems(Index);
9804 if (selectionMarkIndex > -1 && LView.VirtualListSize > selectionMarkIndex)
9806 LView.SelectionMark = selectionMarkIndex;
9808 if (focusedIndex > -1 && LView.VirtualListSize > focusedIndex)
9810 LView.Items[focusedIndex].Focused = true;
9812 else if (Index != null && Index.Length != 0)
9814 LView.Items[Index.Last()].Focused = true;
9817 if (flg) LView.Invalidate(bnd);
9820 private void StartUserStream()
9822 tw.NewPostFromStream += tw_NewPostFromStream;
9823 tw.UserStreamStarted += tw_UserStreamStarted;
9824 tw.UserStreamStopped += tw_UserStreamStopped;
9825 tw.PostDeleted += tw_PostDeleted;
9826 tw.UserStreamEventReceived += tw_UserStreamEventArrived;
9828 this.RefreshUserStreamsMenu();
9830 if (SettingManager.Common.UserstreamStartup)
9831 tw.StartUserStream();
9834 private async void TweenMain_Shown(object sender, EventArgs e)
9836 NotifyIcon1.Visible = true;
9838 if (this.IsNetworkAvailable())
9842 var loadTasks = new List<Task>
9844 this.RefreshMuteUserIdsAsync(),
9845 this.RefreshBlockIdsAsync(),
9846 this.RefreshNoRetweetIdsAsync(),
9847 this.RefreshTwitterConfigurationAsync(),
9848 this.RefreshTabAsync<HomeTabModel>(),
9849 this.RefreshTabAsync<MentionsTabModel>(),
9850 this.RefreshTabAsync<DirectMessagesTabModel>(),
9851 this.RefreshTabAsync<PublicSearchTabModel>(),
9852 this.RefreshTabAsync<UserTimelineTabModel>(),
9853 this.RefreshTabAsync<ListTimelineTabModel>(),
9856 if (SettingManager.Common.StartupFollowers)
9857 loadTasks.Add(this.RefreshFollowerIdsAsync());
9859 if (SettingManager.Common.GetFav)
9860 loadTasks.Add(this.RefreshTabAsync<FavoritesTabModel>());
9862 var allTasks = Task.WhenAll(loadTasks);
9867 var timeout = Task.Delay(5000);
9868 if (await Task.WhenAny(allTasks, timeout) != timeout)
9872 if (i > 24) break; // 120秒間初期処理が終了しなかったら強制的に打ち切る
9874 if (MyCommon._endingFlag)
9878 if (MyCommon._endingFlag) return;
9880 if (ApplicationSettings.VersionInfoUrl != null)
9882 //バージョンチェック(引数:起動時チェックの場合はtrue・・・チェック結果のメッセージを表示しない)
9883 if (SettingManager.Common.StartupVersion)
9884 await this.CheckNewVersion(true);
9888 // ApplicationSetting.cs の設定により更新チェックが無効化されている場合
9889 this.VerUpMenuItem.Enabled = false;
9890 this.VerUpMenuItem.Available = false;
9891 this.ToolStripSeparator16.Available = false; // VerUpMenuItem の一つ上にあるセパレータ
9894 // 権限チェック read/write権限(xAuthで取得したトークン)の場合は再認証を促す
9895 if (MyCommon.TwitterApiInfo.AccessLevel == TwitterApiAccessLevel.ReadWrite)
9897 MessageBox.Show(Properties.Resources.ReAuthorizeText);
9898 SettingStripMenuItem_Click(null, null);
9902 var reloadTasks = new List<Task>();
9904 if (!tw.GetFollowersSuccess && SettingManager.Common.StartupFollowers)
9905 reloadTasks.Add(this.RefreshFollowerIdsAsync());
9907 if (!tw.GetNoRetweetSuccess)
9908 reloadTasks.Add(this.RefreshNoRetweetIdsAsync());
9910 if (this.tw.Configuration.PhotoSizeLimit == 0)
9911 reloadTasks.Add(this.RefreshTwitterConfigurationAsync());
9913 await Task.WhenAll(reloadTasks);
9918 TimerTimeline.Enabled = true;
9921 private async Task doGetFollowersMenu()
9923 await this.RefreshFollowerIdsAsync();
9924 await this.DispSelectedPost(true);
9927 private async void GetFollowersAllToolStripMenuItem_Click(object sender, EventArgs e)
9928 => await this.doGetFollowersMenu();
9930 private void ReTweetUnofficialStripMenuItem_Click(object sender, EventArgs e)
9931 => this.doReTweetUnofficial();
9933 private async Task doReTweetOfficial(bool isConfirm)
9936 if (this.ExistCurrentPost)
9938 if (!_curPost.CanRetweetBy(this.twitterApi.CurrentUserId))
9940 if (this._curPost.IsProtect)
9941 MessageBox.Show("Protected.");
9943 _DoFavRetweetFlags = false;
9946 if (_curList.SelectedIndices.Count > 15)
9948 MessageBox.Show(Properties.Resources.RetweetLimitText);
9949 _DoFavRetweetFlags = false;
9952 else if (_curList.SelectedIndices.Count > 1)
9954 string QuestionText = Properties.Resources.RetweetQuestion2;
9955 if (_DoFavRetweetFlags) QuestionText = Properties.Resources.FavoriteRetweetQuestionText1;
9956 switch (MessageBox.Show(QuestionText, "Retweet", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question))
9958 case DialogResult.Cancel:
9959 case DialogResult.No:
9960 _DoFavRetweetFlags = false;
9966 if (!SettingManager.Common.RetweetNoConfirm)
9968 string Questiontext = Properties.Resources.RetweetQuestion1;
9969 if (_DoFavRetweetFlags) Questiontext = Properties.Resources.FavoritesRetweetQuestionText2;
9970 if (isConfirm && MessageBox.Show(Questiontext, "Retweet", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
9972 _DoFavRetweetFlags = false;
9978 var statusIds = new List<long>();
9979 foreach (int idx in _curList.SelectedIndices)
9981 PostClass post = GetCurTabPost(idx);
9982 if (post.CanRetweetBy(this.twitterApi.CurrentUserId))
9983 statusIds.Add(post.StatusId);
9986 await this.RetweetAsync(statusIds);
9990 private async void ReTweetStripMenuItem_Click(object sender, EventArgs e)
9991 => await this.doReTweetOfficial(true);
9993 private async Task FavoritesRetweetOfficial()
9995 if (!this.ExistCurrentPost) return;
9996 _DoFavRetweetFlags = true;
9997 var retweetTask = this.doReTweetOfficial(true);
9998 if (_DoFavRetweetFlags)
10000 _DoFavRetweetFlags = false;
10001 var favoriteTask = this.FavoriteChange(true, false);
10003 await Task.WhenAll(retweetTask, favoriteTask);
10011 private async Task FavoritesRetweetUnofficial()
10013 if (this.ExistCurrentPost && !_curPost.IsDm)
10015 _DoFavRetweetFlags = true;
10016 var favoriteTask = this.FavoriteChange(true);
10017 if (!_curPost.IsProtect && _DoFavRetweetFlags)
10019 _DoFavRetweetFlags = false;
10020 doReTweetUnofficial();
10023 await favoriteTask;
10028 /// TweetFormatterクラスによって整形された状態のHTMLを、非公式RT用に元のツイートに復元します
10030 /// <param name="statusHtml">TweetFormatterによって整形された状態のHTML</param>
10031 /// <param name="multiline">trueであればBRタグを改行に、falseであればスペースに変換します</param>
10032 /// <returns>復元されたツイート本文</returns>
10033 internal static string CreateRetweetUnofficial(string statusHtml, bool multiline)
10035 // TweetFormatterクラスによって整形された状態のHTMLを元のツイートに復元します
10038 statusHtml = Regex.Replace(statusHtml, "<a href=\"(?<href>.+?)\" title=\"(?<title>.+?)\">(?<text>.+?)</a>", "${title}");
10040 statusHtml = Regex.Replace(statusHtml, "<a class=\"mention\" href=\"(?<href>.+?)\">(?<text>.+?)</a>", "${text}");
10042 statusHtml = Regex.Replace(statusHtml, "<a class=\"hashtag\" href=\"(?<href>.+?)\">(?<text>.+?)</a>", "${text}");
10044 statusHtml = Regex.Replace(statusHtml, "<img class=\"emoji\" src=\".+?\" alt=\"(?<text>.+?)\" />", "${text}");
10048 statusHtml = statusHtml.Replace("<br>", Environment.NewLine);
10050 statusHtml = statusHtml.Replace("<br>", " ");
10052 // は本来であれば U+00A0 (NON-BREAK SPACE) に置換すべきですが、
10053 // 現状では半角スペースの代用として を使用しているため U+0020 に置換します
10054 statusHtml = statusHtml.Replace(" ", " ");
10056 return WebUtility.HtmlDecode(statusHtml);
10059 private async void DumpPostClassToolStripMenuItem_Click(object sender, EventArgs e)
10061 this.tweetDetailsView.DumpPostClass = this.DumpPostClassToolStripMenuItem.Checked;
10063 if (_curPost != null)
10064 await this.DispSelectedPost(true);
10067 private void MenuItemHelp_DropDownOpening(object sender, EventArgs e)
10069 if (MyCommon.DebugBuild || MyCommon.IsKeyDown(Keys.CapsLock, Keys.Control, Keys.Shift))
10070 DebugModeToolStripMenuItem.Visible = true;
10072 DebugModeToolStripMenuItem.Visible = false;
10075 private void UrlMultibyteSplitMenuItem_CheckedChanged(object sender, EventArgs e)
10076 => this.urlMultibyteSplit = ((ToolStripMenuItem)sender).Checked;
10078 private void PreventSmsCommandMenuItem_CheckedChanged(object sender, EventArgs e)
10079 => this.preventSmsCommand = ((ToolStripMenuItem)sender).Checked;
10081 private void UrlAutoShortenMenuItem_CheckedChanged(object sender, EventArgs e)
10082 => SettingManager.Common.UrlConvertAuto = ((ToolStripMenuItem)sender).Checked;
10084 private void IdeographicSpaceToSpaceMenuItem_Click(object sender, EventArgs e)
10086 SettingManager.Common.WideSpaceConvert = ((ToolStripMenuItem)sender).Checked;
10087 ModifySettingCommon = true;
10090 private void FocusLockMenuItem_CheckedChanged(object sender, EventArgs e)
10092 SettingManager.Common.FocusLockToStatusText = ((ToolStripMenuItem)sender).Checked;
10093 ModifySettingCommon = true;
10096 private void PostModeMenuItem_DropDownOpening(object sender, EventArgs e)
10098 UrlMultibyteSplitMenuItem.Checked = this.urlMultibyteSplit;
10099 PreventSmsCommandMenuItem.Checked = this.preventSmsCommand;
10100 UrlAutoShortenMenuItem.Checked = SettingManager.Common.UrlConvertAuto;
10101 IdeographicSpaceToSpaceMenuItem.Checked = SettingManager.Common.WideSpaceConvert;
10102 MultiLineMenuItem.Checked = SettingManager.Local.StatusMultiline;
10103 FocusLockMenuItem.Checked = SettingManager.Common.FocusLockToStatusText;
10106 private void ContextMenuPostMode_Opening(object sender, CancelEventArgs e)
10108 UrlMultibyteSplitPullDownMenuItem.Checked = this.urlMultibyteSplit;
10109 PreventSmsCommandPullDownMenuItem.Checked = this.preventSmsCommand;
10110 UrlAutoShortenPullDownMenuItem.Checked = SettingManager.Common.UrlConvertAuto;
10111 IdeographicSpaceToSpacePullDownMenuItem.Checked = SettingManager.Common.WideSpaceConvert;
10112 MultiLinePullDownMenuItem.Checked = SettingManager.Local.StatusMultiline;
10113 FocusLockPullDownMenuItem.Checked = SettingManager.Common.FocusLockToStatusText;
10116 private void TraceOutToolStripMenuItem_Click(object sender, EventArgs e)
10118 if (TraceOutToolStripMenuItem.Checked)
10119 MyCommon.TraceFlag = true;
10121 MyCommon.TraceFlag = false;
10124 private void TweenMain_Deactivate(object sender, EventArgs e)
10125 => this.StatusText_Leave(StatusText, EventArgs.Empty); // 画面が非アクティブになったら、発言欄の背景色をデフォルトへ
10127 private void TabRenameMenuItem_Click(object sender, EventArgs e)
10129 if (string.IsNullOrEmpty(_rclickTabName)) return;
10131 TabRename(_rclickTabName, out var _);
10134 private async void BitlyToolStripMenuItem_Click(object sender, EventArgs e)
10135 => await this.UrlConvertAsync(MyCommon.UrlConverter.Bitly);
10137 private async void JmpToolStripMenuItem_Click(object sender, EventArgs e)
10138 => await this.UrlConvertAsync(MyCommon.UrlConverter.Jmp);
10140 private async void ApiUsageInfoMenuItem_Click(object sender, EventArgs e)
10142 TwitterApiStatus apiStatus;
10144 using (var dialog = new WaitingDialog(Properties.Resources.ApiInfo6))
10146 var cancellationToken = dialog.EnableCancellation();
10150 var task = this.tw.GetInfoApi();
10151 apiStatus = await dialog.WaitForAsync(this, task);
10153 catch (WebApiException)
10158 if (cancellationToken.IsCancellationRequested)
10161 if (apiStatus == null)
10163 MessageBox.Show(Properties.Resources.ApiInfo5, Properties.Resources.ApiInfo4, MessageBoxButtons.OK, MessageBoxIcon.Information);
10168 using (var apiDlg = new ApiInfoDialog())
10170 apiDlg.ShowDialog(this);
10174 private async void FollowCommandMenuItem_Click(object sender, EventArgs e)
10176 var id = _curPost?.ScreenName ?? "";
10178 await this.FollowCommand(id);
10181 internal async Task FollowCommand(string id)
10183 using (var inputName = new InputTabName())
10185 inputName.FormTitle = "Follow";
10186 inputName.FormDescription = Properties.Resources.FRMessage1;
10187 inputName.TabName = id;
10189 if (inputName.ShowDialog(this) != DialogResult.OK)
10191 if (string.IsNullOrWhiteSpace(inputName.TabName))
10194 id = inputName.TabName.Trim();
10197 using (var dialog = new WaitingDialog(Properties.Resources.FollowCommandText1))
10201 var task = this.twitterApi.FriendshipsCreate(id).IgnoreResponse();
10202 await dialog.WaitForAsync(this, task);
10204 catch (WebApiException ex)
10206 MessageBox.Show(Properties.Resources.FRMessage2 + ex.Message);
10211 MessageBox.Show(Properties.Resources.FRMessage3);
10214 private async void RemoveCommandMenuItem_Click(object sender, EventArgs e)
10216 var id = _curPost?.ScreenName ?? "";
10218 await this.RemoveCommand(id, false);
10221 internal async Task RemoveCommand(string id, bool skipInput)
10225 using (var inputName = new InputTabName())
10227 inputName.FormTitle = "Unfollow";
10228 inputName.FormDescription = Properties.Resources.FRMessage1;
10229 inputName.TabName = id;
10231 if (inputName.ShowDialog(this) != DialogResult.OK)
10233 if (string.IsNullOrWhiteSpace(inputName.TabName))
10236 id = inputName.TabName.Trim();
10240 using (var dialog = new WaitingDialog(Properties.Resources.RemoveCommandText1))
10244 var task = this.twitterApi.FriendshipsDestroy(id).IgnoreResponse();
10245 await dialog.WaitForAsync(this, task);
10247 catch (WebApiException ex)
10249 MessageBox.Show(Properties.Resources.FRMessage2 + ex.Message);
10254 MessageBox.Show(Properties.Resources.FRMessage3);
10257 private async void FriendshipMenuItem_Click(object sender, EventArgs e)
10259 var id = _curPost?.ScreenName ?? "";
10261 await this.ShowFriendship(id);
10264 internal async Task ShowFriendship(string id)
10266 using (var inputName = new InputTabName())
10268 inputName.FormTitle = "Show Friendships";
10269 inputName.FormDescription = Properties.Resources.FRMessage1;
10270 inputName.TabName = id;
10272 if (inputName.ShowDialog(this) != DialogResult.OK)
10274 if (string.IsNullOrWhiteSpace(inputName.TabName))
10277 id = inputName.TabName.Trim();
10280 bool isFollowing, isFollowed;
10282 using (var dialog = new WaitingDialog(Properties.Resources.ShowFriendshipText1))
10284 var cancellationToken = dialog.EnableCancellation();
10288 var task = this.twitterApi.FriendshipsShow(this.twitterApi.CurrentScreenName, id);
10289 var friendship = await dialog.WaitForAsync(this, task);
10291 isFollowing = friendship.Relationship.Source.Following;
10292 isFollowed = friendship.Relationship.Source.FollowedBy;
10294 catch (WebApiException ex)
10296 if (!cancellationToken.IsCancellationRequested)
10297 MessageBox.Show($"Err:{ex.Message}(FriendshipsShow)");
10301 if (cancellationToken.IsCancellationRequested)
10305 string result = "";
10308 result = Properties.Resources.GetFriendshipInfo1 + System.Environment.NewLine;
10312 result = Properties.Resources.GetFriendshipInfo2 + System.Environment.NewLine;
10316 result += Properties.Resources.GetFriendshipInfo3;
10320 result += Properties.Resources.GetFriendshipInfo4;
10322 result = id + Properties.Resources.GetFriendshipInfo5 + System.Environment.NewLine + result;
10323 MessageBox.Show(result);
10326 internal async Task ShowFriendship(string[] ids)
10328 foreach (string id in ids)
10330 bool isFollowing, isFollowed;
10332 using (var dialog = new WaitingDialog(Properties.Resources.ShowFriendshipText1))
10334 var cancellationToken = dialog.EnableCancellation();
10338 var task = this.twitterApi.FriendshipsShow(this.twitterApi.CurrentScreenName, id);
10339 var friendship = await dialog.WaitForAsync(this, task);
10341 isFollowing = friendship.Relationship.Source.Following;
10342 isFollowed = friendship.Relationship.Source.FollowedBy;
10344 catch (WebApiException ex)
10346 if (!cancellationToken.IsCancellationRequested)
10347 MessageBox.Show($"Err:{ex.Message}(FriendshipsShow)");
10351 if (cancellationToken.IsCancellationRequested)
10355 string result = "";
10361 ff += Properties.Resources.GetFriendshipInfo1;
10365 ff += Properties.Resources.GetFriendshipInfo2;
10368 ff += System.Environment.NewLine + " ";
10371 ff += Properties.Resources.GetFriendshipInfo3;
10375 ff += Properties.Resources.GetFriendshipInfo4;
10377 result += id + Properties.Resources.GetFriendshipInfo5 + System.Environment.NewLine + ff;
10380 if (MessageBox.Show(
10381 Properties.Resources.GetFriendshipInfo7 + System.Environment.NewLine + result, Properties.Resources.GetFriendshipInfo8,
10382 MessageBoxButtons.YesNo,
10383 MessageBoxIcon.Question,
10384 MessageBoxDefaultButton.Button2) == DialogResult.Yes)
10386 await this.RemoveCommand(id, true);
10391 MessageBox.Show(result);
10396 private async void OwnStatusMenuItem_Click(object sender, EventArgs e)
10397 => await this.doShowUserStatus(tw.Username, false);
10399 // TwitterIDでない固定文字列を調べる(文字列検証のみ 実際に取得はしない)
10400 // URLから切り出した文字列を渡す
10402 public bool IsTwitterId(string name)
10404 if (this.tw.Configuration.NonUsernamePaths == null || this.tw.Configuration.NonUsernamePaths.Length == 0)
10405 return !Regex.Match(name, @"^(about|jobs|tos|privacy|who_to_follow|download|messages)$", RegexOptions.IgnoreCase).Success;
10407 return !this.tw.Configuration.NonUsernamePaths.Contains(name, StringComparer.InvariantCultureIgnoreCase);
10410 private void doQuoteOfficial()
10412 if (this.ExistCurrentPost)
10414 if (_curPost.IsDm ||
10415 !StatusText.Enabled) return;
10417 if (_curPost.IsProtect)
10419 MessageBox.Show("Protected.");
10423 var selection = (this.StatusText.SelectionStart, this.StatusText.SelectionLength);
10425 this.inReplyTo = null;
10427 StatusText.Text += " " + MyCommon.GetStatusUrl(_curPost);
10429 (this.StatusText.SelectionStart, this.StatusText.SelectionLength) = selection;
10430 StatusText.Focus();
10434 private void doReTweetUnofficial()
10437 if (this.ExistCurrentPost)
10439 if (_curPost.IsDm || !StatusText.Enabled)
10442 if (_curPost.IsProtect)
10444 MessageBox.Show("Protected.");
10447 string rtdata = _curPost.Text;
10448 rtdata = CreateRetweetUnofficial(rtdata, this.StatusText.Multiline);
10450 var selection = (this.StatusText.SelectionStart, this.StatusText.SelectionLength);
10452 // 投稿時に in_reply_to_status_id を付加する
10453 var inReplyToStatusId = this._curPost.RetweetedId ?? this._curPost.StatusId;
10454 var inReplyToScreenName = this._curPost.ScreenName;
10455 this.inReplyTo = (inReplyToStatusId, inReplyToScreenName);
10457 StatusText.Text += " RT @" + _curPost.ScreenName + ": " + rtdata;
10459 (this.StatusText.SelectionStart, this.StatusText.SelectionLength) = selection;
10460 StatusText.Focus();
10464 private void QuoteStripMenuItem_Click(object sender, EventArgs e)
10465 => this.doQuoteOfficial();
10467 private async void SearchButton_Click(object sender, EventArgs e)
10470 Control pnl = ((Control)sender).Parent;
10471 if (pnl == null) return;
10472 string tbName = pnl.Parent.Text;
10473 var tb = (PublicSearchTabModel)_statuses.Tabs[tbName];
10474 ComboBox cmb = (ComboBox)pnl.Controls["comboSearch"];
10475 ComboBox cmbLang = (ComboBox)pnl.Controls["comboLang"];
10476 cmb.Text = cmb.Text.Trim();
10477 // 検索式演算子 OR についてのみ大文字しか認識しないので強制的に大文字とする
10478 bool Quote = false;
10479 StringBuilder buf = new StringBuilder();
10480 char[] c = cmb.Text.ToCharArray();
10481 for (int cnt = 0; cnt < cmb.Text.Length; cnt++)
10483 if (cnt > cmb.Text.Length - 4)
10485 buf.Append(cmb.Text.Substring(cnt));
10494 if (!Quote && cmb.Text.Substring(cnt, 4).Equals(" or ", StringComparison.OrdinalIgnoreCase))
10496 buf.Append(" OR ");
10501 buf.Append(c[cnt]);
10503 cmb.Text = buf.ToString();
10505 var listView = (DetailsListView)pnl.Parent.Tag;
10507 var queryChanged = tb.SearchWords != cmb.Text || tb.SearchLang != cmbLang.Text;
10509 tb.SearchWords = cmb.Text;
10510 tb.SearchLang = cmbLang.Text;
10511 if (string.IsNullOrEmpty(cmb.Text))
10519 int idx = cmb.Items.IndexOf(tb.SearchWords);
10520 if (idx > -1) cmb.Items.RemoveAt(idx);
10521 cmb.Items.Insert(0, tb.SearchWords);
10522 cmb.Text = tb.SearchWords;
10524 this.PurgeListViewItemCache();
10525 listView.VirtualListSize = 0;
10526 _statuses.ClearTabIds(tbName);
10527 SaveConfigsTabs(); //検索条件の保存
10531 await this.RefreshTabAsync(tb);
10534 private async void RefreshMoreStripMenuItem_Click(object sender, EventArgs e)
10535 => await this.DoRefreshMore(); // もっと前を取得
10538 /// 指定されたタブのListTabにおける位置を返します
10541 /// 非表示のタブについて -1 が返ることを常に考慮して下さい
10543 public int GetTabPageIndex(string tabName)
10546 foreach (var tabPage in this.ListTab.TabPages.Cast<TabPage>())
10548 if (tabPage.Text == tabName)
10557 private void UndoRemoveTabMenuItem_Click(object sender, EventArgs e)
10559 if (_statuses.RemovedTab.Count == 0)
10561 MessageBox.Show("There isn't removed tab.", "Undo", MessageBoxButtons.OK, MessageBoxIcon.Information);
10566 DetailsListView listView = null;
10568 TabModel tb = _statuses.RemovedTab.Pop();
10569 if (tb.TabType == MyCommon.TabUsageType.Related)
10571 var relatedTab = _statuses.GetTabByType(MyCommon.TabUsageType.Related);
10572 if (relatedTab != null)
10574 // 関連発言なら既存のタブを置き換える
10575 tb.TabName = relatedTab.TabName;
10576 this.ClearTab(tb.TabName, false);
10577 _statuses.Tabs[tb.TabName] = tb;
10579 for (int i = 0; i < ListTab.TabPages.Count; i++)
10581 var tabPage = ListTab.TabPages[i];
10582 if (tb.TabName == tabPage.Text)
10584 listView = (DetailsListView)tabPage.Tag;
10585 ListTab.SelectedIndex = i;
10592 const string TabName = "Related Tweets";
10593 string renamed = TabName;
10594 for (int i = 2; i <= 100; i++)
10596 if (!_statuses.ContainsTab(renamed)) break;
10597 renamed = TabName + i;
10599 tb.TabName = renamed;
10601 _statuses.AddTab(tb);
10602 AddNewTab(tb, startup: false);
10604 var tabPage = ListTab.TabPages[ListTab.TabPages.Count - 1];
10605 listView = (DetailsListView)tabPage.Tag;
10606 ListTab.SelectedIndex = ListTab.TabPages.Count - 1;
10611 string renamed = tb.TabName;
10612 for (int i = 1; i < int.MaxValue; i++)
10614 if (!_statuses.ContainsTab(renamed)) break;
10615 renamed = tb.TabName + "(" + i + ")";
10617 tb.TabName = renamed;
10619 _statuses.AddTab(tb);
10620 AddNewTab(tb, startup: false);
10622 var tabPage = ListTab.TabPages[ListTab.TabPages.Count - 1];
10623 listView = (DetailsListView)tabPage.Tag;
10624 ListTab.SelectedIndex = ListTab.TabPages.Count - 1;
10628 if (listView != null)
10630 using (ControlTransaction.Update(listView))
10632 listView.VirtualListSize = tb.AllCount;
10638 private async Task doMoveToRTHome()
10640 if (_curList.SelectedIndices.Count > 0)
10642 PostClass post = GetCurTabPost(_curList.SelectedIndices[0]);
10643 if (post.RetweetedId != null)
10645 await this.OpenUriInBrowserAsync("https://twitter.com/" + GetCurTabPost(_curList.SelectedIndices[0]).RetweetedBy);
10650 private async void MoveToRTHomeMenuItem_Click(object sender, EventArgs e)
10651 => await this.doMoveToRTHome();
10653 private void ListManageUserContextToolStripMenuItem_Click(object sender, EventArgs e)
10655 var screenName = this._curPost?.ScreenName;
10656 if (screenName != null)
10657 this.ListManageUserContext(screenName);
10660 public void ListManageUserContext(string screenName)
10662 using (var listSelectForm = new MyLists(screenName, this.twitterApi))
10664 listSelectForm.ShowDialog(this);
10668 private void SearchControls_Enter(object sender, EventArgs e)
10670 Control pnl = (Control)sender;
10671 foreach (Control ctl in pnl.Controls)
10673 ctl.TabStop = true;
10677 private void SearchControls_Leave(object sender, EventArgs e)
10679 Control pnl = (Control)sender;
10680 foreach (Control ctl in pnl.Controls)
10682 ctl.TabStop = false;
10686 private void PublicSearchQueryMenuItem_Click(object sender, EventArgs e)
10688 if (ListTab.SelectedTab != null)
10690 if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType != MyCommon.TabUsageType.PublicSearch) return;
10691 ListTab.SelectedTab.Controls["panelSearch"].Controls["comboSearch"].Focus();
10695 private void StatusLabel_DoubleClick(object sender, EventArgs e)
10696 => MessageBox.Show(StatusLabel.TextHistory, "Logs", MessageBoxButtons.OK, MessageBoxIcon.None);
10698 private void HashManageMenuItem_Click(object sender, EventArgs e)
10700 DialogResult rslt = DialogResult.Cancel;
10703 rslt = HashMgr.ShowDialog();
10709 this.TopMost = SettingManager.Common.AlwaysTop;
10710 if (rslt == DialogResult.Cancel) return;
10711 if (!string.IsNullOrEmpty(HashMgr.UseHash))
10713 HashStripSplitButton.Text = HashMgr.UseHash;
10714 HashTogglePullDownMenuItem.Checked = true;
10715 HashToggleMenuItem.Checked = true;
10719 HashStripSplitButton.Text = "#[-]";
10720 HashTogglePullDownMenuItem.Checked = false;
10721 HashToggleMenuItem.Checked = false;
10723 //if (HashMgr.IsInsert && HashMgr.UseHash != "")
10725 // int sidx = StatusText.SelectionStart;
10726 // string hash = HashMgr.UseHash + " ";
10729 // if (StatusText.Text.Substring(sidx - 1, 1) != " ")
10730 // hash = " " + hash;
10732 // StatusText.Text = StatusText.Text.Insert(sidx, hash);
10733 // sidx += hash.Length;
10734 // StatusText.SelectionStart = sidx;
10735 // StatusText.Focus();
10737 ModifySettingCommon = true;
10738 this.StatusText_TextChanged(null, null);
10741 private void HashToggleMenuItem_Click(object sender, EventArgs e)
10743 HashMgr.ToggleHash();
10744 if (!string.IsNullOrEmpty(HashMgr.UseHash))
10746 HashStripSplitButton.Text = HashMgr.UseHash;
10747 HashToggleMenuItem.Checked = true;
10748 HashTogglePullDownMenuItem.Checked = true;
10752 HashStripSplitButton.Text = "#[-]";
10753 HashToggleMenuItem.Checked = false;
10754 HashTogglePullDownMenuItem.Checked = false;
10756 ModifySettingCommon = true;
10757 this.StatusText_TextChanged(null, null);
10760 private void HashStripSplitButton_ButtonClick(object sender, EventArgs e)
10761 => this.HashToggleMenuItem_Click(null, null);
10763 public void SetPermanentHashtag(string hashtag)
10765 HashMgr.SetPermanentHash("#" + hashtag);
10766 HashStripSplitButton.Text = HashMgr.UseHash;
10767 HashTogglePullDownMenuItem.Checked = true;
10768 HashToggleMenuItem.Checked = true;
10770 ModifySettingCommon = true;
10773 private void MenuItemOperate_DropDownOpening(object sender, EventArgs e)
10775 if (ListTab.SelectedTab == null) return;
10776 if (_statuses == null || _statuses.Tabs == null || !_statuses.Tabs.ContainsKey(ListTab.SelectedTab.Text)) return;
10777 if (!this.ExistCurrentPost)
10779 this.ReplyOpMenuItem.Enabled = false;
10780 this.ReplyAllOpMenuItem.Enabled = false;
10781 this.DmOpMenuItem.Enabled = false;
10782 this.ShowProfMenuItem.Enabled = false;
10783 this.ShowUserTimelineToolStripMenuItem.Enabled = false;
10784 this.ListManageMenuItem.Enabled = false;
10785 this.OpenFavOpMenuItem.Enabled = false;
10786 this.CreateTabRuleOpMenuItem.Enabled = false;
10787 this.CreateIdRuleOpMenuItem.Enabled = false;
10788 this.CreateSourceRuleOpMenuItem.Enabled = false;
10789 this.ReadOpMenuItem.Enabled = false;
10790 this.UnreadOpMenuItem.Enabled = false;
10794 this.ReplyOpMenuItem.Enabled = true;
10795 this.ReplyAllOpMenuItem.Enabled = true;
10796 this.DmOpMenuItem.Enabled = true;
10797 this.ShowProfMenuItem.Enabled = true;
10798 this.ShowUserTimelineToolStripMenuItem.Enabled = true;
10799 this.ListManageMenuItem.Enabled = true;
10800 this.OpenFavOpMenuItem.Enabled = true;
10801 this.CreateTabRuleOpMenuItem.Enabled = true;
10802 this.CreateIdRuleOpMenuItem.Enabled = true;
10803 this.CreateSourceRuleOpMenuItem.Enabled = true;
10804 this.ReadOpMenuItem.Enabled = true;
10805 this.UnreadOpMenuItem.Enabled = true;
10808 if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType == MyCommon.TabUsageType.DirectMessage || !this.ExistCurrentPost || _curPost.IsDm)
10810 this.FavOpMenuItem.Enabled = false;
10811 this.UnFavOpMenuItem.Enabled = false;
10812 this.OpenStatusOpMenuItem.Enabled = false;
10813 this.ShowRelatedStatusesMenuItem2.Enabled = false;
10814 this.RtOpMenuItem.Enabled = false;
10815 this.RtUnOpMenuItem.Enabled = false;
10816 this.QtOpMenuItem.Enabled = false;
10817 this.FavoriteRetweetMenuItem.Enabled = false;
10818 this.FavoriteRetweetUnofficialMenuItem.Enabled = false;
10822 this.FavOpMenuItem.Enabled = true;
10823 this.UnFavOpMenuItem.Enabled = true;
10824 this.OpenStatusOpMenuItem.Enabled = true;
10825 this.ShowRelatedStatusesMenuItem2.Enabled = true; //PublicSearchの時問題出るかも
10827 if (!_curPost.CanRetweetBy(this.twitterApi.CurrentUserId))
10829 this.RtOpMenuItem.Enabled = false;
10830 this.RtUnOpMenuItem.Enabled = false;
10831 this.QtOpMenuItem.Enabled = false;
10832 this.FavoriteRetweetMenuItem.Enabled = false;
10833 this.FavoriteRetweetUnofficialMenuItem.Enabled = false;
10837 this.RtOpMenuItem.Enabled = true;
10838 this.RtUnOpMenuItem.Enabled = true;
10839 this.QtOpMenuItem.Enabled = true;
10840 this.FavoriteRetweetMenuItem.Enabled = true;
10841 this.FavoriteRetweetUnofficialMenuItem.Enabled = true;
10845 if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType != MyCommon.TabUsageType.Favorites)
10847 this.RefreshPrevOpMenuItem.Enabled = true;
10851 this.RefreshPrevOpMenuItem.Enabled = false;
10853 if (!this.ExistCurrentPost
10854 || _curPost.InReplyToStatusId == null)
10856 OpenRepSourceOpMenuItem.Enabled = false;
10860 OpenRepSourceOpMenuItem.Enabled = true;
10862 if (!this.ExistCurrentPost || string.IsNullOrEmpty(_curPost.RetweetedBy))
10864 OpenRterHomeMenuItem.Enabled = false;
10868 OpenRterHomeMenuItem.Enabled = true;
10871 if (this.ExistCurrentPost)
10873 this.DelOpMenuItem.Enabled = this._curPost.CanDeleteBy(this.tw.UserId);
10877 private void MenuItemTab_DropDownOpening(object sender, EventArgs e)
10878 => this.ContextMenuTabProperty_Opening(sender, null);
10880 public Twitter TwitterInstance
10883 private void SplitContainer3_SplitterMoved(object sender, SplitterEventArgs e)
10885 if (this._initialLayout)
10888 int splitterDistance;
10889 switch (this.WindowState)
10891 case FormWindowState.Normal:
10892 splitterDistance = this.SplitContainer3.SplitterDistance;
10894 case FormWindowState.Maximized:
10895 // 最大化時は、通常時のウィンドウサイズに換算した SplitterDistance を算出する
10896 var normalContainerWidth = this._mySize.Width - SystemInformation.Border3DSize.Width * 2;
10897 splitterDistance = this.SplitContainer3.SplitterDistance - (this.SplitContainer3.Width - normalContainerWidth);
10898 splitterDistance = Math.Min(splitterDistance, normalContainerWidth - this.SplitContainer3.SplitterWidth - this.SplitContainer3.Panel2MinSize);
10904 this._mySpDis3 = splitterDistance;
10905 this.ModifySettingLocal = true;
10908 private void MenuItemEdit_DropDownOpening(object sender, EventArgs e)
10910 if (_statuses.RemovedTab.Count == 0)
10912 UndoRemoveTabMenuItem.Enabled = false;
10916 UndoRemoveTabMenuItem.Enabled = true;
10918 if (ListTab.SelectedTab != null)
10920 if (_statuses.Tabs[ListTab.SelectedTab.Text].TabType == MyCommon.TabUsageType.PublicSearch)
10921 PublicSearchQueryMenuItem.Enabled = true;
10923 PublicSearchQueryMenuItem.Enabled = false;
10927 PublicSearchQueryMenuItem.Enabled = false;
10929 if (!this.ExistCurrentPost)
10931 this.CopySTOTMenuItem.Enabled = false;
10932 this.CopyURLMenuItem.Enabled = false;
10933 this.CopyUserIdStripMenuItem.Enabled = false;
10937 this.CopySTOTMenuItem.Enabled = true;
10938 this.CopyURLMenuItem.Enabled = true;
10939 this.CopyUserIdStripMenuItem.Enabled = true;
10940 if (_curPost.IsDm) this.CopyURLMenuItem.Enabled = false;
10941 if (_curPost.IsProtect) this.CopySTOTMenuItem.Enabled = false;
10945 private void NotifyIcon1_MouseMove(object sender, MouseEventArgs e)
10946 => this.SetNotifyIconText();
10948 private async void UserStatusToolStripMenuItem_Click(object sender, EventArgs e)
10949 => await this.ShowUserStatus(this._curPost?.ScreenName ?? "");
10951 private async Task doShowUserStatus(string id, bool ShowInputDialog)
10953 TwitterUser user = null;
10955 if (ShowInputDialog)
10957 using (var inputName = new InputTabName())
10959 inputName.FormTitle = "Show UserStatus";
10960 inputName.FormDescription = Properties.Resources.FRMessage1;
10961 inputName.TabName = id;
10963 if (inputName.ShowDialog(this) != DialogResult.OK)
10965 if (string.IsNullOrWhiteSpace(inputName.TabName))
10968 id = inputName.TabName.Trim();
10972 using (var dialog = new WaitingDialog(Properties.Resources.doShowUserStatusText1))
10974 var cancellationToken = dialog.EnableCancellation();
10978 var task = this.twitterApi.UsersShow(id);
10979 user = await dialog.WaitForAsync(this, task);
10981 catch (WebApiException ex)
10983 if (!cancellationToken.IsCancellationRequested)
10984 MessageBox.Show($"Err:{ex.Message}(UsersShow)");
10988 if (cancellationToken.IsCancellationRequested)
10992 await this.doShowUserStatus(user);
10995 private async Task doShowUserStatus(TwitterUser user)
10997 using (var userDialog = new UserInfoDialog(this, this.twitterApi))
10999 var showUserTask = userDialog.ShowUserAsync(user);
11000 userDialog.ShowDialog(this);
11003 this.BringToFront();
11005 // ユーザー情報の表示が完了するまで userDialog を破棄しない
11006 await showUserTask;
11010 internal Task ShowUserStatus(string id, bool ShowInputDialog)
11011 => this.doShowUserStatus(id, ShowInputDialog);
11013 internal Task ShowUserStatus(string id)
11014 => this.doShowUserStatus(id, true);
11016 private async void ShowProfileMenuItem_Click(object sender, EventArgs e)
11018 if (_curPost != null)
11020 await this.ShowUserStatus(_curPost.ScreenName, false);
11024 private async void RtCountMenuItem_Click(object sender, EventArgs e)
11026 if (!this.ExistCurrentPost)
11029 var statusId = this._curPost.RetweetedId ?? this._curPost.StatusId;
11030 TwitterStatus status;
11032 using (var dialog = new WaitingDialog(Properties.Resources.RtCountMenuItem_ClickText1))
11034 var cancellationToken = dialog.EnableCancellation();
11038 var task = this.twitterApi.StatusesShow(statusId);
11039 status = await dialog.WaitForAsync(this, task);
11041 catch (WebApiException ex)
11043 if (!cancellationToken.IsCancellationRequested)
11044 MessageBox.Show(Properties.Resources.RtCountText2 + Environment.NewLine + "Err:" + ex.Message);
11048 if (cancellationToken.IsCancellationRequested)
11052 MessageBox.Show(status.RetweetCount + Properties.Resources.RtCountText1);
11055 private HookGlobalHotkey _hookGlobalHotkey;
11058 _hookGlobalHotkey = new HookGlobalHotkey(this);
11060 // この呼び出しは、Windows フォーム デザイナで必要です。
11061 InitializeComponent();
11063 // InitializeComponent() 呼び出しの後で初期化を追加します。
11065 if (!this.DesignMode)
11067 // デザイナでの編集時にレイアウトが縦方向に数pxずれる問題の対策
11068 this.StatusText.Dock = DockStyle.Fill;
11071 this.tweetDetailsView.Owner = this;
11073 this.TimerTimeline.Elapsed += this.TimerTimeline_Elapsed;
11074 this._hookGlobalHotkey.HotkeyPressed += _hookGlobalHotkey_HotkeyPressed;
11075 this.gh.NotifyClicked += GrowlHelper_Callback;
11077 // メイリオフォント指定時にタブの最小幅が広くなる問題の対策
11078 this.ListTab.HandleCreated += (s, e) => NativeMethods.SetMinTabWidth((TabControl)s, 40);
11080 this.ImageSelector.Visible = false;
11081 this.ImageSelector.Enabled = false;
11082 this.ImageSelector.FilePickDialog = OpenFileDialog1;
11084 this.workerProgress = new Progress<string>(x => this.StatusLabel.Text = x);
11086 this.ReplaceAppName();
11087 this.InitializeShortcuts();
11090 private void _hookGlobalHotkey_HotkeyPressed(object sender, KeyEventArgs e)
11092 if ((this.WindowState == FormWindowState.Normal || this.WindowState == FormWindowState.Maximized) && this.Visible && Form.ActiveForm == this)
11095 this.Visible = false;
11097 else if (Form.ActiveForm == null)
11099 this.Visible = true;
11100 if (this.WindowState == FormWindowState.Minimized) this.WindowState = FormWindowState.Normal;
11102 this.BringToFront();
11103 this.StatusText.Focus();
11107 private void SplitContainer2_MouseDoubleClick(object sender, MouseEventArgs e)
11108 => this.MultiLinePullDownMenuItem.PerformClick();
11110 public PostClass CurPost
11114 private void ImageSelectMenuItem_Click(object sender, EventArgs e)
11116 if (ImageSelector.Visible)
11117 ImageSelector.EndSelection();
11119 ImageSelector.BeginSelection();
11122 private void SelectMedia_DragEnter(DragEventArgs e)
11124 if (ImageSelector.HasUploadableService(((string[])e.Data.GetData(DataFormats.FileDrop, false))[0], true))
11126 e.Effect = DragDropEffects.Copy;
11129 e.Effect = DragDropEffects.None;
11132 private void SelectMedia_DragDrop(DragEventArgs e)
11135 this.BringToFront();
11136 ImageSelector.BeginSelection((string[])e.Data.GetData(DataFormats.FileDrop, false));
11137 StatusText.Focus();
11140 private void ImageSelector_BeginSelecting(object sender, EventArgs e)
11142 TimelinePanel.Visible = false;
11143 TimelinePanel.Enabled = false;
11146 private void ImageSelector_EndSelecting(object sender, EventArgs e)
11148 TimelinePanel.Visible = true;
11149 TimelinePanel.Enabled = true;
11150 ((DetailsListView)ListTab.SelectedTab.Tag).Focus();
11153 private void ImageSelector_FilePickDialogOpening(object sender, EventArgs e)
11154 => this.AllowDrop = false;
11156 private void ImageSelector_FilePickDialogClosed(object sender, EventArgs e)
11157 => this.AllowDrop = true;
11159 private void ImageSelector_SelectedServiceChanged(object sender, EventArgs e)
11161 if (ImageSelector.Visible)
11163 ModifySettingCommon = true;
11164 SaveConfigsAll(true);
11166 this.StatusText_TextChanged(null, null);
11170 private void ImageSelector_VisibleChanged(object sender, EventArgs e)
11171 => this.StatusText_TextChanged(null, null);
11174 /// StatusTextでCtrl+Vが押下された時の処理
11176 private void ProcClipboardFromStatusTextWhenCtrlPlusV()
11180 if (Clipboard.ContainsText())
11182 // clipboardにテキストがある場合は貼り付け処理
11183 this.StatusText.Paste(Clipboard.GetText());
11185 else if (Clipboard.ContainsImage())
11188 if (MessageBox.Show(Properties.Resources.PostPictureConfirm3,
11189 Properties.Resources.PostPictureWarn4,
11190 MessageBoxButtons.OKCancel,
11191 MessageBoxIcon.Question,
11192 MessageBoxDefaultButton.Button2)
11193 == DialogResult.OK)
11195 // clipboardから画像を取得
11196 using (var image = Clipboard.GetImage())
11198 this.ImageSelector.BeginSelection(image);
11203 catch (ExternalException ex)
11205 MessageBox.Show(ex.Message);
11210 private void ListManageToolStripMenuItem_Click(object sender, EventArgs e)
11212 using (ListManage form = new ListManage(tw))
11214 form.ShowDialog(this);
11218 public bool ModifySettingCommon { get; set; }
11219 public bool ModifySettingLocal { get; set; }
11220 public bool ModifySettingAtId { get; set; }
11222 private void MenuItemCommand_DropDownOpening(object sender, EventArgs e)
11224 if (this.ExistCurrentPost && !_curPost.IsDm)
11225 RtCountMenuItem.Enabled = true;
11227 RtCountMenuItem.Enabled = false;
11229 //if (SettingDialog.UrlConvertAuto && SettingDialog.ShortenTco)
11230 // TinyUrlConvertToolStripMenuItem.Enabled = false;
11232 // TinyUrlConvertToolStripMenuItem.Enabled = true;
11235 private void CopyUserIdStripMenuItem_Click(object sender, EventArgs e)
11236 => this.CopyUserId();
11238 private void CopyUserId()
11240 if (_curPost == null) return;
11241 string clstr = _curPost.ScreenName;
11244 Clipboard.SetDataObject(clstr, false, 5, 100);
11246 catch (Exception ex)
11248 MessageBox.Show(ex.Message);
11252 private async void ShowRelatedStatusesMenuItem_Click(object sender, EventArgs e)
11254 if (this.ExistCurrentPost && !_curPost.IsDm)
11258 await this.OpenRelatedTab(this._curPost);
11260 catch (TabException ex)
11262 MessageBox.Show(this, ex.Message, ApplicationSettings.ApplicationName, MessageBoxButtons.OK, MessageBoxIcon.Error);
11268 /// 指定されたツイートに対する関連発言タブを開きます
11270 /// <param name="statusId">表示するツイートのID</param>
11271 /// <exception cref="TabException">名前の重複が多すぎてタブを作成できない場合</exception>
11272 public async Task OpenRelatedTab(long statusId)
11274 var post = this._statuses[statusId];
11279 post = await this.tw.GetStatusApi(false, statusId);
11281 catch (WebApiException ex)
11283 this.StatusLabel.Text = $"Err:{ex.Message}(GetStatus)";
11288 await this.OpenRelatedTab(post);
11292 /// 指定されたツイートに対する関連発言タブを開きます
11294 /// <param name="post">表示する対象となるツイート</param>
11295 /// <exception cref="TabException">名前の重複が多すぎてタブを作成できない場合</exception>
11296 private async Task OpenRelatedTab(PostClass post)
11298 var tabRelated = this._statuses.GetTabByType<RelatedPostsTabModel>();
11299 if (tabRelated != null)
11301 this.RemoveSpecifiedTab(tabRelated.TabName, confirm: false);
11304 var tabName = this._statuses.MakeTabName("Related Tweets");
11306 tabRelated = new RelatedPostsTabModel(tabName, post)
11308 UnreadManage = false,
11312 this._statuses.AddTab(tabRelated);
11313 this.AddNewTab(tabRelated, startup: false);
11316 for (int i = 0; i < this.ListTab.TabPages.Count; i++)
11318 tabPage = this.ListTab.TabPages[i];
11319 if (tabName == tabPage.Text)
11321 this.ListTab.SelectedIndex = i;
11326 await this.RefreshTabAsync(tabRelated);
11328 tabPage = this.ListTab.TabPages.Cast<TabPage>()
11329 .FirstOrDefault(x => x.Text == tabRelated.TabName);
11331 if (tabPage != null)
11333 // TODO: 非同期更新中にタブが閉じられている場合を厳密に考慮したい
11335 var listView = (DetailsListView)tabPage.Tag;
11336 var targetPost = tabRelated.TargetPost;
11337 var index = tabRelated.IndexOf(targetPost.RetweetedId ?? targetPost.StatusId);
11339 if (index != -1 && index < listView.Items.Count)
11341 listView.SelectedIndices.Add(index);
11342 listView.Items[index].Focused = true;
11347 private void CacheInfoMenuItem_Click(object sender, EventArgs e)
11349 StringBuilder buf = new StringBuilder();
11350 //buf.AppendFormat("キャッシュメモリ容量 : {0}bytes({1}MB)" + Environment.NewLine, IconCache.CacheMemoryLimit, ((ImageDictionary)IconCache).CacheMemoryLimit / 1048576);
11351 //buf.AppendFormat("物理メモリ使用割合 : {0}%" + Environment.NewLine, IconCache.PhysicalMemoryLimit);
11352 buf.AppendFormat("キャッシュエントリ保持数 : {0}" + Environment.NewLine, IconCache.CacheCount);
11353 buf.AppendFormat("キャッシュエントリ破棄数 : {0}" + Environment.NewLine, IconCache.CacheRemoveCount);
11354 MessageBox.Show(buf.ToString(), "アイコンキャッシュ使用状況");
11357 private void tw_UserIdChanged()
11358 => this.ModifySettingCommon = true;
11360 #region "Userstream"
11361 private async void tw_PostDeleted(object sender, PostDeletedEventArgs e)
11365 if (InvokeRequired && !IsDisposed)
11367 await this.InvokeAsync(async () =>
11369 this._statuses.RemovePostFromAllTabs(e.StatusId, setIsDeleted: true);
11370 if (_curTab != null && _statuses.Tabs[_curTab.Text].Contains(e.StatusId))
11372 this.PurgeListViewItemCache();
11373 ((DetailsListView)_curTab.Tag).Update();
11374 if (_curPost != null && _curPost.StatusId == e.StatusId)
11375 await this.DispSelectedPost(true);
11381 catch (ObjectDisposedException)
11385 catch (InvalidOperationException)
11391 private void tw_NewPostFromStream(object sender, EventArgs e)
11393 if (SettingManager.Common.ReadOldPosts)
11395 _statuses.SetReadHomeTab(); //新着時未読クリア
11398 this._statuses.DistributePosts();
11400 this.RefreshThrottlingTimer.Invoke();
11403 private async void tw_UserStreamStarted(object sender, EventArgs e)
11407 if (InvokeRequired && !IsDisposed)
11409 await this.InvokeAsync(() => this.tw_UserStreamStarted(sender, e));
11413 catch (ObjectDisposedException)
11417 catch (InvalidOperationException)
11422 this.RefreshUserStreamsMenu();
11423 this.MenuItemUserStream.Enabled = true;
11425 StatusLabel.Text = "UserStream Started.";
11428 private async void tw_UserStreamStopped(object sender, EventArgs e)
11432 if (InvokeRequired && !IsDisposed)
11434 await this.InvokeAsync(() => this.tw_UserStreamStopped(sender, e));
11438 catch (ObjectDisposedException)
11442 catch (InvalidOperationException)
11447 this.RefreshUserStreamsMenu();
11448 this.MenuItemUserStream.Enabled = true;
11450 StatusLabel.Text = "UserStream Stopped.";
11453 private void RefreshUserStreamsMenu()
11455 if (this.tw.UserStreamActive)
11457 this.MenuItemUserStream.Text = "&UserStream ▶";
11458 this.StopToolStripMenuItem.Text = "&Stop";
11462 this.MenuItemUserStream.Text = "&UserStream ■";
11463 this.StopToolStripMenuItem.Text = "&Start";
11467 private async void tw_UserStreamEventArrived(object sender, UserStreamEventReceivedEventArgs e)
11471 if (InvokeRequired && !IsDisposed)
11473 await this.InvokeAsync(() => this.tw_UserStreamEventArrived(sender, e));
11477 catch (ObjectDisposedException)
11481 catch (InvalidOperationException)
11485 var ev = e.EventData;
11486 StatusLabel.Text = "Event: " + ev.Event;
11487 //if (ev.Event == "favorite")
11489 // NotifyFavorite(ev);
11492 if (ev.Event == "favorite" || ev.Event == "unfavorite")
11494 if (_curTab != null && _statuses.Tabs[_curTab.Text].Contains(ev.Id))
11496 this.PurgeListViewItemCache();
11497 ((DetailsListView)_curTab.Tag).Update();
11499 if (ev.Event == "unfavorite" && ev.Username.Equals(tw.Username, StringComparison.InvariantCultureIgnoreCase))
11501 var favTab = this._statuses.GetTabByType(MyCommon.TabUsageType.Favorites);
11502 favTab.EnqueueRemovePost(ev.Id, setIsDeleted: false);
11507 private void NotifyEvent(Twitter.FormattedEvent ev)
11510 if (BalloonRequired(ev))
11512 NotifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
11513 //if (SettingDialog.DispUsername) NotifyIcon1.BalloonTipTitle = tw.Username + " - "; else NotifyIcon1.BalloonTipTitle = "";
11514 //NotifyIcon1.BalloonTipTitle += Application.ProductName + " [" + ev.Event.ToUpper() + "] by " + ((string)(!string.IsNullOrEmpty(ev.Username) ? ev.Username : ""), string);
11515 StringBuilder title = new StringBuilder();
11516 if (SettingManager.Common.DispUsername)
11518 title.Append(tw.Username);
11519 title.Append(" - ");
11525 title.Append(ApplicationSettings.ApplicationName);
11526 title.Append(" [");
11527 title.Append(ev.Event.ToUpper(CultureInfo.CurrentCulture));
11528 title.Append("] by ");
11529 if (!string.IsNullOrEmpty(ev.Username))
11531 title.Append(ev.Username);
11535 //title.Append("");
11538 if (!string.IsNullOrEmpty(ev.Target))
11540 //NotifyIcon1.BalloonTipText = ev.Target;
11545 //NotifyIcon1.BalloonTipText = " ";
11548 //NotifyIcon1.ShowBalloonTip(500);
11549 if (SettingManager.Common.IsUseNotifyGrowl)
11551 gh.Notify(GrowlHelper.NotifyType.UserStreamEvent,
11552 ev.Id.ToString(), title.ToString(), text);
11556 NotifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
11557 NotifyIcon1.BalloonTipTitle = title.ToString();
11558 NotifyIcon1.BalloonTipText = text;
11559 NotifyIcon1.ShowBalloonTip(500);
11564 string snd = SettingManager.Common.EventSoundFile;
11565 if (!_initial && SettingManager.Common.PlaySound && !string.IsNullOrEmpty(snd))
11567 if ((ev.Eventtype & SettingManager.Common.EventNotifyFlag) != 0 && IsMyEventNotityAsEventType(ev))
11571 string dir = Application.StartupPath;
11572 if (Directory.Exists(Path.Combine(dir, "Sounds")))
11574 dir = Path.Combine(dir, "Sounds");
11576 using (SoundPlayer player = new SoundPlayer(Path.Combine(dir, snd)))
11588 private void StopToolStripMenuItem_Click(object sender, EventArgs e)
11590 MenuItemUserStream.Enabled = false;
11591 if (StopRefreshAllMenuItem.Checked)
11593 StopRefreshAllMenuItem.Checked = false;
11596 if (this.tw.UserStreamActive)
11598 tw.StopUserStream();
11602 tw.StartUserStream();
11606 private static string inputTrack = "";
11608 private void TrackToolStripMenuItem_Click(object sender, EventArgs e)
11610 if (TrackToolStripMenuItem.Checked)
11612 using (InputTabName inputForm = new InputTabName())
11614 inputForm.TabName = inputTrack;
11615 inputForm.FormTitle = "Input track word";
11616 inputForm.FormDescription = "Track word";
11617 if (inputForm.ShowDialog() != DialogResult.OK)
11619 TrackToolStripMenuItem.Checked = false;
11622 inputTrack = inputForm.TabName.Trim();
11624 if (!inputTrack.Equals(tw.TrackWord))
11626 tw.TrackWord = inputTrack;
11627 this.ModifySettingCommon = true;
11628 TrackToolStripMenuItem.Checked = !string.IsNullOrEmpty(inputTrack);
11629 tw.ReconnectUserStream();
11635 tw.ReconnectUserStream();
11637 this.ModifySettingCommon = true;
11640 private void AllrepliesToolStripMenuItem_Click(object sender, EventArgs e)
11642 tw.AllAtReply = AllrepliesToolStripMenuItem.Checked;
11643 this.ModifySettingCommon = true;
11644 tw.ReconnectUserStream();
11647 private void EventViewerMenuItem_Click(object sender, EventArgs e)
11649 if (evtDialog == null || evtDialog.IsDisposed)
11651 this.evtDialog = new EventViewerDialog
11657 this.evtDialog.Location = new Point
11659 X = Convert.ToInt32(this.Location.X + this.Size.Width / 2 - evtDialog.Size.Width / 2),
11660 Y = Convert.ToInt32(this.Location.Y + this.Size.Height / 2 - evtDialog.Size.Height / 2),
11663 evtDialog.EventSource = tw.StoredEvent;
11664 if (!evtDialog.Visible)
11666 evtDialog.Show(this);
11670 evtDialog.Activate();
11672 this.TopMost = SettingManager.Common.AlwaysTop;
11676 private void TweenRestartMenuItem_Click(object sender, EventArgs e)
11678 MyCommon._endingFlag = true;
11682 Application.Restart();
11686 MessageBox.Show("Failed to restart. Please run " + ApplicationSettings.ApplicationName + " manually.");
11690 private async void OpenOwnHomeMenuItem_Click(object sender, EventArgs e)
11691 => await this.OpenUriInBrowserAsync(MyCommon.TwitterUrl + tw.Username);
11693 private bool ExistCurrentPost
11697 if (_curPost == null) return false;
11698 if (_curPost.IsDeleted) return false;
11703 private async void ShowUserTimelineToolStripMenuItem_Click(object sender, EventArgs e)
11704 => await this.ShowUserTimeline();
11706 private string GetUserIdFromCurPostOrInput(string caption)
11708 var id = _curPost?.ScreenName ?? "";
11710 using (InputTabName inputName = new InputTabName())
11712 inputName.FormTitle = caption;
11713 inputName.FormDescription = Properties.Resources.FRMessage1;
11714 inputName.TabName = id;
11715 if (inputName.ShowDialog() == DialogResult.OK &&
11716 !string.IsNullOrEmpty(inputName.TabName.Trim()))
11718 id = inputName.TabName.Trim();
11728 private async void UserTimelineToolStripMenuItem_Click(object sender, EventArgs e)
11730 string id = GetUserIdFromCurPostOrInput("Show UserTimeline");
11731 if (!string.IsNullOrEmpty(id))
11733 await this.AddNewTabForUserTimeline(id);
11737 private void SystemEvents_PowerModeChanged(object sender, Microsoft.Win32.PowerModeChangedEventArgs e)
11739 if (e.Mode == Microsoft.Win32.PowerModes.Resume) osResumed = true;
11742 private async void SystemEvents_TimeChanged(object sender, EventArgs e)
11744 var prevTimeOffset = TimeZoneInfo.Local.BaseUtcOffset;
11746 TimeZoneInfo.ClearCachedData();
11748 var curTimeOffset = TimeZoneInfo.Local.BaseUtcOffset;
11750 if (curTimeOffset != prevTimeOffset)
11753 this.PurgeListViewItemCache();
11754 this._curList.Refresh();
11756 await this.DispSelectedPost(forceupdate: true);
11760 private void TimelineRefreshEnableChange(bool isEnable)
11764 tw.StartUserStream();
11768 tw.StopUserStream();
11770 TimerTimeline.Enabled = isEnable;
11773 private void StopRefreshAllMenuItem_CheckedChanged(object sender, EventArgs e)
11774 => this.TimelineRefreshEnableChange(!StopRefreshAllMenuItem.Checked);
11776 private async Task OpenUserAppointUrl()
11778 if (SettingManager.Common.UserAppointUrl != null)
11780 if (SettingManager.Common.UserAppointUrl.Contains("{ID}") || SettingManager.Common.UserAppointUrl.Contains("{STATUS}"))
11782 if (_curPost != null)
11784 string xUrl = SettingManager.Common.UserAppointUrl;
11785 xUrl = xUrl.Replace("{ID}", _curPost.ScreenName);
11787 var statusId = _curPost.RetweetedId ?? _curPost.StatusId;
11788 xUrl = xUrl.Replace("{STATUS}", statusId.ToString());
11790 await this.OpenUriInBrowserAsync(xUrl);
11795 await this.OpenUriInBrowserAsync(SettingManager.Common.UserAppointUrl);
11800 private async void OpenUserSpecifiedUrlMenuItem_Click(object sender, EventArgs e)
11801 => await this.OpenUserAppointUrl();
11803 private async void GrowlHelper_Callback(object sender, GrowlHelper.NotifyCallbackEventArgs e)
11805 if (Form.ActiveForm == null)
11807 await this.InvokeAsync(() =>
11809 this.Visible = true;
11810 if (this.WindowState == FormWindowState.Minimized) this.WindowState = FormWindowState.Normal;
11812 this.BringToFront();
11813 if (e.NotifyType == GrowlHelper.NotifyType.DirectMessage)
11815 if (!this.GoDirectMessage(e.StatusId)) this.StatusText.Focus();
11819 if (!this.GoStatus(e.StatusId)) this.StatusText.Focus();
11825 private void ReplaceAppName()
11827 MatomeMenuItem.Text = MyCommon.ReplaceAppName(MatomeMenuItem.Text);
11828 AboutMenuItem.Text = MyCommon.ReplaceAppName(AboutMenuItem.Text);
11831 private void tweetThumbnail1_ThumbnailLoading(object sender, EventArgs e)
11832 => this.SplitContainer3.Panel2Collapsed = false;
11834 private async void tweetThumbnail1_ThumbnailDoubleClick(object sender, ThumbnailDoubleClickEventArgs e)
11835 => await this.OpenThumbnailPicture(e.Thumbnail);
11837 private async void tweetThumbnail1_ThumbnailImageSearchClick(object sender, ThumbnailImageSearchEventArgs e)
11838 => await this.OpenUriInBrowserAsync(e.ImageUrl);
11840 private async Task OpenThumbnailPicture(ThumbnailInfo thumbnail)
11842 var url = thumbnail.FullSizeImageUrl ?? thumbnail.MediaPageUrl;
11844 await this.OpenUriInBrowserAsync(url);
11847 private async void TwitterApiStatusToolStripMenuItem_Click(object sender, EventArgs e)
11848 => await this.OpenUriInBrowserAsync(Twitter.ServiceAvailabilityStatusUrl);
11850 private void PostButton_KeyDown(object sender, KeyEventArgs e)
11852 if (e.KeyCode == Keys.Space)
11854 this.JumpUnreadMenuItem_Click(null, null);
11856 e.SuppressKeyPress = true;
11860 private void ContextMenuColumnHeader_Opening(object sender, CancelEventArgs e)
11862 this.IconSizeNoneToolStripMenuItem.Checked = SettingManager.Common.IconSize == MyCommon.IconSizes.IconNone;
11863 this.IconSize16ToolStripMenuItem.Checked = SettingManager.Common.IconSize == MyCommon.IconSizes.Icon16;
11864 this.IconSize24ToolStripMenuItem.Checked = SettingManager.Common.IconSize == MyCommon.IconSizes.Icon24;
11865 this.IconSize48ToolStripMenuItem.Checked = SettingManager.Common.IconSize == MyCommon.IconSizes.Icon48;
11866 this.IconSize48_2ToolStripMenuItem.Checked = SettingManager.Common.IconSize == MyCommon.IconSizes.Icon48_2;
11868 this.LockListSortOrderToolStripMenuItem.Checked = SettingManager.Common.SortOrderLock;
11871 private void IconSizeNoneToolStripMenuItem_Click(object sender, EventArgs e)
11872 => this.ChangeListViewIconSize(MyCommon.IconSizes.IconNone);
11874 private void IconSize16ToolStripMenuItem_Click(object sender, EventArgs e)
11875 => this.ChangeListViewIconSize(MyCommon.IconSizes.Icon16);
11877 private void IconSize24ToolStripMenuItem_Click(object sender, EventArgs e)
11878 => this.ChangeListViewIconSize(MyCommon.IconSizes.Icon24);
11880 private void IconSize48ToolStripMenuItem_Click(object sender, EventArgs e)
11881 => this.ChangeListViewIconSize(MyCommon.IconSizes.Icon48);
11883 private void IconSize48_2ToolStripMenuItem_Click(object sender, EventArgs e)
11884 => this.ChangeListViewIconSize(MyCommon.IconSizes.Icon48_2);
11886 private void ChangeListViewIconSize(MyCommon.IconSizes iconSize)
11888 if (SettingManager.Common.IconSize == iconSize) return;
11890 var oldIconCol = _iconCol;
11892 SettingManager.Common.IconSize = iconSize;
11893 ApplyListViewIconSize(iconSize);
11895 if (_iconCol != oldIconCol)
11897 foreach (TabPage tp in ListTab.TabPages)
11899 ResetColumns((DetailsListView)tp.Tag);
11903 _curList?.Refresh();
11905 ModifySettingCommon = true;
11908 private void LockListSortToolStripMenuItem_Click(object sender, EventArgs e)
11910 var state = this.LockListSortOrderToolStripMenuItem.Checked;
11911 if (SettingManager.Common.SortOrderLock == state) return;
11913 SettingManager.Common.SortOrderLock = state;
11915 ModifySettingCommon = true;
11918 private void tweetDetailsView_StatusChanged(object sender, TweetDetailsViewStatusChengedEventArgs e)
11920 if (!string.IsNullOrEmpty(e.StatusText))
11922 this.StatusLabelUrl.Text = e.StatusText;
11926 this.SetStatusLabelUrl();