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-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-08-13 16:21:01 +00:00
|
|
|
const AppIcon = imports.ui.appIcon;
|
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-07-02 09:04:33 +00:00
|
|
|
const Workspaces = imports.ui.workspaces;
|
2009-04-01 19:51:17 +00:00
|
|
|
|
|
|
|
const ENTERED_MENU_COLOR = new Clutter.Color();
|
|
|
|
ENTERED_MENU_COLOR.from_pixel(0x00ff0022);
|
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
const WELL_DEFAULT_COLUMNS = 4;
|
|
|
|
const WELL_ITEM_HSPACING = 0;
|
|
|
|
const WELL_ITEM_VSPACING = 4;
|
2009-06-30 20:35:39 +00:00
|
|
|
|
2009-09-01 18:15:29 +00:00
|
|
|
const WELL_MENU_POPUP_TIMEOUT_MS = 600;
|
|
|
|
|
|
|
|
const TRANSPARENT_COLOR = new Clutter.Color();
|
|
|
|
TRANSPARENT_COLOR.from_pixel(0x00000000);
|
|
|
|
|
|
|
|
const WELL_MENU_BACKGROUND_COLOR = new Clutter.Color();
|
|
|
|
WELL_MENU_BACKGROUND_COLOR.from_pixel(0x292929ff);
|
|
|
|
const WELL_MENU_FONT = 'Sans 14px';
|
|
|
|
const WELL_MENU_COLOR = new Clutter.Color();
|
|
|
|
WELL_MENU_COLOR.from_pixel(0xffffffff);
|
|
|
|
const WELL_MENU_SELECTED_COLOR = new Clutter.Color();
|
|
|
|
WELL_MENU_SELECTED_COLOR.from_pixel(0x005b97ff);
|
|
|
|
const WELL_MENU_BORDER_COLOR = new Clutter.Color();
|
|
|
|
WELL_MENU_BORDER_COLOR.from_pixel(0x787878ff);
|
|
|
|
const WELL_MENU_SEPARATOR_COLOR = new Clutter.Color();
|
|
|
|
WELL_MENU_SEPARATOR_COLOR.from_pixel(0x787878ff);
|
|
|
|
const WELL_MENU_BORDER_WIDTH = 1;
|
|
|
|
const WELL_MENU_ARROW_SIZE = 12;
|
|
|
|
const WELL_MENU_CORNER_RADIUS = 4;
|
|
|
|
const WELL_MENU_PADDING = 4;
|
|
|
|
|
2009-04-01 19:51:17 +00:00
|
|
|
const MENU_ICON_SIZE = 24;
|
|
|
|
const MENU_SPACING = 15;
|
2008-12-20 04:27:57 +00:00
|
|
|
|
2009-04-23 14:41:24 +00:00
|
|
|
const MAX_ITEMS = 30;
|
|
|
|
|
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-09-09 20:43:29 +00:00
|
|
|
let windows = Shell.AppMonitor.get_default().get_windows_for_app(this._appInfo.get_id());
|
|
|
|
if (windows.length > 0) {
|
|
|
|
let mostRecentWindow = windows[0];
|
|
|
|
Main.overview.activateWindow(mostRecentWindow, Clutter.get_current_event_time());
|
|
|
|
} 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
|
|
|
|
2009-04-01 19:51:17 +00:00
|
|
|
const MENU_UNSELECTED = 0;
|
|
|
|
const MENU_SELECTED = 1;
|
|
|
|
const MENU_ENTERED = 2;
|
|
|
|
|
2009-05-02 22:33:13 +00:00
|
|
|
function MenuItem(name, id, iconName) {
|
|
|
|
this._init(name, id, iconName);
|
2009-04-01 19:51:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* MenuItem:
|
|
|
|
* Shows the list of menus in the sidebar.
|
|
|
|
*/
|
|
|
|
MenuItem.prototype = {
|
2009-05-02 22:33:13 +00:00
|
|
|
_init: function(name, id, iconName) {
|
2009-04-01 19:51:17 +00:00
|
|
|
this.id = id;
|
|
|
|
|
|
|
|
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
|
|
|
spacing: 4,
|
|
|
|
corner_radius: 4,
|
|
|
|
padding_right: 4,
|
|
|
|
reactive: true });
|
|
|
|
this.actor.connect('button-press-event', Lang.bind(this, function (a, e) {
|
|
|
|
this.setState(MENU_SELECTED);
|
|
|
|
}));
|
|
|
|
|
|
|
|
let iconTheme = Gtk.IconTheme.get_default();
|
|
|
|
let pixbuf = null;
|
|
|
|
this._icon = new Clutter.Texture({ width: MENU_ICON_SIZE,
|
|
|
|
height: MENU_ICON_SIZE });
|
|
|
|
// Wine manages not to have an icon
|
|
|
|
try {
|
2009-05-02 22:33:13 +00:00
|
|
|
pixbuf = iconTheme.load_icon(iconName, MENU_ICON_SIZE, 0 /* flags */);
|
2009-04-01 19:51:17 +00:00
|
|
|
} catch (e) {
|
|
|
|
pixbuf = iconTheme.load_icon('gtk-file', MENU_ICON_SIZE, 0);
|
|
|
|
}
|
|
|
|
if (pixbuf != null)
|
|
|
|
Shell.clutter_texture_set_from_pixbuf(this._icon, pixbuf);
|
2009-07-27 22:59:26 +00:00
|
|
|
this.actor.append(this._icon, Big.BoxPackFlags.NONE);
|
2009-04-01 19:51:17 +00:00
|
|
|
this._text = new Clutter.Text({ color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
|
|
|
|
font_name: "Sans 14px",
|
|
|
|
text: name });
|
|
|
|
|
2009-07-27 22:59:26 +00:00
|
|
|
// We use individual boxes for the label and the arrow to ensure that they
|
|
|
|
// are aligned vertically. Just setting y_align: Big.BoxAlignment.CENTER
|
|
|
|
// on this.actor does not seem to achieve that.
|
|
|
|
let labelBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
|
2009-04-01 19:51:17 +00:00
|
|
|
|
2009-07-27 22:59:26 +00:00
|
|
|
labelBox.append(this._text, Big.BoxPackFlags.NONE);
|
|
|
|
|
|
|
|
this.actor.append(labelBox, Big.BoxPackFlags.EXPAND);
|
|
|
|
|
|
|
|
let arrowBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
|
|
|
|
|
|
|
|
this._arrow = new Shell.Arrow({ surface_width: MENU_ICON_SIZE / 2,
|
|
|
|
surface_height: MENU_ICON_SIZE / 2,
|
2009-04-01 19:51:17 +00:00
|
|
|
direction: Gtk.ArrowType.RIGHT,
|
2009-07-27 22:59:26 +00:00
|
|
|
opacity: 0 });
|
|
|
|
arrowBox.append(this._arrow, Big.BoxPackFlags.NONE);
|
|
|
|
this.actor.append(arrowBox, Big.BoxPackFlags.NONE);
|
2009-04-01 19:51:17 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
getState: function() {
|
|
|
|
return this._state;
|
|
|
|
},
|
|
|
|
|
|
|
|
setState: function (state) {
|
|
|
|
if (state == this._state)
|
|
|
|
return;
|
|
|
|
this._state = state;
|
|
|
|
if (this._state == MENU_UNSELECTED) {
|
|
|
|
this.actor.background_color = null;
|
|
|
|
this._arrow.set_opacity(0);
|
|
|
|
} else if (this._state == MENU_ENTERED) {
|
|
|
|
this.actor.background_color = ENTERED_MENU_COLOR;
|
|
|
|
this._arrow.set_opacity(0xFF/2);
|
|
|
|
} else {
|
|
|
|
this.actor.background_color = GenericDisplay.ITEM_DISPLAY_SELECTED_BACKGROUND_COLOR;
|
|
|
|
this._arrow.set_opacity(0xFF);
|
|
|
|
}
|
|
|
|
this.emit('state-changed')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Signals.addSignalMethods(MenuItem.prototype);
|
|
|
|
|
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-09-11 21:48:02 +00:00
|
|
|
function AppDisplay(showPrefs) {
|
|
|
|
this._init(showPrefs);
|
2008-11-21 00:53:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
AppDisplay.prototype = {
|
2008-12-20 04:27:57 +00:00
|
|
|
__proto__: GenericDisplay.GenericDisplay.prototype,
|
|
|
|
|
2009-09-11 21:48:02 +00:00
|
|
|
_init : function(showPrefs) {
|
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
|
|
|
GenericDisplay.GenericDisplay.prototype._init.call(this);
|
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-01-21 21:50:57 +00:00
|
|
|
// map<itemId, array of category names>
|
2009-04-01 19:51:17 +00:00
|
|
|
this._appCategories = {};
|
2009-04-23 14:41:24 +00:00
|
|
|
|
2009-06-18 16:27:19 +00:00
|
|
|
this._appMonitor = Shell.AppMonitor.get_default();
|
|
|
|
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;
|
|
|
|
this._redisplay(false);
|
|
|
|
this._redisplayMenus();
|
|
|
|
}));
|
2009-06-30 20:35:39 +00:00
|
|
|
this._appSystem.connect('favorites-changed', Lang.bind(this, function(appSys) {
|
|
|
|
this._redisplay(false);
|
|
|
|
}));
|
2009-08-12 20:05:25 +00:00
|
|
|
this._appMonitor.connect('app-added', Lang.bind(this, function(monitor) {
|
|
|
|
this._redisplay(false);
|
|
|
|
}));
|
|
|
|
this._appMonitor.connect('app-removed', Lang.bind(this, function(monitor) {
|
2009-05-14 18:14:18 +00:00
|
|
|
this._redisplay(false);
|
2009-04-23 14:41:24 +00:00
|
|
|
}));
|
2009-01-13 20:45:54 +00:00
|
|
|
|
2009-07-07 20:08:41 +00:00
|
|
|
// Load the apps now so it doesn't slow down the first
|
2009-08-11 11:46:10 +00:00
|
|
|
// transition into the Overview
|
2009-01-13 20:45:54 +00:00
|
|
|
this._refreshCache();
|
2009-04-01 19:51:17 +00:00
|
|
|
|
|
|
|
this._focusInMenus = true;
|
|
|
|
this._activeMenuIndex = -1;
|
|
|
|
this._activeMenu = null;
|
|
|
|
this._activeMenuApps = null;
|
|
|
|
this._menuDisplay = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
|
|
|
spacing: MENU_SPACING
|
|
|
|
});
|
|
|
|
this._redisplayMenus();
|
|
|
|
|
|
|
|
this.connect('expanded', Lang.bind(this, function (self) {
|
|
|
|
this._filterReset();
|
|
|
|
}));
|
2009-07-04 16:46:35 +00:00
|
|
|
this._filterReset();
|
2009-04-01 19:51:17 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
moveRight: function() {
|
|
|
|
if (this._expanded && this._focusInMenu) {
|
|
|
|
this._focusInMenu = false;
|
|
|
|
this._activeMenu.setState(MENU_ENTERED);
|
|
|
|
this.selectFirstItem();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
moveLeft: function() {
|
|
|
|
if (this._expanded && !this._focusInMenu) {
|
|
|
|
this._activeMenu.setState(MENU_SELECTED);
|
|
|
|
this.unsetSelected();
|
|
|
|
this._focusInMenu = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// Override genericDisplay.js
|
2009-07-04 16:46:35 +00:00
|
|
|
getNavigationArea: function() {
|
2009-04-01 19:51:17 +00:00
|
|
|
return this._menuDisplay;
|
|
|
|
},
|
|
|
|
|
|
|
|
selectUp: function() {
|
|
|
|
if (!(this._expanded && this._focusInMenu))
|
|
|
|
return GenericDisplay.GenericDisplay.prototype.selectUp.call(this);
|
|
|
|
this._selectMenuIndex(this._activeMenuIndex - 1);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
selectDown: function() {
|
|
|
|
if (!(this._expanded && this._focusInMenu))
|
|
|
|
return GenericDisplay.GenericDisplay.prototype.selectDown.call(this);
|
|
|
|
this._selectMenuIndex(this._activeMenuIndex+1);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
// Protected overrides
|
|
|
|
|
|
|
|
_filterActive: function() {
|
2009-07-07 20:08:41 +00:00
|
|
|
// We always have a filter now since a menu must be selected
|
|
|
|
return true;
|
2009-04-01 19:51:17 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_filterReset: function() {
|
|
|
|
GenericDisplay.GenericDisplay.prototype._filterReset.call(this);
|
2009-07-04 16:46:35 +00:00
|
|
|
this._selectMenuIndex(0);
|
2009-04-01 19:51:17 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
//// Private ////
|
|
|
|
|
|
|
|
_emitStateChange: function() {
|
|
|
|
this.emit('state-changed');
|
|
|
|
},
|
|
|
|
|
|
|
|
_selectMenuIndex: function(index) {
|
|
|
|
if (index < 0 || index >= this._menus.length)
|
|
|
|
return;
|
2009-04-13 19:41:13 +00:00
|
|
|
this._menuDisplays[index].setState(MENU_SELECTED);
|
2009-04-01 19:51:17 +00:00
|
|
|
},
|
|
|
|
|
2009-07-07 20:08:41 +00:00
|
|
|
_getMostUsed: function() {
|
2009-07-30 19:40:26 +00:00
|
|
|
let context = "";
|
|
|
|
return this._appMonitor.get_most_used_apps(context, 30).map(Lang.bind(this, function (id) {
|
2009-08-05 15:27:06 +00:00
|
|
|
return this._appSystem.lookup_cached_app(id);
|
2009-07-07 20:08:41 +00:00
|
|
|
})).filter(function (e) { return e != null });
|
|
|
|
},
|
|
|
|
|
2009-07-04 16:46:35 +00:00
|
|
|
_addMenuItem: function(name, id, icon, index) {
|
|
|
|
let display = new MenuItem(name, id, icon);
|
|
|
|
this._menuDisplays.push(display);
|
|
|
|
display.connect('state-changed', Lang.bind(this, function (display) {
|
|
|
|
let activated = display.getState() != MENU_UNSELECTED;
|
|
|
|
if (!activated && display == this._activeMenu) {
|
|
|
|
this._activeMenuIndex = -1;
|
|
|
|
this._activeMenu = null;
|
|
|
|
} else if (activated) {
|
|
|
|
if (display != this._activeMenu && this._activeMenu != null)
|
|
|
|
this._activeMenu.setState(MENU_UNSELECTED);
|
|
|
|
this._activeMenuIndex = index;
|
|
|
|
this._activeMenu = display;
|
|
|
|
if (id == null) {
|
2009-07-07 20:08:41 +00:00
|
|
|
this._activeMenuApps = this._getMostUsed();
|
2009-07-04 16:46:35 +00:00
|
|
|
} else {
|
|
|
|
this._activeMenuApps = this._appSystem.get_applications_for_menu(id);
|
|
|
|
}
|
|
|
|
}
|
2009-07-07 10:20:17 +00:00
|
|
|
this._redisplay(true);
|
2009-07-04 16:46:35 +00:00
|
|
|
}));
|
|
|
|
this._menuDisplay.append(display.actor, 0);
|
|
|
|
},
|
|
|
|
|
2009-04-01 19:51:17 +00:00
|
|
|
_redisplayMenus: function() {
|
|
|
|
this._menuDisplay.remove_all();
|
2009-08-14 13:30:48 +00:00
|
|
|
this._addMenuItem(_("Frequent"), null, 'gtk-select-all');
|
2009-04-01 19:51:17 +00:00
|
|
|
for (let i = 0; i < this._menus.length; i++) {
|
|
|
|
let menu = this._menus[i];
|
2009-07-04 16:46:35 +00:00
|
|
|
this._addMenuItem(menu.name, menu.id, menu.icon, i+1);
|
2009-04-01 19:51:17 +00:00
|
|
|
}
|
2008-12-01 19:51:43 +00:00
|
|
|
},
|
|
|
|
|
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() {
|
|
|
|
let me = this;
|
2008-12-01 19:51:43 +00:00
|
|
|
if (!this._appsStale)
|
|
|
|
return;
|
2008-12-20 04:27:57 +00:00
|
|
|
this._allItems = {};
|
2009-04-01 19:51:17 +00:00
|
|
|
this._appCategories = {};
|
|
|
|
|
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 {
|
|
|
|
// Loop over the toplevel menu items, load the set of desktop file ids
|
|
|
|
// associated with each one, skipping empty menus
|
|
|
|
let allMenus = this._appSystem.get_menus();
|
|
|
|
this._menus = [];
|
|
|
|
for (let i = 0; i < allMenus.length; i++) {
|
|
|
|
let menu = allMenus[i];
|
|
|
|
let menuApps = this._appSystem.get_applications_for_menu(menu.id);
|
|
|
|
let hasVisibleApps = menuApps.some(function (app) { return !app.get_is_nodisplay(); });
|
|
|
|
if (!hasVisibleApps) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
this._menus.push(menu);
|
|
|
|
for (let j = 0; j < menuApps.length; j++) {
|
|
|
|
let app = menuApps[j];
|
|
|
|
this._addApp(app);
|
|
|
|
}
|
|
|
|
}
|
2009-05-14 18:14:18 +00:00
|
|
|
}
|
|
|
|
|
2008-12-01 19:51:43 +00:00
|
|
|
this._appsStale = false;
|
|
|
|
},
|
|
|
|
|
2009-07-07 20:08:41 +00:00
|
|
|
// Stub this out; the app display always has a category selected
|
2008-12-20 04:27:57 +00:00
|
|
|
_setDefaultList : function() {
|
2009-07-07 20:08:41 +00:00
|
|
|
this._matchedItems = [];
|
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-07-07 20:08:41 +00:00
|
|
|
// the name, description, execution command, and categories for the application.
|
|
|
|
// 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
|
|
|
// Search takes precedence; not typically useful to search within a
|
|
|
|
// menu
|
|
|
|
if (this._activeMenu == null || search != "")
|
|
|
|
return this._isInfoMatchingSearch(itemInfo, search);
|
|
|
|
else
|
|
|
|
return this._isInfoMatchingMenu(itemInfo, search);
|
|
|
|
},
|
|
|
|
|
|
|
|
_isInfoMatchingMenu : function(itemInfo, search) {
|
2009-07-07 20:08:41 +00:00
|
|
|
let id = itemInfo.get_id();
|
2009-04-01 19:51:17 +00:00
|
|
|
for (let i = 0; i < this._activeMenuApps.length; i++) {
|
2009-07-07 20:08:41 +00:00
|
|
|
let activeApp = this._activeMenuApps[i];
|
|
|
|
if (activeApp.get_id() == id)
|
2009-04-01 19:51:17 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
_isInfoMatchingSearch: function(itemInfo, search) {
|
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;
|
|
|
|
}
|
|
|
|
|
2009-07-07 20:08:41 +00:00
|
|
|
let categories = itemInfo.get_categories();
|
2009-05-26 16:56:38 +00:00
|
|
|
for (let i = 0; i < categories.length; i++) {
|
2009-07-07 20:08:41 +00:00
|
|
|
let category = fold(categories[i]);
|
2009-05-26 16:56:38 +00:00
|
|
|
if (category.indexOf(search) >= 0)
|
|
|
|
return true;
|
2009-01-21 21:50:57 +00:00
|
|
|
}
|
2009-08-05 23:54:22 +00:00
|
|
|
|
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-09-01 18:15:29 +00:00
|
|
|
function WellMenu(source) {
|
|
|
|
this._init(source);
|
2009-06-30 20:35:39 +00:00
|
|
|
}
|
|
|
|
|
2009-09-01 18:15:29 +00:00
|
|
|
WellMenu.prototype = {
|
|
|
|
_init: function(source) {
|
|
|
|
this._source = source;
|
2009-08-13 16:21:01 +00:00
|
|
|
|
2009-06-30 20:35:39 +00:00
|
|
|
|
2009-09-01 18:15:29 +00:00
|
|
|
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._windowContainer = new Shell.Menu({ orientation: Big.BoxOrientation.VERTICAL,
|
|
|
|
border_color: WELL_MENU_BORDER_COLOR,
|
|
|
|
border: WELL_MENU_BORDER_WIDTH,
|
|
|
|
background_color: WELL_MENU_BACKGROUND_COLOR,
|
|
|
|
padding: 4,
|
|
|
|
corner_radius: WELL_MENU_CORNER_RADIUS,
|
|
|
|
width: Main.overview._dash.actor.width * 0.75 });
|
|
|
|
this._windowContainer.connect('popdown', Lang.bind(this, this._onPopdown));
|
|
|
|
this._windowContainer.connect('unselected', Lang.bind(this, this._onWindowUnselected));
|
|
|
|
this._windowContainer.connect('selected', Lang.bind(this, this._onWindowSelected));
|
|
|
|
this._windowContainer.connect('activate', Lang.bind(this, this._onWindowActivate));
|
|
|
|
this.actor.add_actor(this._windowContainer);
|
|
|
|
|
|
|
|
this._arrow = new Shell.DrawingArea();
|
|
|
|
this._arrow.connect('redraw', Lang.bind(this, function (area, texture) {
|
|
|
|
Shell.draw_box_pointer(texture, WELL_MENU_BORDER_COLOR, WELL_MENU_BACKGROUND_COLOR);
|
|
|
|
}));
|
|
|
|
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();
|
2009-06-30 20:35:39 +00:00
|
|
|
}));
|
2009-09-01 18:15:29 +00:00
|
|
|
source.actor.connect('destroy', Lang.bind(this, function () { this.actor.destroy(); }));
|
2009-06-30 20:35:39 +00:00
|
|
|
|
2009-09-11 21:23:23 +00:00
|
|
|
global.stage.add_actor(this.actor);
|
2009-06-30 20:35:39 +00:00
|
|
|
},
|
|
|
|
|
2009-09-01 18:15:29 +00:00
|
|
|
_getPreferredWidth: function(actor, forHeight, alloc) {
|
|
|
|
let [min, natural] = this._windowContainer.get_preferred_width(forHeight);
|
|
|
|
alloc.min_size = min + WELL_MENU_ARROW_SIZE;
|
|
|
|
alloc.natural_size = natural + WELL_MENU_ARROW_SIZE;
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredHeight: function(actor, forWidth, alloc) {
|
|
|
|
let [min, natural] = this._windowContainer.get_preferred_height(forWidth);
|
|
|
|
alloc.min_size = min;
|
|
|
|
alloc.natural_size = natural;
|
|
|
|
},
|
|
|
|
|
|
|
|
_allocate: function(actor, box, flags) {
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
|
|
|
|
|
|
let width = box.x2 - box.x1;
|
|
|
|
let height = box.y2 - box.y1;
|
|
|
|
|
|
|
|
childBox.x1 = 0;
|
|
|
|
childBox.x2 = WELL_MENU_ARROW_SIZE;
|
2009-09-08 21:53:45 +00:00
|
|
|
childBox.y1 = Math.floor((height / 2) - (WELL_MENU_ARROW_SIZE / 2));
|
2009-09-01 18:15:29 +00:00
|
|
|
childBox.y2 = childBox.y1 + WELL_MENU_ARROW_SIZE;
|
|
|
|
this._arrow.allocate(childBox, flags);
|
|
|
|
|
|
|
|
/* overlap by one pixel to hide the border */
|
|
|
|
childBox.x1 = WELL_MENU_ARROW_SIZE - 1;
|
|
|
|
childBox.x2 = width;
|
|
|
|
childBox.y1 = 0;
|
|
|
|
childBox.y2 = height;
|
|
|
|
this._windowContainer.allocate(childBox, flags);
|
|
|
|
},
|
|
|
|
|
|
|
|
_redisplay: function() {
|
|
|
|
this._windowContainer.remove_all();
|
|
|
|
|
|
|
|
let windows = this._source.windows;
|
|
|
|
|
|
|
|
this._windowContainer.show();
|
|
|
|
|
|
|
|
let iconsDiffer = false;
|
|
|
|
let texCache = Shell.TextureCache.get_default();
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-09-11 21:23:23 +00:00
|
|
|
let activeWorkspace = global.screen.get_active_workspace();
|
2009-09-01 18:15:29 +00:00
|
|
|
|
|
|
|
let currentWorkspaceWindows = windows.filter(function (w) {
|
|
|
|
return w.get_workspace() == activeWorkspace;
|
|
|
|
});
|
|
|
|
let otherWorkspaceWindows = windows.filter(function (w) {
|
|
|
|
return w.get_workspace() != activeWorkspace;
|
|
|
|
});
|
|
|
|
|
|
|
|
this._appendWindows(currentWorkspaceWindows, iconsDiffer);
|
|
|
|
if (currentWorkspaceWindows.length > 0 && otherWorkspaceWindows.length > 0) {
|
|
|
|
let box = new Big.Box({ padding_top: 2, padding_bottom: 2 });
|
|
|
|
box.append(new Clutter.Rectangle({ height: 1,
|
|
|
|
color: WELL_MENU_SEPARATOR_COLOR }),
|
|
|
|
Big.BoxPackFlags.EXPAND);
|
|
|
|
this._windowContainer.append_separator(box, Big.BoxPackFlags.NONE);
|
|
|
|
}
|
|
|
|
this._appendWindows(otherWorkspaceWindows, iconsDiffer);
|
|
|
|
},
|
|
|
|
|
|
|
|
_appendWindows: function (windows, iconsDiffer) {
|
|
|
|
for (let i = 0; i < windows.length; i++) {
|
2009-09-11 22:58:30 +00:00
|
|
|
let window = windows[i];
|
2009-09-01 18:15:29 +00:00
|
|
|
/* Use padding here rather than spacing in the box above so that
|
|
|
|
* we have a larger reactive area.
|
|
|
|
*/
|
|
|
|
let box = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
|
|
|
padding_top: 4,
|
|
|
|
padding_bottom: 4,
|
|
|
|
spacing: 4,
|
|
|
|
reactive: true });
|
2009-09-11 22:58:30 +00:00
|
|
|
box._window = window;
|
2009-09-01 18:15:29 +00:00
|
|
|
let vCenter;
|
|
|
|
if (iconsDiffer) {
|
|
|
|
vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
|
2009-09-11 22:58:30 +00:00
|
|
|
let icon = Shell.TextureCache.get_default().bind_pixbuf_property(window, "mini-icon");
|
2009-09-01 18:15:29 +00:00
|
|
|
vCenter.append(icon, Big.BoxPackFlags.NONE);
|
|
|
|
box.append(vCenter, Big.BoxPackFlags.NONE);
|
|
|
|
}
|
|
|
|
vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
|
2009-09-11 22:58:30 +00:00
|
|
|
let label = new Clutter.Text({ text: window.title,
|
2009-09-01 18:15:29 +00:00
|
|
|
font_name: WELL_MENU_FONT,
|
|
|
|
ellipsize: Pango.EllipsizeMode.END,
|
|
|
|
color: WELL_MENU_COLOR });
|
|
|
|
vCenter.append(label, Big.BoxPackFlags.NONE);
|
|
|
|
box.append(vCenter, Big.BoxPackFlags.NONE);
|
|
|
|
this._windowContainer.append(box, Big.BoxPackFlags.NONE);
|
2009-07-04 21:13:13 +00:00
|
|
|
}
|
2009-06-30 20:35:39 +00:00
|
|
|
},
|
|
|
|
|
2009-09-01 18:15:29 +00:00
|
|
|
popup: function() {
|
|
|
|
let [stageX, stageY] = this._source.actor.get_transformed_position();
|
|
|
|
let [stageWidth, stageHeight] = this._source.actor.get_transformed_size();
|
|
|
|
|
|
|
|
this._redisplay();
|
|
|
|
|
|
|
|
this._windowContainer.popup(0, Clutter.get_current_event_time());
|
|
|
|
|
|
|
|
this.emit('popup', true);
|
|
|
|
|
2009-09-08 21:53:45 +00:00
|
|
|
let x = Math.floor(stageX + stageWidth);
|
2009-09-01 18:15:29 +00:00
|
|
|
let y = Math.floor(stageY + (stageHeight / 2) - (this.actor.height / 2));
|
2009-09-08 21:53:45 +00:00
|
|
|
this.actor.set_position(x, y);
|
2009-09-01 18:15:29 +00:00
|
|
|
this.actor.show();
|
|
|
|
},
|
|
|
|
|
2009-09-10 01:14:31 +00:00
|
|
|
_onWindowUnselected: function (actor, child) {
|
2009-09-11 22:58:30 +00:00
|
|
|
child.background_color = TRANSPARENT_COLOR;
|
|
|
|
|
|
|
|
this.emit('highlight-window', null);
|
2009-09-10 01:14:31 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onWindowSelected: function (actor, child) {
|
2009-09-11 22:58:30 +00:00
|
|
|
child.background_color = WELL_MENU_SELECTED_COLOR;
|
|
|
|
|
|
|
|
let window = child._window;
|
|
|
|
this.emit('highlight-window', window);
|
2009-09-01 18:15:29 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onWindowActivate: function (actor, child) {
|
2009-09-11 22:58:30 +00:00
|
|
|
let window = child._window;
|
|
|
|
Main.overview.activateWindow(window, Clutter.get_current_event_time());
|
2009-09-01 18:15:29 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onPopdown: function () {
|
|
|
|
this.emit('highlight-window', null);
|
|
|
|
this.emit('popup', false);
|
|
|
|
this.actor.hide();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Signals.addSignalMethods(WellMenu.prototype);
|
|
|
|
|
|
|
|
function BaseWellItem(appInfo, isFavorite) {
|
|
|
|
this._init(appInfo, isFavorite);
|
|
|
|
}
|
|
|
|
|
|
|
|
BaseWellItem.prototype = {
|
|
|
|
_init: function(appInfo, isFavorite) {
|
|
|
|
this.appInfo = appInfo;
|
|
|
|
this.isFavorite = isFavorite;
|
|
|
|
this.icon = new AppIcon.AppIcon(appInfo);
|
|
|
|
this.windows = this.icon.windows;
|
2009-09-08 19:53:49 +00:00
|
|
|
this.actor = new Shell.ButtonBox({ orientation: Big.BoxOrientation.VERTICAL,
|
|
|
|
border: WELL_MENU_BORDER_WIDTH,
|
|
|
|
corner_radius: WELL_MENU_CORNER_RADIUS,
|
|
|
|
reactive: true });
|
|
|
|
this.icon.actor._delegate = this;
|
|
|
|
this._draggable = DND.makeDraggable(this.icon.actor, true);
|
|
|
|
|
|
|
|
// Do these as anonymous functions to avoid conflict with handlers in subclasses
|
|
|
|
this.actor.connect('button-press-event', Lang.bind(this, function(actor, event) {
|
|
|
|
let [stageX, stageY] = event.get_coords();
|
|
|
|
this._dragStartX = stageX;
|
|
|
|
this._dragStartY = stageY;
|
|
|
|
return false;
|
|
|
|
}));
|
|
|
|
this.actor.connect('notify::hover', Lang.bind(this, function () {
|
|
|
|
let hover = this.actor.hover;
|
|
|
|
if (!hover) {
|
|
|
|
if (this.actor.pressed && this._dragStartX != null) {
|
|
|
|
this.actor.fake_release();
|
|
|
|
this._draggable.startDrag(this.icon.actor, this._dragStartX, this._dragStartY,
|
|
|
|
Clutter.get_current_event_time());
|
|
|
|
} else {
|
|
|
|
this._dragStartX = null;
|
|
|
|
this._dragStartY = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
this.actor.append(this.icon.actor, Big.BoxPackFlags.NONE);
|
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.
|
|
|
|
this.appInfo.launch();
|
2009-08-18 00:29:54 +00:00
|
|
|
},
|
|
|
|
|
2009-06-30 20:35:39 +00:00
|
|
|
getDragActor: function(stageX, stageY) {
|
2009-09-01 18:15:29 +00:00
|
|
|
return this.icon.getDragActor(stageX, stageY);
|
2009-06-30 20:35:39 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
// 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() {
|
2009-09-01 18:15:29 +00:00
|
|
|
return this.icon.getDragActorSource();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function RunningWellItem(appInfo, isFavorite) {
|
|
|
|
this._init(appInfo, isFavorite);
|
|
|
|
}
|
|
|
|
|
|
|
|
RunningWellItem.prototype = {
|
|
|
|
__proto__: BaseWellItem.prototype,
|
|
|
|
|
|
|
|
_init: function(appInfo, isFavorite) {
|
|
|
|
BaseWellItem.prototype._init.call(this, appInfo, isFavorite);
|
|
|
|
|
|
|
|
this._menuTimeoutId = 0;
|
|
|
|
this._menu = null;
|
|
|
|
this._dragStartX = 0;
|
|
|
|
this._dragStartY = 0;
|
|
|
|
|
|
|
|
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
|
|
|
this.actor.connect('notify::hover', Lang.bind(this, this._onHoverChanged));
|
2009-09-08 21:29:22 +00:00
|
|
|
this.actor.connect('activate', Lang.bind(this, this._onActivate));
|
|
|
|
},
|
|
|
|
|
|
|
|
_onActivate: function (actor, event) {
|
|
|
|
let modifiers = event.get_state();
|
|
|
|
|
|
|
|
if (this._menuTimeoutId > 0) {
|
|
|
|
Mainloop.source_remove(this._menuTimeoutId);
|
|
|
|
this._menuTimeoutId = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
|
|
|
|
this.appInfo.launch();
|
|
|
|
} else {
|
|
|
|
this.activateMostRecentWindow();
|
|
|
|
}
|
2009-09-01 18:15:29 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
activateMostRecentWindow: function () {
|
|
|
|
// The _get_windows_for_app sorts them for us
|
|
|
|
let mostRecentWindow = this.windows[0];
|
|
|
|
Main.overview.activateWindow(mostRecentWindow, Clutter.get_current_event_time());
|
|
|
|
},
|
|
|
|
|
|
|
|
_onHoverChanged: function() {
|
|
|
|
let hover = this.actor.hover;
|
|
|
|
if (!hover && this._menuTimeoutId > 0) {
|
|
|
|
Mainloop.source_remove(this._menuTimeoutId);
|
|
|
|
this._menuTimeoutId = 0;
|
|
|
|
}
|
2009-07-04 21:28:34 +00:00
|
|
|
},
|
|
|
|
|
2009-09-01 18:15:29 +00:00
|
|
|
_onButtonPress: function(actor, event) {
|
|
|
|
if (this._menuTimeoutId > 0)
|
|
|
|
Mainloop.source_remove(this._menuTimeoutId);
|
|
|
|
this._menuTimeoutId = Mainloop.timeout_add(WELL_MENU_POPUP_TIMEOUT_MS,
|
|
|
|
Lang.bind(this, this._popupMenu));
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
_popupMenu: function() {
|
|
|
|
this._menuTimeoutId = 0;
|
|
|
|
|
|
|
|
this.actor.fake_release();
|
|
|
|
|
|
|
|
if (this._menu == null) {
|
|
|
|
this._menu = new WellMenu(this);
|
2009-09-11 22:58:30 +00:00
|
|
|
this._menu.connect('highlight-window', Lang.bind(this, function (menu, window) {
|
|
|
|
Main.overview.setHighlightWindow(window);
|
2009-09-01 18:15:29 +00:00
|
|
|
}));
|
|
|
|
this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) {
|
|
|
|
let id;
|
|
|
|
if (isPoppedUp)
|
|
|
|
id = this.appInfo.get_id();
|
|
|
|
else
|
|
|
|
id = null;
|
2009-09-09 20:42:43 +00:00
|
|
|
Main.overview.setApplicationWindowSelection(id);
|
2009-09-01 18:15:29 +00:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
this._menu.popup();
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function InactiveWellItem(appInfo, isFavorite) {
|
|
|
|
this._init(appInfo, isFavorite);
|
|
|
|
}
|
|
|
|
|
|
|
|
InactiveWellItem.prototype = {
|
|
|
|
__proto__: BaseWellItem.prototype,
|
|
|
|
|
|
|
|
_init : function(appInfo, isFavorite) {
|
|
|
|
BaseWellItem.prototype._init.call(this, appInfo, isFavorite);
|
|
|
|
|
|
|
|
this.actor.connect('notify::pressed', Lang.bind(this, this._onPressedChanged));
|
|
|
|
this.actor.connect('notify::hover', Lang.bind(this, this._onHoverChanged));
|
|
|
|
this.actor.connect('activate', Lang.bind(this, this._onActivate));
|
|
|
|
},
|
|
|
|
|
|
|
|
_onPressedChanged: function() {
|
|
|
|
let pressed = this.actor.pressed;
|
|
|
|
if (pressed) {
|
|
|
|
this.actor.border_color = WELL_MENU_BORDER_COLOR;
|
|
|
|
} else {
|
|
|
|
this.actor.border_color = TRANSPARENT_COLOR;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_onHoverChanged: function() {
|
|
|
|
let hover = this.actor.hover;
|
|
|
|
},
|
|
|
|
|
|
|
|
_onActivate: function() {
|
|
|
|
if (this.windows.length == 0) {
|
|
|
|
this.appInfo.launch();
|
|
|
|
Main.overview.hide();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2009-06-30 20:35:39 +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() {
|
|
|
|
this.actor = new Shell.GenericContainer();
|
2009-06-30 20:35:39 +00:00
|
|
|
|
2009-08-09 23:48:54 +00:00
|
|
|
this._separator = new Big.Box({ height: 1 });
|
2009-08-06 19:39:09 +00:00
|
|
|
this.actor.add_actor(this._separator);
|
|
|
|
this._separatorIndex = 0;
|
|
|
|
this._cachedSeparatorY = 0;
|
2009-07-02 04:35:26 +00:00
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
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));
|
2009-06-30 20:35:39 +00:00
|
|
|
},
|
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
_getPreferredWidth: function (grid, forHeight, alloc) {
|
|
|
|
let [itemMin, itemNatural] = this._getItemPreferredWidth();
|
|
|
|
let children = this._getItemChildren();
|
|
|
|
let nColumns;
|
|
|
|
if (children.length < WELL_DEFAULT_COLUMNS)
|
|
|
|
nColumns = children.length;
|
|
|
|
else
|
|
|
|
nColumns = WELL_DEFAULT_COLUMNS;
|
|
|
|
let spacing = Math.max(nColumns - 1, 0) * WELL_ITEM_HSPACING;
|
|
|
|
alloc.min_size = itemMin * nColumns + spacing;
|
|
|
|
alloc.natural_size = itemNatural * nColumns + spacing;
|
|
|
|
},
|
2009-06-30 20:35:39 +00:00
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
_getPreferredHeight: function (grid, forWidth, alloc) {
|
|
|
|
let [rows, columns, itemWidth, itemHeight] = this._computeLayout(forWidth);
|
|
|
|
let totalVerticalSpacing = Math.max(rows - 1, 0) * WELL_ITEM_VSPACING;
|
2009-06-30 20:35:39 +00:00
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
let [separatorMin, separatorNatural] = this._separator.get_preferred_height(forWidth);
|
|
|
|
alloc.min_size = alloc.natural_size = rows * itemHeight + totalVerticalSpacing + separatorNatural;
|
|
|
|
},
|
|
|
|
|
|
|
|
_allocate: function (grid, box, flags) {
|
|
|
|
let children = this._getItemChildren();
|
|
|
|
let availWidth = box.x2 - box.x1;
|
|
|
|
let availHeight = box.y2 - box.y1;
|
|
|
|
|
|
|
|
let [rows, columns, itemWidth, itemHeight] = this._computeLayout(availWidth);
|
|
|
|
|
|
|
|
let [separatorMin, separatorNatural] = this._separator.get_preferred_height(-1);
|
|
|
|
|
|
|
|
let x = box.x1;
|
|
|
|
let y = box.y1;
|
|
|
|
let columnIndex = 0;
|
|
|
|
for (let i = 0; i < children.length; i++) {
|
2009-08-14 08:35:48 +00:00
|
|
|
let [childMinWidth, childNaturalWidth] = children[i].get_preferred_width(-1);
|
2009-08-06 19:39:09 +00:00
|
|
|
|
2009-08-14 08:35:48 +00:00
|
|
|
/* Center the item in its allocation horizontally */
|
2009-08-06 19:39:09 +00:00
|
|
|
let width = Math.min(itemWidth, childNaturalWidth);
|
|
|
|
let horizSpacing = (itemWidth - width) / 2;
|
|
|
|
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
|
|
childBox.x1 = Math.floor(x + horizSpacing);
|
2009-08-14 08:35:48 +00:00
|
|
|
childBox.y1 = y;
|
2009-08-06 19:39:09 +00:00
|
|
|
childBox.x2 = childBox.x1 + width;
|
2009-08-14 08:35:48 +00:00
|
|
|
childBox.y2 = childBox.y1 + itemHeight;
|
2009-08-06 19:39:09 +00:00
|
|
|
children[i].allocate(childBox, flags);
|
|
|
|
|
|
|
|
let atSeparator = (i == this._separatorIndex - 1);
|
|
|
|
|
|
|
|
columnIndex++;
|
|
|
|
if (columnIndex == columns || atSeparator) {
|
|
|
|
columnIndex = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (columnIndex == 0) {
|
|
|
|
y += itemHeight + WELL_ITEM_VSPACING;
|
|
|
|
x = box.x1;
|
|
|
|
} else {
|
|
|
|
x += itemWidth + WELL_ITEM_HSPACING;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (atSeparator) {
|
|
|
|
y += separatorNatural + WELL_ITEM_VSPACING;
|
|
|
|
}
|
2009-07-07 20:08:41 +00:00
|
|
|
}
|
2009-08-06 19:39:09 +00:00
|
|
|
|
|
|
|
let separatorRowIndex = Math.ceil(this._separatorIndex / columns);
|
|
|
|
|
|
|
|
/* Allocate the separator */
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
|
|
childBox.x1 = box.x1;
|
|
|
|
childBox.y1 = (itemHeight + WELL_ITEM_VSPACING) * separatorRowIndex;
|
|
|
|
this._cachedSeparatorY = childBox.y1;
|
|
|
|
childBox.x2 = box.x2;
|
|
|
|
childBox.y2 = childBox.y1+separatorNatural;
|
|
|
|
this._separator.allocate(childBox, flags);
|
2009-07-04 21:28:34 +00:00
|
|
|
},
|
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
setSeparatorIndex: function (index) {
|
|
|
|
this._separatorIndex = index;
|
|
|
|
this.actor.queue_relayout();
|
2009-07-04 21:28:34 +00:00
|
|
|
},
|
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
removeAll: function () {
|
|
|
|
let itemChildren = this._getItemChildren();
|
|
|
|
for (let i = 0; i < itemChildren.length; i++) {
|
|
|
|
itemChildren[i].destroy();
|
2009-07-04 21:28:34 +00:00
|
|
|
}
|
2009-08-06 19:39:09 +00:00
|
|
|
this._separatorIndex = 0;
|
2009-06-30 20:35:39 +00:00
|
|
|
},
|
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
isBeforeSeparator: function(x, y) {
|
|
|
|
return y < this._cachedSeparatorY;
|
|
|
|
},
|
2009-06-30 20:35:39 +00:00
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
_getItemChildren: function () {
|
|
|
|
let children = this.actor.get_children();
|
|
|
|
children.shift();
|
|
|
|
return children;
|
|
|
|
},
|
|
|
|
|
|
|
|
_computeLayout: function (forWidth) {
|
|
|
|
let [itemMinWidth, itemNaturalWidth] = this._getItemPreferredWidth();
|
|
|
|
let columnsNatural;
|
|
|
|
let i;
|
|
|
|
let children = this._getItemChildren();
|
2009-08-09 01:34:35 +00:00
|
|
|
if (children.length == 0)
|
|
|
|
return [0, WELL_DEFAULT_COLUMNS, 0, 0];
|
2009-08-06 19:39:09 +00:00
|
|
|
let nColumns;
|
|
|
|
if (children.length < WELL_DEFAULT_COLUMNS)
|
|
|
|
nColumns = children.length;
|
|
|
|
else
|
|
|
|
nColumns = WELL_DEFAULT_COLUMNS;
|
|
|
|
|
|
|
|
if (forWidth >= 0 && forWidth < minWidth) {
|
|
|
|
log("WellGrid: trying to allocate for width " + forWidth + " but min is " + minWidth);
|
|
|
|
/* FIXME - we should fall back to fewer than WELL_DEFAULT_COLUMNS here */
|
2009-06-30 20:35:39 +00:00
|
|
|
}
|
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
let horizSpacingTotal = Math.max(nColumns - 1, 0) * WELL_ITEM_HSPACING;
|
|
|
|
let minWidth = itemMinWidth * nColumns + horizSpacingTotal;
|
2009-07-02 04:35:26 +00:00
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
let lastColumnIndex = nColumns - 1;
|
|
|
|
let separatorColumns = lastColumnIndex - ((lastColumnIndex + this._separatorIndex) % nColumns);
|
|
|
|
let rows = Math.ceil((children.length + separatorColumns) / nColumns);
|
|
|
|
|
|
|
|
let itemWidth;
|
|
|
|
if (forWidth < 0) {
|
|
|
|
itemWidth = itemNaturalWidth;
|
2009-06-30 20:35:39 +00:00
|
|
|
} else {
|
2009-08-06 19:39:09 +00:00
|
|
|
itemWidth = Math.max(forWidth - horizSpacingTotal, 0) / nColumns;
|
2009-06-30 20:35:39 +00:00
|
|
|
}
|
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
let itemNaturalHeight = 0;
|
|
|
|
for (let i = 0; i < children.length; i++) {
|
|
|
|
let [childMin, childNatural] = children[i].get_preferred_height(itemWidth);
|
|
|
|
if (childNatural > itemNaturalHeight)
|
|
|
|
itemNaturalHeight = childNatural;
|
|
|
|
}
|
|
|
|
|
|
|
|
return [rows, WELL_DEFAULT_COLUMNS, itemWidth, itemNaturalHeight];
|
|
|
|
},
|
|
|
|
|
|
|
|
_getItemPreferredWidth: function () {
|
|
|
|
let children = this._getItemChildren();
|
|
|
|
let minWidth = 0;
|
|
|
|
let naturalWidth = 0;
|
|
|
|
for (let i = 0; i < children.length; i++) {
|
|
|
|
let [childMin, childNatural] = children[i].get_preferred_width(-1);
|
|
|
|
if (childMin > minWidth)
|
|
|
|
minWidth = childMin;
|
|
|
|
if (childNatural > naturalWidth)
|
|
|
|
naturalWidth = childNatural;
|
|
|
|
}
|
|
|
|
return [minWidth, naturalWidth];
|
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 = [];
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
this._grid = new WellGrid();
|
|
|
|
this.actor.append(this._grid.actor, Big.BoxPackFlags.EXPAND);
|
2009-06-30 20:35:39 +00:00
|
|
|
|
|
|
|
this._appSystem = Shell.AppSystem.get_default();
|
|
|
|
this._appMonitor = Shell.AppMonitor.get_default();
|
|
|
|
|
|
|
|
this._appSystem.connect('installed-changed', Lang.bind(this, function(appSys) {
|
|
|
|
this._redisplay();
|
|
|
|
}));
|
|
|
|
this._appSystem.connect('favorites-changed', Lang.bind(this, function(appSys) {
|
|
|
|
this._redisplay();
|
|
|
|
}));
|
2009-08-12 19:53:42 +00:00
|
|
|
this._appMonitor.connect('window-added', Lang.bind(this, function(monitor) {
|
2009-08-12 20:05:25 +00:00
|
|
|
this._redisplay();
|
|
|
|
}));
|
2009-08-12 19:53:42 +00:00
|
|
|
this._appMonitor.connect('window-removed', Lang.bind(this, function(monitor) {
|
2009-06-30 20:35:39 +00:00
|
|
|
this._redisplay();
|
|
|
|
}));
|
|
|
|
|
|
|
|
this._redisplay();
|
|
|
|
},
|
|
|
|
|
2009-07-08 15:54:15 +00:00
|
|
|
_lookupApps: function(appIds) {
|
|
|
|
let result = [];
|
|
|
|
for (let i = 0; i < appIds.length; i++) {
|
|
|
|
let id = appIds[i];
|
2009-08-05 15:27:06 +00:00
|
|
|
let app = this._appSystem.lookup_cached_app(id);
|
2009-07-08 15:54:15 +00:00
|
|
|
if (!app)
|
|
|
|
continue;
|
|
|
|
result.push(app);
|
|
|
|
}
|
|
|
|
return result;
|
2009-07-07 20:08:41 +00:00
|
|
|
},
|
|
|
|
|
2009-08-06 19:39:09 +00:00
|
|
|
_arrayValues: function(array) {
|
|
|
|
return array.reduce(function (values, id, index) {
|
|
|
|
values[id] = index; return values; }, {});
|
|
|
|
},
|
|
|
|
|
|
|
|
_redisplay: function () {
|
|
|
|
this._grid.removeAll();
|
2009-07-30 19:40:26 +00:00
|
|
|
|
2009-07-07 20:08:41 +00:00
|
|
|
let favoriteIds = this._appSystem.get_favorites();
|
2009-08-06 19:39:09 +00:00
|
|
|
let favoriteIdsHash = this._arrayValues(favoriteIds);
|
|
|
|
|
|
|
|
/* hardcode here pending some design about how exactly desktop contexts behave */
|
|
|
|
let contextId = "";
|
2009-07-30 19:40:26 +00:00
|
|
|
|
2009-08-14 08:35:48 +00:00
|
|
|
let running = this._appMonitor.get_running_apps(contextId).filter(function (e) {
|
|
|
|
return !(e.get_id() in favoriteIdsHash);
|
2009-06-30 20:35:39 +00:00
|
|
|
});
|
2009-07-08 15:54:15 +00:00
|
|
|
let favorites = this._lookupApps(favoriteIds);
|
2009-08-06 19:39:09 +00:00
|
|
|
|
|
|
|
let displays = []
|
|
|
|
this._addApps(favorites, true);
|
|
|
|
this._grid.setSeparatorIndex(favorites.length);
|
|
|
|
this._addApps(running, false);
|
|
|
|
this._displays = displays;
|
|
|
|
},
|
|
|
|
|
2009-09-01 18:15:29 +00:00
|
|
|
_addApps: function(apps, isFavorite) {
|
2009-08-06 19:39:09 +00:00
|
|
|
for (let i = 0; i < apps.length; i++) {
|
|
|
|
let app = apps[i];
|
2009-09-01 18:15:29 +00:00
|
|
|
let windows = this._appMonitor.get_windows_for_app(app.get_id());
|
|
|
|
let display;
|
|
|
|
if (windows.length > 0)
|
|
|
|
display = new RunningWellItem(app, isFavorite);
|
|
|
|
else
|
|
|
|
display = new InactiveWellItem(app, isFavorite);
|
2009-08-06 19:39:09 +00:00
|
|
|
this._grid.actor.add_actor(display.actor);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// Draggable target interface
|
|
|
|
acceptDrop : function(source, actor, x, y, time) {
|
2009-08-14 08:35:48 +00:00
|
|
|
let appSystem = Shell.AppSystem.get_default();
|
|
|
|
|
|
|
|
let app = null;
|
2009-09-01 18:15:29 +00:00
|
|
|
if (source instanceof BaseWellItem) {
|
2009-08-14 08:35:48 +00:00
|
|
|
app = source.appInfo;
|
2009-08-06 19:39:09 +00:00
|
|
|
} else if (source instanceof AppDisplayItem) {
|
2009-08-14 08:35:48 +00:00
|
|
|
app = appSystem.lookup_cached_app(source.getId());
|
2009-08-06 19:39:09 +00:00
|
|
|
} else if (source instanceof Workspaces.WindowClone) {
|
|
|
|
let appMonitor = Shell.AppMonitor.get_default();
|
2009-08-14 08:35:48 +00:00
|
|
|
app = appMonitor.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-08-06 19:39:09 +00:00
|
|
|
let favoriteIds = this._appSystem.get_favorites();
|
|
|
|
let favoriteIdsObject = this._arrayValues(favoriteIds);
|
|
|
|
|
|
|
|
let dropIsFavorite = this._grid.isBeforeSeparator(x - this._grid.actor.x,
|
|
|
|
y - this._grid.actor.y);
|
|
|
|
let srcIsFavorite = (id in favoriteIdsObject);
|
|
|
|
|
|
|
|
if (srcIsFavorite && (!dropIsFavorite)) {
|
|
|
|
Mainloop.idle_add(function () {
|
|
|
|
appSystem.remove_favorite(id);
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
} else if ((!srcIsFavorite) && dropIsFavorite) {
|
|
|
|
Mainloop.idle_add(function () {
|
|
|
|
appSystem.add_favorite(id);
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2009-06-30 20:35:39 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Signals.addSignalMethods(AppWell.prototype);
|