diff --git a/README.md b/README.md index 21f5d102..1a8249a8 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,15 @@ Audio Band allows you to display song information in the taskbar. ![](screenshots/custom-2.png) +# Table of contents +1. [Installation](#installation) +2. [Usage](#usage) +3. [Plugins](#audiosources) + 1. [Spotify](#spotify-setup) + 2. [iTunes](#itunes) + 3. [MusicBee](#musicbee) +4. [Customization](#customization) + ## Installation There is currently no installer available, however there are prereleases in the [Release](https://github.com/dsafa/audio-band/releases) page that come with a script to install manually. @@ -31,7 +40,7 @@ There is currently no installer available, however there are prereleases in the **IMPORTANT** If nothing happens after selecting `Audio Band` from the toolbars menu or if there are no options in the `Audio Source` menu, some files are being blocked by windows. To fix it, run `unblock.ps1` with powershell. If that doesn't work you can manually fix it by right clicking the files -> properties and clicking unblock. If there are still problems, feel free to post an issue. -## Current Supported Audio Sources (see below for setup instructions and issues) +## Current Supported Audio Sources (see below for setup instructions and issues) - Spotify - iTunes - MusicBee @@ -49,7 +58,6 @@ There is currently no installer available, however there are prereleases in the ![](./screenshots/spotify-app-settings-callback.png) #### Spotify Issues -- The song progress will be out of sync if you change the song's progress. This is due to current limitations making the song progress being tracked locally. This can be fixed if you pause and play again or go to a different song. - The first time you open up Spotify, the current song may not be displayed. This is because Spotify doesn't report any song information if you have no devices playing songs. Just start playing a song and it will start displaying. ### iTunes diff --git a/src/AudioBand.AudioSource/AudioBand.AudioSource.csproj b/src/AudioBand.AudioSource/AudioBand.AudioSource.csproj index 98043841..1ea6633a 100644 --- a/src/AudioBand.AudioSource/AudioBand.AudioSource.csproj +++ b/src/AudioBand.AudioSource/AudioBand.AudioSource.csproj @@ -21,6 +21,8 @@ prompt 4 bin\Debug\AudioBand.AudioSource.xml + ..\AudioBandRules.ruleset + true pdbonly @@ -30,6 +32,8 @@ prompt 4 .none + ..\AudioBandRules.ruleset + bin\Release\AudioBand.AudioSource.xml @@ -41,10 +45,23 @@ + + - + + + + + + stylecop.json + + + + + + \ No newline at end of file diff --git a/src/AudioBand.AudioSource/AudioBand.AudioSource.nuspec b/src/AudioBand.AudioSource/AudioBand.AudioSource.nuspec new file mode 100644 index 00000000..9aca6da7 --- /dev/null +++ b/src/AudioBand.AudioSource/AudioBand.AudioSource.nuspec @@ -0,0 +1,19 @@ + + + + AudioBand.AudioSource + $version$ + $title$ + Brandon Chong + dsafa + MIT + https://github.com/dsafa/audio-band + false + Library to develop audio sources for AudioBand + Copyright 2019 + + + + + + \ No newline at end of file diff --git a/src/AudioBand.AudioSource/AudioSourceSettingAttribute.cs b/src/AudioBand.AudioSource/AudioSourceSettingAttribute.cs index bc7b89a6..2c3c1889 100644 --- a/src/AudioBand.AudioSource/AudioSourceSettingAttribute.cs +++ b/src/AudioBand.AudioSource/AudioSourceSettingAttribute.cs @@ -7,64 +7,39 @@ namespace AudioBand.AudioSource /// /// [AttributeUsage(AttributeTargets.Property)] + [Serializable] public class AudioSourceSettingAttribute : Attribute { /// - /// Name of the validator function - /// - /// The validation function should have the signature (object valueToValidate, string nameOfPropertyBeingSet) -> + /// Initializes a new instance of the class + /// with the setting name. /// - public string ValidatorName { get; set; } + /// Name of the setting. + public AudioSourceSettingAttribute(string name) + { + Name = name; + } /// - /// Name of the setting. + /// Gets or sets the name of the setting. /// public string Name { get; set; } /// - /// Gets or Sets the for the setting. + /// Gets or sets the flags for the setting. /// public SettingOptions Options { get; set; } /// - /// Priority when changing more that one setting. Higher is higher priority + /// Gets or sets the priority when changing more that one setting. + /// + /// A higher value is higher priority. /// public int Priority { get; set; } /// - /// Description of the setting. + /// Gets or sets the description of the setting. /// public string Description { get; set; } - - /// - /// Expose this property as a setting with a given name - /// - /// Name of the setting. - public AudioSourceSettingAttribute(string name) - { - Name = name; - } - } - - /// - /// Flags for audio source settings. - /// - [Flags] - public enum SettingOptions - { - /// - /// Setting is invisible to the user. - /// - Hidden = 1 << 0, - - /// - /// Setting cannot be modified by the user. - /// - ReadOnly = 1 << 1, - - /// - /// Indicates a sensitive setting such as a password, causing a warning to be given. - /// - Sensitive = 1 << 2, } } diff --git a/src/AudioBand.AudioSource/IAudioSource.cs b/src/AudioBand.AudioSource/IAudioSource.cs index 94165d74..519e16be 100644 --- a/src/AudioBand.AudioSource/IAudioSource.cs +++ b/src/AudioBand.AudioSource/IAudioSource.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel.Composition; -using System.Threading; using System.Threading.Tasks; namespace AudioBand.AudioSource @@ -12,70 +11,114 @@ namespace AudioBand.AudioSource public interface IAudioSource { /// - /// Gets the name of the audio source. + /// Occurs when a setting has changed internally. /// - /// The name of the audio source. - string Name { get; } + event EventHandler SettingChanged; /// - /// Gets or sets the used for logging. + /// Occurs when track information has changed. /// - /// Audio source logger that will be injected. - IAudioSourceLogger Logger { get; set; } + event EventHandler TrackInfoChanged; /// - /// Occurs when a setting has changed internally. + /// Occurs when the track play state has changed. if playing; otherwise; /// - event EventHandler SettingChanged; + event EventHandler IsPlayingChanged; /// - /// Occurs when track information has changed. + /// Occurs when the current track progress has changed. /// - event EventHandler TrackInfoChanged; + event EventHandler TrackProgressChanged; /// - /// Occurs when the playback state has changed to playing. + /// Occurs when the volume of the audio source changes. The range of the volume is between 0.0 and 1.0 inclusive. /// - event EventHandler TrackPlaying; + event EventHandler VolumeChanged; /// - /// Occurs when the playback state has changed to paused. + /// Occurs when the shuffle state changes. if shuffle is on; otherwise; /// - event EventHandler TrackPaused; + event EventHandler ShuffleChanged; /// - /// Occurs when the current track progress has changed. + /// Occurs when the repeat mode changes. /// - event EventHandler TrackProgressChanged; + event EventHandler RepeatModeChanged; + + /// + /// Gets the name of the audio source. + /// + /// The name of the audio source. + string Name { get; } /// - /// Called when the audio source is active. + /// Gets or sets the used for logging. /// - Task ActivateAsync(CancellationToken cancellationToken = default(CancellationToken)); + /// Audio source logger that will be injected. + IAudioSourceLogger Logger { get; set; } + + /// + /// Called when the audio source becomes active. + /// + /// A representing the asynchronous activate operation. + Task ActivateAsync(); /// /// Called when the audio source is no longer active. /// - Task DeactivateAsync(CancellationToken cancellationToken = default(CancellationToken)); + /// A representing the asynchronous deactivate operation. + Task DeactivateAsync(); /// /// Called when there is a request to start playback. /// - Task PlayTrackAsync(CancellationToken cancellationToken = default(CancellationToken)); + /// A representing the asynchronous play operation. + Task PlayTrackAsync(); /// /// Called when there is a request to stop playback. /// - Task PauseTrackAsync(CancellationToken cancellationToken = default(CancellationToken)); + /// A representing the asynchronous pause operation. + Task PauseTrackAsync(); /// /// Called when there is a request to skip to the previous track. /// - Task PreviousTrackAsync(CancellationToken cancellationToken = default(CancellationToken)); + /// A representing the asynchronous skip to previous track operation. + Task PreviousTrackAsync(); /// /// Called when there is a request to skip to the next track. /// - Task NextTrackAsync(CancellationToken cancellationToken = default(CancellationToken)); + /// A representing the asynchronous skip to next track operation. + Task NextTrackAsync(); + + /// + /// Called when there is a request to change the volume. + /// + /// The new volume to set. The range is between 0.0 and 1.0 inclusive. + /// A representing the asynchronous set volume operation. + Task SetVolumeAsync(float newVolume); + + /// + /// Called when there is a request to change to current playback progress. + /// + /// The new time to seek to. + /// A representing the asynchronous set playback progress operation. + Task SetPlaybackProgressAsync(TimeSpan newProgress); + + /// + /// Called when there is a request to change the shuffle state. + /// + /// The new shuffle state, if shuffle should be on; otherwise. + /// A representing the asynchronous set shuffle operation. + Task SetShuffleAsync(bool shuffleOn); + + /// + /// Called when there is a request to change the repeat mode. + /// + /// The new . + /// A representing the asynchronous set repeat mode operation. + Task SetRepeatModeAsync(RepeatMode newRepeatMode); } } diff --git a/src/AudioBand.AudioSource/ISupportsRatings.cs b/src/AudioBand.AudioSource/ISupportsRatings.cs new file mode 100644 index 00000000..95374a6f --- /dev/null +++ b/src/AudioBand.AudioSource/ISupportsRatings.cs @@ -0,0 +1,23 @@ +using System; +using System.Threading.Tasks; + +namespace AudioBand.AudioSource +{ + /// + /// Indicates that this audio source supports ratings on tracks. + /// + public interface ISupportsRatings + { + /// + /// Occurs when the the track's rating has changed. + /// + event EventHandler TrackRatingChanged; + + /// + /// Called when a new rating is being set. + /// + /// The new rating of the track + /// A representing the asynchronous set rating operation. + Task SetTrackRatingAsync(TrackRating newRating); + } +} diff --git a/src/AudioBand.AudioSource/Properties/AssemblyInfo.cs b/src/AudioBand.AudioSource/Properties/AssemblyInfo.cs index 94617118..8dcbd9db 100644 --- a/src/AudioBand.AudioSource/Properties/AssemblyInfo.cs +++ b/src/AudioBand.AudioSource/Properties/AssemblyInfo.cs @@ -6,7 +6,7 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("AudioBand.AudioSource")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("Library that provides the interfaces for implementing audio sources")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("AudioBand.AudioSource")] @@ -35,4 +35,4 @@ [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: InternalsVisibleTo("AudioBand")] \ No newline at end of file +[assembly: InternalsVisibleTo("AudioBand")] diff --git a/src/AudioBand.AudioSource/RepeatMode.cs b/src/AudioBand.AudioSource/RepeatMode.cs new file mode 100644 index 00000000..5096b77d --- /dev/null +++ b/src/AudioBand.AudioSource/RepeatMode.cs @@ -0,0 +1,23 @@ +namespace AudioBand.AudioSource +{ + /// + /// Specifies the repeat mode of the audio source. + /// + public enum RepeatMode + { + /// + /// No repeat. + /// + Off, + + /// + /// Repeat the current context. + /// + RepeatContext, + + /// + /// Repeat the current track. + /// + RepeatTrack, + } +} diff --git a/src/AudioBand.AudioSource/SettingChangedEventArgs.cs b/src/AudioBand.AudioSource/SettingChangedEventArgs.cs index 23309c7a..21e102bd 100644 --- a/src/AudioBand.AudioSource/SettingChangedEventArgs.cs +++ b/src/AudioBand.AudioSource/SettingChangedEventArgs.cs @@ -5,20 +5,22 @@ namespace AudioBand.AudioSource /// /// Provides data for a event. /// - public class SettingChangedEventArgs : EventArgs + [Serializable] + public sealed class SettingChangedEventArgs : EventArgs { /// - /// Name of the setting's property that changed. + /// Initializes a new instance of the class + /// with the setting's name. /// - public string PropertyName { get; set; } + /// Name of the setting that changed. + public SettingChangedEventArgs(string settingName) + { + SettingName = settingName; + } /// - /// Creates a new instance of a with the setting name. + /// Gets or sets the name of the setting's property that changed. /// - /// Name of the setting's property that changed. - public SettingChangedEventArgs(string propertyName) - { - PropertyName = propertyName; - } + public string SettingName { get; set; } } } diff --git a/src/AudioBand.AudioSource/SettingOptions.cs b/src/AudioBand.AudioSource/SettingOptions.cs new file mode 100644 index 00000000..9f7360b4 --- /dev/null +++ b/src/AudioBand.AudioSource/SettingOptions.cs @@ -0,0 +1,26 @@ +using System; + +namespace AudioBand.AudioSource +{ + /// + /// Flags for audio source settings. + /// + [Flags] + public enum SettingOptions + { + /// + /// Setting is invisible to the user. + /// + Hidden = 1 << 0, + + /// + /// Setting cannot be modified by the user. + /// + ReadOnly = 1 << 1, + + /// + /// Indicates a sensitive setting such as a password, causing a warning to be given. + /// + Sensitive = 1 << 2, + } +} diff --git a/src/AudioBand.AudioSource/SettingValidationResult.cs b/src/AudioBand.AudioSource/SettingValidationResult.cs deleted file mode 100644 index c7667df4..00000000 --- a/src/AudioBand.AudioSource/SettingValidationResult.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace AudioBand.AudioSource -{ - /// - /// Class that represents the results of validation - /// - internal class SettingValidationResult - { - /// - /// The value is valid - /// - public bool IsValid { get; } - - /// - /// Associated error message if the value was not valid - /// - public string ErrorMessage { get; } - - public SettingValidationResult(bool isValid, string errorMessage = null) - { - IsValid = isValid; - ErrorMessage = errorMessage; - } - - public static SettingValidationResult Fail => new SettingValidationResult(false); - public static SettingValidationResult Success => new SettingValidationResult(true); - } -} diff --git a/src/AudioBand.AudioSource/TrackInfoChangedEventArgs.cs b/src/AudioBand.AudioSource/TrackInfoChangedEventArgs.cs index 4882c4a5..d90a2edc 100644 --- a/src/AudioBand.AudioSource/TrackInfoChangedEventArgs.cs +++ b/src/AudioBand.AudioSource/TrackInfoChangedEventArgs.cs @@ -1,33 +1,86 @@ using System; using System.Drawing; +using System.Runtime.Serialization; namespace AudioBand.AudioSource { - public class TrackInfoChangedEventArgs : EventArgs + /// + /// Event arguments for the event. + /// + [Serializable] + public sealed class TrackInfoChangedEventArgs : EventArgs, ISerializable { /// - /// Track Name + /// Initializes a new instance of the class. + /// + public TrackInfoChangedEventArgs() + { + // Empty + } + + private TrackInfoChangedEventArgs(SerializationInfo info, StreamingContext context) + { + TrackName = info.GetString(nameof(TrackName)); + Artist = info.GetString(nameof(Artist)); + AlbumArt = ByteArrayToImage((byte[])info.GetValue(nameof(AlbumArt), typeof(byte[]))); + Album = info.GetString(nameof(Album)); + TrackLength = (TimeSpan)info.GetValue(nameof(TrackLength), typeof(TimeSpan)); + } + + /// + /// Gets or sets the track Name /// public string TrackName { get; set; } /// - /// Artist + /// Gets or sets the artist. /// public string Artist { get; set; } /// - /// Album art image. If null, a placeholder will be used + /// Gets or sets the album art image. If , a placeholder will be used. /// public Image AlbumArt { get; set; } /// - /// Album name + /// Gets or sets the album name. /// public string Album { get; set; } /// - /// Length of the track + /// Gets or sets the length of the track /// public TimeSpan TrackLength { get; set; } + + /// + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(nameof(TrackName), TrackName); + info.AddValue(nameof(Artist), Artist); + info.AddValue(nameof(AlbumArt), ImageToByteArray(AlbumArt), typeof(byte[])); + info.AddValue(nameof(Album), Album); + info.AddValue(nameof(TrackLength), TrackLength, typeof(TimeSpan)); + } + + private static byte[] ImageToByteArray(Image image) + { + if (image == null) + { + return null; + } + + var copy = new Bitmap(image); + return new ImageConverter().ConvertTo(copy, typeof(byte[])) as byte[]; + } + + private static Image ByteArrayToImage(byte[] data) + { + if (data == null) + { + return null; + } + + return new ImageConverter().ConvertFrom(data) as Image; + } } } diff --git a/src/AudioBand.AudioSource/TrackRating.cs b/src/AudioBand.AudioSource/TrackRating.cs new file mode 100644 index 00000000..275af4b2 --- /dev/null +++ b/src/AudioBand.AudioSource/TrackRating.cs @@ -0,0 +1,23 @@ +namespace AudioBand.AudioSource +{ + /// + /// Options for the rating of a track. + /// + public enum TrackRating + { + /// + /// No rating. + /// + None, + + /// + /// The track has a positive rating. + /// + Positive, + + /// + /// The song has a negative rating. + /// + Negative + } +} diff --git a/src/AudioBand.AudioSource/content/AudioSource.manifest b/src/AudioBand.AudioSource/content/AudioSource.manifest new file mode 100644 index 00000000..550c2b51 --- /dev/null +++ b/src/AudioBand.AudioSource/content/AudioSource.manifest @@ -0,0 +1 @@ +AudioSource = "ASSEMBLY_NAME_HERE.dll" \ No newline at end of file diff --git a/src/AudioBand.AudioSource/content/readme.txt b/src/AudioBand.AudioSource/content/readme.txt new file mode 100644 index 00000000..5a5fa139 --- /dev/null +++ b/src/AudioBand.AudioSource/content/readme.txt @@ -0,0 +1,4 @@ +AudioBand.AudioSource readme + +1. Fill in the AudioSource.manifest file +2. Deploy your audio source in a folder under the Audiosources folder of AudioBand. The folder should contain the manifest file and NOT contain this AudioBand.AudioSource dll \ No newline at end of file diff --git a/src/AudioBand.AudioSource/packages.config b/src/AudioBand.AudioSource/packages.config new file mode 100644 index 00000000..119108d1 --- /dev/null +++ b/src/AudioBand.AudioSource/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/AudioBand.Test/AudioBand.Test.csproj b/src/AudioBand.Test/AudioBand.Test.csproj index 33eceff1..1035324d 100644 --- a/src/AudioBand.Test/AudioBand.Test.csproj +++ b/src/AudioBand.Test/AudioBand.Test.csproj @@ -1,6 +1,6 @@  - + Debug @@ -29,6 +29,8 @@ DEBUG;TRACE prompt 4 + + pdbonly @@ -37,35 +39,41 @@ TRACE prompt 4 + + ..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll - ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + ..\packages\MSTest.TestFramework.1.4.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + ..\packages\MSTest.TestFramework.1.4.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - ..\packages\Moq.4.10.0\lib\net45\Moq.dll + ..\packages\Moq.4.10.1\lib\net45\Moq.dll - - ..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll + + ..\packages\System.Threading.Tasks.Extensions.4.5.2\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll + @@ -74,6 +82,10 @@ + + {30F2BFEA-788A-494D-88E7-F2070528EBEA} + AudioBand.AudioSource + {b69832ad-8373-47ac-a52a-183238903896} AudioBand @@ -85,8 +97,8 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + - + \ No newline at end of file diff --git a/src/AudioBand.Test/SettingsMapping.cs b/src/AudioBand.Test/SettingsMapping.cs index 8f690867..aa1e911b 100644 --- a/src/AudioBand.Test/SettingsMapping.cs +++ b/src/AudioBand.Test/SettingsMapping.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; -using AudioBand.Settings.Models.v2; +using AudioBand.Settings.Models.V2; using AudioBand.Settings; using AudioBand.Models; diff --git a/src/AudioBand.Test/SettingsMigrationTests.cs b/src/AudioBand.Test/SettingsMigrationTests.cs index 7c8aa138..ed24a14a 100644 --- a/src/AudioBand.Test/SettingsMigrationTests.cs +++ b/src/AudioBand.Test/SettingsMigrationTests.cs @@ -2,11 +2,11 @@ using System.Drawing; using AudioBand.Models; using AudioBand.Settings.Migrations; -using AudioBand.Settings.Models.v1; -using V1Settings = AudioBand.Settings.Models.v1.AudioBandSettings; -using V2Settings = AudioBand.Settings.Models.v2.Settings; +using AudioBand.Settings.Models.V1; +using V1Settings = AudioBand.Settings.Models.V1.AudioBandSettings; +using V2Settings = AudioBand.Settings.Models.V2.Settings; using Microsoft.VisualStudio.TestTools.UnitTesting; -using AudioSourceSetting = AudioBand.Settings.Models.v1.AudioSourceSetting; +using AudioSourceSetting = AudioBand.Settings.Models.V1.AudioSourceSetting; namespace AudioBand.Test { diff --git a/src/AudioBand.Test/SettingsViewModelsTests.cs b/src/AudioBand.Test/SettingsViewModelsTests.cs new file mode 100644 index 00000000..d385036f --- /dev/null +++ b/src/AudioBand.Test/SettingsViewModelsTests.cs @@ -0,0 +1,133 @@ +using AudioBand.AudioSource; +using AudioBand.Models; +using AudioBand.ViewModels; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace AudioBand.Test +{ + [TestClass] + public class SettingsViewModelsTests + { + private Mock _audioSourceMock; + + + [TestInitialize] + public void Init() + { + _audioSourceMock = new Mock(); + } + + [TestMethod] + public void NoMatchingSettings() + { + _audioSourceMock.SetupGet(s => s.Settings).Returns(new List()); + + var name = "test"; + var keyVals = new List + { + new AudioSourceSetting { Name = "key1", Value = "val1" }, + new AudioSourceSetting { Name = "key2", Value = "val2" } + }; + var settings = new AudioSourceSettings { AudioSourceName = name, Settings = keyVals }; + + var vm = new AudioSourceSettingsVM(settings, _audioSourceMock.Object); + + Assert.AreEqual(0, vm.Settings.Count); + Assert.AreEqual(name, vm.AudioSourceName); + } + + [TestMethod] + public void MatchingSettingsShouldCreateVmsInOrder() + { + var setting1 = "Setting1"; + var setting2 = "setting2"; + object val1 = "val1"; + object val2 = 2; + + _audioSourceMock.SetupGet(s => s.Settings).Returns(new List + { + new AudioSourceSettingAttribute(setting1), + new AudioSourceSettingAttribute(setting2), + }); + + _audioSourceMock.SetupGet(s => s[It.Is(x => x == setting1)]).Returns(val1); + _audioSourceMock.SetupGet(s => s[It.Is(x => x == setting2)]).Returns(val2); + + var settingModels = new List + { + new AudioSourceSetting { Name = setting1, Value = val1 }, + new AudioSourceSetting { Name = setting2, Value = val2 } + }; + + var settings = new AudioSourceSettings { Settings = settingModels }; + var vm = new AudioSourceSettingsVM(settings, _audioSourceMock.Object); + + Assert.AreEqual(settingModels.Count, vm.Settings.Count); + Assert.AreEqual(settingModels[0].Name, vm.Settings[0].Name); + Assert.AreEqual(settingModels[1].Name, vm.Settings[1].Name); + + Assert.AreEqual(settingModels[0].Value, vm.Settings[0].Value); + Assert.AreEqual(settingModels[1].Value, vm.Settings[1].Value); + } + + [TestMethod] + public void AudioSourceSettingUpdatesAreHandled() + { + var setting = "setting"; + + _audioSourceMock.SetupGet(s => s.Settings).Returns(new List { new AudioSourceSettingAttribute(setting) }); + var settingModel = new AudioSourceSetting { Name = setting }; + var settings = new AudioSourceSettings + { + Settings = new List { settingModel } + }; + + var vm = new AudioSourceSettingsVM(settings, _audioSourceMock.Object); + + object newSettingValue = 1; + _audioSourceMock.SetupGet(s => s[It.Is(x => x == setting)]).Returns(newSettingValue); + _audioSourceMock.Raise(s => s.SettingChanged += null, new SettingChangedEventArgs(setting)); + + Assert.AreEqual(settingModel.Value, newSettingValue); + } + + [TestMethod] + public void AudioSourceSettingsCalledInPriority() + { + var setting1 = new AudioSourceSettingAttribute("test1") { Priority = 10 }; + var setting2 = new AudioSourceSettingAttribute("test2") { Priority = 5 }; + var setting3 = new AudioSourceSettingAttribute("test3") { Priority = 20 }; + + _audioSourceMock.SetupAllProperties(); + _audioSourceMock.SetupGet(m => m.Settings).Returns(new List { setting1, setting2, setting3 }); + + var settings = new AudioSourceSettings + { + Settings = new List + { + new AudioSourceSetting {Name = setting1.Name}, + new AudioSourceSetting {Name = setting2.Name}, + new AudioSourceSetting {Name = setting3.Name}, + } + }; + + var s = new MockSequence(); + _audioSourceMock.InSequence(s).SetupSet(source => source[It.Is(x => x == setting3.Name)] = null); + _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 AudioSourceSettingsVM(settings, _audioSourceMock.Object); + + _audioSourceMock.VerifySet(source => source[setting3.Name] = null); + _audioSourceMock.VerifySet(source => source[setting1.Name] = null); + _audioSourceMock.VerifySet(source => source[setting2.Name] = null); + } + } +} diff --git a/src/AudioBand.Test/app.config b/src/AudioBand.Test/app.config index a18c8d72..71eae980 100644 --- a/src/AudioBand.Test/app.config +++ b/src/AudioBand.Test/app.config @@ -4,7 +4,15 @@ - + + + + + + + + + diff --git a/src/AudioBand.Test/packages.config b/src/AudioBand.Test/packages.config index 06c17081..173da68a 100644 --- a/src/AudioBand.Test/packages.config +++ b/src/AudioBand.Test/packages.config @@ -1,9 +1,10 @@  - - - - - + + + + + + \ No newline at end of file diff --git a/src/AudioBand.sln b/src/AudioBand.sln index 87a0d331..dfe6926e 100644 --- a/src/AudioBand.sln +++ b/src/AudioBand.sln @@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iTunesAudioSource", "iTunes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MusicBeeAudioSource", "MusicBeeAudioSource\MusicBeeAudioSource.csproj", "{741DB79C-921D-4D91-85F1-CD10C746F46E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AudioSourceHost", "AudioSourceHost\AudioSourceHost.csproj", "{D3F92C3E-E546-4A6B-ADA2-FACD95E229F7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,6 +47,10 @@ Global {741DB79C-921D-4D91-85F1-CD10C746F46E}.Debug|Any CPU.Build.0 = Debug|Any CPU {741DB79C-921D-4D91-85F1-CD10C746F46E}.Release|Any CPU.ActiveCfg = Release|Any CPU {741DB79C-921D-4D91-85F1-CD10C746F46E}.Release|Any CPU.Build.0 = Release|Any CPU + {D3F92C3E-E546-4A6B-ADA2-FACD95E229F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3F92C3E-E546-4A6B-ADA2-FACD95E229F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3F92C3E-E546-4A6B-ADA2-FACD95E229F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3F92C3E-E546-4A6B-ADA2-FACD95E229F7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/AudioBand/AudioBand.csproj b/src/AudioBand/AudioBand.csproj index 227a1ef1..f497f803 100644 --- a/src/AudioBand/AudioBand.csproj +++ b/src/AudioBand/AudioBand.csproj @@ -22,6 +22,9 @@ DEBUG;TRACE prompt 4 + ..\AudioBandRules.ruleset + true + bin\Debug\AudioBand.xml pdbonly @@ -31,6 +34,7 @@ prompt 4 .none + ..\AudioBandRules.ruleset @@ -58,18 +62,17 @@ ..\packages\MahApps.Metro.IconPacks.Material.2.3.0\lib\net46\MahApps.Metro.IconPacks.Material.dll - ..\packages\Nett.0.10.0\lib\Net40\Nett.dll + ..\packages\Nett.0.10.1\lib\Net40\Nett.dll ..\packages\NLog.4.5.11\lib\net45\NLog.dll - - ..\packages\Svg.2.3.0\lib\net35\Svg.dll + + ..\packages\Svg.2.4.1\lib\Svg.dll - @@ -78,7 +81,6 @@ - ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll @@ -97,15 +99,17 @@ - + + - + + UserControl MainControl.cs @@ -146,20 +150,21 @@ + + + - - + Component - @@ -173,6 +178,7 @@ AudioSourceSettingsView.xaml + @@ -210,8 +216,8 @@ + - @@ -233,7 +239,7 @@ Component - + True @@ -257,11 +263,17 @@ + + stylecop.json + UserControl MainControl.cs + + PreserveNewest + @@ -286,6 +298,10 @@ {30f2bfea-788a-494d-88e7-f2070528ebea} AudioBand.AudioSource + + {D3F92C3E-E546-4A6B-ADA2-FACD95E229F7} + AudioSourceHost + @@ -349,7 +365,10 @@ MSBuild:Compile - + + + + if "$(ConfigurationName)" == "Debug" ( diff --git a/src/AudioBand/AudioSource/AudioSourceLoader.cs b/src/AudioBand/AudioSource/AudioSourceLoader.cs deleted file mode 100644 index 5f65c816..00000000 --- a/src/AudioBand/AudioSource/AudioSourceLoader.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.ComponentModel.Composition.Hosting; -using System.IO; -using System.Linq; -using Nett; -using NLog; - -namespace AudioBand.AudioSource -{ - internal class AudioSourceLoader - { - [ImportMany(AllowRecomposition = true)] - public IEnumerable AudioSources { get; private set; } = new List(); - - public event EventHandler AudioSourcesChanged; - - private const string PluginFolderName = "AudioSources"; - private const string ManifestFileName = "AudioSource.manifest"; - private static readonly string PluginFolderPath = Path.Combine(DirectoryHelper.BaseDirectory, PluginFolderName); - private static readonly ILogger Logger = LogManager.GetCurrentClassLogger(); - - public void LoadAudioSources() - { - Logger.Debug($"Searching for audio sources in path `{PluginFolderPath}`"); - - var catalog = new AggregateCatalog(); - var directories = Directory.EnumerateDirectories(PluginFolderPath, "*", SearchOption.TopDirectoryOnly); - - foreach (var directory in directories) - { - Logger.Debug($"Searching in {directory} for {ManifestFileName}"); - - var manifest = Directory.GetFiles(directory, ManifestFileName, SearchOption.TopDirectoryOnly).FirstOrDefault(); - if (manifest == null) - { - continue; - } - - var manifestData = Toml.ReadFile(manifest); - Logger.Debug($"Reading {manifest}. Audio sources: [{string.Join("," , manifestData.AudioSources)}]"); - - foreach (var audioSourceFileName in manifestData.AudioSources) - { - var audioSourcePath = Path.Combine(directory, audioSourceFileName); - catalog.Catalogs.Add(new AssemblyCatalog(audioSourcePath)); - } - } - - var container = new CompositionContainer(catalog); - container.ComposeParts(this); - - foreach (var audioSource in AudioSources) - { - audioSource.Logger = new AudioSourceLogger(audioSource.Name); - } - } - - private class Manifest - { - public List AudioSources { get; set; } - } - } -} diff --git a/src/AudioBand/AudioSource/AudioSourceManager.cs b/src/AudioBand/AudioSource/AudioSourceManager.cs new file mode 100644 index 00000000..129a080f --- /dev/null +++ b/src/AudioBand/AudioSource/AudioSourceManager.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using NLog; + +namespace AudioBand.AudioSource +{ + /// + /// Detects and loads audio sources. + /// + internal class AudioSourceManager + { + private const string PluginFolderName = "AudioSources"; + private static readonly string PluginFolderPath = Path.Combine(DirectoryHelper.BaseDirectory, PluginFolderName); + private static readonly ILogger Logger = LogManager.GetCurrentClassLogger(); + private static readonly object _audioSourcesLock = new object(); + private readonly List _uninitializedProxies = new List(); + + /// + /// Gets the list of audio sources available. + /// + public ObservableCollection AudioSources { get; private set; } = new ObservableCollection(); + + /// + /// Load all audio sources. + /// + public void LoadAudioSources() + { + Logger.Debug("Loading audio sources"); + foreach (var dir in Directory.EnumerateDirectories(PluginFolderPath)) + { + try + { + var proxy = new AudioSourceProxy(dir); + AudioSources.Add(proxy); + } + catch (Exception e) + { + Logger.Error(e, $"Error creating proxy for {dir}"); + } + } + } + } +} diff --git a/src/AudioBand/AudioSource/AudioSourceProxy.cs b/src/AudioBand/AudioSource/AudioSourceProxy.cs new file mode 100644 index 00000000..160a9316 --- /dev/null +++ b/src/AudioBand/AudioSource/AudioSourceProxy.cs @@ -0,0 +1,355 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using AudioSourceHost; +using NLog; + +namespace AudioBand.AudioSource +{ + /// + /// Proxy class for an IAudioSource in another process. + /// + internal class AudioSourceProxy : IInternalAudioSource + { + private readonly ILogger _logger; + private readonly object _isActivatedLock = new object(); + private readonly Dictionary _settingsCache = new Dictionary(); + private readonly string _directory; + private bool _isActivated; + private AppDomain _appDomain; + private AudioSourceWrapper _wrapper; + + /// + /// Initializes a new instance of the class + /// with the directory and the host service. + /// + /// The audio source directory. + public AudioSourceProxy(string directory) + { + _directory = directory; + var directoryName = new DirectoryInfo(directory).Name; + _logger = LogManager.GetLogger($"AudioSourceProxy({directoryName})"); + + AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve; + + var domainSetupInfo = new AppDomainSetup + { + ApplicationName = $"AudioSourceHost({directoryName})", + ApplicationBase = DirectoryHelper.BaseDirectory, + }; + + _appDomain = AppDomain.CreateDomain(directoryName, null, domainSetupInfo); + CreateWrapper(); + } + + /// + public event EventHandler SettingChanged; + + /// + public event EventHandler TrackInfoChanged; + + /// + public event EventHandler IsPlayingChanged; + + /// + public event EventHandler TrackProgressChanged; + + /// + public event EventHandler VolumeChanged; + + /// + public event EventHandler ShuffleChanged; + + /// + public event EventHandler RepeatModeChanged; + + /// + public string Name + { + get + { + try + { + return _wrapper.Name; + } + catch (Exception e) + { + _logger.Error(e, "Error trying to get the name"); + throw; + } + } + } + + /// + public IAudioSourceLogger Logger { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + /// + /// Gets the settings that the audio source has. + /// + public List Settings { get; private set; } = new List(); + + private bool IsActivated + { + get + { + lock (_isActivatedLock) + { + return _isActivated; + } + } + + set + { + lock (_isActivatedLock) + { + _isActivated = value; + } + } + } + + /// + /// Get or set a setting. + /// + /// Name of the setting + /// The setting value. + public object this[string settingName] + { + get + { + try + { + var settingValue = _wrapper.GetSettingValue(settingName); + _settingsCache[settingName] = settingValue; + return settingValue; + } + catch (Exception e) + { + _logger.Error(e, "Error trying to get setting."); + throw; + } + } + + set + { + try + { + _settingsCache[settingName] = value; + _wrapper.UpdateSetting(settingName, value); + } + catch (Exception e) + { + _logger.Error(e, "Error tring to update setting."); + throw; + } + } + } + + /// + public async Task ActivateAsync() + { + Debug.Assert(!IsActivated, "Audio source already activated"); + if (await CallWrapperAsync(_wrapper.Activate)) + { + IsActivated = true; + } + else + { + throw new InvalidOperationException("Unable to activate audiosource"); + } + } + + /// + public async Task DeactivateAsync() + { + Debug.Assert(IsActivated, "Audio source is not activated"); + await CallWrapperAsync(_wrapper.Deactivate); + IsActivated = false; + } + + /// + public async Task NextTrackAsync() + { + if (!IsActivated) + { + return; + } + + await CallWrapperAsync(_wrapper.NextTrack); + } + + /// + public async Task PauseTrackAsync() + { + if (!IsActivated) + { + return; + } + + await CallWrapperAsync(_wrapper.PauseTrack); + } + + /// + public async Task PlayTrackAsync() + { + if (!IsActivated) + { + return; + } + + await CallWrapperAsync(_wrapper.PlayTrack); + } + + /// + public async Task PreviousTrackAsync() + { + if (!IsActivated) + { + return; + } + + await CallWrapperAsync(_wrapper.PreviousTrack); + } + + /// + public async Task SetVolumeAsync(float newVolume) + { + if (!IsActivated) + { + return; + } + + await CallWrapperAsync(_wrapper.SetVolume, newVolume); + } + + /// + public async Task SetPlaybackProgressAsync(TimeSpan newProgress) + { + if (!IsActivated) + { + return; + } + + await CallWrapperAsync(_wrapper.SetPlayback, newProgress); + } + + /// + public async Task SetShuffleAsync(bool shuffleOn) + { + if (!IsActivated) + { + return; + } + + await CallWrapperAsync(_wrapper.SetShuffle, shuffleOn); + } + + /// + public async Task SetRepeatModeAsync(RepeatMode newRepeatMode) + { + if (!IsActivated) + { + return; + } + + await CallWrapperAsync(_wrapper.SetRepeatMode, newRepeatMode); + } + + /// + public Type GetSettingType(string settingName) + { + return _wrapper.GetSettingType(settingName); + } + + // Use load from context + private static Assembly AssemblyResolve(object sender, ResolveEventArgs e) + { + string shortName = e.Name.Substring(0, e.Name.IndexOf(',')); + string fileName = Path.Combine(DirectoryHelper.BaseDirectory, shortName + ".dll"); + return File.Exists(fileName) ? Assembly.LoadFrom(fileName) : null; + } + + private async Task Restart() + { + // Assuming this won't throw because it was created previously without errors + CreateWrapper(); + + // re-activate if the host was restarted + if (IsActivated) + { + // If activation fails then don't auto restart otherwise it will be a loop + await CallWrapperAsync(_wrapper.Activate, autoRestart: false); + } + } + + private void LoadAudioSourceSettings() + { + try + { + Settings = _wrapper.Settings; + + // apply the settings again, if host restarted, the cache should be populated + foreach (var keyVal in _settingsCache) + { + _wrapper.UpdateSetting(keyVal.Key, keyVal.Value); + } + } + catch (Exception e) + { + _logger.Error(e, "Error loading settings"); + } + } + + private void CreateWrapper() + { + var wrapperType = typeof(AudioSourceWrapper); + var dllPath = Path.Combine(DirectoryHelper.BaseDirectory, wrapperType.Assembly.GetName().Name + ".dll"); + _wrapper = (AudioSourceWrapper)_appDomain.CreateInstanceFromAndUnwrap(dllPath, wrapperType.FullName); + + if (!_wrapper.Initialize(_directory)) + { + throw new InvalidOperationException("Unable to initialize wrapper"); + } + + _wrapper.SettingChanged += new MarshaledEventHandler(e => SettingChanged?.Invoke(this, e)).Handler; + _wrapper.TrackInfoChanged += new MarshaledEventHandler(e => TrackInfoChanged?.Invoke(this, e)).Handler; + _wrapper.IsPlayingChanged += new MarshaledEventHandler(e => IsPlayingChanged?.Invoke(this, e)).Handler; + _wrapper.TrackProgressChanged += new MarshaledEventHandler(e => TrackProgressChanged?.Invoke(this, e)).Handler; + _wrapper.VolumeChanged += new MarshaledEventHandler(e => VolumeChanged?.Invoke(this, e)).Handler; + _wrapper.ShuffleChanged += new MarshaledEventHandler(e => ShuffleChanged?.Invoke(this, e)).Handler; + _wrapper.RepeatModeChanged += new MarshaledEventHandler(e => RepeatModeChanged?.Invoke(this, e)).Handler; + + LoadAudioSourceSettings(); + } + + private async Task CallWrapperAsync(Action wrapperAction, bool autoRestart = true) + { + try + { + var tcs = new MarshaledTaskCompletionSource(); + wrapperAction(tcs); + + await tcs.Task.ConfigureAwait(false); + return true; + } + catch (Exception e) + { + _logger.Error(e, $"Unexpected error when calling the wrapper, recreating wrapper"); + if (autoRestart) + { + await Restart(); + } + + return false; + } + } + + private async Task CallWrapperAsync(Action wrapperAction, TArg arg, bool autoRestart = true) + { + return await CallWrapperAsync((tcs) => wrapperAction(arg, tcs), autoRestart); + } + } +} diff --git a/src/AudioBand/AudioSource/AudioSourceSettingInfo.cs b/src/AudioBand/AudioSource/AudioSourceSettingInfo.cs deleted file mode 100644 index ab3a79a4..00000000 --- a/src/AudioBand/AudioSource/AudioSourceSettingInfo.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System; -using System.ComponentModel; -using System.Reflection; -using NLog; - -namespace AudioBand.AudioSource -{ - /// - /// Contains information for an audio source setting - /// - internal class AudioSourceSettingInfo - { - private static readonly ILogger Logger = LogManager.GetCurrentClassLogger(); - - /// - /// Audio source that this setting belongs to - /// - public IAudioSource Source { get; } - - /// - /// Property that the is applied to. - /// - public PropertyInfo Property { get; } - - /// - /// attached to the property. - /// - public AudioSourceSettingAttribute Attribute { get; } - - /// - /// Type of the property. - /// - public Type PropertyType { get; } - - public AudioSourceSettingInfo(IAudioSource source, PropertyInfo property, AudioSourceSettingAttribute attribute) - { - Source = source; - Property = property; - Attribute = attribute; - PropertyType = property.PropertyType; - } - - /// - /// Validate a value for this setting. - /// - /// Value to validate. - /// Result of validation. - public SettingValidationResult ValidateSetting(object value) - { - var validationMethodName = Attribute.ValidatorName; - if (validationMethodName == null) - { - return new SettingValidationResult(true); - } - - var method = Source.GetType().GetMethod(validationMethodName); - if (method == null) - { - Logger.Error($"Validation method {validationMethodName} not found for {Source}"); - return new SettingValidationResult(false, "Error with validation. See log for more details."); - } - - return (SettingValidationResult)method.Invoke(Source, new[] { value, Property.Name }); - } - - - /// - /// Updates the audio source with the new value. - /// - /// New value. - public void UpdateAudioSource(object value) - { - try - { - var newValue = ConvertToSettingType(value); - Property.SetValue(Source, newValue); - } - catch (Exception e) - { - Logger.Error($"Error occured while change audio source settings. value: {value}, Exception: {e}"); - throw; - } - } - - /// - /// Gets the current setting value from the audio source. - /// - /// - public object GetValue() - { - return Property.GetValue(Source); - } - - /// - /// Convert the value to the type of the setting. - /// - /// Value to convert - /// The value converted to the type of the setting. - public object ConvertToSettingType(object value) - { - if (value != null && value.GetType() != PropertyType) - { - var converter = TypeDescriptor.GetConverter(value.GetType()); - if (converter.CanConvertTo(PropertyType)) - { - return converter.ConvertTo(value, PropertyType); - } - - if (TryChangeType(value, out var converted)) - { - return converted; - } - - Logger.Error($"Unable to convert value to the setting's type. setting: `{Attribute.Name}` using value `{value}`. Setting type: `{PropertyType}`, value type: `{value.GetType()}`"); - throw new ArgumentException(); - } - - return value; - } - - private bool TryChangeType(object value, out object converted) - { - try - { - converted = Convert.ChangeType(value, PropertyType); - return true; - } - catch (Exception e) - { - Logger.Error(e); - converted = null; - return false; - } - } - } -} diff --git a/src/AudioBand/AudioSource/IInternalAudioSource.cs b/src/AudioBand/AudioSource/IInternalAudioSource.cs new file mode 100644 index 00000000..07e83ae2 --- /dev/null +++ b/src/AudioBand/AudioSource/IInternalAudioSource.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; + +namespace AudioBand.AudioSource +{ + /// + /// Internal extension to that exposes additional methods. + /// + internal interface IInternalAudioSource : IAudioSource + { + /// + /// Gets the settings that the audio source exposes. + /// + List Settings { get; } + + /// + /// Gets or sets a setting. + /// + /// The name of the setting. + /// The value of the setting. + object this[string settingName] { get; set; } + + /// + /// Gets the type of the setting. + /// + /// Name of the setting. + /// The type of the setting. + Type GetSettingType(string settingName); + } +} diff --git a/src/AudioBand/Commands/AsyncRelayCommand.cs b/src/AudioBand/Commands/AsyncRelayCommand.cs index 23720310..35f011f0 100644 --- a/src/AudioBand/Commands/AsyncRelayCommand.cs +++ b/src/AudioBand/Commands/AsyncRelayCommand.cs @@ -7,20 +7,23 @@ namespace AudioBand.Commands /// /// Basic async command. /// - /// + /// Type of the command parameter. internal class AsyncRelayCommand : ICommand { private readonly Func _execute; private readonly Predicate _canExecute; /// - /// Create an instance of with a delegate to execute. + /// Initializes a new instance of the class + /// with a delegate to be executed. /// /// Action to execute. - public AsyncRelayCommand(Func execute) : this(execute, null) { } + public AsyncRelayCommand(Func execute) + : this(execute, null) { } /// - /// Create an instace of with a delegate to execute and a predicate to determine if it can be executed. + /// Initializes a new instance of the class + /// with a delegate to be executed and a predicate to determine if it can be executed. /// /// Async delegate to execute. /// Predicate to check if the commmand can execute. @@ -30,12 +33,6 @@ public AsyncRelayCommand(Func execute, Predicate canExecute) _canExecute = canExecute; } - /// - public bool CanExecute(object parameter) - { - return _canExecute?.Invoke((T)parameter) ?? true; - } - /// public event EventHandler CanExecuteChanged { @@ -43,6 +40,12 @@ public event EventHandler CanExecuteChanged remove => CommandManager.RequerySuggested -= value; } + /// + public bool CanExecute(object parameter) + { + return _canExecute?.Invoke((T)parameter) ?? true; + } + /// public async void Execute(object parameter) { diff --git a/src/AudioBand/Commands/RelayCommand.cs b/src/AudioBand/Commands/RelayCommand.cs index 3554c235..744f6a2d 100644 --- a/src/AudioBand/Commands/RelayCommand.cs +++ b/src/AudioBand/Commands/RelayCommand.cs @@ -1,65 +1,25 @@ using System; -using System.Windows.Input; namespace AudioBand.Commands { - /// - public class RelayCommand : ICommand - { - private readonly Action _execute; - private readonly Predicate _canExecute; - - /// - /// Create an instance of with a delegate to execute. - /// - /// Action to execute. - public RelayCommand(Action execute) : this(execute, null) {} - - /// - /// Create an instace of with a delegate to execute and a predicate to determine if it can be executed. - /// - /// Action to execute. - /// Predicate to check if the commmand can be execute. - public RelayCommand(Action execute, Predicate canExecute) - { - _execute = execute ?? throw new ArgumentNullException(nameof(execute)); - _canExecute = canExecute; - } - - /// - public bool CanExecute(object parameter) - { - return _canExecute?.Invoke((T)parameter) ?? true; - } - - /// - public event EventHandler CanExecuteChanged - { - add => CommandManager.RequerySuggested += value; - remove => CommandManager.RequerySuggested -= value; - } - - /// - public void Execute(object parameter) - { - _execute((T)parameter); - } - } - /// public class RelayCommand : RelayCommand { /// - /// Create instance of with a delefate to execute. + /// Initializes a new instance of the class. + /// with an action to be executed. /// /// Action to execute. - public RelayCommand(Action execute) : base(execute) {} + public RelayCommand(Action execute) + : base(execute) { } /// - /// Create instance of with a delegate to execute and a predicate to determine if it can be executed. + /// Initializes a new instance of the class. + /// with an action to be executed and a predicate to determine if it can be executed. /// /// Action to execute /// Predicate to check if the command can be executed. - public RelayCommand(Action execute, Predicate canExecute) : base(execute, canExecute) {} + public RelayCommand(Action execute, Predicate canExecute) + : base(execute, canExecute) { } } } diff --git a/src/AudioBand/Commands/RelayCommand{T}.cs b/src/AudioBand/Commands/RelayCommand{T}.cs new file mode 100644 index 00000000..4fad2809 --- /dev/null +++ b/src/AudioBand/Commands/RelayCommand{T}.cs @@ -0,0 +1,51 @@ +using System; +using System.Windows.Input; + +namespace AudioBand.Commands +{ + /// + public class RelayCommand : ICommand + { + private readonly Action _execute; + private readonly Predicate _canExecute; + + /// + /// Initializes a new instance of the class. + /// with an action to be executed. + /// + /// Action to execute. + public RelayCommand(Action execute) + : this(execute, null) { } + + /// + /// Initializes a new instance of the class. + /// with an action to be executed and a predicate to determine if it can be executed. + /// + /// Action to execute. + /// Predicate to check if the commmand can be executed. + public RelayCommand(Action execute, Predicate canExecute) + { + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _canExecute = canExecute; + } + + /// + public event EventHandler CanExecuteChanged + { + add => CommandManager.RequerySuggested += value; + remove => CommandManager.RequerySuggested -= value; + } + + /// + public bool CanExecute(object parameter) + { + return _canExecute?.Invoke((T)parameter) ?? true; + } + + /// + public void Execute(object parameter) + { + _execute((T)parameter); + } + } +} diff --git a/src/AudioBand/DirectoryHelper.cs b/src/AudioBand/DirectoryHelper.cs index de22eabf..555e7d24 100644 --- a/src/AudioBand/DirectoryHelper.cs +++ b/src/AudioBand/DirectoryHelper.cs @@ -3,9 +3,15 @@ namespace AudioBand { + /// + /// Provides directory helper functions. + /// internal static class DirectoryHelper { - // Since this assembly is loaded by explorer we use this to get the directory + /// + /// Gets the base directory of the assembly. + /// + /// Since this assembly is loaded by explorer we use this to get the directory. public static string BaseDirectory => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); } } diff --git a/src/AudioBand/Extensions/AudioSourceExtenstions.cs b/src/AudioBand/Extensions/AudioSourceExtenstions.cs deleted file mode 100644 index dc9086dd..00000000 --- a/src/AudioBand/Extensions/AudioSourceExtenstions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using AudioBand.AudioSource; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace AudioBand.Extensions -{ - internal static class AudioSourceExtenstions - { - /// - /// Get settings exposed by the audio source. - /// - /// Audio source. - /// A list of settings. - public static List GetSettings(this IAudioSource audiosource) - { - return audiosource.GetType().GetProperties() - .Where(prop => Attribute.IsDefined(prop, typeof(AudioSourceSettingAttribute))) - .Select(prop => new AudioSourceSettingInfo(audiosource, prop, prop.GetCustomAttribute())) - .ToList(); - } - } -} diff --git a/src/AudioBand/Extensions/BindingExtensions.cs b/src/AudioBand/Extensions/BindingExtensions.cs index b228d8a8..b2fc1722 100644 --- a/src/AudioBand/Extensions/BindingExtensions.cs +++ b/src/AudioBand/Extensions/BindingExtensions.cs @@ -1,8 +1,12 @@ using System; +using System.ComponentModel; using System.Windows.Forms; namespace AudioBand.Extensions { + /// + /// Extensions for . + /// internal static class BindingExtensions { /// @@ -18,8 +22,8 @@ public static T As(this Binding binding) throw new ArgumentNullException(nameof(binding)); } - var manager = binding.BindingManagerBase; - var itemProperty = manager.GetItemProperties().Find(binding.BindingMemberInfo.BindingField, true); + BindingManagerBase manager = binding.BindingManagerBase; + PropertyDescriptor itemProperty = manager.GetItemProperties().Find(binding.BindingMemberInfo.BindingField, true); return (T)itemProperty.GetValue(manager.Current); } diff --git a/src/AudioBand/Extensions/ColorExtensions.cs b/src/AudioBand/Extensions/ColorExtensions.cs index 5ab9f2f0..8fdd44dd 100644 --- a/src/AudioBand/Extensions/ColorExtensions.cs +++ b/src/AudioBand/Extensions/ColorExtensions.cs @@ -4,6 +4,9 @@ namespace AudioBand.Extensions { + /// + /// Extensions for . + /// internal static class ColorExtensions { private static readonly List<(Color color, string name)> _colors; @@ -16,10 +19,15 @@ static ColorExtensions() .ToList(); } + /// + /// Get the known name of the color if available otherwise get the hex code. + /// + /// Color to get the name of. + /// The known name or hex value as a string. public static string GetColorName(this Color color) { - var col = _colors.Where(x => Color.AreClose(x.color, color)).Select(x => x.name).Take(1).ToList(); - return col.Any() ? col[0] : new ColorConverter().ConvertToString(color); + var matchingColorNames = _colors.Where(x => Color.AreClose(x.color, color)).Select(x => x.name); + return matchingColorNames.FirstOrDefault() ?? new ColorConverter().ConvertToString(color); } } } diff --git a/src/AudioBand/Extensions/ImageExtensions.cs b/src/AudioBand/Extensions/ImageExtensions.cs index 5a5b85ed..09c59a45 100644 --- a/src/AudioBand/Extensions/ImageExtensions.cs +++ b/src/AudioBand/Extensions/ImageExtensions.cs @@ -3,6 +3,9 @@ namespace AudioBand.Extensions { + /// + /// Extension for . + /// internal static class ImageExtensions { /// @@ -11,7 +14,7 @@ internal static class ImageExtensions /// Image to resize. /// Width of the new image. /// Height of the new image. - /// + /// The resized image. public static Image Resize(this Image image, int width, int height) { // Padding issues @@ -36,7 +39,7 @@ public static Image Resize(this Image image, int width, int height) /// Image to scale. /// Target width to scale to. /// Target height to scale to. - /// + /// The scaled image. public static Image Scale(this Image image, int targetWidth, int targetHeight) { var ratiow = (float)image.Width / targetWidth; diff --git a/src/AudioBand/Extensions/ObjectExtensions.cs b/src/AudioBand/Extensions/ObjectExtensions.cs index a4c4c06e..a3bffc95 100644 --- a/src/AudioBand/Extensions/ObjectExtensions.cs +++ b/src/AudioBand/Extensions/ObjectExtensions.cs @@ -2,8 +2,16 @@ namespace AudioBand.Extensions { + /// + /// Extensions for . + /// internal static class ObjectExtensions { + /// + /// Gets the string containing all the properties and their values. + /// + /// Object. + /// A string with the object's properties and values. public static string FormattedString(this object o) { var sb = new StringBuilder(); @@ -12,6 +20,7 @@ public static string FormattedString(this object o) { sb.Append($"{prop.Name}: {prop.GetValue(o)}, "); } + sb.Append("}"); return sb.ToString(); diff --git a/src/AudioBand/Extensions/SvgExtensions.cs b/src/AudioBand/Extensions/SvgExtensions.cs index 4a1d80d2..5ebfcae8 100644 --- a/src/AudioBand/Extensions/SvgExtensions.cs +++ b/src/AudioBand/Extensions/SvgExtensions.cs @@ -1,9 +1,12 @@ -using Svg; -using System.Drawing; +using System.Drawing; using System.Drawing.Drawing2D; +using Svg; namespace AudioBand.Extensions { + /// + /// Extensions for . + /// internal static class SvgExtensions { /// diff --git a/src/AudioBand/GlobalSuppressions.cs b/src/AudioBand/GlobalSuppressions.cs new file mode 100644 index 00000000..e2852700 --- /dev/null +++ b/src/AudioBand/GlobalSuppressions.cs @@ -0,0 +1,126 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1601:Partial elements must be documented", Justification = "None", Scope = "type", Target = "~T:AudioBand.MainControl")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "None", Scope = "member", Target = "~M:AudioBand.MainControl.#ctor")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "None", Scope = "member", Target = "~M:AudioBand.MainControl.OnResize(System.EventArgs)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1205:Partial elements must declare access", Justification = "None", Scope = "type", Target = "~T:AudioBand.MainControl")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1502:Element must not be on a single line", Justification = "None", Scope = "member", Target = "~M:AudioBand.Commands.AsyncRelayCommand`1.#ctor(System.Func{`0,System.Threading.Tasks.Task})")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1502:Element must not be on a single line", Justification = "None", Scope = "member", Target = "~M:AudioBand.Commands.RelayCommand`1.#ctor(System.Action{`0})")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1502:Element must not be on a single line", Justification = "None", Scope = "member", Target = "~M:AudioBand.Commands.RelayCommand.#ctor(System.Action{System.Object})")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1502:Element must not be on a single line", Justification = "None", Scope = "member", Target = "~M:AudioBand.Commands.RelayCommand.#ctor(System.Action{System.Object},System.Predicate{System.Object})")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1009:Closing parenthesis must be spaced correctly", Justification = "Tuple issues", Scope = "type", Target = "~T:AudioBand.Extensions.ColorExtensions")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1008:Opening parenthesis must be spaced correctly", Justification = "Tuple issues", Scope = "member", Target = "~M:AudioBand.Extensions.ColorExtensions.#cctor")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "None", Scope = "member", Target = "~P:AudioBand.Models.ModelBase.Logger")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1008:Opening parenthesis must be spaced correctly", Justification = "Tuple issues", Scope = "member", Target = "~F:AudioBand.Settings.Migrations.Migration.SupportedMigrations")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V1.AlbumArtAppearance")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V1.AlbumArtPopupAppearance")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V1.AudioBandAppearance")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V1.AudioBandAppearance")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V1.AudioBandSettings")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V1.AudioSourceSetting")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V1.AudioSourceSettingsCollection")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V1.NextSongButtonAppearance")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V1.PlayPauseButtonAppearance")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V1.PreviousSongButtonAppearance")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V1.ProgressBarAppearance")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V1.TextAppearance")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V2.AlbumArtPopupSettings")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V2.AlbumArtSettings")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V2.AudioBandSettings")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V2.AudioSourceSetting")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V2.AudioSourceSettings")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V2.CustomLabelSettings")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V2.NextButtonSettings")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V2.PlayPauseButtonSettings")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V2.PreviousButtonSettings")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V2.ProgressBarSettings")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "type", Target = "~T:AudioBand.Settings.Models.V2.Settings")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.AlbumArtPopupVM.IsVisible")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.AlbumArtPopupVM.Width")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.AlbumArtPopupVM.Height")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.AlbumArtPopupVM.XPosition")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.AlbumArtPopupVM.Margin")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.AlbumArtPopupVM.AlbumArt")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.AlbumArtVM.IsVisible")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.AlbumArtVM.Width")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.AlbumArtVM.Height")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.AlbumArtVM.XPosition")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.AlbumArtVM.YPosition")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.AlbumArtVM.PlaceholderPath")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.AlbumArtVM.AlbumArt")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~M:AudioBand.ViewModels.AlbumArtVM.#ctor(AudioBand.Models.AlbumArt,AudioBand.Models.Track)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~M:AudioBand.ViewModels.AlbumArtPopupVM.#ctor(AudioBand.Models.AlbumArtPopup,AudioBand.Models.Track)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.AudioBandVM.Width")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.AudioBandVM.Height")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~M:AudioBand.ViewModels.AudioBandVM.#ctor(AudioBand.Models.AudioBand)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1502:Element must not be on a single line", Justification = "Ignore", Scope = "member", Target = "~M:AudioBand.ViewModels.AudioBandVM.#ctor(AudioBand.Models.AudioBand)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1502:Element must not be on a single line", Justification = "Ignore", Scope = "member", Target = "~M:AudioBand.ViewModels.CustomLabelVM.#ctor(AudioBand.Models.CustomLabel)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~M:AudioBand.ViewModels.CustomLabelVM.#ctor(AudioBand.Models.CustomLabel)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.CustomLabelVM.Name")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.CustomLabelVM.FontFamily")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.CustomLabelVM.FontSize")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.CustomLabelVM.Color")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.CustomLabelVM.FormatString")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.CustomLabelVM.TextAlignment")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.CustomLabelVM.IsVisible")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.CustomLabelVM.Width")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.CustomLabelVM.Height")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.CustomLabelVM.XPosition")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.CustomLabelVM.YPosition")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.CustomLabelVM.ScrollSpeed")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.NextButtonVM.Image")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.NextButtonVM.ImagePath")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.NextButtonVM.IsVisible")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.NextButtonVM.Width")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.NextButtonVM.Height")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.NextButtonVM.XPosition")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.NextButtonVM.YPosition")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~M:AudioBand.ViewModels.NextButtonVM.#ctor(AudioBand.Models.NextButton)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~M:AudioBand.ViewModels.PlayPauseButtonVM.#ctor(AudioBand.Models.PlayPauseButton,AudioBand.Models.Track)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PlayPauseButtonVM.PlayImage")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PlayPauseButtonVM.PlayImagePath")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PlayPauseButtonVM.PauseImage")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PlayPauseButtonVM.PauseImagePath")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PlayPauseButtonVM.IsVisible")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PlayPauseButtonVM.Width")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PlayPauseButtonVM.Height")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PlayPauseButtonVM.XPosition")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PlayPauseButtonVM.YPosition")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PlayPauseButtonVM.Image")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~M:AudioBand.ViewModels.PreviousButtonVM.#ctor(AudioBand.Models.PreviousButton)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PreviousButtonVM.Image")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PreviousButtonVM.ImagePath")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PreviousButtonVM.IsVisible")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PreviousButtonVM.Width")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PreviousButtonVM.Height")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PreviousButtonVM.XPosition")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.PreviousButtonVM.YPosition")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.ProgressBarVM.ForegroundColor")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.ProgressBarVM.BackgroundColor")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.ProgressBarVM.IsVisible")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.ProgressBarVM.Width")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.ProgressBarVM.Height")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.ProgressBarVM.XPosition")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.ProgressBarVM.YPosition")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.ProgressBarVM.TrackProgress")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.ProgressBarVM.TrackLength")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~M:AudioBand.ViewModels.ProgressBarVM.#ctor(AudioBand.Models.ProgressBar,AudioBand.Models.Track)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.SettingsWindowVM.AudioBandVM")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.SettingsWindowVM.AlbumArtPopupVM")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.SettingsWindowVM.AlbumArtVM")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.SettingsWindowVM.CustomLabelsVM")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.SettingsWindowVM.AboutVm")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.SettingsWindowVM.NextButtonVM")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.SettingsWindowVM.PlayPauseButtonVM")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.SettingsWindowVM.PreviousButtonVM")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.SettingsWindowVM.ProgressBarVM")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Ignore", Scope = "member", Target = "~P:AudioBand.ViewModels.SettingsWindowVM.AudioSourceSettingsVM")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1502:Element must not be on a single line", Justification = "Ignore", Scope = "member", Target = "~M:AudioBand.ViewModels.ViewModelBase.OnReset")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1502:Element must not be on a single line", Justification = "Ignore", Scope = "member", Target = "~M:AudioBand.ViewModels.ViewModelBase.OnCancelEdit")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1502:Element must not be on a single line", Justification = "Ignore", Scope = "member", Target = "~M:AudioBand.ViewModels.ViewModelBase.OnEndEdit")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1502:Element must not be on a single line", Justification = "Ignore", Scope = "member", Target = "~M:AudioBand.ViewModels.ViewModelBase.OnBeginEdit")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1502:Element must not be on a single line", Justification = "Ignore", Scope = "member", Target = "~M:AudioBand.ViewModels.ViewModelBase`1.OnModelPropertyChanged(System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1502:Element must not be on a single line", Justification = "Ignore", Scope = "member", Target = "~M:AudioBand.AudioSource.AudioSourceManager.IsAlive")] diff --git a/src/AudioBand/MainControl.Bindings.cs b/src/AudioBand/MainControl.Bindings.cs index 8888e985..401ba4a9 100644 --- a/src/AudioBand/MainControl.Bindings.cs +++ b/src/AudioBand/MainControl.Bindings.cs @@ -1,40 +1,61 @@ using System; using System.Linq; using System.Windows.Forms; -using System.Windows.Threading; using AudioBand.ViewModels; using AudioBand.Views.Winforms; namespace AudioBand { +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member partial class MainControl : ICustomLabelHost +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member { private const string CustomLabelControlsKey = "CustomLabel"; - private void InitializeBindingSources(AlbumArtPopupVM albumArtPopupVm, AlbumArtVM albumartVm, AudioBandVM audioBandVm, NextButtonVM nextButtonVm, - PlayPauseButtonVM playPauseButtonVm, PreviousButtonVM previousButtonVm, ProgressBarVM progressBarVm) + /// + void ICustomLabelHost.AddCustomTextLabel(CustomLabelVM customLabel) { - AlbumArtPopupVMBindingSource.DataSource = albumArtPopupVm; - AlbumArtVMBindingSource.DataSource = albumartVm; - AudioBandVMBindingSource.DataSource = audioBandVm; - NextButtonVMBindingSource.DataSource = nextButtonVm; - PlayPauseButtonVMBindingSource.DataSource = playPauseButtonVm; - PreviousButtonVMBindingSource.DataSource = previousButtonVm; - ProgressBarVMBindingSource.DataSource = progressBarVm; + if (InvokeRequired) + { + BeginInvoke((Action)(() => AddCustomLabelText(customLabel))); + } + else + { + AddCustomLabelText(customLabel); + } } - void ICustomLabelHost.AddCustomTextLabel(CustomLabelVM customLabel) + /// + void ICustomLabelHost.RemoveCustomTextLabel(CustomLabelVM customLabel) { if (InvokeRequired) { - BeginInvoke((Action) (() => AddCustomLabelText(customLabel))); + BeginInvoke((Action)(() => RemoveCustomTextLabel(customLabel))); } else { - AddCustomLabelText(customLabel); + RemoveCustomTextLabel(customLabel); } } + private void InitializeBindingSources( + AlbumArtPopupVM albumArtPopupVm, + AlbumArtVM albumartVm, + AudioBandVM audioBandVm, + NextButtonVM nextButtonVm, + PlayPauseButtonVM playPauseButtonVm, + PreviousButtonVM previousButtonVm, + ProgressBarVM progressBarVm) + { + AlbumArtPopupVMBindingSource.DataSource = albumArtPopupVm; + AlbumArtVMBindingSource.DataSource = albumartVm; + AudioBandVMBindingSource.DataSource = audioBandVm; + NextButtonVMBindingSource.DataSource = nextButtonVm; + PlayPauseButtonVMBindingSource.DataSource = playPauseButtonVm; + PreviousButtonVMBindingSource.DataSource = previousButtonVm; + ProgressBarVMBindingSource.DataSource = progressBarVm; + } + private void AddCustomLabelText(CustomLabelVM customLabel) { var label = new FormattedTextLabel(customLabel.FormatString, customLabel.Color, customLabel.FontSize, customLabel.FontFamily, customLabel.TextAlignment); @@ -64,19 +85,7 @@ private void AddCustomLabelText(CustomLabelVM customLabel) Controls.Add(label); } - void ICustomLabelHost.RemoveCustomTextLabel(CustomLabelVM customLabel) - { - if (InvokeRequired) - { - BeginInvoke((Action) (() => RemoveCustomTextLabel(customLabel))); - } - else - { - RemoveCustomTextLabel(customLabel); - } - } - - void RemoveCustomTextLabel(CustomLabelVM customLabel) + private void RemoveCustomTextLabel(CustomLabelVM customLabel) { var control = Controls.Find(CustomLabelControlsKey, true) .Cast() diff --git a/src/AudioBand/MainControl.EventHandlers.cs b/src/AudioBand/MainControl.EventHandlers.cs index 178dbcc3..dde2ff29 100644 --- a/src/AudioBand/MainControl.EventHandlers.cs +++ b/src/AudioBand/MainControl.EventHandlers.cs @@ -1,13 +1,18 @@ -using AudioBand.AudioSource; -using CSDeskBand.ContextMenu; -using System; +using System; +using System.Collections.Specialized; using System.Linq; using System.Threading.Tasks; +using AudioBand.AudioSource; +using AudioBand.Models; +using AudioBand.ViewModels; +using CSDeskBand.ContextMenu; namespace AudioBand { partial class MainControl { + private static readonly object _audiosourceListLock = new object(); + #region Winforms event handlers private void AlbumArtOnMouseLeave(object o, EventArgs args) @@ -24,22 +29,22 @@ private async void PlayPauseButtonOnClick(object sender, EventArgs eventArgs) { if (_trackModel.IsPlaying) { - await (_currentAudioSource?.PauseTrackAsync(_audioSourceTokenSource.Token) ?? Task.CompletedTask); + await (_currentAudioSource?.PauseTrackAsync() ?? Task.CompletedTask); } else { - await (_currentAudioSource?.PlayTrackAsync(_audioSourceTokenSource.Token) ?? Task.CompletedTask); + await (_currentAudioSource?.PlayTrackAsync() ?? Task.CompletedTask); } } private async void PreviousButtonOnClick(object sender, EventArgs eventArgs) { - await (_currentAudioSource?.PreviousTrackAsync(_audioSourceTokenSource.Token) ?? Task.CompletedTask); + await (_currentAudioSource?.PreviousTrackAsync() ?? Task.CompletedTask); } private async void NextButtonOnClick(object sender, EventArgs eventArgs) { - await (_currentAudioSource?.NextTrackAsync(_audioSourceTokenSource.Token) ?? Task.CompletedTask); + await (_currentAudioSource?.NextTrackAsync() ?? Task.CompletedTask); } #endregion @@ -53,63 +58,29 @@ private void SettingsMenuItemOnClicked(object sender, EventArgs eventArgs) private async void AudioSourceMenuItemOnClicked(object sender, EventArgs eventArgs) { - try - { - var item = (DeskBandMenuAction) sender; - if (item.Checked) - { - item.Checked = false; - await UnsubscribeToAudioSource(_currentAudioSource); - return; - } - - // Uncheck old item and unsubscribe from the current source - var lastItemChecked = _pluginSubMenu.Items.Cast().FirstOrDefault(i => i.Text == _currentAudioSource?.Name); - if (lastItemChecked != null) - { - lastItemChecked.Checked = false; - } - - await UnsubscribeToAudioSource(_currentAudioSource); - - item.Checked = true; - _currentAudioSource = _audioSourceLoader.AudioSources.First(c => c.Name == item.Text); - await SubscribeToAudioSource(_currentAudioSource); - } - catch (Exception e) - { - Logger.Debug(e, "Error activating audio source"); - } + await HandleAudioSourceContextMenuItemClick(sender as DeskBandMenuAction).ConfigureAwait(false); } #endregion #region Audio source event handlers - private void AudioSourceOnTrackProgressChanged(object o, TimeSpan progress) + private async void AudioSourceOnTrackProgressChanged(object o, TimeSpan progress) { - BeginInvoke(new Action(() => { _trackModel.TrackProgress = progress; })); + await _uiDispatcher.InvokeAsync(() => { _trackModel.TrackProgress = progress; }); } - private void AudioSourceOnTrackPaused(object o, EventArgs args) + private async void AudioSourceOnIsPlayingChanged(object sender, bool isPlaying) { - Logger.Debug("State set to paused"); - - BeginInvoke(new Action(() => _trackModel.IsPlaying = false)); - } - - private void AudioSourceOnTrackPlaying(object o, EventArgs args) - { - Logger.Debug("State set to playing"); - - BeginInvoke(new Action(() => _trackModel.IsPlaying = true)); + Logger.Debug($"Play state changed. Is playing: {isPlaying}"); + await _uiDispatcher.InvokeAsync(() => _trackModel.IsPlaying = isPlaying); } private void AudioSourceOnTrackInfoChanged(object sender, TrackInfoChangedEventArgs trackInfoChangedEventArgs) { if (trackInfoChangedEventArgs == null) { - Logger.Error("TrackInforChanged event arg is empty"); + Logger.Error("TrackInfoChanged event arg is null"); return; } @@ -127,16 +98,84 @@ private void AudioSourceOnTrackInfoChanged(object sender, TrackInfoChangedEventA Logger.Debug($"Track changed - Name: '{trackInfoChangedEventArgs.TrackName}', Artist: '{trackInfoChangedEventArgs.Artist}'"); - BeginInvoke(new Action(() => + _uiDispatcher.InvokeAsync(() => { _trackModel.AlbumArt = trackInfoChangedEventArgs.AlbumArt; _trackModel.Artist = trackInfoChangedEventArgs.Artist; _trackModel.TrackName = trackInfoChangedEventArgs.TrackName; _trackModel.TrackLength = trackInfoChangedEventArgs.TrackLength; _trackModel.AlbumName = trackInfoChangedEventArgs.Album; - })); + }); } #endregion + + private void SettingsWindowOnSaved(object o, EventArgs eventArgs) + { + _settingsWindowVm.EndEdit(); + _appSettings.Save(); + } + + private void SettingsWindowOnCanceled(object sender, EventArgs e) + { + _settingsWindowVm.CancelEdit(); + } + + private async void AudioSourcesOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Add) + { + foreach (var audioSource in e.NewItems.Cast()) + { + await AddNewAudioSource(audioSource); + } + } + else + { + Logger.Warn($"Action {e.Action} not supported"); + } + } + + private async Task AddNewAudioSource(IInternalAudioSource audioSource) + { + var menuItem = new DeskBandMenuAction(audioSource.Name); + menuItem.Clicked += AudioSourceMenuItemOnClicked; + + lock (_audiosourceListLock) + { + _audioSourceContextMenuItems.Add(menuItem); + RefreshContextMenu(); + } + + var settings = audioSource.Settings; + if (settings.Count > 0) + { + // check if the settings were saved previously and reuse them + var matchingSetting = _audioSourceSettingsModel.FirstOrDefault(s => s.AudioSourceName == audioSource.Name); + if (matchingSetting != null) + { + var viewModel = new AudioSourceSettingsVM(matchingSetting, audioSource); + + // the collection was created on the ui thread + await _uiDispatcher.InvokeAsync(() => _settingsWindowVm.AudioSourceSettingsVM.Add(viewModel)); + } + else + { + var newSettingsModel = new AudioSourceSettings { AudioSourceName = audioSource.Name }; + _audioSourceSettingsModel.Add(newSettingsModel); + var newViewModel = new AudioSourceSettingsVM(newSettingsModel, audioSource); + await _uiDispatcher.InvokeAsync(() => _settingsWindowVm.AudioSourceSettingsVM.Add(newViewModel)); + } + } + + // If user was using this audio source last, then automatically activate it + var savedAudioSourceName = _appSettings.AudioSource; + if (savedAudioSourceName == null || audioSource.Name != savedAudioSourceName) + { + return; + } + + await HandleAudioSourceContextMenuItemClick(menuItem).ConfigureAwait(false); + } } } diff --git a/src/AudioBand/MainControl.cs b/src/AudioBand/MainControl.cs index 307629b5..a9e889be 100644 --- a/src/AudioBand/MainControl.cs +++ b/src/AudioBand/MainControl.cs @@ -1,4 +1,14 @@ -using AudioBand.AudioSource; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms.Integration; +using System.Windows.Threading; +using AudioBand.AudioSource; using AudioBand.Models; using AudioBand.Settings; using AudioBand.ViewModels; @@ -7,15 +17,6 @@ using CSDeskBand.Win; using NLog; using NLog.Config; -using NLog.Targets; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms.Integration; -using System.Windows.Threading; using SettingsWindow = AudioBand.Views.Wpf.SettingsWindow; using Size = System.Drawing.Size; @@ -27,13 +28,14 @@ namespace AudioBand public partial class MainControl : CSDeskBandWin { private static readonly ILogger Logger = LogManager.GetLogger("Audio Band"); - private readonly AudioSourceLoader _audioSourceLoader = new AudioSourceLoader(); private readonly AppSettings _appSettings = new AppSettings(); private readonly Dispatcher _uiDispatcher; + private AudioSourceManager _audioSourceManager; private SettingsWindow _settingsWindow; private IAudioSource _currentAudioSource; - private DeskBandMenu _pluginSubMenu; - private CancellationTokenSource _audioSourceTokenSource = new CancellationTokenSource(); + private DeskBandMenuAction _settingsMenuItem; + private DeskBandMenu _pluginSubMenu; + private List _audioSourceContextMenuItems; private SettingsWindowVM _settingsWindowVm; #region Models @@ -53,41 +55,57 @@ public partial class MainControl : CSDeskBandWin static MainControl() { - var fileTarget = new FileTarget - { - MaxArchiveFiles = 3, - ArchiveOldFileOnStartup = true, - FileName = "${environment:variable=TEMP}/AudioBand.log", - KeepFileOpen = true, - OpenFileCacheTimeout = 30, - Layout = NLog.Layouts.Layout.FromString("${longdate}|${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}") - }; - - var nullTarget = new NullTarget(); - - var filter = new LoggingRule("CSDeskBand.*", LogLevel.Trace, nullTarget) {Final = true}; - var fileRule = new LoggingRule("*", LogLevel.Debug, fileTarget); - - var config = new LoggingConfiguration(); - config.AddTarget("logfile", fileTarget); - config.AddTarget("null", nullTarget); - config.LoggingRules.Add(filter); - config.LoggingRules.Add(fileRule); - - LogManager.Configuration = config; - - AppDomain.CurrentDomain.UnhandledException += (sender, args) => LogManager.GetCurrentClassLogger().Error((Exception) args.ExceptionObject); + AppDomain.CurrentDomain.UnhandledException += (sender, args) => LogManager.GetCurrentClassLogger().Error((Exception)args.ExceptionObject, "Unhandled Exception"); } + /// + /// Initializes a new instance of the class. + /// Entry point. + /// public MainControl() { - InitializeComponent(); -#if DEBUG - System.Diagnostics.Debugger.Launch(); -#endif + + LogManager.ThrowExceptions = true; + LogManager.Configuration = new XmlLoggingConfiguration(Path.Combine(DirectoryHelper.BaseDirectory, "nlog.config")); _uiDispatcher = Dispatcher.CurrentDispatcher; +#pragma warning disable CS4014 InitializeAsync(); +#pragma warning restore CS4014 + } + + /// + /// Update deskband options when the size changes + /// + /// . + protected override void OnResize(EventArgs eventArgs) + { + if (_audioBandModel == null) + { + return; + } + + var audioBandSize = new Size(_audioBandModel.Width, _audioBandModel.Height); + Options.MinHorizontalSize = audioBandSize; + Options.HorizontalSize = audioBandSize; + Options.MaxHorizontalHeight = audioBandSize.Height; + } + + /// + /// Save on close + /// + protected override void OnClose() + { + base.OnClose(); + _appSettings.Save(); + try + { + _currentAudioSource?.DeactivateAsync(); + } + catch (Exception) + { + // ignore + } } private async Task InitializeAsync() @@ -96,8 +114,11 @@ private async Task InitializeAsync() { await Task.Run(() => { - _audioSourceLoader.LoadAudioSources(); - Options.ContextMenuItems = BuildContextMenu(); + _audioSourceContextMenuItems = new List(); + _settingsMenuItem = new DeskBandMenuAction("Audio Band Settings"); + _settingsMenuItem.Clicked += SettingsMenuItemOnClicked; + RefreshContextMenu(); + InitializeModels(); }).ConfigureAwait(false); @@ -106,12 +127,15 @@ await Task.Run(() => await _uiDispatcher.InvokeAsync(() => { _settingsWindow = new SettingsWindow(_settingsWindowVm); - _settingsWindow.Saved += Saved; - _settingsWindow.Canceled += Canceled; + _settingsWindow.Saved += SettingsWindowOnSaved; + _settingsWindow.Canceled += SettingsWindowOnCanceled; ElementHost.EnableModelessKeyboardInterop(_settingsWindow); }); - await SelectAudioSourceFromSettings().ConfigureAwait(false); + _audioSourceManager = new AudioSourceManager(); + _audioSourceManager.AudioSources.CollectionChanged += AudioSourcesOnCollectionChanged; + _audioSourceManager.LoadAudioSources(); + Logger.Debug("Initialization complete"); } catch (Exception e) @@ -131,7 +155,7 @@ private void InitializeModels() _previousButtonModel = _appSettings.PreviousButton; _progressBarModel = _appSettings.ProgressBar; _audioSourceSettingsModel = _appSettings.AudioSourceSettings; - _trackModel = new Track(); + _trackModel = new Track(); } private async Task SetupViewModels() @@ -144,22 +168,6 @@ private async Task SetupViewModels() var playPauseButton = new PlayPauseButtonVM(_playPauseButtonModel, _trackModel); var prevButton = new PreviousButtonVM(_previousButtonModel); var progressBar = new ProgressBarVM(_progressBarModel, _trackModel); - var allAudioSourceSettings = new List(); - - foreach (var audioSource in _audioSourceLoader.AudioSources) - { - var matchingSetting = _audioSourceSettingsModel.FirstOrDefault(s => s.AudioSourceName == audioSource.Name); - if (matchingSetting != null) - { - allAudioSourceSettings.Add(new AudioSourceSettingsVM(matchingSetting, audioSource)); - } - else - { - var newSettings = new AudioSourceSettings {AudioSourceName = audioSource.Name}; - _audioSourceSettingsModel.Add(newSettings); - allAudioSourceSettings.Add(new AudioSourceSettingsVM(newSettings, audioSource)); - } - } await _uiDispatcher.InvokeAsync(() => InitializeBindingSources(albumArtPopup, albumArt, audioBand, nextButton, playPauseButton, prevButton, progressBar)); @@ -174,41 +182,20 @@ private async Task SetupViewModels() AboutVm = new AboutVM(), AlbumArtVM = albumArt, CustomLabelsVM = customLabels, - AudioSourceSettingsVM = allAudioSourceSettings + AudioSourceSettingsVM = new ObservableCollection() }; } - private void Saved(object o, EventArgs eventArgs) - { - _settingsWindowVm.EndEdit(); - _appSettings.Save(); - } - - private void Canceled(object sender, EventArgs e) - { - _settingsWindowVm.CancelEdit(); - } - private void OpenSettingsWindow() { _settingsWindowVm.BeginEdit(); _settingsWindow.Show(); } - private List BuildContextMenu() + private void RefreshContextMenu() { - var pluginList = _audioSourceLoader.AudioSources.Select(audioSource => - { - var item = new DeskBandMenuAction(audioSource.Name); - item.Clicked += AudioSourceMenuItemOnClicked; - return item; - }); - - _pluginSubMenu = new DeskBandMenu("Audio Source", pluginList); - var settingsMenuItem = new DeskBandMenuAction("Audio Band Settings"); - settingsMenuItem.Clicked += SettingsMenuItemOnClicked; - - return new List{ settingsMenuItem, _pluginSubMenu }; + _pluginSubMenu = new DeskBandMenu("Audio Source", _audioSourceContextMenuItems); + Options.ContextMenuItems = new List { _settingsMenuItem, _pluginSubMenu }; } private async Task SubscribeToAudioSource(IAudioSource source) @@ -221,16 +208,14 @@ private async Task SubscribeToAudioSource(IAudioSource source) ResetTrack(); source.TrackInfoChanged += AudioSourceOnTrackInfoChanged; - source.TrackPlaying += AudioSourceOnTrackPlaying; - source.TrackPaused += AudioSourceOnTrackPaused; + source.IsPlayingChanged += AudioSourceOnIsPlayingChanged; source.TrackProgressChanged += AudioSourceOnTrackProgressChanged; - _audioSourceTokenSource = new CancellationTokenSource(); - await source.ActivateAsync(_audioSourceTokenSource.Token); + await source.ActivateAsync().ConfigureAwait(false); _appSettings.AudioSource = source.Name; - Logger.Debug($"Audio source selected: {source.Name}"); + Logger.Debug($"Audio source selected: `{source.Name}`"); } private async Task UnsubscribeToAudioSource(IAudioSource source) @@ -241,60 +226,52 @@ private async Task UnsubscribeToAudioSource(IAudioSource source) } source.TrackInfoChanged -= AudioSourceOnTrackInfoChanged; - source.TrackPlaying -= AudioSourceOnTrackPlaying; - source.TrackPaused -= AudioSourceOnTrackPaused; + source.IsPlayingChanged -= AudioSourceOnIsPlayingChanged; source.TrackProgressChanged -= AudioSourceOnTrackProgressChanged; - _audioSourceTokenSource.Cancel(); - await source.DeactivateAsync(); + await source.DeactivateAsync().ConfigureAwait(false); _appSettings.AudioSource = null; _currentAudioSource = null; ResetTrack(); - Logger.Debug("Audio source deactivated"); - } - - protected override void OnResize(EventArgs eventArgs) - { - if (_audioBandModel == null) - { - return; - } - - var audioBandSize = new Size(_audioBandModel.Width, _audioBandModel.Height); - Options.MinHorizontalSize = audioBandSize; - Options.HorizontalSize = audioBandSize; - Options.MaxHorizontalHeight = audioBandSize.Height; + Logger.Debug($"Audio source `{source.Name}` deactivated"); } - protected override void OnClose() - { - base.OnClose(); - _appSettings.Save(); - _currentAudioSource.DeactivateAsync(); - } - - private async Task SelectAudioSourceFromSettings() + private async Task HandleAudioSourceContextMenuItemClick(DeskBandMenuAction menuItem) { try { - var audioSource = _appSettings.AudioSource; - if (String.IsNullOrEmpty(audioSource)) + if (menuItem.Checked) { + menuItem.Checked = false; + await UnsubscribeToAudioSource(_currentAudioSource).ConfigureAwait(false); return; } - var menuItem = _pluginSubMenu.Items.Cast().FirstOrDefault(i => i.Text == audioSource); - if (menuItem != null) + // Uncheck old items and unsubscribe from the current source + foreach (var otherMenuItem in _pluginSubMenu.Items.Cast().Where(i => i != null)) { - await Task.Run(() => AudioSourceMenuItemOnClicked(menuItem, EventArgs.Empty)); + otherMenuItem.Checked = false; } + + await UnsubscribeToAudioSource(_currentAudioSource).ConfigureAwait(false); + + _currentAudioSource = _audioSourceManager.AudioSources.FirstOrDefault(c => c.Name == menuItem.Text); + if (_currentAudioSource == null) + { + Logger.Warn($"Could not find matching audio source. Looking for {menuItem.Text}."); + return; + } + + await SubscribeToAudioSource(_currentAudioSource).ConfigureAwait(false); + menuItem.Checked = true; } catch (Exception e) { - Logger.Error(e); + Logger.Debug(e, $"Error activating audio source `{_currentAudioSource?.Name}`"); + _currentAudioSource = null; } } diff --git a/src/AudioBand/Models/AlbumArt.cs b/src/AudioBand/Models/AlbumArt.cs index 492287bf..69a86f8e 100644 --- a/src/AudioBand/Models/AlbumArt.cs +++ b/src/AudioBand/Models/AlbumArt.cs @@ -1,5 +1,8 @@ namespace AudioBand.Models { + /// + /// Model for the album art. + /// internal class AlbumArt : ModelBase { private bool _isVisible = true; @@ -9,36 +12,54 @@ internal class AlbumArt : ModelBase private int _yPosition = 0; private string _placeholderPath = ""; + /// + /// Gets or sets a value indicating whether the album art is visible. + /// public bool IsVisible { get => _isVisible; set => SetProperty(ref _isVisible, value); } + /// + /// Gets or sets the width of the album art. + /// public int Width { get => _width; set => SetProperty(ref _width, value); } + /// + /// Gets or sets the height of the album art. + /// public int Height { get => _height; set => SetProperty(ref _height, value); } + /// + /// Gets or sets the x position of the album art. + /// public int XPosition { get => _xPosition; set => SetProperty(ref _xPosition, value); } + /// + /// Gets or sets the y position of the album art. + /// public int YPosition { get => _yPosition; set => SetProperty(ref _yPosition, value); } + /// + /// Gets or sets the path of the placeholder image. + /// public string PlaceholderPath { get => _placeholderPath; diff --git a/src/AudioBand/Models/AlbumArtPopup.cs b/src/AudioBand/Models/AlbumArtPopup.cs index d4d103be..791af021 100644 --- a/src/AudioBand/Models/AlbumArtPopup.cs +++ b/src/AudioBand/Models/AlbumArtPopup.cs @@ -1,5 +1,8 @@ namespace AudioBand.Models { + /// + /// Model for the album art popup. + /// internal class AlbumArtPopup : ModelBase { private bool _isVisible = true; @@ -8,30 +11,45 @@ internal class AlbumArtPopup : ModelBase private int _xPosition = 0; private int _margin = 4; + /// + /// Gets or sets a value indicating whether the album art popup is visible. + /// public bool IsVisible { get => _isVisible; set => SetProperty(ref _isVisible, value); } + /// + /// Gets or sets the width of the album art popup + /// public int Width { get => _width; set => SetProperty(ref _width, value); } + /// + /// Gets or sets the height of the album art popup. + /// public int Height { get => _height; set => SetProperty(ref _height, value); } + /// + /// Gets or sets the x position of the album art popup. + /// public int XPosition { get => _xPosition; set => SetProperty(ref _xPosition, value); } + /// + /// Gets or sets the margin between the album art popup and the taskbar. + /// public int Margin { get => _margin; diff --git a/src/AudioBand/Models/AudioBand.cs b/src/AudioBand/Models/AudioBand.cs index 48104282..60519012 100644 --- a/src/AudioBand/Models/AudioBand.cs +++ b/src/AudioBand/Models/AudioBand.cs @@ -1,16 +1,25 @@ namespace AudioBand.Models { + /// + /// The model for the toolbar. + /// internal class AudioBand : ModelBase { private int _width = 250; private int _height = 30; + /// + /// Gets or sets the width of the toolbar. + /// public int Width { get => _width; set => SetProperty(ref _width, value); } + /// + /// Gets or sets the height of the toolbar. + /// public int Height { get => _height; diff --git a/src/AudioBand/Models/AudioSourceSetting.cs b/src/AudioBand/Models/AudioSourceSetting.cs index 08c7281e..1b0f3b01 100644 --- a/src/AudioBand/Models/AudioSourceSetting.cs +++ b/src/AudioBand/Models/AudioSourceSetting.cs @@ -1,11 +1,9 @@ -using System; -using System.ComponentModel; -using AudioBand.AudioSource; +using AudioBand.AudioSource; namespace AudioBand.Models { /// - /// A key / value pair that represents a single setting for an audio source + /// A key / value pair that represents a single setting for an audio source. /// internal class AudioSourceSetting : ModelBase { @@ -14,7 +12,7 @@ internal class AudioSourceSetting : ModelBase private bool _remember = true; /// - /// Name of the setting + /// Gets or sets the name of the setting provided by the . /// public string Name { @@ -23,8 +21,12 @@ public string Name } /// - /// Value of the setting serialized as a string + /// Gets or sets the value of the setting. /// + /// + /// The value of the setting, either provided by the or the user. + /// The default value is set by the . + /// public object Value { get => _value; @@ -32,12 +34,16 @@ public object Value } /// - /// Whether or not to save this setting + /// Gets or sets a value indicating whether or not to save this setting. /// + /// + /// if the setting should be saved; otherwise . + /// The default value is . + /// public bool Remember { get => _remember; set => SetProperty(ref _remember, value); } } -} \ No newline at end of file +} diff --git a/src/AudioBand/Models/AudioSourceSettings.cs b/src/AudioBand/Models/AudioSourceSettings.cs index fc96fb0b..bdb38603 100644 --- a/src/AudioBand/Models/AudioSourceSettings.cs +++ b/src/AudioBand/Models/AudioSourceSettings.cs @@ -1,21 +1,19 @@ -using System; -using System.Collections.Generic; -using AudioBand.AudioSource; +using System.Collections.Generic; namespace AudioBand.Models { /// - /// Collection of settings for a specific audio source + /// Collection of settings for a specific audio source. /// internal class AudioSourceSettings : ModelBase { /// - /// Name of the audio source + /// Gets or sets the name of the audio source. /// public string AudioSourceName { get; set; } /// - /// List of settings that the audio source exposes + /// Gets or sets the list of settings that the audio source exposes. /// public List Settings { get; set; } = new List(); } diff --git a/src/AudioBand/Models/CustomLabel.cs b/src/AudioBand/Models/CustomLabel.cs index 9d26bffa..9e6b0fef 100644 --- a/src/AudioBand/Models/CustomLabel.cs +++ b/src/AudioBand/Models/CustomLabel.cs @@ -2,9 +2,11 @@ namespace AudioBand.Models { + /// + /// Model for a custom label. + /// internal class CustomLabel : ModelBase { - private int _tag; private bool _isVisible = true; private int _width = 220; private int _height = 15; @@ -18,89 +20,133 @@ internal class CustomLabel : ModelBase private string _name = "Now Playing"; private int _scrollSpeed = 50; - public int Tag + /// + /// Specifies the alignment of the text in the label. + /// + public enum TextAlignment { - get => _tag; - set => _tag = value; + /// + /// Align the text to the left. + /// + Left, + + /// + /// Align the text to the right. + /// + Right, + + /// + /// Align the text in the center. + /// + Center } + /// + /// Gets or sets a value indicating whether if the label is visible. + /// public bool IsVisible { get => _isVisible; set => SetProperty(ref _isVisible, value); } + /// + /// Gets or sets the width of the label. + /// public int Width { get => _width; set => SetProperty(ref _width, value); } + /// + /// Gets or sets the height of the label. + /// public int Height { get => _height; set => SetProperty(ref _height, value); } + /// + /// Gets or sets the x position of the label. + /// public int XPosition { get => _xPosition; set => SetProperty(ref _xPosition, value); } + /// + /// Gets or sets the y position of the label. + /// public int YPosition { get => _yPosition; set => SetProperty(ref _yPosition, value); } + /// + /// Gets or sets the font family of the label. + /// public string FontFamily { get => _fontFamily; set => SetProperty(ref _fontFamily, value); } + /// + /// Gets or sets the font size of the label. + /// public float FontSize { get => _fontSize; set => SetProperty(ref _fontSize, value); } + /// + /// Gets or sets the color of the label. + /// public Color Color { get => _color; set => SetProperty(ref _color, value); } + /// + /// Gets or sets the format for the label. + /// public string FormatString { get => _formatString; set => SetProperty(ref _formatString, value); } + /// + /// Gets or sets the alignment of the text. + /// public TextAlignment Alignment { get => _alignment; set => SetProperty(ref _alignment, value); } + /// + /// Gets or sets the name of the label. + /// public string Name { get => _name; set => SetProperty(ref _name, value); } + /// + /// Gets or sets the scollspeed of the label. + /// public int ScrollSpeed { get => _scrollSpeed; set => SetProperty(ref _scrollSpeed, value); } - - public enum TextAlignment - { - Left, - Right, - Center - } } } diff --git a/src/AudioBand/Models/ModelBase.cs b/src/AudioBand/Models/ModelBase.cs index 03aa23e5..0b7de92a 100644 --- a/src/AudioBand/Models/ModelBase.cs +++ b/src/AudioBand/Models/ModelBase.cs @@ -10,6 +10,14 @@ namespace AudioBand.Models /// internal class ModelBase : INotifyPropertyChanged { + /// + /// Initializes a new instance of the class. + /// + public ModelBase() + { + Logger = LogManager.GetLogger(GetType().FullName); + } + /// public event PropertyChangedEventHandler PropertyChanged; @@ -43,10 +51,5 @@ protected virtual bool SetProperty(ref T field, T newValue, [CallerMemberName RaisePropertyChanged(propertyName); return true; } - - public ModelBase() - { - Logger = LogManager.GetLogger(GetType().FullName); - } } } diff --git a/src/AudioBand/Models/NextButton.cs b/src/AudioBand/Models/NextButton.cs index 3413ba93..ae8cfb3d 100644 --- a/src/AudioBand/Models/NextButton.cs +++ b/src/AudioBand/Models/NextButton.cs @@ -1,5 +1,8 @@ namespace AudioBand.Models { + /// + /// Model for the next button. + /// internal class NextButton : ModelBase { private string _imagePath = ""; @@ -9,36 +12,54 @@ internal class NextButton : ModelBase private int _xPosition = 176; private int _yPosition = 15; + /// + /// Gets or sets the path of the button image. + /// public string ImagePath { get => _imagePath; set => SetProperty(ref _imagePath, value); } + /// + /// Gets or sets a value indicating whether the button is visible. + /// public bool IsVisible { get => _isVisible; set => SetProperty(ref _isVisible, value); } + /// + /// Gets or sets the width of the button. + /// public int Width { get => _width; set => SetProperty(ref _width, value); } + /// + /// Gets or sets the height of the button. + /// public int Height { get => _height; set => SetProperty(ref _height, value); } + /// + /// Gets or sets the x position of the button. + /// public int XPosition { get => _xPosition; set => SetProperty(ref _xPosition, value); } + /// + /// Gets or sets the y position of the button. + /// public int YPosition { get => _yPosition; diff --git a/src/AudioBand/Models/PlayPauseButton.cs b/src/AudioBand/Models/PlayPauseButton.cs index 3f0a9673..cf4d98e0 100644 --- a/src/AudioBand/Models/PlayPauseButton.cs +++ b/src/AudioBand/Models/PlayPauseButton.cs @@ -1,5 +1,8 @@ namespace AudioBand.Models { + /// + /// Model for the play/pause button. + /// internal class PlayPauseButton : ModelBase { private string _playButtonImagePath = ""; @@ -10,42 +13,63 @@ internal class PlayPauseButton : ModelBase private int _height = 12; private bool _isVisible = true; + /// + /// Gets or sets the path for the play image. + /// public string PlayButtonImagePath { get => _playButtonImagePath; set => SetProperty(ref _playButtonImagePath, value); } + /// + /// Gets or sets the path for the pause image. + /// public string PauseButtonImagePath { get => _pauseButtonImagePath; set => SetProperty(ref _pauseButtonImagePath, value); } + /// + /// Gets or sets a value indicating whether the button is visible. + /// public bool IsVisible { get => _isVisible; set => SetProperty(ref _isVisible, value); } + /// + /// Gets or sets the width of the button. + /// public int Width { get => _width; set => SetProperty(ref _width, value); } + /// + /// Gets or sets the height of the button. + /// public int Height { get => _height; set => SetProperty(ref _height, value); } + /// + /// Gets or sets the x position of the button. + /// public int XPosition { get => _xPosition; set => SetProperty(ref _xPosition, value); } + /// + /// Gets or sets the y position of the button. + /// public int YPosition { get => _yPosition; diff --git a/src/AudioBand/Models/PreviousButton.cs b/src/AudioBand/Models/PreviousButton.cs index 7b267952..40f0cdfa 100644 --- a/src/AudioBand/Models/PreviousButton.cs +++ b/src/AudioBand/Models/PreviousButton.cs @@ -1,5 +1,8 @@ namespace AudioBand.Models { + /// + /// Model for the previous button. + /// internal class PreviousButton : ModelBase { private string _imagePath = ""; @@ -9,36 +12,54 @@ internal class PreviousButton : ModelBase private int _xPosition = 30; private int _yPosition = 15; + /// + /// Gets or sets the path of the button image. + /// public string ImagePath { get => _imagePath; set => SetProperty(ref _imagePath, value); } + /// + /// Gets or sets a value indicating whether the button is visible. + /// public bool IsVisible { get => _isVisible; set => SetProperty(ref _isVisible, value); } + /// + /// Gets or sets the width of the button. + /// public int Width { get => _width; set => SetProperty(ref _width, value); } + /// + /// Gets or sets the height of the button. + /// public int Height { get => _height; set => SetProperty(ref _height, value); } + /// + /// Gets or sets the x position of the button. + /// public int XPosition { get => _xPosition; set => SetProperty(ref _xPosition, value); } + /// + /// Gets or sets the y position of the button. + /// public int YPosition { get => _yPosition; diff --git a/src/AudioBand/Models/ProgressBar.cs b/src/AudioBand/Models/ProgressBar.cs index 99e48c5e..4edd1cdc 100644 --- a/src/AudioBand/Models/ProgressBar.cs +++ b/src/AudioBand/Models/ProgressBar.cs @@ -2,6 +2,9 @@ namespace AudioBand.Models { + /// + /// Model for the progress bar. + /// internal class ProgressBar : ModelBase { private Color _foregroundColor = Color.DodgerBlue; @@ -12,42 +15,63 @@ internal class ProgressBar : ModelBase private int _width = 220; private int _height = 2; + /// + /// Gets or sets the foreground color. + /// public Color ForegroundColor { get => _foregroundColor; set => SetProperty(ref _foregroundColor, value); } + /// + /// Gets or sets the background color. + /// public Color BackgroundColor { get => _backgroundColor; set => SetProperty(ref _backgroundColor, value); } + /// + /// Gets or sets a value indicating whether the progress bar is visible. + /// public bool IsVisible { get => _isVisible; set => SetProperty(ref _isVisible, value); } + /// + /// Gets or sets the width of the progress bar. + /// public int Width { get => _width; set => SetProperty(ref _width, value); } + /// + /// Gets or sets the height of the progress bar. + /// public int Height { get => _height; set => SetProperty(ref _height, value); } + /// + /// Gets or sets the x position of the progress bar. + /// public int XPosition { get => _xPosition; set => SetProperty(ref _xPosition, value); } + /// + /// Gets or sets the y position of the progress bar. + /// public int YPosition { get => _yPosition; diff --git a/src/AudioBand/Models/Track.cs b/src/AudioBand/Models/Track.cs index 60539920..ce66a61f 100644 --- a/src/AudioBand/Models/Track.cs +++ b/src/AudioBand/Models/Track.cs @@ -3,6 +3,9 @@ namespace AudioBand.Models { + /// + /// Model for the current track. + /// internal class Track : ModelBase { private bool _isPlaying; @@ -14,6 +17,9 @@ internal class Track : ModelBase private Image _albumArt; private Image _placeholderImage; + /// + /// Gets or sets a value indicating whether the track is playing. + /// public bool IsPlaying { get => _isPlaying; @@ -24,42 +30,64 @@ public bool IsPlaying } } + /// + /// Gets or sets the playback progress of the track. + /// public TimeSpan TrackProgress { get => _trackProgress; set => SetProperty(ref _trackProgress, value); } + /// + /// Gets or sets the length of the track. + /// public TimeSpan TrackLength { get => _trackLength; set => SetProperty(ref _trackLength, value); } + /// + /// Gets or sets the name of the track. + /// public string TrackName { get => _trackName; set => SetProperty(ref _trackName, value); } + /// + /// Gets or sets the artist of the track. + /// public string Artist { get => _artist; set => SetProperty(ref _artist, value); } + /// + /// Gets or sets the album name. + /// public string AlbumName { get => _albumName; set => SetProperty(ref _albumName, value); } + /// + /// Gets or sets the album art. + /// public Image AlbumArt { get => _albumArt ?? _placeholderImage; set => SetProperty(ref _albumArt, value); } + /// + /// Change the placeholder image. + /// + /// The new placeholder image. public void UpdatePlaceholder(Image placeholder) { _placeholderImage = placeholder; diff --git a/src/AudioBand/Properties/AssemblyInfo.cs b/src/AudioBand/Properties/AssemblyInfo.cs index c40a41a7..e4461e58 100644 --- a/src/AudioBand/Properties/AssemblyInfo.cs +++ b/src/AudioBand/Properties/AssemblyInfo.cs @@ -42,4 +42,4 @@ [assembly: AssemblyInformationalVersion("$version$")] [assembly: InternalsVisibleTo("AudioBand.Test")] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/src/AudioBand/Settings/AppSettings.cs b/src/AudioBand/Settings/AppSettings.cs index 7bb3c274..c8bcd883 100644 --- a/src/AudioBand/Settings/AppSettings.cs +++ b/src/AudioBand/Settings/AppSettings.cs @@ -1,56 +1,37 @@ -using AudioBand.Models; -using AudioBand.Settings.Migrations; -using Nett; -using NLog; -using System; +using System; using System.Collections.Generic; using System.Drawing; using System.IO; -using AudioBand.Settings.Models.v2; +using AudioBand.Models; +using AudioBand.Settings.Migrations; +using AudioBand.Settings.Models.V2; +using Nett; +using NLog; using AudioSourceSettings = AudioBand.Models.AudioSourceSettings; namespace AudioBand.Settings { + /// + /// Manages application settings. + /// internal class AppSettings { private static readonly Dictionary SettingsTable = new Dictionary() { - {"0.1", typeof(Settings.Models.v1.AudioBandSettings)}, - {"2", typeof(Settings.Models.v2.Settings)} + { "0.1", typeof(Settings.Models.V1.AudioBandSettings) }, + { "2", typeof(Settings.Models.V2.Settings) } }; - private static string CurrentVersion = "2"; + + private static readonly string CurrentVersion = "2"; 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 = LogManager.GetCurrentClassLogger(); private readonly TomlSettings _tomlSettings; - private Models.v2.Settings _settings; - - public string Version => _settings.Version; - - public string AudioSource - { - get => _settings.AudioSource; - set => _settings.AudioSource = value; - } - - public AlbumArtPopup AlbumArtPopup { get; set; } - - public AlbumArt AlbumArt { get; set; } - - public AudioBand.Models.AudioBand AudioBand { get;set; } - - public List CustomLabels { get; set; } - - public NextButton NextButton { get; set; } - - public PreviousButton PreviousButton { get; set; } - - public PlayPauseButton PlayPauseButton { get; set; } - - public ProgressBar ProgressBar { get; set; } - - public List AudioSourceSettings { get; set; } + private Models.V2.Settings _settings; + /// + /// Initializes a new instance of the class. + /// public AppSettings() { _tomlSettings = TomlSettings.Create(cfg => @@ -81,6 +62,63 @@ public AppSettings() GetModels(); } + /// + /// Gets or sets the name of the current audio source. + /// + public string AudioSource + { + get => _settings.AudioSource; + set => _settings.AudioSource = value; + } + + /// + /// Gets the saved album art popup model. + /// + public AlbumArtPopup AlbumArtPopup { get; private set; } + + /// + /// Gets the saved album art model. + /// + public AlbumArt AlbumArt { get; private set; } + + /// + /// Gets the saved audio band model. + /// + public AudioBand.Models.AudioBand AudioBand { get; private set; } + + /// + /// Gets the saved labels. + /// + public List CustomLabels { get; private set; } + + /// + /// Gets the saved button model. + /// + public NextButton NextButton { get; private set; } + + /// + /// Gets the saved previous button model. + /// + public PreviousButton PreviousButton { get; private set; } + + /// + /// Gets the saved play pause button model. + /// + public PlayPauseButton PlayPauseButton { get; private set; } + + /// + /// Gets the saved progress bar model. + /// + public ProgressBar ProgressBar { get; private set; } + + /// + /// Gets the saved audio source settings. + /// + public List AudioSourceSettings { get; private set; } + + /// + /// Save the settings. + /// public void Save() { try @@ -93,7 +131,7 @@ public void Save() _settings.PreviousButtonSettings = ToSetting(PreviousButton); _settings.PlayPauseButtonSettings = ToSetting(PlayPauseButton); _settings.ProgressBarSettings = ToSetting(ProgressBar); - _settings.AudioSourceSettings = ToSetting>(AudioSourceSettings); + _settings.AudioSourceSettings = ToSetting>(AudioSourceSettings); Toml.WriteFile(_settings, SettingsFilePath, _tomlSettings); } catch (Exception e) @@ -125,7 +163,8 @@ private void LoadSettings() { Toml.WriteFile(file, Path.Combine(SettingsDirectory, $"audioband.settings.{version}"), _tomlSettings); } - _settings = Migration.MigrateSettings(file.Get(SettingsTable[version]), version, CurrentVersion); + + _settings = Migration.MigrateSettings(file.Get(SettingsTable[version]), version, CurrentVersion); } catch (Exception e) { @@ -142,7 +181,7 @@ private TModel ToModel(object setting) } catch (Exception e) { - Logger.Error($"Cannot convert setting {setting} to model {typeof(TModel)}"); + Logger.Error(e, $"Cannot convert setting {setting} to model {typeof(TModel)}"); throw; } } @@ -155,17 +194,17 @@ private T ToSetting(object model) } catch (Exception e) { - Logger.Error($"Cannot model to settings {model} target: {typeof(T)}"); + Logger.Error(e, $"Cannot model to settings {model} target: {typeof(T)}"); throw; } } private void CreateDefault() { - _settings = new Models.v2.Settings + _settings = new Models.V2.Settings { - AudioSourceSettings = new List(), - AudioBandSettings = ToSetting(new AudioBand.Models.AudioBand()), + AudioSourceSettings = new List(), + AudioBandSettings = ToSetting(new AudioBand.Models.AudioBand()), AlbumArtSettings = ToSetting(new AlbumArt()), AudioSource = null, AlbumArtPopupSettings = ToSetting(new AlbumArtPopup()), @@ -173,7 +212,7 @@ private void CreateDefault() NextButtonSettings = ToSetting(new NextButton()), PreviousButtonSettings = ToSetting(new PreviousButton()), ProgressBarSettings = ToSetting(new ProgressBar()), - CustomLabelSettings = new List {ToSetting(new CustomLabel())} + CustomLabelSettings = new List { ToSetting(new CustomLabel()) } }; } } diff --git a/src/AudioBand/Settings/Migrations/ISettingsMigrator.cs b/src/AudioBand/Settings/Migrations/ISettingsMigrator.cs index 68be3361..c8164758 100644 --- a/src/AudioBand/Settings/Migrations/ISettingsMigrator.cs +++ b/src/AudioBand/Settings/Migrations/ISettingsMigrator.cs @@ -5,6 +5,11 @@ /// internal interface ISettingsMigrator { + /// + /// Migrate settings to new version. + /// + /// Old settings to migrate. + /// The new settings. object MigrateSetting(object oldSetting); } } diff --git a/src/AudioBand/Settings/Migrations/Migration.cs b/src/AudioBand/Settings/Migrations/Migration.cs index 01e40014..2fb05e67 100644 --- a/src/AudioBand/Settings/Migrations/Migration.cs +++ b/src/AudioBand/Settings/Migrations/Migration.cs @@ -1,8 +1,7 @@ -using AudioBand.Settings.Models; -using NLog; -using System; +using System; using System.Collections.Generic; using System.Linq; +using NLog; namespace AudioBand.Settings.Migrations { @@ -13,16 +12,24 @@ internal static class Migration { private static readonly Dictionary<(string From, string To), ISettingsMigrator> SupportedMigrations = new Dictionary<(string From, string To), ISettingsMigrator>() { - {("0.1", "2"), new V1ToV2()} + { ("0.1", "2"), new V1ToV2() } }; private static readonly ILogger Logger = LogManager.GetCurrentClassLogger(); + /// + /// Migrate settings to new version. + /// + /// Type of the new settings. + /// The old settings. + /// The version of the old settings. + /// The version of the new settings. + /// New settings. public static TNew MigrateSettings(object oldSettings, string oldVersion, string newVersion) { if (oldVersion == newVersion) { - return (TNew) oldSettings; + return (TNew)oldSettings; } var plan = FindPlan(oldVersion, newVersion); @@ -31,10 +38,10 @@ public static TNew MigrateSettings(object oldSettings, string oldVersion, throw new ArgumentException($"No migration plan from {oldVersion} to {newVersion}"); } - Logger.Debug($"Found old settings v{oldVersion}. Migrating settings using {String.Join("->", plan)}"); + Logger.Debug($"Found old settings v{oldVersion}. Migrating settings using {string.Join("->", plan)}"); object settings = plan.Aggregate(oldSettings, (current, settingsMigrator) => settingsMigrator.MigrateSetting(current)); - return (TNew) settings; + return (TNew)settings; } private static List FindPlan(string from, string to) diff --git a/src/AudioBand/Settings/Migrations/V1ToV2.cs b/src/AudioBand/Settings/Migrations/V1ToV2.cs index 7432be45..42a4eae9 100644 --- a/src/AudioBand/Settings/Migrations/V1ToV2.cs +++ b/src/AudioBand/Settings/Migrations/V1ToV2.cs @@ -1,10 +1,13 @@ using System; using AutoMapper; -using V1Settings = AudioBand.Settings.Models.v1.AudioBandSettings; -using V2Settings = AudioBand.Settings.Models.v2.Settings; +using V1Settings = AudioBand.Settings.Models.V1.AudioBandSettings; +using V2Settings = AudioBand.Settings.Models.V2.Settings; namespace AudioBand.Settings.Migrations { + /// + /// Migrates settings from version 1 to version 2. + /// internal class V1ToV2 : ISettingsMigrator { private static readonly MapperConfiguration MapConfig; @@ -13,35 +16,25 @@ static V1ToV2() { MapConfig = new MapperConfiguration(c => { - c.CreateMap() - .ForMember(dest => dest.XPosition, - opts => opts.MapFrom(source => source.XOffset)); - c.CreateMap(); - c.CreateMap() - .ForMember(dest => dest.AudioSourceName, - opts => opts.MapFrom(source => source.Name)); + c.CreateMap() + .ForMember(dest => dest.XPosition, opts => opts.MapFrom(source => source.XOffset)); + c.CreateMap(); + c.CreateMap() + .ForMember(dest => dest.AudioSourceName, opts => opts.MapFrom(source => source.Name)); c.CreateMap() - .ForMember(dest => dest.AlbumArtPopupSettings, - opts => opts.MapFrom(source => source.AlbumArtPopupAppearance)) - .ForMember(dest => dest.AlbumArtSettings, - opts => opts.MapFrom(source => source.AlbumArtAppearance)) - .ForMember(dest => dest.AudioBandSettings, - opts => opts.MapFrom(source => source.AudioBandAppearance)) - .ForMember(dest => dest.NextButtonSettings, - opts => opts.MapFrom(source => source.NextSongButtonAppearance)) - .ForMember(dest => dest.PlayPauseButtonSettings, - opts => opts.MapFrom(source => source.PlayPauseButtonAppearance)) - .ForMember(dest => dest.PreviousButtonSettings, - opts => opts.MapFrom(source => source.PreviousSongButtonAppearance)) - .ForMember(dest => dest.ProgressBarSettings, - opts => opts.MapFrom(source => source.ProgressBarAppearance)) - .ForMember(dest => dest.CustomLabelSettings, - opts => opts.MapFrom(source => source.TextAppearances)) - .ForMember(dest => dest.Version, - opts => opts.Ignore()); + .ForMember(dest => dest.AlbumArtPopupSettings, opts => opts.MapFrom(source => source.AlbumArtPopupAppearance)) + .ForMember(dest => dest.AlbumArtSettings, opts => opts.MapFrom(source => source.AlbumArtAppearance)) + .ForMember(dest => dest.AudioBandSettings, opts => opts.MapFrom(source => source.AudioBandAppearance)) + .ForMember(dest => dest.NextButtonSettings, opts => opts.MapFrom(source => source.NextSongButtonAppearance)) + .ForMember(dest => dest.PlayPauseButtonSettings, opts => opts.MapFrom(source => source.PlayPauseButtonAppearance)) + .ForMember(dest => dest.PreviousButtonSettings, opts => opts.MapFrom(source => source.PreviousSongButtonAppearance)) + .ForMember(dest => dest.ProgressBarSettings, opts => opts.MapFrom(source => source.ProgressBarAppearance)) + .ForMember(dest => dest.CustomLabelSettings, opts => opts.MapFrom(source => source.TextAppearances)) + .ForMember(dest => dest.Version, opts => opts.Ignore()); }); } + /// public object MigrateSetting(object oldSetting) { var source = oldSetting as V1Settings; diff --git a/src/AudioBand/Settings/Models/v1/AlbumArtAppearance.cs b/src/AudioBand/Settings/Models/v1/AlbumArtAppearance.cs index 28d8d64a..32d07874 100644 --- a/src/AudioBand/Settings/Models/v1/AlbumArtAppearance.cs +++ b/src/AudioBand/Settings/Models/v1/AlbumArtAppearance.cs @@ -1,18 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace AudioBand.Settings.Models.v1 +namespace AudioBand.Settings.Models.V1 { internal class AlbumArtAppearance { public bool IsVisible { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public int XPosition { get; set; } + public int YPosition { get; set; } + public string PlaceholderPath { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v1/AlbumArtPopupAppearance.cs b/src/AudioBand/Settings/Models/v1/AlbumArtPopupAppearance.cs index adc3114b..1e0aaa09 100644 --- a/src/AudioBand/Settings/Models/v1/AlbumArtPopupAppearance.cs +++ b/src/AudioBand/Settings/Models/v1/AlbumArtPopupAppearance.cs @@ -1,17 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace AudioBand.Settings.Models.v1 +namespace AudioBand.Settings.Models.V1 { internal class AlbumArtPopupAppearance { public bool IsVisible { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public int XOffset { get; set; } + public int Margin { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v1/AudioBandAppearance.cs b/src/AudioBand/Settings/Models/v1/AudioBandAppearance.cs index 76171ace..b8ce14b1 100644 --- a/src/AudioBand/Settings/Models/v1/AudioBandAppearance.cs +++ b/src/AudioBand/Settings/Models/v1/AudioBandAppearance.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace AudioBand.Settings.Models.v1 +namespace AudioBand.Settings.Models.V1 { internal class AudioBandAppearance { public int Width { get; set; } + public int Height { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v1/AudioBandSettings.cs b/src/AudioBand/Settings/Models/v1/AudioBandSettings.cs index a9978757..8165a751 100644 --- a/src/AudioBand/Settings/Models/v1/AudioBandSettings.cs +++ b/src/AudioBand/Settings/Models/v1/AudioBandSettings.cs @@ -1,19 +1,29 @@ using System.Collections.Generic; -namespace AudioBand.Settings.Models.v1 +namespace AudioBand.Settings.Models.V1 { internal class AudioBandSettings { public string Version { get; set; } = "0.1"; + public string AudioSource { get; set; } + public AudioBandAppearance AudioBandAppearance { get; set; } = new AudioBandAppearance(); + public PlayPauseButtonAppearance PlayPauseButtonAppearance { get; set; } = new PlayPauseButtonAppearance(); + public NextSongButtonAppearance NextSongButtonAppearance { get; set; } = new NextSongButtonAppearance(); + public PreviousSongButtonAppearance PreviousSongButtonAppearance { get; set; } = new PreviousSongButtonAppearance(); + public List TextAppearances { get; set; } = new List { new TextAppearance() }; + public ProgressBarAppearance ProgressBarAppearance { get; set; } = new ProgressBarAppearance(); + public AlbumArtAppearance AlbumArtAppearance { get; set; } = new AlbumArtAppearance(); + public AlbumArtPopupAppearance AlbumArtPopupAppearance { get; set; } = new AlbumArtPopupAppearance(); + public List AudioSourceSettings { get; set; } = new List(); } } diff --git a/src/AudioBand/Settings/Models/v1/AudioSourceSetting.cs b/src/AudioBand/Settings/Models/v1/AudioSourceSetting.cs index f7018495..86c57922 100644 --- a/src/AudioBand/Settings/Models/v1/AudioSourceSetting.cs +++ b/src/AudioBand/Settings/Models/v1/AudioSourceSetting.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace AudioBand.Settings.Models.v1 +namespace AudioBand.Settings.Models.V1 { internal class AudioSourceSetting { public string Name { get; set; } + public string Value { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v1/AudioSourceSettingsCollection.cs b/src/AudioBand/Settings/Models/v1/AudioSourceSettingsCollection.cs index bb888a03..1a45e4db 100644 --- a/src/AudioBand/Settings/Models/v1/AudioSourceSettingsCollection.cs +++ b/src/AudioBand/Settings/Models/v1/AudioSourceSettingsCollection.cs @@ -1,14 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; -namespace AudioBand.Settings.Models.v1 +namespace AudioBand.Settings.Models.V1 { internal class AudioSourceSettingsCollection { public string Name { get; set; } + public List Settings { get; set; } = new List(); } } diff --git a/src/AudioBand/Settings/Models/v1/NextSongButtonAppearance.cs b/src/AudioBand/Settings/Models/v1/NextSongButtonAppearance.cs index a34d46d0..2fffcf72 100644 --- a/src/AudioBand/Settings/Models/v1/NextSongButtonAppearance.cs +++ b/src/AudioBand/Settings/Models/v1/NextSongButtonAppearance.cs @@ -1,18 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace AudioBand.Settings.Models.v1 +namespace AudioBand.Settings.Models.V1 { internal class NextSongButtonAppearance { public string ImagePath { get; set; } + public bool IsVisible { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public int XPosition { get; set; } + public int YPosition { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v1/PlayPauseButtonAppearance.cs b/src/AudioBand/Settings/Models/v1/PlayPauseButtonAppearance.cs index dbc6e371..f9b0f759 100644 --- a/src/AudioBand/Settings/Models/v1/PlayPauseButtonAppearance.cs +++ b/src/AudioBand/Settings/Models/v1/PlayPauseButtonAppearance.cs @@ -1,19 +1,19 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace AudioBand.Settings.Models.v1 +namespace AudioBand.Settings.Models.V1 { internal class PlayPauseButtonAppearance { public string PlayButtonImagePath { get; set; } + public string PauseButtonImagePath { get; set; } + public int XPosition { get; set; } + public int YPosition { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public bool IsVisible { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v1/PreviousSongButtonAppearance.cs b/src/AudioBand/Settings/Models/v1/PreviousSongButtonAppearance.cs index 49027407..0e03e431 100644 --- a/src/AudioBand/Settings/Models/v1/PreviousSongButtonAppearance.cs +++ b/src/AudioBand/Settings/Models/v1/PreviousSongButtonAppearance.cs @@ -1,18 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace AudioBand.Settings.Models.v1 +namespace AudioBand.Settings.Models.V1 { internal class PreviousSongButtonAppearance { public string ImagePath { get; set; } + public bool IsVisible { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public int XPosition { get; set; } + public int YPosition { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v1/ProgressBarAppearance.cs b/src/AudioBand/Settings/Models/v1/ProgressBarAppearance.cs index e881df02..81ca62ba 100644 --- a/src/AudioBand/Settings/Models/v1/ProgressBarAppearance.cs +++ b/src/AudioBand/Settings/Models/v1/ProgressBarAppearance.cs @@ -1,20 +1,21 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Drawing; -namespace AudioBand.Settings.Models.v1 +namespace AudioBand.Settings.Models.V1 { internal class ProgressBarAppearance { public Color ForegroundColor { get; set; } + public Color BackgroundColor { get; set; } + public bool IsVisible { get; set; } + public int XPosition { get; set; } + public int YPosition { get; set; } + public int Width { get; set; } + public int Height { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v1/TextAppearance.cs b/src/AudioBand/Settings/Models/v1/TextAppearance.cs index 160fa859..ce6fed35 100644 --- a/src/AudioBand/Settings/Models/v1/TextAppearance.cs +++ b/src/AudioBand/Settings/Models/v1/TextAppearance.cs @@ -1,26 +1,32 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Drawing; using AudioBand.Models; -namespace AudioBand.Settings.Models.v1 +namespace AudioBand.Settings.Models.V1 { internal class TextAppearance { public bool IsVisible { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public int XPosition { get; set; } + public int YPosition { get; set; } + public string FontFamily { get; set; } + public float FontSize { get; set; } + public Color Color { get; set; } + public string FormatString { get; set; } + public CustomLabel.TextAlignment Alignment { get; set; } + public string Name { get; set; } + public int ScrollSpeed { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v2/AlbumArtPopupSettings.cs b/src/AudioBand/Settings/Models/v2/AlbumArtPopupSettings.cs index 4d212c82..a6ae293d 100644 --- a/src/AudioBand/Settings/Models/v2/AlbumArtPopupSettings.cs +++ b/src/AudioBand/Settings/Models/v2/AlbumArtPopupSettings.cs @@ -1,11 +1,15 @@ -namespace AudioBand.Settings.Models.v2 +namespace AudioBand.Settings.Models.V2 { internal class AlbumArtPopupSettings { public bool IsVisible { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public int XPosition { get; set; } + public int Margin { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v2/AlbumArtSettings.cs b/src/AudioBand/Settings/Models/v2/AlbumArtSettings.cs index e3d400b4..cd180a2e 100644 --- a/src/AudioBand/Settings/Models/v2/AlbumArtSettings.cs +++ b/src/AudioBand/Settings/Models/v2/AlbumArtSettings.cs @@ -1,12 +1,17 @@ -namespace AudioBand.Settings.Models.v2 +namespace AudioBand.Settings.Models.V2 { internal class AlbumArtSettings { public bool IsVisible { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public int XPosition { get; set; } + public int YPosition { get; set; } + public string PlaceholderPath { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v2/AudioBandSettings.cs b/src/AudioBand/Settings/Models/v2/AudioBandSettings.cs index 166b8f6e..ca4f0b4c 100644 --- a/src/AudioBand/Settings/Models/v2/AudioBandSettings.cs +++ b/src/AudioBand/Settings/Models/v2/AudioBandSettings.cs @@ -1,8 +1,9 @@ -namespace AudioBand.Settings.Models.v2 +namespace AudioBand.Settings.Models.V2 { internal class AudioBandSettings { public int Width { get; set; } + public int Height { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v2/AudioSourceSetting.cs b/src/AudioBand/Settings/Models/v2/AudioSourceSetting.cs index 14e2dda2..82c23204 100644 --- a/src/AudioBand/Settings/Models/v2/AudioSourceSetting.cs +++ b/src/AudioBand/Settings/Models/v2/AudioSourceSetting.cs @@ -1,8 +1,9 @@ -namespace AudioBand.Settings.Models.v2 +namespace AudioBand.Settings.Models.V2 { internal class AudioSourceSetting { public string Name { get; set; } + public string Value { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v2/AudioSourceSettings.cs b/src/AudioBand/Settings/Models/v2/AudioSourceSettings.cs index ad396ea3..1f5d58d5 100644 --- a/src/AudioBand/Settings/Models/v2/AudioSourceSettings.cs +++ b/src/AudioBand/Settings/Models/v2/AudioSourceSettings.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; -namespace AudioBand.Settings.Models.v2 +namespace AudioBand.Settings.Models.V2 { internal class AudioSourceSettings { public string AudioSourceName { get; set; } + public List Settings { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v2/CustomLabelSettings.cs b/src/AudioBand/Settings/Models/v2/CustomLabelSettings.cs index 5c2eb5d4..1c466685 100644 --- a/src/AudioBand/Settings/Models/v2/CustomLabelSettings.cs +++ b/src/AudioBand/Settings/Models/v2/CustomLabelSettings.cs @@ -1,21 +1,32 @@ -using AudioBand.Models; -using System.Drawing; +using System.Drawing; +using AudioBand.Models; -namespace AudioBand.Settings.Models.v2 +namespace AudioBand.Settings.Models.V2 { internal class CustomLabelSettings { public bool IsVisible { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public int XPosition { get; set; } + public int YPosition { get; set; } + public string FontFamily { get; set; } + public float FontSize { get; set; } + public Color Color { get; set; } + public string FormatString { get; set; } + public CustomLabel.TextAlignment Alignment { get; set; } + public string Name { get; set; } + public int ScrollSpeed { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v2/NextButtonSettings.cs b/src/AudioBand/Settings/Models/v2/NextButtonSettings.cs index bca1aef6..02aaf766 100644 --- a/src/AudioBand/Settings/Models/v2/NextButtonSettings.cs +++ b/src/AudioBand/Settings/Models/v2/NextButtonSettings.cs @@ -1,12 +1,17 @@ -namespace AudioBand.Settings.Models.v2 +namespace AudioBand.Settings.Models.V2 { internal class NextButtonSettings { public string ImagePath { get; set; } + public bool IsVisible { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public int XPosition { get; set; } + public int YPosition { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v2/PlayPauseButtonSettings.cs b/src/AudioBand/Settings/Models/v2/PlayPauseButtonSettings.cs index cff8fffa..70122a8d 100644 --- a/src/AudioBand/Settings/Models/v2/PlayPauseButtonSettings.cs +++ b/src/AudioBand/Settings/Models/v2/PlayPauseButtonSettings.cs @@ -1,13 +1,19 @@ -namespace AudioBand.Settings.Models.v2 +namespace AudioBand.Settings.Models.V2 { internal class PlayPauseButtonSettings { public string PlayButtonImagePath { get; set; } + public string PauseButtonImagePath { get; set; } + public int XPosition { get; set; } + public int YPosition { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public bool IsVisible { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v2/PreviousButtonSettings.cs b/src/AudioBand/Settings/Models/v2/PreviousButtonSettings.cs index 1861b9f6..727775a4 100644 --- a/src/AudioBand/Settings/Models/v2/PreviousButtonSettings.cs +++ b/src/AudioBand/Settings/Models/v2/PreviousButtonSettings.cs @@ -1,12 +1,17 @@ -namespace AudioBand.Settings.Models.v2 +namespace AudioBand.Settings.Models.V2 { internal class PreviousButtonSettings { public string ImagePath { get; set; } + public bool IsVisible { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public int XPosition { get; set; } + public int YPosition { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v2/ProgressBarSettings.cs b/src/AudioBand/Settings/Models/v2/ProgressBarSettings.cs index 483578f4..384d6f67 100644 --- a/src/AudioBand/Settings/Models/v2/ProgressBarSettings.cs +++ b/src/AudioBand/Settings/Models/v2/ProgressBarSettings.cs @@ -1,15 +1,21 @@ using System.Drawing; -namespace AudioBand.Settings.Models.v2 +namespace AudioBand.Settings.Models.V2 { internal class ProgressBarSettings { public Color ForegroundColor { get; set; } + public Color BackgroundColor { get; set; } + public bool IsVisible { get; set; } + public int XPosition { get; set; } + public int YPosition { get; set; } + public int Width { get; set; } + public int Height { get; set; } } } diff --git a/src/AudioBand/Settings/Models/v2/Settings.cs b/src/AudioBand/Settings/Models/v2/Settings.cs index b8ed1f78..24301a8d 100644 --- a/src/AudioBand/Settings/Models/v2/Settings.cs +++ b/src/AudioBand/Settings/Models/v2/Settings.cs @@ -1,19 +1,29 @@ using System.Collections.Generic; -namespace AudioBand.Settings.Models.v2 +namespace AudioBand.Settings.Models.V2 { internal class Settings { public string Version { get; set; } = "2"; + public string AudioSource { get; set; } + public AudioBandSettings AudioBandSettings { get; set; } + public PreviousButtonSettings PreviousButtonSettings { get; set; } - public PlayPauseButtonSettings PlayPauseButtonSettings{ get; set; } + + public PlayPauseButtonSettings PlayPauseButtonSettings { get; set; } + public NextButtonSettings NextButtonSettings { get; set; } + public ProgressBarSettings ProgressBarSettings { get; set; } + public AlbumArtSettings AlbumArtSettings { get; set; } + public AlbumArtPopupSettings AlbumArtPopupSettings { get; set; } + public List CustomLabelSettings { get; set; } + public List AudioSourceSettings { get; set; } } } diff --git a/src/AudioBand/Settings/SerializationConversions.cs b/src/AudioBand/Settings/SerializationConversions.cs index d91cccec..63fdc772 100644 --- a/src/AudioBand/Settings/SerializationConversions.cs +++ b/src/AudioBand/Settings/SerializationConversions.cs @@ -3,24 +3,49 @@ namespace AudioBand.Settings { + /// + /// Conversion helpers for serialization. + /// internal static class SerializationConversions { + /// + /// Convert a font to a string representation. + /// + /// Font to convert. + /// The font serialized as a string. public static string FontToString(Font font) { - return String.Join(";", font.Name, font.Size.ToString(), font.Style.ToString(), font.Unit.ToString()); + return string.Join(";", font.Name, font.Size.ToString(), font.Style.ToString(), font.Unit.ToString()); } + /// + /// Converts the string from back to a . + /// + /// String representation of the font. + /// The font. public static Font StringToFont(string fontString) { var vals = fontString.Split(';'); return new Font(vals[0], float.Parse(vals[1]), StringToEnum(vals[2]), StringToEnum(vals[3])); } + /// + /// Converts a string to an enum. + /// + /// The type of the enum. + /// The string value of an enum value. + /// The enum. public static T StringToEnum(string val) { return (T)Enum.Parse(typeof(T), val); } + /// + /// Converts the enum value to a string. + /// + /// Type of the enum. + /// The enum value. + /// The string representing its value. public static string EnumToString(T value) { if (!typeof(T).IsEnum) diff --git a/src/AudioBand/Settings/SettingsMapper.cs b/src/AudioBand/Settings/SettingsMapper.cs index 8eb7bc44..bae63864 100644 --- a/src/AudioBand/Settings/SettingsMapper.cs +++ b/src/AudioBand/Settings/SettingsMapper.cs @@ -1,6 +1,6 @@ using System.Linq; using AudioBand.Models; -using AudioBand.Settings.Models.v2; +using AudioBand.Settings.Models.V2; using AutoMapper; namespace AudioBand.Settings @@ -25,13 +25,13 @@ static SettingsMapper() cfg.CreateMap(); cfg.CreateMap(); - cfg.CreateMap() + cfg.CreateMap() .ForMember(m => m.Value, c => c.MapFrom(s => s.Value)); - cfg.CreateMap() + cfg.CreateMap() .ForMember(m => m.Value, c => c.MapFrom(s => s.Remember ? s.Value.ToString() : null)); - cfg.CreateMap(); - cfg.CreateMap() + cfg.CreateMap(); + cfg.CreateMap() .ForMember(m => m.Settings, c => c.MapFrom(s => s.Settings.Where(se => se.Remember))); cfg.CreateMap(); @@ -62,6 +62,12 @@ public static TModel ToModel(object source) return MapperConfig.CreateMapper().Map(source); } + /// + /// Convert model to setting. + /// + /// Type of model. + /// Model to convert. + /// The setting representation of the model. public static T ToSettings(object source) { return MapperConfig.CreateMapper().Map(source); diff --git a/src/AudioBand/ViewModels/AboutVM.cs b/src/AudioBand/ViewModels/AboutVM.cs index 85ffa31f..106cc977 100644 --- a/src/AudioBand/ViewModels/AboutVM.cs +++ b/src/AudioBand/ViewModels/AboutVM.cs @@ -1,25 +1,56 @@ -using AudioBand.Commands; -using AudioBand.Views.Wpf; -using System.Diagnostics; +using System.Diagnostics; using System.Reflection; +using AudioBand.Commands; +using AudioBand.Views.Wpf; namespace AudioBand.ViewModels { + /// + /// View model for the `about audioband` view. + /// internal class AboutVM { - public string Version { get; } = "AudioBand " + typeof(SettingsWindow).Assembly.GetCustomAttribute().InformationalVersion; - public string ProjectLink { get; } = @"https://github.com/dsafa/audio-band"; - - public RelayCommand ShowHelp { get; } - public RelayCommand OpenProjectLink { get; } - + /// + /// Initializes a new instance of the class. + /// public AboutVM() { OpenProjectLink = new RelayCommand(OpenProjectLinkOnExecute); - ShowHelp = new RelayCommand(Execute); + ShowHelp = new RelayCommand(Execute); + } + + /// + /// Represents the view for the about dialog. + /// + internal interface IAboutView + { + /// + /// Show the about dialong. + /// + void Show(); } - private void Execute(IHelpView helpView) + /// + /// Gets the current audioband version. + /// + public string Version => "AudioBand " + typeof(SettingsWindow).Assembly.GetCustomAttribute().InformationalVersion; + + /// + /// Gets the link to the project + /// + public string ProjectLink => @"https://github.com/dsafa/audio-band"; + + /// + /// Gets the command to show the help dialog. + /// + public RelayCommand ShowHelp { get; } + + /// + /// Gets the command to open the link to the project. + /// + public RelayCommand OpenProjectLink { get; } + + private void Execute(IAboutView helpView) { helpView?.Show(); } @@ -29,9 +60,4 @@ private void OpenProjectLinkOnExecute(object o) Process.Start(ProjectLink); } } - - internal interface IHelpView - { - void Show(); - } } diff --git a/src/AudioBand/ViewModels/AlbumArtPopupVM.cs b/src/AudioBand/ViewModels/AlbumArtPopupVM.cs index dd8ee8d9..9a064016 100644 --- a/src/AudioBand/ViewModels/AlbumArtPopupVM.cs +++ b/src/AudioBand/ViewModels/AlbumArtPopupVM.cs @@ -1,13 +1,23 @@ -using AudioBand.Extensions; +using System.Drawing; +using AudioBand.Extensions; using AudioBand.Models; -using System.Drawing; namespace AudioBand.ViewModels { + /// + /// View model for the album art popup. + /// internal class AlbumArtPopupVM : ViewModelBase { private readonly Track _track; + public AlbumArtPopupVM(AlbumArtPopup model, Track track) + : base(model) + { + _track = track; + SetupModelBindings(_track); + } + [PropertyChangeBinding(nameof(AlbumArtPopup.IsVisible))] public bool IsVisible { @@ -48,12 +58,10 @@ public int Margin [PropertyChangeBinding(nameof(Track.AlbumArt))] public Image AlbumArt => _track.AlbumArt?.Resize(Width, Height); + /// + /// Gets the size of the popup. + /// + /// This property exists so the designer can bind to it. public Size Size => new Size(Width, Height); - - public AlbumArtPopupVM(AlbumArtPopup model, Track track) : base(model) - { - _track = track; - SetupModelBindings(_track); - } } } diff --git a/src/AudioBand/ViewModels/AlbumArtVM.cs b/src/AudioBand/ViewModels/AlbumArtVM.cs index 3baa5e0b..c3ed4c3f 100644 --- a/src/AudioBand/ViewModels/AlbumArtVM.cs +++ b/src/AudioBand/ViewModels/AlbumArtVM.cs @@ -1,15 +1,26 @@ -using AudioBand.Extensions; +using System.Drawing; +using System.IO; +using AudioBand.Extensions; using AudioBand.Models; using Svg; -using System.Drawing; -using System.IO; namespace AudioBand.ViewModels { + /// + /// View model for the album art. + /// internal class AlbumArtVM : ViewModelBase { - private readonly Track _track; private static readonly SvgDocument DefaultAlbumArtPlaceholderSvg = SvgDocument.Open(new MemoryStream(Properties.Resources.placeholder_album)); + private readonly Track _track; + + public AlbumArtVM(AlbumArt model, Track track) + : base(model) + { + _track = track; + SetupModelBindings(_track); + LoadPlaceholder(); + } [PropertyChangeBinding(nameof(Models.AlbumArt.IsVisible))] public bool IsVisible @@ -66,32 +77,35 @@ public string PlaceholderPath [PropertyChangeBinding(nameof(Track.AlbumArt))] public Image AlbumArt => _track.AlbumArt?.Resize(Width, Height); + /// + /// Gets the location of the album art. + /// + /// This property is exists so the designer can bind to it. public Point Location => new Point(Model.XPosition, Model.YPosition); + /// + /// Gets the size of the album art. + /// + /// This property exists so that designer can bind to it. public Size Size => new Size(Width, Height); - public AlbumArtVM(AlbumArt model, Track track) : base(model) - { - _track = track; - SetupModelBindings(_track); - LoadPlaceholder(); - } - - private void LoadPlaceholder() - { - _track.UpdatePlaceholder(LoadImage(Model.PlaceholderPath, DefaultAlbumArtPlaceholderSvg.ToBitmap())); - } - + /// protected override void OnReset() { base.OnReset(); LoadPlaceholder(); } + /// protected override void OnCancelEdit() { base.OnCancelEdit(); LoadPlaceholder(); } + + private void LoadPlaceholder() + { + _track.UpdatePlaceholder(LoadImage(Model.PlaceholderPath, DefaultAlbumArtPlaceholderSvg.ToBitmap())); + } } } diff --git a/src/AudioBand/ViewModels/AlsoNotifyAttribute.cs b/src/AudioBand/ViewModels/AlsoNotifyAttribute.cs new file mode 100644 index 00000000..1f25c7bb --- /dev/null +++ b/src/AudioBand/ViewModels/AlsoNotifyAttribute.cs @@ -0,0 +1,27 @@ +using System; +using System.ComponentModel; + +namespace AudioBand.ViewModels +{ + /// + /// Specifies that event of will be raised for other properties. + /// + [AttributeUsage(AttributeTargets.Property)] + internal class AlsoNotifyAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// with a list of property names to also raise change notifications for. + /// + /// Name of other properties to also notify when the model's value changes. + public AlsoNotifyAttribute(params string[] alsoNotify) + { + AlsoNotify = alsoNotify; + } + + /// + /// Gets the names of other properties to also notify. + /// + public string[] AlsoNotify { get; } + } +} diff --git a/src/AudioBand/ViewModels/AudioBandVM.cs b/src/AudioBand/ViewModels/AudioBandVM.cs index 340c0af4..43232900 100644 --- a/src/AudioBand/ViewModels/AudioBandVM.cs +++ b/src/AudioBand/ViewModels/AudioBandVM.cs @@ -2,8 +2,14 @@ namespace AudioBand.ViewModels { + /// + /// View model for the general application. + /// internal class AudioBandVM : ViewModelBase { + public AudioBandVM(Models.AudioBand model) + : base(model) { } + [PropertyChangeBinding(nameof(Models.AudioBand.Width))] [AlsoNotify(nameof(Size))] public int Width @@ -20,8 +26,10 @@ public int Height set => SetProperty(nameof(Model.Height), value); } + /// + /// Gets the size of the toolbar. + /// + /// This property exists so the designer can bind to it. public Size Size => new Size(Width, Height); - - public AudioBandVM(Models.AudioBand model) : base(model) {} } } diff --git a/src/AudioBand/ViewModels/AudioSourceSettingVM.cs b/src/AudioBand/ViewModels/AudioSourceSettingVM.cs index ca8afd20..e6161ceb 100644 --- a/src/AudioBand/ViewModels/AudioSourceSettingVM.cs +++ b/src/AudioBand/ViewModels/AudioSourceSettingVM.cs @@ -1,75 +1,97 @@ -using AudioBand.AudioSource; +using System; +using AudioBand.AudioSource; using AudioBand.Models; -using System; -using AudioBand.Extensions; +using AudioSourceHost; namespace AudioBand.ViewModels { + /// + /// View model for . + /// internal class AudioSourceSettingVM : ViewModelBase { - private readonly AudioSourceSettingInfo _settingInfo; + private readonly AudioSourceSettingAttribute _settingAttribute; + private readonly IInternalAudioSource _audioSource; /// - /// Setting name + /// Initializes a new instance of the class + /// with the model, setting information and if it was saved, + /// + /// The associated . + /// The . + /// The . + /// If this setting was saved. + public AudioSourceSettingVM(IInternalAudioSource audioSource, AudioSourceSetting model, AudioSourceSettingAttribute settingAttribute, bool saved = true) + : base(model) + { + _settingAttribute = settingAttribute; + _audioSource = audioSource; + + // Model value was deserialized from string maybe so change to correct type + model.Value = TypeConvertHelper.ConvertToType(model.Value, SettingType); + + // If sensitive data and was not saved from before, don't automatically remember it + if (Sensitive && !saved) + { + Remember = false; + } + } + + /// + /// Gets the setting name. /// [PropertyChangeBinding(nameof(AudioSourceSetting.Name))] public string Name => Model.Name; /// - /// Value of the setting, can be any basic type + /// Gets or sets the value of the setting, can be any basic type. /// [PropertyChangeBinding(nameof(AudioSourceSetting.Value))] public object Value { get => Model.Value; - set - { - var res = _settingInfo.ValidateSetting(value); - if (res.IsValid) - { - SetProperty(nameof(Model.Value), value); - } - else - { - RaiseValidationError(res.ErrorMessage); - } - } + set => SetProperty(nameof(Model.Value), value); } /// - /// Whether or not to save the data + /// Gets a value indicating whether gets whether or not to save the data. /// [PropertyChangeBinding(nameof(AudioSourceSetting.Remember))] public bool Remember { get => Model.Remember; - set => SetProperty(nameof(Model.Remember), value); + private set => SetProperty(nameof(Model.Remember), value); } /// - /// If the user shouldn't modify it + /// Gets a value indicating whether the user shouldn't modify it. /// - public bool ReadOnly => _settingInfo.Attribute.Options.HasFlag(SettingOptions.ReadOnly); - public bool Visible => !_settingInfo.Attribute.Options.HasFlag(SettingOptions.Hidden); - public bool Sensitive => _settingInfo.Attribute.Options.HasFlag(SettingOptions.Sensitive); - public string PropertyName => _settingInfo.Property.Name; - public Type SettingType => _settingInfo.PropertyType; - public string Description => _settingInfo.Attribute.Description; - public int Priority => _settingInfo.Attribute.Priority; - - public AudioSourceSettingVM(AudioSourceSetting model, AudioSourceSettingInfo settingInfo, bool saved = true ) : base(model) - { - _settingInfo = settingInfo; - model.Value = settingInfo.ConvertToSettingType(Model.Value); + public bool ReadOnly => _settingAttribute.Options.HasFlag(SettingOptions.ReadOnly); - // If sensitive data and was not saved from before, don't automatically remember it - if (Sensitive && !saved) - { - Remember = false; - } + /// + /// Gets a value indicating whether the setting is visible in the settings window. + /// + public bool Visible => !_settingAttribute.Options.HasFlag(SettingOptions.Hidden); - ApplyChanges(); - } + /// + /// Gets a value indicating whether the setting is sensitive. + /// + public bool Sensitive => _settingAttribute.Options.HasFlag(SettingOptions.Sensitive); + + /// + /// Gets the type of the setting. + /// + public Type SettingType => _audioSource.GetSettingType(Name); + + /// + /// Gets the description of the setting. + /// + public string Description => _settingAttribute.Description; + + /// + /// Gets the priority of the setting. + /// + public int Priority => _settingAttribute.Priority; /// /// Applies the value to the audio source @@ -78,7 +100,7 @@ public void ApplyChanges() { try { - _settingInfo.UpdateAudioSource(Value); + _audioSource[Name] = Value; } catch (Exception) { @@ -86,16 +108,24 @@ public void ApplyChanges() } } - protected override void OnEndEdit() + /// + /// Notify that the value of the setting was changed by the audio source so we have to update the model. + /// + public void ValueChanged() { - base.OnEndEdit(); - if (ReadOnly) return; - ApplyChanges(); + Model.Value = _audioSource[Name]; } - public void ValueChanged() + /// + protected override void OnEndEdit() { - Model.Value = _settingInfo.GetValue(); + base.OnEndEdit(); + if (ReadOnly) + { + return; + } + + ApplyChanges(); } } } diff --git a/src/AudioBand/ViewModels/AudioSourceSettingsVM.cs b/src/AudioBand/ViewModels/AudioSourceSettingsVM.cs index e8403d00..ccb74b3d 100644 --- a/src/AudioBand/ViewModels/AudioSourceSettingsVM.cs +++ b/src/AudioBand/ViewModels/AudioSourceSettingsVM.cs @@ -1,8 +1,7 @@ -using AudioBand.AudioSource; -using AudioBand.Extensions; -using AudioBand.Models; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using AudioBand.AudioSource; +using AudioBand.Models; namespace AudioBand.ViewModels { @@ -11,45 +10,30 @@ namespace AudioBand.ViewModels /// internal class AudioSourceSettingsVM : ViewModelBase { - public string AudioSourceName => Model.AudioSourceName; - public List Settings { get; } - - public AudioSourceSettingsVM(AudioSourceSettings settings, IAudioSource audioSource) : base(settings) + /// + /// Initializes a new instance of the class + /// with the settings and the audio source. + /// + /// Settings for the audio source. + /// The audio source. + public AudioSourceSettingsVM(AudioSourceSettings settings, IInternalAudioSource audioSource) + : base(settings) { Settings = CreateSettingViewModels(Model, audioSource); audioSource.SettingChanged += AudioSourceOnSettingChanged; } - private void AudioSourceOnSettingChanged(object sender, SettingChangedEventArgs e) - { - Settings.FirstOrDefault(s => s.PropertyName == e.PropertyName)?.ValueChanged(); - } - - private List CreateSettingViewModels(AudioSourceSettings existingSettings, IAudioSource source) - { - var viewmodels = new List(); - var audioSourceSettingInfos = source.GetSettings(); - - foreach (var audioSourceSettingInfo in audioSourceSettingInfos.OrderByDescending(s => s.Attribute.Priority)) - { - var matchingSetting = existingSettings.Settings.FirstOrDefault(s => s.Name == audioSourceSettingInfo.Attribute.Name); - if (matchingSetting != null) - { - viewmodels.Add(new AudioSourceSettingVM(matchingSetting, audioSourceSettingInfo)); - } - else - { - var name = audioSourceSettingInfo.Attribute.Name; - var defaultValue = audioSourceSettingInfo.GetValue(); - var newSetting = new AudioSourceSetting {Name = name, Value = defaultValue}; - Model.Settings.Add(newSetting); - viewmodels.Add(new AudioSourceSettingVM(newSetting, audioSourceSettingInfo, false)); - } - } + /// + /// Gets the name of the audio source. + /// + public string AudioSourceName => Model.AudioSourceName; - return viewmodels; - } + /// + /// Gets a of belonging to this audio source. + /// + public List Settings { get; } + /// protected override void OnCancelEdit() { base.OnCancelEdit(); @@ -59,6 +43,7 @@ protected override void OnCancelEdit() } } + /// protected override void OnEndEdit() { base.OnEndEdit(); @@ -68,6 +53,7 @@ protected override void OnEndEdit() } } + /// protected override void OnBeginEdit() { base.OnBeginEdit(); @@ -76,5 +62,41 @@ protected override void OnBeginEdit() audioSourceSettingVm.BeginEdit(); } } + + private void AudioSourceOnSettingChanged(object sender, SettingChangedEventArgs e) + { + Settings.FirstOrDefault(s => s.Name == e.SettingName)?.ValueChanged(); + } + + private List CreateSettingViewModels(AudioSourceSettings existingSettings, IInternalAudioSource source) + { + var viewmodels = new List(); + var settingAttributes = source.Settings; + + foreach (var settingAttribute in settingAttributes) + { + var matchingSetting = existingSettings.Settings.FirstOrDefault(s => s.Name == settingAttribute.Name); + if (matchingSetting != null) + { + viewmodels.Add(new AudioSourceSettingVM(source, matchingSetting, settingAttribute)); + } + else + { + var name = settingAttribute.Name; + var defaultValue = source[name]; + var newSetting = new AudioSourceSetting { Name = name, Value = defaultValue }; + Model.Settings.Add(newSetting); + viewmodels.Add(new AudioSourceSettingVM(source, newSetting, settingAttribute, false)); + } + } + + // apply changes in priority + foreach (var viewModel in viewmodels.OrderByDescending(vm => vm.Priority)) + { + viewModel.ApplyChanges(); + } + + return viewmodels; + } } } diff --git a/src/AudioBand/ViewModels/CustomLabelVM.cs b/src/AudioBand/ViewModels/CustomLabelVM.cs index 882afbe7..c2feb2fc 100644 --- a/src/AudioBand/ViewModels/CustomLabelVM.cs +++ b/src/AudioBand/ViewModels/CustomLabelVM.cs @@ -1,16 +1,22 @@ using System; using System.Collections.Generic; -using AudioBand.Models; using System.Drawing; using System.Linq; +using AudioBand.Models; using Point = System.Drawing.Point; using Size = System.Drawing.Size; using TextAlignment = AudioBand.Models.CustomLabel.TextAlignment; namespace AudioBand.ViewModels { + /// + /// The view model for a custom label. + /// internal class CustomLabelVM : ViewModelBase { + public CustomLabelVM(CustomLabel model) + : base(model) { } + [PropertyChangeBinding(nameof(CustomLabel.Name))] public string Name { @@ -99,12 +105,21 @@ public int ScrollSpeed set => SetProperty(nameof(Model.ScrollSpeed), value); } + /// + /// Gets the location of the custom label. + /// + /// This property exists so the designer can bind to it. public Point Location => new Point(XPosition, YPosition); + /// + /// Gets the size of the custom label. + /// + /// This property exists so the designer can bind to it. public Size Size => new Size(Width, Height); + /// + /// Gets the values of . + /// public IEnumerable TextAlignValues { get; } = Enum.GetValues(typeof(TextAlignment)).Cast(); - - public CustomLabelVM(CustomLabel model) : base(model) {} } -} \ No newline at end of file +} diff --git a/src/AudioBand/ViewModels/CustomLabelsVM.cs b/src/AudioBand/ViewModels/CustomLabelsVM.cs index db5365b4..383a3273 100644 --- a/src/AudioBand/ViewModels/CustomLabelsVM.cs +++ b/src/AudioBand/ViewModels/CustomLabelsVM.cs @@ -1,25 +1,28 @@ -using System; -using AudioBand.Commands; -using AudioBand.Models; -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; +using AudioBand.Commands; +using AudioBand.Models; namespace AudioBand.ViewModels { + /// + /// Viewmodel for all the custom labels. + /// internal class CustomLabelsVM : ViewModelBase { - private List _customLabels; private readonly ICustomLabelHost _labelHost; private readonly HashSet _added = new HashSet(); private readonly HashSet _removed = new HashSet(); + private List _customLabels; - public ObservableCollection CustomLabels { get; } - public RelayCommand AddLabelCommand { get; } - public AsyncRelayCommand RemoveLabelCommand { get; } - public IDialogService DialogService { get; set; } - + /// + /// Initializes a new instance of the class + /// with the list of custom labels and a label host. + /// + /// The custom labels. + /// The host for the labels. public CustomLabelsVM(List customLabels, ICustomLabelHost labelHost) { _customLabels = customLabels; @@ -35,32 +38,28 @@ public CustomLabelsVM(List customLabels, ICustomLabelHost labelHost RemoveLabelCommand = new AsyncRelayCommand(RemoveLabelCommandOnExecute); } - private void AddLabelCommandOnExecute(object o) - { - var newLabel = new CustomLabelVM(new CustomLabel()) {Name = "New Label"}; - CustomLabels.Add(newLabel); - _labelHost.AddCustomTextLabel(newLabel); - - _added.Add(newLabel); - } + /// + /// Gets the collection of custom label viewmodels. + /// + public ObservableCollection CustomLabels { get; } - private async Task RemoveLabelCommandOnExecute(CustomLabelVM labelVm) - { - if (!await DialogService.ShowConfirmationDialogAsync("Delete Label", $"Are you sure you want to delete the label '{labelVm.Name}'?")) - { - return; - } + /// + /// Gets the command to add a new label. + /// + public RelayCommand AddLabelCommand { get; } - CustomLabels.Remove(labelVm); - _labelHost.RemoveCustomTextLabel(labelVm); + /// + /// Gets the command to remove a label. + /// + /// Async so its easier to show a dialog. + public AsyncRelayCommand RemoveLabelCommand { get; } - // Only add to removed if not a new label - if (!_added.Remove(labelVm)) - { - _removed.Add(labelVm); - } - } + /// + /// Gets or sets the dialog service used to show a dialog. + /// + public IDialogService DialogService { get; set; } + /// protected override void OnBeginEdit() { _added.Clear(); @@ -72,6 +71,7 @@ protected override void OnBeginEdit() } } + /// protected override void OnCancelEdit() { foreach (var label in _added) @@ -95,6 +95,7 @@ protected override void OnCancelEdit() } } + /// protected override void OnEndEdit() { _added.Clear(); @@ -108,11 +109,31 @@ protected override void OnEndEdit() _customLabels.Add(customLabelVm.GetModel()); } } - } - internal interface ICustomLabelHost - { - void AddCustomTextLabel(CustomLabelVM label); - void RemoveCustomTextLabel(CustomLabelVM label); + private void AddLabelCommandOnExecute(object o) + { + var newLabel = new CustomLabelVM(new CustomLabel()) { Name = "New Label" }; + CustomLabels.Add(newLabel); + _labelHost.AddCustomTextLabel(newLabel); + + _added.Add(newLabel); + } + + private async Task RemoveLabelCommandOnExecute(CustomLabelVM labelVm) + { + if (!await DialogService.ShowConfirmationDialogAsync("Delete Label", $"Are you sure you want to delete the label '{labelVm.Name}'?")) + { + return; + } + + CustomLabels.Remove(labelVm); + _labelHost.RemoveCustomTextLabel(labelVm); + + // Only add to removed if not a new label + if (!_added.Remove(labelVm)) + { + _removed.Add(labelVm); + } + } } } diff --git a/src/AudioBand/ViewModels/ICustomLabelHost.cs b/src/AudioBand/ViewModels/ICustomLabelHost.cs new file mode 100644 index 00000000..1c829b1c --- /dev/null +++ b/src/AudioBand/ViewModels/ICustomLabelHost.cs @@ -0,0 +1,20 @@ +namespace AudioBand.ViewModels +{ + /// + /// Represents a host for custom labels. + /// + internal interface ICustomLabelHost + { + /// + /// Adds a new custom label. + /// + /// The new label to add. + void AddCustomTextLabel(CustomLabelVM label); + + /// + /// Removes an existing label. + /// + /// The label to remove. + void RemoveCustomTextLabel(CustomLabelVM label); + } +} diff --git a/src/AudioBand/ViewModels/IDialogService.cs b/src/AudioBand/ViewModels/IDialogService.cs index d7c8c549..4c3a2a99 100644 --- a/src/AudioBand/ViewModels/IDialogService.cs +++ b/src/AudioBand/ViewModels/IDialogService.cs @@ -1,10 +1,18 @@ using System.Threading.Tasks; -using System.Windows.Media; namespace AudioBand.ViewModels { + /// + /// Provides functionality to show dialogs. + /// internal interface IDialogService { + /// + /// Show a confirmation dialog. + /// + /// Title of the dialog. + /// Message of the dialog. + /// True if accepted; false otherwise. Task ShowConfirmationDialogAsync(string title, string message); } } diff --git a/src/AudioBand/ViewModels/IResettableObject.cs b/src/AudioBand/ViewModels/IResettableObject.cs index 0dee07a4..0ff4fc93 100644 --- a/src/AudioBand/ViewModels/IResettableObject.cs +++ b/src/AudioBand/ViewModels/IResettableObject.cs @@ -1,12 +1,12 @@ namespace AudioBand.ViewModels { /// - /// Object that can be reset to a default state + /// Provides functionality to reset back to the initial state. /// - interface IResettableObject + internal interface IResettableObject { /// - /// Reset to default state + /// Reset to the initial state. /// void Reset(); } diff --git a/src/AudioBand/ViewModels/NextButtonVM.cs b/src/AudioBand/ViewModels/NextButtonVM.cs index 6ae67204..8741db06 100644 --- a/src/AudioBand/ViewModels/NextButtonVM.cs +++ b/src/AudioBand/ViewModels/NextButtonVM.cs @@ -1,18 +1,28 @@ -using AudioBand.Extensions; +using System.Drawing; +using System.IO; +using AudioBand.Extensions; using AudioBand.Models; using Svg; -using System.Drawing; -using System.IO; namespace AudioBand.ViewModels { + /// + /// View model for the next button. + /// internal class NextButtonVM : ViewModelBase { private static readonly SvgDocument DefaultNextButtonSvg = SvgDocument.Open(new MemoryStream(Properties.Resources.next)); private Image _image; + public NextButtonVM(NextButton model) + : base(model) + { + LoadImage(); + } + public Image Image { + // Need padding to fit image in a button. get => _image.Scale(Width - 1, Height - 1); set => SetProperty(ref _image, value); } @@ -69,30 +79,35 @@ public int YPosition set => SetProperty(nameof(Model.YPosition), value); } + /// + /// Gets the location of the next button. + /// + /// This property exists so the designer can bind to it. public Point Location => new Point(Model.XPosition, Model.YPosition); + /// + /// Gets the size of the next button. + /// + /// This property exists so the designer can bind to it. public Size Size => new Size(Width, Height); - public NextButtonVM(NextButton model) : base(model) - { - LoadImage(); - } - - private void LoadImage() - { - Image = LoadImage(ImagePath, DefaultNextButtonSvg.ToBitmap()); - } - + /// protected override void OnReset() { base.OnReset(); LoadImage(); } + /// protected override void OnCancelEdit() { base.OnCancelEdit(); LoadImage(); } + + private void LoadImage() + { + Image = LoadImage(ImagePath, DefaultNextButtonSvg.ToBitmap()); + } } } diff --git a/src/AudioBand/ViewModels/PlayPauseButtonVM.cs b/src/AudioBand/ViewModels/PlayPauseButtonVM.cs index 09771a52..fc07a3f9 100644 --- a/src/AudioBand/ViewModels/PlayPauseButtonVM.cs +++ b/src/AudioBand/ViewModels/PlayPauseButtonVM.cs @@ -1,11 +1,14 @@ -using AudioBand.Extensions; +using System.Drawing; +using System.IO; +using AudioBand.Extensions; using AudioBand.Models; using Svg; -using System.Drawing; -using System.IO; namespace AudioBand.ViewModels { + /// + /// View model for the play/pause button. + /// internal class PlayPauseButtonVM : ViewModelBase { private static readonly SvgDocument DefaultPlayButtonSvg = SvgDocument.Open(new MemoryStream(Properties.Resources.play)); @@ -14,6 +17,14 @@ internal class PlayPauseButtonVM : ViewModelBase private Image _playImage; private Image _pauseImage; + public PlayPauseButtonVM(PlayPauseButton model, Track track) + : base(model) + { + _track = track; + SetupModelBindings(_track); + LoadImages(); + } + [AlsoNotify(nameof(Image))] public Image PlayImage { @@ -99,37 +110,42 @@ public Image Image get { var image = _track.IsPlaying ? PauseImage : PlayImage; - return image.Scale(Width - 1, Height - 1); // To fit image in button + + // Need padding so the image can fit properly in a button. + return image.Scale(Width - 1, Height - 1); } } + /// + /// Gets the location of the button. + /// + /// This property exists so the designer can bind to it. public Point Location => new Point(Model.XPosition, Model.YPosition); + /// + /// Gets the size of the button. + /// + /// This property exists so the designer can bind to it. public Size Size => new Size(Width, Height); - public PlayPauseButtonVM(PlayPauseButton model, Track track) : base(model) - { - _track = track; - SetupModelBindings(_track); - LoadImages(); - } - - private void LoadImages() - { - PlayImage = LoadImage(PlayImagePath, DefaultPlayButtonSvg.ToBitmap()); - PauseImage = LoadImage(PauseImagePath, DefaultPauseButtonSvg.ToBitmap()); - } - + /// protected override void OnReset() { base.OnReset(); LoadImages(); } + /// protected override void OnCancelEdit() { base.OnCancelEdit(); LoadImages(); } + + private void LoadImages() + { + PlayImage = LoadImage(PlayImagePath, DefaultPlayButtonSvg.ToBitmap()); + PauseImage = LoadImage(PauseImagePath, DefaultPauseButtonSvg.ToBitmap()); + } } } diff --git a/src/AudioBand/ViewModels/PreviousButtonVM.cs b/src/AudioBand/ViewModels/PreviousButtonVM.cs index 8cc4058f..3e679091 100644 --- a/src/AudioBand/ViewModels/PreviousButtonVM.cs +++ b/src/AudioBand/ViewModels/PreviousButtonVM.cs @@ -1,18 +1,28 @@ -using AudioBand.Extensions; +using System.Drawing; +using System.IO; +using AudioBand.Extensions; using AudioBand.Models; using Svg; -using System.Drawing; -using System.IO; namespace AudioBand.ViewModels { + /// + /// View model for the previous button. + /// internal class PreviousButtonVM : ViewModelBase { private static readonly SvgDocument DefaultPreviousButtonSvg = SvgDocument.Open(new MemoryStream(Properties.Resources.previous)); private Image _image; + public PreviousButtonVM(PreviousButton model) + : base(model) + { + LoadImage(); + } + public Image Image { + // Need padding to fit image in a button. get => _image.Scale(Width - 1, Height - 1); set => SetProperty(ref _image, value); } @@ -69,30 +79,33 @@ public int YPosition set => SetProperty(nameof(Model.YPosition), value); } + /// + /// Gets the location of the button. + /// public Point Location => new Point(Model.XPosition, Model.YPosition); + /// + /// Gets the size of the button. + /// public Size Size => new Size(Width, Height); - public PreviousButtonVM(PreviousButton model) : base(model) - { - LoadImage(); - } - - private void LoadImage() - { - Image = LoadImage(ImagePath, DefaultPreviousButtonSvg.ToBitmap()); - } - + /// protected override void OnReset() { base.OnReset(); LoadImage(); } + /// protected override void OnCancelEdit() { base.OnCancelEdit(); LoadImage(); } + + private void LoadImage() + { + Image = LoadImage(ImagePath, DefaultPreviousButtonSvg.ToBitmap()); + } } } diff --git a/src/AudioBand/ViewModels/ProgressBarVM.cs b/src/AudioBand/ViewModels/ProgressBarVM.cs index 9307c015..63c5cf73 100644 --- a/src/AudioBand/ViewModels/ProgressBarVM.cs +++ b/src/AudioBand/ViewModels/ProgressBarVM.cs @@ -1,13 +1,23 @@ using System; -using AudioBand.Models; using System.Drawing; +using AudioBand.Models; namespace AudioBand.ViewModels { + /// + /// View model for the progress bar. + /// internal class ProgressBarVM : ViewModelBase { private readonly Track _track; + public ProgressBarVM(ProgressBar model, Track track) + : base(model) + { + _track = track; + SetupModelBindings(_track); + } + [PropertyChangeBinding(nameof(ProgressBar.ForegroundColor))] public Color ForegroundColor { @@ -67,14 +77,16 @@ public int YPosition [PropertyChangeBinding(nameof(Track.TrackLength))] public TimeSpan TrackLength => _track.TrackLength; + /// + /// Gets the location of the progress bar. + /// + /// This property exists so the designer can bind to it. public Point Location => new Point(Model.XPosition, Model.YPosition); + /// + /// Gets the size of the progress bar. + /// + /// This property exists so the designer can bind to it. public Size Size => new Size(Width, Height); - - public ProgressBarVM(ProgressBar model, Track track) : base(model) - { - _track = track; - SetupModelBindings(_track); - } } } diff --git a/src/AudioBand/ViewModels/PropertyChangeBindingAttribute.cs b/src/AudioBand/ViewModels/PropertyChangeBindingAttribute.cs new file mode 100644 index 00000000..2235bd9c --- /dev/null +++ b/src/AudioBand/ViewModels/PropertyChangeBindingAttribute.cs @@ -0,0 +1,28 @@ +using System; +using System.ComponentModel; + +namespace AudioBand.ViewModels +{ + /// + /// Specifies that event of will be raised for this property + /// when the property of a model changes. + /// + [AttributeUsage(AttributeTargets.Property)] + internal class PropertyChangeBindingAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the property name of the model. + /// + /// Name of the property in the model that will trigger a event. + public PropertyChangeBindingAttribute(string propertyName) + { + PropertyName = propertyName; + } + + /// + /// Gets the name of the property in the model that will trigger a event. + /// + public string PropertyName { get; } + } +} diff --git a/src/AudioBand/ViewModels/SettingsWindowVM.cs b/src/AudioBand/ViewModels/SettingsWindowVM.cs index 438a50d4..173b033e 100644 --- a/src/AudioBand/ViewModels/SettingsWindowVM.cs +++ b/src/AudioBand/ViewModels/SettingsWindowVM.cs @@ -1,27 +1,45 @@ using System.Collections.Generic; +using System.Collections.ObjectModel; namespace AudioBand.ViewModels { + /// + /// View model for the settings window. + /// internal class SettingsWindowVM : ViewModelBase { + private object _selectedVM; + public AudioBandVM AudioBandVM { get; set; } + public AlbumArtPopupVM AlbumArtPopupVM { get; set; } + public AlbumArtVM AlbumArtVM { get; set; } + public CustomLabelsVM CustomLabelsVM { get; set; } + public AboutVM AboutVm { get; set; } + public NextButtonVM NextButtonVM { get; set; } + public PlayPauseButtonVM PlayPauseButtonVM { get; set; } + public PreviousButtonVM PreviousButtonVM { get; set; } + public ProgressBarVM ProgressBarVM { get; set; } - public List AudioSourceSettingsVM { get; set; } - private object _selectedVM; + public ObservableCollection AudioSourceSettingsVM { get; set; } + + /// + /// Gets or sets the currently selected view model. + /// public object SelectedVM { get => _selectedVM; set => SetProperty(ref _selectedVM, value); } + /// protected override void OnBeginEdit() { base.OnBeginEdit(); @@ -40,6 +58,7 @@ protected override void OnBeginEdit() } } + /// protected override void OnCancelEdit() { base.OnCancelEdit(); @@ -58,6 +77,7 @@ protected override void OnCancelEdit() } } + /// protected override void OnEndEdit() { base.OnEndEdit(); diff --git a/src/AudioBand/ViewModels/ViewModelBase.cs b/src/AudioBand/ViewModels/ViewModelBase.cs index ef5348a1..630c7399 100644 --- a/src/AudioBand/ViewModels/ViewModelBase.cs +++ b/src/AudioBand/ViewModels/ViewModelBase.cs @@ -3,36 +3,37 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; using AudioBand.Commands; -using NLog; using AutoMapper; using FastMember; +using NLog; namespace AudioBand.ViewModels { /// - /// Base class for view models. With automatic support for + /// Base class for view models with automatic support for /// , , , and commands. /// internal abstract class ViewModelBase : INotifyPropertyChanged, IEditableObject, IResettableObject, INotifyDataErrorInfo { private readonly Dictionary> _propertyErrors = new Dictionary>(); - /// - public IEnumerable GetErrors(string propertyName) + /// + /// Initializes a new instance of the class. + /// + protected ViewModelBase() { - if (_propertyErrors.TryGetValue(propertyName, out var errors) && errors.Any()) - { - return _propertyErrors[propertyName]; - } + Logger = LogManager.GetLogger(GetType().FullName); + Accessor = TypeAccessor.Create(GetType()); - return null; - } + BeginEditCommand = new RelayCommand(o => BeginEdit()); + EndEditCommand = new RelayCommand(o => EndEdit()); + CancelEditCommand = new RelayCommand(o => CancelEdit()); + ResetCommand = new RelayCommand(o => Reset()); - /// - public bool HasErrors => _propertyErrors.Any(entry => entry.Value?.Any() ?? false); + SetupAlsoNotify(); + } /// public event EventHandler ErrorsChanged; @@ -40,26 +41,55 @@ public IEnumerable GetErrors(string propertyName) /// public event PropertyChangedEventHandler PropertyChanged; + /// + public bool HasErrors => _propertyErrors.Any(entry => entry.Value?.Any() ?? false); + /// - /// Command to start editing. + /// Gets the command to start editing. /// public RelayCommand BeginEditCommand { get; } /// - /// Command to end editing. + /// Gets the command to end editing. /// public RelayCommand EndEditCommand { get; } /// - /// Command to cancel edit. + /// Gets the command to cancel edit. /// public RelayCommand CancelEditCommand { get; } /// - /// Command to reset the state to default. + /// Gets the command to reset the state. /// public RelayCommand ResetCommand { get; } + /// + /// Gets the map from [model property name] to [other vm property names] + /// + protected Dictionary AlsoNotifyMap { get; } = new Dictionary(); + + /// + /// Gets the logger for the view model + /// + protected Logger Logger { get; } + + /// + /// Gets the type accessor for this object. + /// + protected TypeAccessor Accessor { get; } + + /// + public IEnumerable GetErrors(string propertyName) + { + if (_propertyErrors.TryGetValue(propertyName, out var errors) && errors.Any()) + { + return _propertyErrors[propertyName]; + } + + return null; + } + /// public void BeginEdit() { @@ -71,6 +101,7 @@ public void EndEdit() { OnEndEdit(); } + /// public void CancelEdit() { @@ -83,34 +114,6 @@ public void Reset() OnReset(); } - /// - /// Map from [model property name] to [other vm property names] - /// - protected Dictionary AlsoNotifyMap { get; } = new Dictionary(); - - /// - /// Logger for the view model - /// - protected Logger Logger { get; } - - /// - /// Type accessor for the current object. - /// - protected TypeAccessor Accessor { get; } - - protected ViewModelBase() - { - Logger = LogManager.GetLogger(GetType().FullName); - Accessor = TypeAccessor.Create(GetType()); - - BeginEditCommand = new RelayCommand(o => BeginEdit()); - EndEditCommand = new RelayCommand(o => EndEdit()); - CancelEditCommand = new RelayCommand(o => CancelEdit()); - ResetCommand = new RelayCommand(o => Reset()); - - SetupAlsoNotify(); - } - /// /// Notifies subsribers to a property change. /// @@ -125,7 +128,8 @@ protected void RaisePropertyChanged([CallerMemberName] string propertyName = nul /// /// Object type. /// Object to reset. - protected void ResetObject(T obj) where T: new() + protected void ResetObject(T obj) + where T : new() { var mapper = new MapperConfiguration(cfg => cfg.CreateMap()).CreateMapper(); mapper.Map(new T(), obj); @@ -199,7 +203,7 @@ protected void RaiseValidationError(IEnumerable errors, [CallerMemberNam /// Property that failed validation. protected void RaiseValidationError(string error, [CallerMemberName] string propertyName = null) { - RaiseValidationError(new[] {error}, propertyName); + RaiseValidationError(new[] { error }, propertyName); } /// @@ -217,7 +221,7 @@ private void SetupAlsoNotify() var alsoNotifyProperties = Accessor.GetMembers().Where(m => m.IsDefined(typeof(AlsoNotifyAttribute))); foreach (var propertyInfo in alsoNotifyProperties) { - var attr = (AlsoNotifyAttribute) propertyInfo.GetAttribute(typeof(AlsoNotifyAttribute), true); + var attr = (AlsoNotifyAttribute)propertyInfo.GetAttribute(typeof(AlsoNotifyAttribute), true); AlsoNotifyMap.Add(propertyInfo.Name, attr.AlsoNotify); } } diff --git a/src/AudioBand/ViewModels/ViewModelBaseAttributes.cs b/src/AudioBand/ViewModels/ViewModelBaseAttributes.cs deleted file mode 100644 index 97296b49..00000000 --- a/src/AudioBand/ViewModels/ViewModelBaseAttributes.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; - -namespace AudioBand.ViewModels -{ - /// - /// Specifies that will be raised for this property. - /// when the property of a model changes. - /// - [AttributeUsage(AttributeTargets.Property)] - internal class PropertyChangeBindingAttribute : Attribute - { - /// - /// Name of the property in the model that will trigger a . - /// - public string PropertyName { get; } - - /// - /// Creates attribute with the property name. - /// - /// Name of the property in the model that will trigger a . - public PropertyChangeBindingAttribute(string propertyName) - { - PropertyName = propertyName; - } - } - - /// - /// Specifies that will be raise for other properties. - /// - [AttributeUsage(AttributeTargets.Property)] - internal class AlsoNotifyAttribute : Attribute - { - /// - /// Name of other properties to also notify when the model's value changes. - /// - public string[] AlsoNotify { get; } - - /// - /// Creates attribute with list of attributes to notify - /// - /// Name of other properties to also notify when the model's value changes. - public AlsoNotifyAttribute(params string[] alsoNotify) - { - AlsoNotify = alsoNotify; - } - } -} diff --git a/src/AudioBand/ViewModels/ViewModelBase{T}.cs b/src/AudioBand/ViewModels/ViewModelBase{TModel}.cs similarity index 75% rename from src/AudioBand/ViewModels/ViewModelBase{T}.cs rename to src/AudioBand/ViewModels/ViewModelBase{TModel}.cs index 6019e9fa..58acfaa4 100644 --- a/src/AudioBand/ViewModels/ViewModelBase{T}.cs +++ b/src/AudioBand/ViewModels/ViewModelBase{TModel}.cs @@ -16,36 +16,37 @@ namespace AudioBand.ViewModels /// /// Type of the model internal abstract class ViewModelBase : ViewModelBase - where TModel: ModelBase, new() + where TModel : ModelBase, new() { // Mapping from a model and model property to the viewmodel property name private readonly Dictionary<(object model, string modelPropName), string> _modelToPropertyName = new Dictionary<(object model, string modelPropName), string>(); private readonly Dictionary _modelToAccessor = new Dictionary(); - private TModel _backup; private readonly MapperConfiguration _mapperConfiguration = new MapperConfiguration(cfg => cfg.CreateMap()); + private TModel _backup; /// - /// Get the model representation of this viewmodel. By default it returns . + /// Initializes a new instance of the class + /// with the model and hooks up change notifications. /// - /// A model representation - public virtual TModel GetModel() + /// The model. + protected ViewModelBase(TModel model) { - return Model; + Model = model; + SetupModelBindings(Model); } /// - /// Model associated with this view model. + /// Gets the model associated with this view model. /// - protected TModel Model { get; set; } + protected TModel Model { get; } /// - /// Create and instance of a viewmodel and hook up change notifications. + /// Get the model representation of this viewmodel. By default it returns . /// - /// - protected ViewModelBase(TModel model) + /// A model representation + public virtual TModel GetModel() { - Model = model; - SetupModelBindings(Model); + return Model; } /// @@ -62,28 +63,33 @@ protected bool SetProperty(string modelPropertyName, TValue newValue, [C _modelToAccessor[Model][modelPropertyName] = newValue; // See if the value has changed - var currentValue = (TValue) _modelToAccessor[Model][modelPropertyName]; + var currentValue = (TValue)_modelToAccessor[Model][modelPropertyName]; return EqualityComparer.Default.Equals(currentValue, newValue); } /// - /// Setup to recieve change notifications from model. + /// Performs setup to recieve change notifications from . /// /// Model to subscribe to for . - protected void SetupModelBindings(T model) where T : ModelBase + /// The type of the model. + protected void SetupModelBindings(T model) + where T : ModelBase { _modelToAccessor.Add(model, ObjectAccessor.Create(model)); var members = Accessor.GetMembers().Where(m => m.IsDefined(typeof(PropertyChangeBindingAttribute))); foreach (var member in members) { - var bindingAttr = (PropertyChangeBindingAttribute) member.GetAttribute(typeof(PropertyChangeBindingAttribute), true); + var bindingAttr = (PropertyChangeBindingAttribute)member.GetAttribute(typeof(PropertyChangeBindingAttribute), true); _modelToPropertyName.Add((model, bindingAttr.PropertyName), member.Name); } model.PropertyChanged += ModelOnPropertyChanged; } + /// + /// Resets the to a state as if it was just instantiated. + /// protected override void OnReset() { base.OnReset(); @@ -91,6 +97,9 @@ protected override void OnReset() ResetObject(Model); } + /// + /// Cancels edits to the . + /// protected override void OnCancelEdit() { base.OnCancelEdit(); @@ -103,6 +112,9 @@ protected override void OnCancelEdit() _backup = null; } + /// + /// Accepts edits to the . + /// protected override void OnEndEdit() { base.OnEndEdit(); @@ -114,6 +126,9 @@ protected override void OnEndEdit() _backup = null; } + /// + /// Starts tracking changes to the . + /// protected override void OnBeginEdit() { base.OnBeginEdit(); @@ -128,24 +143,6 @@ protected override void OnBeginEdit() _mapperConfiguration.CreateMapper().Map(Model, _backup); } - /// - /// When a model property changes, raise for the corresponding property marked with - /// and also properties in . - /// - private void ModelOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) - { - if (!_modelToPropertyName.TryGetValue((sender, propertyChangedEventArgs.PropertyName), out var propertyName)) return; - RaisePropertyChanged(propertyName); - - if (!AlsoNotifyMap.TryGetValue(propertyName, out var alsoNotfify)) return; - foreach (var name in alsoNotfify) - { - RaisePropertyChanged(name); - } - - OnModelPropertyChanged(propertyName); - } - /// /// Try to load image from path or default image if invalid file. /// @@ -169,6 +166,34 @@ protected Image LoadImage(string path, Image defaultImage) /// Called when a model property changes. /// /// Name of the property that changed - protected virtual void OnModelPropertyChanged(string propertyName) {} + protected virtual void OnModelPropertyChanged(string propertyName) { } + + /// + /// When a model property changes, raise for the corresponding property marked with + /// and also properties in . + /// + /// The sender. + /// The event args. + private void ModelOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) + { + if (!_modelToPropertyName.TryGetValue((sender, propertyChangedEventArgs.PropertyName), out var propertyName)) + { + return; + } + + RaisePropertyChanged(propertyName); + + if (!AlsoNotifyMap.TryGetValue(propertyName, out var alsoNotfify)) + { + return; + } + + foreach (var name in alsoNotfify) + { + RaisePropertyChanged(name); + } + + OnModelPropertyChanged(propertyName); + } } -} \ No newline at end of file +} diff --git a/src/AudioBand/Views/Winforms/AlbumArtTooltip.cs b/src/AudioBand/Views/Winforms/AlbumArtTooltip.cs index 3f767987..998455bf 100644 --- a/src/AudioBand/Views/Winforms/AlbumArtTooltip.cs +++ b/src/AudioBand/Views/Winforms/AlbumArtTooltip.cs @@ -1,35 +1,64 @@ -using CSDeskBand; -using System.ComponentModel; +using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Reflection; using System.Windows.Forms; +using CSDeskBand; using Size = System.Drawing.Size; namespace AudioBand.Views.Winforms { + /// + /// The tooltip for the album art. + /// public class AlbumArtTooltip : ToolTip, IBindableComponent { private readonly MethodInfo _setToolMethod = typeof(ToolTip).GetMethod("SetTool", BindingFlags.Instance | BindingFlags.NonPublic); + /// + /// Initializes a new instance of the class. + /// + public AlbumArtTooltip() + { + Popup += OnPopup; + Draw += OnDraw; + DataBindings = new ControlBindingsCollection(this); + } + + /// + /// Gets or sets the album art of the tooltip. + /// public Image AlbumArt { get; set; } + + /// + /// Gets or sets the x position of the tooltip. + /// public int XPosition { get; set; } + + /// + /// Gets or sets the margin of the tooltip. + /// public int Margin { get; set; } + + /// + /// Gets or sets the size of the tooltip. + /// public Size Size { get; set; } + /// [Browsable(false)] public BindingContext BindingContext { get; set; } = new BindingContext(); + /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] public ControlBindingsCollection DataBindings { get; } - public AlbumArtTooltip() - { - Popup += OnPopup; - Draw += OnDraw; - DataBindings = new ControlBindingsCollection(this); - } - + /// + /// Show the tooltip without needing focus. + /// + /// Name of the tooltip + /// The parent control. + /// The taskbar information. public void ShowWithoutRequireFocus(string name, MainControl control, TaskbarInfo taskbarInfo) { if (!Active) diff --git a/src/AudioBand/Views/Winforms/EnhancedProgressBar.cs b/src/AudioBand/Views/Winforms/EnhancedProgressBar.cs index 1ba244fe..f1d3c477 100644 --- a/src/AudioBand/Views/Winforms/EnhancedProgressBar.cs +++ b/src/AudioBand/Views/Winforms/EnhancedProgressBar.cs @@ -5,12 +5,25 @@ namespace AudioBand.Views.Winforms { - // So we can draw with our own color + /// + /// A enhanced progress bar supporting custom colors. + /// public class EnhancedProgressBar : ProgressBar { private TimeSpan _progress; private TimeSpan _total; + /// + /// Initializes a new instance of the class. + /// + public EnhancedProgressBar() + { + SetStyle(ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true); + } + + /// + /// Gets or sets the current progress. + /// [Browsable(true)] [Bindable(BindableSupport.Yes)] public TimeSpan Progress @@ -23,6 +36,9 @@ public TimeSpan Progress } } + /// + /// Gets or sets the total time of the progress bar. + /// [Browsable(true)] [Bindable(BindableSupport.Yes)] public TimeSpan Total @@ -35,11 +51,7 @@ public TimeSpan Total } } - public EnhancedProgressBar() - { - SetStyle(ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true); - } - + /// protected override void OnPaint(PaintEventArgs e) { var rect = e.ClipRectangle; diff --git a/src/AudioBand/Views/Winforms/FormattedTextLabel.cs b/src/AudioBand/Views/Winforms/FormattedTextLabel.cs index 1efbae0a..abf8db2f 100644 --- a/src/AudioBand/Views/Winforms/FormattedTextLabel.cs +++ b/src/AudioBand/Views/Winforms/FormattedTextLabel.cs @@ -1,17 +1,59 @@ using System; using System.Drawing; using System.Windows.Forms; -using AudioBand.Models; using TextAlignment= AudioBand.Models.CustomLabel.TextAlignment; using Timer = System.Windows.Forms.Timer; namespace AudioBand.Views.Winforms { /// - /// A text label that supports formatting with placeholders + /// A text label that supports custom formatting. /// internal class FormattedTextLabel : Control { + private const int TickRateMs = 20; + private const int TickPerS = 1000 / TickRateMs; + private const int TextMargin = 60; // Spacing between scrolling text + private const int WidthPadding = 4; + private readonly Timer _scrollingTimer = new Timer { Interval = TickRateMs }; + private readonly FormattedTextRenderer _renderer; + private float _textXPos; + private float _duplicateXPos; // Draw 2 labels so that there wont be a gap between + private int _textWidth; + + private string _format; + private Color _defaultColor; + private float _fontSize; + private string _fontFamily; + private TextAlignment _alignment; + private string _artist; + private string _songName; + private string _albumName; + private TimeSpan _songProgress; + private TimeSpan _songLength; + private int _scrollSpeed; + private float _scrollDelta; + private Bitmap _renderedText; + + /// + /// Initializes a new instance of the class + /// with initial display values. + /// + /// The text format. + /// The default color. + /// The font size. + /// The font family. + /// The text alignment. + public FormattedTextLabel(string format, Color defaultColor, float fontSize, string fontFamily, TextAlignment alignment) + { + DoubleBuffered = true; + _renderer = new FormattedTextRenderer(format, defaultColor, fontSize, fontFamily, alignment); + _scrollingTimer.Tick += ScrollingTimerOnTick; + } + + /// + /// Gets or sets the format of the label. + /// public string Format { get => _format; @@ -23,6 +65,9 @@ public string Format } } + /// + /// Gets or sets the default color of the text. + /// public Color DefaultColor { get => _defaultColor; @@ -34,6 +79,9 @@ public Color DefaultColor } } + /// + /// Gets or sets the font size of the text. + /// public float FontSize { get => _fontSize; @@ -45,6 +93,9 @@ public float FontSize } } + /// + /// Gets or sets the font family of the text. + /// public string FontFamily { get => _fontFamily; @@ -56,6 +107,9 @@ public string FontFamily } } + /// + /// Gets or sets the text alignment. + /// public TextAlignment Alignment { get => _alignment; @@ -67,6 +121,9 @@ public TextAlignment Alignment } } + /// + /// Gets or sets the current artist to display. + /// public string Artist { get => _artist; @@ -82,6 +139,9 @@ public string Artist } } + /// + /// Gets or sets the current song name. + /// public string SongName { get => _songName; @@ -97,6 +157,9 @@ public string SongName } } + /// + /// Gets or sets the current album name. + /// public string AlbumName { get => _albumName; @@ -112,6 +175,9 @@ public string AlbumName } } + /// + /// Gets or sets the current song progress. + /// public TimeSpan SongProgress { get => _songProgress; @@ -127,6 +193,9 @@ public TimeSpan SongProgress } } + /// + /// Gets or sets the current song length. + /// public TimeSpan SongLength { get => _songLength; @@ -142,6 +211,9 @@ public TimeSpan SongLength } } + /// + /// Gets or sets the scroll speed of the label. + /// public int ScrollSpeed { get => _scrollSpeed; @@ -153,61 +225,7 @@ public int ScrollSpeed } } - internal int TagId { get; set; } - - private const int TickRateMs = 20; - private const int TickPerS = 1000 / TickRateMs; - private readonly Timer _scrollingTimer = new Timer { Interval = TickRateMs}; - private const int TextMargin = 60; //Spacing between scrolling text - private float _textXPos; - private float _duplicateXPos; // Draw 2 labels so that there wont be a gap between - private int _textWidth; - private const int WidthPadding = 4; - - private readonly FormattedTextRenderer _renderer; - private string _format; - private Color _defaultColor; - private float _fontSize; - private string _fontFamily; - private TextAlignment _alignment; - private string _artist; - private string _songName; - private string _albumName; - private TimeSpan _songProgress; - private TimeSpan _songLength; - private int _scrollSpeed; - private float _scrollDelta; - private Bitmap _renderedText; - - public FormattedTextLabel(string format, Color defaultColor, float fontSize, string fontFamily, TextAlignment alignment) - { - DoubleBuffered = true; - _renderer = new FormattedTextRenderer(format, defaultColor, fontSize, fontFamily, alignment); - _scrollingTimer.Tick += ScrollingTimerOnTick; - } - - private void ScrollingTimerOnTick(object sender, EventArgs eventArgs) - { - UpdateTextPositions(); - Refresh(); - } - - private void UpdateTextPositions() - { - if (_textXPos + _textWidth + TextMargin < 0) - { - _textXPos = _duplicateXPos + _textWidth + TextMargin; - } - - if (_duplicateXPos + _textWidth + TextMargin < 0) - { - _duplicateXPos = _textXPos + _textWidth + TextMargin; - } - - _textXPos -= _scrollDelta; - _duplicateXPos -= _scrollDelta; - } - + /// protected override void OnPaint(PaintEventArgs e) { // measure @@ -233,16 +251,47 @@ protected override void OnPaint(PaintEventArgs e) _textXPos = GetNormalTextPos(); } - Draw(e.Graphics, (int) _textXPos); + Draw(e.Graphics, (int)_textXPos); if (_scrollingTimer.Enabled) { - Draw(e.Graphics, (int) _duplicateXPos); + Draw(e.Graphics, (int)_duplicateXPos); + } + } + /// + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + _scrollingTimer.Stop(); + } + + private void ScrollingTimerOnTick(object sender, EventArgs eventArgs) + { + UpdateTextPositions(); + Refresh(); + } + + private void UpdateTextPositions() + { + if (_textXPos + _textWidth + TextMargin < 0) + { + _textXPos = _duplicateXPos + _textWidth + TextMargin; } + + if (_duplicateXPos + _textWidth + TextMargin < 0) + { + _duplicateXPos = _textXPos + _textWidth + TextMargin; + } + + _textXPos -= _scrollDelta; + _duplicateXPos -= _scrollDelta; } - // Text position when not scrolling + /// + /// Get the text position when not scrolling + /// + /// The x position. private int GetNormalTextPos() { switch (Alignment) @@ -270,11 +319,5 @@ private void Draw(Graphics g, int xpos) rect.X = xpos; g.DrawImage(_renderedText, xpos, 0, _renderedText.Width, _renderedText.Height); } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - _scrollingTimer.Stop(); - } } } diff --git a/src/AudioBand/Views/Winforms/FormattedTextRenderer.cs b/src/AudioBand/Views/Winforms/FormattedTextRenderer.cs index 2aabbc06..8c0da2c2 100644 --- a/src/AudioBand/Views/Winforms/FormattedTextRenderer.cs +++ b/src/AudioBand/Views/Winforms/FormattedTextRenderer.cs @@ -10,6 +10,9 @@ namespace AudioBand.Views.Winforms { + /// + /// Renders formatted text. + /// internal class FormattedTextRenderer { private const char PlaceholderStartToken = '{'; @@ -23,8 +26,103 @@ internal class FormattedTextRenderer private const string ItalicsStyle = "&"; private const string UnderlineStyle = "_"; + private const string TimeFormat = @"m\:ss"; + private const string Styles = BoldStyle + ItalicsStyle + UnderlineStyle; + private const string Tags = ArtistPlaceholder + "|" + SongNamePlaceholder + "|" + AlbumNamePlaceholder + "|" + CurrentTimePlaceholder + "|" + SongLengthPlaceholder; + private static readonly Regex PlaceholderPattern = new Regex($@"(?