Improved client handling and episode models
This commit is contained in:
168
EstusShots.Gtk/Controls/BindableListControl.cs
Normal file
168
EstusShots.Gtk/Controls/BindableListControl.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using GLib;
|
||||
using Gtk;
|
||||
using DateTime = GLib.DateTime;
|
||||
|
||||
namespace EstusShots.Gtk.Controls
|
||||
{
|
||||
public class SelectionChangedEventArgs : EventArgs
|
||||
{
|
||||
public SelectionChangedEventArgs(object selection)
|
||||
{
|
||||
Selection = selection;
|
||||
}
|
||||
|
||||
public object Selection { get; }
|
||||
}
|
||||
|
||||
public delegate void SelectionChangedEventHandler(object o, SelectionChangedEventArgs args);
|
||||
|
||||
public class BindableListControl<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialize a new BindableListView with an existing TreeView Widget
|
||||
/// </summary>
|
||||
/// <param name="columns">The columns of the grid</param>
|
||||
/// <param name="keyField">Unique key field in the item sources type</param>
|
||||
/// <param name="treeView">An instance of an existing TreeView Widget</param>
|
||||
public BindableListControl(List<DataColumn> columns, string keyField, TreeView treeView = null)
|
||||
{
|
||||
TreeView = treeView ?? new TreeView();
|
||||
Columns = columns;
|
||||
KeyField = keyField;
|
||||
InitTreeViewColumns();
|
||||
InitListStore();
|
||||
TreeView.Model = ListStore;
|
||||
Items = new List<T>();
|
||||
|
||||
TreeView.Selection.Changed += TreeView_SelectionChanged;
|
||||
}
|
||||
|
||||
/// <summary> The GTK ListStore that is managed by this <see cref="BindableListControl{T}" />. </summary>
|
||||
public ListStore ListStore { get; internal set; }
|
||||
|
||||
/// <summary> The GTK TreeView control that is managed by this <see cref="BindableListControl{T}" />. </summary>
|
||||
public TreeView TreeView { get; }
|
||||
|
||||
/// <summary> Property of the element type that is used as a unique identifier for accessing elements. </summary>
|
||||
public string KeyField { get; }
|
||||
|
||||
/// <summary> The collection of all elements, that should be shown in the list view. </summary>
|
||||
public List<T> Items { get; set; }
|
||||
|
||||
/// <summary> The currently selected item in the view. </summary>
|
||||
public T SelectedItem { get; set; }
|
||||
|
||||
/// <summary> All columns that are displayed in the list. </summary>
|
||||
public List<DataColumn> Columns { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Event will be invoked when the selected item in the <see cref="TreeView" /> has changed.
|
||||
/// </summary>
|
||||
public event SelectionChangedEventHandler OnSelectionChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Set elements from the <see cref="Items" /> property in the <see cref="ListStore" />.
|
||||
/// </summary>
|
||||
/// <exception cref="TypeLoadException"></exception>
|
||||
public void DataBind()
|
||||
{
|
||||
ListStore.Clear();
|
||||
Items.ForEach(BindItem);
|
||||
}
|
||||
|
||||
private void BindItem(T item)
|
||||
{
|
||||
var row = new List<object>();
|
||||
foreach (var column in Columns)
|
||||
{
|
||||
var prop = item.GetType().GetProperty(column.PropertyName);
|
||||
if (prop == null)
|
||||
throw new TypeLoadException(
|
||||
$"Property '{column.PropertyName}' does not exist on Type '{item.GetType()}'");
|
||||
var val = prop.GetValue(item);
|
||||
if (column.Format != null) val = column.Format(val);
|
||||
row.Add(val.ToString());
|
||||
}
|
||||
|
||||
// The key value must be the first value in the row
|
||||
var key = item.GetType().GetProperty(KeyField)?.GetValue(item);
|
||||
row.Insert(0, key);
|
||||
try
|
||||
{
|
||||
ListStore.AppendValues(row.ToArray());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void TreeView_SelectionChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (!(sender is TreeSelection selection)) return;
|
||||
selection.GetSelected(out var model, out var iter);
|
||||
var key = model.GetValue(iter, 0);
|
||||
var item = Items.FirstOrDefault(x =>
|
||||
{
|
||||
var prop = x.GetType().GetProperty(KeyField);
|
||||
var value = prop?.GetValue(x);
|
||||
return value != null && value.Equals(key);
|
||||
});
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
Console.WriteLine($"No item for key '{key}' found in data store");
|
||||
return;
|
||||
}
|
||||
|
||||
SelectedItem = item;
|
||||
OnSelectionChanged?.Invoke(this, new SelectionChangedEventArgs(SelectedItem));
|
||||
}
|
||||
|
||||
private void InitTreeViewColumns()
|
||||
{
|
||||
foreach (var dataColumn in Columns)
|
||||
{
|
||||
// Offset by one, because the first column in the data store is fixed to the key value of the row
|
||||
var index = Columns.IndexOf(dataColumn) + 1;
|
||||
var column = new TreeViewColumn(
|
||||
dataColumn.Title,
|
||||
new CellRendererText(),
|
||||
"text", index)
|
||||
{
|
||||
Resizable = true,
|
||||
Reorderable = true
|
||||
};
|
||||
TreeView.AppendColumn(column);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitListStore()
|
||||
{
|
||||
var types = Columns
|
||||
.Select(x =>
|
||||
{
|
||||
var propType = typeof(T).GetProperty(x.PropertyName)?.PropertyType;
|
||||
var gType = (GType) propType;
|
||||
if (gType.ToString() == "GtkSharpValue") gType = MapType(propType);
|
||||
return gType;
|
||||
});
|
||||
var data = new DateTime();
|
||||
// The first column in the data store is always the key field.
|
||||
var columns = new List<GType> {(GType) typeof(T).GetProperty(KeyField)?.PropertyType};
|
||||
columns.AddRange(types);
|
||||
ListStore = new ListStore(columns.ToArray());
|
||||
}
|
||||
|
||||
private static GType MapType(Type type)
|
||||
{
|
||||
return type.Name switch
|
||||
{
|
||||
_ => GType.String
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Gtk;
|
||||
|
||||
namespace EstusShots.Gtk.Controls
|
||||
{
|
||||
public class SelectionChangedEventArgs : EventArgs
|
||||
{
|
||||
public object Selection { get; }
|
||||
|
||||
public SelectionChangedEventArgs(object selection)
|
||||
{
|
||||
Selection = selection;
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void SelectionChangedEventHandler(object o, SelectionChangedEventArgs args);
|
||||
|
||||
public class BindableListView<T>
|
||||
{
|
||||
public ListStore ListStore { get; internal set; }
|
||||
|
||||
public TreeView TreeView { get; }
|
||||
|
||||
public string KeyField { get; }
|
||||
|
||||
public IEnumerable<T> Items { get; set; }
|
||||
|
||||
public T SelectedItem { get; set; }
|
||||
|
||||
public List<DataColumn> Columns { get; }
|
||||
|
||||
public event SelectionChangedEventHandler OnSelectionChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new BindableListView with an existing TreeView Widget
|
||||
/// </summary>
|
||||
/// <param name="columns">The columns of the grid</param>
|
||||
/// <param name="keyField">Unique key field in the item sources type</param>
|
||||
/// <param name="treeView">An instance of an existing TreeView Widget</param>
|
||||
public BindableListView(List<DataColumn> columns, string keyField, TreeView treeView = null)
|
||||
{
|
||||
TreeView = treeView ?? new TreeView();
|
||||
Columns = columns;
|
||||
KeyField = keyField;
|
||||
InitTreeViewColumns();
|
||||
InitListStore();
|
||||
TreeView.Model = ListStore;
|
||||
Items = new List<T>();
|
||||
|
||||
TreeView.Selection.Changed += TreeView_SelectionChanged;
|
||||
}
|
||||
|
||||
private void TreeView_SelectionChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (!(sender is TreeSelection selection)) return;
|
||||
selection.GetSelected(out var model, out var iter);
|
||||
var key = model.GetValue(iter, 0);
|
||||
var item = Items.FirstOrDefault(x =>
|
||||
{
|
||||
var prop = x.GetType().GetProperty(KeyField);
|
||||
var value = prop?.GetValue(x);
|
||||
return value != null && value.Equals(key);
|
||||
});
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
Console.WriteLine($"No item for key '{key}' found in data store");
|
||||
return;
|
||||
}
|
||||
|
||||
SelectedItem = item;
|
||||
OnSelectionChanged?.Invoke(this, new SelectionChangedEventArgs(SelectedItem));
|
||||
}
|
||||
|
||||
public void DataBind()
|
||||
{
|
||||
ListStore.Clear();
|
||||
foreach (var item in Items)
|
||||
{
|
||||
var row = new List<object>();
|
||||
foreach (var column in Columns)
|
||||
{
|
||||
var prop = item.GetType().GetProperty(column.PropertyName);
|
||||
|
||||
if (prop == null)
|
||||
throw new TypeLoadException(
|
||||
$"Property '{column.PropertyName}' does not exist on Type '{item.GetType()}'");
|
||||
|
||||
row.Add(prop.GetValue(item));
|
||||
}
|
||||
|
||||
// The key value must be the first value in the row
|
||||
var key = item.GetType().GetProperty(KeyField)?.GetValue(item);
|
||||
row.Insert(0, key);
|
||||
|
||||
try
|
||||
{
|
||||
ListStore.AppendValues(row.ToArray());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitTreeViewColumns()
|
||||
{
|
||||
foreach (var dataColumn in Columns)
|
||||
{
|
||||
// Offset by one, because the first column in the data store is fixed to the key value of the row
|
||||
var index = Columns.IndexOf(dataColumn) + 1;
|
||||
var column = new TreeViewColumn(
|
||||
dataColumn.Title,
|
||||
new CellRendererText(),
|
||||
"text", index);
|
||||
TreeView.AppendColumn(column);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitListStore()
|
||||
{
|
||||
var types = Columns
|
||||
.Select(x => typeof(T).GetProperty(x.PropertyName)?.PropertyType);
|
||||
|
||||
// The first column in the data store is always the key field.
|
||||
var columns = new List<Type> {typeof(T).GetProperty(KeyField)?.PropertyType};
|
||||
columns.AddRange(types);
|
||||
|
||||
ListStore = new ListStore(columns.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,32 @@
|
||||
using System;
|
||||
|
||||
namespace EstusShots.Gtk.Controls
|
||||
{
|
||||
public class DataColumn
|
||||
{
|
||||
public string PropertyName { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public DataColumn() { }
|
||||
public DataColumn()
|
||||
{
|
||||
}
|
||||
|
||||
public DataColumn(string propertyName)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name of the property in the data source, that should be show nin the view
|
||||
/// </summary>
|
||||
public string PropertyName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The column header.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Applies the given transformation on each item in the column.
|
||||
/// This changes only the display of the value.
|
||||
/// </summary>
|
||||
public Func<object, string> Format { get; set; }
|
||||
}
|
||||
}
|
||||
26
EstusShots.Gtk/Controls/LoadingMode.cs
Normal file
26
EstusShots.Gtk/Controls/LoadingMode.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
|
||||
namespace EstusShots.Gtk.Controls
|
||||
{
|
||||
internal class LoadingMode : IDisposable
|
||||
{
|
||||
private MainWindow Window { get; set; }
|
||||
|
||||
public LoadingMode(MainWindow window)
|
||||
{
|
||||
Window = window;
|
||||
Window.LoadButton.Sensitive = false;
|
||||
Window.NewSeasonButton.Sensitive = false;
|
||||
Window.SeasonsView.Sensitive = false;
|
||||
Window.SeasonsOverlay.AddOverlay(Window.LoadingSpinner);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Window.LoadButton.Sensitive = true;
|
||||
Window.NewSeasonButton.Sensitive = true;
|
||||
Window.SeasonsView.Sensitive = true;
|
||||
Window.SeasonsOverlay.Remove(Window.LoadingSpinner);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user