[gnome-maps/wip/mlundblad/search-as-you-type: 1/2] placeEntry, placePopover: Auto-complete searches
- From: Marcus Lundblad <mlundblad src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-maps/wip/mlundblad/search-as-you-type: 1/2] placeEntry, placePopover: Auto-complete searches
- Date: Mon, 10 Jun 2019 19:24:17 +0000 (UTC)
commit 94b334cb18595e53270b2282b7adfbe593709419
Author: Marcus Lundblad <ml update uu se>
Date: Sun May 5 21:38:14 2019 +0200
placeEntry, placePopover: Auto-complete searches
Implement auto-complete search-as-you-type.
src/placeEntry.js | 151 ++++++++++++++++++++++++++++++++++++----------------
src/placePopover.js | 61 ++++++---------------
2 files changed, 121 insertions(+), 91 deletions(-)
---
diff --git a/src/placeEntry.js b/src/placeEntry.js
index 4872f46..c9950e3 100644
--- a/src/placeEntry.js
+++ b/src/placeEntry.js
@@ -34,6 +34,12 @@ const PlaceStore = imports.placeStore;
const PlacePopover = imports.placePopover;
const Utils = imports.utils;
+// minimum number of characters to start completion
+const MIN_CHARS_COMPLETION = 3;
+
+// pattern matching CJK ideographic characters
+const IDEOGRAPH_PATTERN = /[\u3300-\u9fff]/
+
var PlaceEntry = GObject.registerClass({
Properties: {
'place': GObject.ParamSpec.object('place',
@@ -54,11 +60,13 @@ var PlaceEntry = GObject.registerClass({
if (p) {
if (p.name) {
- this.text = p.name;
+ this._placeText = p.name;
} else
- this.text = p.location.latitude + ', ' + p.location.longitude;
+ this._placeText = p.location.latitude + ', ' + p.location.longitude;
} else
- this.text = '';
+ this._placeText = '';
+
+ this.text = this._placeText;
this._place = p;
this.notify('place');
@@ -85,9 +93,6 @@ var PlaceEntry = GObject.registerClass({
let maxChars = props.maxChars;
delete props.maxChars;
- let parseOnFocusOut = props.parseOnFocusOut;
- delete props.parseOnFocusOut;
-
this._matchRoute = props.matchRoute || false;
delete props.matchRoute;
@@ -98,30 +103,48 @@ var PlaceEntry = GObject.registerClass({
this._popover = this._createPopover(numVisible, maxChars);
- this.connect('activate', this._onActivate.bind(this));
- this.connect('search-changed', () => {
- if (this._cancellable)
- this._cancellable.cancel();
+ this.connect('search-changed', this._onSearchChanged.bind(this));
- this._refreshFilter();
+ this._cache = {};
- if (this.text.length === 0) {
- this._popover.hide();
- this.place = null;
- return;
- }
+ // clear cache when view moves, as result are location-dependent
+ this._mapView.view.connect('notify::latitude', () => this._cache = {});
+ }
- if (this._filter.iter_n_children(null) > 0)
- this._popover.showCompletion();
- else
- this._popover.hide();
- });
+ _onSearchChanged() {
+ if (this._parse())
+ return;
+
+ // wait for an ongoing search
+ if (this._cancellable)
+ return;
- if (parseOnFocusOut) {
- this.connect('focus-out-event', () => {
- this._parse();
- return false;
- });
+ /* start search if more than the threashold number of characters have
+ * been entered, or if the first character is in the ideographic CJK
+ * block, as for these, shorter strings could be meaningful
+ */
+ if ((this.text.length >= MIN_CHARS_COMPLETION ||
+ (this.text.length > 0 && this.text[0].match(IDEOGRAPH_PATTERN))) &&
+ this.text !== this._placeText) {
+ let cachedResults = this._cache[this.text];
+
+ if (cachedResults) {
+ this._updateResults(cachedResults);
+ } else {
+ // if no previous search has been performed, show spinner
+ if (!this._previousSearch ||
+ this._previousSearch.length < MIN_CHARS_COMPLETION ||
+ this._placeText) {
+ this._popover.showSpinner();
+ }
+ this._placeText = '';
+ this._doSearch();
+ }
+ } else {
+ this._popover.hide();
+ if (this.text.length === 0)
+ this.place = null;
+ this._previousSearch = null;
}
}
@@ -162,7 +185,9 @@ var PlaceEntry = GObject.registerClass({
let place = model.get_value(iter, PlaceStore.Columns.PLACE);
let type = model.get_value(iter, PlaceStore.Columns.TYPE);
- if (!this._matchRoute && type === PlaceStore.PlaceType.RECENT_ROUTE)
+ if (type !== PlaceStore.PlaceType.CONTACT &&
+ type !== PlaceStore.PlaceType.RECENT_ROUTE ||
+ (!this._matchRoute && type === PlaceStore.PlaceType.RECENT_ROUTE))
return false;
if (place !== null)
@@ -172,10 +197,7 @@ var PlaceEntry = GObject.registerClass({
}
_parse() {
- if (this.text.length === 0) {
- this.place = null;
- return true;
- }
+ let parsed = false;
if (this.text.startsWith('geo:')) {
let location = new Geocode.Location();
@@ -188,40 +210,77 @@ var PlaceEntry = GObject.registerClass({
Utils.showDialog(msg, Gtk.MessageType.ERROR, this.get_toplevel());
}
- return true;
+ parsed = true;
}
let parsedLocation = Place.Place.parseCoordinates(this.text);
if (parsedLocation) {
this.place = new Place.Place({ location: parsedLocation });
- return true;
+ parsed = true;
}
- return false;
- }
-
- _onActivate() {
- if (this._parse())
- return;
+ if (parsed && this._cancellable)
+ this._cancellable.cancel();
- let bbox = this._mapView.view.get_bounding_box();
-
- this._popover.showSpinner();
+ return parsed;
+ }
+ _doSearch() {
+ if (this._cancellable)
+ this._cancellable.cancel();
this._cancellable = new Gio.Cancellable();
+ this._previousSearch = this.text;
GeocodeFactory.getGeocoder().search(this.text,
this._mapView.view.latitude,
this._mapView.view.longitude,
this._cancellable,
(places, error) => {
+ this._cancellable = null;
+ this._updateResults(places);
+
+ // cache results for later
+ this._cache[this.text] = places;
+
+ // if search input has been updated, trigger a refresh
+ if (this.text !== this._previousSearch)
+ this._onSearchChanged();
+ });
+ }
- if (!places) {
+ _updateResults(places) {
+ if (!places) {
this.place = null;
this._popover.showNoResult();
return;
- }
- this._popover.updateResult(places, this.text);
- this._popover.showResult();
+ }
+
+ let completedPlaces = [];
+
+
+ this._filter.refilter();
+ this._filter.foreach((model, path, iter) => {
+ let place = model.get_value(iter, PlaceStore.Columns.PLACE);
+ let type = model.get_value(iter, PlaceStore.Columns.TYPE);
+
+ completedPlaces.push({ place: place, type: type });
+ });
+
+ let placeStore = Application.placeStore;
+
+ places.forEach((place) => {
+ let type;
+
+ if (placeStore.exists(place, PlaceStore.PlaceType.RECENT))
+ type = PlaceStore.PlaceType.RECENT;
+ else if (placeStore.exists(place, PlaceStore.PlaceType.FAVORITE))
+ type = PlaceStore.PlaceType.FAVORITE;
+ else
+ type = PlaceStore.PlaceType.ANY;
+
+ completedPlaces.push({ place: place, type: type });
});
+
+ this._popover.updateResult(completedPlaces, this.text);
+ this._popover.showResult();
}
});
diff --git a/src/placePopover.js b/src/placePopover.js
index 108137a..75d1382 100644
--- a/src/placePopover.js
+++ b/src/placePopover.js
@@ -27,13 +27,6 @@ const SearchPopover = imports.searchPopover;
const _PLACE_ICON_SIZE = 20;
-const Mode = {
- IDLE: 0, // Nothing going on
- ACTIVATED: 1, // Just activated, ignore changes to text
- COMPLETION: 2, // We are doing completion against placeStore
- RESULT: 3 // We are displaying results
-};
-
var PlacePopover = GObject.registerClass({
Signals : {
'selected' : { param_types: [ GObject.TYPE_OBJECT ] }
@@ -57,20 +50,12 @@ var PlacePopover = GObject.registerClass({
super._init(props);
this._entry = this.relative_to;
- this._entry.connect('notify::place', () => this._mode = Mode.ACTIVATED);
-
- Application.routingDelegator.graphHopper.route.connect('update', () => {
- this._mode = Mode.ACTIVATED;
- });
this._list.connect('row-activated', (list, row) => {
if (row)
this.emit('selected', row.place);
});
- // Make sure we clear all selected rows when the search string change
- this._entry.connect('changed', () => this._list.unselect_all());
-
this._list.set_header_func((row, before) => {
let header = new Gtk.Separator();
if (before)
@@ -96,8 +81,6 @@ var PlacePopover = GObject.registerClass({
}
showResult() {
- this._mode = Mode.RESULT;
-
if (this._spinner.active)
this._spinner.stop();
@@ -112,46 +95,34 @@ var PlacePopover = GObject.registerClass({
}
showNoResult() {
- this._mode = Mode.IDLE;
-
if (this._spinner.active)
this._spinner.stop();
this._stack.visible_child = this._noResultsLabel;
}
- showCompletion() {
- if (this._mode === undefined || this._mode === Mode.ACTIVATED) {
- this._mode = Mode.IDLE;
- return;
- }
-
- this._mode = Mode.COMPLETION;
- this._stack.visible_child = this._scrolledWindow;
-
- if (!this.visible)
- this.show();
- }
-
updateResult(places, searchString) {
- this._list.forall((row) => row.destroy());
+ let i = 0;
+
+ places.forEach((p) => {
+ let row = this._list.get_row_at_index(i);
- places.forEach((place) => {
- if (!place.location)
- return;
+ // update existing row, if there is one, otherwise create new
+ if (row)
+ row.update(p.place, p.type, searchString);
+ else
+ this._addRow(p.place, p.type, searchString);
- this._addRow(place, null, searchString);
+ i++;
});
- }
- updateCompletion(filter, searchString) {
- this._list.forall((row) => row.destroy());
+ // remove remaining rows
+ let row = this._list.get_row_at_index(i);
- filter.foreach((model, path, iter) => {
- let place = model.get_value(iter, PlaceStore.Columns.PLACE);
- let type = model.get_value(iter, PlaceStore.Columns.TYPE);
- this._addRow(place, type, searchString);
- });
+ while (row) {
+ row.destroy();
+ row = this._list.get_row_at_index(i);
+ }
}
_addRow(place, type, searchString) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]