2011-09-28 09:16:26 -04:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2019-01-31 09:07:06 -05:00
|
|
|
/* exported AppDisplay, AppSearchProvider */
|
2008-11-20 19:53:11 -05:00
|
|
|
|
2019-02-20 14:54:29 -05:00
|
|
|
const { Clutter, Gio, GLib, GObject, Graphene, Meta, Shell, St } = imports.gi;
|
2009-02-02 18:02:16 -05:00
|
|
|
const Signals = imports.signals;
|
2008-11-20 19:53:11 -05:00
|
|
|
|
2009-10-15 19:28:29 -04:00
|
|
|
const AppFavorites = imports.ui.appFavorites;
|
2009-06-30 16:35:39 -04:00
|
|
|
const DND = imports.ui.dnd;
|
2014-06-11 07:13:12 -04:00
|
|
|
const GrabHelper = imports.ui.grabHelper;
|
2010-07-20 22:22:19 -04:00
|
|
|
const IconGrid = imports.ui.iconGrid;
|
2009-07-31 17:20:26 -04:00
|
|
|
const Main = imports.ui.main;
|
2019-01-22 06:10:51 -05:00
|
|
|
const PageIndicators = imports.ui.pageIndicators;
|
2010-05-20 11:18:46 -04:00
|
|
|
const PopupMenu = imports.ui.popupMenu;
|
2017-08-21 09:20:25 -04:00
|
|
|
const Search = imports.ui.search;
|
2019-06-30 08:15:37 -04:00
|
|
|
const SwipeTracker = imports.ui.swipeTracker;
|
2010-06-22 12:39:14 -04:00
|
|
|
const Params = imports.misc.params;
|
2012-06-11 13:53:32 -04:00
|
|
|
const Util = imports.misc.util;
|
2017-08-21 09:20:25 -04:00
|
|
|
const SystemActions = imports.misc.systemActions;
|
2009-04-01 15:51:17 -04:00
|
|
|
|
2018-09-05 20:55:20 -04:00
|
|
|
const { loadInterfaceXML } = imports.misc.fileUtils;
|
|
|
|
|
2017-07-18 13:47:27 -04:00
|
|
|
var MENU_POPUP_TIMEOUT = 600;
|
|
|
|
var MAX_COLUMNS = 6;
|
|
|
|
var MIN_COLUMNS = 4;
|
|
|
|
var MIN_ROWS = 4;
|
2009-04-23 10:41:24 -04:00
|
|
|
|
2017-07-18 13:47:27 -04:00
|
|
|
var INACTIVE_GRID_OPACITY = 77;
|
2014-06-17 13:10:54 -04:00
|
|
|
// This time needs to be less than IconGrid.EXTRA_SPACE_ANIMATION_TIME
|
|
|
|
// to not clash with other animations
|
2019-08-01 19:13:10 -04:00
|
|
|
var INACTIVE_GRID_OPACITY_ANIMATION_TIME = 240;
|
2017-07-18 13:47:27 -04:00
|
|
|
var FOLDER_SUBICON_FRACTION = .4;
|
2013-01-31 11:13:37 -05:00
|
|
|
|
2017-07-18 13:47:27 -04:00
|
|
|
var MIN_FREQUENT_APPS_COUNT = 3;
|
2013-05-03 12:20:21 -04:00
|
|
|
|
2019-08-01 19:13:10 -04:00
|
|
|
var VIEWS_SWITCH_TIME = 400;
|
|
|
|
var VIEWS_SWITCH_ANIMATION_DELAY = 100;
|
2017-07-18 13:23:12 -04:00
|
|
|
|
2019-11-02 07:37:06 -04:00
|
|
|
var PAGE_SWITCH_TIME = 250;
|
2013-01-31 11:13:37 -05:00
|
|
|
|
2019-06-29 13:09:32 -04:00
|
|
|
var APP_ICON_SCALE_IN_TIME = 500;
|
|
|
|
var APP_ICON_SCALE_IN_DELAY = 700;
|
|
|
|
|
2019-12-12 13:54:18 -05:00
|
|
|
const FOLDER_DIALOG_ANIMATION_TIME = 200;
|
|
|
|
|
2019-11-23 09:06:14 -05:00
|
|
|
const OVERSHOOT_THRESHOLD = 20;
|
2019-11-23 16:25:00 -05:00
|
|
|
const OVERSHOOT_TIMEOUT = 1000;
|
2019-11-23 09:06:14 -05:00
|
|
|
|
2016-10-19 09:58:16 -04:00
|
|
|
const SWITCHEROO_BUS_NAME = 'net.hadess.SwitcherooControl';
|
|
|
|
const SWITCHEROO_OBJECT_PATH = '/net/hadess/SwitcherooControl';
|
|
|
|
|
2018-09-05 20:55:20 -04:00
|
|
|
const SwitcherooProxyInterface = loadInterfaceXML('net.hadess.SwitcherooControl');
|
2016-10-19 09:58:16 -04:00
|
|
|
const SwitcherooProxy = Gio.DBusProxy.makeProxyWrapper(SwitcherooProxyInterface);
|
|
|
|
let discreteGpuAvailable = false;
|
|
|
|
|
2014-02-14 08:00:17 -05:00
|
|
|
function _getCategories(info) {
|
|
|
|
let categoriesStr = info.get_categories();
|
|
|
|
if (!categoriesStr)
|
|
|
|
return [];
|
|
|
|
return categoriesStr.split(';');
|
|
|
|
}
|
|
|
|
|
2014-01-28 12:06:11 -05:00
|
|
|
function _listsIntersect(a, b) {
|
2019-08-19 20:51:42 -04:00
|
|
|
for (let itemA of a) {
|
2018-07-14 16:56:22 -04:00
|
|
|
if (b.includes(itemA))
|
2014-01-28 12:06:11 -05:00
|
|
|
return true;
|
2019-08-19 20:51:42 -04:00
|
|
|
}
|
2014-01-28 12:06:11 -05:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-12-15 22:24:25 -05:00
|
|
|
function _getFolderName(folder) {
|
|
|
|
let name = folder.get_string('name');
|
|
|
|
|
|
|
|
if (folder.get_boolean('translate')) {
|
2020-02-27 22:36:14 -05:00
|
|
|
let translated = Shell.util_get_translated_folder_name(name);
|
|
|
|
if (translated !== null)
|
|
|
|
return translated;
|
2013-01-31 09:16:25 -05:00
|
|
|
}
|
2013-12-15 22:24:25 -05:00
|
|
|
|
|
|
|
return name;
|
2014-01-13 11:39:30 -05:00
|
|
|
}
|
2013-01-31 09:16:25 -05:00
|
|
|
|
2014-04-27 11:05:10 -04:00
|
|
|
function clamp(value, min, max) {
|
|
|
|
return Math.max(min, Math.min(max, value));
|
|
|
|
}
|
|
|
|
|
2019-06-28 18:47:32 -04:00
|
|
|
function _getViewFromIcon(icon) {
|
2019-07-16 05:24:13 -04:00
|
|
|
for (let parent = icon.get_parent(); parent; parent = parent.get_parent()) {
|
|
|
|
if (parent instanceof BaseAppView)
|
|
|
|
return parent;
|
|
|
|
}
|
|
|
|
return null;
|
2019-06-28 18:47:32 -04:00
|
|
|
}
|
|
|
|
|
2019-07-16 16:51:25 -04:00
|
|
|
function _findBestFolderName(apps) {
|
|
|
|
let appInfos = apps.map(app => app.get_app_info());
|
|
|
|
|
|
|
|
let categoryCounter = {};
|
|
|
|
let commonCategories = [];
|
|
|
|
|
|
|
|
appInfos.reduce((categories, appInfo) => {
|
2019-10-31 19:19:38 -04:00
|
|
|
for (let category of _getCategories(appInfo)) {
|
2019-07-16 16:51:25 -04:00
|
|
|
if (!(category in categoryCounter))
|
|
|
|
categoryCounter[category] = 0;
|
|
|
|
|
|
|
|
categoryCounter[category] += 1;
|
|
|
|
|
|
|
|
// If a category is present in all apps, its counter will
|
|
|
|
// reach appInfos.length
|
|
|
|
if (category.length > 0 &&
|
2019-08-19 20:51:42 -04:00
|
|
|
categoryCounter[category] == appInfos.length)
|
2019-07-16 16:51:25 -04:00
|
|
|
categories.push(category);
|
|
|
|
}
|
|
|
|
return categories;
|
|
|
|
}, commonCategories);
|
|
|
|
|
|
|
|
for (let category of commonCategories) {
|
2020-02-27 22:36:14 -05:00
|
|
|
let translated = Shell.util_get_translated_folder_name(category);
|
|
|
|
if (translated !== null)
|
|
|
|
return translated;
|
2019-07-16 16:51:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
var BaseAppView = GObject.registerClass({
|
|
|
|
GTypeFlags: GObject.TypeFlags.ABSTRACT,
|
|
|
|
Properties: {
|
|
|
|
'use-pagination': GObject.ParamSpec.boolean(
|
|
|
|
'use-pagination', 'use-pagination', 'use-pagination',
|
|
|
|
GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
|
2019-08-20 17:43:54 -04:00
|
|
|
false),
|
2019-07-16 05:24:13 -04:00
|
|
|
},
|
|
|
|
Signals: {
|
|
|
|
'view-loaded': {},
|
2019-08-20 17:43:54 -04:00
|
|
|
},
|
2019-07-16 05:24:13 -04:00
|
|
|
}, class BaseAppView extends St.Widget {
|
|
|
|
_init(params = {}, gridParams) {
|
|
|
|
super._init(params);
|
|
|
|
|
|
|
|
gridParams = Params.parse(gridParams, {
|
|
|
|
columnLimit: MAX_COLUMNS,
|
|
|
|
minRows: MIN_ROWS,
|
|
|
|
minColumns: MIN_COLUMNS,
|
2019-08-20 17:43:54 -04:00
|
|
|
padWithSpacing: true,
|
2019-07-16 05:24:13 -04:00
|
|
|
}, true);
|
|
|
|
|
|
|
|
if (this.use_pagination)
|
2013-08-27 15:22:16 -04:00
|
|
|
this._grid = new IconGrid.PaginatedIconGrid(gridParams);
|
|
|
|
else
|
|
|
|
this._grid = new IconGrid.IconGrid(gridParams);
|
2010-12-18 14:18:10 -05:00
|
|
|
|
2018-08-21 05:21:26 -04:00
|
|
|
this._grid.connect('child-focused', (grid, actor) => {
|
|
|
|
this._childFocused(actor);
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2013-02-19 18:38:11 -05:00
|
|
|
// Standard hack for ClutterBinLayout
|
2018-07-02 10:03:07 -04:00
|
|
|
this._grid.x_expand = true;
|
2013-02-19 18:38:11 -05:00
|
|
|
|
2019-10-31 19:46:35 -04:00
|
|
|
this._items = new Map();
|
2019-10-31 19:44:02 -04:00
|
|
|
this._orderedItems = [];
|
2020-04-02 13:47:52 -04:00
|
|
|
|
|
|
|
this._animateLaterId = 0;
|
|
|
|
this._viewLoadedHandlerId = 0;
|
|
|
|
this._viewIsReady = false;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-02-19 17:23:41 -05:00
|
|
|
|
2019-01-31 09:08:10 -05:00
|
|
|
_childFocused(_actor) {
|
2014-03-20 09:54:04 -04:00
|
|
|
// Nothing by default
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-03-20 09:54:04 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_redisplay() {
|
2019-10-31 19:44:02 -04:00
|
|
|
let oldApps = this._orderedItems.slice();
|
2019-07-01 22:13:07 -04:00
|
|
|
let oldAppIds = oldApps.map(icon => icon.id);
|
|
|
|
|
|
|
|
let newApps = this._loadApps().sort(this._compareItems);
|
|
|
|
let newAppIds = newApps.map(icon => icon.id);
|
|
|
|
|
|
|
|
let addedApps = newApps.filter(icon => !oldAppIds.includes(icon.id));
|
|
|
|
let removedApps = oldApps.filter(icon => !newAppIds.includes(icon.id));
|
|
|
|
|
|
|
|
// Remove old app icons
|
|
|
|
removedApps.forEach(icon => {
|
2019-10-31 19:44:02 -04:00
|
|
|
let iconIndex = this._orderedItems.indexOf(icon);
|
2019-11-21 16:21:44 -05:00
|
|
|
let id = icon.id;
|
2019-07-01 22:13:07 -04:00
|
|
|
|
2019-10-31 19:44:02 -04:00
|
|
|
this._orderedItems.splice(iconIndex, 1);
|
2019-11-21 16:21:44 -05:00
|
|
|
icon.destroy();
|
2019-10-31 19:46:35 -04:00
|
|
|
this._items.delete(id);
|
2019-07-01 22:13:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
// Add new app icons
|
|
|
|
addedApps.forEach(icon => {
|
|
|
|
let iconIndex = newApps.indexOf(icon);
|
|
|
|
|
2019-10-31 19:44:02 -04:00
|
|
|
this._orderedItems.splice(iconIndex, 0, icon);
|
2019-07-01 22:13:07 -04:00
|
|
|
this._grid.addItem(icon, iconIndex);
|
2019-10-31 19:46:35 -04:00
|
|
|
this._items.set(icon.id, icon);
|
2019-07-01 22:13:07 -04:00
|
|
|
});
|
2019-07-01 22:01:59 -04:00
|
|
|
|
appDisplay: Don't start animation from the 'paint' signal
Starting the animation from the actor 'paint' signal has various
unwanted consequences, such as sometimes trigger a
clutter_actor_queue_relayout() during the paint phase. One unwanted
consequence was that an offscreen actor effect was disabled during
painting, meaning the effect would begin being active, but later during
the post-paint processing being disabled. The caused said effect to push
an offscreen framebuffer to the paint context, but then just destroy it
instead of popping it. When this happened, we'd end up trying to operate
on a framebuffer that may had been finalized, or not, depending on the
garbage collector. Sometimes, for some users, this caused a segmentation
fault when trying to pop a matrix from the framebuffer matrix stack.
Deal with this more properly, by using the 'view-loaded' signal to wait
with animation until the view is loaded, as well as using MetaLater to
schedule the start of the animation.
For when a view was signalled to be ready, we're in a state where we can
start animation before the next frame as the layout is ready, but when
not, we have to add back the "hack" where we must wait for one frame for
the target icon positions to be up to date. Do this by adding a
MetaLater IDLE callback that starts the animation *after* the next
frame. This also needs the old 'opacity = 0' work around to not show an
incorrect first frame.
Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2418
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1154
2020-03-30 09:16:53 -04:00
|
|
|
this._viewIsReady = true;
|
2019-07-01 22:01:59 -04:00
|
|
|
this.emit('view-loaded');
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-01-28 11:11:10 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
getAllItems() {
|
2019-10-31 19:44:02 -04:00
|
|
|
return this._orderedItems;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-01-28 11:36:57 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_compareItems(a, b) {
|
2014-01-28 11:36:57 -05:00
|
|
|
return a.name.localeCompare(b.name);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-01-28 11:36:57 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_selectAppInternal(id) {
|
2019-10-31 19:46:35 -04:00
|
|
|
if (this._items.has(id))
|
|
|
|
this._items.get(id).navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
2011-07-09 09:30:42 -04:00
|
|
|
else
|
2020-02-14 10:10:34 -05:00
|
|
|
log('No such application %s'.format(id));
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2011-07-09 09:30:42 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
selectApp(id) {
|
2019-10-31 19:46:35 -04:00
|
|
|
if (this._items.has(id)) {
|
|
|
|
let item = this._items.get(id);
|
|
|
|
|
|
|
|
if (item.mapped) {
|
|
|
|
this._selectAppInternal(id);
|
|
|
|
} else {
|
|
|
|
// Need to wait until the view is mapped
|
|
|
|
let signalId = item.connect('notify::mapped', actor => {
|
2017-10-30 20:38:18 -04:00
|
|
|
if (actor.mapped) {
|
|
|
|
actor.disconnect(signalId);
|
|
|
|
this._selectAppInternal(id);
|
|
|
|
}
|
|
|
|
});
|
2019-10-31 19:46:35 -04:00
|
|
|
}
|
2011-07-09 09:30:42 -04:00
|
|
|
} else {
|
|
|
|
// Need to wait until the view is built
|
2017-10-30 20:38:18 -04:00
|
|
|
let signalId = this.connect('view-loaded', () => {
|
2011-07-09 09:30:42 -04:00
|
|
|
this.disconnect(signalId);
|
|
|
|
this.selectApp(id);
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2011-07-09 09:30:42 -04:00
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-06-17 13:10:54 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_doSpringAnimation(animationDirection) {
|
2018-07-02 10:03:07 -04:00
|
|
|
this._grid.opacity = 255;
|
2019-08-30 21:51:02 -04:00
|
|
|
this._grid.animateSpring(
|
|
|
|
animationDirection,
|
|
|
|
Main.overview.dash.showAppsButton);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-06-17 13:10:54 -04:00
|
|
|
|
appDisplay: Don't start animation from the 'paint' signal
Starting the animation from the actor 'paint' signal has various
unwanted consequences, such as sometimes trigger a
clutter_actor_queue_relayout() during the paint phase. One unwanted
consequence was that an offscreen actor effect was disabled during
painting, meaning the effect would begin being active, but later during
the post-paint processing being disabled. The caused said effect to push
an offscreen framebuffer to the paint context, but then just destroy it
instead of popping it. When this happened, we'd end up trying to operate
on a framebuffer that may had been finalized, or not, depending on the
garbage collector. Sometimes, for some users, this caused a segmentation
fault when trying to pop a matrix from the framebuffer matrix stack.
Deal with this more properly, by using the 'view-loaded' signal to wait
with animation until the view is loaded, as well as using MetaLater to
schedule the start of the animation.
For when a view was signalled to be ready, we're in a state where we can
start animation before the next frame as the layout is ready, but when
not, we have to add back the "hack" where we must wait for one frame for
the target icon positions to be up to date. Do this by adding a
MetaLater IDLE callback that starts the animation *after* the next
frame. This also needs the old 'opacity = 0' work around to not show an
incorrect first frame.
Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2418
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1154
2020-03-30 09:16:53 -04:00
|
|
|
_clearAnimateLater() {
|
|
|
|
if (this._animateLaterId) {
|
|
|
|
Meta.later_remove(this._animateLaterId);
|
|
|
|
this._animateLaterId = 0;
|
|
|
|
}
|
|
|
|
if (this._viewLoadedHandlerId) {
|
|
|
|
this.disconnect(this._viewLoadedHandlerId);
|
|
|
|
this._viewLoadedHandlerId = 0;
|
|
|
|
}
|
|
|
|
this._grid.opacity = 255;
|
|
|
|
}
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
animate(animationDirection, onComplete) {
|
2016-11-18 03:00:01 -05:00
|
|
|
if (onComplete) {
|
2017-10-30 20:38:18 -04:00
|
|
|
let animationDoneId = this._grid.connect('animation-done', () => {
|
|
|
|
this._grid.disconnect(animationDoneId);
|
|
|
|
onComplete();
|
|
|
|
});
|
2016-11-18 03:00:01 -05:00
|
|
|
}
|
|
|
|
|
appDisplay: Don't start animation from the 'paint' signal
Starting the animation from the actor 'paint' signal has various
unwanted consequences, such as sometimes trigger a
clutter_actor_queue_relayout() during the paint phase. One unwanted
consequence was that an offscreen actor effect was disabled during
painting, meaning the effect would begin being active, but later during
the post-paint processing being disabled. The caused said effect to push
an offscreen framebuffer to the paint context, but then just destroy it
instead of popping it. When this happened, we'd end up trying to operate
on a framebuffer that may had been finalized, or not, depending on the
garbage collector. Sometimes, for some users, this caused a segmentation
fault when trying to pop a matrix from the framebuffer matrix stack.
Deal with this more properly, by using the 'view-loaded' signal to wait
with animation until the view is loaded, as well as using MetaLater to
schedule the start of the animation.
For when a view was signalled to be ready, we're in a state where we can
start animation before the next frame as the layout is ready, but when
not, we have to add back the "hack" where we must wait for one frame for
the target icon positions to be up to date. Do this by adding a
MetaLater IDLE callback that starts the animation *after* the next
frame. This also needs the old 'opacity = 0' work around to not show an
incorrect first frame.
Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2418
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1154
2020-03-30 09:16:53 -04:00
|
|
|
this._clearAnimateLater();
|
|
|
|
|
2014-06-17 13:10:54 -04:00
|
|
|
if (animationDirection == IconGrid.AnimationDirection.IN) {
|
appDisplay: Don't start animation from the 'paint' signal
Starting the animation from the actor 'paint' signal has various
unwanted consequences, such as sometimes trigger a
clutter_actor_queue_relayout() during the paint phase. One unwanted
consequence was that an offscreen actor effect was disabled during
painting, meaning the effect would begin being active, but later during
the post-paint processing being disabled. The caused said effect to push
an offscreen framebuffer to the paint context, but then just destroy it
instead of popping it. When this happened, we'd end up trying to operate
on a framebuffer that may had been finalized, or not, depending on the
garbage collector. Sometimes, for some users, this caused a segmentation
fault when trying to pop a matrix from the framebuffer matrix stack.
Deal with this more properly, by using the 'view-loaded' signal to wait
with animation until the view is loaded, as well as using MetaLater to
schedule the start of the animation.
For when a view was signalled to be ready, we're in a state where we can
start animation before the next frame as the layout is ready, but when
not, we have to add back the "hack" where we must wait for one frame for
the target icon positions to be up to date. Do this by adding a
MetaLater IDLE callback that starts the animation *after* the next
frame. This also needs the old 'opacity = 0' work around to not show an
incorrect first frame.
Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2418
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1154
2020-03-30 09:16:53 -04:00
|
|
|
const doSpringAnimationLater = laterType => {
|
|
|
|
this._animateLaterId = Meta.later_add(laterType,
|
|
|
|
() => {
|
|
|
|
this._animateLaterId = 0;
|
|
|
|
this._doSpringAnimation(animationDirection);
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
if (this._viewIsReady) {
|
|
|
|
this._grid.opacity = 0;
|
|
|
|
doSpringAnimationLater(Meta.LaterType.IDLE);
|
|
|
|
} else {
|
|
|
|
this._viewLoadedHandlerId = this.connect('view-loaded',
|
|
|
|
() => {
|
|
|
|
this._clearAnimateLater();
|
|
|
|
doSpringAnimationLater(Meta.LaterType.BEFORE_REDRAW);
|
|
|
|
});
|
|
|
|
}
|
2014-06-17 13:10:54 -04:00
|
|
|
} else {
|
|
|
|
this._doSpringAnimation(animationDirection);
|
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-06-17 15:31:53 -04:00
|
|
|
|
2020-03-31 13:00:58 -04:00
|
|
|
vfunc_unmap() {
|
|
|
|
this._clearAnimateLater();
|
|
|
|
super.vfunc_unmap();
|
|
|
|
}
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
animateSwitch(animationDirection) {
|
2019-07-16 05:24:13 -04:00
|
|
|
this.remove_all_transitions();
|
2018-07-20 15:46:19 -04:00
|
|
|
this._grid.remove_all_transitions();
|
2014-06-17 15:31:53 -04:00
|
|
|
|
2018-07-20 15:46:19 -04:00
|
|
|
let params = {
|
|
|
|
duration: VIEWS_SWITCH_TIME,
|
2019-08-20 17:43:54 -04:00
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
2018-07-20 15:46:19 -04:00
|
|
|
};
|
2014-06-17 15:31:53 -04:00
|
|
|
if (animationDirection == IconGrid.AnimationDirection.IN) {
|
2019-07-16 05:24:13 -04:00
|
|
|
this.show();
|
2014-06-17 15:31:53 -04:00
|
|
|
params.opacity = 255;
|
2018-07-20 15:46:19 -04:00
|
|
|
params.delay = VIEWS_SWITCH_ANIMATION_DELAY;
|
2014-06-17 15:31:53 -04:00
|
|
|
} else {
|
|
|
|
params.opacity = 0;
|
|
|
|
params.delay = 0;
|
2019-07-16 05:24:13 -04:00
|
|
|
params.onComplete = () => this.hide();
|
2014-06-17 15:31:53 -04:00
|
|
|
}
|
|
|
|
|
2018-07-20 15:46:19 -04:00
|
|
|
this._grid.ease(params);
|
2013-02-19 17:23:41 -05:00
|
|
|
}
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
adaptToSize(_width, _height) {
|
2020-02-14 10:10:34 -05:00
|
|
|
throw new GObject.NotImplementedError('adaptToSize in %s'.format(this.constructor.name));
|
2019-07-16 05:24:13 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var AllView = GObject.registerClass({
|
|
|
|
}, class AllView extends BaseAppView {
|
|
|
|
_init() {
|
|
|
|
super._init({
|
|
|
|
layout_manager: new Clutter.BinLayout(),
|
|
|
|
x_expand: true,
|
|
|
|
y_expand: true,
|
2019-08-20 17:43:54 -04:00
|
|
|
use_pagination: true,
|
2019-07-16 05:24:13 -04:00
|
|
|
});
|
|
|
|
|
2019-08-20 17:43:54 -04:00
|
|
|
this._scrollView = new St.ScrollView({
|
|
|
|
style_class: 'all-apps',
|
|
|
|
x_expand: true,
|
|
|
|
y_expand: true,
|
|
|
|
reactive: true,
|
|
|
|
});
|
2019-07-16 05:24:13 -04:00
|
|
|
this.add_actor(this._scrollView);
|
2019-06-28 18:47:32 -04:00
|
|
|
this._grid._delegate = this;
|
2013-09-03 12:59:06 -04:00
|
|
|
|
2018-11-27 07:45:36 -05:00
|
|
|
this._scrollView.set_policy(St.PolicyType.NEVER,
|
|
|
|
St.PolicyType.EXTERNAL);
|
2013-09-03 12:59:06 -04:00
|
|
|
this._adjustment = this._scrollView.vscroll.adjustment;
|
2019-11-21 17:24:46 -05:00
|
|
|
this._adjustment.connect('notify::value', adj => {
|
|
|
|
this._pageIndicators.setCurrentPosition(adj.value / adj.page_size);
|
|
|
|
});
|
2013-08-27 15:22:16 -04:00
|
|
|
|
2019-01-22 06:10:51 -05:00
|
|
|
this._pageIndicators = new PageIndicators.AnimatedPageIndicators();
|
2017-10-30 20:38:18 -04:00
|
|
|
this._pageIndicators.connect('page-activated',
|
|
|
|
(indicators, pageIndex) => {
|
2013-08-12 13:09:43 -04:00
|
|
|
this.goToPage(pageIndex);
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2019-06-30 08:15:37 -04:00
|
|
|
this._pageIndicators.connect('scroll-event', (actor, event) => {
|
|
|
|
this._scrollView.event(event, false);
|
|
|
|
});
|
2019-07-16 05:24:13 -04:00
|
|
|
this.add_actor(this._pageIndicators);
|
2013-08-12 13:09:43 -04:00
|
|
|
|
2019-12-05 08:06:48 -05:00
|
|
|
this._folderIcons = [];
|
2013-08-30 12:50:35 -04:00
|
|
|
|
2013-08-27 15:22:16 -04:00
|
|
|
this._stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
|
2019-10-17 17:40:24 -04:00
|
|
|
let box = new St.BoxLayout({
|
|
|
|
vertical: true,
|
|
|
|
y_align: Clutter.ActorAlign.START,
|
|
|
|
});
|
2013-08-27 15:22:16 -04:00
|
|
|
|
2014-06-17 13:10:54 -04:00
|
|
|
this._grid.currentPage = 0;
|
2018-07-02 10:03:07 -04:00
|
|
|
this._stack.add_actor(this._grid);
|
2019-11-23 15:11:14 -05:00
|
|
|
this._eventBlocker = new St.Widget({
|
|
|
|
x_expand: true,
|
|
|
|
y_expand: true,
|
|
|
|
reactive: true,
|
|
|
|
visible: false,
|
|
|
|
});
|
2013-02-18 15:04:51 -05:00
|
|
|
this._stack.add_actor(this._eventBlocker);
|
2013-08-27 15:22:16 -04:00
|
|
|
|
|
|
|
box.add_actor(this._stack);
|
2013-09-03 12:59:06 -04:00
|
|
|
this._scrollView.add_actor(box);
|
2013-08-27 15:22:16 -04:00
|
|
|
|
2017-12-01 19:27:35 -05:00
|
|
|
this._scrollView.connect('scroll-event', this._onScroll.bind(this));
|
2013-02-18 15:04:51 -05:00
|
|
|
|
2019-06-30 08:15:37 -04:00
|
|
|
this._swipeTracker = new SwipeTracker.SwipeTracker(
|
|
|
|
this._scrollView, Shell.ActionMode.OVERVIEW);
|
|
|
|
this._swipeTracker.connect('begin', this._swipeBegin.bind(this));
|
|
|
|
this._swipeTracker.connect('update', this._swipeUpdate.bind(this));
|
|
|
|
this._swipeTracker.connect('end', this._swipeEnd.bind(this));
|
|
|
|
|
2013-02-18 15:04:51 -05:00
|
|
|
this._clickAction = new Clutter.ClickAction();
|
2017-10-30 20:38:18 -04:00
|
|
|
this._clickAction.connect('clicked', () => {
|
2019-12-12 14:00:01 -05:00
|
|
|
if (!this._currentDialog)
|
2013-02-18 15:04:51 -05:00
|
|
|
return;
|
|
|
|
|
|
|
|
let [x, y] = this._clickAction.get_coords();
|
|
|
|
let actor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
|
2019-12-12 14:00:01 -05:00
|
|
|
if (!this._currentDialog.contains(actor))
|
|
|
|
this._currentDialog.popdown();
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2013-02-18 15:04:51 -05:00
|
|
|
this._eventBlocker.add_action(this._clickAction);
|
2013-08-27 15:22:16 -04:00
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
this._currentDialog = null;
|
|
|
|
this._displayingDialog = false;
|
|
|
|
this._currentDialogDestroyId = 0;
|
2013-07-09 09:11:03 -04:00
|
|
|
|
2019-10-16 03:30:14 -04:00
|
|
|
this._canScroll = true; // limiting scrolling speed
|
|
|
|
this._scrollTimeoutId = 0;
|
|
|
|
|
2013-08-27 15:22:16 -04:00
|
|
|
this._availWidth = 0;
|
|
|
|
this._availHeight = 0;
|
2013-08-19 08:02:02 -04:00
|
|
|
|
2019-11-23 09:06:14 -05:00
|
|
|
this._lastOvershootY = -1;
|
2019-11-23 16:25:00 -05:00
|
|
|
this._lastOvershootTimeoutId = 0;
|
2019-11-23 09:06:14 -05:00
|
|
|
|
2019-01-27 19:42:00 -05:00
|
|
|
Main.overview.connect('hidden', () => this.goToPage(0));
|
2017-10-30 20:38:18 -04:00
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
this._redisplayWorkId = Main.initializeDeferredWork(this, this._redisplay.bind(this));
|
2014-01-28 11:11:10 -05:00
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
Shell.AppSystem.get_default().connect('installed-changed', () => {
|
appDisplay: Don't start animation from the 'paint' signal
Starting the animation from the actor 'paint' signal has various
unwanted consequences, such as sometimes trigger a
clutter_actor_queue_relayout() during the paint phase. One unwanted
consequence was that an offscreen actor effect was disabled during
painting, meaning the effect would begin being active, but later during
the post-paint processing being disabled. The caused said effect to push
an offscreen framebuffer to the paint context, but then just destroy it
instead of popping it. When this happened, we'd end up trying to operate
on a framebuffer that may had been finalized, or not, depending on the
garbage collector. Sometimes, for some users, this caused a segmentation
fault when trying to pop a matrix from the framebuffer matrix stack.
Deal with this more properly, by using the 'view-loaded' signal to wait
with animation until the view is loaded, as well as using MetaLater to
schedule the start of the animation.
For when a view was signalled to be ready, we're in a state where we can
start animation before the next frame as the layout is ready, but when
not, we have to add back the "hack" where we must wait for one frame for
the target icon positions to be up to date. Do this by adding a
MetaLater IDLE callback that starts the animation *after* the next
frame. This also needs the old 'opacity = 0' work around to not show an
incorrect first frame.
Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2418
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1154
2020-03-30 09:16:53 -04:00
|
|
|
this._viewIsReady = false;
|
2014-01-28 11:11:10 -05:00
|
|
|
Main.queueDeferredWork(this._redisplayWorkId);
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2014-06-24 15:17:09 -04:00
|
|
|
this._folderSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' });
|
2017-10-30 20:38:18 -04:00
|
|
|
this._folderSettings.connect('changed::folder-children', () => {
|
appDisplay: Don't start animation from the 'paint' signal
Starting the animation from the actor 'paint' signal has various
unwanted consequences, such as sometimes trigger a
clutter_actor_queue_relayout() during the paint phase. One unwanted
consequence was that an offscreen actor effect was disabled during
painting, meaning the effect would begin being active, but later during
the post-paint processing being disabled. The caused said effect to push
an offscreen framebuffer to the paint context, but then just destroy it
instead of popping it. When this happened, we'd end up trying to operate
on a framebuffer that may had been finalized, or not, depending on the
garbage collector. Sometimes, for some users, this caused a segmentation
fault when trying to pop a matrix from the framebuffer matrix stack.
Deal with this more properly, by using the 'view-loaded' signal to wait
with animation until the view is loaded, as well as using MetaLater to
schedule the start of the animation.
For when a view was signalled to be ready, we're in a state where we can
start animation before the next frame as the layout is ready, but when
not, we have to add back the "hack" where we must wait for one frame for
the target icon positions to be up to date. Do this by adding a
MetaLater IDLE callback that starts the animation *after* the next
frame. This also needs the old 'opacity = 0' work around to not show an
incorrect first frame.
Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2418
https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1154
2020-03-30 09:16:53 -04:00
|
|
|
this._viewIsReady = false;
|
2014-01-28 11:11:10 -05:00
|
|
|
Main.queueDeferredWork(this._redisplayWorkId);
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2019-06-28 18:44:20 -04:00
|
|
|
|
2019-06-28 19:48:22 -04:00
|
|
|
Main.overview.connect('item-drag-begin', this._onDragBegin.bind(this));
|
|
|
|
Main.overview.connect('item-drag-end', this._onDragEnd.bind(this));
|
2019-10-16 03:30:14 -04:00
|
|
|
|
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
|
|
|
}
|
|
|
|
|
|
|
|
_onDestroy() {
|
|
|
|
if (this._scrollTimeoutId !== 0) {
|
|
|
|
GLib.source_remove(this._scrollTimeoutId);
|
|
|
|
this._scrollTimeoutId = 0;
|
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-01-28 11:11:10 -05:00
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
vfunc_map() {
|
|
|
|
this._keyPressEventId =
|
|
|
|
global.stage.connect('key-press-event',
|
|
|
|
this._onKeyPressEvent.bind(this));
|
2019-06-30 08:15:37 -04:00
|
|
|
this._swipeTracker.enabled = true;
|
2019-09-10 01:42:48 -04:00
|
|
|
super.vfunc_map();
|
|
|
|
}
|
|
|
|
|
|
|
|
vfunc_unmap() {
|
|
|
|
if (this._keyPressEventId) {
|
|
|
|
global.stage.disconnect(this._keyPressEventId);
|
|
|
|
this._keyPressEventId = 0;
|
|
|
|
}
|
2019-06-30 08:15:37 -04:00
|
|
|
this._swipeTracker.enabled = false;
|
2019-09-10 01:42:48 -04:00
|
|
|
super.vfunc_unmap();
|
|
|
|
}
|
|
|
|
|
2019-07-18 11:13:27 -04:00
|
|
|
_redisplay() {
|
|
|
|
super._redisplay();
|
2020-02-17 10:21:58 -05:00
|
|
|
|
|
|
|
this._folderIcons.forEach(icon => {
|
|
|
|
icon.view._redisplay();
|
|
|
|
});
|
2019-07-01 22:01:59 -04:00
|
|
|
this._refilterApps();
|
2019-07-18 11:13:27 -04:00
|
|
|
}
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_itemNameChanged(item) {
|
2014-01-28 11:36:57 -05:00
|
|
|
// If an item's name changed, we can pluck it out of where it's
|
|
|
|
// supposed to be and reinsert it where it's sorted.
|
2019-10-31 19:44:02 -04:00
|
|
|
let oldIdx = this._orderedItems.indexOf(item);
|
|
|
|
this._orderedItems.splice(oldIdx, 1);
|
|
|
|
let newIdx = Util.insertSorted(this._orderedItems, item, this._compareItems);
|
2014-01-28 11:36:57 -05:00
|
|
|
|
|
|
|
this._grid.removeItem(item);
|
|
|
|
this._grid.addItem(item, newIdx);
|
2019-12-04 15:10:40 -05:00
|
|
|
this.selectApp(item.id);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-01-28 11:36:57 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_refilterApps() {
|
2019-10-31 19:44:02 -04:00
|
|
|
let filteredApps = this._orderedItems.filter(icon => !icon.visible);
|
2019-06-29 13:09:32 -04:00
|
|
|
|
2019-10-31 19:44:02 -04:00
|
|
|
this._orderedItems.forEach(icon => {
|
2014-01-28 11:36:57 -05:00
|
|
|
if (icon instanceof AppIcon)
|
2019-07-16 05:24:13 -04:00
|
|
|
icon.visible = true;
|
2014-01-28 11:36:57 -05:00
|
|
|
});
|
|
|
|
|
2019-12-05 08:06:48 -05:00
|
|
|
this._folderIcons.forEach(folder => {
|
2014-01-28 11:36:57 -05:00
|
|
|
let folderApps = folder.getAppIds();
|
2017-10-30 20:38:18 -04:00
|
|
|
folderApps.forEach(appId => {
|
2019-10-31 19:46:35 -04:00
|
|
|
let appIcon = this._items.get(appId);
|
2019-07-16 05:24:13 -04:00
|
|
|
appIcon.visible = false;
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
|
|
|
});
|
2019-06-29 13:09:32 -04:00
|
|
|
|
|
|
|
// Scale in app icons that weren't visible, but now are
|
2019-07-16 05:24:13 -04:00
|
|
|
filteredApps.filter(icon => icon.visible).forEach(icon => {
|
2019-06-29 13:09:32 -04:00
|
|
|
if (icon instanceof AppIcon)
|
|
|
|
icon.scaleIn();
|
|
|
|
});
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-01-28 11:36:57 -05:00
|
|
|
|
2018-12-03 07:18:19 -05:00
|
|
|
getAppInfos() {
|
|
|
|
return this._appInfoList;
|
|
|
|
}
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_loadApps() {
|
2019-11-21 16:03:34 -05:00
|
|
|
let appIcons = [];
|
2018-12-19 07:26:54 -05:00
|
|
|
this._appInfoList = Shell.AppSystem.get_default().get_installed().filter(appInfo => {
|
2015-07-30 13:55:19 -04:00
|
|
|
try {
|
2019-08-19 15:38:51 -04:00
|
|
|
appInfo.get_id(); // catch invalid file encodings
|
2019-01-28 20:26:39 -05:00
|
|
|
} catch (e) {
|
2015-07-30 13:55:19 -04:00
|
|
|
return false;
|
|
|
|
}
|
2014-01-28 11:11:10 -05:00
|
|
|
return appInfo.should_show();
|
2018-12-03 07:18:19 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
let apps = this._appInfoList.map(app => app.get_id());
|
2014-01-28 11:11:10 -05:00
|
|
|
|
|
|
|
let appSys = Shell.AppSystem.get_default();
|
|
|
|
|
2019-12-05 08:06:48 -05:00
|
|
|
this._folderIcons = [];
|
2019-07-01 22:13:07 -04:00
|
|
|
|
2014-01-28 11:11:10 -05:00
|
|
|
let folders = this._folderSettings.get_strv('folder-children');
|
2017-10-30 20:38:18 -04:00
|
|
|
folders.forEach(id => {
|
2020-02-14 10:10:34 -05:00
|
|
|
let path = '%sfolders/%s/'.format(this._folderSettings.path, id);
|
2019-10-31 19:46:35 -04:00
|
|
|
let icon = this._items.get(id);
|
2019-07-30 11:15:51 -04:00
|
|
|
if (!icon) {
|
|
|
|
icon = new FolderIcon(id, path, this);
|
|
|
|
icon.connect('name-changed', this._itemNameChanged.bind(this));
|
2019-07-02 12:33:07 -04:00
|
|
|
icon.connect('apps-changed', this._redisplay.bind(this));
|
2019-07-30 11:15:51 -04:00
|
|
|
}
|
2019-11-21 16:03:34 -05:00
|
|
|
appIcons.push(icon);
|
2019-12-05 08:06:48 -05:00
|
|
|
this._folderIcons.push(icon);
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2014-01-28 11:11:10 -05:00
|
|
|
|
2015-01-27 16:10:45 -05:00
|
|
|
// Allow dragging of the icon only if the Dash would accept a drop to
|
|
|
|
// change favorite-apps. There are no other possible drop targets from
|
|
|
|
// the app picker, so there's no other need for a drag to start,
|
|
|
|
// at least on single-monitor setups.
|
|
|
|
// This also disables drag-to-launch on multi-monitor setups,
|
|
|
|
// but we hope that is not used much.
|
2015-03-20 15:30:57 -04:00
|
|
|
let favoritesWritable = global.settings.is_writable('favorite-apps');
|
2015-01-27 16:10:45 -05:00
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
apps.forEach(appId => {
|
2019-10-31 19:46:35 -04:00
|
|
|
let icon = this._items.get(appId);
|
2019-11-21 15:57:17 -05:00
|
|
|
if (!icon) {
|
|
|
|
let app = appSys.lookup_app(appId);
|
|
|
|
|
|
|
|
icon = new AppIcon(app, {
|
|
|
|
isDraggable: favoritesWritable,
|
|
|
|
});
|
|
|
|
}
|
2015-01-27 16:10:45 -05:00
|
|
|
|
2019-11-21 16:03:34 -05:00
|
|
|
appIcons.push(icon);
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2019-07-01 22:13:07 -04:00
|
|
|
|
2019-11-21 16:03:34 -05:00
|
|
|
return appIcons;
|
2019-06-29 12:22:08 -04:00
|
|
|
}
|
2014-01-28 11:11:10 -05:00
|
|
|
|
2019-05-15 15:32:29 -04:00
|
|
|
// Overridden from BaseAppView
|
2017-10-30 20:03:21 -04:00
|
|
|
animate(animationDirection, onComplete) {
|
2015-03-04 04:29:22 -05:00
|
|
|
this._scrollView.reactive = false;
|
2020-02-05 14:59:38 -05:00
|
|
|
this._swipeTracker.enabled = false;
|
2017-10-30 20:38:18 -04:00
|
|
|
let completionFunc = () => {
|
2015-03-04 04:29:22 -05:00
|
|
|
this._scrollView.reactive = true;
|
2020-02-05 14:59:38 -05:00
|
|
|
this._swipeTracker.enabled = this.mapped;
|
2015-03-04 12:58:14 -05:00
|
|
|
if (onComplete)
|
|
|
|
onComplete();
|
2017-10-30 20:38:18 -04:00
|
|
|
};
|
2015-03-04 04:29:22 -05:00
|
|
|
|
2014-06-17 13:10:54 -04:00
|
|
|
if (animationDirection == IconGrid.AnimationDirection.OUT &&
|
2019-12-12 14:00:01 -05:00
|
|
|
this._displayingDialog && this._currentDialog) {
|
|
|
|
this._currentDialog.popdown();
|
2017-10-30 20:38:18 -04:00
|
|
|
let spaceClosedId = this._grid.connect('space-closed', () => {
|
|
|
|
this._grid.disconnect(spaceClosedId);
|
2017-10-30 21:19:44 -04:00
|
|
|
super.animate(animationDirection, completionFunc);
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2014-06-17 13:10:54 -04:00
|
|
|
} else {
|
2017-10-30 21:19:44 -04:00
|
|
|
super.animate(animationDirection, completionFunc);
|
2014-06-17 15:31:53 -04:00
|
|
|
if (animationDirection == IconGrid.AnimationDirection.OUT)
|
|
|
|
this._pageIndicators.animateIndicators(animationDirection);
|
2014-06-17 13:10:54 -04:00
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-06-17 13:10:54 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
animateSwitch(animationDirection) {
|
2017-10-30 21:19:44 -04:00
|
|
|
super.animateSwitch(animationDirection);
|
2014-06-17 15:31:53 -04:00
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
if (this._currentDialog && this._displayingDialog &&
|
2019-08-19 20:51:42 -04:00
|
|
|
animationDirection == IconGrid.AnimationDirection.OUT) {
|
2019-12-12 14:00:01 -05:00
|
|
|
this._currentDialog.ease({
|
2018-07-20 15:46:19 -04:00
|
|
|
opacity: 0,
|
|
|
|
duration: VIEWS_SWITCH_TIME,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
2019-08-20 17:43:54 -04:00
|
|
|
onComplete: () => (this.opacity = 255),
|
2018-07-20 15:46:19 -04:00
|
|
|
});
|
2019-08-19 20:51:42 -04:00
|
|
|
}
|
2014-06-17 15:31:53 -04:00
|
|
|
|
|
|
|
if (animationDirection == IconGrid.AnimationDirection.OUT)
|
|
|
|
this._pageIndicators.animateIndicators(animationDirection);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-06-17 15:31:53 -04:00
|
|
|
|
2019-06-30 08:15:37 -04:00
|
|
|
goToPage(pageNumber, animate = true) {
|
2014-04-27 11:05:10 -04:00
|
|
|
pageNumber = clamp(pageNumber, 0, this._grid.nPages() - 1);
|
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
if (this._grid.currentPage === pageNumber &&
|
|
|
|
this._displayingDialog &&
|
|
|
|
this._currentDialog)
|
2013-07-09 09:11:03 -04:00
|
|
|
return;
|
2019-12-12 14:00:01 -05:00
|
|
|
if (this._displayingDialog && this._currentDialog)
|
|
|
|
this._currentDialog.popdown();
|
2013-07-09 09:11:03 -04:00
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
if (!this.mapped) {
|
2019-07-25 21:04:38 -04:00
|
|
|
this._adjustment.value = this._grid.getPageY(pageNumber);
|
2019-11-21 17:24:46 -05:00
|
|
|
this._pageIndicators.setCurrentPosition(pageNumber);
|
2019-08-06 18:41:54 -04:00
|
|
|
this._grid.currentPage = pageNumber;
|
2019-07-25 21:04:38 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-06-30 08:15:37 -04:00
|
|
|
if (this._grid.currentPage === pageNumber)
|
|
|
|
return;
|
2013-08-12 10:36:45 -04:00
|
|
|
|
2014-06-17 13:10:54 -04:00
|
|
|
this._grid.currentPage = pageNumber;
|
2013-08-12 10:36:45 -04:00
|
|
|
|
2019-06-30 08:15:37 -04:00
|
|
|
// Tween the change between pages.
|
|
|
|
this._adjustment.ease(this._grid.getPageY(this._grid.currentPage), {
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
|
|
|
|
duration: animate ? PAGE_SWITCH_TIME : 0,
|
|
|
|
});
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-02-18 15:04:51 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_onScroll(actor, event) {
|
2019-12-12 14:00:01 -05:00
|
|
|
if (this._displayingDialog || !this._scrollView.reactive)
|
2013-11-29 13:17:34 -05:00
|
|
|
return Clutter.EVENT_STOP;
|
2013-09-13 12:53:02 -04:00
|
|
|
|
2019-06-30 08:15:37 -04:00
|
|
|
if (this._swipeTracker.canHandleScrollEvent(event))
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
|
2019-10-16 03:30:14 -04:00
|
|
|
if (!this._canScroll)
|
|
|
|
return Clutter.EVENT_STOP;
|
|
|
|
|
2013-08-27 15:22:16 -04:00
|
|
|
let direction = event.get_scroll_direction();
|
2013-09-13 12:53:02 -04:00
|
|
|
if (direction == Clutter.ScrollDirection.UP)
|
2014-06-17 13:10:54 -04:00
|
|
|
this.goToPage(this._grid.currentPage - 1);
|
2013-09-13 12:53:02 -04:00
|
|
|
else if (direction == Clutter.ScrollDirection.DOWN)
|
2014-06-17 13:10:54 -04:00
|
|
|
this.goToPage(this._grid.currentPage + 1);
|
2019-10-16 03:30:14 -04:00
|
|
|
else
|
|
|
|
return Clutter.EVENT_STOP;
|
|
|
|
|
|
|
|
this._canScroll = false;
|
|
|
|
this._scrollTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
|
|
|
|
PAGE_SWITCH_TIME, () => {
|
|
|
|
this._canScroll = true;
|
|
|
|
this._scrollTimeoutId = 0;
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
|
|
}
|
|
|
|
);
|
2013-09-13 12:53:02 -04:00
|
|
|
|
2013-11-29 13:17:34 -05:00
|
|
|
return Clutter.EVENT_STOP;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2008-12-19 23:27:57 -05:00
|
|
|
|
2019-06-30 08:15:37 -04:00
|
|
|
_swipeBegin(tracker, monitor) {
|
|
|
|
if (monitor !== Main.layoutManager.primaryIndex)
|
2013-07-09 09:11:03 -04:00
|
|
|
return;
|
appDisplay: Make page panning smoother on touch
Currently, our logic for page panning isn't great. If the user starts a
pan upwards and hesitates over a new page, we'll go to the *next* page
on release, since the difference is greater, but the velocity wound down
to 0.
Instead of trying to treat it like page down or scrolls, simply do the
math to find the page where the user scrolled to.
This is unfortunately broken for fast swipes, since the user doesn't get
far enough into the new page to make a difference. I'm getting the
impression we'll need a gesture recognizer for this, though, however
crude. Simple hacks I tried, like a velocity multiplier, didn't work
properly.
https://bugzilla.gnome.org/show_bug.cgi?id=729064
2014-04-27 11:18:04 -04:00
|
|
|
|
2019-06-30 08:15:37 -04:00
|
|
|
let adjustment = this._adjustment;
|
|
|
|
adjustment.remove_transition('value');
|
appDisplay: Make page panning smoother on touch
Currently, our logic for page panning isn't great. If the user starts a
pan upwards and hesitates over a new page, we'll go to the *next* page
on release, since the difference is greater, but the velocity wound down
to 0.
Instead of trying to treat it like page down or scrolls, simply do the
math to find the page where the user scrolled to.
This is unfortunately broken for fast swipes, since the user doesn't get
far enough into the new page to make a difference. I'm getting the
impression we'll need a gesture recognizer for this, though, however
crude. Simple hacks I tried, like a velocity multiplier, didn't work
properly.
https://bugzilla.gnome.org/show_bug.cgi?id=729064
2014-04-27 11:18:04 -04:00
|
|
|
|
2019-06-30 08:15:37 -04:00
|
|
|
let progress = adjustment.value / adjustment.page_size;
|
|
|
|
let points = Array.from({ length: this._grid.nPages() }, (v, i) => i);
|
appDisplay: Make page panning smoother on touch
Currently, our logic for page panning isn't great. If the user starts a
pan upwards and hesitates over a new page, we'll go to the *next* page
on release, since the difference is greater, but the velocity wound down
to 0.
Instead of trying to treat it like page down or scrolls, simply do the
math to find the page where the user scrolled to.
This is unfortunately broken for fast swipes, since the user doesn't get
far enough into the new page to make a difference. I'm getting the
impression we'll need a gesture recognizer for this, though, however
crude. Simple hacks I tried, like a velocity multiplier, didn't work
properly.
https://bugzilla.gnome.org/show_bug.cgi?id=729064
2014-04-27 11:18:04 -04:00
|
|
|
|
2019-06-30 08:15:37 -04:00
|
|
|
tracker.confirmSwipe(this._scrollView.height,
|
|
|
|
points, progress, Math.round(progress));
|
|
|
|
}
|
appDisplay: Make page panning smoother on touch
Currently, our logic for page panning isn't great. If the user starts a
pan upwards and hesitates over a new page, we'll go to the *next* page
on release, since the difference is greater, but the velocity wound down
to 0.
Instead of trying to treat it like page down or scrolls, simply do the
math to find the page where the user scrolled to.
This is unfortunately broken for fast swipes, since the user doesn't get
far enough into the new page to make a difference. I'm getting the
impression we'll need a gesture recognizer for this, though, however
crude. Simple hacks I tried, like a velocity multiplier, didn't work
properly.
https://bugzilla.gnome.org/show_bug.cgi?id=729064
2014-04-27 11:18:04 -04:00
|
|
|
|
2019-06-30 08:15:37 -04:00
|
|
|
_swipeUpdate(tracker, progress) {
|
|
|
|
let adjustment = this._adjustment;
|
|
|
|
adjustment.value = progress * adjustment.page_size;
|
|
|
|
}
|
appDisplay: Make page panning smoother on touch
Currently, our logic for page panning isn't great. If the user starts a
pan upwards and hesitates over a new page, we'll go to the *next* page
on release, since the difference is greater, but the velocity wound down
to 0.
Instead of trying to treat it like page down or scrolls, simply do the
math to find the page where the user scrolled to.
This is unfortunately broken for fast swipes, since the user doesn't get
far enough into the new page to make a difference. I'm getting the
impression we'll need a gesture recognizer for this, though, however
crude. Simple hacks I tried, like a velocity multiplier, didn't work
properly.
https://bugzilla.gnome.org/show_bug.cgi?id=729064
2014-04-27 11:18:04 -04:00
|
|
|
|
2019-06-30 08:15:37 -04:00
|
|
|
_swipeEnd(tracker, duration, endProgress) {
|
|
|
|
let adjustment = this._adjustment;
|
|
|
|
let value = endProgress * adjustment.page_size;
|
|
|
|
|
|
|
|
adjustment.ease(value, {
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
|
|
|
|
duration,
|
|
|
|
onComplete: () => {
|
|
|
|
this.goToPage(endProgress, false);
|
|
|
|
},
|
|
|
|
});
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-08-12 10:36:45 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_onKeyPressEvent(actor, event) {
|
2019-12-12 14:00:01 -05:00
|
|
|
if (this._displayingDialog)
|
2013-11-29 13:17:34 -05:00
|
|
|
return Clutter.EVENT_STOP;
|
2013-09-12 11:20:07 -04:00
|
|
|
|
2019-11-05 14:37:28 -05:00
|
|
|
if (event.get_key_symbol() === Clutter.KEY_Page_Up) {
|
2014-06-17 13:10:54 -04:00
|
|
|
this.goToPage(this._grid.currentPage - 1);
|
2013-11-29 13:17:34 -05:00
|
|
|
return Clutter.EVENT_STOP;
|
2019-11-05 14:37:28 -05:00
|
|
|
} else if (event.get_key_symbol() === Clutter.KEY_Page_Down) {
|
2014-06-17 13:10:54 -04:00
|
|
|
this.goToPage(this._grid.currentPage + 1);
|
2013-11-29 13:17:34 -05:00
|
|
|
return Clutter.EVENT_STOP;
|
2013-09-12 11:20:07 -04:00
|
|
|
}
|
|
|
|
|
2013-11-29 13:17:34 -05:00
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-09-12 11:20:07 -04:00
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
addFolderDialog(dialog) {
|
|
|
|
this.add_child(dialog);
|
|
|
|
dialog.connect('open-state-changed', (o, isOpen) => {
|
2019-11-23 15:11:14 -05:00
|
|
|
this._eventBlocker.visible = isOpen;
|
2019-07-18 10:06:38 -04:00
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
if (this._currentDialog) {
|
|
|
|
this._currentDialog.disconnect(this._currentDialogDestroyId);
|
|
|
|
this._currentDialogDestroyId = 0;
|
2019-07-18 10:06:38 -04:00
|
|
|
}
|
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
this._currentDialog = null;
|
2019-07-18 10:06:38 -04:00
|
|
|
|
|
|
|
if (isOpen) {
|
2019-12-12 14:00:01 -05:00
|
|
|
this._currentDialog = dialog;
|
|
|
|
this._currentDialogDestroyId = dialog.connect('destroy', () => {
|
|
|
|
this._currentDialog = null;
|
|
|
|
this._currentDialogDestroyId = 0;
|
2019-11-23 15:11:14 -05:00
|
|
|
this._eventBlocker.visible = false;
|
2019-07-18 10:06:38 -04:00
|
|
|
});
|
|
|
|
}
|
2017-10-30 20:38:18 -04:00
|
|
|
this._updateIconOpacities(isOpen);
|
2020-01-09 15:44:22 -05:00
|
|
|
|
|
|
|
// Toggle search entry
|
|
|
|
Main.overview.searchEntry.reactive = !isOpen;
|
|
|
|
|
2019-12-12 15:48:03 -05:00
|
|
|
this._displayingPopup = isOpen;
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-02-18 14:19:18 -05:00
|
|
|
|
2018-08-21 05:21:26 -04:00
|
|
|
_childFocused(icon) {
|
2013-08-27 15:22:16 -04:00
|
|
|
let itemPage = this._grid.getItemPage(icon);
|
|
|
|
this.goToPage(itemPage);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-08-27 15:22:16 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_updateIconOpacities(folderOpen) {
|
2019-10-31 19:46:35 -04:00
|
|
|
for (let icon of this._items.values()) {
|
2018-07-20 15:46:19 -04:00
|
|
|
let opacity;
|
2019-10-31 19:46:35 -04:00
|
|
|
if (folderOpen && !icon.checked)
|
2013-08-22 11:31:41 -04:00
|
|
|
opacity = INACTIVE_GRID_OPACITY;
|
2013-02-18 15:04:51 -05:00
|
|
|
else
|
2013-08-22 11:31:41 -04:00
|
|
|
opacity = 255;
|
2019-10-31 19:46:35 -04:00
|
|
|
|
|
|
|
icon.ease({
|
2019-08-19 15:06:04 -04:00
|
|
|
opacity,
|
2018-07-20 15:46:19 -04:00
|
|
|
duration: INACTIVE_GRID_OPACITY_ANIMATION_TIME,
|
2019-08-20 17:43:54 -04:00
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
2018-07-20 15:46:19 -04:00
|
|
|
});
|
2013-02-18 15:04:51 -05:00
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-08-27 15:22:16 -04:00
|
|
|
|
|
|
|
// Called before allocation to calculate dynamic spacing
|
2017-10-30 20:03:21 -04:00
|
|
|
adaptToSize(width, height) {
|
2013-08-27 15:22:16 -04:00
|
|
|
let box = new Clutter.ActorBox();
|
|
|
|
box.x1 = 0;
|
|
|
|
box.x2 = width;
|
|
|
|
box.y1 = 0;
|
|
|
|
box.y2 = height;
|
2019-07-16 05:24:13 -04:00
|
|
|
box = this.get_theme_node().get_content_box(box);
|
2013-09-03 12:59:06 -04:00
|
|
|
box = this._scrollView.get_theme_node().get_content_box(box);
|
2018-07-02 10:03:07 -04:00
|
|
|
box = this._grid.get_theme_node().get_content_box(box);
|
2013-08-27 15:22:16 -04:00
|
|
|
let availWidth = box.x2 - box.x1;
|
|
|
|
let availHeight = box.y2 - box.y1;
|
|
|
|
let oldNPages = this._grid.nPages();
|
|
|
|
|
2013-08-15 04:38:17 -04:00
|
|
|
this._grid.adaptToSize(availWidth, availHeight);
|
|
|
|
|
2013-09-03 12:59:06 -04:00
|
|
|
let fadeOffset = Math.min(this._grid.topPadding,
|
|
|
|
this._grid.bottomPadding);
|
|
|
|
this._scrollView.update_fade_effect(fadeOffset, 0);
|
2015-06-10 10:05:41 -04:00
|
|
|
if (fadeOffset > 0)
|
|
|
|
this._scrollView.get_effect('fade').fade_edges = true;
|
2013-09-03 12:59:06 -04:00
|
|
|
|
2013-08-12 13:09:43 -04:00
|
|
|
if (this._availWidth != availWidth || this._availHeight != availHeight || oldNPages != this._grid.nPages()) {
|
2017-10-30 20:38:18 -04:00
|
|
|
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
|
2019-06-17 06:59:01 -04:00
|
|
|
this._adjustment.value = 0;
|
|
|
|
this._grid.currentPage = 0;
|
2017-10-30 20:38:18 -04:00
|
|
|
this._pageIndicators.setNPages(this._grid.nPages());
|
2019-11-21 17:24:46 -05:00
|
|
|
this._pageIndicators.setCurrentPosition(0);
|
2019-07-22 14:37:33 -04:00
|
|
|
return GLib.SOURCE_REMOVE;
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2013-08-12 13:09:43 -04:00
|
|
|
}
|
2013-08-27 15:22:16 -04:00
|
|
|
|
|
|
|
this._availWidth = availWidth;
|
|
|
|
this._availHeight = availHeight;
|
2009-03-20 12:06:34 -04:00
|
|
|
}
|
2019-06-28 18:44:20 -04:00
|
|
|
|
2019-11-23 16:25:00 -05:00
|
|
|
_resetOvershoot() {
|
|
|
|
if (this._lastOvershootTimeoutId)
|
|
|
|
GLib.source_remove(this._lastOvershootTimeoutId);
|
|
|
|
this._lastOvershootTimeoutId = 0;
|
|
|
|
this._lastOvershootY = -1;
|
|
|
|
}
|
|
|
|
|
2019-06-28 19:48:22 -04:00
|
|
|
_handleDragOvershoot(dragEvent) {
|
2019-07-16 05:24:13 -04:00
|
|
|
let [, gridY] = this.get_transformed_position();
|
|
|
|
let [, gridHeight] = this.get_transformed_size();
|
2019-06-28 19:48:22 -04:00
|
|
|
let gridBottom = gridY + gridHeight;
|
|
|
|
|
2019-11-23 09:06:14 -05:00
|
|
|
// Already animating
|
|
|
|
if (this._adjustment.get_transition('value') !== null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Within the grid boundaries
|
|
|
|
if (dragEvent.y > gridY && dragEvent.y < gridBottom) {
|
|
|
|
// Check whether we moved out the area of the last switch
|
|
|
|
if (Math.abs(this._lastOvershootY - dragEvent.y) > OVERSHOOT_THRESHOLD)
|
2019-11-23 16:25:00 -05:00
|
|
|
this._resetOvershoot();
|
|
|
|
|
2019-06-28 19:48:22 -04:00
|
|
|
return;
|
2019-11-23 09:06:14 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Still in the area of the previous page switch
|
|
|
|
if (this._lastOvershootY >= 0)
|
|
|
|
return;
|
|
|
|
|
2019-06-28 19:48:22 -04:00
|
|
|
let currentY = this._adjustment.value;
|
|
|
|
let maxY = this._adjustment.upper - this._adjustment.page_size;
|
2019-11-23 16:25:00 -05:00
|
|
|
|
|
|
|
if (dragEvent.y <= gridY && currentY > 0)
|
|
|
|
this.goToPage(this._grid.currentPage - 1);
|
|
|
|
else if (dragEvent.y >= gridBottom && currentY < maxY)
|
2019-06-28 19:48:22 -04:00
|
|
|
this.goToPage(this._grid.currentPage + 1);
|
2019-11-23 16:25:00 -05:00
|
|
|
else
|
|
|
|
return; // don't go beyond first/last page
|
|
|
|
|
|
|
|
this._lastOvershootY = dragEvent.y;
|
|
|
|
|
|
|
|
if (this._lastOvershootTimeoutId > 0)
|
|
|
|
GLib.source_remove(this._lastOvershootTimeoutId);
|
|
|
|
|
|
|
|
this._lastOvershootTimeoutId =
|
|
|
|
GLib.timeout_add(GLib.PRIORITY_DEFAULT, OVERSHOOT_TIMEOUT, () => {
|
|
|
|
this._resetOvershoot();
|
|
|
|
this._handleDragOvershoot(dragEvent);
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
|
|
});
|
|
|
|
GLib.Source.set_name_by_id(this._lastOvershootTimeoutId,
|
|
|
|
'[gnome-shell] this._lastOvershootTimeoutId');
|
2019-06-28 19:48:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
_onDragBegin() {
|
|
|
|
this._dragMonitor = {
|
2019-08-20 17:43:54 -04:00
|
|
|
dragMotion: this._onDragMotion.bind(this),
|
2019-06-28 19:48:22 -04:00
|
|
|
};
|
|
|
|
DND.addDragMonitor(this._dragMonitor);
|
2019-11-23 15:11:14 -05:00
|
|
|
|
|
|
|
this._eventBlocker.visible = false;
|
2019-06-28 19:48:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
_onDragMotion(dragEvent) {
|
|
|
|
if (!(dragEvent.source instanceof AppIcon))
|
|
|
|
return DND.DragMotionResult.CONTINUE;
|
|
|
|
|
|
|
|
let appIcon = dragEvent.source;
|
|
|
|
|
|
|
|
// Handle the drag overshoot. When dragging to above the
|
|
|
|
// icon grid, move to the page above; when dragging below,
|
|
|
|
// move to the page below.
|
2019-07-16 05:24:13 -04:00
|
|
|
if (this._grid.contains(appIcon))
|
2019-06-28 19:48:22 -04:00
|
|
|
this._handleDragOvershoot(dragEvent);
|
|
|
|
|
|
|
|
return DND.DragMotionResult.CONTINUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
_onDragEnd() {
|
|
|
|
if (this._dragMonitor) {
|
|
|
|
DND.removeDragMonitor(this._dragMonitor);
|
|
|
|
this._dragMonitor = null;
|
|
|
|
}
|
2019-11-23 15:11:14 -05:00
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
this._eventBlocker.visible = this._currentDialog !== null;
|
2019-11-23 16:25:00 -05:00
|
|
|
this._resetOvershoot();
|
2019-06-28 19:48:22 -04:00
|
|
|
}
|
|
|
|
|
2019-06-29 00:26:08 -04:00
|
|
|
_canAccept(source) {
|
|
|
|
if (!(source instanceof AppIcon))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
let view = _getViewFromIcon(source);
|
|
|
|
if (!(view instanceof FolderView))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
handleDragOver(source) {
|
|
|
|
if (!this._canAccept(source))
|
|
|
|
return DND.DragMotionResult.NO_DROP;
|
|
|
|
|
|
|
|
return DND.DragMotionResult.MOVE_DROP;
|
|
|
|
}
|
|
|
|
|
|
|
|
acceptDrop(source) {
|
|
|
|
if (!this._canAccept(source))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
let view = _getViewFromIcon(source);
|
|
|
|
view.removeApp(source.app);
|
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
if (this._currentDialog)
|
|
|
|
this._currentDialog.popdown();
|
2019-06-29 00:26:08 -04:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-07-16 16:51:25 -04:00
|
|
|
createFolder(apps) {
|
|
|
|
let newFolderId = GLib.uuid_string_random();
|
|
|
|
|
|
|
|
let folders = this._folderSettings.get_strv('folder-children');
|
|
|
|
folders.push(newFolderId);
|
|
|
|
this._folderSettings.set_strv('folder-children', folders);
|
|
|
|
|
|
|
|
// Create the new folder
|
|
|
|
let newFolderPath = this._folderSettings.path.concat('folders/', newFolderId, '/');
|
|
|
|
let newFolderSettings = new Gio.Settings({
|
|
|
|
schema_id: 'org.gnome.desktop.app-folders.folder',
|
2019-08-20 17:43:54 -04:00
|
|
|
path: newFolderPath,
|
2019-07-16 16:51:25 -04:00
|
|
|
});
|
|
|
|
if (!newFolderSettings) {
|
|
|
|
log('Error creating new folder');
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-10-31 19:46:35 -04:00
|
|
|
let appItems = apps.map(id => this._items.get(id).app);
|
2019-07-16 16:51:25 -04:00
|
|
|
let folderName = _findBestFolderName(appItems);
|
|
|
|
if (!folderName)
|
|
|
|
folderName = _("Unnamed Folder");
|
|
|
|
|
|
|
|
newFolderSettings.delay();
|
|
|
|
newFolderSettings.set_string('name', folderName);
|
|
|
|
newFolderSettings.set_strv('apps', apps);
|
|
|
|
newFolderSettings.apply();
|
|
|
|
|
2019-12-04 15:09:10 -05:00
|
|
|
this.selectApp(newFolderId);
|
|
|
|
|
2019-07-16 16:51:25 -04:00
|
|
|
return true;
|
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
});
|
2015-01-27 16:10:45 -05:00
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
var FrequentView = GObject.registerClass(
|
|
|
|
class FrequentView extends BaseAppView {
|
|
|
|
_init() {
|
|
|
|
super._init({
|
|
|
|
style_class: 'frequent-apps',
|
|
|
|
layout_manager: new Clutter.BinLayout(),
|
|
|
|
x_expand: true,
|
2019-08-20 17:43:54 -04:00
|
|
|
y_expand: true,
|
2019-07-16 05:24:13 -04:00
|
|
|
}, { fillParent: true });
|
2013-05-03 12:20:21 -04:00
|
|
|
|
|
|
|
this._noFrequentAppsLabel = new St.Label({ text: _("Frequently used applications will appear here"),
|
|
|
|
style_class: 'no-frequent-applications-label',
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
x_expand: true,
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
y_expand: true });
|
|
|
|
|
2018-07-02 10:03:07 -04:00
|
|
|
this._grid.y_expand = true;
|
2013-05-03 12:20:21 -04:00
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
this.add_actor(this._grid);
|
|
|
|
this.add_actor(this._noFrequentAppsLabel);
|
2013-05-03 12:20:21 -04:00
|
|
|
this._noFrequentAppsLabel.hide();
|
2013-02-18 14:18:08 -05:00
|
|
|
|
|
|
|
this._usage = Shell.AppUsage.get_default();
|
2019-09-10 01:42:48 -04:00
|
|
|
}
|
2014-01-28 11:06:21 -05:00
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
vfunc_map() {
|
|
|
|
this._redisplay();
|
|
|
|
super.vfunc_map();
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-02-18 14:18:08 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
hasUsefulData() {
|
2012-04-09 02:27:47 -04:00
|
|
|
return this._usage.get_most_used().length >= MIN_FREQUENT_APPS_COUNT;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-08-30 14:25:19 -04:00
|
|
|
|
2019-06-29 12:15:20 -04:00
|
|
|
_compareItems() {
|
|
|
|
// The FrequentView does not need to be sorted alphabetically
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_loadApps() {
|
2019-07-01 22:13:07 -04:00
|
|
|
let apps = [];
|
2012-04-09 02:27:47 -04:00
|
|
|
let mostUsed = this._usage.get_most_used();
|
2013-08-30 14:25:19 -04:00
|
|
|
let hasUsefulData = this.hasUsefulData();
|
2013-05-03 12:20:21 -04:00
|
|
|
this._noFrequentAppsLabel.visible = !hasUsefulData;
|
2019-01-28 20:27:05 -05:00
|
|
|
if (!hasUsefulData)
|
2019-07-01 22:13:07 -04:00
|
|
|
return [];
|
2013-05-03 12:20:21 -04:00
|
|
|
|
2015-01-27 16:10:45 -05:00
|
|
|
// Allow dragging of the icon only if the Dash would accept a drop to
|
|
|
|
// change favorite-apps. There are no other possible drop targets from
|
|
|
|
// the app picker, so there's no other need for a drag to start,
|
|
|
|
// at least on single-monitor setups.
|
|
|
|
// This also disables drag-to-launch on multi-monitor setups,
|
|
|
|
// but we hope that is not used much.
|
2015-03-20 15:30:57 -04:00
|
|
|
let favoritesWritable = global.settings.is_writable('favorite-apps');
|
2015-01-27 16:10:45 -05:00
|
|
|
|
2013-02-18 14:18:08 -05:00
|
|
|
for (let i = 0; i < mostUsed.length; i++) {
|
2013-03-31 14:05:49 -04:00
|
|
|
if (!mostUsed[i].get_app_info().should_show())
|
|
|
|
continue;
|
2019-10-31 19:46:35 -04:00
|
|
|
let appIcon = this._items.get(mostUsed[i].get_id());
|
2019-11-21 15:57:17 -05:00
|
|
|
if (!appIcon) {
|
|
|
|
appIcon = new AppIcon(mostUsed[i], {
|
|
|
|
isDraggable: favoritesWritable,
|
|
|
|
});
|
|
|
|
}
|
2019-07-01 22:13:07 -04:00
|
|
|
apps.push(appIcon);
|
2013-02-18 14:18:08 -05:00
|
|
|
}
|
2019-07-01 22:13:07 -04:00
|
|
|
|
|
|
|
return apps;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-08-27 15:22:16 -04:00
|
|
|
|
|
|
|
// Called before allocation to calculate dynamic spacing
|
2017-10-30 20:03:21 -04:00
|
|
|
adaptToSize(width, height) {
|
2013-08-27 15:22:16 -04:00
|
|
|
let box = new Clutter.ActorBox();
|
|
|
|
box.x1 = box.y1 = 0;
|
|
|
|
box.x2 = width;
|
|
|
|
box.y2 = height;
|
2019-07-16 05:24:13 -04:00
|
|
|
box = this.get_theme_node().get_content_box(box);
|
2018-07-02 10:03:07 -04:00
|
|
|
box = this._grid.get_theme_node().get_content_box(box);
|
2013-08-27 15:22:16 -04:00
|
|
|
let availWidth = box.x2 - box.x1;
|
|
|
|
let availHeight = box.y2 - box.y1;
|
2013-08-15 04:38:17 -04:00
|
|
|
this._grid.adaptToSize(availWidth, availHeight);
|
2013-02-18 14:18:08 -05:00
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
});
|
2013-02-18 14:18:08 -05:00
|
|
|
|
2017-07-18 13:47:27 -04:00
|
|
|
var Views = {
|
2013-02-18 14:18:08 -05:00
|
|
|
FREQUENT: 0,
|
2019-08-20 17:43:54 -04:00
|
|
|
ALL: 1,
|
2013-02-18 14:18:08 -05:00
|
|
|
};
|
|
|
|
|
2017-10-30 21:23:39 -04:00
|
|
|
var ControlsBoxLayout = GObject.registerClass(
|
|
|
|
class ControlsBoxLayout extends Clutter.BoxLayout {
|
2019-10-17 13:49:18 -04:00
|
|
|
/*
|
2013-05-24 06:03:00 -04:00
|
|
|
* Override the BoxLayout behavior to use the maximum preferred width of all
|
|
|
|
* buttons for each child
|
|
|
|
*/
|
2017-10-30 20:03:21 -04:00
|
|
|
vfunc_get_preferred_width(container, forHeight) {
|
2013-05-24 06:03:00 -04:00
|
|
|
let maxMinWidth = 0;
|
|
|
|
let maxNaturalWidth = 0;
|
|
|
|
for (let child = container.get_first_child();
|
2019-01-29 14:36:54 -05:00
|
|
|
child;
|
|
|
|
child = child.get_next_sibling()) {
|
|
|
|
let [minWidth, natWidth] = child.get_preferred_width(forHeight);
|
|
|
|
maxMinWidth = Math.max(maxMinWidth, minWidth);
|
|
|
|
maxNaturalWidth = Math.max(maxNaturalWidth, natWidth);
|
2013-05-24 06:03:00 -04:00
|
|
|
}
|
|
|
|
let childrenCount = container.get_n_children();
|
|
|
|
let totalSpacing = this.spacing * (childrenCount - 1);
|
|
|
|
return [maxMinWidth * childrenCount + totalSpacing,
|
|
|
|
maxNaturalWidth * childrenCount + totalSpacing];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-10-30 21:23:39 -04:00
|
|
|
var ViewStackLayout = GObject.registerClass({
|
2017-02-14 12:24:50 -05:00
|
|
|
Signals: { 'allocated-size-changed': { param_types: [GObject.TYPE_INT,
|
|
|
|
GObject.TYPE_INT] } },
|
2017-10-30 21:23:39 -04:00
|
|
|
}, class ViewStackLayout extends Clutter.BinLayout {
|
2017-10-30 20:03:21 -04:00
|
|
|
vfunc_allocate(actor, box, flags) {
|
2013-08-27 15:22:16 -04:00
|
|
|
let availWidth = box.x2 - box.x1;
|
|
|
|
let availHeight = box.y2 - box.y1;
|
|
|
|
// Prepare children of all views for the upcoming allocation, calculate all
|
|
|
|
// the needed values to adapt available size
|
|
|
|
this.emit('allocated-size-changed', availWidth, availHeight);
|
2017-10-30 21:23:39 -04:00
|
|
|
super.vfunc_allocate(actor, box, flags);
|
2013-08-27 15:22:16 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
var AppDisplay = GObject.registerClass(
|
|
|
|
class AppDisplay extends St.BoxLayout {
|
|
|
|
_init() {
|
|
|
|
super._init({
|
|
|
|
style_class: 'app-display',
|
|
|
|
vertical: true,
|
|
|
|
x_expand: true,
|
2019-08-20 17:43:54 -04:00
|
|
|
y_expand: true,
|
2019-07-16 05:24:13 -04:00
|
|
|
});
|
|
|
|
|
2014-06-24 15:17:09 -04:00
|
|
|
this._privacySettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.privacy' });
|
2013-03-01 18:56:18 -05:00
|
|
|
this._privacySettings.connect('changed::remember-app-usage',
|
2017-12-01 19:27:35 -05:00
|
|
|
this._updateFrequentVisibility.bind(this));
|
2013-01-30 17:58:42 -05:00
|
|
|
|
2013-02-18 14:18:08 -05:00
|
|
|
this._views = [];
|
|
|
|
|
|
|
|
let view, button;
|
|
|
|
view = new FrequentView();
|
|
|
|
button = new St.Button({ label: _("Frequent"),
|
2015-01-16 09:33:56 -05:00
|
|
|
style_class: 'app-view-control button',
|
2013-02-18 14:18:08 -05:00
|
|
|
can_focus: true,
|
|
|
|
x_expand: true });
|
2019-08-19 15:06:04 -04:00
|
|
|
this._views[Views.FREQUENT] = { view, 'control': button };
|
2013-02-18 14:18:08 -05:00
|
|
|
|
|
|
|
view = new AllView();
|
|
|
|
button = new St.Button({ label: _("All"),
|
2015-01-16 09:33:56 -05:00
|
|
|
style_class: 'app-view-control button',
|
2013-02-18 14:18:08 -05:00
|
|
|
can_focus: true,
|
|
|
|
x_expand: true });
|
2019-08-19 15:06:04 -04:00
|
|
|
this._views[Views.ALL] = { view, 'control': button };
|
2013-02-18 14:18:08 -05:00
|
|
|
|
2013-08-27 15:22:16 -04:00
|
|
|
this._viewStackLayout = new ViewStackLayout();
|
|
|
|
this._viewStack = new St.Widget({ x_expand: true, y_expand: true,
|
|
|
|
layout_manager: this._viewStackLayout });
|
2017-12-01 19:27:35 -05:00
|
|
|
this._viewStackLayout.connect('allocated-size-changed', this._onAllocatedSizeChanged.bind(this));
|
2019-07-16 05:24:13 -04:00
|
|
|
this.add_actor(this._viewStack);
|
2013-05-24 06:03:00 -04:00
|
|
|
let layout = new ControlsBoxLayout({ homogeneous: true });
|
2019-10-17 17:40:24 -04:00
|
|
|
this._controls = new St.Widget({
|
|
|
|
style_class: 'app-view-controls',
|
|
|
|
layout_manager: layout,
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
});
|
2017-10-30 20:38:18 -04:00
|
|
|
this._controls.connect('notify::mapped', () => {
|
|
|
|
// controls are faded either with their parent or
|
|
|
|
// explicitly in animate(); we can't know how they'll be
|
|
|
|
// shown next, so make sure to restore their opacity
|
|
|
|
// when they are hidden
|
|
|
|
if (this._controls.mapped)
|
2019-01-29 14:36:54 -05:00
|
|
|
return;
|
2017-10-30 20:38:18 -04:00
|
|
|
|
2018-07-20 15:46:19 -04:00
|
|
|
this._controls.remove_all_transitions();
|
2017-10-30 20:38:18 -04:00
|
|
|
this._controls.opacity = 255;
|
|
|
|
});
|
2014-06-17 13:10:54 -04:00
|
|
|
|
2013-07-09 17:15:02 -04:00
|
|
|
layout.hookup_style(this._controls);
|
2019-07-16 05:24:13 -04:00
|
|
|
this.add_actor(new St.Bin({ child: this._controls }));
|
2013-02-18 14:18:08 -05:00
|
|
|
|
|
|
|
for (let i = 0; i < this._views.length; i++) {
|
2019-07-16 05:24:13 -04:00
|
|
|
this._viewStack.add_actor(this._views[i].view);
|
2013-02-18 14:18:08 -05:00
|
|
|
this._controls.add_actor(this._views[i].control);
|
|
|
|
|
|
|
|
let viewIndex = i;
|
2019-02-04 06:30:53 -05:00
|
|
|
this._views[i].control.connect('clicked', () => {
|
2017-10-30 20:38:18 -04:00
|
|
|
this._showView(viewIndex);
|
|
|
|
global.settings.set_uint('app-picker-view', viewIndex);
|
|
|
|
});
|
2013-02-18 14:18:08 -05:00
|
|
|
}
|
2013-10-13 10:48:12 -04:00
|
|
|
let initialView = Math.min(global.settings.get_uint('app-picker-view'),
|
|
|
|
this._views.length - 1);
|
2013-08-30 14:25:19 -04:00
|
|
|
let frequentUseful = this._views[Views.FREQUENT].view.hasUsefulData();
|
2013-10-13 10:48:12 -04:00
|
|
|
if (initialView == Views.FREQUENT && !frequentUseful)
|
|
|
|
initialView = Views.ALL;
|
|
|
|
this._showView(initialView);
|
2013-03-01 18:46:34 -05:00
|
|
|
this._updateFrequentVisibility();
|
2016-10-19 09:58:16 -04:00
|
|
|
|
|
|
|
Gio.DBus.system.watch_name(SWITCHEROO_BUS_NAME,
|
|
|
|
Gio.BusNameWatcherFlags.NONE,
|
2017-12-01 19:27:35 -05:00
|
|
|
this._switcherooProxyAppeared.bind(this),
|
2017-10-30 20:38:18 -04:00
|
|
|
() => {
|
2016-10-19 09:58:16 -04:00
|
|
|
this._switcherooProxy = null;
|
|
|
|
this._updateDiscreteGpuAvailable();
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2016-10-19 09:58:16 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_updateDiscreteGpuAvailable() {
|
2016-10-19 09:58:16 -04:00
|
|
|
if (!this._switcherooProxy)
|
|
|
|
discreteGpuAvailable = false;
|
|
|
|
else
|
|
|
|
discreteGpuAvailable = this._switcherooProxy.HasDualGpu;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2016-10-19 09:58:16 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_switcherooProxyAppeared() {
|
2016-10-19 09:58:16 -04:00
|
|
|
this._switcherooProxy = new SwitcherooProxy(Gio.DBus.system, SWITCHEROO_BUS_NAME, SWITCHEROO_OBJECT_PATH,
|
2017-10-30 20:38:18 -04:00
|
|
|
(proxy, error) => {
|
2016-10-19 09:58:16 -04:00
|
|
|
if (error) {
|
|
|
|
log(error.message);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._updateDiscreteGpuAvailable();
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2010-06-04 18:01:32 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
animate(animationDirection, onComplete) {
|
2016-11-17 10:24:44 -05:00
|
|
|
let currentView = this._views.filter(v => v.control.has_style_pseudo_class('checked')).pop().view;
|
2014-06-17 13:10:54 -04:00
|
|
|
|
|
|
|
// Animate controls opacity using iconGrid animation time, since
|
|
|
|
// it will be the time the AllView or FrequentView takes to show
|
|
|
|
// it entirely.
|
|
|
|
let finalOpacity;
|
|
|
|
if (animationDirection == IconGrid.AnimationDirection.IN) {
|
|
|
|
this._controls.opacity = 0;
|
|
|
|
finalOpacity = 255;
|
|
|
|
} else {
|
2019-01-28 20:18:52 -05:00
|
|
|
finalOpacity = 0;
|
2014-06-17 13:10:54 -04:00
|
|
|
}
|
|
|
|
|
2018-07-20 15:46:19 -04:00
|
|
|
this._controls.ease({
|
|
|
|
opacity: finalOpacity,
|
|
|
|
duration: IconGrid.ANIMATION_TIME_IN,
|
2019-08-20 17:43:54 -04:00
|
|
|
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
|
2018-07-20 15:46:19 -04:00
|
|
|
});
|
2014-06-17 13:10:54 -04:00
|
|
|
|
|
|
|
currentView.animate(animationDirection, onComplete);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-06-17 13:10:54 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_showView(activeIndex) {
|
2013-02-18 14:18:08 -05:00
|
|
|
for (let i = 0; i < this._views.length; i++) {
|
|
|
|
if (i == activeIndex)
|
|
|
|
this._views[i].control.add_style_pseudo_class('checked');
|
|
|
|
else
|
|
|
|
this._views[i].control.remove_style_pseudo_class('checked');
|
2014-06-17 15:31:53 -04:00
|
|
|
|
2019-08-19 15:33:15 -04:00
|
|
|
let animationDirection = i == activeIndex
|
|
|
|
? IconGrid.AnimationDirection.IN
|
|
|
|
: IconGrid.AnimationDirection.OUT;
|
2014-06-17 15:31:53 -04:00
|
|
|
this._views[i].view.animateSwitch(animationDirection);
|
2013-02-18 14:18:08 -05:00
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2010-06-04 18:01:32 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_updateFrequentVisibility() {
|
2013-03-01 18:56:18 -05:00
|
|
|
let enabled = this._privacySettings.get_boolean('remember-app-usage');
|
2013-03-01 18:46:34 -05:00
|
|
|
this._views[Views.FREQUENT].control.visible = enabled;
|
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
let visibleViews = this._views.filter(v => v.control.visible);
|
2013-03-01 18:46:34 -05:00
|
|
|
this._controls.visible = visibleViews.length > 1;
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
if (!enabled && this._views[Views.FREQUENT].view.visible)
|
2013-03-01 18:46:34 -05:00
|
|
|
this._showView(Views.ALL);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-03-01 18:46:34 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
selectApp(id) {
|
2011-07-09 09:30:42 -04:00
|
|
|
this._showView(Views.ALL);
|
|
|
|
this._views[Views.ALL].view.selectApp(id);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-08-27 15:22:16 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_onAllocatedSizeChanged(actor, width, height) {
|
2013-08-27 15:22:16 -04:00
|
|
|
let box = new Clutter.ActorBox();
|
2019-01-28 20:27:05 -05:00
|
|
|
box.x1 = box.y1 = 0;
|
2013-08-27 15:22:16 -04:00
|
|
|
box.x2 = width;
|
|
|
|
box.y2 = height;
|
|
|
|
box = this._viewStack.get_theme_node().get_content_box(box);
|
|
|
|
let availWidth = box.x2 - box.x1;
|
|
|
|
let availHeight = box.y2 - box.y1;
|
|
|
|
for (let i = 0; i < this._views.length; i++)
|
|
|
|
this._views[i].view.adaptToSize(availWidth, availHeight);
|
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
});
|
2009-11-29 17:45:30 -05:00
|
|
|
|
2017-10-30 21:19:44 -04:00
|
|
|
var AppSearchProvider = class AppSearchProvider {
|
|
|
|
constructor() {
|
2009-11-29 17:45:30 -05:00
|
|
|
this._appSys = Shell.AppSystem.get_default();
|
2012-08-09 17:52:36 -04:00
|
|
|
this.id = 'applications';
|
2017-06-12 22:24:12 -04:00
|
|
|
this.isRemoteProvider = false;
|
|
|
|
this.canLaunchSearch = false;
|
2017-08-21 09:20:25 -04:00
|
|
|
|
|
|
|
this._systemActions = new SystemActions.getDefault();
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2009-11-29 17:45:30 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
getResultMetas(apps, callback) {
|
2012-02-17 10:39:27 -05:00
|
|
|
let metas = [];
|
2017-08-21 09:20:25 -04:00
|
|
|
for (let id of apps) {
|
|
|
|
if (id.endsWith('.desktop')) {
|
|
|
|
let app = this._appSys.lookup_app(id);
|
|
|
|
|
2019-02-12 09:02:09 -05:00
|
|
|
metas.push({
|
|
|
|
id: app.get_id(),
|
|
|
|
name: app.get_name(),
|
|
|
|
createIcon: size => app.create_icon_texture(size),
|
2017-08-21 09:20:25 -04:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
let name = this._systemActions.getName(id);
|
|
|
|
let iconName = this._systemActions.getIconName(id);
|
|
|
|
|
|
|
|
let createIcon = size => new St.Icon({ icon_name: iconName,
|
|
|
|
width: size,
|
|
|
|
height: size,
|
|
|
|
style_class: 'system-action-icon' });
|
|
|
|
|
|
|
|
metas.push({ id, name, createIcon });
|
|
|
|
}
|
2012-02-17 10:39:27 -05:00
|
|
|
}
|
2017-08-21 09:20:25 -04:00
|
|
|
|
2012-05-02 15:45:37 -04:00
|
|
|
callback(metas);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2009-11-29 17:45:30 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
filterResults(results, maxNumber) {
|
2013-05-29 16:48:30 -04:00
|
|
|
return results.slice(0, maxNumber);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-05-29 16:48:30 -04:00
|
|
|
|
2019-01-31 09:08:10 -05:00
|
|
|
getInitialResultSet(terms, callback, _cancellable) {
|
2013-11-02 20:13:42 -04:00
|
|
|
let query = terms.join(' ');
|
2015-07-30 13:55:19 -04:00
|
|
|
let groups = Shell.AppSystem.search(query);
|
2013-11-02 20:13:42 -04:00
|
|
|
let usage = Shell.AppUsage.get_default();
|
|
|
|
let results = [];
|
2017-10-30 20:38:18 -04:00
|
|
|
groups.forEach(group => {
|
|
|
|
group = group.filter(appID => {
|
2020-03-14 09:45:42 -04:00
|
|
|
const app = this._appSys.lookup_app(appID);
|
|
|
|
return app && app.app_info.should_show();
|
2013-11-02 20:13:42 -04:00
|
|
|
});
|
2017-10-30 20:38:18 -04:00
|
|
|
results = results.concat(group.sort(
|
2012-04-09 02:27:47 -04:00
|
|
|
(a, b) => usage.compare(a, b)
|
2017-10-30 20:38:18 -04:00
|
|
|
));
|
2013-11-02 20:13:42 -04:00
|
|
|
});
|
2017-08-21 09:20:25 -04:00
|
|
|
|
|
|
|
results = results.concat(this._systemActions.getMatchingActions(terms));
|
|
|
|
|
2013-11-02 20:13:42 -04:00
|
|
|
callback(results);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
Kill off ShellAppInfo, move into ShellApp
This dramatically thins down and sanitizes the application code.
The ShellAppSystem changes in a number of ways:
* Preferences are special cased more explicitly; they aren't apps,
they're shortcuts for an app), and we don't have many of them, so
don't need e.g. the optimizations in ShellAppSystem for searching.
* get_app() changes to lookup_app() and returns null if an app isn't
found. The semantics where it tried to find the .desktop file
if we didn't know about it were just broken; I am pretty sure no
caller needs this, and if they do we'll fix them.
* ShellAppSystem maintains two indexes on apps (by desktop file id
and by GMenuTreeEntry), but is no longer in the business of
dealing with GMenuTree as far as hierarchy and categories go. That
is moved up into js/ui/appDisplay.js. Actually, it flattens both
apps and settings.
Also, ShellWindowTracker is now the sole reference-owner for
window-backed apps. We still do the weird "window:0x1234beef" id
for these apps, but a reference is not stored in ShellAppSystem.
The js/ui/appDisplay.js code is rewritten, and sucks a lot less.
Variable names are clearer:
_apps -> _appIcons
_filterApp -> _visibleApps
_filters -> _categoryBox
Similarly for function names. We no longer call (for every app) a
recursive lookup in GMenuTree to see if it's in a particular section
on every category switch; it's all cached.
NOTE - this intentionally reverts the incremental loading code from
commit 7813c5b93f6bcde8c4beae286e82bfc472b2b656. It's fast enough
here without that.
https://bugzilla.gnome.org/show_bug.cgi?id=648149
2011-04-21 13:35:01 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
getSubsearchResultSet(previousResults, terms, callback, cancellable) {
|
2013-11-02 20:13:42 -04:00
|
|
|
this.getInitialResultSet(terms, callback, cancellable);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
Kill off ShellAppInfo, move into ShellApp
This dramatically thins down and sanitizes the application code.
The ShellAppSystem changes in a number of ways:
* Preferences are special cased more explicitly; they aren't apps,
they're shortcuts for an app), and we don't have many of them, so
don't need e.g. the optimizations in ShellAppSystem for searching.
* get_app() changes to lookup_app() and returns null if an app isn't
found. The semantics where it tried to find the .desktop file
if we didn't know about it were just broken; I am pretty sure no
caller needs this, and if they do we'll fix them.
* ShellAppSystem maintains two indexes on apps (by desktop file id
and by GMenuTreeEntry), but is no longer in the business of
dealing with GMenuTree as far as hierarchy and categories go. That
is moved up into js/ui/appDisplay.js. Actually, it flattens both
apps and settings.
Also, ShellWindowTracker is now the sole reference-owner for
window-backed apps. We still do the weird "window:0x1234beef" id
for these apps, but a reference is not stored in ShellAppSystem.
The js/ui/appDisplay.js code is rewritten, and sucks a lot less.
Variable names are clearer:
_apps -> _appIcons
_filterApp -> _visibleApps
_filters -> _categoryBox
Similarly for function names. We no longer call (for every app) a
recursive lookup in GMenuTree to see if it's in a particular section
on every category switch; it's all cached.
NOTE - this intentionally reverts the incremental loading code from
commit 7813c5b93f6bcde8c4beae286e82bfc472b2b656. It's fast enough
here without that.
https://bugzilla.gnome.org/show_bug.cgi?id=648149
2011-04-21 13:35:01 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
createResultObject(resultMeta) {
|
2017-08-21 09:20:25 -04:00
|
|
|
if (resultMeta.id.endsWith('.desktop'))
|
|
|
|
return new AppIcon(this._appSys.lookup_app(resultMeta['id']));
|
|
|
|
else
|
|
|
|
return new SystemActionIcon(this, resultMeta);
|
2009-11-29 17:45:30 -05:00
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
};
|
2013-08-20 04:49:02 -04:00
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
var FolderView = GObject.registerClass(
|
|
|
|
class FolderView extends BaseAppView {
|
|
|
|
_init(folder, id, parentView) {
|
|
|
|
super._init({
|
|
|
|
layout_manager: new Clutter.BinLayout(),
|
|
|
|
x_expand: true,
|
2019-08-20 17:43:54 -04:00
|
|
|
y_expand: true,
|
2019-12-17 15:00:57 -05:00
|
|
|
}, {
|
|
|
|
minRows: 1,
|
2019-07-16 05:24:13 -04:00
|
|
|
});
|
|
|
|
|
2013-08-30 12:50:35 -04:00
|
|
|
// If it not expand, the parent doesn't take into account its preferred_width when allocating
|
|
|
|
// the second time it allocates, so we apply the "Standard hack for ClutterBinLayout"
|
2018-07-02 10:03:07 -04:00
|
|
|
this._grid.x_expand = true;
|
2019-07-16 16:51:25 -04:00
|
|
|
this._id = id;
|
2019-06-29 11:58:26 -04:00
|
|
|
this._folder = folder;
|
|
|
|
this._parentView = parentView;
|
2019-06-28 18:47:32 -04:00
|
|
|
this._grid._delegate = this;
|
2013-08-30 12:50:35 -04:00
|
|
|
|
2019-08-30 23:21:10 -04:00
|
|
|
this._scrollView = new St.ScrollView({
|
|
|
|
overlay_scrollbars: true,
|
|
|
|
x_expand: true,
|
2019-08-20 17:43:54 -04:00
|
|
|
y_expand: true,
|
2019-08-30 23:21:10 -04:00
|
|
|
});
|
2020-02-16 15:55:39 -05:00
|
|
|
this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.EXTERNAL);
|
2019-07-16 05:24:13 -04:00
|
|
|
this.add_actor(this._scrollView);
|
2019-08-30 23:21:10 -04:00
|
|
|
|
2019-10-17 17:40:24 -04:00
|
|
|
let scrollableContainer = new St.BoxLayout({
|
|
|
|
vertical: true,
|
|
|
|
reactive: true,
|
|
|
|
x_expand: true,
|
|
|
|
y_expand: true,
|
|
|
|
});
|
2018-07-02 10:03:07 -04:00
|
|
|
scrollableContainer.add_actor(this._grid);
|
2019-08-30 23:21:10 -04:00
|
|
|
this._scrollView.add_actor(scrollableContainer);
|
2013-08-23 14:49:27 -04:00
|
|
|
|
|
|
|
let action = new Clutter.PanAction({ interpolate: true });
|
2017-12-01 19:27:35 -05:00
|
|
|
action.connect('pan', this._onPan.bind(this));
|
2019-08-30 23:21:10 -04:00
|
|
|
this._scrollView.add_action(action);
|
2019-06-29 11:58:26 -04:00
|
|
|
|
|
|
|
this._redisplay();
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-08-20 04:49:02 -04:00
|
|
|
|
2018-08-21 05:21:26 -04:00
|
|
|
_childFocused(actor) {
|
2019-08-30 23:21:10 -04:00
|
|
|
Util.ensureActorVisibleInScrollView(this._scrollView, actor);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-03-20 09:54:04 -04:00
|
|
|
|
2019-05-15 15:32:29 -04:00
|
|
|
// Overridden from BaseAppView
|
2017-10-30 20:03:21 -04:00
|
|
|
animate(animationDirection) {
|
2014-06-17 06:47:00 -04:00
|
|
|
this._grid.animatePulse(animationDirection);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-06-17 06:47:00 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
createFolderIcon(size) {
|
2014-08-05 17:26:45 -04:00
|
|
|
let layout = new Clutter.GridLayout();
|
2019-11-11 16:07:19 -05:00
|
|
|
let icon = new St.Widget({
|
|
|
|
layout_manager: layout,
|
|
|
|
style_class: 'app-folder-icon',
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
});
|
2014-03-14 12:03:10 -04:00
|
|
|
layout.hookup_style(icon);
|
2013-08-20 04:49:02 -04:00
|
|
|
let subSize = Math.floor(FOLDER_SUBICON_FRACTION * size);
|
|
|
|
|
2019-10-31 19:44:02 -04:00
|
|
|
let numItems = this._orderedItems.length;
|
2014-06-20 11:46:22 -04:00
|
|
|
let rtl = icon.get_text_direction() == Clutter.TextDirection.RTL;
|
2014-03-14 10:50:18 -04:00
|
|
|
for (let i = 0; i < 4; i++) {
|
2020-04-03 17:09:09 -04:00
|
|
|
const style = 'width: %dpx; height: %dpx;'.format(subSize, subSize);
|
|
|
|
let bin = new St.Bin({ style });
|
2017-08-11 05:04:37 -04:00
|
|
|
if (i < numItems)
|
2019-10-31 19:44:02 -04:00
|
|
|
bin.child = this._orderedItems[i].app.create_icon_texture(subSize);
|
2014-08-05 17:26:45 -04:00
|
|
|
layout.attach(bin, rtl ? (i + 1) % 2 : i % 2, Math.floor(i / 2), 1, 1);
|
2013-08-20 04:49:02 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return icon;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-08-30 12:50:35 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_onPan(action) {
|
2019-01-31 09:08:00 -05:00
|
|
|
let [dist_, dx_, dy] = action.get_motion_delta(0);
|
2019-08-30 23:21:10 -04:00
|
|
|
let adjustment = this._scrollView.vscroll.adjustment;
|
|
|
|
adjustment.value -= (dy / this._scrollView.height) * adjustment.page_size;
|
2013-08-23 14:49:27 -04:00
|
|
|
return false;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-08-23 14:49:27 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
adaptToSize(width, height) {
|
2013-08-30 12:50:35 -04:00
|
|
|
this._parentAvailableWidth = width;
|
|
|
|
this._parentAvailableHeight = height;
|
|
|
|
|
2013-08-15 04:38:17 -04:00
|
|
|
this._grid.adaptToSize(width, height);
|
2013-08-30 12:50:35 -04:00
|
|
|
|
2013-09-05 21:12:28 -04:00
|
|
|
// To avoid the fade effect being applied to the unscrolled grid,
|
|
|
|
// the offset would need to be applied after adjusting the padding;
|
|
|
|
// however the final padding is expected to be too small for the
|
|
|
|
// effect to look good, so use the unadjusted padding
|
|
|
|
let fadeOffset = Math.min(this._grid.topPadding,
|
|
|
|
this._grid.bottomPadding);
|
2019-08-30 23:21:10 -04:00
|
|
|
this._scrollView.update_fade_effect(fadeOffset, 0);
|
2013-09-05 21:12:28 -04:00
|
|
|
|
2013-08-30 12:50:35 -04:00
|
|
|
// Set extra padding to avoid popup or close button being cut off
|
2019-12-12 13:54:18 -05:00
|
|
|
this._grid.topPadding = Math.max(this._grid.topPadding, 0);
|
|
|
|
this._grid.bottomPadding = Math.max(this._grid.bottomPadding, 0);
|
|
|
|
this._grid.leftPadding = Math.max(this._grid.leftPadding, 0);
|
|
|
|
this._grid.rightPadding = Math.max(this._grid.rightPadding, 0);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-08-30 12:50:35 -04:00
|
|
|
|
2019-06-29 11:58:26 -04:00
|
|
|
_loadApps() {
|
2019-07-01 22:13:07 -04:00
|
|
|
let apps = [];
|
2019-06-29 11:58:26 -04:00
|
|
|
let excludedApps = this._folder.get_strv('excluded-apps');
|
|
|
|
let appSys = Shell.AppSystem.get_default();
|
|
|
|
let addAppId = appId => {
|
|
|
|
if (excludedApps.includes(appId))
|
|
|
|
return;
|
|
|
|
|
|
|
|
let app = appSys.lookup_app(appId);
|
|
|
|
if (!app)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!app.get_app_info().should_show())
|
|
|
|
return;
|
|
|
|
|
2019-07-30 11:15:51 -04:00
|
|
|
if (apps.some(appIcon => appIcon.id == appId))
|
|
|
|
return;
|
|
|
|
|
2019-10-31 19:46:35 -04:00
|
|
|
let icon = this._items.get(appId);
|
2019-11-23 15:44:55 -05:00
|
|
|
if (!icon)
|
|
|
|
icon = new AppIcon(app);
|
|
|
|
|
2019-07-01 22:13:07 -04:00
|
|
|
apps.push(icon);
|
2019-06-29 11:58:26 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
let folderApps = this._folder.get_strv('apps');
|
|
|
|
folderApps.forEach(addAppId);
|
|
|
|
|
|
|
|
let folderCategories = this._folder.get_strv('categories');
|
|
|
|
let appInfos = this._parentView.getAppInfos();
|
|
|
|
appInfos.forEach(appInfo => {
|
|
|
|
let appCategories = _getCategories(appInfo);
|
|
|
|
if (!_listsIntersect(folderCategories, appCategories))
|
|
|
|
return;
|
|
|
|
|
|
|
|
addAppId(appInfo.get_id());
|
|
|
|
});
|
2019-07-01 22:13:07 -04:00
|
|
|
|
|
|
|
return apps;
|
2019-06-29 11:58:26 -04:00
|
|
|
}
|
2019-06-29 00:26:08 -04:00
|
|
|
|
2020-02-17 10:56:10 -05:00
|
|
|
addApp(app) {
|
|
|
|
let folderApps = this._folder.get_strv('apps');
|
|
|
|
folderApps.push(app.id);
|
|
|
|
|
|
|
|
this._folder.set_strv('apps', folderApps);
|
|
|
|
|
|
|
|
// Also remove from 'excluded-apps' if the app id is listed
|
|
|
|
// there. This is only possible on categories-based folders.
|
|
|
|
let excludedApps = this._folder.get_strv('excluded-apps');
|
|
|
|
let index = excludedApps.indexOf(app.id);
|
|
|
|
if (index >= 0) {
|
|
|
|
excludedApps.splice(index, 1);
|
|
|
|
this._folder.set_strv('excluded-apps', excludedApps);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-29 00:26:08 -04:00
|
|
|
removeApp(app) {
|
|
|
|
let folderApps = this._folder.get_strv('apps');
|
|
|
|
let index = folderApps.indexOf(app.id);
|
2019-07-16 16:51:25 -04:00
|
|
|
if (index >= 0)
|
2019-06-29 00:26:08 -04:00
|
|
|
folderApps.splice(index, 1);
|
|
|
|
|
|
|
|
// If this is a categories-based folder, also add it to
|
|
|
|
// the list of excluded apps
|
|
|
|
let categories = this._folder.get_strv('categories');
|
|
|
|
if (categories.length > 0) {
|
|
|
|
let excludedApps = this._folder.get_strv('excluded-apps');
|
|
|
|
excludedApps.push(app.id);
|
|
|
|
this._folder.set_strv('excluded-apps', excludedApps);
|
|
|
|
}
|
|
|
|
|
2019-07-16 16:51:25 -04:00
|
|
|
// Remove the folder if this is the last app icon; otherwise,
|
|
|
|
// just remove the icon
|
|
|
|
if (folderApps.length == 0) {
|
|
|
|
// Resetting all keys deletes the relocatable schema
|
|
|
|
let keys = this._folder.settings_schema.list_keys();
|
|
|
|
for (let key of keys)
|
|
|
|
this._folder.reset(key);
|
2019-11-21 16:45:12 -05:00
|
|
|
|
|
|
|
let settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' });
|
|
|
|
let folders = settings.get_strv('folder-children');
|
|
|
|
folders.splice(folders.indexOf(this._id), 1);
|
|
|
|
settings.set_strv('folder-children', folders);
|
2019-07-16 16:51:25 -04:00
|
|
|
} else {
|
|
|
|
this._folder.set_strv('apps', folderApps);
|
|
|
|
}
|
2019-06-29 00:26:08 -04:00
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
});
|
2013-01-31 11:13:37 -05:00
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
var FolderIcon = GObject.registerClass({
|
|
|
|
Signals: {
|
|
|
|
'apps-changed': {},
|
|
|
|
'name-changed': {},
|
2019-08-20 17:43:54 -04:00
|
|
|
},
|
2019-07-16 05:24:13 -04:00
|
|
|
}, class FolderIcon extends St.Button {
|
|
|
|
_init(id, path, parentView) {
|
|
|
|
super._init({
|
|
|
|
style_class: 'app-well-app app-folder',
|
|
|
|
button_mask: St.ButtonMask.ONE,
|
|
|
|
toggle_mode: true,
|
|
|
|
can_focus: true,
|
|
|
|
});
|
2013-12-15 22:24:25 -05:00
|
|
|
this.id = id;
|
2017-06-12 22:24:12 -04:00
|
|
|
this.name = '';
|
2013-01-31 11:13:37 -05:00
|
|
|
this._parentView = parentView;
|
|
|
|
|
2014-01-28 11:36:57 -05:00
|
|
|
this._folder = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders.folder',
|
2019-08-19 15:06:04 -04:00
|
|
|
path });
|
2019-07-16 05:24:13 -04:00
|
|
|
this._delegate = this;
|
2013-01-31 11:13:37 -05:00
|
|
|
|
2019-06-28 22:46:16 -04:00
|
|
|
this.icon = new IconGrid.BaseIcon('', {
|
|
|
|
createIcon: this._createIcon.bind(this),
|
2019-08-20 17:43:54 -04:00
|
|
|
setSizeManually: true,
|
2019-06-28 22:46:16 -04:00
|
|
|
});
|
2019-07-16 05:24:13 -04:00
|
|
|
this.set_child(this.icon);
|
|
|
|
this.label_actor = this.icon.label;
|
2013-01-31 11:13:37 -05:00
|
|
|
|
2019-07-16 16:51:25 -04:00
|
|
|
this.view = new FolderView(this._folder, id, parentView);
|
2013-01-31 11:13:37 -05:00
|
|
|
|
2019-10-29 19:06:10 -04:00
|
|
|
this._iconIsHovering = false;
|
2019-06-28 18:47:32 -04:00
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
2014-01-28 11:36:57 -05:00
|
|
|
|
2020-02-17 09:40:55 -05:00
|
|
|
this._folderChangedId = this._folder.connect(
|
|
|
|
'changed', this._sync.bind(this));
|
2020-02-17 09:35:49 -05:00
|
|
|
this._sync();
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-01-28 11:36:57 -05:00
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
_onDestroy() {
|
2019-11-23 13:57:02 -05:00
|
|
|
if (this._dragMonitor) {
|
|
|
|
DND.removeDragMonitor(this._dragMonitor);
|
|
|
|
this._dragMonitor = null;
|
|
|
|
}
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
this.view.destroy();
|
2019-07-22 10:57:57 -04:00
|
|
|
|
2020-02-17 09:40:55 -05:00
|
|
|
if (this._folderChangedId) {
|
|
|
|
this._folder.disconnect(this._folderChangedId);
|
|
|
|
delete this._folderChangedId;
|
2019-07-18 14:49:30 -04:00
|
|
|
}
|
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
if (this._dialog)
|
|
|
|
this._dialog.destroy();
|
2019-07-22 10:57:57 -04:00
|
|
|
}
|
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
vfunc_clicked() {
|
|
|
|
this.open();
|
|
|
|
}
|
|
|
|
|
|
|
|
vfunc_unmap() {
|
2019-12-12 14:00:01 -05:00
|
|
|
if (this._dialog)
|
|
|
|
this._dialog.popdown();
|
2020-02-07 04:57:19 -05:00
|
|
|
|
|
|
|
super.vfunc_unmap();
|
2019-09-10 01:42:48 -04:00
|
|
|
}
|
|
|
|
|
2019-07-18 10:19:13 -04:00
|
|
|
open() {
|
2019-12-12 14:00:01 -05:00
|
|
|
this._ensureFolderDialog();
|
2019-08-30 23:21:10 -04:00
|
|
|
this.view._scrollView.vscroll.adjustment.value = 0;
|
2019-12-12 14:00:01 -05:00
|
|
|
this._dialog.popup();
|
2019-07-18 10:19:13 -04:00
|
|
|
}
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
getAppIds() {
|
2017-10-30 20:38:18 -04:00
|
|
|
return this.view.getAllItems().map(item => item.id);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-01-28 11:36:57 -05:00
|
|
|
|
2019-10-29 19:06:10 -04:00
|
|
|
_setHoveringByDnd(hovering) {
|
|
|
|
if (this._iconIsHovering == hovering)
|
|
|
|
return;
|
2019-06-28 18:47:32 -04:00
|
|
|
|
2019-10-29 19:06:10 -04:00
|
|
|
this._iconIsHovering = hovering;
|
2019-07-01 20:37:35 -04:00
|
|
|
|
2019-10-29 19:06:10 -04:00
|
|
|
if (hovering) {
|
|
|
|
this._dragMonitor = {
|
|
|
|
dragMotion: this._onDragMotion.bind(this),
|
|
|
|
};
|
|
|
|
DND.addDragMonitor(this._dragMonitor);
|
2019-07-16 05:24:13 -04:00
|
|
|
this.add_style_pseudo_class('drop');
|
2019-10-29 19:06:10 -04:00
|
|
|
} else {
|
|
|
|
DND.removeDragMonitor(this._dragMonitor);
|
|
|
|
this.remove_style_pseudo_class('drop');
|
|
|
|
}
|
2019-07-01 20:37:35 -04:00
|
|
|
}
|
|
|
|
|
2019-10-29 19:06:10 -04:00
|
|
|
_onDragMotion(dragEvent) {
|
|
|
|
if (!this.contains(dragEvent.targetActor) ||
|
|
|
|
!this._canAccept(dragEvent.source))
|
|
|
|
this._setHoveringByDnd(false);
|
|
|
|
|
|
|
|
return DND.DragMotionResult.CONTINUE;
|
2019-06-28 18:47:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
_canAccept(source) {
|
|
|
|
if (!(source instanceof AppIcon))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
let view = _getViewFromIcon(source);
|
|
|
|
if (!view || !(view instanceof AllView))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (this._folder.get_strv('apps').includes(source.id))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
handleDragOver(source) {
|
|
|
|
if (!this._canAccept(source))
|
|
|
|
return DND.DragMotionResult.NO_DROP;
|
|
|
|
|
2019-10-29 19:06:10 -04:00
|
|
|
this._setHoveringByDnd(true);
|
|
|
|
|
2019-06-28 18:47:32 -04:00
|
|
|
return DND.DragMotionResult.MOVE_DROP;
|
|
|
|
}
|
|
|
|
|
|
|
|
acceptDrop(source) {
|
2019-10-29 19:06:10 -04:00
|
|
|
this._setHoveringByDnd(false);
|
|
|
|
|
2019-06-28 18:47:32 -04:00
|
|
|
if (!this._canAccept(source))
|
2019-08-09 09:56:37 -04:00
|
|
|
return false;
|
2019-06-28 18:47:32 -04:00
|
|
|
|
2020-02-17 10:56:10 -05:00
|
|
|
this.view.addApp(source.app);
|
2019-06-28 18:47:32 -04:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_updateName() {
|
2014-01-28 11:36:57 -05:00
|
|
|
let name = _getFolderName(this._folder);
|
|
|
|
if (this.name == name)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this.name = name;
|
|
|
|
this.icon.label.text = this.name;
|
|
|
|
this.emit('name-changed');
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-01-28 11:36:57 -05:00
|
|
|
|
2020-02-17 09:35:49 -05:00
|
|
|
_sync() {
|
2020-02-17 10:21:58 -05:00
|
|
|
this.emit('apps-changed');
|
2014-01-28 11:36:57 -05:00
|
|
|
this._updateName();
|
2019-07-16 05:24:13 -04:00
|
|
|
this.visible = this.view.getAllItems().length > 0;
|
2019-06-28 18:49:18 -04:00
|
|
|
this.icon.update();
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-01-31 11:13:37 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_createIcon(iconSize) {
|
2013-08-15 04:38:17 -04:00
|
|
|
return this.view.createFolderIcon(iconSize, this);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-01-31 11:13:37 -05:00
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
_ensureFolderDialog() {
|
2019-12-12 14:07:37 -05:00
|
|
|
if (this._dialog)
|
2013-08-30 12:50:35 -04:00
|
|
|
return;
|
2019-12-12 14:00:01 -05:00
|
|
|
if (!this._dialog) {
|
2019-12-17 14:39:24 -05:00
|
|
|
this._dialog = new AppFolderDialog(this, this._folder);
|
2019-12-12 14:00:01 -05:00
|
|
|
this._parentView.addFolderDialog(this._dialog);
|
|
|
|
this._dialog.connect('open-state-changed', (popup, isOpen) => {
|
2017-10-30 20:38:18 -04:00
|
|
|
if (!isOpen)
|
2019-07-16 05:24:13 -04:00
|
|
|
this.checked = false;
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2013-01-31 11:13:37 -05:00
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2019-09-13 21:03:15 -04:00
|
|
|
});
|
|
|
|
|
2019-12-12 14:00:01 -05:00
|
|
|
var AppFolderDialog = GObject.registerClass({
|
2019-07-16 05:24:13 -04:00
|
|
|
Signals: {
|
|
|
|
'open-state-changed': { param_types: [GObject.TYPE_BOOLEAN] },
|
2019-08-20 17:43:54 -04:00
|
|
|
},
|
2019-12-12 14:00:01 -05:00
|
|
|
}, class AppFolderDialog extends St.Widget {
|
2019-12-17 14:39:24 -05:00
|
|
|
_init(source, folder) {
|
2019-07-16 05:24:13 -04:00
|
|
|
super._init({
|
|
|
|
layout_manager: new Clutter.BinLayout(),
|
2019-12-12 13:54:18 -05:00
|
|
|
style_class: 'app-folder-dialog-container',
|
2019-07-16 05:24:13 -04:00
|
|
|
visible: false,
|
|
|
|
x_expand: true,
|
|
|
|
y_expand: true,
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
2019-12-12 13:54:18 -05:00
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
2019-07-16 05:24:13 -04:00
|
|
|
});
|
2019-12-12 13:54:18 -05:00
|
|
|
|
2013-01-31 11:13:37 -05:00
|
|
|
this._source = source;
|
2019-12-17 14:39:24 -05:00
|
|
|
this._folder = folder;
|
2013-01-31 11:13:37 -05:00
|
|
|
this._view = source.view;
|
|
|
|
|
|
|
|
this._isOpen = false;
|
2013-05-16 11:20:32 -04:00
|
|
|
this.parentOffset = 0;
|
2013-01-31 11:13:37 -05:00
|
|
|
|
2019-12-12 13:54:18 -05:00
|
|
|
this._viewBox = new St.BoxLayout({
|
|
|
|
style_class: 'app-folder-dialog',
|
2019-08-20 17:43:54 -04:00
|
|
|
x_expand: true,
|
2019-12-12 13:54:18 -05:00
|
|
|
y_expand: true,
|
|
|
|
x_align: Clutter.ActorAlign.FILL,
|
|
|
|
y_align: Clutter.ActorAlign.FILL,
|
2019-12-17 14:39:24 -05:00
|
|
|
vertical: true,
|
2019-08-20 17:43:54 -04:00
|
|
|
});
|
2019-12-12 13:54:18 -05:00
|
|
|
this.add_child(this._viewBox);
|
2019-12-17 14:39:24 -05:00
|
|
|
|
|
|
|
this._addFolderNameEntry();
|
2019-12-12 13:54:18 -05:00
|
|
|
this._viewBox.add_child(this._view);
|
2013-01-31 11:13:37 -05:00
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
global.focus_manager.add_group(this);
|
2013-03-06 15:42:36 -05:00
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
this._grabHelper = new GrabHelper.GrabHelper(this, {
|
2019-08-20 17:43:54 -04:00
|
|
|
actionMode: Shell.ActionMode.POPUP,
|
2018-10-11 14:12:54 -04:00
|
|
|
});
|
2014-06-16 04:24:39 -04:00
|
|
|
this._grabHelper.addActor(Main.layoutManager.overviewGroup);
|
2019-07-16 05:24:13 -04:00
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
2019-12-12 13:54:18 -05:00
|
|
|
|
|
|
|
this._sourceMappedId = 0;
|
|
|
|
this._needsZoomAndFade = false;
|
|
|
|
}
|
|
|
|
|
2019-12-17 14:39:24 -05:00
|
|
|
_addFolderNameEntry() {
|
|
|
|
this._entryBox = new St.BoxLayout({
|
|
|
|
style_class: 'folder-name-container',
|
|
|
|
});
|
|
|
|
this._viewBox.add_child(this._entryBox);
|
|
|
|
|
|
|
|
// Empty actor to center the title
|
|
|
|
let ghostButton = new Clutter.Actor();
|
|
|
|
this._entryBox.add_child(ghostButton);
|
|
|
|
|
|
|
|
let stack = new Shell.Stack({
|
|
|
|
x_expand: true,
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
});
|
|
|
|
this._entryBox.add_child(stack);
|
|
|
|
|
|
|
|
// Folder name label
|
|
|
|
this._folderNameLabel = new St.Label({
|
|
|
|
style_class: 'folder-name-label',
|
|
|
|
x_expand: true,
|
|
|
|
y_expand: true,
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
});
|
|
|
|
|
|
|
|
stack.add_child(this._folderNameLabel);
|
|
|
|
|
|
|
|
// Folder name entry
|
|
|
|
this._entry = new St.Entry({
|
|
|
|
style_class: 'folder-name-entry',
|
|
|
|
opacity: 0,
|
|
|
|
reactive: false,
|
|
|
|
});
|
|
|
|
this._entry.clutter_text.set({
|
|
|
|
x_expand: true,
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
});
|
|
|
|
|
|
|
|
this._entry.clutter_text.connect('activate', () => {
|
|
|
|
this._showFolderLabel();
|
|
|
|
});
|
|
|
|
|
|
|
|
stack.add_child(this._entry);
|
|
|
|
|
|
|
|
// Edit button
|
|
|
|
this._editButton = new St.Button({
|
|
|
|
style_class: 'edit-folder-button',
|
|
|
|
button_mask: St.ButtonMask.ONE,
|
|
|
|
toggle_mode: true,
|
|
|
|
reactive: true,
|
|
|
|
can_focus: true,
|
|
|
|
x_align: Clutter.ActorAlign.END,
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
child: new St.Icon({
|
|
|
|
icon_name: 'document-edit-symbolic',
|
|
|
|
icon_size: 16,
|
|
|
|
}),
|
|
|
|
});
|
|
|
|
|
|
|
|
this._editButton.connect('notify::checked', () => {
|
|
|
|
if (this._editButton.checked)
|
|
|
|
this._showFolderEntry();
|
|
|
|
else
|
|
|
|
this._showFolderLabel();
|
|
|
|
});
|
|
|
|
|
|
|
|
this._entryBox.add_child(this._editButton);
|
|
|
|
|
|
|
|
ghostButton.add_constraint(new Clutter.BindConstraint({
|
|
|
|
source: this._editButton,
|
|
|
|
coordinate: Clutter.BindCoordinate.SIZE,
|
|
|
|
}));
|
|
|
|
|
|
|
|
this._folder.connect('changed::name', () => this._syncFolderName());
|
|
|
|
this._syncFolderName();
|
|
|
|
}
|
|
|
|
|
|
|
|
_syncFolderName() {
|
|
|
|
let newName = _getFolderName(this._folder);
|
|
|
|
|
|
|
|
this._folderNameLabel.text = newName;
|
|
|
|
this._entry.text = newName;
|
|
|
|
}
|
|
|
|
|
|
|
|
_switchActor(from, to) {
|
|
|
|
to.reactive = true;
|
|
|
|
to.ease({
|
|
|
|
opacity: 255,
|
|
|
|
duration: 300,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
});
|
|
|
|
|
|
|
|
from.ease({
|
|
|
|
opacity: 0,
|
|
|
|
duration: 300,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
onComplete: () => {
|
|
|
|
from.reactive = false;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
_showFolderLabel() {
|
|
|
|
if (this._editButton.checked)
|
|
|
|
this._editButton.checked = false;
|
|
|
|
|
|
|
|
this._maybeUpdateFolderName();
|
|
|
|
this._switchActor(this._entry, this._folderNameLabel);
|
|
|
|
}
|
|
|
|
|
|
|
|
_showFolderEntry() {
|
|
|
|
this._switchActor(this._folderNameLabel, this._entry);
|
|
|
|
|
|
|
|
this._entry.clutter_text.set_selection(0, -1);
|
|
|
|
this._entry.clutter_text.grab_key_focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
_maybeUpdateFolderName() {
|
|
|
|
let folderName = _getFolderName(this._folder);
|
|
|
|
let newFolderName = this._entry.text.trim();
|
|
|
|
|
|
|
|
if (newFolderName.length === 0 || newFolderName === folderName)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._folder.set_string('name', newFolderName);
|
|
|
|
this._folder.set_boolean('translate', false);
|
|
|
|
}
|
|
|
|
|
2019-12-12 13:54:18 -05:00
|
|
|
_zoomAndFadeIn() {
|
|
|
|
let [sourceX, sourceY] =
|
|
|
|
this._source.get_transformed_position();
|
|
|
|
let [dialogX, dialogY] =
|
|
|
|
this.get_transformed_position();
|
|
|
|
|
|
|
|
this.set({
|
|
|
|
translation_x: sourceX - dialogX,
|
|
|
|
translation_y: sourceY - dialogY,
|
|
|
|
scale_x: this._source.width / this.width,
|
|
|
|
scale_y: this._source.height / this.height,
|
|
|
|
opacity: 0,
|
|
|
|
});
|
|
|
|
|
|
|
|
this.ease({
|
|
|
|
translation_x: 0,
|
|
|
|
translation_y: 0,
|
|
|
|
scale_x: 1,
|
|
|
|
scale_y: 1,
|
|
|
|
opacity: 255,
|
|
|
|
duration: FOLDER_DIALOG_ANIMATION_TIME,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
});
|
|
|
|
|
|
|
|
this._needsZoomAndFade = false;
|
|
|
|
|
|
|
|
if (this._sourceMappedId === 0) {
|
|
|
|
this._sourceMappedId = this._source.connect(
|
|
|
|
'notify::mapped', this._zoomAndFadeOut.bind(this));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_zoomAndFadeOut() {
|
|
|
|
if (!this._isOpen)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!this._source.mapped) {
|
|
|
|
this.hide();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let [sourceX, sourceY] =
|
|
|
|
this._source.get_transformed_position();
|
|
|
|
let [dialogX, dialogY] =
|
|
|
|
this.get_transformed_position();
|
|
|
|
|
|
|
|
this.ease({
|
|
|
|
translation_x: sourceX - dialogX,
|
|
|
|
translation_y: sourceY - dialogY,
|
|
|
|
scale_x: this._source.width / this.width,
|
|
|
|
scale_y: this._source.height / this.height,
|
|
|
|
opacity: 0,
|
|
|
|
duration: FOLDER_DIALOG_ANIMATION_TIME,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
onComplete: () => {
|
|
|
|
this.set({
|
|
|
|
translation_x: 0,
|
|
|
|
translation_y: 0,
|
|
|
|
scale_x: 1,
|
|
|
|
scale_y: 1,
|
|
|
|
opacity: 255,
|
|
|
|
});
|
|
|
|
this.hide();
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
this._needsZoomAndFade = false;
|
2019-07-22 11:02:10 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
_onDestroy() {
|
|
|
|
if (this._isOpen) {
|
|
|
|
this._isOpen = false;
|
2019-07-16 05:24:13 -04:00
|
|
|
this._grabHelper.ungrab({ actor: this });
|
2019-07-22 11:02:10 -04:00
|
|
|
this._grabHelper = null;
|
|
|
|
}
|
2019-12-12 13:54:18 -05:00
|
|
|
|
|
|
|
if (this._sourceMappedId) {
|
|
|
|
this._source.disconnect(this._sourceMappedId);
|
|
|
|
this._sourceMappedId = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
vfunc_allocate(box, flags) {
|
|
|
|
let contentBox = this.get_theme_node().get_content_box(box);
|
2020-02-17 08:12:47 -05:00
|
|
|
contentBox = this._viewBox.get_theme_node().get_content_box(contentBox);
|
2019-12-17 14:39:24 -05:00
|
|
|
|
|
|
|
let [, entryBoxHeight] = this._entryBox.get_size();
|
|
|
|
let spacing = this._viewBox.layout_manager.spacing;
|
|
|
|
|
|
|
|
this._view.adaptToSize(
|
|
|
|
contentBox.get_width(),
|
|
|
|
contentBox.get_height() - entryBoxHeight - spacing);
|
2019-12-12 13:54:18 -05:00
|
|
|
|
2020-02-17 08:21:51 -05:00
|
|
|
this._view._grid.topPadding = 0;
|
|
|
|
|
2019-12-12 13:54:18 -05:00
|
|
|
super.vfunc_allocate(box, flags);
|
|
|
|
|
|
|
|
// We can only start zooming after receiving an allocation
|
|
|
|
if (this._needsZoomAndFade)
|
|
|
|
this._zoomAndFadeIn();
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-06-11 09:27:40 -04:00
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
vfunc_key_press_event(keyEvent) {
|
|
|
|
if (global.stage.get_key_focus() != this)
|
2014-06-11 09:27:40 -04:00
|
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
|
|
|
|
// Since we need to only grab focus on one item child when the user
|
|
|
|
// actually press a key we don't use navigate_focus when opening
|
|
|
|
// the popup.
|
|
|
|
// Instead of that, grab the focus on the AppFolderPopup actor
|
|
|
|
// and actually moves the focus to a child only when the user
|
|
|
|
// actually press a key.
|
|
|
|
// It should work with just grab_key_focus on the AppFolderPopup
|
|
|
|
// actor, but since the arrow keys are not wrapping_around the focus
|
|
|
|
// is not grabbed by a child when the widget that has the current focus
|
|
|
|
// is the same that is requesting focus, so to make it works with arrow
|
|
|
|
// keys we need to connect to the key-press-event and navigate_focus
|
|
|
|
// when that happens using TAB_FORWARD or TAB_BACKWARD instead of arrow
|
|
|
|
// keys
|
|
|
|
|
|
|
|
// Use TAB_FORWARD for down key and right key
|
|
|
|
// and TAB_BACKWARD for up key and left key on ltr
|
|
|
|
// languages
|
|
|
|
let direction;
|
|
|
|
let isLtr = Clutter.get_default_text_direction() == Clutter.TextDirection.LTR;
|
2019-09-10 01:42:48 -04:00
|
|
|
switch (keyEvent.keyval) {
|
2019-11-05 14:37:28 -05:00
|
|
|
case Clutter.KEY_Down:
|
2019-02-01 07:21:00 -05:00
|
|
|
direction = St.DirectionType.TAB_FORWARD;
|
|
|
|
break;
|
2019-11-05 14:37:28 -05:00
|
|
|
case Clutter.KEY_Right:
|
2019-09-12 18:27:56 -04:00
|
|
|
direction = isLtr
|
|
|
|
? St.DirectionType.TAB_FORWARD
|
|
|
|
: St.DirectionType.TAB_BACKWARD;
|
2019-02-01 07:21:00 -05:00
|
|
|
break;
|
2019-11-05 14:37:28 -05:00
|
|
|
case Clutter.KEY_Up:
|
2019-02-01 07:21:00 -05:00
|
|
|
direction = St.DirectionType.TAB_BACKWARD;
|
|
|
|
break;
|
2019-11-05 14:37:28 -05:00
|
|
|
case Clutter.KEY_Left:
|
2019-09-12 18:27:56 -04:00
|
|
|
direction = isLtr
|
|
|
|
? St.DirectionType.TAB_BACKWARD
|
|
|
|
: St.DirectionType.TAB_FORWARD;
|
2019-02-01 07:21:00 -05:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2014-06-11 09:27:40 -04:00
|
|
|
}
|
2019-09-10 01:42:48 -04:00
|
|
|
return this.navigate_focus(null, direction, false);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-01-31 11:13:37 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
toggle() {
|
2013-01-31 11:13:37 -05:00
|
|
|
if (this._isOpen)
|
|
|
|
this.popdown();
|
|
|
|
else
|
|
|
|
this.popup();
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-01-31 11:13:37 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
popup() {
|
2013-01-31 11:13:37 -05:00
|
|
|
if (this._isOpen)
|
|
|
|
return;
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
this._isOpen = this._grabHelper.grab({ actor: this,
|
2017-12-01 19:27:35 -05:00
|
|
|
onUngrab: this.popdown.bind(this) });
|
2014-06-11 07:13:12 -04:00
|
|
|
|
|
|
|
if (!this._isOpen)
|
|
|
|
return;
|
|
|
|
|
2019-12-12 13:54:18 -05:00
|
|
|
this._needsZoomAndFade = true;
|
2019-07-16 05:24:13 -04:00
|
|
|
this.show();
|
2013-02-20 08:13:46 -05:00
|
|
|
|
2013-01-31 11:13:37 -05:00
|
|
|
this.emit('open-state-changed', true);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-01-31 11:13:37 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
popdown() {
|
2013-01-31 11:13:37 -05:00
|
|
|
if (!this._isOpen)
|
|
|
|
return;
|
|
|
|
|
2019-12-12 13:54:18 -05:00
|
|
|
this._zoomAndFadeOut();
|
2019-12-17 14:39:24 -05:00
|
|
|
this._showFolderLabel();
|
2014-06-11 07:13:12 -04:00
|
|
|
|
2019-12-12 13:54:18 -05:00
|
|
|
this._grabHelper.ungrab({ actor: this });
|
2013-01-31 11:13:37 -05:00
|
|
|
this._isOpen = false;
|
|
|
|
this.emit('open-state-changed', false);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
var AppIcon = GObject.registerClass({
|
|
|
|
Signals: {
|
|
|
|
'menu-state-changed': { param_types: [GObject.TYPE_BOOLEAN] },
|
|
|
|
'sync-tooltip': {},
|
2019-08-20 17:43:54 -04:00
|
|
|
},
|
2019-07-16 05:24:13 -04:00
|
|
|
}, class AppIcon extends St.Button {
|
|
|
|
_init(app, iconParams = {}) {
|
|
|
|
super._init({
|
|
|
|
style_class: 'app-well-app',
|
|
|
|
pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
|
|
|
|
reactive: true,
|
|
|
|
button_mask: St.ButtonMask.ONE | St.ButtonMask.TWO,
|
|
|
|
can_focus: true,
|
|
|
|
});
|
2013-01-30 17:51:43 -05:00
|
|
|
|
2009-12-08 12:51:05 -05:00
|
|
|
this.app = app;
|
2013-12-15 22:18:17 -05:00
|
|
|
this.id = app.get_id();
|
|
|
|
this.name = app.get_name();
|
|
|
|
|
2015-02-17 12:33:37 -05:00
|
|
|
this._iconContainer = new St.Widget({ layout_manager: new Clutter.BinLayout(),
|
|
|
|
x_expand: true, y_expand: true });
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
this.set_child(this._iconContainer);
|
2015-02-17 12:33:37 -05:00
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
this._delegate = this;
|
2009-12-08 12:51:05 -05:00
|
|
|
|
2019-07-12 18:32:54 -04:00
|
|
|
this._folderPreviewId = 0;
|
|
|
|
|
2015-01-27 16:10:45 -05:00
|
|
|
// Get the isDraggable property without passing it on to the BaseIcon:
|
|
|
|
let appIconParams = Params.parse(iconParams, { isDraggable: true }, true);
|
|
|
|
let isDraggable = appIconParams['isDraggable'];
|
|
|
|
delete iconParams['isDraggable'];
|
|
|
|
|
2017-12-01 19:27:35 -05:00
|
|
|
iconParams['createIcon'] = this._createIcon.bind(this);
|
2013-08-15 04:38:17 -04:00
|
|
|
iconParams['setSizeManually'] = true;
|
2013-01-30 17:51:43 -05:00
|
|
|
this.icon = new IconGrid.BaseIcon(app.get_name(), iconParams);
|
2018-06-30 14:00:08 -04:00
|
|
|
this._iconContainer.add_child(this.icon);
|
2009-12-08 12:51:05 -05:00
|
|
|
|
2019-11-26 19:13:24 -05:00
|
|
|
this._dot = new St.Widget({
|
|
|
|
style_class: 'app-well-app-running-dot',
|
|
|
|
layout_manager: new Clutter.BinLayout(),
|
|
|
|
x_expand: true,
|
|
|
|
y_expand: true,
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
y_align: Clutter.ActorAlign.END,
|
|
|
|
});
|
|
|
|
this._iconContainer.add_child(this._dot);
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
this.label_actor = this.icon.label;
|
2011-03-08 13:33:57 -05:00
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
this.connect('popup-menu', this._onKeyboardPopupMenu.bind(this));
|
2010-05-20 11:18:46 -04:00
|
|
|
|
2009-12-08 12:51:05 -05:00
|
|
|
this._menu = null;
|
2019-07-16 05:24:13 -04:00
|
|
|
this._menuManager = new PopupMenu.PopupMenuManager(this);
|
2009-12-08 12:51:05 -05:00
|
|
|
|
2015-01-27 16:10:45 -05:00
|
|
|
if (isDraggable) {
|
2019-07-16 05:24:13 -04:00
|
|
|
this._draggable = DND.makeDraggable(this);
|
2017-10-30 20:38:18 -04:00
|
|
|
this._draggable.connect('drag-begin', () => {
|
2019-06-29 00:32:40 -04:00
|
|
|
this._dragging = true;
|
2019-07-04 17:39:13 -04:00
|
|
|
this.scaleAndFade();
|
2017-10-30 20:38:18 -04:00
|
|
|
this._removeMenuTimeout();
|
|
|
|
Main.overview.beginItemDrag(this);
|
|
|
|
});
|
|
|
|
this._draggable.connect('drag-cancelled', () => {
|
2019-06-29 00:32:40 -04:00
|
|
|
this._dragging = false;
|
2017-10-30 20:38:18 -04:00
|
|
|
Main.overview.cancelledItemDrag(this);
|
|
|
|
});
|
|
|
|
this._draggable.connect('drag-end', () => {
|
2019-06-29 00:32:40 -04:00
|
|
|
this._dragging = false;
|
2019-07-04 17:39:13 -04:00
|
|
|
this.undoScaleAndFade();
|
2019-01-29 14:36:54 -05:00
|
|
|
Main.overview.endItemDrag(this);
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2015-01-27 16:10:45 -05:00
|
|
|
}
|
2009-12-08 12:51:05 -05:00
|
|
|
|
2019-10-29 19:06:10 -04:00
|
|
|
this._otherIconIsHovering = false;
|
2019-07-12 18:32:54 -04:00
|
|
|
|
2010-03-10 08:52:28 -05:00
|
|
|
this._menuTimeoutId = 0;
|
2017-10-30 20:38:18 -04:00
|
|
|
this._stateChangedId = this.app.connect('notify::state', () => {
|
|
|
|
this._updateRunningStyle();
|
|
|
|
});
|
2014-11-01 10:24:03 -04:00
|
|
|
this._updateRunningStyle();
|
2019-07-16 05:24:13 -04:00
|
|
|
|
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2010-01-07 00:40:21 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_onDestroy() {
|
2019-07-12 18:32:54 -04:00
|
|
|
if (this._folderPreviewId > 0) {
|
|
|
|
GLib.source_remove(this._folderPreviewId);
|
|
|
|
this._folderPreviewId = 0;
|
|
|
|
}
|
Major ShellApp API cleanup, startup notification, window focus handling
This patch combines several high level changes which are conceptually
independent but in practice rather intertwined.
* Add a "state" property to ShellApp which reflects whether it's
stopped, starting, or started. This will allow us to later clean
up all the callers that are using ".get_windows().length > 0" as
a proxy for this property
* Replace shell_app_launch with shell_app_activate and shell_app_open_new_window
A lot of code was calling .launch, but it's signficantly clearer
if we call this ".open_new_window()", and later if we gain the ability
to call into an application's menu, we can implement this correctly rather
than trying to update all .launch callers.
* Because ShellApp now has a "starting" state, rebase panel.js on top of
this so that when we get a startup-notification sequence for an app
and transition it to starting, it becomes the focus app, and panel.js
cleanly just tracks the focus app, rather than bouncing between SN
sequences. This removes display of non-app startup sequences, which
I consider an acceptable action in light of the committed changes
to startup-notification and GTK+.
https://bugzilla.gnome.org/show_bug.cgi?id=614755
2010-04-03 14:07:44 -04:00
|
|
|
if (this._stateChangedId > 0)
|
|
|
|
this.app.disconnect(this._stateChangedId);
|
2019-11-21 16:50:11 -05:00
|
|
|
|
|
|
|
if (this._dragMonitor) {
|
|
|
|
DND.removeDragMonitor(this._dragMonitor);
|
|
|
|
this._dragMonitor = null;
|
|
|
|
}
|
|
|
|
|
2019-08-09 17:30:44 -04:00
|
|
|
if (this._draggable) {
|
|
|
|
if (this._dragging)
|
|
|
|
Main.overview.endItemDrag(this);
|
|
|
|
this._draggable = null;
|
2019-06-29 00:32:40 -04:00
|
|
|
}
|
2010-06-05 18:35:26 -04:00
|
|
|
this._stateChangedId = 0;
|
2010-03-10 08:52:28 -05:00
|
|
|
this._removeMenuTimeout();
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2010-03-10 08:52:28 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_createIcon(iconSize) {
|
2013-01-30 17:51:43 -05:00
|
|
|
return this.app.create_icon_texture(iconSize);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-01-30 17:51:43 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_removeMenuTimeout() {
|
2010-03-10 08:52:28 -05:00
|
|
|
if (this._menuTimeoutId > 0) {
|
2019-08-19 14:50:33 -04:00
|
|
|
GLib.source_remove(this._menuTimeoutId);
|
2010-03-10 08:52:28 -05:00
|
|
|
this._menuTimeoutId = 0;
|
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2010-01-07 00:40:21 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_updateRunningStyle() {
|
Major ShellApp API cleanup, startup notification, window focus handling
This patch combines several high level changes which are conceptually
independent but in practice rather intertwined.
* Add a "state" property to ShellApp which reflects whether it's
stopped, starting, or started. This will allow us to later clean
up all the callers that are using ".get_windows().length > 0" as
a proxy for this property
* Replace shell_app_launch with shell_app_activate and shell_app_open_new_window
A lot of code was calling .launch, but it's signficantly clearer
if we call this ".open_new_window()", and later if we gain the ability
to call into an application's menu, we can implement this correctly rather
than trying to update all .launch callers.
* Because ShellApp now has a "starting" state, rebase panel.js on top of
this so that when we get a startup-notification sequence for an app
and transition it to starting, it becomes the focus app, and panel.js
cleanly just tracks the focus app, rather than bouncing between SN
sequences. This removes display of non-app startup sequences, which
I consider an acceptable action in light of the committed changes
to startup-notification and GTK+.
https://bugzilla.gnome.org/show_bug.cgi?id=614755
2010-04-03 14:07:44 -04:00
|
|
|
if (this.app.state != Shell.AppState.STOPPED)
|
2015-02-17 12:33:37 -05:00
|
|
|
this._dot.show();
|
Major ShellApp API cleanup, startup notification, window focus handling
This patch combines several high level changes which are conceptually
independent but in practice rather intertwined.
* Add a "state" property to ShellApp which reflects whether it's
stopped, starting, or started. This will allow us to later clean
up all the callers that are using ".get_windows().length > 0" as
a proxy for this property
* Replace shell_app_launch with shell_app_activate and shell_app_open_new_window
A lot of code was calling .launch, but it's signficantly clearer
if we call this ".open_new_window()", and later if we gain the ability
to call into an application's menu, we can implement this correctly rather
than trying to update all .launch callers.
* Because ShellApp now has a "starting" state, rebase panel.js on top of
this so that when we get a startup-notification sequence for an app
and transition it to starting, it becomes the focus app, and panel.js
cleanly just tracks the focus app, rather than bouncing between SN
sequences. This removes display of non-app startup sequences, which
I consider an acceptable action in light of the committed changes
to startup-notification and GTK+.
https://bugzilla.gnome.org/show_bug.cgi?id=614755
2010-04-03 14:07:44 -04:00
|
|
|
else
|
2015-02-17 12:33:37 -05:00
|
|
|
this._dot.hide();
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2009-11-12 17:46:59 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_setPopupTimeout() {
|
2014-07-22 06:40:54 -04:00
|
|
|
this._removeMenuTimeout();
|
2019-08-19 14:50:33 -04:00
|
|
|
this._menuTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, MENU_POPUP_TIMEOUT, () => {
|
2017-10-30 20:38:18 -04:00
|
|
|
this._menuTimeoutId = 0;
|
|
|
|
this.popupMenu();
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
|
|
});
|
2014-07-22 06:40:54 -04:00
|
|
|
GLib.Source.set_name_by_id(this._menuTimeoutId, '[gnome-shell] this.popupMenu');
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-07-22 06:40:54 -04:00
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
vfunc_leave_event(crossingEvent) {
|
|
|
|
let ret = super.vfunc_leave_event(crossingEvent);
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
this.fake_release();
|
2014-07-22 06:40:54 -04:00
|
|
|
this._removeMenuTimeout();
|
2019-09-10 01:42:48 -04:00
|
|
|
return ret;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-07-22 06:40:54 -04:00
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
vfunc_button_press_event(buttonEvent) {
|
|
|
|
super.vfunc_button_press_event(buttonEvent);
|
|
|
|
if (buttonEvent.button == 1) {
|
2014-07-22 06:40:54 -04:00
|
|
|
this._setPopupTimeout();
|
2019-09-10 01:42:48 -04:00
|
|
|
} else if (buttonEvent.button == 3) {
|
2011-01-26 11:15:41 -05:00
|
|
|
this.popupMenu();
|
2013-11-29 13:17:34 -05:00
|
|
|
return Clutter.EVENT_STOP;
|
2010-03-10 08:52:28 -05:00
|
|
|
}
|
2013-11-29 13:17:34 -05:00
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2009-11-12 17:46:59 -05:00
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
vfunc_touch_event(touchEvent) {
|
|
|
|
super.vfunc_touch_event(touchEvent);
|
|
|
|
if (touchEvent.type == Clutter.EventType.TOUCH_BEGIN)
|
2014-07-22 06:40:54 -04:00
|
|
|
this._setPopupTimeout();
|
|
|
|
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-07-22 06:40:54 -04:00
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
vfunc_clicked(button) {
|
2010-03-10 08:52:28 -05:00
|
|
|
this._removeMenuTimeout();
|
2014-08-20 12:39:02 -04:00
|
|
|
this.activate(button);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2009-11-12 17:46:59 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_onKeyboardPopupMenu() {
|
2011-02-03 12:38:03 -05:00
|
|
|
this.popupMenu();
|
2018-11-27 07:58:25 -05:00
|
|
|
this._menu.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2011-02-03 12:38:03 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
getId() {
|
2010-02-15 19:50:36 -05:00
|
|
|
return this.app.get_id();
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2010-02-15 19:50:36 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
popupMenu() {
|
2010-03-10 08:52:28 -05:00
|
|
|
this._removeMenuTimeout();
|
2019-07-16 05:24:13 -04:00
|
|
|
this.fake_release();
|
2015-01-27 16:10:45 -05:00
|
|
|
|
|
|
|
if (this._draggable)
|
|
|
|
this._draggable.fakeRelease();
|
2010-03-10 08:52:28 -05:00
|
|
|
|
2009-11-12 17:46:59 -05:00
|
|
|
if (!this._menu) {
|
|
|
|
this._menu = new AppIconMenu(this);
|
2017-10-30 20:38:18 -04:00
|
|
|
this._menu.connect('activate-window', (menu, window) => {
|
2009-11-12 17:46:59 -05:00
|
|
|
this.activateWindow(window);
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
|
|
|
this._menu.connect('open-state-changed', (menu, isPoppedUp) => {
|
2011-02-12 14:03:44 -05:00
|
|
|
if (!isPoppedUp)
|
2009-11-12 17:46:59 -05:00
|
|
|
this._onMenuPoppedDown();
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
|
|
|
let id = Main.overview.connect('hiding', () => {
|
|
|
|
this._menu.close();
|
|
|
|
});
|
2019-07-16 05:24:13 -04:00
|
|
|
this.connect('destroy', () => {
|
2015-03-03 19:51:19 -05:00
|
|
|
Main.overview.disconnect(id);
|
|
|
|
});
|
2010-05-20 11:18:46 -04:00
|
|
|
|
2010-10-07 14:15:51 -04:00
|
|
|
this._menuManager.addMenu(this._menu);
|
2009-11-12 17:46:59 -05:00
|
|
|
}
|
|
|
|
|
2012-11-03 16:03:33 -04:00
|
|
|
this.emit('menu-state-changed', true);
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
this.set_hover(true);
|
2010-05-20 11:18:46 -04:00
|
|
|
this._menu.popup();
|
2012-02-29 19:09:41 -05:00
|
|
|
this._menuManager.ignoreRelease();
|
2013-08-18 10:20:22 -04:00
|
|
|
this.emit('sync-tooltip');
|
2009-11-12 17:46:59 -05:00
|
|
|
|
|
|
|
return false;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2009-11-12 17:46:59 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
activateWindow(metaWindow) {
|
2019-08-19 20:51:42 -04:00
|
|
|
if (metaWindow)
|
2010-02-17 14:05:06 -05:00
|
|
|
Main.activateWindow(metaWindow);
|
2019-08-19 20:51:42 -04:00
|
|
|
else
|
2009-12-08 12:51:05 -05:00
|
|
|
Main.overview.hide();
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2009-12-08 12:51:05 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_onMenuPoppedDown() {
|
2019-07-16 05:24:13 -04:00
|
|
|
this.sync_hover();
|
2012-11-03 16:03:33 -04:00
|
|
|
this.emit('menu-state-changed', false);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2009-12-08 12:51:05 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
activate(button) {
|
2014-08-20 12:39:02 -04:00
|
|
|
let event = Clutter.get_current_event();
|
|
|
|
let modifiers = event ? event.get_state() : 0;
|
2018-05-29 00:27:22 -04:00
|
|
|
let isMiddleButton = button && button == Clutter.BUTTON_MIDDLE;
|
|
|
|
let isCtrlPressed = (modifiers & Clutter.ModifierType.CONTROL_MASK) != 0;
|
|
|
|
let openNewWindow = this.app.can_open_new_window() &&
|
|
|
|
this.app.state == Shell.AppState.RUNNING &&
|
|
|
|
(isCtrlPressed || isMiddleButton);
|
2009-12-08 12:51:05 -05:00
|
|
|
|
2014-06-17 15:31:53 -04:00
|
|
|
if (this.app.state == Shell.AppState.STOPPED || openNewWindow)
|
|
|
|
this.animateLaunch();
|
|
|
|
|
2014-08-20 12:39:02 -04:00
|
|
|
if (openNewWindow)
|
2013-02-17 23:08:41 -05:00
|
|
|
this.app.open_new_window(-1);
|
2014-08-20 12:39:02 -04:00
|
|
|
else
|
2013-02-17 23:08:41 -05:00
|
|
|
this.app.activate();
|
|
|
|
|
Major ShellApp API cleanup, startup notification, window focus handling
This patch combines several high level changes which are conceptually
independent but in practice rather intertwined.
* Add a "state" property to ShellApp which reflects whether it's
stopped, starting, or started. This will allow us to later clean
up all the callers that are using ".get_windows().length > 0" as
a proxy for this property
* Replace shell_app_launch with shell_app_activate and shell_app_open_new_window
A lot of code was calling .launch, but it's signficantly clearer
if we call this ".open_new_window()", and later if we gain the ability
to call into an application's menu, we can implement this correctly rather
than trying to update all .launch callers.
* Because ShellApp now has a "starting" state, rebase panel.js on top of
this so that when we get a startup-notification sequence for an app
and transition it to starting, it becomes the focus app, and panel.js
cleanly just tracks the focus app, rather than bouncing between SN
sequences. This removes display of non-app startup sequences, which
I consider an acceptable action in light of the committed changes
to startup-notification and GTK+.
https://bugzilla.gnome.org/show_bug.cgi?id=614755
2010-04-03 14:07:44 -04:00
|
|
|
Main.overview.hide();
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2009-09-01 14:15:29 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
animateLaunch() {
|
2014-06-17 15:31:53 -04:00
|
|
|
this.icon.animateZoomOut();
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-06-17 15:31:53 -04:00
|
|
|
|
2019-09-15 05:40:27 -04:00
|
|
|
animateLaunchAtPos(x, y) {
|
|
|
|
this.icon.animateZoomOutAtPos(x, y);
|
|
|
|
}
|
|
|
|
|
2019-06-29 13:09:32 -04:00
|
|
|
scaleIn() {
|
2019-07-16 05:24:13 -04:00
|
|
|
this.scale_x = 0;
|
|
|
|
this.scale_y = 0;
|
2019-06-29 13:09:32 -04:00
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
this.ease({
|
2019-06-29 13:09:32 -04:00
|
|
|
scale_x: 1,
|
|
|
|
scale_y: 1,
|
2019-10-09 20:14:28 -04:00
|
|
|
duration: APP_ICON_SCALE_IN_TIME,
|
2019-06-29 13:09:32 -04:00
|
|
|
delay: APP_ICON_SCALE_IN_DELAY,
|
2019-08-20 17:43:54 -04:00
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUINT,
|
2019-06-29 13:09:32 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
shellWorkspaceLaunch(params) {
|
2019-08-02 06:58:34 -04:00
|
|
|
let { stack } = new Error();
|
2020-02-14 10:10:34 -05:00
|
|
|
log('shellWorkspaceLaunch is deprecated, use app.open_new_window() instead\n%s'.format(stack));
|
2019-08-02 06:58:34 -04:00
|
|
|
|
2011-08-11 05:35:23 -04:00
|
|
|
params = Params.parse(params, { workspace: -1,
|
|
|
|
timestamp: 0 });
|
2011-01-30 16:09:58 -05:00
|
|
|
|
2011-08-11 05:35:23 -04:00
|
|
|
this.app.open_new_window(params.workspace);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2009-08-17 20:29:54 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
getDragActor() {
|
2019-08-30 21:51:02 -04:00
|
|
|
return this.app.create_icon_texture(Main.overview.dash.iconSize);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2009-06-30 16:35:39 -04:00
|
|
|
|
2010-02-04 16:57:38 -05:00
|
|
|
// Returns the original actor that should align with the actor
|
|
|
|
// we show as the item is being dragged.
|
2017-10-30 20:03:21 -04:00
|
|
|
getDragActorSource() {
|
2010-07-21 19:29:02 -04:00
|
|
|
return this.icon.icon;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-08-18 10:20:22 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
shouldShowTooltip() {
|
2019-07-16 05:24:13 -04:00
|
|
|
return this.hover && (!this._menu || !this._menu.isOpen);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2019-07-04 17:39:13 -04:00
|
|
|
|
|
|
|
scaleAndFade() {
|
2019-07-16 05:24:13 -04:00
|
|
|
this.reactive = false;
|
|
|
|
this.ease({
|
2019-07-04 17:39:13 -04:00
|
|
|
scale_x: 0.75,
|
|
|
|
scale_y: 0.75,
|
2019-08-20 17:43:54 -04:00
|
|
|
opacity: 128,
|
2019-07-04 17:39:13 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
undoScaleAndFade() {
|
2019-07-16 05:24:13 -04:00
|
|
|
this.reactive = true;
|
|
|
|
this.ease({
|
2019-07-04 17:39:13 -04:00
|
|
|
scale_x: 1.0,
|
|
|
|
scale_y: 1.0,
|
2019-08-20 17:43:54 -04:00
|
|
|
opacity: 255,
|
2019-07-04 17:39:13 -04:00
|
|
|
});
|
|
|
|
}
|
2019-07-12 18:32:54 -04:00
|
|
|
|
|
|
|
_showFolderPreview() {
|
|
|
|
this.icon.label.opacity = 0;
|
|
|
|
this.icon.icon.ease({
|
|
|
|
scale_x: FOLDER_SUBICON_FRACTION,
|
2019-08-20 17:43:54 -04:00
|
|
|
scale_y: FOLDER_SUBICON_FRACTION,
|
2019-07-12 18:32:54 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
_hideFolderPreview() {
|
|
|
|
this.icon.label.opacity = 255;
|
|
|
|
this.icon.icon.ease({
|
|
|
|
scale_x: 1.0,
|
2019-08-20 17:43:54 -04:00
|
|
|
scale_y: 1.0,
|
2019-07-12 18:32:54 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
_canAccept(source) {
|
|
|
|
let view = _getViewFromIcon(source);
|
|
|
|
|
|
|
|
return source != this &&
|
2019-10-29 18:47:55 -04:00
|
|
|
(source instanceof this.constructor) &&
|
2019-07-12 18:32:54 -04:00
|
|
|
(view instanceof AllView);
|
|
|
|
}
|
|
|
|
|
|
|
|
_setHoveringByDnd(hovering) {
|
2019-10-29 19:06:10 -04:00
|
|
|
if (this._otherIconIsHovering == hovering)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._otherIconIsHovering = hovering;
|
|
|
|
|
2019-07-12 18:32:54 -04:00
|
|
|
if (hovering) {
|
2019-10-29 19:06:10 -04:00
|
|
|
this._dragMonitor = {
|
|
|
|
dragMotion: this._onDragMotion.bind(this),
|
|
|
|
};
|
|
|
|
DND.addDragMonitor(this._dragMonitor);
|
|
|
|
|
2019-07-12 18:32:54 -04:00
|
|
|
if (this._folderPreviewId > 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._folderPreviewId =
|
|
|
|
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
|
2019-07-16 05:24:13 -04:00
|
|
|
this.add_style_pseudo_class('drop');
|
2019-07-12 18:32:54 -04:00
|
|
|
this._showFolderPreview();
|
|
|
|
this._folderPreviewId = 0;
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
|
|
});
|
|
|
|
} else {
|
2019-10-29 19:06:10 -04:00
|
|
|
DND.removeDragMonitor(this._dragMonitor);
|
|
|
|
|
2019-07-12 18:32:54 -04:00
|
|
|
if (this._folderPreviewId > 0) {
|
|
|
|
GLib.source_remove(this._folderPreviewId);
|
|
|
|
this._folderPreviewId = 0;
|
|
|
|
}
|
|
|
|
this._hideFolderPreview();
|
2019-07-16 05:24:13 -04:00
|
|
|
this.remove_style_pseudo_class('drop');
|
2019-07-12 18:32:54 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_onDragMotion(dragEvent) {
|
2019-10-29 19:06:10 -04:00
|
|
|
if (!this.contains(dragEvent.targetActor))
|
|
|
|
this._setHoveringByDnd(false);
|
2019-07-12 18:32:54 -04:00
|
|
|
|
|
|
|
return DND.DragMotionResult.CONTINUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
handleDragOver(source) {
|
|
|
|
if (source == this)
|
|
|
|
return DND.DragMotionResult.NO_DROP;
|
|
|
|
|
|
|
|
if (!this._canAccept(source))
|
|
|
|
return DND.DragMotionResult.CONTINUE;
|
|
|
|
|
2019-10-29 19:06:10 -04:00
|
|
|
this._setHoveringByDnd(true);
|
|
|
|
|
2019-07-12 18:32:54 -04:00
|
|
|
return DND.DragMotionResult.MOVE_DROP;
|
|
|
|
}
|
|
|
|
|
|
|
|
acceptDrop(source) {
|
|
|
|
this._setHoveringByDnd(false);
|
|
|
|
|
|
|
|
if (!this._canAccept(source))
|
|
|
|
return false;
|
|
|
|
|
2019-07-16 16:51:25 -04:00
|
|
|
let view = _getViewFromIcon(this);
|
|
|
|
let apps = [this.id, source.id];
|
|
|
|
|
|
|
|
return view.createFolder(apps);
|
2019-07-12 18:32:54 -04:00
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
});
|
2009-11-12 17:46:59 -05:00
|
|
|
|
2017-10-30 21:19:44 -04:00
|
|
|
var AppIconMenu = class AppIconMenu extends PopupMenu.PopupMenu {
|
|
|
|
constructor(source) {
|
2010-11-29 16:07:10 -05:00
|
|
|
let side = St.Side.LEFT;
|
2012-02-13 20:37:28 -05:00
|
|
|
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
2010-11-29 16:07:10 -05:00
|
|
|
side = St.Side.RIGHT;
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
super(source, 0.5, side);
|
2010-05-20 11:18:46 -04:00
|
|
|
|
2011-03-22 10:43:27 -04:00
|
|
|
// We want to keep the item hovered while the menu is up
|
|
|
|
this.blockSourceEvents = true;
|
|
|
|
|
2009-11-12 17:46:59 -05:00
|
|
|
this._source = source;
|
|
|
|
|
2010-05-20 11:18:46 -04:00
|
|
|
this.actor.add_style_class_name('app-well-menu');
|
2009-11-12 17:46:59 -05:00
|
|
|
|
|
|
|
// Chain our visibility and lifecycle to that of the source
|
2019-07-16 05:24:13 -04:00
|
|
|
this._sourceMappedId = source.connect('notify::mapped', () => {
|
|
|
|
if (!source.mapped)
|
2010-05-20 11:18:46 -04:00
|
|
|
this.close();
|
2017-12-06 00:55:37 -05:00
|
|
|
});
|
2019-07-16 05:24:13 -04:00
|
|
|
source.connect('destroy', () => {
|
|
|
|
source.disconnect(this._sourceMappedId);
|
2017-12-06 00:55:37 -05:00
|
|
|
this.destroy();
|
|
|
|
});
|
2009-11-12 17:46:59 -05:00
|
|
|
|
2010-05-06 17:18:10 -04:00
|
|
|
Main.uiGroup.add_actor(this.actor);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2009-11-12 17:46:59 -05:00
|
|
|
|
2020-02-17 09:35:49 -05:00
|
|
|
_rebuildMenu() {
|
2010-05-20 11:18:46 -04:00
|
|
|
this.removeAll();
|
2009-11-12 17:46:59 -05:00
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
let windows = this._source.app.get_windows().filter(
|
|
|
|
w => !w.skip_taskbar
|
|
|
|
);
|
2009-11-12 17:46:59 -05:00
|
|
|
|
2019-08-19 20:51:42 -04:00
|
|
|
if (windows.length > 0) {
|
2019-02-12 07:37:39 -05:00
|
|
|
this.addMenuItem(
|
|
|
|
/* Translators: This is the heading of a list of open windows */
|
|
|
|
new PopupMenu.PopupSeparatorMenuItem(_("Open Windows"))
|
|
|
|
);
|
2019-08-19 20:51:42 -04:00
|
|
|
}
|
2019-02-12 07:37:39 -05:00
|
|
|
|
|
|
|
windows.forEach(window => {
|
2019-08-19 15:33:15 -04:00
|
|
|
let title = window.title
|
|
|
|
? window.title : this._source.app.get_name();
|
2018-02-20 12:16:52 -05:00
|
|
|
let item = this._appendMenuItem(title);
|
2017-10-30 20:38:18 -04:00
|
|
|
item.connect('activate', () => {
|
2011-11-21 13:01:05 -05:00
|
|
|
this.emit('activate-window', window);
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2019-02-12 07:37:39 -05:00
|
|
|
});
|
2009-11-12 17:46:59 -05:00
|
|
|
|
2011-08-11 06:06:38 -04:00
|
|
|
if (!this._source.app.is_window_backed()) {
|
2011-11-21 13:01:05 -05:00
|
|
|
this._appendSeparator();
|
2009-11-12 17:46:59 -05:00
|
|
|
|
2015-02-12 20:03:33 -05:00
|
|
|
let appInfo = this._source.app.get_app_info();
|
|
|
|
let actions = appInfo.list_actions();
|
|
|
|
if (this._source.app.can_open_new_window() &&
|
2019-07-10 16:52:09 -04:00
|
|
|
!actions.includes('new-window')) {
|
2014-01-19 12:05:16 -05:00
|
|
|
this._newWindowMenuItem = this._appendMenuItem(_("New Window"));
|
2017-10-30 20:38:18 -04:00
|
|
|
this._newWindowMenuItem.connect('activate', () => {
|
2019-08-08 11:27:40 -04:00
|
|
|
this._source.animateLaunch();
|
2014-01-19 12:05:16 -05:00
|
|
|
this._source.app.open_new_window(-1);
|
|
|
|
this.emit('activate-window', null);
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2014-01-19 12:05:16 -05:00
|
|
|
this._appendSeparator();
|
|
|
|
}
|
2011-11-21 13:01:05 -05:00
|
|
|
|
2016-10-19 09:58:16 -04:00
|
|
|
if (discreteGpuAvailable &&
|
2019-10-21 06:48:43 -04:00
|
|
|
this._source.app.state == Shell.AppState.STOPPED) {
|
2016-10-19 09:58:16 -04:00
|
|
|
this._onDiscreteGpuMenuItem = this._appendMenuItem(_("Launch using Dedicated Graphics Card"));
|
2017-10-30 20:38:18 -04:00
|
|
|
this._onDiscreteGpuMenuItem.connect('activate', () => {
|
2019-08-08 11:27:40 -04:00
|
|
|
this._source.animateLaunch();
|
2016-10-19 09:58:16 -04:00
|
|
|
this._source.app.launch(0, -1, true);
|
|
|
|
this.emit('activate-window', null);
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2016-10-19 09:58:16 -04:00
|
|
|
}
|
|
|
|
|
2014-01-19 12:46:36 -05:00
|
|
|
for (let i = 0; i < actions.length; i++) {
|
|
|
|
let action = actions[i];
|
|
|
|
let item = this._appendMenuItem(appInfo.get_action_name(action));
|
2017-10-30 20:38:18 -04:00
|
|
|
item.connect('activate', (emitter, event) => {
|
2019-10-21 06:48:43 -04:00
|
|
|
if (action == 'new-window')
|
2019-08-02 08:23:13 -04:00
|
|
|
this._source.animateLaunch();
|
|
|
|
|
2014-01-19 12:46:36 -05:00
|
|
|
this._source.app.launch_action(action, event.get_time(), -1);
|
|
|
|
this.emit('activate-window', null);
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2014-01-19 12:46:36 -05:00
|
|
|
}
|
2009-11-12 17:46:59 -05:00
|
|
|
|
2015-03-20 15:30:57 -04:00
|
|
|
let canFavorite = global.settings.is_writable('favorite-apps');
|
2011-11-21 13:01:05 -05:00
|
|
|
|
2014-12-11 10:22:19 -05:00
|
|
|
if (canFavorite) {
|
|
|
|
this._appendSeparator();
|
|
|
|
|
|
|
|
let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id());
|
|
|
|
|
|
|
|
if (isFavorite) {
|
|
|
|
let item = this._appendMenuItem(_("Remove from Favorites"));
|
2017-10-30 20:38:18 -04:00
|
|
|
item.connect('activate', () => {
|
2014-12-11 10:22:19 -05:00
|
|
|
let favs = AppFavorites.getAppFavorites();
|
|
|
|
favs.removeFavorite(this._source.app.get_id());
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2014-12-11 10:22:19 -05:00
|
|
|
} else {
|
|
|
|
let item = this._appendMenuItem(_("Add to Favorites"));
|
2017-10-30 20:38:18 -04:00
|
|
|
item.connect('activate', () => {
|
2014-12-11 10:22:19 -05:00
|
|
|
let favs = AppFavorites.getAppFavorites();
|
|
|
|
favs.addFavorite(this._source.app.get_id());
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2014-12-11 10:22:19 -05:00
|
|
|
}
|
2011-11-21 13:01:05 -05:00
|
|
|
}
|
2013-11-02 15:36:34 -04:00
|
|
|
|
|
|
|
if (Shell.AppSystem.get_default().lookup_app('org.gnome.Software.desktop')) {
|
|
|
|
this._appendSeparator();
|
|
|
|
let item = this._appendMenuItem(_("Show Details"));
|
2019-12-19 14:50:37 -05:00
|
|
|
item.connect('activate', async () => {
|
2013-11-02 15:36:34 -04:00
|
|
|
let id = this._source.app.get_id();
|
|
|
|
let args = GLib.Variant.new('(ss)', [id, '']);
|
2019-12-19 14:50:37 -05:00
|
|
|
const bus = await Gio.DBus.get(Gio.BusType.SESSION, null);
|
|
|
|
bus.call(
|
|
|
|
'org.gnome.Software',
|
|
|
|
'/org/gnome/Software',
|
|
|
|
'org.gtk.Actions', 'Activate',
|
|
|
|
new GLib.Variant.new(
|
|
|
|
'(sava{sv})', ['details', [args], null]),
|
2019-12-19 14:50:37 -05:00
|
|
|
null, 0, -1, null);
|
2019-12-19 14:50:37 -05:00
|
|
|
Main.overview.hide();
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2013-11-02 15:36:34 -04:00
|
|
|
}
|
2011-08-11 06:06:38 -04:00
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2009-11-12 17:46:59 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_appendSeparator() {
|
2010-05-20 11:18:46 -04:00
|
|
|
let separator = new PopupMenu.PopupSeparatorMenuItem();
|
|
|
|
this.addMenuItem(separator);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2009-11-12 17:46:59 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_appendMenuItem(labelText) {
|
2010-05-20 11:18:46 -04:00
|
|
|
// FIXME: app-well-menu-item style
|
|
|
|
let item = new PopupMenu.PopupMenuItem(labelText);
|
|
|
|
this.addMenuItem(item);
|
|
|
|
return item;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2009-11-12 17:46:59 -05:00
|
|
|
|
2019-01-31 09:08:10 -05:00
|
|
|
popup(_activatingButton) {
|
2020-02-17 09:35:49 -05:00
|
|
|
this._rebuildMenu();
|
2010-05-20 11:18:46 -04:00
|
|
|
this.open();
|
2009-11-12 17:46:59 -05:00
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
};
|
2009-11-12 17:46:59 -05:00
|
|
|
Signals.addSignalMethods(AppIconMenu.prototype);
|
2017-08-21 09:20:25 -04:00
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
var SystemActionIcon = GObject.registerClass(
|
|
|
|
class SystemActionIcon extends Search.GridSearchResult {
|
2017-10-30 20:03:21 -04:00
|
|
|
activate() {
|
2017-08-21 09:20:25 -04:00
|
|
|
SystemActions.getDefault().activateAction(this.metaInfo['id']);
|
|
|
|
Main.overview.hide();
|
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
});
|