Merge branch 'master' into message-tray
Conflicts: data/theme/gnome-shell.css
This commit is contained in:
@ -36,8 +36,8 @@ DocInfo.prototype = {
|
||||
// shorter in terms of lines of code, we are not doing so
|
||||
// because that would duplicate the work of retrieving the
|
||||
// mime type.
|
||||
|
||||
let appInfo = Gio.app_info_get_default_for_type(this.mimeType, true);
|
||||
let needsUri = Gio.file_new_for_uri(this.uri).get_path() == null;
|
||||
let appInfo = Gio.app_info_get_default_for_type(this.mimeType, needsUri);
|
||||
|
||||
if (appInfo != null) {
|
||||
appInfo.launch_uris([this.uri], Main.createAppLaunchContext());
|
||||
|
@ -20,10 +20,11 @@ dist_jsui_DATA = \
|
||||
messageTray.js \
|
||||
overview.js \
|
||||
panel.js \
|
||||
places.js \
|
||||
placeDisplay.js \
|
||||
runDialog.js \
|
||||
shellDBus.js \
|
||||
sidebar.js \
|
||||
statusMenu.js \
|
||||
tweener.js \
|
||||
widget.js \
|
||||
widgetBox.js \
|
||||
|
@ -724,7 +724,19 @@ ThumbnailList.prototype = {
|
||||
_init : function(windows) {
|
||||
SwitcherList.prototype._init.call(this);
|
||||
|
||||
let activeWorkspace = global.screen.get_active_workspace();
|
||||
|
||||
// We fake the value of "separatorAdded" when the app has no window
|
||||
// on the current workspace, to avoid displaying a useless separator in
|
||||
// that case.
|
||||
let separatorAdded = windows.length == 0 || windows[0].get_workspace() != activeWorkspace;
|
||||
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
if (!separatorAdded && windows[i].get_workspace() != activeWorkspace) {
|
||||
this.addSeparator();
|
||||
separatorAdded = true;
|
||||
}
|
||||
|
||||
let mutterWindow = windows[i].get_compositor_private();
|
||||
let windowTexture = mutterWindow.get_texture ();
|
||||
let [width, height] = windowTexture.get_size();
|
||||
@ -739,11 +751,14 @@ ThumbnailList.prototype = {
|
||||
height: height * scale });
|
||||
box.add_actor(clone);
|
||||
|
||||
let name = new St.Label({ text: windows[i].get_title() });
|
||||
// St.Label doesn't support text-align so use a Bin
|
||||
let bin = new St.Bin({ x_align: St.Align.MIDDLE });
|
||||
bin.add_actor(name);
|
||||
box.add_actor(bin);
|
||||
let title = windows[i].get_title();
|
||||
if (title) {
|
||||
let name = new St.Label({ text: title });
|
||||
// St.Label doesn't support text-align so use a Bin
|
||||
let bin = new St.Bin({ x_align: St.Align.MIDDLE });
|
||||
bin.add_actor(name);
|
||||
box.add_actor(bin);
|
||||
}
|
||||
|
||||
this.addItem(box);
|
||||
}
|
||||
|
@ -166,15 +166,15 @@ Signals.addSignalMethods(MenuItem.prototype);
|
||||
* showPrefs - a boolean indicating if this AppDisplay should contain preference
|
||||
* applets, rather than applications
|
||||
*/
|
||||
function AppDisplay(showPrefs) {
|
||||
this._init(showPrefs);
|
||||
function AppDisplay(showPrefs, flags) {
|
||||
this._init(showPrefs, flags);
|
||||
}
|
||||
|
||||
AppDisplay.prototype = {
|
||||
__proto__: GenericDisplay.GenericDisplay.prototype,
|
||||
|
||||
_init : function(showPrefs) {
|
||||
GenericDisplay.GenericDisplay.prototype._init.call(this);
|
||||
_init : function(showPrefs, flags) {
|
||||
GenericDisplay.GenericDisplay.prototype._init.call(this, flags);
|
||||
|
||||
this._showPrefs = showPrefs;
|
||||
|
||||
@ -190,156 +190,10 @@ AppDisplay.prototype = {
|
||||
this._appsStale = true;
|
||||
this._redisplay(GenericDisplay.RedisplayFlags.NONE);
|
||||
}));
|
||||
|
||||
this._focusInMenus = true;
|
||||
this._activeMenuIndex = -1;
|
||||
this._activeMenu = null;
|
||||
this._activeMenuApps = null;
|
||||
this._menuDisplay = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
||||
spacing: MENU_SPACING
|
||||
});
|
||||
|
||||
this.connect('expanded', Lang.bind(this, function (self) {
|
||||
this._filterReset();
|
||||
}));
|
||||
this._filterReset();
|
||||
},
|
||||
|
||||
moveRight: function() {
|
||||
if (this._expanded && this._focusInMenu) {
|
||||
this._focusInMenu = false;
|
||||
this._activeMenu.setState(MENU_ENTERED);
|
||||
this.selectFirstItem();
|
||||
}
|
||||
},
|
||||
|
||||
moveLeft: function() {
|
||||
if (this._expanded && !this._focusInMenu) {
|
||||
this._activeMenu.setState(MENU_SELECTED);
|
||||
this.unsetSelected();
|
||||
this._focusInMenu = true;
|
||||
}
|
||||
},
|
||||
|
||||
// Override genericDisplay.js
|
||||
getNavigationArea: function() {
|
||||
return this._menuDisplay;
|
||||
},
|
||||
|
||||
selectUp: function() {
|
||||
if (!(this._expanded && this._focusInMenu))
|
||||
return GenericDisplay.GenericDisplay.prototype.selectUp.call(this);
|
||||
this._selectMenuIndex(this._activeMenuIndex - 1);
|
||||
return true;
|
||||
},
|
||||
|
||||
selectDown: function() {
|
||||
if (!(this._expanded && this._focusInMenu))
|
||||
return GenericDisplay.GenericDisplay.prototype.selectDown.call(this);
|
||||
this._selectMenuIndex(this._activeMenuIndex+1);
|
||||
return true;
|
||||
},
|
||||
|
||||
setSearch: function(text) {
|
||||
let lowertext = text.toLowerCase();
|
||||
if (lowertext == this._search)
|
||||
return;
|
||||
|
||||
// We prepare menu matches up-front, so that we don't
|
||||
// need to go over all menu items for each application
|
||||
// and then get all applications for a matching menu
|
||||
// to see if a particular application passed to
|
||||
// _isInfoMatching() is a match.
|
||||
let terms = lowertext.split(/\s+/);
|
||||
this._menuSearchAppMatches = {};
|
||||
for (let i = 0; i < terms.length; i++) {
|
||||
let term = terms[i];
|
||||
this._menuSearchAppMatches[term] = {};
|
||||
for (let j = 0; j < this._menus.length; j++) {
|
||||
let menuItem = this._menus[j];
|
||||
// Match only on the beginning of the words in category names,
|
||||
// because otherwise it introduces unnecessary noise in the results.
|
||||
if (menuItem.name.toLowerCase().indexOf(term) == 0 ||
|
||||
menuItem.name.toLowerCase().indexOf(" " + term) > 0) {
|
||||
let menuApps = this._appSystem.get_applications_for_menu(menuItem.id);
|
||||
for (let k = 0; k < menuApps.length; k++) {
|
||||
let menuApp = menuApps[k];
|
||||
this._menuSearchAppMatches[term][menuApp.get_id()] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GenericDisplay.GenericDisplay.prototype.setSearch.call(this, text);
|
||||
},
|
||||
|
||||
// Protected overrides
|
||||
|
||||
_filterActive: function() {
|
||||
// We always have a filter now since a menu must be selected
|
||||
return true;
|
||||
},
|
||||
|
||||
_filterReset: function() {
|
||||
GenericDisplay.GenericDisplay.prototype._filterReset.call(this);
|
||||
this._selectMenuIndex(0);
|
||||
},
|
||||
|
||||
//// Private ////
|
||||
|
||||
_emitStateChange: function() {
|
||||
this.emit('state-changed');
|
||||
},
|
||||
|
||||
_selectMenuIndex: function(index) {
|
||||
if (index < 0 || index >= this._menus.length)
|
||||
return;
|
||||
this._menuDisplays[index].setState(MENU_SELECTED);
|
||||
},
|
||||
|
||||
_getMostUsed: function() {
|
||||
let context = "";
|
||||
let usage = Shell.AppUsage.get_default();
|
||||
return usage.get_most_used(context, 30);
|
||||
},
|
||||
|
||||
_addMenuItem: function(name, id, index) {
|
||||
let display = new MenuItem(name, id);
|
||||
this._menuDisplays.push(display);
|
||||
display.connect('state-changed', Lang.bind(this, function (display) {
|
||||
let activated = display.getState() != MENU_UNSELECTED;
|
||||
if (!activated && display == this._activeMenu) {
|
||||
this._activeMenuIndex = -1;
|
||||
this._activeMenu = null;
|
||||
} else if (activated) {
|
||||
if (display != this._activeMenu && this._activeMenu != null)
|
||||
this._activeMenu.setState(MENU_UNSELECTED);
|
||||
this._activeMenuIndex = index;
|
||||
this._activeMenu = display;
|
||||
if (id == null) {
|
||||
this._activeMenuApps = this._getMostUsed();
|
||||
} else {
|
||||
this._activeMenuApps = this._appSystem.get_applications_for_menu(id);
|
||||
}
|
||||
}
|
||||
this._redisplay(GenericDisplay.RedisplayFlags.FULL);
|
||||
}));
|
||||
this._menuDisplay.append(display.actor, 0);
|
||||
},
|
||||
|
||||
_redisplayMenus: function() {
|
||||
this._menuDisplay.remove_all();
|
||||
this._addMenuItem(_("Frequent"), null, 'gtk-select-all');
|
||||
// Adding an empty box here results in double spacing between
|
||||
// "Frequent" and the other items.
|
||||
let separator_actor = new Big.Box();
|
||||
this._menuDisplay.append(separator_actor, 0);
|
||||
for (let i = 0; i < this._menus.length; i++) {
|
||||
let menu = this._menus[i];
|
||||
this._addMenuItem(menu.name, menu.id, i+1);
|
||||
}
|
||||
},
|
||||
|
||||
_addApp: function(appInfo) {
|
||||
let appId = appInfo.get_id();
|
||||
this._allItems[appId] = appInfo;
|
||||
@ -363,33 +217,33 @@ AppDisplay.prototype = {
|
||||
}
|
||||
} else {
|
||||
// Loop over the toplevel menu items, load the set of desktop file ids
|
||||
// associated with each one, skipping empty menus
|
||||
// associated with each one.
|
||||
let allMenus = this._appSystem.get_menus();
|
||||
this._menus = [];
|
||||
for (let i = 0; i < allMenus.length; i++) {
|
||||
let menu = allMenus[i];
|
||||
let menuApps = this._appSystem.get_applications_for_menu(menu.id);
|
||||
let hasVisibleApps = menuApps.some(function (app) { return !app.get_is_nodisplay(); });
|
||||
if (!hasVisibleApps) {
|
||||
continue;
|
||||
}
|
||||
this._menus.push(menu);
|
||||
|
||||
for (let j = 0; j < menuApps.length; j++) {
|
||||
let app = menuApps[j];
|
||||
this._addApp(app);
|
||||
}
|
||||
}
|
||||
this._redisplayMenus();
|
||||
}
|
||||
|
||||
this._appsStale = false;
|
||||
return false;
|
||||
},
|
||||
|
||||
// Stub this out; the app display always has a category selected
|
||||
_setDefaultList : function() {
|
||||
this._matchedItems = {};
|
||||
this._matchedItems = this._allItems;
|
||||
this._matchedItemKeys = [];
|
||||
for (let itemId in this._matchedItems) {
|
||||
let app = this._allItems[itemId];
|
||||
if (app.get_is_nodisplay())
|
||||
continue;
|
||||
this._matchedItemKeys.push(itemId);
|
||||
}
|
||||
this._matchedItemKeys.sort(Lang.bind(this, this._compareItems));
|
||||
},
|
||||
|
||||
// Compares items associated with the item ids based on the alphabetical order
|
||||
@ -409,25 +263,7 @@ AppDisplay.prototype = {
|
||||
// Don't show nodisplay items here
|
||||
if (itemInfo.get_is_nodisplay())
|
||||
return false;
|
||||
// Search takes precedence; not typically useful to search within a
|
||||
// menu
|
||||
if (this._activeMenu == null || search != "")
|
||||
return this._isInfoMatchingSearch(itemInfo, search);
|
||||
else
|
||||
return this._isInfoMatchingMenu(itemInfo);
|
||||
},
|
||||
|
||||
_isInfoMatchingMenu: function(itemInfo) {
|
||||
let id = itemInfo.get_id();
|
||||
for (let i = 0; i < this._activeMenuApps.length; i++) {
|
||||
let activeApp = this._activeMenuApps[i];
|
||||
if (activeApp.get_id() == id)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_isInfoMatchingSearch: function(itemInfo, search) {
|
||||
if (search == null || search == '')
|
||||
return true;
|
||||
|
||||
@ -455,13 +291,6 @@ AppDisplay.prototype = {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._menuSearchAppMatches[search]) {
|
||||
if (this._menuSearchAppMatches[search].hasOwnProperty(itemInfo.get_id()))
|
||||
return true;
|
||||
} else {
|
||||
log("Missing an entry for search term " + search + " in this._menuSearchAppMatches");
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
|
@ -8,6 +8,7 @@ const Mainloop = imports.mainloop;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
@ -99,11 +100,9 @@ AppIcon.prototype = {
|
||||
nameBox.connect('allocate', Lang.bind(this, this._nameBoxAllocate));
|
||||
this._nameBox = nameBox;
|
||||
|
||||
this._name = new Clutter.Text({ color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
|
||||
font_name: "Sans 12px",
|
||||
line_alignment: Pango.Alignment.CENTER,
|
||||
ellipsize: Pango.EllipsizeMode.END,
|
||||
text: this.app.get_name() });
|
||||
this._name = new St.Label({ style_class: "app-icon-label",
|
||||
text: this.app.get_name() });
|
||||
this._name.clutter_text.line_alignment = Pango.Alignment.CENTER;
|
||||
nameBox.add_actor(this._name);
|
||||
if (this._showGlow) {
|
||||
this._glowBox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
|
||||
|
347
js/ui/dash.js
347
js/ui/dash.js
@ -8,22 +8,19 @@ const Pango = imports.gi.Pango;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const Lang = imports.lang;
|
||||
const St = imports.gi.St;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
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;
|
||||
|
||||
const DEFAULT_PADDING = 4;
|
||||
const DEFAULT_SPACING = 4;
|
||||
const DASH_SECTION_PADDING = 6;
|
||||
const DASH_SECTION_SPACING = 40;
|
||||
const DASH_CORNER_RADIUS = 5;
|
||||
const DASH_PADDING_SIDE = 14;
|
||||
|
||||
const BACKGROUND_COLOR = new Clutter.Color();
|
||||
BACKGROUND_COLOR.from_pixel(0x000000c0);
|
||||
@ -43,42 +40,22 @@ SEARCH_TEXT_COLOR.from_pixel(0x333333ff);
|
||||
const SEARCH_CURSOR_COLOR = BRIGHT_TEXT_COLOR;
|
||||
const HIGHLIGHTED_SEARCH_CURSOR_COLOR = SEARCH_TEXT_COLOR;
|
||||
|
||||
const HIGHLIGHTED_SEARCH_BACKGROUND_COLOR = new Clutter.Color();
|
||||
HIGHLIGHTED_SEARCH_BACKGROUND_COLOR.from_pixel(0xc4c4c4ff);
|
||||
|
||||
const SEARCH_BORDER_BOTTOM_COLOR = new Clutter.Color();
|
||||
SEARCH_BORDER_BOTTOM_COLOR.from_pixel(0x191919ff);
|
||||
|
||||
const SECTION_BORDER_COLOR = new Clutter.Color();
|
||||
SECTION_BORDER_COLOR.from_pixel(0x262626ff);
|
||||
const SECTION_BORDER = 1;
|
||||
const SECTION_INNER_BORDER_COLOR = new Clutter.Color();
|
||||
SECTION_INNER_BORDER_COLOR.from_pixel(0x000000ff);
|
||||
const SECTION_BACKGROUND_TOP_COLOR = new Clutter.Color();
|
||||
SECTION_BACKGROUND_TOP_COLOR.from_pixel(0x161616ff);
|
||||
const SECTION_BACKGROUND_BOTTOM_COLOR = new Clutter.Color();
|
||||
SECTION_BACKGROUND_BOTTOM_COLOR.from_pixel(0x000000ff);
|
||||
const SECTION_INNER_SPACING = 8;
|
||||
|
||||
const BROWSE_ACTIVATED_BG = new Clutter.Color();
|
||||
BROWSE_ACTIVATED_BG.from_pixel(0x303030f0);
|
||||
|
||||
const PANE_BORDER_COLOR = new Clutter.Color();
|
||||
PANE_BORDER_COLOR.from_pixel(0x101d3cfa);
|
||||
const PANE_BORDER_WIDTH = 2;
|
||||
|
||||
const PANE_BACKGROUND_COLOR = new Clutter.Color();
|
||||
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
|
||||
* if the provided index is incremented by an increment and the array
|
||||
* is wrapped in if necessary.
|
||||
*
|
||||
*
|
||||
* index: prior index, expects 0 <= index < length
|
||||
* increment: the change in index, expects abs(increment) <= length
|
||||
* length: the length of the array
|
||||
@ -87,14 +64,15 @@ function _getIndexWrapped(index, increment, length) {
|
||||
return (index + increment + length) % length;
|
||||
}
|
||||
|
||||
function _createDisplay(displayType) {
|
||||
function _createDisplay(displayType, flags) {
|
||||
if (displayType == APPS)
|
||||
return new AppDisplay.AppDisplay();
|
||||
return new AppDisplay.AppDisplay(false, flags);
|
||||
else if (displayType == PREFS)
|
||||
return new AppDisplay.AppDisplay(true);
|
||||
return new AppDisplay.AppDisplay(true, flags);
|
||||
else if (displayType == DOCS)
|
||||
return new DocDisplay.DocDisplay();
|
||||
|
||||
return new DocDisplay.DocDisplay(flags);
|
||||
else if (displayType == PLACES)
|
||||
return new PlaceDisplay.PlaceDisplay(flags);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -106,36 +84,27 @@ Pane.prototype = {
|
||||
_init: function () {
|
||||
this._open = false;
|
||||
|
||||
this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
||||
background_color: PANE_BACKGROUND_COLOR,
|
||||
border: PANE_BORDER_WIDTH,
|
||||
border_color: PANE_BORDER_COLOR,
|
||||
padding: DEFAULT_PADDING,
|
||||
reactive: true });
|
||||
this.actor = new St.BoxLayout({ style_class: "dash-pane",
|
||||
vertical: true,
|
||||
reactive: true });
|
||||
this.actor.connect('button-press-event', Lang.bind(this, function (a, e) {
|
||||
// Eat button press events so they don't go through and close the pane
|
||||
return true;
|
||||
}));
|
||||
|
||||
let chromeTop = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
spacing: 6 });
|
||||
let chromeTop = new St.BoxLayout();
|
||||
|
||||
let closeIconUri = "file://" + global.imagedir + "close.svg";
|
||||
let closeIcon = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
|
||||
closeIconUri,
|
||||
16,
|
||||
16);
|
||||
closeIcon.reactive = true;
|
||||
closeIcon.connect('button-press-event', Lang.bind(this, function (b, e) {
|
||||
let closeIcon = new St.Button({ style_class: "dash-pane-close" });
|
||||
closeIcon.connect('clicked', Lang.bind(this, function (b, e) {
|
||||
this.close();
|
||||
return true;
|
||||
}));
|
||||
chromeTop.append(closeIcon, Big.BoxPackFlags.END);
|
||||
this.actor.append(chromeTop, Big.BoxPackFlags.NONE);
|
||||
let dummy = new St.Bin();
|
||||
chromeTop.add(dummy, { expand: true });
|
||||
chromeTop.add(closeIcon, { x_align: St.Align.END });
|
||||
this.actor.add(chromeTop);
|
||||
|
||||
this.content = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
||||
spacing: DEFAULT_PADDING });
|
||||
this.actor.append(this.content, Big.BoxPackFlags.EXPAND);
|
||||
this.content = new St.BoxLayout({ vertical: true });
|
||||
this.actor.add(this.content, { expand: true });
|
||||
|
||||
// Hidden by default
|
||||
this.actor.hide();
|
||||
@ -173,32 +142,20 @@ Pane.prototype = {
|
||||
}
|
||||
Signals.addSignalMethods(Pane.prototype);
|
||||
|
||||
function ResultArea(displayType, enableNavigation) {
|
||||
this._init(displayType, enableNavigation);
|
||||
function ResultArea(displayType, flags) {
|
||||
this._init(displayType, flags);
|
||||
}
|
||||
|
||||
ResultArea.prototype = {
|
||||
_init : function(displayType, enableNavigation) {
|
||||
_init : function(displayType, flags) {
|
||||
this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
|
||||
this.resultsContainer = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
spacing: DEFAULT_PADDING
|
||||
});
|
||||
this.actor.append(this.resultsContainer, Big.BoxPackFlags.EXPAND);
|
||||
this.navContainer = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
|
||||
this.resultsContainer.append(this.navContainer, Big.BoxPackFlags.NONE);
|
||||
|
||||
this.display = _createDisplay(displayType);
|
||||
|
||||
this.navArea = this.display.getNavigationArea();
|
||||
if (enableNavigation && this.navArea)
|
||||
this.navContainer.append(this.navArea, Big.BoxPackFlags.EXPAND);
|
||||
|
||||
this.display = _createDisplay(displayType, flags);
|
||||
this.resultsContainer.append(this.display.actor, Big.BoxPackFlags.EXPAND);
|
||||
|
||||
this.controlBox = new Big.Box({ x_align: Big.BoxAlignment.CENTER });
|
||||
this.controlBox.append(this.display.displayControl, Big.BoxPackFlags.NONE);
|
||||
this.actor.append(this.controlBox, Big.BoxPackFlags.NONE);
|
||||
|
||||
this.display.load();
|
||||
}
|
||||
}
|
||||
@ -223,7 +180,7 @@ function createPaneForDetails(dash, display) {
|
||||
if (index >= 0) {
|
||||
detailPane.destroyContent();
|
||||
let details = display.createDetailsForIndex(index);
|
||||
detailPane.content.append(details, Big.BoxPackFlags.EXPAND);
|
||||
detailPane.content.add(details, { expand: true });
|
||||
detailPane.open();
|
||||
} else {
|
||||
detailPane.close();
|
||||
@ -246,12 +203,12 @@ ResultPane.prototype = {
|
||||
|
||||
// Create a display of displayType and pack it into this pane's
|
||||
// content area. Return the display.
|
||||
packResults: function(displayType, enableNavigation) {
|
||||
let resultArea = new ResultArea(displayType, enableNavigation);
|
||||
packResults: function(displayType) {
|
||||
let resultArea = new ResultArea(displayType);
|
||||
|
||||
createPaneForDetails(this._dash, resultArea.display);
|
||||
|
||||
this.content.append(resultArea.actor, Big.BoxPackFlags.EXPAND);
|
||||
this.content.add(resultArea.actor, { expand: true });
|
||||
this.connect('open-state-changed', Lang.bind(this, function(pane, isOpen) {
|
||||
resultArea.display.resetState();
|
||||
}));
|
||||
@ -265,14 +222,11 @@ function SearchEntry() {
|
||||
|
||||
SearchEntry.prototype = {
|
||||
_init : function() {
|
||||
this.actor = new Big.Box({ padding: DEFAULT_PADDING,
|
||||
border_bottom: SECTION_BORDER,
|
||||
border_color: SEARCH_BORDER_BOTTOM_COLOR,
|
||||
corner_radius: DASH_CORNER_RADIUS,
|
||||
reactive: true });
|
||||
this.actor = new St.BoxLayout({ name: "searchEntry",
|
||||
reactive: true });
|
||||
let box = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
y_align: Big.BoxAlignment.CENTER });
|
||||
this.actor.append(box, Big.BoxPackFlags.EXPAND);
|
||||
this.actor.add(box, { expand: true });
|
||||
this.actor.connect('button-press-event', Lang.bind(this, function () {
|
||||
this._resetTextState(true);
|
||||
return false;
|
||||
@ -332,7 +286,7 @@ SearchEntry.prototype = {
|
||||
else
|
||||
this.entry.text = '';
|
||||
|
||||
// Return true to stop the signal emission, so that this.actor doesn't get
|
||||
// Return true to stop the signal emission, so that this.actor doesn't get
|
||||
// the button-press-event and re-highlight itself.
|
||||
return true;
|
||||
}));
|
||||
@ -355,18 +309,18 @@ SearchEntry.prototype = {
|
||||
_resetTextState: function (searchEntryClicked) {
|
||||
let text = this.getText();
|
||||
this._iconBox.remove_all();
|
||||
// We highlight the search box if the user starts typing in it
|
||||
// We highlight the search box if the user starts typing in it
|
||||
// or just clicks in it to indicate that the search is active.
|
||||
if (text != '' || searchEntryClicked) {
|
||||
if (!searchEntryClicked)
|
||||
this._defaultText.hide();
|
||||
this._iconBox.append(this._closeIcon, Big.BoxPackFlags.NONE);
|
||||
this.actor.background_color = HIGHLIGHTED_SEARCH_BACKGROUND_COLOR;
|
||||
this.actor.set_style_pseudo_class('active');
|
||||
this.entry.cursor_color = HIGHLIGHTED_SEARCH_CURSOR_COLOR;
|
||||
} else {
|
||||
this._defaultText.show();
|
||||
this._iconBox.append(this._magnifierIcon, Big.BoxPackFlags.NONE);
|
||||
this.actor.background_color = BACKGROUND_COLOR;
|
||||
this.actor.set_style_pseudo_class(null);
|
||||
this.entry.cursor_color = SEARCH_CURSOR_COLOR;
|
||||
}
|
||||
},
|
||||
@ -385,20 +339,12 @@ function MoreLink() {
|
||||
|
||||
MoreLink.prototype = {
|
||||
_init : function () {
|
||||
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
padding_right: DEFAULT_PADDING,
|
||||
padding_left: DEFAULT_PADDING,
|
||||
reactive: true,
|
||||
x_align: Big.BoxAlignment.CENTER,
|
||||
y_align: Big.BoxAlignment.CENTER,
|
||||
border_left: SECTION_BORDER,
|
||||
border_color: SECTION_BORDER_COLOR });
|
||||
this.actor = new St.BoxLayout({ style_class: "more-link",
|
||||
reactive: true });
|
||||
this.pane = null;
|
||||
|
||||
let text = new Clutter.Text({ font_name: "Sans 12px",
|
||||
color: BRIGHT_TEXT_COLOR,
|
||||
text: _("More") });
|
||||
this.actor.append(text, Big.BoxPackFlags.NONE);
|
||||
let expander = new St.Bin({ style_class: "more-link-expander" });
|
||||
this.actor.add(expander, { expand: true, y_fill: false });
|
||||
|
||||
this.actor.connect('button-press-event', Lang.bind(this, function (b, e) {
|
||||
if (this.pane == null) {
|
||||
@ -425,21 +371,9 @@ function BackLink() {
|
||||
|
||||
BackLink.prototype = {
|
||||
_init : function () {
|
||||
this.actor = new Shell.ButtonBox({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
padding_right: DEFAULT_PADDING,
|
||||
padding_left: DEFAULT_PADDING,
|
||||
reactive: true,
|
||||
x_align: Big.BoxAlignment.CENTER,
|
||||
y_align: Big.BoxAlignment.CENTER,
|
||||
border_right: SECTION_BORDER,
|
||||
border_color: SECTION_BORDER_COLOR });
|
||||
|
||||
let backIconUri = "file://" + global.imagedir + "back.svg";
|
||||
let backIcon = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
|
||||
backIconUri,
|
||||
12,
|
||||
16);
|
||||
this.actor.append(backIcon, Big.BoxPackFlags.NONE);
|
||||
this.actor = new St.Button({ style_class: "section-header-back",
|
||||
reactive: true });
|
||||
this.actor.set_child(new St.Bin({ style_class: "section-header-back-image" }));
|
||||
}
|
||||
}
|
||||
|
||||
@ -449,52 +383,72 @@ function SectionHeader(title, suppressBrowse) {
|
||||
|
||||
SectionHeader.prototype = {
|
||||
_init : function (title, suppressBrowse) {
|
||||
this.actor = new Big.Box({ border: SECTION_BORDER,
|
||||
border_color: SECTION_BORDER_COLOR });
|
||||
this._innerBox = new Big.Box({ border: SECTION_BORDER,
|
||||
border_color: SECTION_INNER_BORDER_COLOR,
|
||||
padding_left: DEFAULT_PADDING,
|
||||
padding_right: DEFAULT_PADDING,
|
||||
orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
spacing: DEFAULT_SPACING });
|
||||
this.actor.append(this._innerBox, Big.BoxPackFlags.EXPAND);
|
||||
let backgroundGradient = Shell.create_vertical_gradient(SECTION_BACKGROUND_TOP_COLOR,
|
||||
SECTION_BACKGROUND_BOTTOM_COLOR);
|
||||
this._innerBox.add_actor(backgroundGradient);
|
||||
this._innerBox.connect('notify::allocation', Lang.bind(this, function (actor) {
|
||||
let [width, height] = actor.get_size();
|
||||
backgroundGradient.set_size(width, height);
|
||||
this.actor = new St.Bin({ style_class: "section-header",
|
||||
x_align: St.Align.START,
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
this._innerBox = new St.BoxLayout({ style_class: "section-header-inner" });
|
||||
this.actor.set_child(this._innerBox);
|
||||
|
||||
this._backgroundGradient = null;
|
||||
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChanged));
|
||||
this.actor.connect('notify::allocation', Lang.bind(this, function (actor) {
|
||||
if (!this._backgroundGradient)
|
||||
return;
|
||||
this._onStyleChanged();
|
||||
}));
|
||||
|
||||
this.backLink = new BackLink();
|
||||
this._innerBox.append(this.backLink.actor, Big.BoxPackFlags.NONE);
|
||||
this._innerBox.add(this.backLink.actor);
|
||||
this.backLink.actor.hide();
|
||||
|
||||
this.backLink.actor.connect('activate', Lang.bind(this, function (actor) {
|
||||
this.emit('back-link-activated');
|
||||
this.backLink.actor.connect('clicked', Lang.bind(this, function (actor) {
|
||||
this.emit('back-link-activated');
|
||||
}));
|
||||
|
||||
let textBox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
padding_top: DEFAULT_PADDING,
|
||||
padding_bottom: DEFAULT_PADDING });
|
||||
this.text = new Clutter.Text({ color: TEXT_COLOR,
|
||||
font_name: "Sans Bold 12px",
|
||||
text: title });
|
||||
textBox.append(this.text, Big.BoxPackFlags.NONE);
|
||||
let textBox = new St.BoxLayout({ style_class: "section-text-content" });
|
||||
this.text = new St.Label({ style_class: "section-title",
|
||||
text: title });
|
||||
textBox.add(this.text, { x_align: St.Align.START });
|
||||
|
||||
this.countText = new Clutter.Text({ color: TEXT_COLOR,
|
||||
font_name: 'Sans Bold 14px' });
|
||||
textBox.append(this.countText, Big.BoxPackFlags.END);
|
||||
this.countText = new St.Label({ style_class: "section-count" });
|
||||
textBox.add(this.countText, { expand: true, x_fill: false, x_align: St.Align.END });
|
||||
this.countText.hide();
|
||||
|
||||
this._innerBox.append(textBox, Big.BoxPackFlags.EXPAND);
|
||||
this._innerBox.add(textBox, { expand: true });
|
||||
|
||||
if (!suppressBrowse) {
|
||||
this.moreLink = new MoreLink();
|
||||
this._innerBox.append(this.moreLink.actor, Big.BoxPackFlags.END);
|
||||
this._innerBox.add(this.moreLink.actor, { x_align: St.Align.END });
|
||||
}
|
||||
},
|
||||
|
||||
_onStyleChanged: function () {
|
||||
if (this._backgroundGradient) {
|
||||
this._backgroundGradient.destroy();
|
||||
}
|
||||
// Manually implement the gradient
|
||||
let themeNode = this.actor.get_theme_node();
|
||||
let gradientTopColor = new Clutter.Color();
|
||||
if (!themeNode.get_color("-shell-gradient-top", false, gradientTopColor))
|
||||
return;
|
||||
let gradientBottomColor = new Clutter.Color();
|
||||
if (!themeNode.get_color("-shell-gradient-bottom", false, gradientBottomColor))
|
||||
return;
|
||||
this._backgroundGradient = Shell.create_vertical_gradient(gradientTopColor,
|
||||
gradientBottomColor);
|
||||
let box = this.actor.allocation;
|
||||
let contentBox = new Clutter.ActorBox();
|
||||
themeNode.get_content_box(box, contentBox);
|
||||
let width = contentBox.x2 - contentBox.x1;
|
||||
let height = contentBox.y2 - contentBox.y1;
|
||||
this._backgroundGradient.set_size(width, height);
|
||||
// This will set a fixed position, which puts us outside of the normal box layout
|
||||
this._backgroundGradient.set_position(0, 0);
|
||||
|
||||
this._innerBox.add_actor(this._backgroundGradient);
|
||||
this._backgroundGradient.lower_bottom();
|
||||
},
|
||||
|
||||
setTitle : function(title) {
|
||||
this.text.text = title;
|
||||
},
|
||||
@ -531,41 +485,19 @@ function SearchSectionHeader(title, onClick) {
|
||||
|
||||
SearchSectionHeader.prototype = {
|
||||
_init : function(title, onClick) {
|
||||
let box = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
padding_top: DASH_SECTION_PADDING,
|
||||
padding_bottom: DASH_SECTION_PADDING,
|
||||
spacing: DEFAULT_SPACING });
|
||||
let titleText = new Clutter.Text({ color: BRIGHTER_TEXT_COLOR,
|
||||
font_name: 'Sans Bold 12px',
|
||||
text: title });
|
||||
this.tooltip = new Clutter.Text({ color: BRIGHTER_TEXT_COLOR,
|
||||
font_name: 'Sans 12px',
|
||||
text: _("(see all)") });
|
||||
this.countText = new Clutter.Text({ color: BRIGHTER_TEXT_COLOR,
|
||||
font_name: 'Sans Bold 14px' });
|
||||
this.actor = new St.Button({ style_class: "dash-search-section-header",
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
let box = new St.BoxLayout();
|
||||
this.actor.set_child(box);
|
||||
let titleText = new St.Label({ style_class: "dash-search-section-title",
|
||||
text: title });
|
||||
this.countText = new St.Label({ style_class: "dash-search-section-count" });
|
||||
|
||||
box.append(titleText, Big.BoxPackFlags.NONE);
|
||||
box.append(this.tooltip, Big.BoxPackFlags.NONE);
|
||||
box.append(this.countText, Big.BoxPackFlags.END);
|
||||
box.add(titleText);
|
||||
box.add(this.countText, { expand: true, x_fill: false, x_align: St.Align.END });
|
||||
|
||||
this.tooltip.hide();
|
||||
|
||||
let button = new Button.Button(box, PRELIGHT_COLOR, BACKGROUND_COLOR,
|
||||
TEXT_COLOR);
|
||||
button.actor.height = box.height;
|
||||
button.actor.padding_left = DEFAULT_PADDING;
|
||||
button.actor.padding_right = DEFAULT_PADDING;
|
||||
|
||||
button.actor.connect('activate', onClick);
|
||||
button.actor.connect('notify::hover', Lang.bind(this, this._updateTooltip));
|
||||
this.actor = button.actor;
|
||||
},
|
||||
|
||||
_updateTooltip : function(actor) {
|
||||
if (actor.hover)
|
||||
this.tooltip.show();
|
||||
else
|
||||
this.tooltip.hide();
|
||||
this.actor.connect('clicked', onClick);
|
||||
}
|
||||
}
|
||||
|
||||
@ -575,11 +507,13 @@ function Section(titleString, suppressBrowse) {
|
||||
|
||||
Section.prototype = {
|
||||
_init: function(titleString, suppressBrowse) {
|
||||
this.actor = new Big.Box({ spacing: SECTION_INNER_SPACING });
|
||||
this.actor = new St.BoxLayout({ style_class: 'dash-section',
|
||||
vertical: true });
|
||||
this.header = new SectionHeader(titleString, suppressBrowse);
|
||||
this.actor.append(this.header.actor, Big.BoxPackFlags.NONE);
|
||||
this.content = new Big.Box({spacing: SECTION_INNER_SPACING });
|
||||
this.actor.append(this.content, Big.BoxPackFlags.EXPAND);
|
||||
this.actor.add(this.header.actor);
|
||||
this.content = new St.BoxLayout({ style_class: 'dash-section-content',
|
||||
vertical: true });
|
||||
this.actor.add(this.content);
|
||||
}
|
||||
}
|
||||
|
||||
@ -598,21 +532,18 @@ Dash.prototype = {
|
||||
// of the Group actor ends up including the width of its hidden children, so we were getting a reactive object as
|
||||
// wide as the details pane that was blocking the clicks to the workspaces underneath it even when the details pane
|
||||
// was actually hidden.
|
||||
this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
||||
background_color: BACKGROUND_COLOR,
|
||||
corner_radius: DASH_CORNER_RADIUS,
|
||||
padding_left: DASH_PADDING_SIDE,
|
||||
padding_right: DASH_PADDING_SIDE,
|
||||
reactive: true });
|
||||
this.actor = new St.BoxLayout({ name: "dash",
|
||||
vertical: true,
|
||||
reactive: true });
|
||||
|
||||
// Size for this one explicitly set from overlay.js
|
||||
this.searchArea = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
|
||||
|
||||
this.sectionArea = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
||||
spacing: DASH_SECTION_SPACING });
|
||||
this.sectionArea = new St.BoxLayout({ name: "dashSections",
|
||||
vertical: true });
|
||||
|
||||
this.actor.append(this.searchArea, Big.BoxPackFlags.NONE);
|
||||
this.actor.append(this.sectionArea, Big.BoxPackFlags.NONE);
|
||||
this.actor.add(this.searchArea);
|
||||
this.actor.add(this.sectionArea);
|
||||
|
||||
// The currently active popup display
|
||||
this._activePane = null;
|
||||
@ -724,41 +655,41 @@ Dash.prototype = {
|
||||
|
||||
this._appsSection = new Section(_("APPLICATIONS"));
|
||||
let appWell = new AppDisplay.AppWell();
|
||||
this._appsSection.content.append(appWell.actor, Big.BoxPackFlags.EXPAND);
|
||||
this._appsSection.content.add(appWell.actor, { expand: true });
|
||||
|
||||
this._moreAppsPane = null;
|
||||
this._appsSection.header.moreLink.connect('activated', Lang.bind(this, function (link) {
|
||||
if (this._moreAppsPane == null) {
|
||||
this._moreAppsPane = new ResultPane(this);
|
||||
this._moreAppsPane.packResults(APPS, true);
|
||||
this._moreAppsPane.packResults(APPS);
|
||||
this._addPane(this._moreAppsPane);
|
||||
link.setPane(this._moreAppsPane);
|
||||
}
|
||||
}));
|
||||
|
||||
this.sectionArea.append(this._appsSection.actor, Big.BoxPackFlags.NONE);
|
||||
this.sectionArea.add(this._appsSection.actor);
|
||||
|
||||
/***** Places *****/
|
||||
|
||||
/* Translators: This is in the sense of locations for documents,
|
||||
network locations, etc. */
|
||||
this._placesSection = new Section(_("PLACES"), true);
|
||||
let placesDisplay = new Places.Places();
|
||||
this._placesSection.content.append(placesDisplay.actor, Big.BoxPackFlags.EXPAND);
|
||||
this.sectionArea.append(this._placesSection.actor, Big.BoxPackFlags.NONE);
|
||||
let placesDisplay = new PlaceDisplay.DashPlaceDisplay();
|
||||
this._placesSection.content.add(placesDisplay.actor, { expand: true });
|
||||
this.sectionArea.add(this._placesSection.actor);
|
||||
|
||||
/***** Documents *****/
|
||||
|
||||
this._docsSection = new Section(_("RECENT DOCUMENTS"));
|
||||
|
||||
this._docDisplay = new DocDisplay.DashDocDisplay();
|
||||
this._docsSection.content.append(this._docDisplay.actor, Big.BoxPackFlags.EXPAND);
|
||||
this._docsSection.content.add(this._docDisplay.actor, { expand: true });
|
||||
|
||||
this._moreDocsPane = null;
|
||||
this._docsSection.header.moreLink.connect('activated', Lang.bind(this, function (link) {
|
||||
if (this._moreDocsPane == null) {
|
||||
this._moreDocsPane = new ResultPane(this);
|
||||
this._moreDocsPane.packResults(DOCS, true);
|
||||
this._moreDocsPane.packResults(DOCS);
|
||||
this._addPane(this._moreDocsPane);
|
||||
link.setPane(this._moreDocsPane);
|
||||
}
|
||||
@ -770,7 +701,7 @@ Dash.prototype = {
|
||||
}));
|
||||
this._docDisplay.emit('changed');
|
||||
|
||||
this.sectionArea.append(this._docsSection.actor, Big.BoxPackFlags.EXPAND);
|
||||
this.sectionArea.add(this._docsSection.actor, { expand: true });
|
||||
|
||||
/***** Search Results *****/
|
||||
|
||||
@ -797,6 +728,11 @@ Dash.prototype = {
|
||||
title: _("RECENT DOCUMENTS"),
|
||||
header: null,
|
||||
resultArea: null
|
||||
},
|
||||
{ type: PLACES,
|
||||
title: _("PLACES"),
|
||||
header: null,
|
||||
resultArea: null
|
||||
}
|
||||
];
|
||||
|
||||
@ -807,14 +743,13 @@ Dash.prototype = {
|
||||
function () {
|
||||
this._showSingleSearchSection(section.type);
|
||||
}));
|
||||
this._searchResultsSection.content.append(section.header.actor, Big.BoxPackFlags.NONE);
|
||||
section.resultArea = new ResultArea(section.type, false);
|
||||
section.resultArea.controlBox.hide();
|
||||
this._searchResultsSection.content.append(section.resultArea.actor, Big.BoxPackFlags.EXPAND);
|
||||
this._searchResultsSection.content.add(section.header.actor);
|
||||
section.resultArea = new ResultArea(section.type, GenericDisplay.GenericDisplayFlags.DISABLE_VSCROLLING);
|
||||
this._searchResultsSection.content.add(section.resultArea.actor, { expand: true });
|
||||
createPaneForDetails(this, section.resultArea.display);
|
||||
}
|
||||
|
||||
this.sectionArea.append(this._searchResultsSection.actor, Big.BoxPackFlags.EXPAND);
|
||||
this.sectionArea.add(this._searchResultsSection.actor, { expand: true });
|
||||
this._searchResultsSection.actor.hide();
|
||||
},
|
||||
|
||||
@ -859,6 +794,11 @@ Dash.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
// Here work around a bug that I never quite tracked down
|
||||
// the root cause of; it appeared that the search results
|
||||
// section was getting a 0 height allocation.
|
||||
this._searchResultsSection.content.queue_relayout();
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
@ -926,7 +866,6 @@ Dash.prototype = {
|
||||
if (section.type == type) {
|
||||
// This will be the only section shown.
|
||||
section.resultArea.display.selectFirstItem();
|
||||
section.resultArea.controlBox.show();
|
||||
let itemCount = section.resultArea.display.getMatchedItemsCount();
|
||||
let itemCountText = itemCount + "";
|
||||
section.header.actor.hide();
|
||||
@ -950,8 +889,6 @@ Dash.prototype = {
|
||||
let section = this._searchSections[i];
|
||||
if (section.type == this._searchResultsSingleShownSection) {
|
||||
// This will no longer be the only section shown.
|
||||
section.resultArea.display.displayPage(0);
|
||||
section.resultArea.controlBox.hide();
|
||||
let itemCount = section.resultArea.display.getMatchedItemsCount();
|
||||
if (itemCount != 0) {
|
||||
section.header.actor.show();
|
||||
|
@ -8,6 +8,7 @@ const Lang = imports.lang;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
const Mainloop = imports.mainloop;
|
||||
|
||||
const DocInfo = imports.misc.docInfo;
|
||||
@ -111,19 +112,19 @@ DocDisplayItem.prototype = {
|
||||
/* This class represents a display containing a collection of document items.
|
||||
* The documents are sorted by how recently they were last visited.
|
||||
*/
|
||||
function DocDisplay() {
|
||||
this._init();
|
||||
function DocDisplay(flags) {
|
||||
this._init(flags);
|
||||
}
|
||||
|
||||
DocDisplay.prototype = {
|
||||
__proto__: GenericDisplay.GenericDisplay.prototype,
|
||||
|
||||
_init : function() {
|
||||
GenericDisplay.GenericDisplay.prototype._init.call(this);
|
||||
_init : function(flags) {
|
||||
GenericDisplay.GenericDisplay.prototype._init.call(this, flags);
|
||||
// We keep a single timeout callback for updating last visited times
|
||||
// for all the items in the display. This avoids creating individual
|
||||
// callbacks for each item in the display. So proper time updates
|
||||
// for individual items and item details depend on the item being
|
||||
// for individual items and item details depend on the item being
|
||||
// associated with one of the displays.
|
||||
this._updateTimeoutTargetTime = -1;
|
||||
this._updateTimeoutId = 0;
|
||||
@ -278,10 +279,8 @@ DashDocDisplayItem.prototype = {
|
||||
let iconBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
|
||||
iconBox.append(this._icon, Big.BoxPackFlags.NONE);
|
||||
this.actor.append(iconBox, Big.BoxPackFlags.NONE);
|
||||
let name = new Clutter.Text({ font_name: "Sans 14px",
|
||||
color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
|
||||
ellipsize: Pango.EllipsizeMode.END,
|
||||
text: docInfo.name });
|
||||
let name = new St.Label({ style_class: "dash-recent-docs-item",
|
||||
text: docInfo.name });
|
||||
this.actor.append(name, Big.BoxPackFlags.EXPAND);
|
||||
|
||||
let draggable = DND.makeDraggable(this.actor);
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const St = imports.gi.St;
|
||||
const Gettext_gtk20 = imports.gettext.domain('gtk20');
|
||||
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
@ -33,4 +34,9 @@ _patchContainerClass(St.Table);
|
||||
function init() {
|
||||
Tweener.init();
|
||||
String.prototype.format = Format.format;
|
||||
|
||||
// Set the default direction for St widgets (this needs to be done before any use of St)
|
||||
if (Gettext_gtk20.gettext("default:LTR") == "default:RTL") {
|
||||
St.Widget.set_default_direction(St.TextDirection.RTL);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ const Meta = imports.gi.Meta;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Signals = imports.signals;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Button = imports.ui.button;
|
||||
const DND = imports.ui.dnd;
|
||||
@ -18,18 +19,12 @@ const Link = imports.ui.link;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const RedisplayFlags = { NONE: 0,
|
||||
RESET_CONTROLS: 1 << 0,
|
||||
FULL: 1 << 1,
|
||||
SUBSEARCH: 1 << 2 };
|
||||
SUBSEARCH: 1 << 2,
|
||||
IMMEDIATE: 1 << 3 };
|
||||
|
||||
const ITEM_DISPLAY_NAME_COLOR = new Clutter.Color();
|
||||
ITEM_DISPLAY_NAME_COLOR.from_pixel(0xffffffff);
|
||||
const ITEM_DISPLAY_DESCRIPTION_COLOR = new Clutter.Color();
|
||||
ITEM_DISPLAY_DESCRIPTION_COLOR.from_pixel(0xffffffbb);
|
||||
const ITEM_DISPLAY_BACKGROUND_COLOR = new Clutter.Color();
|
||||
ITEM_DISPLAY_BACKGROUND_COLOR.from_pixel(0x00000000);
|
||||
const ITEM_DISPLAY_SELECTED_BACKGROUND_COLOR = new Clutter.Color();
|
||||
ITEM_DISPLAY_SELECTED_BACKGROUND_COLOR.from_pixel(0x4f6fadaa);
|
||||
const DISPLAY_CONTROL_SELECTED_COLOR = new Clutter.Color();
|
||||
DISPLAY_CONTROL_SELECTED_COLOR.from_pixel(0x112288ff);
|
||||
const PREVIEW_BOX_BACKGROUND_COLOR = new Clutter.Color();
|
||||
@ -37,10 +32,7 @@ PREVIEW_BOX_BACKGROUND_COLOR.from_pixel(0xADADADf0);
|
||||
|
||||
const DEFAULT_PADDING = 4;
|
||||
|
||||
const ITEM_DISPLAY_HEIGHT = 50;
|
||||
const ITEM_DISPLAY_ICON_SIZE = 48;
|
||||
const ITEM_DISPLAY_PADDING = 1;
|
||||
const ITEM_DISPLAY_PADDING_RIGHT = 2;
|
||||
const DEFAULT_COLUMN_GAP = 6;
|
||||
|
||||
const PREVIEW_ICON_SIZE = 96;
|
||||
@ -63,12 +55,8 @@ function GenericDisplayItem() {
|
||||
|
||||
GenericDisplayItem.prototype = {
|
||||
_init: function() {
|
||||
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
spacing: ITEM_DISPLAY_PADDING,
|
||||
reactive: true,
|
||||
background_color: ITEM_DISPLAY_BACKGROUND_COLOR,
|
||||
corner_radius: 4,
|
||||
height: ITEM_DISPLAY_HEIGHT });
|
||||
this.actor = new St.BoxLayout({ style_class: "generic-display-item",
|
||||
reactive: true });
|
||||
|
||||
this.actor._delegate = this;
|
||||
this.actor.connect('button-release-event',
|
||||
@ -82,16 +70,12 @@ GenericDisplayItem.prototype = {
|
||||
let draggable = DND.makeDraggable(this.actor);
|
||||
draggable.connect('drag-begin', Lang.bind(this, this._onDragBegin));
|
||||
|
||||
this._infoContent = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
spacing: DEFAULT_PADDING });
|
||||
this.actor.append(this._infoContent, Big.BoxPackFlags.EXPAND);
|
||||
this._iconBin = new St.Bin();
|
||||
this.actor.add(this._iconBin);
|
||||
|
||||
this._iconBox = new Big.Box();
|
||||
this._infoContent.append(this._iconBox, Big.BoxPackFlags.NONE);
|
||||
|
||||
this._infoText = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
||||
spacing: DEFAULT_PADDING });
|
||||
this._infoContent.append(this._infoText, Big.BoxPackFlags.EXPAND);
|
||||
this._infoText = new St.BoxLayout({ style_class: "generic-display-item-text",
|
||||
vertical: true });
|
||||
this.actor.add(this._infoText, { expand: true, y_fill: false });
|
||||
|
||||
let infoIconUri = "file://" + global.imagedir + "info.svg";
|
||||
let infoIcon = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
|
||||
@ -104,7 +88,7 @@ GenericDisplayItem.prototype = {
|
||||
padding_left: DEFAULT_PADDING, padding_right: DEFAULT_PADDING,
|
||||
y_align: Big.BoxAlignment.CENTER });
|
||||
buttonBox.append(this._informationButton.actor, Big.BoxPackFlags.NONE);
|
||||
this.actor.append(buttonBox, Big.BoxPackFlags.END);
|
||||
this.actor.add(buttonBox, { x_fill: false, x_align: St.Align.END });
|
||||
|
||||
// Connecting to the button-press-event for the information button ensures that the actor,
|
||||
// which is a draggable actor, does not get the button-press-event and doesn't initiate
|
||||
@ -160,16 +144,8 @@ GenericDisplayItem.prototype = {
|
||||
// Highlights the item by setting a different background color than the default
|
||||
// if isSelected is true, removes the highlighting otherwise.
|
||||
markSelected: function(isSelected) {
|
||||
let color;
|
||||
if (isSelected) {
|
||||
color = ITEM_DISPLAY_SELECTED_BACKGROUND_COLOR;
|
||||
this._informationButton.forceShow(true);
|
||||
}
|
||||
else {
|
||||
color = ITEM_DISPLAY_BACKGROUND_COLOR;
|
||||
this._informationButton.forceShow(false);
|
||||
}
|
||||
this.actor.background_color = color;
|
||||
this.actor.set_style_pseudo_class(isSelected ? "selected" : null);
|
||||
this._informationButton.forceShow(isSelected)
|
||||
},
|
||||
|
||||
/*
|
||||
@ -185,19 +161,14 @@ GenericDisplayItem.prototype = {
|
||||
spacing: PREVIEW_BOX_SPACING });
|
||||
|
||||
// Inner box with name and description
|
||||
let textDetails = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
||||
spacing: PREVIEW_BOX_SPACING });
|
||||
let detailsName = new Clutter.Text({ color: ITEM_DISPLAY_NAME_COLOR,
|
||||
font_name: "Sans bold 14px",
|
||||
line_wrap: true,
|
||||
text: this._name.text });
|
||||
textDetails.append(detailsName, Big.BoxPackFlags.NONE);
|
||||
let textDetails = new St.BoxLayout({ style_class: 'generic-display-details',
|
||||
vertical: true });
|
||||
let detailsName = new St.Label({ style_class: 'generic-display-details-name',
|
||||
text: this._name.text });
|
||||
textDetails.add(detailsName);
|
||||
|
||||
let detailsDescription = new Clutter.Text({ color: ITEM_DISPLAY_NAME_COLOR,
|
||||
font_name: "Sans 14px",
|
||||
line_wrap: true,
|
||||
text: this._description.text });
|
||||
textDetails.append(detailsDescription, Big.BoxPackFlags.NONE);
|
||||
let detailsDescription = new St.Label({ text: this._description.text });
|
||||
textDetails.add(detailsDescription);
|
||||
|
||||
this._detailsDescriptions.push(detailsDescription);
|
||||
|
||||
@ -223,7 +194,7 @@ GenericDisplayItem.prototype = {
|
||||
|
||||
// Destroys the item.
|
||||
destroy: function() {
|
||||
this.actor.destroy();
|
||||
this.actor.destroy();
|
||||
},
|
||||
|
||||
//// Pure virtual public methods ////
|
||||
@ -260,20 +231,15 @@ GenericDisplayItem.prototype = {
|
||||
}
|
||||
|
||||
this._icon = this._createIcon();
|
||||
this._iconBox.append(this._icon, Big.BoxPackFlags.NONE);
|
||||
this._iconBin.set_child(this._icon);
|
||||
|
||||
this._name = new Clutter.Text({ color: ITEM_DISPLAY_NAME_COLOR,
|
||||
font_name: "Sans 14px",
|
||||
ellipsize: Pango.EllipsizeMode.END,
|
||||
text: nameText });
|
||||
this._infoText.append(this._name, Big.BoxPackFlags.EXPAND);
|
||||
this._name = new St.Label({ style_class: "generic-display-item-name",
|
||||
text: nameText });
|
||||
this._infoText.add(this._name);
|
||||
|
||||
this._description = new Clutter.Text({ color: ITEM_DISPLAY_DESCRIPTION_COLOR,
|
||||
font_name: "Sans 12px",
|
||||
ellipsize: Pango.EllipsizeMode.END,
|
||||
text: descriptionText ? descriptionText : ""
|
||||
});
|
||||
this._infoText.append(this._description, Big.BoxPackFlags.EXPAND);
|
||||
this._description = new St.Label({ style_class: "generic-display-item-description",
|
||||
text: descriptionText ? descriptionText : "" });
|
||||
this._infoText.add(this._description);
|
||||
},
|
||||
|
||||
// Sets the description text for the item, including the description text
|
||||
@ -319,31 +285,36 @@ GenericDisplayItem.prototype = {
|
||||
|
||||
Signals.addSignalMethods(GenericDisplayItem.prototype);
|
||||
|
||||
const GenericDisplayFlags = {
|
||||
DISABLE_VSCROLLING: 1 << 0
|
||||
}
|
||||
|
||||
/* This is a virtual class that represents a display containing a collection of items
|
||||
* that can be filtered with a search string.
|
||||
*/
|
||||
function GenericDisplay() {
|
||||
this._init();
|
||||
function GenericDisplay(flags) {
|
||||
this._init(flags);
|
||||
}
|
||||
|
||||
GenericDisplay.prototype = {
|
||||
_init : function() {
|
||||
_init : function(flags) {
|
||||
let disableVScrolling = (flags & GenericDisplayFlags.DISABLE_VSCROLLING) != 0;
|
||||
this._search = '';
|
||||
this._expanded = false;
|
||||
|
||||
this._maxItemsPerPage = null;
|
||||
this._list = new Shell.OverflowList({ spacing: 6.0,
|
||||
item_height: ITEM_DISPLAY_HEIGHT });
|
||||
|
||||
this._list.connect('notify::n-pages', Lang.bind(this, function () {
|
||||
this._updateDisplayControl(true);
|
||||
}));
|
||||
this._list.connect('notify::page', Lang.bind(this, function () {
|
||||
this._updateDisplayControl(false);
|
||||
}));
|
||||
if (disableVScrolling) {
|
||||
this.actor = this._list = new Shell.OverflowList({ spacing: 6,
|
||||
item_height: 50 });
|
||||
} else {
|
||||
this.actor = new St.ScrollView({ x_fill: true, y_fill: true });
|
||||
this.actor.get_hscroll_bar().hide();
|
||||
this._list = new St.BoxLayout({ style_class: 'generic-display-container',
|
||||
vertical: true });
|
||||
this.actor.add_actor(this._list);
|
||||
}
|
||||
|
||||
this._pendingRedisplay = RedisplayFlags.NONE;
|
||||
this._list.connect('notify::mapped', Lang.bind(this, this._onMappedNotify));
|
||||
this.actor.connect('notify::mapped', Lang.bind(this, this._onMappedNotify));
|
||||
|
||||
// map<itemId, Object> where Object represents the item info
|
||||
this._allItems = {};
|
||||
@ -355,13 +326,6 @@ GenericDisplay.prototype = {
|
||||
this._displayedItems = {};
|
||||
this._openDetailIndex = -1;
|
||||
this._selectedIndex = -1;
|
||||
// These two are public - .actor is the normal "actor subclass" property,
|
||||
// but we also expose a .displayControl actor which is separate.
|
||||
// See also getNavigationArea.
|
||||
this.actor = this._list;
|
||||
this.displayControl = new Big.Box({ background_color: ITEM_DISPLAY_BACKGROUND_COLOR,
|
||||
spacing: 12,
|
||||
orientation: Big.BoxOrientation.HORIZONTAL});
|
||||
},
|
||||
|
||||
//// Public methods ////
|
||||
@ -369,16 +333,18 @@ GenericDisplay.prototype = {
|
||||
// Sets the search string and displays the matching items.
|
||||
setSearch: function(text) {
|
||||
let lowertext = text.toLowerCase();
|
||||
if (lowertext == this._search)
|
||||
if (lowertext == this._search) {
|
||||
return;
|
||||
let flags = RedisplayFlags.RESET_CONTROLS;
|
||||
}
|
||||
let flags = RedisplayFlags.IMMEDIATE;
|
||||
if (this._search != '') {
|
||||
// Because we combine search terms with OR, we have to be sure that no new term
|
||||
// was introduced before deciding that the new search results will be a subset of
|
||||
// the existing search results.
|
||||
if (lowertext.indexOf(this._search) == 0 &&
|
||||
lowertext.split(/\s+/).length == this._search.split(/\s+/).length)
|
||||
lowertext.split(/\s+/).length == this._search.split(/\s+/).length) {
|
||||
flags |= RedisplayFlags.SUBSEARCH;
|
||||
}
|
||||
}
|
||||
this._search = lowertext;
|
||||
this._redisplay(flags);
|
||||
@ -398,7 +364,7 @@ GenericDisplay.prototype = {
|
||||
// to the bottom one. Returns true if the selection actually moved up, false if it wrapped
|
||||
// around to the bottom.
|
||||
selectUp: function() {
|
||||
let count = this._list.displayedCount;
|
||||
let count = this._getVisibleCount();
|
||||
let selectedUp = true;
|
||||
let prev = this._selectedIndex - 1;
|
||||
if (this._selectedIndex <= 0) {
|
||||
@ -413,7 +379,7 @@ GenericDisplay.prototype = {
|
||||
// to the top one. Returns true if the selection actually moved down, false if it wrapped
|
||||
// around to the top.
|
||||
selectDown: function() {
|
||||
let count = this._list.displayedCount;
|
||||
let count = this._getVisibleCount();
|
||||
let selectedDown = true;
|
||||
let next = this._selectedIndex + 1;
|
||||
if (this._selectedIndex == count - 1) {
|
||||
@ -432,7 +398,7 @@ GenericDisplay.prototype = {
|
||||
|
||||
// Selects the last item among the displayed items.
|
||||
selectLastItem: function() {
|
||||
let count = this._list.displayedCount;
|
||||
let count = this._getVisibleCount();
|
||||
if (this.hasItems())
|
||||
this._selectIndex(count - 1);
|
||||
},
|
||||
@ -469,6 +435,8 @@ GenericDisplay.prototype = {
|
||||
resetState: function() {
|
||||
this._filterReset();
|
||||
this._openDetailIndex = -1;
|
||||
if (!(this.actor instanceof Shell.OverflowList))
|
||||
this.actor.get_vscroll_bar().get_adjustment().value = 0;
|
||||
},
|
||||
|
||||
// Returns an actor which acts as a sidebar; this is used for
|
||||
@ -482,15 +450,6 @@ GenericDisplay.prototype = {
|
||||
return item.createDetailsActor();
|
||||
},
|
||||
|
||||
// Displays the page specified by the pageNumber argument.
|
||||
displayPage: function(pageNumber) {
|
||||
// Cleanup from the previous selection, but don't unset this._selectedIndex
|
||||
if (this.hasSelected()) {
|
||||
this._findDisplayedByIndex(this._selectedIndex).markSelected(false);
|
||||
}
|
||||
this._list.page = pageNumber;
|
||||
},
|
||||
|
||||
//// Protected methods ////
|
||||
|
||||
_recreateDisplayItems: function() {
|
||||
@ -516,14 +475,14 @@ GenericDisplay.prototype = {
|
||||
Lang.bind(this,
|
||||
function() {
|
||||
// update the selection
|
||||
this._selectIndex(this._list.get_actor_index(displayItem.actor));
|
||||
this._selectIndex(this._list.get_children().indexOf(displayItem.actor));
|
||||
this.activateSelected();
|
||||
}));
|
||||
|
||||
displayItem.connect('show-details',
|
||||
Lang.bind(this,
|
||||
function() {
|
||||
let index = this._list.get_actor_index(displayItem.actor);
|
||||
let index = this._list.get_children().indexOf(displayItem.actor);
|
||||
/* Close the details pane if already open */
|
||||
if (index == this._openDetailIndex) {
|
||||
this._openDetailIndex = -1;
|
||||
@ -538,9 +497,10 @@ GenericDisplay.prototype = {
|
||||
|
||||
// Removes an item identifed by the itemId from the displayed items.
|
||||
_removeDisplayItem: function(itemId) {
|
||||
let count = this._list.displayedCount;
|
||||
let children = this._list.get_children();
|
||||
let count = children.length;
|
||||
let displayItem = this._displayedItems[itemId];
|
||||
let displayItemIndex = this._list.get_actor_index(displayItem.actor);
|
||||
let displayItemIndex = children.indexOf(displayItem.actor);
|
||||
|
||||
if (this.hasSelected() && count == 1) {
|
||||
this.unsetSelected();
|
||||
@ -635,24 +595,22 @@ GenericDisplay.prototype = {
|
||||
/*
|
||||
* Updates the displayed items, applying the search string if one exists.
|
||||
* @flags: Flags controlling redisplay behavior as follows:
|
||||
* RESET_CONTROLS - indicates if the page selection should be reset when displaying the matching results.
|
||||
* We reset the page selection when the change in results was initiated by the user by
|
||||
* entering a different search criteria or by viewing the results list in a different
|
||||
* size mode, but we keep the page selection the same if the results got updated on
|
||||
* their own while the user was browsing through the result pages.
|
||||
* SUBSEARCH - Indicates that the current _search is a superstring of the previous
|
||||
* one, which implies we only need to re-search through previous results.
|
||||
* FULL - Indicates that we need recreate all displayed items; implies RESET_CONTROLS as well
|
||||
* FULL - Indicates that we need recreate all displayed items.
|
||||
* IMMEDIATE - Do the full redisplay even if we're not mapped. This is useful
|
||||
* if you want to get the number of matched items and show/hide a section based on
|
||||
* that number.
|
||||
*/
|
||||
_redisplay: function(flags) {
|
||||
if (!this._list.mapped) {
|
||||
let immediate = (flags & RedisplayFlags.IMMEDIATE) != 0;
|
||||
if (!immediate && !this.actor.mapped) {
|
||||
this._pendingRedisplay |= flags;
|
||||
return;
|
||||
}
|
||||
|
||||
let isSubSearch = (flags & RedisplayFlags.SUBSEARCH) > 0;
|
||||
let fullReload = (flags & RedisplayFlags.FULL) > 0;
|
||||
let resetPage = (flags & RedisplayFlags.RESET_CONTROLS) > 0 || fullReload;
|
||||
let isSubSearch = (flags & RedisplayFlags.SUBSEARCH) != 0;
|
||||
let fullReload = (flags & RedisplayFlags.FULL) != 0;
|
||||
|
||||
let hadSelected = this.hasSelected();
|
||||
this.unsetSelected();
|
||||
@ -674,9 +632,6 @@ GenericDisplay.prototype = {
|
||||
this._redisplayReordering();
|
||||
}
|
||||
|
||||
if (resetPage)
|
||||
this._list.page = 0;
|
||||
|
||||
if (hadSelected) {
|
||||
this._selectedIndex = -1;
|
||||
this.selectFirstItem();
|
||||
@ -776,59 +731,14 @@ GenericDisplay.prototype = {
|
||||
return matchScores;
|
||||
},
|
||||
|
||||
/*
|
||||
* Updates the display control to reflect the matched items set and the page selected.
|
||||
*
|
||||
* resetDisplayControl - indicates if the display control should be re-created because
|
||||
* the results or the space allocated for them changed. If it's false,
|
||||
* the existing display control is used and only the page links are
|
||||
* updated to reflect the current page selection.
|
||||
*/
|
||||
_updateDisplayControl: function(resetDisplayControl) {
|
||||
if (resetDisplayControl) {
|
||||
this.displayControl.remove_all();
|
||||
let nPages = this._list.n_pages;
|
||||
// Don't show the page indicator if there is only one page.
|
||||
if (nPages == 1)
|
||||
return;
|
||||
let pageNumber = this._list.page;
|
||||
for (let i = 0; i < nPages; i++) {
|
||||
let pageControl = new Link.Link({ color: (i == pageNumber) ? DISPLAY_CONTROL_SELECTED_COLOR : ITEM_DISPLAY_DESCRIPTION_COLOR,
|
||||
font_name: "Sans Bold 16px",
|
||||
text: (i+1) + "",
|
||||
reactive: (i == pageNumber) ? false : true});
|
||||
this.displayControl.append(pageControl.actor, Big.BoxPackFlags.NONE);
|
||||
|
||||
// we use pageNumberLocalScope to get the page number right in the callback function
|
||||
let pageNumberLocalScope = i;
|
||||
pageControl.connect('clicked',
|
||||
Lang.bind(this,
|
||||
function(o, event) {
|
||||
this.displayPage(pageNumberLocalScope);
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
let pageControlActors = this.displayControl.get_children();
|
||||
for (let i = 0; i < pageControlActors.length; i++) {
|
||||
let pageControlActor = pageControlActors[i];
|
||||
if (i == this._list.page) {
|
||||
pageControlActor.color = DISPLAY_CONTROL_SELECTED_COLOR;
|
||||
pageControlActor.reactive = false;
|
||||
} else {
|
||||
pageControlActor.color = ITEM_DISPLAY_DESCRIPTION_COLOR;
|
||||
pageControlActor.reactive = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.hasSelected()) {
|
||||
this.selectFirstItem();
|
||||
}
|
||||
},
|
||||
|
||||
// Returns a display item based on its index in the ordering of the
|
||||
// display children.
|
||||
_findDisplayedByIndex: function(index) {
|
||||
let actor = this._list.get_displayed_actor(index);
|
||||
let actor;
|
||||
if (this.actor instanceof Shell.OverflowList)
|
||||
actor = this.actor.get_displayed_actor(index);
|
||||
else
|
||||
actor = this._list.get_children()[index];
|
||||
return this._findDisplayedByActor(actor);
|
||||
},
|
||||
|
||||
@ -862,8 +772,14 @@ GenericDisplay.prototype = {
|
||||
this.emit('selected');
|
||||
},
|
||||
|
||||
_getVisibleCount: function() {
|
||||
if (this.actor instanceof Shell.OverflowList)
|
||||
return this._list.displayed_count;
|
||||
return this._list.get_n_children();
|
||||
},
|
||||
|
||||
_onMappedNotify: function () {
|
||||
let mapped = this._list.mapped;
|
||||
let mapped = this.actor.mapped;
|
||||
if (mapped && this._pendingRedisplay > RedisplayFlags.NONE)
|
||||
this._redisplay(this._pendingRedisplay);
|
||||
|
||||
|
@ -320,6 +320,10 @@ LookingGlass.prototype = {
|
||||
this._savedText = null;
|
||||
this._historyNavIndex = -1;
|
||||
this._history = [];
|
||||
this._borderPaintTarget = null;
|
||||
this._borderPaintId = 0;
|
||||
this._borderDestroyId = 0;
|
||||
|
||||
this._readHistory();
|
||||
|
||||
this._open = false;
|
||||
@ -487,6 +491,18 @@ LookingGlass.prototype = {
|
||||
this._results.push(result);
|
||||
this._resultsArea.append(result.actor, Big.BoxPackFlags.NONE);
|
||||
this._propInspector.setTarget(obj);
|
||||
if (this._borderPaintTarget != null) {
|
||||
this._borderPaintTarget.disconnect(this._borderPaintId);
|
||||
this._borderPaintTarget = null;
|
||||
}
|
||||
if (obj instanceof Clutter.Actor) {
|
||||
this._borderPaintTarget = obj;
|
||||
this._borderPaintId = Shell.add_hook_paint_red_border(obj);
|
||||
this._borderDestroyId = obj.connect('destroy', Lang.bind(this, function () {
|
||||
this._borderDestroyId = 0;
|
||||
this._borderPaintTarget = null;
|
||||
}));
|
||||
}
|
||||
let children = this._resultsArea.get_children();
|
||||
if (children.length > this._maxItems) {
|
||||
this._results.shift();
|
||||
@ -582,6 +598,12 @@ LookingGlass.prototype = {
|
||||
this._open = false;
|
||||
Tweener.removeTweens(this.actor);
|
||||
|
||||
if (this._borderPaintTarget != null) {
|
||||
this._borderPaintTarget.disconnect(this._borderPaintId);
|
||||
this._borderPaintTarget.disconnect(this._borderDestroyId);
|
||||
this._borderPaintTarget = null;
|
||||
}
|
||||
|
||||
Main.popModal(this.actor);
|
||||
|
||||
Tweener.addTween(this.actor, { time: 0.5,
|
||||
|
@ -18,6 +18,7 @@ const MessageTray = imports.ui.messageTray;
|
||||
const Messaging = imports.ui.messaging;
|
||||
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 NotificationDaemon = imports.ui.notificationDaemon;
|
||||
@ -31,6 +32,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;
|
||||
@ -102,6 +104,7 @@ function start() {
|
||||
getRunDialog().open();
|
||||
});
|
||||
|
||||
placesManager = new PlaceDisplay.PlacesManager();
|
||||
overview = new Overview.Overview();
|
||||
chrome = new Chrome.Chrome();
|
||||
panel = new Panel.Panel();
|
||||
|
@ -123,8 +123,7 @@ Overview.prototype = {
|
||||
|
||||
// Container to hold popup pane chrome.
|
||||
this._paneContainer = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
spacing: 6
|
||||
});
|
||||
spacing: 6 });
|
||||
// Note here we explicitly don't set the paneContainer to be reactive yet; that's done
|
||||
// inside the notify::visible handler on panes.
|
||||
this._paneContainer.connect('button-release-event', Lang.bind(this, function(background) {
|
||||
@ -149,11 +148,11 @@ Overview.prototype = {
|
||||
// We divide the screen into an imaginary grid which helps us determine the layout of
|
||||
// different visual components.
|
||||
if (wideScreen) {
|
||||
displayGridColumnWidth = primary.width / COLUMNS_WIDE_SCREEN;
|
||||
displayGridRowHeight = primary.height / ROWS_WIDE_SCREEN;
|
||||
displayGridColumnWidth = Math.floor(primary.width / COLUMNS_WIDE_SCREEN);
|
||||
displayGridRowHeight = Math.floor(primary.height / ROWS_WIDE_SCREEN);
|
||||
} else {
|
||||
displayGridColumnWidth = primary.width / COLUMNS_REGULAR_SCREEN;
|
||||
displayGridRowHeight = primary.height / ROWS_REGULAR_SCREEN;
|
||||
displayGridColumnWidth = Math.floor(primary.width / COLUMNS_REGULAR_SCREEN);
|
||||
displayGridRowHeight = Math.floor(primary.height / ROWS_REGULAR_SCREEN);
|
||||
}
|
||||
},
|
||||
|
||||
@ -175,11 +174,11 @@ Overview.prototype = {
|
||||
- WORKSPACE_GRID_PADDING * 2;
|
||||
// We scale the vertical padding by (primary.height / primary.width)
|
||||
// so that the workspace preserves its aspect ratio.
|
||||
this._workspacesHeight = displayGridRowHeight * workspaceRowsUsed
|
||||
- WORKSPACE_GRID_PADDING * (primary.height / primary.width) * 2;
|
||||
this._workspacesHeight = Math.floor(displayGridRowHeight * workspaceRowsUsed
|
||||
- WORKSPACE_GRID_PADDING * (primary.height / primary.width) * 2);
|
||||
|
||||
this._workspacesX = displayGridColumnWidth + WORKSPACE_GRID_PADDING;
|
||||
this._workspacesY = displayGridRowHeight + WORKSPACE_GRID_PADDING * (primary.height / primary.width);
|
||||
this._workspacesY = Math.floor(displayGridRowHeight + WORKSPACE_GRID_PADDING * (primary.height / primary.width));
|
||||
|
||||
this._dash.actor.set_position(0, contentY);
|
||||
this._dash.actor.set_size(displayGridColumnWidth, contentHeight);
|
||||
@ -197,9 +196,9 @@ Overview.prototype = {
|
||||
this._backOver.set_size(global.screen_width, global.screen_height);
|
||||
|
||||
this._paneContainer.set_position(this._dash.actor.x + this._dash.actor.width + DEFAULT_PADDING,
|
||||
contentY);
|
||||
this._workspacesY);
|
||||
// Dynamic width
|
||||
this._paneContainer.height = contentHeight;
|
||||
this._paneContainer.height = this._workspacesHeight;
|
||||
|
||||
this._transparentBackground.set_position(this._paneContainer.x, this._paneContainer.y);
|
||||
this._transparentBackground.set_size(primary.width - this._paneContainer.x,
|
||||
@ -228,6 +227,7 @@ Overview.prototype = {
|
||||
this._activeDisplayPane.close();
|
||||
return true;
|
||||
}));
|
||||
this._workspaces.actor.opacity = 64;
|
||||
} else if (pane == this._activeDisplayPane) {
|
||||
this._activeDisplayPane = null;
|
||||
if (backgroundEventId != null) {
|
||||
@ -236,6 +236,7 @@ Overview.prototype = {
|
||||
}
|
||||
this._transparentBackground.lower_bottom();
|
||||
this._paneContainer.lower_bottom();
|
||||
this._workspaces.actor.opacity = 255;
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
@ -16,6 +16,7 @@ const _ = Gettext.gettext;
|
||||
const Button = imports.ui.button;
|
||||
const Calendar = imports.ui.calendar;
|
||||
const Main = imports.ui.main;
|
||||
const StatusMenu = imports.ui.statusMenu;
|
||||
|
||||
const PANEL_HEIGHT = 26;
|
||||
const TRAY_HEIGHT = PANEL_HEIGHT - 1;
|
||||
@ -176,6 +177,8 @@ Panel.prototype = {
|
||||
|
||||
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL
|
||||
});
|
||||
this.actor._delegate = this;
|
||||
|
||||
let backgroundGradient = Shell.create_vertical_gradient(BACKGROUND_TOP,
|
||||
BACKGROUND_BOTTOM);
|
||||
this.actor.connect('notify::allocation', Lang.bind(this, function () {
|
||||
@ -371,11 +374,8 @@ Panel.prototype = {
|
||||
this._traymanager.manage_stage(global.stage);
|
||||
|
||||
let statusbox = new Big.Box();
|
||||
let statusmenu = this._statusmenu = new Shell.StatusMenu();
|
||||
statusmenu.get_icon().hide();
|
||||
statusmenu.get_name().fontName = DEFAULT_FONT;
|
||||
statusmenu.get_name().color = PANEL_FOREGROUND_COLOR;
|
||||
statusbox.append(this._statusmenu, Big.BoxPackFlags.NONE);
|
||||
let statusmenu = this._statusmenu = new StatusMenu.StatusMenu();
|
||||
statusbox.append(this._statusmenu.actor, Big.BoxPackFlags.NONE);
|
||||
let statusbutton = new Button.Button(statusbox,
|
||||
PANEL_BUTTON_COLOR,
|
||||
PRESSED_BUTTON_BACKGROUND_COLOR,
|
||||
@ -385,7 +385,7 @@ Panel.prototype = {
|
||||
if (e.get_button() == 1 && e.get_click_count() == 1) {
|
||||
statusmenu.toggle(e);
|
||||
// The statusmenu might not pop up if it couldn't get a pointer grab
|
||||
if (statusmenu.is_active())
|
||||
if (statusmenu.isActive())
|
||||
statusbutton.actor.active = true;
|
||||
return true;
|
||||
} else {
|
||||
|
540
js/ui/placeDisplay.js
Normal file
540
js/ui/placeDisplay.js
Normal file
@ -0,0 +1,540 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Big = imports.gi.Big;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Pango = imports.gi.Pango;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const DND = imports.ui.dnd;
|
||||
const Main = imports.ui.main;
|
||||
const GenericDisplay = imports.ui.genericDisplay;
|
||||
|
||||
const NAUTILUS_PREFS_DIR = '/apps/nautilus/preferences';
|
||||
const DESKTOP_IS_HOME_KEY = NAUTILUS_PREFS_DIR + '/desktop_is_home_dir';
|
||||
|
||||
const PLACES_ICON_SIZE = 16;
|
||||
|
||||
/**
|
||||
* 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 given a size parameter
|
||||
* @launch: A JavaScript callback to launch the entry
|
||||
*/
|
||||
function PlaceInfo(name, iconFactory, launch) {
|
||||
this._init(name, iconFactory, launch);
|
||||
}
|
||||
|
||||
PlaceInfo.prototype = {
|
||||
_init: function(name, iconFactory, launch) {
|
||||
this.name = name;
|
||||
this.iconFactory = iconFactory;
|
||||
this.launch = launch;
|
||||
this.id = null;
|
||||
}
|
||||
}
|
||||
|
||||
function PlacesManager() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
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);
|
||||
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());
|
||||
});
|
||||
|
||||
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 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._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
|
||||
*/
|
||||
this._volumeMonitor = Gio.VolumeMonitor.get();
|
||||
this._volumeMonitor.connect('volume-added', Lang.bind(this, this._updateDevices));
|
||||
this._volumeMonitor.connect('volume-removed',Lang.bind(this, this._updateDevices));
|
||||
this._volumeMonitor.connect('volume-changed', Lang.bind(this, this._updateDevices));
|
||||
this._volumeMonitor.connect('mount-added', Lang.bind(this, this._updateDevices));
|
||||
this._volumeMonitor.connect('mount-removed', Lang.bind(this, this._updateDevices));
|
||||
this._volumeMonitor.connect('mount-changed', Lang.bind(this, this._updateDevices));
|
||||
this._volumeMonitor.connect('drive-connected', Lang.bind(this, this._updateDevices));
|
||||
this._volumeMonitor.connect('drive-disconnected', Lang.bind(this, this._updateDevices));
|
||||
this._volumeMonitor.connect('drive-changed', Lang.bind(this, this._updateDevices));
|
||||
this._updateDevices();
|
||||
|
||||
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);
|
||||
this._bookmarkTimeoutId = 0;
|
||||
monitor.connect('changed', Lang.bind(this, function () {
|
||||
if (this._bookmarkTimeoutId > 0)
|
||||
return;
|
||||
/* Defensive event compression */
|
||||
this._bookmarkTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, function () {
|
||||
this._bookmarkTimeoutId = 0;
|
||||
this._reloadBookmarks();
|
||||
return false;
|
||||
}));
|
||||
}));
|
||||
|
||||
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._bookmarks = [];
|
||||
|
||||
if (!GLib.file_test(this._bookmarksPath, GLib.FileTest.EXISTS))
|
||||
return;
|
||||
|
||||
let [success, bookmarksContent, len] = GLib.file_get_contents(this._bookmarksPath);
|
||||
|
||||
if (!success)
|
||||
return;
|
||||
|
||||
let bookmarks = bookmarksContent.split('\n');
|
||||
|
||||
let bookmarksToLabel = {};
|
||||
let bookmarksOrder = [];
|
||||
for (let i = 0; i < bookmarks.length; i++) {
|
||||
let bookmarkLine = bookmarks[i];
|
||||
let components = bookmarkLine.split(' ');
|
||||
let bookmark = components[0];
|
||||
if (bookmark in bookmarksToLabel)
|
||||
continue;
|
||||
let label = null;
|
||||
if (components.length > 1)
|
||||
label = components.slice(1).join(' ');
|
||||
bookmarksToLabel[bookmark] = label;
|
||||
bookmarksOrder.push(bookmark);
|
||||
}
|
||||
|
||||
for (let i = 0; i < bookmarksOrder.length; i++) {
|
||||
let bookmark = bookmarksOrder[i];
|
||||
let label = bookmarksToLabel[bookmark];
|
||||
let file = Gio.file_new_for_uri(bookmark);
|
||||
if (!file.query_exists(null))
|
||||
continue;
|
||||
if (label == null)
|
||||
label = Shell.util_get_label_for_uri(bookmark);
|
||||
if (label == null)
|
||||
continue;
|
||||
let icon = Shell.util_get_icon_for_uri(bookmark);
|
||||
|
||||
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._bookmarks.push(item);
|
||||
}
|
||||
|
||||
/* See comment in _updateDevices for explanation why there are two signals. */
|
||||
this.emit('bookmarks-updated');
|
||||
this.emit('places-updated');
|
||||
},
|
||||
|
||||
_updateDesktopMenuVisibility: function() {
|
||||
let gconf = Shell.GConf.get_default();
|
||||
this._isDesktopHome = gconf.get_boolean(DESKTOP_IS_HOME_KEY);
|
||||
|
||||
/* See comment in _updateDevices for explanation why there are two signals. */
|
||||
this.emit('defaults-updated');
|
||||
this.emit('places-updated');
|
||||
},
|
||||
|
||||
_addMount: function(mount) {
|
||||
let mountLabel = mount.get_name();
|
||||
let mountIcon = mount.get_icon();
|
||||
let root = mount.get_root();
|
||||
let mountUri = root.get_uri();
|
||||
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._mounts.push(devItem);
|
||||
},
|
||||
|
||||
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 St.Label({ style_class: 'places-item',
|
||||
text: info.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 St.BoxLayout({ style_class: 'places-actions',
|
||||
vertical: true });
|
||||
|
||||
this._devBox = new St.BoxLayout({ style_class: 'places-actions',
|
||||
name: 'placesDevices',
|
||||
vertical: true });
|
||||
|
||||
this._dirsBox = new St.BoxLayout({ style_class: 'places-actions',
|
||||
vertical: true });
|
||||
|
||||
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.destroy_children();
|
||||
|
||||
let places = Main.placesManager.getDefaultPlaces();
|
||||
for (let i = 0; i < places.length; i++)
|
||||
this._actionsBox.add(new DashPlaceDisplayItem(places[i]).actor);
|
||||
},
|
||||
|
||||
_updateMounts: function() {
|
||||
this._devBox.destroy_children();
|
||||
|
||||
let places = Main.placesManager.getMounts();
|
||||
for (let i = 0; i < places.length; i++)
|
||||
this._devBox.add(new DashPlaceDisplayItem(places[i]).actor);
|
||||
},
|
||||
|
||||
_updateBookmarks: function() {
|
||||
this._dirsBox.destroy_children();
|
||||
|
||||
let places = Main.placesManager.getBookmarks();
|
||||
for (let i = 0; i < places.length; i ++)
|
||||
this._dirsBox.add(new DashPlaceDisplayItem(places[i]).actor);
|
||||
}
|
||||
};
|
||||
|
||||
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(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);
|
||||
}
|
||||
};
|
321
js/ui/places.js
321
js/ui/places.js
@ -1,321 +0,0 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Big = imports.gi.Big;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Pango = imports.gi.Pango;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Signals = imports.signals;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const DND = imports.ui.dnd;
|
||||
const Main = imports.ui.main;
|
||||
const GenericDisplay = imports.ui.genericDisplay;
|
||||
|
||||
const NAUTILUS_PREFS_DIR = '/apps/nautilus/preferences';
|
||||
const DESKTOP_IS_HOME_KEY = NAUTILUS_PREFS_DIR + '/desktop_is_home_dir';
|
||||
|
||||
const PLACES_VSPACING = 8;
|
||||
const PLACES_ICON_SIZE = 16;
|
||||
|
||||
/**
|
||||
* An entry in the places menu.
|
||||
* @name: String title
|
||||
* @iconFactory: A JavaScript callback which will create an icon texture
|
||||
* @onActivate: A JavaScript callback to launch the entry
|
||||
*/
|
||||
function PlaceDisplay(name, iconFactory, onActivate) {
|
||||
this._init(name, iconFactory, onActivate);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(PlaceDisplay.prototype);
|
||||
|
||||
function Places() {
|
||||
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);
|
||||
|
||||
let gconf = Shell.GConf.get_default();
|
||||
gconf.watch_directory(NAUTILUS_PREFS_DIR);
|
||||
|
||||
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);
|
||||
},
|
||||
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);
|
||||
},
|
||||
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));
|
||||
|
||||
/*
|
||||
* Show devices, code more or less ported from nautilus-places-sidebar.c
|
||||
*/
|
||||
this._volumeMonitor = Gio.VolumeMonitor.get();
|
||||
this._volumeMonitor.connect('volume-added', Lang.bind(this, this._updateDevices));
|
||||
this._volumeMonitor.connect('volume-removed',Lang.bind(this, this._updateDevices));
|
||||
this._volumeMonitor.connect('volume-changed', Lang.bind(this, this._updateDevices));
|
||||
this._volumeMonitor.connect('mount-added', Lang.bind(this, this._updateDevices));
|
||||
this._volumeMonitor.connect('mount-removed', Lang.bind(this, this._updateDevices));
|
||||
this._volumeMonitor.connect('mount-changed', Lang.bind(this, this._updateDevices));
|
||||
this._volumeMonitor.connect('drive-connected', Lang.bind(this, this._updateDevices));
|
||||
this._volumeMonitor.connect('drive-disconnected', Lang.bind(this, this._updateDevices));
|
||||
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);
|
||||
this._bookmarkTimeoutId = 0;
|
||||
monitor.connect('changed', Lang.bind(this, function () {
|
||||
if (this._bookmarkTimeoutId > 0)
|
||||
return;
|
||||
/* Defensive event compression */
|
||||
this._bookmarkTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, function () {
|
||||
this._bookmarkTimeoutId = 0;
|
||||
this._reloadBookmarks();
|
||||
return false;
|
||||
}));
|
||||
}));
|
||||
|
||||
this._reloadBookmarks();
|
||||
},
|
||||
|
||||
_reloadBookmarks: function() {
|
||||
|
||||
this._dirsBox.remove_all();
|
||||
|
||||
if (!GLib.file_test(this._bookmarksPath, GLib.FileTest.EXISTS))
|
||||
return;
|
||||
|
||||
let [success, bookmarksContent, len] = GLib.file_get_contents(this._bookmarksPath);
|
||||
|
||||
if (!success)
|
||||
return;
|
||||
|
||||
let bookmarks = bookmarksContent.split('\n');
|
||||
|
||||
let bookmarksToLabel = {};
|
||||
let bookmarksOrder = [];
|
||||
for (let i = 0; i < bookmarks.length; i++) {
|
||||
let bookmarkLine = bookmarks[i];
|
||||
let components = bookmarkLine.split(' ');
|
||||
let bookmark = components[0];
|
||||
if (bookmark in bookmarksToLabel)
|
||||
continue;
|
||||
let label = null;
|
||||
if (components.length > 1)
|
||||
label = components.slice(1).join(' ');
|
||||
bookmarksToLabel[bookmark] = label;
|
||||
bookmarksOrder.push(bookmark);
|
||||
}
|
||||
|
||||
for (let i = 0; i < bookmarksOrder.length; i++) {
|
||||
let bookmark = bookmarksOrder[i];
|
||||
let label = bookmarksToLabel[bookmark];
|
||||
let file = Gio.file_new_for_uri(bookmark);
|
||||
if (!file.query_exists(null))
|
||||
continue;
|
||||
if (label == null)
|
||||
label = Shell.util_get_label_for_uri(bookmark);
|
||||
if (label == null)
|
||||
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);
|
||||
},
|
||||
function() {
|
||||
Gio.app_info_launch_default_for_uri(bookmark, Main.createAppLaunchContext());
|
||||
});
|
||||
this._dirsBox.append(item.actor, Big.BoxPackFlags.NONE);
|
||||
}
|
||||
},
|
||||
|
||||
_updateDevices: function() {
|
||||
this._devBox.remove_all();
|
||||
|
||||
/* 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]);
|
||||
}
|
||||
},
|
||||
|
||||
_addMount: function(mount) {
|
||||
let mountLabel = mount.get_name();
|
||||
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);
|
||||
},
|
||||
function() {
|
||||
Gio.app_info_launch_default_for_uri(mountUri, Main.createAppLaunchContext());
|
||||
});
|
||||
this._devBox.append(devItem.actor, Big.BoxPackFlags.NONE);
|
||||
},
|
||||
|
||||
_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();
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(Places.prototype);
|
@ -48,6 +48,9 @@ GnomeShell.prototype = {
|
||||
let success;
|
||||
try {
|
||||
returnValue = JSON.stringify(eval(code));
|
||||
// A hack; DBus doesn't have null/undefined
|
||||
if (returnValue == undefined)
|
||||
returnValue = "";
|
||||
success = true;
|
||||
} catch (e) {
|
||||
returnValue = JSON.stringify(e);
|
||||
|
297
js/ui/statusMenu.js
Normal file
297
js/ui/statusMenu.js
Normal file
@ -0,0 +1,297 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const DBus = imports.dbus;
|
||||
const Gdm = imports.gi.Gdm;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
const Signals = imports.signals;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const Panel = imports.ui.panel;
|
||||
|
||||
// Adapted from gdm/gui/user-switch-applet/applet.c
|
||||
//
|
||||
// Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
|
||||
// Copyright (C) 2008,2009 Red Hat, Inc.
|
||||
|
||||
const SIDEBAR_VISIBLE_KEY = 'sidebar/visible';
|
||||
|
||||
function StatusMenu() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
StatusMenu.prototype = {
|
||||
_init: function() {
|
||||
this._gdm = Gdm.UserManager.ref_default();
|
||||
this._user = this._gdm.get_user(GLib.get_user_name());
|
||||
this._presence = new GnomeSessionPresence();
|
||||
|
||||
this.actor = new St.BoxLayout({ name: 'StatusMenu' });
|
||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||
|
||||
this._iconBox = new St.Bin();
|
||||
this.actor.add(this._iconBox, { y_align: St.Align.MIDDLE });
|
||||
|
||||
let textureCache = Shell.TextureCache.get_default();
|
||||
// FIXME: these icons are all wrong (likewise in createSubMenu)
|
||||
this._availableIcon = textureCache.load_icon_name('gtk-yes', 16);
|
||||
this._busyIcon = textureCache.load_icon_name('gtk-no', 16);
|
||||
this._invisibleIcon = textureCache.load_icon_name('gtk-close', 16);
|
||||
this._idleIcon = textureCache.load_icon_name('gtk-media-pause', 16);
|
||||
|
||||
this._presence.connect('StatusChanged', Lang.bind(this, this._updatePresenceIcon));
|
||||
this._presence.getStatus(Lang.bind(this, this._updatePresenceIcon));
|
||||
|
||||
this._name = new St.Label({ text: this._user.get_real_name() });
|
||||
this.actor.add(this._name, { expand: true, y_align: St.Align.MIDDLE });
|
||||
this._userNameChangedId = this._user.connect('notify::display-name', Lang.bind(this, this._updateUserName));
|
||||
|
||||
this._createSubMenu();
|
||||
this._gdm.connect('users-loaded', Lang.bind(this, this._updateSwitchUser));
|
||||
this._gdm.connect('user-added', Lang.bind(this, this._updateSwitchUser));
|
||||
this._gdm.connect('user-removed', Lang.bind(this, this._updateSwitchUser));
|
||||
},
|
||||
|
||||
_onDestroy: function() {
|
||||
this._user.disconnect(this._userNameChangedId);
|
||||
},
|
||||
|
||||
_updateUserName: function() {
|
||||
this._name.set_text(this._user.get_real_name());
|
||||
},
|
||||
|
||||
_updateSwitchUser: function() {
|
||||
let users = this._gdm.list_users();
|
||||
if (users.length > 1)
|
||||
this._loginScreenItem.show();
|
||||
else
|
||||
this._loginScreenItem.hide();
|
||||
},
|
||||
|
||||
_updatePresenceIcon: function(presence, status) {
|
||||
if (status == GnomeSessionPresenceStatus.AVAILABLE)
|
||||
this._iconBox.child = this._availableIcon;
|
||||
else if (status == GnomeSessionPresenceStatus.BUSY)
|
||||
this._iconBox.child = this._busyIcon;
|
||||
else if (status == GnomeSessionPresenceStatus.INVISIBLE)
|
||||
this._iconBox.child = this._invisibleIcon;
|
||||
else
|
||||
this._iconBox.child = this._idleIcon;
|
||||
},
|
||||
|
||||
// The menu
|
||||
|
||||
_createImageMenuItem: function(label, iconName, forceIcon) {
|
||||
let image = new Gtk.Image();
|
||||
let item = new Gtk.ImageMenuItem({ label: label,
|
||||
image: image,
|
||||
always_show_image: forceIcon == true });
|
||||
item.connect('style-set', Lang.bind(this,
|
||||
function() {
|
||||
image.set_from_icon_name(iconName, Gtk.IconSize.MENU);
|
||||
}));
|
||||
|
||||
return item;
|
||||
},
|
||||
|
||||
_createSubMenu: function() {
|
||||
this._menu = new Gtk.Menu();
|
||||
this._menu.connect('deactivate', Lang.bind(this, function() { this.emit('deactivated'); }));
|
||||
|
||||
let item;
|
||||
|
||||
item = this._createImageMenuItem(_('Available'), 'gtk-yes', true);
|
||||
item.connect('activate', Lang.bind(this, this._setPresenceStatus, GnomeSessionPresenceStatus.AVAILABLE));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
|
||||
item = this._createImageMenuItem(_('Busy'), 'gtk-no', true);
|
||||
item.connect('activate', Lang.bind(this, this._setPresenceStatus, GnomeSessionPresenceStatus.BUSY));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
|
||||
item = this._createImageMenuItem(_('Invisible'), 'gtk-close', true);
|
||||
item.connect('activate', Lang.bind(this, this._setPresenceStatus, GnomeSessionPresenceStatus.INVISIBLE));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
|
||||
item = new Gtk.SeparatorMenuItem();
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
|
||||
item = this._createImageMenuItem(_('Account Information...'), 'user-info');
|
||||
item.connect('activate', Lang.bind(this, this._onAccountInformationActivate));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
|
||||
let gconf = Shell.GConf.get_default();
|
||||
item = new Gtk.CheckMenuItem({ label: _('Sidebar'),
|
||||
active: gconf.get_boolean(SIDEBAR_VISIBLE_KEY) });
|
||||
item.connect('activate', Lang.bind(this,
|
||||
function() {
|
||||
gconf.set_boolean(SIDEBAR_VISIBLE_KEY, this._sidebarItem.active);
|
||||
}));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
this._sidebarItem = item;
|
||||
|
||||
item = this._createImageMenuItem(_('System Preferences...'), 'preferences-desktop');
|
||||
item.connect('activate', Lang.bind(this, this._onPreferencesActivate));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
|
||||
item = new Gtk.SeparatorMenuItem();
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
|
||||
item = this._createImageMenuItem(_('Lock Screen'), 'system-lock-screen');
|
||||
item.connect('activate', Lang.bind(this, this._onLockScreenActivate));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
|
||||
item = this._createImageMenuItem(_('Switch User'), 'system-users');
|
||||
item.connect('activate', Lang.bind(this, this._onLoginScreenActivate));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
this._loginScreenItem = item;
|
||||
|
||||
item = this._createImageMenuItem(_('Log Out...'), 'system-log-out');
|
||||
item.connect('activate', Lang.bind(this, this._onQuitSessionActivate));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
|
||||
item = this._createImageMenuItem(_('Shut Down...'), 'system-shutdown');
|
||||
item.connect('activate', Lang.bind(this, this._onShutDownActivate));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
},
|
||||
|
||||
_setPresenceStatus: function(item, status) {
|
||||
this._presence.setStatus(status);
|
||||
},
|
||||
|
||||
_onAccountInformationActivate: function() {
|
||||
this._spawn(['gnome-about-me']);
|
||||
},
|
||||
|
||||
_onPreferencesActivate: function() {
|
||||
this._spawn(['gnome-control-center']);
|
||||
},
|
||||
|
||||
_onLockScreenActivate: function() {
|
||||
this._spawn(['gnome-screensaver-command', '--lock']);
|
||||
},
|
||||
|
||||
_onLoginScreenActivate: function() {
|
||||
this._gdm.goto_login_session();
|
||||
this._onLockScreenActivate();
|
||||
},
|
||||
|
||||
_onQuitSessionActivate: function() {
|
||||
this._spawn(['gnome-session-save', '--logout-dialog']);
|
||||
},
|
||||
|
||||
_onShutDownActivate: function() {
|
||||
this._spawn(['gnome-session-save', '--shutdown-dialog']);
|
||||
},
|
||||
|
||||
_spawn: function(args) {
|
||||
// FIXME: once Shell.Process gets support for signalling
|
||||
// errors we should pop up an error dialog or something here
|
||||
// on failure
|
||||
let p = new Shell.Process({'args' : args});
|
||||
p.run();
|
||||
},
|
||||
|
||||
// shell_status_menu_toggle:
|
||||
// @event: event causing the toggle
|
||||
//
|
||||
// If the menu is not currently up, pops it up. Otherwise, hides it.
|
||||
// Popping up may fail if another grab is already active; check with
|
||||
// isActive().
|
||||
toggle: function(event) {
|
||||
if (this._menu.visible)
|
||||
this._menu.popdown();
|
||||
else {
|
||||
// We don't want to overgrab a Mutter grab with the grab
|
||||
// that GTK+ uses on menus.
|
||||
if (global.display_is_grabbed())
|
||||
return;
|
||||
|
||||
let [menuWidth, menuHeight] = this._menu.get_size_request ();
|
||||
|
||||
let panel;
|
||||
for (panel = this.actor; panel; panel = panel.get_parent()) {
|
||||
if (panel._delegate instanceof Panel.Panel)
|
||||
break;
|
||||
}
|
||||
|
||||
let [panelX, panelY] = panel.get_transformed_position();
|
||||
let [panelWidth, panelHeight] = panel.get_transformed_size();
|
||||
|
||||
let menuX = Math.round(panelX + panelWidth - menuWidth);
|
||||
let menuY = Math.round(panelY + panelHeight);
|
||||
|
||||
Shell.popup_menu(this._menu, event.get_button(), event.get_time(),
|
||||
menuX, menuY);
|
||||
}
|
||||
},
|
||||
|
||||
// isActive:
|
||||
//
|
||||
// Gets whether the menu is currently popped up
|
||||
//
|
||||
// Return value: %true if the menu is currently popped up
|
||||
isActive: function() {
|
||||
return this._menu.visible;
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(StatusMenu.prototype);
|
||||
|
||||
|
||||
const GnomeSessionPresenceIface = {
|
||||
name: 'org.gnome.SessionManager.Presence',
|
||||
methods: [{ name: 'SetStatus',
|
||||
inSignature: 'u' }],
|
||||
properties: [{ name: 'status',
|
||||
signature: 'u',
|
||||
access: 'readwrite' }],
|
||||
signals: [{ name: 'StatusChanged',
|
||||
inSignature: 'u' }]
|
||||
};
|
||||
|
||||
const GnomeSessionPresenceStatus = {
|
||||
AVAILABLE: 0,
|
||||
INVISIBLE: 1,
|
||||
BUSY: 2,
|
||||
IDLE: 3
|
||||
};
|
||||
|
||||
function GnomeSessionPresence() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
GnomeSessionPresence.prototype = {
|
||||
_init: function() {
|
||||
DBus.session.proxifyObject(this, 'org.gnome.SessionManager', '/org/gnome/SessionManager/Presence', this);
|
||||
this.connect('StatusChanged', Lang.bind(this, function (proxy, status) { this.status = status; }));
|
||||
},
|
||||
|
||||
getStatus: function(callback) {
|
||||
this.GetRemote('status', Lang.bind(this,
|
||||
function(status, ex) {
|
||||
if (!ex)
|
||||
callback(this, status);
|
||||
}));
|
||||
},
|
||||
|
||||
setStatus: function(status) {
|
||||
this.SetStatusRemote(status);
|
||||
}
|
||||
};
|
||||
DBus.proxifyPrototype(GnomeSessionPresence.prototype, GnomeSessionPresenceIface);
|
||||
|
@ -575,6 +575,9 @@ Workspace.prototype = {
|
||||
this._lightbox.destroy();
|
||||
this._lightbox = null;
|
||||
}
|
||||
if (this._frame) {
|
||||
this._frame.set_opacity(showLightbox ? 150 : 255);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user