From e57b7ec3359ce39e4fa360c744883fb37a069a49 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 25 Jun 2009 17:45:06 -0400 Subject: [PATCH] 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. --- js/misc/appInfo.js | 68 ++++++++------- js/ui/appDisplay.js | 197 ++++++++++++++++++++++++++++++++++++++++++++ js/ui/overlay.js | 58 ++----------- 3 files changed, 235 insertions(+), 88 deletions(-) diff --git a/js/misc/appInfo.js b/js/misc/appInfo.js index 32f9bd0cf..61b36bdcf 100644 --- a/js/misc/appInfo.js +++ b/js/misc/appInfo.js @@ -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)); +} diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 1f2a6b753..3729b1416 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -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); diff --git a/js/ui/overlay.js b/js/ui/overlay.js index fdfda5a55..03078ff95 100644 --- a/js/ui/overlay.js +++ b/js/ui/overlay.js @@ -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; }