diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..4baaf616
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,10 @@
+## Summary
+
+## Checklist
+- [ ] Tests passed
+- [ ] Changes validated (manually or automated)
+- [ ] Closes / Fixes #xxx (if applicable)
+
+## Additional details / comments
+
+## Manual test steps (if applicable)
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..89ce0b91
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,76 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at brndnchong@gmail.com. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..51e0402b
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,19 @@
+# Intro
+Thanks for taking the time to read this!
+This doc contains guidelines and resources for contributing. It's not too complete but I'll add to it.
+
+# Ways to contribute
+## Reporting Bugs
+Feel free to report bugs in the [issues tracker](https://github.com/dsafa/audio-band/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).
+There are templates when creating a new issue to help you get started. Try to search for existing bugs first, the keyword search is quite good.
+
+## Suggest Features
+You can also suggest features and improvements in the issue tracker as well.
+
+## Development
+Development docs are on the [github page](https://dsafa.github.io/audio-band/audioband/development/setup.html) for this repo.
+The site will have instructions for setting up a build environment including instructions on how to debug a local setup as well as
+an overview of the project itself.
+
+I try to keep it as up to date as possible but like all docs, they tend to get outdated.
+If something is out of date feel free to raise an issue about it.
diff --git a/src/AudioBand.Test/AudioBand.Test.csproj b/src/AudioBand.Test/AudioBand.Test.csproj
index b4dca433..2af20833 100644
--- a/src/AudioBand.Test/AudioBand.Test.csproj
+++ b/src/AudioBand.Test/AudioBand.Test.csproj
@@ -91,9 +91,18 @@
C:\Users\bdonc\.nuget\packages\unosquare.swan.lite\0.38.1\lib\net452\Unosquare.Swan.Lite.dll
+
-
+
+
+
+
+
+
+
+
+
diff --git a/src/AudioBand.Test/AudioBand.Test.csproj.DotSettings b/src/AudioBand.Test/AudioBand.Test.csproj.DotSettings
index bf95c7ff..868fb131 100644
--- a/src/AudioBand.Test/AudioBand.Test.csproj.DotSettings
+++ b/src/AudioBand.Test/AudioBand.Test.csproj.DotSettings
@@ -1,5 +1,6 @@
True
True
+ True
True
True
\ No newline at end of file
diff --git a/src/AudioBand.Test/Settings/SettingsMigrationTests.cs b/src/AudioBand.Test/Settings/SettingsMigrationTests.cs
index 9c28c433..39fd35b1 100644
--- a/src/AudioBand.Test/Settings/SettingsMigrationTests.cs
+++ b/src/AudioBand.Test/Settings/SettingsMigrationTests.cs
@@ -9,7 +9,7 @@
using V2Settings = AudioBand.Settings.Models.V2.Settings;
using Nett;
using Xunit;
-using AudioSourceSetting = AudioBand.Settings.Models.V1.AudioSourceSetting;
+using V1AudioSourceSetting = AudioBand.Settings.Models.V1.AudioSourceSetting;
namespace AudioBand.Test
{
@@ -137,13 +137,13 @@ public void MigrateV1ToV2_AudioSourceSettings()
var setting1 = new AudioSourceSettingsCollection
{
Name = "test",
- Settings = new List {new AudioSourceSetting {Name = "key1", Value = "val1"}}
+ Settings = new List {new V1AudioSourceSetting {Name = "key1", Value = "val1"}}
};
var setting2 = new AudioSourceSettingsCollection
{
Name = "test2",
- Settings = new List { new AudioSourceSetting { Name = "key2", Value = "val2" } }
+ Settings = new List { new V1AudioSourceSetting { Name = "key2", Value = "val2" } }
};
var settings = new List {setting1,setting2};
@@ -415,17 +415,7 @@ public void MigrateV2ToV3_MigratesSuccessfully()
Name = ""Spotify Client secret""
Value = ""secret""
";
- var settings = TomlSettings.Create(cfg =>
- {
- cfg.ConfigureType(type => type.WithConversionFor(convert => convert
- .ToToml(SerializationConversions.ColorToString)
- .FromToml(tomlString => SerializationConversions.StringToColor(tomlString.Value))));
- cfg.ConfigureType(type => type.WithConversionFor(convert => convert
- .ToToml(SerializationConversions.EnumToString)
- .FromToml(str => SerializationConversions.StringToEnum(str.Value))));
- cfg.ConfigureType(type => type.WithConversionFor(c => c
- .FromToml(tml => tml.Value)));
- });
+ var settings = TomlHelper.DefaultSettings;
var v2 = Toml.ReadString(settingsFile, settings);
var v3 = Migration.MigrateSettings(v2, "2", "3");
@@ -499,5 +489,119 @@ public void MigrateV2ToV3_MigratesSuccessfully()
Assert.Equal(v2.CustomLabelSettings[0].Alignment, v3.Profiles[SettingsV3.DefaultProfileName].CustomLabelSettings[0].Alignment);
Assert.Equal(v2.CustomLabelSettings[0].FormatString, v3.Profiles[SettingsV3.DefaultProfileName].CustomLabelSettings[0].FormatString);
}
+
+ [Fact]
+ public void ChainedMigrations_V1ToV3_SuccessfulMigration()
+ {
+ var v1Settings = @"
+Version = ""0.1""
+
+[AudioBandAppearance]
+Width = 300
+Height = 30
+
+[PlayPauseButtonAppearance]
+XPosition = 0
+YPosition = 0
+Width = 30
+Height = 10
+IsVisible = true
+
+[NextSongButtonAppearance]
+IsVisible = true
+Width = 30
+Height = 10
+XPosition = 0
+YPosition = 0
+
+[PreviousSongButtonAppearance]
+IsVisible = true
+Width = 30
+Height = 10
+XPosition = 0
+YPosition = 0
+
+[[TextAppearances]]
+IsVisible = true
+Width = 100
+Height = 15
+XPosition = 150
+YPosition = 10
+FontSize = 10.0
+Color = ""White""
+Alignment = ""Center""
+ScrollSpeed = 0
+FormatString = ""{song}""
+
+[ProgressBarAppearance]
+ForegroundColor = ""Blue""
+BackgroundColor = ""Gray""
+IsVisible = true
+XPosition = 0
+YPosition = 26
+Width = 200
+Height = 2
+
+[AlbumArtAppearance]
+IsVisible = true
+Width = 30
+Height = 30
+XPosition = 0
+YPosition = 0
+
+[AlbumArtPopupAppearance]
+IsVisible = true
+Width = 500
+Height = 500
+XOffset = 50
+Margin = 6
+
+";
+ var v1 = Toml.ReadString(v1Settings, TomlHelper.DefaultSettings);
+ var v3 = Migration.MigrateSettings(v1, "0.1", "3");
+ var v3Profile = v3.Profiles[SettingsV3.DefaultProfileName];
+
+ Assert.Equal(v1.AudioBandAppearance.Width, v3Profile.AudioBandSettings.Width);
+ Assert.Equal(v1.AudioBandAppearance.Height, v3Profile.AudioBandSettings.Height);
+
+ Assert.Equal(v1.PlayPauseButtonAppearance.Width, v3Profile.PlayPauseButtonSettings.Width);
+ Assert.Equal(v1.PlayPauseButtonAppearance.Height, v3Profile.PlayPauseButtonSettings.Height);
+ Assert.Equal(v1.PlayPauseButtonAppearance.XPosition, v3Profile.PlayPauseButtonSettings.XPosition);
+ Assert.Equal(v1.PlayPauseButtonAppearance.YPosition, v3Profile.PlayPauseButtonSettings.YPosition);
+ Assert.Equal(v1.PlayPauseButtonAppearance.IsVisible, v3Profile.PlayPauseButtonSettings.IsVisible);
+
+ Assert.Equal(v1.NextSongButtonAppearance.Width, v3Profile.NextButtonSettings.Width);
+ Assert.Equal(v1.NextSongButtonAppearance.Height, v3Profile.NextButtonSettings.Height);
+ Assert.Equal(v1.NextSongButtonAppearance.XPosition, v3Profile.NextButtonSettings.XPosition);
+ Assert.Equal(v1.NextSongButtonAppearance.YPosition, v3Profile.NextButtonSettings.YPosition);
+ Assert.Equal(v1.NextSongButtonAppearance.IsVisible, v3Profile.NextButtonSettings.IsVisible);
+
+ Assert.Equal(v1.PreviousSongButtonAppearance.Width, v3Profile.PreviousButtonSettings.Width);
+ Assert.Equal(v1.PreviousSongButtonAppearance.Height, v3Profile.PreviousButtonSettings.Height);
+ Assert.Equal(v1.PreviousSongButtonAppearance.XPosition, v3Profile.PreviousButtonSettings.XPosition);
+ Assert.Equal(v1.PreviousSongButtonAppearance.YPosition, v3Profile.PreviousButtonSettings.YPosition);
+ Assert.Equal(v1.PreviousSongButtonAppearance.IsVisible, v3Profile.PreviousButtonSettings.IsVisible);
+
+ Assert.Single(v3Profile.CustomLabelSettings);
+ Assert.Equal(v1.TextAppearances[0].Color, v3Profile.CustomLabelSettings[0].Color);
+ Assert.Equal(v1.TextAppearances[0].Alignment, v3Profile.CustomLabelSettings[0].Alignment);
+ Assert.Equal(v1.TextAppearances[0].FontFamily, v3Profile.CustomLabelSettings[0].FontFamily);
+ Assert.Equal(v1.TextAppearances[0].FontSize, v3Profile.CustomLabelSettings[0].FontSize);
+ Assert.Equal(v1.TextAppearances[0].FormatString, v3Profile.CustomLabelSettings[0].FormatString);
+ Assert.Equal(v1.TextAppearances[0].Height, v3Profile.CustomLabelSettings[0].Height);
+ Assert.Equal(v1.TextAppearances[0].IsVisible, v3Profile.CustomLabelSettings[0].IsVisible);
+ Assert.Equal(v1.TextAppearances[0].Name, v3Profile.CustomLabelSettings[0].Name);
+ Assert.Equal(v1.TextAppearances[0].ScrollSpeed, v3Profile.CustomLabelSettings[0].ScrollSpeed);
+ Assert.Equal(v1.TextAppearances[0].Width, v3Profile.CustomLabelSettings[0].Width);
+ Assert.Equal(v1.TextAppearances[0].XPosition, v3Profile.CustomLabelSettings[0].XPosition);
+ Assert.Equal(v1.TextAppearances[0].YPosition, v3Profile.CustomLabelSettings[0].YPosition);
+
+ Assert.Equal(v1.AlbumArtAppearance.Height, v3Profile.AlbumArtSettings.Height);
+ Assert.Equal(v1.AlbumArtAppearance.IsVisible, v3Profile.AlbumArtSettings.IsVisible);
+ Assert.Equal(v1.AlbumArtAppearance.PlaceholderPath, v3Profile.AlbumArtSettings.PlaceholderPath);
+ Assert.Equal(v1.AlbumArtAppearance.Width, v3Profile.AlbumArtSettings.Width);
+ Assert.Equal(v1.AlbumArtAppearance.XPosition, v3Profile.AlbumArtSettings.XPosition);
+ Assert.Equal(v1.AlbumArtAppearance.YPosition, v3Profile.AlbumArtSettings.YPosition);
+ }
}
}
diff --git a/src/AudioBand.Test/ValueConverters/BoolToVisibilityConverterTests.cs b/src/AudioBand.Test/ValueConverters/BoolToVisibilityConverterTests.cs
new file mode 100644
index 00000000..e2cce975
--- /dev/null
+++ b/src/AudioBand.Test/ValueConverters/BoolToVisibilityConverterTests.cs
@@ -0,0 +1,101 @@
+using System.Globalization;
+using System.Windows;
+using AudioBand.ValueConverters;
+using Xunit;
+using Visibility = System.Windows.Visibility;
+
+namespace AudioBand.Test
+{
+ public class BoolToVisibilityConverterTests
+ {
+ private readonly BoolToVisibilityConverter _converter = new BoolToVisibilityConverter();
+
+ [Fact]
+ public void ConvertTrue_ReturnsVisible()
+ {
+ var result = ConvertToVisibility(true, null);
+ Assert.Equal(Visibility.Visible, result);
+ }
+
+ [Fact]
+ public void ConvertFalse_ParameterNull_ReturnsCollapsed()
+ {
+ var result = ConvertToVisibility(false, null);
+ Assert.Equal(Visibility.Collapsed, result);
+ }
+
+ [Fact]
+ public void ConvertFalse_ParameterTrue_ReturnsCollapsed()
+ {
+ var result = ConvertToVisibility(false, true);
+ Assert.Equal(Visibility.Collapsed, result);
+ }
+
+ [Fact]
+ public void ConvertFalse_ParameterFalse_ReturnsHidden()
+ {
+ var result = ConvertToVisibility(false, false);
+ Assert.Equal(Visibility.Hidden, result);
+ }
+
+ [Fact]
+ public void ConvertNull_ReturnsUnsetValue()
+ {
+ Assert.Equal(DependencyProperty.UnsetValue, _converter.Convert(null, typeof(Visibility), null, CultureInfo.CurrentCulture));
+ }
+
+ [Fact]
+ public void ConvertNonBool_ReturnsUnsetValue()
+ {
+ Assert.Equal(DependencyProperty.UnsetValue, _converter.Convert(null, typeof(Visibility), null, CultureInfo.CurrentCulture));
+ }
+
+ [Fact]
+ public void ConvertBackVisible_ReturnsTrue()
+ {
+ Assert.True(ConvertToBool(Visibility.Visible));
+ }
+
+ [Fact]
+ public void ConvertBackCollapsed_ReturnsFalse()
+ {
+ Assert.False(ConvertToBool(Visibility.Collapsed));
+ }
+
+ [Fact]
+ public void ConvertBackHidden_ReturnsFalse()
+ {
+ Assert.False(ConvertToBool(Visibility.Hidden));
+ }
+
+ [Fact]
+ public void ConvertBackNull_ReturnsUnsetValue()
+ {
+ Assert.Equal(DependencyProperty.UnsetValue, _converter.ConvertBack(null, typeof(bool), null, CultureInfo.CurrentCulture));
+ }
+
+ [Fact]
+ public void ConvertBackNonVisibility_ReturnsUnsetValue()
+ {
+ Assert.Equal(DependencyProperty.UnsetValue, _converter.ConvertBack("invalid", typeof(bool), null, CultureInfo.CurrentCulture));
+ }
+
+ private Visibility ConvertToVisibility(bool value, object parameter)
+ {
+ var result = _converter.Convert(value, typeof(Visibility), parameter, CultureInfo.CurrentCulture);
+
+ Assert.IsType(result);
+
+ return (Visibility)result;
+ }
+
+ private bool ConvertToBool(Visibility visibility)
+ {
+ var result = _converter.ConvertBack(visibility, typeof(bool), null, CultureInfo.CurrentCulture);
+
+ Assert.IsType(result);
+
+ return (bool)result;
+ }
+ }
+}
diff --git a/src/AudioBand.Test/ValueConverters/ColorToBrushConverterTests.cs b/src/AudioBand.Test/ValueConverters/ColorToBrushConverterTests.cs
new file mode 100644
index 00000000..588f598d
--- /dev/null
+++ b/src/AudioBand.Test/ValueConverters/ColorToBrushConverterTests.cs
@@ -0,0 +1,45 @@
+using System.Globalization;
+using System.Windows;
+using System.Windows.Media;
+using AudioBand.ValueConverters;
+using Xunit;
+
+namespace AudioBand.Test
+{
+ public class ColorToBrushConverterTests
+ {
+ private readonly ColorToBrushConverter _converter = new ColorToBrushConverter();
+
+ [Fact]
+ public void ConvertColor_ReturnsSolidColorBrushWithSameColor()
+ {
+ var color = Colors.Red;
+ var result = _converter.Convert(color, typeof(SolidColorBrush), null, CultureInfo.CurrentCulture);
+
+ Assert.IsType(result);
+ Assert.Equal(color, ((SolidColorBrush)result).Color);
+ }
+
+ [Fact]
+ public void ConvertNull_ReturnsUnsetValue()
+ {
+ Assert.Equal(DependencyProperty.UnsetValue, _converter.Convert(null, typeof(SolidColorBrush), null, CultureInfo.CurrentCulture));
+ }
+
+ [Fact]
+ public void ConvertBackSolidColorBrush_ReturnsColorWithSameValue()
+ {
+ var brush = Brushes.Blue;
+ var result = _converter.ConvertBack(brush, typeof(Color), null, CultureInfo.CurrentCulture);
+
+ Assert.IsType(result);
+ Assert.Equal(brush.Color, (Color)result);
+ }
+
+ [Fact]
+ public void ConvertBackNull_ReturnsUnsetValue()
+ {
+ Assert.Equal(DependencyProperty.UnsetValue, _converter.ConvertBack(null, typeof(Color), null, CultureInfo.CurrentCulture));
+ }
+ }
+}
diff --git a/src/AudioBand.Test/ValueConverters/FlagToBoolConverterTests.cs b/src/AudioBand.Test/ValueConverters/FlagToBoolConverterTests.cs
new file mode 100644
index 00000000..2eb7412a
--- /dev/null
+++ b/src/AudioBand.Test/ValueConverters/FlagToBoolConverterTests.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using AudioBand.ValueConverters;
+using Xunit;
+
+namespace AudioBand.Test
+{
+ public class FlagToBoolConverterTests
+ {
+ private readonly FlagToBoolConverter _converter = new FlagToBoolConverter();
+
+ [Flags]
+ private enum TestEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ Flag2 = 2,
+ }
+
+ [Fact]
+ public void ConvertNoFlag_ReturnsFalse()
+ {
+ var result = _converter.Convert(TestEnum.None, typeof(bool), TestEnum.Flag2, CultureInfo.CurrentCulture);
+
+ Assert.IsType(result);
+ Assert.False((bool)result);
+ }
+
+ [Fact]
+ public void ConvertHasFlag_ReturnsTrue()
+ {
+ var value = TestEnum.Flag1 | TestEnum.Flag2;
+ var result = _converter.Convert(value, typeof(bool), TestEnum.Flag2, CultureInfo.CurrentCulture);
+
+ Assert.IsType(result);
+ Assert.True((bool)result);
+ }
+
+ [Fact]
+ public void ConvertNull_ReturnsFalse()
+ {
+ Assert.False((bool)_converter.Convert(null, typeof(bool), null, CultureInfo.CurrentCulture));
+ }
+
+ [Fact]
+ public void ConvertBack_ReturnsUnsetValue()
+ {
+ Assert.Equal(DependencyProperty.UnsetValue, _converter.ConvertBack(null, typeof(Enum), null, CultureInfo.CurrentCulture));
+ }
+ }
+}
diff --git a/src/AudioBand.Test/ValueConverters/MultiplyConverterTests.cs b/src/AudioBand.Test/ValueConverters/MultiplyConverterTests.cs
new file mode 100644
index 00000000..1b52614f
--- /dev/null
+++ b/src/AudioBand.Test/ValueConverters/MultiplyConverterTests.cs
@@ -0,0 +1,46 @@
+using System.Globalization;
+using AudioBand.ValueConverters;
+using Xunit;
+
+namespace AudioBand.Test
+{
+ public class MultiplyConverterTests
+ {
+ private readonly MultiplierConverter _converter = new MultiplierConverter();
+
+ [Fact]
+ public void ConvertSingleValue_ReturnsValue()
+ {
+ var result = _converter.Convert(new object[] {5.0}, typeof(double), null, CultureInfo.CurrentCulture);
+
+ Assert.IsType(result);
+ Assert.Equal(5.0, (double)result, 4);
+ }
+
+ [Fact]
+ public void ConvertMultipleValues_ReturnsMultipliedValue()
+ {
+ var values = new object[] {1.0, 2, 3};
+ var result = _converter.Convert(values, typeof(double), null, CultureInfo.CurrentCulture);
+
+ Assert.IsType(result);
+ Assert.Equal(6.0, (double)result, 4);
+ }
+
+ [Fact]
+ public void ConvertNone_Returns1()
+ {
+ var result = _converter.Convert(null, typeof(double), null, CultureInfo.CurrentCulture);
+
+ Assert.IsType(result);
+ Assert.Equal(1.0, (double)result);
+ }
+
+ [Fact]
+ public void ConvertBack_NotSupported_ReturnsNull()
+ {
+ var result = _converter.ConvertBack(new object[]{"a", 1}, new[] { typeof(string), typeof(int)}, null, CultureInfo.CurrentCulture);
+ Assert.Null(result);
+ }
+ }
+}
diff --git a/src/AudioBand.Test/ValueConverters/ObjectToTypeConverterTests.cs b/src/AudioBand.Test/ValueConverters/ObjectToTypeConverterTests.cs
new file mode 100644
index 00000000..2e81ac3e
--- /dev/null
+++ b/src/AudioBand.Test/ValueConverters/ObjectToTypeConverterTests.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using AudioBand.ValueConverters;
+using Xunit;
+
+namespace AudioBand.Test
+{
+ public class ObjectToTypeConverterTests
+ {
+ private readonly ObjectToTypeConverter _converter = new ObjectToTypeConverter();
+
+ [Fact]
+ public void ConvertNull_ReturnsUnsetValue()
+ {
+ Assert.Equal(DependencyProperty.UnsetValue, _converter.Convert(null, typeof(Type), null, CultureInfo.CurrentCulture));
+ }
+
+ [Fact]
+ public void ConvertObject_ReturnsTypeOfObject()
+ {
+ var result = _converter.Convert("string", typeof(Type), null, CultureInfo.CurrentCulture);
+
+ Assert.Equal(typeof(string), result);
+ }
+
+ [Fact]
+ public void ConvertBack_NotSupported_ReturnsUnsetValue()
+ {
+ Assert.Equal(DependencyProperty.UnsetValue, _converter.ConvertBack(null, typeof(object), null, CultureInfo.CurrentCulture));
+ }
+ }
+}
diff --git a/src/AudioBand.Test/ValueConveters/PathToImageConverterTests.cs b/src/AudioBand.Test/ValueConverters/PathToImageConverterTests.cs
similarity index 96%
rename from src/AudioBand.Test/ValueConveters/PathToImageConverterTests.cs
rename to src/AudioBand.Test/ValueConverters/PathToImageConverterTests.cs
index 88a43bbf..0bf133a2 100644
--- a/src/AudioBand.Test/ValueConveters/PathToImageConverterTests.cs
+++ b/src/AudioBand.Test/ValueConverters/PathToImageConverterTests.cs
@@ -1,57 +1,57 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows.Media;
-using AudioBand.ValueConverters;
-using Xunit;
-
-namespace AudioBand.Test
-{
- public class PathToImageConverterTests
- {
- private PathToImageSourceConverter _converter = new PathToImageSourceConverter();
- private string _sampleImagePath = Path.GetFullPath("Assets/imgsource.png");
-
- [Fact]
- void Convert_EmptyPath_ReturnsNull()
- {
- Assert.Null(_converter.Convert("", typeof(ImageSource), null, CultureInfo.CurrentCulture));
- }
-
- [Fact]
- void Convert_Null_ReturnsNull()
- {
- Assert.Null(_converter.Convert((object)null, typeof(ImageSource), null, CultureInfo.CurrentCulture));
- }
-
- [Fact]
- void Convert_NoFile_ReturnsNull()
- {
- Assert.Null(_converter.Convert("nonexistingfile.png", typeof(ImageSource), null, CultureInfo.CurrentCulture));
- }
-
- [Fact]
- void Convert_ValidFile_ReturnsImageSource()
- {
- var imgSource = _converter.Convert(_sampleImagePath, typeof(ImageSource), null, CultureInfo.CurrentCulture);
-
- Assert.NotNull(imgSource);
- Assert.IsAssignableFrom(imgSource);
- }
-
- [Fact]
- void MultiConvert_InvalidFile_UsesFallback()
- {
- object fallback = new object();
- var imgSource = _converter.Convert(new[] {"invalidfile.png", fallback}, typeof(ImageSource), null,
- CultureInfo.CurrentCulture);
-
- Assert.NotNull(imgSource);
- Assert.Equal(fallback, imgSource);
- }
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Media;
+using AudioBand.ValueConverters;
+using Xunit;
+
+namespace AudioBand.Test
+{
+ public class PathToImageConverterTests
+ {
+ private PathToImageSourceConverter _converter = new PathToImageSourceConverter();
+ private string _sampleImagePath = Path.GetFullPath("Assets/imgsource.png");
+
+ [Fact]
+ void Convert_EmptyPath_ReturnsNull()
+ {
+ Assert.Null(_converter.Convert("", typeof(ImageSource), null, CultureInfo.CurrentCulture));
+ }
+
+ [Fact]
+ void Convert_Null_ReturnsNull()
+ {
+ Assert.Null(_converter.Convert((object)null, typeof(ImageSource), null, CultureInfo.CurrentCulture));
+ }
+
+ [Fact]
+ void Convert_NoFile_ReturnsNull()
+ {
+ Assert.Null(_converter.Convert("nonexistingfile.png", typeof(ImageSource), null, CultureInfo.CurrentCulture));
+ }
+
+ [Fact]
+ void Convert_ValidFile_ReturnsImageSource()
+ {
+ var imgSource = _converter.Convert(_sampleImagePath, typeof(ImageSource), null, CultureInfo.CurrentCulture);
+
+ Assert.NotNull(imgSource);
+ Assert.IsAssignableFrom(imgSource);
+ }
+
+ [Fact]
+ void MultiConvert_InvalidFile_UsesFallback()
+ {
+ object fallback = new object();
+ var imgSource = _converter.Convert(new[] {"invalidfile.png", fallback}, typeof(ImageSource), null,
+ CultureInfo.CurrentCulture);
+
+ Assert.NotNull(imgSource);
+ Assert.Equal(fallback, imgSource);
+ }
+ }
+}
diff --git a/src/AudioBand.Test/ValueConverters/PointConverterTests.cs b/src/AudioBand.Test/ValueConverters/PointConverterTests.cs
new file mode 100644
index 00000000..70394cf5
--- /dev/null
+++ b/src/AudioBand.Test/ValueConverters/PointConverterTests.cs
@@ -0,0 +1,31 @@
+using System.Globalization;
+using System.Windows;
+using Xunit;
+using PointConverter = AudioBand.ValueConverters.PointConverter;
+
+namespace AudioBand.Test
+{
+ public class PointConverterTests
+ {
+ private readonly PointConverter _converter = new PointConverter();
+
+ [Fact]
+ public void ConvertXAndY_ReturnsPointWithXAndY()
+ {
+ var x = 1;
+ var y = 2;
+ var result = _converter.Convert(new object[] {x, y}, typeof(Point), null, CultureInfo.CurrentCulture);
+
+ Assert.IsType(result);
+ var point = (Point) result;
+ Assert.Equal(x, point.X);
+ Assert.Equal(y, point.Y);
+ }
+
+ [Fact]
+ public void ConvertBack_Unsupported_ReturnsNull()
+ {
+ Assert.Null(_converter.ConvertBack(null, null, null, CultureInfo.CurrentCulture));
+ }
+ }
+}
diff --git a/src/AudioBand.Test/ValueConverters/StringToFontFamilyConverterTests.cs b/src/AudioBand.Test/ValueConverters/StringToFontFamilyConverterTests.cs
new file mode 100644
index 00000000..5f288534
--- /dev/null
+++ b/src/AudioBand.Test/ValueConverters/StringToFontFamilyConverterTests.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Media;
+using AudioBand.ValueConverters;
+using Xunit;
+
+namespace AudioBand.Test
+{
+ public class StringToFontFamilyConverterTests
+ {
+ private readonly StringToFontFamilyConverter _converter = new StringToFontFamilyConverter();
+
+ [Fact]
+ public void ConvertString_ReturnsFontFamily()
+ {
+ var result = _converter.Convert("Arial", typeof(FontFamily), null, CultureInfo.CurrentCulture);
+
+ Assert.IsType(result);
+ Assert.Equal("Arial", ((FontFamily)result).Source);
+ }
+
+ [Fact]
+ public void ConvertNull_ReturnsUnsetValue()
+ {
+ Assert.Equal(DependencyProperty.UnsetValue, _converter.Convert(null, typeof(FontFamily), null, CultureInfo.CurrentCulture));
+ }
+
+ [Fact]
+ public void ConvertBackFontFamily_ReturnsStringName()
+ {
+ var ff = new FontFamily("Arial");
+
+ var result = _converter.ConvertBack(ff, typeof(string), null, CultureInfo.CurrentCulture);
+
+ Assert.IsType(result);
+ Assert.Equal("Arial", (string)result);
+ }
+
+ [Fact]
+ public void ConvertBackNull_ReturnsUnsetValue()
+ {
+ Assert.Equal(DependencyProperty.UnsetValue, _converter.ConvertBack(null, typeof(string), null, CultureInfo.CurrentCulture));
+
+ }
+ }
+}
diff --git a/src/AudioBand.Test/ValueConverters/TimeSpanToMsConverterTests.cs b/src/AudioBand.Test/ValueConverters/TimeSpanToMsConverterTests.cs
new file mode 100644
index 00000000..cd7099c1
--- /dev/null
+++ b/src/AudioBand.Test/ValueConverters/TimeSpanToMsConverterTests.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using AudioBand.ValueConverters;
+using Xunit;
+
+namespace AudioBand.Test
+{
+ public class TimeSpanToMsConverterTests
+ {
+ private readonly TimeSpanToMsConverter _converter = new TimeSpanToMsConverter();
+
+ [Fact]
+ public void ConvertTimeSpan_ReturnsMs()
+ {
+ var time = TimeSpan.FromMilliseconds(500);
+ var result = _converter.Convert(time, typeof(double), null, CultureInfo.CurrentCulture);
+
+ Assert.IsType(result);
+ Assert.Equal(time.TotalMilliseconds, (double)result);
+ }
+
+ [Fact]
+ public void ConvertTimeSpanInvalid_Returns0()
+ {
+ var result = _converter.Convert(null, typeof(double), null, CultureInfo.CurrentCulture);
+
+ Assert.IsType(result);
+ Assert.Equal(0.0, (double)result);
+ }
+
+ [Fact]
+ public void ConvertBackMs_ReturnsTimespan()
+ {
+ var ms = 1000.0;
+ var result = _converter.ConvertBack(ms, typeof(TimeSpan), null, CultureInfo.CurrentCulture);
+
+ Assert.IsType(result);
+ Assert.Equal(TimeSpan.FromMilliseconds(ms), (TimeSpan)result);
+ }
+
+ [Fact]
+ public void ConvertBackInvalid_ReturnsTimespan0()
+ {
+ var result = _converter.ConvertBack(null, typeof(TimeSpan), null, CultureInfo.CurrentCulture);
+
+ Assert.IsType(result);
+ Assert.Equal(TimeSpan.Zero, (TimeSpan)result);
+ }
+ }
+}
diff --git a/src/AudioBand.Test/ViewModels/AudioSourceSettingsCollectionTests.cs b/src/AudioBand.Test/ViewModels/AudioSourceSettingsCollectionTests.cs
index 03e69864..27a50c8e 100644
--- a/src/AudioBand.Test/ViewModels/AudioSourceSettingsCollectionTests.cs
+++ b/src/AudioBand.Test/ViewModels/AudioSourceSettingsCollectionTests.cs
@@ -4,6 +4,7 @@
using Moq;
using System.Collections.Generic;
using AudioBand.Messages;
+using AudioBand.Settings;
using Xunit;
namespace AudioBand.Test
@@ -12,11 +13,13 @@ public class AudioSourceSettingsCollectionTests
{
private Mock _audioSourceMock;
private Mock _messageBus;
+ private Mock _appSettings;
public AudioSourceSettingsCollectionTests()
{
_audioSourceMock = new Mock();
_messageBus = new Mock();
+ _appSettings = new Mock();
}
[Fact]
@@ -33,7 +36,7 @@ public void NoMatchingSettings_CreatesNoChildViewModels()
};
var settings = new AudioSourceSettings { AudioSourceName = name, Settings = keyVals };
- var vm = new AudioSourceSettingsCollectionViewModel(_audioSourceMock.Object, settings, _messageBus.Object);
+ var vm = new AudioSourceSettingsCollectionViewModel(_audioSourceMock.Object, settings, _messageBus.Object, _appSettings.Object);
Assert.Empty(vm.SettingsList);
Assert.Equal(name, vm.AudioSourceName);
@@ -65,7 +68,7 @@ public void MatchingSettings_ShouldCreateChildViewModelsInOrder()
};
var settings = new AudioSourceSettings { Settings = settingModels };
- var vm = new AudioSourceSettingsCollectionViewModel(_audioSourceMock.Object, settings, _messageBus.Object);
+ var vm = new AudioSourceSettingsCollectionViewModel(_audioSourceMock.Object, settings, _messageBus.Object, _appSettings.Object);
Assert.Equal(settingModels.Count, vm.SettingsList.Count);
Assert.Equal(settingModels[0].Name, vm.SettingsList[0].Name);
@@ -88,12 +91,12 @@ public void AudioSourceSettingUpdate_NewValueIsWrittenBackToSettings()
object newSettingValue = 1;
_audioSourceMock.SetupGet(s => s[It.Is(x => x == setting)]).Returns(newSettingValue);
- var vm = new AudioSourceSettingsCollectionViewModel(_audioSourceMock.Object, settings, _messageBus.Object);
+ var vm = new AudioSourceSettingsCollectionViewModel(_audioSourceMock.Object, settings, _messageBus.Object, _appSettings.Object);
_audioSourceMock.Raise(s => s.SettingChanged += null, new SettingChangedEventArgs(setting));
- vm.EndEdit();
Assert.Equal(newSettingValue, settingModel.Value);
+ _appSettings.Verify(m => m.Save(), Times.Once);
}
[Fact]
@@ -121,7 +124,7 @@ public void AudioSourceSettingsUpdated_AudioSourceIsUpdatedInPriority()
_audioSourceMock.InSequence(s).SetupSet(source => source[It.Is(x => x == setting1.Name)] = null);
_audioSourceMock.InSequence(s).SetupSet(source => source[It.Is(x => x == setting2.Name)] = null);
- var vm = new AudioSourceSettingsCollectionViewModel(_audioSourceMock.Object, settings, _messageBus.Object);
+ var vm = new AudioSourceSettingsCollectionViewModel(_audioSourceMock.Object, settings, _messageBus.Object, _appSettings.Object);
_audioSourceMock.VerifySet(source => source[setting3.Name] = null);
_audioSourceMock.VerifySet(source => source[setting1.Name] = null);
diff --git a/src/AudioBand.Test/ViewModels/CustomLabelViewModelTests.cs b/src/AudioBand.Test/ViewModels/CustomLabelViewModelTests.cs
index 396aef52..6a80d1ed 100644
--- a/src/AudioBand.Test/ViewModels/CustomLabelViewModelTests.cs
+++ b/src/AudioBand.Test/ViewModels/CustomLabelViewModelTests.cs
@@ -73,5 +73,34 @@ void CustomLabel_ColorChangedThenCanceled_TextSegmentsHaveCorrectColor()
Assert.All(vm.TextSegments, segment => Assert.Equal(Colors.Blue, segment.Color));
}
+
+ [Fact]
+ void CustomLabel_CancelEdit_ModelHasNoChanges()
+ {
+ var formatString = "test";
+ var model = new CustomLabel {FormatString = formatString};
+ var vm = new CustomLabelViewModel(model, _dialogMock.Object, _sessionMock.Object, _messageBusMock.Object);
+
+ vm.FormatString = "new";
+ vm.CancelEdit();
+
+ Assert.Equal(formatString, vm.FormatString);
+ Assert.Equal(formatString, model.FormatString);
+ }
+
+ [Fact]
+ void CustomLabel_SaveEdit_ModelHasNewChanges()
+ {
+ var formatString = "test";
+ var newFormatstring = "format";
+ var model = new CustomLabel { FormatString = formatString };
+ var vm = new CustomLabelViewModel(model, _dialogMock.Object, _sessionMock.Object, _messageBusMock.Object);
+
+ vm.FormatString = newFormatstring;
+ vm.EndEdit();
+
+ Assert.Equal(newFormatstring, vm.FormatString);
+ Assert.Equal(newFormatstring, model.FormatString);
+ }
}
}
diff --git a/src/AudioBand.Test/ViewModels/CustomLabelsViewModelTests.cs b/src/AudioBand.Test/ViewModels/CustomLabelsViewModelTests.cs
index 2dafa58d..151bedde 100644
--- a/src/AudioBand.Test/ViewModels/CustomLabelsViewModelTests.cs
+++ b/src/AudioBand.Test/ViewModels/CustomLabelsViewModelTests.cs
@@ -137,5 +137,29 @@ public void ProfileChanged_NewLabelsHaveCorrectAudioSessionData()
_appSettingsMock.Raise(m => m.ProfileChanged += null, null, EventArgs.Empty);
Assert.True(vm.CustomLabels[0].IsPlaying);
}
+
+ [Fact]
+ public void RemoveLabel_PublishEdit()
+ {
+ _appSettingsMock.SetupGet(x => x.CustomLabels).Returns(new List { new CustomLabel() });
+ _dialogMock.Setup(o => o.ShowConfirmationDialog(It.IsAny(), It.IsAny
+
+ True
+ True
+ GlobalSettings.settings
+
@@ -107,6 +112,9 @@
+
+
+
@@ -303,6 +311,10 @@
Designer
+
+ SettingsSingleFileGenerator
+ GlobalSettings.Designer.cs
+
PreserveNewest
diff --git a/src/AudioBand/Behaviors/DpiScaling.cs b/src/AudioBand/Behaviors/DpiScaling.cs
index 7e71484d..183ace78 100644
--- a/src/AudioBand/Behaviors/DpiScaling.cs
+++ b/src/AudioBand/Behaviors/DpiScaling.cs
@@ -1,9 +1,11 @@
using System;
using System.Diagnostics;
+using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Interop;
using System.Windows.Media;
+using System.Windows.Threading;
namespace AudioBand.Behaviors
{
@@ -108,24 +110,43 @@ private IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam
switch (msg)
{
case WM_DPICHANGED:
- UpdateDpi(HiWord(wparam));
-
+ var newDpi = HiWord(wparam);
+ if (AssociatedObject is Window window)
+ {
+ UpdateDpi(newDpi, false);
+ var suggestedRect = Marshal.PtrToStructure(lparam);
+ window.Left = suggestedRect.Left;
+ window.Top = suggestedRect.Top;
+ window.Width = suggestedRect.Right - suggestedRect.Left;
+ window.Height = suggestedRect.Bottom - suggestedRect.Top;
+ }
+ else
+ {
+ UpdateDpi(newDpi);
+ }
+
+ handled = true;
break;
case WM_DPICHANGED_AFTERPARENT:
+ // Used for the toolbar since we don't receive WM_DPICHANGED messages there.
UpdateDpi(GetParentWindowDpi(AssociatedObject));
+ handled = true;
break;
}
return IntPtr.Zero;
}
- private void UpdateDpi(double newDpi)
+ private void UpdateDpi(double newDpi, bool updateSize = true)
{
- var dpiScale = newDpi / CurrentDpi;
- CurrentDpi = newDpi;
+ if (updateSize)
+ {
+ var dpiScale = newDpi / CurrentDpi;
+ AssociatedObject.Width *= dpiScale;
+ AssociatedObject.Height *= dpiScale;
+ }
- AssociatedObject.Width *= dpiScale;
- AssociatedObject.Height *= dpiScale;
+ CurrentDpi = newDpi;
if (VisualTreeHelper.GetChildrenCount(AssociatedObject) == 0)
{
diff --git a/src/AudioBand/Deskband.cs b/src/AudioBand/Deskband.cs
index 60509f52..537c6b62 100644
--- a/src/AudioBand/Deskband.cs
+++ b/src/AudioBand/Deskband.cs
@@ -16,7 +16,6 @@
using CSDeskBand;
using NLog;
using SimpleInjector;
-using SimpleInjector.Advanced;
namespace AudioBand
{
@@ -39,6 +38,7 @@ public class Deskband : CSDeskBandWpf
private AudioBandToolbar _audioBandToolbar;
private Container _container;
private Window _settingsWindow;
+ private ILogger _logger;
///
/// Initializes a new instance of the class.
@@ -55,11 +55,13 @@ public Deskband()
Options.HorizontalSize = initialSize;
Options.MinHorizontalSize = initialSize;
AudioBandLogManager.Initialize();
- var logger = AudioBandLogManager.GetLogger("AudioBand");
- logger.Info("Starting AudioBand. Version: {version}, OS: {os}", GetType().Assembly.GetCustomAttribute().InformationalVersion, Environment.OSVersion);
+ _logger = AudioBandLogManager.GetLogger("AudioBand");
+ _logger.Info("Starting AudioBand. Version: {version}, OS: {os}", GetType().Assembly.GetCustomAttribute().InformationalVersion, Environment.OSVersion);
- AppDomain.CurrentDomain.UnhandledException += (sender, args) => AudioBandLogManager.GetLogger("AudioBand").Error((Exception)args.ExceptionObject, "Unhandled Exception");
- AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
+ StartupCheck();
+
+ AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
+ AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve;
ConfigureDependencies();
@@ -81,7 +83,7 @@ protected override void DeskbandOnClosed()
}
// Problem with late binding. Fuslogvw shows its not probing the original location.
- private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
+ private static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args)
{
// name is in this format Xceed.Wpf.Toolkit, Version=3.4.0.0, Culture=neutral, PublicKeyToken=3e4669d2f30244f4
var asmName = args.Name.Split(',')[0];
@@ -94,6 +96,14 @@ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEven
return File.Exists(filename) ? Assembly.LoadFrom(filename) : null;
}
+ private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs args)
+ {
+ GlobalSettings.Default.UnhandledException = true;
+ GlobalSettings.Default.Save();
+
+ AudioBandLogManager.GetLogger("AudioBand").Error((Exception)args.ExceptionObject, "Unhandled Exception");
+ }
+
private void ConfigureDependencies()
{
try
@@ -139,5 +149,21 @@ private void FocusCaptured(FocusChangedMessage msg)
UpdateFocus(true);
}
}
+
+ private void StartupCheck()
+ {
+ if (!GlobalSettings.Default.UnhandledException)
+ {
+ return;
+ }
+
+ // Unhandled exception from last run. Prevent audioband from starting in case there is a crash loop.
+ _logger.Info("Startup prevented due to previous unhandled exception. Open audioband again to ignore.");
+ GlobalSettings.Default.UnhandledException = false;
+ GlobalSettings.Default.Save();
+
+ // Exception should make explorer remove the deskband from being automatically started.
+ throw new Exception("Startup prevented due to previous unhandled exception.");
+ }
}
}
diff --git a/src/AudioBand/GlobalSettings.Designer.cs b/src/AudioBand/GlobalSettings.Designer.cs
new file mode 100644
index 00000000..24cb5d28
--- /dev/null
+++ b/src/AudioBand/GlobalSettings.Designer.cs
@@ -0,0 +1,38 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace AudioBand {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.1.0.0")]
+ internal sealed partial class GlobalSettings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static GlobalSettings defaultInstance = ((GlobalSettings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new GlobalSettings())));
+
+ public static GlobalSettings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool UnhandledException {
+ get {
+ return ((bool)(this["UnhandledException"]));
+ }
+ set {
+ this["UnhandledException"] = value;
+ }
+ }
+ }
+}
diff --git a/src/AudioBand/GlobalSettings.settings b/src/AudioBand/GlobalSettings.settings
new file mode 100644
index 00000000..be549678
--- /dev/null
+++ b/src/AudioBand/GlobalSettings.settings
@@ -0,0 +1,9 @@
+
+
+
+
+
+ False
+
+
+
\ No newline at end of file
diff --git a/src/AudioBand/NativeMethods.cs b/src/AudioBand/NativeMethods.cs
index 056e5a4f..9b29d323 100644
--- a/src/AudioBand/NativeMethods.cs
+++ b/src/AudioBand/NativeMethods.cs
@@ -195,6 +195,23 @@ internal struct AccentPolicy
public uint GradientColor;
public int AnimationId;
}
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct RECT
+ {
+ public int Left;
+ public int Top;
+ public int Right;
+ public int Bottom;
+
+ public RECT(int left, int top, int right, int bottom)
+ {
+ Left = left;
+ Top = top;
+ Right = right;
+ Bottom = bottom;
+ }
+ }
}
#pragma warning restore
}
diff --git a/src/AudioBand/Settings/AppSettings.cs b/src/AudioBand/Settings/AppSettings.cs
index 6e2b0f20..2ab6e939 100644
--- a/src/AudioBand/Settings/AppSettings.cs
+++ b/src/AudioBand/Settings/AppSettings.cs
@@ -32,7 +32,6 @@ public class AppSettings : IAppSettings
private static readonly string SettingsDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AudioBand");
private static readonly string SettingsFilePath = Path.Combine(SettingsDirectory, "audioband.settings");
private static readonly ILogger Logger = AudioBandLogManager.GetLogger();
- private readonly TomlSettings _tomlSettings;
private SettingsV3 _settings;
private ProfileV3 _currentProfile;
@@ -41,31 +40,7 @@ public class AppSettings : IAppSettings
///
public AppSettings()
{
- _tomlSettings = TomlSettings.Create(cfg =>
- {
- cfg.ConfigureType(type => type.WithConversionFor(convert => convert
- .ToToml(SerializationConversions.ColorToString)
- .FromToml(tomlString => SerializationConversions.StringToColor(tomlString.Value))));
- cfg.ConfigureType(type => type.WithConversionFor(convert => convert
- .ToToml(SerializationConversions.EnumToString)
- .FromToml(str => SerializationConversions.StringToEnum(str.Value))));
- cfg.ConfigureType(type => type.WithConversionFor(c => c
- .FromToml(tml => tml.Value)));
- });
-
- if (!Directory.Exists(SettingsDirectory))
- {
- Directory.CreateDirectory(SettingsDirectory);
- }
-
- if (!File.Exists(SettingsFilePath))
- {
- CreateDefaultSettingsFile();
- }
- else
- {
- LoadSettingsFromPath(SettingsFilePath);
- }
+ InitSettings();
if (_settings.AudioSourceSettings == null)
{
@@ -243,7 +218,7 @@ public void Save()
{
try
{
- Toml.WriteFile(_settings, SettingsFilePath, _tomlSettings);
+ Toml.WriteFile(_settings, SettingsFilePath, TomlHelper.DefaultSettings);
}
catch (Exception e)
{
@@ -254,7 +229,7 @@ public void Save()
///
public void ImportProfilesFromPath(string path)
{
- var profilesToImport = Toml.ReadFile(path, _tomlSettings);
+ var profilesToImport = Toml.ReadFile(path, TomlHelper.DefaultSettings);
foreach (var keyVal in profilesToImport.Profiles)
{
var key = GetUniqueProfileName(keyVal.Key);
@@ -266,18 +241,18 @@ public void ImportProfilesFromPath(string path)
public void ExportProfilesToPath(string path)
{
var exportObject = new ProfileExportV3 { Profiles = _settings.Profiles };
- Toml.WriteFile(exportObject, path, _tomlSettings);
+ Toml.WriteFile(exportObject, path, TomlHelper.DefaultSettings);
}
private void LoadSettingsFromPath(string path)
{
- var tomlFile = Toml.ReadFile(path, _tomlSettings);
+ var tomlFile = Toml.ReadFile(path, TomlHelper.DefaultSettings);
var version = tomlFile["Version"].Get();
// Create backup
if (version != CurrentVersion)
{
- Toml.WriteFile(tomlFile, Path.Combine(SettingsDirectory, $"audioband.settings.{version}"), _tomlSettings);
+ Toml.WriteFile(tomlFile, Path.Combine(SettingsDirectory, $"audioband.settings.{version}"), TomlHelper.DefaultSettings);
_settings = Migration.MigrateSettings(tomlFile.Get(SettingsTable[version]), version, CurrentVersion);
Save();
}
@@ -402,5 +377,32 @@ private string GetUniqueProfileName(string name)
return newName;
}
+
+ private void InitSettings()
+ {
+ if (!Directory.Exists(SettingsDirectory))
+ {
+ Directory.CreateDirectory(SettingsDirectory);
+ }
+
+ if (!File.Exists(SettingsFilePath))
+ {
+ CreateDefaultSettingsFile();
+ return;
+ }
+
+ try
+ {
+ LoadSettingsFromPath(SettingsFilePath);
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e, "Unable to load settings");
+ var backupPath = Path.Combine(SettingsDirectory, "audioband.settings.backup-" + DateTime.Now.Ticks);
+ File.Copy(SettingsFilePath, backupPath, true);
+ Logger.Info("Creating new default settings. Backup created at {backup}", backupPath);
+ CreateDefaultSettingsFile();
+ }
+ }
}
}
diff --git a/src/AudioBand/Settings/Migrations/IdentityMigrator.cs b/src/AudioBand/Settings/Migrations/IdentityMigrator.cs
new file mode 100644
index 00000000..d3b7f4fb
--- /dev/null
+++ b/src/AudioBand/Settings/Migrations/IdentityMigrator.cs
@@ -0,0 +1,18 @@
+namespace AudioBand.Settings.Migrations
+{
+ ///
+ /// Performs no migration.
+ ///
+ internal class IdentityMigrator : ISettingsMigrator
+ {
+ ///
+ /// Migrate settings to new version.
+ ///
+ /// Old settings to migrate.
+ /// The new settings.
+ public object MigrateSetting(object oldSetting)
+ {
+ return oldSetting;
+ }
+ }
+}
diff --git a/src/AudioBand/Settings/Migrations/Migration.cs b/src/AudioBand/Settings/Migrations/Migration.cs
index 5e97c4fc..ce1f264e 100644
--- a/src/AudioBand/Settings/Migrations/Migration.cs
+++ b/src/AudioBand/Settings/Migrations/Migration.cs
@@ -11,10 +11,12 @@ namespace AudioBand.Settings.Migrations
///
internal static class Migration
{
- private static readonly Dictionary<(string From, string To), ISettingsMigrator> SupportedMigrations = new Dictionary<(string From, string To), ISettingsMigrator>()
+ // Assume that migrations can be applied in order.
+ private static readonly List<(string version, ISettingsMigrator migrator)> MigrationsList = new List<(string, ISettingsMigrator)>
{
- { ("0.1", "2"), new V1ToV2() },
- { ("2", "3"), new V2ToV3() },
+ ("0.1", new V1ToV2()),
+ ("2", new V2ToV3()),
+ ("3", new IdentityMigrator()),
};
private static readonly ILogger Logger = AudioBandLogManager.GetLogger(typeof(Migration).FullName);
@@ -35,7 +37,7 @@ public static TNew MigrateSettings(object oldSettings, string oldVersion,
}
var plan = FindPlan(oldVersion, newVersion);
- if (!plan.Any())
+ if (plan == null || !plan.Any())
{
throw new ArgumentException($"No migration plan from {oldVersion} to {newVersion}");
}
@@ -48,7 +50,20 @@ public static TNew MigrateSettings(object oldSettings, string oldVersion,
private static List FindPlan(string from, string to)
{
- return SupportedMigrations.Where(x => x.Key.From == from && x.Key.To == to).Select(x => x.Value).ToList();
+ var startIndex = MigrationsList.FindIndex(m => m.version == from);
+ var endIndex = MigrationsList.FindIndex(m => m.version == to);
+ if (startIndex == -1 || endIndex == -1)
+ {
+ return null;
+ }
+
+ if (endIndex < startIndex)
+ {
+ Logger.Error("End index less than start index");
+ return null;
+ }
+
+ return MigrationsList.GetRange(startIndex, endIndex - startIndex).Select(m => m.migrator).ToList();
}
}
}
diff --git a/src/AudioBand/Settings/TomlHelper.cs b/src/AudioBand/Settings/TomlHelper.cs
new file mode 100644
index 00000000..62bcb029
--- /dev/null
+++ b/src/AudioBand/Settings/TomlHelper.cs
@@ -0,0 +1,27 @@
+using System.Windows.Media;
+using AudioBand.Models;
+using Nett;
+
+namespace AudioBand.Settings
+{
+ ///
+ /// Helper for TOML serialization and deserialization.
+ ///
+ internal static class TomlHelper
+ {
+ ///
+ /// Gets the default settings for TOML de/serialization.
+ ///
+ public static TomlSettings DefaultSettings { get; } = TomlSettings.Create(cfg =>
+ {
+ cfg.ConfigureType(type => type.WithConversionFor(convert => convert
+ .ToToml(SerializationConversions.ColorToString)
+ .FromToml(tomlString => SerializationConversions.StringToColor(tomlString.Value))));
+ cfg.ConfigureType(type => type.WithConversionFor(convert => convert
+ .ToToml(SerializationConversions.EnumToString)
+ .FromToml(str => SerializationConversions.StringToEnum(str.Value))));
+ cfg.ConfigureType(type => type.WithConversionFor(c => c
+ .FromToml(tml => tml.Value)));
+ });
+ }
+}
diff --git a/src/AudioBand/ValueConverters/BoolToVisibilityConverter.cs b/src/AudioBand/ValueConverters/BoolToVisibilityConverter.cs
index 9f825efd..0734ea0d 100644
--- a/src/AudioBand/ValueConverters/BoolToVisibilityConverter.cs
+++ b/src/AudioBand/ValueConverters/BoolToVisibilityConverter.cs
@@ -8,19 +8,53 @@ namespace AudioBand.ValueConverters
///
/// Converter from to .
///
- [ValueConversion(typeof(bool), typeof(Visibility))]
+ [ValueConversion(typeof(bool), typeof(Visibility), ParameterType = typeof(bool))]
public class BoolToVisibilityConverter : IValueConverter
{
- ///
+ ///
+ /// Converts a to a .
+ ///
+ /// The value of the bool to convert.
+ /// Type is .
+ /// A bool indicating whether the visibility should be collapsed.
+ /// The culture info.
+ /// if is true.
+ /// Otherwise returns except when is false in which case
+ /// is returned.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
+ if (!ValueConverterHelper.IsValid(value))
+ {
+ return DependencyProperty.UnsetValue;
+ }
+
var collapse = parameter == null ? true : System.Convert.ToBoolean(parameter);
- return (bool)value ? Visibility.Visible : (collapse ? Visibility.Collapsed : Visibility.Hidden);
+ var isVisible = (bool)value;
+ if (isVisible)
+ {
+ return Visibility.Visible;
+ }
+ else
+ {
+ return collapse ? Visibility.Collapsed : Visibility.Hidden;
+ }
}
- ///
+ ///
+ /// Converts a to a value.
+ ///
+ /// The .
+ /// The type of .
+ /// Parameter is ignored.
+ /// The current culture.
+ /// True if is . False otherwise.
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
+ if (!ValueConverterHelper.IsValid(value))
+ {
+ return DependencyProperty.UnsetValue;
+ }
+
var visibility = (Visibility)value;
return visibility == Visibility.Visible;
}
diff --git a/src/AudioBand/ValueConverters/ColorToBrushConverter.cs b/src/AudioBand/ValueConverters/ColorToBrushConverter.cs
index b219acb7..3c12b8b2 100644
--- a/src/AudioBand/ValueConverters/ColorToBrushConverter.cs
+++ b/src/AudioBand/ValueConverters/ColorToBrushConverter.cs
@@ -1,5 +1,6 @@
using System;
using System.Globalization;
+using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
@@ -8,18 +9,42 @@ namespace AudioBand.ValueConverters
///
/// Converter from to .
///
- [ValueConversion(typeof(Color), typeof(Brush))]
+ [ValueConversion(typeof(Color), typeof(SolidColorBrush))]
public class ColorToBrushConverter : IValueConverter
{
- ///
+ ///
+ /// Converts a to a .
+ ///
+ /// The color.
+ /// The type of .
+ /// The converter parameter.
+ /// The current culture.
+ /// A representing the color.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
+ if (!ValueConverterHelper.IsValid(value))
+ {
+ return DependencyProperty.UnsetValue;
+ }
+
return new SolidColorBrush((Color)value);
}
- ///
+ ///
+ /// Converts a to a .
+ ///
+ /// The brush.
+ /// The type of .
+ /// The converter parameter.
+ /// The current culture.
+ /// The color component of a .
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
+ if (!ValueConverterHelper.IsValid(value))
+ {
+ return DependencyProperty.UnsetValue;
+ }
+
return ((SolidColorBrush)value).Color;
}
}
diff --git a/src/AudioBand/ValueConverters/ColorToNameConverter.cs b/src/AudioBand/ValueConverters/ColorToNameConverter.cs
index 9e55f414..622e2171 100644
--- a/src/AudioBand/ValueConverters/ColorToNameConverter.cs
+++ b/src/AudioBand/ValueConverters/ColorToNameConverter.cs
@@ -1,5 +1,6 @@
using System;
using System.Globalization;
+using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using AudioBand.Extensions;
@@ -7,7 +8,7 @@
namespace AudioBand.ValueConverters
{
///
- /// Convert a string representation to a .
+ /// Convert a to a string representation.
///
[ValueConversion(typeof(Color), typeof(string))]
public class ColorToNameConverter : IValueConverter
@@ -15,12 +16,22 @@ public class ColorToNameConverter : IValueConverter
///
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
+ if (!ValueConverterHelper.IsValid(value))
+ {
+ return DependencyProperty.UnsetValue;
+ }
+
return ((Color)value).GetColorName();
}
///
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
+ if (!ValueConverterHelper.IsValid(value))
+ {
+ return DependencyProperty.UnsetValue;
+ }
+
return ColorConverter.ConvertFromString((string)value);
}
}
diff --git a/src/AudioBand/ValueConverters/ComparisonConverter.cs b/src/AudioBand/ValueConverters/ComparisonConverter.cs
index 2b52cbf2..035ae126 100644
--- a/src/AudioBand/ValueConverters/ComparisonConverter.cs
+++ b/src/AudioBand/ValueConverters/ComparisonConverter.cs
@@ -18,7 +18,7 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur
///
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
- throw new NotImplementedException();
+ return null;
}
}
}
diff --git a/src/AudioBand/ValueConverters/DoubleToCornerRadiusConverter.cs b/src/AudioBand/ValueConverters/DoubleToCornerRadiusConverter.cs
index fa7d7205..d2822ad0 100644
--- a/src/AudioBand/ValueConverters/DoubleToCornerRadiusConverter.cs
+++ b/src/AudioBand/ValueConverters/DoubleToCornerRadiusConverter.cs
@@ -11,7 +11,14 @@ namespace AudioBand.ValueConverters
[ValueConversion(typeof(double), typeof(CornerRadius))]
public class DoubleToCornerRadiusConverter : IValueConverter
{
- ///
+ ///
+ /// Uses a double as the width of a corner radius.
+ ///
+ /// The double to convert.
+ /// The type of .
+ /// The converter parameter.
+ /// The culture.
+ /// A uniform corner radius with each side equal to value / 2.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new CornerRadius(System.Convert.ToDouble(value) / 2);
@@ -20,7 +27,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
///
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
- throw new NotImplementedException();
+ return DependencyProperty.UnsetValue;
}
}
}
diff --git a/src/AudioBand/ValueConverters/EmptyStringToBoolConverter.cs b/src/AudioBand/ValueConverters/EmptyStringToBoolConverter.cs
index 4c8b25a3..e576b395 100644
--- a/src/AudioBand/ValueConverters/EmptyStringToBoolConverter.cs
+++ b/src/AudioBand/ValueConverters/EmptyStringToBoolConverter.cs
@@ -1,5 +1,6 @@
using System;
using System.Globalization;
+using System.Windows;
using System.Windows.Data;
namespace AudioBand.ValueConverters
@@ -25,7 +26,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
///
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
- throw new NotImplementedException();
+ return DependencyProperty.UnsetValue;
}
}
}
diff --git a/src/AudioBand/ValueConverters/FlagToBoolConverter.cs b/src/AudioBand/ValueConverters/FlagToBoolConverter.cs
index 9c25bf0b..3ba12f83 100644
--- a/src/AudioBand/ValueConverters/FlagToBoolConverter.cs
+++ b/src/AudioBand/ValueConverters/FlagToBoolConverter.cs
@@ -8,13 +8,20 @@ namespace AudioBand.ValueConverters
///
/// Converts enum flag to bool.
///
- [ValueConversion(typeof(Enum), typeof(bool))]
+ [ValueConversion(typeof(Enum), typeof(bool), ParameterType = typeof(Enum))]
public class FlagToBoolConverter : IValueConverter
{
- ///
+ ///
+ /// Converts an enum flag to a bool value.
+ ///
+ /// The value of the flag.
+ /// The target type.
+ /// The flag to check if set.
+ /// The culture.
+ /// True if the flag specified by is set.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
- if (value == null || value == DependencyProperty.UnsetValue || parameter == null)
+ if (value == null || parameter == null)
{
return false;
}
@@ -25,7 +32,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
///
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
- throw new NotImplementedException();
+ return DependencyProperty.UnsetValue;
}
}
}
diff --git a/src/AudioBand/ValueConverters/MultiplierConverter.cs b/src/AudioBand/ValueConverters/MultiplierConverter.cs
index d685cc34..33798d3b 100644
--- a/src/AudioBand/ValueConverters/MultiplierConverter.cs
+++ b/src/AudioBand/ValueConverters/MultiplierConverter.cs
@@ -6,17 +6,24 @@
namespace AudioBand.ValueConverters
{
///
- /// Mutliples all values together.
+ /// Multiplies all values together.
///
[ValueConversion(typeof(double), typeof(double), ParameterType = typeof(double))]
public class MultiplierConverter : IMultiValueConverter
{
- ///
+ ///
+ /// Converts a list of numeric values into a single value equal to multiplying them together.
+ ///
+ /// The list of values to multiply together.
+ /// The target type.
+ /// The converter parameter.
+ /// The culture.
+ /// A single value as the result of multiplying all the values together. Returns 1 if no values.
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values == null)
{
- return 1;
+ return 1.0;
}
double acc = 1;
@@ -31,7 +38,7 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur
///
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
- throw new NotImplementedException();
+ return null;
}
}
}
diff --git a/src/AudioBand/ValueConverters/ObjectToTypeConverter.cs b/src/AudioBand/ValueConverters/ObjectToTypeConverter.cs
index bc595057..8a8cae5b 100644
--- a/src/AudioBand/ValueConverters/ObjectToTypeConverter.cs
+++ b/src/AudioBand/ValueConverters/ObjectToTypeConverter.cs
@@ -1,11 +1,12 @@
using System;
using System.Globalization;
+using System.Windows;
using System.Windows.Data;
namespace AudioBand.ValueConverters
{
///
- /// Converts and object to a type.
+ /// Converts an object to its type.
///
[ValueConversion(typeof(object), typeof(Type))]
public class ObjectToTypeConverter : IValueConverter
@@ -13,13 +14,18 @@ public class ObjectToTypeConverter : IValueConverter
///
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
- return value?.GetType();
+ if (value == null)
+ {
+ return DependencyProperty.UnsetValue;
+ }
+
+ return value.GetType();
}
///
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
- throw new NotImplementedException();
+ return DependencyProperty.UnsetValue;
}
}
}
diff --git a/src/AudioBand/ValueConverters/PathToImageSourceConverter.cs b/src/AudioBand/ValueConverters/PathToImageSourceConverter.cs
index ec7b4e48..18e48cfb 100644
--- a/src/AudioBand/ValueConverters/PathToImageSourceConverter.cs
+++ b/src/AudioBand/ValueConverters/PathToImageSourceConverter.cs
@@ -55,7 +55,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
///
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
- throw new NotImplementedException();
+ return DependencyProperty.UnsetValue;
}
///
@@ -78,7 +78,7 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur
///
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
- throw new NotImplementedException();
+ return null;
}
}
}
diff --git a/src/AudioBand/ValueConverters/PointConverter.cs b/src/AudioBand/ValueConverters/PointConverter.cs
index 21985fd7..0af10920 100644
--- a/src/AudioBand/ValueConverters/PointConverter.cs
+++ b/src/AudioBand/ValueConverters/PointConverter.cs
@@ -16,23 +16,30 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur
{
if (values == null)
{
- return default(Point);
+ return DependencyProperty.UnsetValue;
}
- if (values.Any(v => v == DependencyProperty.UnsetValue))
+ if (values.Any(v => v == DependencyProperty.UnsetValue || v == null))
{
- return default(Point);
+ return DependencyProperty.UnsetValue;
}
- var x = System.Convert.ToDouble(values[0]);
- var y = System.Convert.ToDouble(values[1]);
- return new Point(x, y);
+ try
+ {
+ var x = System.Convert.ToDouble(values[0]);
+ var y = System.Convert.ToDouble(values[1]);
+ return new Point(x, y);
+ }
+ catch
+ {
+ return DependencyProperty.UnsetValue;
+ }
}
///
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
- throw new NotImplementedException();
+ return null;
}
}
}
diff --git a/src/AudioBand/ValueConverters/StringFormatConverter.cs b/src/AudioBand/ValueConverters/StringFormatConverter.cs
index 0418bf91..bc3887bf 100644
--- a/src/AudioBand/ValueConverters/StringFormatConverter.cs
+++ b/src/AudioBand/ValueConverters/StringFormatConverter.cs
@@ -1,5 +1,6 @@
using System;
using System.Globalization;
+using System.Windows;
using System.Windows.Data;
namespace AudioBand.ValueConverters
@@ -24,7 +25,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
///
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
- throw new NotImplementedException();
+ return DependencyProperty.UnsetValue;
}
}
}
diff --git a/src/AudioBand/ValueConverters/StringToFontFamilyConverter.cs b/src/AudioBand/ValueConverters/StringToFontFamilyConverter.cs
index 941cf375..17c84fc9 100644
--- a/src/AudioBand/ValueConverters/StringToFontFamilyConverter.cs
+++ b/src/AudioBand/ValueConverters/StringToFontFamilyConverter.cs
@@ -1,5 +1,6 @@
using System;
using System.Globalization;
+using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
@@ -14,21 +15,20 @@ public class StringToFontFamilyConverter : IValueConverter
///
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
- if (value == null)
+ if (!ValueConverterHelper.IsValid(value))
{
- throw new ArgumentNullException(nameof(value));
+ return DependencyProperty.UnsetValue;
}
- var fontFamilyString = (string)value;
- return new FontFamily(fontFamilyString);
+ return new FontFamily((string)value);
}
///
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
- if (value == null)
+ if (!ValueConverterHelper.IsValid(value))
{
- throw new ArgumentNullException(nameof(value));
+ return DependencyProperty.UnsetValue;
}
var fontFamily = (FontFamily)value;
diff --git a/src/AudioBand/ValueConverters/StringToVisibilityConverter.cs b/src/AudioBand/ValueConverters/StringToVisibilityConverter.cs
index bb375d14..5dc600c6 100644
--- a/src/AudioBand/ValueConverters/StringToVisibilityConverter.cs
+++ b/src/AudioBand/ValueConverters/StringToVisibilityConverter.cs
@@ -8,7 +8,7 @@ namespace AudioBand.ValueConverters
///
/// Converts a string to a value.
///
- [ValueConversion(typeof(string), typeof(Visibility))]
+ [ValueConversion(typeof(string), typeof(Visibility), ParameterType = typeof(bool))]
public class StringToVisibilityConverter : IValueConverter
{
///
@@ -21,7 +21,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
///
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
- throw new NotImplementedException();
+ return DependencyProperty.UnsetValue;
}
}
}
diff --git a/src/AudioBand/ValueConverters/TextAlignmentConverter.cs b/src/AudioBand/ValueConverters/TextAlignmentConverter.cs
index 5d782fff..d87d32fb 100644
--- a/src/AudioBand/ValueConverters/TextAlignmentConverter.cs
+++ b/src/AudioBand/ValueConverters/TextAlignmentConverter.cs
@@ -15,6 +15,11 @@ public class TextAlignmentConverter : IValueConverter
///
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
+ if (!ValueConverterHelper.IsValid(value))
+ {
+ return DependencyProperty.UnsetValue;
+ }
+
switch ((CustomLabel.TextAlignment)value)
{
case CustomLabel.TextAlignment.Center:
@@ -31,7 +36,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
///
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
- throw new NotImplementedException();
+ return DependencyProperty.UnsetValue;
}
}
}
diff --git a/src/AudioBand/ValueConverters/TimeSpanToMsConverter.cs b/src/AudioBand/ValueConverters/TimeSpanToMsConverter.cs
index 25a7530e..b1a037ad 100644
--- a/src/AudioBand/ValueConverters/TimeSpanToMsConverter.cs
+++ b/src/AudioBand/ValueConverters/TimeSpanToMsConverter.cs
@@ -1,6 +1,5 @@
using System;
using System.Globalization;
-using System.Windows;
using System.Windows.Data;
namespace AudioBand.ValueConverters
@@ -14,9 +13,9 @@ public class TimeSpanToMsConverter : IValueConverter
///
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
- if (value == null || value == DependencyProperty.UnsetValue)
+ if (!ValueConverterHelper.IsValid(value))
{
- return 0;
+ return 0.0;
}
return ((TimeSpan)value).TotalMilliseconds;
@@ -25,7 +24,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
///
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
- if (value == null || value == DependencyProperty.UnsetValue)
+ if (!ValueConverterHelper.IsValid(value))
{
return TimeSpan.Zero;
}
diff --git a/src/AudioBand/ValueConverters/ValueConverterHelper.cs b/src/AudioBand/ValueConverters/ValueConverterHelper.cs
new file mode 100644
index 00000000..2ed1785b
--- /dev/null
+++ b/src/AudioBand/ValueConverters/ValueConverterHelper.cs
@@ -0,0 +1,23 @@
+using System.Windows;
+
+namespace AudioBand.ValueConverters
+{
+ ///
+ /// Helpers for value conversion.
+ ///
+ internal static class ValueConverterHelper
+ {
+ ///
+ /// Provides a standard guard clause for the converter value.
+ ///
+ /// Expected type.
+ /// The value passed to the converter.
+ /// True if valid, false otherwise.
+ internal static bool IsValid(object value)
+ {
+ return value != null
+ && value != DependencyProperty.UnsetValue
+ && value.GetType() == typeof(TType);
+ }
+ }
+}
diff --git a/src/AudioBand/ViewModels/AudioSourceSettingKeyValue.cs b/src/AudioBand/ViewModels/AudioSourceSettingKeyValue.cs
index 4bfd94b6..95665f8b 100644
--- a/src/AudioBand/ViewModels/AudioSourceSettingKeyValue.cs
+++ b/src/AudioBand/ViewModels/AudioSourceSettingKeyValue.cs
@@ -96,6 +96,14 @@ public void PropagateSettingToAudioSource()
}
}
+ ///
+ /// When the audio sources updates the setting itself instead of from audioband. Sync the changes back to the model.
+ ///
+ public void SyncToModel()
+ {
+ MapSelf(_model, _originalSource);
+ }
+
///
protected override void OnBeginEdit()
{
@@ -124,7 +132,7 @@ protected override void OnEndEdit()
return;
}
- MapSelf(_model, _originalSource);
+ SyncToModel();
PropagateSettingToAudioSource();
}
}
diff --git a/src/AudioBand/ViewModels/AudioSourceSettingsCollectionViewModel.cs b/src/AudioBand/ViewModels/AudioSourceSettingsCollectionViewModel.cs
index 2f41eae2..0cc8d0f1 100644
--- a/src/AudioBand/ViewModels/AudioSourceSettingsCollectionViewModel.cs
+++ b/src/AudioBand/ViewModels/AudioSourceSettingsCollectionViewModel.cs
@@ -6,6 +6,7 @@
using AudioBand.AudioSource;
using AudioBand.Messages;
using AudioBand.Models;
+using AudioBand.Settings;
namespace AudioBand.ViewModels
{
@@ -16,6 +17,7 @@ public class AudioSourceSettingsCollectionViewModel : ViewModelBase
{
private readonly IInternalAudioSource _audioSource;
private readonly IMessageBus _messageBus;
+ private readonly IAppSettings _appSettings;
///
/// Initializes a new instance of the class.
@@ -23,10 +25,12 @@ public class AudioSourceSettingsCollectionViewModel : ViewModelBase
/// The audiosource that these settings belong to.
/// The settings model.
/// The message bus.
- public AudioSourceSettingsCollectionViewModel(IInternalAudioSource audioSource, AudioSourceSettings settingsModel, IMessageBus messageBus)
+ /// The app settings.
+ public AudioSourceSettingsCollectionViewModel(IInternalAudioSource audioSource, AudioSourceSettings settingsModel, IMessageBus messageBus, IAppSettings appSettings)
{
_audioSource = audioSource;
_messageBus = messageBus;
+ _appSettings = appSettings;
SettingsList = new ObservableCollection(CreateKeyValuePairs(audioSource, settingsModel));
_audioSource.SettingChanged += AudioSourceOnSettingChanged;
@@ -102,6 +106,9 @@ private List CreateKeyValuePairs(IInternalAudioSourc
}
// Audio sources can change settings themselves so we need to listen for them.
+ // Usually, settings are saved after the user edits a setting and clicks apply.
+ // These changes occur outside of the normal settings lifecycle, so the only time that the new values can be saved
+ // are when the application closes but there are some issues with detected that, so instead just save now.
private void AudioSourceOnSettingChanged(object sender, SettingChangedEventArgs e)
{
var settingThatChanged = SettingsList.FirstOrDefault(s => s.Name == e.SettingName);
@@ -114,6 +121,8 @@ private void AudioSourceOnSettingChanged(object sender, SettingChangedEventArgs
try
{
settingThatChanged.Value = _audioSource[e.SettingName];
+ settingThatChanged.SyncToModel();
+ _appSettings.Save();
}
catch (Exception ex)
{
diff --git a/src/AudioBand/ViewModels/AudioSourceSettingsViewModel.cs b/src/AudioBand/ViewModels/AudioSourceSettingsViewModel.cs
index be4ae67f..2f01512b 100644
--- a/src/AudioBand/ViewModels/AudioSourceSettingsViewModel.cs
+++ b/src/AudioBand/ViewModels/AudioSourceSettingsViewModel.cs
@@ -48,14 +48,14 @@ public void CreateViewModelForAudioSource(IInternalAudioSource audioSource)
var matchingSetting = _appSettings.AudioSourceSettings.FirstOrDefault(s => s.AudioSourceName == audioSource.Name);
if (matchingSetting != null)
{
- var viewModel = new AudioSourceSettingsCollectionViewModel(audioSource, matchingSetting, _messageBus);
+ var viewModel = new AudioSourceSettingsCollectionViewModel(audioSource, matchingSetting, _messageBus, _appSettings);
AudioSourcesSettings.Add(viewModel);
}
else
{
var newSettingsModel = new AudioSourceSettings { AudioSourceName = audioSource.Name };
_appSettings.AudioSourceSettings.Add(newSettingsModel);
- var newViewModel = new AudioSourceSettingsCollectionViewModel(audioSource, newSettingsModel, _messageBus);
+ var newViewModel = new AudioSourceSettingsCollectionViewModel(audioSource, newSettingsModel, _messageBus, _appSettings);
AudioSourcesSettings.Add(newViewModel);
}
diff --git a/src/AudioBand/ViewModels/CustomLabelViewModel.cs b/src/AudioBand/ViewModels/CustomLabelViewModel.cs
index 7cd5526c..59fd5720 100644
--- a/src/AudioBand/ViewModels/CustomLabelViewModel.cs
+++ b/src/AudioBand/ViewModels/CustomLabelViewModel.cs
@@ -17,6 +17,7 @@ namespace AudioBand.ViewModels
///
public class CustomLabelViewModel : LayoutViewModelBase
{
+ private readonly CustomLabel _source;
private readonly IAudioSession _audioSession;
private bool _isPlaying;
private IEnumerable _textSegments;
@@ -31,6 +32,7 @@ public class CustomLabelViewModel : LayoutViewModelBase
public CustomLabelViewModel(CustomLabel source, IDialogService dialogService, IAudioSession audioSession, IMessageBus messageBus)
: base(messageBus, source)
{
+ _source = source;
_audioSession = audioSession;
_audioSession.PropertyChanged += AudioSessionOnPropertyChanged;
@@ -243,6 +245,13 @@ protected override void OnReset()
ReParseSegments();
}
+ ///
+ protected override void OnEndEdit()
+ {
+ base.OnEndEdit();
+ MapSelf(Model, _source);
+ }
+
private void AudioSessionOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(IAudioSession.IsPlaying))
diff --git a/src/AudioBand/ViewModels/CustomLabelsViewModel.cs b/src/AudioBand/ViewModels/CustomLabelsViewModel.cs
index 206b4101..46749b76 100644
--- a/src/AudioBand/ViewModels/CustomLabelsViewModel.cs
+++ b/src/AudioBand/ViewModels/CustomLabelsViewModel.cs
@@ -61,6 +61,8 @@ public CustomLabelsViewModel(IAppSettings appsettings, IDialogService dialogServ
///
protected override void OnBeginEdit()
{
+ base.OnBeginEdit();
+
_added.Clear();
_removed.Clear();
}
@@ -68,6 +70,8 @@ protected override void OnBeginEdit()
///
protected override void OnCancelEdit()
{
+ base.OnCancelEdit();
+
foreach (var label in _added)
{
CustomLabels.Remove(label);
@@ -85,6 +89,8 @@ protected override void OnCancelEdit()
///
protected override void OnEndEdit()
{
+ base.OnEndEdit();
+
_added.Clear();
_removed.Clear();
@@ -108,13 +114,13 @@ private void AddLabelCommandOnExecute()
private void RemoveLabelCommandOnExecute(CustomLabelViewModel labelViewModel)
{
- BeginEdit();
-
if (!_dialogService.ShowConfirmationDialog(ConfirmationDialogType.DeleteLabel, labelViewModel.Name))
{
return;
}
+ BeginEdit();
+
CustomLabels.Remove(labelViewModel);
// Only add to removed if not a new label
diff --git a/src/AudioBand/app.config b/src/AudioBand/app.config
index 74f5bf7e..71d88d87 100644
--- a/src/AudioBand/app.config
+++ b/src/AudioBand/app.config
@@ -1,5 +1,13 @@
+
+
+
+
+
+
+
+
@@ -12,4 +20,18 @@
+
+
+
+ False
+
+
+
+
+
+
+ False
+
+
+
\ No newline at end of file
diff --git a/src/AudioBandRules.ruleset b/src/AudioBandRules.ruleset
index 0a65f425..b68806b6 100644
--- a/src/AudioBandRules.ruleset
+++ b/src/AudioBandRules.ruleset
@@ -19,5 +19,6 @@
+
\ No newline at end of file