/* 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 } }