//"C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\sgen.exe" /f /a:"$(TargetPath)"
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Media;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenTween.Api;
+using OpenTween.Api.DataModel;
using OpenTween.Connection;
+using OpenTween.Models;
using OpenTween.OpenTweenCustomControl;
using OpenTween.Thumbnail;
+ "a:link, a:visited, a:active, a:hover {color:rgb(%LINK_COLOR%); } "
+ "img.emoji {width: 1em; height: 1em; margin: 0 .05em 0 .1em; vertical-align: -0.1em; border: none;} "
+ ".quote-tweet {border: 1px solid #ccc; margin: 1em; padding: 0.5em;} "
+ + ".quote-tweet.reply {border-color: #f33;} "
+ ".quote-tweet-link {color: inherit !important; text-decoration: none;}"
+ "--></style>"
+ "</head><body><pre>";
+ "a:link, a:visited, a:active, a:hover {color:rgb(%LINK_COLOR%); } "
+ "img.emoji {width: 1em; height: 1em; margin: 0 .05em 0 .1em; vertical-align: -0.1em; border: none;} "
+ ".quote-tweet {border: 1px solid #ccc; margin: 1em; padding: 0.5em;} "
+ + ".quote-tweet.reply {border-color: rgb(%BG_REPLY_COLOR%);} "
+ ".quote-tweet-link {color: inherit !important; text-decoration: none;}"
+ "--></style>"
+ "</head><body><p>";
//設定ファイル関連
//private SettingToConfig _cfg; //旧
- private SettingLocal _cfgLocal;
+ internal SettingLocal _cfgLocal;
private SettingCommon _cfgCommon;
- private bool _modifySettingLocal = false;
- private bool _modifySettingCommon = false;
- private bool _modifySettingAtId = false;
//twitter解析部
- private Twitter tw = new Twitter();
+ private TwitterApi twitterApi = new TwitterApi();
+ private Twitter tw;
//Growl呼び出し部
private GrowlHelper gh = new GrowlHelper(Application.ProductName);
//サブ画面インスタンス
- private SearchWordDialog SearchDialog = new SearchWordDialog(); //検索画面インスタンス
+ internal SearchWordDialog SearchDialog = new SearchWordDialog(); //検索画面インスタンス
private OpenURL UrlDialog = new OpenURL();
public AtIdSupplement AtIdSupl; //@id補助
public AtIdSupplement HashSupl; //Hashtag補助
//時速表示用
private List<DateTime> _postTimestamps = new List<DateTime>();
private List<DateTime> _favTimestamps = new List<DateTime>();
- private Dictionary<DateTime, int> _tlTimestamps = new Dictionary<DateTime, int>();
- private int _tlCount;
// 以下DrawItem関連
private SolidBrush _brsHighLight = new SolidBrush(Color.FromKnownColor(KnownColor.Highlight));
//////////////////////////////////////////////////////////////////////////////////////////////////////////
private TabInformations _statuses;
- // ListViewItem のキャッシュ関連
- private int _itemCacheIndex;
- private ListViewItem[] _itemCache;
- private PostClass[] _postCache;
- private ReaderWriterLockSlim itemCacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
+ /// <summary>
+ /// 現在表示している発言一覧の <see cref="ListView"/> に対するキャッシュ
+ /// </summary>
+ /// <remarks>
+ /// キャッシュクリアのために null が代入されることがあるため、
+ /// 使用する場合には <see cref="_listItemCache"/> に対して直接メソッド等を呼び出さずに
+ /// 一旦ローカル変数に代入してから参照すること。
+ /// </remarks>
+ private ListViewItemCache _listItemCache = null;
+
+ internal class ListViewItemCache
+ {
+ /// <summary>アイテムをキャッシュする対象の <see cref="ListView"/></summary>
+ public ListView TargetList { get; set; }
+
+ /// <summary>キャッシュする範囲の開始インデックス</summary>
+ public int StartIndex { get; set; }
+
+ /// <summary>キャッシュする範囲の終了インデックス</summary>
+ public int EndIndex { get; set; }
+
+ /// <summary>キャッシュされた <see cref="ListViewItem"/> インスタンス</summary>
+ public ListViewItem[] ListItem { get; set; }
+
+ /// <summary>キャッシュされた範囲に対応する <see cref="PostClass"/> インスタンス</summary>
+ public PostClass[] Post { get; set; }
+
+ /// <summary>キャッシュされたアイテムの件数</summary>
+ public int Count
+ => this.EndIndex - this.StartIndex + 1;
+
+ /// <summary>指定されたインデックスがキャッシュの範囲内であるか判定します</summary>
+ /// <returns><paramref name="index"/> がキャッシュの範囲内であれば true、それ以外は false</returns>
+ public bool Contains(int index)
+ => index >= this.StartIndex && index <= this.EndIndex;
+
+ /// <summary>指定されたインデックスの範囲が全てキャッシュの範囲内であるか判定します</summary>
+ /// <returns><paramref name="rangeStart"/> から <paramref name="rangeEnd"/> の範囲が全てキャッシュの範囲内であれば true、それ以外は false</returns>
+ public bool IsSupersetOf(int rangeStart, int rangeEnd)
+ => rangeStart >= this.StartIndex && rangeEnd <= this.EndIndex;
+
+ /// <summary>指定されたインデックスの <see cref="ListViewItem"/> と <see cref="PostClass"/> をキャッシュから取得することを試みます</summary>
+ /// <returns>取得に成功すれば true、それ以外は false</returns>
+ public bool TryGetValue(int index, out ListViewItem item, out PostClass post)
+ {
+ if (this.Contains(index))
+ {
+ item = this.ListItem[index - this.StartIndex];
+ post = this.Post[index - this.StartIndex];
+ return true;
+ }
+ else
+ {
+ item = null;
+ post = null;
+ return false;
+ }
+ }
+ }
private TabPage _curTab;
private int _curItemIndex;
private const int MAX_WORKER_THREADS = 20;
private SemaphoreSlim workerSemaphore = new SemaphoreSlim(MAX_WORKER_THREADS);
private CancellationTokenSource workerCts = new CancellationTokenSource();
+ private IProgress<string> workerProgress;
private int UnreadCounter = -1;
private int UnreadAtCounter = -1;
private bool osResumed = false;
//////////////////////////////////////////////////////////////////////////////////////////////////////////
- private string _postBrowserStatusText = "";
-
private bool _colorize = false;
private System.Timers.Timer TimerTimeline = new System.Timers.Timer();
- private ImageListViewItem displayItem;
-
private string recommendedStatusFooter;
//URL短縮のUndo用
private Stack<Tuple<TabPage, PostClass>> selectPostChains = new Stack<Tuple<TabPage, PostClass>>(); //ポスト選択履歴
//検索処理タイプ
- private enum SEARCHTYPE
+ internal enum SEARCHTYPE
{
DialogSearch,
NextSearch,
this.thumbnailTokenSource?.Dispose();
- this.itemCacheLock.Dispose();
this.tw.Dispose();
+ this.twitterApi.Dispose();
this._hookGlobalHotkey.Dispose();
}
{
this.InitColumnText();
- ColumnHeader[] columns;
- if (this._iconCol)
+ ColumnHeader[] columns = null;
+ try
{
- columns = new[]
+ if (this._iconCol)
{
- new ColumnHeader { Text = this.ColumnText[0], Width = 48 }, // アイコン
- new ColumnHeader { Text = this.ColumnText[2], Width = 300 }, // 本文
- };
+ columns = new[]
+ {
+ new ColumnHeader(), // アイコン
+ new ColumnHeader(), // 本文
+ };
- if (startup)
- {
- var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / this._cfgLocal.ScaleDimension.Width;
+ columns[0].Text = this.ColumnText[0];
+ columns[1].Text = this.ColumnText[2];
- columns[0].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width1);
- columns[1].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width3);
- columns[0].DisplayIndex = 0;
- columns[1].DisplayIndex = 1;
- }
- else
- {
- var idx = 0;
- foreach (var curListColumn in this._curList.Columns.Cast<ColumnHeader>())
+ if (startup)
+ {
+ var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / this._cfgLocal.ScaleDimension.Width;
+
+ columns[0].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width1);
+ columns[1].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width3);
+ columns[0].DisplayIndex = 0;
+ columns[1].DisplayIndex = 1;
+ }
+ else
{
- columns[idx].Width = curListColumn.Width;
- columns[idx].DisplayIndex = curListColumn.DisplayIndex;
- idx++;
+ var idx = 0;
+ foreach (var curListColumn in this._curList.Columns.Cast<ColumnHeader>())
+ {
+ columns[idx].Width = curListColumn.Width;
+ columns[idx].DisplayIndex = curListColumn.DisplayIndex;
+ idx++;
+ }
}
}
- }
- else
- {
- columns = new[]
+ else
{
- new ColumnHeader { Text = this.ColumnText[0], Width = 48 }, // アイコン
- new ColumnHeader { Text = this.ColumnText[1], Width = 80 }, // ニックネーム
- new ColumnHeader { Text = this.ColumnText[2], Width = 300 }, // 本文
- new ColumnHeader { Text = this.ColumnText[3], Width = 50 }, // 日付
- new ColumnHeader { Text = this.ColumnText[4], Width = 50 }, // ユーザID
- new ColumnHeader { Text = this.ColumnText[5], Width = 16 }, // 未読
- new ColumnHeader { Text = this.ColumnText[6], Width = 16 }, // マーク&プロテクト
- new ColumnHeader { Text = this.ColumnText[7], Width = 50 }, // ソース
- };
+ columns = new[]
+ {
+ new ColumnHeader(), // アイコン
+ new ColumnHeader(), // ニックネーム
+ new ColumnHeader(), // 本文
+ new ColumnHeader(), // 日付
+ new ColumnHeader(), // ユーザID
+ new ColumnHeader(), // 未読
+ new ColumnHeader(), // マーク&プロテクト
+ new ColumnHeader(), // ソース
+ };
- if (startup)
- {
- var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / this._cfgLocal.ScaleDimension.Width;
+ foreach (var i in Enumerable.Range(0, columns.Length))
+ columns[i].Text = this.ColumnText[i];
+
+ if (startup)
+ {
+ var widthScaleFactor = this.CurrentAutoScaleDimensions.Width / this._cfgLocal.ScaleDimension.Width;
- columns[0].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width1);
- columns[1].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width2);
- columns[2].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width3);
- columns[3].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width4);
- columns[4].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width5);
- columns[5].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width6);
- columns[6].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width7);
- columns[7].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width8);
+ columns[0].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width1);
+ columns[1].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width2);
+ columns[2].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width3);
+ columns[3].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width4);
+ columns[4].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width5);
+ columns[5].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width6);
+ columns[6].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width7);
+ columns[7].Width = ScaleBy(widthScaleFactor, _cfgLocal.Width8);
- var displayIndex = new[] {
- this._cfgLocal.DisplayIndex1, this._cfgLocal.DisplayIndex2,
- this._cfgLocal.DisplayIndex3, this._cfgLocal.DisplayIndex4,
- this._cfgLocal.DisplayIndex5, this._cfgLocal.DisplayIndex6,
- this._cfgLocal.DisplayIndex7, this._cfgLocal.DisplayIndex8
- };
+ var displayIndex = new[] {
+ this._cfgLocal.DisplayIndex1, this._cfgLocal.DisplayIndex2,
+ this._cfgLocal.DisplayIndex3, this._cfgLocal.DisplayIndex4,
+ this._cfgLocal.DisplayIndex5, this._cfgLocal.DisplayIndex6,
+ this._cfgLocal.DisplayIndex7, this._cfgLocal.DisplayIndex8
+ };
- foreach (var i in Enumerable.Range(0, displayIndex.Length))
+ foreach (var i in Enumerable.Range(0, displayIndex.Length))
+ {
+ columns[i].DisplayIndex = displayIndex[i];
+ }
+ }
+ else
{
- columns[i].DisplayIndex = displayIndex[i];
+ var idx = 0;
+ foreach (var curListColumn in this._curList.Columns.Cast<ColumnHeader>())
+ {
+ columns[idx].Width = curListColumn.Width;
+ columns[idx].DisplayIndex = curListColumn.DisplayIndex;
+ idx++;
+ }
}
}
- else
+
+ list.Columns.AddRange(columns);
+
+ columns = null;
+ }
+ finally
+ {
+ if (columns != null)
{
- var idx = 0;
- foreach (var curListColumn in this._curList.Columns.Cast<ColumnHeader>())
- {
- columns[idx].Width = curListColumn.Width;
- columns[idx].DisplayIndex = curListColumn.DisplayIndex;
- idx++;
- }
+ foreach (var column in columns)
+ column?.Dispose();
}
}
-
- list.Columns.AddRange(columns);
}
private void InitColumnText()
TraceOutToolStripMenuItem.Checked = true;
MyCommon.TraceFlag = true;
#endif
- if (!MyCommon.FileVersion.EndsWith("0"))
+ if (!MyCommon.FileVersion.EndsWith("0", StringComparison.Ordinal))
{
TraceOutToolStripMenuItem.Checked = true;
MyCommon.TraceFlag = true;
//Win32Api.SetProxy(HttpConnection.ProxyType.Specified, "127.0.0.1", 8080, "user", "pass")
- new InternetSecurityManager(PostBrowser);
- this.PostBrowser.AllowWebBrowserDrop = false; // COMException を回避するため、ActiveX の初期化が終わってから設定する
-
MyCommon.TwitterApiInfo.AccessLimitUpdated += TwitterApiStatus_AccessLimitUpdated;
Microsoft.Win32.SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
if (this._cfgCommon.AutoShortUrlFirst < 0)
this._cfgCommon.AutoShortUrlFirst = MyCommon.UrlConverter.Uxnu;
- HttpTwitter.TwitterUrl = this._cfgCommon.TwitterUrl;
+ TwitterApiConnection.RestApiHost = this._cfgCommon.TwitterApiHost;
+ this.tw = new Twitter(this.twitterApi);
//認証関連
if (string.IsNullOrEmpty(this._cfgCommon.Token)) this._cfgCommon.UserName = "";
this._cfgLocal.ProxyUser, this._cfgLocal.ProxyPassword);
Networking.ForceIPv4 = this._cfgCommon.ForceIPv4;
- HttpTwitter.TwitterUrl = this._cfgCommon.TwitterUrl;
+ TwitterApiConnection.RestApiHost = this._cfgCommon.TwitterApiHost;
tw.RestrictFavCheck = this._cfgCommon.RestrictFavCheck;
tw.ReadOwnPost = this._cfgCommon.ReadOwnPost;
tw.TrackWord = this._cfgCommon.TrackWord;
imgazyobizinet.Enabled = this._cfgCommon.EnableImgAzyobuziNet;
imgazyobizinet.DisabledInDM = this._cfgCommon.ImgAzyobuziNetDisabledInDM;
- Thumbnail.Services.TonTwitterCom.InitializeOAuthToken = x =>
- x.Initialize(ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
- this.tw.AccessToken, this.tw.AccessTokenSecret, "", "");
+ Thumbnail.Services.TonTwitterCom.GetApiConnection = () => this.twitterApi.Connection;
//画像投稿サービス
ImageSelector.Initialize(tw, this.tw.Configuration, _cfgCommon.UseImageServiceName, _cfgCommon.UseImageService);
//アイコンリスト作成
this.IconCache = new ImageCache();
+ this.tweetDetailsView.IconCache = this.IconCache;
//フォント&文字色&背景色保持
_fntUnread = this._cfgLocal.FontUnread;
this.StatusText.Multiline = false; // _cfgLocal.StatusMultiline の設定は後で反映される
this.SplitContainer2.Panel2MinSize = this.StatusText.Height;
- // NameLabel のフォントを OTBaseForm.GlobalFont に変更
- this.NameLabel.Font = this.ReplaceToGlobalFont(this.NameLabel.Font);
-
// 必要であれば、発言一覧と発言詳細部・入力欄の上下を入れ替える
SplitContainer1.IsPanelInverted = !this._cfgCommon.StatusAreaAtBottom;
UnreadStripMenuItem.Enabled = false;
}
- //発言詳細部の初期化
- NameLabel.Text = "";
- DateTimeLabel.Text = "";
- SourceLinkLabel.Text = "";
-
//リンク先URL表示部の初期化(画面左下)
StatusLabelUrl.Text = "";
//状態表示部の初期化(画面右下)
CopyURLMenuItem.ShortcutKeyDisplayString = "Ctrl+Shift+C";
CopyUserIdStripMenuItem.ShortcutKeyDisplayString = "Shift+Alt+C";
+ // SourceLinkLabel のテキストが SplitContainer2.Panel2.AccessibleName にセットされるのを防ぐ
+ // (タブオーダー順で SourceLinkLabel の次にある PostBrowser が TabStop = false となっているため、
+ // さらに次のコントロールである SplitContainer2.Panel2 の AccessibleName がデフォルトで SourceLinkLabel のテキストになってしまう)
+ this.SplitContainer2.Panel2.AccessibleName = "";
+
////////////////////////////////////////////////////////////////////////////////
var sortOrder = (SortOrder)_cfgCommon.SortOrder;
var mode = ComparerMode.Id;
SetTabAlignment();
//デフォルトタブの存在チェック、ない場合には追加
- if (_statuses.GetTabByType(MyCommon.TabUsageType.Home) == null)
- {
- TabClass tab;
- if (!_statuses.Tabs.TryGetValue(MyCommon.DEFAULTTAB.RECENT, out tab))
- {
- _statuses.AddTab(MyCommon.DEFAULTTAB.RECENT, MyCommon.TabUsageType.Home, null);
- }
- else
- {
- tab.TabType = MyCommon.TabUsageType.Home;
- }
- }
- if (_statuses.GetTabByType(MyCommon.TabUsageType.Mentions) == null)
- {
- TabClass tab;
- if (!_statuses.Tabs.TryGetValue(MyCommon.DEFAULTTAB.REPLY, out tab))
- {
- _statuses.AddTab(MyCommon.DEFAULTTAB.REPLY, MyCommon.TabUsageType.Mentions, null);
- }
- else
- {
- tab.TabType = MyCommon.TabUsageType.Mentions;
- }
- }
- if (_statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage) == null)
- {
- TabClass tab;
- if (!_statuses.Tabs.TryGetValue(MyCommon.DEFAULTTAB.DM, out tab))
- {
- _statuses.AddTab(MyCommon.DEFAULTTAB.DM, MyCommon.TabUsageType.DirectMessage, null);
- }
- else
- {
- tab.TabType = MyCommon.TabUsageType.DirectMessage;
- }
- }
- if (_statuses.GetTabByType(MyCommon.TabUsageType.Favorites) == null)
- {
- TabClass tab;
- if (!_statuses.Tabs.TryGetValue(MyCommon.DEFAULTTAB.FAV, out tab))
- {
- _statuses.AddTab(MyCommon.DEFAULTTAB.FAV, MyCommon.TabUsageType.Favorites, null);
- }
- else
- {
- tab.TabType = MyCommon.TabUsageType.Favorites;
- }
- }
- if (_statuses.GetTabByType(MyCommon.TabUsageType.Mute) == null)
- {
- TabClass tab;
- if (!_statuses.Tabs.TryGetValue(MyCommon.DEFAULTTAB.MUTE, out tab))
- {
- _statuses.AddTab(MyCommon.DEFAULTTAB.MUTE, MyCommon.TabUsageType.Mute, null);
- }
- else
- {
- tab.TabType = MyCommon.TabUsageType.Mute;
- }
- }
+ if (this._statuses.GetTabByType<HomeTabModel>() == null)
+ this._statuses.AddTab(new HomeTabModel());
+
+ if (this._statuses.GetTabByType<MentionsTabModel>() == null)
+ this._statuses.AddTab(new MentionsTabModel());
+
+ if (this._statuses.GetTabByType<DirectMessagesTabModel>() == null)
+ this._statuses.AddTab(new DirectMessagesTabModel());
+
+ if (this._statuses.GetTabByType<FavoritesTabModel>() == null)
+ this._statuses.AddTab(new FavoritesTabModel());
+
+ if (this._statuses.GetTabByType<MuteTabModel>() == null)
+ this._statuses.AddTab(new MuteTabModel());
foreach (var tab in _statuses.Tabs.Values)
{
if (tab.TabType == MyCommon.TabUsageType.Mute)
continue;
- if (tab.TabType == MyCommon.TabUsageType.Undefined)
- {
- tab.TabType = MyCommon.TabUsageType.UserDefined;
- }
- if (!AddNewTab(tab.TabName, true, tab.TabType, tab.ListInfo))
+ if (!AddNewTab(tab, startup: true))
throw new TabException(Properties.Resources.TweenMain_LoadText1);
}
foreach (var ua in this._cfgCommon.UserAccounts)
{
- if (ua.UserId == 0 && ua.Username.ToLower() == tw.Username.ToLower())
+ if (ua.UserId == 0 && ua.Username.ToLowerInvariant() == tw.Username.ToLowerInvariant())
{
ua.UserId = tw.UserId;
break;
detailHtmlFormatHeader = detailHtmlFormatHeader
.Replace("%FONT_FAMILY%", _fntDetail.Name)
.Replace("%FONT_SIZE%", _fntDetail.Size.ToString())
- .Replace("%FONT_COLOR%", _clDetail.R.ToString() + "," + _clDetail.G.ToString() + "," + _clDetail.B.ToString())
- .Replace("%LINK_COLOR%", _clDetailLink.R.ToString() + "," + _clDetailLink.G.ToString() + "," + _clDetailLink.B.ToString())
- .Replace("%BG_COLOR%", _clDetailBackcolor.R.ToString() + "," + _clDetailBackcolor.G.ToString() + "," + _clDetailBackcolor.B.ToString());
+ .Replace("%FONT_COLOR%", $"{_clDetail.R},{_clDetail.G},{_clDetail.B}")
+ .Replace("%LINK_COLOR%", $"{_clDetailLink.R},{_clDetailLink.G},{_clDetailLink.B}")
+ .Replace("%BG_COLOR%", $"{_clDetailBackcolor.R},{_clDetailBackcolor.G},{_clDetailBackcolor.B}")
+ .Replace("%BG_REPLY_COLOR%", $"{_clAtTo.R}, {_clAtTo.G}, {_clAtTo.B}");
}
private void ListTab_DrawItem(object sender, DrawItemEventArgs e)
if (_cfgLocal.ScaleDimension.IsEmpty)
_cfgLocal.ScaleDimension = this.CurrentAutoScaleDimensions;
- List<TabClass> tabs = SettingTabs.Load().Tabs;
- foreach (TabClass tb in tabs)
+ var tabsSetting = SettingTabs.Load().Tabs;
+ foreach (var tabSetting in tabsSetting)
{
- try
+ TabModel tab;
+ switch (tabSetting.TabType)
{
- tb.FilterModified = false;
- _statuses.Tabs.Add(tb.TabName, tb);
+ case MyCommon.TabUsageType.Home:
+ tab = new HomeTabModel(tabSetting.TabName);
+ break;
+ case MyCommon.TabUsageType.Mentions:
+ tab = new MentionsTabModel(tabSetting.TabName);
+ break;
+ case MyCommon.TabUsageType.DirectMessage:
+ tab = new DirectMessagesTabModel(tabSetting.TabName);
+ break;
+ case MyCommon.TabUsageType.Favorites:
+ tab = new FavoritesTabModel(tabSetting.TabName);
+ break;
+ case MyCommon.TabUsageType.UserDefined:
+ tab = new FilterTabModel(tabSetting.TabName);
+ break;
+ case MyCommon.TabUsageType.UserTimeline:
+ tab = new UserTimelineTabModel(tabSetting.TabName, tabSetting.User);
+ break;
+ case MyCommon.TabUsageType.PublicSearch:
+ tab = new PublicSearchTabModel(tabSetting.TabName)
+ {
+ SearchWords = tabSetting.SearchWords,
+ SearchLang = tabSetting.SearchLang,
+ };
+ break;
+ case MyCommon.TabUsageType.Lists:
+ tab = new ListTimelineTabModel(tabSetting.TabName, tabSetting.ListInfo);
+ break;
+ case MyCommon.TabUsageType.Mute:
+ tab = new MuteTabModel(tabSetting.TabName);
+ break;
+ default:
+ continue;
}
- catch (Exception)
+
+ tab.UnreadManage = tabSetting.UnreadManage;
+ tab.Protected = tabSetting.Protected;
+ tab.Notify = tabSetting.Notify;
+ tab.SoundFile = tabSetting.SoundFile;
+
+ if (tab.IsDistributableTabType)
{
- tb.TabName = _statuses.GetUniqueTabName();
- _statuses.Tabs.Add(tb.TabName, tb);
+ var filterTab = (FilterTabModel)tab;
+ filterTab.FilterArray = tabSetting.FilterArray;
+ filterTab.FilterModified = false;
}
+
+ if (this._statuses.ContainsTab(tab.TabName))
+ tab.TabName = this._statuses.MakeTabName("MyTab");
+
+ this._statuses.AddTab(tab);
}
if (_statuses.Tabs.Count == 0)
{
- _statuses.AddTab(MyCommon.DEFAULTTAB.RECENT, MyCommon.TabUsageType.Home, null);
- _statuses.AddTab(MyCommon.DEFAULTTAB.REPLY, MyCommon.TabUsageType.Mentions, null);
- _statuses.AddTab(MyCommon.DEFAULTTAB.DM, MyCommon.TabUsageType.DirectMessage, null);
- _statuses.AddTab(MyCommon.DEFAULTTAB.FAV, MyCommon.TabUsageType.Favorites, null);
+ _statuses.AddTab(new HomeTabModel());
+ _statuses.AddTab(new MentionsTabModel());
+ _statuses.AddTab(new DirectMessagesTabModel());
+ _statuses.AddTab(new FavoritesTabModel());
}
}
if (ResetTimers.UserStream || usCounter <= 0 && this._cfgCommon.UserstreamPeriod > 0)
{
Interlocked.Exchange(ref usCounter, this._cfgCommon.UserstreamPeriod);
- if (this._isActiveUserstream)
- {
- refreshTasks.Add(this.RefreshTasktrayIcon(true));
- this.RefreshTimeline(true);
- }
+ if (this.tw.UserStreamActive)
+ this.RefreshTimeline();
ResetTimers.UserStream = false;
}
if (refreshFollowers > 6 * 3600)
await Task.WhenAll(refreshTasks);
}
- private void RefreshTimeline(bool isUserStream)
+ private void RefreshTimeline()
{
- //スクロール制御準備
- int smode = -1; //-1:制御しない,-2:最新へ,その他:topitem使用
- long topId = GetScrollPos(ref smode);
- int befCnt = _curList.VirtualListSize;
+ // 現在表示中のタブのスクロール位置を退避
+ var curListScroll = this.SaveListViewScroll(this._curList, this._statuses.Tabs[this._curTab.Text]);
- //現在の選択状態を退避
- var selId = new Dictionary<string, long[]>();
- var focusedId = new Dictionary<string, Tuple<long, long>>();
- SaveSelectedStatus(selId, focusedId);
-
- //mentionsの更新前件数を保持
- int dmCount = _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage).AllCount;
+ // 各タブのリスト上の選択位置などを退避
+ var listSelections = this.SaveListViewSelection();
//更新確定
PostClass[] notifyPosts;
string soundFile;
int addCount;
- bool isMention;
+ bool newMentionOrDm;
bool isDelete;
- addCount = _statuses.SubmitUpdate(out soundFile, out notifyPosts, out isMention, out isDelete, isUserStream);
+ addCount = _statuses.SubmitUpdate(out soundFile, out notifyPosts, out newMentionOrDm, out isDelete);
if (MyCommon._endingFlag) return;
foreach (TabPage tab in ListTab.TabPages)
{
DetailsListView lst = (DetailsListView)tab.Tag;
- TabClass tabInfo = _statuses.Tabs[tab.Text];
- using (ControlTransaction.Update(lst))
+ TabModel tabInfo = _statuses.Tabs[tab.Text];
+ if (isDelete || lst.VirtualListSize != tabInfo.AllCount)
{
- if (isDelete || lst.VirtualListSize != tabInfo.AllCount)
+ using (ControlTransaction.Update(lst))
{
if (lst.Equals(_curList))
{
//アイコン描画不具合あり?
}
- // status_id から ListView 上のインデックスに変換
- var selectedIndices = selId[tab.Text] != null
- ? tabInfo.IndexOf(selId[tab.Text]).Where(x => x != -1).ToArray()
- : null;
- var focusedIndex = tabInfo.IndexOf(focusedId[tab.Text].Item1);
- var selectionMarkIndex = tabInfo.IndexOf(focusedId[tab.Text].Item2);
-
- this.SelectListItem(lst, selectedIndices, focusedIndex, selectionMarkIndex);
+ // 選択位置などを復元
+ this.RestoreListViewSelection(lst, tabInfo, listSelections[tabInfo.TabName]);
}
}
if (tabInfo.UnreadCount > 0)
//throw;
}
- //スクロール制御後処理
- if (smode != -1)
- {
- try
- {
- if (befCnt != _curList.VirtualListSize)
- {
- switch (smode)
- {
- case -3:
- //最上行
- if (_curList.VirtualListSize > 0) _curList.EnsureVisible(0);
- break;
- case -2:
- //最下行へ
- if (_curList.VirtualListSize > 0) _curList.EnsureVisible(_curList.VirtualListSize - 1);
- break;
- case -1:
- //制御しない
- break;
- default:
- //表示位置キープ
- var topIndex = _statuses.Tabs[_curTab.Text].IndexOf(topId);
- if (_curList.VirtualListSize > 0 && topIndex > -1)
- this._curList.TopItem = _curList.Items[topIndex];
- break;
- }
- }
- }
- catch (Exception ex)
- {
- ex.Data["Msg"] = "Ref2";
- throw;
- }
- }
+ // スクロール位置を復元
+ this.RestoreListViewScroll(this._curList, this._statuses.Tabs[this._curTab.Text], curListScroll);
//新着通知
- NotifyNewPosts(notifyPosts,
- soundFile,
- addCount,
- isMention || dmCount != _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage).AllCount);
+ NotifyNewPosts(notifyPosts, soundFile, addCount, newMentionOrDm);
SetMainWindowTitle();
- if (!StatusLabelUrl.Text.StartsWith("http")) SetStatusLabelUrl();
+ if (!StatusLabelUrl.Text.StartsWith("http", StringComparison.Ordinal)) SetStatusLabelUrl();
HashSupl.AddRangeItem(tw.GetHashList());
}
- private long GetScrollPos(ref int smode)
+ internal struct ListViewScroll
+ {
+ public ScrollLockMode ScrollLockMode { get; set; }
+ public long? TopItemStatusId { get; set; }
+ }
+
+ internal enum ScrollLockMode
+ {
+ /// <summary>固定しない</summary>
+ None,
+
+ /// <summary>最上部に固定する</summary>
+ FixedToTop,
+
+ /// <summary>最下部に固定する</summary>
+ FixedToBottom,
+
+ /// <summary><see cref="ListViewScroll.TopItemStatusId"/> の位置に固定する</summary>
+ FixedToItem,
+ }
+
+ /// <summary>
+ /// <see cref="ListView"/> のスクロール位置に関する情報を <see cref="ListViewScroll"/> として返します
+ /// </summary>
+ private ListViewScroll SaveListViewScroll(DetailsListView listView, TabModel tab)
{
- long topId = -1;
- if (_curList != null && _curTab != null && _curList.VirtualListSize > 0)
+ var listScroll = new ListViewScroll
{
- if (_statuses.SortMode == ComparerMode.Id)
+ ScrollLockMode = this.GetScrollLockMode(listView),
+ };
+
+ if (listScroll.ScrollLockMode == ScrollLockMode.FixedToItem)
+ {
+ var topItem = listView.TopItem;
+ if (topItem != null)
+ listScroll.TopItemStatusId = tab.GetStatusIdAt(topItem.Index);
+ }
+
+ return listScroll;
+ }
+
+ private ScrollLockMode GetScrollLockMode(DetailsListView listView)
+ {
+ if (this._statuses.SortMode == ComparerMode.Id)
+ {
+ if (this._statuses.SortOrder == SortOrder.Ascending)
{
- if (_statuses.SortOrder == SortOrder.Ascending)
- {
- //Id昇順
- if (ListLockMenuItem.Checked)
- {
- //制御しない
- smode = -1;
- ////現在表示位置へ強制スクロール
- //if (_curList.TopItem != null) topId = _statuses.GetId(_curTab.Text, _curList.TopItem.Index);
- //smode = 0;
- }
- else
- {
- //最下行が表示されていたら、最下行へ強制スクロール。最下行が表示されていなかったら制御しない
- ListViewItem _item;
- _item = _curList.GetItemAt(0, _curList.ClientSize.Height - 1); //一番下
- if (_item == null) _item = _curList.Items[_curList.VirtualListSize - 1];
- if (_item.Index == _curList.VirtualListSize - 1)
- {
- smode = -2;
- }
- else
- {
- smode = -1;
- //if (_curList.TopItem != null) topId = _statuses.GetId(_curTab.Text, _curList.TopItem.Index);
- //smode = 0;
- }
- }
- }
- else
- {
- //Id降順
- if (ListLockMenuItem.Checked)
- {
- //現在表示位置へ強制スクロール
- if (_curList.TopItem != null) topId = _statuses.Tabs[_curTab.Text].GetId(_curList.TopItem.Index);
- smode = 0;
- }
- else
- {
- //最上行が表示されていたら、制御しない。最上行が表示されていなかったら、現在表示位置へ強制スクロール
- ListViewItem _item;
+ // Id昇順
+ if (this.ListLockMenuItem.Checked)
+ return ScrollLockMode.None;
- _item = _curList.GetItemAt(0, 10); //一番上
- if (_item == null) _item = _curList.Items[0];
- if (_item.Index == 0)
- {
- smode = -3; //最上行
- }
- else
- {
- if (_curList.TopItem != null) topId = _statuses.Tabs[_curTab.Text].GetId(_curList.TopItem.Index);
- smode = 0;
- }
- }
- }
+ // 最下行が表示されていたら、最下行へ強制スクロール。最下行が表示されていなかったら制御しない
+
+ // 一番下に表示されているアイテム
+ var bottomItem = listView.GetItemAt(0, listView.ClientSize.Height - 1);
+ if (bottomItem == null || bottomItem.Index == listView.VirtualListSize - 1)
+ return ScrollLockMode.FixedToBottom;
+ else
+ return ScrollLockMode.None;
}
else
{
- //現在表示位置へ強制スクロール
- if (_curList.TopItem != null) topId = _statuses.Tabs[_curTab.Text].GetId(_curList.TopItem.Index);
- smode = 0;
+ // Id降順
+ if (this.ListLockMenuItem.Checked)
+ return ScrollLockMode.FixedToItem;
+
+ // 最上行が表示されていたら、制御しない。最上行が表示されていなかったら、現在表示位置へ強制スクロール
+ var topItem = listView.TopItem;
+ if (topItem == null || topItem.Index == 0)
+ return ScrollLockMode.FixedToTop;
+ else
+ return ScrollLockMode.FixedToItem;
}
}
else
{
- smode = -1;
+ return ScrollLockMode.FixedToItem;
}
- return topId;
}
- private void SaveSelectedStatus(Dictionary<string, long[]> selId, Dictionary<string, Tuple<long, long>> focusedIdDict)
+ internal struct ListViewSelection
{
- if (MyCommon._endingFlag) return;
- foreach (TabPage tab in ListTab.TabPages)
+ public long[] SelectedStatusIds { get; set; }
+ public long? SelectionMarkStatusId { get; set; }
+ public long? FocusedStatusId { get; set; }
+ }
+
+ /// <summary>
+ /// <see cref="ListView"/> の選択状態を <see cref="ListViewSelection"/> として返します
+ /// </summary>
+ private IReadOnlyDictionary<string, ListViewSelection> SaveListViewSelection()
+ {
+ var listsDict = new Dictionary<string, ListViewSelection>();
+
+ foreach (var tabPage in this.ListTab.TabPages.Cast<TabPage>())
{
- var lst = (DetailsListView)tab.Tag;
- var tabInfo = _statuses.Tabs[tab.Text];
- if (lst.SelectedIndices.Count > 0 && lst.SelectedIndices.Count < 61)
+ var listView = (DetailsListView)tabPage.Tag;
+ var tab = _statuses.Tabs[tabPage.Text];
+
+ ListViewSelection listStatus;
+ if (listView.VirtualListSize != 0)
{
- selId.Add(tab.Text, tabInfo.GetId(lst.SelectedIndices));
+ listStatus = new ListViewSelection
+ {
+ SelectedStatusIds = this.GetSelectedStatusIds(listView, tab),
+ FocusedStatusId = this.GetFocusedStatusId(listView, tab),
+ SelectionMarkStatusId = this.GetSelectionMarkStatusId(listView, tab),
+ };
}
else
{
- selId.Add(tab.Text, null);
+ listStatus = new ListViewSelection
+ {
+ SelectedStatusIds = new long[0],
+ SelectionMarkStatusId = null,
+ FocusedStatusId = null,
+ };
}
- var focusedItem = lst.FocusedItem;
- var focusedId = focusedItem != null ? tabInfo.GetId(focusedItem.Index) : -2;
-
- var selectionMarkIndex = lst.SelectionMark;
- var selectionMarkId = selectionMarkIndex != -1 ? tabInfo.GetId(selectionMarkIndex) : -2;
-
- focusedIdDict[tab.Text] = Tuple.Create(focusedId, selectionMarkId);
+ listsDict[tab.TabName] = listStatus;
}
+ return listsDict;
}
- private bool BalloonRequired()
+ private long[] GetSelectedStatusIds(DetailsListView listView, TabModel tab)
{
- Twitter.FormattedEvent ev = new Twitter.FormattedEvent();
- ev.Eventtype = MyCommon.EVENTTYPE.None;
-
- return BalloonRequired(ev);
+ var selectedIndices = listView.SelectedIndices;
+ if (selectedIndices.Count > 0 && selectedIndices.Count < 61)
+ return tab.GetStatusIdAt(selectedIndices.Cast<int>());
+ else
+ return null;
}
- private bool IsEventNotifyAsEventType(MyCommon.EVENTTYPE type)
+ private long? GetFocusedStatusId(DetailsListView listView, TabModel tab)
{
- return this._cfgCommon.EventNotifyEnabled && (type & this._cfgCommon.EventNotifyFlag) != 0 || type == MyCommon.EVENTTYPE.None;
+ var focusedItem = listView.FocusedItem;
+
+ return focusedItem != null ? tab.GetStatusIdAt(focusedItem.Index) : (long?)null;
}
- private bool IsMyEventNotityAsEventType(Twitter.FormattedEvent ev)
+ private long? GetSelectionMarkStatusId(DetailsListView listView, TabModel tab)
{
- return (ev.Eventtype & this._cfgCommon.IsMyEventNotifyFlag) != 0 ? true : !ev.IsMe;
+ var selectionMarkIndex = listView.SelectionMark;
+
+ return selectionMarkIndex != -1 ? tab.GetStatusIdAt(selectionMarkIndex) : (long?)null;
}
- private bool BalloonRequired(Twitter.FormattedEvent ev)
+ /// <summary>
+ /// <see cref="SaveListViewScroll"/> によって保存されたスクロール位置を復元します
+ /// </summary>
+ private void RestoreListViewScroll(DetailsListView listView, TabModel tab, ListViewScroll listScroll)
{
- if ((
- IsEventNotifyAsEventType(ev.Eventtype) && IsMyEventNotityAsEventType(ev) &&
- (NewPostPopMenuItem.Checked || (this._cfgCommon.ForceEventNotify && ev.Eventtype != MyCommon.EVENTTYPE.None)) &&
- !_initial &&
- (
- (
- this._cfgCommon.LimitBalloon &&
- (
- this.WindowState == FormWindowState.Minimized ||
- !this.Visible ||
- Form.ActiveForm == null
- )
- ) ||
- !this._cfgCommon.LimitBalloon
- )
- ) &&
- !NativeMethods.IsScreenSaverRunning())
- {
- return true;
- }
- else
+ if (listView.VirtualListSize == 0)
+ return;
+
+ switch (listScroll.ScrollLockMode)
{
- return false;
+ case ScrollLockMode.FixedToTop:
+ listView.EnsureVisible(0);
+ break;
+ case ScrollLockMode.FixedToBottom:
+ listView.EnsureVisible(listView.VirtualListSize - 1);
+ break;
+ case ScrollLockMode.FixedToItem:
+ var topIndex = listScroll.TopItemStatusId != null ? tab.IndexOf(listScroll.TopItemStatusId.Value) : -1;
+ if (topIndex != -1)
+ listView.TopItem = listView.Items[topIndex];
+ break;
+ case ScrollLockMode.None:
+ default:
+ break;
}
}
- private void NotifyNewPosts(PostClass[] notifyPosts, string soundFile, int addCount, bool newMentions)
+ /// <summary>
+ /// <see cref="SaveListViewStatus"/> によって保存された選択状態を復元します
+ /// </summary>
+ private void RestoreListViewSelection(DetailsListView listView, TabModel tab, ListViewSelection listSelection)
{
- if (notifyPosts != null &&
- notifyPosts.Length > 0 &&
- this._cfgCommon.ReadOwnPost &&
- notifyPosts.All((post) => { return post.UserId == tw.UserId || post.ScreenName == tw.Username; }))
- {
- return;
+ // status_id から ListView 上のインデックスに変換
+ int[] selectedIndices = null;
+ if (listSelection.SelectedStatusIds != null)
+ selectedIndices = tab.IndexOf(listSelection.SelectedStatusIds).Where(x => x != -1).ToArray();
+
+ var focusedIndex = -1;
+ if (listSelection.FocusedStatusId != null)
+ focusedIndex = tab.IndexOf(listSelection.FocusedStatusId.Value);
+
+ var selectionMarkIndex = -1;
+ if (listSelection.SelectionMarkStatusId != null)
+ selectionMarkIndex = tab.IndexOf(listSelection.SelectionMarkStatusId.Value);
+
+ this.SelectListItem(listView, selectedIndices, focusedIndex, selectionMarkIndex);
+ }
+
+ private bool BalloonRequired()
+ {
+ Twitter.FormattedEvent ev = new Twitter.FormattedEvent();
+ ev.Eventtype = MyCommon.EVENTTYPE.None;
+
+ return BalloonRequired(ev);
+ }
+
+ private bool IsEventNotifyAsEventType(MyCommon.EVENTTYPE type)
+ {
+ if (type == MyCommon.EVENTTYPE.None)
+ return true;
+
+ if (!this._cfgCommon.EventNotifyEnabled)
+ return false;
+
+ return this._cfgCommon.EventNotifyFlag.HasFlag(type);
+ }
+
+ private bool IsMyEventNotityAsEventType(Twitter.FormattedEvent ev)
+ {
+ if (!ev.IsMe)
+ return true;
+
+ return this._cfgCommon.IsMyEventNotifyFlag.HasFlag(ev.Eventtype);
+ }
+
+ private bool BalloonRequired(Twitter.FormattedEvent ev)
+ {
+ if (this._initial)
+ return false;
+
+ if (NativeMethods.IsScreenSaverRunning())
+ return false;
+
+ // 「新着通知」が無効
+ if (!this.NewPostPopMenuItem.Checked)
+ {
+ // 「新着通知が無効でもイベントを通知する」にも該当しない
+ if (!this._cfgCommon.ForceEventNotify || ev.Eventtype == MyCommon.EVENTTYPE.None)
+ return false;
+ }
+
+ // 「画面最小化・アイコン時のみバルーンを表示する」が有効
+ if (this._cfgCommon.LimitBalloon)
+ {
+ if (this.WindowState != FormWindowState.Minimized && this.Visible && Form.ActiveForm != null)
+ return false;
+ }
+
+ return this.IsEventNotifyAsEventType(ev.Eventtype) && this.IsMyEventNotityAsEventType(ev);
+ }
+
+ private void NotifyNewPosts(PostClass[] notifyPosts, string soundFile, int addCount, bool newMentions)
+ {
+ if (this._cfgCommon.ReadOwnPost)
+ {
+ if (notifyPosts != null && notifyPosts.Length > 0 && notifyPosts.All(x => x.UserId == tw.UserId))
+ return;
}
//新着通知
//NotifyIcon1.BalloonTipTitle += Application.ProductName + " [DM] " + Properties.Resources.RefreshDirectMessageText1 + " " + addCount.ToString() + Properties.Resources.RefreshDirectMessageText2;
title.Append(Application.ProductName);
title.Append(" [DM] ");
- title.Append(Properties.Resources.RefreshDirectMessageText1);
- title.Append(" ");
- title.Append(addCount);
- title.Append(Properties.Resources.RefreshDirectMessageText2);
+ title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
nt = GrowlHelper.NotifyType.DirectMessage;
}
else if (reply)
//NotifyIcon1.BalloonTipTitle += Application.ProductName + " [Reply!] " + Properties.Resources.RefreshTimelineText1 + " " + addCount.ToString() + Properties.Resources.RefreshTimelineText2;
title.Append(Application.ProductName);
title.Append(" [Reply!] ");
- title.Append(Properties.Resources.RefreshTimelineText1);
- title.Append(" ");
- title.Append(addCount);
- title.Append(Properties.Resources.RefreshTimelineText2);
+ title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
nt = GrowlHelper.NotifyType.Reply;
}
else
//NotifyIcon1.BalloonTipTitle += Application.ProductName + " " + Properties.Resources.RefreshTimelineText1 + " " + addCount.ToString() + Properties.Resources.RefreshTimelineText2;
title.Append(Application.ProductName);
title.Append(" ");
- title.Append(Properties.Resources.RefreshTimelineText1);
- title.Append(" ");
- title.Append(addCount);
- title.Append(Properties.Resources.RefreshTimelineText2);
+ title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
nt = GrowlHelper.NotifyType.Notify;
}
string bText = sb.ToString();
ntIcon = ToolTipIcon.Warning;
title.Append(Application.ProductName);
title.Append(" [DM] ");
- title.Append(Properties.Resources.RefreshDirectMessageText1);
- title.Append(" ");
- title.Append(addCount);
- title.Append(Properties.Resources.RefreshDirectMessageText2);
+ title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
}
else if (reply)
{
ntIcon = ToolTipIcon.Warning;
title.Append(Application.ProductName);
title.Append(" [Reply!] ");
- title.Append(Properties.Resources.RefreshTimelineText1);
- title.Append(" ");
- title.Append(addCount);
- title.Append(Properties.Resources.RefreshTimelineText2);
+ title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
}
else
{
ntIcon = ToolTipIcon.Info;
title.Append(Application.ProductName);
title.Append(" ");
- title.Append(Properties.Resources.RefreshTimelineText1);
- title.Append(" ");
- title.Append(addCount);
- title.Append(Properties.Resources.RefreshTimelineText2);
+ title.AppendFormat(Properties.Resources.RefreshTimeline_NotifyText, addCount);
}
string bText = sb.ToString();
if (string.IsNullOrEmpty(bText)) return;
try
{
- _curPost = GetCurTabPost(_curItemIndex);
+ this._curPost = GetCurTabPost(_curItemIndex);
}
catch (ArgumentException)
{
if (!tabInfo.UnreadManage ||
!this._cfgCommon.UnreadManage) Read = true;
- //対象の特定
- ListViewItem itm = null;
- PostClass post = null;
-
- this.TryGetListViewItemCache(Index, out itm, out post);
+ var listCache = this._listItemCache;
+ if (listCache == null)
+ return;
// キャッシュに含まれていないアイテムは対象外
- if (itm == null)
+ ListViewItem itm;
+ PostClass post;
+ if (!listCache.TryGetValue(Index, out itm, out post))
return;
ChangeItemStyleRead(Read, itm, post, ((DetailsListView)_curTab.Tag));
if (_post == null) return;
- var itemColors = new Color[] { };
- int itemIndex = -1;
-
- this.itemCacheLock.EnterReadLock();
- try
- {
- if (this._itemCache == null) return;
-
- var query =
- from i in Enumerable.Range(0, this._itemCache.Length)
- select this.JudgeColor(_post, this._postCache[i]);
-
- itemColors = query.ToArray();
- itemIndex = _itemCacheIndex;
- }
- finally { this.itemCacheLock.ExitReadLock(); }
-
- if (itemIndex < 0) return;
+ var listCache = this._listItemCache;
+ if (listCache == null)
+ return;
- foreach (var backColor in itemColors)
+ var index = listCache.StartIndex;
+ foreach (var cachedPost in listCache.Post)
{
- // この処理中に MyList_CacheVirtualItems が呼ばれることがあるため、
- // 同一スレッド内での二重ロックを避けるためにロックの外で実行する必要がある
- _curList.ChangeItemBackColor(itemIndex++, backColor);
+ var backColor = this.JudgeColor(_post, cachedPost);
+ this._curList.ChangeItemBackColor(index++, backColor);
}
}
else if (TargetPost.IsReply)
//自分宛返信
cl = _clAtSelf;
- else if (BasePost.ReplyToList.Contains(TargetPost.ScreenName.ToLower()))
+ else if (BasePost.ReplyToList.Contains(TargetPost.ScreenName.ToLowerInvariant()))
//返信先
cl = _clAtFromTarget;
- else if (TargetPost.ReplyToList.Contains(BasePost.ScreenName.ToLower()))
+ else if (TargetPost.ReplyToList.Contains(BasePost.ScreenName.ToLowerInvariant()))
//その人への返信
cl = _clAtTarget;
else if (TargetPost.ScreenName.Equals(BasePost.ScreenName, StringComparison.OrdinalIgnoreCase))
try
{
- var progress = new Progress<string>(x => this.StatusLabel.Text = x);
+ var homeTab = this._statuses.GetTabByType<HomeTabModel>();
+ await homeTab.RefreshAsync(this.tw, loadMore, this._initial, this.workerProgress);
- await this.GetHomeTimelineAsyncInternal(progress, this.workerCts.Token, loadMore);
+ this.RefreshTimeline();
}
catch (WebApiException ex)
{
this._myStatusError = true;
- this.StatusLabel.Text = ex.Message;
+ this.StatusLabel.Text = $"Err:{ex.Message}(GetTimeline)";
}
finally
{
}
}
- private async Task GetHomeTimelineAsyncInternal(IProgress<string> p, CancellationToken ct, bool loadMore)
- {
- if (ct.IsCancellationRequested)
- return;
-
- if (!CheckAccountValid())
- throw new WebApiException("Auth error. Check your account");
-
- bool read;
- if (!this._cfgCommon.UnreadManage)
- read = true;
- else
- read = this._initial && this._cfgCommon.Read;
-
- p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText5 +
- (loadMore ? "-1" : "1") +
- Properties.Resources.GetTimelineWorker_RunWorkerCompletedText6);
-
- await Task.Run(() =>
- {
- this.tw.GetTimelineApi(read, MyCommon.WORKERTYPE.Timeline, loadMore, this._initial);
-
- // 新着時未読クリア
- if (this._cfgCommon.ReadOldPosts)
- this._statuses.SetReadHomeTab();
-
- var addCount = this._statuses.DistributePosts();
-
- if (!this._initial)
- {
- lock (this._syncObject)
- {
- var tm = DateTime.Now;
- if (this._tlTimestamps.ContainsKey(tm))
- this._tlTimestamps[tm] += addCount;
- else
- this._tlTimestamps[tm] = addCount;
-
- var removeKeys = new List<DateTime>();
- var oneHour = DateTime.Now - TimeSpan.FromHours(1);
-
- this._tlCount = 0;
- foreach (var pair in this._tlTimestamps)
- {
- if (pair.Key < oneHour)
- removeKeys.Add(pair.Key);
- else
- this._tlCount += pair.Value;
- }
-
- foreach (var key in removeKeys)
- this._tlTimestamps.Remove(key);
- }
- }
- });
-
- if (ct.IsCancellationRequested)
- return;
-
- p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText1);
-
- this.RefreshTimeline(false);
- }
-
private Task GetReplyAsync()
{
return this.GetReplyAsync(loadMore: false);
try
{
- var progress = new Progress<string>(x => this.StatusLabel.Text = x);
+ var replyTab = this._statuses.GetTabByType<MentionsTabModel>();
+ await replyTab.RefreshAsync(this.tw, loadMore, this._initial, this.workerProgress);
- await this.GetReplyAsyncInternal(progress, this.workerCts.Token, loadMore);
+ this.RefreshTimeline();
}
catch (WebApiException ex)
{
this._myStatusError = true;
- this.StatusLabel.Text = ex.Message;
+ this.StatusLabel.Text = $"Err:{ex.Message}(GetTimeline)";
}
finally
{
}
}
- private async Task GetReplyAsyncInternal(IProgress<string> p, CancellationToken ct, bool loadMore)
- {
- if (ct.IsCancellationRequested)
- return;
-
- if (!CheckAccountValid())
- throw new WebApiException("Auth error. Check your account");
-
- bool read;
- if (!this._cfgCommon.UnreadManage)
- read = true;
- else
- read = this._initial && this._cfgCommon.Read;
-
- p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText4 +
- (loadMore ? "-1" : "1") +
- Properties.Resources.GetTimelineWorker_RunWorkerCompletedText6);
-
- await Task.Run(() =>
- {
- this.tw.GetTimelineApi(read, MyCommon.WORKERTYPE.Reply, loadMore, this._initial);
-
- this._statuses.DistributePosts();
- });
-
- if (ct.IsCancellationRequested)
- return;
-
- p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText9);
-
- this.RefreshTimeline(false);
- }
-
private Task GetDirectMessagesAsync()
{
return this.GetDirectMessagesAsync(loadMore: false);
try
{
- var progress = new Progress<string>(x => this.StatusLabel.Text = x);
+ var dmTab = this._statuses.GetTabByType<DirectMessagesTabModel>();
+ await dmTab.RefreshAsync(this.tw, loadMore, this._initial, this.workerProgress);
- await this.GetDirectMessagesAsyncInternal(progress, this.workerCts.Token, loadMore);
+ this.RefreshTimeline();
}
catch (WebApiException ex)
{
this._myStatusError = true;
- this.StatusLabel.Text = ex.Message;
+ this.StatusLabel.Text = $"Err:{ex.Message}(GetDirectMessage)";
}
finally
{
}
}
- private async Task GetDirectMessagesAsyncInternal(IProgress<string> p, CancellationToken ct, bool loadMore)
- {
- if (ct.IsCancellationRequested)
- return;
-
- if (!CheckAccountValid())
- throw new WebApiException("Auth error. Check your account");
-
- bool read;
- if (!this._cfgCommon.UnreadManage)
- read = true;
- else
- read = this._initial && this._cfgCommon.Read;
-
- p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText8 +
- (loadMore ? "-1" : "1") +
- Properties.Resources.GetTimelineWorker_RunWorkerCompletedText6);
-
- await Task.Run(() =>
- {
- this.tw.GetDirectMessageApi(read, MyCommon.WORKERTYPE.DirectMessegeRcv, loadMore);
- this.tw.GetDirectMessageApi(read, MyCommon.WORKERTYPE.DirectMessegeSnt, loadMore);
-
- this._statuses.DistributePosts();
- });
-
- if (ct.IsCancellationRequested)
- return;
-
- p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText11);
-
- this.RefreshTimeline(false);
- }
-
private Task GetFavoritesAsync()
{
return this.GetFavoritesAsync(loadMore: false);
try
{
- var progress = new Progress<string>(x => this.StatusLabel.Text = x);
+ var favTab = this._statuses.GetTabByType<FavoritesTabModel>();
+ await favTab.RefreshAsync(this.tw, loadMore, this._initial, this.workerProgress);
- await this.GetFavoritesAsyncInternal(progress, this.workerCts.Token, loadMore);
+ this.RefreshTimeline();
}
catch (WebApiException ex)
{
}
}
- private async Task GetFavoritesAsyncInternal(IProgress<string> p, CancellationToken ct, bool loadMore)
+ private Task GetPublicSearchAllAsync()
{
- if (ct.IsCancellationRequested)
- return;
-
- if (!CheckAccountValid())
- throw new WebApiException("Auth error. Check your account");
-
- bool read;
- if (!this._cfgCommon.UnreadManage)
- read = true;
- else
- read = this._initial && this._cfgCommon.Read;
+ var tabs = this._statuses.GetTabsByType<PublicSearchTabModel>();
- p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText19);
-
- await Task.Run(() =>
- {
- this.tw.GetFavoritesApi(read, loadMore);
-
- this._statuses.DistributePosts();
- });
-
- if (ct.IsCancellationRequested)
- return;
-
- p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText20);
-
- this.RefreshTimeline(false);
+ return this.GetPublicSearchAsync(tabs, loadMore: false);
}
- private Task GetPublicSearchAllAsync()
+ private Task GetPublicSearchAsync(PublicSearchTabModel tab)
{
- return this.GetPublicSearchAsync(null, loadMore: false);
+ return this.GetPublicSearchAsync(tab, loadMore: false);
}
- private Task GetPublicSearchAsync(TabClass tab)
+ private Task GetPublicSearchAsync(PublicSearchTabModel tab, bool loadMore)
{
- return this.GetPublicSearchAsync(tab, loadMore: false);
+ return this.GetPublicSearchAsync(new[] { tab }, loadMore);
}
- private async Task GetPublicSearchAsync(TabClass tab, bool loadMore)
+ private async Task GetPublicSearchAsync(IEnumerable<PublicSearchTabModel> tabs, bool loadMore)
{
await this.workerSemaphore.WaitAsync();
try
{
- var progress = new Progress<string>(x => this.StatusLabel.Text = x);
-
- var tabs = tab != null
- ? new[] { tab }.AsEnumerable()
- : this._statuses.GetTabsByType(MyCommon.TabUsageType.PublicSearch);
-
- await this.GetPublicSearchAsyncInternal(progress, this.workerCts.Token, tabs, loadMore);
- }
- catch (WebApiException ex)
- {
- this._myStatusError = true;
- this.StatusLabel.Text = ex.Message;
- }
- finally
- {
- this.workerSemaphore.Release();
- }
- }
-
- private async Task GetPublicSearchAsyncInternal(IProgress<string> p, CancellationToken ct, IEnumerable<TabClass> tabs, bool loadMore)
- {
- if (ct.IsCancellationRequested)
- return;
-
- if (!CheckAccountValid())
- throw new WebApiException("Auth error. Check your account");
-
- bool read;
- if (!this._cfgCommon.UnreadManage)
- read = true;
- else
- read = this._initial && this._cfgCommon.Read;
-
- p.Report("Search refreshing...");
-
- await Task.Run(() =>
- {
- WebApiException lastException = null;
-
foreach (var tab in tabs)
{
try
{
- if (string.IsNullOrEmpty(tab.SearchWords))
- continue;
-
- this.tw.GetSearch(read, tab, false);
-
- if (loadMore)
- this.tw.GetSearch(read, tab, true);
+ await tab.RefreshAsync(this.tw, loadMore, this._initial, this.workerProgress);
}
catch (WebApiException ex)
{
- lastException = ex;
+ this._myStatusError = true;
+ this.StatusLabel.Text = $"Err:{ex.Message}(GetSearch)";
}
}
- this._statuses.DistributePosts();
-
- if (lastException != null)
- throw new WebApiException(lastException.Message, lastException);
- });
-
- if (ct.IsCancellationRequested)
- return;
-
- p.Report("Search refreshed");
-
- this.RefreshTimeline(false);
+ this.RefreshTimeline();
+ }
+ finally
+ {
+ this.workerSemaphore.Release();
+ }
}
private Task GetUserTimelineAllAsync()
{
- return this.GetUserTimelineAsync(null, loadMore: false);
+ var tabs = this._statuses.GetTabsByType<UserTimelineTabModel>();
+
+ return this.GetUserTimelineAsync(tabs, loadMore: false);
}
- private Task GetUserTimelineAsync(TabClass tab)
+ private Task GetUserTimelineAsync(UserTimelineTabModel tab)
{
return this.GetUserTimelineAsync(tab, loadMore: false);
}
- private async Task GetUserTimelineAsync(TabClass tab, bool loadMore)
+ private Task GetUserTimelineAsync(UserTimelineTabModel tab, bool loadMore)
{
- await this.workerSemaphore.WaitAsync();
-
- try
- {
- var progress = new Progress<string>(x => this.StatusLabel.Text = x);
-
- var tabs = tab != null
- ? new[] { tab }.AsEnumerable()
- : this._statuses.GetTabsByType(MyCommon.TabUsageType.UserTimeline);
-
- await this.GetUserTimelineAsyncInternal(progress, this.workerCts.Token, tabs, loadMore);
- }
- catch (WebApiException ex)
- {
- this._myStatusError = true;
- this.StatusLabel.Text = ex.Message;
- }
- finally
- {
- this.workerSemaphore.Release();
- }
+ return this.GetUserTimelineAsync(new[] { tab }, loadMore);
}
- private async Task GetUserTimelineAsyncInternal(IProgress<string> p, CancellationToken ct, IEnumerable<TabClass> tabs, bool loadMore)
+ private async Task GetUserTimelineAsync(IEnumerable<UserTimelineTabModel> tabs, bool loadMore)
{
- if (ct.IsCancellationRequested)
- return;
-
- if (!CheckAccountValid())
- throw new WebApiException("Auth error. Check your account");
-
- bool read;
- if (!this._cfgCommon.UnreadManage)
- read = true;
- else
- read = this._initial && this._cfgCommon.Read;
-
- p.Report("UserTimeline refreshing...");
+ await this.workerSemaphore.WaitAsync();
- await Task.Run(() =>
+ try
{
- WebApiException lastException = null;
-
foreach (var tab in tabs)
{
try
{
- if (string.IsNullOrEmpty(tab.User))
- continue;
-
- this.tw.GetUserTimelineApi(read, tab.User, tab, loadMore);
+ await tab.RefreshAsync(this.tw, loadMore, this._initial, this.workerProgress);
}
catch (WebApiException ex)
{
- lastException = ex;
+ this._myStatusError = true;
+ this.StatusLabel.Text = $"Err:{ex.Message}(GetUserTimeline)";
}
}
- this._statuses.DistributePosts();
-
- if (lastException != null)
- throw new WebApiException(lastException.Message, lastException);
- });
-
- if (ct.IsCancellationRequested)
- return;
-
- p.Report("UserTimeline refreshed");
-
- this.RefreshTimeline(false);
+ this.RefreshTimeline();
+ }
+ finally
+ {
+ this.workerSemaphore.Release();
+ }
}
private Task GetListTimelineAllAsync()
{
- return this.GetListTimelineAsync(null, loadMore: false);
+ var tabs = this._statuses.GetTabsByType<ListTimelineTabModel>();
+
+ return this.GetListTimelineAsync(tabs, loadMore: false);
}
- private Task GetListTimelineAsync(TabClass tab)
+ private Task GetListTimelineAsync(ListTimelineTabModel tab)
{
return this.GetListTimelineAsync(tab, loadMore: false);
}
- private async Task GetListTimelineAsync(TabClass tab, bool loadMore)
+ private Task GetListTimelineAsync(ListTimelineTabModel tab, bool loadMore)
{
- await this.workerSemaphore.WaitAsync();
-
- try
- {
- var progress = new Progress<string>(x => this.StatusLabel.Text = x);
-
- var tabs = tab != null
- ? new[] { tab }.AsEnumerable()
- : this._statuses.GetTabsByType(MyCommon.TabUsageType.Lists);
-
- await this.GetListTimelineAsyncInternal(progress, this.workerCts.Token, tabs, loadMore);
- }
- catch (WebApiException ex)
- {
- this._myStatusError = true;
- this.StatusLabel.Text = ex.Message;
- }
- finally
- {
- this.workerSemaphore.Release();
- }
+ return this.GetListTimelineAsync(new[] { tab }, loadMore);
}
- private async Task GetListTimelineAsyncInternal(IProgress<string> p, CancellationToken ct, IEnumerable<TabClass> tabs, bool loadMore)
+ private async Task GetListTimelineAsync(IEnumerable<ListTimelineTabModel> tabs, bool loadMore)
{
- if (ct.IsCancellationRequested)
- return;
-
- if (!CheckAccountValid())
- throw new WebApiException("Auth error. Check your account");
-
- bool read;
- if (!this._cfgCommon.UnreadManage)
- read = true;
- else
- read = this._initial && this._cfgCommon.Read;
-
- p.Report("List refreshing...");
+ await this.workerSemaphore.WaitAsync();
- await Task.Run(() =>
+ try
{
- WebApiException lastException = null;
-
foreach (var tab in tabs)
{
try
{
- if (tab.ListInfo == null || tab.ListInfo.Id == 0)
- continue;
-
- this.tw.GetListStatus(read, tab, loadMore, this._initial);
+ await tab.RefreshAsync(this.tw, loadMore, this._initial, this.workerProgress);
}
catch (WebApiException ex)
{
- lastException = ex;
+ this._myStatusError = true;
+ this.StatusLabel.Text = $"Err:{ex.Message}(GetListStatus)";
}
}
- this._statuses.DistributePosts();
-
- if (lastException != null)
- throw new WebApiException(lastException.Message, lastException);
- });
-
- if (ct.IsCancellationRequested)
- return;
-
- p.Report("List refreshed");
-
- this.RefreshTimeline(false);
+ this.RefreshTimeline();
+ }
+ finally
+ {
+ this.workerSemaphore.Release();
+ }
}
- private async Task GetRelatedTweetsAsync(TabClass tab)
+ private async Task GetRelatedTweetsAsync(RelatedPostsTabModel tab)
{
await this.workerSemaphore.WaitAsync();
try
{
- var progress = new Progress<string>(x => this.StatusLabel.Text = x);
+ await tab.RefreshAsync(this.tw, this._initial, this.workerProgress);
- await this.GetRelatedTweetsAsyncInternal(progress, this.workerCts.Token, tab);
+ this.RefreshTimeline();
}
catch (WebApiException ex)
{
this._myStatusError = true;
- this.StatusLabel.Text = ex.Message;
+ this.StatusLabel.Text = $"Err:{ex.Message}(GetRelatedTweets)";
}
finally
{
}
}
- private async Task GetRelatedTweetsAsyncInternal(IProgress<string> p, CancellationToken ct, TabClass tab)
- {
- if (ct.IsCancellationRequested)
- return;
-
- if (!CheckAccountValid())
- throw new WebApiException("Auth error. Check your account");
-
- bool read;
- if (!this._cfgCommon.UnreadManage)
- read = true;
- else
- read = this._initial && this._cfgCommon.Read;
-
- p.Report("Related refreshing...");
-
- await Task.Run(() =>
- {
- this.tw.GetRelatedResult(read, tab);
-
- this._statuses.DistributePosts();
- });
-
- if (ct.IsCancellationRequested)
- return;
-
- p.Report("Related refreshed");
-
- this.RefreshTimeline(false);
-
- var tabPage = this.ListTab.TabPages.Cast<TabPage>()
- .FirstOrDefault(x => x.Text == tab.TabName);
-
- if (tabPage != null)
- {
- // TODO: 非同期更新中にタブが閉じられている場合を厳密に考慮したい
-
- var listView = (DetailsListView)tabPage.Tag;
- var index = tab.IndexOf(tab.RelationTargetPost.RetweetedId ?? tab.RelationTargetPost.StatusId);
-
- if (index != -1 && index < listView.Items.Count)
- {
- listView.SelectedIndices.Add(index);
- listView.Items[index].Focused = true;
- }
- }
- }
-
- private async Task FavAddAsync(IReadOnlyList<long> statusIds, TabClass tab)
+ private async Task FavAddAsync(long statusId, TabModel tab)
{
await this.workerSemaphore.WaitAsync();
{
var progress = new Progress<string>(x => this.StatusLabel.Text = x);
- await this.FavAddAsyncInternal(progress, this.workerCts.Token, statusIds, tab);
+ await this.FavAddAsyncInternal(progress, this.workerCts.Token, statusId, tab);
}
catch (WebApiException ex)
{
this._myStatusError = true;
- this.StatusLabel.Text = ex.Message;
+ this.StatusLabel.Text = $"Err:{ex.Message}(PostFavAdd)";
}
finally
{
}
}
- private async Task FavAddAsyncInternal(IProgress<string> p, CancellationToken ct, IReadOnlyList<long> statusIds, TabClass tab)
+ private async Task FavAddAsyncInternal(IProgress<string> p, CancellationToken ct, long statusId, TabModel tab)
{
if (ct.IsCancellationRequested)
return;
if (!CheckAccountValid())
throw new WebApiException("Auth error. Check your account");
- var successIds = new List<long>();
+ PostClass post;
+ if (!tab.Posts.TryGetValue(statusId, out post))
+ return;
- await Task.Run(() =>
+ if (post.IsFav)
+ return;
+
+ await Task.Run(async () =>
{
- //スレッド処理はしない
- var allCount = 0;
- var failedCount = 0;
+ p.Report(string.Format(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText15, 0, 1, 0));
- foreach (var statusId in statusIds)
+ try
{
- allCount++;
-
- p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText15 +
- allCount + "/" + statusIds.Count +
- Properties.Resources.GetTimelineWorker_RunWorkerCompletedText16 +
- failedCount);
-
- var post = tab.Posts[statusId];
-
- if (post.IsFav)
- continue;
+ await this.twitterApi.FavoritesCreate(post.RetweetedId ?? post.StatusId)
+ .IgnoreResponse()
+ .ConfigureAwait(false);
- try
+ if (this._cfgCommon.RestrictFavCheck)
{
- this.tw.PostFavAdd(post.RetweetedId ?? post.StatusId);
- }
- catch (WebApiException)
- {
- failedCount++;
- continue;
- }
+ var status = await this.twitterApi.StatusesShow(post.RetweetedId ?? post.StatusId)
+ .ConfigureAwait(false);
- successIds.Add(statusId);
- post.IsFav = true; // リスト再描画必要
+ if (status.Favorited != true)
+ throw new WebApiException("NG(Restricted?)");
+ }
this._favTimestamps.Add(DateTime.Now);
postTl.IsFav = true;
var favTab = this._statuses.GetTabByType(MyCommon.TabUsageType.Favorites);
- favTab.AddPostImmediately(statusId, postTl.IsRead);
+ favTab.AddPostQueue(postTl);
}
// 検索,リスト,UserTimeline,Relatedの各タブに反映
if (tb.Contains(statusId))
tb.Posts[statusId].IsFav = true;
}
+
+ p.Report(string.Format(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText15, 1, 1, 0));
+ }
+ catch (WebApiException)
+ {
+ p.Report(string.Format(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText15, 1, 1, 1));
+ throw;
}
// 時速表示用
if (ct.IsCancellationRequested)
return;
- this.RefreshTimeline(false);
+ this.RefreshTimeline();
if (this._curList != null && this._curTab != null && this._curTab.Text == tab.TabName)
{
using (ControlTransaction.Update(this._curList))
{
- foreach (var statusId in successIds)
- {
- var idx = tab.IndexOf(statusId);
- if (idx == -1)
- continue;
-
- var post = tab.Posts[statusId];
+ var idx = tab.IndexOf(statusId);
+ if (idx != -1)
this.ChangeCacheStyleRead(post.IsRead, idx);
- }
}
- if (successIds.Contains(this._curPost.StatusId))
+ if (statusId == this._curPost.StatusId)
await this.DispSelectedPost(true); // 選択アイテム再表示
}
}
- private async Task FavRemoveAsync(IReadOnlyList<long> statusIds, TabClass tab)
+ private async Task FavRemoveAsync(IReadOnlyList<long> statusIds, TabModel tab)
{
await this.workerSemaphore.WaitAsync();
catch (WebApiException ex)
{
this._myStatusError = true;
- this.StatusLabel.Text = ex.Message;
+ this.StatusLabel.Text = $"Err:{ex.Message}(PostFavRemove)";
}
finally
{
}
}
- private async Task FavRemoveAsyncInternal(IProgress<string> p, CancellationToken ct, IReadOnlyList<long> statusIds, TabClass tab)
+ private async Task FavRemoveAsyncInternal(IProgress<string> p, CancellationToken ct, IReadOnlyList<long> statusIds, TabModel tab)
{
if (ct.IsCancellationRequested)
return;
var successIds = new List<long>();
- await Task.Run(() =>
+ await Task.Run(async () =>
{
//スレッド処理はしない
var allCount = 0;
var post = tab.Posts[statusId];
- p.Report(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText17 +
- allCount + "/" + statusIds.Count +
- Properties.Resources.GetTimelineWorker_RunWorkerCompletedText18 +
- failedCount);
+ p.Report(string.Format(Properties.Resources.GetTimelineWorker_RunWorkerCompletedText17, allCount, statusIds.Count, failedCount));
if (!post.IsFav)
continue;
try
{
- this.tw.PostFavRemove(post.RetweetedId ?? post.StatusId);
+ await this.twitterApi.FavoritesDestroy(post.RetweetedId ?? post.StatusId)
+ .IgnoreResponse()
+ .ConfigureAwait(false);
}
catch (WebApiException)
{
if (ct.IsCancellationRequested)
return;
- this.RemovePostFromFavTab(successIds.ToArray());
+ var favTab = this._statuses.GetTabByType(MyCommon.TabUsageType.Favorites);
+ foreach (var statusId in successIds)
+ {
+ // ツイートが削除された訳ではないので IsDeleted はセットしない
+ favTab.EnqueueRemovePost(statusId, setIsDeleted: false);
+ }
- this.RefreshTimeline(false);
+ this.RefreshTimeline();
if (this._curList != null && this._curTab != null && this._curTab.Text == tab.TabName)
{
catch (WebApiException ex)
{
this._myStatusError = true;
- this.StatusLabel.Text = ex.Message;
+ this.StatusLabel.Text = $"Err:{ex.Message}(PostMessage)";
}
finally
{
{
if (status.mediaItems == null || status.mediaItems.Length == 0)
{
- this.tw.PostStatus(status.status, status.inReplyToId);
+ await this.tw.PostStatus(status.status, status.inReplyToId)
+ .ConfigureAwait(false);
}
else
{
catch (WebApiException ex)
{
// 処理は中断せずエラーの表示のみ行う
- errMsg = ex.Message;
+ errMsg = $"Err:{ex.Message}(PostMessage)";
p.Report(errMsg);
this._myStatusError = true;
}
if (this._cfgCommon.PostAndGet)
{
- if (this._isActiveUserstream)
- this.RefreshTimeline(true);
+ if (this.tw.UserStreamActive)
+ this.RefreshTimeline();
else
await this.GetHomeTimelineAsync();
}
catch (WebApiException ex)
{
this._myStatusError = true;
- this.StatusLabel.Text = ex.Message;
+ this.StatusLabel.Text = $"Err:{ex.Message}(PostRetweet)";
}
finally
{
p.Report("Posting...");
- await Task.Run(() =>
- {
- foreach (var statusId in statusIds)
- {
- this.tw.PostRetweet(statusId, read);
- }
- });
+ var retweetTasks = from statusId in statusIds
+ select this.tw.PostRetweet(statusId, read);
+
+ await Task.WhenAll(retweetTasks)
+ .ConfigureAwait(false);
if (ct.IsCancellationRequested)
return;
this._postTimestamps.RemoveAt(i);
}
- if (this._cfgCommon.PostAndGet && !this._isActiveUserstream)
+ if (this._cfgCommon.PostAndGet && !this.tw.UserStreamActive)
await this.GetHomeTimelineAsync();
}
{
this.StatusLabel.Text = Properties.Resources.UpdateFollowersMenuItem1_ClickText1;
- await Task.Run(() => tw.RefreshFollowerIds());
+ await this.tw.RefreshFollowerIds();
this.StatusLabel.Text = Properties.Resources.UpdateFollowersMenuItem1_ClickText3;
- this.RefreshTimeline(false);
+ this.RefreshTimeline();
this.PurgeListViewItemCache();
this._curList?.Refresh();
}
catch (WebApiException ex)
{
- this.StatusLabel.Text = ex.Message;
+ this.StatusLabel.Text = $"Err:{ex.Message}(RefreshFollowersIds)";
}
finally
{
await this.workerSemaphore.WaitAsync();
try
{
- await Task.Run(() => tw.RefreshNoRetweetIds());
+ await this.tw.RefreshNoRetweetIds();
this.StatusLabel.Text = "NoRetweetIds refreshed";
}
catch (WebApiException ex)
{
- this.StatusLabel.Text = ex.Message;
+ this.StatusLabel.Text = $"Err:{ex.Message}(RefreshNoRetweetIds)";
}
finally
{
{
this.StatusLabel.Text = Properties.Resources.UpdateBlockUserText1;
- await Task.Run(() => tw.RefreshBlockIds());
+ await this.tw.RefreshBlockIds();
this.StatusLabel.Text = Properties.Resources.UpdateBlockUserText3;
}
catch (WebApiException ex)
{
- this.StatusLabel.Text = ex.Message;
+ this.StatusLabel.Text = $"Err:{ex.Message}(RefreshBlockIds)";
}
finally
{
await this.workerSemaphore.WaitAsync();
try
{
- await Task.Run(() => tw.RefreshConfiguration());
+ await this.tw.RefreshConfiguration();
if (this.tw.Configuration.PhotoSizeLimit != 0)
{
}
catch (WebApiException ex)
{
- this.StatusLabel.Text = ex.Message;
+ this.StatusLabel.Text = $"Err:{ex.Message}(RefreshConfiguration)";
}
finally
{
this.StatusLabel.Text = Properties.Resources.UpdateMuteUserIds_Finish;
}
- private void RemovePostFromFavTab(Int64[] ids)
- {
- var favTab = this._statuses.GetTabByType(MyCommon.TabUsageType.Favorites);
- string favTabName = favTab.TabName;
- int fidx = 0;
- if (_curTab.Text.Equals(favTabName))
- {
- fidx = _curList.FocusedItem?.Index ?? _curList.TopItem?.Index ?? 0;
- }
-
- foreach (long i in ids)
- {
- try
- {
- _statuses.RemoveFavPost(i);
- }
- catch (Exception)
- {
- continue;
- }
- }
- if (_curTab != null && _curTab.Text.Equals(favTabName))
- {
- this.PurgeListViewItemCache();
- _curPost = null;
- //_curItemIndex = -1;
- }
- foreach (TabPage tp in ListTab.TabPages)
- {
- if (tp.Text == favTabName)
- {
- ((DetailsListView)tp.Tag).VirtualListSize = favTab.AllCount;
- break;
- }
- }
- if (_curTab.Text.Equals(favTabName))
- {
- do
- {
- _curList.SelectedIndices.Clear();
- }
- while (_curList.SelectedIndices.Count > 0);
-
- if (favTab.AllCount > 0)
- {
- if (favTab.AllCount - 1 > fidx && fidx > -1)
- {
- _curList.SelectedIndices.Add(fidx);
- }
- else
- {
- _curList.SelectedIndices.Add(favTab.AllCount - 1);
- }
- if (_curList.SelectedIndices.Count > 0)
- {
- _curList.EnsureVisible(_curList.SelectedIndices[0]);
- _curList.FocusedItem = _curList.Items[_curList.SelectedIndices[0]];
- }
- }
- }
- }
-
private void NotifyIcon1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
await this.FavoritesRetweetUnofficial();
}
- private async Task FavoriteChange(bool FavAdd , bool multiFavoriteChangeDialogEnable = true)
+ private async Task FavoriteChange(bool FavAdd, bool multiFavoriteChangeDialogEnable = true)
{
- TabClass tab;
+ TabModel tab;
if (!this._statuses.Tabs.TryGetValue(this._curTab.Text, out tab))
return;
if (tab.TabType == MyCommon.TabUsageType.DirectMessage || _curList.SelectedIndices.Count == 0
|| !this.ExistCurrentPost) return;
- // 誤ふぁぼ・故意によるふぁぼ爆対策 (変更しないこと)
- // https://support.twitter.com/articles/76915#favoriting
- const int MultiSelectFavLimit = 1;
-
- //複数fav確認msg
- if (_curList.SelectedIndices.Count > MultiSelectFavLimit && FavAdd)
- {
- MessageBox.Show(string.Format(Properties.Resources.FavoriteLimitCountText, MultiSelectFavLimit));
- _DoFavRetweetFlags = false;
- return;
- }
- else if (multiFavoriteChangeDialogEnable && _curList.SelectedIndices.Count > 1)
+ if (this._curList.SelectedIndices.Count > 1)
{
if (FavAdd)
{
- string QuestionText = Properties.Resources.FavAddToolStripMenuItem_ClickText1;
- if (_DoFavRetweetFlags) QuestionText = Properties.Resources.FavoriteRetweetQuestionText3;
- if (MessageBox.Show(QuestionText, Properties.Resources.FavAddToolStripMenuItem_ClickText2,
- MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
- {
- _DoFavRetweetFlags = false;
- return;
- }
+ // 複数ツイートの一括ふぁぼは禁止
+ // https://support.twitter.com/articles/76915#favoriting
+ MessageBox.Show(string.Format(Properties.Resources.FavoriteLimitCountText, 1));
+ _DoFavRetweetFlags = false;
+ return;
}
else
{
- if (MessageBox.Show(Properties.Resources.FavRemoveToolStripMenuItem_ClickText1, Properties.Resources.FavRemoveToolStripMenuItem_ClickText2,
- MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
+ if (multiFavoriteChangeDialogEnable)
{
- return;
+ var confirm = MessageBox.Show(Properties.Resources.FavRemoveToolStripMenuItem_ClickText1,
+ Properties.Resources.FavRemoveToolStripMenuItem_ClickText2,
+ MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
+
+ if (confirm == DialogResult.Cancel)
+ return;
}
}
}
- var statusIds = new List<long>();
- foreach (int idx in _curList.SelectedIndices)
+ if (FavAdd)
{
- PostClass post = GetCurTabPost(idx);
- if (FavAdd)
- {
- if (!post.IsFav)
- statusIds.Add(post.StatusId);
- }
- else
+ var selectedPost = this.GetCurTabPost(_curList.SelectedIndices[0]);
+ if (selectedPost.IsFav)
{
- if (post.IsFav)
- statusIds.Add(post.StatusId);
+ this.StatusLabel.Text = Properties.Resources.FavAddToolStripMenuItem_ClickText4;
+ return;
}
+
+ await this.FavAddAsync(selectedPost.StatusId, tab);
}
- if (statusIds.Count == 0)
+ else
{
- if (FavAdd)
- StatusLabel.Text = Properties.Resources.FavAddToolStripMenuItem_ClickText4;
- else
- StatusLabel.Text = Properties.Resources.FavRemoveToolStripMenuItem_ClickText4;
+ var selectedPosts = this._curList.SelectedIndices.Cast<int>()
+ .Select(x => this.GetCurTabPost(x))
+ .Where(x => x.IsFav);
- return;
- }
+ var statusIds = selectedPosts.Select(x => x.StatusId).ToArray();
+ if (statusIds.Length == 0)
+ {
+ this.StatusLabel.Text = Properties.Resources.FavRemoveToolStripMenuItem_ClickText4;
+ return;
+ }
- if (FavAdd)
- await this.FavAddAsync(statusIds, tab);
- else
await this.FavRemoveAsync(statusIds, tab);
+ }
}
private PostClass GetCurTabPost(int Index)
{
- this.itemCacheLock.EnterReadLock();
- try
+ var listCache = this._listItemCache;
+ if (listCache != null)
{
- if (_postCache != null && Index >= _itemCacheIndex && Index < _itemCacheIndex + _postCache.Length)
- return _postCache[Index - _itemCacheIndex];
+ ListViewItem item;
+ PostClass post;
+ if (listCache.TryGetValue(Index, out item, out post))
+ return post;
}
- finally { this.itemCacheLock.ExitReadLock(); }
return _statuses.Tabs[_curTab.Text][Index];
}
-
private async void MoveToHomeToolStripMenuItem_Click(object sender, EventArgs e)
{
if (_curList.SelectedIndices.Count > 0)
_mySpDis = this.SplitContainer1.SplitterDistance;
_mySpDis3 = this.SplitContainer3.SplitterDistance;
if (StatusText.Multiline) _mySpDis2 = this.StatusText.Height;
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
}
}
}
{
// 表示上の列の位置から ColumnHeader を求める
var col = this._curList.Columns.Cast<ColumnHeader>()
- .Where(x => x.DisplayIndex == columnIndex)
- .FirstOrDefault();
+ .FirstOrDefault(x => x.DisplayIndex == columnIndex);
if (col == null)
return;
}
list.Refresh();
- this._modifySettingCommon = true;
+ this.ModifySettingCommon = true;
}
private void TweenMain_LocationChanged(object sender, EventArgs e)
if (this.WindowState == FormWindowState.Normal && !_initialLayout)
{
_myLoc = this.DesktopLocation;
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
}
}
MakeReplyOrDirectStatus(false, false);
}
- private void doStatusDelete()
+ private async Task doStatusDelete()
{
if (this._curTab == null || this._curList == null)
return;
{
if (post.IsDm)
{
- this.tw.RemoveDirectMessage(post.StatusId, post);
+ await this.twitterApi.DirectMessagesDestroy(post.StatusId)
+ .IgnoreResponse();
}
else
{
if (post.RetweetedId != null && post.UserId == this.tw.UserId)
// 他人に RT された自分のツイート
- this.tw.RemoveStatus(post.RetweetedId.Value);
+ await this.twitterApi.StatusesDestroy(post.RetweetedId.Value)
+ .IgnoreResponse();
else
// 自分のツイート or 自分が RT したツイート
- this.tw.RemoveStatus(post.StatusId);
+ await this.twitterApi.StatusesDestroy(post.StatusId)
+ .IgnoreResponse();
}
}
catch (WebApiException ex)
continue;
}
- this._statuses.RemovePost(post.StatusId);
+ this._statuses.RemovePostFromAllTabs(post.StatusId, setIsDeleted: true);
}
if (lastException == null)
}
}
- private void DeleteStripMenuItem_Click(object sender, EventArgs e)
+ private async void DeleteStripMenuItem_Click(object sender, EventArgs e)
{
- doStatusDelete();
+ await this.doStatusDelete();
}
private void ReadedStripMenuItem_Click(object sender, EventArgs e)
{
if (_curTab != null)
{
- TabClass tab;
+ TabModel tab;
if (!this._statuses.Tabs.TryGetValue(this._curTab.Text, out tab))
return;
//case MyCommon.TabUsageType.Profile:
//// TODO
case MyCommon.TabUsageType.PublicSearch:
- //// TODO
- if (string.IsNullOrEmpty(tab.SearchWords)) return;
- await this.GetPublicSearchAsync(tab);
+ var searchTab = (PublicSearchTabModel)tab;
+ if (string.IsNullOrEmpty(searchTab.SearchWords)) return;
+ await this.GetPublicSearchAsync(searchTab);
break;
case MyCommon.TabUsageType.UserTimeline:
- await this.GetUserTimelineAsync(tab);
+ await this.GetUserTimelineAsync((UserTimelineTabModel)tab);
break;
case MyCommon.TabUsageType.Lists:
- //// TODO
- if (tab.ListInfo == null || tab.ListInfo.Id == 0) return;
- await this.GetListTimelineAsync(tab);
+ var listTab = (ListTimelineTabModel)tab;
+ if (listTab.ListInfo == null || listTab.ListInfo.Id == 0) return;
+ await this.GetListTimelineAsync(listTab);
break;
default:
await this.GetHomeTimelineAsync();
//ページ指定をマイナス1に
if (_curTab != null)
{
- TabClass tab;
+ TabModel tab;
if (!this._statuses.Tabs.TryGetValue(this._curTab.Text, out tab))
return;
//// TODO
break;
case MyCommon.TabUsageType.PublicSearch:
- // TODO
- if (string.IsNullOrEmpty(tab.SearchWords)) return;
- await this.GetPublicSearchAsync(tab, loadMore: true);
+ var searchTab = (PublicSearchTabModel)tab;
+ if (string.IsNullOrEmpty(searchTab.SearchWords)) return;
+ await this.GetPublicSearchAsync(searchTab, loadMore: true);
break;
case MyCommon.TabUsageType.UserTimeline:
- await this.GetUserTimelineAsync(tab, loadMore: true);
+ await this.GetUserTimelineAsync((UserTimelineTabModel)tab, loadMore: true);
break;
case MyCommon.TabUsageType.Lists:
- //// TODO
- if (tab.ListInfo == null || tab.ListInfo.Id == 0) return;
- await this.GetListTimelineAsync(tab, loadMore: true);
+ var listTab = (ListTimelineTabModel)tab;
+ if (listTab.ListInfo == null || listTab.ListInfo.Id == 0) return;
+ await this.GetListTimelineAsync(listTab, loadMore: true);
break;
default:
await this.GetHomeTimelineAsync(loadMore: true);
settingDialog.IntervalChanged += this.TimerInterval_Changed;
settingDialog.tw = this.tw;
+ settingDialog.twitterApi = this.twitterApi;
+
settingDialog.LoadConfig(this._cfgCommon, this._cfgLocal);
try
ShortUrl.Instance.DisableExpanding = !this._cfgCommon.TinyUrlResolve;
ShortUrl.Instance.BitlyId = this._cfgCommon.BilyUser;
ShortUrl.Instance.BitlyKey = this._cfgCommon.BitlyPwd;
- HttpTwitter.TwitterUrl = _cfgCommon.TwitterUrl;
+ TwitterApiConnection.RestApiHost = this._cfgCommon.TwitterApiHost;
Networking.DefaultTimeout = TimeSpan.FromSeconds(this._cfgCommon.DefaultTimeOut);
Networking.SetWebProxy(this._cfgLocal.ProxyType,
var newAlignment = this._cfgCommon.ViewTabBottom ? TabAlignment.Bottom : TabAlignment.Top;
if (ListTab.Alignment == newAlignment) return;
- //現在の選択状態を退避
- var selId = new Dictionary<string, long[]>();
- var focusedId = new Dictionary<string, Tuple<long, long>>();
- SaveSelectedStatus(selId, focusedId);
+ // 各タブのリスト上の選択位置などを退避
+ var listSelections = this.SaveListViewSelection();
ListTab.Alignment = newAlignment;
- //選択状態を復帰
foreach (TabPage tab in ListTab.TabPages)
{
DetailsListView lst = (DetailsListView)tab.Tag;
- TabClass tabInfo = _statuses.Tabs[tab.Text];
+ TabModel tabInfo = _statuses.Tabs[tab.Text];
using (ControlTransaction.Update(lst))
{
- // status_id から ListView 上のインデックスに変換
- var selectedIndices = selId[tab.Text] != null
- ? tabInfo.IndexOf(selId[tab.Text]).Where(x => x != -1).ToArray()
- : null;
- var focusedIndex = tabInfo.IndexOf(focusedId[tab.Text].Item1);
- var selectionMarkIndex = tabInfo.IndexOf(focusedId[tab.Text].Item2);
-
- this.SelectListItem(lst, selectedIndices, focusedIndex, selectionMarkIndex);
+ // 選択位置などを復元
+ this.RestoreListViewSelection(lst, tabInfo, listSelections[tabInfo.TabName]);
}
}
}
}
}
- private async void PostBrowser_Navigated(object sender, WebBrowserNavigatedEventArgs e)
- {
- if (e.Url.AbsoluteUri != "about:blank")
- {
- await this.DispSelectedPost();
- await this.OpenUriInBrowserAsync(e.Url.OriginalString);
- }
- }
-
- private async void PostBrowser_Navigating(object sender, WebBrowserNavigatingEventArgs e)
- {
- if (e.Url.Scheme == "data")
- {
- StatusLabelUrl.Text = PostBrowser.StatusText.Replace("&", "&&");
- }
- else if (e.Url.AbsoluteUri != "about:blank")
- {
- e.Cancel = true;
- // Ctrlを押しながらリンクを開いた場合は、設定と逆の動作をするフラグを true としておく
- await this.OpenUriAsync( e.Url, MyCommon.IsKeyDown( Keys.Control ) );
- }
- }
-
public void AddNewTabForSearch(string searchWord)
{
//同一検索条件のタブが既に存在すれば、そのタブアクティブにして終了
- foreach (TabClass tb in _statuses.GetTabsByType(MyCommon.TabUsageType.PublicSearch))
+ foreach (var tb in _statuses.GetTabsByType<PublicSearchTabModel>())
{
if (tb.SearchWords == searchWord && string.IsNullOrEmpty(tb.SearchLang))
{
break;
}
//タブ追加
- _statuses.AddTab(tabName, MyCommon.TabUsageType.PublicSearch, null);
- AddNewTab(tabName, false, MyCommon.TabUsageType.PublicSearch);
+ var tab = new PublicSearchTabModel(tabName);
+ _statuses.AddTab(tab);
+ AddNewTab(tab, startup: false);
//追加したタブをアクティブに
ListTab.SelectedIndex = ListTab.TabPages.Count - 1;
//検索条件の設定
public void AddNewTabForUserTimeline(string user)
{
//同一検索条件のタブが既に存在すれば、そのタブアクティブにして終了
- foreach (TabClass tb in _statuses.GetTabsByType(MyCommon.TabUsageType.UserTimeline))
+ foreach (var tb in _statuses.GetTabsByType<UserTimelineTabModel>())
{
- if (tb.User == user)
+ if (tb.ScreenName == user)
{
foreach (TabPage tp in ListTab.TabPages)
{
tabName += "_";
}
//タブ追加
- _statuses.AddTab(tabName, MyCommon.TabUsageType.UserTimeline, null);
- var tab = this._statuses.Tabs[tabName];
- tab.User = user;
- AddNewTab(tabName, false, MyCommon.TabUsageType.UserTimeline);
+ var tab = new UserTimelineTabModel(tabName, user);
+ this._statuses.AddTab(tab);
+ this.AddNewTab(tab, startup: false);
//追加したタブをアクティブに
ListTab.SelectedIndex = ListTab.TabPages.Count - 1;
SaveConfigsTabs();
this.GetUserTimelineAsync(tab);
}
- public bool AddNewTab(string tabName, bool startup, MyCommon.TabUsageType tabType, ListElement listInfo = null)
+ public bool AddNewTab(TabModel tab, bool startup)
{
//重複チェック
foreach (TabPage tb in ListTab.TabPages)
{
- if (tb.Text == tabName) return false;
+ if (tb.Text == tab.TabName) return false;
}
//新規タブ名チェック
- if (tabName == Properties.Resources.AddNewTabText1) return false;
-
- //タブタイプ重複チェック
- if (!startup)
- {
- if (tabType == MyCommon.TabUsageType.DirectMessage ||
- tabType == MyCommon.TabUsageType.Favorites ||
- tabType == MyCommon.TabUsageType.Home ||
- tabType == MyCommon.TabUsageType.Mentions ||
- tabType == MyCommon.TabUsageType.Related)
- {
- if (_statuses.GetTabByType(tabType) != null) return false;
- }
- }
+ if (tab.TabName == Properties.Resources.AddNewTabText1) return false;
var _tabPage = new TabPage();
var _listCustom = new DetailsListView();
_tabPage.Controls.Add(_listCustom);
/// UserTimeline関連
- if (tabType == MyCommon.TabUsageType.UserTimeline || tabType == MyCommon.TabUsageType.Lists)
+ var userTab = tab as UserTimelineTabModel;
+ var listTab = tab as ListTimelineTabModel;
+ var searchTab = tab as PublicSearchTabModel;
+
+ if (userTab != null || listTab != null)
{
var label = new Label();
label.Dock = DockStyle.Top;
label.Name = "labelUser";
- if (tabType == MyCommon.TabUsageType.Lists)
+ label.TabIndex = 0;
+
+ if (listTab != null)
{
- label.Text = listInfo.ToString();
+ label.Text = listTab.ListInfo.ToString();
}
- else
+ else if (userTab != null)
{
- label.Text = _statuses.Tabs[tabName].User + "'s Timeline";
+ label.Text = userTab.ScreenName + "'s Timeline";
}
label.TextAlign = ContentAlignment.MiddleLeft;
using (ComboBox tmpComboBox = new ComboBox())
_tabPage.Controls.Add(label);
}
/// 検索関連の準備
- else if (tabType == MyCommon.TabUsageType.PublicSearch)
+ else if (searchTab != null)
{
var pnl = new Panel();
pnl.Controls.Add(btn);
pnl.Controls.Add(lbl);
pnl.Name = "panelSearch";
+ pnl.TabIndex = 0;
pnl.Dock = DockStyle.Top;
pnl.Height = cmb.Height;
pnl.Enter += SearchControls_Enter;
cmb.DropDownStyle = ComboBoxStyle.DropDown;
cmb.ImeMode = ImeMode.NoControl;
cmb.TabStop = false;
+ cmb.TabIndex = 1;
cmb.AutoCompleteMode = AutoCompleteMode.None;
cmb.KeyDown += SearchComboBox_KeyDown;
cmbLang.Name = "comboLang";
cmbLang.DropDownStyle = ComboBoxStyle.DropDownList;
cmbLang.TabStop = false;
+ cmbLang.TabIndex = 2;
cmbLang.Items.Add("");
cmbLang.Items.Add("ja");
cmbLang.Items.Add("en");
lbl.Width = 90;
lbl.Height = cmb.Height;
lbl.TextAlign = ContentAlignment.MiddleLeft;
+ lbl.TabIndex = 0;
btn.Text = "Search";
btn.Name = "buttonSearch";
btn.UseVisualStyleBackColor = true;
btn.Dock = DockStyle.Right;
btn.TabStop = false;
+ btn.TabIndex = 3;
btn.Click += SearchButton_Click;
- TabClass tab;
- if (_statuses.Tabs.TryGetValue(tabName, out tab))
+ if (!string.IsNullOrEmpty(searchTab.SearchWords))
{
- if (!string.IsNullOrEmpty(tab.SearchWords))
- {
- cmb.Items.Add(tab.SearchWords);
- cmb.Text = tab.SearchWords;
- }
-
- cmbLang.Text = tab.SearchLang;
+ cmb.Items.Add(searchTab.SearchWords);
+ cmb.Text = searchTab.SearchWords;
}
+ cmbLang.Text = searchTab.SearchLang;
+
_tabPage.Controls.Add(pnl);
}
}
this.ListTab.Controls.Add(_tabPage);
_tabPage.Location = new Point(4, 4);
- _tabPage.Name = "CTab" + cnt.ToString();
+ _tabPage.Name = "CTab" + cnt;
_tabPage.Size = new Size(380, 260);
_tabPage.TabIndex = 2 + cnt;
- _tabPage.Text = tabName;
+ _tabPage.Text = tab.TabName;
_tabPage.UseVisualStyleBackColor = true;
_tabPage.AccessibleRole = AccessibleRole.PageTab;
+ _listCustom.AccessibleName = Properties.Resources.AddNewTab_ListView_AccessibleName;
+ _listCustom.TabIndex = 1;
_listCustom.AllowColumnReorder = true;
_listCustom.ContextMenuStrip = this.ContextMenuOperate;
_listCustom.ColumnHeaderContextMenuStrip = this.ContextMenuColumnHeader;
_listCustom.HideSelection = false;
_listCustom.Location = new Point(0, 0);
_listCustom.Margin = new Padding(0);
- _listCustom.Name = "CList" + Environment.TickCount.ToString();
+ _listCustom.Name = "CList" + Environment.TickCount;
_listCustom.ShowItemToolTips = true;
_listCustom.Size = new Size(380, 260);
_listCustom.UseCompatibleStateImageBehavior = false;
{
DetailsListView lst = (DetailsListView)tp.Tag;
var count = _statuses.Tabs[tp.Text].AllCount;
- if (lst.VirtualListSize != count)
- {
- lst.VirtualListSize = count;
- }
+ lst.VirtualListSize = count;
}
return true;
if (!this._cfgCommon.TabMouseLock && e.Button == MouseButtons.Left && _tabDrag)
{
string tn = "";
- Rectangle dragEnableRectangle = new Rectangle((int)(_tabMouseDownPoint.X - (SystemInformation.DragSize.Width / 2)), (int)(_tabMouseDownPoint.Y - (SystemInformation.DragSize.Height / 2)), SystemInformation.DragSize.Width, SystemInformation.DragSize.Height);
+ Rectangle dragEnableRectangle = new Rectangle(_tabMouseDownPoint.X - (SystemInformation.DragSize.Width / 2), _tabMouseDownPoint.Y - (SystemInformation.DragSize.Height / 2), SystemInformation.DragSize.Width, SystemInformation.DragSize.Height);
if (!dragEnableRectangle.Contains(e.Location))
{
//タブが多段の場合にはMouseDownの前の段階で選択されたタブの段が変わっているので、このタイミングでカーソルの位置からタブを判定出来ない。
_isColumnChanged = false;
}
- private void PostBrowser_StatusTextChanged(object sender, EventArgs e)
- {
- try
- {
- if (PostBrowser.StatusText.StartsWith("http") || PostBrowser.StatusText.StartsWith("ftp")
- || PostBrowser.StatusText.StartsWith("data"))
- {
- StatusLabelUrl.Text = PostBrowser.StatusText.Replace("&", "&&");
- }
- if (string.IsNullOrEmpty(PostBrowser.StatusText))
- {
- SetStatusLabelUrl();
- }
- }
- catch (Exception)
- {
- }
- }
-
private void StatusText_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '@')
//@マーク
int cnt = AtIdSupl.ItemCount;
ShowSuplDialog(StatusText, AtIdSupl);
- if (cnt != AtIdSupl.ItemCount) _modifySettingAtId = true;
+ if (cnt != AtIdSupl.ItemCount) ModifySettingAtId = true;
e.Handled = true;
}
else if (e.KeyChar == '#')
if (e.KeyCode == Keys.Space || e.KeyCode == Keys.ProcessKey)
{
bool isSpace = false;
- foreach (char c in StatusText.Text.ToCharArray())
+ foreach (char c in StatusText.Text)
{
if (c == ' ' || c == ' ')
{
}
else
{
- if (this.StatusText.Multiline)
+ if (this.StatusText.Multiline && !this._cfgCommon.PostCtrlEnter)
disableFooter = MyCommon.IsKeyDown(Keys.Control);
else
disableFooter = MyCommon.IsKeyDown(Keys.Shift);
statusText = header + statusText + footer;
- if (this.ToolStripMenuItemApiCommandEvasion.Checked)
+ if (this.ToolStripMenuItemPreventSmsCommand.Checked)
{
- // APIコマンド回避
- if (Regex.IsMatch(statusText, @"^[+\-\[\]\s\\.,*/(){}^~|='&%$#""<>?]*(get|g|fav|follow|f|on|off|stop|quit|leave|l|whois|w|nudge|n|stats|invite|track|untrack|tracks|tracking|\*)([+\-\[\]\s\\.,*/(){}^~|='&%$#""<>?]+|$)", RegexOptions.IgnoreCase)
- && !statusText.EndsWith(" .", StringComparison.Ordinal))
- statusText += " .";
+ // ツイートが意図せず SMS コマンドとして解釈されることを回避 (D, DM, M のみ)
+ // 参照: https://support.twitter.com/articles/14020
+
+ if (Regex.IsMatch(statusText, @"^[+\-\[\]\s\\.,*/(){}^~|='&%$#""<>?]*(d|dm|m)([+\-\[\]\s\\.,*/(){}^~|='&%$#""<>?]+|$)", RegexOptions.IgnoreCase)
+ && !Twitter.DMSendTextRegex.IsMatch(statusText))
+ {
+ // U+200B (ZERO WIDTH SPACE) を先頭に加えて回避
+ statusText = '\u200b' + statusText;
+ }
}
return statusText;
private void MyList_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e)
{
- this.itemCacheLock.EnterUpgradeableReadLock();
- try
- {
- if (_curList.Equals(sender))
- {
- if (_itemCache != null &&
- e.StartIndex >= _itemCacheIndex &&
- e.EndIndex < _itemCacheIndex + _itemCache.Length)
- {
- //If the newly requested cache is a subset of the old cache,
- //no need to rebuild everything, so do nothing.
- return;
- }
+ if (sender != this._curList)
+ return;
- //Now we need to rebuild the cache.
- CreateCache(e.StartIndex, e.EndIndex);
- }
+ var listCache = this._listItemCache;
+ if (listCache != null && listCache.IsSupersetOf(e.StartIndex, e.EndIndex))
+ {
+ // If the newly requested cache is a subset of the old cache,
+ // no need to rebuild everything, so do nothing.
+ return;
}
- finally { this.itemCacheLock.ExitUpgradeableReadLock(); }
+
+ // Now we need to rebuild the cache.
+ this.CreateCache(e.StartIndex, e.EndIndex);
}
private void MyList_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
- ListViewItem item = null;
- PostClass cacheItemPost = null;
-
- if (_curList.Equals(sender))
- this.TryGetListViewItemCache(e.ItemIndex, out item, out cacheItemPost);
-
- if (item == null)
+ var listCache = this._listItemCache;
+ if (listCache != null && listCache.TargetList == sender)
{
- //A cache miss, so create a new ListViewItem and pass it back.
- TabPage tb = (TabPage)((DetailsListView)sender).Parent;
- try
- {
- item = this.CreateItem(tb, _statuses.Tabs[tb.Text][e.ItemIndex], e.ItemIndex);
- }
- catch (Exception)
+ ListViewItem item;
+ PostClass cacheItemPost;
+ if (listCache.TryGetValue(e.ItemIndex, out item, out cacheItemPost))
{
- //不正な要求に対する間に合わせの応答
- string[] sitem = {"", "", "", "", "", "", "", ""};
- item = new ImageListViewItem(sitem);
+ e.Item = item;
+ return;
}
}
- e.Item = item;
+ // A cache miss, so create a new ListViewItem and pass it back.
+ TabPage tb = (TabPage)((DetailsListView)sender).Parent;
+ try
+ {
+ e.Item = this.CreateItem(tb, _statuses.Tabs[tb.Text][e.ItemIndex], e.ItemIndex);
+ }
+ catch (Exception)
+ {
+ // 不正な要求に対する間に合わせの応答
+ string[] sitem = {"", "", "", "", "", "", "", ""};
+ e.Item = new ImageListViewItem(sitem);
+ }
}
- private void CreateCache(int StartIndex, int EndIndex)
+ private void CreateCache(int startIndex, int endIndex)
{
- this.itemCacheLock.EnterWriteLock();
- try
- {
- var tabInfo = _statuses.Tabs[_curTab.Text];
+ var tabInfo = this._statuses.Tabs[this._curTab.Text];
- //キャッシュ要求(要求範囲±30を作成)
- StartIndex -= 30;
- if (StartIndex < 0) StartIndex = 0;
- EndIndex += 30;
- if (EndIndex >= tabInfo.AllCount) EndIndex = tabInfo.AllCount - 1;
- _postCache = tabInfo[StartIndex, EndIndex]; //配列で取得
- _itemCacheIndex = StartIndex;
+ if (tabInfo.AllCount == 0)
+ return;
- _itemCache = new ListViewItem[0] {};
- Array.Resize(ref _itemCache, _postCache.Length);
+ // キャッシュ要求(要求範囲±30を作成)
+ startIndex = Math.Max(startIndex - 30, 0);
+ endIndex = Math.Min(endIndex + 30, tabInfo.AllCount - 1);
- for (int i = 0; i < _postCache.Length; i++)
- {
- _itemCache[i] = CreateItem(_curTab, _postCache[i], StartIndex + i);
- }
- }
- catch (Exception)
+ var cacheLength = endIndex - startIndex + 1;
+
+ var posts = tabInfo[startIndex, endIndex]; //配列で取得
+ var listItems = Enumerable.Range(0, cacheLength)
+ .Select(x => this.CreateItem(this._curTab, posts[x], startIndex + x))
+ .ToArray();
+
+ var listCache = new ListViewItemCache
{
- //キャッシュ要求が実データとずれるため(イベントの遅延?)
- _postCache = null;
- _itemCacheIndex = -1;
- _itemCache = null;
- }
- finally { this.itemCacheLock.ExitWriteLock(); }
+ TargetList = this._curList,
+ StartIndex = startIndex,
+ EndIndex = endIndex,
+ Post = posts,
+ ListItem = listItems,
+ };
+
+ Interlocked.Exchange(ref this._listItemCache, listCache);
}
/// <summary>
/// </summary>
private void PurgeListViewItemCache()
{
- this.itemCacheLock.EnterWriteLock();
- try
- {
- this._itemCache = null;
- this._itemCacheIndex = -1;
- this._postCache = null;
- }
- finally { this.itemCacheLock.ExitWriteLock(); }
- }
-
- private bool TryGetListViewItemCache(int index, out ListViewItem item, out PostClass post)
- {
- this.itemCacheLock.EnterReadLock();
- try
- {
- if (this._itemCache != null && index >= this._itemCacheIndex && index < this._itemCacheIndex + this._itemCache.Length)
- {
- item = this._itemCache[index - _itemCacheIndex];
- post = this._postCache[index - _itemCacheIndex];
- return true;
- }
- }
- finally { this.itemCacheLock.ExitReadLock(); }
-
- item = null;
- post = null;
- return false;
+ Interlocked.Exchange(ref this._listItemCache, null);
}
private ListViewItem CreateItem(TabPage Tab, PostClass Post, int Index)
//if (Post.IsMark) mk.Append("♪");
//if (Post.IsProtect) mk.Append("Ю");
//if (Post.InReplyToStatusId != null) mk.Append("⇒");
- if (Post.FavoritedCount > 0) mk.Append("+" + Post.FavoritedCount.ToString());
+ if (Post.FavoritedCount > 0) mk.Append("+" + Post.FavoritedCount);
ImageListViewItem itm;
if (Post.RetweetedId == null)
{
// }
//}
- private void DoTabSearch(string _word,
- bool CaseSensitive,
- bool UseRegex,
- SEARCHTYPE SType)
+ internal void DoTabSearch(string searchWord, bool caseSensitive, bool useRegex, SEARCHTYPE searchType)
{
- int cidx = 0;
- bool fnd = false;
- int toIdx;
- int stp = 1;
+ var tab = this._statuses.Tabs[this._curTab.Text];
- if (_curList.VirtualListSize == 0)
+ if (tab.AllCount == 0)
{
MessageBox.Show(Properties.Resources.DoTabSearchText2, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Information);
+ return;
}
- if (_curList.SelectedIndices.Count > 0)
- {
- cidx = _curList.SelectedIndices[0];
- }
- toIdx = _curList.VirtualListSize;
+ var selectedIndex = this._curList.SelectedIndices.Count != 0 ? this._curList.SelectedIndices[0] : -1;
- switch (SType)
+ int startIndex;
+ switch (searchType)
{
- case SEARCHTYPE.DialogSearch: //ダイアログからの検索
- if (_curList.SelectedIndices.Count > 0)
- cidx = _curList.SelectedIndices[0];
- else
- cidx = 0;
- break;
- case SEARCHTYPE.NextSearch: //次を検索
- if (_curList.SelectedIndices.Count > 0)
- {
- cidx = _curList.SelectedIndices[0] + 1;
- if (cidx > toIdx) cidx = toIdx;
- }
+ case SEARCHTYPE.NextSearch: // 次を検索
+ if (selectedIndex != -1)
+ startIndex = Math.Min(selectedIndex + 1, tab.AllCount - 1);
else
- {
- cidx = 0;
- }
+ startIndex = 0;
break;
- case SEARCHTYPE.PrevSearch: //前を検索
- if (_curList.SelectedIndices.Count > 0)
- {
- cidx = _curList.SelectedIndices[0] - 1;
- if (cidx < 0) cidx = 0;
- }
+ case SEARCHTYPE.PrevSearch: // 前を検索
+ if (selectedIndex != -1)
+ startIndex = Math.Max(selectedIndex - 1, 0);
else
- {
- cidx = toIdx;
- }
- toIdx = -1;
- stp = -1;
- break;
- }
-
- RegexOptions regOpt = RegexOptions.None;
- StringComparison fndOpt = StringComparison.Ordinal;
- if (!CaseSensitive)
- {
- regOpt = RegexOptions.IgnoreCase;
- fndOpt = StringComparison.OrdinalIgnoreCase;
- }
- try
- {
- RETRY:
- if (UseRegex)
- {
- // 正規表現検索
- Regex _search;
- try
- {
- _search = new Regex(_word, regOpt);
- for (int idx = cidx; idx != toIdx; idx += stp)
- {
- PostClass post;
- try
- {
- post = _statuses.Tabs[_curTab.Text][idx];
- }
- catch (Exception)
- {
- continue;
- }
- if (_search.IsMatch(post.Nickname)
- || _search.IsMatch(post.TextFromApi)
- || _search.IsMatch(post.ScreenName))
- {
- SelectListItem(_curList, idx);
- _curList.EnsureVisible(idx);
- return;
- }
- }
- }
- catch (ArgumentException)
- {
- MessageBox.Show(Properties.Resources.DoTabSearchText1, "", MessageBoxButtons.OK, MessageBoxIcon.Error);
- return;
- }
- }
- else
- {
- // 通常検索
- for (int idx = cidx; idx != toIdx; idx += stp)
- {
- PostClass post;
- try
- {
- post = _statuses.Tabs[_curTab.Text][idx];
- }
- catch (Exception)
- {
- continue;
- }
- if (post.Nickname.IndexOf(_word, fndOpt) > -1
- || post.TextFromApi.IndexOf(_word, fndOpt) > -1
- || post.ScreenName.IndexOf(_word, fndOpt) > -1)
- {
- SelectListItem(_curList, idx);
- _curList.EnsureVisible(idx);
- return;
- }
- }
- }
+ startIndex = tab.AllCount - 1;
+ break;
+ case SEARCHTYPE.DialogSearch: // ダイアログからの検索
+ default:
+ if (selectedIndex != -1)
+ startIndex = selectedIndex;
+ else
+ startIndex = 0;
+ break;
+ }
- if (!fnd)
- {
- switch (SType)
- {
- case SEARCHTYPE.DialogSearch:
- case SEARCHTYPE.NextSearch:
- toIdx = cidx;
- cidx = 0;
- break;
- case SEARCHTYPE.PrevSearch:
- toIdx = cidx;
- cidx = _curList.VirtualListSize - 1;
- break;
- }
- fnd = true;
- goto RETRY;
- }
+ Func<string, bool> stringComparer;
+ try
+ {
+ stringComparer = this.CreateSearchComparer(searchWord, useRegex, caseSensitive);
+ }
+ catch (ArgumentException)
+ {
+ MessageBox.Show(Properties.Resources.DoTabSearchText1, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Error);
+ return;
}
- catch (ArgumentOutOfRangeException)
+
+ var reverse = searchType == SEARCHTYPE.PrevSearch;
+ var foundIndex = tab.SearchPostsAll(stringComparer, startIndex, reverse)
+ .DefaultIfEmpty(-1).First();
+
+ if (foundIndex == -1)
{
+ MessageBox.Show(Properties.Resources.DoTabSearchText2, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Information);
+ return;
}
- MessageBox.Show(Properties.Resources.DoTabSearchText2, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Information);
+
+ this.SelectListItem(this._curList, foundIndex);
+ this._curList.EnsureVisible(foundIndex);
}
private void MenuItemSubSearch_Click(object sender, EventArgs e)
MessageBox.Show(this, ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
- this.AddNewTab(tabName, false, MyCommon.TabUsageType.SearchResults);
- this._statuses.AddTab(tabName, MyCommon.TabUsageType.SearchResults, null);
+ var resultTab = new LocalSearchTabModel(tabName);
+ this.AddNewTab(resultTab, startup: false);
+ this._statuses.AddTab(resultTab);
+
+ var targetTab = this._statuses.Tabs[this._curTab.Text];
- var filter = new PostFilterRule
+ Func<string, bool> stringComparer;
+ try
{
- FilterBody = new[] { searchOptions.Query },
- UseRegex = searchOptions.UseRegex,
- CaseSensitive = searchOptions.CaseSensitive,
- };
+ stringComparer = this.CreateSearchComparer(searchOptions.Query, searchOptions.UseRegex, searchOptions.CaseSensitive);
+ }
+ catch (ArgumentException)
+ {
+ MessageBox.Show(Properties.Resources.DoTabSearchText1, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Error);
+ return;
+ }
- var targetTab = this._statuses.Tabs[this._curTab.Text];
- var posts = targetTab.Posts.Values
- .Where(x => filter.ExecFilter(x) == MyCommon.HITRESULT.CopyAndMark)
- .Where(x => targetTab.Contains(x.StatusId));
+ var foundIndices = targetTab.SearchPostsAll(stringComparer).ToArray();
+ if (foundIndices.Length == 0)
+ {
+ MessageBox.Show(Properties.Resources.DoTabSearchText2, Properties.Resources.DoTabSearchText3, MessageBoxButtons.OK, MessageBoxIcon.Information);
+ return;
+ }
- var resultTab = this._statuses.Tabs[tabName];
- foreach (var post in posts)
+ var foundPosts = foundIndices.Select(x => targetTab[x]);
+ foreach (var post in foundPosts)
{
- resultTab.AddPostToInnerStorage(post);
+ resultTab.AddPostQueue(post);
}
this._statuses.DistributePosts();
- this.RefreshTimeline(false);
+ this.RefreshTimeline();
var tabPage = this.ListTab.TabPages.Cast<TabPage>()
.First(x => x.Text == tabName);
}
}
+ /// <summary>発言検索に使用するメソッドを生成します</summary>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="useRegex"/> が true かつ、<paramref name="query"> が不正な正規表現な場合
+ /// </exception>
+ private Func<string, bool> CreateSearchComparer(string query, bool useRegex, bool caseSensitive)
+ {
+ if (useRegex)
+ {
+ var regexOption = caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase;
+ var regex = new Regex(query, regexOption);
+
+ return x => regex.IsMatch(x);
+ }
+ else
+ {
+ var comparisonType = caseSensitive ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase;
+
+ return x => x.IndexOf(query, comparisonType) != -1;
+ }
+ }
+
private void AboutMenuItem_Click(object sender, EventArgs e)
{
using (TweenAboutBox about = new TweenAboutBox())
return detailHtmlFormatHeader + orgdata + detailHtmlFormatFooter;
}
- private async void DisplayItemImage_Downloaded(object sender, EventArgs e)
- {
- if (sender.Equals(displayItem))
- {
- this.ClearUserPicture();
-
- var img = displayItem.Image;
- try
- {
- if (img != null)
- img = await img.CloneAsync();
-
- UserPicture.Image = img;
- }
- catch (Exception)
- {
- UserPicture.ShowErrorImage();
- }
- }
- }
-
private Task DispSelectedPost()
{
return this.DispSelectedPost(false);
if (!forceupdate && this._curPost.Equals(oldDisplayPost))
return;
- if (displayItem != null)
- {
- displayItem.ImageDownloaded -= this.DisplayItemImage_Downloaded;
- displayItem = null;
- }
- displayItem = (ImageListViewItem)_curList.Items[_curList.SelectedIndices[0]];
- displayItem.ImageDownloaded += this.DisplayItemImage_Downloaded;
-
- using (ControlTransaction.Update(this.TableLayoutPanel1))
- {
- SourceLinkLabel.Text = this._curPost.Source;
- SourceLinkLabel.Tag = this._curPost.SourceUri;
- SourceLinkLabel.TabStop = false; // Text を更新すると勝手に true にされる
-
- string nameText;
- if (_curPost.IsDm)
- {
- if (_curPost.IsOwl)
- nameText = "DM FROM <- ";
- else
- nameText = "DM TO -> ";
- }
- else
- {
- nameText = "";
- }
- nameText += _curPost.ScreenName + "/" + _curPost.Nickname;
- if (_curPost.RetweetedId != null)
- nameText += " (RT:" + _curPost.RetweetedBy + ")";
-
- NameLabel.Text = nameText;
- NameLabel.Tag = _curPost.ScreenName;
-
- var nameForeColor = SystemColors.ControlText;
- if (_curPost.IsOwl && (this._cfgCommon.OneWayLove || _curPost.IsDm))
- nameForeColor = this._clOWL;
- if (_curPost.RetweetedId != null)
- nameForeColor = this._clRetweet;
- if (_curPost.IsFav)
- nameForeColor = this._clFav;
- NameLabel.ForeColor = nameForeColor;
-
- this.ClearUserPicture();
-
- if (!string.IsNullOrEmpty(_curPost.ImageUrl))
- {
- var image = IconCache.TryGetFromCache(_curPost.ImageUrl);
- try
- {
- UserPicture.Image = image?.Clone();
- }
- catch (Exception)
- {
- UserPicture.ShowErrorImage();
- }
- }
-
- DateTimeLabel.Text = _curPost.CreatedAt.ToString();
- }
-
- if (DumpPostClassToolStripMenuItem.Checked)
+ var loadTasks = new List<Task>
{
- StringBuilder sb = new StringBuilder(512);
-
- sb.Append("-----Start PostClass Dump<br>");
- sb.AppendFormat("TextFromApi : {0}<br>", _curPost.TextFromApi);
- sb.AppendFormat("(PlainText) : <xmp>{0}</xmp><br>", _curPost.TextFromApi);
- sb.AppendFormat("StatusId : {0}<br>", _curPost.StatusId.ToString());
- //sb.AppendFormat("ImageIndex : {0}<br>", _curPost.ImageIndex.ToString());
- sb.AppendFormat("ImageUrl : {0}<br>", _curPost.ImageUrl);
- sb.AppendFormat("InReplyToStatusId : {0}<br>", _curPost.InReplyToStatusId.ToString());
- sb.AppendFormat("InReplyToUser : {0}<br>", _curPost.InReplyToUser);
- sb.AppendFormat("IsDM : {0}<br>", _curPost.IsDm.ToString());
- sb.AppendFormat("IsFav : {0}<br>", _curPost.IsFav.ToString());
- sb.AppendFormat("IsMark : {0}<br>", _curPost.IsMark.ToString());
- sb.AppendFormat("IsMe : {0}<br>", _curPost.IsMe.ToString());
- sb.AppendFormat("IsOwl : {0}<br>", _curPost.IsOwl.ToString());
- sb.AppendFormat("IsProtect : {0}<br>", _curPost.IsProtect.ToString());
- sb.AppendFormat("IsRead : {0}<br>", _curPost.IsRead.ToString());
- sb.AppendFormat("IsReply : {0}<br>", _curPost.IsReply.ToString());
-
- foreach (string nm in _curPost.ReplyToList)
- {
- sb.AppendFormat("ReplyToList : {0}<br>", nm);
- }
-
- sb.AppendFormat("ScreenName : {0}<br>", _curPost.ScreenName);
- sb.AppendFormat("NickName : {0}<br>", _curPost.Nickname);
- sb.AppendFormat("Text : {0}<br>", _curPost.Text);
- sb.AppendFormat("(PlainText) : <xmp>{0}</xmp><br>", _curPost.Text);
- sb.AppendFormat("CreatedAt : {0}<br>", _curPost.CreatedAt.ToString());
- sb.AppendFormat("Source : {0}<br>", _curPost.Source);
- sb.AppendFormat("UserId : {0}<br>", _curPost.UserId);
- sb.AppendFormat("FilterHit : {0}<br>", _curPost.FilterHit);
- sb.AppendFormat("RetweetedBy : {0}<br>", _curPost.RetweetedBy);
- sb.AppendFormat("RetweetedId : {0}<br>", _curPost.RetweetedId);
-
- sb.AppendFormat("Media.Count : {0}<br>", _curPost.Media.Count);
- if (_curPost.Media.Count > 0)
- {
- for (int i = 0; i < _curPost.Media.Count; i++)
- {
- var info = _curPost.Media[i];
- sb.AppendFormat("Media[{0}].Url : {1}<br>", i, info.Url);
- sb.AppendFormat("Media[{0}].VideoUrl : {1}<br>", i, info.VideoUrl ?? "---");
- }
- }
- sb.Append("-----End PostClass Dump<br>");
-
- PostBrowser.DocumentText = detailHtmlFormatHeader + sb.ToString() + detailHtmlFormatFooter;
- return;
- }
+ this.tweetDetailsView.ShowPostDetails(this._curPost),
+ };
- var loadTasks = new List<Task>();
+ this.SplitContainer3.Panel2Collapsed = true;
- // 同じIDのツイートであれば WebBrowser とサムネイルの更新を行わない
- // (同一ツイートの RT は文面が同じであるため同様に更新しない)
- if (_curPost.StatusId != oldDisplayPost.StatusId)
+ if (this._cfgCommon.PreviewEnable)
{
- using (ControlTransaction.Update(this.PostBrowser))
- {
- this.PostBrowser.DocumentText =
- this.createDetailHtml(_curPost.IsDeleted ? "(DELETED)" : _curPost.Text);
-
- this.PostBrowser.Document.Window.ScrollTo(0, 0);
- }
-
- this.SplitContainer3.Panel2Collapsed = true;
-
- if (this._cfgCommon.PreviewEnable)
- {
- var oldTokenSource = Interlocked.Exchange(ref this.thumbnailTokenSource, new CancellationTokenSource());
- oldTokenSource?.Cancel();
-
- var token = this.thumbnailTokenSource.Token;
- loadTasks.Add(this.tweetThumbnail1.ShowThumbnailAsync(_curPost, token));
- }
+ var oldTokenSource = Interlocked.Exchange(ref this.thumbnailTokenSource, new CancellationTokenSource());
+ oldTokenSource?.Cancel();
- loadTasks.Add(this.AppendQuoteTweetAsync(this._curPost));
+ var token = this.thumbnailTokenSource.Token;
+ loadTasks.Add(this.tweetThumbnail1.ShowThumbnailAsync(_curPost, token));
}
try
catch (OperationCanceledException) { }
}
- /// <summary>
- /// 発言詳細欄のツイートURLを展開する
- /// </summary>
- private async Task AppendQuoteTweetAsync(PostClass post)
- {
- var statusIds = post.QuoteStatusIds;
- if (statusIds.Length == 0)
- return;
-
- // 「読み込み中」テキストを表示
- var loadingQuoteHtml = statusIds.Select(x => FormatQuoteTweetHtml(x, Properties.Resources.LoadingText));
- var body = post.Text + string.Concat(loadingQuoteHtml);
-
- using (ControlTransaction.Update(this.PostBrowser))
- this.PostBrowser.DocumentText = this.createDetailHtml(body);
-
- // 引用ツイートを読み込み
- var quoteHtmls = await Task.WhenAll(statusIds.Select(x => this.CreateQuoteTweetHtml(x)));
-
- // 非同期処理中に表示中のツイートが変わっていたらキャンセルされたものと扱う
- if (this._curPost != post || this._curPost.IsDeleted)
- return;
-
- body = post.Text + string.Concat(quoteHtmls);
-
- using (ControlTransaction.Update(this.PostBrowser))
- this.PostBrowser.DocumentText = this.createDetailHtml(body);
- }
-
- private async Task<string> CreateQuoteTweetHtml(long statusId)
- {
- PostClass post = this._statuses[statusId];
- if (post == null)
- {
- try
- {
- post = await Task.Run(() => this.tw.GetStatusApi(false, statusId))
- .ConfigureAwait(false);
- }
- catch (WebApiException ex)
- {
- return FormatQuoteTweetHtml(statusId, WebUtility.HtmlEncode(ex.Message));
- }
-
- post.IsRead = true;
- if (!this._statuses.AddQuoteTweet(post))
- return FormatQuoteTweetHtml(statusId, "This Tweet is unavailable.");
- }
-
- return FormatQuoteTweetHtml(post);
- }
-
- internal static string FormatQuoteTweetHtml(PostClass post)
- {
- var innerHtml = "<p>" + StripLinkTagHtml(post.Text) + "</p>" +
- " — " + WebUtility.HtmlEncode(post.Nickname) +
- " (@" + WebUtility.HtmlEncode(post.ScreenName) + ") " +
- WebUtility.HtmlEncode(post.CreatedAt.ToString());
-
- return FormatQuoteTweetHtml(post.StatusId, innerHtml);
- }
-
- internal static string FormatQuoteTweetHtml(long statusId, string innerHtml)
- {
- return "<a class=\"quote-tweet-link\" href=\"//opentween/status/" + statusId + "\">" +
- "<blockquote class=\"quote-tweet\">" + innerHtml + "</blockquote>" +
- "</a>";
- }
-
- /// <summary>
- /// 指定されたHTMLからリンクを除去します
- /// </summary>
- internal static string StripLinkTagHtml(string html)
- {
- // a 要素はネストされていない前提の正規表現パターン
- return Regex.Replace(html, @"<a[^>]*>(.*?)</a>", "$1");
- }
-
private async void MatomeMenuItem_Click(object sender, EventArgs e)
{
await this.OpenApplicationWebsite();
.FocusedOn(FocusedControl.ListTab)
.Do(() => this.GoNextTab(forward: false)),
- ShortcutCommand.Create(Keys.Control | Keys.C)
- .FocusedOn(FocusedControl.ListTab)
- .Do(() => this.CopyStot()),
-
- ShortcutCommand.Create(Keys.Control | Keys.C)
+ ShortcutCommand.Create(Keys.Control | Keys.C, Keys.Control | Keys.Insert)
.FocusedOn(FocusedControl.ListTab)
.Do(() => this.CopyStot()),
.Do(() => this.doShowUserStatus(_curPost.ScreenName, ShowInputDialog: false)),
ShortcutCommand.Create(Keys.Alt | Keys.Up)
- .Do(() => this.ScrollDownPostBrowser(forward: false)),
+ .Do(() => this.tweetDetailsView.ScrollDownPostBrowser(forward: false)),
ShortcutCommand.Create(Keys.Alt | Keys.Down)
- .Do(() => this.ScrollDownPostBrowser(forward: true)),
+ .Do(() => this.tweetDetailsView.ScrollDownPostBrowser(forward: true)),
ShortcutCommand.Create(Keys.Alt | Keys.PageUp)
- .Do(() => this.PageDownPostBrowser(forward: false)),
+ .Do(() => this.tweetDetailsView.PageDownPostBrowser(forward: false)),
ShortcutCommand.Create(Keys.Alt | Keys.PageDown)
- .Do(() => this.PageDownPostBrowser(forward: true)),
+ .Do(() => this.tweetDetailsView.PageDownPostBrowser(forward: true)),
// 別タブの同じ書き込みへ(ALT+←/→)
ShortcutCommand.Create(Keys.Alt | Keys.Right)
ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.R)
.Do(() => this.MakeReplyOrDirectStatus(isAuto: false, isReply: true, isAll: true)),
- ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.C)
+ ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.C, Keys.Control | Keys.Shift | Keys.Insert)
.Do(() => this.CopyIdUri()),
ShortcutCommand.Create(Keys.Control | Keys.Shift | Keys.F)
startstr = StatusText.Text.Substring(i + 1, endidx - i);
int cnt = AtIdSupl.ItemCount;
ShowSuplDialog(StatusText, AtIdSupl, startstr.Length + 1, startstr);
- if (AtIdSupl.ItemCount != cnt) _modifySettingAtId = true;
+ if (AtIdSupl.ItemCount != cnt) ModifySettingAtId = true;
}
else if (c == '#')
{
.FocusedOn(FocusedControl.PostBrowser)
.Do(() => this.doReTweetUnofficial()),
- ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.C)
- .FocusedOn(FocusedControl.PostBrowser)
- .Do(() => this.CopyUserId()),
-
ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.T)
.OnlyWhen(() => this.ExistCurrentPost)
- .Do(() => this.doTranslation(_curPost.TextFromApi)),
+ .Do(() => this.tweetDetailsView.DoTranslation()),
ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.R)
.Do(() => this.doReTweetUnofficial()),
- ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.C)
+ ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.C, Keys.Alt | Keys.Shift | Keys.Insert)
.Do(() => this.CopyUserId()),
ShortcutCommand.Create(Keys.Alt | Keys.Shift | Keys.Up)
};
}
- private bool CommonKeyDown(Keys keyData, FocusedControl focusedOn, out Task asyncTask)
+ internal bool CommonKeyDown(Keys keyData, FocusedControl focusedOn, out Task asyncTask)
{
// Task を返す非同期処理があれば asyncTask に代入する
asyncTask = null;
return false;
}
- private void ScrollDownPostBrowser(bool forward)
- {
- var doc = PostBrowser.Document;
- if (doc == null) return;
-
- var tags = doc.GetElementsByTagName("html");
- if (tags.Count > 0)
- {
- if (forward)
- tags[0].ScrollTop += this._fntDetail.Height;
- else
- tags[0].ScrollTop -= this._fntDetail.Height;
- }
- }
-
- private void PageDownPostBrowser(bool forward)
- {
- var doc = PostBrowser.Document;
- if (doc == null) return;
-
- var tags = doc.GetElementsByTagName("html");
- if (tags.Count > 0)
- {
- if (forward)
- tags[0].ScrollTop += PostBrowser.ClientRectangle.Height - this._fntDetail.Height;
- else
- tags[0].ScrollTop -= PostBrowser.ClientRectangle.Height - this._fntDetail.Height;
- }
- }
-
private void GoNextTab(bool forward)
{
int idx = ListTab.SelectedIndex;
post.RetweetedBy == _anchorPost.ScreenName ||
post.ScreenName == _anchorPost.RetweetedBy ||
(!string.IsNullOrEmpty(post.RetweetedBy) && post.RetweetedBy == _anchorPost.RetweetedBy) ||
- _anchorPost.ReplyToList.Contains(post.ScreenName.ToLower()) ||
- _anchorPost.ReplyToList.Contains(post.RetweetedBy.ToLower()) ||
- post.ReplyToList.Contains(_anchorPost.ScreenName.ToLower()) ||
- post.ReplyToList.Contains(_anchorPost.RetweetedBy.ToLower()))
+ _anchorPost.ReplyToList.Contains(post.ScreenName.ToLowerInvariant()) ||
+ _anchorPost.ReplyToList.Contains(post.RetweetedBy.ToLowerInvariant()) ||
+ post.ReplyToList.Contains(_anchorPost.ScreenName.ToLowerInvariant()) ||
+ post.ReplyToList.Contains(_anchorPost.RetweetedBy.ToLowerInvariant()))
{
SelectListItem(_curList, idx);
_curList.EnsureVisible(idx);
{
if (_curPost == null) return;
- TabClass curTabClass = _statuses.Tabs[_curTab.Text];
+ TabModel curTabClass = _statuses.Tabs[_curTab.Text];
if (curTabClass.TabType == MyCommon.TabUsageType.PublicSearch && _curPost.InReplyToStatusId == null && _curPost.TextFromApi.Contains("@"))
{
try
{
- var post = tw.GetStatusApi(false, _curPost.StatusId);
+ var post = await tw.GetStatusApi(false, _curPost.StatusId);
_curPost.InReplyToStatusId = post.InReplyToStatusId;
_curPost.InReplyToUser = post.InReplyToUser;
}
catch (WebApiException ex)
{
- this.StatusLabel.Text = ex.Message;
+ this.StatusLabel.Text = $"Err:{ex.Message}(GetStatus)";
}
}
{
try
{
- await Task.Run(() =>
+ await Task.Run(async () =>
{
- var post = tw.GetStatusApi(false, _curPost.InReplyToStatusId.Value);
+ var post = await tw.GetStatusApi(false, _curPost.InReplyToStatusId.Value)
+ .ConfigureAwait(false);
post.IsRead = true;
_statuses.AddPost(post);
}
catch (WebApiException ex)
{
- this.StatusLabel.Text = ex.Message;
- await this.OpenUriInBrowserAsync("https://twitter.com/" + inReplyToUser + "/statuses/" + inReplyToId.ToString());
+ this.StatusLabel.Text = $"Err:{ex.Message}(GetStatus)";
+ await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(inReplyToUser, inReplyToId));
return;
}
- this.RefreshTimeline(false);
+ this.RefreshTimeline();
inReplyPost = inReplyToPosts.FirstOrDefault();
if (inReplyPost == null)
{
- await this.OpenUriInBrowserAsync("https://twitter.com/" + inReplyToUser + "/statuses/" + inReplyToId.ToString());
+ await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(inReplyToUser, inReplyToId));
return;
}
}
{
if (_curPost == null) return;
- TabClass curTabClass = _statuses.Tabs[_curTab.Text];
+ TabModel curTabClass = _statuses.Tabs[_curTab.Text];
//Dictionary<long, PostClass> curTabPosts = curTabClass.Posts;
if (parallel)
}
else
{
- if (_modifySettingCommon) SaveConfigsCommon();
- if (_modifySettingLocal) SaveConfigsLocal();
- if (_modifySettingAtId) SaveConfigsAtId();
+ if (ModifySettingCommon) SaveConfigsCommon();
+ if (ModifySettingLocal) SaveConfigsLocal();
+ if (ModifySettingAtId) SaveConfigsAtId();
}
}
{
if (_ignoreConfigSave || !this._cfgCommon.UseAtIdSupplement && AtIdSupl == null) return;
- _modifySettingAtId = false;
+ ModifySettingAtId = false;
SettingAtIdList cfgAtId = new SettingAtIdList(AtIdSupl.GetItemList());
cfgAtId.Save();
}
{
if (_ignoreConfigSave) return;
- _modifySettingCommon = false;
+ ModifySettingCommon = false;
lock (_syncObject)
{
_cfgCommon.UserName = tw.Username;
_cfgCommon.UserId = tw.UserId;
- _cfgCommon.Password = tw.Password;
_cfgCommon.Token = tw.AccessToken;
_cfgCommon.TokenSecret = tw.AccessTokenSecret;
if (_ignoreConfigSave) return;
lock (_syncObject)
{
- _modifySettingLocal = false;
+ ModifySettingLocal = false;
_cfgLocal.ScaleDimension = this.CurrentAutoScaleDimensions;
_cfgLocal.FormSize = _mySize;
_cfgLocal.FormLocation = _myLoc;
private void SaveConfigsTabs()
{
- SettingTabs tabSetting = new SettingTabs();
- for (int i = 0; i < ListTab.TabPages.Count; i++)
+ var tabsSetting = new SettingTabs();
+
+ var tabs = this.ListTab.TabPages.Cast<TabPage>()
+ .Select(x => this._statuses.Tabs[x.Text])
+ .Concat(new[] { this._statuses.GetTabByType(MyCommon.TabUsageType.Mute) });
+
+ foreach (var tab in tabs)
{
- var tab = _statuses.Tabs[ListTab.TabPages[i].Text];
- if (tab.TabType != MyCommon.TabUsageType.Related && tab.TabType != MyCommon.TabUsageType.SearchResults)
- tabSetting.Tabs.Add(tab);
+ if (!tab.IsPermanentTabType)
+ continue;
+
+ var tabSetting = new SettingTabs.SettingTabItem
+ {
+ TabName = tab.TabName,
+ TabType = tab.TabType,
+ UnreadManage = tab.UnreadManage,
+ Protected = tab.Protected,
+ Notify = tab.Notify,
+ SoundFile = tab.SoundFile,
+ };
+
+ var filterTab = tab as FilterTabModel;
+ if (filterTab != null)
+ tabSetting.FilterArray = filterTab.FilterArray;
+
+ var userTab = tab as UserTimelineTabModel;
+ if (userTab != null)
+ tabSetting.User = userTab.ScreenName;
+
+ var searchTab = tab as PublicSearchTabModel;
+ if (searchTab != null)
+ {
+ tabSetting.SearchWords = searchTab.SearchWords;
+ tabSetting.SearchLang = searchTab.SearchLang;
+ }
+
+ var listTab = tab as ListTimelineTabModel;
+ if (listTab != null)
+ tabSetting.ListInfo = listTab.ListInfo;
+
+ tabsSetting.Tabs.Add(tabSetting);
}
- tabSetting.Tabs.Add(this._statuses.GetTabByType(MyCommon.TabUsageType.Mute));
- tabSetting.Save();
+
+ tabsSetting.Save();
}
private async void OpenURLFileMenuItem_Click(object sender, EventArgs e)
if (post.IsProtect) protect = "Protect";
sw.WriteLine(post.Nickname + "\t" +
"\"" + post.TextFromApi.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
- post.CreatedAt.ToString() + "\t" +
+ post.CreatedAt + "\t" +
post.ScreenName + "\t" +
- post.StatusId.ToString() + "\t" +
+ post.StatusId + "\t" +
post.ImageUrl + "\t" +
"\"" + post.Text.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
protect);
if (post.IsProtect) protect = "Protect";
sw.WriteLine(post.Nickname + "\t" +
"\"" + post.TextFromApi.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
- post.CreatedAt.ToString() + "\t" +
+ post.CreatedAt + "\t" +
post.ScreenName + "\t" +
- post.StatusId.ToString() + "\t" +
+ post.StatusId + "\t" +
post.ImageUrl + "\t" +
"\"" + post.Text.Replace("\n", "").Replace("\"", "\"\"") + "\"" + "\t" +
protect);
this.TopMost = this._cfgCommon.AlwaysTop;
}
- private async void PostBrowser_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
- {
- Task asyncTask;
- bool KeyRes = CommonKeyDown(e.KeyData, FocusedControl.PostBrowser, out asyncTask);
- if (KeyRes)
- {
- e.IsInputKey = true;
- }
- else
- {
- if (Enum.IsDefined(typeof(Shortcut), (Shortcut)e.KeyData))
- {
- var shortcut = (Shortcut)e.KeyData;
- switch (shortcut)
- {
- case Shortcut.CtrlA:
- case Shortcut.CtrlC:
- case Shortcut.CtrlIns:
- // 既定の動作を有効にする
- break;
- default:
- // その他のショートカットキーは無効にする
- e.IsInputKey = true;
- break;
- }
- }
- }
-
- if (asyncTask != null)
- await asyncTask;
- }
public bool TabRename(ref string tabName)
{
//タブ名変更
private void ListTab_MouseDown(object sender, MouseEventArgs e)
{
if (this._cfgCommon.TabMouseLock) return;
- Point cpos = new Point(e.X, e.Y);
if (e.Button == MouseButtons.Left)
{
for (int i = 0; i < ListTab.TabPages.Count; i++)
}
return;
}
- if (!StatusText.Text.StartsWith("@"))
+ if (!StatusText.Text.StartsWith("@", StringComparison.Ordinal))
{
//文頭@以外
- if (StatusText.Text.StartsWith(". "))
+ if (StatusText.Text.StartsWith(". ", StringComparison.Ordinal))
{
// 複数リプライ
StatusText.Text = StatusText.Text.Insert(2, "@" + _curPost.ScreenName + " ");
//Enter or DoubleClick
string sTxt = StatusText.Text;
- if (!sTxt.StartsWith(". "))
+ if (!sTxt.StartsWith(". ", StringComparison.Ordinal))
{
sTxt = ". " + sTxt;
this.inReplyTo = null;
}
}
if (ids.Length == 0) return;
- if (!StatusText.Text.StartsWith(". "))
+ if (!StatusText.Text.StartsWith(". ", StringComparison.Ordinal))
{
StatusText.Text = ". " + StatusText.Text;
sidx += 2;
private static bool blink = false;
private static bool idle = false;
- private async Task RefreshTasktrayIcon(bool forceRefresh)
+ private async Task RefreshTasktrayIcon()
{
if (_colorize)
await this.Colorize();
//Static iconDlListTopItem As ListViewItem = null
- if (forceRefresh) idle = false;
-
//if (((ListView)ListTab.SelectedTab.Tag).TopItem == iconDlListTopItem)
// ((ImageDictionary)this.TIconDic).PauseGetImage = false;
//else
return;
}
- TabClass tb = _statuses.GetTabByType(MyCommon.TabUsageType.Mentions);
+ TabModel tb = _statuses.GetTabByType(MyCommon.TabUsageType.Mentions);
if (this._cfgCommon.ReplyIconState != MyCommon.REPLY_ICONSTATE.None && tb != null && tb.UnreadCount > 0)
{
if (blinkCnt > 0) return;
private async void TimerRefreshIcon_Tick(object sender, EventArgs e)
{
//200ms
- await this.RefreshTasktrayIcon(false);
+ await this.RefreshTasktrayIcon();
}
private void ContextMenuTabProperty_Opening(object sender, CancelEventArgs e)
if (_statuses == null) return;
if (_statuses.Tabs == null) return;
- TabClass tb = _statuses.Tabs[_rclickTabName];
+ TabModel tb = _statuses.Tabs[_rclickTabName];
if (tb == null) return;
NotifyDispMenuItem.Checked = tb.Notify;
MyCommon.TabUsageType tabUsage;
using (InputTabName inputName = new InputTabName())
{
- inputName.TabName = _statuses.GetUniqueTabName();
+ inputName.TabName = _statuses.MakeTabName("MyTab");
inputName.IsShowUsage = true;
inputName.ShowDialog();
if (inputName.DialogResult == DialogResult.Cancel) return;
list = listAvail.SelectedList;
}
}
- if (!_statuses.AddTab(tabName, tabUsage, list) || !AddNewTab(tabName, false, tabUsage, list))
+
+ TabModel tab;
+ switch (tabUsage)
+ {
+ case MyCommon.TabUsageType.UserDefined:
+ tab = new FilterTabModel(tabName);
+ break;
+ case MyCommon.TabUsageType.PublicSearch:
+ tab = new PublicSearchTabModel(tabName);
+ break;
+ case MyCommon.TabUsageType.Lists:
+ tab = new ListTimelineTabModel(tabName, list);
+ break;
+ default:
+ return;
+ }
+
+ if (!_statuses.AddTab(tab) || !AddNewTab(tab, startup: false))
{
string tmp = string.Format(Properties.Resources.AddTabMenuItem_ClickText1, tabName);
MessageBox.Show(tmp, Properties.Resources.AddTabMenuItem_ClickText2, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
if (tabUsage == MyCommon.TabUsageType.Lists)
{
ListTab.SelectedIndex = ListTab.TabPages.Count - 1;
- var tab = this._statuses.Tabs[this._curTab.Text];
- this.GetListTimelineAsync(tab);
+ var listTab = (ListTimelineTabModel)this._statuses.Tabs[this._curTab.Text];
+ this.GetListTimelineAsync(listTab);
}
}
}
private void IDRuleMenuItem_Click(object sender, EventArgs e)
{
- string tabName;
-
//未選択なら処理終了
if (_curList.SelectedIndices.Count == 0) return;
+ var tab = this._statuses.Tabs[this._curTab.Text];
+ var screenNameArray = this._curList.SelectedIndices.Cast<int>()
+ .Select(x => tab[x])
+ .Select(x => x.RetweetedId != null ? x.RetweetedBy : x.ScreenName)
+ .ToArray();
+
+ this.AddFilterRuleByScreenName(screenNameArray);
+
+ if (screenNameArray.Length != 0)
+ {
+ List<string> atids = new List<string>();
+ foreach (var screenName in screenNameArray)
+ {
+ atids.Add("@" + screenName);
+ }
+ int cnt = AtIdSupl.ItemCount;
+ AtIdSupl.AddRangeItem(atids.ToArray());
+ if (AtIdSupl.ItemCount != cnt) ModifySettingAtId = true;
+ }
+ }
+
+ private void SourceRuleMenuItem_Click(object sender, EventArgs e)
+ {
+ if (this._curList.SelectedIndices.Count == 0)
+ return;
+
+ var tab = this._statuses.Tabs[this._curTab.Text];
+ var sourceArray = this._curList.SelectedIndices.Cast<int>()
+ .Select(x => tab[x].Source).ToArray();
+
+ this.AddFilterRuleBySource(sourceArray);
+ }
+
+ public void AddFilterRuleByScreenName(params string[] screenNameArray)
+ {
//タブ選択(or追加)
+ string tabName;
if (!SelectTab(out tabName)) return;
- var tab = this._statuses.Tabs[tabName];
+ var tab = (FilterTabModel)this._statuses.Tabs[tabName];
bool mv;
bool mk;
this.MoveOrCopy(out mv, out mk);
}
else
- {
- // ミュートタブでは常に MoveMatches を true にする
- mv = true;
- mk = false;
- }
-
- List<string> ids = new List<string>();
- foreach (int idx in _curList.SelectedIndices)
- {
- PostClass post = _statuses.Tabs[_curTab.Text][idx];
- if (!ids.Contains(post.ScreenName))
- {
- PostFilterRule fc = new PostFilterRule();
- ids.Add(post.ScreenName);
- if (post.RetweetedId == null)
- {
- fc.FilterName = post.ScreenName;
- }
- else
- {
- fc.FilterName = post.RetweetedBy;
- }
- fc.UseNameField = true;
- fc.MoveMatches = mv;
- fc.MarkMatches = mk;
- fc.UseRegex = false;
- fc.FilterByUrl = false;
- tab.AddFilter(fc);
- }
+ {
+ // ミュートタブでは常に MoveMatches を true にする
+ mv = true;
+ mk = false;
}
- if (ids.Count != 0)
+
+ foreach (var screenName in screenNameArray)
{
- List<string> atids = new List<string>();
- foreach (string id in ids)
+ tab.AddFilter(new PostFilterRule
{
- atids.Add("@" + id);
- }
- int cnt = AtIdSupl.ItemCount;
- AtIdSupl.AddRangeItem(atids.ToArray());
- if (AtIdSupl.ItemCount != cnt) _modifySettingAtId = true;
+ FilterName = screenName,
+ UseNameField = true,
+ MoveMatches = mv,
+ MarkMatches = mk,
+ UseRegex = false,
+ FilterByUrl = false,
+ });
}
this.ApplyPostFilters();
SaveConfigsTabs();
}
- private void SourceRuleMenuItem_Click(object sender, EventArgs e)
+ public void AddFilterRuleBySource(params string[] sourceArray)
{
- if (this._curList.SelectedIndices.Count == 0)
- return;
-
// タブ選択ダイアログを表示(or追加)
string tabName;
if (!this.SelectTab(out tabName))
return;
- var currentTab = this._statuses.Tabs[this._curTab.Text];
- var filterTab = this._statuses.Tabs[tabName];
+ var filterTab = (FilterTabModel)this._statuses.Tabs[tabName];
bool mv;
bool mk;
}
// 振り分けルールに追加するSource
- var sources = new HashSet<string>();
-
- foreach (var idx in this._curList.SelectedIndices.Cast<int>())
+ foreach (var source in sourceArray)
{
- var post = currentTab[idx];
- var filterSource = post.Source;
-
- if (sources.Add(filterSource))
+ filterTab.AddFilter(new PostFilterRule
{
- var filter = new PostFilterRule
- {
- FilterSource = filterSource,
- MoveMatches = mv,
- MarkMatches = mk,
- UseRegex = false,
- FilterByUrl = false,
- };
- filterTab.AddFilter(filter);
- }
+ FilterSource = source,
+ MoveMatches = mv,
+ MarkMatches = mk,
+ UseRegex = false,
+ FilterByUrl = false,
+ });
}
this.ApplyPostFilters();
{
using (InputTabName inputName = new InputTabName())
{
- inputName.TabName = _statuses.GetUniqueTabName();
+ inputName.TabName = _statuses.MakeTabName("MyTab");
inputName.ShowDialog();
if (inputName.DialogResult == DialogResult.Cancel) return false;
tabName = inputName.TabName;
this.TopMost = this._cfgCommon.AlwaysTop;
if (!string.IsNullOrEmpty(tabName))
{
- if (!_statuses.AddTab(tabName, MyCommon.TabUsageType.UserDefined, null) || !AddNewTab(tabName, false, MyCommon.TabUsageType.UserDefined))
+ var tab = new FilterTabModel(tabName);
+ if (!_statuses.AddTab(tab) || !AddNewTab(tab, startup: false))
{
string tmp = string.Format(Properties.Resources.IDRuleMenuItem_ClickText2, tabName);
MessageBox.Show(tmp, Properties.Resources.IDRuleMenuItem_ClickText3, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
private async void OpenURLMenuItem_Click(object sender, EventArgs e)
{
- var linkElements = this.PostBrowser.Document.Links.Cast<HtmlElement>()
- .Where(x => x.GetAttribute("className") != "tweet-quote-link") // 引用ツイートで追加されたリンクを除く
- .ToArray();
+ var linkElements = this.tweetDetailsView.GetLinkElements();
if (linkElements.Length > 0)
{
//ステータス欄にカウント表示
//タブ未読数/タブ発言数 全未読数/総発言数 (未読@+未読DM数)
if (_statuses == null) return "";
- TabClass tbRep = _statuses.GetTabByType(MyCommon.TabUsageType.Mentions);
- TabClass tbDm = _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage);
+ TabModel tbRep = _statuses.GetTabByType(MyCommon.TabUsageType.Mentions);
+ TabModel tbDm = _statuses.GetTabByType(MyCommon.TabUsageType.DirectMessage);
if (tbRep == null || tbDm == null) return "";
int urat = tbRep.UnreadCount + tbDm.UnreadCount;
int ur = 0;
UnreadCounter = ur;
UnreadAtCounter = urat;
- slbl.AppendFormat(Properties.Resources.SetStatusLabelText1, tur, tal, ur, al, urat, _postTimestamps.Count, _favTimestamps.Count, _tlCount);
+ var homeTab = this._statuses.GetTabByType<HomeTabModel>();
+
+ slbl.AppendFormat(Properties.Resources.SetStatusLabelText1, tur, tal, ur, al, urat, _postTimestamps.Count, _favTimestamps.Count, homeTab.TweetsPerHour);
if (this._cfgCommon.TimelinePeriod == 0)
{
slbl.Append(Properties.Resources.SetStatusLabelText2);
return slbl.ToString();
}
- private void TwitterApiStatus_AccessLimitUpdated(object sender, EventArgs e)
+ private async void TwitterApiStatus_AccessLimitUpdated(object sender, EventArgs e)
{
try
{
if (this.InvokeRequired && !this.IsDisposed)
{
- this.Invoke((MethodInvoker)(() => this.TwitterApiStatus_AccessLimitUpdated(sender, e)));
+ await this.InvokeAsync(() => this.TwitterApiStatus_AccessLimitUpdated(sender, e));
}
else
{
{
AtIdSupl.AddItem(mid.Result("${id}"));
}
- if (bCnt != AtIdSupl.ItemCount) _modifySettingAtId = true;
+ if (bCnt != AtIdSupl.ItemCount) ModifySettingAtId = true;
}
// リプライ先ステータスIDの指定がない場合は指定しない
if (m != null)
{
var inReplyToScreenName = this.inReplyTo.Item2;
- if (StatusText.StartsWith("@"))
+ if (StatusText.StartsWith("@", StringComparison.Ordinal))
{
- if (StatusText.StartsWith("@" + inReplyToScreenName)) return;
+ if (StatusText.StartsWith("@" + inReplyToScreenName, StringComparison.Ordinal)) return;
}
else
{
{
this.SplitContainer3.SplitterDistance = previewDistance;
}
+
+ // Panel2Collapsed は SplitterDistance の設定を終えるまで true にしない
+ this.SplitContainer3.Panel2Collapsed = true;
+
_initialLayout = false;
}
if (this.WindowState != FormWindowState.Minimized)
{
this._cfgCommon.PlaySound = false;
}
- _modifySettingCommon = true;
+ ModifySettingCommon = true;
}
private void SplitContainer1_SplitterMoved(object sender, SplitterEventArgs e)
}
this._mySpDis = splitterDistance;
- this._modifySettingLocal = true;
+ this.ModifySettingLocal = true;
}
private async Task doRepliedStatusOpen()
if (_statuses.ContainsKey(_curPost.InReplyToStatusId.Value))
{
PostClass repPost = _statuses[_curPost.InReplyToStatusId.Value];
- MessageBox.Show(repPost.ScreenName + " / " + repPost.Nickname + " (" + repPost.CreatedAt.ToString() + ")" + Environment.NewLine + repPost.TextFromApi);
+ MessageBox.Show($"{repPost.ScreenName} / {repPost.Nickname} ({repPost.CreatedAt})" + Environment.NewLine + repPost.TextFromApi);
}
else
{
- foreach (TabClass tb in _statuses.GetTabsByType(MyCommon.TabUsageType.Lists | MyCommon.TabUsageType.PublicSearch))
+ foreach (TabModel tb in _statuses.GetTabsByType(MyCommon.TabUsageType.Lists | MyCommon.TabUsageType.PublicSearch))
{
if (tb == null || !tb.Contains(_curPost.InReplyToStatusId.Value)) break;
PostClass repPost = _statuses[_curPost.InReplyToStatusId.Value];
- MessageBox.Show(repPost.ScreenName + " / " + repPost.Nickname + " (" + repPost.CreatedAt.ToString() + ")" + Environment.NewLine + repPost.TextFromApi);
+ MessageBox.Show($"{repPost.ScreenName} / {repPost.Nickname} ({repPost.CreatedAt})" + Environment.NewLine + repPost.TextFromApi);
return;
}
await this.OpenUriInBrowserAsync(MyCommon.GetStatusUrl(_curPost.InReplyToUser, _curPost.InReplyToStatusId.Value));
await this.doRepliedStatusOpen();
}
- /// <summary>
- /// UserPicture.Image に設定されている画像を破棄します。
- /// </summary>
- private void ClearUserPicture()
- {
- if (this.UserPicture.Image != null)
- {
- var oldImage = this.UserPicture.Image;
- this.UserPicture.Image = null;
- oldImage.Dispose();
- }
- }
-
- private void ContextMenuUserPicture_Opening(object sender, CancelEventArgs e)
- {
- //発言詳細のアイコン右クリック時のメニュー制御
- if (_curList.SelectedIndices.Count > 0 && _curPost != null)
- {
- string name = _curPost.ImageUrl;
- if (name != null && name.Length > 0)
- {
- int idx = name.LastIndexOf('/');
- if (idx != -1)
- {
- name = Path.GetFileName(name.Substring(idx));
- if (name.Contains("_normal.") || name.EndsWith("_normal"))
- {
- name = name.Replace("_normal", "");
- this.IconNameToolStripMenuItem.Text = name;
- this.IconNameToolStripMenuItem.Enabled = true;
- }
- else
- {
- this.IconNameToolStripMenuItem.Enabled = false;
- this.IconNameToolStripMenuItem.Text = Properties.Resources.ContextMenuStrip3_OpeningText1;
- }
- }
- else
- {
- this.IconNameToolStripMenuItem.Enabled = false;
- this.IconNameToolStripMenuItem.Text = Properties.Resources.ContextMenuStrip3_OpeningText1;
- }
-
- this.ReloadIconToolStripMenuItem.Enabled = true;
-
- if (this.IconCache.TryGetFromCache(_curPost.ImageUrl) != null)
- {
- this.SaveIconPictureToolStripMenuItem.Enabled = true;
- }
- else
- {
- this.SaveIconPictureToolStripMenuItem.Enabled = false;
- }
- }
- else
- {
- this.IconNameToolStripMenuItem.Enabled = false;
- this.ReloadIconToolStripMenuItem.Enabled = false;
- this.SaveIconPictureToolStripMenuItem.Enabled = false;
- this.IconNameToolStripMenuItem.Text = Properties.Resources.ContextMenuStrip3_OpeningText1;
- }
- }
- else
- {
- this.IconNameToolStripMenuItem.Enabled = false;
- this.ReloadIconToolStripMenuItem.Enabled = false;
- this.SaveIconPictureToolStripMenuItem.Enabled = false;
- this.IconNameToolStripMenuItem.Text = Properties.Resources.ContextMenuStrip3_OpeningText2;
- }
- if (NameLabel.Tag != null)
- {
- string id = (string)NameLabel.Tag;
- if (id == tw.Username)
- {
- FollowToolStripMenuItem.Enabled = false;
- UnFollowToolStripMenuItem.Enabled = false;
- ShowFriendShipToolStripMenuItem.Enabled = false;
- ShowUserStatusToolStripMenuItem.Enabled = true;
- SearchPostsDetailNameToolStripMenuItem.Enabled = true;
- SearchAtPostsDetailNameToolStripMenuItem.Enabled = false;
- ListManageUserContextToolStripMenuItem3.Enabled = true;
- }
- else
- {
- FollowToolStripMenuItem.Enabled = true;
- UnFollowToolStripMenuItem.Enabled = true;
- ShowFriendShipToolStripMenuItem.Enabled = true;
- ShowUserStatusToolStripMenuItem.Enabled = true;
- SearchPostsDetailNameToolStripMenuItem.Enabled = true;
- SearchAtPostsDetailNameToolStripMenuItem.Enabled = true;
- ListManageUserContextToolStripMenuItem3.Enabled = true;
- }
- }
- else
- {
- FollowToolStripMenuItem.Enabled = false;
- UnFollowToolStripMenuItem.Enabled = false;
- ShowFriendShipToolStripMenuItem.Enabled = false;
- ShowUserStatusToolStripMenuItem.Enabled = false;
- SearchPostsDetailNameToolStripMenuItem.Enabled = false;
- SearchAtPostsDetailNameToolStripMenuItem.Enabled = false;
- ListManageUserContextToolStripMenuItem3.Enabled = false;
- }
- }
-
- private async void IconNameToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (_curPost == null) return;
- string name = _curPost.ImageUrl;
- await this.OpenUriInBrowserAsync(name.Remove(name.LastIndexOf("_normal"), 7)); // "_normal".Length
- }
-
- private async void ReloadIconToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (this._curPost == null) return;
-
- await this.UserPicture.SetImageFromTask(async () =>
- {
- var imageUrl = this._curPost.ImageUrl;
-
- var image = await this.IconCache.DownloadImageAsync(imageUrl, force: true)
- .ConfigureAwait(false);
-
- return await image.CloneAsync()
- .ConfigureAwait(false);
- });
- }
-
- private void SaveOriginalSizeIconPictureToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (_curPost == null) return;
- string name = _curPost.ImageUrl;
- name = Path.GetFileNameWithoutExtension(name.Substring(name.LastIndexOf('/')));
-
- this.SaveFileDialog1.FileName = name.Substring(0, name.Length - 8); // "_normal".Length + 1
-
- if (this.SaveFileDialog1.ShowDialog() == DialogResult.OK)
- {
- // STUB
- }
- }
-
- private void SaveIconPictureToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (_curPost == null) return;
- string name = _curPost.ImageUrl;
-
- this.SaveFileDialog1.FileName = name.Substring(name.LastIndexOf('/') + 1);
-
- if (this.SaveFileDialog1.ShowDialog() == DialogResult.OK)
- {
- try
- {
- using (Image orgBmp = new Bitmap(IconCache.TryGetFromCache(name).Image))
- {
- using (Bitmap bmp2 = new Bitmap(orgBmp.Size.Width, orgBmp.Size.Height))
- {
- using (Graphics g = Graphics.FromImage(bmp2))
- {
- g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
- g.DrawImage(orgBmp, 0, 0, orgBmp.Size.Width, orgBmp.Size.Height);
- }
- bmp2.Save(this.SaveFileDialog1.FileName);
- }
- }
- }
- catch (Exception)
- {
- //処理中にキャッシュアウトする可能性あり
- }
- }
- }
-
private void SplitContainer2_Panel2_Resize(object sender, EventArgs e)
{
var multiline = this.SplitContainer2.Panel2.Height > this.SplitContainer2.Panel2MinSize + 2;
{
this.StatusText.Multiline = multiline;
MultiLineMenuItem.Checked = multiline;
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
}
}
else
this.StatusText.ScrollBars = ScrollBars.None;
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
}
private void MultiLineMenuItem_Click(object sender, EventArgs e)
{
SplitContainer2.SplitterDistance = SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth;
}
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
}
private async Task<bool> UrlConvertAsync(MyCommon.UrlConverter Converter_Type)
this.NotifyFileMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
this.NewPostPopMenuItem.Checked = this.NotifyFileMenuItem.Checked;
_cfgCommon.NewAllPop = NewPostPopMenuItem.Checked;
- _modifySettingCommon = true;
+ ModifySettingCommon = true;
}
private void ListLockMenuItem_CheckStateChanged(object sender, EventArgs e)
ListLockMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
this.LockListFileMenuItem.Checked = ListLockMenuItem.Checked;
_cfgCommon.ListLock = ListLockMenuItem.Checked;
- _modifySettingCommon = true;
+ ModifySettingCommon = true;
}
private void MenuStrip1_MenuActivate(object sender, EventArgs e)
_cfgLocal.Width7 = lst.Columns[6].Width;
_cfgLocal.Width8 = lst.Columns[7].Width;
}
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
_isColumnChanged = true;
}
if (_cfgLocal.Width1 != lst.Columns[0].Width)
{
_cfgLocal.Width1 = lst.Columns[0].Width;
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
_isColumnChanged = true;
}
if (_cfgLocal.Width3 != lst.Columns[1].Width)
{
_cfgLocal.Width3 = lst.Columns[1].Width;
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
_isColumnChanged = true;
}
}
if (_cfgLocal.Width1 != lst.Columns[0].Width)
{
_cfgLocal.Width1 = lst.Columns[0].Width;
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
_isColumnChanged = true;
}
if (_cfgLocal.Width2 != lst.Columns[1].Width)
{
_cfgLocal.Width2 = lst.Columns[1].Width;
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
_isColumnChanged = true;
}
if (_cfgLocal.Width3 != lst.Columns[2].Width)
{
_cfgLocal.Width3 = lst.Columns[2].Width;
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
_isColumnChanged = true;
}
if (_cfgLocal.Width4 != lst.Columns[3].Width)
{
_cfgLocal.Width4 = lst.Columns[3].Width;
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
_isColumnChanged = true;
}
if (_cfgLocal.Width5 != lst.Columns[4].Width)
{
_cfgLocal.Width5 = lst.Columns[4].Width;
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
_isColumnChanged = true;
}
if (_cfgLocal.Width6 != lst.Columns[5].Width)
{
_cfgLocal.Width6 = lst.Columns[5].Width;
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
_isColumnChanged = true;
}
if (_cfgLocal.Width7 != lst.Columns[6].Width)
{
_cfgLocal.Width7 = lst.Columns[6].Width;
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
_isColumnChanged = true;
}
if (_cfgLocal.Width8 != lst.Columns[7].Width)
{
_cfgLocal.Width8 = lst.Columns[7].Width;
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
_isColumnChanged = true;
}
}
//}
}
- private void SelectionCopyContextMenuItem_Click(object sender, EventArgs e)
- {
- //発言詳細で「選択文字列をコピー」
- string _selText = this.PostBrowser.GetSelectedText();
- try
- {
- Clipboard.SetDataObject(_selText, false, 5, 100);
- }
- catch (Exception ex)
- {
- MessageBox.Show(ex.Message);
- }
- }
-
- private async Task doSearchToolStrip(string url)
- {
- //発言詳細で「選択文字列で検索」(選択文字列取得)
- string _selText = this.PostBrowser.GetSelectedText();
-
- if (_selText != null)
- {
- if (url == Properties.Resources.SearchItem4Url)
- {
- //公式検索
- AddNewTabForSearch(_selText);
- return;
- }
-
- string tmp = string.Format(url, Uri.EscapeDataString(_selText));
- await this.OpenUriInBrowserAsync(tmp);
- }
- }
-
- private void SelectionAllContextMenuItem_Click(object sender, EventArgs e)
- {
- //発言詳細ですべて選択
- PostBrowser.Document.ExecCommand("SelectAll", false, null);
- }
-
- private async void SearchWikipediaContextMenuItem_Click(object sender, EventArgs e)
- {
- await this.doSearchToolStrip(Properties.Resources.SearchItem1Url);
- }
-
- private async void SearchGoogleContextMenuItem_Click(object sender, EventArgs e)
- {
- await this.doSearchToolStrip(Properties.Resources.SearchItem2Url);
- }
-
- private async void SearchPublicSearchContextMenuItem_Click(object sender, EventArgs e)
- {
- await this.doSearchToolStrip(Properties.Resources.SearchItem4Url);
- }
-
- private void UrlCopyContextMenuItem_Click(object sender, EventArgs e)
- {
- try
- {
- foreach (var link in this.PostBrowser.Document.Links.Cast<HtmlElement>())
- {
- if (link.GetAttribute("href") == this._postBrowserStatusText)
- {
- Clipboard.SetDataObject(link.GetAttribute("title"), false, 5, 100);
- return;
- }
- }
-
- Clipboard.SetDataObject(this._postBrowserStatusText, false, 5, 100);
- }
- catch (Exception ex)
- {
- MessageBox.Show(ex.Message);
- }
- }
-
- private void ContextMenuPostBrowser_Opening(object ender, CancelEventArgs e)
- {
- // URLコピーの項目の表示/非表示
- if (PostBrowser.StatusText.StartsWith("http"))
- {
- this._postBrowserStatusText = PostBrowser.StatusText;
- string name = GetUserId();
- UrlCopyContextMenuItem.Enabled = true;
- if (name != null)
- {
- FollowContextMenuItem.Enabled = true;
- RemoveContextMenuItem.Enabled = true;
- FriendshipContextMenuItem.Enabled = true;
- ShowUserStatusContextMenuItem.Enabled = true;
- SearchPostsDetailToolStripMenuItem.Enabled = true;
- IdFilterAddMenuItem.Enabled = true;
- ListManageUserContextToolStripMenuItem.Enabled = true;
- SearchAtPostsDetailToolStripMenuItem.Enabled = true;
- }
- else
- {
- FollowContextMenuItem.Enabled = false;
- RemoveContextMenuItem.Enabled = false;
- FriendshipContextMenuItem.Enabled = false;
- ShowUserStatusContextMenuItem.Enabled = false;
- SearchPostsDetailToolStripMenuItem.Enabled = false;
- IdFilterAddMenuItem.Enabled = false;
- ListManageUserContextToolStripMenuItem.Enabled = false;
- SearchAtPostsDetailToolStripMenuItem.Enabled = false;
- }
-
- if (Regex.IsMatch(this._postBrowserStatusText, @"^https?://twitter.com/search\?q=%23"))
- UseHashtagMenuItem.Enabled = true;
- else
- UseHashtagMenuItem.Enabled = false;
- }
- else
- {
- this._postBrowserStatusText = "";
- UrlCopyContextMenuItem.Enabled = false;
- FollowContextMenuItem.Enabled = false;
- RemoveContextMenuItem.Enabled = false;
- FriendshipContextMenuItem.Enabled = false;
- ShowUserStatusContextMenuItem.Enabled = false;
- SearchPostsDetailToolStripMenuItem.Enabled = false;
- SearchAtPostsDetailToolStripMenuItem.Enabled = false;
- UseHashtagMenuItem.Enabled = false;
- IdFilterAddMenuItem.Enabled = false;
- ListManageUserContextToolStripMenuItem.Enabled = false;
- }
- // 文字列選択されていないときは選択文字列関係の項目を非表示に
- string _selText = this.PostBrowser.GetSelectedText();
- if (_selText == null)
- {
- SelectionSearchContextMenuItem.Enabled = false;
- SelectionCopyContextMenuItem.Enabled = false;
- SelectionTranslationToolStripMenuItem.Enabled = false;
- }
- else
- {
- SelectionSearchContextMenuItem.Enabled = true;
- SelectionCopyContextMenuItem.Enabled = true;
- SelectionTranslationToolStripMenuItem.Enabled = true;
- }
- //発言内に自分以外のユーザーが含まれてればフォロー状態全表示を有効に
- MatchCollection ma = Regex.Matches(this.PostBrowser.DocumentText, @"href=""https?://twitter.com/(#!/)?(?<ScreenName>[a-zA-Z0-9_]+)(/status(es)?/[0-9]+)?""");
- bool fAllFlag = false;
- foreach (Match mu in ma)
- {
- if (mu.Result("${ScreenName}").ToLower() != tw.Username.ToLower())
- {
- fAllFlag = true;
- break;
- }
- }
- this.FriendshipAllMenuItem.Enabled = fAllFlag;
-
- if (_curPost == null)
- TranslationToolStripMenuItem.Enabled = false;
- else
- TranslationToolStripMenuItem.Enabled = true;
-
- e.Cancel = false;
- }
-
- private void CurrentTabToolStripMenuItem_Click(object sender, EventArgs e)
- {
- //発言詳細の選択文字列で現在のタブを検索
- string _selText = this.PostBrowser.GetSelectedText();
-
- if (_selText != null)
- {
- var searchOptions = new SearchWordDialog.SearchOptions(
- SearchWordDialog.SearchType.Timeline,
- _selText,
- newTab: false,
- caseSensitive: false,
- useRegex: false);
-
- this.SearchDialog.ResultOptions = searchOptions;
-
- this.DoTabSearch(
- searchOptions.Query,
- searchOptions.CaseSensitive,
- searchOptions.UseRegex,
- SEARCHTYPE.NextSearch);
- }
- }
-
private void SplitContainer2_SplitterMoved(object sender, SplitterEventArgs e)
{
if (StatusText.Multiline) _mySpDis2 = StatusText.Height;
- _modifySettingLocal = true;
+ ModifySettingLocal = true;
}
private void TweenMain_DragDrop(object sender, DragEventArgs e)
var configBrowserPath = this._cfgLocal.BrowserPath;
if (!string.IsNullOrEmpty(configBrowserPath))
{
- if (configBrowserPath.StartsWith("\"") && configBrowserPath.Length > 2 && configBrowserPath.IndexOf("\"", 2) > -1)
+ if (configBrowserPath.StartsWith("\"", StringComparison.Ordinal) && configBrowserPath.Length > 2 && configBrowserPath.IndexOf("\"", 2, StringComparison.Ordinal) > -1)
{
- int sep = configBrowserPath.IndexOf("\"", 2);
+ int sep = configBrowserPath.IndexOf("\"", 2, StringComparison.Ordinal);
string browserPath = configBrowserPath.Substring(1, sep - 1);
string arg = "";
if (sep < configBrowserPath.Length - 1)
tw.PostDeleted += tw_PostDeleted;
tw.UserStreamEventReceived += tw_UserStreamEventArrived;
- MenuItemUserStream.Text = "&UserStream ■";
- MenuItemUserStream.Enabled = true;
- StopToolStripMenuItem.Text = "&Start";
- StopToolStripMenuItem.Enabled = true;
- if (this._cfgCommon.UserstreamStartup) tw.StartUserStream();
+ this.RefreshUserStreamsMenu();
+
+ if (this._cfgCommon.UserstreamStartup)
+ tw.StartUserStream();
}
private async void TweenMain_Shown(object sender, EventArgs e)
{
- try
- {
- using (ControlTransaction.Update(this.PostBrowser))
- {
- PostBrowser.Url = new Uri("about:blank");
- PostBrowser.DocumentText = ""; //発言詳細部初期化
- }
- }
- catch (Exception)
- {
- }
-
NotifyIcon1.Visible = true;
if (this.IsNetworkAvailable())
private async void DumpPostClassToolStripMenuItem_Click(object sender, EventArgs e)
{
+ this.tweetDetailsView.DumpPostClass = this.DumpPostClassToolStripMenuItem.Checked;
+
if (_curPost != null)
await this.DispSelectedPost(true);
}
try
{
- var task = Task.Run(() => this.tw.GetInfoApi());
+ var task = this.tw.GetInfoApi();
apiStatus = await dialog.WaitForAsync(this, task);
}
catch (WebApiException)
await this.FollowCommand(id);
}
- private async Task FollowCommand(string id)
+ internal async Task FollowCommand(string id)
{
using (var inputName = new InputTabName())
{
{
try
{
- var task = Task.Run(() => this.tw.PostFollowCommand(id));
+ var task = this.twitterApi.FriendshipsCreate(id);
await dialog.WaitForAsync(this, task);
}
catch (WebApiException ex)
await this.RemoveCommand(id, false);
}
- private async Task RemoveCommand(string id, bool skipInput)
+ internal async Task RemoveCommand(string id, bool skipInput)
{
if (!skipInput)
{
{
try
{
- var task = Task.Run(() => this.tw.PostRemoveCommand(id));
+ var task = this.twitterApi.FriendshipsDestroy(id);
await dialog.WaitForAsync(this, task);
}
catch (WebApiException ex)
await this.ShowFriendship(id);
}
- private async Task ShowFriendship(string id)
+ internal async Task ShowFriendship(string id)
{
using (var inputName = new InputTabName())
{
try
{
- var task = Task.Run(() => this.tw.GetFriendshipInfo(id));
+ var task = this.twitterApi.FriendshipsShow(this.twitterApi.CurrentScreenName, id);
var friendship = await dialog.WaitForAsync(this, task);
isFollowing = friendship.Relationship.Source.Following;
catch (WebApiException ex)
{
if (!cancellationToken.IsCancellationRequested)
- MessageBox.Show(ex.Message);
+ MessageBox.Show($"Err:{ex.Message}(FriendshipsShow)");
return;
}
MessageBox.Show(result);
}
- private async Task ShowFriendship(string[] ids)
+ internal async Task ShowFriendship(string[] ids)
{
foreach (string id in ids)
{
try
{
- var task = Task.Run(() => this.tw.GetFriendshipInfo(id));
+ var task = this.twitterApi.FriendshipsShow(this.twitterApi.CurrentScreenName, id);
var friendship = await dialog.WaitForAsync(this, task);
isFollowing = friendship.Relationship.Source.Following;
catch (WebApiException ex)
{
if (!cancellationToken.IsCancellationRequested)
- MessageBox.Show(ex.Message);
+ MessageBox.Show($"Err:{ex.Message}(FriendshipsShow)");
return;
}
await this.RemoveCommand(id, true);
}
}
- else
- {
- MessageBox.Show(result);
- }
- }
- }
-
- private async void OwnStatusMenuItem_Click(object sender, EventArgs e)
- {
- await this.doShowUserStatus(tw.Username, false);
- //if (!string.IsNullOrEmpty(tw.UserInfoXml))
- //{
- // doShowUserStatus(tw.Username, false);
- //}
- //else
- //{
- // MessageBox.Show(Properties.Resources.ShowYourProfileText1, "Your status", MessageBoxButtons.OK, MessageBoxIcon.Information);
- // return;
- //}
- }
-
- // TwitterIDでない固定文字列を調べる(文字列検証のみ 実際に取得はしない)
- // URLから切り出した文字列を渡す
-
- public bool IsTwitterId(string name)
- {
- if (this.tw.Configuration.NonUsernamePaths == null || this.tw.Configuration.NonUsernamePaths.Length == 0)
- return !Regex.Match(name, @"^(about|jobs|tos|privacy|who_to_follow|download|messages)$", RegexOptions.IgnoreCase).Success;
- else
- return !this.tw.Configuration.NonUsernamePaths.Contains(name.ToLower());
- }
-
- private string GetUserId()
- {
- Match m = Regex.Match(this._postBrowserStatusText, @"^https?://twitter.com/(#!/)?(?<ScreenName>[a-zA-Z0-9_]+)(/status(es)?/[0-9]+)?$");
- if (m.Success && IsTwitterId(m.Result("${ScreenName}")))
- return m.Result("${ScreenName}");
- else
- return null;
- }
-
- private async void FollowContextMenuItem_Click(object sender, EventArgs e)
- {
- string name = GetUserId();
- if (name != null)
- await this.FollowCommand(name);
- }
-
- private async void RemoveContextMenuItem_Click(object sender, EventArgs e)
- {
- string name = GetUserId();
- if (name != null)
- await this.RemoveCommand(name, false);
- }
-
- private async void FriendshipContextMenuItem_Click(object sender, EventArgs e)
- {
- string name = GetUserId();
- if (name != null)
- await this.ShowFriendship(name);
- }
-
- private async void FriendshipAllMenuItem_Click(object sender, EventArgs e)
- {
- MatchCollection ma = Regex.Matches(this.PostBrowser.DocumentText, @"href=""https?://twitter.com/(#!/)?(?<ScreenName>[a-zA-Z0-9_]+)(/status(es)?/[0-9]+)?""");
- List<string> ids = new List<string>();
- foreach (Match mu in ma)
- {
- if (mu.Result("${ScreenName}").ToLower() != tw.Username.ToLower())
+ else
{
- ids.Add(mu.Result("${ScreenName}"));
+ MessageBox.Show(result);
}
}
-
- await this.ShowFriendship(ids.ToArray());
}
- private async void ShowUserStatusContextMenuItem_Click(object sender, EventArgs e)
+ private async void OwnStatusMenuItem_Click(object sender, EventArgs e)
{
- string name = GetUserId();
- if (name != null)
- await this.ShowUserStatus(name);
+ await this.doShowUserStatus(tw.Username, false);
+ //if (!string.IsNullOrEmpty(tw.UserInfoXml))
+ //{
+ // doShowUserStatus(tw.Username, false);
+ //}
+ //else
+ //{
+ // MessageBox.Show(Properties.Resources.ShowYourProfileText1, "Your status", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ // return;
+ //}
}
- private void SearchPostsDetailToolStripMenuItem_Click(object sender, EventArgs e)
- {
- string name = GetUserId();
- if (name != null) AddNewTabForUserTimeline(name);
- }
+ // TwitterIDでない固定文字列を調べる(文字列検証のみ 実際に取得はしない)
+ // URLから切り出した文字列を渡す
- private void SearchAtPostsDetailToolStripMenuItem_Click(object sender, EventArgs e)
+ public bool IsTwitterId(string name)
{
- string name = GetUserId();
- if (name != null) AddNewTabForSearch("@" + name);
+ if (this.tw.Configuration.NonUsernamePaths == null || this.tw.Configuration.NonUsernamePaths.Length == 0)
+ return !Regex.Match(name, @"^(about|jobs|tos|privacy|who_to_follow|download|messages)$", RegexOptions.IgnoreCase).Success;
+ else
+ return !this.tw.Configuration.NonUsernamePaths.Contains(name.ToLowerInvariant());
}
private void IdeographicSpaceToSpaceToolStripMenuItem_Click(object sender, EventArgs e)
{
- _modifySettingCommon = true;
+ ModifySettingCommon = true;
}
private void ToolStripFocusLockMenuItem_CheckedChanged(object sender, EventArgs e)
{
- _modifySettingCommon = true;
+ ModifySettingCommon = true;
}
private void doQuoteOfficial()
Control pnl = ((Control)sender).Parent;
if (pnl == null) return;
string tbName = pnl.Parent.Text;
- TabClass tb = _statuses.Tabs[tbName];
+ var tb = (PublicSearchTabModel)_statuses.Tabs[tbName];
ComboBox cmb = (ComboBox)pnl.Controls["comboSearch"];
ComboBox cmbLang = (ComboBox)pnl.Controls["comboLang"];
cmb.Text = cmb.Text.Trim();
var listView = (DetailsListView)pnl.Parent.Tag;
+ var queryChanged = tb.SearchWords != cmb.Text || tb.SearchLang != cmbLang.Text;
+
tb.SearchWords = cmb.Text;
tb.SearchLang = cmbLang.Text;
if (string.IsNullOrEmpty(cmb.Text))
SaveConfigsTabs();
return;
}
- if (tb.IsSearchQueryChanged)
+ if (queryChanged)
{
int idx = cmb.Items.IndexOf(tb.SearchWords);
if (idx > -1) cmb.Items.RemoveAt(idx);
{
DetailsListView listView = null;
- TabClass tb = _statuses.RemovedTab.Pop();
+ TabModel tb = _statuses.RemovedTab.Pop();
if (tb.TabType == MyCommon.TabUsageType.Related)
{
var relatedTab = _statuses.GetTabByType(MyCommon.TabUsageType.Related);
for (int i = 2; i <= 100; i++)
{
if (!_statuses.ContainsTab(renamed)) break;
- renamed = TabName + i.ToString();
+ renamed = TabName + i;
}
tb.TabName = renamed;
- AddNewTab(renamed, false, tb.TabType, tb.ListInfo);
- _statuses.Tabs.Add(renamed, tb); // 後に
+
+ _statuses.AddTab(tb);
+ AddNewTab(tb, startup: false);
var tabPage = ListTab.TabPages[ListTab.TabPages.Count - 1];
listView = (DetailsListView)tabPage.Tag;
for (int i = 1; i < int.MaxValue; i++)
{
if (!_statuses.ContainsTab(renamed)) break;
- renamed = tb.TabName + "(" + i.ToString() + ")";
+ renamed = tb.TabName + "(" + i + ")";
}
tb.TabName = renamed;
- _statuses.Tabs.Add(renamed, tb); // 先に
- AddNewTab(renamed, false, tb.TabType, tb.ListInfo);
+
+ _statuses.AddTab(tb);
+ AddNewTab(tb, startup: false);
var tabPage = ListTab.TabPages[ListTab.TabPages.Count - 1];
listView = (DetailsListView)tabPage.Tag;
await this.doMoveToRTHome();
}
- private void IdFilterAddMenuItem_Click(object sender, EventArgs e)
+ private async void ListManageUserContextToolStripMenuItem_Click(object sender, EventArgs e)
{
- string name = GetUserId();
- if (name != null)
- {
- string tabName;
-
- //未選択なら処理終了
- if (_curList.SelectedIndices.Count == 0) return;
-
- //タブ選択(or追加)
- if (!SelectTab(out tabName)) return;
-
- var tab = this._statuses.Tabs[tabName];
-
- bool mv;
- bool mk;
- if (tab.TabType != MyCommon.TabUsageType.Mute)
- {
- this.MoveOrCopy(out mv, out mk);
- }
- else
- {
- // ミュートタブでは常に MoveMatches を true にする
- mv = true;
- mk = false;
- }
-
- PostFilterRule fc = new PostFilterRule();
- fc.FilterName = name;
- fc.UseNameField = true;
- fc.MoveMatches = mv;
- fc.MarkMatches = mk;
- fc.UseRegex = false;
- fc.FilterByUrl = false;
- tab.AddFilter(fc);
-
- this.ApplyPostFilters();
- SaveConfigsTabs();
- }
+ var screenName = this._curPost?.ScreenName;
+ if (screenName != null)
+ await this.ListManageUserContext(screenName);
}
- private void ListManageUserContextToolStripMenuItem_Click(object sender, EventArgs e)
+ public async Task ListManageUserContext(string screenName)
{
- string user;
-
- ToolStripMenuItem menuItem = (ToolStripMenuItem)sender;
-
- if (menuItem.Owner == this.ContextMenuPostBrowser)
- {
- user = GetUserId();
- if (user == null) return;
- }
- else if (this._curPost != null)
- {
- user = this._curPost.ScreenName;
- }
- else
- {
- return;
- }
-
- if (TabInformations.GetInstance().SubscribableLists.Count == 0)
+ if (this._statuses.SubscribableLists.Count == 0)
{
try
{
- this.tw.GetListsApi();
+ using (var dialog = new WaitingDialog(Properties.Resources.ListsGetting))
+ {
+ var cancellationToken = dialog.EnableCancellation();
+
+ var task = this.tw.GetListsApi();
+ await dialog.WaitForAsync(this, task);
+
+ cancellationToken.ThrowIfCancellationRequested();
+ }
}
+ catch (OperationCanceledException) { return; }
catch (WebApiException ex)
{
MessageBox.Show("Failed to get lists. (" + ex.Message + ")");
}
}
- using (MyLists listSelectForm = new MyLists(user, this.tw))
+ using (MyLists listSelectForm = new MyLists(screenName, this.tw))
{
listSelectForm.ShowDialog(this);
}
}
}
- private void UseHashtagMenuItem_Click(object sender, EventArgs e)
- {
- Match m = Regex.Match(this._postBrowserStatusText, @"^https?://twitter.com/search\?q=%23(?<hash>.+)$");
- if (m.Success)
- {
- HashMgr.SetPermanentHash("#" + Uri.UnescapeDataString(m.Result("${hash}")));
- HashStripSplitButton.Text = HashMgr.UseHash;
- HashToggleMenuItem.Checked = true;
- HashToggleToolStripMenuItem.Checked = true;
- //使用ハッシュタグとして設定
- _modifySettingCommon = true;
- }
- }
-
private void StatusLabel_DoubleClick(object sender, EventArgs e)
{
MessageBox.Show(StatusLabel.TextHistory, "Logs", MessageBoxButtons.OK, MessageBoxIcon.None);
// StatusText.SelectionStart = sidx;
// StatusText.Focus();
//}
- _modifySettingCommon = true;
+ ModifySettingCommon = true;
this.StatusText_TextChanged(null, null);
}
HashToggleMenuItem.Checked = false;
HashToggleToolStripMenuItem.Checked = false;
}
- _modifySettingCommon = true;
+ ModifySettingCommon = true;
this.StatusText_TextChanged(null, null);
}
HashToggleMenuItem_Click(null, null);
}
+ public void SetPermanentHashtag(string hashtag)
+ {
+ HashMgr.SetPermanentHash("#" + hashtag);
+ HashStripSplitButton.Text = HashMgr.UseHash;
+ HashToggleMenuItem.Checked = true;
+ HashToggleToolStripMenuItem.Checked = true;
+ //使用ハッシュタグとして設定
+ ModifySettingCommon = true;
+ }
+
private void MenuItemOperate_DropDownOpening(object sender, EventArgs e)
{
if (ListTab.SelectedTab == null) return;
}
this._mySpDis3 = splitterDistance;
- this._modifySettingLocal = true;
+ this.ModifySettingLocal = true;
}
private void MenuItemEdit_DropDownOpening(object sender, EventArgs e)
try
{
- var task = Task.Run(() => this.tw.GetUserInfo(id));
+ var task = this.twitterApi.UsersShow(id);
user = await dialog.WaitForAsync(this, task);
}
catch (WebApiException ex)
{
if (!cancellationToken.IsCancellationRequested)
- MessageBox.Show(ex.Message);
+ MessageBox.Show($"Err:{ex.Message}(UsersShow)");
return;
}
private async Task doShowUserStatus(TwitterUser user)
{
- using (var userDialog = new UserInfoDialog(this, this.tw))
+ using (var userDialog = new UserInfoDialog(this, this.twitterApi))
{
var showUserTask = userDialog.ShowUserAsync(user);
userDialog.ShowDialog(this);
}
}
- private Task ShowUserStatus(string id, bool ShowInputDialog)
+ internal Task ShowUserStatus(string id, bool ShowInputDialog)
{
return this.doShowUserStatus(id, ShowInputDialog);
}
- private Task ShowUserStatus(string id)
+ internal Task ShowUserStatus(string id)
{
return this.doShowUserStatus(id, true);
}
- private async void FollowToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (NameLabel.Tag != null)
- {
- string id = (string)NameLabel.Tag;
- if (id != tw.Username)
- {
- await this.FollowCommand(id);
- }
- }
- }
-
- private async void UnFollowToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (NameLabel.Tag != null)
- {
- string id = (string)NameLabel.Tag;
- if (id != tw.Username)
- {
- await this.RemoveCommand(id, false);
- }
- }
- }
-
- private async void ShowFriendShipToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (NameLabel.Tag != null)
- {
- string id = (string)NameLabel.Tag;
- if (id != tw.Username)
- {
- await this.ShowFriendship(id);
- }
- }
- }
-
- private async void ShowUserStatusToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (NameLabel.Tag != null)
- {
- string id = (string)NameLabel.Tag;
- await this.ShowUserStatus(id, false);
- }
- }
-
- private void SearchPostsDetailNameToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (NameLabel.Tag != null)
- {
- string id = (string)NameLabel.Tag;
- AddNewTabForUserTimeline(id);
- }
- }
-
- private void SearchAtPostsDetailNameToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (NameLabel.Tag != null)
- {
- string id = (string)NameLabel.Tag;
- AddNewTabForSearch("@" + id);
- }
- }
-
private async void ShowProfileMenuItem_Click(object sender, EventArgs e)
{
if (_curPost != null)
return;
var statusId = this._curPost.RetweetedId ?? this._curPost.StatusId;
- int retweetCount = 0;
+ TwitterStatus status;
using (var dialog = new WaitingDialog(Properties.Resources.RtCountMenuItem_ClickText1))
{
try
{
- var task = Task.Run(() => this.tw.GetStatus_Retweeted_Count(statusId));
- retweetCount = await dialog.WaitForAsync(this, task);
+ var task = this.twitterApi.StatusesShow(statusId);
+ status = await dialog.WaitForAsync(this, task);
}
catch (WebApiException ex)
{
if (!cancellationToken.IsCancellationRequested)
- MessageBox.Show(Properties.Resources.RtCountText2 + Environment.NewLine + ex.Message);
+ MessageBox.Show(Properties.Resources.RtCountText2 + Environment.NewLine + "Err:" + ex.Message);
return;
}
return;
}
- MessageBox.Show(retweetCount + Properties.Resources.RtCountText1);
+ MessageBox.Show(status.RetweetCount + Properties.Resources.RtCountText1);
}
private HookGlobalHotkey _hookGlobalHotkey;
this.StatusText.Dock = DockStyle.Fill;
}
+ this.tweetDetailsView.Owner = this;
+
this.TimerTimeline.Elapsed += this.TimerTimeline_Elapsed;
this._hookGlobalHotkey.HotkeyPressed += _hookGlobalHotkey_HotkeyPressed;
this.gh.NotifyClicked += GrowlHelper_Callback;
this.ImageSelector.Enabled = false;
this.ImageSelector.FilePickDialog = OpenFileDialog1;
+ this.workerProgress = new Progress<string>(x => this.StatusLabel.Text = x);
+
this.ReplaceAppName();
this.InitializeShortcuts();
}
}
}
- private void UserPicture_MouseEnter(object sender, EventArgs e)
- {
- this.UserPicture.Cursor = Cursors.Hand;
- }
-
- private void UserPicture_MouseLeave(object sender, EventArgs e)
- {
- this.UserPicture.Cursor = Cursors.Default;
- }
-
- private async void UserPicture_DoubleClick(object sender, EventArgs e)
- {
- if (NameLabel.Tag != null)
- {
- await this.OpenUriInBrowserAsync(MyCommon.TwitterUrl + NameLabel.Tag.ToString());
- }
- }
-
private void SplitContainer2_MouseDoubleClick(object sender, MouseEventArgs e)
{
this.MultiLineMenuItem.PerformClick();
{
if (ImageSelector.Visible)
{
- _modifySettingCommon = true;
+ ModifySettingCommon = true;
SaveConfigsAll(true);
if (ImageSelector.ServiceName.Equals("Twitter"))
}
}
- public bool ModifySettingCommon
- {
- set { _modifySettingCommon = value; }
- }
-
- public bool ModifySettingLocal
- {
- set { _modifySettingLocal = value; }
- }
-
- public bool ModifySettingAtId
- {
- set { _modifySettingAtId = value; }
- }
-
- private async void SourceLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
- {
- var sourceUri = (Uri)this.SourceLinkLabel.Tag;
- if (sourceUri != null && e.Button == MouseButtons.Left)
- {
- await this.OpenUriInBrowserAsync(sourceUri.AbsoluteUri);
- }
- }
-
- private void SourceLinkLabel_MouseEnter(object sender, EventArgs e)
- {
- var sourceUri = (Uri)this.SourceLinkLabel.Tag;
- if (sourceUri != null)
- {
- StatusLabelUrl.Text = MyCommon.ConvertToReadableUrl(sourceUri.AbsoluteUri);
- }
- }
-
- private void SourceLinkLabel_MouseLeave(object sender, EventArgs e)
- {
- SetStatusLabelUrl();
- }
+ public bool ModifySettingCommon { get; set; }
+ public bool ModifySettingLocal { get; set; }
+ public bool ModifySettingAtId { get; set; }
private void MenuItemCommand_DropDownOpening(object sender, EventArgs e)
{
{
try
{
- post = await Task.Run(() => this.tw.GetStatusApi(false, statusId));
+ post = await this.tw.GetStatusApi(false, statusId);
}
catch (WebApiException ex)
{
- this.StatusLabel.Text = ex.Message;
+ this.StatusLabel.Text = $"Err:{ex.Message}(GetStatus)";
return;
}
}
/// <exception cref="TabException">名前の重複が多すぎてタブを作成できない場合</exception>
private async Task OpenRelatedTab(PostClass post)
{
- var tabRelated = this._statuses.GetTabByType(MyCommon.TabUsageType.Related);
- string tabName;
-
- if (tabRelated == null)
+ var tabRelated = this._statuses.GetTabByType<RelatedPostsTabModel>();
+ if (tabRelated != null)
{
- tabName = this._statuses.MakeTabName("Related Tweets");
+ this.RemoveSpecifiedTab(tabRelated.TabName, confirm: false);
+ }
- this.AddNewTab(tabName, false, MyCommon.TabUsageType.Related);
- this._statuses.AddTab(tabName, MyCommon.TabUsageType.Related, null);
+ var tabName = this._statuses.MakeTabName("Related Tweets");
- tabRelated = this._statuses.GetTabByType(MyCommon.TabUsageType.Related);
- tabRelated.UnreadManage = false;
- tabRelated.Notify = false;
- }
- else
- {
- tabName = tabRelated.TabName;
- }
+ tabRelated = new RelatedPostsTabModel(tabName, post);
+ tabRelated.UnreadManage = false;
+ tabRelated.Notify = false;
- tabRelated.RelationTargetPost = post;
- this.ClearTab(tabName, false);
+ this._statuses.AddTab(tabRelated);
+ this.AddNewTab(tabRelated, startup: false);
+ TabPage tabPage;
for (int i = 0; i < this.ListTab.TabPages.Count; i++)
{
- var tabPage = this.ListTab.TabPages[i];
+ tabPage = this.ListTab.TabPages[i];
if (tabName == tabPage.Text)
{
this.ListTab.SelectedIndex = i;
}
await this.GetRelatedTweetsAsync(tabRelated);
+
+ tabPage = this.ListTab.TabPages.Cast<TabPage>()
+ .FirstOrDefault(x => x.Text == tabRelated.TabName);
+
+ if (tabPage != null)
+ {
+ // TODO: 非同期更新中にタブが閉じられている場合を厳密に考慮したい
+
+ var listView = (DetailsListView)tabPage.Tag;
+ var targetPost = tabRelated.TargetPost;
+ var index = tabRelated.IndexOf(targetPost.RetweetedId ?? targetPost.StatusId);
+
+ if (index != -1 && index < listView.Items.Count)
+ {
+ listView.SelectedIndices.Add(index);
+ listView.Items[index].Focused = true;
+ }
+ }
}
private void CacheInfoMenuItem_Click(object sender, EventArgs e)
private void tw_UserIdChanged()
{
- this._modifySettingCommon = true;
+ this.ModifySettingCommon = true;
}
#region "Userstream"
- private bool _isActiveUserstream = false;
-
private void tw_PostDeleted(object sender, PostDeletedEventArgs e)
{
try
{
Invoke((Action) (async () =>
{
- _statuses.RemovePostReserve(e.StatusId);
+ this._statuses.RemovePostFromAllTabs(e.StatusId, setIsDeleted: true);
if (_curTab != null && _statuses.Tabs[_curTab.Text].Contains(e.StatusId))
{
this.PurgeListViewItemCache();
}
}
- private void tw_NewPostFromStream(object sender, EventArgs e)
+ private int userStreamsRefreshing = 0;
+
+ private async void tw_NewPostFromStream(object sender, EventArgs e)
{
if (this._cfgCommon.ReadOldPosts)
{
_statuses.SetReadHomeTab(); //新着時未読クリア
}
- int rsltAddCount = _statuses.DistributePosts();
- lock (_syncObject)
- {
- DateTime tm = DateTime.Now;
- if (_tlTimestamps.ContainsKey(tm))
- {
- _tlTimestamps[tm] += rsltAddCount;
- }
- else
- {
- _tlTimestamps.Add(tm, rsltAddCount);
- }
- DateTime oneHour = DateTime.Now.Subtract(new TimeSpan(1, 0, 0));
- List<DateTime> keys = new List<DateTime>();
- _tlCount = 0;
- foreach (DateTime key in _tlTimestamps.Keys)
- {
- if (key.CompareTo(oneHour) < 0)
- keys.Add(key);
- else
- _tlCount += _tlTimestamps[key];
- }
- foreach (DateTime key in keys)
- {
- _tlTimestamps.Remove(key);
- }
- keys.Clear();
-
- //Static DateTime before = Now;
- //if (before.Subtract(Now).Seconds > -5) return;
- //before = Now;
- }
+ this._statuses.DistributePosts();
if (this._cfgCommon.UserstreamPeriod > 0) return;
- try
+ // userStreamsRefreshing が 0 (インクリメント後は1) であれば RefreshTimeline を実行
+ if (Interlocked.Increment(ref this.userStreamsRefreshing) == 1)
{
- if (InvokeRequired && !IsDisposed)
+ try
{
- Invoke((Action)(async () =>
- {
- await this.RefreshTasktrayIcon(true);
- this.RefreshTimeline(true);
- }));
- return;
+ await this.InvokeAsync(() => this.RefreshTimeline())
+ .ConfigureAwait(false);
+ }
+ finally
+ {
+ Interlocked.Exchange(ref this.userStreamsRefreshing, 0);
}
- }
- catch (ObjectDisposedException)
- {
- return;
- }
- catch (InvalidOperationException)
- {
- return;
}
}
private void tw_UserStreamStarted(object sender, EventArgs e)
{
- this._isActiveUserstream = true;
try
{
if (InvokeRequired && !IsDisposed)
return;
}
- MenuItemUserStream.Text = "&UserStream ▶";
- MenuItemUserStream.Enabled = true;
- StopToolStripMenuItem.Text = "&Stop";
- StopToolStripMenuItem.Enabled = true;
+ this.RefreshUserStreamsMenu();
+ this.MenuItemUserStream.Enabled = true;
StatusLabel.Text = "UserStream Started.";
}
private void tw_UserStreamStopped(object sender, EventArgs e)
{
- this._isActiveUserstream = false;
try
{
if (InvokeRequired && !IsDisposed)
return;
}
- MenuItemUserStream.Text = "&UserStream ■";
- MenuItemUserStream.Enabled = true;
- StopToolStripMenuItem.Text = "&Start";
- StopToolStripMenuItem.Enabled = true;
+ this.RefreshUserStreamsMenu();
+ this.MenuItemUserStream.Enabled = true;
StatusLabel.Text = "UserStream Stopped.";
}
+ private void RefreshUserStreamsMenu()
+ {
+ if (this.tw.UserStreamActive)
+ {
+ this.MenuItemUserStream.Text = "&UserStream ▶";
+ this.StopToolStripMenuItem.Text = "&Stop";
+ }
+ else
+ {
+ this.MenuItemUserStream.Text = "&UserStream ■";
+ this.StopToolStripMenuItem.Text = "&Start";
+ }
+ }
+
private void tw_UserStreamEventArrived(object sender, UserStreamEventReceivedEventArgs e)
{
try
this.PurgeListViewItemCache();
((DetailsListView)_curTab.Tag).Update();
}
- if (ev.Event == "unfavorite" && ev.Username.ToLower().Equals(tw.Username.ToLower()))
+ if (ev.Event == "unfavorite" && ev.Username.ToLowerInvariant().Equals(tw.Username.ToLowerInvariant()))
{
- RemovePostFromFavTab(new long[] {ev.Id});
+ var favTab = this._statuses.GetTabByType(MyCommon.TabUsageType.Favorites);
+ favTab.EnqueueRemovePost(ev.Id, setIsDeleted: false);
}
}
}
}
title.Append(Application.ProductName);
title.Append(" [");
- title.Append(ev.Event.ToUpper());
+ title.Append(ev.Event.ToUpper(CultureInfo.CurrentCulture));
title.Append("] by ");
if (!string.IsNullOrEmpty(ev.Username))
{
- title.Append(ev.Username.ToString());
+ title.Append(ev.Username);
}
else
{
StopRefreshAllMenuItem.Checked = false;
return;
}
- if (this._isActiveUserstream)
+ if (this.tw.UserStreamActive)
{
tw.StopUserStream();
}
if (!inputTrack.Equals(tw.TrackWord))
{
tw.TrackWord = inputTrack;
- this._modifySettingCommon = true;
+ this.ModifySettingCommon = true;
TrackToolStripMenuItem.Checked = !string.IsNullOrEmpty(inputTrack);
tw.ReconnectUserStream();
}
tw.TrackWord = "";
tw.ReconnectUserStream();
}
- this._modifySettingCommon = true;
+ this.ModifySettingCommon = true;
}
private void AllrepliesToolStripMenuItem_Click(object sender, EventArgs e)
{
tw.AllAtReply = AllrepliesToolStripMenuItem.Checked;
- this._modifySettingCommon = true;
+ this.ModifySettingCommon = true;
tw.ReconnectUserStream();
}
await this.OpenUriInBrowserAsync(MyCommon.TwitterUrl + tw.Username);
}
- private async Task doTranslation(string str)
- {
- if (string.IsNullOrEmpty(str))
- return;
-
- var bing = new Bing();
- try
- {
- var translatedText = await bing.TranslateAsync(str,
- langFrom: null,
- langTo: this._cfgCommon.TranslateLanguage);
-
- this.PostBrowser.DocumentText = this.createDetailHtml(translatedText);
- }
- catch (HttpRequestException e)
- {
- this.StatusLabel.Text = "Err:" + e.Message;
- }
- }
-
- private async void TranslationToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (!this.ExistCurrentPost)
- return;
-
- await this.doTranslation(this._curPost.TextFromApi);
- }
-
- private async void SelectionTranslationToolStripMenuItem_Click(object sender, EventArgs e)
- {
- var text = this.PostBrowser.GetSelectedText();
- await this.doTranslation(text);
- }
-
private bool ExistCurrentPost
{
get
await this.OpenUserAppointUrl();
}
- private void SourceCopyMenuItem_Click(object sender, EventArgs e)
- {
- string selText = SourceLinkLabel.Text;
- try
- {
- Clipboard.SetDataObject(selText, false, 5, 100);
- }
- catch (Exception ex)
- {
- MessageBox.Show(ex.Message);
- }
- }
-
- private void SourceUrlCopyMenuItem_Click(object sender, EventArgs e)
- {
- var sourceUri = (Uri)this.SourceLinkLabel.Tag;
- try
- {
- Clipboard.SetDataObject(sourceUri.AbsoluteUri, false, 5, 100);
- }
- catch (Exception ex)
- {
- MessageBox.Show(ex.Message);
- }
- }
-
- private void ContextMenuSource_Opening(object sender, CancelEventArgs e)
- {
- if (_curPost == null || !ExistCurrentPost || _curPost.IsDm)
- {
- SourceCopyMenuItem.Enabled = false;
- SourceUrlCopyMenuItem.Enabled = false;
- }
- else
- {
- SourceCopyMenuItem.Enabled = true;
- SourceUrlCopyMenuItem.Enabled = true;
- }
- }
-
private void GrowlHelper_Callback(object sender, GrowlHelper.NotifyCallbackEventArgs e)
{
if (Form.ActiveForm == null)
private void tweetThumbnail1_ThumbnailLoading(object sender, EventArgs e)
{
this.SplitContainer3.Panel2Collapsed = false;
-
- // PreviewDistance が起動のたびに広がっていく問題の回避策
- // FixedPanel が Panel2 に設定された状態で Panel2 を開くと、初回だけ SplitterDistance が再計算されておかしくなるため、
- // None で開いた後に設定するようにする
- if (this.SplitContainer3.FixedPanel == FixedPanel.None)
- this.SplitContainer3.FixedPanel = FixedPanel.Panel2;
}
private async void tweetThumbnail1_ThumbnailDoubleClick(object sender, ThumbnailDoubleClickEventArgs e)
private async Task OpenThumbnailPicture(ThumbnailInfo thumbnail)
{
- var url = thumbnail.FullSizeImageUrl ?? thumbnail.ImageUrl;
+ var url = thumbnail.FullSizeImageUrl ?? thumbnail.MediaPageUrl;
await this.OpenUriInBrowserAsync(url);
}
_curList?.Refresh();
- _modifySettingCommon = true;
+ ModifySettingCommon = true;
}
private void LockListSortToolStripMenuItem_Click(object sender, EventArgs e)
this._cfgCommon.SortOrderLock = state;
- _modifySettingCommon = true;
+ ModifySettingCommon = true;
+ }
+
+ private void tweetDetailsView_StatusChanged(object sender, TweetDetailsViewStatusChengedEventArgs e)
+ {
+ if (!string.IsNullOrEmpty(e.StatusText))
+ {
+ this.StatusLabelUrl.Text = e.StatusText;
+ }
+ else
+ {
+ this.SetStatusLabelUrl();
+ }
}
}
}