// OpenTween - Client of Twitter // Copyright (c) 2007-2011 kiri_feather (@kiri_feather) // (c) 2008-2011 Moz (@syo68k) // (c) 2008-2011 takeshik (@takeshik) // (c) 2010-2011 anis774 (@anis774) // (c) 2010-2011 fantasticswallow (@f_swallow) // (c) 2011 kim_upsilon (@kim_upsilon) // All rights reserved. // // This file is part of OpenTween. // // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU General public License as published by the Free // Software Foundation; either version 3 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General public License // for more details. // // You should have received a copy of the GNU General public License along // with this program. If not, see , or write to // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. #nullable enable using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; using OpenTween.Models; using OpenTween.OpenTweenCustomControl; namespace OpenTween { public class TimelineListViewState { private readonly DetailsListView listView; private readonly TabModel tab; private ListViewScroll savedScrollState; private ListViewSelection savedSelectionState; internal readonly record struct ListViewScroll( ScrollLockMode ScrollLockMode, PostId? TopItemStatusId ); internal enum ScrollLockMode { /// 固定しない None, /// 最上部に固定する FixedToTop, /// 最下部に固定する FixedToBottom, /// の位置に固定する FixedToItem, } internal readonly record struct ListViewSelection( PostId[] SelectedStatusIds, PostId? SelectionMarkStatusId, PostId? FocusedStatusId ); public TimelineListViewState(DetailsListView listView, TabModel tab) { this.listView = listView; this.tab = tab; } public void Save(bool lockScroll) { this.savedScrollState = this.SaveListViewScroll(lockScroll); this.savedSelectionState = this.SaveListViewSelection(); } public void Restore(bool forceScroll = false) { this.RestoreScroll(forceScroll); this.RestoreSelection(); } public void RestoreScroll(bool forceScroll = false) => this.RestoreListViewScroll(this.savedScrollState, forceScroll); public void RestoreSelection() => this.RestoreListViewSelection(this.savedSelectionState); /// /// のスクロール位置に関する情報を として返します /// private ListViewScroll SaveListViewScroll(bool lockScroll) { var lockMode = this.GetScrollLockMode(lockScroll); PostId? topItemStatusId = null; if (lockMode == ScrollLockMode.FixedToItem || lockMode == ScrollLockMode.None) { // ScrollLockMode.None の場合も forceScroll = true の時に topItemStatusId を使用するため保存する var topItemIndex = this.listView.TopItem?.Index ?? -1; if (topItemIndex != -1 && topItemIndex < this.tab.AllCount) topItemStatusId = this.tab.GetStatusIdAt(topItemIndex); } return new ListViewScroll { ScrollLockMode = lockMode, TopItemStatusId = topItemStatusId, }; } private ScrollLockMode GetScrollLockMode(bool lockScroll) { if (this.tab.SortMode == ComparerMode.Id) { if (this.tab.SortOrder == SortOrder.Ascending) { // Id昇順 if (lockScroll) return ScrollLockMode.None; // 最下行が表示されていたら、最下行へ強制スクロール。最下行が表示されていなかったら制御しない // 一番下に表示されているアイテム var bottomItem = this.listView.GetItemAt(0, this.listView.ClientSize.Height - 1); if (bottomItem == null || bottomItem.Index == this.listView.VirtualListSize - 1) return ScrollLockMode.FixedToBottom; else return ScrollLockMode.None; } else { // Id降順 if (lockScroll) return ScrollLockMode.FixedToItem; // 最上行が表示されていたら、制御しない。最上行が表示されていなかったら、現在表示位置へ強制スクロール var topItem = this.listView.TopItem; if (topItem == null || topItem.Index == 0) return ScrollLockMode.FixedToTop; else return ScrollLockMode.FixedToItem; } } else { return ScrollLockMode.FixedToItem; } } /// /// の選択状態を として返します /// private ListViewSelection SaveListViewSelection() { if (this.listView.VirtualListSize == 0) { return new ListViewSelection { SelectedStatusIds = Array.Empty(), SelectionMarkStatusId = null, FocusedStatusId = null, }; } return new ListViewSelection { SelectedStatusIds = this.tab.SelectedStatusIds, FocusedStatusId = this.GetFocusedStatusId(), SelectionMarkStatusId = this.GetSelectionMarkStatusId(), }; } private PostId? GetFocusedStatusId() { var index = this.listView.FocusedItem?.Index ?? -1; return index != -1 && index < this.tab.AllCount ? this.tab.GetStatusIdAt(index) : null; } private PostId? GetSelectionMarkStatusId() { var index = this.listView.SelectionMark; return index != -1 && index < this.tab.AllCount ? this.tab.GetStatusIdAt(index) : null; } /// /// によって保存されたスクロール位置を復元します /// private void RestoreListViewScroll(ListViewScroll listScroll, bool forceScroll) { if (this.listView.VirtualListSize == 0) return; var scrollLockMode = listScroll.ScrollLockMode; if (forceScroll && scrollLockMode == ScrollLockMode.None) scrollLockMode = ScrollLockMode.FixedToItem; switch (scrollLockMode) { case ScrollLockMode.FixedToTop: this.listView.EnsureVisible(0); break; case ScrollLockMode.FixedToBottom: this.listView.EnsureVisible(this.listView.VirtualListSize - 1); break; case ScrollLockMode.FixedToItem: var topIndex = listScroll.TopItemStatusId != null ? this.tab.IndexOf(listScroll.TopItemStatusId) : -1; if (topIndex != -1) { var topItem = this.listView.Items[topIndex]; try { this.listView.TopItem = topItem; } catch (NullReferenceException) { this.listView.EnsureVisible(this.listView.VirtualListSize - 1); this.listView.EnsureVisible(topIndex); } } break; case ScrollLockMode.None: default: break; } } /// /// によって保存された選択状態を復元します /// private void RestoreListViewSelection(ListViewSelection listSelection) { var invalidateBounds = (Rectangle?)null; var previousFocusedItem = this.listView.FocusedItem; if (previousFocusedItem != null) invalidateBounds = previousFocusedItem.Bounds; // status_id から ListView 上のインデックスに変換 if (listSelection.SelectedStatusIds != null) { var selectedIndices = this.tab.IndexOf(listSelection.SelectedStatusIds).Where(x => x != -1).ToArray(); this.listView.SelectItems(selectedIndices); } if (listSelection.FocusedStatusId != null) { var focusedIndex = this.tab.IndexOf(listSelection.FocusedStatusId); if (focusedIndex != -1) this.listView.Items[focusedIndex].Focused = true; } if (listSelection.SelectionMarkStatusId != null) { var selectionMarkIndex = this.tab.IndexOf(listSelection.SelectionMarkStatusId); if (selectionMarkIndex != -1) this.listView.SelectionMark = selectionMarkIndex; } if (invalidateBounds != null) this.listView.Invalidate(invalidateBounds.Value); } } }