#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2010 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 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
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Nuclex.Support.Collections;
namespace Nuclex.UserInterface.Controls {
/// Collection of GUI controls
///
/// This class is for internal use only. Do not expose it to the user. If it was
/// exposed, the user might decide to use it for storing his own controls, causing
/// exceptions because the collection tries to parent the controls which are already
/// belonging to another collection.
///
internal class ParentingControlCollection : Collection {
/// Initializes a new parenting control collection
/// Parent control to assign to all children
public ParentingControlCollection(Control parent) {
this.parent = parent;
}
/// Clears all elements from the collection
protected override void ClearItems() {
for(int index = 0; index < base.Count; ++index)
unassignParent(base[index]);
base.ClearItems();
}
/// Inserts a new element into the collection
/// Index at which to insert the element
/// Item to be inserted
protected override void InsertItem(int index, Control item) {
ensureIntegrity(item);
base.InsertItem(index, item);
assignParent(item);
}
/// Removes an element from the collection
/// Index of the element to remove
protected override void RemoveItem(int index) {
unassignParent(base[index]);
base.RemoveItem(index);
}
/// Takes over a new element that is directly assigned
/// Index of the element that was assigned
/// New item
protected override void SetItem(int index, Control item) {
ensureIntegrity(item);
unassignParent(base[index]);
assignParent(item);
}
/// Switches the control to a specific GUI
/// Screen that owns the control from now on
internal void SetScreen(Screen screen) {
this.screen = screen;
for(int index = 0; index < base.Count; ++index)
base[index].SetScreen(screen);
}
///
/// Checks whether the provided name is already taken by a control
///
/// Id that will be checked
/// True if the id is already taken, false otherwise
internal bool IsNameTaken(string name) {
// Empty names are an exception and will not be checked for duplicates.
if(name == null)
return false;
// Look for any controls with the provided name. This is a stupid sequential
// search, but given the typical number of controls in a Gui and the fact
// that this operation usually only happens once, there's no point in adding
// the overhead of managing a synchronized look-up dictionary here.
for(int index = 0; index < base.Count; ++index)
if(base[index].Name == name)
return true;
// If we reach this point, no control is using the specified name.
return false;
}
#if false
/// Moves the specified control to the end of the list
///
/// Index of the control that will be moved to the end of the list
///
internal void MoveToEnd(int controlIndex) {
Control control = base[controlIndex];
// We explicitely circumvent the additional logic for adding and removing items
// in this collection since we're only relocating an item. Removal and readdition
// have no risk of causing an exception in a normal collection, otherwise the
// rollback attempt would be futile anyway since it would mean to repeat exactly
// what has caused failed: adding an item ;)
base.RemoveAt(controlIndex);
base.Add(control);
}
#endif
/// Moves the specified control to the start of the list
///
/// Index of the control that will be moved to the start of the list
///
internal void MoveToStart(int controlIndex) {
Control control = base[controlIndex];
// We explicitely circumvent the additional logic for adding and removing items
// in this collection since we're only relocating an item. Removal and readdition
// have no risk of causing an exception in a normal collection, otherwise the
// rollback attempt would be futile anyway since it would mean to repeat exactly
// what has caused failed: adding an item ;)
base.RemoveAt(controlIndex);
base.Insert(0, control);
}
/// Ensures the integrity of the parent/child relationships
/// Control that is to become one of our childs
private void ensureIntegrity(Control proposedChild) {
// The item must not have a parent (otherwise, by being added to this collection,
// it would either be contained twice in the same collection or have two parents).
if(!ReferenceEquals(proposedChild.Parent, null))
throw new InvalidOperationException("Control already is the child of another control");
// The item must not become its own parent. I cannot imagine this ever happenning
// unless someone deliberately tried to crash the GUI library :)
if(ReferenceEquals(this.parent, proposedChild))
throw new InvalidOperationException("Attempt to instate control as its own parent");
// The item also must not be any of our parent's parents (and so on). Otherwise,
// a stack overflow is likely to occur.
if(isParent(proposedChild))
throw new InvalidOperationException(
"Attempt to instate one of the control's parents as its child"
);
// We also do not allow a child control to have the same id as an existing
// control (with the exception of an empty name)
if(IsNameTaken(proposedChild.Name))
throw new DuplicateNameException(
"The name of the added control has already been taken by another child"
);
}
///
/// Determines whether the provided control is a parent of this control.
///
/// Control to check for parentage
/// True if the control is one of our parents, otherwise false
///
/// This method takes into account all ancestors up to the tree's root.
///
private bool isParent(Control control) {
Control parent = this.parent;
while(parent != null) {
// Does one of our parents happen to be the control we are checking?
if(ReferenceEquals(parent, control))
return true;
// Walk upwards in the tree until we reach the top
parent = parent.Parent;
}
// The control is not one of our parents in the tree all the way up
// to the tree's root.
return false;
}
/// Gives up the parentage on the item provided
/// Item to be unparented
private void unassignParent(Control item) {
item.SetParent(null);
}
/// Sets up the parentage on the specified item
/// Item to be parented
private void assignParent(Control item) {
item.SetParent(this.parent);
}
/// GUI this control is currently assigned to. Can be null.
private Screen screen;
/// Parent control to assign to all controls in this collection.
private Control parent;
}
} // namespace Nuclex.UserInterface.Controls