#region Disclaimer / License // Copyright (C) 2011, Jackie Ng // http://trac.osgeo.org/mapguide/wiki/maestro, jumpinjackie@gmail.com // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // #endregion using System; using System.Collections.Generic; using System.ComponentModel; using System.Text; using OSGeo.MapGuide.MaestroAPI.Exceptions; using System.Collections; namespace OSGeo.MapGuide.MaestroAPI.Mapping { /// /// A generic key/value collection /// /// The type of the key. /// The type of the value. public abstract class KeyValueCollection : IList, IList where TVal : class { /// /// The internal list of value /// protected List _values; /// /// The internal dictionary of values keyed by its key /// protected Dictionary _valuesByKey; /// /// Initializes a new instance of the class. /// protected KeyValueCollection() { _values = new List(); _valuesByKey = new Dictionary(); } /// /// Determines the index of a specific item in the . /// /// The object to locate in the . /// /// The index of if found in the list; otherwise, -1. /// public int IndexOf(TVal item) { return _values.IndexOf(item); } /// /// Determines the index of a specific key in the collection /// /// The key of the object to locate in the collection /// /// The index of if found in the list; otherwise, -1. /// public int IndexOf(TKey key) { var item = this[key]; return IndexOf(item); } /// /// Inserts an item to the at the specified index. /// /// The zero-based index at which should be inserted. /// The object to insert into the . /// /// is not a valid index in the . /// /// /// /// The is read-only. /// public virtual void Insert(int index, TVal item) { var key = SelectKey(item); if (_valuesByKey.ContainsKey(key)) throw new DuplicateKeyException(string.Format(Strings.DuplicateKeyExceptionMessage, key)); OnBeforeItemAdded(item); _values.Insert(index, item); _valuesByKey.Add(key, item); OnItemAdded(item); OnCollectionChanged(); } /// /// Removes the item at the specified index. /// /// The zero-based index of the item to remove. /// /// is not a valid index in the . /// /// /// /// The is read-only. /// public void RemoveAt(int index) { var item = this[index]; OnBeforeItemRemove(item); _values.RemoveAt(index); if (item != null) { var key = SelectKey(item); _valuesByKey.Remove(key); OnItemRemoved(item); OnCollectionChanged(); } } /// /// Gets or sets the element at the specified index. /// /// /// The element at the specified index. /// /// /// /// is not a valid index in the . /// /// /// /// The property is set and the is read-only. /// public virtual TVal this[int index] { get { return _values[index]; } set { if (_values[index] != null) RemoveAt(index); _values[index] = value; OnCollectionChanged(); } } /// /// Sets the new index of the specified object. This object must already be present inside the collection /// /// /// public void SetNewIndex(int newIndex, TVal item) { int idx = this.IndexOf(item); if (idx >= 0) { bool bSuccess = false; try { bSuppressCollectionChanged = true; this.RemoveAt(idx); this.Insert(newIndex, item); bSuccess = true; } finally { bSuppressCollectionChanged = false; if (bSuccess) OnCollectionChanged(); } } } /// /// Adds an item to the . /// /// The object to add to the . /// /// The is read-only. /// public virtual void Add(TVal item) { var key = SelectKey(item); if (_valuesByKey.ContainsKey(key)) throw new DuplicateKeyException(string.Format(Strings.DuplicateKeyExceptionMessage, key)); OnBeforeItemAdded(item); _values.Add(item); _valuesByKey.Add(key, item); OnItemAdded(item); OnCollectionChanged(); } /// /// Removes all items from the . /// /// /// The is read-only. /// public virtual void Clear() { bool hasRemovedAnItem = false; try { bSuppressCollectionChanged = true; //We don't call Clear() directly because we need to propagate removal of each //item back to the map var items = new List(this); foreach (var item in items) { Remove(item); hasRemovedAnItem = true; } //This shouldn't happen if (_values.Count > 0) { System.Diagnostics.Trace.TraceWarning("Expected empty values collection!"); //NOXLATE _values.Clear(); } if (_valuesByKey.Count > 0) { System.Diagnostics.Trace.TraceWarning("Expected empty values collection!"); //NOXLATE _valuesByKey.Clear(); } } finally { bSuppressCollectionChanged = false; if (hasRemovedAnItem) OnCollectionChanged(); } } /// /// Determines whether the contains a specific value. /// /// The object to locate in the . /// /// true if is found in the ; otherwise, false. /// public bool Contains(TVal item) { return _values.Contains(item); } /// /// Copies the elements of the to an , starting at a particular index. /// /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. /// The zero-based index in at which copying begins. /// /// is null. /// /// /// /// is less than 0. /// /// /// /// is multidimensional. /// -or- /// is equal to or greater than the length of . /// -or- /// The number of elements in the source is greater than the available space from to the end of the destination . /// -or- /// Type cannot be cast automatically to the type of the destination . /// public void CopyTo(TVal[] array, int arrayIndex) { _values.CopyTo(array, arrayIndex); } /// /// Gets the number of elements contained in the . /// /// /// The number of elements contained in the . /// public int Count { get { return _values.Count; } } /// /// Gets a value indicating whether the is read-only. /// /// true if the is read-only; otherwise, false. /// public bool IsReadOnly { get { return false; } } /// /// Removes the first occurrence of a specific object from the . /// /// The object to remove from the . /// /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . /// /// /// The is read-only. /// public bool Remove(TVal item) { OnBeforeItemRemove(item); var ret = _values.Remove(item); if (ret) { var key = SelectKey(item); _valuesByKey.Remove(key); OnItemRemoved(item); OnCollectionChanged(); return ret; } return ret; } /// /// Returns an enumerator that iterates through the collection. /// /// /// A that can be used to iterate through the collection. /// public IEnumerator GetEnumerator() { return _values.GetEnumerator(); } /// /// Returns an enumerator that iterates through a collection. /// /// /// An object that can be used to iterate through the collection. /// System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return _values.GetEnumerator(); } /// /// Gets or sets the element at the specified index. /// /// /// The element at the specified index. /// /// /// /// is not a valid index in the . /// /// /// /// The property is set and the is read-only. /// public TVal this[TKey key] { get { return _valuesByKey.ContainsKey(key) ? _valuesByKey[key] : null; } set { _valuesByKey[key] = value; OnCollectionChanged(); } } /// /// Raised when the collection has been modified /// public event EventHandler CollectionChanged; private bool bSuppressCollectionChanged = false; /// /// Raises the event /// protected virtual void OnCollectionChanged() { if (bSuppressCollectionChanged) return; var h = this.CollectionChanged; if (h != null) h(this, EventArgs.Empty); } /// /// Called before an item is added /// /// The item. protected virtual void OnBeforeItemAdded(TVal item) { } /// /// Called before an item is removed /// /// The item. protected virtual void OnBeforeItemRemove(TVal item) { } /// /// Called after an item has been added /// /// The item. protected abstract void OnItemAdded(TVal item); /// /// Called after an item has been removed. Note this is only called if the remove /// operation removed the item in question /// /// The value. protected abstract void OnItemRemoved(TVal value); /// /// Selects the key given the value. /// /// The value. /// protected abstract TKey SelectKey(TVal value); int IList.Add(object value) { this.Add((TVal)value); return this.Count - 1; } void IList.Clear() { this.Clear(); } bool IList.Contains(object value) { return this.Contains((TVal)value); } int IList.IndexOf(object value) { return this.IndexOf((TVal)value); } void IList.Insert(int index, object value) { this.Insert(index, (TVal)value); } bool IList.IsFixedSize { get { return false; } } bool IList.IsReadOnly { get { return false; } } void IList.Remove(object value) { this.Remove((TVal)value); } void IList.RemoveAt(int index) { this.RemoveAt(index); } object IList.this[int index] { get { return this[index]; } set { this[index] = (TVal)value; } } void ICollection.CopyTo(Array array, int index) { this.CopyTo((TVal[])array, index); } int ICollection.Count { get { return this.Count; } } bool ICollection.IsSynchronized { get { return false; } } private readonly object _syncRoot = new object(); object ICollection.SyncRoot { get { return _syncRoot; } } } /// /// A collection of runtime map layers /// public class RuntimeMapLayerCollection : KeyValueCollection { private RuntimeMap _parent; private Dictionary _layerIdMap; internal RuntimeMapLayerCollection(RuntimeMap parent) { _parent = parent; _layerIdMap = new Dictionary(); } /// /// Adds the specified layer. /// /// The layer. public override void Add(RuntimeMapLayer layer) { //calculate and set the zorder for the new layer RuntimeMapLayer prevLayer = (this.Count == 0) ? null : this[this.Count - 1]; double zOrder = prevLayer == null ? RuntimeMap.Z_ORDER_TOP : prevLayer.DisplayOrder + RuntimeMap.Z_ORDER_INCREMENT; layer.DisplayOrder = zOrder; base.Add(layer); } /// /// Inserts an item to the at the specified index. /// /// The zero-based index at which should be inserted. /// The object to insert into the . /// /// is not a valid index in the . /// /// /// /// The is read-only. /// public override void Insert(int index, RuntimeMapLayer item) { CalculateDisplayOrder(index, item); base.Insert(index, item); } /// /// Called when [before item added]. /// /// The layer. protected override void OnBeforeItemAdded(RuntimeMapLayer layer) { if (_layerIdMap.ContainsKey(layer.ObjectId)) throw new DuplicateKeyException(string.Format(Strings.DuplicateKeyExceptionMessage, layer.ObjectId)); } /// /// Called when [item added]. /// /// The layer. protected override void OnItemAdded(RuntimeMapLayer layer) { _layerIdMap[layer.ObjectId] = layer; _parent.OnLayerAdded(layer); } /// /// Called when [item removed]. /// /// The layer. protected override void OnItemRemoved(RuntimeMapLayer layer) { if (_layerIdMap.ContainsKey(layer.ObjectId)) _layerIdMap.Remove(layer.ObjectId); _parent.OnLayerRemoved(layer); } /// /// Selects the key given the value. /// /// The value. /// protected override string SelectKey(RuntimeMapLayer value) { return value.Name; } /// /// Gets a runtime map layer by its object id. /// /// The object id. /// public RuntimeMapLayer GetByObjectId(string id) { return _layerIdMap.ContainsKey(id) ? _layerIdMap[id] : null; } /// /// Gets or sets the element at the specified index. /// /// /// The element at the specified index. /// /// /// /// is not a valid index in the . /// /// /// /// The property is set and the is read-only. /// public override RuntimeMapLayer this[int index] { get { return base[index]; } set { CalculateDisplayOrder(index, value); base[index] = value; } } /// /// Removes the specified layer by its name. /// /// The name. public void Remove(string name) { var layer = this[name]; if (layer != null) Remove(layer); } private void CalculateDisplayOrder(int index, RuntimeMapLayer value) { //calculate zorder for the new layer double zOrderLow, zOrderHigh; RuntimeMapLayer layer; if (index == 0) { zOrderLow = 0; layer = base.Count > 0 ? base[index] : null; if (layer != null) zOrderHigh = layer.DisplayOrder; else zOrderHigh = 2.0 * RuntimeMap.Z_ORDER_INCREMENT; } else { layer = base[index - 1]; zOrderLow = layer.DisplayOrder; layer = base.Count > index ? base[index] : null; zOrderHigh = layer != null ? layer.DisplayOrder : zOrderLow + 2.0 * RuntimeMap.Z_ORDER_INCREMENT; } value.DisplayOrder = (zOrderLow + (zOrderHigh - zOrderLow) / 2.0); } } /// /// A collection of runtime map groups /// public class RuntimeMapGroupCollection : KeyValueCollection { private RuntimeMap _parent; internal RuntimeMapGroupCollection(RuntimeMap parent) { _parent = parent; } /// /// Called after an item has been added /// /// The item. protected override void OnItemAdded(RuntimeMapGroup item) { _parent.OnGroupAdded(item); } /// /// Called after an item has been removed. Note this is only called if the remove /// operation removed the item in question /// /// The value. protected override void OnItemRemoved(RuntimeMapGroup value) { _parent.OnGroupRemoved(value); } /// /// Selects the key given the value. /// /// The value. /// protected override string SelectKey(RuntimeMapGroup value) { return value.Name; } /// /// Removes the specified group by its name. /// /// Name of the group. public void Remove(string groupName) { var group = this[groupName]; if (group != null) Remove(group); } } }