• R/O
  • HTTP
  • SSH
  • HTTPS

open-tween: Commit

開発に使用するリポジトリ


Commit MetaInfo

Revision31f9940c4b8884774ea15448e60beecfbd190100 (tree)
Time2019-08-16 05:31:47
AuthorKimura Youichi <kim.upsilon@bucy...>
CommiterKimura Youichi

Log Message

Merge branch 'reduce-timer-events'

Change Summary

Incremental Difference

--- a/OpenTween/DateTimeUtc.cs
+++ b/OpenTween/DateTimeUtc.cs
@@ -63,6 +63,11 @@ namespace OpenTween
6363 {
6464 }
6565
66+ public DateTimeUtc(long utcTicks)
67+ : this(new DateTime(utcTicks, DateTimeKind.Utc))
68+ {
69+ }
70+
6671 public DateTimeUtc(DateTime datetime)
6772 {
6873 if (datetime.Kind != DateTimeKind.Utc)
@@ -71,6 +76,9 @@ namespace OpenTween
7176 this.datetime = datetime;
7277 }
7378
79+ public long UtcTicks
80+ => this.datetime.Ticks;
81+
7482 public long ToUnixTime()
7583 => (long)((this - UnixEpoch).TotalSeconds);
7684
--- a/OpenTween/DetailsListView.cs
+++ b/OpenTween/DetailsListView.cs
@@ -130,87 +130,46 @@ namespace OpenTween.OpenTweenCustomControl
130130 this.OnSelectedIndexChanged(EventArgs.Empty);
131131 }
132132
133- public void ChangeItemBackColor(int index, Color backColor)
134- => this.ChangeSubItemBackColor(index, 0, backColor);
135-
136- public void ChangeItemForeColor(int index, Color foreColor)
137- => this.ChangeSubItemForeColor(index, 0, foreColor);
138-
139- public void ChangeItemFont(int index, Font fnt)
140- => this.ChangeSubItemFont(index, 0, fnt);
141-
142- public void ChangeItemFontAndColor(int index, Color foreColor, Font fnt)
143- => this.ChangeSubItemStyles(index, 0, BackColor, foreColor, fnt);
144-
145- public void ChangeItemStyles(int index, Color backColor, Color foreColor, Font fnt)
146- => this.ChangeSubItemStyles(index, 0, backColor, foreColor, fnt);
147-
148- public void ChangeSubItemBackColor(int itemIndex, int subitemIndex, Color backColor)
133+ public void ChangeItemBackColor(ListViewItem item, Color backColor)
149134 {
150- var item = this.Items[itemIndex];
151- item.SubItems[subitemIndex].BackColor = backColor;
152- SetUpdateBounds(item, subitemIndex);
153- this.Update();
154- this.changeBounds = Rectangle.Empty;
155- }
135+ if (item.BackColor == backColor)
136+ return;
156137
157- public void ChangeSubItemForeColor(int itemIndex, int subitemIndex, Color foreColor)
158- {
159- var item = this.Items[itemIndex];
160- item.SubItems[subitemIndex].ForeColor = foreColor;
161- SetUpdateBounds(item, subitemIndex);
162- this.Update();
163- this.changeBounds = Rectangle.Empty;
138+ item.BackColor = backColor;
139+ this.RefreshItemBounds(item);
164140 }
165141
166- public void ChangeSubItemFont(int itemIndex, int subitemIndex, Font fnt)
142+ public void ChangeItemForeColor(ListViewItem item, Color foreColor)
167143 {
168- var item = this.Items[itemIndex];
169- item.SubItems[subitemIndex].Font = fnt;
170- SetUpdateBounds(item, subitemIndex);
171- this.Update();
172- this.changeBounds = Rectangle.Empty;
173- }
144+ if (item.ForeColor == foreColor)
145+ return;
174146
175- public void ChangeSubItemFontAndColor(int itemIndex, int subitemIndex, Color foreColor, Font fnt)
176- {
177- var item = this.Items[itemIndex];
178- var subItem = item.SubItems[subitemIndex];
179- subItem.ForeColor = foreColor;
180- subItem.Font = fnt;
181- SetUpdateBounds(item, subitemIndex);
182- this.Update();
183- this.changeBounds = Rectangle.Empty;
147+ item.ForeColor = foreColor;
148+ this.RefreshItemBounds(item);
184149 }
185150
186- public void ChangeSubItemStyles(int itemIndex, int subitemIndex, Color backColor, Color foreColor, Font fnt)
151+ public void ChangeItemFontAndColor(ListViewItem item, Color foreColor, Font fnt)
187152 {
188- var item = this.Items[itemIndex];
189- var subItem = item.SubItems[subitemIndex];
190- subItem.BackColor = backColor;
191- subItem.ForeColor = foreColor;
192- subItem.Font = fnt;
193- SetUpdateBounds(item, subitemIndex);
194- this.Update();
195- this.changeBounds = Rectangle.Empty;
153+ if (item.ForeColor == foreColor && item.Font == fnt)
154+ return;
155+
156+ item.ForeColor = foreColor;
157+ item.Font = fnt;
158+ this.RefreshItemBounds(item);
196159 }
197160
198- private void SetUpdateBounds(ListViewItem item, int subItemIndex)
161+ private void RefreshItemBounds(ListViewItem item)
199162 {
200163 try
201164 {
202- if (subItemIndex > this.Columns.Count)
203- {
204- throw new ArgumentOutOfRangeException(nameof(subItemIndex));
205- }
206- if (item.UseItemStyleForSubItems)
207- {
208- this.changeBounds = item.Bounds;
209- }
210- else
211- {
212- this.changeBounds = this.GetSubItemBounds(item, subItemIndex);
213- }
165+ var itemBounds = item.Bounds;
166+ var drawBounds = Rectangle.Intersect(this.ClientRectangle, itemBounds);
167+ if (drawBounds == Rectangle.Empty)
168+ return;
169+
170+ this.changeBounds = drawBounds;
171+ this.Update();
172+ this.changeBounds = Rectangle.Empty;
214173 }
215174 catch (ArgumentException)
216175 {
@@ -219,19 +178,6 @@ namespace OpenTween.OpenTweenCustomControl
219178 }
220179 }
221180
222- private Rectangle GetSubItemBounds(ListViewItem item, int subitemIndex)
223- {
224- if (subitemIndex == 0 && this.Columns.Count > 0)
225- {
226- Rectangle col0 = item.Bounds;
227- return new Rectangle(col0.Left, col0.Top, item.SubItems[1].Bounds.X + 1, col0.Height);
228- }
229- else
230- {
231- return item.SubItems[subitemIndex].Bounds;
232- }
233- }
234-
235181 [StructLayout(LayoutKind.Sequential)]
236182 private struct NMHDR
237183 {
--- a/OpenTween/FilterDialog.cs
+++ b/OpenTween/FilterDialog.cs
@@ -662,7 +662,7 @@ namespace OpenTween
662662 owner.AtIdSupl.AddItem("@" + ft.FilterName);
663663 if (cnt != owner.AtIdSupl.ItemCount)
664664 {
665- owner.ModifySettingAtId = true;
665+ owner.MarkSettingAtIdModified();
666666 }
667667 ft.UseNameField = true;
668668 bdy = MSG1.Text;
--- a/OpenTween/OpenTween.csproj
+++ b/OpenTween/OpenTween.csproj
@@ -183,6 +183,7 @@
183183 <Compile Include="ShortcutCommand.cs" />
184184 <Compile Include="ThrottlingTimer.cs" />
185185 <Compile Include="Thumbnail\Services\PbsTwimgCom.cs" />
186+ <Compile Include="TimelineScheduler.cs" />
186187 <Compile Include="TweetDetailsView.cs">
187188 <SubType>UserControl</SubType>
188189 </Compile>
--- a/OpenTween/Resources/ChangeLog.txt
+++ b/OpenTween/Resources/ChangeLog.txt
@@ -5,6 +5,8 @@
55 - Unicode 12.0 で追加された絵文字が表示されるようになります
66 * CHG: DMの送信が完了したらDirectタブに送信したDMを即座に反映する
77 * CHG: htn.to の短縮URLを展開する際に強制的にHTTPSを使用する
8+ * CHG: OpenTween内部で動作するタイマーの使用方法を見直し
9+ - 従来まで毎秒5回程度のタイマーイベントが常に発生していたのを、無操作時かつ発言一覧の更新がない間はほぼゼロになるよう改善しました
810 * FIX: Twemojiを有効にすると絵文字の後に余分な文字が表示される場合がある不具合を修正
911 * FIX: Tumblrのサムネイル表示時にエラーが表示される場合がある不具合を修正
1012 * FIX: 発言内URLを開く(Ctrl+E)でURLにマルチバイト文字を含むとエラーが発生する場合がある不具合を修正
--- a/OpenTween/ThrottlingTimer.cs
+++ b/OpenTween/ThrottlingTimer.cs
@@ -36,55 +36,95 @@ namespace OpenTween
3636 private readonly Timer throttlingTimer;
3737 private readonly Func<Task> timerCallback;
3838
39- private DateTimeUtc lastInvoked = DateTimeUtc.MinValue;
40- private DateTimeUtc lastExecuted = DateTimeUtc.MinValue;
41- private int refreshTimerEnabled = 0;
39+ private long lastCalledTick;
40+ private long lastInvokedTick;
41+ private int refreshTimerEnabled = TIMER_DISABLED;
4242
4343 public TimeSpan Interval { get; }
44+ public TimeSpan MaxWait { get; }
45+ public bool InvokeLeading { get; }
46+ public bool InvokeTrailing { get; }
4447
45- public ThrottlingTimer(TimeSpan interval, Func<Task> timerCallback)
48+ private DateTimeUtc LastCalled
49+ {
50+ get => new DateTimeUtc(Interlocked.Read(ref this.lastCalledTick));
51+ set => Interlocked.Exchange(ref this.lastCalledTick, value.UtcTicks);
52+ }
53+
54+ private DateTimeUtc LastInvoked
55+ {
56+ get => new DateTimeUtc(Interlocked.Read(ref this.lastInvokedTick));
57+ set => Interlocked.Exchange(ref this.lastInvokedTick, value.UtcTicks);
58+ }
59+
60+ public ThrottlingTimer(Func<Task> timerCallback, TimeSpan interval, TimeSpan maxWait, bool leading, bool trailing)
4661 {
47- this.Interval = interval;
4862 this.timerCallback = timerCallback;
63+ this.Interval = interval;
64+ this.MaxWait = maxWait;
65+ this.LastCalled = DateTimeUtc.MinValue;
66+ this.LastInvoked = DateTimeUtc.MinValue;
67+ this.InvokeLeading = leading;
68+ this.InvokeTrailing = trailing;
4969 this.throttlingTimer = new Timer(this.Execute);
5070 }
5171
52- public void Invoke()
72+ public void Call()
5373 {
54- this.lastInvoked = DateTimeUtc.Now;
74+ this.LastCalled = DateTimeUtc.Now;
5575
5676 if (this.refreshTimerEnabled == TIMER_DISABLED)
5777 {
58- lock (this.throttlingTimer)
78+ this.refreshTimerEnabled = TIMER_ENABLED;
79+ this.LastInvoked = DateTimeUtc.MinValue;
80+ _ = Task.Run(async () =>
5981 {
60- if (Interlocked.CompareExchange(ref this.refreshTimerEnabled, TIMER_ENABLED, TIMER_DISABLED) == TIMER_DISABLED)
61- this.throttlingTimer.Change(dueTime: 0, period: Timeout.Infinite);
62- }
82+ if (this.InvokeLeading)
83+ await this.timerCallback().ConfigureAwait(false);
84+
85+ this.throttlingTimer.Change(dueTime: this.Interval, period: Timeout.InfiniteTimeSpan);
86+ });
6387 }
6488 }
6589
6690 private async void Execute(object _)
6791 {
68- var timerExpired = this.lastInvoked < this.lastExecuted;
92+ var lastCalled = this.LastCalled;
93+ var lastInvoked = this.LastInvoked;
94+
95+ var timerExpired = lastCalled < lastInvoked;
6996 if (timerExpired)
7097 {
7198 // 前回実行時より後に lastInvoked が更新されていなければタイマーを止める
72- Interlocked.CompareExchange(ref this.refreshTimerEnabled, TIMER_DISABLED, TIMER_ENABLED);
99+ this.refreshTimerEnabled = TIMER_DISABLED;
100+
101+ if (this.InvokeTrailing)
102+ await this.timerCallback().ConfigureAwait(false);
73103 }
74104 else
75105 {
76- this.lastExecuted = DateTimeUtc.Now;
106+ var now = DateTimeUtc.Now;
77107
78- await this.timerCallback().ConfigureAwait(false);
108+ if ((now - lastInvoked) >= this.MaxWait)
109+ await this.timerCallback().ConfigureAwait(false);
110+
111+ this.LastInvoked = now;
79112
80113 // dueTime は Execute が呼ばれる度に再設定する (period は使用しない)
81114 // これにより timerCallback の実行に Interval 以上の時間が掛かっても重複して実行されることはなくなる
82115 lock (this.throttlingTimer)
83- this.throttlingTimer.Change(dueTime: (int)this.Interval.TotalMilliseconds, period: Timeout.Infinite);
116+ this.throttlingTimer.Change(dueTime: this.Interval, period: Timeout.InfiniteTimeSpan);
84117 }
85118 }
86119
87120 public void Dispose()
88121 => this.throttlingTimer.Dispose();
122+
123+ // lodash.js の _.throttle, _.debounce 的な処理をしたかったメソッド群
124+ public static ThrottlingTimer Throttle(Func<Task> callback, TimeSpan wait, bool leading = true, bool trailing = true)
125+ => new ThrottlingTimer(callback, wait, maxWait: wait, leading, trailing);
126+
127+ public static ThrottlingTimer Debounce(Func<Task> callback, TimeSpan wait, bool leading = false, bool trailing = true)
128+ => new ThrottlingTimer(callback, wait, maxWait: TimeSpan.MaxValue, leading, trailing);
89129 }
90130 }
--- /dev/null
+++ b/OpenTween/TimelineScheduler.cs
@@ -0,0 +1,298 @@
1+// OpenTween - Client of Twitter
2+// Copyright (c) 2019 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3+// All rights reserved.
4+//
5+// This file is part of OpenTween.
6+//
7+// This program is free software; you can redistribute it and/or modify it
8+// under the terms of the GNU General Public License as published by the Free
9+// Software Foundation; either version 3 of the License, or (at your option)
10+// any later version.
11+//
12+// This program is distributed in the hope that it will be useful, but
13+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15+// for more details.
16+//
17+// You should have received a copy of the GNU General Public License along
18+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20+// Boston, MA 02110-1301, USA.
21+
22+using System;
23+using System.Collections.Generic;
24+using System.Threading;
25+using System.Threading.Tasks;
26+
27+namespace OpenTween
28+{
29+ public class TimelineScheduler
30+ {
31+ private readonly Timer timer;
32+
33+ private bool enabled = false;
34+ private bool systemResumeMode = false;
35+ private bool preventTimerUpdate = false;
36+
37+ public bool Enabled
38+ {
39+ get => this.enabled;
40+ set
41+ {
42+ if (this.enabled == value)
43+ return;
44+
45+ if (value)
46+ {
47+ var now = DateTimeUtc.Now;
48+ this.LastUpdateHome = now;
49+ this.LastUpdateMention = now;
50+ this.LastUpdateDm = now;
51+ this.LastUpdatePublicSearch = now;
52+ this.LastUpdateUser = now;
53+ this.LastUpdateList = now;
54+ this.LastUpdateConfig = now;
55+ }
56+ this.enabled = value;
57+ this.RefreshSchedule();
58+ }
59+ }
60+
61+ public DateTimeUtc LastUpdateHome { get; private set; } = DateTimeUtc.MinValue;
62+ public DateTimeUtc LastUpdateMention { get; private set; } = DateTimeUtc.MinValue;
63+ public DateTimeUtc LastUpdateDm { get; private set; } = DateTimeUtc.MinValue;
64+ public DateTimeUtc LastUpdatePublicSearch { get; private set; } = DateTimeUtc.MinValue;
65+ public DateTimeUtc LastUpdateUser { get; private set; } = DateTimeUtc.MinValue;
66+ public DateTimeUtc LastUpdateList { get; private set; } = DateTimeUtc.MinValue;
67+ public DateTimeUtc LastUpdateConfig { get; private set; } = DateTimeUtc.MinValue;
68+ public DateTimeUtc SystemResumedAt { get; private set; } = DateTimeUtc.MinValue;
69+
70+ public TimeSpan UpdateIntervalHome { get; set; } = Timeout.InfiniteTimeSpan;
71+ public TimeSpan UpdateIntervalMention { get; set; } = Timeout.InfiniteTimeSpan;
72+ public TimeSpan UpdateIntervalDm { get; set; } = Timeout.InfiniteTimeSpan;
73+ public TimeSpan UpdateIntervalPublicSearch { get; set; } = Timeout.InfiniteTimeSpan;
74+ public TimeSpan UpdateIntervalUser { get; set; } = Timeout.InfiniteTimeSpan;
75+ public TimeSpan UpdateIntervalList { get; set; } = Timeout.InfiniteTimeSpan;
76+ public TimeSpan UpdateIntervalConfig { get; set; } = Timeout.InfiniteTimeSpan;
77+ public TimeSpan UpdateAfterSystemResume { get; set; } = Timeout.InfiniteTimeSpan;
78+
79+ public Func<Task> UpdateHome;
80+ public Func<Task> UpdateMention;
81+ public Func<Task> UpdateDm;
82+ public Func<Task> UpdatePublicSearch;
83+ public Func<Task> UpdateUser;
84+ public Func<Task> UpdateList;
85+ public Func<Task> UpdateConfig;
86+
87+ [Flags]
88+ private enum UpdateTask
89+ {
90+ None = 0,
91+ Home = 1,
92+ Mention = 1 << 2,
93+ Dm = 1 << 3,
94+ PublicSearch = 1 << 4,
95+ User = 1 << 5,
96+ List = 1 << 6,
97+ Config = 1 << 7,
98+ All = Home | Mention | Dm | PublicSearch | User | List | Config,
99+ }
100+
101+ public TimelineScheduler()
102+ => this.timer = new Timer(_ => this.TimerCallback());
103+
104+ public void RefreshSchedule()
105+ {
106+ if (this.preventTimerUpdate)
107+ return; // TimerCallback 内で更新されるのでここは単に無視してよい
108+
109+ if (this.Enabled)
110+ this.timer.Change(this.NextTimerDelay(), Timeout.InfiniteTimeSpan);
111+ else
112+ this.timer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
113+ }
114+
115+ public void SystemResumed()
116+ {
117+ this.SystemResumedAt = DateTimeUtc.Now;
118+ this.systemResumeMode = true;
119+ this.RefreshSchedule();
120+ }
121+
122+ private async void TimerCallback()
123+ {
124+ try
125+ {
126+ this.preventTimerUpdate = true;
127+
128+ if (this.systemResumeMode)
129+ await this.TimerCallback_AfterSystemResume().ConfigureAwait(false);
130+ else
131+ await this.TimerCallback_Normal().ConfigureAwait(false);
132+ }
133+ finally
134+ {
135+ this.preventTimerUpdate = false;
136+ this.RefreshSchedule();
137+ }
138+ }
139+
140+ private async Task TimerCallback_Normal()
141+ {
142+ var now = DateTimeUtc.Now;
143+ var round = TimeSpan.FromSeconds(1); // 1秒未満の差異であればまとめて実行する
144+ var tasks = UpdateTask.None;
145+
146+ var nextScheduledHome = this.LastUpdateHome + this.UpdateIntervalHome;
147+ if (nextScheduledHome - now < round)
148+ tasks |= UpdateTask.Home;
149+
150+ var nextScheduledMention = this.LastUpdateMention + this.UpdateIntervalMention;
151+ if (nextScheduledMention - now < round)
152+ tasks |= UpdateTask.Mention;
153+
154+ var nextScheduledDm = this.LastUpdateDm + this.UpdateIntervalDm;
155+ if (nextScheduledDm - now < round)
156+ tasks |= UpdateTask.Dm;
157+
158+ var nextScheduledPublicSearch = this.LastUpdatePublicSearch + this.UpdateIntervalPublicSearch;
159+ if (nextScheduledPublicSearch - now < round)
160+ tasks |= UpdateTask.PublicSearch;
161+
162+ var nextScheduledUser = this.LastUpdateUser + this.UpdateIntervalUser;
163+ if (nextScheduledUser - now < round)
164+ tasks |= UpdateTask.User;
165+
166+ var nextScheduledList = this.LastUpdateList + this.UpdateIntervalList;
167+ if (nextScheduledList - now < round)
168+ tasks |= UpdateTask.List;
169+
170+ var nextScheduledConfig = this.LastUpdateConfig + this.UpdateIntervalConfig;
171+ if (nextScheduledConfig - now < round)
172+ tasks |= UpdateTask.Config;
173+
174+ await this.RunUpdateTasks(tasks, now).ConfigureAwait(false);
175+ }
176+
177+ private async Task TimerCallback_AfterSystemResume()
178+ {
179+ // systemResumeMode では一定期間経過後に全てのタイムラインを更新する
180+ var now = DateTimeUtc.Now;
181+
182+ var nextScheduledUpdateAll = this.SystemResumedAt + this.UpdateAfterSystemResume;
183+ if (nextScheduledUpdateAll - now < TimeSpan.Zero)
184+ {
185+ this.systemResumeMode = false;
186+ await this.RunUpdateTasks(UpdateTask.All, now).ConfigureAwait(false);
187+ }
188+ }
189+
190+ private async Task RunUpdateTasks(UpdateTask tasks, DateTimeUtc now)
191+ {
192+ var updateTasks = new List<Task>(capacity: 7);
193+
194+ // LastUpdate* を次の時刻に更新してから Update* を実行すること
195+ // (LastUpdate* が更新されずに Update* が例外を投げると無限ループに陥る)
196+
197+ if ((tasks & UpdateTask.Home) == UpdateTask.Home)
198+ {
199+ this.LastUpdateHome = now;
200+ if (this.UpdateHome != null)
201+ updateTasks.Add(this.UpdateHome());
202+ }
203+
204+ if ((tasks & UpdateTask.Mention) == UpdateTask.Mention)
205+ {
206+ this.LastUpdateMention = now;
207+ if (this.UpdateMention != null)
208+ updateTasks.Add(this.UpdateMention());
209+ }
210+
211+ if ((tasks & UpdateTask.Dm) == UpdateTask.Dm)
212+ {
213+ this.LastUpdateDm = now;
214+ if (this.UpdateDm != null)
215+ updateTasks.Add(this.UpdateDm());
216+ }
217+
218+ if ((tasks & UpdateTask.PublicSearch) == UpdateTask.PublicSearch)
219+ {
220+ this.LastUpdatePublicSearch = now;
221+ if (this.UpdatePublicSearch != null)
222+ updateTasks.Add(this.UpdatePublicSearch());
223+ }
224+
225+ if ((tasks & UpdateTask.User) == UpdateTask.User)
226+ {
227+ this.LastUpdateUser = now;
228+ if (this.UpdateUser != null)
229+ updateTasks.Add(this.UpdateUser());
230+ }
231+
232+ if ((tasks & UpdateTask.List) == UpdateTask.List)
233+ {
234+ this.LastUpdateList = now;
235+ if (this.UpdateList != null)
236+ updateTasks.Add(this.UpdateList());
237+ }
238+
239+ if ((tasks & UpdateTask.Config) == UpdateTask.Config)
240+ {
241+ this.LastUpdateConfig = now;
242+ if (this.UpdateConfig != null)
243+ updateTasks.Add(this.UpdateConfig());
244+ }
245+
246+ await Task.WhenAll(updateTasks).ConfigureAwait(false);
247+ }
248+
249+ private TimeSpan NextTimerDelay()
250+ {
251+ TimeSpan delay;
252+
253+ if (this.systemResumeMode)
254+ {
255+ // systemResumeMode が有効な間は UpdateAfterSystemResume 以外の設定値を見ない
256+ var nextScheduledUpdateAll = this.SystemResumedAt + this.UpdateAfterSystemResume;
257+ delay = nextScheduledUpdateAll - DateTimeUtc.Now;
258+
259+ return delay > TimeSpan.Zero ? delay : TimeSpan.Zero;
260+ }
261+
262+ // 次に更新が予定される時刻を判定する
263+ var min = DateTimeUtc.MaxValue;
264+
265+ var nextScheduledHome = this.LastUpdateHome + this.UpdateIntervalHome;
266+ if (nextScheduledHome < min)
267+ min = nextScheduledHome;
268+
269+ var nextScheduledMention = this.LastUpdateMention + this.UpdateIntervalMention;
270+ if (nextScheduledMention < min)
271+ min = nextScheduledMention;
272+
273+ var nextScheduledDm = this.LastUpdateDm + this.UpdateIntervalDm;
274+ if (nextScheduledDm < min)
275+ min = nextScheduledDm;
276+
277+ var nextScheduledPublicSearch = this.LastUpdatePublicSearch + this.UpdateIntervalPublicSearch;
278+ if (nextScheduledPublicSearch < min)
279+ min = nextScheduledPublicSearch;
280+
281+ var nextScheduledUser = this.LastUpdateUser + this.UpdateIntervalUser;
282+ if (nextScheduledUser < min)
283+ min = nextScheduledUser;
284+
285+ var nextScheduledList = this.LastUpdateList + this.UpdateIntervalList;
286+ if (nextScheduledList < min)
287+ min = nextScheduledList;
288+
289+ var nextScheduledConfig = this.LastUpdateConfig + this.UpdateIntervalConfig;
290+ if (nextScheduledConfig < min)
291+ min = nextScheduledConfig;
292+
293+ delay = min - DateTimeUtc.Now;
294+
295+ return delay > TimeSpan.Zero ? delay : TimeSpan.Zero;
296+ }
297+ }
298+}
--- a/OpenTween/Tween.Designer.cs
+++ b/OpenTween/Tween.Designer.cs
@@ -2025,7 +2025,6 @@
20252025 //
20262026 // TimerRefreshIcon
20272027 //
2028- this.TimerRefreshIcon.Interval = 50;
20292028 this.TimerRefreshIcon.Tick += new System.EventHandler(this.TimerRefreshIcon_Tick);
20302029 //
20312030 // PostStateImageList
--- a/OpenTween/Tween.cs
+++ b/OpenTween/Tween.cs
@@ -211,11 +211,8 @@ namespace OpenTween
211211 /// <summary>キャッシュする範囲の終了インデックス</summary>
212212 public int EndIndex { get; set; }
213213
214- /// <summary>キャッシュされた <see cref="ListViewItem"/> インスタンス</summary>
215- public ListViewItem[] ListItem { get; set; }
216-
217- /// <summary>キャッシュされた範囲に対応する <see cref="PostClass"/> インスタンス</summary>
218- public PostClass[] Post { get; set; }
214+ /// <summary>キャッシュされた範囲に対応する <see cref="ListViewItem"/> と <see cref="PostClass"/> の組</summary>
215+ public (ListViewItem, PostClass)[] Cache { get; set; }
219216
220217 /// <summary>キャッシュされたアイテムの件数</summary>
221218 public int Count
@@ -237,8 +234,7 @@ namespace OpenTween
237234 {
238235 if (this.Contains(index))
239236 {
240- item = this.ListItem[index - this.StartIndex];
241- post = this.Post[index - this.StartIndex];
237+ (item, post) = this.Cache[index - this.StartIndex];
242238 return true;
243239 }
244240 else
@@ -264,13 +260,14 @@ namespace OpenTween
264260 private string[] ColumnText = new string[9];
265261
266262 private bool _DoFavRetweetFlags = false;
267- private bool osResumed = false;
268263
269264 //////////////////////////////////////////////////////////////////////////////////////////////////////////
270- private bool _colorize = false;
271265
272- private System.Timers.Timer TimerTimeline = new System.Timers.Timer();
266+ private readonly TimelineScheduler timelineScheduler = new TimelineScheduler();
273267 private ThrottlingTimer RefreshThrottlingTimer;
268+ private ThrottlingTimer colorizeDebouncer;
269+ private ThrottlingTimer selectionDebouncer;
270+ private ThrottlingTimer saveConfigDebouncer;
274271
275272 private string recommendedStatusFooter;
276273 private bool urlMultibyteSplit = false;
@@ -1113,18 +1110,31 @@ namespace OpenTween
11131110
11141111 //タイマー設定
11151112
1113+ this.timelineScheduler.UpdateHome = () => this.InvokeAsync(() => this.RefreshTabAsync<HomeTabModel>());
1114+ this.timelineScheduler.UpdateMention = () => this.InvokeAsync(() => this.RefreshTabAsync<MentionsTabModel>());
1115+ this.timelineScheduler.UpdateDm = () => this.InvokeAsync(() => this.RefreshTabAsync<DirectMessagesTabModel>());
1116+ this.timelineScheduler.UpdatePublicSearch = () => this.InvokeAsync(() => this.RefreshTabAsync<PublicSearchTabModel>());
1117+ this.timelineScheduler.UpdateUser = () => this.InvokeAsync(() => this.RefreshTabAsync<UserTimelineTabModel>());
1118+ this.timelineScheduler.UpdateList = () => this.InvokeAsync(() => this.RefreshTabAsync<ListTimelineTabModel>());
1119+ this.timelineScheduler.UpdateConfig = () => this.InvokeAsync(() => Task.WhenAll(new[]
1120+ {
1121+ this.doGetFollowersMenu(),
1122+ this.RefreshBlockIdsAsync(),
1123+ this.RefreshMuteUserIdsAsync(),
1124+ this.RefreshNoRetweetIdsAsync(),
1125+ this.RefreshTwitterConfigurationAsync(),
1126+ }));
1127+ this.RefreshTimelineScheduler();
1128+
11161129 var streamingRefreshInterval = TimeSpan.FromSeconds(SettingManager.Common.UserstreamPeriod);
1117- this.RefreshThrottlingTimer = new ThrottlingTimer(streamingRefreshInterval,
1118- () => this.InvokeAsync(() => this.RefreshTimeline()));
1119-
1120- TimerTimeline.AutoReset = true;
1121- TimerTimeline.SynchronizingObject = this;
1122- //Recent取得間隔
1123- TimerTimeline.Interval = 1000;
1124- TimerTimeline.Enabled = true;
1130+ this.RefreshThrottlingTimer = ThrottlingTimer.Throttle(() => this.InvokeAsync(() => this.RefreshTimeline()), streamingRefreshInterval);
1131+ this.colorizeDebouncer = ThrottlingTimer.Debounce(() => this.InvokeAsync(() => this.ColorizeList()), TimeSpan.FromMilliseconds(100), leading: true);
1132+ this.selectionDebouncer = ThrottlingTimer.Debounce(() => this.InvokeAsync(() => this.UpdateSelectedPost()), TimeSpan.FromMilliseconds(100), leading: true);
1133+ this.saveConfigDebouncer = ThrottlingTimer.Debounce(() => this.InvokeAsync(() => this.SaveConfigsAll(ifModified: true)), TimeSpan.FromSeconds(1));
1134+
11251135 //更新中アイコンアニメーション間隔
11261136 TimerRefreshIcon.Interval = 200;
1127- TimerRefreshIcon.Enabled = true;
1137+ TimerRefreshIcon.Enabled = false;
11281138
11291139 _ignoreConfigSave = false;
11301140 this.TweenMain_Resize(null, null);
@@ -1277,117 +1287,56 @@ namespace OpenTween
12771287
12781288 private void TimerInterval_Changed(object sender, IntervalChangedEventArgs e) //Handles SettingDialog.IntervalChanged
12791289 {
1280- if (!TimerTimeline.Enabled) return;
1281-
12821290 if (e.UserStream)
12831291 {
12841292 var interval = TimeSpan.FromSeconds(SettingManager.Common.UserstreamPeriod);
1285- var newTimer = new ThrottlingTimer(interval, () => this.InvokeAsync(() => this.RefreshTimeline()));
1293+ var newTimer = ThrottlingTimer.Throttle(() => this.InvokeAsync(() => this.RefreshTimeline()), interval);
12861294 var oldTimer = Interlocked.Exchange(ref this.RefreshThrottlingTimer, newTimer);
12871295 oldTimer.Dispose();
12881296 }
12891297
1290- ResetTimers = e;
1298+ this.RefreshTimelineScheduler();
12911299 }
12921300
1293- private IntervalChangedEventArgs ResetTimers = IntervalChangedEventArgs.ResetAll;
1301+ private void RefreshTimelineScheduler()
1302+ {
1303+ this.timelineScheduler.UpdateIntervalHome = TimeSpan.FromSeconds(SettingManager.Common.TimelinePeriod);
1304+ this.timelineScheduler.UpdateIntervalMention = TimeSpan.FromSeconds(SettingManager.Common.ReplyPeriod);
1305+ this.timelineScheduler.UpdateIntervalDm = TimeSpan.FromSeconds(SettingManager.Common.DMPeriod);
1306+ this.timelineScheduler.UpdateIntervalPublicSearch = TimeSpan.FromSeconds(SettingManager.Common.PubSearchPeriod);
1307+ this.timelineScheduler.UpdateIntervalUser = TimeSpan.FromSeconds(SettingManager.Common.UserTimelinePeriod);
1308+ this.timelineScheduler.UpdateIntervalList = TimeSpan.FromSeconds(SettingManager.Common.ListsPeriod);
1309+ this.timelineScheduler.UpdateIntervalConfig = TimeSpan.FromHours(6);
1310+ this.timelineScheduler.UpdateAfterSystemResume = TimeSpan.FromSeconds(30);
12941311
1295- private static int homeCounter = 0;
1296- private static int mentionCounter = 0;
1297- private static int dmCounter = 0;
1298- private static int pubSearchCounter = 0;
1299- private static int userTimelineCounter = 0;
1300- private static int listsCounter = 0;
1301- private static int ResumeWait = 0;
1302- private static int refreshFollowers = 0;
1312+ this.timelineScheduler.RefreshSchedule();
1313+ }
13031314
1304- private async void TimerTimeline_Elapsed(object sender, EventArgs e)
1315+ private void MarkSettingCommonModified()
13051316 {
1306- if (homeCounter > 0) Interlocked.Decrement(ref homeCounter);
1307- if (mentionCounter > 0) Interlocked.Decrement(ref mentionCounter);
1308- if (dmCounter > 0) Interlocked.Decrement(ref dmCounter);
1309- if (pubSearchCounter > 0) Interlocked.Decrement(ref pubSearchCounter);
1310- if (userTimelineCounter > 0) Interlocked.Decrement(ref userTimelineCounter);
1311- if (listsCounter > 0) Interlocked.Decrement(ref listsCounter);
1312- Interlocked.Increment(ref refreshFollowers);
1317+ if (this.saveConfigDebouncer == null)
1318+ return;
13131319
1314- var refreshTasks = new List<Task>();
1320+ this.ModifySettingCommon = true;
1321+ this.saveConfigDebouncer.Call();
1322+ }
13151323
1316- ////タイマー初期化
1317- if (ResetTimers.Timeline || homeCounter <= 0 && SettingManager.Common.TimelinePeriod > 0)
1318- {
1319- Interlocked.Exchange(ref homeCounter, SettingManager.Common.TimelinePeriod);
1320- if (!tw.IsUserstreamDataReceived && !ResetTimers.Timeline)
1321- refreshTasks.Add(this.RefreshTabAsync<HomeTabModel>());
1322- ResetTimers.Timeline = false;
1323- }
1324- if (ResetTimers.Reply || mentionCounter <= 0 && SettingManager.Common.ReplyPeriod > 0)
1325- {
1326- Interlocked.Exchange(ref mentionCounter, SettingManager.Common.ReplyPeriod);
1327- if (!tw.IsUserstreamDataReceived && !ResetTimers.Reply)
1328- refreshTasks.Add(this.RefreshTabAsync<MentionsTabModel>());
1329- ResetTimers.Reply = false;
1330- }
1331- if (ResetTimers.DirectMessage || dmCounter <= 0 && SettingManager.Common.DMPeriod > 0)
1332- {
1333- Interlocked.Exchange(ref dmCounter, SettingManager.Common.DMPeriod);
1334- if (!tw.IsUserstreamDataReceived && !ResetTimers.DirectMessage)
1335- refreshTasks.Add(this.RefreshTabAsync<DirectMessagesTabModel>());
1336- ResetTimers.DirectMessage = false;
1337- }
1338- if (ResetTimers.PublicSearch || pubSearchCounter <= 0 && SettingManager.Common.PubSearchPeriod > 0)
1339- {
1340- Interlocked.Exchange(ref pubSearchCounter, SettingManager.Common.PubSearchPeriod);
1341- if (!ResetTimers.PublicSearch)
1342- refreshTasks.Add(this.RefreshTabAsync<PublicSearchTabModel>());
1343- ResetTimers.PublicSearch = false;
1344- }
1345- if (ResetTimers.UserTimeline || userTimelineCounter <= 0 && SettingManager.Common.UserTimelinePeriod > 0)
1346- {
1347- Interlocked.Exchange(ref userTimelineCounter, SettingManager.Common.UserTimelinePeriod);
1348- if (!ResetTimers.UserTimeline)
1349- refreshTasks.Add(this.RefreshTabAsync<UserTimelineTabModel>());
1350- ResetTimers.UserTimeline = false;
1351- }
1352- if (ResetTimers.Lists || listsCounter <= 0 && SettingManager.Common.ListsPeriod > 0)
1353- {
1354- Interlocked.Exchange(ref listsCounter, SettingManager.Common.ListsPeriod);
1355- if (!ResetTimers.Lists)
1356- refreshTasks.Add(this.RefreshTabAsync<ListTimelineTabModel>());
1357- ResetTimers.Lists = false;
1358- }
1359- if (refreshFollowers > 6 * 3600)
1360- {
1361- Interlocked.Exchange(ref refreshFollowers, 0);
1362- refreshTasks.AddRange(new[]
1363- {
1364- this.doGetFollowersMenu(),
1365- this.RefreshNoRetweetIdsAsync(),
1366- this.RefreshTwitterConfigurationAsync(),
1367- });
1368- }
1369- if (osResumed)
1370- {
1371- Interlocked.Increment(ref ResumeWait);
1372- if (ResumeWait > 30)
1373- {
1374- osResumed = false;
1375- Interlocked.Exchange(ref ResumeWait, 0);
1376- refreshTasks.AddRange(new[]
1377- {
1378- this.RefreshTabAsync<HomeTabModel>(),
1379- this.RefreshTabAsync<MentionsTabModel>(),
1380- this.RefreshTabAsync<DirectMessagesTabModel>(),
1381- this.RefreshTabAsync<PublicSearchTabModel>(),
1382- this.RefreshTabAsync<UserTimelineTabModel>(),
1383- this.RefreshTabAsync<ListTimelineTabModel>(),
1384- this.doGetFollowersMenu(),
1385- this.RefreshTwitterConfigurationAsync(),
1386- });
1387- }
1388- }
1324+ private void MarkSettingLocalModified()
1325+ {
1326+ if (this.saveConfigDebouncer == null)
1327+ return;
1328+
1329+ this.ModifySettingLocal = true;
1330+ this.saveConfigDebouncer.Call();
1331+ }
1332+
1333+ internal void MarkSettingAtIdModified()
1334+ {
1335+ if (this.saveConfigDebouncer == null)
1336+ return;
13891337
1390- await Task.WhenAll(refreshTasks);
1338+ this.ModifySettingAtId = true;
1339+ this.saveConfigDebouncer.Call();
13911340 }
13921341
13931342 private void RefreshTimeline()
@@ -1933,11 +1882,12 @@ namespace OpenTween
19331882
19341883 var post = this.CurrentPost;
19351884 this._statuses.SetReadAllTab(post.StatusId, read: true);
1885+
19361886 //キャッシュの書き換え
19371887 ChangeCacheStyleRead(true, index); // 既読へ(フォント、文字色)
19381888
1939- ColorizeList();
1940- _colorize = true;
1889+ this.colorizeDebouncer.Call();
1890+ this.selectionDebouncer.Call();
19411891 }
19421892
19431893 private void ChangeCacheStyleRead(bool Read, int Index)
@@ -1962,17 +1912,21 @@ namespace OpenTween
19621912 private void ChangeItemStyleRead(bool Read, ListViewItem Item, PostClass Post, DetailsListView DList)
19631913 {
19641914 Font fnt;
1915+ string star;
19651916 //フォント
19661917 if (Read)
19671918 {
19681919 fnt = _fntReaded;
1969- Item.SubItems[5].Text = "";
1920+ star = "";
19701921 }
19711922 else
19721923 {
19731924 fnt = _fntUnread;
1974- Item.SubItems[5].Text = "★";
1925+ star = "★";
19751926 }
1927+ if (Item.SubItems[5].Text != star)
1928+ Item.SubItems[5].Text = star;
1929+
19761930 //文字色
19771931 Color cl;
19781932 if (Post.IsFav)
@@ -1996,9 +1950,9 @@ namespace OpenTween
19961950 {
19971951 DList.Update();
19981952 if (SettingManager.Common.UseUnreadStyle)
1999- DList.ChangeItemFontAndColor(Item.Index, cl, fnt);
1953+ DList.ChangeItemFontAndColor(Item, cl, fnt);
20001954 else
2001- DList.ChangeItemForeColor(Item.Index, cl);
1955+ DList.ChangeItemForeColor(Item, cl);
20021956 //if (_itemCache != null) DList.RedrawItems(_itemCacheIndex, _itemCacheIndex + _itemCache.Length - 1, false);
20031957 }
20041958 }
@@ -2019,16 +1973,19 @@ namespace OpenTween
20191973 if (listCache == null)
20201974 return;
20211975
2022- var index = listCache.StartIndex;
20231976 var listView = (DetailsListView)listCache.TargetList;
2024- foreach (var cachedPost in listCache.Post)
1977+
1978+ // ValidateRectが呼ばれる前に選択色などの描画を済ませておく
1979+ listView.Update();
1980+
1981+ foreach (var (listViewItem, cachedPost) in listCache.Cache)
20251982 {
20261983 var backColor = this.JudgeColor(_post, cachedPost);
2027- listView.ChangeItemBackColor(index++, backColor);
1984+ listView.ChangeItemBackColor(listViewItem, backColor);
20281985 }
20291986 }
20301987
2031- private void ColorizeList(ListViewItem Item, int Index)
1988+ private void ColorizeList(ListViewItem Item, PostClass post, int Index)
20321989 {
20331990 //Index:更新対象のListviewItem.Index。Colorを返す。
20341991 //-1は全キャッシュ。Colorは返さない(ダミーを戻す)
@@ -2038,14 +1995,12 @@ namespace OpenTween
20381995 else
20391996 _post = this.CurrentPost;
20401997
2041- PostClass tPost = GetCurTabPost(Index);
2042-
20431998 if (_post == null) return;
20441999
20452000 if (Item.Index == -1)
2046- Item.BackColor = JudgeColor(_post, tPost);
2001+ Item.BackColor = JudgeColor(_post, post);
20472002 else
2048- this.CurrentListView.ChangeItemBackColor(Item.Index, JudgeColor(_post, tPost));
2003+ this.CurrentListView.ChangeItemBackColor(Item, JudgeColor(_post, post));
20492004 }
20502005
20512006 private Color JudgeColor(PostClass BasePost, PostClass TargetPost)
@@ -2066,7 +2021,7 @@ namespace OpenTween
20662021 else if (TargetPost.ReplyToList.Any(x => x.UserId == BasePost.UserId))
20672022 //その人への返信
20682023 cl = _clAtTarget;
2069- else if (TargetPost.ScreenName.Equals(BasePost.ScreenName, StringComparison.OrdinalIgnoreCase))
2024+ else if (TargetPost.UserId == BasePost.UserId)
20702025 //発言者
20712026 cl = _clTarget;
20722027 else
@@ -2251,7 +2206,7 @@ namespace OpenTween
22512206 _hookGlobalHotkey.UnregisterAllOriginalHotkey();
22522207 _ignoreConfigSave = true;
22532208 MyCommon._endingFlag = true;
2254- TimerTimeline.Enabled = false;
2209+ this.timelineScheduler.Enabled = false;
22552210 TimerRefreshIcon.Enabled = false;
22562211 }
22572212 }
@@ -2308,6 +2263,7 @@ namespace OpenTween
23082263 private async Task RefreshTabAsync(TabModel tab, bool backward)
23092264 {
23102265 await this.workerSemaphore.WaitAsync();
2266+ this.RefreshTasktrayIcon();
23112267
23122268 try
23132269 {
@@ -2361,6 +2317,7 @@ namespace OpenTween
23612317 private async Task FavAddAsync(long statusId, TabModel tab)
23622318 {
23632319 await this.workerSemaphore.WaitAsync();
2320+ this.RefreshTasktrayIcon();
23642321
23652322 try
23662323 {
@@ -2474,13 +2431,14 @@ namespace OpenTween
24742431
24752432 var currentPost = this.CurrentPost;
24762433 if (currentPost != null && statusId == currentPost.StatusId)
2477- await this.DispSelectedPost(true); // 選択アイテム再表示
2434+ this.DispSelectedPost(true); // 選択アイテム再表示
24782435 }
24792436 }
24802437
24812438 private async Task FavRemoveAsync(IReadOnlyList<long> statusIds, TabModel tab)
24822439 {
24832440 await this.workerSemaphore.WaitAsync();
2441+ this.RefreshTasktrayIcon();
24842442
24852443 try
24862444 {
@@ -2589,7 +2547,7 @@ namespace OpenTween
25892547
25902548 var currentPost = this.CurrentPost;
25912549 if (currentPost != null && successIds.Contains(currentPost.StatusId))
2592- await this.DispSelectedPost(true); // 選択アイテム再表示
2550+ this.DispSelectedPost(true); // 選択アイテム再表示
25932551 }
25942552 }
25952553 }
@@ -2597,6 +2555,7 @@ namespace OpenTween
25972555 private async Task PostMessageAsync(PostStatusParams postParams, IMediaUploadService uploadService, IMediaItem[] uploadItems)
25982556 {
25992557 await this.workerSemaphore.WaitAsync();
2558+ this.RefreshTasktrayIcon();
26002559
26012560 try
26022561 {
@@ -2743,6 +2702,7 @@ namespace OpenTween
27432702 private async Task RetweetAsync(IReadOnlyList<long> statusIds)
27442703 {
27452704 await this.workerSemaphore.WaitAsync();
2705+ this.RefreshTasktrayIcon();
27462706
27472707 try
27482708 {
@@ -2822,6 +2782,8 @@ namespace OpenTween
28222782 private async Task RefreshFollowerIdsAsync()
28232783 {
28242784 await this.workerSemaphore.WaitAsync();
2785+ this.RefreshTasktrayIcon();
2786+
28252787 try
28262788 {
28272789 this.StatusLabel.Text = Properties.Resources.UpdateFollowersMenuItem1_ClickText1;
@@ -2847,6 +2809,8 @@ namespace OpenTween
28472809 private async Task RefreshNoRetweetIdsAsync()
28482810 {
28492811 await this.workerSemaphore.WaitAsync();
2812+ this.RefreshTasktrayIcon();
2813+
28502814 try
28512815 {
28522816 await this.tw.RefreshNoRetweetIds();
@@ -2866,6 +2830,8 @@ namespace OpenTween
28662830 private async Task RefreshBlockIdsAsync()
28672831 {
28682832 await this.workerSemaphore.WaitAsync();
2833+ this.RefreshTasktrayIcon();
2834+
28692835 try
28702836 {
28712837 this.StatusLabel.Text = Properties.Resources.UpdateBlockUserText1;
@@ -2887,6 +2853,8 @@ namespace OpenTween
28872853 private async Task RefreshTwitterConfigurationAsync()
28882854 {
28892855 await this.workerSemaphore.WaitAsync();
2856+ this.RefreshTasktrayIcon();
2857+
28902858 try
28912859 {
28922860 await this.tw.RefreshConfiguration();
@@ -3085,7 +3053,7 @@ namespace OpenTween
30853053 _mySpDis = this.SplitContainer1.SplitterDistance;
30863054 _mySpDis3 = this.SplitContainer3.SplitterDistance;
30873055 if (StatusText.Multiline) _mySpDis2 = this.StatusText.Height;
3088- ModifySettingLocal = true;
3056+ this.MarkSettingLocalModified();
30893057 }
30903058 }
30913059 }
@@ -3204,7 +3172,7 @@ namespace OpenTween
32043172 }
32053173 list.Refresh();
32063174
3207- this.ModifySettingCommon = true;
3175+ this.MarkSettingCommonModified();
32083176 }
32093177
32103178 private void TweenMain_LocationChanged(object sender, EventArgs e)
@@ -3212,7 +3180,7 @@ namespace OpenTween
32123180 if (this.WindowState == FormWindowState.Normal && !_initialLayout)
32133181 {
32143182 _myLoc = this.DesktopLocation;
3215- ModifySettingLocal = true;
3183+ this.MarkSettingLocalModified();
32163184 }
32173185 }
32183186
@@ -4335,7 +4303,7 @@ namespace OpenTween
43354303 }
43364304 }
43374305
4338- private async void ListTab_SelectedIndexChanged(object sender, EventArgs e)
4306+ private void ListTab_SelectedIndexChanged(object sender, EventArgs e)
43394307 {
43404308 //_curList.Refresh();
43414309 SetMainWindowTitle();
@@ -4345,7 +4313,7 @@ namespace OpenTween
43454313 this.Tag = ListTab.Tag;
43464314 TabMenuControl(this.CurrentTabName);
43474315 this.PushSelectPostChain();
4348- await DispSelectedPost();
4316+ DispSelectedPost();
43494317 }
43504318
43514319 private void SetListProperty()
@@ -4395,7 +4363,8 @@ namespace OpenTween
43954363 //@マーク
43964364 int cnt = AtIdSupl.ItemCount;
43974365 ShowSuplDialog(StatusText, AtIdSupl);
4398- if (cnt != AtIdSupl.ItemCount) ModifySettingAtId = true;
4366+ if (cnt != AtIdSupl.ItemCount)
4367+ this.MarkSettingAtIdModified();
43994368 e.Handled = true;
44004369 }
44014370 else if (e.KeyChar == '#')
@@ -4797,8 +4766,7 @@ namespace OpenTween
47974766 TargetList = this.CurrentListView,
47984767 StartIndex = startIndex,
47994768 EndIndex = endIndex,
4800- Post = posts,
4801- ListItem = listItems,
4769+ Cache = Enumerable.Zip(listItems, posts, (x, y) => (x, y)).ToArray(),
48024770 };
48034771
48044772 Interlocked.Exchange(ref this._listItemCache, listCache);
@@ -4854,7 +4822,7 @@ namespace OpenTween
48544822 ChangeItemStyleRead(read, itm, Post, null);
48554823
48564824 if (tab.TabName == this.CurrentTabName)
4857- this.ColorizeList(itm, Index);
4825+ this.ColorizeList(itm, Post, Index);
48584826
48594827 return itm;
48604828 }
@@ -5568,7 +5536,7 @@ namespace OpenTween
55685536 else if (dialog.SkipButtonPressed)
55695537 {
55705538 SettingManager.Common.SkipUpdateVersion = versionInfo.Version;
5571- this.ModifySettingCommon = true;
5539+ this.MarkSettingCommonModified();
55725540 }
55735541 }
55745542 }
@@ -5584,10 +5552,8 @@ namespace OpenTween
55845552 }
55855553 }
55865554
5587- private async Task Colorize()
5555+ private void UpdateSelectedPost()
55885556 {
5589- _colorize = false;
5590- await this.DispSelectedPost();
55915557 //件数関連の場合、タイトル即時書き換え
55925558 if (SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.None &&
55935559 SettingManager.Common.DispLatestPost != MyCommon.DispTitleEnum.Post &&
@@ -5599,11 +5565,11 @@ namespace OpenTween
55995565 if (!StatusLabelUrl.Text.StartsWith("http", StringComparison.OrdinalIgnoreCase))
56005566 SetStatusLabelUrl();
56015567
5602- foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
5568+ if (SettingManager.Common.TabIconDisp)
56035569 {
5604- if (tab.UnreadCount == 0)
5570+ foreach (var (tab, index) in this._statuses.Tabs.WithIndex())
56055571 {
5606- if (SettingManager.Common.TabIconDisp)
5572+ if (tab.UnreadCount == 0)
56075573 {
56085574 var tabPage = this.ListTab.TabPages[index];
56095575 if (tabPage.ImageIndex == 0)
@@ -5611,13 +5577,18 @@ namespace OpenTween
56115577 }
56125578 }
56135579 }
5614- if (!SettingManager.Common.TabIconDisp) ListTab.Refresh();
5580+ else
5581+ {
5582+ this.ListTab.Refresh();
5583+ }
5584+
5585+ this.DispSelectedPost();
56155586 }
56165587
56175588 public string createDetailHtml(string orgdata)
56185589 => detailHtmlFormatHeader + orgdata + detailHtmlFormatFooter;
56195590
5620- private Task DispSelectedPost()
5591+ private void DispSelectedPost()
56215592 => this.DispSelectedPost(false);
56225593
56235594 private PostClass displayPost = new PostClass();
@@ -5627,7 +5598,7 @@ namespace OpenTween
56275598 /// </summary>
56285599 private CancellationTokenSource thumbnailTokenSource = null;
56295600
5630- private async Task DispSelectedPost(bool forceupdate)
5601+ private void DispSelectedPost(bool forceupdate)
56315602 {
56325603 var currentPost = this.CurrentPost;
56335604 if (currentPost == null)
@@ -5655,11 +5626,17 @@ namespace OpenTween
56555626 loadTasks.Add(this.tweetThumbnail1.ShowThumbnailAsync(currentPost, token));
56565627 }
56575628
5658- try
5629+ async Task delayedTasks()
56595630 {
5660- await Task.WhenAll(loadTasks);
5631+ try
5632+ {
5633+ await Task.WhenAll(loadTasks);
5634+ }
5635+ catch (OperationCanceledException) { }
56615636 }
5662- catch (OperationCanceledException) { }
5637+
5638+ // サムネイルの読み込みを待たずに次に選択されたツイートを表示するため await しない
5639+ _ = delayedTasks();
56635640 }
56645641
56655642 private async void MatomeMenuItem_Click(object sender, EventArgs e)
@@ -5870,7 +5847,7 @@ namespace OpenTween
58705847
58715848 ShortcutCommand.Create(Keys.Control | Keys.Home, Keys.Control | Keys.End)
58725849 .FocusedOn(FocusedControl.ListTab)
5873- .Do(() => this._colorize = true, preventDefault: false),
5850+ .Do(() => this.selectionDebouncer.Call(), preventDefault: false),
58745851
58755852 ShortcutCommand.Create(Keys.Control | Keys.N)
58765853 .FocusedOn(FocusedControl.ListTab)
@@ -6140,7 +6117,8 @@ namespace OpenTween
61406117 startstr = StatusText.Text.Substring(i + 1, endidx - i);
61416118 int cnt = AtIdSupl.ItemCount;
61426119 ShowSuplDialog(StatusText, AtIdSupl, startstr.Length + 1, startstr);
6143- if (AtIdSupl.ItemCount != cnt) ModifySettingAtId = true;
6120+ if (AtIdSupl.ItemCount != cnt)
6121+ this.MarkSettingAtIdModified();
61446122 }
61456123 else if (c == '#')
61466124 {
@@ -7740,101 +7718,74 @@ namespace OpenTween
77407718 private void ListTab_MouseUp(object sender, MouseEventArgs e)
77417719 => this._tabDrag = false;
77427720
7743- private static int iconCnt = 0;
7744- private static int blinkCnt = 0;
7745- private static bool blink = false;
7746- private static bool idle = false;
7721+ private int iconCnt = 0;
7722+ private int blinkCnt = 0;
7723+ private bool blink = false;
77477724
7748- private async Task RefreshTasktrayIcon()
7725+ private void RefreshTasktrayIcon()
77497726 {
7750- if (_colorize)
7751- await this.Colorize();
7752-
7753- if (!TimerRefreshIcon.Enabled) return;
7754- //Static usCheckCnt As int = 0
7727+ void EnableTasktrayAnimation()
7728+ => this.TimerRefreshIcon.Enabled = true;
77557729
7756- //Static iconDlListTopItem As ListViewItem = null
7730+ void DisableTasktrayAnimation()
7731+ => this.TimerRefreshIcon.Enabled = false;
77577732
7758- //if (((ListView)ListTab.SelectedTab.Tag).TopItem == iconDlListTopItem)
7759- // ((ImageDictionary)this.TIconDic).PauseGetImage = false;
7760- //else
7761- // ((ImageDictionary)this.TIconDic).PauseGetImage = true;
7762- //
7763- //iconDlListTopItem = ((ListView)ListTab.SelectedTab.Tag).TopItem;
7764-
7765- iconCnt += 1;
7766- blinkCnt += 1;
7767- //usCheckCnt += 1;
7768-
7769- //if (usCheckCnt > 300) //1min
7770- //{
7771- // usCheckCnt = 0;
7772- // if (!this.IsReceivedUserStream)
7773- // {
7774- // TraceOut("ReconnectUserStream");
7775- // tw.ReconnectUserStream();
7776- // }
7777- //}
7778-
7779- var busy = this.workerSemaphore.CurrentCount != MAX_WORKER_THREADS;
7780-
7781- if (iconCnt >= this.NIconRefresh.Length)
7782- {
7783- iconCnt = 0;
7784- }
7785- if (blinkCnt > 10)
7733+ var busyTasks = this.workerSemaphore.CurrentCount != MAX_WORKER_THREADS;
7734+ if (busyTasks)
77867735 {
7787- blinkCnt = 0;
7788- //未保存の変更を保存
7789- SaveConfigsAll(true);
7790- }
7736+ iconCnt += 1;
7737+ if (iconCnt >= this.NIconRefresh.Length)
7738+ iconCnt = 0;
77917739
7792- if (busy)
7793- {
77947740 NotifyIcon1.Icon = NIconRefresh[iconCnt];
7795- idle = false;
77967741 _myStatusError = false;
7742+ EnableTasktrayAnimation();
77977743 return;
77987744 }
77997745
7800- TabModel tb = _statuses.GetTabByType(MyCommon.TabUsageType.Mentions);
7801- if (SettingManager.Common.ReplyIconState != MyCommon.REPLY_ICONSTATE.None && tb != null && tb.UnreadCount > 0)
7746+ var replyIconType = SettingManager.Common.ReplyIconState;
7747+ var reply = false;
7748+ if (replyIconType != MyCommon.REPLY_ICONSTATE.None)
78027749 {
7803- if (blinkCnt > 0) return;
7804- blink = !blink;
7805- if (blink || SettingManager.Common.ReplyIconState == MyCommon.REPLY_ICONSTATE.StaticIcon)
7806- {
7807- NotifyIcon1.Icon = ReplyIcon;
7808- }
7809- else
7810- {
7811- NotifyIcon1.Icon = ReplyIconBlink;
7812- }
7813- idle = false;
7814- return;
7750+ var replyTab = this._statuses.GetTabByType<MentionsTabModel>();
7751+ if (replyTab != null && replyTab.UnreadCount > 0)
7752+ reply = true;
78157753 }
78167754
7817- if (idle) return;
7818- idle = true;
7819- //優先度:エラー→オフライン→アイドル
7820- //エラーは更新アイコンでクリアされる
7821- if (_myStatusError)
7755+ if (replyIconType == MyCommon.REPLY_ICONSTATE.BlinkIcon && reply)
78227756 {
7823- NotifyIcon1.Icon = NIconAtRed;
7757+ blinkCnt += 1;
7758+ if (blinkCnt > 10)
7759+ blinkCnt = 0;
7760+
7761+ if (blinkCnt == 0)
7762+ blink = !blink;
7763+
7764+ NotifyIcon1.Icon = blink ? ReplyIconBlink : ReplyIcon;
7765+ EnableTasktrayAnimation();
78247766 return;
78257767 }
7826- if (_myStatusOnline)
7827- {
7768+
7769+ DisableTasktrayAnimation();
7770+
7771+ iconCnt = 0;
7772+ blinkCnt = 0;
7773+ blink = false;
7774+
7775+ // 優先度:リプライ→エラー→オフライン→アイドル
7776+ // エラーは更新アイコンでクリアされる
7777+ if (replyIconType == MyCommon.REPLY_ICONSTATE.StaticIcon && reply)
7778+ NotifyIcon1.Icon = ReplyIcon;
7779+ else if (_myStatusError)
7780+ NotifyIcon1.Icon = NIconAtRed;
7781+ else if (_myStatusOnline)
78287782 NotifyIcon1.Icon = NIconAt;
7829- }
78307783 else
7831- {
78327784 NotifyIcon1.Icon = NIconAtSmoke;
7833- }
78347785 }
78357786
7836- private async void TimerRefreshIcon_Tick(object sender, EventArgs e)
7837- => await this.RefreshTasktrayIcon(); // 200ms
7787+ private void TimerRefreshIcon_Tick(object sender, EventArgs e)
7788+ => this.RefreshTasktrayIcon(); // 200ms
78387789
78397790 private void ContextMenuTabProperty_Opening(object sender, CancelEventArgs e)
78407791 {
@@ -8234,7 +8185,8 @@ namespace OpenTween
82348185 }
82358186 int cnt = AtIdSupl.ItemCount;
82368187 AtIdSupl.AddRangeItem(atids.ToArray());
8237- if (AtIdSupl.ItemCount != cnt) ModifySettingAtId = true;
8188+ if (AtIdSupl.ItemCount != cnt)
8189+ this.MarkSettingAtIdModified();
82388190 }
82398191 }
82408192
@@ -8841,7 +8793,8 @@ namespace OpenTween
88418793 {
88428794 AtIdSupl.AddItem(mid.Result("${id}"));
88438795 }
8844- if (bCnt != AtIdSupl.ItemCount) ModifySettingAtId = true;
8796+ if (bCnt != AtIdSupl.ItemCount)
8797+ this.MarkSettingAtIdModified();
88458798 }
88468799
88478800 // リプライ先ステータスIDの指定がない場合は指定しない
@@ -8943,7 +8896,7 @@ namespace OpenTween
89438896 {
89448897 SettingManager.Common.PlaySound = false;
89458898 }
8946- ModifySettingCommon = true;
8899+ this.MarkSettingCommonModified();
89478900 }
89488901
89498902 private void SplitContainer1_SplitterMoved(object sender, SplitterEventArgs e)
@@ -8968,7 +8921,7 @@ namespace OpenTween
89688921 }
89698922
89708923 this._mySpDis = splitterDistance;
8971- this.ModifySettingLocal = true;
8924+ this.MarkSettingLocalModified();
89728925 }
89738926
89748927 private async Task doRepliedStatusOpen()
@@ -9013,7 +8966,7 @@ namespace OpenTween
90138966 {
90148967 this.StatusText.Multiline = multiline;
90158968 SettingManager.Local.StatusMultiline = multiline;
9016- ModifySettingLocal = true;
8969+ this.MarkSettingLocalModified();
90178970 }
90188971 }
90198972
@@ -9024,7 +8977,8 @@ namespace OpenTween
90248977 else
90258978 this.StatusText.ScrollBars = ScrollBars.None;
90268979
9027- ModifySettingLocal = true;
8980+ if (!this._initialLayout)
8981+ this.MarkSettingLocalModified();
90288982 }
90298983
90308984 private void MultiLineMenuItem_Click(object sender, EventArgs e)
@@ -9044,7 +8998,7 @@ namespace OpenTween
90448998 {
90458999 SplitContainer2.SplitterDistance = SplitContainer2.Height - SplitContainer2.Panel2MinSize - SplitContainer2.SplitterWidth;
90469000 }
9047- ModifySettingLocal = true;
9001+ this.MarkSettingLocalModified();
90489002 }
90499003
90509004 private async Task<bool> UrlConvertAsync(MyCommon.UrlConverter Converter_Type)
@@ -9270,7 +9224,7 @@ namespace OpenTween
92709224 this.NotifyFileMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
92719225 this.NewPostPopMenuItem.Checked = this.NotifyFileMenuItem.Checked;
92729226 SettingManager.Common.NewAllPop = NewPostPopMenuItem.Checked;
9273- ModifySettingCommon = true;
9227+ this.MarkSettingCommonModified();
92749228 }
92759229
92769230 private void ListLockMenuItem_CheckStateChanged(object sender, EventArgs e)
@@ -9278,7 +9232,7 @@ namespace OpenTween
92789232 ListLockMenuItem.Checked = ((ToolStripMenuItem)sender).Checked;
92799233 this.LockListFileMenuItem.Checked = ListLockMenuItem.Checked;
92809234 SettingManager.Common.ListLock = ListLockMenuItem.Checked;
9281- ModifySettingCommon = true;
9235+ this.MarkSettingCommonModified();
92829236 }
92839237
92849238 private void MenuStrip1_MenuActivate(object sender, EventArgs e)
@@ -9365,7 +9319,7 @@ namespace OpenTween
93659319 SettingManager.Local.Width7 = lst.Columns[6].Width;
93669320 SettingManager.Local.Width8 = lst.Columns[7].Width;
93679321 }
9368- ModifySettingLocal = true;
9322+ this.MarkSettingLocalModified();
93699323 _isColumnChanged = true;
93709324 }
93719325
@@ -9373,19 +9327,19 @@ namespace OpenTween
93739327 {
93749328 DetailsListView lst = (DetailsListView)sender;
93759329 if (SettingManager.Local == null) return;
9330+
9331+ var modified = false;
93769332 if (_iconCol)
93779333 {
93789334 if (SettingManager.Local.Width1 != lst.Columns[0].Width)
93799335 {
93809336 SettingManager.Local.Width1 = lst.Columns[0].Width;
9381- ModifySettingLocal = true;
9382- _isColumnChanged = true;
9337+ modified = true;
93839338 }
93849339 if (SettingManager.Local.Width3 != lst.Columns[1].Width)
93859340 {
93869341 SettingManager.Local.Width3 = lst.Columns[1].Width;
9387- ModifySettingLocal = true;
9388- _isColumnChanged = true;
9342+ modified = true;
93899343 }
93909344 }
93919345 else
@@ -9393,52 +9347,49 @@ namespace OpenTween
93939347 if (SettingManager.Local.Width1 != lst.Columns[0].Width)
93949348 {
93959349 SettingManager.Local.Width1 = lst.Columns[0].Width;
9396- ModifySettingLocal = true;
9397- _isColumnChanged = true;
9350+ modified = true;
93989351 }
93999352 if (SettingManager.Local.Width2 != lst.Columns[1].Width)
94009353 {
94019354 SettingManager.Local.Width2 = lst.Columns[1].Width;
9402- ModifySettingLocal = true;
9403- _isColumnChanged = true;
9355+ modified = true;
94049356 }
94059357 if (SettingManager.Local.Width3 != lst.Columns[2].Width)
94069358 {
94079359 SettingManager.Local.Width3 = lst.Columns[2].Width;
9408- ModifySettingLocal = true;
9409- _isColumnChanged = true;
9360+ modified = true;
94109361 }
94119362 if (SettingManager.Local.Width4 != lst.Columns[3].Width)
94129363 {
94139364 SettingManager.Local.Width4 = lst.Columns[3].Width;
9414- ModifySettingLocal = true;
9415- _isColumnChanged = true;
9365+ modified = true;
94169366 }
94179367 if (SettingManager.Local.Width5 != lst.Columns[4].Width)
94189368 {
94199369 SettingManager.Local.Width5 = lst.Columns[4].Width;
9420- ModifySettingLocal = true;
9421- _isColumnChanged = true;
9370+ modified = true;
94229371 }
94239372 if (SettingManager.Local.Width6 != lst.Columns[5].Width)
94249373 {
94259374 SettingManager.Local.Width6 = lst.Columns[5].Width;
9426- ModifySettingLocal = true;
9427- _isColumnChanged = true;
9375+ modified = true;
94289376 }
94299377 if (SettingManager.Local.Width7 != lst.Columns[6].Width)
94309378 {
94319379 SettingManager.Local.Width7 = lst.Columns[6].Width;
9432- ModifySettingLocal = true;
9433- _isColumnChanged = true;
9380+ modified = true;
94349381 }
94359382 if (SettingManager.Local.Width8 != lst.Columns[7].Width)
94369383 {
94379384 SettingManager.Local.Width8 = lst.Columns[7].Width;
9438- ModifySettingLocal = true;
9439- _isColumnChanged = true;
9385+ modified = true;
94409386 }
94419387 }
9388+ if (modified)
9389+ {
9390+ this.MarkSettingLocalModified();
9391+ this._isColumnChanged = true;
9392+ }
94429393 // 非表示の時にColumnChangedが呼ばれた場合はForm初期化処理中なので保存しない
94439394 //if (changed)
94449395 //{
@@ -9449,7 +9400,7 @@ namespace OpenTween
94499400 private void SplitContainer2_SplitterMoved(object sender, SplitterEventArgs e)
94509401 {
94519402 if (StatusText.Multiline) _mySpDis2 = StatusText.Height;
9452- ModifySettingLocal = true;
9403+ this.MarkSettingLocalModified();
94539404 }
94549405
94559406 private void TweenMain_DragDrop(object sender, DragEventArgs e)
@@ -9872,13 +9823,13 @@ namespace OpenTween
98729823
98739824 _initial = false;
98749825
9875- TimerTimeline.Enabled = true;
9826+ this.timelineScheduler.Enabled = true;
98769827 }
98779828
98789829 private async Task doGetFollowersMenu()
98799830 {
98809831 await this.RefreshFollowerIdsAsync();
9881- await this.DispSelectedPost(true);
9832+ this.DispSelectedPost(true);
98829833 }
98839834
98849835 private async void GetFollowersAllToolStripMenuItem_Click(object sender, EventArgs e)
@@ -10011,12 +9962,12 @@ namespace OpenTween
100119962 return WebUtility.HtmlDecode(statusHtml);
100129963 }
100139964
10014- private async void DumpPostClassToolStripMenuItem_Click(object sender, EventArgs e)
9965+ private void DumpPostClassToolStripMenuItem_Click(object sender, EventArgs e)
100159966 {
100169967 this.tweetDetailsView.DumpPostClass = this.DumpPostClassToolStripMenuItem.Checked;
100179968
100189969 if (this.CurrentPost != null)
10019- await this.DispSelectedPost(true);
9970+ this.DispSelectedPost(true);
100209971 }
100219972
100229973 private void MenuItemHelp_DropDownOpening(object sender, EventArgs e)
@@ -10039,13 +9990,13 @@ namespace OpenTween
100399990 private void IdeographicSpaceToSpaceMenuItem_Click(object sender, EventArgs e)
100409991 {
100419992 SettingManager.Common.WideSpaceConvert = ((ToolStripMenuItem)sender).Checked;
10042- ModifySettingCommon = true;
9993+ this.MarkSettingCommonModified();
100439994 }
100449995
100459996 private void FocusLockMenuItem_CheckedChanged(object sender, EventArgs e)
100469997 {
100479998 SettingManager.Common.FocusLockToStatusText = ((ToolStripMenuItem)sender).Checked;
10048- ModifySettingCommon = true;
9999+ this.MarkSettingCommonModified();
1004910000 }
1005010001
1005110002 private void PostModeMenuItem_DropDownOpening(object sender, EventArgs e)
@@ -10672,7 +10623,7 @@ namespace OpenTween
1067210623 // StatusText.SelectionStart = sidx;
1067310624 // StatusText.Focus();
1067410625 //}
10675- ModifySettingCommon = true;
10626+ this.MarkSettingCommonModified();
1067610627 this.StatusText_TextChanged(null, null);
1067710628 }
1067810629
@@ -10691,7 +10642,7 @@ namespace OpenTween
1069110642 HashToggleMenuItem.Checked = false;
1069210643 HashTogglePullDownMenuItem.Checked = false;
1069310644 }
10694- ModifySettingCommon = true;
10645+ this.MarkSettingCommonModified();
1069510646 this.StatusText_TextChanged(null, null);
1069610647 }
1069710648
@@ -10705,7 +10656,7 @@ namespace OpenTween
1070510656 HashTogglePullDownMenuItem.Checked = true;
1070610657 HashToggleMenuItem.Checked = true;
1070710658 //使用ハッシュタグとして設定
10708- ModifySettingCommon = true;
10659+ this.MarkSettingCommonModified();
1070910660 }
1071010661
1071110662 private void MenuItemOperate_DropDownOpening(object sender, EventArgs e)
@@ -10839,7 +10790,7 @@ namespace OpenTween
1083910790 }
1084010791
1084110792 this._mySpDis3 = splitterDistance;
10842- this.ModifySettingLocal = true;
10793+ this.MarkSettingLocalModified();
1084310794 }
1084410795
1084510796 private void MenuItemEdit_DropDownOpening(object sender, EventArgs e)
@@ -11006,7 +10957,6 @@ namespace OpenTween
1100610957
1100710958 this.tweetDetailsView.Owner = this;
1100810959
11009- this.TimerTimeline.Elapsed += this.TimerTimeline_Elapsed;
1101010960 this._hookGlobalHotkey.HotkeyPressed += _hookGlobalHotkey_HotkeyPressed;
1101110961 this.gh.NotifyClicked += GrowlHelper_Callback;
1101210962
@@ -11093,9 +11043,7 @@ namespace OpenTween
1109311043 {
1109411044 if (ImageSelector.Visible)
1109511045 {
11096- ModifySettingCommon = true;
11097- SaveConfigsAll(true);
11098-
11046+ this.MarkSettingCommonModified();
1109911047 this.StatusText_TextChanged(null, null);
1110011048 }
1110111049 }
@@ -11148,9 +11096,9 @@ namespace OpenTween
1114811096 }
1114911097 }
1115011098
11151- public bool ModifySettingCommon { get; set; }
11152- public bool ModifySettingLocal { get; set; }
11153- public bool ModifySettingAtId { get; set; }
11099+ private bool ModifySettingCommon { get; set; }
11100+ private bool ModifySettingLocal { get; set; }
11101+ private bool ModifySettingAtId { get; set; }
1115411102
1115511103 private void MenuItemCommand_DropDownOpening(object sender, EventArgs e)
1115611104 {
@@ -11281,9 +11229,6 @@ namespace OpenTween
1128111229 MessageBox.Show(buf.ToString(), "アイコンキャッシュ使用状況");
1128211230 }
1128311231
11284- private void tw_UserIdChanged()
11285- => this.ModifySettingCommon = true;
11286-
1128711232 #region "Userstream"
1128811233 private async void tw_PostDeleted(object sender, PostDeletedEventArgs e)
1128911234 {
@@ -11291,7 +11236,7 @@ namespace OpenTween
1129111236 {
1129211237 if (InvokeRequired && !IsDisposed)
1129311238 {
11294- await this.InvokeAsync(async () =>
11239+ await this.InvokeAsync(() =>
1129511240 {
1129611241 this._statuses.RemovePostFromAllTabs(e.StatusId, setIsDeleted: true);
1129711242 if (this.CurrentTab.Contains(e.StatusId))
@@ -11300,7 +11245,7 @@ namespace OpenTween
1130011245 this.CurrentListView.Update();
1130111246 var post = this.CurrentPost;
1130211247 if (post != null && post.StatusId == e.StatusId)
11303- await this.DispSelectedPost(true);
11248+ this.DispSelectedPost(true);
1130411249 }
1130511250 });
1130611251 return;
@@ -11325,7 +11270,7 @@ namespace OpenTween
1132511270
1132611271 this._statuses.DistributePosts();
1132711272
11328- this.RefreshThrottlingTimer.Invoke();
11273+ this.RefreshThrottlingTimer.Call();
1132911274 }
1133011275
1133111276 private async void tw_UserStreamStarted(object sender, EventArgs e)
@@ -11552,7 +11497,7 @@ namespace OpenTween
1155211497 if (!inputTrack.Equals(tw.TrackWord))
1155311498 {
1155411499 tw.TrackWord = inputTrack;
11555- this.ModifySettingCommon = true;
11500+ this.MarkSettingCommonModified();
1155611501 TrackToolStripMenuItem.Checked = !string.IsNullOrEmpty(inputTrack);
1155711502 tw.ReconnectUserStream();
1155811503 }
@@ -11562,13 +11507,13 @@ namespace OpenTween
1156211507 tw.TrackWord = "";
1156311508 tw.ReconnectUserStream();
1156411509 }
11565- this.ModifySettingCommon = true;
11510+ this.MarkSettingCommonModified();
1156611511 }
1156711512
1156811513 private void AllrepliesToolStripMenuItem_Click(object sender, EventArgs e)
1156911514 {
1157011515 tw.AllAtReply = AllrepliesToolStripMenuItem.Checked;
11571- this.ModifySettingCommon = true;
11516+ this.MarkSettingCommonModified();
1157211517 tw.ReconnectUserStream();
1157311518 }
1157411519
@@ -11663,10 +11608,11 @@ namespace OpenTween
1166311608
1166411609 private void SystemEvents_PowerModeChanged(object sender, Microsoft.Win32.PowerModeChangedEventArgs e)
1166511610 {
11666- if (e.Mode == Microsoft.Win32.PowerModes.Resume) osResumed = true;
11611+ if (e.Mode == Microsoft.Win32.PowerModes.Resume)
11612+ this.timelineScheduler.SystemResumed();
1166711613 }
1166811614
11669- private async void SystemEvents_TimeChanged(object sender, EventArgs e)
11615+ private void SystemEvents_TimeChanged(object sender, EventArgs e)
1167011616 {
1167111617 var prevTimeOffset = TimeZoneInfo.Local.BaseUtcOffset;
1167211618
@@ -11680,7 +11626,7 @@ namespace OpenTween
1168011626 this.PurgeListViewItemCache();
1168111627 this.CurrentListView.Refresh();
1168211628
11683- await this.DispSelectedPost(forceupdate: true);
11629+ this.DispSelectedPost(forceupdate: true);
1168411630 }
1168511631 }
1168611632
@@ -11694,7 +11640,7 @@ namespace OpenTween
1169411640 {
1169511641 tw.StopUserStream();
1169611642 }
11697- TimerTimeline.Enabled = isEnable;
11643+ this.timelineScheduler.Enabled = isEnable;
1169811644 }
1169911645
1170011646 private void StopRefreshAllMenuItem_CheckedChanged(object sender, EventArgs e)
@@ -11829,8 +11775,7 @@ namespace OpenTween
1182911775 }
1183011776
1183111777 this.CurrentListView.Refresh();
11832-
11833- ModifySettingCommon = true;
11778+ this.MarkSettingCommonModified();
1183411779 }
1183511780
1183611781 private void LockListSortToolStripMenuItem_Click(object sender, EventArgs e)
@@ -11839,8 +11784,7 @@ namespace OpenTween
1183911784 if (SettingManager.Common.SortOrderLock == state) return;
1184011785
1184111786 SettingManager.Common.SortOrderLock = state;
11842-
11843- ModifySettingCommon = true;
11787+ this.MarkSettingCommonModified();
1184411788 }
1184511789
1184611790 private void tweetDetailsView_StatusChanged(object sender, TweetDetailsViewStatusChengedEventArgs e)
Show on old repository browser