1 // OpenTween - Client of Twitter
2 // Copyright (c) 2007-2011 kiri_feather (@kiri_feather) <kiri.feather@gmail.com>
3 // (c) 2008-2011 Moz (@syo68k)
4 // (c) 2008-2011 takeshik (@takeshik) <http://www.takeshik.org/>
5 // (c) 2010-2011 anis774 (@anis774) <http://d.hatena.ne.jp/anis774/>
6 // (c) 2010-2011 fantasticswallow (@f_swallow) <http://twitter.com/f_swallow>
7 // (c) 2011 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
8 // All rights reserved.
10 // This file is part of OpenTween.
12 // This program is free software; you can redistribute it and/or modify it
13 // under the terms of the GNU General public License as published by the Free
14 // Software Foundation; either version 3 of the License, or (at your option)
17 // This program is distributed in the hope that it will be useful, but
18 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General public License
22 // You should have received a copy of the GNU General public License along
23 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
24 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
25 // Boston, MA 02110-1301, USA.
30 using System.Collections.Generic;
33 using System.Windows.Forms;
34 using OpenTween.Models;
35 using OpenTween.OpenTweenCustomControl;
39 public class TimelineListViewState
41 private readonly DetailsListView listView;
42 private readonly TabModel tab;
44 private ListViewScroll savedScrollState;
45 private ListViewSelection savedSelectionState;
47 internal readonly record struct ListViewScroll(
48 ScrollLockMode ScrollLockMode,
49 PostId? TopItemStatusId
52 internal enum ScrollLockMode
54 /// <summary>固定しない</summary>
57 /// <summary>最上部に固定する</summary>
60 /// <summary>最下部に固定する</summary>
63 /// <summary><see cref="ListViewScroll.TopItemStatusId"/> の位置に固定する</summary>
67 internal readonly record struct ListViewSelection(
68 PostId[] SelectedStatusIds,
69 PostId? SelectionMarkStatusId,
70 PostId? FocusedStatusId
73 public TimelineListViewState(DetailsListView listView, TabModel tab)
75 this.listView = listView;
79 public void Save(bool lockScroll)
81 this.savedScrollState = this.SaveListViewScroll(lockScroll);
82 this.savedSelectionState = this.SaveListViewSelection();
85 public void Restore(bool forceScroll = false)
87 this.RestoreScroll(forceScroll);
88 this.RestoreSelection();
91 public void RestoreScroll(bool forceScroll = false)
92 => this.RestoreListViewScroll(this.savedScrollState, forceScroll);
94 public void RestoreSelection()
95 => this.RestoreListViewSelection(this.savedSelectionState);
98 /// <see cref="ListView"/> のスクロール位置に関する情報を <see cref="ListViewScroll"/> として返します
100 private ListViewScroll SaveListViewScroll(bool lockScroll)
102 var lockMode = this.GetScrollLockMode(lockScroll);
103 PostId? topItemStatusId = null;
105 if (lockMode == ScrollLockMode.FixedToItem || lockMode == ScrollLockMode.None)
107 // ScrollLockMode.None の場合も forceScroll = true の時に topItemStatusId を使用するため保存する
108 var topItemIndex = this.listView.TopItem?.Index ?? -1;
109 if (topItemIndex != -1 && topItemIndex < this.tab.AllCount)
110 topItemStatusId = this.tab.GetStatusIdAt(topItemIndex);
113 return new ListViewScroll
115 ScrollLockMode = lockMode,
116 TopItemStatusId = topItemStatusId,
120 private ScrollLockMode GetScrollLockMode(bool lockScroll)
122 if (this.tab.SortMode == ComparerMode.Id)
124 if (this.tab.SortOrder == SortOrder.Ascending)
128 return ScrollLockMode.None;
130 // 最下行が表示されていたら、最下行へ強制スクロール。最下行が表示されていなかったら制御しない
133 var bottomItem = this.listView.GetItemAt(0, this.listView.ClientSize.Height - 1);
134 if (bottomItem == null || bottomItem.Index == this.listView.VirtualListSize - 1)
135 return ScrollLockMode.FixedToBottom;
137 return ScrollLockMode.None;
143 return ScrollLockMode.FixedToItem;
145 // 最上行が表示されていたら、制御しない。最上行が表示されていなかったら、現在表示位置へ強制スクロール
146 var topItem = this.listView.TopItem;
147 if (topItem == null || topItem.Index == 0)
148 return ScrollLockMode.FixedToTop;
150 return ScrollLockMode.FixedToItem;
155 return ScrollLockMode.FixedToItem;
160 /// <see cref="ListView"/> の選択状態を <see cref="ListViewSelection"/> として返します
162 private ListViewSelection SaveListViewSelection()
164 if (this.listView.VirtualListSize == 0)
166 return new ListViewSelection
168 SelectedStatusIds = Array.Empty<PostId>(),
169 SelectionMarkStatusId = null,
170 FocusedStatusId = null,
174 return new ListViewSelection
176 SelectedStatusIds = this.tab.SelectedStatusIds,
177 FocusedStatusId = this.GetFocusedStatusId(),
178 SelectionMarkStatusId = this.GetSelectionMarkStatusId(),
182 private PostId? GetFocusedStatusId()
184 var index = this.listView.FocusedItem?.Index ?? -1;
186 return index != -1 && index < this.tab.AllCount ? this.tab.GetStatusIdAt(index) : null;
189 private PostId? GetSelectionMarkStatusId()
191 var index = this.listView.SelectionMark;
193 return index != -1 && index < this.tab.AllCount ? this.tab.GetStatusIdAt(index) : null;
197 /// <see cref="SaveListViewScroll"/> によって保存されたスクロール位置を復元します
199 private void RestoreListViewScroll(ListViewScroll listScroll, bool forceScroll)
201 if (this.listView.VirtualListSize == 0)
204 var scrollLockMode = listScroll.ScrollLockMode;
205 if (forceScroll && scrollLockMode == ScrollLockMode.None)
206 scrollLockMode = ScrollLockMode.FixedToItem;
208 switch (scrollLockMode)
210 case ScrollLockMode.FixedToTop:
211 this.listView.EnsureVisible(0);
213 case ScrollLockMode.FixedToBottom:
214 this.listView.EnsureVisible(this.listView.VirtualListSize - 1);
216 case ScrollLockMode.FixedToItem:
217 var topIndex = listScroll.TopItemStatusId != null ? this.tab.IndexOf(listScroll.TopItemStatusId) : -1;
220 var topItem = this.listView.Items[topIndex];
223 this.listView.TopItem = topItem;
225 catch (NullReferenceException)
227 this.listView.EnsureVisible(this.listView.VirtualListSize - 1);
228 this.listView.EnsureVisible(topIndex);
232 case ScrollLockMode.None:
239 /// <see cref="SaveListViewSelection"/> によって保存された選択状態を復元します
241 private void RestoreListViewSelection(ListViewSelection listSelection)
243 var invalidateBounds = (Rectangle?)null;
245 var previousFocusedItem = this.listView.FocusedItem;
246 if (previousFocusedItem != null)
247 invalidateBounds = previousFocusedItem.Bounds;
249 // status_id から ListView 上のインデックスに変換
250 if (listSelection.SelectedStatusIds != null)
252 var selectedIndices = this.tab.IndexOf(listSelection.SelectedStatusIds).Where(x => x != -1).ToArray();
253 this.listView.SelectItems(selectedIndices);
256 if (listSelection.FocusedStatusId != null)
258 var focusedIndex = this.tab.IndexOf(listSelection.FocusedStatusId);
259 if (focusedIndex != -1)
260 this.listView.Items[focusedIndex].Focused = true;
263 if (listSelection.SelectionMarkStatusId != null)
265 var selectionMarkIndex = this.tab.IndexOf(listSelection.SelectionMarkStatusId);
266 if (selectionMarkIndex != -1)
267 this.listView.SelectionMark = selectionMarkIndex;
270 if (invalidateBounds != null)
271 this.listView.Invalidate(invalidateBounds.Value);