/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy 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.
dnSpy 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 dnSpy. If not, see .
*/
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Windows.Input;
using dnSpy.Contracts.MVVM;
namespace dnSpy.AsmEditor.Commands {
class IndexObservableCollection : ObservableCollection where T : class, IIndexedItem {
public ICommand AddItemBeforeCommand => new RelayCommand(a => AddItemBefore((T[])a!), a => AddItemBeforeCanExecute((T[])a!));
public ICommand AddItemAfterCommand => new RelayCommand(a => AddItemAfter((T[])a!), a => AddItemAfterCanExecute((T[])a!));
public ICommand AppendItemCommand => new RelayCommand(a => AppendItem((T[])a!), a => AppendItemCanExecute((T[])a!));
public ICommand ItemMoveUpCommand => new RelayCommand(a => ItemMoveUp((T[])a!), a => ItemMoveUpCanExecute((T[])a!));
public ICommand ItemMoveDownCommand => new RelayCommand(a => ItemMoveDown((T[])a!), a => ItemMoveDownCanExecute((T[])a!));
public ICommand RemoveItemCommand => new RelayCommand(a => RemoveItem((T[])a!), a => RemoveItemCanExecute((T[])a!));
public ICommand RemoveAllItemsCommand => new RelayCommand(a => RemoveAllItems((T[])a!), a => RemoveAllItemsCanExecute((T[])a!));
public bool DisableAutoUpdateProps { get; set; }
public Action? UpdateIndexesDelegate { get; set; }
public bool CanCreateNewItems => createNewItem is not null;
public bool CanRemoveItems => true;
public bool CanMoveItems => true;
readonly Func? createNewItem;
public IndexObservableCollection()
: this(null) {
}
public IndexObservableCollection(Func? createNewItem) => this.createNewItem = createNewItem;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) {
if (!DisableAutoUpdateProps) {
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
UpdateIndexes(e.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Replace:
UpdateIndexes(e.OldStartingIndex);
break;
case NotifyCollectionChangedAction.Move:
UpdateIndexes(Math.Min(e.NewStartingIndex, e.OldStartingIndex));
break;
case NotifyCollectionChangedAction.Reset:
UpdateIndexes(0);
break;
default:
throw new InvalidOperationException();
}
}
base.OnCollectionChanged(e);
}
protected override void ClearItems() {
// Must do this before calling base method since some listeners
// validate that they don't reference removed items.
foreach (var item in Items)
item.Index = -1;
base.ClearItems();
}
protected override void RemoveItem(int index) {
// Must do this before calling base method since some listeners
// validate that they don't reference removed items.
Items[index].Index = -1;
base.RemoveItem(index);
}
public void UpdateIndexes(int index) {
if (UpdateIndexesDelegate is not null)
UpdateIndexesDelegate(index);
else
DefaultUpdateIndexes(index);
}
public void DefaultUpdateIndexes(int index) {
for (; index < Count; index++)
this[index].Index = index;
}
void AddNewItem(T[] items, int indexDisp) {
if (items.Length == 0)
return;
int index = IndexOf(items[0]);
Debug.Assert(index >= 0);
if (index < 0)
throw new InvalidOperationException();
AddNewItem(index + indexDisp);
}
void AddNewItem(int index) {
if (createNewItem is null)
throw new InvalidOperationException();
Insert(index, createNewItem());
}
void AddItemBefore(T[] items) => AddNewItem(items, 0);
bool AddItemBeforeCanExecute(T[] items) => CanCreateNewItems && items.Length == 1;
void AddItemAfter(T[] items) => AddNewItem(items, 1);
bool AddItemAfterCanExecute(T[] items) => CanCreateNewItems && items.Length == 1;
void AppendItem(T[] items) => AddNewItem(Count);
bool AppendItemCanExecute(T[] items) => CanCreateNewItems;
void ItemMoveUp(T[] items) {
if (items.Length == 0)
return;
Array.Sort(items, (a, b) => a.Index.CompareTo(b.Index));
var old = DisableAutoUpdateProps;
try {
DisableAutoUpdateProps = true;
int index = items[0].Index - 1;
if (index < 0)
index = 0;
foreach (var instr in items) {
if (index != instr.Index)
Move(instr.Index, index);
index++;
}
}
finally {
DisableAutoUpdateProps = old;
}
UpdateIndexes(0);
}
bool ItemMoveUpCanExecute(T[] items) => CanMoveItems && items.Length > 0;
void ItemMoveDown(T[] items) {
if (items.Length == 0)
return;
Array.Sort(items, (a, b) => a.Index.CompareTo(b.Index));
var old = DisableAutoUpdateProps;
try {
DisableAutoUpdateProps = true;
int index = items[items.Length - 1].Index + 1;
if (index >= Count)
index = Count - 1;
for (int i = 0; i < items.Length; i++) {
var item = items[i];
int currIndex = item.Index - i;
Debug.Assert(currIndex >= 0 && this[currIndex] == item);
if (index != currIndex)
Move(currIndex, index);
}
}
finally {
DisableAutoUpdateProps = old;
}
UpdateIndexes(0);
}
bool ItemMoveDownCanExecute(T[] items) => CanMoveItems && items.Length > 0;
void RemoveItem(T[] items) {
Array.Sort(items, (a, b) => b.Index.CompareTo(a.Index));
var old = DisableAutoUpdateProps;
try {
DisableAutoUpdateProps = true;
foreach (var item in items) {
bool b = item.Index < Count && item == this[item.Index];
Debug.Assert(b);
if (!b)
throw new InvalidOperationException();
RemoveAt(item.Index);
}
}
finally {
DisableAutoUpdateProps = old;
}
UpdateIndexes(0);
}
bool RemoveItemCanExecute(T[] items) => CanRemoveItems && items.Length > 0;
void RemoveAllItems(T[] items) => Clear();
bool RemoveAllItemsCanExecute(T[] items) => CanRemoveItems && Count > 0;
}
}