26a074588b
The "expanded background" that is behind the workspaces etc. in the overview should be removed. It isn't in the designs, it is distracting, and it breaks the overview metaphor. At least temporarily, just make the background of the overview black. Change the pane popup to black as well, but keep a blue border so that it is visually distinguished from the background. Based on a patch by Colin Walters, with fixes from Marina Zhurakhinskaya.
519 lines
21 KiB
JavaScript
519 lines
21 KiB
JavaScript
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
const Big = imports.gi.Big;
|
|
const Clutter = imports.gi.Clutter;
|
|
const Gio = imports.gi.Gio;
|
|
const Gtk = imports.gi.Gtk;
|
|
const Mainloop = imports.mainloop;
|
|
const Shell = imports.gi.Shell;
|
|
const Signals = imports.signals;
|
|
const Lang = imports.lang;
|
|
|
|
const AppDisplay = imports.ui.appDisplay;
|
|
const DocDisplay = imports.ui.docDisplay;
|
|
const Places = imports.ui.places;
|
|
const GenericDisplay = imports.ui.genericDisplay;
|
|
const Button = imports.ui.button;
|
|
const Main = imports.ui.main;
|
|
|
|
const DEFAULT_PADDING = 4;
|
|
const DASH_SECTION_PADDING = 6;
|
|
const DASH_SECTION_SPACING = 12;
|
|
const DASH_CORNER_RADIUS = 5;
|
|
const DASH_SEARCH_BG_COLOR = new Clutter.Color();
|
|
DASH_SEARCH_BG_COLOR.from_pixel(0xffffffff);
|
|
const DASH_SECTION_COLOR = new Clutter.Color();
|
|
DASH_SECTION_COLOR.from_pixel(0x846c3dff);
|
|
const DASH_TEXT_COLOR = new Clutter.Color();
|
|
DASH_TEXT_COLOR.from_pixel(0xffffffff);
|
|
|
|
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);
|
|
|
|
|
|
function Pane() {
|
|
this._init();
|
|
}
|
|
|
|
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.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 global = Shell.Global.get();
|
|
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) {
|
|
this.close();
|
|
return true;
|
|
}));
|
|
chromeTop.append(closeIcon, Big.BoxPackFlags.END);
|
|
this.actor.append(chromeTop, Big.BoxPackFlags.NONE);
|
|
|
|
this.content = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
|
spacing: DEFAULT_PADDING });
|
|
this.actor.append(this.content, Big.BoxPackFlags.EXPAND);
|
|
|
|
// Hidden by default
|
|
this.actor.hide();
|
|
},
|
|
|
|
open: function () {
|
|
if (this._open)
|
|
return;
|
|
this._open = true;
|
|
this.actor.show();
|
|
this.emit('open-state-changed', this._open);
|
|
},
|
|
|
|
close: function () {
|
|
if (!this._open)
|
|
return;
|
|
this._open = false;
|
|
this.actor.hide();
|
|
this.emit('open-state-changed', this._open);
|
|
},
|
|
|
|
destroyContent: function() {
|
|
let children = this.content.get_children();
|
|
for (let i = 0; i < children.length; i++) {
|
|
children[i].destroy();
|
|
}
|
|
},
|
|
|
|
toggle: function () {
|
|
if (this._open)
|
|
this.close();
|
|
else
|
|
this.open();
|
|
}
|
|
}
|
|
Signals.addSignalMethods(Pane.prototype);
|
|
|
|
function ResultArea(displayClass, enableNavigation) {
|
|
this._init(displayClass, enableNavigation);
|
|
}
|
|
|
|
ResultArea.prototype = {
|
|
_init : function(displayClass, enableNavigation) {
|
|
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 = new displayClass();
|
|
|
|
this.navArea = this.display.getNavigationArea();
|
|
if (enableNavigation && this.navArea)
|
|
this.navContainer.append(this.navArea, Big.BoxPackFlags.EXPAND);
|
|
|
|
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.EXPAND);
|
|
|
|
this.display.load();
|
|
}
|
|
}
|
|
|
|
// Utility function shared between ResultPane and the DocDisplay in the main dash.
|
|
// Connects to the detail signal of the display, and on-demand creates a new
|
|
// pane.
|
|
function createPaneForDetails(dash, display, detailsWidth) {
|
|
let detailPane = null;
|
|
display.connect('show-details', Lang.bind(this, function(display, index) {
|
|
if (detailPane == null) {
|
|
detailPane = new Pane();
|
|
detailPane.connect('open-state-changed', Lang.bind(this, function (pane, isOpen) {
|
|
if (!isOpen) {
|
|
/* Ensure we don't keep around large preview textures */
|
|
detailPane.destroyContent();
|
|
}
|
|
}));
|
|
dash._addPane(detailPane);
|
|
}
|
|
|
|
if (index >= 0) {
|
|
detailPane.destroyContent();
|
|
let details = display.createDetailsForIndex(index, detailsWidth, -1);
|
|
detailPane.content.append(details, Big.BoxPackFlags.EXPAND);
|
|
detailPane.open();
|
|
} else {
|
|
detailPane.close();
|
|
}
|
|
}));
|
|
return null;
|
|
}
|
|
|
|
function ResultPane(dash, detailsWidth) {
|
|
this._init(dash, detailsWidth);
|
|
}
|
|
|
|
ResultPane.prototype = {
|
|
__proto__: Pane.prototype,
|
|
|
|
_init: function(dash, detailsWidth) {
|
|
Pane.prototype._init.call(this);
|
|
this._dash = dash;
|
|
this._detailsWidth = detailsWidth;
|
|
},
|
|
|
|
// Create an instance of displayClass and pack it into this pane's
|
|
// content area. Return the displayClass instance.
|
|
packResults: function(displayClass, enableNavigation) {
|
|
let resultArea = new ResultArea(displayClass, enableNavigation);
|
|
|
|
createPaneForDetails(this._dash, resultArea.display, this._detailsWidth);
|
|
|
|
this.content.append(resultArea.actor, Big.BoxPackFlags.EXPAND);
|
|
this.connect('open-state-changed', Lang.bind(this, function(pane, isOpen) {
|
|
resultArea.display.resetState();
|
|
}));
|
|
return resultArea.display;
|
|
}
|
|
}
|
|
|
|
function SearchEntry() {
|
|
this._init();
|
|
}
|
|
|
|
SearchEntry.prototype = {
|
|
_init : function() {
|
|
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
|
y_align: Big.BoxAlignment.CENTER,
|
|
background_color: DASH_SEARCH_BG_COLOR,
|
|
corner_radius: 4,
|
|
spacing: DEFAULT_PADDING,
|
|
padding: DEFAULT_PADDING
|
|
});
|
|
|
|
let icon = new Gio.ThemedIcon({ name: 'gtk-find' });
|
|
let searchIconTexture = Shell.TextureCache.get_default().load_gicon(icon, 16);
|
|
this.actor.append(searchIconTexture, Big.BoxPackFlags.NONE);
|
|
|
|
this.pane = null;
|
|
|
|
// We need to initialize the text for the entry to have the cursor displayed
|
|
// in it. See http://bugzilla.openedhand.com/show_bug.cgi?id=1365
|
|
this.entry = new Clutter.Text({ font_name: "Sans 14px",
|
|
editable: true,
|
|
activatable: true,
|
|
singleLineMode: true,
|
|
text: ""
|
|
});
|
|
this.actor.append(this.entry, Big.BoxPackFlags.EXPAND);
|
|
},
|
|
|
|
setPane: function (pane) {
|
|
this._pane = pane;
|
|
}
|
|
};
|
|
Signals.addSignalMethods(SearchEntry.prototype);
|
|
|
|
function MoreLink() {
|
|
this._init();
|
|
}
|
|
|
|
MoreLink.prototype = {
|
|
_init : function () {
|
|
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
|
padding_left: DEFAULT_PADDING,
|
|
padding_right: DEFAULT_PADDING });
|
|
let global = Shell.Global.get();
|
|
let inactiveUri = "file://" + global.imagedir + "view-more.svg";
|
|
let activeUri = "file://" + global.imagedir + "view-more-activated.svg";
|
|
this._inactiveIcon = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
|
|
inactiveUri, 29, 18);
|
|
this._activeIcon = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
|
|
activeUri, 29, 18);
|
|
this._iconBox = new Big.Box({ reactive: true });
|
|
this._iconBox.append(this._inactiveIcon, Big.BoxPackFlags.NONE);
|
|
this.actor.append(this._iconBox, Big.BoxPackFlags.END);
|
|
|
|
this.pane = null;
|
|
|
|
this._iconBox.connect('button-press-event', Lang.bind(this, function (b, e) {
|
|
if (this.pane == null) {
|
|
// Ensure the pane is created; the activated handler will call setPane
|
|
this.emit('activated');
|
|
}
|
|
this._pane.toggle();
|
|
return true;
|
|
}));
|
|
},
|
|
|
|
setPane: function (pane) {
|
|
this._pane = pane;
|
|
this._pane.connect('open-state-changed', Lang.bind(this, function(pane, isOpen) {
|
|
this._iconBox.remove_all();
|
|
this._iconBox.append(isOpen ? this._activeIcon : this._inactiveIcon,
|
|
Big.BoxPackFlags.NONE);
|
|
}));
|
|
}
|
|
}
|
|
|
|
Signals.addSignalMethods(MoreLink.prototype);
|
|
|
|
function SectionHeader(title) {
|
|
this._init(title);
|
|
}
|
|
|
|
SectionHeader.prototype = {
|
|
_init : function (title) {
|
|
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
|
|
let text = new Clutter.Text({ color: DASH_SECTION_COLOR,
|
|
font_name: "Sans Bold 10px",
|
|
text: title });
|
|
this.moreLink = new MoreLink();
|
|
this.actor.append(text, Big.BoxPackFlags.EXPAND);
|
|
this.actor.append(this.moreLink.actor, Big.BoxPackFlags.END);
|
|
}
|
|
}
|
|
|
|
function Dash(displayGridColumnWidth) {
|
|
this._init(displayGridColumnWidth);
|
|
}
|
|
|
|
Dash.prototype = {
|
|
_init : function(displayGridColumnWidth) {
|
|
this._width = displayGridColumnWidth;
|
|
|
|
this._detailsWidth = displayGridColumnWidth * 2;
|
|
|
|
let global = Shell.Global.get();
|
|
|
|
// dash and the popup panes need to be reactive so that the clicks in unoccupied places on them
|
|
// are not passed to the transparent background underneath them. This background is used for the workspaces area when
|
|
// the additional dash panes are being shown and it handles clicks by closing the additional panes, so that the user
|
|
// can interact with the workspaces. However, this behavior is not desirable when the click is actually over a pane.
|
|
//
|
|
// We have to make the individual panes reactive instead of making the whole dash actor reactive because the width
|
|
// 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.HORIZONTAL,
|
|
width: this._width,
|
|
padding: DEFAULT_PADDING,
|
|
reactive: true });
|
|
|
|
this.dashContainer = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
|
spacing: DASH_SECTION_SPACING });
|
|
this.actor.append(this.dashContainer, Big.BoxPackFlags.EXPAND);
|
|
|
|
// The currently active popup display
|
|
this._activePane = null;
|
|
|
|
/***** Search *****/
|
|
|
|
this._searchPane = null;
|
|
this._searchActive = false;
|
|
this._searchEntry = new SearchEntry();
|
|
this.dashContainer.append(this._searchEntry.actor, Big.BoxPackFlags.NONE);
|
|
|
|
this._searchAreaApps = null;
|
|
this._searchAreaDocs = null;
|
|
|
|
this._searchQueued = false;
|
|
this._searchEntry.entry.connect('text-changed', Lang.bind(this, function (se, prop) {
|
|
this._searchActive = this._searchEntry.text != '';
|
|
if (this._searchQueued)
|
|
return;
|
|
if (this._searchPane == null) {
|
|
this._searchPane = new ResultPane(this, this._detailsWidth);
|
|
this._searchPane.content.append(new Clutter.Text({ color: DASH_SECTION_COLOR,
|
|
font_name: 'Sans Bold 10px',
|
|
text: "APPLICATIONS" }),
|
|
Big.BoxPackFlags.NONE);
|
|
this._searchAreaApps = this._searchPane.packResults(AppDisplay.AppDisplay, false);
|
|
this._searchPane.content.append(new Clutter.Text({ color: DASH_SECTION_COLOR,
|
|
font_name: 'Sans Bold 10px',
|
|
text: "RECENT DOCUMENTS" }),
|
|
Big.BoxPackFlags.NONE);
|
|
this._searchAreaDocs = this._searchPane.packResults(DocDisplay.DocDisplay, false);
|
|
this._addPane(this._searchPane);
|
|
this._searchEntry.setPane(this._searchPane);
|
|
}
|
|
this._searchQueued = true;
|
|
Mainloop.timeout_add(250, Lang.bind(this, function() {
|
|
// Strip leading and trailing whitespace
|
|
let text = this._searchEntry.entry.text.replace(/^\s+/g, "").replace(/\s+$/g, "");
|
|
this._searchQueued = false;
|
|
this._searchAreaApps.setSearch(text);
|
|
this._searchAreaDocs.setSearch(text);
|
|
if (text == '')
|
|
this._searchPane.close();
|
|
else
|
|
this._searchPane.open();
|
|
return false;
|
|
}));
|
|
}));
|
|
this._searchEntry.entry.connect('activate', Lang.bind(this, function (se) {
|
|
// only one of the displays will have an item selected, so it's ok to
|
|
// call activateSelected() on all of them
|
|
this._searchAreaApps.activateSelected();
|
|
this._searchAreaDocs.activateSelected();
|
|
return true;
|
|
}));
|
|
this._searchEntry.entry.connect('key-press-event', Lang.bind(this, function (se, e) {
|
|
let symbol = Shell.get_event_key_symbol(e);
|
|
if (symbol == Clutter.Escape) {
|
|
// Escape will keep clearing things back to the desktop. First, if
|
|
// we have active text, we remove it.
|
|
if (this._searchEntry.entry.text != '')
|
|
this._searchEntry.entry.text = '';
|
|
// Next, if we're in one of the "more" modes or showing the details pane, close them
|
|
else if (this._activePane != null)
|
|
this._activePane.close();
|
|
// Finally, just close the overlay entirely
|
|
else
|
|
Main.overlay.hide();
|
|
return true;
|
|
} else if (symbol == Clutter.Up) {
|
|
if (!this._searchActive)
|
|
return true;
|
|
// selectUp and selectDown wrap around in their respective displays
|
|
// too, but there doesn't seem to be any flickering if we first select
|
|
// something in one display, but then unset the selection, and move
|
|
// it to the other display, so it's ok to do that.
|
|
if (this._searchAreaDocs.hasSelected())
|
|
this._searchAreaDocs.selectUp();
|
|
else if (this._searchAreaApps.hasItems())
|
|
this._searchAreaApps.selectUp();
|
|
else
|
|
this._searchAreaDocs.selectUp();
|
|
return true;
|
|
} else if (symbol == Clutter.Down) {
|
|
if (!this._searchActive)
|
|
return true;
|
|
if (this._searchAreaDocs.hasSelected())
|
|
this._searchAreaDocs.selectDown();
|
|
else if (this._searchAreaApps.hasItems())
|
|
this._searchAreaApps.selectDown();
|
|
else
|
|
this._searchAreaDocs.selectDown();
|
|
return true;
|
|
}
|
|
return false;
|
|
}));
|
|
|
|
/***** Applications *****/
|
|
|
|
let appsHeader = new SectionHeader("APPLICATIONS");
|
|
this._appsSection = new Big.Box({ spacing: DEFAULT_PADDING });
|
|
this._appsSection.append(appsHeader.actor, Big.BoxPackFlags.NONE);
|
|
|
|
this._appsContent = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
|
|
this._appsSection.append(this._appsContent, Big.BoxPackFlags.EXPAND);
|
|
this._appWell = new AppDisplay.AppWell();
|
|
this._appsContent.append(this._appWell.actor, Big.BoxPackFlags.EXPAND);
|
|
|
|
this._moreAppsPane = null;
|
|
appsHeader.moreLink.connect('activated', Lang.bind(this, function (link) {
|
|
if (this._moreAppsPane == null) {
|
|
this._moreAppsPane = new ResultPane(this, this._detailsWidth);
|
|
this._moreAppsPane.packResults(AppDisplay.AppDisplay, true);
|
|
this._addPane(this._moreAppsPane);
|
|
link.setPane(this._moreAppsPane);
|
|
}
|
|
}));
|
|
|
|
this.dashContainer.append(this._appsSection, Big.BoxPackFlags.NONE);
|
|
|
|
/***** Places *****/
|
|
|
|
let placesSection = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
|
spacing: DEFAULT_PADDING });
|
|
let placesHeader = new SectionHeader("PLACES");
|
|
placesSection.append(placesHeader.actor, Big.BoxPackFlags.NONE);
|
|
|
|
let placesDisplay = new Places.Places();
|
|
placesSection.append(placesDisplay.actor, Big.BoxPackFlags.EXPAND);
|
|
|
|
this.dashContainer.append(placesSection, Big.BoxPackFlags.NONE);
|
|
|
|
/***** Documents *****/
|
|
|
|
this._docsSection = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
|
spacing: DEFAULT_PADDING });
|
|
this._moreDocsPane = null;
|
|
|
|
let docsHeader = new SectionHeader("RECENT DOCUMENTS");
|
|
this._docsSection.append(docsHeader.actor, Big.BoxPackFlags.NONE);
|
|
|
|
this._docDisplay = new DocDisplay.DocDisplay();
|
|
this._docDisplay.load();
|
|
this._docsSection.append(this._docDisplay.actor, Big.BoxPackFlags.EXPAND);
|
|
|
|
createPaneForDetails(this, this._docDisplay, this._detailsWidth);
|
|
|
|
docsHeader.moreLink.connect('activated', Lang.bind(this, function (link) {
|
|
if (this._moreDocsPane == null) {
|
|
this._moreDocsPane = new ResultPane(this, this._detailsWidth);
|
|
this._moreDocsPane.packResults(DocDisplay.DocDisplay, true);
|
|
this._addPane(this._moreDocsPane);
|
|
link.setPane(this._moreDocsPane);
|
|
}
|
|
}));
|
|
|
|
this.dashContainer.append(this._docsSection, Big.BoxPackFlags.EXPAND);
|
|
},
|
|
|
|
show: function() {
|
|
let global = Shell.Global.get();
|
|
global.stage.set_key_focus(this._searchEntry.entry);
|
|
},
|
|
|
|
hide: function() {
|
|
this._firstSelectAfterOverlayShow = true;
|
|
if (this._searchEntry.entry.text != '')
|
|
this._searchEntry.entry.text = '';
|
|
if (this._activePane != null)
|
|
this._activePane.close();
|
|
},
|
|
|
|
closePanes: function () {
|
|
if (this._activePane != null)
|
|
this._activePane.close();
|
|
},
|
|
|
|
_addPane: function(pane) {
|
|
pane.connect('open-state-changed', Lang.bind(this, function (pane, isOpen) {
|
|
if (isOpen) {
|
|
if (pane != this._activePane && this._activePane != null) {
|
|
this._activePane.close();
|
|
}
|
|
this._activePane = pane;
|
|
} else if (pane == this._activePane) {
|
|
this._activePane = null;
|
|
}
|
|
}));
|
|
Main.overlay.addPane(pane);
|
|
}
|
|
};
|
|
Signals.addSignalMethods(Dash.prototype);
|