OSDN Git Service

PostRequestクラスを追加
[opentween/open-tween.git] / OpenTween / TimelineListViewState.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2007-2011 kiri_feather (@kiri_feather) <kiri.feather@gmail.com>
3 //           (c) 2008-2011 Moz (@syo68k)
4 //           (c) 2008-2011 takeshik (@takeshik) <http://www.takeshik.org/>
5 //           (c) 2010-2011 anis774 (@anis774) <http://d.hatena.ne.jp/anis774/>
6 //           (c) 2010-2011 fantasticswallow (@f_swallow) <http://twitter.com/f_swallow>
7 //           (c) 2011      kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
8 // All rights reserved.
9 //
10 // This file is part of OpenTween.
11 //
12 // This program is free software; you can redistribute it and/or modify it
13 // under the terms of the GNU General public License as published by the Free
14 // Software Foundation; either version 3 of the License, or (at your option)
15 // any later version.
16 //
17 // This program is distributed in the hope that it will be useful, but
18 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General public License
20 // for more details.
21 //
22 // You should have received a copy of the GNU General public License along
23 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
24 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
25 // Boston, MA 02110-1301, USA.
26
27 #nullable enable
28
29 using System;
30 using System.Collections.Generic;
31 using System.Drawing;
32 using System.Linq;
33 using System.Windows.Forms;
34 using OpenTween.Models;
35 using OpenTween.OpenTweenCustomControl;
36
37 namespace OpenTween
38 {
39     public class TimelineListViewState
40     {
41         private readonly DetailsListView listView;
42         private readonly TabModel tab;
43
44         private ListViewScroll savedScrollState;
45         private ListViewSelection savedSelectionState;
46
47         internal readonly record struct ListViewScroll(
48             ScrollLockMode ScrollLockMode,
49             PostId? TopItemStatusId
50         );
51
52         internal enum ScrollLockMode
53         {
54             /// <summary>固定しない</summary>
55             None,
56
57             /// <summary>最上部に固定する</summary>
58             FixedToTop,
59
60             /// <summary>最下部に固定する</summary>
61             FixedToBottom,
62
63             /// <summary><see cref="ListViewScroll.TopItemStatusId"/> の位置に固定する</summary>
64             FixedToItem,
65         }
66
67         internal readonly record struct ListViewSelection(
68             PostId[] SelectedStatusIds,
69             PostId? SelectionMarkStatusId,
70             PostId? FocusedStatusId
71         );
72
73         public TimelineListViewState(DetailsListView listView, TabModel tab)
74         {
75             this.listView = listView;
76             this.tab = tab;
77         }
78
79         public void Save(bool lockScroll)
80         {
81             this.savedScrollState = this.SaveListViewScroll(lockScroll);
82             this.savedSelectionState = this.SaveListViewSelection();
83         }
84
85         public void Restore(bool forceScroll = false)
86         {
87             this.RestoreScroll(forceScroll);
88             this.RestoreSelection();
89         }
90
91         public void RestoreScroll(bool forceScroll = false)
92             => this.RestoreListViewScroll(this.savedScrollState, forceScroll);
93
94         public void RestoreSelection()
95             => this.RestoreListViewSelection(this.savedSelectionState);
96
97         /// <summary>
98         /// <see cref="ListView"/> のスクロール位置に関する情報を <see cref="ListViewScroll"/> として返します
99         /// </summary>
100         private ListViewScroll SaveListViewScroll(bool lockScroll)
101         {
102             var lockMode = this.GetScrollLockMode(lockScroll);
103             PostId? topItemStatusId = null;
104
105             if (lockMode == ScrollLockMode.FixedToItem || lockMode == ScrollLockMode.None)
106             {
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);
111             }
112
113             return new ListViewScroll
114             {
115                 ScrollLockMode = lockMode,
116                 TopItemStatusId = topItemStatusId,
117             };
118         }
119
120         private ScrollLockMode GetScrollLockMode(bool lockScroll)
121         {
122             if (this.tab.SortMode == ComparerMode.Id)
123             {
124                 if (this.tab.SortOrder == SortOrder.Ascending)
125                 {
126                     // Id昇順
127                     if (lockScroll)
128                         return ScrollLockMode.None;
129
130                     // 最下行が表示されていたら、最下行へ強制スクロール。最下行が表示されていなかったら制御しない
131
132                     // 一番下に表示されているアイテム
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;
136                     else
137                         return ScrollLockMode.None;
138                 }
139                 else
140                 {
141                     // Id降順
142                     if (lockScroll)
143                         return ScrollLockMode.FixedToItem;
144
145                     // 最上行が表示されていたら、制御しない。最上行が表示されていなかったら、現在表示位置へ強制スクロール
146                     var topItem = this.listView.TopItem;
147                     if (topItem == null || topItem.Index == 0)
148                         return ScrollLockMode.FixedToTop;
149                     else
150                         return ScrollLockMode.FixedToItem;
151                 }
152             }
153             else
154             {
155                 return ScrollLockMode.FixedToItem;
156             }
157         }
158
159         /// <summary>
160         /// <see cref="ListView"/> の選択状態を <see cref="ListViewSelection"/> として返します
161         /// </summary>
162         private ListViewSelection SaveListViewSelection()
163         {
164             if (this.listView.VirtualListSize == 0)
165             {
166                 return new ListViewSelection
167                 {
168                     SelectedStatusIds = Array.Empty<PostId>(),
169                     SelectionMarkStatusId = null,
170                     FocusedStatusId = null,
171                 };
172             }
173
174             return new ListViewSelection
175             {
176                 SelectedStatusIds = this.tab.SelectedStatusIds,
177                 FocusedStatusId = this.GetFocusedStatusId(),
178                 SelectionMarkStatusId = this.GetSelectionMarkStatusId(),
179             };
180         }
181
182         private PostId? GetFocusedStatusId()
183         {
184             var index = this.listView.FocusedItem?.Index ?? -1;
185
186             return index != -1 && index < this.tab.AllCount ? this.tab.GetStatusIdAt(index) : null;
187         }
188
189         private PostId? GetSelectionMarkStatusId()
190         {
191             var index = this.listView.SelectionMark;
192
193             return index != -1 && index < this.tab.AllCount ? this.tab.GetStatusIdAt(index) : null;
194         }
195
196         /// <summary>
197         /// <see cref="SaveListViewScroll"/> によって保存されたスクロール位置を復元します
198         /// </summary>
199         private void RestoreListViewScroll(ListViewScroll listScroll, bool forceScroll)
200         {
201             if (this.listView.VirtualListSize == 0)
202                 return;
203
204             var scrollLockMode = listScroll.ScrollLockMode;
205             if (forceScroll && scrollLockMode == ScrollLockMode.None)
206                 scrollLockMode = ScrollLockMode.FixedToItem;
207
208             switch (scrollLockMode)
209             {
210                 case ScrollLockMode.FixedToTop:
211                     this.listView.EnsureVisible(0);
212                     break;
213                 case ScrollLockMode.FixedToBottom:
214                     this.listView.EnsureVisible(this.listView.VirtualListSize - 1);
215                     break;
216                 case ScrollLockMode.FixedToItem:
217                     var topIndex = listScroll.TopItemStatusId != null ? this.tab.IndexOf(listScroll.TopItemStatusId) : -1;
218                     if (topIndex != -1)
219                     {
220                         var topItem = this.listView.Items[topIndex];
221                         try
222                         {
223                             this.listView.TopItem = topItem;
224                         }
225                         catch (NullReferenceException)
226                         {
227                             this.listView.EnsureVisible(this.listView.VirtualListSize - 1);
228                             this.listView.EnsureVisible(topIndex);
229                         }
230                     }
231                     break;
232                 case ScrollLockMode.None:
233                 default:
234                     break;
235             }
236         }
237
238         /// <summary>
239         /// <see cref="SaveListViewSelection"/> によって保存された選択状態を復元します
240         /// </summary>
241         private void RestoreListViewSelection(ListViewSelection listSelection)
242         {
243             var invalidateBounds = (Rectangle?)null;
244
245             var previousFocusedItem = this.listView.FocusedItem;
246             if (previousFocusedItem != null)
247                 invalidateBounds = previousFocusedItem.Bounds;
248
249             // status_id から ListView 上のインデックスに変換
250             if (listSelection.SelectedStatusIds != null)
251             {
252                 var selectedIndices = this.tab.IndexOf(listSelection.SelectedStatusIds).Where(x => x != -1).ToArray();
253                 this.listView.SelectItems(selectedIndices);
254             }
255
256             if (listSelection.FocusedStatusId != null)
257             {
258                 var focusedIndex = this.tab.IndexOf(listSelection.FocusedStatusId);
259                 if (focusedIndex != -1)
260                     this.listView.Items[focusedIndex].Focused = true;
261             }
262
263             if (listSelection.SelectionMarkStatusId != null)
264             {
265                 var selectionMarkIndex = this.tab.IndexOf(listSelection.SelectionMarkStatusId);
266                 if (selectionMarkIndex != -1)
267                     this.listView.SelectionMark = selectionMarkIndex;
268             }
269
270             if (invalidateBounds != null)
271                 this.listView.Invalidate(invalidateBounds.Value);
272         }
273     }
274 }