Skip to content

Commit

Permalink
feat: adds playlist search control
Browse files Browse the repository at this point in the history
it selects any items matching your query
  • Loading branch information
sophie authored and sophie-gilbert committed Jan 16, 2024
1 parent 745a7fb commit 9d474c9
Show file tree
Hide file tree
Showing 15 changed files with 405 additions and 31 deletions.
77 changes: 77 additions & 0 deletions FoxTunes.Core/Behaviours/PlaylistSearchBehaviour.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using FoxTunes.Interfaces;
using System;
using System.Linq;

namespace FoxTunes
{
public class PlaylistSearchBehaviour : StandardBehaviour
{
public IPlaylistManager PlaylistManager { get; private set; }

public IPlaylistBrowser PlaylistBrowser { get; private set; }

public override void InitializeComponent(ICore core)
{
this.PlaylistManager = core.Managers.Playlist;
this.PlaylistManager.FilterChanged += this.OnFilterChanged;
this.PlaylistBrowser = core.Components.PlaylistBrowser;
base.InitializeComponent(core);
}

protected virtual void OnFilterChanged(object sender, EventArgs e)
{
if (this.PlaylistManager.SelectedPlaylist == null || string.IsNullOrEmpty(this.PlaylistManager.Filter))
{
return;
}
this.Dispatch(this.Search);
}

public void Search()
{
var playlistItems = this.PlaylistBrowser.GetItems(this.PlaylistManager.SelectedPlaylist, this.PlaylistManager.Filter);
if (playlistItems != null && playlistItems.Any())
{
this.PlaylistManager.SelectedItems = playlistItems;
}
}

public bool IsDisposed { get; private set; }

public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (this.IsDisposed || !disposing)
{
return;
}
this.OnDisposing();
this.IsDisposed = true;
}

protected virtual void OnDisposing()
{
if (this.PlaylistManager != null)
{
this.PlaylistManager.FilterChanged -= this.OnFilterChanged;
}
}

~PlaylistSearchBehaviour()
{
try
{
this.Dispose(true);
}
catch
{
//Nothing can be done, never throw on GC thread.
}
}
}
}
4 changes: 4 additions & 0 deletions FoxTunes.Core/Interfaces/Managers/IPlaylistManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ public interface IPlaylistManager : IStandardManager, IInvocableComponent, IFile
PlaylistItem[] SelectedItems { get; set; }

event EventHandler SelectedItemsChanged;

string Filter { get; set; }

event EventHandler FilterChanged;
}

public enum PlaylistQueueFlags : byte
Expand Down
2 changes: 2 additions & 0 deletions FoxTunes.Core/Interfaces/Playlist/IPlaylistBrowser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public interface IPlaylistBrowser : IStandardComponent

PlaylistItem[] GetItems(Playlist playlist);

PlaylistItem[] GetItems(Playlist playlist, string filter);

PlaylistItem GetItemById(Playlist playlist, int id);

PlaylistItem GetItemBySequence(Playlist playlist, int sequence);
Expand Down
30 changes: 30 additions & 0 deletions FoxTunes.Core/Managers/PlaylistManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,36 @@ protected virtual void OnSelectedItemsChanged()

public event EventHandler SelectedItemsChanged;

private string _Filter { get; set; }

public string Filter
{
get
{
return this._Filter;
}
set
{
if (string.Equals(this._Filter, value, StringComparison.OrdinalIgnoreCase))
{
return;
}
this._Filter = value;
this.OnFilterChanged();
}
}

protected virtual void OnFilterChanged()
{
if (this.FilterChanged != null)
{
this.FilterChanged(this, EventArgs.Empty);
}
this.OnPropertyChanged("Filter");
}

public event EventHandler FilterChanged;

public IEnumerable<string> InvocationCategories
{
get
Expand Down
10 changes: 10 additions & 0 deletions FoxTunes.Core/Playlist/PlaylistBrowser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ public PlaylistItem[] GetItems(Playlist playlist)
return this.PlaylistCache.GetItems(playlist, () => this.GetItemsCore(playlist));
}

public PlaylistItem[] GetItems(Playlist playlist, string filter)
{
var playlistItems = this.GetItems(playlist);
return playlistItems.Where(
playlistItem => playlistItem.MetaDatas.Any(
metaDataItem => !string.IsNullOrEmpty(metaDataItem.Value) && metaDataItem.Value.Contains(filter, true)
)
).ToArray();
}

private IEnumerable<PlaylistItem> GetItemsCore(Playlist playlist)
{
this.State |= PlaylistBrowserState.Loading;
Expand Down
2 changes: 1 addition & 1 deletion FoxTunes.UI.Windows.Layout/UIComponentContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ public Task Wrap(string name)
{
return;
}
var component = LayoutManager.Instance.GetComponent(name, this.Configuration.Component.Role);
var component = LayoutManager.Instance.GetComponent(name, UIComponentRole.Container);
if (component == null)
{
return;
Expand Down
7 changes: 0 additions & 7 deletions FoxTunes.UI.Windows.Themes/Themes/Adamantine.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -1605,7 +1605,6 @@
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.CanContentScroll" Value="true" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="AlternationCount" Value="2" />
<Setter Property="Template" Value="{DynamicResource ListViewTemplate}"/>
</Style>

Expand Down Expand Up @@ -1692,12 +1691,6 @@
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter TargetName="Background" Property="Fill" Value="{DynamicResource ShadeBrush}"></Setter>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter TargetName="Background" Property="Fill" Value="Transparent"></Setter>
</Trigger>
<Trigger Property="IsSelected" Value="true">
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource SelectedOff}"/>
Expand Down
7 changes: 0 additions & 7 deletions FoxTunes.UI.Windows.Themes/Themes/Transparent.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -1605,7 +1605,6 @@
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.CanContentScroll" Value="true" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="AlternationCount" Value="2" />
<Setter Property="Template" Value="{DynamicResource ListViewTemplate}"/>
</Style>

Expand Down Expand Up @@ -1692,12 +1691,6 @@
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter TargetName="Background" Property="Fill" Value="{DynamicResource ShadeBrush}"></Setter>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter TargetName="Background" Property="Fill" Value="Transparent"></Setter>
</Trigger>
<Trigger Property="IsSelected" Value="true">
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource SelectedOff}"/>
Expand Down
1 change: 1 addition & 0 deletions FoxTunes.UI.Windows/DefaultPlaylist.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
Windows:ListViewExtensions.DragSource="True"
Windows:ListViewExtensions.DragSourceInitialized="DragSourceInitialized"
Windows:ListViewExtensions.AutoSizeColumns="True"
Windows:ListViewExtensions.EnsureSelectedItemVisible="True"
GridViewColumnHeader.Click="OnHeaderClick"
SelectionChanged="OnSelectionChanged"
Background="{DynamicResource ControlBackgroundBrush}">
Expand Down
110 changes: 110 additions & 0 deletions FoxTunes.UI.Windows/Extensions/ListView_EnsureSelectedItemVisible.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;

namespace FoxTunes
{
public static partial class ListViewExtensions
{
private static readonly ConditionalWeakTable<ListView, EnsureSelectedItemVisibleBehaviour> EnsureSelectedItemVisibleBehaviours = new ConditionalWeakTable<ListView, EnsureSelectedItemVisibleBehaviour>();

public static readonly DependencyProperty EnsureSelectedItemVisibleProperty = DependencyProperty.RegisterAttached(
"EnsureSelectedItemVisible",
typeof(bool),
typeof(ListViewExtensions),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnEnsureSelectedItemVisiblePropertyChanged))
);

public static bool GetEnsureSelectedItemVisible(ListView source)
{
return (bool)source.GetValue(EnsureSelectedItemVisibleProperty);
}

public static void SetEnsureSelectedItemVisible(ListView source, bool value)
{
source.SetValue(EnsureSelectedItemVisibleProperty, value);
}

private static void OnEnsureSelectedItemVisiblePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var listView = sender as ListView;
if (listView == null)
{
return;
}
if (GetEnsureSelectedItemVisible(listView))
{
var behaviour = default(EnsureSelectedItemVisibleBehaviour);
if (!EnsureSelectedItemVisibleBehaviours.TryGetValue(listView, out behaviour))
{
EnsureSelectedItemVisibleBehaviours.Add(listView, new EnsureSelectedItemVisibleBehaviour(listView));
}
}
else
{
var behaviour = default(EnsureSelectedItemVisibleBehaviour);
if (EnsureSelectedItemVisibleBehaviours.TryGetValue(listView, out behaviour))
{
EnsureSelectedItemVisibleBehaviours.Remove(listView);
behaviour.Dispose();
}
}
}

private class EnsureSelectedItemVisibleBehaviour : UIBehaviour
{
public EnsureSelectedItemVisibleBehaviour(ListView listView)
{
this.ListView = listView;
this.ListView.SelectionChanged += this.OnSelectionChanged;
}

public ListView ListView { get; private set; }

protected virtual void EnsureVisible(object value)
{
if (value == null)
{
return;
}
var index = this.ListView.Items.IndexOf(value);
if (index < 0)
{
return;
}
var scrollViewer = this.ListView.FindChild<ScrollViewer>();
if (scrollViewer != null)
{
if (scrollViewer.ScrollToItemOffset<ListViewItem>(index, this.OnItemLoaded))
{
this.ListView.UpdateLayout();
}
}
var item = this.ListView.ItemContainerGenerator.ContainerFromItem(value) as ListViewItem;
if (item != null)
{
item.BringIntoView();
}
}

protected virtual void OnItemLoaded(object sender, RoutedEventArgs e)
{
this.EnsureVisible(this.ListView.SelectedItem);
}

protected virtual void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.EnsureVisible(this.ListView.SelectedItem);
}

protected override void OnDisposing()
{
if (this.ListView != null)
{
this.ListView.SelectionChanged -= this.OnSelectionChanged;
}
base.OnDisposing();
}
}
}
}
47 changes: 31 additions & 16 deletions FoxTunes.UI.Windows/Extensions/ListView_SelectedItems.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
Expand All @@ -12,6 +13,8 @@ public static partial class ListViewExtensions

private static readonly ConditionalWeakTable<ListView, SelectedItemsBehaviour> SelectedItemsBehaviours = new ConditionalWeakTable<ListView, SelectedItemsBehaviour>();

public static bool IsSuspended { get; private set; }

public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached(
"SelectedItems",
typeof(IList),
Expand All @@ -31,6 +34,10 @@ public static void SetSelectedItems(ListView source, IList value)

private static void OnSelectedItemsPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (IsSuspended)
{
return;
}
var listView = sender as ListView;
if (listView == null)
{
Expand All @@ -41,22 +48,30 @@ private static void OnSelectedItemsPropertyChanged(DependencyObject sender, Depe
{
SelectedItemsBehaviours.Add(listView, new SelectedItemsBehaviour(listView));
}
//We only need this if we need two way binding (it's sketchy and causes weird recursive
//calls to this handler anyway so let's just not).
//var items = (e.NewValue as IList);
//if (items == null)
//{
// return;
//}
//if (Enumerable.SequenceEqual(listView.SelectedItems.Cast<object>(), items.Cast<object>()))
//{
// return;
//}
//listView.SelectedItems.Clear();
//foreach (var item in items)
//{
// listView.SelectedItems.Add(item);
//}
IsSuspended = true;
try
{
//We only need this if we need two way binding (it's sketchy and causes weird recursive
//calls to this handler anyway so let's just not).
var items = (e.NewValue as IList);
if (items == null)
{
return;
}
if (Enumerable.SequenceEqual(listView.SelectedItems.Cast<object>(), items.Cast<object>()))
{
return;
}
listView.SelectedItems.Clear();
foreach (var item in items)
{
listView.SelectedItems.Add(item);
}
}
finally
{
IsSuspended = false;
}
}

private class SelectedItemsBehaviour : UIBehaviour
Expand Down
Loading

0 comments on commit 9d474c9

Please sign in to comment.