Make places results available in search

Places is one of the dash sections and it should be included in search results.

Factor out the code for getting and updating the information about places from
Places to PlacesManager.

Introduce PlaceInfo class that contains information about the place and can be
used by classes that display it in different ways. Rename classes so that their
names are consistent with corresponding classes in appDisplay.js and
docDisplay.js

https://bugzilla.gnome.org/show_bug.cgi?id=599125
This commit is contained in:
JP St. Pierre 2009-10-31 22:25:28 -04:00 committed by Marina Zhurakhinskaya
parent 5ee72d807d
commit 585bfe5b5a
4 changed files with 412 additions and 174 deletions

View File

@ -19,7 +19,7 @@ dist_jsui_DATA = \
main.js \
overview.js \
panel.js \
places.js \
placeDisplay.js \
runDialog.js \
shellDBus.js \
sidebar.js \

View File

@ -14,7 +14,7 @@ const _ = Gettext.gettext;
const AppDisplay = imports.ui.appDisplay;
const DocDisplay = imports.ui.docDisplay;
const Places = imports.ui.places;
const PlaceDisplay = imports.ui.placeDisplay;
const GenericDisplay = imports.ui.genericDisplay;
const Button = imports.ui.button;
const Main = imports.ui.main;
@ -60,6 +60,7 @@ PANE_BACKGROUND_COLOR.from_pixel(0x000000f4);
const APPS = "apps";
const PREFS = "prefs";
const DOCS = "docs";
const PLACES = "places";
/*
* Returns the index in an array of a given length that is obtained
@ -81,7 +82,8 @@ function _createDisplay(displayType) {
return new AppDisplay.AppDisplay(true);
else if (displayType == DOCS)
return new DocDisplay.DocDisplay();
else if (displayType == PLACES)
return new PlaceDisplay.PlaceDisplay();
return null;
}
@ -729,7 +731,7 @@ Dash.prototype = {
/* Translators: This is in the sense of locations for documents,
network locations, etc. */
this._placesSection = new Section(_("PLACES"), true);
let placesDisplay = new Places.Places();
let placesDisplay = new PlaceDisplay.DashPlaceDisplay();
this._placesSection.content.append(placesDisplay.actor, Big.BoxPackFlags.EXPAND);
this.sectionArea.append(this._placesSection.actor, Big.BoxPackFlags.NONE);
@ -783,6 +785,11 @@ Dash.prototype = {
title: _("RECENT DOCUMENTS"),
header: null,
resultArea: null
},
{ type: PLACES,
title: _("PLACES"),
header: null,
resultArea: null
}
];

View File

@ -16,6 +16,7 @@ const Chrome = imports.ui.chrome;
const Environment = imports.ui.environment;
const Overview = imports.ui.overview;
const Panel = imports.ui.panel;
const PlaceDisplay = imports.ui.placeDisplay;
const RunDialog = imports.ui.runDialog;
const LookingGlass = imports.ui.lookingGlass;
const ShellDBus = imports.ui.shellDBus;
@ -28,6 +29,7 @@ DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff);
let chrome = null;
let panel = null;
let sidebar = null;
let placesManager = null;
let overview = null;
let runDialog = null;
let lookingGlass = null;
@ -96,6 +98,7 @@ function start() {
getRunDialog().open();
});
placesManager = new PlaceDisplay.PlacesManager();
overview = new Overview.Overview();
chrome = new Chrome.Chrome();
panel = new Panel.Panel();

View File

@ -23,120 +23,93 @@ const PLACES_VSPACING = 8;
const PLACES_ICON_SIZE = 16;
/**
* An entry in the places menu.
* Represents a place object, which is most normally a bookmark entry,
* a mount/volume, or a special place like the Home Folder, Computer, and Network.
*
* @name: String title
* @iconFactory: A JavaScript callback which will create an icon texture
* @onActivate: A JavaScript callback to launch the entry
* @iconFactory: A JavaScript callback which will create an icon texture given a size parameter
* @launch: A JavaScript callback to launch the entry
*/
function PlaceDisplay(name, iconFactory, onActivate) {
this._init(name, iconFactory, onActivate);
function PlaceInfo(name, iconFactory, launch) {
this._init(name, iconFactory, launch);
}
PlaceDisplay.prototype = {
_init : function(name, iconFactory, onActivate) {
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
reactive: true,
spacing: 4 });
this.actor.connect('button-release-event', Lang.bind(this, function (b, e) {
onActivate(this);
Main.overview.hide();
}));
let text = new Clutter.Text({ font_name: "Sans 14px",
ellipsize: Pango.EllipsizeMode.END,
color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
text: name });
let iconBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
this._icon = iconFactory();
iconBox.append(this._icon, Big.BoxPackFlags.NONE);
this.actor.append(iconBox, Big.BoxPackFlags.NONE);
this.actor.append(text, Big.BoxPackFlags.EXPAND);
this._iconFactory = iconFactory;
this._onActivate = onActivate;
this.actor._delegate = this;
let draggable = DND.makeDraggable(this.actor);
},
getDragActorSource: function() {
return this._icon;
},
getDragActor: function(stageX, stageY) {
return this._iconFactory();
},
//// Drag and drop methods ////
shellWorkspaceLaunch : function() {
this._onActivate();
PlaceInfo.prototype = {
_init: function(name, iconFactory, launch) {
this.name = name;
this.iconFactory = iconFactory;
this.launch = launch;
this.id = null;
}
};
Signals.addSignalMethods(PlaceDisplay.prototype);
}
function Places() {
function PlacesManager() {
this._init();
}
Places.prototype = {
_init : function() {
// Places is divided semi-arbitrarily into left and right; a grid would
// look better in that there would be an even number of items left+right,
// but it seems like we want some sort of differentiation between actions
// like "Connect to server..." and regular folders
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
spacing: 4 });
this._leftBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
this.actor.append(this._leftBox, Big.BoxPackFlags.EXPAND);
this._rightBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
this.actor.append(this._rightBox, Big.BoxPackFlags.EXPAND);
// Subdivide left into actions and devices
this._actionsBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
spacing: PLACES_VSPACING });
this._leftBox.append(this._actionsBox, Big.BoxPackFlags.NONE);
this._devBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
spacing: PLACES_VSPACING,
padding_top: 6 });
this._leftBox.append(this._devBox, Big.BoxPackFlags.NONE);
// Right is bookmarks
this._dirsBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
spacing: PLACES_VSPACING });
this._rightBox.append(this._dirsBox, Big.BoxPackFlags.NONE);
PlacesManager.prototype = {
_init: function() {
let gconf = Shell.GConf.get_default();
gconf.watch_directory(NAUTILUS_PREFS_DIR);
this._mounts = [];
this._bookmarks = [];
this._isDesktopHome = false;
let homeFile = Gio.file_new_for_path (GLib.get_home_dir());
let homeUri = homeFile.get_uri();
let homeLabel = Shell.util_get_label_for_uri (homeUri);
let homeIcon = Shell.util_get_icon_for_uri (homeUri);
let home = new PlaceDisplay(homeLabel,
function() {
return Shell.TextureCache.get_default().load_gicon(homeIcon, PLACES_ICON_SIZE);
this._home = new PlaceInfo(homeLabel,
function(size) {
return Shell.TextureCache.get_default().load_gicon(homeIcon, size);
},
function() {
Gio.app_info_launch_default_for_uri(homeUri, Main.createAppLaunchContext());
});
this._actionsBox.append(home.actor, Big.BoxPackFlags.NONE);
let desktopPath = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP);
let desktopFile = Gio.file_new_for_path (desktopPath);
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 PlaceDisplay(desktopLabel,
function() {
return Shell.TextureCache.get_default().load_gicon(desktopIcon, PLACES_ICON_SIZE);
this._desktopMenu = new PlaceInfo(desktopLabel,
function(size) {
return Shell.TextureCache.get_default().load_gicon(desktopIcon, size);
},
function() {
Gio.app_info_launch_default_for_uri(desktopUri, Main.createAppLaunchContext());
});
this._actionsBox.append(this._desktopMenu.actor, Big.BoxPackFlags.NONE);
this._updateDesktopMenuVisibility();
gconf.connect('changed::' + DESKTOP_IS_HOME_KEY, Lang.bind(this, this._updateDesktopMenuVisibility));
this._connect = new PlaceInfo(_("Connect to..."),
function (size) {
return Shell.TextureCache.get_default().load_icon_name("applications-internet", size);
},
function () {
new Shell.Process({ args: ['nautilus-connect-server'] }).run();
});
let networkApp = null;
try {
networkApp = Shell.AppSystem.get_default().load_from_desktop_file('gnome-network-scheme.desktop');
} catch(e) {
try {
networkApp = Shell.AppSystem.get_default().load_from_desktop_file('network-scheme.desktop');
} catch(e) {
log("Cannot create \"Network\" item, .desktop file not found or corrupt.");
}
}
if (networkApp != null) {
this._network = new PlaceInfo(networkApp.get_name(),
function(size) {
return networkApp.create_icon_texture(size);
},
function () {
networkApp.launch();
});
}
/*
* Show devices, code more or less ported from nautilus-places-sidebar.c
@ -153,37 +126,6 @@ Places.prototype = {
this._volumeMonitor.connect('drive-changed', Lang.bind(this, this._updateDevices));
this._updateDevices();
let networkApp = null;
try {
networkApp = Shell.AppSystem.get_default().load_from_desktop_file('gnome-network-scheme.desktop');
} catch(e) {
try {
networkApp = Shell.AppSystem.get_default().load_from_desktop_file('network-scheme.desktop');
} catch(e) {
log("Cannot create \"Network\" item, .desktop file not found or corrupt.");
}
}
if (networkApp != null) {
let network = new PlaceDisplay(networkApp.get_name(),
function() {
return networkApp.create_icon_texture(PLACES_ICON_SIZE);
},
function () {
networkApp.launch();
});
this._actionsBox.append(network.actor, Big.BoxPackFlags.NONE);
}
let connect = new PlaceDisplay(_("Connect to..."),
function () {
return Shell.TextureCache.get_default().load_icon_name("applications-internet", PLACES_ICON_SIZE);
},
function () {
new Shell.Process({ args: ['nautilus-connect-server'] }).run();
});
this._actionsBox.append(connect.actor, Big.BoxPackFlags.NONE);
this._bookmarksPath = GLib.build_filenamev([GLib.get_home_dir(), ".gtk-bookmarks"]);
this._bookmarksFile = Gio.file_new_for_path(this._bookmarksPath);
let monitor = this._bookmarksFile.monitor_file(Gio.FileMonitorFlags.NONE, null);
@ -200,14 +142,68 @@ Places.prototype = {
}));
this._reloadBookmarks();
this._updateDesktopMenuVisibility();
gconf.connect('changed::' + DESKTOP_IS_HOME_KEY, Lang.bind(this, this._updateDesktopMenuVisibility));
},
_updateDevices: function() {
this._mounts = [];
/* first go through all connected drives */
let drives = this._volumeMonitor.get_connected_drives();
for (let i = 0; i < drives.length; i++) {
let volumes = drives[i].get_volumes();
for(let j = 0; j < volumes.length; j++) {
let mount = volumes[j].get_mount();
if(mount != null) {
this._addMount(mount);
}
}
}
/* add all volumes that is not associated with a drive */
let volumes = this._volumeMonitor.get_volumes();
for(let i = 0; i < volumes.length; i++) {
if(volumes[i].get_drive() != null)
continue;
let mount = volumes[i].get_mount();
if(mount != null) {
this._addMount(mount);
}
}
/* add mounts that have no volume (/etc/mtab mounts, ftp, sftp,...) */
let mounts = this._volumeMonitor.get_mounts();
for(let i = 0; i < mounts.length; i++) {
if(mounts[i].is_shadowed())
continue;
if(mounts[i].get_volume())
continue;
this._addMount(mounts[i]);
}
/* We emit two signals, one for a generic 'all places' update
* and the other for one specific to mounts. We do this because
* clients like PlaceDisplay may only care about places in general
* being updated while clients like DashPlaceDisplay care which
* specific type of place got updated.
*/
this.emit('mounts-updated');
this.emit('places-updated');
},
_reloadBookmarks: function() {
this._dirsBox.remove_all();
this._bookmarks = [];
if (!GLib.file_test(this._bookmarksPath, GLib.FileTest.EXISTS))
return;
return;
let [success, bookmarksContent, len] = GLib.file_get_contents(this._bookmarksPath);
@ -243,55 +239,28 @@ Places.prototype = {
continue;
let icon = Shell.util_get_icon_for_uri(bookmark);
let item = new PlaceDisplay(label,
function() {
return Shell.TextureCache.get_default().load_gicon(icon, PLACES_ICON_SIZE);
let item = new PlaceInfo(label,
function(size) {
return Shell.TextureCache.get_default().load_gicon(icon, size);
},
function() {
Gio.app_info_launch_default_for_uri(bookmark, Main.createAppLaunchContext());
});
this._dirsBox.append(item.actor, Big.BoxPackFlags.NONE);
this._bookmarks.push(item);
}
/* See comment in _updateDevices for explanation why there are two signals. */
this.emit('bookmarks-updated');
this.emit('places-updated');
},
_updateDevices: function() {
this._devBox.remove_all();
_updateDesktopMenuVisibility: function() {
let gconf = Shell.GConf.get_default();
this._isDesktopHome = gconf.get_boolean(DESKTOP_IS_HOME_KEY);
/* first go through all connected drives */
let drives = this._volumeMonitor.get_connected_drives();
for (let i = 0; i < drives.length; i++) {
let volumes = drives[i].get_volumes();
for(let j = 0; j < volumes.length; j++) {
let mount = volumes[j].get_mount();
if(mount != null) {
this._addMount(mount);
}
}
}
/* add all volumes that is not associated with a drive */
let volumes = this._volumeMonitor.get_volumes();
for(let i = 0; i < volumes.length; i++) {
if(volumes[i].get_drive() != null)
continue;
let mount = volumes[i].get_mount();
if(mount != null) {
this._addMount(mount);
}
}
/* add mounts that have no volume (/etc/mtab mounts, ftp, sftp,...) */
let mounts = this._volumeMonitor.get_mounts();
for(let i = 0; i < mounts.length; i++) {
if(mounts[i].is_shadowed())
continue;
if(mounts[i].get_volume())
continue;
this._addMount(mounts[i]);
}
/* See comment in _updateDevices for explanation why there are two signals. */
this.emit('defaults-updated');
this.emit('places-updated');
},
_addMount: function(mount) {
@ -299,23 +268,282 @@ Places.prototype = {
let mountIcon = mount.get_icon();
let root = mount.get_root();
let mountUri = root.get_uri();
let devItem = new PlaceDisplay(mountLabel,
function() {
return Shell.TextureCache.get_default().load_gicon(mountIcon, PLACES_ICON_SIZE);
let devItem = new PlaceInfo(mountLabel,
function(size) {
return Shell.TextureCache.get_default().load_gicon(mountIcon, size);
},
function() {
Gio.app_info_launch_default_for_uri(mountUri, Main.createAppLaunchContext());
});
this._devBox.append(devItem.actor, Big.BoxPackFlags.NONE);
this._mounts.push(devItem);
},
_updateDesktopMenuVisibility: function() {
let gconf = Shell.GConf.get_default();
let desktopIsHome = gconf.get_boolean(DESKTOP_IS_HOME_KEY);
if (desktopIsHome)
this._desktopMenu.actor.hide();
else
this._desktopMenu.actor.show();
getAllPlaces: function () {
return this.getDefaultPlaces().concat(this.getBookmarks(), this.getMounts());
},
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;
},
getBookmarks: function () {
return this._bookmarks;
},
getMounts: function () {
return this._mounts;
}
};
Signals.addSignalMethods(PlacesManager.prototype);
/**
* An entry in the places menu.
* @info The corresponding PlaceInfo to populate this entry.
*/
function DashPlaceDisplayItem(info) {
this._init(info);
}
DashPlaceDisplayItem.prototype = {
_init: function(info) {
this.name = info.name;
this._info = info;
this._icon = info.iconFactory(PLACES_ICON_SIZE);
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
reactive: true,
spacing: 4 });
this.actor.connect('button-release-event', Lang.bind(this, function (b, e) {
this._info.launch();
Main.overview.hide();
}));
let text = new Clutter.Text({ font_name: 'Sans 14px',
ellipsize: Pango.EllipsizeMode.END,
color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
text: this.name });
let iconBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
iconBox.append(this._icon, Big.BoxPackFlags.NONE);
this.actor.append(iconBox, Big.BoxPackFlags.NONE);
this.actor.append(text, Big.BoxPackFlags.EXPAND);
this.actor._delegate = this;
let draggable = DND.makeDraggable(this.actor);
},
getDragActorSource: function() {
return this._icon;
},
getDragActor: function(stageX, stageY) {
return this._info.iconFactory(PLACES_ICON_SIZE);
},
//// Drag and drop methods ////
shellWorkspaceLaunch: function() {
this._info.launch();
}
};
function DashPlaceDisplay() {
this._init();
}
DashPlaceDisplay.prototype = {
_init: function() {
// Places is divided semi-arbitrarily into left and right; a grid would
// look better in that there would be an even number of items left+right,
// but it seems like we want some sort of differentiation between actions
// like "Connect to server..." and regular folders
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
spacing: 4 });
this._leftBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
this.actor.append(this._leftBox, Big.BoxPackFlags.EXPAND);
this._rightBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
this.actor.append(this._rightBox, Big.BoxPackFlags.EXPAND);
// Subdivide left into actions and devices
this._actionsBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
spacing: PLACES_VSPACING });
this._devBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
spacing: PLACES_VSPACING,
padding_top: 6 });
this._dirsBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
spacing: PLACES_VSPACING });
this._leftBox.append(this._actionsBox, Big.BoxPackFlags.NONE);
this._leftBox.append(this._devBox, Big.BoxPackFlags.NONE);
this._rightBox.append(this._dirsBox, Big.BoxPackFlags.NONE);
Main.placesManager.connect('defaults-updated', Lang.bind(this, this._updateDefaults));
Main.placesManager.connect('bookmarks-updated', Lang.bind(this, this._updateBookmarks));
Main.placesManager.connect('mounts-updated', Lang.bind(this, this._updateMounts));
this._updateDefaults();
this._updateMounts();
this._updateBookmarks();
},
_updateDefaults: function() {
this._actionsBox.get_children().forEach(function (child) {
child.destroy();
});
let places = Main.placesManager.getDefaultPlaces();
for (let i = 0; i < places.length; i++)
this._actionsBox.append(new DashPlaceDisplayItem(places[i]).actor, Big.BoxPackFlags.NONE);
},
_updateMounts: function() {
this._devBox.get_children().forEach(function (child) {
child.destroy();
});
let places = Main.placesManager.getMounts();
for (let i = 0; i < places.length; i++)
this._devBox.append(new DashPlaceDisplayItem(places[i]).actor, Big.BoxPackFlags.NONE);
},
_updateBookmarks: function() {
this._dirsBox.get_children().forEach(function (child) {
child.destroy();
});
let places = Main.placesManager.getBookmarks();
for (let i = 0; i < places.length; i ++)
this._dirsBox.append(new DashPlaceDisplayItem(places[i]).actor, Big.BoxPackFlags.NONE);
}
};
Signals.addSignalMethods(DashPlaceDisplay.prototype);
function PlaceDisplayItem(placeInfo) {
this._init(placeInfo);
}
PlaceDisplayItem.prototype = {
__proto__: GenericDisplay.GenericDisplayItem.prototype,
_init : function(placeInfo) {
GenericDisplay.GenericDisplayItem.prototype._init.call(this);
this._info = placeInfo;
this._setItemInfo(placeInfo.name, '');
},
//// Public method overrides ////
// Opens an application represented by this display item.
launch : function() {
this._info.launch();
},
shellWorkspaceLaunch: function() {
this._info.launch();
},
//// Protected method overrides ////
// Returns an icon for the item.
_createIcon: function() {
return this._info.iconFactory(GenericDisplay.ITEM_DISPLAY_ICON_SIZE);
},
// Returns a preview icon for the item.
_createPreviewIcon: function() {
return this._info.iconFactory(GenericDisplay.PREVIEW_ICON_SIZE);
}
};
function PlaceDisplay() {
this._init();
}
PlaceDisplay.prototype = {
__proto__: GenericDisplay.GenericDisplay.prototype,
_init: function() {
GenericDisplay.GenericDisplay.prototype._init.call(this);
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);
}
};
Signals.addSignalMethods(Places.prototype);