diff --git a/EstusShots.Gtk/Controls/BindableListControl.cs b/EstusShots.Gtk/Controls/BindableListControl.cs index 6fec306..27074b2 100644 --- a/EstusShots.Gtk/Controls/BindableListControl.cs +++ b/EstusShots.Gtk/Controls/BindableListControl.cs @@ -21,6 +21,8 @@ namespace EstusShots.Gtk.Controls public delegate void ItemActivatedEventHandler(T item); + public delegate void RowContextMenuHandler(T selection); + public class BindableListControl { /// The GTK ListStore that is managed by this . @@ -50,6 +52,11 @@ namespace EstusShots.Gtk.Controls /// Will be invoked when a row in the view has ben acitvated (e.g. double clicked) /// public event ItemActivatedEventHandler ItemActivated; + + /// + /// Will be invoked when a row is right clicked + /// + public event RowContextMenuHandler RowContextMenuOpened; /// /// Initialize a new BindableListView with an existing TreeView Widget @@ -68,9 +75,10 @@ namespace EstusShots.Gtk.Controls Items = new List(); TreeView.RowActivated += TreeViewOnRowActivated; + TreeView.ButtonReleaseEvent += TreeViewOnButtonReleaseEvent; TreeView.Selection.Changed += TreeViewSelectionOnChanged; } - + /// /// Set elements from the property in the . /// @@ -152,6 +160,12 @@ namespace EstusShots.Gtk.Controls SelectedItem = item; SelectionChanged?.Invoke(selection, new SelectionChangedEventArgs(SelectedItem)); } + + private void TreeViewOnButtonReleaseEvent(object o, ButtonReleaseEventArgs args) + { + if (args.Event.Button != 3) return; + RowContextMenuOpened?.Invoke(SelectedItem); + } private void InitTreeViewColumns() { @@ -175,7 +189,6 @@ namespace EstusShots.Gtk.Controls 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) typeof(T).GetProperty(KeyField)?.PropertyType}; columns.AddRange(types); diff --git a/EstusShots.Gtk/Controls/Controls.glade b/EstusShots.Gtk/Controls/Controls.glade new file mode 100644 index 0000000..eec03f2 --- /dev/null +++ b/EstusShots.Gtk/Controls/Controls.glade @@ -0,0 +1,78 @@ + + + + + + True + False + vertical + 5 + + + True + False + 5 + + + True + False + True + + + True + edit-find-symbolic + + + + + True + True + 0 + + + + + Add + True + True + True + + + False + True + 1 + + + + + False + True + 0 + + + + + True + True + True + True + in + + + True + True + horizontal + + + + + + + + False + True + 1 + + + + diff --git a/EstusShots.Gtk/Controls/LookupSelectionControl.cs b/EstusShots.Gtk/Controls/LookupSelectionControl.cs new file mode 100644 index 0000000..9551f1c --- /dev/null +++ b/EstusShots.Gtk/Controls/LookupSelectionControl.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Gdk; +using GLib; +using Gtk; +using Key = Gtk.Key; +using Menu = Gtk.Menu; +using MenuItem = Gtk.MenuItem; +using UI = Gtk.Builder.ObjectAttribute; + +namespace EstusShots.Gtk.Controls +{ + public class LookupSelectionControlOptions + { + public List Columns { get; set; } + public List SearchSpace { get; set; } + public string KeyProperty { get; set; } + public string DisplayProperty { get; set; } + } + + public class LookupSelectionControl : Box + { + [UI] private readonly ComboBox _searchBox = null; + [UI] private readonly TreeView _selectionTreeView = null; + [UI] private readonly Button _addButton = null; + private readonly BindableListControl _selectedItemsListControl; + + public string KeyProperty { get; } + public string DisplayProperty { get; } + public List AllItems { get; } + public List SelectedItems { get; set; } + + public LookupSelectionControl(LookupSelectionControlOptions options) : + this(options, new Builder("Controls.glade")) + { + } + + public LookupSelectionControl(LookupSelectionControlOptions options, Builder builder) : + base(builder.GetObject("_multiLookupControl").Handle) + { + builder.Autoconnect(this); + + KeyProperty = options.KeyProperty; + DisplayProperty = options.DisplayProperty; + AllItems = options.SearchSpace; + SelectedItems = new List(); + + var completionStore = new ListStore(GType.String, GType.String); + BindCompletionList(completionStore); + + _searchBox.Model = completionStore; + _searchBox.IdColumn = 0; + _searchBox.EntryTextColumn = 1; + + _searchBox.Entry.Completion = new EntryCompletion + { + Model = completionStore, + InlineSelection = true, + InlineCompletion = true, + TextColumn = 1, + PopupCompletion = true + }; + + // Init the selected items TreeView + _selectedItemsListControl = new BindableListControl(options.Columns, KeyProperty, _selectionTreeView) + { + Items = SelectedItems + }; + _selectionTreeView.CanFocus = false; + + _selectedItemsListControl.RowContextMenuOpened += selection => + { + var remove = new MenuItem {Label = "Remove"}; + remove.Activated += (sender, eventArgs) => + { + SelectedItems.Remove(_selectedItemsListControl.SelectedItem); + _selectedItemsListControl.DataBind(); + }; + var menu = new Menu {remove}; + menu.ShowAll(); + menu.Popup(); + }; + + _addButton.Clicked += (sender, args) => AddSelectionToTreeView(); + _searchBox.Entry.Activated += (sender, args) => AddSelectionToTreeView(); + } + + private void AddSelectionToTreeView() + { + // FInd item preferably via its ID, try matching the display value as fallback + var item = Guid.TryParse(_searchBox.ActiveId, out var selectedSeason) + ? AllItems.FirstOrDefault(x => x.GetPropertyValue(KeyProperty).Equals(selectedSeason)) + : AllItems.FirstOrDefault(y => y.GetPropertyValue(DisplayProperty).Equals(_searchBox.Entry.Text)); + if (item == null) return; + + if (!SelectedItems.Contains(item)) SelectedItems.Add(item); + _selectedItemsListControl.DataBind(); + _searchBox.Entry.Text = ""; + } + + private void BindCompletionList(ListStore store) + { + foreach (var item in AllItems) + { + // value of key + var keyValue = item.GetType().GetProperty(KeyProperty)?.GetValue(item).ToString(); + // value for display + var displayValue = item.GetType().GetProperty(DisplayProperty)?.GetValue(item); + store.AppendValues(keyValue, displayValue); + } + } + } +} \ No newline at end of file diff --git a/EstusShots.Gtk/Dialogs/EnemyEditor.cs b/EstusShots.Gtk/Dialogs/EnemyEditor.cs index 35b0ee9..565534e 100644 --- a/EstusShots.Gtk/Dialogs/EnemyEditor.cs +++ b/EstusShots.Gtk/Dialogs/EnemyEditor.cs @@ -10,39 +10,33 @@ namespace EstusShots.Gtk.Dialogs { [UI] private readonly Entry _nameEntry = null; [UI] private readonly CheckButton _isBossCheckButton = null; - [UI] private readonly SearchEntry _searchSeasonEntry = null; - [UI] private readonly TreeView _selectedSeasonsTreeView = null; + [UI] private readonly Box _seasonSelectionContainer = null; - private BindableListControl _selectedSeasonsControl; - private readonly List _allSeasons; - private readonly EntryCompletion _allSeasonsCompletion; + private LookupSelectionControl _seasonSelectionControl; public EnemyEditor(Window parent, Enemy enemy, List seasons) : base(parent, new Builder("EnemyEditor.glade")) { EditObject = enemy; - _allSeasons = seasons; - - var columns = new List + _seasonSelectionControl = new LookupSelectionControl(new LookupSelectionControlOptions { - new DataColumnText(nameof(Season.DisplayName)) {Title = "Seasons"} - }; - _selectedSeasonsControl = - new BindableListControl(columns, nameof(Season.SeasonId), _selectedSeasonsTreeView); - - _allSeasonsCompletion = new EntryCompletion(); - - _searchSeasonEntry.Completion = new EntryCompletion(); + KeyProperty = nameof(Season.SeasonId), + DisplayProperty = nameof(Season.DisplayName), + Columns = new List + { + new DataColumnText(nameof(Season.DisplayName)) {Title = "Seasons"} + }, + SearchSpace = seasons + }); + _seasonSelectionContainer.PackStart(_seasonSelectionControl, true, true, 5); } protected override void LoadToModel() { - throw new System.NotImplementedException(); } protected override void LoadFromModel() { - throw new System.NotImplementedException(); } } } \ No newline at end of file diff --git a/EstusShots.Gtk/Dialogs/Glade/EnemyEditor.glade b/EstusShots.Gtk/Dialogs/Glade/EnemyEditor.glade index 4e8d279..b157a1f 100644 --- a/EstusShots.Gtk/Dialogs/Glade/EnemyEditor.glade +++ b/EstusShots.Gtk/Dialogs/Glade/EnemyEditor.glade @@ -2,7 +2,6 @@ - False Enemy @@ -10,7 +9,7 @@ True center-on-parent 350 - 250 + 300 True dialog center @@ -119,45 +118,19 @@ - + True - True - edit-find-symbolic - False - False - entrycompletion1 + False + vertical + + + 1 2 - - - True - True - True - True - in - - - True - True - horizontal - - - - - - - - 1 - 3 - - - - - diff --git a/EstusShots.Gtk/Pages/EnemiesPage.cs b/EstusShots.Gtk/Pages/EnemiesPage.cs index f6c2e53..500cc03 100644 --- a/EstusShots.Gtk/Pages/EnemiesPage.cs +++ b/EstusShots.Gtk/Pages/EnemiesPage.cs @@ -33,7 +33,8 @@ namespace EstusShots.Gtk private void NewEnemyButtonOnClicked(object sender, EventArgs e) { - + var enemyEditor = new EnemyEditor(this, new Enemy(), SeasonsControl.Items); + enemyEditor.Show(); } private async void ReloadEnemies()