/* SettingsViewModel.cs
* part of zaaReloaded2
*
* Copyright 2015 Daniel Kraus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Bovender.Mvvm;
using Bovender.Mvvm.ViewModels;
using Bovender.Mvvm.Messaging;
using zaaReloaded2.Controller;
using zaaReloaded2.Controller.Elements;
using zaaReloaded2.Formatter;
using System.Collections.ObjectModel;
namespace zaaReloaded2.ViewModels
{
///
/// View model for the zaaReloaded2.Controller.Settings class.
///
public class SettingsViewModel : ViewModelBase, ICloneable
{
#region Properties
///
/// Gets or sets the name of the Settings
///
public string Name
{
[DebuggerStepThrough]
get
{
return _settings.Name;
}
[DebuggerStepThrough]
set
{
_settings.Name = value;
OnPropertyChanged("Name");
}
}
///
/// Is true if the settings' name is editable.
/// If the settings' name is a default, preconfigured name,
/// this will be false.
///
public bool IsNameEnabled
{
get
{
return (Name != Properties.Settings.Default.SettingsNameClinic) &&
(Name != Properties.Settings.Default.SettingsNameWard);
}
}
///
/// Gets a list of element view models.
///
public IList Elements { get; private set; }
///
/// Gets or sets the currently selected element.
///
///
/// Due to the way the WPF ListBox (for example) is implemented, selecting
/// a list item will trigger an PropertyChanged event twice: Once for the
/// item being selected, and once for the item being deselected. Thus we
/// can only capture the last item that actually was selected. We cannot
/// howeve capture if an item was deselected without a new selection,
/// because we cannot logicaly connect two occurrences of the same event
/// from different objects.
///
public ElementViewModel LastSelectedElement
{
get { return _selectedElement; }
set
{
_selectedElement = value;
OnPropertyChanged("SelectedElement");
}
}
///
/// Returns an EnumProvider object for the ReferenceStyle
///
public EnumProvider ReferenceStyle
{
get
{
if (_referenceStyle == null)
{
_referenceStyle = new EnumProvider();
_referenceStyle.AsEnum = _settings.ReferenceStyle;
_referenceStyle.PropertyChanged += (sender, args) =>
{
_settings.ReferenceStyle = _referenceStyle.AsEnum;
};
}
return _referenceStyle;
}
}
#endregion
#region Constructors
public SettingsViewModel()
: this(new Settings())
{ }
public SettingsViewModel(Settings settings)
: base()
{
_settings = settings;
Elements = new ObservableCollection();
foreach (ElementBase element in settings.Elements)
{
ElementViewModel vm;
if (element is FormatElementBase)
{
vm = new FormatElementViewModel(element as FormatElementBase);
}
else if (element is ControlElementBase)
{
vm = new ControlElementViewModel(element as ControlElementBase);
foreach (FormatElementViewModel childVM in ((ControlElementViewModel)vm).Elements)
{
childVM.Parent = vm as ControlElementViewModel;
childVM.PropertyChanged += ElementViewModel_PropertyChanged;
}
}
else
{
throw new InvalidOperationException(
"Cannot create ViewModel for " + element.GetType().ToString());
}
vm.PropertyChanged += ElementViewModel_PropertyChanged;
Elements.Add(vm);
}
}
#endregion
#region Messages
public Message AddElementMessage
{
get
{
if (_addElementMessage == null)
{
_addElementMessage = new Message();
}
return _addElementMessage;
}
}
public Message AddChildElementMessage
{
get
{
if (_addChildElementMessage == null)
{
_addChildElementMessage = new Message();
}
return _addChildElementMessage;
}
}
public Message EditElementMessage
{
get
{
if (_editElementMessage == null)
{
_editElementMessage = new Message();
}
return _editElementMessage;
}
}
public Message ChangeControlElementMessage
{
get
{
if (_changeControlElementMessage == null)
{
_changeControlElementMessage = new Message();
}
return _changeControlElementMessage;
}
}
#endregion
#region Commands
public DelegatingCommand AddElementCommand
{
get
{
if (_addElementCommand == null)
{
_addElementCommand = new DelegatingCommand(
param => DoAddElement());
}
return _addElementCommand;
}
}
public DelegatingCommand AddChildElementCommand
{
get
{
if (_addChildElementCommand == null)
{
_addChildElementCommand = new DelegatingCommand(
param => DoAddChildElement(),
param => CanAddChildElement());
}
return _addChildElementCommand;
}
}
public DelegatingCommand EditElementCommand
{
get
{
if (_editElementCommand == null)
{
_editElementCommand = new DelegatingCommand(
param => DoEditElement(),
param => CanEditElement());
}
return _editElementCommand;
}
}
public DelegatingCommand DeleteElementCommand
{
get
{
if (_deleteElementCommand == null)
{
_deleteElementCommand = new DelegatingCommand(
param => DoDeleteElement(),
param => CanDeleteElement());
}
return _deleteElementCommand;
}
}
public DelegatingCommand CopyElementCommand
{
get
{
if (_copyElementCommand == null)
{
_copyElementCommand = new DelegatingCommand(
param => DoCopyElement(),
param => CanCopyElement());
}
return _copyElementCommand;
}
}
public DelegatingCommand MoveElementUpCommand
{
get
{
if (_moveElementUpCommand == null)
{
_moveElementUpCommand = new DelegatingCommand(
param => DoMoveElementUp(),
param => CanMoveElementUp());
}
return _moveElementUpCommand;
}
}
public DelegatingCommand MoveElementDownCommand
{
get
{
if (_moveElementDownCommand == null)
{
_moveElementDownCommand = new DelegatingCommand(
param => DoMoveElementDown(),
param => CanMoveElementDown());
}
return _moveElementDownCommand;
}
}
#endregion
#region Public methods
///
/// Wires the OnProperty changed event of an ElementViewModel's
/// wrapped model and adds the view model to the Elements collection.
///
public void AddElementViewModel(ElementViewModel elementViewModel)
{
elementViewModel.PropertyChanged += ElementViewModel_PropertyChanged;
Elements.Add(elementViewModel);
_settings.Elements.Add(elementViewModel.RevealModelObject() as ElementBase);
}
///
/// Wires the OnProperty changed event of an ElementViewModel's
/// wrapped model and adds the view model as a child of another
/// view model.
///
public void AddChildElementViewModel(ControlElementViewModel parent, FormatElementViewModel child)
{
child.PropertyChanged += ElementViewModel_PropertyChanged;
parent.AddChildElement(child);
}
#endregion
#region Private methods
void DoAddElement()
{
// Create a new element picker; it will automatically create and
// send us a new element view model if one is chosen by the view.
ElementPickerViewModel picker = new ElementPickerViewModel(
allowControlElements: IsTopLevelElement());
picker.ElementChosenMessage.Sent += (sender, args) =>
{
ElementViewModel newVM = args.Content.ViewModel as ElementViewModel;
if (LastSelectedElement == null || IsTopLevelElement())
{
AddElementViewModel(newVM);
}
else
{
// If the selected element is on the second level, it
// must be a FormatElementViewModel.
ControlElementViewModel parent = ((FormatElementViewModel)LastSelectedElement).Parent;
AddChildElementViewModel(parent, newVM as FormatElementViewModel);
}
newVM.IsSelected = true;
if (newVM is FormatElementViewModel) DoEditElement();
};
AddElementMessage.Send(new ViewModelMessageContent(picker));
}
void DoAddChildElement()
{
if (CanAddChildElement())
{
// Create a new element picker; it will automatically create and
// send us a new element view model if one is chosen by the view.
ElementPickerViewModel picker = new ElementPickerViewModel(false);
picker.ElementChosenMessage.Sent += (sender, args) =>
{
FormatElementViewModel newVM = args.Content.ViewModel as FormatElementViewModel;
AddChildElementViewModel(LastSelectedElement as ControlElementViewModel, newVM);
newVM.IsSelected = true;
DoEditElement();
};
AddChildElementMessage.Send(new ViewModelMessageContent(picker));
}
}
bool CanAddChildElement()
{
return LastSelectedElement is ControlElementViewModel &&
((ControlElementViewModel)LastSelectedElement).CanHaveChildren;
}
void DoEditElement()
{
if (CanEditElement())
{
if (LastSelectedElement is ControlElementViewModel)
{
ElementPickerViewModel picker = new ElementPickerViewModel(
LastSelectedElement as ControlElementViewModel);
picker.ElementChosenMessage.Sent += (sender, args) =>
{
// Replace the previously selected element with the new
// one that we get from the ElementPickerViewModel.
int index = Elements.IndexOf(LastSelectedElement);
ElementViewModel newVM = args.Content.ViewModel as ElementViewModel;
ControlElementBase oldModel = LastSelectedElement.RevealModelObject() as ControlElementBase;
ControlElementBase newModel = newVM.RevealModelObject() as ControlElementBase;
// Caveat: once we modify the Elements collection, LastSelectedElement will change!
Elements.RemoveAt(index);
Elements.Insert(index, newVM);
newModel.Children = oldModel.Children;
_settings.Elements.RemoveAt(index);
_settings.Elements.Insert(index, newModel);
newVM.PropertyChanged += ElementViewModel_PropertyChanged;
newVM.IsSelected = true;
};
ChangeControlElementMessage.Send(
new ViewModelMessageContent(picker));
}
else
{
EditElementMessage.Send(new ViewModelMessageContent(LastSelectedElement));
}
}
}
bool CanEditElement()
{
return LastSelectedElement != null && LastSelectedElement.IsSelected;
}
///
/// Deletes the selected element.
///
///
/// The following algorithm ist used to find out whether the selected
/// element is at the first or the second level of the hierarchy:
/// If the Element is a ControlElement, it must be at the first level.
/// If the Element is a FormatElement, its Parent property will be
/// Null if the Element is at the first level.
///
void DoDeleteElement()
{
if (CanDeleteElement())
{
if (IsTopLevelElement())
{
// First level of the hierarchy
int index = Elements.IndexOf(LastSelectedElement);
Elements.RemoveAt(index);
_settings.Elements.RemoveAt(index);
if (index == Elements.Count) index--;
LastSelectedElement = null;
if (Elements.Count > 0) Elements[index].IsSelected = true;
}
else
{
// Second level of the hierarchy
FormatElementViewModel formatVM = LastSelectedElement as FormatElementViewModel;
ControlElementViewModel parent = formatVM.Parent;
int index = parent.Elements.IndexOf(formatVM);
parent.RemoveChildElement(formatVM);
if (index == parent.Elements.Count) index--;
LastSelectedElement = null;
if (parent.Elements.Count > 0) parent.Elements[index].IsSelected = true;
}
}
}
bool CanDeleteElement() { return LastSelectedElement != null && LastSelectedElement.IsSelected; }
void DoCopyElement()
{
if (CanCopyElement())
{
if (IsTopLevelElement())
{
ElementViewModel newControlVM = LastSelectedElement.Clone() as ElementViewModel;
AddElementViewModel(newControlVM);
newControlVM.IsSelected = true;
}
else
{
FormatElementViewModel originalVM = LastSelectedElement as FormatElementViewModel;
FormatElementViewModel newFormatVM = originalVM.Clone() as FormatElementViewModel;
originalVM.Parent.AddChildElement(newFormatVM);
newFormatVM.IsSelected = true;
}
}
}
bool CanCopyElement() { return LastSelectedElement != null && LastSelectedElement.IsSelected; }
void DoMoveElementUp()
{
if (CanMoveElementUp())
{
// We need to get a hold of the LastSelectedElement because a TreeView
// might reset the selection when we move elements around.
ElementViewModel lastSelectedElement = LastSelectedElement;
// Top-level elements are either control elements or format elements;
// child elements on the second level however are always format elements
// and must be treated differently.
if (IsTopLevelElement())
{
int index = Elements.IndexOf(lastSelectedElement);
if (lastSelectedElement is ControlElementViewModel ||
Elements[index - 1] is FormatElementViewModel ||
!((ControlElementViewModel)Elements[index - 1]).CanHaveChildren
)
{
// Simple case: top-level control element -- just move it up;
// if the selected element is a format element and the element
// above it is a format element too, just move it up as well.
// If the element above the selected element is a control element,
// but cannot have children, move the selected element up as well.
Elements.RemoveAt(index);
Elements.Insert(index - 1, lastSelectedElement);
_settings.Elements.RemoveAt(index);
_settings.Elements.Insert(
index - 1,
lastSelectedElement.RevealModelObject() as ElementBase);
}
else
{
// If we get here, the selected element is a format element
// and the element above it is a control element that can
// have child elements, i.e. the selected element is demoted
// to a child element of the control element above it.
ControlElementViewModel controlElementAbove =
Elements[index - 1] as ControlElementViewModel;
Elements.RemoveAt(index);
controlElementAbove.IsExpanded = true;
controlElementAbove.AddChildElement(
lastSelectedElement as FormatElementViewModel);
FormatElementBase model = lastSelectedElement.RevealModelObject() as FormatElementBase;
ControlElementBase modelAbove = _settings.Elements[index - 1] as ControlElementBase;
_settings.Elements.RemoveAt(index);
}
}
else
{
// The selected element is a child element.
// If it is at the top of the child elements list, promote it
// to a top-level element; if not, just move it up in the
// child elements list.
FormatElementViewModel selected = lastSelectedElement as FormatElementViewModel;
int index = selected.Parent.Elements.IndexOf(selected);
if (index == 0)
{
// Promote the element from the top of the children list
// to a top-level element above its parent.
int parentIndex = Elements.IndexOf(selected.Parent);
selected.Parent.Elements.RemoveAt(0);
Elements.Insert(parentIndex, selected);
FormatElementBase model = selected.RevealModelObject() as FormatElementBase;
ControlElementBase parentModel = selected.Parent.RevealModelObject() as ControlElementBase;
parentModel.Children.RemoveAt(0);
_settings.Elements.Insert(parentIndex, model);
selected.Parent = null;
}
else
{
selected.Parent.Elements.Move(index, index - 1);
ControlElementBase parentModel = selected.Parent.RevealModelObject() as ControlElementBase;
FormatElementBase selectedModel = parentModel.Children[index];
parentModel.Children.RemoveAt(index);
parentModel.Children.Insert(index - 1, selectedModel);
}
}
// Select the last selected element again.
lastSelectedElement.IsSelected = true;
}
}
bool CanMoveElementUp()
{
if (IsTopLevelElement())
{
return Elements.IndexOf(LastSelectedElement) > 0;
}
else
{
// If the selected element is a child element, it can always be moved
// up before the parent element.
return LastSelectedElement != null && LastSelectedElement.IsSelected;
}
}
void DoMoveElementDown()
{
if (CanMoveElementDown())
{
// We need to get a hold of the LastSelectedElement because a TreeView
// might reset the selection when we move elements around.
ElementViewModel lastSelectedElement = LastSelectedElement;
// Top-level elements are either control elements or format elements;
// child elements on the second level however are always format elements
// and must be treated differently.
if (IsTopLevelElement())
{
int index = Elements.IndexOf(lastSelectedElement);
if (lastSelectedElement is ControlElementViewModel ||
Elements[index + 1 ] is FormatElementViewModel ||
!((ControlElementViewModel)Elements[index + 1]).CanHaveChildren
)
{
// Simple case: top-level control element -- just move it down;
// if the selected element is a format element and the element
// below it is a format element too, just move it down as well.
// If the element below the selected element is a control element,
// but cannot have children, move the selected element down as well.
Elements.RemoveAt(index);
Elements.Insert(index + 1, lastSelectedElement);
_settings.Elements.RemoveAt(index);
_settings.Elements.Insert(
index + 1,
lastSelectedElement.RevealModelObject() as ElementBase);
}
else
{
// If we get here, the selected element is a format element
// and the element below it is a control element that can
// have child elements, i.e. the selected element is demoted
// to a child element of the control element below it.
ControlElementViewModel controlElementBelow =
Elements[index + 1] as ControlElementViewModel;
Elements.RemoveAt(index);
controlElementBelow.IsExpanded = true;
controlElementBelow.Elements.Insert(
0,
lastSelectedElement as FormatElementViewModel);
((FormatElementViewModel)lastSelectedElement).Parent = controlElementBelow;
FormatElementBase model = lastSelectedElement.RevealModelObject() as FormatElementBase;
ControlElementBase modelBelow = _settings.Elements[index + 1] as ControlElementBase;
_settings.Elements.RemoveAt(index);
modelBelow.Children.Insert(0, model);
}
}
else
{
// The selected element is a child element.
// If it is at the bottom of the child elements list, promote it
// to a top-level element; if not, just move it down in the
// child elements list.
FormatElementViewModel selected = lastSelectedElement as FormatElementViewModel;
int index = selected.Parent.Elements.IndexOf(selected);
if (index == selected.Parent.Elements.Count - 1)
{
// Promote the element from the bottom of the children list
// to a top-level element below its parent.
int parentIndex = Elements.IndexOf(selected.Parent);
selected.Parent.Elements.RemoveAt(selected.Parent.Elements.Count - 1);
Elements.Insert(parentIndex + 1, selected);
FormatElementBase model = selected.RevealModelObject() as FormatElementBase;
ControlElementBase parentModel = selected.Parent.RevealModelObject() as ControlElementBase;
parentModel.Children.RemoveAt(parentModel.Children.Count - 1);
_settings.Elements.Insert(parentIndex + 1, model);
selected.Parent = null;
}
else
{
selected.Parent.Elements.Move(index, index + 1);
ControlElementBase parentModel = selected.Parent.RevealModelObject() as ControlElementBase;
FormatElementBase selectedModel = parentModel.Children[index];
parentModel.Children.RemoveAt(index);
parentModel.Children.Insert(index + 1, selectedModel);
}
}
// Select the last selected element again.
lastSelectedElement.IsSelected = true;
}
}
bool CanMoveElementDown()
{
if (IsTopLevelElement())
{
return Elements.IndexOf(LastSelectedElement) < Elements.Count - 1;
}
else
{
// If the selected element is a child element, it can always be moved
// down after the parent element.
return LastSelectedElement != null && LastSelectedElement.IsSelected;
}
}
///
/// Sets LastSelectedElement property whenever the IsSelected
/// property of an ElementViewModel changes
///
///
/// Please see the remarks on the LastSelectedElement property.
///
void ElementViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
ElementViewModel vm = sender as ElementViewModel;
if (vm != null && e.PropertyName == "IsSelected")
{
if (vm.IsSelected) LastSelectedElement = vm;
}
}
///
/// Returns true if the selected ElementViewModel is at the top
/// level of the hierarchy.
///
bool IsTopLevelElement()
{
return LastSelectedElement != null &&
(LastSelectedElement is ControlElementViewModel ||
((FormatElementViewModel)LastSelectedElement).Parent == null);
}
#endregion
#region Implementation of ViewModelBase
public override object RevealModelObject()
{
return _settings;
}
#endregion
#region Implementation of ICloneable
public object Clone()
{
return new SettingsViewModel(_settings.Clone() as Settings);
}
#endregion
#region Fields
Settings _settings;
DelegatingCommand _addElementCommand;
DelegatingCommand _addChildElementCommand;
DelegatingCommand _editElementCommand;
DelegatingCommand _deleteElementCommand;
DelegatingCommand _copyElementCommand;
DelegatingCommand _moveElementUpCommand;
DelegatingCommand _moveElementDownCommand;
Message _addElementMessage;
Message _addChildElementMessage;
Message _editElementMessage;
Message _changeControlElementMessage;
ElementViewModel _selectedElement;
EnumProvider _referenceStyle;
#endregion
}
}