Create ShellApp, rebase things on it

Previously, we had ShellAppInfo, which contains fundamental
information about an application, and methods on ShellAppMonitor
to retrieve "live" information like the window list.

AppIcon ended up being used as the "App" class which was painful
for various reasons; among them that we need to handle window
list changes, and some consumers weren't ready for that.

Clean things up a bit by introducing a new ShellApp class in C,
which currently wraps a ShellAppInfo.

AppIcon then is more like the display actor for a ShellApp.  Notably,
the ".windows" property moves out of it.  The altTab code which
won't handle dynamic changes instead is changed to maintain a
cached version.

ShellAppMonitor gains some more methods related to ShellApp now.

In the future, we might consider changing ShellApp to be a GInterface,
which could be implemented by ShellDesktopFileApp, ShellWindowApp.

Then we could axe ShellAppInfo from the "public" API and it would
return to being an internal loss mitigation layer for GMenu.

https://bugzilla.gnome.org/show_bug.cgi?id=598227
This commit is contained in:
Colin Walters
2009-10-11 16:40:00 -04:00
parent 6623ca1287
commit 38c06ca837
11 changed files with 611 additions and 271 deletions

View File

@ -93,7 +93,7 @@ AltTabPopup.prototype = {
// Make the initial selection
if (this._appIcons.length == 1) {
if (!backward && this._appIcons[0].windows.length > 1) {
if (!backward && this._appIcons[0].cachedWindows.length > 1) {
// For compatibility with the multi-app case below
this._select(0, 1);
} else
@ -101,9 +101,10 @@ AltTabPopup.prototype = {
} else if (backward) {
this._select(this._appIcons.length - 1);
} else {
if (this._appIcons[0].windows.length > 1) {
let curAppNextWindow = this._appIcons[0].windows[1];
let nextAppWindow = this._appIcons[1].windows[0];
let firstWindows = this._appIcons[0].cachedWindows;
if (firstWindows.length > 1) {
let curAppNextWindow = firstWindows[1];
let nextAppWindow = this._appIcons[1].cachedWindows[0];
// If the next window of the current app is more-recently-used
// than the first window of the next app, then select it.
@ -139,11 +140,11 @@ AltTabPopup.prototype = {
_nextWindow : function() {
return mod(this._currentWindows[this._currentApp] + 1,
this._appIcons[this._currentApp].windows.length);
this._appIcons[this._currentApp].cachedWindows.length);
},
_previousWindow : function() {
return mod(this._currentWindows[this._currentApp] - 1,
this._appIcons[this._currentApp].windows.length);
this._appIcons[this._currentApp].cachedWindows.length);
},
_keyPressEvent : function(actor, event) {
@ -188,7 +189,7 @@ AltTabPopup.prototype = {
},
_appActivated : function(appSwitcher, n) {
Main.activateWindow(this._appIcons[n].windows[this._currentWindows[n]]);
Main.activateWindow(this._appIcons[n].cachedWindows[this._currentWindows[n]]);
this.destroy();
},
@ -200,7 +201,7 @@ AltTabPopup.prototype = {
},
_windowActivated : function(thumbnailList, n) {
Main.activateWindow(this._appIcons[this._currentApp].windows[n]);
Main.activateWindow(this._appIcons[this._currentApp].cachedWindows[n]);
this.destroy();
},
@ -225,7 +226,7 @@ AltTabPopup.prototype = {
_finish : function() {
let app = this._appIcons[this._currentApp];
let window = app.windows[this._currentWindows[this._currentApp]];
let window = app.cachedWindows[this._currentWindows[this._currentApp]];
Main.activateWindow(window);
this.destroy();
},
@ -267,7 +268,7 @@ AltTabPopup.prototype = {
this._appSwitcher.showArrow(app);
} else {
this._appSwitcher.highlight(app);
if (this._appIcons[this._currentApp].windows.length > 1)
if (this._appIcons[this._currentApp].cachedWindows.length > 1)
this._appSwitcher.showArrow(app);
}
@ -276,7 +277,7 @@ AltTabPopup.prototype = {
this._createThumbnails();
this._currentWindows[this._currentApp] = window;
this._thumbnails.highlight(window);
} else if (this._appIcons[this._currentApp].windows.length > 1 &&
} else if (this._appIcons[this._currentApp].cachedWindows.length > 1 &&
!noTimeout) {
this._thumbnailTimeoutId = Mainloop.timeout_add (
THUMBNAIL_POPUP_TIME,
@ -289,7 +290,7 @@ AltTabPopup.prototype = {
},
_createThumbnails : function() {
this._thumbnails = new ThumbnailList (this._appIcons[this._currentApp].windows);
this._thumbnails = new ThumbnailList (this._appIcons[this._currentApp].cachedWindows);
this._thumbnails.connect('item-activated', Lang.bind(this, this._windowActivated));
this._thumbnails.connect('item-hovered', Lang.bind(this, this._windowHovered));
@ -552,8 +553,11 @@ AppSwitcher.prototype = {
let workspaceIcons = [];
let otherIcons = [];
for (let i = 0; i < apps.length; i++) {
let appIcon = new AppIcon.AppIcon({ appInfo: apps[i],
let appIcon = new AppIcon.AppIcon({ app: apps[i],
size: POPUP_APPICON_SIZE });
// Cache the window list now; we don't handle dynamic changes here,
// and we don't want to be continually retrieving it
appIcon.cachedWindows = appIcon.app.get_windows();
if (this._hasWindowsOnWorkspace(appIcon, activeWorkspace))
workspaceIcons.push(appIcon);
else
@ -621,35 +625,16 @@ AppSwitcher.prototype = {
},
_hasWindowsOnWorkspace: function(appIcon, workspace) {
for (let i = 0; i < appIcon.windows.length; i++) {
if (appIcon.windows[i].get_workspace() == workspace)
return true;
}
return false;
},
_hasVisibleWindows : function(appIcon) {
for (let i = 0; i < appIcon.windows.length; i++) {
if (appIcon.windows[i].showing_on_its_workspace())
let windows = appIcon.cachedWindows;
for (let i = 0; i < windows.length; i++) {
if (windows[i].get_workspace() == workspace)
return true;
}
return false;
},
_sortAppIcon : function(appIcon1, appIcon2) {
let vis1 = this._hasVisibleWindows(appIcon1);
let vis2 = this._hasVisibleWindows(appIcon2);
if (vis1 && !vis2) {
return -1;
} else if (vis2 && !vis1) {
return 1;
} else {
// The app's most-recently-used window is first
// in its list
return (appIcon2.windows[0].get_user_time() -
appIcon1.windows[0].get_user_time());
}
return appIcon1.app.compare(appIcon2.app);
}
};

View File

@ -476,15 +476,15 @@ AppDisplay.prototype = {
Signals.addSignalMethods(AppDisplay.prototype);
function BaseWellItem(appInfo, isFavorite, hasMenu) {
this._init(appInfo, isFavorite, hasMenu);
function BaseWellItem(app, isFavorite, hasMenu) {
this._init(app, isFavorite, hasMenu);
}
BaseWellItem.prototype = {
__proto__: AppIcon.AppIcon.prototype,
_init: function(appInfo, isFavorite) {
AppIcon.AppIcon.prototype._init.call(this, { appInfo: appInfo,
_init: function(app, isFavorite) {
AppIcon.AppIcon.prototype._init.call(this, { app: app,
menuType: AppIcon.MenuType.ON_RIGHT,
glow: true });
@ -523,7 +523,7 @@ BaseWellItem.prototype = {
// as say Pidgin, but ideally what we do there is have the app
// express to us that it doesn't do relaunch=new-window in the
// .desktop file.
this.appInfo.launch();
this.app.get_info().launch();
},
getDragActor: function() {
@ -537,15 +537,15 @@ BaseWellItem.prototype = {
}
}
function RunningWellItem(appInfo, isFavorite) {
this._init(appInfo, isFavorite);
function RunningWellItem(app, isFavorite) {
this._init(app, isFavorite);
}
RunningWellItem.prototype = {
__proto__: BaseWellItem.prototype,
_init: function(appInfo, isFavorite) {
BaseWellItem.prototype._init.call(this, appInfo, isFavorite);
_init: function(app, isFavorite) {
BaseWellItem.prototype._init.call(this, app, isFavorite);
this._dragStartX = 0;
this._dragStartY = 0;
@ -557,15 +557,14 @@ RunningWellItem.prototype = {
let modifiers = Shell.get_event_state(event);
if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
this.appInfo.launch();
this.app.get_info().launch();
} else {
this.activateMostRecentWindow();
}
},
activateMostRecentWindow: function () {
// The _get_windows_for_app sorts them for us
let mostRecentWindow = this.windows[0];
let mostRecentWindow = this.app.get_windows()[0];
Main.overview.activateWindow(mostRecentWindow, Main.currentTime());
},
@ -582,7 +581,7 @@ RunningWellItem.prototype = {
},
menuPoppedUp: function() {
Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(this.appInfo.get_id());
Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(this.app.get_id());
},
menuPoppedDown: function() {
@ -593,15 +592,15 @@ RunningWellItem.prototype = {
}
};
function InactiveWellItem(appInfo, isFavorite) {
this._init(appInfo, isFavorite);
function InactiveWellItem(app, isFavorite) {
this._init(app, isFavorite);
}
InactiveWellItem.prototype = {
__proto__: BaseWellItem.prototype,
_init : function(appInfo, isFavorite) {
BaseWellItem.prototype._init.call(this, appInfo, isFavorite);
_init : function(app, isFavorite) {
BaseWellItem.prototype._init.call(this, app, isFavorite);
this.actor.connect('notify::pressed', Lang.bind(this, this._onPressedChanged));
this.actor.connect('activate', Lang.bind(this, this._onActivate));
@ -612,12 +611,9 @@ InactiveWellItem.prototype = {
},
_onActivate: function() {
if (this.windows.length == 0) {
this.appInfo.launch();
Main.overview.hide();
return true;
}
return false;
this.app.get_info().launch();
Main.overview.hide();
return true;
},
menuPoppedUp: function() {
@ -824,21 +820,11 @@ AppWell.prototype = {
this._redisplay();
},
_lookupApps: function(appIds) {
let result = [];
for (let i = 0; i < appIds.length; i++) {
let id = appIds[i];
let app = this._appSystem.lookup_cached_app(id);
if (!app)
continue;
result.push(app);
}
return result;
},
_arrayValues: function(array) {
return array.reduce(function (values, id, index) {
values[id] = index; return values; }, {});
_appIdListToHash: function(apps) {
let ids = {};
for (let i = 0; i < apps.length; i++)
ids[apps[i].get_id()] = apps[i];
return ids;
},
_onMappedNotify: function() {
@ -857,32 +843,29 @@ AppWell.prototype = {
this._grid.removeAll();
let favoriteIds = this._appSystem.get_favorites();
let favoriteIdsHash = this._arrayValues(favoriteIds);
let favorites = this._appMonitor.get_favorites();
let favoriteIds = this._appIdListToHash(favorites);
/* hardcode here pending some design about how exactly desktop contexts behave */
let contextId = "";
let running = this._appMonitor.get_running_apps(contextId).filter(function (e) {
return !(e.get_id() in favoriteIdsHash);
});
let favorites = this._lookupApps(favoriteIds);
let running = this._appMonitor.get_running_apps(contextId);
let runningIds = this._appIdListToHash(running)
let displays = []
this._addApps(favorites, true);
this._addApps(running, false);
this._displays = displays;
},
_addApps: function(apps, isFavorite) {
for (let i = 0; i < apps.length; i++) {
let app = apps[i];
let windows = this._appMonitor.get_windows_for_app(app.get_id());
for (let i = 0; i < favorites.length; i++) {
let app = favorites[i];
let display;
if (windows.length > 0)
display = new RunningWellItem(app, isFavorite);
else
display = new InactiveWellItem(app, isFavorite);
if (app.get_windows().length > 0) {
display = new RunningWellItem(app, true);
} else {
display = new InactiveWellItem(app, true);
}
this._grid.actor.add_actor(display.actor);
}
for (let i = 0; i < running.length; i++) {
let app = running[i];
let display = new RunningWellItem(app, false);
this._grid.actor.add_actor(display.actor);
}
},

View File

@ -55,9 +55,9 @@ function AppIcon(params) {
AppIcon.prototype = {
_init : function(params) {
this.appInfo = params.appInfo;
if (!this.appInfo)
throw new Error('AppIcon constructor requires "appInfo" param');
this.app = params.app;
if (!this.app)
throw new Error('AppIcon constructor requires "app" param');
this._menuType = ('menuType' in params) ? params.menuType : MenuType.NONE;
this._iconSize = ('size' in params) ? params.size : APPICON_DEFAULT_ICON_SIZE;
@ -70,20 +70,6 @@ AppIcon.prototype = {
reactive: true });
this.actor._delegate = this;
this.highlight_border_color = APPICON_DEFAULT_BORDER_COLOR;
this._signalIds = [];
// Note, we don't presently update the window list dynamically here; this actor
// gets destroyed and recreated by AppWell, and in alt-tab the whole thing is
// created each time
this.windows = Shell.AppMonitor.get_default().get_windows_for_app(this.appInfo.get_id());
for (let i = 0; i < this.windows.length; i++) {
let sigId = this.windows[i].connect('notify::user-time', Lang.bind(this, this._resortWindows));
this._signalIds.push([this.windows[i], sigId]);
}
this._resortWindows();
this.actor.connect('destroy', Lang.bind(this, this._destroy));
this.actor.connect('notify::mapped', Lang.bind(this, this._onMappedChanged));
if (this._menuType != MenuType.NONE) {
this.actor.connect('button-press-event', Lang.bind(this, this._updateMenuOnButtonPress));
@ -99,7 +85,7 @@ AppIcon.prototype = {
y_align: Big.BoxAlignment.CENTER,
width: this._iconSize,
height: this._iconSize });
this.icon = this.appInfo.create_icon_texture(this._iconSize);
this.icon = this.app.create_icon_texture(this._iconSize);
iconBox.append(this.icon, Big.BoxPackFlags.NONE);
this.actor.append(iconBox, Big.BoxPackFlags.EXPAND);
@ -114,12 +100,13 @@ AppIcon.prototype = {
font_name: "Sans 12px",
line_alignment: Pango.Alignment.CENTER,
ellipsize: Pango.EllipsizeMode.END,
text: this.appInfo.get_name() });
text: this.app.get_name() });
nameBox.add_actor(this._name);
if (showGlow) {
this._glowBox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
let glowPath = GLib.filename_to_uri(global.imagedir + 'app-well-glow.png', '');
for (let i = 0; i < this.windows.length && i < 3; i++) {
let windows = this.app.get_windows();
for (let i = 0; i < windows.length && i < 3; i++) {
let glow = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
glowPath, -1, -1);
glow.keep_aspect_ratio = false;
@ -175,53 +162,11 @@ AppIcon.prototype = {
}
},
_destroy: function() {
for (let i = 0; i < this._signalIds.length; i++) {
let [obj, sigId] = this._signalIds[i];
obj.disconnect(sigId);
}
},
_onMappedChanged: function() {
let mapped = this.actor.mapped;
if (mapped && this._windowSortStale)
this._resortWindows();
},
_resortWindows: function() {
let mapped = this.actor.mapped;
if (!mapped) {
this._windowSortStale = true;
return;
}
this._windowSortStale = false;
this.windows.sort(function (a, b) {
let activeWorkspace = global.screen.get_active_workspace();
let wsA = a.get_workspace() == activeWorkspace;
let wsB = b.get_workspace() == activeWorkspace;
if (wsA && !wsB)
return -1;
else if (wsB && !wsA)
return 1;
let visA = a.showing_on_its_workspace();
let visB = b.showing_on_its_workspace();
if (visA && !visB)
return -1;
else if (visB && !visA)
return 1;
else
return b.get_user_time() - a.get_user_time();
});
},
// AppIcon itself is not a draggable, but if you want to make
// a subclass of it draggable, you can use this method to create
// a drag actor
createDragActor: function() {
return this.appInfo.create_icon_texture(this._iconSize);
return this.app.create_icon_texture(this._iconSize);
},
setHighlight: function(highlight) {
@ -421,7 +366,7 @@ AppIconMenu.prototype = {
_redisplay: function() {
this._windowContainer.remove_all();
let windows = this._source.windows;
let windows = this._source.app.get_windows();
this._windowContainer.show();
@ -462,7 +407,7 @@ AppIconMenu.prototype = {
this._newWindowMenuItem = windows.length > 0 ? this._appendMenuItem(null, _("New Window")) : null;
let favorites = Shell.AppSystem.get_default().get_favorites();
let id = this._source.appInfo.get_id();
let id = this._source.app.get_id();
this._isFavorite = false;
for (let i = 0; i < favorites.length; i++) {
if (id == favorites[i]) {
@ -615,14 +560,14 @@ AppIconMenu.prototype = {
let metaWindow = child._window;
this.emit('activate-window', metaWindow);
} else if (child == this._newWindowMenuItem) {
this._source.appInfo.launch();
this._source.app.get_info().launch();
this.emit('activate-window', null);
} else if (child == this._toggleFavoriteMenuItem) {
let appSys = Shell.AppSystem.get_default();
if (this._isFavorite)
appSys.remove_favorite(this._source.appInfo.get_id());
appSys.remove_favorite(this._source.app.get_id());
else
appSys.add_favorite(this._source.appInfo.get_id());
appSys.add_favorite(this._source.app.get_id());
}
this.popdown();
},

View File

@ -148,10 +148,11 @@ AppPanelMenu.prototype = {
this._iconBox.hide();
this._label.text = '';
if (this._focusedApp != null) {
let icon = focusedApp.create_icon_texture(PANEL_ICON_SIZE);
let info = focusedApp.get_info();
let icon = info.create_icon_texture(PANEL_ICON_SIZE);
this._iconBox.append(icon, Big.BoxPackFlags.NONE);
this._iconBox.show();
this._label.text = focusedApp.get_name();
this._label.text = info.get_name();
} else if (this._activeSequence != null) {
let icon = this._activeSequence.create_icon(PANEL_ICON_SIZE);
this._iconBox.append(icon, Big.BoxPackFlags.NONE);

View File

@ -1227,7 +1227,7 @@ Workspace.prototype = {
_createWindowIcon: function(window) {
let appSys = Shell.AppSystem.get_default();
let appMon = Shell.AppMonitor.get_default()
let appInfo = appMon.get_window_app(window.metaWindow);
let appInfo = appMon.get_window_app(window.metaWindow).get_info();
let iconTexture = null;
// The design is application based, so prefer the application
// icon here if we have it. FIXME - should move this fallback code