diff --git a/AnnoDesigner.Core/Converters/FlagToBoolConverter.cs b/AnnoDesigner.Core/Converters/FlagToBoolConverter.cs
new file mode 100644
index 00000000..6b7cd1ef
--- /dev/null
+++ b/AnnoDesigner.Core/Converters/FlagToBoolConverter.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace AnnoDesigner.Core.Converters
+{
+ [ValueConversion(typeof(Enum), typeof(bool))]
+ public sealed class FlagToBoolConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is Enum e && parameter is Enum p)
+ {
+ return e.HasFlag(p);
+ }
+ return DependencyProperty.UnsetValue;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/AnnoDesigner/MainWindow.xaml b/AnnoDesigner/MainWindow.xaml
index 88e812f0..ae377c1e 100644
--- a/AnnoDesigner/MainWindow.xaml
+++ b/AnnoDesigner/MainWindow.xaml
@@ -37,6 +37,7 @@
+
@@ -334,52 +335,6 @@
-
-
@@ -639,6 +594,69 @@
Command="{Binding PlaceBuildingCommand}"
Height="23"
TabIndex="12" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AnnoDesigner/MainWindow.xaml.cs b/AnnoDesigner/MainWindow.xaml.cs
index c4aa0d47..2b274ee2 100644
--- a/AnnoDesigner/MainWindow.xaml.cs
+++ b/AnnoDesigner/MainWindow.xaml.cs
@@ -2,6 +2,7 @@
using System.ComponentModel;
using System.Configuration;
using System.Windows;
+using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
@@ -146,5 +147,15 @@ private void WindowClosing(object sender, CancelEventArgs e)
logger.Trace($"saving settings: \"{userConfig}\"");
#endif
}
+
+ private void BuildingSettings_ApplySettings_Checked(object sender, RoutedEventArgs e)
+ {
+ DataContext.BuildingSettingsViewModel.ApplySettings |= (ApplySettings)(sender as CheckBox).Tag;
+ }
+
+ private void BuildingSettings_ApplySettings_Unchecked(object sender, RoutedEventArgs e)
+ {
+ DataContext.BuildingSettingsViewModel.ApplySettings &= ~(ApplySettings)(sender as CheckBox).Tag;
+ }
}
}
\ No newline at end of file
diff --git a/AnnoDesigner/Models/LayoutObject.cs b/AnnoDesigner/Models/LayoutObject.cs
index d041c4db..061626a5 100644
--- a/AnnoDesigner/Models/LayoutObject.cs
+++ b/AnnoDesigner/Models/LayoutObject.cs
@@ -591,5 +591,11 @@ public Rect GridInfluenceRangeRect
return _gridInfluenceRangeRect;
}
}
+
+ public string Label { get => WrappedAnnoObject.Label; set => WrappedAnnoObject.Label = value; }
+ public double InfluenceRange { get => WrappedAnnoObject.InfluenceRange; set => WrappedAnnoObject.InfluenceRange = value; }
+ public double Radius { get => WrappedAnnoObject.Radius; set => WrappedAnnoObject.Radius = value; }
+ public bool Borderless { get => WrappedAnnoObject.Borderless; set => WrappedAnnoObject.Borderless = value; }
+ public bool Road { get => WrappedAnnoObject.Road; set => WrappedAnnoObject.Road = value; }
}
}
diff --git a/AnnoDesigner/Undo/Operations/CompositeOperation.cs b/AnnoDesigner/Undo/Operations/CompositeOperation.cs
index 19171a73..a94b1874 100644
--- a/AnnoDesigner/Undo/Operations/CompositeOperation.cs
+++ b/AnnoDesigner/Undo/Operations/CompositeOperation.cs
@@ -1,11 +1,63 @@
-using System.Collections.Generic;
+using System.Collections;
+using System.Collections.Generic;
using System.Linq;
namespace AnnoDesigner.Undo.Operations
{
- public class CompositeOperation : BaseOperation
+ public class CompositeOperation : BaseOperation, ICollection
{
- public ICollection Operations { get; set; } = new List();
+ private ICollection operations = new List();
+
+ public IEnumerable Operations => operations;
+
+ public int Count => operations.Count;
+
+ public bool IsReadOnly => operations.IsReadOnly;
+
+ public CompositeOperation()
+ {
+ operations = new List();
+ }
+
+ public CompositeOperation(IEnumerable ops)
+ {
+ operations = new List(ops);
+ }
+
+ public void Add(IOperation item)
+ {
+ operations.Add(item);
+ }
+
+ public void Clear()
+ {
+ operations.Clear();
+ }
+
+ public bool Contains(IOperation item)
+ {
+ return operations.Contains(item);
+ }
+
+ public void CopyTo(IOperation[] array, int arrayIndex)
+ {
+ operations.CopyTo(array, arrayIndex);
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return operations.GetEnumerator();
+ }
+
+ public bool Remove(IOperation item)
+ {
+ return operations.Remove(item);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)operations).GetEnumerator();
+ }
protected override void RedoOperation()
{
diff --git a/AnnoDesigner/Undo/UndoManager.cs b/AnnoDesigner/Undo/UndoManager.cs
index bf18bac5..934f2237 100644
--- a/AnnoDesigner/Undo/UndoManager.cs
+++ b/AnnoDesigner/Undo/UndoManager.cs
@@ -57,7 +57,7 @@ public void RegisterOperation(IOperation operation)
{
if (CurrentCompositeOperation != null)
{
- CurrentCompositeOperation.Operations.Add(operation);
+ CurrentCompositeOperation.Add(operation);
}
else
{
@@ -78,7 +78,7 @@ public void AsSingleUndoableOperation(Action action)
finally
{
CurrentCompositeOperation = null;
- if (operation != null && operation.Operations.Count > 0)
+ if (operation != null && operation.Count > 0)
{
RegisterOperation(operation);
}
diff --git a/AnnoDesigner/ViewModels/BuildingSettingsViewModel.cs b/AnnoDesigner/ViewModels/BuildingSettingsViewModel.cs
index 835fee23..df6856c6 100644
--- a/AnnoDesigner/ViewModels/BuildingSettingsViewModel.cs
+++ b/AnnoDesigner/ViewModels/BuildingSettingsViewModel.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
@@ -14,9 +15,55 @@
namespace AnnoDesigner.ViewModels
{
+ [Flags]
+ public enum ApplySettings
+ {
+ None = 0,
+ Color = 1 << 0,
+ Label = 1 << 1,
+ Icon = 1 << 2,
+ Influence = 1 << 3,
+ Borderless = 1 << 4,
+ Road = 1 << 5,
+ }
+
+ public interface Aaa
+ {
+ ApplySettings ApplySettings { get; }
+
+ IOperation SetValueAndGetUndoableOperation(IEnumerable objs);
+ }
+
+ public class Aaaa : Aaa
+ {
+ public ApplySettings ApplySettings { get; set; }
+
+ public string PropertyName { get; set; }
+
+ public Func OldValueGetter { get; set; }
+
+ public Func NewValueGetter { get; set; }
+
+ public IOperation SetValueAndGetUndoableOperation(IEnumerable objs)
+ {
+ var operation = new ModifyObjectPropertiesOperation()
+ {
+ PropertyName = PropertyName,
+ ObjectPropertyValues = objs
+ .Select(obj => (obj, OldValueGetter(obj), NewValueGetter()))
+ .ToList()
+ };
+
+ operation.Redo();
+
+ return operation;
+ }
+ }
+
public class BuildingSettingsViewModel : Notify
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
+ private readonly List applySettingsTemplates;
private readonly IAppSettings _appSettings;
private readonly IMessageBoxService _messageBoxService;
@@ -38,8 +85,11 @@ public class BuildingSettingsViewModel : Notify
private bool _isEnableLabelChecked;
private bool _isBorderlessChecked;
private bool _isRoadChecked;
+ private ApplySettings _applySettings = ApplySettings.Color | ApplySettings.Borderless;
private ObservableCollection _colorsInLayout;
private IAnnoCanvas _annoCanvasToUse;
+ private ICollection _availableIcons;
+ private IconImage _selectedIcon;
private ColorHueSaturationBrightnessComparer _colorSorter;
private ObservableCollection _buildingInfluences;
private BuildingInfluence _selectedBuildingInfluence;
@@ -51,14 +101,17 @@ public class BuildingSettingsViewModel : Notify
///
public BuildingSettingsViewModel(IAppSettings appSettingsToUse,
IMessageBoxService messageBoxServiceToUse,
- ILocalizationHelper localizationHelperToUse)
+ ILocalizationHelper localizationHelperToUse,
+ ICollection availableIcons)
{
_appSettings = appSettingsToUse;
_messageBoxService = messageBoxServiceToUse;
_localizationHelper = localizationHelperToUse;
+ _availableIcons = availableIcons;
- ApplyColorToSelectionCommand = new RelayCommand(ApplyColorToSelection, CanApplyColorToSelection);
- ApplyPredefinedColorToSelectionCommand = new RelayCommand(ApplyPredefinedColorToSelection, CanApplyPredefinedColorToSelection);
+ ApplyColorToSelectionCommand = new RelayCommand(ApplyColorToSelection, AreSomeObjectsSelected);
+ ApplyPredefinedColorToSelectionCommand = new RelayCommand(ApplyPredefinedColorToSelection, AreSomeObjectsSelected);
+ ApplySettingsToSelectionCommand = new RelayCommand(ApplySettingsToSelection, AreSomeObjectsSelected);
UseColorInLayoutCommand = new RelayCommand(UseColorInLayout, CanUseColorInLayout);
SelectedColor = Colors.Red;
@@ -77,6 +130,59 @@ public BuildingSettingsViewModel(IAppSettings appSettingsToUse,
BuildingInfluences = new ObservableCollection();
InitBuildingInfluences();
SelectedBuildingInfluence = BuildingInfluences.SingleOrDefault(x => x.Type == BuildingInfluenceType.None);
+
+ applySettingsTemplates = new()
+ {
+ new Aaaa()
+ {
+ ApplySettings = ApplySettings.Color,
+ PropertyName = nameof(LayoutObject.Color),
+ OldValueGetter = x => x.Color,
+ NewValueGetter = () => SelectedColor.Value
+ },
+ new Aaaa()
+ {
+ ApplySettings = ApplySettings.Label,
+ PropertyName = nameof(LayoutObject.Label),
+ OldValueGetter = x => x.WrappedAnnoObject.Label,
+ NewValueGetter = () => BuildingName
+ },
+ new Aaaa()
+ {
+ ApplySettings = ApplySettings.Icon,
+ PropertyName = nameof(LayoutObject.Icon),
+ OldValueGetter = x => x.Icon,
+ NewValueGetter = () => SelectedIcon
+ },
+ new Aaaa()
+ {
+ ApplySettings = ApplySettings.Influence,
+ PropertyName = nameof(LayoutObject.InfluenceRange),
+ OldValueGetter = x => x.WrappedAnnoObject.InfluenceRange,
+ NewValueGetter = () => BuildingInfluenceRange
+ },
+ new Aaaa()
+ {
+ ApplySettings = ApplySettings.Influence,
+ PropertyName = nameof(LayoutObject.Radius),
+ OldValueGetter = x => x.WrappedAnnoObject.Radius,
+ NewValueGetter = () => BuildingRadius
+ },
+ new Aaaa()
+ {
+ ApplySettings = ApplySettings.Borderless,
+ PropertyName = nameof(LayoutObject.Borderless),
+ OldValueGetter = x => x.WrappedAnnoObject.Borderless,
+ NewValueGetter = () => IsBorderlessChecked
+ },
+ new Aaaa()
+ {
+ ApplySettings = ApplySettings.Road,
+ PropertyName = nameof(LayoutObject.Road),
+ OldValueGetter = x => x.WrappedAnnoObject.Road,
+ NewValueGetter = () => IsRoadChecked
+ },
+ };
}
private void InitBuildingInfluences()
@@ -109,6 +215,18 @@ public Color? SelectedColor
set { UpdateProperty(ref _selectedColor, value); }
}
+ public ICollection AvailableIcons
+ {
+ get { return _availableIcons; }
+ set { UpdateProperty(ref _availableIcons, value); }
+ }
+
+ public IconImage SelectedIcon
+ {
+ get { return _selectedIcon; }
+ set { UpdateProperty(ref _selectedIcon, value); }
+ }
+
public int BuildingHeight
{
get { return _buildingHeight; }
@@ -205,6 +323,12 @@ public bool IsRoadChecked
set { UpdateProperty(ref _isRoadChecked, value); }
}
+ public ApplySettings ApplySettings
+ {
+ get { return _applySettings; }
+ set { UpdateProperty(ref _applySettings, value); }
+ }
+
public IAnnoCanvas AnnoCanvasToUse
{
get { return _annoCanvasToUse; }
@@ -237,7 +361,7 @@ public ObservableCollection ColorsInLayout
private ColorHueSaturationBrightnessComparer ColorSorter
{
- get { return _colorSorter ?? (_colorSorter = new ColorHueSaturationBrightnessComparer()); }
+ get { return _colorSorter ??= new ColorHueSaturationBrightnessComparer(); }
}
public ObservableCollection BuildingInfluences
@@ -382,7 +506,7 @@ private void ApplyColorToSelection(object param)
ObjectPropertyValues = AnnoCanvasToUse.SelectedObjects
.Select(obj => (obj, obj.Color, selectedColor: (SerializableColor)SelectedColor.Value))
.ToList(),
- AfterAction = ColorChangeUndone
+ AfterAction = RerenderCanvas
});
foreach (var curSelectedObject in AnnoCanvasToUse.SelectedObjects)
@@ -395,11 +519,6 @@ private void ApplyColorToSelection(object param)
AnnoCanvasToUse_ColorsUpdated(this, EventArgs.Empty);
}
- private bool CanApplyColorToSelection(object param)
- {
- return AnnoCanvasToUse?.SelectedObjects.Count > 0;
- }
-
public ICommand ApplyPredefinedColorToSelectionCommand { get; private set; }
private void ApplyPredefinedColorToSelection(object param)
@@ -416,7 +535,7 @@ private void ApplyPredefinedColorToSelection(object param)
.Where(obj => ColorPresetsHelper.Instance.GetPredefinedColor(obj.WrappedAnnoObject).HasValue)
.Select(obj => (obj, obj.Color, (SerializableColor)ColorPresetsHelper.Instance.GetPredefinedColor(obj.WrappedAnnoObject).Value))
.ToList(),
- AfterAction = ColorChangeUndone
+ AfterAction = RerenderCanvas
});
foreach (var curSelectedObject in AnnoCanvasToUse.SelectedObjects)
@@ -428,11 +547,32 @@ private void ApplyPredefinedColorToSelection(object param)
}
}
- AnnoCanvasToUse.ForceRendering();
- AnnoCanvasToUse_ColorsUpdated(this, EventArgs.Empty);
+ RerenderCanvas();
+ }
+
+ public ICommand ApplySettingsToSelectionCommand { get; private set; }
+
+ private void ApplySettingsToSelection(object param)
+ {
+ if (AnnoCanvasToUse == null)
+ {
+ return;
+ }
+
+ var operations = applySettingsTemplates.Where(x => ApplySettings.HasFlag(x.ApplySettings)).Select(x => x.SetValueAndGetUndoableOperation(AnnoCanvasToUse.SelectedObjects)).ToList();
+
+ if (operations.Count > 0)
+ {
+ AnnoCanvasToUse.UndoManager.RegisterOperation(new CompositeOperation(operations)
+ {
+ AfterAction = RerenderCanvas
+ });
+
+ RerenderCanvas();
+ }
}
- private bool CanApplyPredefinedColorToSelection(object param)
+ private bool AreSomeObjectsSelected(object param)
{
return AnnoCanvasToUse?.SelectedObjects.Count > 0;
}
@@ -477,7 +617,7 @@ private void AnnoCanvasToUse_ColorsUpdated(object sender, EventArgs e)
OnPropertyChanged(nameof(ShowColorsInLayout));
}
- private void ColorChangeUndone()
+ private void RerenderCanvas()
{
AnnoCanvasToUse.ForceRendering();
AnnoCanvasToUse_ColorsUpdated(this, EventArgs.Empty);
diff --git a/AnnoDesigner/ViewModels/MainViewModel.cs b/AnnoDesigner/ViewModels/MainViewModel.cs
index 827efdfb..36008ff5 100644
--- a/AnnoDesigner/ViewModels/MainViewModel.cs
+++ b/AnnoDesigner/ViewModels/MainViewModel.cs
@@ -126,7 +126,11 @@ public MainViewModel(ICommons commonsToUse,
StatisticsViewModel.IsVisible = _appSettings.StatsShowStats;
StatisticsViewModel.ShowStatisticsBuildingCount = _appSettings.StatsShowBuildingCount;
- BuildingSettingsViewModel = new BuildingSettingsViewModel(_appSettings, _messageBoxService, _localizationHelper);
+ AvailableIcons = new ObservableCollection();
+ _noIconItem = GenerateNoIconItem();
+ AvailableIcons.Add(_noIconItem);
+
+ BuildingSettingsViewModel = new BuildingSettingsViewModel(_appSettings, _messageBoxService, _localizationHelper, AvailableIcons);
// load tree localization
try
@@ -175,11 +179,6 @@ public MainViewModel(ICommons commonsToUse,
ShowLicensesWindowCommand = new RelayCommand(ExecuteShowLicensesWindow);
OpenRecentFileCommand = new RelayCommand(ExecuteOpenRecentFile);
- AvailableIcons = new ObservableCollection();
- _noIconItem = GenerateNoIconItem();
- AvailableIcons.Add(_noIconItem);
- SelectedIcon = _noIconItem;
-
RecentFiles = new ObservableCollection();
_recentFilesHelper.Updated += RecentFilesHelper_Updated;
@@ -265,7 +264,7 @@ private void Commons_SelectedLanguageChanged(object sender, EventArgs e)
AvailableIcons.Clear();
AvailableIcons.Add(_noIconItem);
LoadAvailableIcons();
- SelectedIcon = _noIconItem;
+ BuildingSettingsViewModel.SelectedIcon = _noIconItem;
}
catch (Exception ex)
{
@@ -356,7 +355,7 @@ private void ApplyCurrentObject()
Size = new Size(BuildingSettingsViewModel.BuildingWidth, BuildingSettingsViewModel.BuildingHeight),
Color = BuildingSettingsViewModel.SelectedColor ?? Colors.Red,
Label = BuildingSettingsViewModel.IsEnableLabelChecked ? BuildingSettingsViewModel.BuildingName : string.Empty,
- Icon = SelectedIcon == _noIconItem ? null : SelectedIcon.Name,
+ Icon = BuildingSettingsViewModel.SelectedIcon == _noIconItem ? null : BuildingSettingsViewModel.SelectedIcon.Name,
Radius = BuildingSettingsViewModel.BuildingRadius,
InfluenceRange = BuildingSettingsViewModel.BuildingInfluenceRange,
PavedStreet = BuildingSettingsViewModel.IsPavedStreet,
@@ -459,19 +458,19 @@ private void UpdateUIFromObject(LayoutObject layoutObject)
{
if (string.IsNullOrWhiteSpace(obj.Icon))
{
- SelectedIcon = _noIconItem;
+ BuildingSettingsViewModel.SelectedIcon = _noIconItem;
}
else
{
var foundIconImage = AvailableIcons.SingleOrDefault(x => x.Name.Equals(Path.GetFileNameWithoutExtension(obj.Icon), StringComparison.OrdinalIgnoreCase));
- SelectedIcon = foundIconImage ?? _noIconItem;
+ BuildingSettingsViewModel.SelectedIcon = foundIconImage ?? _noIconItem;
}
}
catch (Exception ex)
{
Console.WriteLine($"Error finding {nameof(IconImage)} for value \"{obj.Icon}\".{Environment.NewLine}{ex}");
- SelectedIcon = _noIconItem;
+ BuildingSettingsViewModel.SelectedIcon = _noIconItem;
}
// radius
@@ -1002,12 +1001,6 @@ public ObservableCollection AvailableIcons
set { UpdateProperty(ref _availableIcons, value); }
}
- public IconImage SelectedIcon
- {
- get { return _selectedIcon; }
- set { UpdateProperty(ref _selectedIcon, value); }
- }
-
public string MainWindowTitle
{
get { return _mainWindowTitle; }
diff --git a/Tests/AnnoDesigner.Tests/BuildingSettingsViewModelTests.cs b/Tests/AnnoDesigner.Tests/BuildingSettingsViewModelTests.cs
index 2108c265..d614450c 100644
--- a/Tests/AnnoDesigner.Tests/BuildingSettingsViewModelTests.cs
+++ b/Tests/AnnoDesigner.Tests/BuildingSettingsViewModelTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Media;
@@ -41,7 +42,8 @@ private BuildingSettingsViewModel GetViewModel(IAppSettings appSettingsToUse = n
{
return new BuildingSettingsViewModel(appSettingsToUse ?? _mockedAppSettings,
_mockedMessageBoxService,
- _mockedLocalization);
+ _mockedLocalization,
+ new List());
}
#region ctor tests
diff --git a/Tests/AnnoDesigner.Tests/MainViewModelTests.cs b/Tests/AnnoDesigner.Tests/MainViewModelTests.cs
index c4d9c47c..a1fa730e 100644
--- a/Tests/AnnoDesigner.Tests/MainViewModelTests.cs
+++ b/Tests/AnnoDesigner.Tests/MainViewModelTests.cs
@@ -136,7 +136,6 @@ public void Ctor_ShouldSetDefaultValues()
Assert.Null(viewModel.StatusMessageClipboard);
Assert.NotNull(viewModel.AvailableIcons);
- Assert.NotNull(viewModel.SelectedIcon);
Assert.NotNull(viewModel.Languages);
Assert.NotNull(viewModel.MainWindowTitle);
Assert.NotNull(viewModel.PresetsSectionHeader);
diff --git a/Tests/AnnoDesigner.Tests/Undo/CompositeOperationTests.cs b/Tests/AnnoDesigner.Tests/Undo/CompositeOperationTests.cs
index 8c82a854..f28c2c27 100644
--- a/Tests/AnnoDesigner.Tests/Undo/CompositeOperationTests.cs
+++ b/Tests/AnnoDesigner.Tests/Undo/CompositeOperationTests.cs
@@ -20,14 +20,11 @@ public void Undo_ShouldUndoOperationsInCorrectOrder()
var op2 = new Mock();
_ = op2.Setup(op => op.Undo()).Callback(() => order.Add(op2.Object));
- var operation = new CompositeOperation()
+ var operation = new CompositeOperation(new List()
{
- Operations = new List()
- {
- op1.Object,
- op2.Object
- }
- };
+ op1.Object,
+ op2.Object
+ });
// Act
operation.Undo();
@@ -51,14 +48,11 @@ public void Redo_ShouldRedoOperationsInCorrectOrder()
var op2 = new Mock();
_ = op2.Setup(op => op.Redo()).Callback(() => order.Add(op2.Object));
- var operation = new CompositeOperation()
+ var operation = new CompositeOperation(new List()
{
- Operations = new List()
- {
- op1.Object,
- op2.Object
- }
- };
+ op1.Object,
+ op2.Object
+ });
// Act
operation.Redo();