Compare commits
	
		
			2 Commits
		
	
	
		
			c5e8534af7
			...
			792437b839
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 792437b839 | |||
| 5bfa7d3b5b | 
@@ -155,7 +155,9 @@ namespace unison
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                Debug.WriteLine("SafelySendCommandAsync => before command");
 | 
			
		||||
                IMpdMessage<T> response = await _commandConnection.SendAsync(command);
 | 
			
		||||
                Debug.WriteLine("SafelySendCommandAsync => after command");
 | 
			
		||||
                if (!response.IsResponseValid)
 | 
			
		||||
                {
 | 
			
		||||
                    string mpdError = response.Response?.Result?.MpdError;
 | 
			
		||||
@@ -498,6 +500,8 @@ namespace unison
 | 
			
		||||
        public void Next() => SendCommand(new NextCommand());
 | 
			
		||||
        public void PlayPause() => SendCommand(new PauseResumeCommand());
 | 
			
		||||
 | 
			
		||||
        public void Play(int pos) => SendCommand(new PlayCommand(pos));
 | 
			
		||||
 | 
			
		||||
        public void Random() => SendCommand(new RandomCommand(!_currentRandom));
 | 
			
		||||
        public void Repeat() => SendCommand(new RepeatCommand(!_currentRepeat));
 | 
			
		||||
        public void Single() => SendCommand(new SingleCommand(!_currentSingle));
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@@ -1,13 +1,14 @@
 | 
			
		||||
# unison
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
**unison** is a very simple [Music Player Daemon (MPD)](https://www.musicpd.org/) daemon client with the following goals:
 | 
			
		||||
 | 
			
		||||
* lightweight window that can be toggled with shortcuts
 | 
			
		||||
* music control through rebindable shortcuts
 | 
			
		||||
* shuffle panel
 | 
			
		||||
* [Snapcast](https://mjaggard.github.io/snapcast/) integration
 | 
			
		||||
* Radio stations
 | 
			
		||||
* radio stations
 | 
			
		||||
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
@@ -15,13 +16,25 @@
 | 
			
		||||
 | 
			
		||||
By default, unison works as a daemon in the taskbar system tray. You can display the main window when needed at any time with a shortcut.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
### Shortcuts
 | 
			
		||||
 | 
			
		||||
You can control your music at anytime with the shortcuts. They are usable system-wide, even if the window is not visible. They are of course fully rebindable.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
### Shuffle panel
 | 
			
		||||
 | 
			
		||||
One of unison's main feature is a complete shuffle system based on criterias, aka a smart playlist.
 | 
			
		||||
 | 
			
		||||
You have two options:
 | 
			
		||||
* **Add to queue** allows you to add a defined number of songs to the queue.
 | 
			
		||||
* **Continuous shuffle** allows you, as long as the program is running, to automatically add songs to the queue.
 | 
			
		||||
 | 
			
		||||
Each of these options work with filters, but if none are selected, it is based on the entire library.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
### Snapcast
 | 
			
		||||
 | 
			
		||||
@@ -31,12 +44,13 @@ Embedding a Snapcast client allows to listen to music on multiple devices. For e
 | 
			
		||||
 | 
			
		||||
Through [Radio-Browser](https://www.radio-browser.info), a community database, you can play radio streams directly from unison. There are more than 28,000 stations recorded on this service, so it is a nice way to discover new music and cultures.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## Planned features
 | 
			
		||||
 | 
			
		||||
* A complete shuffle system based on set criteria, aka a smart playlist.
 | 
			
		||||
* Playlist, queue and library management. I use other software to do it, but I will implement them at some point.
 | 
			
		||||
* Playlist, queue and library management
 | 
			
		||||
* More options for the shuffle panel
 | 
			
		||||
* Dark mode
 | 
			
		||||
 | 
			
		||||
## Translations
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								Screenshots/screen-mainwindow.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 48 KiB  | 
| 
		 Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Screenshots/screen-shortcuts.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 13 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Screenshots/screen-shuffle.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 9.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Screenshots/screen-systray.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 34 KiB  | 
| 
		 Before Width: | Height: | Size: 48 KiB  | 
| 
		 Before Width: | Height: | Size: 34 KiB  | 
| 
		 Before Width: | Height: | Size: 12 KiB  | 
@@ -50,12 +50,13 @@ namespace unison
 | 
			
		||||
        {
 | 
			
		||||
            if (_mpd.IsConnected())
 | 
			
		||||
            {
 | 
			
		||||
                _mpd.QueryStats();
 | 
			
		||||
                _settingsWin.UpdateStats();
 | 
			
		||||
 | 
			
		||||
                Snapcast.IsEnabled = true;
 | 
			
		||||
                ConnectionOkIcon.Visibility = Visibility.Visible;
 | 
			
		||||
                ConnectionFailIcon.Visibility = Visibility.Collapsed;
 | 
			
		||||
 | 
			
		||||
                _shuffleWin.ListGenre();
 | 
			
		||||
                _shuffleWin.ListFolder();
 | 
			
		||||
                _shuffleWin.Initialize();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
@@ -117,29 +118,15 @@ namespace unison
 | 
			
		||||
 | 
			
		||||
            Debug.WriteLine("Song changed called!");
 | 
			
		||||
 | 
			
		||||
            // handle continuous shuffle
 | 
			
		||||
            if (_shuffleWin.GetContinuous())
 | 
			
		||||
            {
 | 
			
		||||
                System.Collections.Generic.IEnumerable<MpcNET.Types.IMpdFile> a = await _mpd.SafelySendCommandAsync(new PlaylistCommand());
 | 
			
		||||
                int queueSize = 0;
 | 
			
		||||
                foreach (var i in a)
 | 
			
		||||
                {
 | 
			
		||||
                    Debug.WriteLine(i.Path);
 | 
			
		||||
                    queueSize++;
 | 
			
		||||
                }
 | 
			
		||||
                Debug.WriteLine("queue size is: " + queueSize);
 | 
			
		||||
            if (!_shuffleWin.GetContinuous())
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
                if (queueSize < 5)
 | 
			
		||||
                {
 | 
			
		||||
                    _shuffleWin.AddContinuousSongs();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // query queue
 | 
			
		||||
                // if (queue.SongRemaining < 5)
 | 
			
		||||
                //{
 | 
			
		||||
                //    // query shuffle songs
 | 
			
		||||
                //}
 | 
			
		||||
            }
 | 
			
		||||
            NextTrack.IsEnabled = false;
 | 
			
		||||
            PreviousTrack.IsEnabled = false;
 | 
			
		||||
            await _shuffleWin.HandleContinuous();
 | 
			
		||||
            NextTrack.IsEnabled = true;
 | 
			
		||||
            PreviousTrack.IsEnabled = true;
 | 
			
		||||
            Debug.WriteLine("finished continuous");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void OnStatusChanged(object sender, EventArgs e)
 | 
			
		||||
@@ -168,9 +155,6 @@ namespace unison
 | 
			
		||||
                    DefaultState();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _mpd.QueryStats();
 | 
			
		||||
            _settingsWin.UpdateStats();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void DefaultState(bool LostConnection = false)
 | 
			
		||||
@@ -243,8 +227,18 @@ namespace unison
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Pause_Clicked(object sender, RoutedEventArgs e) => _mpd.PlayPause();
 | 
			
		||||
        public void Previous_Clicked(object sender, RoutedEventArgs e) =>  _mpd.Prev();
 | 
			
		||||
        public void Next_Clicked(object sender, RoutedEventArgs e) => _mpd.Next();
 | 
			
		||||
 | 
			
		||||
        public void Previous_Clicked(object sender, RoutedEventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            if (PreviousTrack.IsEnabled)
 | 
			
		||||
                _mpd.Prev();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Next_Clicked(object sender, RoutedEventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            if (NextTrack.IsEnabled)
 | 
			
		||||
                _mpd.Next();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Random_Clicked(object sender, RoutedEventArgs e) => _mpd.Random();
 | 
			
		||||
        public void Repeat_Clicked(object sender, RoutedEventArgs e) => _mpd.Repeat();
 | 
			
		||||
 
 | 
			
		||||
@@ -208,19 +208,16 @@
 | 
			
		||||
                            </GroupBox.Header>
 | 
			
		||||
                            <Grid MaxWidth="500">
 | 
			
		||||
                                <StackPanel>
 | 
			
		||||
                                    <StackPanel Orientation="Horizontal">
 | 
			
		||||
                                        <TextBox TextWrapping="Wrap" Width="25" PreviewTextInput="NumberValidationTextBox" Margin="0,2,0,0"/>
 | 
			
		||||
                                        <TextBlock Text="Prevent repetition rate (0-100%)" TextWrapping="Wrap" Margin="5,2,0,0"/>
 | 
			
		||||
                                    </StackPanel>
 | 
			
		||||
                                    <TextBlock TextWrapping="Wrap" Margin="0,10,0,0">
 | 
			
		||||
                                    <TextBlock TextWrapping="Wrap">
 | 
			
		||||
                                        <Run>The shuffle window allows to add random songs to your queue. Both options take into account the filter.</Run>
 | 
			
		||||
                                        <Run>If the filter is empty, the entire music library is taken into account.</Run><LineBreak/><LineBreak/>
 | 
			
		||||
                                        <Run FontWeight="Bold">Continuous shuffle</Run><LineBreak/>
 | 
			
		||||
                                        <Run>By enabling this option, unison will automatically add songs to the queue so you never run out of songs to listen to.</Run>
 | 
			
		||||
                                        <LineBreak/><LineBreak/>
 | 
			
		||||
                                        
 | 
			
		||||
                                        <Run FontWeight="Bold">Add to queue</Run><LineBreak/>
 | 
			
		||||
                                        <Run>Add a fixed number of songs to the queue. It can take a long time to add more than 100 songs, so the option is limited to 1000 songs.</Run>
 | 
			
		||||
                                        <LineBreak/><LineBreak/>
 | 
			
		||||
                                        
 | 
			
		||||
                                        <Run FontWeight="Bold">Continuous shuffle</Run><LineBreak/>
 | 
			
		||||
                                        <Run>By enabling this option, unison will automatically add songs to the queue so you never run out of songs to listen to.</Run>
 | 
			
		||||
                                    </TextBlock>
 | 
			
		||||
                                </StackPanel>
 | 
			
		||||
                            </Grid>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,44 @@
 | 
			
		||||
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 | 
			
		||||
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 | 
			
		||||
        xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
 | 
			
		||||
        xmlns:local="clr-namespace:unison"
 | 
			
		||||
        xmlns:local="clr-namespace:unison" xmlns:sys="clr-namespace:System;assembly=System.Runtime"
 | 
			
		||||
        mc:Ignorable="d"
 | 
			
		||||
        Title="Shuffle" Closing="Window_Closing" SizeToContent="WidthAndHeight" ResizeMode="NoResize">
 | 
			
		||||
 | 
			
		||||
    <Window.Resources>
 | 
			
		||||
        <x:Array x:Key="FilterType" Type="sys:String">
 | 
			
		||||
            <sys:String>Song</sys:String>
 | 
			
		||||
            <sys:String>Artist</sys:String>
 | 
			
		||||
            <sys:String>Album</sys:String>
 | 
			
		||||
            <sys:String>Year</sys:String>
 | 
			
		||||
            <sys:String>Genre</sys:String>
 | 
			
		||||
            <sys:String>Directory</sys:String>
 | 
			
		||||
        </x:Array>
 | 
			
		||||
        <x:Array x:Key="OperatorTypeA" Type="sys:String">
 | 
			
		||||
            <sys:String>contains</sys:String>
 | 
			
		||||
            <sys:String>is</sys:String>
 | 
			
		||||
            <sys:String>is not</sys:String>
 | 
			
		||||
        </x:Array>
 | 
			
		||||
        <x:Array x:Key="OperatorTypeB" Type="sys:String">
 | 
			
		||||
            <sys:String>is</sys:String>
 | 
			
		||||
            <sys:String>is not</sys:String>
 | 
			
		||||
        </x:Array>
 | 
			
		||||
        <x:Array x:Key="OperatorTypeC" Type="sys:String">
 | 
			
		||||
            <sys:String>is</sys:String>
 | 
			
		||||
        </x:Array>
 | 
			
		||||
 | 
			
		||||
        <DataTemplate x:Key="FilterPanel">
 | 
			
		||||
            <StackPanel Orientation="Horizontal" Margin="0,5,0,0">
 | 
			
		||||
                <ComboBox x:Name="FilterType" SelectionChanged="FilterType_SelectionChanged" ItemsSource="{StaticResource FilterType}" SelectedIndex="0" Width="100" ScrollViewer.CanContentScroll="False" FocusVisualStyle="{x:Null}"/>
 | 
			
		||||
                <ComboBox x:Name="FilterOperator" SelectionChanged="OperatorType_SelectionChanged" ItemsSource="{StaticResource OperatorTypeA}" SelectedIndex="0" Width="80" ScrollViewer.CanContentScroll="False" Margin="5,0,0,0" FocusVisualStyle="{x:Null}"/>
 | 
			
		||||
                <ComboBox x:Name="FilterList" SelectedIndex="0" Width="240" Visibility="Collapsed" ScrollViewer.CanContentScroll="False" Margin="5,0,0,0" FocusVisualStyle="{x:Null}"/>
 | 
			
		||||
                <TextBox x:Name="FilterValue" Width="240" Margin="5,0,0,0"/>
 | 
			
		||||
                <Button Content="-" Padding="5, 2" Click="RemoveFilter_Clicked" Width="20" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}" Margin="5,0,0,0"/>
 | 
			
		||||
                <Button Content="+" Padding="5, 2" Click="AddFilter_Clicked" Width="20" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}" Margin="5,0,0,0"/>
 | 
			
		||||
            </StackPanel>
 | 
			
		||||
        </DataTemplate>
 | 
			
		||||
    </Window.Resources>
 | 
			
		||||
 | 
			
		||||
    <Grid>
 | 
			
		||||
        <StackPanel>
 | 
			
		||||
            <StackPanel HorizontalAlignment="Left" Orientation="Vertical" Margin="5,0,5,5">
 | 
			
		||||
@@ -19,83 +53,63 @@
 | 
			
		||||
                        </TextBlock>
 | 
			
		||||
                    </GroupBox.Header>
 | 
			
		||||
                    <StackPanel Orientation="Vertical" Margin="5,0,5,0">
 | 
			
		||||
                        <StackPanel Orientation="Horizontal">
 | 
			
		||||
                            <StackPanel Orientation="Vertical">
 | 
			
		||||
                                <TextBlock Text="Song" Margin="0,0,0,2"/>
 | 
			
		||||
                                <TextBox x:Name="Song" Width="240" Margin="5,0,0,0"/>
 | 
			
		||||
 | 
			
		||||
                        <StackPanel x:Name="FilterPanel">
 | 
			
		||||
                            <ContentPresenter ContentTemplate="{StaticResource FilterPanel}"/>
 | 
			
		||||
                        </StackPanel>
 | 
			
		||||
 | 
			
		||||
                            <StackPanel Orientation="Vertical" Margin="20,0,0,0">
 | 
			
		||||
                                <TextBlock Text="Artist" Margin="0,0,0,2"/>
 | 
			
		||||
                                <TextBox x:Name="Artist" Width="240" Margin="5,0,0,0"/>
 | 
			
		||||
                            </StackPanel>
 | 
			
		||||
                        </StackPanel>
 | 
			
		||||
                        <StackPanel Orientation="Horizontal" Margin="0,5,0,0">
 | 
			
		||||
                            <StackPanel Orientation="Vertical">
 | 
			
		||||
                                <TextBlock Text="Album" Margin="0,0,0,2"/>
 | 
			
		||||
                                <TextBox x:Name="Album" Width="240" Margin="5,0,0,0"/>
 | 
			
		||||
                            </StackPanel>
 | 
			
		||||
 | 
			
		||||
                            <StackPanel Orientation="Vertical" Margin="20,0,0,0">
 | 
			
		||||
                                <TextBlock Text="Year" Margin="0,0,0,2"/>
 | 
			
		||||
                                <TextBox x:Name="Year" Width="240" Margin="5,0,0,0"/>
 | 
			
		||||
                            </StackPanel>
 | 
			
		||||
                        </StackPanel>
 | 
			
		||||
                        <StackPanel Orientation="Horizontal" Margin="0,5,0,5">
 | 
			
		||||
                            <StackPanel Orientation="Vertical">
 | 
			
		||||
                                <TextBlock Text="Genre" Margin="0,0,0,2"/>
 | 
			
		||||
                                <ComboBox x:Name="Genre" SelectedIndex="0" Width="240" ScrollViewer.CanContentScroll="False" Margin="5,0,0,0" FocusVisualStyle="{x:Null}"/>
 | 
			
		||||
                            </StackPanel>
 | 
			
		||||
                            <StackPanel Orientation="Vertical" Margin="20,0,0,0">
 | 
			
		||||
                                <TextBlock Text="Directory" Margin="0,0,0,2" TextDecorations="{x:Null}"/>
 | 
			
		||||
                                <ComboBox x:Name="Directory" SelectedIndex="0" Width="240" ScrollViewer.CanContentScroll="False" Margin="5,0,0,0" IsEnabled="True"/>
 | 
			
		||||
                            </StackPanel>
 | 
			
		||||
                        </StackPanel>
 | 
			
		||||
                        <StackPanel Orientation="Horizontal" Margin="0,5,0,5">
 | 
			
		||||
                            <Button Content="Reset" Click="Reset_Clicked" Padding="5, 2" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}"/>
 | 
			
		||||
                        </StackPanel>
 | 
			
		||||
                        <StackPanel x:Name="SongFilterPanel" Margin="0,5,0,0" Visibility="Collapsed">
 | 
			
		||||
                        <StackPanel x:Name="SongFilterPanel" Margin="0,10,0,0">
 | 
			
		||||
                            <TextBlock>
 | 
			
		||||
                                <Run Text="Number of songs in filter: "/><Run x:Name="SongFilterNumber" FontWeight="Bold"/>
 | 
			
		||||
                            </TextBlock>
 | 
			
		||||
                        </StackPanel>
 | 
			
		||||
 | 
			
		||||
                        <StackPanel Margin="0,5,0,0">
 | 
			
		||||
                            <StackPanel Orientation="Horizontal" Margin="0,5,0,5">
 | 
			
		||||
                                <Button Content="Query filter" Click="UpdateFilter_Clicked" Padding="5, 2" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}" Margin="0,0,10,0"/>
 | 
			
		||||
                                <Button Content="Reset" Click="Reset_Clicked" Padding="5, 2" VerticalAlignment="Bottom" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}"/>
 | 
			
		||||
                            </StackPanel>
 | 
			
		||||
                        </StackPanel>
 | 
			
		||||
                    </StackPanel>
 | 
			
		||||
                </GroupBox>
 | 
			
		||||
 | 
			
		||||
                <GroupBox Margin="0,5,0,0" HorizontalAlignment="Stretch">
 | 
			
		||||
                    <GroupBox.Header>
 | 
			
		||||
                        <TextBlock>
 | 
			
		||||
                            <emoji:EmojiInline Text="♾️"/>
 | 
			
		||||
                            <Run Text="Continuous shuffle"/>
 | 
			
		||||
                        </TextBlock>
 | 
			
		||||
                    </GroupBox.Header>
 | 
			
		||||
                    <StackPanel Orientation="Horizontal" Margin="5,8,0,0">
 | 
			
		||||
                        <CheckBox x:Name="ContinuousShuffle" Checked="ContinuousShuffle_Checked" Unchecked="ContinuousShuffle_Checked" FocusVisualStyle="{x:Null}">
 | 
			
		||||
                            <TextBlock Text="Enable continuous shuffle" TextWrapping="Wrap"/>
 | 
			
		||||
                        </CheckBox>
 | 
			
		||||
                    </StackPanel>
 | 
			
		||||
                </GroupBox>
 | 
			
		||||
 | 
			
		||||
                <GroupBox x:Name="AddToQueueGroup" Margin="0,5,0,0" HorizontalAlignment="Stretch">
 | 
			
		||||
                <StackPanel Orientation="Horizontal" Margin="0,5,0,0">
 | 
			
		||||
                    <GroupBox DockPanel.Dock="Right" Padding="0,4,0,0" Width="248">
 | 
			
		||||
                        <GroupBox.Header>
 | 
			
		||||
                            <TextBlock>
 | 
			
		||||
                            <emoji:EmojiInline Text="➕"/>
 | 
			
		||||
                            <Run Text="Add to queue"/>
 | 
			
		||||
                            </TextBlock>
 | 
			
		||||
                        </GroupBox.Header>
 | 
			
		||||
                    <StackPanel Margin="5,0,0,0">
 | 
			
		||||
                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,10,0,0">
 | 
			
		||||
                            <TextBox x:Name="SongNumber" Text="100" Width="35" HorizontalAlignment="Left" VerticalAlignment="Top"/>
 | 
			
		||||
                            <TextBlock Text="Number of songs to add" Margin="5,0,0,5"/>
 | 
			
		||||
                        <StackPanel Orientation="Vertical" Margin="5,5,5,0">
 | 
			
		||||
                            <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
 | 
			
		||||
                                <TextBlock Text="Songs to add" Margin="0,0,5,5"/>
 | 
			
		||||
                                <TextBox x:Name="SongNumber" PreviewTextInput="QueueValidationTextBox" MaxLength="4" Text="100" Width="35" HorizontalAlignment="Left" VerticalAlignment="Top"/>
 | 
			
		||||
                            </StackPanel>
 | 
			
		||||
                        <StackPanel Orientation="Horizontal" Margin="0,2,0,0">
 | 
			
		||||
                            <StackPanel Orientation="Horizontal" Margin="0,5,0,0">
 | 
			
		||||
                                <Button Content="Add to queue" Click="AddToQueue_Clicked" Padding="5, 2" HorizontalAlignment="Left" FocusVisualStyle="{x:Null}"/>
 | 
			
		||||
                                <TextBlock x:Name="SearchStatus" Margin="15,3,0,0" FontStyle="Italic" Visibility="Collapsed">
 | 
			
		||||
                                <Run Text="Added "/><Run x:Name="NumberAddedSongs"/><Run Text=" songs to the queue..."/>
 | 
			
		||||
                                    <Run Text="Added "/><Run x:Name="NumberAddedSongs"/><Run Text=" songs"/>
 | 
			
		||||
                                </TextBlock>
 | 
			
		||||
                            </StackPanel>
 | 
			
		||||
                        </StackPanel>
 | 
			
		||||
                    </GroupBox>
 | 
			
		||||
                    
 | 
			
		||||
                    <GroupBox DockPanel.Dock="Left" Padding="0,4,0,0" Width="248" Margin="0,0,5,0">
 | 
			
		||||
                        <GroupBox.Header>
 | 
			
		||||
                            <TextBlock>
 | 
			
		||||
                                <emoji:EmojiInline Text="♾️"/>
 | 
			
		||||
                                <Run Text="Continuous shuffle"/>
 | 
			
		||||
                            </TextBlock>
 | 
			
		||||
                        </GroupBox.Header>
 | 
			
		||||
                        <StackPanel Orientation="Horizontal" Margin="5,7,5,0">
 | 
			
		||||
                            <CheckBox x:Name="ContinuousShuffle" Checked="ContinuousShuffle_Checked" Unchecked="ContinuousShuffle_Checked" FocusVisualStyle="{x:Null}" VerticalAlignment="Top">
 | 
			
		||||
                                <TextBlock Text="Enable continuous shuffle" TextWrapping="Wrap"/>
 | 
			
		||||
                            </CheckBox>
 | 
			
		||||
                        </StackPanel>
 | 
			
		||||
                    </GroupBox>
 | 
			
		||||
                </StackPanel>
 | 
			
		||||
            </StackPanel>
 | 
			
		||||
        </StackPanel>
 | 
			
		||||
    </Grid>
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,13 @@ using System.ComponentModel;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Windows;
 | 
			
		||||
using System.Windows.Controls;
 | 
			
		||||
using System.Windows.Interop;
 | 
			
		||||
using System.Windows.Media;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using MpcNET.Commands.Queue;
 | 
			
		||||
using System.Windows.Input;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
 | 
			
		||||
namespace unison
 | 
			
		||||
{
 | 
			
		||||
@@ -19,81 +25,61 @@ namespace unison
 | 
			
		||||
        private MPDHandler _mpd;
 | 
			
		||||
        bool _continuous = false;
 | 
			
		||||
        List<string> _songList { get; }
 | 
			
		||||
        List<string> _genreList { get; }
 | 
			
		||||
        List<string> _folderList { get; }
 | 
			
		||||
        List<IFilter> _filters { get; }
 | 
			
		||||
 | 
			
		||||
        public Shuffle()
 | 
			
		||||
        {
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
            _songList = new();
 | 
			
		||||
            _genreList = new();
 | 
			
		||||
            _folderList = new();
 | 
			
		||||
            _filters = new();
 | 
			
		||||
            SongFilterNumber.Text = "0";
 | 
			
		||||
 | 
			
		||||
            _mpd = (MPDHandler)Application.Current.Properties["mpd"];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool IsFilterEmpty()
 | 
			
		||||
        public void Initialize()
 | 
			
		||||
        {
 | 
			
		||||
            if (Song.Text.Length == 0 && Artist.Text.Length == 0 && Album.Text.Length == 0 && Year.Text.Length == 0 && Genre.SelectedIndex == 0 && Directory.Text.Length == 0)
 | 
			
		||||
                return true;
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool GetContinuous()
 | 
			
		||||
        {
 | 
			
		||||
            return _continuous;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void AddContinuousSongs()
 | 
			
		||||
        {
 | 
			
		||||
            if (IsFilterEmpty())
 | 
			
		||||
            {
 | 
			
		||||
                // add a completely random song
 | 
			
		||||
                ContinuousShuffle_AddToQueueRandom();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            int AddedSongs = 0;
 | 
			
		||||
            NumberAddedSongs.Text = AddedSongs.ToString();
 | 
			
		||||
            SearchStatus.Visibility = Visibility.Visible;
 | 
			
		||||
 | 
			
		||||
            HashSet<int> SongIndex = new();
 | 
			
		||||
            while (SongIndex.Count < 2)
 | 
			
		||||
            {
 | 
			
		||||
                int MaxIndex = new Random().Next(0, _songList.Count - 1);
 | 
			
		||||
                SongIndex.Add(MaxIndex);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            foreach (int index in SongIndex)
 | 
			
		||||
                _mpd.AddSong(_songList[index]);
 | 
			
		||||
 | 
			
		||||
            SearchStatus.Visibility = Visibility.Collapsed;
 | 
			
		||||
            ListGenre();
 | 
			
		||||
            ListFolder();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async void ListGenre()
 | 
			
		||||
        {
 | 
			
		||||
            if (Genre.Items.Count == 0)
 | 
			
		||||
            {
 | 
			
		||||
                _mpd = (MPDHandler)Application.Current.Properties["mpd"];
 | 
			
		||||
            if (_genreList.Count != 0)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            List<string> Response = await _mpd.SafelySendCommandAsync(new ListCommand(MpdTags.Genre, null, null));
 | 
			
		||||
 | 
			
		||||
                if (Response.Count > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    Genre.Items.Add("");
 | 
			
		||||
                    foreach (var genre in Response)
 | 
			
		||||
                        Genre.Items.Add(genre);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (Response == null)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            foreach (string genre in Response)
 | 
			
		||||
                _genreList.Add(genre);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async void ListFolder()
 | 
			
		||||
        {
 | 
			
		||||
            if (Directory.Items.Count == 0)
 | 
			
		||||
            {
 | 
			
		||||
                _mpd = (MPDHandler)Application.Current.Properties["mpd"];
 | 
			
		||||
            if (_folderList.Count != 0)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            IEnumerable<IMpdFilePath> Response = await _mpd.SafelySendCommandAsync(new LsInfoCommand(""));
 | 
			
		||||
 | 
			
		||||
                if (Response != null)
 | 
			
		||||
            if (Response == null)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            foreach (IMpdFilePath folder in Response)
 | 
			
		||||
                _folderList.Add(folder.Name);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool IsFilterEmpty()
 | 
			
		||||
        {
 | 
			
		||||
                    Directory.Items.Add("");
 | 
			
		||||
                    foreach (var directory in Response)
 | 
			
		||||
                        Directory.Items.Add(directory.Name);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (_filters.Count() == 0)
 | 
			
		||||
                return true;
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void Window_Closing(object sender, CancelEventArgs e)
 | 
			
		||||
@@ -109,120 +95,139 @@ namespace unison
 | 
			
		||||
            helper.EnsureHandle();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool IsOnMainThread()
 | 
			
		||||
        {
 | 
			
		||||
            return App.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private T FindParent<T>(DependencyObject child) where T : DependencyObject
 | 
			
		||||
        {
 | 
			
		||||
            var parent = VisualTreeHelper.GetParent(child);
 | 
			
		||||
 | 
			
		||||
            if (parent == null)
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
            if (parent is T)
 | 
			
		||||
                return parent as T;
 | 
			
		||||
            else
 | 
			
		||||
                return FindParent<T>(parent);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        private void AddFilter_Clicked(object sender, RoutedEventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            FilterPanel.Children.Add(new ContentPresenter { ContentTemplate = (DataTemplate)FindResource("FilterPanel") });
 | 
			
		||||
            SongFilterNumber.Text = "0";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void RemoveFilter_Clicked(object sender, RoutedEventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            if (FilterPanel.Children.Count > 1)
 | 
			
		||||
                FilterPanel.Children.Remove(FindParent<ContentPresenter>(sender as Button));
 | 
			
		||||
            else
 | 
			
		||||
                Reset_Clicked(null, null);
 | 
			
		||||
            SongFilterNumber.Text = "0";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void Reset_Clicked(object sender, RoutedEventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            Song.Text = "";
 | 
			
		||||
            Artist.Text = "";
 | 
			
		||||
            Album.Text = "";
 | 
			
		||||
            Year.Text = "";
 | 
			
		||||
            Genre.SelectedIndex = 0;
 | 
			
		||||
            Directory.SelectedIndex = 0;
 | 
			
		||||
            FilterPanel.Children.RemoveRange(0, FilterPanel.Children.Count);
 | 
			
		||||
            FilterPanel.Children.Add(new ContentPresenter { ContentTemplate = (DataTemplate)FindResource("FilterPanel") });
 | 
			
		||||
            SongFilterNumber.Text = "0";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async void ContinuousShuffle_Checked(object sender, RoutedEventArgs e)
 | 
			
		||||
        private ITag FilterEquivalence_Type(string value)
 | 
			
		||||
        {
 | 
			
		||||
            if (ContinuousShuffle.IsChecked == true)
 | 
			
		||||
            {
 | 
			
		||||
                AddToQueueGroup.IsEnabled = false;
 | 
			
		||||
                _continuous = true;
 | 
			
		||||
                _songList.Clear();
 | 
			
		||||
                if (!IsFilterEmpty())
 | 
			
		||||
                {
 | 
			
		||||
                    /*await*/
 | 
			
		||||
                    GetSongsFromFilter();
 | 
			
		||||
            if (value == "Song")
 | 
			
		||||
                return MpdTags.Title;
 | 
			
		||||
            else if (value == "Artist")
 | 
			
		||||
                return MpdTags.Artist;
 | 
			
		||||
            else if (value == "Album")
 | 
			
		||||
                return MpdTags.Album;
 | 
			
		||||
            else if (value == "Year")
 | 
			
		||||
                return MpdTags.Date;
 | 
			
		||||
            else if (value == "Genre")
 | 
			
		||||
                return MpdTags.Genre;
 | 
			
		||||
            return MpdTags.Title;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private FilterOperator FilterEquivalence_Operator(string value)
 | 
			
		||||
        {
 | 
			
		||||
            if (value == "contains")
 | 
			
		||||
                return FilterOperator.Contains;
 | 
			
		||||
            else if (value == "is")
 | 
			
		||||
                return FilterOperator.Equal;
 | 
			
		||||
            else if (value == "is not")
 | 
			
		||||
                return FilterOperator.Different;
 | 
			
		||||
            return FilterOperator.Equal;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async void UpdateFilter_Clicked(object sender, RoutedEventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            _filters.Clear();
 | 
			
		||||
 | 
			
		||||
            Debug.WriteLine("is on main thread => " + IsOnMainThread());
 | 
			
		||||
 | 
			
		||||
            foreach (ContentPresenter superChild in FilterPanel.Children)
 | 
			
		||||
            {
 | 
			
		||||
                ITag tag = MpdTags.Title;
 | 
			
		||||
                FilterOperator op = FilterOperator.None;
 | 
			
		||||
                string value = "";
 | 
			
		||||
                bool isDir = false;
 | 
			
		||||
 | 
			
		||||
                StackPanel stackPanel = VisualTreeHelper.GetChild(superChild, 0) as StackPanel;
 | 
			
		||||
                foreach (TextBox child in stackPanel.Children.OfType<TextBox>())
 | 
			
		||||
                {
 | 
			
		||||
                    if (child.Name == "FilterValue")
 | 
			
		||||
                        value = child.Text;
 | 
			
		||||
                }
 | 
			
		||||
                foreach (ComboBox child in stackPanel.Children.OfType<ComboBox>())
 | 
			
		||||
                {
 | 
			
		||||
                    if (child.Name == "FilterType")
 | 
			
		||||
                    {
 | 
			
		||||
                        if (child.SelectedItem.ToString() == "Directory")
 | 
			
		||||
                            isDir = true;
 | 
			
		||||
                        else
 | 
			
		||||
            {
 | 
			
		||||
                AddToQueueGroup.IsEnabled = true;
 | 
			
		||||
                _continuous = false;
 | 
			
		||||
            }
 | 
			
		||||
                            tag = FilterEquivalence_Type(child.SelectedItem.ToString());
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
        private async void ContinuousShuffle_AddToQueueRandom()
 | 
			
		||||
        {
 | 
			
		||||
            for (int i = 0; i < 2; i++)
 | 
			
		||||
            {
 | 
			
		||||
                // generate random number
 | 
			
		||||
                int song = new Random().Next(0, _mpd.GetStats().Songs - 1);
 | 
			
		||||
                    if (child.Name == "FilterOperator")
 | 
			
		||||
                        op = FilterEquivalence_Operator(child.SelectedItem.ToString());
 | 
			
		||||
 | 
			
		||||
                // query random song
 | 
			
		||||
                CommandList commandList = new CommandList(new IMpcCommand<object>[] { new SearchCommand(MpdTags.Title, "", song, song + 1) });
 | 
			
		||||
                string Response = await _mpd.SafelySendCommandAsync(commandList);
 | 
			
		||||
 | 
			
		||||
                await Task.Delay(1);
 | 
			
		||||
                if (Response.Length > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    // parse song and add it to queue
 | 
			
		||||
                    int start = Response.IndexOf("[file, ");
 | 
			
		||||
                    int end = Response.IndexOf("],");
 | 
			
		||||
                    string filePath = Response.Substring(start + 7, end - (start + 7));
 | 
			
		||||
                    _mpd.AddSong(filePath);
 | 
			
		||||
                }
 | 
			
		||||
                    if (child.Name == "FilterList" && child.Visibility == Visibility.Visible)
 | 
			
		||||
                        value = child.SelectedItem.ToString();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            SearchStatus.Visibility = Visibility.Collapsed;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async void AddToQueueRandom()
 | 
			
		||||
                if (value != "")
 | 
			
		||||
                {
 | 
			
		||||
            int AddedSongs = 0;
 | 
			
		||||
            NumberAddedSongs.Text = AddedSongs.ToString();
 | 
			
		||||
            SearchStatus.Visibility = Visibility.Visible;
 | 
			
		||||
                    if (!isDir)
 | 
			
		||||
                        _filters.Add(new FilterTag(tag, value, op));
 | 
			
		||||
                    else
 | 
			
		||||
                        _filters.Add(new FilterBase(value, FilterOperator.None));
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < int.Parse(SongNumber.Text); i++)
 | 
			
		||||
            {
 | 
			
		||||
                // generate random number
 | 
			
		||||
                int song = new Random().Next(0, _mpd.GetStats().Songs - 1);
 | 
			
		||||
 | 
			
		||||
                // query random song
 | 
			
		||||
                CommandList commandList = new CommandList(new IMpcCommand<object>[] { new SearchCommand(MpdTags.Title, "", song, song + 1) });
 | 
			
		||||
                string Response = await _mpd.SafelySendCommandAsync(commandList);
 | 
			
		||||
 | 
			
		||||
                await Task.Delay(1);
 | 
			
		||||
                if (Response.Length > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    // parse song and add it to queue
 | 
			
		||||
                    int start = Response.IndexOf("[file, ");
 | 
			
		||||
                    int end = Response.IndexOf("],");
 | 
			
		||||
                    string filePath = Response.Substring(start + 7, end - (start + 7));
 | 
			
		||||
                    _mpd.AddSong(filePath);
 | 
			
		||||
 | 
			
		||||
                    AddedSongs++;
 | 
			
		||||
                    NumberAddedSongs.Text = AddedSongs.ToString();
 | 
			
		||||
                    await GetSongsFromFilter();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            SearchStatus.Visibility = Visibility.Collapsed;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task GetSongsFromFilter()
 | 
			
		||||
        {
 | 
			
		||||
            Debug.WriteLine("get songs from filter => start");
 | 
			
		||||
 | 
			
		||||
            Debug.WriteLine("is on main thread => " + IsOnMainThread());
 | 
			
		||||
 | 
			
		||||
            _songList.Clear();
 | 
			
		||||
            SongFilterPanel.Visibility = Visibility.Visible;
 | 
			
		||||
 | 
			
		||||
            int song = _mpd.GetStats().Songs;
 | 
			
		||||
 | 
			
		||||
            List<IFilter> filtersA = new();
 | 
			
		||||
            if (Song.Text != "")
 | 
			
		||||
                filtersA.Add(new FilterTag(MpdTags.Title, Song.Text, FilterOperator.Contains));
 | 
			
		||||
            if (Artist.Text != "")
 | 
			
		||||
                filtersA.Add(new FilterTag(MpdTags.Artist, Artist.Text, FilterOperator.Contains));
 | 
			
		||||
            if (Album.Text != "")
 | 
			
		||||
                filtersA.Add(new FilterTag(MpdTags.Album, Album.Text, FilterOperator.Contains));
 | 
			
		||||
            if (Year.Text != "")
 | 
			
		||||
                filtersA.Add(new FilterTag(MpdTags.Date, Year.Text, FilterOperator.Contains));
 | 
			
		||||
            if (Genre.Text != "")
 | 
			
		||||
                filtersA.Add(new FilterTag(MpdTags.Genre, Genre.Text, FilterOperator.Contains));
 | 
			
		||||
            if (Directory.Text != "")
 | 
			
		||||
                filtersA.Add(new FilterBase(Directory.Text, FilterOperator.None));
 | 
			
		||||
            Debug.WriteLine("get songs from filter => before command");
 | 
			
		||||
 | 
			
		||||
            Debug.WriteLine(Directory.Text);
 | 
			
		||||
 | 
			
		||||
            CommandList commandList = new CommandList(new IMpcCommand<object>[] { new SearchCommand(filtersA, 0, song + 1) });
 | 
			
		||||
            CommandList commandList = new CommandList(new IMpcCommand<object>[] { new SearchCommand(_filters, 0, song + 1) });
 | 
			
		||||
            string Response = await _mpd.SafelySendCommandAsync(commandList);
 | 
			
		||||
 | 
			
		||||
            Debug.WriteLine(Response);
 | 
			
		||||
            Debug.WriteLine("get songs from filter => after command");
 | 
			
		||||
 | 
			
		||||
            // create a list of the file url
 | 
			
		||||
            string[] value = Response.Split(", [file, ");
 | 
			
		||||
@@ -234,40 +239,185 @@ namespace unison
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Debug.WriteLine("get songs from filter => before adding to list");
 | 
			
		||||
 | 
			
		||||
            foreach (string file in value)
 | 
			
		||||
            {
 | 
			
		||||
                int start = 0;
 | 
			
		||||
                int end = file.IndexOf("],");
 | 
			
		||||
                string filePath = file.Substring(start, end - start);
 | 
			
		||||
 | 
			
		||||
                Debug.WriteLine(filePath);
 | 
			
		||||
                _songList.Add(filePath);
 | 
			
		||||
 | 
			
		||||
                SongFilterNumber.Text = _songList.Count.ToString();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Debug.WriteLine("get songs from filter => after adding to list");
 | 
			
		||||
 | 
			
		||||
            // remove characters from first file
 | 
			
		||||
            _songList[0] = _songList[0].Substring(7, _songList[0].Length - 7);
 | 
			
		||||
 | 
			
		||||
            SongFilterPanel.Visibility = Visibility.Visible;
 | 
			
		||||
            SongFilterNumber.Text = _songList.Count.ToString();
 | 
			
		||||
 | 
			
		||||
            Debug.WriteLine("get songs from filter => finish");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async void AddToQueueFilter()
 | 
			
		||||
        private void FilterType_Change(object sender, string Operator, List<string> Listing)
 | 
			
		||||
        {
 | 
			
		||||
            await GetSongsFromFilter();
 | 
			
		||||
            ComboBox comboBox = sender as ComboBox;
 | 
			
		||||
            StackPanel stackPanel = comboBox.Parent as StackPanel;
 | 
			
		||||
            foreach (ComboBox child in stackPanel.Children.OfType<ComboBox>())
 | 
			
		||||
            {
 | 
			
		||||
                if (child.Name == "FilterOperator")
 | 
			
		||||
                {
 | 
			
		||||
                    child.ItemsSource = (Array)FindResource(Operator);
 | 
			
		||||
                    child.SelectedItem = child.Items[0];
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (child.Name == "FilterList")
 | 
			
		||||
                {
 | 
			
		||||
                    child.Visibility = Visibility.Visible;
 | 
			
		||||
                    child.ItemsSource = Listing;
 | 
			
		||||
                    child.SelectedItem = child.Items[0];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            foreach (TextBox child in stackPanel.Children.OfType<TextBox>())
 | 
			
		||||
            {
 | 
			
		||||
                if (child.Name == "FilterValue")
 | 
			
		||||
                    child.Visibility = Visibility.Collapsed;
 | 
			
		||||
            }
 | 
			
		||||
            SongFilterNumber.Text = "0";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void FilterType_SelectionChanged(object sender, SelectionChangedEventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            string item = e.AddedItems[0].ToString();
 | 
			
		||||
            if (item == "Genre")
 | 
			
		||||
                FilterType_Change(sender, "OperatorTypeB", _genreList);
 | 
			
		||||
            else if (item == "Directory")
 | 
			
		||||
                FilterType_Change(sender, "OperatorTypeC", _folderList);
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                ComboBox combobox = sender as ComboBox;
 | 
			
		||||
                StackPanel stackpanel = combobox.Parent as StackPanel;
 | 
			
		||||
                foreach (ComboBox child in stackpanel.Children.OfType<ComboBox>())
 | 
			
		||||
                {
 | 
			
		||||
                    if (child.Name == "FilterOperator")
 | 
			
		||||
                    {
 | 
			
		||||
                        child.ItemsSource = (Array)FindResource("OperatorTypeA");
 | 
			
		||||
                        child.SelectedItem = child.Items[0];
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (child.Name == "FilterList")
 | 
			
		||||
                        child.Visibility = Visibility.Collapsed;
 | 
			
		||||
                }
 | 
			
		||||
                foreach (TextBox child in stackpanel.Children.OfType<TextBox>())
 | 
			
		||||
                {
 | 
			
		||||
                    if (child.Name == "FilterValue")
 | 
			
		||||
                        child.Visibility = Visibility.Visible;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            SongFilterNumber.Text = "0";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void OperatorType_SelectionChanged(object sender, SelectionChangedEventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            Debug.WriteLine("selection changed => operator type");
 | 
			
		||||
            SongFilterNumber.Text = "0";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void QueueValidationTextBox(object sender, TextCompositionEventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            Regex regex = new Regex("[^0-9]+");
 | 
			
		||||
            e.Handled = regex.IsMatch(e.Text);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void QueueValidationNumber()
 | 
			
		||||
        {
 | 
			
		||||
            if (int.Parse(SongNumber.Text) < 1)
 | 
			
		||||
                SongNumber.Text = "1";
 | 
			
		||||
            if (int.Parse(SongNumber.Text) > 1000)
 | 
			
		||||
                SongNumber.Text = "1000";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        private async void AddToQueue_Clicked(object sender, RoutedEventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            QueueValidationNumber();
 | 
			
		||||
 | 
			
		||||
            if (_mpd.GetStats() == null)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            if (IsFilterEmpty())
 | 
			
		||||
                await AddToQueueRandom(int.Parse(SongNumber.Text)); // @TODO await or not???
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                UpdateFilter_Clicked(null, null);
 | 
			
		||||
                await AddToQueueFilter(int.Parse(SongNumber.Text)); // @TODO await or not???
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async /*void*/ Task AddToQueueRandom(int SongNumber)
 | 
			
		||||
        {
 | 
			
		||||
            int AddedSongs = 0;
 | 
			
		||||
            NumberAddedSongs.Text = AddedSongs.ToString();
 | 
			
		||||
            SearchStatus.Visibility = Visibility.Visible;
 | 
			
		||||
 | 
			
		||||
            Debug.WriteLine("song to add => " + SongNumber);
 | 
			
		||||
            for (int i = 0; i < SongNumber; i++)
 | 
			
		||||
            {
 | 
			
		||||
                // generate random number
 | 
			
		||||
                int song = new Random().Next(0, _mpd.GetStats().Songs - 1);
 | 
			
		||||
 | 
			
		||||
                // query random song
 | 
			
		||||
                CommandList commandList = new CommandList(new IMpcCommand<object>[] { new SearchCommand(new FilterTag(MpdTags.Title, "", FilterOperator.Contains), song, song + 1) });
 | 
			
		||||
                string Response = await _mpd.SafelySendCommandAsync(commandList);
 | 
			
		||||
 | 
			
		||||
                await Task.Delay(1);
 | 
			
		||||
                if (Response.Length > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    // parse song and add it to queue
 | 
			
		||||
                    int start = Response.IndexOf("[file, ");
 | 
			
		||||
                    int end = Response.IndexOf("],");
 | 
			
		||||
                    string filePath = Response.Substring(start + 7, end - (start + 7));
 | 
			
		||||
                    _mpd.AddSong(filePath);
 | 
			
		||||
                    Debug.WriteLine("song path => " + filePath);
 | 
			
		||||
 | 
			
		||||
                    AddedSongs++;
 | 
			
		||||
                    NumberAddedSongs.Text = AddedSongs.ToString();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // TODO make play at first position added, as soon as possible
 | 
			
		||||
            if (!_mpd.IsPlaying())
 | 
			
		||||
                _mpd.Play(0);
 | 
			
		||||
 | 
			
		||||
            SearchStatus.Visibility = Visibility.Collapsed;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async /*void*/ Task AddToQueueFilter(int SongNumber)
 | 
			
		||||
        {
 | 
			
		||||
            int AddedSongs = 0;
 | 
			
		||||
            NumberAddedSongs.Text = AddedSongs.ToString();
 | 
			
		||||
            SearchStatus.Visibility = Visibility.Visible;
 | 
			
		||||
 | 
			
		||||
            Debug.WriteLine("song to add => " + SongNumber);
 | 
			
		||||
 | 
			
		||||
            // more requested songs than available => add everything
 | 
			
		||||
            if (int.Parse(SongNumber.Text) > _songList.Count)
 | 
			
		||||
            if (SongNumber > _songList.Count)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (string path in _songList)
 | 
			
		||||
                {
 | 
			
		||||
                    await Task.Delay(1);
 | 
			
		||||
                    _mpd.AddSong(path);
 | 
			
		||||
                    Debug.WriteLine("song path => " + path);
 | 
			
		||||
 | 
			
		||||
                    AddedSongs++;
 | 
			
		||||
                    NumberAddedSongs.Text = AddedSongs.ToString();
 | 
			
		||||
@@ -278,7 +428,7 @@ namespace unison
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                HashSet<int> SongIndex = new();
 | 
			
		||||
                while (SongIndex.Count < int.Parse(SongNumber.Text))
 | 
			
		||||
                while (SongIndex.Count < SongNumber)
 | 
			
		||||
                {
 | 
			
		||||
                    int MaxIndex = new Random().Next(0, _songList.Count - 1);
 | 
			
		||||
                    SongIndex.Add(MaxIndex);
 | 
			
		||||
@@ -289,19 +439,55 @@ namespace unison
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            SearchStatus.Visibility = Visibility.Collapsed;
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void AddToQueue_Clicked(object sender, RoutedEventArgs e)
 | 
			
		||||
 | 
			
		||||
        public bool GetContinuous()
 | 
			
		||||
        {
 | 
			
		||||
            return _continuous;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task HandleContinuous()
 | 
			
		||||
        {
 | 
			
		||||
            if (!GetContinuous())
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            Debug.WriteLine("is on main thread => " + IsOnMainThread());
 | 
			
		||||
 | 
			
		||||
            IEnumerable<IMpdFile> Playlist = await _mpd.SafelySendCommandAsync(new PlaylistCommand());
 | 
			
		||||
            int queueSize = 0;
 | 
			
		||||
            foreach (IMpdFile file in Playlist)
 | 
			
		||||
            {
 | 
			
		||||
                Debug.WriteLine(file.Path);
 | 
			
		||||
                queueSize++;
 | 
			
		||||
            }
 | 
			
		||||
            Debug.WriteLine("queue size is: " + queueSize);
 | 
			
		||||
 | 
			
		||||
            if (queueSize < 5)
 | 
			
		||||
            {
 | 
			
		||||
            _mpd = (MPDHandler)Application.Current.Properties["mpd"];
 | 
			
		||||
                if (_mpd.GetStats() == null)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                if (IsFilterEmpty())
 | 
			
		||||
                AddToQueueRandom();
 | 
			
		||||
                    await AddToQueueRandom(5); // @TODO await or not?
 | 
			
		||||
                else
 | 
			
		||||
                AddToQueueFilter();
 | 
			
		||||
                    await AddToQueueFilter(5);  // @TODO await or not?
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async void ContinuousShuffle_Checked(object sender, RoutedEventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            if (ContinuousShuffle.IsChecked == true)
 | 
			
		||||
            {
 | 
			
		||||
                _continuous = true;
 | 
			
		||||
                _songList.Clear();
 | 
			
		||||
                if (!IsFilterEmpty())
 | 
			
		||||
                    UpdateFilter_Clicked(null, null);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
                _continuous = false;
 | 
			
		||||
 | 
			
		||||
            await HandleContinuous();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||