Add search.js, rebase search system on top

The high level goal is to separate the concern of searching for
things with display of those things; for example in newer mockups,
applications are displayed exactly the same as they look in the
AppWell.

Another goal was optimizing for speed; for example,
application search was pushed mostly down into C, and we avoid
lowercasing and normalizing every item over and over.

https://bugzilla.gnome.org/show_bug.cgi?id=603523
This commit is contained in:
Colin Walters
2009-11-29 17:45:30 -05:00
parent f5f92b2e79
commit b7646d18ae
10 changed files with 1163 additions and 566 deletions

View File

@ -15,7 +15,7 @@ const _ = Gettext.gettext;
const DND = imports.ui.dnd;
const Main = imports.ui.main;
const GenericDisplay = imports.ui.genericDisplay;
const Search = imports.ui.search;
const NAUTILUS_PREFS_DIR = '/apps/nautilus/preferences';
const DESKTOP_IS_HOME_KEY = NAUTILUS_PREFS_DIR + '/desktop_is_home_dir';
@ -30,16 +30,30 @@ const PLACES_ICON_SIZE = 16;
* @iconFactory: A JavaScript callback which will create an icon texture given a size parameter
* @launch: A JavaScript callback to launch the entry
*/
function PlaceInfo(name, iconFactory, launch) {
this._init(name, iconFactory, launch);
function PlaceInfo(id, name, iconFactory, launch) {
this._init(id, name, iconFactory, launch);
}
PlaceInfo.prototype = {
_init: function(name, iconFactory, launch) {
_init: function(id, name, iconFactory, launch) {
this.id = id;
this.name = name;
this._lowerName = name.toLowerCase();
this.iconFactory = iconFactory;
this.launch = launch;
this.id = null;
},
matchTerms: function(terms) {
let mtype = Search.MatchType.NONE;
for (let i = 0; i < terms.length; i++) {
let term = terms[i];
let idx = this._lowerName.indexOf(term);
if (idx == 0)
return Search.MatchType.PREFIX;
else if (idx > 0)
mtype = Search.MatchType.SUBSTRING;
}
return mtype;
}
}
@ -52,6 +66,7 @@ PlacesManager.prototype = {
let gconf = Shell.GConf.get_default();
gconf.watch_directory(NAUTILUS_PREFS_DIR);
this._defaultPlaces = [];
this._mounts = [];
this._bookmarks = [];
this._isDesktopHome = false;
@ -60,7 +75,7 @@ PlacesManager.prototype = {
let homeUri = homeFile.get_uri();
let homeLabel = Shell.util_get_label_for_uri (homeUri);
let homeIcon = Shell.util_get_icon_for_uri (homeUri);
this._home = new PlaceInfo(homeLabel,
this._home = new PlaceInfo('special:home', homeLabel,
function(size) {
return Shell.TextureCache.get_default().load_gicon(homeIcon, size);
},
@ -73,7 +88,7 @@ PlacesManager.prototype = {
let desktopUri = desktopFile.get_uri();
let desktopLabel = Shell.util_get_label_for_uri (desktopUri);
let desktopIcon = Shell.util_get_icon_for_uri (desktopUri);
this._desktopMenu = new PlaceInfo(desktopLabel,
this._desktopMenu = new PlaceInfo('special:desktop', desktopLabel,
function(size) {
return Shell.TextureCache.get_default().load_gicon(desktopIcon, size);
},
@ -81,7 +96,7 @@ PlacesManager.prototype = {
Gio.app_info_launch_default_for_uri(desktopUri, global.create_app_launch_context());
});
this._connect = new PlaceInfo(_("Connect to..."),
this._connect = new PlaceInfo('special:connect', _("Connect to..."),
function (size) {
return Shell.TextureCache.get_default().load_icon_name("applications-internet", size);
},
@ -101,7 +116,7 @@ PlacesManager.prototype = {
}
if (networkApp != null) {
this._network = new PlaceInfo(networkApp.get_name(),
this._network = new PlaceInfo('special:network', networkApp.get_name(),
function(size) {
return networkApp.create_icon_texture(size);
},
@ -110,6 +125,16 @@ PlacesManager.prototype = {
});
}
this._defaultPlaces.push(this._home);
if (!this._isDesktopHome)
this._defaultPlaces.push(this._desktopMenu);
if (this._network)
this._defaultPlaces.push(this._network);
this._defaultPlaces.push(this._connect);
/*
* Show devices, code more or less ported from nautilus-places-sidebar.c
*/
@ -238,7 +263,7 @@ PlacesManager.prototype = {
continue;
let icon = Shell.util_get_icon_for_uri(bookmark);
let item = new PlaceInfo(label,
let item = new PlaceInfo('bookmark:' + bookmark, label,
function(size) {
return Shell.TextureCache.get_default().load_gicon(icon, size);
},
@ -267,7 +292,8 @@ PlacesManager.prototype = {
let mountIcon = mount.get_icon();
let root = mount.get_root();
let mountUri = root.get_uri();
let devItem = new PlaceInfo(mountLabel,
let devItem = new PlaceInfo('mount:' + mountUri,
mountLabel,
function(size) {
return Shell.TextureCache.get_default().load_gicon(mountIcon, size);
},
@ -282,16 +308,7 @@ PlacesManager.prototype = {
},
getDefaultPlaces: function () {
let places = [this._home];
if (!this._isDesktopHome)
places.push(this._desktopMenu);
if (this._network)
places.push(this._network);
places.push(this._connect);
return places;
return this._defaultPlaces;
},
getBookmarks: function () {
@ -300,6 +317,28 @@ PlacesManager.prototype = {
getMounts: function () {
return this._mounts;
},
_lookupById: function(sourceArray, id) {
for (let i = 0; i < sourceArray.length; i++) {
let place = sourceArray[i];
if (place.id == id)
return place;
}
return null;
},
lookupPlaceById: function(id) {
let colonIdx = id.indexOf(':');
let type = id.substring(0, colonIdx);
let sourceArray = null;
if (type == 'special')
sourceArray = this._defaultPlaces;
else if (type == 'mount')
sourceArray = this._mounts;
else if (type == 'bookmark')
sourceArray = this._bookmarks;
return this._lookupById(sourceArray, id);
}
};
@ -421,120 +460,67 @@ DashPlaceDisplay.prototype = {
Signals.addSignalMethods(DashPlaceDisplay.prototype);
function PlaceDisplayItem(placeInfo) {
this._init(placeInfo);
function PlaceSearchProvider() {
this._init();
}
PlaceDisplayItem.prototype = {
__proto__: GenericDisplay.GenericDisplayItem.prototype,
PlaceSearchProvider.prototype = {
__proto__: Search.SearchProvider.prototype,
_init : function(placeInfo) {
GenericDisplay.GenericDisplayItem.prototype._init.call(this);
this._info = placeInfo;
this._setItemInfo(placeInfo.name, '');
_init: function() {
Search.SearchProvider.prototype._init.call(this, _("PLACES"));
},
//// Public method overrides ////
// Opens an application represented by this display item.
launch : function() {
this._info.launch();
getResultMeta: function(resultId) {
let placeInfo = Main.placesManager.lookupPlaceById(resultId);
if (!placeInfo)
return null;
return { 'id': resultId,
'name': placeInfo.name,
'icon': placeInfo.iconFactory(Search.RESULT_ICON_SIZE) };
},
shellWorkspaceLaunch: function() {
this._info.launch();
activateResult: function(id) {
let placeInfo = Main.placesManager.lookupPlaceById(id);
placeInfo.launch();
},
//// Protected method overrides ////
// Returns an icon for the item.
_createIcon: function() {
return this._info.iconFactory(GenericDisplay.ITEM_DISPLAY_ICON_SIZE);
_compareResultMeta: function (idA, idB) {
let infoA = Main.placesManager.lookupPlaceById(idA);
let infoB = Main.placesManager.lookupPlaceById(idB);
return infoA.name.localeCompare(infoB.name);
},
// Returns a preview icon for the item.
_createPreviewIcon: function() {
return this._info.iconFactory(GenericDisplay.PREVIEW_ICON_SIZE);
_searchPlaces: function(places, terms) {
let multipleResults = [];
let prefixResults = [];
let substringResults = [];
terms = terms.map(String.toLowerCase);
for (let i = 0; i < places.length; i++) {
let place = places[i];
let mtype = place.matchTerms(terms);
if (mtype == Search.MatchType.MULTIPLE)
multipleResults.push(place.id);
else if (mtype == Search.MatchType.PREFIX)
prefixResults.push(place.id);
else if (mtype == Search.MatchType.SUBSTRING)
substringResults.push(place.id);
}
multipleResults.sort(this._compareResultMeta);
prefixResults.sort(this._compareResultMeta);
substringResults.sort(this._compareResultMeta);
return multipleResults.concat(prefixResults.concat(substringResults));
},
getInitialResultSet: function(terms) {
let places = Main.placesManager.getAllPlaces();
return this._searchPlaces(places, terms);
},
getSubsearchResultSet: function(previousResults, terms) {
let places = previousResults.map(function (id) { return Main.placesManager.lookupPlaceById(id); });
return this._searchPlaces(places, terms);
}
};
function PlaceDisplay(flags) {
this._init(flags);
}
PlaceDisplay.prototype = {
__proto__: GenericDisplay.GenericDisplay.prototype,
_init: function(flags) {
GenericDisplay.GenericDisplay.prototype._init.call(this, flags);
this._stale = true;
Main.placesManager.connect('places-updated', Lang.bind(this, function (e) {
this._stale = true;
}));
},
//// Protected method overrides ////
_refreshCache: function () {
if (!this._stale)
return true;
this._allItems = {};
let array = Main.placesManager.getAllPlaces();
for (let i = 0; i < array.length; i ++) {
// We are using an array id as placeInfo id because placeInfo doesn't have any
// other information piece that can be used as a unique id. There are different
// types of placeInfo, such as devices and directories that would result in differently
// structured ids. Also the home directory can show up in both the default places and in
// bookmarks which means its URI can't be used as a unique id. (This does mean it can
// appear twice in search results, though that doesn't happen at the moment because we
// name it "Home Folder" in default places and it's named with the user's system name
// if it appears as a bookmark.)
let placeInfo = array[i];
placeInfo.id = i;
this._allItems[i] = placeInfo;
}
this._stale = false;
return false;
},
// Sets the list of the displayed items.
_setDefaultList: function() {
this._matchedItems = {};
this._matchedItemKeys = [];
for (id in this._allItems) {
this._matchedItems[id] = 1;
this._matchedItemKeys.push(id);
}
this._matchedItemKeys.sort(Lang.bind(this, this._compareItems));
},
// Checks if the item info can be a match for the search string by checking
// the name of the place. Item info is expected to be PlaceInfo.
// Returns a boolean flag indicating if itemInfo is a match.
_isInfoMatching: function(itemInfo, search) {
if (search == null || search == '')
return true;
let name = itemInfo.name.toLowerCase();
if (name.indexOf(search) >= 0)
return true;
return false;
},
// Compares items associated with the item ids based on the alphabetical order
// of the item names.
// Returns an integer value indicating the result of the comparison.
_compareItems: function(itemIdA, itemIdB) {
let placeA = this._allItems[itemIdA];
let placeB = this._allItems[itemIdB];
return placeA.name.localeCompare(placeB.name);
},
// Creates a PlaceDisplayItem based on itemInfo, which is expected to be a PlaceInfo object.
_createDisplayItem: function(itemInfo) {
return new PlaceDisplayItem(itemInfo);
}
};