PageRenderTime 122ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 2ms

/OpenTween/Tween.cs

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