2011-09-28 13:16:26 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2008-11-21 00:53:11 +00:00
|
|
|
|
|
|
|
const Clutter = imports.gi.Clutter;
|
2012-11-26 22:01:44 +00:00
|
|
|
const Gio = imports.gi.Gio;
|
2011-02-25 16:50:39 +00:00
|
|
|
const GLib = imports.gi.GLib;
|
2013-01-31 16:13:37 +00:00
|
|
|
const GObject = imports.gi.GObject;
|
2008-11-21 00:53:11 +00:00
|
|
|
const Gtk = imports.gi.Gtk;
|
|
|
|
const Shell = imports.gi.Shell;
|
2009-04-01 19:51:17 +00:00
|
|
|
const Lang = imports.lang;
|
2009-02-02 23:02:16 +00:00
|
|
|
const Signals = imports.signals;
|
2011-04-25 19:42:03 +00:00
|
|
|
const Meta = imports.gi.Meta;
|
2009-11-12 22:46:59 +00:00
|
|
|
const St = imports.gi.St;
|
2009-06-30 20:35:39 +00:00
|
|
|
const Mainloop = imports.mainloop;
|
2012-02-18 01:40:40 +00:00
|
|
|
const Atk = imports.gi.Atk;
|
2008-11-21 00:53:11 +00:00
|
|
|
|
2009-10-15 23:28:29 +00:00
|
|
|
const AppFavorites = imports.ui.appFavorites;
|
2013-01-31 16:13:37 +00:00
|
|
|
const BoxPointer = imports.ui.boxpointer;
|
2009-06-30 20:35:39 +00:00
|
|
|
const DND = imports.ui.dnd;
|
2010-07-21 02:22:19 +00:00
|
|
|
const IconGrid = imports.ui.iconGrid;
|
2009-07-31 21:20:26 +00:00
|
|
|
const Main = imports.ui.main;
|
2010-06-06 18:20:00 +00:00
|
|
|
const Overview = imports.ui.overview;
|
2013-02-18 19:18:08 +00:00
|
|
|
const OverviewControls = imports.ui.overviewControls;
|
2010-05-20 15:18:46 +00:00
|
|
|
const PopupMenu = imports.ui.popupMenu;
|
2010-06-06 18:20:00 +00:00
|
|
|
const Tweener = imports.ui.tweener;
|
2010-01-22 02:33:48 +00:00
|
|
|
const Workspace = imports.ui.workspace;
|
2010-06-22 16:39:14 +00:00
|
|
|
const Params = imports.misc.params;
|
2012-06-11 17:53:32 +00:00
|
|
|
const Util = imports.misc.util;
|
2009-04-01 19:51:17 +00:00
|
|
|
|
2011-04-25 19:42:03 +00:00
|
|
|
const MAX_APPLICATION_WORK_MILLIS = 75;
|
2010-03-10 13:52:28 +00:00
|
|
|
const MENU_POPUP_TIMEOUT = 600;
|
2013-02-15 15:47:39 +00:00
|
|
|
const MAX_COLUMNS = 6;
|
2013-08-27 19:23:13 +00:00
|
|
|
const MIN_COLUMNS = 4;
|
|
|
|
const MIN_ROWS = 4;
|
2009-04-23 14:41:24 +00:00
|
|
|
|
2013-02-18 20:04:51 +00:00
|
|
|
const INACTIVE_GRID_OPACITY = 77;
|
2013-07-09 13:11:03 +00:00
|
|
|
const INACTIVE_GRID_OPACITY_ANIMATION_TIME = 0.40;
|
2013-01-31 16:13:37 +00:00
|
|
|
const FOLDER_SUBICON_FRACTION = .4;
|
|
|
|
|
2013-05-03 16:20:21 +00:00
|
|
|
const MIN_FREQUENT_APPS_COUNT = 3;
|
|
|
|
|
2013-09-05 12:48:03 +00:00
|
|
|
const INDICATORS_BASE_TIME = 0.25;
|
|
|
|
const INDICATORS_ANIMATION_DELAY = 0.125;
|
|
|
|
const INDICATORS_ANIMATION_MAX_TIME = 0.75;
|
2013-08-12 14:36:45 +00:00
|
|
|
// Fraction of page height the finger or mouse must reach
|
|
|
|
// to change page
|
|
|
|
const PAGE_SWITCH_TRESHOLD = 0.2;
|
2013-08-27 19:22:16 +00:00
|
|
|
const PAGE_SWITCH_TIME = 0.3;
|
2013-01-31 16:13:37 +00:00
|
|
|
|
2014-01-28 17:09:35 +00:00
|
|
|
function _getCategories(info) {
|
|
|
|
let categoriesStr = info.get_categories();
|
|
|
|
if (!categoriesStr)
|
|
|
|
return [];
|
|
|
|
return categoriesStr.split(';');
|
|
|
|
}
|
|
|
|
|
2014-01-09 14:36:41 +00:00
|
|
|
function _isTerminal(app) {
|
2014-01-18 13:38:41 +00:00
|
|
|
let info = app.get_app_info();
|
|
|
|
if (!info)
|
|
|
|
return false;
|
2014-01-28 17:09:35 +00:00
|
|
|
let categories = _getCategories(info);
|
2014-01-13 22:02:06 +00:00
|
|
|
return categories.indexOf('TerminalEmulator') > -1;
|
2014-01-13 16:39:30 +00:00
|
|
|
}
|
2014-01-09 14:36:41 +00:00
|
|
|
|
2014-01-28 17:06:11 +00:00
|
|
|
function _listsIntersect(a, b) {
|
|
|
|
for (let itemA of a)
|
|
|
|
if (b.indexOf(itemA) >= 0)
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-12-16 03:24:25 +00:00
|
|
|
function _getFolderName(folder) {
|
|
|
|
let name = folder.get_string('name');
|
|
|
|
|
|
|
|
if (folder.get_boolean('translate')) {
|
|
|
|
let keyfile = new GLib.KeyFile();
|
|
|
|
let path = 'desktop-directories/' + name;
|
|
|
|
|
|
|
|
try {
|
|
|
|
keyfile.load_from_data_dirs(path, GLib.KeyFileFlags.NONE);
|
|
|
|
name = keyfile.get_locale_string('Desktop Entry', 'Name', null);
|
|
|
|
} catch(e) {
|
|
|
|
return name;
|
2013-01-31 14:16:25 +00:00
|
|
|
}
|
|
|
|
}
|
2013-12-16 03:24:25 +00:00
|
|
|
|
|
|
|
return name;
|
2014-01-13 16:39:30 +00:00
|
|
|
}
|
2013-01-31 14:16:25 +00:00
|
|
|
|
2013-08-28 17:26:21 +00:00
|
|
|
const BaseAppView = new Lang.Class({
|
|
|
|
Name: 'BaseAppView',
|
2013-02-19 22:23:41 +00:00
|
|
|
Abstract: true,
|
2008-11-21 00:53:11 +00:00
|
|
|
|
2013-08-27 19:22:16 +00:00
|
|
|
_init: function(params, gridParams) {
|
|
|
|
gridParams = Params.parse(gridParams, { xAlign: St.Align.MIDDLE,
|
|
|
|
columnLimit: MAX_COLUMNS,
|
2013-08-27 19:23:13 +00:00
|
|
|
minRows: MIN_ROWS,
|
|
|
|
minColumns: MIN_COLUMNS,
|
2013-08-23 09:14:21 +00:00
|
|
|
fillParent: false,
|
|
|
|
padWithSpacing: true });
|
2013-08-27 19:22:16 +00:00
|
|
|
params = Params.parse(params, { usePagination: false });
|
|
|
|
|
|
|
|
if(params.usePagination)
|
|
|
|
this._grid = new IconGrid.PaginatedIconGrid(gridParams);
|
|
|
|
else
|
|
|
|
this._grid = new IconGrid.IconGrid(gridParams);
|
2010-12-18 19:18:10 +00:00
|
|
|
|
2013-02-19 23:38:11 +00:00
|
|
|
// Standard hack for ClutterBinLayout
|
|
|
|
this._grid.actor.x_expand = true;
|
|
|
|
|
2013-02-19 22:23:41 +00:00
|
|
|
this._items = {};
|
|
|
|
this._allItems = [];
|
|
|
|
},
|
|
|
|
|
|
|
|
removeAll: function() {
|
2013-02-08 23:59:15 +00:00
|
|
|
this._grid.destroyAll();
|
2013-02-19 22:23:41 +00:00
|
|
|
this._items = {};
|
|
|
|
this._allItems = [];
|
|
|
|
},
|
|
|
|
|
2014-01-28 16:11:10 +00:00
|
|
|
_redisplay: function() {
|
|
|
|
this.removeAll();
|
|
|
|
this._loadApps();
|
|
|
|
},
|
|
|
|
|
2014-01-28 16:36:57 +00:00
|
|
|
getAllItems: function() {
|
|
|
|
return this._allItems;
|
|
|
|
},
|
|
|
|
|
2013-12-16 03:24:25 +00:00
|
|
|
addItem: function(icon) {
|
2013-12-16 03:18:17 +00:00
|
|
|
let id = icon.id;
|
2013-02-19 22:23:41 +00:00
|
|
|
if (this._items[id] !== undefined)
|
2013-12-16 03:18:17 +00:00
|
|
|
return;
|
2013-02-19 22:23:41 +00:00
|
|
|
|
2013-12-16 03:18:17 +00:00
|
|
|
this._allItems.push(icon);
|
|
|
|
this._items[id] = icon;
|
2013-02-19 20:31:05 +00:00
|
|
|
},
|
|
|
|
|
2014-01-28 16:36:57 +00:00
|
|
|
_compareItems: function(a, b) {
|
|
|
|
return a.name.localeCompare(b.name);
|
|
|
|
},
|
|
|
|
|
2013-02-19 20:31:05 +00:00
|
|
|
loadGrid: function() {
|
2014-01-28 16:36:57 +00:00
|
|
|
this._allItems.sort(this._compareItems);
|
2013-12-16 03:18:17 +00:00
|
|
|
this._allItems.forEach(Lang.bind(this, function(item) {
|
|
|
|
this._grid.addItem(item);
|
|
|
|
}));
|
2011-07-09 13:30:42 +00:00
|
|
|
this.emit('view-loaded');
|
|
|
|
},
|
|
|
|
|
|
|
|
_selectAppInternal: function(id) {
|
|
|
|
if (this._items[id])
|
|
|
|
this._items[id].actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
|
|
|
else
|
|
|
|
log('No such application ' + id);
|
|
|
|
},
|
|
|
|
|
|
|
|
selectApp: function(id) {
|
|
|
|
if (this._items[id] && this._items[id].actor.mapped) {
|
|
|
|
this._selectAppInternal(id);
|
|
|
|
} else if (this._items[id]) {
|
|
|
|
// Need to wait until the view is mapped
|
|
|
|
let signalId = this._items[id].actor.connect('notify::mapped', Lang.bind(this, function(actor) {
|
|
|
|
if (actor.mapped) {
|
|
|
|
actor.disconnect(signalId);
|
|
|
|
this._selectAppInternal(id);
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
// Need to wait until the view is built
|
|
|
|
let signalId = this.connect('view-loaded', Lang.bind(this, function() {
|
|
|
|
this.disconnect(signalId);
|
|
|
|
this.selectApp(id);
|
|
|
|
}));
|
|
|
|
}
|
2013-02-19 22:23:41 +00:00
|
|
|
}
|
|
|
|
});
|
2013-08-28 17:26:21 +00:00
|
|
|
Signals.addSignalMethods(BaseAppView.prototype);
|
2013-02-19 22:23:41 +00:00
|
|
|
|
2013-03-14 18:08:46 +00:00
|
|
|
|
2013-08-12 17:09:43 +00:00
|
|
|
const PageIndicators = new Lang.Class({
|
|
|
|
Name:'PageIndicators',
|
|
|
|
|
|
|
|
_init: function() {
|
|
|
|
this.actor = new St.BoxLayout({ style_class: 'page-indicators',
|
|
|
|
vertical: true,
|
|
|
|
x_expand: true, y_expand: true,
|
|
|
|
x_align: Clutter.ActorAlign.END,
|
2013-09-06 07:30:51 +00:00
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
reactive: true });
|
2013-08-12 17:09:43 +00:00
|
|
|
this._nPages = 0;
|
|
|
|
this._currentPage = undefined;
|
|
|
|
|
|
|
|
this.actor.connect('notify::mapped',
|
|
|
|
Lang.bind(this, this._animateIndicators));
|
|
|
|
},
|
|
|
|
|
|
|
|
setNPages: function(nPages) {
|
|
|
|
if (this._nPages == nPages)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let diff = nPages - this._nPages;
|
|
|
|
if (diff > 0) {
|
|
|
|
for (let i = 0; i < diff; i++) {
|
|
|
|
let pageIndex = this._nPages + i;
|
|
|
|
let indicator = new St.Button({ style_class: 'page-indicator',
|
|
|
|
button_mask: St.ButtonMask.ONE |
|
|
|
|
St.ButtonMask.TWO |
|
|
|
|
St.ButtonMask.THREE,
|
|
|
|
toggle_mode: true,
|
|
|
|
checked: pageIndex == this._currentPage });
|
2013-09-02 20:33:40 +00:00
|
|
|
indicator.child = new St.Widget({ style_class: 'page-indicator-icon' });
|
2013-08-12 17:09:43 +00:00
|
|
|
indicator.connect('clicked', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
this.emit('page-activated', pageIndex);
|
|
|
|
}));
|
|
|
|
this.actor.add_actor(indicator);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let children = this.actor.get_children().splice(diff);
|
|
|
|
for (let i = 0; i < children.length; i++)
|
|
|
|
children[i].destroy();
|
|
|
|
}
|
|
|
|
this._nPages = nPages;
|
2013-09-03 10:07:28 +00:00
|
|
|
this.actor.visible = (this._nPages > 1);
|
2013-08-12 17:09:43 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
setCurrentPage: function(currentPage) {
|
|
|
|
this._currentPage = currentPage;
|
|
|
|
|
|
|
|
let children = this.actor.get_children();
|
|
|
|
for (let i = 0; i < children.length; i++)
|
|
|
|
children[i].set_checked(i == this._currentPage);
|
|
|
|
},
|
|
|
|
|
|
|
|
_animateIndicators: function() {
|
|
|
|
if (!this.actor.mapped)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let children = this.actor.get_children();
|
|
|
|
if (children.length == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let offset;
|
2013-09-05 22:39:07 +00:00
|
|
|
if (this.actor.get_text_direction() == Clutter.TextDirection.RTL)
|
|
|
|
offset = -children[0].width;
|
2013-08-12 17:09:43 +00:00
|
|
|
else
|
2013-09-05 22:39:07 +00:00
|
|
|
offset = children[0].width;
|
2013-08-12 17:09:43 +00:00
|
|
|
|
2013-09-05 12:48:03 +00:00
|
|
|
let delay = INDICATORS_ANIMATION_DELAY;
|
|
|
|
let totalAnimationTime = INDICATORS_BASE_TIME + INDICATORS_ANIMATION_DELAY * this._nPages;
|
|
|
|
if (totalAnimationTime > INDICATORS_ANIMATION_MAX_TIME)
|
|
|
|
delay -= (totalAnimationTime - INDICATORS_ANIMATION_MAX_TIME) / this._nPages;
|
|
|
|
|
2013-08-12 17:09:43 +00:00
|
|
|
for (let i = 0; i < this._nPages; i++) {
|
|
|
|
children[i].translation_x = offset;
|
|
|
|
Tweener.addTween(children[i],
|
|
|
|
{ translation_x: 0,
|
2013-09-05 12:48:03 +00:00
|
|
|
time: INDICATORS_BASE_TIME + delay * i,
|
|
|
|
transition: 'easeInOutQuad'
|
2013-08-12 17:09:43 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Signals.addSignalMethods(PageIndicators.prototype);
|
|
|
|
|
2013-02-19 22:23:41 +00:00
|
|
|
const AllView = new Lang.Class({
|
|
|
|
Name: 'AllView',
|
2013-08-28 17:26:21 +00:00
|
|
|
Extends: BaseAppView,
|
2013-02-19 22:23:41 +00:00
|
|
|
|
|
|
|
_init: function() {
|
2013-08-27 19:22:16 +00:00
|
|
|
this.parent({ usePagination: true }, null);
|
2013-09-03 16:59:06 +00:00
|
|
|
this._scrollView = new St.ScrollView({ style_class: 'all-apps',
|
|
|
|
x_expand: true,
|
|
|
|
y_expand: true,
|
|
|
|
x_fill: true,
|
|
|
|
y_fill: false,
|
|
|
|
reactive: true,
|
|
|
|
y_align: St.Align.START });
|
2013-08-27 19:22:16 +00:00
|
|
|
this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout(),
|
|
|
|
x_expand:true, y_expand:true });
|
2013-09-03 16:59:06 +00:00
|
|
|
this.actor.add_actor(this._scrollView);
|
|
|
|
|
|
|
|
this._scrollView.set_policy(Gtk.PolicyType.NEVER,
|
|
|
|
Gtk.PolicyType.AUTOMATIC);
|
|
|
|
// we are only using ScrollView for the fade effect, hide scrollbars
|
|
|
|
this._scrollView.vscroll.hide();
|
|
|
|
this._adjustment = this._scrollView.vscroll.adjustment;
|
2013-08-27 19:22:16 +00:00
|
|
|
|
2013-08-12 17:09:43 +00:00
|
|
|
this._pageIndicators = new PageIndicators();
|
|
|
|
this._pageIndicators.connect('page-activated', Lang.bind(this,
|
|
|
|
function(indicators, pageIndex) {
|
|
|
|
this.goToPage(pageIndex);
|
|
|
|
}));
|
2013-09-06 07:30:51 +00:00
|
|
|
this._pageIndicators.actor.connect('scroll-event', Lang.bind(this, this._onScroll));
|
2013-08-12 17:09:43 +00:00
|
|
|
this.actor.add_actor(this._pageIndicators.actor);
|
|
|
|
|
2013-12-16 03:24:25 +00:00
|
|
|
this.folderIcons = [];
|
2013-08-30 16:50:35 +00:00
|
|
|
|
2013-08-27 19:22:16 +00:00
|
|
|
this._stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
|
2010-12-18 19:18:10 +00:00
|
|
|
let box = new St.BoxLayout({ vertical: true });
|
2013-08-27 19:22:16 +00:00
|
|
|
|
|
|
|
this._currentPage = 0;
|
2013-02-18 19:19:18 +00:00
|
|
|
this._stack.add_actor(this._grid.actor);
|
2013-02-18 20:04:51 +00:00
|
|
|
this._eventBlocker = new St.Widget({ x_expand: true, y_expand: true });
|
|
|
|
this._stack.add_actor(this._eventBlocker);
|
2013-08-27 19:22:16 +00:00
|
|
|
|
|
|
|
box.add_actor(this._stack);
|
2013-09-03 16:59:06 +00:00
|
|
|
this._scrollView.add_actor(box);
|
2013-08-27 19:22:16 +00:00
|
|
|
|
2013-09-03 16:59:06 +00:00
|
|
|
this._scrollView.connect('scroll-event', Lang.bind(this, this._onScroll));
|
2013-02-18 20:04:51 +00:00
|
|
|
|
2013-08-12 14:36:45 +00:00
|
|
|
let panAction = new Clutter.PanAction({ interpolate: false });
|
|
|
|
panAction.connect('pan', Lang.bind(this, this._onPan));
|
|
|
|
panAction.connect('gesture-cancel', Lang.bind(this, this._onPanEnd));
|
|
|
|
panAction.connect('gesture-end', Lang.bind(this, this._onPanEnd));
|
|
|
|
this._panAction = panAction;
|
2013-09-03 16:59:06 +00:00
|
|
|
this._scrollView.add_action(panAction);
|
2013-08-12 14:36:45 +00:00
|
|
|
this._panning = false;
|
2013-02-18 20:04:51 +00:00
|
|
|
this._clickAction = new Clutter.ClickAction();
|
|
|
|
this._clickAction.connect('clicked', Lang.bind(this, function() {
|
|
|
|
if (!this._currentPopup)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let [x, y] = this._clickAction.get_coords();
|
|
|
|
let actor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
|
|
|
|
if (!this._currentPopup.actor.contains(actor))
|
|
|
|
this._currentPopup.popdown();
|
|
|
|
}));
|
|
|
|
this._eventBlocker.add_action(this._clickAction);
|
2013-08-27 19:22:16 +00:00
|
|
|
|
2013-07-09 13:11:03 +00:00
|
|
|
this._displayingPopup = false;
|
|
|
|
|
2013-08-27 19:22:16 +00:00
|
|
|
this._availWidth = 0;
|
|
|
|
this._availHeight = 0;
|
2013-08-19 12:02:02 +00:00
|
|
|
|
|
|
|
Main.overview.connect('hidden', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
this.goToPage(0);
|
|
|
|
}));
|
2013-07-09 13:11:03 +00:00
|
|
|
this._grid.connect('space-opened', Lang.bind(this,
|
|
|
|
function() {
|
2013-09-03 16:59:06 +00:00
|
|
|
this._scrollView.get_effect('fade').enabled = false;
|
2013-07-09 13:11:03 +00:00
|
|
|
this.emit('space-ready');
|
|
|
|
}));
|
|
|
|
this._grid.connect('space-closed', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
this._displayingPopup = false;
|
|
|
|
}));
|
2013-09-12 15:20:07 +00:00
|
|
|
|
|
|
|
this.actor.connect('notify::mapped', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
if (this.actor.mapped) {
|
|
|
|
this._keyPressEventId =
|
|
|
|
global.stage.connect('key-press-event',
|
|
|
|
Lang.bind(this, this._onKeyPressEvent));
|
|
|
|
} else {
|
|
|
|
if (this._keyPressEventId)
|
|
|
|
global.stage.disconnect(this._keyPressEventId);
|
|
|
|
this._keyPressEventId = 0;
|
|
|
|
}
|
|
|
|
}));
|
2014-01-28 16:11:10 +00:00
|
|
|
|
|
|
|
this._redisplayWorkId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay));
|
|
|
|
|
|
|
|
Shell.AppSystem.get_default().connect('installed-changed', Lang.bind(this, function() {
|
|
|
|
Main.queueDeferredWork(this._redisplayWorkId);
|
|
|
|
}));
|
|
|
|
this._folderSettings = new Gio.Settings({ schema: 'org.gnome.desktop.app-folders' });
|
|
|
|
this._folderSettings.connect('changed::folder-children', Lang.bind(this, function() {
|
|
|
|
Main.queueDeferredWork(this._redisplayWorkId);
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
|
|
|
removeAll: function() {
|
|
|
|
this.folderIcons = [];
|
|
|
|
this.parent();
|
|
|
|
},
|
|
|
|
|
2014-01-28 16:36:57 +00:00
|
|
|
_itemNameChanged: function(item) {
|
|
|
|
// 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.
|
|
|
|
let oldIdx = this._allItems.indexOf(item);
|
|
|
|
this._allItems.splice(oldIdx, 1);
|
|
|
|
let newIdx = Util.insertSorted(this._allItems, item, this._compareItems);
|
|
|
|
|
|
|
|
this._grid.removeItem(item);
|
|
|
|
this._grid.addItem(item, newIdx);
|
|
|
|
},
|
|
|
|
|
|
|
|
_refilterApps: function() {
|
|
|
|
this._allItems.forEach(function(icon) {
|
|
|
|
if (icon instanceof AppIcon)
|
|
|
|
icon.actor.visible = true;
|
|
|
|
});
|
|
|
|
|
|
|
|
this.folderIcons.forEach(Lang.bind(this, function(folder) {
|
|
|
|
let folderApps = folder.getAppIds();
|
|
|
|
folderApps.forEach(Lang.bind(this, function(appId) {
|
|
|
|
let appIcon = this._items[appId];
|
|
|
|
appIcon.actor.visible = false;
|
|
|
|
}));
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
2014-01-28 16:11:10 +00:00
|
|
|
_loadApps: function() {
|
|
|
|
let apps = Gio.AppInfo.get_all().filter(function(appInfo) {
|
|
|
|
return appInfo.should_show();
|
|
|
|
}).map(function(app) {
|
|
|
|
return app.get_id();
|
|
|
|
});
|
|
|
|
|
|
|
|
let appSys = Shell.AppSystem.get_default();
|
|
|
|
|
|
|
|
let folders = this._folderSettings.get_strv('folder-children');
|
|
|
|
folders.forEach(Lang.bind(this, function(id) {
|
2014-01-28 16:36:57 +00:00
|
|
|
let path = this._folderSettings.path + 'folders/' + id + '/';
|
|
|
|
let icon = new FolderIcon(id, path, this);
|
|
|
|
icon.connect('name-changed', Lang.bind(this, this._itemNameChanged));
|
|
|
|
icon.connect('apps-changed', Lang.bind(this, this._refilterApps));
|
2014-01-28 16:11:10 +00:00
|
|
|
this.addItem(icon);
|
|
|
|
this.folderIcons.push(icon);
|
|
|
|
}));
|
|
|
|
|
|
|
|
apps.forEach(Lang.bind(this, function(appId) {
|
|
|
|
let app = appSys.lookup_app(appId);
|
|
|
|
let icon = new AppIcon(app);
|
|
|
|
this.addItem(icon);
|
|
|
|
}));
|
|
|
|
|
|
|
|
this.loadGrid();
|
2014-01-28 16:36:57 +00:00
|
|
|
this._refilterApps();
|
2012-11-26 04:33:44 +00:00
|
|
|
},
|
2011-01-21 22:01:07 +00:00
|
|
|
|
2013-08-30 16:50:35 +00:00
|
|
|
getCurrentPageY: function() {
|
|
|
|
return this._grid.getPageY(this._currentPage);
|
|
|
|
},
|
|
|
|
|
2013-08-27 19:22:16 +00:00
|
|
|
goToPage: function(pageNumber) {
|
2013-09-13 16:53:02 +00:00
|
|
|
if(pageNumber < 0 || pageNumber > this._grid.nPages() - 1)
|
|
|
|
return;
|
2013-07-09 13:11:03 +00:00
|
|
|
if (this._currentPage == pageNumber && this._displayingPopup && this._currentPopup)
|
|
|
|
return;
|
|
|
|
if (this._displayingPopup && this._currentPopup)
|
|
|
|
this._currentPopup.popdown();
|
|
|
|
|
2013-08-12 14:36:45 +00:00
|
|
|
let velocity;
|
|
|
|
if (!this._panning)
|
|
|
|
velocity = 0;
|
|
|
|
else
|
|
|
|
velocity = Math.abs(this._panAction.get_velocity(0)[2]);
|
|
|
|
// Tween the change between pages.
|
|
|
|
// If velocity is not specified (i.e. scrolling with mouse wheel),
|
|
|
|
// use the same speed regardless of original position
|
|
|
|
// if velocity is specified, it's in pixels per milliseconds
|
|
|
|
let diffToPage = this._diffToPage(pageNumber);
|
2013-09-03 16:59:06 +00:00
|
|
|
let childBox = this._scrollView.get_allocation_box();
|
2013-08-12 14:36:45 +00:00
|
|
|
let totalHeight = childBox.y2 - childBox.y1;
|
|
|
|
let time;
|
|
|
|
// Only take the velocity into account on page changes, otherwise
|
|
|
|
// return smoothly to the current page using the default velocity
|
|
|
|
if (this._currentPage != pageNumber) {
|
|
|
|
let minVelocity = totalHeight / (PAGE_SWITCH_TIME * 1000);
|
|
|
|
velocity = Math.max(minVelocity, velocity);
|
|
|
|
time = (diffToPage / velocity) / 1000;
|
|
|
|
} else {
|
|
|
|
time = PAGE_SWITCH_TIME * diffToPage / totalHeight;
|
|
|
|
}
|
|
|
|
// When changing more than one page, make sure to not take
|
|
|
|
// longer than PAGE_SWITCH_TIME
|
|
|
|
time = Math.min(time, PAGE_SWITCH_TIME);
|
|
|
|
|
|
|
|
if (pageNumber < this._grid.nPages() && pageNumber >= 0) {
|
|
|
|
this._currentPage = pageNumber;
|
2013-09-03 16:59:06 +00:00
|
|
|
Tweener.addTween(this._adjustment,
|
2013-08-12 14:36:45 +00:00
|
|
|
{ value: this._grid.getPageY(this._currentPage),
|
|
|
|
time: time,
|
|
|
|
transition: 'easeOutQuad' });
|
|
|
|
this._pageIndicators.setCurrentPage(pageNumber);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_diffToPage: function (pageNumber) {
|
2013-09-03 16:59:06 +00:00
|
|
|
let currentScrollPosition = this._adjustment.value;
|
2013-08-12 14:36:45 +00:00
|
|
|
return Math.abs(currentScrollPosition - this._grid.getPageY(pageNumber));
|
2013-08-27 19:22:16 +00:00
|
|
|
},
|
2013-02-18 20:04:51 +00:00
|
|
|
|
2013-07-09 13:11:03 +00:00
|
|
|
openSpaceForPopup: function(item, side, nRows) {
|
|
|
|
this._updateIconOpacities(true);
|
|
|
|
this._displayingPopup = true;
|
|
|
|
this._grid.openExtraSpace(item, side, nRows);
|
|
|
|
},
|
|
|
|
|
|
|
|
_closeSpaceForPopup: function() {
|
|
|
|
this._updateIconOpacities(false);
|
2013-09-03 16:59:06 +00:00
|
|
|
this._scrollView.get_effect('fade').enabled = true;
|
2013-07-09 13:11:03 +00:00
|
|
|
this._grid.closeExtraSpace();
|
|
|
|
},
|
|
|
|
|
2013-08-27 19:22:16 +00:00
|
|
|
_onScroll: function(actor, event) {
|
2013-09-04 11:54:53 +00:00
|
|
|
if (this._displayingPopup)
|
2013-11-29 18:17:34 +00:00
|
|
|
return Clutter.EVENT_STOP;
|
2013-09-13 16:53:02 +00:00
|
|
|
|
2013-08-27 19:22:16 +00:00
|
|
|
let direction = event.get_scroll_direction();
|
2013-09-13 16:53:02 +00:00
|
|
|
if (direction == Clutter.ScrollDirection.UP)
|
|
|
|
this.goToPage(this._currentPage - 1);
|
|
|
|
else if (direction == Clutter.ScrollDirection.DOWN)
|
|
|
|
this.goToPage(this._currentPage + 1);
|
|
|
|
|
2013-11-29 18:17:34 +00:00
|
|
|
return Clutter.EVENT_STOP;
|
2008-12-01 19:51:43 +00:00
|
|
|
},
|
2008-12-20 04:27:57 +00:00
|
|
|
|
2013-08-12 14:36:45 +00:00
|
|
|
_onPan: function(action) {
|
2013-07-09 13:11:03 +00:00
|
|
|
if (this._displayingPopup)
|
|
|
|
return false;
|
2013-08-12 14:36:45 +00:00
|
|
|
this._panning = true;
|
|
|
|
this._clickAction.release();
|
|
|
|
let [dist, dx, dy] = action.get_motion_delta(0);
|
2013-09-03 16:59:06 +00:00
|
|
|
let adjustment = this._adjustment;
|
|
|
|
adjustment.value -= (dy / this._scrollView.height) * adjustment.page_size;
|
2013-08-12 14:36:45 +00:00
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
_onPanEnd: function(action) {
|
2013-07-09 13:11:03 +00:00
|
|
|
if (this._displayingPopup)
|
|
|
|
return;
|
2013-08-12 14:36:45 +00:00
|
|
|
let diffCurrentPage = this._diffToPage(this._currentPage);
|
2013-09-03 16:59:06 +00:00
|
|
|
if (diffCurrentPage > this._scrollView.height * PAGE_SWITCH_TRESHOLD) {
|
2013-09-13 16:53:02 +00:00
|
|
|
if (action.get_velocity(0)[2] > 0)
|
2013-08-12 14:36:45 +00:00
|
|
|
this.goToPage(this._currentPage - 1);
|
2013-09-13 16:53:02 +00:00
|
|
|
else
|
2013-08-12 14:36:45 +00:00
|
|
|
this.goToPage(this._currentPage + 1);
|
|
|
|
} else {
|
|
|
|
this.goToPage(this._currentPage);
|
|
|
|
}
|
|
|
|
this._panning = false;
|
|
|
|
},
|
|
|
|
|
2013-09-12 15:20:07 +00:00
|
|
|
_onKeyPressEvent: function(actor, event) {
|
|
|
|
if (this._displayingPopup)
|
2013-11-29 18:17:34 +00:00
|
|
|
return Clutter.EVENT_STOP;
|
2013-09-12 15:20:07 +00:00
|
|
|
|
|
|
|
if (event.get_key_symbol() == Clutter.Page_Up) {
|
|
|
|
this.goToPage(this._currentPage - 1);
|
2013-11-29 18:17:34 +00:00
|
|
|
return Clutter.EVENT_STOP;
|
2013-09-12 15:20:07 +00:00
|
|
|
} else if (event.get_key_symbol() == Clutter.Page_Down) {
|
|
|
|
this.goToPage(this._currentPage + 1);
|
2013-11-29 18:17:34 +00:00
|
|
|
return Clutter.EVENT_STOP;
|
2013-09-12 15:20:07 +00:00
|
|
|
}
|
|
|
|
|
2013-11-29 18:17:34 +00:00
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2013-09-12 15:20:07 +00:00
|
|
|
},
|
|
|
|
|
2013-02-18 19:19:18 +00:00
|
|
|
addFolderPopup: function(popup) {
|
|
|
|
this._stack.add_actor(popup.actor);
|
2013-01-31 16:13:37 +00:00
|
|
|
popup.connect('open-state-changed', Lang.bind(this,
|
|
|
|
function(popup, isOpen) {
|
2013-02-18 20:04:51 +00:00
|
|
|
this._eventBlocker.reactive = isOpen;
|
|
|
|
this._currentPopup = isOpen ? popup : null;
|
|
|
|
this._updateIconOpacities(isOpen);
|
2013-07-09 13:11:03 +00:00
|
|
|
if(!isOpen)
|
|
|
|
this._closeSpaceForPopup();
|
2013-01-31 16:13:37 +00:00
|
|
|
}));
|
2013-02-18 19:19:18 +00:00
|
|
|
},
|
|
|
|
|
2011-02-23 19:21:47 +00:00
|
|
|
_ensureIconVisible: function(icon) {
|
2013-08-27 19:22:16 +00:00
|
|
|
let itemPage = this._grid.getItemPage(icon);
|
|
|
|
this.goToPage(itemPage);
|
|
|
|
},
|
|
|
|
|
2013-02-18 20:04:51 +00:00
|
|
|
_updateIconOpacities: function(folderOpen) {
|
|
|
|
for (let id in this._items) {
|
2013-08-22 15:31:41 +00:00
|
|
|
let params, opacity;
|
2013-02-18 20:04:51 +00:00
|
|
|
if (folderOpen && !this._items[id].actor.checked)
|
2013-08-22 15:31:41 +00:00
|
|
|
opacity = INACTIVE_GRID_OPACITY;
|
2013-02-18 20:04:51 +00:00
|
|
|
else
|
2013-08-22 15:31:41 +00:00
|
|
|
opacity = 255;
|
|
|
|
params = { opacity: opacity,
|
|
|
|
time: INACTIVE_GRID_OPACITY_ANIMATION_TIME,
|
|
|
|
transition: 'easeOutQuad' };
|
|
|
|
Tweener.addTween(this._items[id].actor, params);
|
2013-02-18 20:04:51 +00:00
|
|
|
}
|
2013-08-27 19:22:16 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
// Called before allocation to calculate dynamic spacing
|
|
|
|
adaptToSize: function(width, height) {
|
|
|
|
let box = new Clutter.ActorBox();
|
|
|
|
box.x1 = 0;
|
|
|
|
box.x2 = width;
|
|
|
|
box.y1 = 0;
|
|
|
|
box.y2 = height;
|
|
|
|
box = this.actor.get_theme_node().get_content_box(box);
|
2013-09-03 16:59:06 +00:00
|
|
|
box = this._scrollView.get_theme_node().get_content_box(box);
|
2013-08-27 19:22:16 +00:00
|
|
|
box = this._grid.actor.get_theme_node().get_content_box(box);
|
|
|
|
let availWidth = box.x2 - box.x1;
|
|
|
|
let availHeight = box.y2 - box.y1;
|
|
|
|
let oldNPages = this._grid.nPages();
|
|
|
|
|
2013-08-15 08:38:17 +00:00
|
|
|
this._grid.adaptToSize(availWidth, availHeight);
|
|
|
|
|
2013-09-03 16:59:06 +00:00
|
|
|
let fadeOffset = Math.min(this._grid.topPadding,
|
|
|
|
this._grid.bottomPadding);
|
|
|
|
this._scrollView.update_fade_effect(fadeOffset, 0);
|
|
|
|
this._scrollView.get_effect('fade').fade_edges = true;
|
|
|
|
|
2013-08-12 17:09:43 +00:00
|
|
|
if (this._availWidth != availWidth || this._availHeight != availHeight || oldNPages != this._grid.nPages()) {
|
2013-09-03 16:59:06 +00:00
|
|
|
this._adjustment.value = 0;
|
2013-08-12 17:09:43 +00:00
|
|
|
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
this._pageIndicators.setNPages(this._grid.nPages());
|
|
|
|
this._pageIndicators.setCurrentPage(0);
|
|
|
|
}));
|
|
|
|
}
|
2013-08-27 19:22:16 +00:00
|
|
|
|
|
|
|
this._availWidth = availWidth;
|
|
|
|
this._availHeight = availHeight;
|
2013-08-30 16:50:35 +00:00
|
|
|
// Update folder views
|
2013-12-16 03:24:25 +00:00
|
|
|
for (let i = 0; i < this.folderIcons.length; i++)
|
|
|
|
this.folderIcons[i].adaptToSize(availWidth, availHeight);
|
2009-03-20 16:06:34 +00:00
|
|
|
}
|
2011-11-20 17:56:27 +00:00
|
|
|
});
|
2013-07-09 13:11:03 +00:00
|
|
|
Signals.addSignalMethods(AllView.prototype);
|
2008-11-21 00:53:11 +00:00
|
|
|
|
2013-02-18 19:18:08 +00:00
|
|
|
const FrequentView = new Lang.Class({
|
|
|
|
Name: 'FrequentView',
|
2013-08-27 19:22:16 +00:00
|
|
|
Extends: BaseAppView,
|
2013-02-18 19:18:08 +00:00
|
|
|
|
|
|
|
_init: function() {
|
2013-08-27 19:22:16 +00:00
|
|
|
this.parent(null, { fillParent: true });
|
2013-02-22 03:29:57 +00:00
|
|
|
this.actor = new St.Widget({ style_class: 'frequent-apps',
|
2013-05-03 16:20:21 +00:00
|
|
|
layout_manager: new Clutter.BinLayout(),
|
2013-02-22 03:29:57 +00:00
|
|
|
x_expand: true, y_expand: true });
|
2013-05-03 16:20:21 +00: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 });
|
|
|
|
|
|
|
|
this._grid.actor.y_expand = true;
|
|
|
|
|
2013-02-22 03:29:57 +00:00
|
|
|
this.actor.add_actor(this._grid.actor);
|
2013-05-03 16:20:21 +00:00
|
|
|
this.actor.add_actor(this._noFrequentAppsLabel);
|
|
|
|
this._noFrequentAppsLabel.hide();
|
2013-02-18 19:18:08 +00:00
|
|
|
|
|
|
|
this._usage = Shell.AppUsage.get_default();
|
2014-01-28 16:06:21 +00:00
|
|
|
|
|
|
|
this.actor.connect('notify::mapped', Lang.bind(this, function() {
|
|
|
|
if (this.actor.mapped)
|
|
|
|
this._redisplay();
|
|
|
|
}));
|
2013-02-18 19:18:08 +00:00
|
|
|
},
|
|
|
|
|
2013-08-30 18:25:19 +00:00
|
|
|
hasUsefulData: function() {
|
|
|
|
return this._usage.get_most_used("").length >= MIN_FREQUENT_APPS_COUNT;
|
|
|
|
},
|
|
|
|
|
2014-01-28 16:06:21 +00:00
|
|
|
_loadApps: function() {
|
2013-02-18 19:18:08 +00:00
|
|
|
let mostUsed = this._usage.get_most_used ("");
|
2013-08-30 18:25:19 +00:00
|
|
|
let hasUsefulData = this.hasUsefulData();
|
2013-05-03 16:20:21 +00:00
|
|
|
this._noFrequentAppsLabel.visible = !hasUsefulData;
|
|
|
|
if(!hasUsefulData)
|
|
|
|
return;
|
|
|
|
|
2013-02-18 19:18:08 +00:00
|
|
|
for (let i = 0; i < mostUsed.length; i++) {
|
2013-03-31 18:05:49 +00:00
|
|
|
if (!mostUsed[i].get_app_info().should_show())
|
|
|
|
continue;
|
2013-02-18 19:18:08 +00:00
|
|
|
let appIcon = new AppIcon(mostUsed[i]);
|
2013-08-29 21:25:13 +00:00
|
|
|
this._grid.addItem(appIcon, -1);
|
2013-02-18 19:18:08 +00:00
|
|
|
}
|
2013-08-27 19:22:16 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
// Called before allocation to calculate dynamic spacing
|
|
|
|
adaptToSize: function(width, height) {
|
|
|
|
let box = new Clutter.ActorBox();
|
|
|
|
box.x1 = box.y1 = 0;
|
|
|
|
box.x2 = width;
|
|
|
|
box.y2 = height;
|
|
|
|
box = this.actor.get_theme_node().get_content_box(box);
|
|
|
|
box = this._grid.actor.get_theme_node().get_content_box(box);
|
|
|
|
let availWidth = box.x2 - box.x1;
|
|
|
|
let availHeight = box.y2 - box.y1;
|
2013-08-15 08:38:17 +00:00
|
|
|
this._grid.adaptToSize(availWidth, availHeight);
|
2013-02-18 19:18:08 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const Views = {
|
|
|
|
FREQUENT: 0,
|
|
|
|
ALL: 1
|
|
|
|
};
|
|
|
|
|
2013-05-24 10:03:00 +00:00
|
|
|
const ControlsBoxLayout = Lang.Class({
|
|
|
|
Name: 'ControlsBoxLayout',
|
|
|
|
Extends: Clutter.BoxLayout,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Override the BoxLayout behavior to use the maximum preferred width of all
|
|
|
|
* buttons for each child
|
|
|
|
*/
|
|
|
|
vfunc_get_preferred_width: function(container, forHeight) {
|
|
|
|
let maxMinWidth = 0;
|
|
|
|
let maxNaturalWidth = 0;
|
|
|
|
for (let child = container.get_first_child();
|
|
|
|
child;
|
|
|
|
child = child.get_next_sibling()) {
|
|
|
|
let [minWidth, natWidth] = child.get_preferred_width(forHeight);
|
|
|
|
maxMinWidth = Math.max(maxMinWidth, minWidth);
|
|
|
|
maxNaturalWidth = Math.max(maxNaturalWidth, natWidth);
|
|
|
|
}
|
|
|
|
let childrenCount = container.get_n_children();
|
|
|
|
let totalSpacing = this.spacing * (childrenCount - 1);
|
|
|
|
return [maxMinWidth * childrenCount + totalSpacing,
|
|
|
|
maxNaturalWidth * childrenCount + totalSpacing];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2013-08-27 19:22:16 +00:00
|
|
|
const ViewStackLayout = new Lang.Class({
|
|
|
|
Name: 'ViewStackLayout',
|
|
|
|
Extends: Clutter.BinLayout,
|
|
|
|
|
|
|
|
vfunc_allocate: function (actor, box, flags) {
|
|
|
|
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);
|
|
|
|
this.parent(actor, box, flags);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Signals.addSignalMethods(ViewStackLayout.prototype);
|
|
|
|
|
2013-01-30 22:58:42 +00:00
|
|
|
const AppDisplay = new Lang.Class({
|
|
|
|
Name: 'AppDisplay',
|
2010-06-04 22:01:32 +00:00
|
|
|
|
|
|
|
_init: function() {
|
2013-03-01 23:56:18 +00:00
|
|
|
this._privacySettings = new Gio.Settings({ schema: 'org.gnome.desktop.privacy' });
|
|
|
|
this._privacySettings.connect('changed::remember-app-usage',
|
|
|
|
Lang.bind(this, this._updateFrequentVisibility));
|
2013-01-30 22:58:42 +00:00
|
|
|
|
2013-02-18 19:18:08 +00:00
|
|
|
this._views = [];
|
|
|
|
|
|
|
|
let view, button;
|
|
|
|
view = new FrequentView();
|
|
|
|
button = new St.Button({ label: _("Frequent"),
|
|
|
|
style_class: 'app-view-control',
|
|
|
|
can_focus: true,
|
|
|
|
x_expand: true });
|
|
|
|
this._views[Views.FREQUENT] = { 'view': view, 'control': button };
|
|
|
|
|
|
|
|
view = new AllView();
|
|
|
|
button = new St.Button({ label: _("All"),
|
|
|
|
style_class: 'app-view-control',
|
|
|
|
can_focus: true,
|
|
|
|
x_expand: true });
|
|
|
|
this._views[Views.ALL] = { 'view': view, 'control': button };
|
|
|
|
|
2013-08-27 19:22:16 +00:00
|
|
|
this.actor = new St.BoxLayout ({ style_class: 'app-display',
|
|
|
|
x_expand: true, y_expand: true,
|
|
|
|
vertical: true });
|
|
|
|
this._viewStackLayout = new ViewStackLayout();
|
|
|
|
this._viewStack = new St.Widget({ x_expand: true, y_expand: true,
|
|
|
|
layout_manager: this._viewStackLayout });
|
|
|
|
this._viewStackLayout.connect('allocated-size-changed', Lang.bind(this, this._onAllocatedSizeChanged));
|
|
|
|
this.actor.add_actor(this._viewStack, { expand: true });
|
2013-05-24 10:03:00 +00:00
|
|
|
let layout = new ControlsBoxLayout({ homogeneous: true });
|
2013-07-09 21:15:02 +00:00
|
|
|
this._controls = new St.Widget({ style_class: 'app-view-controls',
|
|
|
|
layout_manager: layout });
|
|
|
|
layout.hookup_style(this._controls);
|
2013-08-27 19:22:16 +00:00
|
|
|
this.actor.add_actor(new St.Bin({ child: this._controls }));
|
2013-02-18 19:18:08 +00:00
|
|
|
|
|
|
|
for (let i = 0; i < this._views.length; i++) {
|
|
|
|
this._viewStack.add_actor(this._views[i].view.actor);
|
|
|
|
this._controls.add_actor(this._views[i].control);
|
|
|
|
|
|
|
|
let viewIndex = i;
|
|
|
|
this._views[i].control.connect('clicked', Lang.bind(this,
|
|
|
|
function(actor) {
|
|
|
|
this._showView(viewIndex);
|
2013-10-13 14:48:12 +00:00
|
|
|
global.settings.set_uint('app-picker-view', viewIndex);
|
2013-02-18 19:18:08 +00:00
|
|
|
}));
|
|
|
|
}
|
2013-10-13 14:48:12 +00:00
|
|
|
let initialView = Math.min(global.settings.get_uint('app-picker-view'),
|
|
|
|
this._views.length - 1);
|
2013-08-30 18:25:19 +00:00
|
|
|
let frequentUseful = this._views[Views.FREQUENT].view.hasUsefulData();
|
2013-10-13 14:48:12 +00:00
|
|
|
if (initialView == Views.FREQUENT && !frequentUseful)
|
|
|
|
initialView = Views.ALL;
|
|
|
|
this._showView(initialView);
|
2013-03-01 23:46:34 +00:00
|
|
|
this._updateFrequentVisibility();
|
2010-06-04 22:01:32 +00:00
|
|
|
},
|
|
|
|
|
2013-02-18 19:18:08 +00:00
|
|
|
_showView: function(activeIndex) {
|
|
|
|
for (let i = 0; i < this._views.length; i++) {
|
|
|
|
let actor = this._views[i].view.actor;
|
|
|
|
let params = { time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
|
|
|
|
opacity: (i == activeIndex) ? 255 : 0 };
|
|
|
|
if (i == activeIndex)
|
|
|
|
actor.visible = true;
|
|
|
|
else
|
|
|
|
params.onComplete = function() { actor.hide(); };
|
|
|
|
Tweener.addTween(actor, params);
|
|
|
|
|
|
|
|
if (i == activeIndex)
|
|
|
|
this._views[i].control.add_style_pseudo_class('checked');
|
|
|
|
else
|
|
|
|
this._views[i].control.remove_style_pseudo_class('checked');
|
|
|
|
}
|
2010-06-04 22:01:32 +00:00
|
|
|
},
|
|
|
|
|
2013-03-01 23:46:34 +00:00
|
|
|
_updateFrequentVisibility: function() {
|
2013-03-01 23:56:18 +00:00
|
|
|
let enabled = this._privacySettings.get_boolean('remember-app-usage');
|
2013-03-01 23:46:34 +00:00
|
|
|
this._views[Views.FREQUENT].control.visible = enabled;
|
|
|
|
|
|
|
|
let visibleViews = this._views.filter(function(v) {
|
|
|
|
return v.control.visible;
|
|
|
|
});
|
|
|
|
this._controls.visible = visibleViews.length > 1;
|
|
|
|
|
|
|
|
if (!enabled && this._views[Views.FREQUENT].view.actor.visible)
|
|
|
|
this._showView(Views.ALL);
|
|
|
|
},
|
|
|
|
|
2011-07-09 13:30:42 +00:00
|
|
|
selectApp: function(id) {
|
|
|
|
this._showView(Views.ALL);
|
|
|
|
this._views[Views.ALL].view.selectApp(id);
|
|
|
|
},
|
2013-08-27 19:22:16 +00:00
|
|
|
|
|
|
|
_onAllocatedSizeChanged: function(actor, width, height) {
|
|
|
|
let box = new Clutter.ActorBox();
|
|
|
|
box.x1 = box.y1 =0;
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
})
|
2010-06-04 22:01:32 +00:00
|
|
|
|
2011-11-20 16:07:14 +00:00
|
|
|
const AppSearchProvider = new Lang.Class({
|
|
|
|
Name: 'AppSearchProvider',
|
2009-11-29 22:45:30 +00: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 17:35:01 +00:00
|
|
|
_init: function() {
|
2009-11-29 22:45:30 +00:00
|
|
|
this._appSys = Shell.AppSystem.get_default();
|
2012-08-09 21:52:36 +00:00
|
|
|
this.id = 'applications';
|
2009-11-29 22:45:30 +00:00
|
|
|
},
|
|
|
|
|
2012-05-02 19:54:25 +00:00
|
|
|
getResultMetas: function(apps, callback) {
|
2012-02-17 15:39:27 +00:00
|
|
|
let metas = [];
|
|
|
|
for (let i = 0; i < apps.length; i++) {
|
2013-02-09 03:08:13 +00:00
|
|
|
let app = this._appSys.lookup_app(apps[i]);
|
|
|
|
metas.push({ 'id': app.get_id(),
|
2012-02-17 15:39:27 +00:00
|
|
|
'name': app.get_name(),
|
|
|
|
'createIcon': function(size) {
|
|
|
|
return app.create_icon_texture(size);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2012-05-02 19:45:37 +00:00
|
|
|
callback(metas);
|
2009-11-29 22:45:30 +00:00
|
|
|
},
|
|
|
|
|
2013-05-29 20:48:30 +00:00
|
|
|
filterResults: function(results, maxNumber) {
|
|
|
|
return results.slice(0, maxNumber);
|
|
|
|
},
|
|
|
|
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
getInitialResultSet: function(terms, callback, cancellable) {
|
2013-11-03 00:13:42 +00:00
|
|
|
let query = terms.join(' ');
|
|
|
|
let groups = Gio.DesktopAppInfo.search(query);
|
|
|
|
let usage = Shell.AppUsage.get_default();
|
|
|
|
let results = [];
|
|
|
|
groups.forEach(function(group) {
|
|
|
|
group = group.filter(function(appID) {
|
|
|
|
let app = Gio.DesktopAppInfo.new(appID);
|
|
|
|
return app && app.should_show();
|
|
|
|
});
|
|
|
|
results = results.concat(group.sort(function(a, b) {
|
|
|
|
return usage.compare('', a, b);
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
callback(results);
|
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 17:35:01 +00:00
|
|
|
},
|
|
|
|
|
2013-11-03 00:09:14 +00:00
|
|
|
getSubsearchResultSet: function(previousResults, terms, callback, cancellable) {
|
2013-11-03 00:13:42 +00:00
|
|
|
this.getInitialResultSet(terms, callback, cancellable);
|
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 17:35:01 +00:00
|
|
|
},
|
|
|
|
|
2013-02-09 03:08:13 +00:00
|
|
|
activateResult: function(result) {
|
|
|
|
let app = this._appSys.lookup_app(result);
|
2011-07-16 19:27:05 +00:00
|
|
|
let event = Clutter.get_current_event();
|
2012-02-28 16:01:48 +00:00
|
|
|
let modifiers = event ? event.get_state() : 0;
|
2014-01-09 14:36:41 +00:00
|
|
|
let openNewWindow = (modifiers & Clutter.ModifierType.CONTROL_MASK) || _isTerminal(app);
|
2011-07-16 19:27:05 +00:00
|
|
|
|
|
|
|
if (openNewWindow)
|
2012-12-05 15:05:48 +00:00
|
|
|
app.open_new_window(-1);
|
2011-07-16 19:27:05 +00:00
|
|
|
else
|
2012-12-05 15:05:48 +00:00
|
|
|
app.activate();
|
2010-02-15 22:11:09 +00:00
|
|
|
},
|
|
|
|
|
2011-01-30 21:09:58 +00:00
|
|
|
dragActivateResult: function(id, params) {
|
2011-08-11 09:35:23 +00:00
|
|
|
params = Params.parse(params, { workspace: -1,
|
|
|
|
timestamp: 0 });
|
2011-01-30 21:09:58 +00: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 17:35:01 +00:00
|
|
|
let app = this._appSys.lookup_app(id);
|
2011-08-11 09:35:23 +00:00
|
|
|
app.open_new_window(workspace);
|
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 17:35:01 +00:00
|
|
|
},
|
|
|
|
|
2013-02-09 00:04:24 +00:00
|
|
|
createResultObject: function (resultMeta) {
|
2013-02-09 03:08:13 +00:00
|
|
|
let app = this._appSys.lookup_app(resultMeta['id']);
|
2013-08-29 21:25:13 +00:00
|
|
|
return new AppIcon(app);
|
2009-11-29 22:45:30 +00:00
|
|
|
}
|
2011-11-20 16:07:14 +00:00
|
|
|
});
|
2009-11-29 22:45:30 +00:00
|
|
|
|
2013-08-20 08:49:02 +00:00
|
|
|
const FolderView = new Lang.Class({
|
|
|
|
Name: 'FolderView',
|
2013-08-28 17:26:21 +00:00
|
|
|
Extends: BaseAppView,
|
2013-08-20 08:49:02 +00:00
|
|
|
|
|
|
|
_init: function() {
|
2013-08-27 19:22:16 +00:00
|
|
|
this.parent(null, null);
|
2013-08-30 16:50:35 +00: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"
|
|
|
|
this._grid.actor.x_expand = true;
|
|
|
|
|
|
|
|
this.actor = new St.ScrollView({ overlay_scrollbars: true });
|
|
|
|
this.actor.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
|
|
|
let scrollableContainer = new St.BoxLayout({ vertical: true, reactive: true });
|
|
|
|
scrollableContainer.add_actor(this._grid.actor);
|
|
|
|
this.actor.add_actor(scrollableContainer);
|
2013-08-23 18:49:27 +00:00
|
|
|
|
|
|
|
let action = new Clutter.PanAction({ interpolate: true });
|
|
|
|
action.connect('pan', Lang.bind(this, this._onPan));
|
|
|
|
this.actor.add_action(action);
|
2013-08-20 08:49:02 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
createFolderIcon: function(size) {
|
|
|
|
let icon = new St.Widget({ layout_manager: new Clutter.BinLayout(),
|
|
|
|
style_class: 'app-folder-icon',
|
|
|
|
width: size, height: size });
|
|
|
|
let subSize = Math.floor(FOLDER_SUBICON_FRACTION * size);
|
|
|
|
|
|
|
|
let aligns = [ Clutter.ActorAlign.START, Clutter.ActorAlign.END ];
|
|
|
|
for (let i = 0; i < Math.min(this._allItems.length, 4); i++) {
|
2013-12-16 03:18:17 +00:00
|
|
|
let texture = this._allItems[i].app.create_icon_texture(subSize);
|
2013-08-20 08:49:02 +00:00
|
|
|
let bin = new St.Bin({ child: texture,
|
|
|
|
x_expand: true, y_expand: true });
|
|
|
|
bin.set_x_align(aligns[i % 2]);
|
|
|
|
bin.set_y_align(aligns[Math.floor(i / 2)]);
|
|
|
|
icon.add_actor(bin);
|
|
|
|
}
|
|
|
|
|
|
|
|
return icon;
|
2013-08-30 16:50:35 +00:00
|
|
|
},
|
|
|
|
|
2013-08-23 18:49:27 +00:00
|
|
|
_onPan: function(action) {
|
|
|
|
let [dist, dx, dy] = action.get_motion_delta(0);
|
|
|
|
let adjustment = this.actor.vscroll.adjustment;
|
|
|
|
adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2013-08-30 16:50:35 +00:00
|
|
|
adaptToSize: function(width, height) {
|
|
|
|
this._parentAvailableWidth = width;
|
|
|
|
this._parentAvailableHeight = height;
|
|
|
|
|
2013-08-15 08:38:17 +00:00
|
|
|
this._grid.adaptToSize(width, height);
|
2013-08-30 16:50:35 +00:00
|
|
|
|
2013-09-06 01:12:28 +00: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);
|
|
|
|
this.actor.update_fade_effect(fadeOffset, 0);
|
|
|
|
|
2013-08-30 16:50:35 +00:00
|
|
|
// Set extra padding to avoid popup or close button being cut off
|
|
|
|
this._grid.topPadding = Math.max(this._grid.topPadding - this._offsetForEachSide, 0);
|
2013-09-06 00:50:09 +00:00
|
|
|
this._grid.bottomPadding = Math.max(this._grid.bottomPadding - this._offsetForEachSide, 0);
|
|
|
|
this._grid.leftPadding = Math.max(this._grid.leftPadding - this._offsetForEachSide, 0);
|
|
|
|
this._grid.rightPadding = Math.max(this._grid.rightPadding - this._offsetForEachSide, 0);
|
2013-08-30 16:50:35 +00:00
|
|
|
|
|
|
|
this.actor.set_width(this.usedWidth());
|
|
|
|
this.actor.set_height(this.usedHeight());
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPageAvailableSize: function() {
|
|
|
|
let pageBox = new Clutter.ActorBox();
|
|
|
|
pageBox.x1 = pageBox.y1 = 0;
|
|
|
|
pageBox.x2 = this._parentAvailableWidth;
|
|
|
|
pageBox.y2 = this._parentAvailableHeight;
|
|
|
|
|
|
|
|
let contentBox = this.actor.get_theme_node().get_content_box(pageBox);
|
|
|
|
// We only can show icons inside the collection view boxPointer
|
|
|
|
// so we have to substract the required padding etc of the boxpointer
|
|
|
|
return [(contentBox.x2 - contentBox.x1) - 2 * this._offsetForEachSide, (contentBox.y2 - contentBox.y1) - 2 * this._offsetForEachSide];
|
|
|
|
},
|
|
|
|
|
|
|
|
usedWidth: function() {
|
|
|
|
let [availWidthPerPage, availHeightPerPage] = this._getPageAvailableSize();
|
|
|
|
return this._grid.usedWidth(availWidthPerPage);
|
|
|
|
},
|
|
|
|
|
|
|
|
usedHeight: function() {
|
|
|
|
return this._grid.usedHeightForNRows(this.nRowsDisplayedAtOnce());
|
|
|
|
},
|
|
|
|
|
|
|
|
nRowsDisplayedAtOnce: function() {
|
|
|
|
let [availWidthPerPage, availHeightPerPage] = this._getPageAvailableSize();
|
|
|
|
let maxRows = this._grid.rowsForHeight(availHeightPerPage) - 1;
|
|
|
|
return Math.min(this._grid.nRows(availWidthPerPage), maxRows);
|
|
|
|
},
|
|
|
|
|
|
|
|
setPaddingOffsets: function(offset) {
|
|
|
|
this._offsetForEachSide = offset;
|
2013-08-20 08:49:02 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2013-01-31 16:13:37 +00:00
|
|
|
const FolderIcon = new Lang.Class({
|
|
|
|
Name: 'FolderIcon',
|
|
|
|
|
2014-01-28 16:36:57 +00:00
|
|
|
_init: function(id, path, parentView) {
|
2013-12-16 03:24:25 +00:00
|
|
|
this.id = id;
|
2013-01-31 16:13:37 +00:00
|
|
|
this._parentView = parentView;
|
|
|
|
|
2014-01-28 16:36:57 +00:00
|
|
|
this._folder = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders.folder',
|
|
|
|
path: path });
|
2013-01-31 16:13:37 +00:00
|
|
|
this.actor = new St.Button({ style_class: 'app-well-app app-folder',
|
|
|
|
button_mask: St.ButtonMask.ONE,
|
|
|
|
toggle_mode: true,
|
|
|
|
can_focus: true,
|
|
|
|
x_fill: true,
|
|
|
|
y_fill: true });
|
|
|
|
this.actor._delegate = this;
|
2013-08-30 16:50:35 +00:00
|
|
|
// whether we need to update arrow side, position etc.
|
|
|
|
this._popupInvalidated = false;
|
2013-01-31 16:13:37 +00:00
|
|
|
|
2014-01-28 16:36:57 +00:00
|
|
|
this.icon = new IconGrid.BaseIcon('', { createIcon: Lang.bind(this, this._createIcon), setSizeManually: true });
|
2013-01-31 16:13:37 +00:00
|
|
|
this.actor.set_child(this.icon.actor);
|
|
|
|
this.actor.label_actor = this.icon.label;
|
|
|
|
|
|
|
|
this.view = new FolderView();
|
|
|
|
|
|
|
|
this.actor.connect('clicked', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
this._ensurePopup();
|
2013-08-19 15:01:45 +00:00
|
|
|
this.view.actor.vscroll.adjustment.value = 0;
|
2013-07-09 13:11:03 +00:00
|
|
|
this._openSpaceForPopup();
|
2013-01-31 16:13:37 +00:00
|
|
|
}));
|
|
|
|
this.actor.connect('notify::mapped', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
if (!this.actor.mapped && this._popup)
|
|
|
|
this._popup.popdown();
|
|
|
|
}));
|
2014-01-28 16:36:57 +00:00
|
|
|
|
|
|
|
this._folder.connect('changed', Lang.bind(this, this._redisplay));
|
|
|
|
this._redisplay();
|
|
|
|
},
|
|
|
|
|
|
|
|
getAppIds: function() {
|
|
|
|
return this.view.getAllItems().map(function(item) {
|
|
|
|
return item.id;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
_updateName: function() {
|
|
|
|
let name = _getFolderName(this._folder);
|
|
|
|
if (this.name == name)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this.name = name;
|
|
|
|
this.icon.label.text = this.name;
|
|
|
|
this.emit('name-changed');
|
|
|
|
},
|
|
|
|
|
|
|
|
_redisplay: function() {
|
|
|
|
this._updateName();
|
|
|
|
|
|
|
|
this.view.removeAll();
|
|
|
|
|
2014-01-28 17:27:22 +00:00
|
|
|
let excludedApps = this._folder.get_strv('excluded-apps');
|
2014-01-28 16:36:57 +00:00
|
|
|
let appSys = Shell.AppSystem.get_default();
|
2014-01-28 17:06:11 +00:00
|
|
|
let addAppId = (function addAppId(appId) {
|
2014-01-28 17:27:22 +00:00
|
|
|
if (excludedApps.indexOf(appId) >= 0)
|
|
|
|
return;
|
|
|
|
|
2014-01-28 16:36:57 +00:00
|
|
|
let app = appSys.lookup_app(appId);
|
|
|
|
if (!app)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!app.get_app_info().should_show())
|
|
|
|
return;
|
|
|
|
|
|
|
|
let icon = new AppIcon(app);
|
|
|
|
this.view.addItem(icon);
|
2014-01-28 17:06:11 +00:00
|
|
|
}).bind(this);
|
|
|
|
|
|
|
|
let folderApps = this._folder.get_strv('apps');
|
|
|
|
folderApps.forEach(addAppId);
|
|
|
|
|
|
|
|
let folderCategories = this._folder.get_strv('categories');
|
|
|
|
Gio.AppInfo.get_all().forEach(function(appInfo) {
|
|
|
|
let appCategories = _getCategories(appInfo);
|
|
|
|
if (!_listsIntersect(folderCategories, appCategories))
|
|
|
|
return;
|
|
|
|
|
|
|
|
addAppId(appInfo.get_id());
|
|
|
|
});
|
|
|
|
|
2014-01-28 16:36:57 +00:00
|
|
|
this.view.loadGrid();
|
|
|
|
this.emit('apps-changed');
|
2013-01-31 16:13:37 +00:00
|
|
|
},
|
|
|
|
|
2013-08-15 08:38:17 +00:00
|
|
|
_createIcon: function(iconSize) {
|
|
|
|
return this.view.createFolderIcon(iconSize, this);
|
2013-01-31 16:13:37 +00:00
|
|
|
},
|
|
|
|
|
2013-08-30 16:50:35 +00:00
|
|
|
_popupHeight: function() {
|
|
|
|
let usedHeight = this.view.usedHeight() + this._popup.getOffset(St.Side.TOP) + this._popup.getOffset(St.Side.BOTTOM);
|
|
|
|
return usedHeight;
|
|
|
|
},
|
|
|
|
|
2013-07-09 13:11:03 +00:00
|
|
|
_openSpaceForPopup: function() {
|
|
|
|
let id = this._parentView.connect('space-ready', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
this._parentView.disconnect(id);
|
|
|
|
this._popup.popup();
|
|
|
|
this._updatePopupPosition();
|
|
|
|
}));
|
|
|
|
this._parentView.openSpaceForPopup(this, this._boxPointerArrowside, this.view.nRowsDisplayedAtOnce());
|
|
|
|
},
|
|
|
|
|
2013-08-30 16:50:35 +00:00
|
|
|
_calculateBoxPointerArrowSide: function() {
|
|
|
|
let spaceTop = this.actor.y - this._parentView.getCurrentPageY();
|
|
|
|
let spaceBottom = this._parentView.actor.height - (spaceTop + this.actor.height);
|
|
|
|
|
|
|
|
return spaceTop > spaceBottom ? St.Side.BOTTOM : St.Side.TOP;
|
|
|
|
},
|
|
|
|
|
|
|
|
_updatePopupSize: function() {
|
|
|
|
// StWidget delays style calculation until needed, make sure we use the correct values
|
|
|
|
this.view._grid.actor.ensure_style();
|
|
|
|
|
2013-09-06 21:31:03 +00:00
|
|
|
let offsetForEachSide = Math.ceil((this._popup.getOffset(St.Side.TOP) +
|
|
|
|
this._popup.getOffset(St.Side.BOTTOM) -
|
|
|
|
this._popup.getCloseButtonOverlap()) / 2);
|
2013-08-30 16:50:35 +00:00
|
|
|
// Add extra padding to prevent boxpointer decorations and close button being cut off
|
|
|
|
this.view.setPaddingOffsets(offsetForEachSide);
|
|
|
|
this.view.adaptToSize(this._parentAvailableWidth, this._parentAvailableHeight);
|
|
|
|
},
|
|
|
|
|
|
|
|
_updatePopupPosition: function() {
|
|
|
|
if (!this._popup)
|
2013-01-31 16:13:37 +00:00
|
|
|
return;
|
|
|
|
|
2013-08-30 16:50:35 +00:00
|
|
|
if (this._boxPointerArrowside == St.Side.BOTTOM)
|
2013-07-09 13:11:03 +00:00
|
|
|
this._popup.actor.y = this.actor.allocation.y1 + this.actor.translation_y - this._popupHeight();
|
2013-08-30 16:50:35 +00:00
|
|
|
else
|
2013-07-09 13:11:03 +00:00
|
|
|
this._popup.actor.y = this.actor.allocation.y1 + this.actor.translation_y + this.actor.height;
|
2013-08-30 16:50:35 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_ensurePopup: function() {
|
|
|
|
if (this._popup && !this._popupInvalidated)
|
|
|
|
return;
|
|
|
|
this._boxPointerArrowside = this._calculateBoxPointerArrowSide();
|
|
|
|
if (!this._popup) {
|
|
|
|
this._popup = new AppFolderPopup(this, this._boxPointerArrowside);
|
|
|
|
this._parentView.addFolderPopup(this._popup);
|
|
|
|
this._popup.connect('open-state-changed', Lang.bind(this,
|
|
|
|
function(popup, isOpen) {
|
|
|
|
if (!isOpen)
|
|
|
|
this.actor.checked = false;
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
this._popup.updateArrowSide(this._boxPointerArrowside);
|
2013-01-31 16:13:37 +00:00
|
|
|
}
|
2013-08-30 16:50:35 +00:00
|
|
|
this._updatePopupSize();
|
|
|
|
this._updatePopupPosition();
|
|
|
|
this._popupInvalidated = false;
|
|
|
|
},
|
2013-01-31 16:13:37 +00:00
|
|
|
|
2013-08-30 16:50:35 +00:00
|
|
|
adaptToSize: function(width, height) {
|
|
|
|
this._parentAvailableWidth = width;
|
|
|
|
this._parentAvailableHeight = height;
|
|
|
|
if(this._popup)
|
|
|
|
this.view.adaptToSize(width, height);
|
|
|
|
this._popupInvalidated = true;
|
2013-01-31 16:13:37 +00:00
|
|
|
},
|
|
|
|
});
|
2014-01-28 16:36:57 +00:00
|
|
|
Signals.addSignalMethods(FolderIcon.prototype);
|
2013-01-31 16:13:37 +00:00
|
|
|
|
|
|
|
const AppFolderPopup = new Lang.Class({
|
|
|
|
Name: 'AppFolderPopup',
|
|
|
|
|
|
|
|
_init: function(source, side) {
|
|
|
|
this._source = source;
|
|
|
|
this._view = source.view;
|
|
|
|
this._arrowSide = side;
|
|
|
|
|
|
|
|
this._isOpen = false;
|
2013-05-16 15:20:32 +00:00
|
|
|
this.parentOffset = 0;
|
2013-01-31 16:13:37 +00:00
|
|
|
|
|
|
|
this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout(),
|
2013-02-20 16:20:46 +00:00
|
|
|
visible: false,
|
|
|
|
// We don't want to expand really, but look
|
|
|
|
// at the layout manager of our parent...
|
|
|
|
//
|
|
|
|
// DOUBLE HACK: if you set one, you automatically
|
|
|
|
// get the effect for the other direction too, so
|
|
|
|
// we need to set the y_align
|
|
|
|
x_expand: true,
|
|
|
|
y_expand: true,
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
y_align: Clutter.ActorAlign.START });
|
2013-01-31 16:13:37 +00:00
|
|
|
this._boxPointer = new BoxPointer.BoxPointer(this._arrowSide,
|
|
|
|
{ style_class: 'app-folder-popup-bin',
|
|
|
|
x_fill: true,
|
|
|
|
y_fill: true,
|
2013-08-30 16:50:35 +00:00
|
|
|
x_expand: true,
|
2013-01-31 16:13:37 +00:00
|
|
|
x_align: St.Align.START });
|
|
|
|
|
|
|
|
this._boxPointer.actor.style_class = 'app-folder-popup';
|
|
|
|
this.actor.add_actor(this._boxPointer.actor);
|
|
|
|
this._boxPointer.bin.set_child(this._view.actor);
|
|
|
|
|
2013-09-10 09:48:55 +00:00
|
|
|
this.closeButton = Util.makeCloseButton(this._boxPointer);
|
2013-05-16 17:35:12 +00:00
|
|
|
this.closeButton.connect('clicked', Lang.bind(this, this.popdown));
|
|
|
|
this.actor.add_actor(this.closeButton);
|
2013-01-31 16:13:37 +00:00
|
|
|
|
2013-05-16 17:35:12 +00:00
|
|
|
this._boxPointer.actor.bind_property('opacity', this.closeButton, 'opacity',
|
2013-01-31 16:13:37 +00:00
|
|
|
GObject.BindingFlags.SYNC_CREATE);
|
|
|
|
|
2013-03-06 20:42:36 +00:00
|
|
|
global.focus_manager.add_group(this.actor);
|
|
|
|
|
2013-01-31 16:13:37 +00:00
|
|
|
source.actor.connect('destroy', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
this.actor.destroy();
|
|
|
|
}));
|
2013-03-06 20:42:36 +00:00
|
|
|
this.actor.connect('key-press-event', Lang.bind(this, this._onKeyPress));
|
|
|
|
},
|
|
|
|
|
|
|
|
_onKeyPress: function(actor, event) {
|
|
|
|
if (!this._isOpen)
|
2013-11-29 18:17:34 +00:00
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2013-03-06 20:42:36 +00:00
|
|
|
|
|
|
|
if (event.get_key_symbol() != Clutter.KEY_Escape)
|
2013-11-29 18:17:34 +00:00
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2013-03-06 20:42:36 +00:00
|
|
|
|
|
|
|
this.popdown();
|
2013-11-29 18:17:34 +00:00
|
|
|
return Clutter.EVENT_STOP;
|
2013-01-31 16:13:37 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
toggle: function() {
|
|
|
|
if (this._isOpen)
|
|
|
|
this.popdown();
|
|
|
|
else
|
|
|
|
this.popup();
|
|
|
|
},
|
|
|
|
|
|
|
|
popup: function() {
|
|
|
|
if (this._isOpen)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this.actor.show();
|
2013-02-20 13:13:46 +00:00
|
|
|
|
|
|
|
this._boxPointer.setArrowActor(this._source.actor);
|
2013-01-31 16:13:37 +00:00
|
|
|
this._boxPointer.show(BoxPointer.PopupAnimation.FADE |
|
|
|
|
BoxPointer.PopupAnimation.SLIDE);
|
|
|
|
|
2013-07-09 13:11:03 +00:00
|
|
|
this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
|
|
|
|
2013-01-31 16:13:37 +00:00
|
|
|
this._isOpen = true;
|
|
|
|
this.emit('open-state-changed', true);
|
|
|
|
},
|
|
|
|
|
|
|
|
popdown: function() {
|
|
|
|
if (!this._isOpen)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._boxPointer.hide(BoxPointer.PopupAnimation.FADE |
|
|
|
|
BoxPointer.PopupAnimation.SLIDE);
|
|
|
|
this._isOpen = false;
|
|
|
|
this.emit('open-state-changed', false);
|
2013-08-30 16:50:35 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
getCloseButtonOverlap: function() {
|
|
|
|
return this.closeButton.get_theme_node().get_length('-shell-close-overlap-y');
|
|
|
|
},
|
|
|
|
|
|
|
|
getOffset: function (side) {
|
|
|
|
let offset = this._boxPointer.getPadding(side);
|
|
|
|
if (this._arrowSide == side)
|
|
|
|
offset += this._boxPointer.getArrowHeight();
|
|
|
|
return offset;
|
|
|
|
},
|
|
|
|
|
|
|
|
updateArrowSide: function (side) {
|
|
|
|
this._arrowSide = side;
|
|
|
|
this._boxPointer.updateArrowSide(side);
|
2013-01-31 16:13:37 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
Signals.addSignalMethods(AppFolderPopup.prototype);
|
2013-01-30 22:51:43 +00:00
|
|
|
|
2011-11-20 16:07:14 +00:00
|
|
|
const AppIcon = new Lang.Class({
|
|
|
|
Name: 'AppIcon',
|
2009-12-08 17:51:05 +00:00
|
|
|
|
2013-02-18 04:08:41 +00:00
|
|
|
_init : function(app, iconParams) {
|
2009-12-08 17:51:05 +00:00
|
|
|
this.app = app;
|
2013-12-16 03:18:17 +00:00
|
|
|
this.id = app.get_id();
|
|
|
|
this.name = app.get_name();
|
|
|
|
|
2011-01-25 21:22:00 +00:00
|
|
|
this.actor = new St.Button({ style_class: 'app-well-app',
|
|
|
|
reactive: true,
|
|
|
|
button_mask: St.ButtonMask.ONE | St.ButtonMask.TWO,
|
2011-02-03 17:38:03 +00:00
|
|
|
can_focus: true,
|
2011-01-25 21:22:00 +00:00
|
|
|
x_fill: true,
|
|
|
|
y_fill: true });
|
2009-12-08 17:51:05 +00:00
|
|
|
this.actor._delegate = this;
|
|
|
|
|
2013-01-30 22:51:43 +00:00
|
|
|
if (!iconParams)
|
|
|
|
iconParams = {};
|
|
|
|
|
|
|
|
iconParams['createIcon'] = Lang.bind(this, this._createIcon);
|
2013-08-15 08:38:17 +00:00
|
|
|
iconParams['setSizeManually'] = true;
|
2013-01-30 22:51:43 +00:00
|
|
|
this.icon = new IconGrid.BaseIcon(app.get_name(), iconParams);
|
2010-07-21 23:29:02 +00:00
|
|
|
this.actor.set_child(this.icon.actor);
|
2009-12-08 17:51:05 +00:00
|
|
|
|
2011-03-08 18:33:57 +00:00
|
|
|
this.actor.label_actor = this.icon.label;
|
|
|
|
|
2011-01-26 16:15:41 +00:00
|
|
|
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
2009-12-08 17:51:05 +00:00
|
|
|
this.actor.connect('clicked', Lang.bind(this, this._onClicked));
|
2011-02-03 17:38:03 +00:00
|
|
|
this.actor.connect('popup-menu', Lang.bind(this, this._onKeyboardPopupMenu));
|
2010-05-20 15:18:46 +00:00
|
|
|
|
2009-12-08 17:51:05 +00:00
|
|
|
this._menu = null;
|
2010-05-20 15:18:46 +00:00
|
|
|
this._menuManager = new PopupMenu.PopupMenuManager(this);
|
2009-12-08 17:51:05 +00:00
|
|
|
|
2010-03-27 01:35:34 +00:00
|
|
|
this._draggable = DND.makeDraggable(this.actor);
|
|
|
|
this._draggable.connect('drag-begin', Lang.bind(this,
|
|
|
|
function () {
|
|
|
|
this._removeMenuTimeout();
|
2010-05-08 14:06:28 +00:00
|
|
|
Main.overview.beginItemDrag(this);
|
|
|
|
}));
|
2011-03-09 15:40:48 +00:00
|
|
|
this._draggable.connect('drag-cancelled', Lang.bind(this,
|
|
|
|
function () {
|
|
|
|
Main.overview.cancelledItemDrag(this);
|
|
|
|
}));
|
2010-05-08 14:06:28 +00:00
|
|
|
this._draggable.connect('drag-end', Lang.bind(this,
|
|
|
|
function () {
|
|
|
|
Main.overview.endItemDrag(this);
|
2010-03-27 01:35:34 +00:00
|
|
|
}));
|
2009-12-08 17:51:05 +00:00
|
|
|
|
2010-06-05 22:35:26 +00:00
|
|
|
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
2010-01-07 05:40:21 +00:00
|
|
|
|
2010-03-10 13:52:28 +00:00
|
|
|
this._menuTimeoutId = 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 18:07:44 +00:00
|
|
|
this._stateChangedId = this.app.connect('notify::state',
|
|
|
|
Lang.bind(this,
|
|
|
|
this._onStateChanged));
|
|
|
|
this._onStateChanged();
|
2010-01-07 05:40:21 +00:00
|
|
|
},
|
|
|
|
|
2010-06-05 22:35:26 +00:00
|
|
|
_onDestroy: function() {
|
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 18:07:44 +00:00
|
|
|
if (this._stateChangedId > 0)
|
|
|
|
this.app.disconnect(this._stateChangedId);
|
2010-06-05 22:35:26 +00:00
|
|
|
this._stateChangedId = 0;
|
2010-03-10 13:52:28 +00:00
|
|
|
this._removeMenuTimeout();
|
|
|
|
},
|
|
|
|
|
2013-01-30 22:51:43 +00:00
|
|
|
_createIcon: function(iconSize) {
|
|
|
|
return this.app.create_icon_texture(iconSize);
|
|
|
|
},
|
|
|
|
|
2010-03-10 13:52:28 +00:00
|
|
|
_removeMenuTimeout: function() {
|
|
|
|
if (this._menuTimeoutId > 0) {
|
|
|
|
Mainloop.source_remove(this._menuTimeoutId);
|
|
|
|
this._menuTimeoutId = 0;
|
|
|
|
}
|
2010-01-07 05:40:21 +00:00
|
|
|
},
|
|
|
|
|
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 18:07:44 +00:00
|
|
|
_onStateChanged: function() {
|
|
|
|
if (this.app.state != Shell.AppState.STOPPED)
|
|
|
|
this.actor.add_style_class_name('running');
|
|
|
|
else
|
|
|
|
this.actor.remove_style_class_name('running');
|
2009-11-12 22:46:59 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onButtonPress: function(actor, event) {
|
2010-03-10 13:52:28 +00:00
|
|
|
let button = event.get_button();
|
|
|
|
if (button == 1) {
|
|
|
|
this._removeMenuTimeout();
|
|
|
|
this._menuTimeoutId = Mainloop.timeout_add(MENU_POPUP_TIMEOUT,
|
|
|
|
Lang.bind(this, function() {
|
2013-11-08 03:48:51 +00:00
|
|
|
this._menuTimeoutId = 0;
|
2010-05-20 15:18:46 +00:00
|
|
|
this.popupMenu();
|
2013-11-29 00:45:39 +00:00
|
|
|
return GLib.SOURCE_REMOVE;
|
2010-03-10 13:52:28 +00:00
|
|
|
}));
|
2011-01-26 16:15:41 +00:00
|
|
|
} else if (button == 3) {
|
|
|
|
this.popupMenu();
|
2013-11-29 18:17:34 +00:00
|
|
|
return Clutter.EVENT_STOP;
|
2010-03-10 13:52:28 +00:00
|
|
|
}
|
2013-11-29 18:17:34 +00:00
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2009-11-12 22:46:59 +00:00
|
|
|
},
|
|
|
|
|
2011-01-25 21:22:00 +00:00
|
|
|
_onClicked: function(actor, button) {
|
2010-03-10 13:52:28 +00:00
|
|
|
this._removeMenuTimeout();
|
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
if (button == 1) {
|
2011-01-25 21:22:00 +00:00
|
|
|
this._onActivate(Clutter.get_current_event());
|
2010-04-14 20:22:41 +00:00
|
|
|
} else if (button == 2) {
|
2011-01-25 21:45:58 +00:00
|
|
|
// Last workspace is always empty
|
|
|
|
let launchWorkspace = global.screen.get_workspace_by_index(global.screen.n_workspaces - 1);
|
|
|
|
launchWorkspace.activate(global.get_current_time());
|
2011-01-30 21:09:58 +00:00
|
|
|
this.app.open_new_window(-1);
|
2011-01-25 21:45:58 +00:00
|
|
|
Main.overview.hide();
|
2009-11-12 22:46:59 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2011-02-03 17:38:03 +00:00
|
|
|
_onKeyboardPopupMenu: function() {
|
|
|
|
this.popupMenu();
|
|
|
|
this._menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
|
|
|
},
|
|
|
|
|
2010-02-16 00:50:36 +00:00
|
|
|
getId: function() {
|
|
|
|
return this.app.get_id();
|
|
|
|
},
|
|
|
|
|
2010-05-20 15:18:46 +00:00
|
|
|
popupMenu: function() {
|
2010-03-10 13:52:28 +00:00
|
|
|
this._removeMenuTimeout();
|
|
|
|
this.actor.fake_release();
|
2013-02-20 15:55:02 +00:00
|
|
|
this._draggable.fakeRelease();
|
2010-03-10 13:52:28 +00:00
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
if (!this._menu) {
|
|
|
|
this._menu = new AppIconMenu(this);
|
|
|
|
this._menu.connect('activate-window', Lang.bind(this, function (menu, window) {
|
|
|
|
this.activateWindow(window);
|
|
|
|
}));
|
2011-09-15 19:49:13 +00:00
|
|
|
this._menu.connect('open-state-changed', Lang.bind(this, function (menu, isPoppedUp) {
|
2011-02-12 19:03:44 +00:00
|
|
|
if (!isPoppedUp)
|
2009-11-12 22:46:59 +00:00
|
|
|
this._onMenuPoppedDown();
|
|
|
|
}));
|
2011-02-03 17:38:03 +00:00
|
|
|
Main.overview.connect('hiding', Lang.bind(this, function () { this._menu.close(); }));
|
2010-05-20 15:18:46 +00:00
|
|
|
|
2010-10-07 18:15:51 +00:00
|
|
|
this._menuManager.addMenu(this._menu);
|
2009-11-12 22:46:59 +00:00
|
|
|
}
|
|
|
|
|
2012-11-03 20:03:33 +00:00
|
|
|
this.emit('menu-state-changed', true);
|
|
|
|
|
2011-03-22 14:43:27 +00:00
|
|
|
this.actor.set_hover(true);
|
2010-05-20 15:18:46 +00:00
|
|
|
this._menu.popup();
|
2012-03-01 00:09:41 +00:00
|
|
|
this._menuManager.ignoreRelease();
|
2013-08-18 14:20:22 +00:00
|
|
|
this.emit('sync-tooltip');
|
2009-11-12 22:46:59 +00:00
|
|
|
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2009-12-08 17:51:05 +00:00
|
|
|
activateWindow: function(metaWindow) {
|
|
|
|
if (metaWindow) {
|
2010-02-17 19:05:06 +00:00
|
|
|
Main.activateWindow(metaWindow);
|
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 18:07:44 +00:00
|
|
|
} else {
|
2009-12-08 17:51:05 +00:00
|
|
|
Main.overview.hide();
|
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 18:07:44 +00:00
|
|
|
}
|
2009-12-08 17:51:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onMenuPoppedDown: function() {
|
2010-03-19 17:47:34 +00:00
|
|
|
this.actor.sync_hover();
|
2012-11-03 20:03:33 +00:00
|
|
|
this.emit('menu-state-changed', false);
|
2009-12-08 17:51:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onActivate: function (event) {
|
2012-02-28 16:01:48 +00:00
|
|
|
let modifiers = event.get_state();
|
2009-12-08 17:51:05 +00:00
|
|
|
|
2014-01-09 14:36:41 +00:00
|
|
|
if ((modifiers & Clutter.ModifierType.CONTROL_MASK
|
|
|
|
&& this.app.state == Shell.AppState.RUNNING)
|
|
|
|
|| _isTerminal(this.app)) {
|
2013-02-18 04:08:41 +00:00
|
|
|
this.app.open_new_window(-1);
|
2009-12-08 17:51:05 +00:00
|
|
|
} else {
|
2013-02-18 04:08:41 +00:00
|
|
|
this.app.activate();
|
2009-12-08 17:51:05 +00:00
|
|
|
}
|
2013-02-18 04:08:41 +00:00
|
|
|
|
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 18:07:44 +00:00
|
|
|
Main.overview.hide();
|
2009-09-01 18:15:29 +00:00
|
|
|
},
|
|
|
|
|
2011-01-30 21:09:58 +00:00
|
|
|
shellWorkspaceLaunch : function(params) {
|
2011-08-11 09:35:23 +00:00
|
|
|
params = Params.parse(params, { workspace: -1,
|
|
|
|
timestamp: 0 });
|
2011-01-30 21:09:58 +00:00
|
|
|
|
2011-08-11 09:35:23 +00:00
|
|
|
this.app.open_new_window(params.workspace);
|
2009-08-18 00:29:54 +00:00
|
|
|
},
|
|
|
|
|
2009-09-11 21:13:50 +00:00
|
|
|
getDragActor: function() {
|
2011-08-28 13:35:13 +00:00
|
|
|
return this.app.create_icon_texture(Main.overview.dashIconSize);
|
2009-06-30 20:35:39 +00:00
|
|
|
},
|
|
|
|
|
2010-02-04 21:57:38 +00:00
|
|
|
// Returns the original actor that should align with the actor
|
|
|
|
// we show as the item is being dragged.
|
2009-06-30 20:35:39 +00:00
|
|
|
getDragActorSource: function() {
|
2010-07-21 23:29:02 +00:00
|
|
|
return this.icon.icon;
|
2013-08-18 14:20:22 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
shouldShowTooltip: function() {
|
|
|
|
return this.actor.hover && (!this._menu || !this._menu.isOpen);
|
|
|
|
},
|
2011-11-20 17:56:27 +00:00
|
|
|
});
|
2013-01-30 22:51:43 +00:00
|
|
|
Signals.addSignalMethods(AppIcon.prototype);
|
2009-11-12 22:46:59 +00:00
|
|
|
|
2011-11-20 13:10:48 +00:00
|
|
|
const AppIconMenu = new Lang.Class({
|
|
|
|
Name: 'AppIconMenu',
|
|
|
|
Extends: PopupMenu.PopupMenu,
|
2010-05-20 15:18:46 +00:00
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
_init: function(source) {
|
2010-11-29 21:07:10 +00:00
|
|
|
let side = St.Side.LEFT;
|
2012-02-14 01:37:28 +00:00
|
|
|
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
|
2010-11-29 21:07:10 +00:00
|
|
|
side = St.Side.RIGHT;
|
|
|
|
|
2011-11-20 13:10:48 +00:00
|
|
|
this.parent(source.actor, 0.5, side);
|
2010-05-20 15:18:46 +00:00
|
|
|
|
2011-03-22 14:43:27 +00:00
|
|
|
// We want to keep the item hovered while the menu is up
|
|
|
|
this.blockSourceEvents = true;
|
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
this._source = source;
|
|
|
|
|
2010-05-20 15:18:46 +00:00
|
|
|
this.actor.add_style_class_name('app-well-menu');
|
2009-11-12 22:46:59 +00:00
|
|
|
|
|
|
|
// Chain our visibility and lifecycle to that of the source
|
|
|
|
source.actor.connect('notify::mapped', Lang.bind(this, function () {
|
|
|
|
if (!source.actor.mapped)
|
2010-05-20 15:18:46 +00:00
|
|
|
this.close();
|
2009-11-12 22:46:59 +00:00
|
|
|
}));
|
|
|
|
source.actor.connect('destroy', Lang.bind(this, function () { this.actor.destroy(); }));
|
|
|
|
|
2013-02-15 07:05:53 +00:00
|
|
|
Main.layoutManager.menuGroup.add_actor(this.actor);
|
2009-11-12 22:46:59 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_redisplay: function() {
|
2010-05-20 15:18:46 +00:00
|
|
|
this.removeAll();
|
2009-11-12 22:46:59 +00:00
|
|
|
|
2013-12-04 16:26:01 +00:00
|
|
|
let windows = this._source.app.get_windows().filter(function(w) {
|
2014-01-30 17:04:18 +00:00
|
|
|
return !w.skip_taskbar;
|
2013-12-04 16:26:01 +00:00
|
|
|
});
|
2009-11-12 22:46:59 +00:00
|
|
|
|
|
|
|
// Display the app windows menu items and the separator between windows
|
|
|
|
// of the current desktop and other windows.
|
|
|
|
let activeWorkspace = global.screen.get_active_workspace();
|
|
|
|
let separatorShown = windows.length > 0 && windows[0].get_workspace() != activeWorkspace;
|
|
|
|
|
|
|
|
for (let i = 0; i < windows.length; i++) {
|
2011-11-21 18:01:05 +00:00
|
|
|
let window = windows[i];
|
|
|
|
if (!separatorShown && window.get_workspace() != activeWorkspace) {
|
2009-11-12 22:46:59 +00:00
|
|
|
this._appendSeparator();
|
|
|
|
separatorShown = true;
|
|
|
|
}
|
2011-11-21 18:01:05 +00:00
|
|
|
let item = this._appendMenuItem(window.title);
|
|
|
|
item.connect('activate', Lang.bind(this, function() {
|
|
|
|
this.emit('activate-window', window);
|
|
|
|
}));
|
2009-11-12 22:46:59 +00:00
|
|
|
}
|
|
|
|
|
2011-08-11 10:06:38 +00:00
|
|
|
if (!this._source.app.is_window_backed()) {
|
2011-11-21 18:01:05 +00:00
|
|
|
this._appendSeparator();
|
2009-11-12 22:46:59 +00:00
|
|
|
|
2011-08-11 10:06:38 +00:00
|
|
|
this._newWindowMenuItem = this._appendMenuItem(_("New Window"));
|
2011-11-21 18:01:05 +00:00
|
|
|
this._newWindowMenuItem.connect('activate', Lang.bind(this, function() {
|
|
|
|
this._source.app.open_new_window(-1);
|
|
|
|
this.emit('activate-window', null);
|
|
|
|
}));
|
2014-01-19 17:46:36 +00:00
|
|
|
this._appendSeparator();
|
2011-11-21 18:01:05 +00:00
|
|
|
|
2014-01-19 17:46:36 +00:00
|
|
|
let appInfo = this._source.app.get_app_info();
|
|
|
|
let actions = appInfo.list_actions();
|
|
|
|
for (let i = 0; i < actions.length; i++) {
|
|
|
|
let action = actions[i];
|
|
|
|
let item = this._appendMenuItem(appInfo.get_action_name(action));
|
|
|
|
item.connect('activate', Lang.bind(this, function(emitter, event) {
|
|
|
|
this._source.app.launch_action(action, event.get_time(), -1);
|
|
|
|
this.emit('activate-window', null);
|
|
|
|
}));
|
|
|
|
}
|
2011-08-11 10:06:38 +00:00
|
|
|
this._appendSeparator();
|
2009-11-12 22:46:59 +00:00
|
|
|
|
2011-11-21 18:01:05 +00:00
|
|
|
let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id());
|
|
|
|
|
|
|
|
if (isFavorite) {
|
|
|
|
let item = this._appendMenuItem(_("Remove from Favorites"));
|
|
|
|
item.connect('activate', Lang.bind(this, function() {
|
|
|
|
let favs = AppFavorites.getAppFavorites();
|
|
|
|
favs.removeFavorite(this._source.app.get_id());
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
let item = this._appendMenuItem(_("Add to Favorites"));
|
|
|
|
item.connect('activate', Lang.bind(this, function() {
|
|
|
|
let favs = AppFavorites.getAppFavorites();
|
|
|
|
favs.addFavorite(this._source.app.get_id());
|
|
|
|
}));
|
|
|
|
}
|
2011-08-11 10:06:38 +00:00
|
|
|
}
|
2009-11-12 22:46:59 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_appendSeparator: function () {
|
2010-05-20 15:18:46 +00:00
|
|
|
let separator = new PopupMenu.PopupSeparatorMenuItem();
|
|
|
|
this.addMenuItem(separator);
|
2009-11-12 22:46:59 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_appendMenuItem: function(labelText) {
|
2010-05-20 15:18:46 +00:00
|
|
|
// FIXME: app-well-menu-item style
|
|
|
|
let item = new PopupMenu.PopupMenuItem(labelText);
|
|
|
|
this.addMenuItem(item);
|
|
|
|
return item;
|
2009-11-12 22:46:59 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
popup: function(activatingButton) {
|
|
|
|
this._redisplay();
|
2010-05-20 15:18:46 +00:00
|
|
|
this.open();
|
2009-11-12 22:46:59 +00:00
|
|
|
}
|
2011-11-20 13:10:48 +00:00
|
|
|
});
|
2009-11-12 22:46:59 +00:00
|
|
|
Signals.addSignalMethods(AppIconMenu.prototype);
|