[banshee/typeahead] Add type-ahead find to ListView
- From: Gabriel Burt <gburt src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [banshee/typeahead] Add type-ahead find to ListView
- Date: Sat, 14 Nov 2009 03:48:15 +0000 (UTC)
commit 85882184a97fe42b423d0db090f08a4691efe3ed
Author: Neil Loknath <neil loknath gmail com>
Date: Fri Nov 13 19:42:07 2009 -0800
Add type-ahead find to ListView
Still need to decide how to handle starting finding, since we have many
keybindings that conflict with the normal type-anything approach.
Signed-off-by: Gabriel Burt <gabriel burt gmail com>
.../DatabaseAlbumListModel.cs | 2 +
.../DatabaseArtistListModel.cs | 2 +
.../DatabaseFilterListModel.cs | 21 ++-
.../DatabaseQueryFilterModel.cs | 9 +
.../DatabaseTrackListModel.cs | 19 ++-
.../IDatabaseTrackModelCache.cs | 2 +
.../Banshee.Collection.Database/ISearchable.cs | 39 +++
src/Core/Banshee.Services/Makefile.am | 1 +
.../Banshee.Collection.Gui/BaseTrackListView.cs | 8 +-
.../Banshee.Collection.Gui/SearchableListView.cs | 275 ++++++++++++++++++++
.../Banshee.Collection.Gui/TrackFilterListView.cs | 2 +-
src/Core/Banshee.ThickClient/Makefile.am | 1 +
.../Hyena.Data.Gui/ListView/ListView_Rendering.cs | 2 +-
.../Hyena.Data.Gui/ListView/ListView_Windowing.cs | 4 +
.../Hyena.Gui/Hyena.Widgets/EntryPopup.cs | 226 ++++++++++++++++
src/Libraries/Hyena.Gui/Makefile.am | 1 +
.../Hyena/Hyena.Data.Sqlite/SqliteModelCache.cs | 41 +++-
17 files changed, 647 insertions(+), 8 deletions(-)
---
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumListModel.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumListModel.cs
index 76c32b9..1c847da 100644
--- a/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumListModel.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseAlbumListModel.cs
@@ -35,6 +35,7 @@ using Mono.Unix;
using Hyena;
using Hyena.Data.Sqlite;
+using Hyena.Query;
using Banshee.Database;
@@ -46,6 +47,7 @@ namespace Banshee.Collection.Database
: base (Banshee.Query.BansheeQuery.AlbumField.Name, Banshee.Query.BansheeQuery.AlbumField.Label,
source, trackModel, connection, DatabaseAlbumInfo.Provider, new AlbumInfo (null), uuid)
{
+ QueryFields = new QueryFieldSet (Banshee.Query.BansheeQuery.AlbumField);
ReloadFragmentFormat = @"
FROM CoreAlbums WHERE CoreAlbums.AlbumID IN
(SELECT CoreTracks.AlbumID FROM CoreTracks, CoreCache{0}
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistListModel.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistListModel.cs
index 697f28c..f105a0f 100644
--- a/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistListModel.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseArtistListModel.cs
@@ -34,6 +34,7 @@ using Mono.Unix;
using Hyena;
using Hyena.Data.Sqlite;
+using Hyena.Query;
using Banshee.Database;
@@ -45,6 +46,7 @@ namespace Banshee.Collection.Database
: base (Banshee.Query.BansheeQuery.ArtistField.Name, Banshee.Query.BansheeQuery.ArtistField.Label,
source, trackModel, connection, DatabaseArtistInfo.Provider, new ArtistInfo (null, null), uuid)
{
+ QueryFields = new QueryFieldSet (Banshee.Query.BansheeQuery.ArtistField);
ReloadFragmentFormat = @"
FROM CoreArtists WHERE CoreArtists.ArtistID IN
(SELECT CoreTracks.ArtistID FROM CoreTracks, CoreCache{0}
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseFilterListModel.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseFilterListModel.cs
index 39342ee..ee5372c 100644
--- a/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseFilterListModel.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseFilterListModel.cs
@@ -35,13 +35,14 @@ using System.Collections.Generic;
using Hyena;
using Hyena.Data;
using Hyena.Data.Sqlite;
+using Hyena.Query;
using Banshee.Collection;
using Banshee.Database;
namespace Banshee.Collection.Database
{
- public abstract class DatabaseFilterListModel<T, U> : FilterListModel<U>, ICacheableDatabaseModel
+ public abstract class DatabaseFilterListModel<T, U> : FilterListModel<U>, ICacheableDatabaseModel, ISearchable
where T : U, new () where U : ICacheableItem, new()
{
private readonly BansheeModelCache<T> cache;
@@ -218,6 +219,24 @@ namespace Banshee.Collection.Database
OnReloaded ();
}
}
+
+ private QueryFieldSet query_fields;
+ public QueryFieldSet QueryFields {
+ get { return query_fields; }
+ protected set { query_fields = value; }
+ }
+
+ public int IndexOf (QueryNode query, long offset)
+ {
+ lock (cache) {
+ if (query == null) {
+ return -1;
+ }
+
+ int index = (int) cache.IndexOf (query.ToSql (QueryFields), offset);
+ return index >= 0 ? index + 1 : index;
+ }
+ }
public abstract string FilterColumn { get; }
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseQueryFilterModel.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseQueryFilterModel.cs
index b9ee544..6da6b5f 100644
--- a/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseQueryFilterModel.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseQueryFilterModel.cs
@@ -34,6 +34,8 @@ using Hyena.Query;
using Hyena.Data;
using Hyena.Data.Sqlite;
+using Mono.Unix;
+
using Banshee.ServiceStack;
namespace Banshee.Collection.Database
@@ -41,6 +43,11 @@ namespace Banshee.Collection.Database
public class DatabaseQueryFilterModel<T> : DatabaseFilterListModel<QueryFilterInfo<T>, QueryFilterInfo<T>>
{
private QueryField field;
+ private readonly QueryField query_filter_field = new QueryField (
+ "itemid", "ItemID",
+ Catalog.GetString ("Value"), "CoreCache.ItemID", false
+ );
+
private string select_all_fmt;
public DatabaseQueryFilterModel (Banshee.Sources.DatabaseSource source, DatabaseTrackListModel trackModel,
@@ -54,6 +61,8 @@ namespace Banshee.Collection.Database
FROM CoreTracks, CoreCache{0}
WHERE CoreCache.ModelID = {1} AND CoreCache.ItemID = {2} {3}
ORDER BY Value";
+
+ QueryFields = new QueryFieldSet (query_filter_field);
}
public override bool CachesValues { get { return true; } }
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs
index afcf511..83bda73 100644
--- a/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/DatabaseTrackListModel.cs
@@ -48,7 +48,7 @@ using Banshee.PlaybackController;
namespace Banshee.Collection.Database
{
public class DatabaseTrackListModel : TrackListModel, IExportableModel,
- ICacheableDatabaseModel, IFilterable, ISortable, ICareAboutView
+ ICacheableDatabaseModel, IFilterable, ISortable, ICareAboutView, ISearchable
{
private readonly BansheeDbConnection connection;
private IDatabaseTrackModelProvider provider;
@@ -319,6 +319,23 @@ namespace Banshee.Collection.Database
cache.Reload ();
}
+ private QueryFieldSet query_fields = BansheeQuery.FieldSet;
+ public QueryFieldSet QueryFields {
+ get { return query_fields; }
+ protected set { query_fields = value; }
+ }
+
+ public int IndexOf (QueryNode query, long offset)
+ {
+ lock (this) {
+ if (query == null) {
+ return -1;
+ }
+
+ return (int) cache.IndexOf (query.ToSql (QueryFields), offset);
+ }
+ }
+
public override int IndexOf (TrackInfo track)
{
lock (this) {
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/IDatabaseTrackModelCache.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/IDatabaseTrackModelCache.cs
index 618e012..abbb98a 100644
--- a/src/Core/Banshee.Services/Banshee.Collection.Database/IDatabaseTrackModelCache.cs
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/IDatabaseTrackModelCache.cs
@@ -30,6 +30,7 @@ using System;
using System.Data;
using Banshee.Collection;
using Hyena.Data.Sqlite;
+using Hyena.Query;
namespace Banshee.Collection.Database
{
@@ -41,6 +42,7 @@ namespace Banshee.Collection.Database
void RestoreSelection ();
long Count { get; }
void Reload ();
+ long IndexOf (string where_fragment, long offset);
long IndexOf (Hyena.Data.ICacheableItem item);
long IndexOf (object item_entry_id);
TrackInfo GetSingle (string random_fragment, params object [] args);
diff --git a/src/Core/Banshee.Services/Banshee.Collection.Database/ISearchable.cs b/src/Core/Banshee.Services/Banshee.Collection.Database/ISearchable.cs
new file mode 100644
index 0000000..3333c61
--- /dev/null
+++ b/src/Core/Banshee.Services/Banshee.Collection.Database/ISearchable.cs
@@ -0,0 +1,39 @@
+//
+// ISearchable.cs
+//
+// Author:
+// Neil Loknath <neil loknath gmail com>
+//
+// Copyright (C) 2009 Neil Loknath
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using Hyena.Query;
+
+namespace Banshee.Collection.Database
+{
+ public interface ISearchable
+ {
+ QueryFieldSet QueryFields { get; }
+ int IndexOf (QueryNode query, long offset);
+ }
+}
\ No newline at end of file
diff --git a/src/Core/Banshee.Services/Makefile.am b/src/Core/Banshee.Services/Makefile.am
index b58ef80..1ac8785 100644
--- a/src/Core/Banshee.Services/Makefile.am
+++ b/src/Core/Banshee.Services/Makefile.am
@@ -21,6 +21,7 @@ SOURCES = \
Banshee.Collection.Database/FilterModelProvider.cs \
Banshee.Collection.Database/IDatabaseTrackModelCache.cs \
Banshee.Collection.Database/IDatabaseTrackModelProvider.cs \
+ Banshee.Collection.Database/ISearchable.cs \
Banshee.Collection.Database/QueryFilterInfo.cs \
Banshee.Collection.Database/RandomBy.cs \
Banshee.Collection.Database/RandomByAlbum.cs \
diff --git a/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/BaseTrackListView.cs b/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/BaseTrackListView.cs
index fb52f7e..d9b106d 100644
--- a/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/BaseTrackListView.cs
+++ b/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/BaseTrackListView.cs
@@ -44,7 +44,7 @@ using Banshee.Gui;
namespace Banshee.Collection.Gui
{
- public class BaseTrackListView : ListView<TrackInfo>
+ public class BaseTrackListView : SearchableListView<TrackInfo>
{
public BaseTrackListView () : base ()
{
@@ -67,6 +67,10 @@ namespace Banshee.Collection.Gui
};
}
+ public override bool SelectOnRowFound {
+ get { return true; }
+ }
+
private static TargetEntry [] source_targets = new TargetEntry [] {
ListViewDragDropTarget.ModelSelection,
Banshee.Gui.DragDrop.DragDropTarget.UriList
@@ -75,7 +79,7 @@ namespace Banshee.Collection.Gui
protected override TargetEntry [] DragDropSourceEntries {
get { return source_targets; }
}
-
+
protected override bool OnKeyPressEvent (Gdk.EventKey press)
{
// Have o act the same as enter - activate the selection
diff --git a/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/SearchableListView.cs b/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/SearchableListView.cs
new file mode 100644
index 0000000..8a68695
--- /dev/null
+++ b/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/SearchableListView.cs
@@ -0,0 +1,275 @@
+//
+// SearchableListView.cs
+//
+// Author:
+// Neil Loknath <neil loknath gmail com>
+//
+// Copyright (C) 2009 Neil Loknath
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using Mono.Unix;
+using Gtk;
+
+using Hyena.Data;
+using Hyena.Data.Gui;
+using Hyena.Gui;
+using Hyena.Query;
+using Hyena.Widgets;
+
+using Banshee.Gui;
+using Banshee.Collection;
+using Banshee.Collection.Database;
+
+namespace Banshee.Collection.Gui
+{
+ public class SearchableListView<T> : ListView<T>
+ {
+ private EntryPopup search_popup;
+
+ private long previous_search_offset;
+ private long search_offset = 0;
+
+ private QueryFieldSet last_query_fields = null;
+
+ private QueryNode last_query = null;
+ public QueryNode LastQuery {
+ get { return last_query; }
+ }
+
+ public virtual bool SelectOnRowFound {
+ get { return false; }
+ }
+
+ private QueryTermNode CreateNode (QueryField field, Operator op, string target)
+ {
+ QueryTermNode node = new QueryTermNode ();
+ if (field == null || op == null || String.IsNullOrEmpty (target)) {
+ return node;
+ }
+
+ node.Field = field;
+ node.Operator = op;
+ node.Value = QueryValue.CreateFromStringValue (target, field);
+
+ return node;
+ }
+
+ private QueryNode BuildQueryTree (QueryFieldSet fields, Operator op, string target)
+ {
+ return BuildQueryTree (fields, op, target, false);
+ }
+
+ private QueryNode BuildQueryTree (QueryFieldSet fields, Operator op, string target, bool force)
+ {
+ if (fields == null || op == null || String.IsNullOrEmpty (target)) {
+ return null;
+ }
+
+ QueryListNode root = new QueryListNode (Keyword.Or);
+
+ foreach (QueryField field in fields) {
+ if (force || field.IsDefault) {
+ root.AddChild (CreateNode (field, op, target));
+ }
+ }
+
+ // force the query to build if no default fields in QueryFieldSet
+ if (!force && root.ChildCount == 0) {
+ return BuildQueryTree (fields, op, target, true);
+ }
+
+ return root.Trim ();
+ }
+
+ private void UpdateQueryTree (QueryNode query, string target)
+ {
+ if (query == null || String.IsNullOrEmpty (target)) {
+ return;
+ }
+
+ foreach (QueryTermNode node in query.GetTerms ()) {
+ node.Value = QueryValue.CreateFromStringValue (target, node.Field);
+ }
+ }
+
+ private bool PerformSearch (string target)
+ {
+ if (String.IsNullOrEmpty (target)) {
+ return false;
+ }
+
+ ISearchable model = Model as ISearchable;
+ if (model == null) {
+ return false;
+ }
+
+ if (last_query == null || !last_query_fields.Equals (model.QueryFields)) {
+ last_query_fields = model.QueryFields;
+ last_query = BuildQueryTree (last_query_fields, StringQueryValue.StartsWith, target);
+ } else {
+ UpdateQueryTree (last_query, target);
+ }
+
+ int i = model.IndexOf (last_query, search_offset);
+ if (i >= 0) {
+ SelectRow (i);
+ return true;
+ }
+
+ return false;
+ }
+
+ private void SelectRow (int i)
+ {
+ CenterOn (i);
+
+ Selection.FocusedIndex = i;
+ if (SelectOnRowFound) {
+ Selection.Clear (false);
+ Selection.Select (i);
+ }
+
+ InvalidateList ();
+ }
+
+ private void PositionPopup (EntryPopup popup)
+ {
+ if (popup == null) {
+ return;
+ }
+
+ int x, y;
+ int widget_x, widget_y;
+ int widget_height, widget_width;
+
+ popup.Realize ();
+
+ Gdk.Window widget_window = EventWindow;
+ Gdk.Screen widget_screen = widget_window.Screen;
+
+ Gtk.Requisition popup_req;
+
+ widget_window.GetOrigin (out widget_x, out widget_y);
+ widget_window.GetSize (out widget_width, out widget_height);
+
+ popup_req = popup.Requisition;
+
+ if (widget_x + widget_width > widget_screen.Width) {
+ x = widget_screen.Width - popup_req.Width;
+ } else if (widget_x + widget_width - popup_req.Width < 0) {
+ x = 0;
+ } else {
+ x = widget_x + widget_width - popup_req.Width;
+ }
+
+ if (widget_y + widget_height + popup_req.Height > widget_screen.Height) {
+ y = widget_screen.Height - popup_req.Height;
+ } else if (widget_y + widget_height < 0) {
+ y = 0;
+ } else {
+ y = widget_y + widget_height;
+ }
+
+ popup.Move (x, y);
+ }
+
+ private bool IsCharValid (char c)
+ {
+ return Char.IsLetterOrDigit (c) ||
+ Char.IsPunctuation (c) ||
+ Char.IsSymbol (c);
+ }
+
+ protected override bool OnKeyPressEvent (Gdk.EventKey press)
+ {
+ char input = Convert.ToChar (Gdk.Keyval.ToUnicode (press.KeyValue));
+ if (!IsCharValid (input) || Model as ISelectable == null) {
+ return base.OnKeyPressEvent (press);
+ }
+
+ if (search_popup == null) {
+ search_popup = new EntryPopup ();
+ search_popup.Changed += (o, a) => {
+ search_offset = 0;
+ PerformSearch (search_popup.Text);
+ };
+
+ search_popup.KeyPressed += OnPopupKeyPressed;
+ }
+
+ PositionPopup (search_popup);
+ search_popup.HasFocus = true;
+ search_popup.Show ();
+ search_popup.Text = String.Format ("{0}{1}", search_popup.Text, input);
+ search_popup.Entry.Position = search_popup.Text.Length;
+ return true;
+ }
+
+ private void OnPopupKeyPressed (object sender, KeyPressEventArgs args)
+ {
+ bool search_forward = false;
+ bool search_backward = false;
+
+ Gdk.EventKey press = args.Event;
+ Gdk.Key key = press.Key;
+
+ switch (key) {
+ case Gdk.Key.Up:
+ case Gdk.Key.KP_Up:
+ search_backward = true;
+ break;
+ case Gdk.Key.g:
+ case Gdk.Key.G:
+ if ((press.State & Gdk.ModifierType.ControlMask) != 0 &&
+ (press.State & Gdk.ModifierType.ShiftMask) != 0) {
+ search_backward = true;
+ } else if ((press.State & Gdk.ModifierType.ControlMask) != 0) {
+ search_forward = true;
+ }
+ break;
+ case Gdk.Key.F3:
+ case Gdk.Key.KP_F3:
+ if ((press.State & Gdk.ModifierType.ShiftMask) != 0) {
+ search_backward = true;
+ } else if (press.State == Gdk.ModifierType.None) {
+ search_forward = true;
+ }
+ break;
+ case Gdk.Key.Down:
+ case Gdk.Key.KP_Down:
+ search_forward = true;
+ break;
+ }
+
+ if (search_forward) {
+ previous_search_offset = search_offset++;
+ if (!PerformSearch (search_popup.Text)) {
+ search_offset = previous_search_offset;
+ }
+ } else if (search_backward) {
+ search_offset = search_offset == 0 ? 0 : search_offset - 1;
+ PerformSearch (search_popup.Text);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/TrackFilterListView.cs b/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/TrackFilterListView.cs
index 259188d..0d4c3c8 100644
--- a/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/TrackFilterListView.cs
+++ b/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/TrackFilterListView.cs
@@ -38,7 +38,7 @@ using Banshee.Gui;
namespace Banshee.Collection.Gui
{
- public class TrackFilterListView<T> : ListView<T>
+ public class TrackFilterListView<T> : SearchableListView<T>
{
protected ColumnController column_controller;
diff --git a/src/Core/Banshee.ThickClient/Makefile.am b/src/Core/Banshee.ThickClient/Makefile.am
index 037370a..94549ce 100644
--- a/src/Core/Banshee.ThickClient/Makefile.am
+++ b/src/Core/Banshee.ThickClient/Makefile.am
@@ -28,6 +28,7 @@ SOURCES = \
Banshee.Collection.Gui/DefaultColumnController.cs \
Banshee.Collection.Gui/PersistentColumnController.cs \
Banshee.Collection.Gui/QueryFilterView.cs \
+ Banshee.Collection.Gui/SearchableListView.cs \
Banshee.Collection.Gui/TerseTrackListView.cs \
Banshee.Collection.Gui/TrackFilterListView.cs \
Banshee.Collection.Gui/TrackListView.cs \
diff --git a/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Rendering.cs b/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Rendering.cs
index 697f2d3..8f4c265 100644
--- a/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Rendering.cs
+++ b/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Rendering.cs
@@ -422,7 +422,7 @@ namespace Hyena.Data.Gui
cairo_context.Stroke ();
}
- private void InvalidateList ()
+ protected void InvalidateList ()
{
if (IsRealized) {
QueueDrawArea (list_rendering_alloc.X, list_rendering_alloc.Y, list_rendering_alloc.Width, list_rendering_alloc.Height);
diff --git a/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Windowing.cs b/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Windowing.cs
index c9b3c0b..27f312c 100644
--- a/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Windowing.cs
+++ b/src/Libraries/Hyena.Gui/Hyena.Data.Gui/ListView/ListView_Windowing.cs
@@ -46,6 +46,10 @@ namespace Hyena.Data.Gui
protected Rectangle ListAllocation {
get { return list_rendering_alloc; }
}
+
+ protected Gdk.Window EventWindow {
+ get { return event_window; }
+ }
protected override void OnRealized ()
{
diff --git a/src/Libraries/Hyena.Gui/Hyena.Widgets/EntryPopup.cs b/src/Libraries/Hyena.Gui/Hyena.Widgets/EntryPopup.cs
new file mode 100644
index 0000000..b0763a5
--- /dev/null
+++ b/src/Libraries/Hyena.Gui/Hyena.Widgets/EntryPopup.cs
@@ -0,0 +1,226 @@
+//
+// EntryPopup.cs
+//
+// Author:
+// Neil Loknath <neil loknath gmail com>
+//
+// Copyright (C) 2009 Neil Loknath
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+using System;
+using System.Timers;
+using Gdk;
+using Gtk;
+
+namespace Hyena.Widgets
+{
+ public class EntryPopup : Gtk.Window
+ {
+ private Entry text_entry;
+ private uint timeout_id = 0;
+
+ public event EventHandler<EventArgs> Changed;
+ public event EventHandler<KeyPressEventArgs> KeyPressed;
+
+ public EntryPopup (string text) : this ()
+ {
+ Text = text;
+ }
+
+ public EntryPopup () : base (Gtk.WindowType.Popup)
+ {
+ CanFocus = true;
+ Resizable = false;
+ TypeHint = Gdk.WindowTypeHint.Utility;
+ Modal = true;
+
+ Frame frame = new Frame ();
+ frame.Shadow = ShadowType.EtchedIn;
+ Add (frame);
+
+ HBox box = new HBox ();
+ text_entry = new Entry();
+ box.PackStart (text_entry, true, true, 0);
+ box.BorderWidth = 3;
+
+ frame.Add (box);
+ frame.ShowAll ();
+
+ text_entry.Text = String.Empty;
+ text_entry.CanFocus = true;
+
+ //TODO figure out why this event does not get raised
+ text_entry.FocusOutEvent += (o, a) => {
+ if (hide_when_focus_lost) {
+ HidePopup ();
+ }
+ };
+
+ text_entry.KeyReleaseEvent += delegate (object o, KeyReleaseEventArgs args) {
+ if (args.Event.Key == Gdk.Key.Escape ||
+ args.Event.Key == Gdk.Key.Return ||
+ args.Event.Key == Gdk.Key.Tab) {
+
+ HidePopup ();
+ }
+
+ InitializeDelayedHide ();
+ };
+
+ text_entry.KeyPressEvent += (o, a) => OnKeyPressed (a);
+
+ text_entry.Changed += (o, a) => {
+ if (GdkWindow.IsVisible) {
+ OnChanged (a);
+ }
+ };
+ }
+
+ public new bool HasFocus {
+ get { return text_entry.HasFocus; }
+ set { text_entry.HasFocus = value; }
+ }
+
+ public string Text {
+ get { return text_entry.Text; }
+ set { text_entry.Text = value; }
+ }
+
+ public Entry Entry {
+ get { return text_entry; }
+ }
+
+ private bool hide_after_timeout = true;
+ public bool HideAfterTimeout {
+ get { return hide_after_timeout; }
+ set { hide_after_timeout = value; }
+ }
+
+ private uint timeout = 5000;
+ public uint Timeout {
+ get { return timeout; }
+ set { timeout = value; }
+ }
+
+ private bool hide_when_focus_lost = true;
+ public bool HideOnFocusOut {
+ get { return hide_when_focus_lost; }
+ set { hide_when_focus_lost = value; }
+ }
+
+ private bool reset_when_hiding = true;
+ public bool ResetOnHide {
+ get { return reset_when_hiding; }
+ set { reset_when_hiding = value; }
+ }
+
+ public override void Dispose ()
+ {
+ text_entry.Dispose ();
+ base.Dispose ();
+ }
+
+ public new void GrabFocus ()
+ {
+ text_entry.GrabFocus ();
+ }
+
+ private void ResetDelayedHide ()
+ {
+ if (timeout_id > 0) {
+ GLib.Source.Remove (timeout_id);
+ timeout_id = 0;
+ }
+ }
+
+ private void InitializeDelayedHide ()
+ {
+ ResetDelayedHide ();
+ timeout_id = GLib.Timeout.Add (timeout, delegate {
+ HidePopup ();
+ return false;
+ });
+ }
+
+ private void HidePopup ()
+ {
+ ResetDelayedHide ();
+ Hide ();
+
+ if (reset_when_hiding) {
+ text_entry.Text = String.Empty;
+ }
+ }
+
+ protected virtual void OnChanged (EventArgs args)
+ {
+ var handler = Changed;
+ if (handler != null) {
+ handler (this, EventArgs.Empty);
+ }
+ }
+
+ protected virtual void OnKeyPressed (KeyPressEventArgs args)
+ {
+ var handler = KeyPressed;
+ if (handler != null) {
+ handler (this, args);
+ }
+ }
+
+ //TODO figure out why this event does not get raised
+ protected override bool OnFocusOutEvent (Gdk.EventFocus evnt)
+ {
+ if (hide_when_focus_lost) {
+ HidePopup ();
+ return true;
+ }
+
+ return base.OnFocusOutEvent (evnt);
+ }
+
+ protected override bool OnExposeEvent (Gdk.EventExpose evnt)
+ {
+ InitializeDelayedHide ();
+ return base.OnExposeEvent (evnt);
+ }
+
+ protected override bool OnButtonReleaseEvent (Gdk.EventButton evnt)
+ {
+ if (!text_entry.HasFocus && hide_when_focus_lost) {
+ HidePopup ();
+ return true;
+ }
+
+ return base.OnButtonReleaseEvent (evnt);
+ }
+
+ protected override bool OnButtonPressEvent (Gdk.EventButton evnt)
+ {
+ if (!text_entry.HasFocus && hide_when_focus_lost) {
+ HidePopup ();
+ return true;
+ }
+
+ return base.OnButtonPressEvent (evnt);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Libraries/Hyena.Gui/Makefile.am b/src/Libraries/Hyena.Gui/Makefile.am
index e8b3de6..c023e8e 100644
--- a/src/Libraries/Hyena.Gui/Makefile.am
+++ b/src/Libraries/Hyena.Gui/Makefile.am
@@ -86,6 +86,7 @@ SOURCES = \
Hyena.Widgets/AnimatedVBox.cs \
Hyena.Widgets/AnimatedWidget.cs \
Hyena.Widgets/ComplexMenuItem.cs \
+ Hyena.Widgets/EntryPopup.cs \
Hyena.Widgets/ImageButton.cs \
Hyena.Widgets/MenuButton.cs \
Hyena.Widgets/MessageBar.cs \
diff --git a/src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteModelCache.cs b/src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteModelCache.cs
index 9698461..4589da6 100644
--- a/src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteModelCache.cs
+++ b/src/Libraries/Hyena/Hyena.Data.Sqlite/SqliteModelCache.cs
@@ -30,6 +30,9 @@
using System;
using System.Collections.Generic;
using System.Data;
+using System.Text;
+
+using Hyena.Query;
namespace Hyena.Data.Sqlite
{
@@ -45,9 +48,11 @@ namespace Hyena.Data.Sqlite
private HyenaSqliteCommand delete_selection_command;
private HyenaSqliteCommand save_selection_command;
private HyenaSqliteCommand get_selection_command;
+ private HyenaSqliteCommand indexof_command;
private string select_str;
private string reload_sql;
+ private string last_indexof_where_fragment;
private long uid;
private long selection_uid;
private long rows;
@@ -102,7 +107,7 @@ namespace Hyena.Data.Sqlite
if (model.CachesJoinTableEntries) {
select_str = String.Format (
- @"SELECT {0}, {5}.ItemID FROM {1}
+ @"SELECT {0}, OrderID, {5}.ItemID FROM {1}
INNER JOIN {2}
ON {3} = {2}.{4}
INNER JOIN {5}
@@ -134,7 +139,7 @@ namespace Hyena.Data.Sqlite
);
} else {
select_str = String.Format (
- @"SELECT {0}, {2}.ItemID FROM {1}
+ @"SELECT {0}, OrderID, {2}.ItemID FROM {1}
INNER JOIN {2}
ON {3} = {2}.ItemID
WHERE
@@ -227,6 +232,38 @@ namespace Hyena.Data.Sqlite
}
}
}
+
+ public long IndexOf (string where_fragment, long offset)
+ {
+ if (String.IsNullOrEmpty (where_fragment)) {
+ return -1;
+ }
+
+ if (!where_fragment.Equals (last_indexof_where_fragment)) {
+ last_indexof_where_fragment = where_fragment;
+
+ if (!where_fragment.Trim ().ToLower ().StartsWith ("and ")) {
+ where_fragment = " AND " + where_fragment;
+ }
+
+ string sql = String.Format ("{0} {1} LIMIT ?, 1", select_str, where_fragment);
+ indexof_command = new HyenaSqliteCommand (sql);
+ }
+
+ lock (this) {
+ using (IDataReader reader = connection.Query (indexof_command, offset)) {
+ if (reader.Read ()) {
+ long target_id = (long) reader[reader.FieldCount - 2];
+ if (target_id == 0) {
+ return -1;
+ }
+ return target_id - FirstOrderId;
+ }
+ }
+
+ return -1;
+ }
+ }
public long IndexOf (ICacheableItem item)
{
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]