Commit e57b7ec3 authored by Colin Walters's avatar Colin Walters
Browse files

Replace main AppDisplay with AppWell

This is a start towards implementing the 02 overlay design.  The
default applications has moved into GConf.  We keep around an AppDisplay
instance for handling the right side behavior.
parent b5988a57
......@@ -7,32 +7,6 @@ const Shell = imports.gi.Shell;
const Main = imports.ui.main;
// TODO - move this into GConf once we're not a plugin anymore
// but have taken over metacity
// This list is taken from GNOME Online popular applications
// http://online.gnome.org/applications
// but with nautilus removed (since it should already be running)
// and evince, totem, and gnome-file-roller removed (since they're
// usually started by opening documents, not by opening the app
// directly)
const DEFAULT_APPLICATIONS = [
'mozilla-firefox.desktop',
'gnome-terminal.desktop',
'evolution.desktop',
'gedit.desktop',
'mozilla-thunderbird.desktop',
'rhythmbox.desktop',
'epiphany.desktop',
'xchat.desktop',
'openoffice.org-1.9-writer.desktop',
'emacs.desktop',
'gnome-system-monitor.desktop',
'openoffice.org-1.9-calc.desktop',
'eclipse.desktop',
'openoffice.org-1.9-impress.desktop',
'vncviewer.desktop'
];
function AppInfo(appId) {
this._init(appId);
}
......@@ -41,8 +15,9 @@ AppInfo.prototype = {
_init : function(appId) {
this.appId = appId;
this._gAppInfo = Gio.DesktopAppInfo.new(appId);
if (!this._gAppInfo)
if (!this._gAppInfo) {
throw new Error('Unknown appId ' + appId);
}
this.id = this._gAppInfo.get_id();
this.name = this._gAppInfo.get_name();
......@@ -120,16 +95,39 @@ function getMostUsedApps(count) {
}
}
let favs = getFavorites();
// Fill the list with default applications it's not full yet
for (let i = 0; i < DEFAULT_APPLICATIONS.length && matches.length <= count; i++) {
let appId = DEFAULT_APPLICATIONS[i];
if (alreadyAdded[appId])
continue;
let appInfo = getAppInfo(appId);
if (appInfo)
matches.push(appInfo);
for (let i = 0; i < favs.length && favs.length <= count; i++) {
matches.push(favs[i]);
}
return matches;
}
function _idListToInfos(ids) {
let infos = [];
for (let i = 0; i < ids.length; i++) {
let display = getAppInfo(ids[i]);
if (display == null)
continue;
infos.push(display);
}
return infos;
}
function getFavorites() {
let system = Shell.AppSystem.get_default();
return _idListToInfos(system.get_favorites());
}
function getRunning() {
let monitor = Shell.AppMonitor.get_default();
let basename = function (n) {
let i = n.lastIndexOf('/');
if (i < 0)
return n;
return n.substring(i+1);
}
return _idListToInfos(monitor.get_running_app_ids().map(basename));
}
......@@ -5,16 +5,21 @@ const Clutter = imports.gi.Clutter;
const Pango = imports.gi.Pango;
const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk;
const Tidy = imports.gi.Tidy;
const Shell = imports.gi.Shell;
const Lang = imports.lang;
const Signals = imports.signals;
const Mainloop = imports.mainloop;
const AppInfo = imports.misc.appInfo;
const DND = imports.ui.dnd;
const GenericDisplay = imports.ui.genericDisplay;
const ENTERED_MENU_COLOR = new Clutter.Color();
ENTERED_MENU_COLOR.from_pixel(0x00ff0022);
const APP_ICON_SIZE = 48;
const MENU_ICON_SIZE = 24;
const MENU_SPACING = 15;
......@@ -147,6 +152,7 @@ MenuItem.prototype = {
}
Signals.addSignalMethods(MenuItem.prototype);
/* This class represents a display containing a collection of application items.
* The applications are sorted based on their popularity by default, and based on
* their name if some search filter is applied.
......@@ -428,3 +434,194 @@ AppDisplay.prototype = {
};
Signals.addSignalMethods(AppDisplay.prototype);
function WellDisplayItem(appInfo, isFavorite) {
this._init(appInfo, isFavorite);
}
WellDisplayItem.prototype = {
_init : function(appInfo, isFavorite) {
this.appInfo = appInfo;
this.isFavorite = isFavorite;
this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
width: APP_ICON_SIZE,
reactive: true });
this.actor._delegate = this;
this.actor.connect('button-release-event', Lang.bind(this, function (b, e) {
this.launch();
this.emit('activated');
}));
let draggable = DND.makeDraggable(this.actor);
this._icon = appInfo.getIcon(APP_ICON_SIZE);
this.actor.append(this._icon, Big.BoxPackFlags.NONE);
let count = Shell.AppMonitor.get_default().get_window_count(appInfo.appId);
this._name = new Clutter.Text({ color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
font_name: "Sans 12px",
ellipsize: Pango.EllipsizeMode.END,
text: appInfo.name });
if (count > 0) {
let runningBox = new Big.Box({ /* border_color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
border: 1,
padding: 1 */ });
runningBox.append(this._name, Big.BoxPackFlags.EXPAND);
this.actor.append(runningBox, Big.BoxPackFlags.NONE);
} else {
this.actor.append(this._name, Big.BoxPackFlags.NONE);
}
},
// Opens an application represented by this display item.
launch : function() {
this.appInfo.launch();
},
// Draggable interface - FIXME deduplicate with GenericDisplay
getDragActor: function(stageX, stageY) {
this.dragActor = new Clutter.Clone({ source: this._icon });
[this.dragActor.width, this.dragActor.height] = this._icon.get_transformed_size();
// If the user dragged from the icon itself, then position
// the dragActor over the original icon. Otherwise center it
// around the pointer
let [iconX, iconY] = this._icon.get_transformed_position();
let [iconWidth, iconHeight] = this._icon.get_transformed_size();
if (stageX > iconX && stageX <= iconX + iconWidth &&
stageY > iconY && stageY <= iconY + iconHeight)
this.dragActor.set_position(iconX, iconY);
else
this.dragActor.set_position(stageX - this.dragActor.width / 2, stageY - this.dragActor.height / 2);
return this.dragActor;
},
// Returns the original icon that is being used as a source for the cloned texture
// that represents the item as it is being dragged.
getDragActorSource: function() {
return this._icon;
}
};
Signals.addSignalMethods(WellDisplayItem.prototype);
function WellArea(width, isFavorite) {
this._init(width, isFavorite);
}
WellArea.prototype = {
_init : function(width, isFavorite) {
this.isFavorite = isFavorite;
this.actor = new Tidy.Grid({ width: width });
this.actor._delegate = this;
},
redisplay: function (infos) {
let children;
children = this.actor.get_children();
children.forEach(Lang.bind(this, function (v) {
this.actor.remove_actor(v);
v.destroy();
}));
for (let i = 0; i < infos.length; i++) {
let display = new WellDisplayItem(infos[i], this.isFavorite);
display.connect('activated', Lang.bind(this, function (display) {
this.emit('activated', display);
}));
this.actor.add_actor(display.actor);
};
},
// Draggable target interface
acceptDrop : function(source, actor, x, y, time) {
let global = Shell.Global.get();
if (!(source instanceof WellDisplayItem)) {
return false;
}
let appSystem = Shell.AppSystem.get_default();
let id = source.appInfo.appId;
if (source.isFavorite && (!this.isFavorite)) {
Mainloop.idle_add(function () {
appSystem.remove_favorite(id);
});
} else if ((!source.isFavorite) && this.isFavorite) {
Mainloop.idle_add(function () {
appSystem.add_favorite(id);
});
}
return true;
}
}
Signals.addSignalMethods(WellArea.prototype);
function AppWell(width) {
this._init(width);
}
AppWell.prototype = {
_init : function(width) {
this._menus = [];
this._menuDisplays = [];
this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
width: width });
this._appSystem = Shell.AppSystem.get_default();
this._appMonitor = Shell.AppMonitor.get_default();
this._appSystem.connect('changed', Lang.bind(this, function(appSys) {
this._redisplay();
}));
this._appMonitor.connect('changed', Lang.bind(this, function(monitor) {
this._redisplay();
}));
this._favoritesArea = new WellArea(width, true);
this._favoritesArea.connect('activated', Lang.bind(this, function (a, display) {
this.emit('activated');
}));
this.actor.append(this._favoritesArea.actor, Big.BoxPackFlags.NONE);
this._runningBox = new Big.Box({ border_color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
border: 1,
corner_radius: 3,
padding: GenericDisplay.PREVIEW_BOX_PADDING });
this._runningArea = new WellArea(width, false);
this._runningArea.connect('activated', Lang.bind(this, function (a, display) {
this.emit('activated');
}));
this._runningBox.append(this._runningArea.actor, Big.BoxPackFlags.EXPAND);
this.actor.append(this._runningBox, Big.BoxPackFlags.NONE);
this._redisplay();
},
_redisplay: function() {
let arrayToObject = function(a) {
let o = {};
for (let i = 0; i < a.length; i++)
o[a[i]] = 1;
return o;
};
let favorites = AppInfo.getFavorites();
let favoriteIds = arrayToObject(favorites.map(function (e) { return e.appId; }));
let running = AppInfo.getRunning().filter(function (e) {
return !(e.appId in favoriteIds);
});
this._favoritesArea.redisplay(favorites);
this._runningArea.redisplay(running);
}
};
Signals.addSignalMethods(AppWell.prototype);
......@@ -283,7 +283,6 @@ Dash.prototype = {
this._searchEntry.entry.connect('activate', function (se) {
// only one of the displays will have an item selected, so it's ok to
// call activateSelected() on all of them
me._appDisplay.activateSelected();
me._docDisplay.activateSelected();
me._resultsAppsSection.display.activateSelected();
me._resultsDocsSection.display.activateSelected();
......@@ -329,11 +328,9 @@ Dash.prototype = {
this._appsContent = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
this._appsSection.append(this._appsContent, Big.BoxPackFlags.EXPAND);
this._appDisplay = new AppDisplay.AppDisplay(this._displayWidth, this._itemDisplayHeight / 2, DASH_COLUMNS, DASH_SECTION_PADDING);
let sideArea = this._appDisplay.getSideArea();
sideArea.hide();
this._appsContent.append(sideArea, Big.BoxPackFlags.NONE);
this._appsContent.append(this._appDisplay.actor, Big.BoxPackFlags.EXPAND);
this._appWell = new AppDisplay.AppWell(this._displayWidth);
this._appWell.actor.show();
this._appsContent.append(this._appWell.actor, Big.BoxPackFlags.EXPAND);
let moreAppsBox = new Big.Box({x_align: Big.BoxAlignment.END});
this._moreAppsLink = new Link.Link({ color: DASH_TEXT_COLOR,
......@@ -444,13 +441,12 @@ Dash.prototype = {
let itemDetailsAvailableWidth = this._detailsWidth - DASH_SECTION_PADDING * 2 - DASH_BORDER_WIDTH * 2;
let itemDetailsAvailableHeight = detailsHeight - DASH_SECTION_PADDING * 2 - DASH_BORDER_WIDTH * 2;
this._appDisplay.setAvailableDimensionsForItemDetails(itemDetailsAvailableWidth, itemDetailsAvailableHeight);
this._docDisplay.setAvailableDimensionsForItemDetails(itemDetailsAvailableWidth, itemDetailsAvailableHeight);
this._resultsAppsSection.display.setAvailableDimensionsForItemDetails(itemDetailsAvailableWidth, itemDetailsAvailableHeight);
this._resultsDocsSection.display.setAvailableDimensionsForItemDetails(itemDetailsAvailableWidth, itemDetailsAvailableHeight);
/* Proxy the activated signals */
this._appDisplay.connect('activated', function(appDisplay) {
this._appWell.connect('activated', function(well) {
me.emit('activated');
});
this._docDisplay.connect('activated', function(docDisplay) {
......@@ -462,27 +458,7 @@ Dash.prototype = {
this._resultsDocsSection.display.connect('activated', function(resultsDocsDisplay) {
me.emit('activated');
});
this._appDisplay.connect('selected', function(appDisplay) {
// We allow clicking on any item to select it, so if an
// item in the app display is selected, we need to make sure that
// no item in the doc display has the selection.
me._docDisplay.unsetSelected();
me._resultsDocsSection.display.unsetSelected();
me._resultsAppsSection.display.unsetSelected();
if (me._firstSelectAfterOverlayShow) {
me._firstSelectAfterOverlayShow = false;
} else if (!me._detailsShowing()) {
me._detailsPane.show();
me.emit('panes-displayed');
}
me._detailsContent.remove_all();
me._detailsContent.append(me._appDisplay.selectedItemDetails, Big.BoxPackFlags.NONE);
});
this._docDisplay.connect('selected', function(docDisplay) {
// We allow clicking on any item to select it, so if an
// item in the doc display is selected, we need to make sure that
// no item in the app display has the selection.
me._appDisplay.unsetSelected();
me._resultsDocsSection.display.unsetSelected();
me._resultsAppsSection.display.unsetSelected();
if (!me._detailsShowing()) {
......@@ -493,7 +469,6 @@ Dash.prototype = {
me._detailsContent.append(me._docDisplay.selectedItemDetails, Big.BoxPackFlags.NONE);
});
this._resultsDocsSection.display.connect('selected', function(resultsDocDisplay) {
me._appDisplay.unsetSelected();
me._docDisplay.unsetSelected();
me._resultsAppsSection.display.unsetSelected();
if (!me._detailsShowing()) {
......@@ -504,7 +479,6 @@ Dash.prototype = {
me._detailsContent.append(me._resultsDocsSection.display.selectedItemDetails, Big.BoxPackFlags.NONE);
});
this._resultsAppsSection.display.connect('selected', function(resultsAppDisplay) {
me._appDisplay.unsetSelected();
me._docDisplay.unsetSelected();
me._resultsDocsSection.display.unsetSelected();
if (!me._detailsShowing()) {
......@@ -514,12 +488,6 @@ Dash.prototype = {
me._detailsContent.remove_all();
me._detailsContent.append(me._resultsAppsSection.display.selectedItemDetails, Big.BoxPackFlags.NONE);
});
this._appDisplay.connect('redisplayed', function(appDisplay) {
me._ensureItemSelected();
});
this._docDisplay.connect('redisplayed', function(docDisplay) {
me._ensureItemSelected();
});
this._moreAppsLink.connect('clicked',
function(o, event) {
......@@ -543,7 +511,6 @@ Dash.prototype = {
show: function() {
let global = Shell.Global.get();
this._appDisplay.show();
this._appsContent.show();
this._docDisplay.show();
global.stage.set_key_focus(this._searchEntry.entry);
......@@ -567,22 +534,6 @@ Dash.prototype = {
this._unsetSearchMode();
},
// Ensures that one of the displays has the selection if neither owns it after the
// latest redisplay. This can be applicable if the display that earlier had the
// selection no longer has any items, or if their is a single section being shown
// in the expanded view and it went from having no matching items to having some.
// We first try to place the selection in the applications section, because it is
// displayed above the documents section.
_ensureItemSelected: function() {
if (!this._appDisplay.hasSelected() && !this._docDisplay.hasSelected()) {
if (this._appDisplay.hasItems()) {
this._appDisplay.selectFirstItem();
} else if (this._docDisplay.hasItems()) {
this._docDisplay.selectFirstItem();
}
}
},
// Sets the 'More' mode for browsing applications.
_setMoreAppsMode: function() {
if (this._moreAppsMode)
......@@ -811,6 +762,7 @@ Overlay.prototype = {
// the item on any workspace.
handleDragOver : function(source, actor, x, y, time) {
if (source instanceof GenericDisplay.GenericDisplayItem) {
log("unsetting more mode");
this._dash.unsetMoreMode();
return true;
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment