2008-12-01 19:51:43 +00:00
|
|
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
2008-11-21 00:53:11 +00:00
|
|
|
|
2009-04-01 19:51:17 +00:00
|
|
|
const Big = imports.gi.Big;
|
2008-11-21 00:53:11 +00:00
|
|
|
const Clutter = imports.gi.Clutter;
|
2009-04-01 19:51:17 +00:00
|
|
|
const Pango = imports.gi.Pango;
|
2009-07-07 20:08:41 +00:00
|
|
|
const GLib = imports.gi.GLib;
|
2008-11-21 00:53:11 +00:00
|
|
|
const Gio = imports.gi.Gio;
|
|
|
|
const Gtk = imports.gi.Gtk;
|
|
|
|
const Shell = imports.gi.Shell;
|
2009-04-01 19:51:17 +00:00
|
|
|
const Lang = imports.lang;
|
2009-02-02 23:02:16 +00:00
|
|
|
const Signals = imports.signals;
|
2009-11-12 22:46:59 +00:00
|
|
|
const St = imports.gi.St;
|
2009-06-30 20:35:39 +00:00
|
|
|
const Mainloop = imports.mainloop;
|
2009-08-14 13:30:48 +00:00
|
|
|
const Gettext = imports.gettext.domain('gnome-shell');
|
|
|
|
const _ = Gettext.gettext;
|
2008-11-21 00:53:11 +00:00
|
|
|
|
2009-10-15 23:28:29 +00:00
|
|
|
const AppFavorites = imports.ui.appFavorites;
|
2009-06-30 20:35:39 +00:00
|
|
|
const DND = imports.ui.dnd;
|
2008-12-20 04:27:57 +00:00
|
|
|
const GenericDisplay = imports.ui.genericDisplay;
|
2009-07-31 21:20:26 +00:00
|
|
|
const Main = imports.ui.main;
|
2009-11-29 22:45:30 +00:00
|
|
|
const Search = imports.ui.search;
|
2010-01-22 02:33:48 +00:00
|
|
|
const Workspace = imports.ui.workspace;
|
2009-04-01 19:51:17 +00:00
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
const APPICON_SIZE = 48;
|
|
|
|
const WELL_MAX_COLUMNS = 8;
|
2009-04-23 14:41:24 +00:00
|
|
|
|
2008-12-20 04:27:57 +00:00
|
|
|
/* This class represents a single display item containing information about an application.
|
|
|
|
*
|
2009-06-16 16:20:12 +00:00
|
|
|
* appInfo - AppInfo object containing information about the application
|
2008-12-20 04:27:57 +00:00
|
|
|
*/
|
Rewrite Dash, remove hardcoded width/height from GenericDisplay
This patch is a near-total rewrite of the Dash. First, the dash
code moves into a separate file, dash.js.
Inside dash.js, the components are more broken up into separate
classes; in particular there's now a Pane class and a MoreLink
class. Instead of each section of the dash, when activated,
attempting to close all N-1 other sections, instead there
is the concept of a single "active pane", and when e.g. activating
the More link for documents, if we know there's an active pane
which happens to be the apps, close it.
Many redundant containers were removed from the dash, and all
manual width, height and x/y offsets are entirely gone. We move
the visual apperance closer to the design by using the view-more.svg,
etc.
To complete the removal of height/width calculations from the dash,
we also had to do the same for GenericDisplay. Also clean up
the positioning inside overlay.js so calculation of children's
positioning is inside a single function that flows from screen.width
and screen.height, so in the future we can stop passing the width
into the Dash constructor and call this once and work on screen
resizing.
2009-08-01 02:12:01 +00:00
|
|
|
function AppDisplayItem(appInfo) {
|
|
|
|
this._init(appInfo);
|
2008-11-21 00:53:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
AppDisplayItem.prototype = {
|
2008-12-20 04:27:57 +00:00
|
|
|
__proto__: GenericDisplay.GenericDisplayItem.prototype,
|
|
|
|
|
Rewrite Dash, remove hardcoded width/height from GenericDisplay
This patch is a near-total rewrite of the Dash. First, the dash
code moves into a separate file, dash.js.
Inside dash.js, the components are more broken up into separate
classes; in particular there's now a Pane class and a MoreLink
class. Instead of each section of the dash, when activated,
attempting to close all N-1 other sections, instead there
is the concept of a single "active pane", and when e.g. activating
the More link for documents, if we know there's an active pane
which happens to be the apps, close it.
Many redundant containers were removed from the dash, and all
manual width, height and x/y offsets are entirely gone. We move
the visual apperance closer to the design by using the view-more.svg,
etc.
To complete the removal of height/width calculations from the dash,
we also had to do the same for GenericDisplay. Also clean up
the positioning inside overlay.js so calculation of children's
positioning is inside a single function that flows from screen.width
and screen.height, so in the future we can stop passing the width
into the Dash constructor and call this once and work on screen
resizing.
2009-08-01 02:12:01 +00:00
|
|
|
_init : function(appInfo) {
|
|
|
|
GenericDisplay.GenericDisplayItem.prototype._init.call(this);
|
2008-12-09 22:10:43 +00:00
|
|
|
this._appInfo = appInfo;
|
2008-12-01 19:51:43 +00:00
|
|
|
|
2009-07-07 20:08:41 +00:00
|
|
|
this._setItemInfo(appInfo.get_name(), appInfo.get_description());
|
2008-12-01 19:51:43 +00:00
|
|
|
},
|
2008-12-20 04:27:57 +00:00
|
|
|
|
2009-07-02 04:35:26 +00:00
|
|
|
getId: function() {
|
2009-07-07 20:08:41 +00:00
|
|
|
return this._appInfo.get_id();
|
2009-07-02 04:35:26 +00:00
|
|
|
},
|
|
|
|
|
2008-12-20 04:27:57 +00:00
|
|
|
//// Public method overrides ////
|
|
|
|
|
|
|
|
// Opens an application represented by this display item.
|
|
|
|
launch : function() {
|
2009-10-15 23:28:29 +00:00
|
|
|
let appSys = Shell.AppSystem.get_default();
|
|
|
|
let app = appSys.get_app(this._appInfo.get_id());
|
|
|
|
let windows = app.get_windows();
|
2009-09-09 20:43:29 +00:00
|
|
|
if (windows.length > 0) {
|
|
|
|
let mostRecentWindow = windows[0];
|
2009-12-03 20:59:52 +00:00
|
|
|
Main.overview.activateWindow(mostRecentWindow, global.get_current_time());
|
2009-09-09 20:43:29 +00:00
|
|
|
} else {
|
|
|
|
this._appInfo.launch();
|
|
|
|
}
|
2009-03-20 16:06:34 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
//// Protected method overrides ////
|
|
|
|
|
2009-06-29 19:08:48 +00:00
|
|
|
// Returns an icon for the item.
|
|
|
|
_createIcon : function() {
|
2009-07-07 20:08:41 +00:00
|
|
|
return this._appInfo.create_icon_texture(GenericDisplay.ITEM_DISPLAY_ICON_SIZE);
|
2009-06-29 19:08:48 +00:00
|
|
|
},
|
|
|
|
|
2009-07-29 21:47:50 +00:00
|
|
|
// Returns a preview icon for the item.
|
|
|
|
_createPreviewIcon : function() {
|
|
|
|
return this._appInfo.create_icon_texture(GenericDisplay.PREVIEW_ICON_SIZE);
|
2009-08-18 00:29:54 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
shellWorkspaceLaunch: function() {
|
|
|
|
this.launch();
|
2009-03-20 16:06:34 +00:00
|
|
|
}
|
2008-12-20 04:27:57 +00:00
|
|
|
};
|
2008-11-21 00:53:11 +00:00
|
|
|
|
2008-12-20 04:27:57 +00:00
|
|
|
/* 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.
|
2009-09-11 21:48:02 +00:00
|
|
|
*
|
|
|
|
* showPrefs - a boolean indicating if this AppDisplay should contain preference
|
|
|
|
* applets, rather than applications
|
2008-12-20 04:27:57 +00:00
|
|
|
*/
|
2009-11-03 23:36:44 +00:00
|
|
|
function AppDisplay(showPrefs, flags) {
|
|
|
|
this._init(showPrefs, flags);
|
2008-11-21 00:53:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
AppDisplay.prototype = {
|
2008-12-20 04:27:57 +00:00
|
|
|
__proto__: GenericDisplay.GenericDisplay.prototype,
|
|
|
|
|
2009-11-03 23:36:44 +00:00
|
|
|
_init : function(showPrefs, flags) {
|
|
|
|
GenericDisplay.GenericDisplay.prototype._init.call(this, flags);
|
2009-01-21 21:50:57 +00:00
|
|
|
|
2009-09-11 21:48:02 +00:00
|
|
|
this._showPrefs = showPrefs;
|
|
|
|
|
2009-04-01 19:51:17 +00:00
|
|
|
this._menus = [];
|
|
|
|
this._menuDisplays = [];
|
2009-10-07 05:41:28 +00:00
|
|
|
// map<search term, map<appId, true>>
|
|
|
|
// We use a map of appIds instead of an array to ensure that we don't have duplicates and for easier lookup.
|
|
|
|
this._menuSearchAppMatches = {};
|
2009-04-23 14:41:24 +00:00
|
|
|
|
2009-06-18 16:27:19 +00:00
|
|
|
this._appSystem = Shell.AppSystem.get_default();
|
2008-12-01 19:51:43 +00:00
|
|
|
this._appsStale = true;
|
2009-06-30 20:35:39 +00:00
|
|
|
this._appSystem.connect('installed-changed', Lang.bind(this, function(appSys) {
|
2009-04-23 14:41:24 +00:00
|
|
|
this._appsStale = true;
|
2009-10-01 21:41:17 +00:00
|
|
|
this._redisplay(GenericDisplay.RedisplayFlags.NONE);
|
2009-04-23 14:41:24 +00:00
|
|
|
}));
|
2009-04-01 19:51:17 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
//// Private ////
|
|
|
|
|
2009-06-17 22:42:05 +00:00
|
|
|
_addApp: function(appInfo) {
|
2009-07-07 20:08:41 +00:00
|
|
|
let appId = appInfo.get_id();
|
2009-06-17 22:42:05 +00:00
|
|
|
this._allItems[appId] = appInfo;
|
|
|
|
},
|
|
|
|
|
2009-07-07 20:08:41 +00:00
|
|
|
//// Protected method overrides ////
|
2008-12-01 19:51:43 +00:00
|
|
|
|
2008-12-20 04:27:57 +00:00
|
|
|
// Gets information about all applications by calling Gio.app_info_get_all().
|
|
|
|
_refreshCache : function() {
|
2008-12-01 19:51:43 +00:00
|
|
|
if (!this._appsStale)
|
2009-09-24 22:36:36 +00:00
|
|
|
return true;
|
2008-12-20 04:27:57 +00:00
|
|
|
this._allItems = {};
|
2009-04-01 19:51:17 +00:00
|
|
|
|
2009-09-11 21:48:02 +00:00
|
|
|
if (this._showPrefs) {
|
|
|
|
// Get the desktop file ids for settings/preferences.
|
|
|
|
// These are used for search results, but not in the app menus.
|
|
|
|
let settings = this._appSystem.get_all_settings();
|
|
|
|
for (let i = 0; i < settings.length; i++) {
|
|
|
|
let app = settings[i];
|
2009-07-07 20:08:41 +00:00
|
|
|
this._addApp(app);
|
2009-05-14 18:14:18 +00:00
|
|
|
}
|
2009-09-11 21:48:02 +00:00
|
|
|
} else {
|
2009-11-29 22:45:30 +00:00
|
|
|
let apps = this._appSystem.get_flattened_apps();
|
|
|
|
for (let i = 0; i < apps.length; i++) {
|
|
|
|
let app = apps[i];
|
|
|
|
this._addApp(app);
|
2009-09-11 21:48:02 +00:00
|
|
|
}
|
2009-05-14 18:14:18 +00:00
|
|
|
}
|
|
|
|
|
2008-12-01 19:51:43 +00:00
|
|
|
this._appsStale = false;
|
2009-09-24 22:36:36 +00:00
|
|
|
return false;
|
2008-12-01 19:51:43 +00:00
|
|
|
},
|
|
|
|
|
2008-12-20 04:27:57 +00:00
|
|
|
_setDefaultList : function() {
|
2009-11-03 23:36:44 +00:00
|
|
|
this._matchedItems = this._allItems;
|
2009-09-24 22:36:36 +00:00
|
|
|
this._matchedItemKeys = [];
|
2009-11-03 23:36:44 +00:00
|
|
|
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));
|
2008-12-01 19:51:43 +00:00
|
|
|
},
|
|
|
|
|
2009-02-10 19:12:13 +00:00
|
|
|
// 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 appA = this._allItems[itemIdA];
|
|
|
|
let appB = this._allItems[itemIdB];
|
2009-07-07 20:08:41 +00:00
|
|
|
return appA.get_name().localeCompare(appB.get_name());
|
2008-12-01 19:51:43 +00:00
|
|
|
},
|
|
|
|
|
2008-12-20 04:27:57 +00:00
|
|
|
// Checks if the item info can be a match for the search string by checking
|
2009-10-07 05:41:28 +00:00
|
|
|
// the name, description, execution command, and category for the application.
|
2009-07-07 20:08:41 +00:00
|
|
|
// Item info is expected to be Shell.AppInfo.
|
2008-12-20 04:27:57 +00:00
|
|
|
// Returns a boolean flag indicating if itemInfo is a match.
|
|
|
|
_isInfoMatching : function(itemInfo, search) {
|
2009-07-07 20:08:41 +00:00
|
|
|
// Don't show nodisplay items here
|
|
|
|
if (itemInfo.get_is_nodisplay())
|
|
|
|
return false;
|
2009-04-01 19:51:17 +00:00
|
|
|
|
2008-12-01 19:51:43 +00:00
|
|
|
if (search == null || search == '')
|
|
|
|
return true;
|
2009-01-21 21:50:57 +00:00
|
|
|
|
2009-07-07 20:08:41 +00:00
|
|
|
let fold = function(s) {
|
|
|
|
if (!s)
|
|
|
|
return s;
|
|
|
|
return GLib.utf8_casefold(GLib.utf8_normalize(s, -1,
|
|
|
|
GLib.NormalizeMode.ALL), -1);
|
|
|
|
};
|
|
|
|
let name = fold(itemInfo.get_name());
|
2008-12-01 19:51:43 +00:00
|
|
|
if (name.indexOf(search) >= 0)
|
|
|
|
return true;
|
2009-01-21 21:50:57 +00:00
|
|
|
|
2009-07-07 20:08:41 +00:00
|
|
|
let description = fold(itemInfo.get_description());
|
2008-12-01 19:51:43 +00:00
|
|
|
if (description) {
|
|
|
|
if (description.indexOf(search) >= 0)
|
|
|
|
return true;
|
|
|
|
}
|
2009-01-21 21:50:57 +00:00
|
|
|
|
2009-07-07 20:08:41 +00:00
|
|
|
let exec = fold(itemInfo.get_executable());
|
|
|
|
if (exec == null) {
|
2009-06-17 16:37:54 +00:00
|
|
|
log("Missing an executable for " + itemInfo.name);
|
2009-01-22 21:28:19 +00:00
|
|
|
} else {
|
|
|
|
if (exec.indexOf(search) >= 0)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2008-12-01 19:51:43 +00:00
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2009-07-07 20:08:41 +00:00
|
|
|
// Creates an AppDisplayItem based on itemInfo, which is expected be an Shell.AppInfo object.
|
Rewrite Dash, remove hardcoded width/height from GenericDisplay
This patch is a near-total rewrite of the Dash. First, the dash
code moves into a separate file, dash.js.
Inside dash.js, the components are more broken up into separate
classes; in particular there's now a Pane class and a MoreLink
class. Instead of each section of the dash, when activated,
attempting to close all N-1 other sections, instead there
is the concept of a single "active pane", and when e.g. activating
the More link for documents, if we know there's an active pane
which happens to be the apps, close it.
Many redundant containers were removed from the dash, and all
manual width, height and x/y offsets are entirely gone. We move
the visual apperance closer to the design by using the view-more.svg,
etc.
To complete the removal of height/width calculations from the dash,
we also had to do the same for GenericDisplay. Also clean up
the positioning inside overlay.js so calculation of children's
positioning is inside a single function that flows from screen.width
and screen.height, so in the future we can stop passing the width
into the Dash constructor and call this once and work on screen
resizing.
2009-08-01 02:12:01 +00:00
|
|
|
_createDisplayItem: function(itemInfo) {
|
|
|
|
return new AppDisplayItem(itemInfo);
|
2009-04-01 19:51:17 +00:00
|
|
|
}
|
2008-12-01 19:51:43 +00:00
|
|
|
};
|
2008-11-21 00:53:11 +00:00
|
|
|
|
2008-12-01 19:51:43 +00:00
|
|
|
Signals.addSignalMethods(AppDisplay.prototype);
|
2009-06-30 20:35:39 +00:00
|
|
|
|
2009-11-29 22:45:30 +00:00
|
|
|
function BaseAppSearchProvider() {
|
|
|
|
this._init();
|
|
|
|
}
|
|
|
|
|
|
|
|
BaseAppSearchProvider.prototype = {
|
|
|
|
__proto__: Search.SearchProvider.prototype,
|
|
|
|
|
|
|
|
_init: function(name) {
|
|
|
|
Search.SearchProvider.prototype._init.call(this, name);
|
|
|
|
this._appSys = Shell.AppSystem.get_default();
|
|
|
|
},
|
|
|
|
|
|
|
|
getResultMeta: function(resultId) {
|
|
|
|
let app = this._appSys.get_app(resultId);
|
|
|
|
if (!app)
|
|
|
|
return null;
|
|
|
|
return { 'id': resultId,
|
|
|
|
'name': app.get_name(),
|
|
|
|
'icon': app.create_icon_texture(Search.RESULT_ICON_SIZE)};
|
|
|
|
},
|
|
|
|
|
|
|
|
activateResult: function(id) {
|
|
|
|
let app = this._appSys.get_app(id);
|
|
|
|
app.launch();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function AppSearchProvider() {
|
|
|
|
this._init();
|
|
|
|
}
|
|
|
|
|
|
|
|
AppSearchProvider.prototype = {
|
|
|
|
__proto__: BaseAppSearchProvider.prototype,
|
|
|
|
|
|
|
|
_init: function() {
|
|
|
|
BaseAppSearchProvider.prototype._init.call(this, _("APPLICATIONS"));
|
|
|
|
},
|
|
|
|
|
|
|
|
getInitialResultSet: function(terms) {
|
|
|
|
return this._appSys.initial_search(false, terms);
|
|
|
|
},
|
|
|
|
|
|
|
|
getSubsearchResultSet: function(previousResults, terms) {
|
|
|
|
return this._appSys.subsearch(false, previousResults, terms);
|
|
|
|
},
|
|
|
|
|
|
|
|
expandSearch: function(terms) {
|
|
|
|
log("TODO expand search");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function PrefsSearchProvider() {
|
|
|
|
this._init();
|
|
|
|
}
|
|
|
|
|
|
|
|
PrefsSearchProvider.prototype = {
|
|
|
|
__proto__: BaseAppSearchProvider.prototype,
|
|
|
|
|
|
|
|
_init: function() {
|
|
|
|
BaseAppSearchProvider.prototype._init.call(this, _("PREFERENCES"));
|
|
|
|
},
|
|
|
|
|
|
|
|
getInitialResultSet: function(terms) {
|
|
|
|
return this._appSys.initial_search(true, terms);
|
|
|
|
},
|
|
|
|
|
|
|
|
getSubsearchResultSet: function(previousResults, terms) {
|
|
|
|
return this._appSys.subsearch(true, previousResults, terms);
|
|
|
|
},
|
|
|
|
|
|
|
|
expandSearch: function(terms) {
|
|
|
|
let controlCenter = this._appSys.load_from_desktop_file('gnomecc.desktop');
|
|
|
|
controlCenter.launch();
|
|
|
|
Main.overview.hide();
|
|
|
|
}
|
|
|
|
}
|
2009-11-12 22:46:59 +00:00
|
|
|
|
2009-12-08 17:51:05 +00:00
|
|
|
function AppIcon(app) {
|
|
|
|
this._init(app);
|
2009-09-01 18:15:29 +00:00
|
|
|
}
|
|
|
|
|
2009-12-08 17:51:05 +00:00
|
|
|
AppIcon.prototype = {
|
|
|
|
_init : function(app) {
|
2009-11-12 22:46:59 +00:00
|
|
|
this.app = app;
|
2009-09-11 21:13:50 +00:00
|
|
|
|
2009-12-08 17:51:05 +00:00
|
|
|
this.actor = new St.Bin({ style_class: 'app-icon',
|
|
|
|
x_fill: true,
|
|
|
|
y_fill: true });
|
2009-11-12 22:46:59 +00:00
|
|
|
this.actor._delegate = this;
|
|
|
|
|
|
|
|
let box = new St.BoxLayout({ vertical: true });
|
|
|
|
this.actor.set_child(box);
|
|
|
|
|
|
|
|
this.icon = this.app.create_icon_texture(APPICON_SIZE);
|
|
|
|
|
|
|
|
box.add(this.icon, { expand: true, x_fill: false, y_fill: false });
|
|
|
|
|
|
|
|
this._name = new St.Label({ text: this.app.get_name() });
|
|
|
|
this._name.clutter_text.line_alignment = Pango.Alignment.CENTER;
|
2010-01-07 05:40:21 +00:00
|
|
|
box.add_actor(this._name);
|
2009-12-08 17:51:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function AppWellIcon(app) {
|
|
|
|
this._init(app);
|
|
|
|
}
|
|
|
|
|
|
|
|
AppWellIcon.prototype = {
|
|
|
|
_init : function(app) {
|
|
|
|
this.app = app;
|
2010-01-07 05:40:21 +00:00
|
|
|
this._running = false;
|
2009-12-08 17:51:05 +00:00
|
|
|
this.actor = new St.Clickable({ style_class: 'app-well-app',
|
|
|
|
reactive: true,
|
|
|
|
x_fill: true,
|
|
|
|
y_fill: true });
|
|
|
|
this.actor._delegate = this;
|
|
|
|
|
|
|
|
this._icon = new AppIcon(app);
|
|
|
|
this.actor.set_child(this._icon.actor);
|
|
|
|
|
|
|
|
this.actor.connect('clicked', Lang.bind(this, this._onClicked));
|
|
|
|
this._menu = null;
|
|
|
|
|
|
|
|
this._draggable = DND.makeDraggable(this.actor, true);
|
|
|
|
this._dragStartX = null;
|
|
|
|
this._dragStartY = null;
|
|
|
|
|
|
|
|
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
|
|
|
this.actor.connect('notify::hover', Lang.bind(this, this._onHoverChange));
|
2010-01-07 05:40:21 +00:00
|
|
|
this.actor.connect('show', Lang.bind(this, this._onShow));
|
|
|
|
this.actor.connect('hide', Lang.bind(this, this._onHideDestroy));
|
|
|
|
this.actor.connect('destroy', Lang.bind(this, this._onHideDestroy));
|
|
|
|
|
|
|
|
this._appWindowChangedId = 0;
|
|
|
|
},
|
|
|
|
|
|
|
|
_onShow: function() {
|
|
|
|
this._appWindowChangedId = this.app.connect('windows-changed',
|
|
|
|
Lang.bind(this,
|
|
|
|
this._updateStyleClass));
|
|
|
|
this._updateStyleClass();
|
|
|
|
},
|
|
|
|
|
|
|
|
_onHideDestroy: function() {
|
|
|
|
if (this._appWindowChangedId > 0)
|
|
|
|
this.app.disconnect(this._appWindowChangedId);
|
|
|
|
},
|
|
|
|
|
|
|
|
_updateStyleClass: function() {
|
|
|
|
let windows = this.app.get_windows();
|
|
|
|
let running = windows.length > 0;
|
|
|
|
if (running == this._running)
|
|
|
|
return;
|
|
|
|
this._running = running;
|
|
|
|
this.actor.style_class = this._running ? "app-well-app running"
|
|
|
|
: "app-well-app";
|
2009-11-12 22:46:59 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onButtonPress: function(actor, event) {
|
|
|
|
let [stageX, stageY] = event.get_coords();
|
|
|
|
this._dragStartX = stageX;
|
|
|
|
this._dragStartY = stageY;
|
|
|
|
},
|
|
|
|
|
|
|
|
_onHoverChange: function(actor) {
|
|
|
|
let hover = this.actor.hover;
|
|
|
|
if (!hover) {
|
|
|
|
if (this.actor.pressed && this._dragStartX != null) {
|
|
|
|
this.actor.fake_release();
|
|
|
|
this._draggable.startDrag(this._dragStartX, this._dragStartY,
|
2009-12-03 20:59:52 +00:00
|
|
|
global.get_current_time());
|
2009-11-12 22:46:59 +00:00
|
|
|
} else {
|
|
|
|
this._dragStartX = null;
|
|
|
|
this._dragStartY = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_onClicked: function(actor, event) {
|
|
|
|
let button = event.get_button();
|
|
|
|
if (button == 1) {
|
|
|
|
this._onActivate(event);
|
|
|
|
} else if (button == 3) {
|
|
|
|
// Don't bind to the right click here; we want left click outside the
|
|
|
|
// area to deactivate as well.
|
|
|
|
this.popupMenu(0);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
popupMenu: function(activatingButton) {
|
|
|
|
if (!this._menu) {
|
|
|
|
this._menu = new AppIconMenu(this);
|
|
|
|
this._menu.connect('highlight-window', Lang.bind(this, function (menu, window) {
|
|
|
|
this.highlightWindow(window);
|
|
|
|
}));
|
|
|
|
this._menu.connect('activate-window', Lang.bind(this, function (menu, window) {
|
|
|
|
this.activateWindow(window);
|
|
|
|
}));
|
|
|
|
this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) {
|
|
|
|
if (isPoppedUp) {
|
|
|
|
this._onMenuPoppedUp();
|
2009-09-08 19:53:49 +00:00
|
|
|
} else {
|
2009-11-12 22:46:59 +00:00
|
|
|
this._onMenuPoppedDown();
|
2009-09-08 19:53:49 +00:00
|
|
|
}
|
2009-11-12 22:46:59 +00:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
this._menu.popup(activatingButton);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2009-12-08 17:51:05 +00:00
|
|
|
activateMostRecentWindow: function () {
|
|
|
|
let mostRecentWindow = this.app.get_windows()[0];
|
2009-12-18 22:35:06 +00:00
|
|
|
Main.overview.activateWindow(mostRecentWindow, global.get_current_time());
|
2009-12-08 17:51:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
highlightWindow: function(metaWindow) {
|
2010-01-17 13:48:29 +00:00
|
|
|
if (this._didActivateWindow)
|
|
|
|
return;
|
2009-12-08 17:51:05 +00:00
|
|
|
if (!this._getRunning())
|
|
|
|
return;
|
|
|
|
Main.overview.getWorkspacesForWindow(metaWindow).setHighlightWindow(metaWindow);
|
|
|
|
},
|
|
|
|
|
|
|
|
activateWindow: function(metaWindow) {
|
|
|
|
if (metaWindow) {
|
|
|
|
this._didActivateWindow = true;
|
2009-12-18 22:35:06 +00:00
|
|
|
Main.overview.activateWindow(metaWindow, global.get_current_time());
|
2009-12-08 17:51:05 +00:00
|
|
|
} else
|
|
|
|
Main.overview.hide();
|
|
|
|
},
|
|
|
|
|
|
|
|
_onMenuPoppedUp: function() {
|
|
|
|
if (this._getRunning()) {
|
|
|
|
Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(this.app.get_id());
|
|
|
|
this._setWindowSelection = true;
|
2010-01-17 13:48:29 +00:00
|
|
|
this._didActivateWindow = false;
|
2009-12-08 17:51:05 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_onMenuPoppedDown: function() {
|
|
|
|
if (this._didActivateWindow)
|
|
|
|
return;
|
|
|
|
if (!this._setWindowSelection)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(null);
|
|
|
|
this._setWindowSelection = false;
|
2009-11-12 22:46:59 +00:00
|
|
|
},
|
|
|
|
|
2009-12-08 17:51:05 +00:00
|
|
|
_getRunning: function() {
|
|
|
|
return this.app.get_windows().length > 0;
|
|
|
|
},
|
|
|
|
|
|
|
|
_onActivate: function (event) {
|
|
|
|
let running = this._getRunning();
|
|
|
|
|
|
|
|
if (!running) {
|
|
|
|
this.app.launch();
|
2010-01-25 19:13:21 +00:00
|
|
|
Main.overview.hide();
|
2009-12-08 17:51:05 +00:00
|
|
|
} else {
|
|
|
|
let modifiers = Shell.get_event_state(event);
|
|
|
|
|
|
|
|
if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
|
|
|
|
this.app.launch();
|
2010-01-25 19:13:21 +00:00
|
|
|
Main.overview.hide();
|
2009-12-08 17:51:05 +00:00
|
|
|
} else {
|
|
|
|
this.activateMostRecentWindow();
|
|
|
|
}
|
|
|
|
}
|
2009-09-01 18:15:29 +00:00
|
|
|
},
|
|
|
|
|
2009-08-18 00:29:54 +00:00
|
|
|
shellWorkspaceLaunch : function() {
|
2009-08-21 00:03:28 +00:00
|
|
|
// Here we just always launch the application again, even if we know
|
|
|
|
// it was already running. For most applications this
|
|
|
|
// should have the effect of creating a new window, whether that's
|
|
|
|
// a second process (in the case of Calculator) or IPC to existing
|
|
|
|
// instance (Firefox). There are a few less-sensical cases such
|
|
|
|
// 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.
|
2009-10-15 23:28:29 +00:00
|
|
|
this.app.launch();
|
2009-08-18 00:29:54 +00:00
|
|
|
},
|
|
|
|
|
2009-09-11 21:13:50 +00:00
|
|
|
getDragActor: function() {
|
2009-11-12 22:46:59 +00:00
|
|
|
return this.app.create_icon_texture(APPICON_SIZE);
|
2009-06-30 20:35:39 +00:00
|
|
|
},
|
|
|
|
|
2010-02-04 21:57:38 +00:00
|
|
|
// Returns the original actor that should align with the actor
|
|
|
|
// we show as the item is being dragged.
|
2009-06-30 20:35:39 +00:00
|
|
|
getDragActorSource: function() {
|
2010-02-04 21:57:38 +00:00
|
|
|
return this._icon.icon;
|
2009-09-01 18:15:29 +00:00
|
|
|
}
|
|
|
|
}
|
2009-12-08 17:51:05 +00:00
|
|
|
Signals.addSignalMethods(AppWellIcon.prototype);
|
2009-11-12 22:46:59 +00:00
|
|
|
|
|
|
|
function AppIconMenu(source) {
|
|
|
|
this._init(source);
|
|
|
|
}
|
|
|
|
|
|
|
|
AppIconMenu.prototype = {
|
|
|
|
_init: function(source) {
|
|
|
|
this._source = source;
|
|
|
|
|
|
|
|
this._arrowSize = 4; // CSS default
|
|
|
|
this._spacing = 0; // CSS default
|
|
|
|
|
|
|
|
this._dragStartX = 0;
|
|
|
|
this._dragStartY = 0;
|
|
|
|
|
|
|
|
this.actor = new Shell.GenericContainer({ reactive: true });
|
|
|
|
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
|
|
|
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
|
|
|
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
|
|
|
|
|
|
|
this._windowContainerBox = new St.Bin({ style_class: 'app-well-menu' });
|
|
|
|
this._windowContainer = new Shell.Menu({ orientation: Big.BoxOrientation.VERTICAL,
|
|
|
|
width: Main.overview._dash.actor.width });
|
|
|
|
this._windowContainerBox.set_child(this._windowContainer);
|
|
|
|
this._windowContainer.connect('unselected', Lang.bind(this, this._onItemUnselected));
|
|
|
|
this._windowContainer.connect('selected', Lang.bind(this, this._onItemSelected));
|
|
|
|
this._windowContainer.connect('cancelled', Lang.bind(this, this._onWindowSelectionCancelled));
|
|
|
|
this._windowContainer.connect('activate', Lang.bind(this, this._onItemActivate));
|
|
|
|
this.actor.add_actor(this._windowContainerBox);
|
|
|
|
|
|
|
|
// Stay popped up on release over application icon
|
|
|
|
this._windowContainer.set_persistent_source(this._source.actor);
|
|
|
|
|
|
|
|
// Intercept events while the menu has the pointer grab to do window-related effects
|
|
|
|
this._windowContainer.connect('enter-event', Lang.bind(this, this._onMenuEnter));
|
|
|
|
this._windowContainer.connect('leave-event', Lang.bind(this, this._onMenuLeave));
|
|
|
|
this._windowContainer.connect('button-release-event', Lang.bind(this, this._onMenuButtonRelease));
|
|
|
|
|
|
|
|
this._borderColor = new Clutter.Color();
|
|
|
|
this._backgroundColor = new Clutter.Color();
|
|
|
|
this._windowContainerBox.connect('style-changed', Lang.bind(this, this._onStyleChanged));
|
|
|
|
|
|
|
|
this._arrow = new St.DrawingArea();
|
|
|
|
this._arrow.connect('redraw', Lang.bind(this, function (area, texture) {
|
|
|
|
Shell.draw_box_pointer(texture,
|
|
|
|
Shell.PointerDirection.LEFT,
|
|
|
|
this._borderColor,
|
|
|
|
this._backgroundColor);
|
|
|
|
}));
|
|
|
|
this.actor.add_actor(this._arrow);
|
|
|
|
|
|
|
|
// Chain our visibility and lifecycle to that of the source
|
|
|
|
source.actor.connect('notify::mapped', Lang.bind(this, function () {
|
|
|
|
if (!source.actor.mapped)
|
|
|
|
this._windowContainer.popdown();
|
|
|
|
}));
|
|
|
|
source.actor.connect('destroy', Lang.bind(this, function () { this.actor.destroy(); }));
|
|
|
|
|
|
|
|
global.stage.add_actor(this.actor);
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredWidth: function(actor, forHeight, alloc) {
|
|
|
|
let [min, natural] = this._windowContainerBox.get_preferred_width(forHeight);
|
|
|
|
min += this._arrowSize;
|
|
|
|
natural += this._arrowSize;
|
|
|
|
alloc.min_size = min;
|
|
|
|
alloc.natural_size = natural;
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredHeight: function(actor, forWidth, alloc) {
|
|
|
|
let [min, natural] = this._windowContainerBox.get_preferred_height(forWidth);
|
|
|
|
alloc.min_size = min;
|
|
|
|
alloc.natural_size = natural;
|
|
|
|
},
|
|
|
|
|
|
|
|
_allocate: function(actor, box, flags) {
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
|
|
let themeNode = this._windowContainerBox.get_theme_node();
|
|
|
|
|
|
|
|
let width = box.x2 - box.x1;
|
|
|
|
let height = box.y2 - box.y1;
|
|
|
|
|
|
|
|
childBox.x1 = 0;
|
|
|
|
childBox.x2 = this._arrowSize;
|
|
|
|
childBox.y1 = Math.floor((height / 2) - (this._arrowSize / 2));
|
|
|
|
childBox.y2 = childBox.y1 + this._arrowSize;
|
|
|
|
this._arrow.allocate(childBox, flags);
|
|
|
|
|
|
|
|
// Ensure the arrow is above the border area
|
|
|
|
let border = themeNode.get_border_width(St.Side.LEFT);
|
|
|
|
childBox.x1 = this._arrowSize - border;
|
|
|
|
childBox.x2 = width;
|
|
|
|
childBox.y1 = 0;
|
|
|
|
childBox.y2 = height;
|
|
|
|
this._windowContainerBox.allocate(childBox, flags);
|
|
|
|
},
|
|
|
|
|
|
|
|
_redisplay: function() {
|
|
|
|
this._windowContainer.remove_all();
|
|
|
|
|
|
|
|
let windows = this._source.app.get_windows();
|
|
|
|
|
|
|
|
this._windowContainer.show();
|
|
|
|
|
|
|
|
let iconsDiffer = false;
|
|
|
|
let texCache = Shell.TextureCache.get_default();
|
|
|
|
if (windows.length > 0) {
|
|
|
|
let firstIcon = windows[0].mini_icon;
|
|
|
|
for (let i = 1; i < windows.length; i++) {
|
|
|
|
if (!texCache.pixbuf_equal(windows[i].mini_icon, firstIcon)) {
|
|
|
|
iconsDiffer = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Display the app windows menu items and the separator between windows
|
|
|
|
// of the current desktop and other windows.
|
|
|
|
let activeWorkspace = global.screen.get_active_workspace();
|
|
|
|
let separatorShown = windows.length > 0 && windows[0].get_workspace() != activeWorkspace;
|
|
|
|
|
|
|
|
for (let i = 0; i < windows.length; i++) {
|
|
|
|
if (!separatorShown && windows[i].get_workspace() != activeWorkspace) {
|
|
|
|
this._appendSeparator();
|
|
|
|
separatorShown = true;
|
|
|
|
}
|
|
|
|
let box = this._appendMenuItem(windows[i].title);
|
|
|
|
box._window = windows[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (windows.length > 0)
|
|
|
|
this._appendSeparator();
|
|
|
|
|
|
|
|
let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id());
|
|
|
|
|
|
|
|
this._newWindowMenuItem = windows.length > 0 ? this._appendMenuItem(_("New Window")) : null;
|
|
|
|
|
|
|
|
if (windows.length > 0)
|
|
|
|
this._appendSeparator();
|
|
|
|
this._toggleFavoriteMenuItem = this._appendMenuItem(isFavorite ? _("Remove from Favorites")
|
|
|
|
: _("Add to Favorites"));
|
|
|
|
|
|
|
|
this._highlightedItem = null;
|
|
|
|
},
|
|
|
|
|
|
|
|
_appendSeparator: function () {
|
|
|
|
let bin = new St.Bin({ style_class: "app-well-menu-separator" });
|
|
|
|
this._windowContainer.append_separator(bin, Big.BoxPackFlags.NONE);
|
|
|
|
},
|
|
|
|
|
|
|
|
_appendMenuItem: function(labelText) {
|
|
|
|
let box = new St.BoxLayout({ style_class: 'app-well-menu-item',
|
|
|
|
reactive: true });
|
|
|
|
let label = new St.Label({ text: labelText });
|
|
|
|
box.add(label);
|
|
|
|
this._windowContainer.append(box, Big.BoxPackFlags.NONE);
|
|
|
|
return box;
|
|
|
|
},
|
|
|
|
|
|
|
|
popup: function(activatingButton) {
|
|
|
|
let [stageX, stageY] = this._source.actor.get_transformed_position();
|
|
|
|
let [stageWidth, stageHeight] = this._source.actor.get_transformed_size();
|
|
|
|
|
|
|
|
this._redisplay();
|
|
|
|
|
2009-12-03 20:59:52 +00:00
|
|
|
this._windowContainer.popup(activatingButton, global.get_current_time());
|
2009-11-12 22:46:59 +00:00
|
|
|
|
|
|
|
this.emit('popup', true);
|
|
|
|
|
|
|
|
let x, y;
|
|
|
|
x = Math.floor(stageX + stageWidth);
|
|
|
|
y = Math.floor(stageY + (stageHeight / 2) - (this.actor.height / 2));
|
|
|
|
|
|
|
|
this.actor.set_position(x, y);
|
|
|
|
this.actor.show();
|
|
|
|
},
|
|
|
|
|
|
|
|
popdown: function() {
|
|
|
|
this._windowContainer.popdown();
|
|
|
|
this.emit('popup', false);
|
|
|
|
this.actor.hide();
|
|
|
|
},
|
|
|
|
|
|
|
|
selectWindow: function(metaWindow) {
|
|
|
|
this._selectMenuItemForWindow(metaWindow);
|
|
|
|
},
|
|
|
|
|
|
|
|
_findMetaWindowForActor: function (actor) {
|
2010-01-22 02:33:48 +00:00
|
|
|
if (actor._delegate instanceof Workspace.WindowClone)
|
2009-11-12 22:46:59 +00:00
|
|
|
return actor._delegate.metaWindow;
|
|
|
|
else if (actor.get_meta_window)
|
|
|
|
return actor.get_meta_window();
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
|
|
|
// This function is called while the menu has a pointer grab; what we want
|
|
|
|
// to do is see if the mouse was released over a window representation
|
|
|
|
_onMenuButtonRelease: function (actor, event) {
|
|
|
|
let metaWindow = this._findMetaWindowForActor(event.get_source());
|
|
|
|
if (metaWindow) {
|
|
|
|
this.emit('activate-window', metaWindow);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_updateHighlight: function (item) {
|
|
|
|
if (this._highlightedItem) {
|
|
|
|
this._highlightedItem.set_style_pseudo_class(null);
|
|
|
|
this.emit('highlight-window', null);
|
|
|
|
}
|
|
|
|
this._highlightedItem = item;
|
|
|
|
if (this._highlightedItem) {
|
|
|
|
item.set_style_pseudo_class('hover');
|
|
|
|
let window = this._highlightedItem._window;
|
|
|
|
if (window)
|
|
|
|
this.emit('highlight-window', window);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_selectMenuItemForWindow: function (metaWindow) {
|
|
|
|
let children = this._windowContainer.get_children();
|
|
|
|
for (let i = 0; i < children.length; i++) {
|
|
|
|
let child = children[i];
|
|
|
|
let menuMetaWindow = child._window;
|
|
|
|
if (menuMetaWindow == metaWindow)
|
|
|
|
this._updateHighlight(child);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// Called while menu has a pointer grab
|
|
|
|
_onMenuEnter: function (actor, event) {
|
|
|
|
let metaWindow = this._findMetaWindowForActor(event.get_source());
|
|
|
|
if (metaWindow) {
|
|
|
|
this._selectMenuItemForWindow(metaWindow);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// Called while menu has a pointer grab
|
|
|
|
_onMenuLeave: function (actor, event) {
|
|
|
|
let metaWindow = this._findMetaWindowForActor(event.get_source());
|
|
|
|
if (metaWindow) {
|
|
|
|
this._updateHighlight(null);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_onItemUnselected: function (actor, child) {
|
|
|
|
this._updateHighlight(null);
|
|
|
|
},
|
|
|
|
|
|
|
|
_onItemSelected: function (actor, child) {
|
|
|
|
this._updateHighlight(child);
|
|
|
|
},
|
|
|
|
|
|
|
|
_onItemActivate: function (actor, child) {
|
|
|
|
if (child._window) {
|
|
|
|
let metaWindow = child._window;
|
|
|
|
this.emit('activate-window', metaWindow);
|
|
|
|
} else if (child == this._newWindowMenuItem) {
|
|
|
|
this._source.app.launch();
|
|
|
|
this.emit('activate-window', null);
|
|
|
|
} else if (child == this._toggleFavoriteMenuItem) {
|
|
|
|
let favs = AppFavorites.getAppFavorites();
|
|
|
|
let isFavorite = favs.isFavorite(this._source.app.get_id());
|
|
|
|
if (isFavorite)
|
|
|
|
favs.removeFavorite(this._source.app.get_id());
|
|
|
|
else
|
|
|
|
favs.addFavorite(this._source.app.get_id());
|
|
|
|
}
|
|
|
|
this.popdown();
|
|
|
|
},
|
|
|
|
|
|
|
|
_onWindowSelectionCancelled: function () {
|
|
|
|
this.emit('highlight-window', null);
|
|
|
|
this.popdown();
|
|
|
|
},
|
|
|
|
|
|
|
|
_onStyleChanged: function() {
|
|
|
|
let themeNode = this._windowContainerBox.get_theme_node();
|
|
|
|
let [success, len] = themeNode.get_length('-shell-arrow-size', false);
|
|
|
|
if (success) {
|
|
|
|
this._arrowSize = len;
|
|
|
|
this.actor.queue_relayout();
|
|
|
|
}
|
|
|
|
[success, len] = themeNode.get_length('-shell-menu-spacing', false)
|
|
|
|
if (success) {
|
|
|
|
this._windowContainer.spacing = len;
|
|
|
|
}
|
|
|
|
let color = new Clutter.Color();
|
|
|
|
if (themeNode.get_background_color(color)) {
|
|
|
|
this._backgroundColor = color;
|
|
|
|
color = new Clutter.Color();
|
|
|
|
}
|
|
|
|
if (themeNode.get_border_color(St.Side.LEFT, color)) {
|
|
|
|
this._borderColor = color;
|
|
|
|
}
|
|
|
|
this._arrow.emit_redraw();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Signals.addSignalMethods(AppIconMenu.prototype);
|
2009-09-01 18:15:29 +00:00
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
function WellGrid() {
|
|
|
|
this._init();
|
2009-06-30 20:35:39 +00:00
|
|
|
}
|
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
WellGrid.prototype = {
|
|
|
|
_init: function() {
|
2009-11-12 22:46:59 +00:00
|
|
|
this.actor = new St.Bin({ name: "dashAppWell" });
|
|
|
|
// Pulled from CSS, but hardcode some defaults here
|
|
|
|
this._spacing = 0;
|
|
|
|
this._item_size = 48;
|
|
|
|
this._grid = new Shell.GenericContainer();
|
|
|
|
this.actor.set_child(this._grid);
|
|
|
|
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChanged));
|
|
|
|
|
|
|
|
this._grid.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
|
|
|
this._grid.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
|
|
|
this._grid.connect('allocate', Lang.bind(this, this._allocate));
|
2009-06-30 20:35:39 +00:00
|
|
|
},
|
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
_getPreferredWidth: function (grid, forHeight, alloc) {
|
2009-11-12 22:46:59 +00:00
|
|
|
let children = this._grid.get_children();
|
|
|
|
let nColumns = children.length;
|
|
|
|
let totalSpacing = Math.max(0, nColumns - 1) * this._spacing;
|
|
|
|
// Kind of a lie, but not really an issue right now. If
|
|
|
|
// we wanted to support some sort of hidden/overflow that would
|
|
|
|
// need higher level design
|
|
|
|
alloc.min_size = this._item_size;
|
|
|
|
alloc.natural_size = nColumns * this._item_size + totalSpacing;
|
2009-08-06 19:39:09 +00:00
|
|
|
},
|
2009-06-30 20:35:39 +00:00
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
_getPreferredHeight: function (grid, forWidth, alloc) {
|
2009-11-12 22:46:59 +00:00
|
|
|
let children = this._grid.get_children();
|
|
|
|
let [nColumns, usedWidth] = this._computeLayout(forWidth);
|
|
|
|
let nRows;
|
|
|
|
if (nColumns > 0)
|
|
|
|
nRows = Math.ceil(children.length / nColumns);
|
|
|
|
else
|
|
|
|
nRows = 0;
|
|
|
|
let totalSpacing = Math.max(0, nRows - 1) * this._spacing;
|
|
|
|
let height = nRows * this._item_size + totalSpacing;
|
|
|
|
alloc.min_size = height;
|
|
|
|
alloc.natural_size = height;
|
2009-08-06 19:39:09 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_allocate: function (grid, box, flags) {
|
2009-11-12 22:46:59 +00:00
|
|
|
let children = this._grid.get_children();
|
2009-08-06 19:39:09 +00:00
|
|
|
let availWidth = box.x2 - box.x1;
|
|
|
|
let availHeight = box.y2 - box.y1;
|
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
let [nColumns, usedWidth] = this._computeLayout(availWidth);
|
2009-08-06 19:39:09 +00:00
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
let overallPaddingX = Math.floor((availWidth - usedWidth) / 2);
|
2009-08-06 19:39:09 +00:00
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
let x = box.x1 + overallPaddingX;
|
2009-08-06 19:39:09 +00:00
|
|
|
let y = box.y1;
|
|
|
|
let columnIndex = 0;
|
|
|
|
for (let i = 0; i < children.length; i++) {
|
2009-11-12 22:46:59 +00:00
|
|
|
let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight]
|
|
|
|
= children[i].get_preferred_size();
|
2009-08-06 19:39:09 +00:00
|
|
|
|
2009-08-14 08:35:48 +00:00
|
|
|
/* Center the item in its allocation horizontally */
|
2009-11-12 22:46:59 +00:00
|
|
|
let width = Math.min(this._item_size, childNaturalWidth);
|
|
|
|
let childXSpacing = Math.max(0, width - childNaturalWidth) / 2;
|
|
|
|
let height = Math.min(this._item_size, childNaturalHeight);
|
|
|
|
let childYSpacing = Math.max(0, height - childNaturalHeight) / 2;
|
2009-08-06 19:39:09 +00:00
|
|
|
|
|
|
|
let childBox = new Clutter.ActorBox();
|
2009-11-12 22:46:59 +00:00
|
|
|
childBox.x1 = Math.floor(x + childXSpacing);
|
|
|
|
childBox.y1 = Math.floor(y + childYSpacing);
|
2009-08-06 19:39:09 +00:00
|
|
|
childBox.x2 = childBox.x1 + width;
|
2009-11-12 22:46:59 +00:00
|
|
|
childBox.y2 = childBox.y1 + height;
|
2009-08-06 19:39:09 +00:00
|
|
|
children[i].allocate(childBox, flags);
|
|
|
|
|
|
|
|
columnIndex++;
|
2009-11-12 22:46:59 +00:00
|
|
|
if (columnIndex == nColumns) {
|
2009-08-06 19:39:09 +00:00
|
|
|
columnIndex = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (columnIndex == 0) {
|
2009-11-12 22:46:59 +00:00
|
|
|
y += this._item_size + this._spacing;
|
|
|
|
x = box.x1 + overallPaddingX;
|
2009-08-06 19:39:09 +00:00
|
|
|
} else {
|
2009-11-12 22:46:59 +00:00
|
|
|
x += this._item_size + this._spacing;
|
2009-08-06 19:39:09 +00:00
|
|
|
}
|
2009-07-07 20:08:41 +00:00
|
|
|
}
|
2009-07-04 21:28:34 +00:00
|
|
|
},
|
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
_computeLayout: function (forWidth) {
|
2009-11-12 22:46:59 +00:00
|
|
|
let children = this._grid.get_children();
|
2009-09-24 04:01:57 +00:00
|
|
|
let nColumns = 0;
|
|
|
|
let usedWidth = 0;
|
2009-11-12 22:46:59 +00:00
|
|
|
while (nColumns < WELL_MAX_COLUMNS &&
|
|
|
|
nColumns < children.length &&
|
|
|
|
(usedWidth + this._item_size <= forWidth)) {
|
|
|
|
usedWidth += this._item_size + this._spacing;
|
|
|
|
nColumns += 1;
|
2009-09-24 04:01:57 +00:00
|
|
|
}
|
2009-08-06 19:39:09 +00:00
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
if (nColumns > 0)
|
|
|
|
usedWidth -= this._spacing;
|
2009-07-02 04:35:26 +00:00
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
return [nColumns, usedWidth];
|
|
|
|
},
|
2009-06-30 20:35:39 +00:00
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
_onStyleChanged: function() {
|
|
|
|
let themeNode = this.actor.get_theme_node();
|
|
|
|
let [success, len] = themeNode.get_length('spacing', false);
|
|
|
|
if (success)
|
|
|
|
this._spacing = len;
|
|
|
|
[success, len] = themeNode.get_length('-shell-grid-item-size', false);
|
|
|
|
if (success)
|
|
|
|
this._item_size = len;
|
|
|
|
this._grid.queue_relayout();
|
|
|
|
},
|
2009-08-06 19:39:09 +00:00
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
removeAll: function () {
|
|
|
|
this._grid.get_children().forEach(Lang.bind(this, function (child) {
|
|
|
|
child.destroy();
|
|
|
|
}));
|
2009-08-06 19:39:09 +00:00
|
|
|
},
|
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
addItem: function(actor) {
|
|
|
|
this._grid.add_actor(actor);
|
2009-06-30 20:35:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
function AppWell() {
|
|
|
|
this._init();
|
2009-06-30 20:35:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
AppWell.prototype = {
|
2009-08-06 19:39:09 +00:00
|
|
|
_init : function() {
|
2009-06-30 20:35:39 +00:00
|
|
|
this._menus = [];
|
|
|
|
this._menuDisplays = [];
|
|
|
|
|
2009-10-15 23:28:29 +00:00
|
|
|
this._favorites = [];
|
|
|
|
|
2009-06-30 20:35:39 +00:00
|
|
|
this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
2009-08-06 19:39:09 +00:00
|
|
|
x_align: Big.BoxAlignment.CENTER });
|
|
|
|
this.actor._delegate = this;
|
|
|
|
|
2009-12-03 17:19:38 +00:00
|
|
|
this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay));
|
2009-10-03 00:17:34 +00:00
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
this._grid = new WellGrid();
|
|
|
|
this.actor.append(this._grid.actor, Big.BoxPackFlags.EXPAND);
|
2009-06-30 20:35:39 +00:00
|
|
|
|
2009-10-15 23:28:29 +00:00
|
|
|
this._tracker = Shell.WindowTracker.get_default();
|
2009-06-30 20:35:39 +00:00
|
|
|
this._appSystem = Shell.AppSystem.get_default();
|
|
|
|
|
2009-12-03 17:19:38 +00:00
|
|
|
this._appSystem.connect('installed-changed', Lang.bind(this, this._queueRedisplay));
|
|
|
|
AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._queueRedisplay));
|
|
|
|
this._tracker.connect('app-running-changed', Lang.bind(this, this._queueRedisplay));
|
2009-06-30 20:35:39 +00:00
|
|
|
},
|
|
|
|
|
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
2009-10-11 20:40:00 +00:00
|
|
|
_appIdListToHash: function(apps) {
|
|
|
|
let ids = {};
|
|
|
|
for (let i = 0; i < apps.length; i++)
|
|
|
|
ids[apps[i].get_id()] = apps[i];
|
|
|
|
return ids;
|
2009-08-06 19:39:09 +00:00
|
|
|
},
|
|
|
|
|
2009-12-03 17:19:38 +00:00
|
|
|
_queueRedisplay: function () {
|
|
|
|
Main.queueDeferredWork(this._workId);
|
2009-10-03 00:17:34 +00:00
|
|
|
},
|
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
_redisplay: function () {
|
|
|
|
this._grid.removeAll();
|
2009-07-30 19:40:26 +00:00
|
|
|
|
2009-10-15 23:28:29 +00:00
|
|
|
let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
|
2009-08-06 19:39:09 +00:00
|
|
|
|
|
|
|
/* hardcode here pending some design about how exactly desktop contexts behave */
|
|
|
|
let contextId = "";
|
2009-07-30 19:40:26 +00:00
|
|
|
|
2009-10-15 23:28:29 +00:00
|
|
|
let running = this._tracker.get_running_apps(contextId);
|
2009-10-14 21:25:17 +00:00
|
|
|
let runningIds = this._appIdListToHash(running);
|
2009-08-06 19:39:09 +00:00
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
let nFavorites = 0;
|
2009-10-15 23:28:29 +00:00
|
|
|
for (let id in favorites) {
|
|
|
|
let app = favorites[id];
|
2009-12-08 17:51:05 +00:00
|
|
|
let display = new AppWellIcon(app);
|
2009-11-12 22:46:59 +00:00
|
|
|
this._grid.addItem(display.actor);
|
|
|
|
nFavorites++;
|
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
2009-10-11 20:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i < running.length; i++) {
|
|
|
|
let app = running[i];
|
2009-10-15 23:28:29 +00:00
|
|
|
if (app.get_id() in favorites)
|
2009-10-14 19:36:58 +00:00
|
|
|
continue;
|
2009-12-08 17:51:05 +00:00
|
|
|
let display = new AppWellIcon(app);
|
2009-11-12 22:46:59 +00:00
|
|
|
this._grid.addItem(display.actor);
|
2009-08-06 19:39:09 +00:00
|
|
|
}
|
2009-10-27 11:54:23 +00:00
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
if (running.length == 0 && nFavorites == 0) {
|
|
|
|
let text = new St.Label({ text: _("Drag here to add favorites")});
|
|
|
|
this._grid.actor.set_child(text);
|
2009-10-27 11:54:23 +00:00
|
|
|
}
|
2009-08-06 19:39:09 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
// Draggable target interface
|
|
|
|
acceptDrop : function(source, actor, x, y, time) {
|
2009-08-14 08:35:48 +00:00
|
|
|
let app = null;
|
2009-09-25 20:20:43 +00:00
|
|
|
if (source instanceof AppDisplayItem) {
|
2009-10-15 23:28:29 +00:00
|
|
|
app = this._appSystem.get_app(source.getId());
|
2010-01-22 02:33:48 +00:00
|
|
|
} else if (source instanceof Workspace.WindowClone) {
|
2009-10-15 23:28:29 +00:00
|
|
|
app = this._tracker.get_window_app(source.metaWindow);
|
2009-08-06 19:39:09 +00:00
|
|
|
}
|
|
|
|
|
2009-08-14 08:35:48 +00:00
|
|
|
// Don't allow favoriting of transient apps
|
|
|
|
if (app == null || app.is_transient()) {
|
2009-08-06 19:39:09 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2009-08-14 08:35:48 +00:00
|
|
|
let id = app.get_id();
|
|
|
|
|
2009-10-15 23:28:29 +00:00
|
|
|
let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
|
2009-08-06 19:39:09 +00:00
|
|
|
|
2009-10-15 23:28:29 +00:00
|
|
|
let srcIsFavorite = (id in favorites);
|
2009-08-06 19:39:09 +00:00
|
|
|
|
2009-09-25 20:20:43 +00:00
|
|
|
if (srcIsFavorite) {
|
|
|
|
return false;
|
|
|
|
} else {
|
2009-10-15 23:28:29 +00:00
|
|
|
Mainloop.idle_add(Lang.bind(this, function () {
|
|
|
|
AppFavorites.getAppFavorites().addFavorite(id);
|
2009-08-06 19:39:09 +00:00
|
|
|
return false;
|
2009-10-15 23:28:29 +00:00
|
|
|
}));
|
2009-08-06 19:39:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2009-06-30 20:35:39 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Signals.addSignalMethods(AppWell.prototype);
|