dot/.local/share/gnome-shell/extensions/workspaces-to-dock@passingt.../shortcutsPanel.js

1458 lines
50 KiB
JavaScript
Raw Normal View History

2020-05-11 09:16:27 +00:00
const Graphene = imports.gi.Graphene;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Clutter = imports.gi.Clutter;
const St = imports.gi.St;
const Shell = imports.gi.Shell;
const Meta = imports.gi.Meta;
const Lang = imports.lang;
const Signals = imports.signals;
const Params = imports.misc.params;
const Config = imports.misc.config;
const GnomeSession = imports.misc.gnomeSession;
const AppDisplay = imports.ui.appDisplay;
const AppFavorites = imports.ui.appFavorites;
const Layout = imports.ui.layout;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const DND = imports.ui.dnd;
const IconGrid = imports.ui.iconGrid;
const Util = imports.misc.util;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const PlaceDisplay = Me.imports.placeDisplay;
const Convenience = Me.imports.convenience;
const MENU_POPUP_TIMEOUT = 600;
const ApplicationType = {
APPLICATION: 0,
PLACE: 1,
RECENT: 2,
APPSBUTTON: 3
};
const ShortcutsPanelOrientation = {
OUTSIDE: 0,
INSIDE: 1
};
let recentlyClickedAppLoopId = 0;
let recentlyClickedApp = null;
let recentlyClickedAppWindows = null;
let recentlyClickedAppIndex = 0;
// Filter out unnecessary windows, for instance nautilus desktop window.
function getInterestingWindows(app) {
return app.get_windows().filter((w) => {
return !w.skip_taskbar;
});
}
/* Return the actual position reverseing left and right in rtl */
function getPosition(settings) {
let position = settings.get_enum('dock-position');
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
if (position == St.Side.LEFT)
position = St.Side.RIGHT;
else if (position == St.Side.RIGHT)
position = St.Side.LEFT;
}
return position;
}
function getAppFromSource(source) {
if (source instanceof AppDisplay.AppIcon) {
return source.app;
} else if (source instanceof ShortcutButton) {
if (source._type == ApplicationType.APPLICATION)
return source.app;
else
return null;
} else {
return null;
}
}
function _getViewFromIcon(icon) {
for (let parent = icon.get_parent(); parent; parent = parent.get_parent()) {
if (parent instanceof ShortcutsPanel)
return parent;
}
return null;
}
var MyDragPlaceholderItem = GObject.registerClass(
class WorkspacesToDock_MyDragPlaceholderItem extends St.Widget {
_init() {
super._init({ style_class: 'placeholder',
opacity: 0,
x_expand: true,
y_expand: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER });
this._settings = Convenience.getSettings('org.gnome.shell.extensions.workspaces-to-dock');
let iconSize = this._settings.get_double('shortcuts-panel-icon-size');
this.set_size(iconSize, iconSize);
}
});
var ShortcutButton = GObject.registerClass({
Signals: {
'menu-state-changed': { param_types: [GObject.TYPE_BOOLEAN] },
'sync-tooltip': {},
},
}, class WorkspacesToDock_ShortcutButton extends St.Button {
_init(app, appType, panel) {
super._init({
style_class: 'app-well-app workspacestodock-shortcut-button',
pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
reactive: true,
button_mask: St.ButtonMask.ONE | St.ButtonMask.TWO,
can_focus: true,
});
this.app = app;
//this.id = app.get_id();
//this.name = app.get_name();
this._type = appType;
this._panel = panel;
this._stateChangedId = 0;
this._countChangedId = 0;
this._maxN = 4;
this._settings = Convenience.getSettings('org.gnome.shell.extensions.workspaces-to-dock');
this._gDesktopInterfaceSettings = Convenience.getSettings('org.gnome.desktop.interface');
this._iconContainer = new St.Widget({ layout_manager: new Clutter.BinLayout(),
x_expand: true, y_expand: true });
this.set_child(this._iconContainer);
this._delegate = this;
this._hasDndHover = false;
this._folderPreviewId = 0;
let iconParams = {
setSizeManually: true,
showLabel: false
};
iconParams['createIcon'] = this._createIcon.bind(this);
this.icon = new IconGrid.BaseIcon(null, iconParams);
this._iconSize = this._settings.get_double('shortcuts-panel-icon-size');
this.icon.setIconSize(this._iconSize);
this.icon.add_style_class_name('workspacestodock-shortcut-button-icon');
if (this._type == ApplicationType.PLACE) {
this.icon.add_style_class_name('workspacestodock-shortcut-button-symbolic-icon');
}
this._iconContainer.add_child(this.icon);
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);
this.label_actor = this.icon.label;
this._menu = null;
this._menuManager = new PopupMenu.PopupMenuManager(this);
if (this._type != ApplicationType.APPSBUTTON) {
this._draggable = DND.makeDraggable(this);
this._draggable.connect('drag-begin', () => {
this._dragging = true;
this.scaleAndFade();
this._removeMenuTimeout();
Main.overview.beginItemDrag(this);
});
this._draggable.connect('drag-cancelled', () => {
this._dragging = false;
Main.overview.cancelledItemDrag(this);
});
this._draggable.connect('drag-end', () => {
this._dragging = false;
this.undoScaleAndFade();
Main.overview.endItemDrag(this);
});
}
this._dragMonitor = null;
this._itemDragBeginId = Main.overview.connect(
'item-drag-begin', this._onDragBegin.bind(this));
this._itemDragEndId = Main.overview.connect(
'item-drag-end', this._onDragEnd.bind(this));
this._menuTimeoutId = 0;
this._stateChangedId = 0;
if (this._type == ApplicationType.APPSBUTTON) {
this._stateChangedId = Main.overview.viewSelector._showAppsButton.connect('notify::checked', () => {
this._onStateChanged.bind(this);
});
} else if (this._type == ApplicationType.APPLICATION) {
this._stateChangedId = this.app.connect('notify::state', () => {
this._onStateChanged();
});
this._countChangedId = this.app.connect('windows-changed', () => {
this._onCountChanged.bind(this);
});
}
this._dot.opacity = 0;
this._onStateChanged();
this.connect('destroy', this._onDestroy.bind(this));
}
_onDestroy() {
Main.overview.disconnect(this._itemDragBeginId);
Main.overview.disconnect(this._itemDragEndId);
if (this._stateChangedId > 0) {
if (this._type == ApplicationType.APPSBUTTON) {
Main.overview.viewSelector._showAppsButton.disconnect(this._stateChangedId);
} else {
this.app.disconnect(this._stateChangedId);
}
}
this._stateChangedId = 0;
if (this._countChangedId > 0) {
if (this._type == ApplicationType.APPLICATION) {
this.app.disconnect(this._countChangedId);
}
}
this._countChangedId = 0;
if (this._dragMonitor) {
DND.removeDragMonitor(this._dragMonitor);
this._dragMonitor = null;
}
if (this._draggable) {
if (this._dragging)
Main.overview.endItemDrag(this);
this._draggable = null;
}
this._removeMenuTimeout();
}
_createIcon(iconSize) {
if (this._type == ApplicationType.APPLICATION) {
return this.app.create_icon_texture(iconSize);
} else if (this._type == ApplicationType.PLACE) {
// Adjust 'places' symbolic icons by reducing their size
// and setting a special class for button padding
this._iconSize -= 4;
this.actor.add_style_class_name('workspacestodock-shortcut-button-symbolic');
return new St.Icon({gicon: this.app.icon, icon_size: iconSize});
} else if (this._type == ApplicationType.RECENT) {
let gicon = Gio.content_type_get_icon(this.app.mime);
return new St.Icon({gicon: gicon, icon_size: iconSize});
} else if (this._type == ApplicationType.APPSBUTTON) {
return new St.Icon({icon_name: 'view-app-grid-symbolic', icon_size: iconSize});
}
}
_removeMenuTimeout() {
if (this._menuTimeoutId > 0) {
GLib.source_remove(this._menuTimeoutId);
this._menuTimeoutId = 0;
}
}
_onStateChanged() {
if (this._type == ApplicationType.APPSBUTTON) {
if (Main.overview.viewSelector._showAppsButton.checked) {
this.add_style_pseudo_class('checked');
} else {
this.remove_style_pseudo_class('checked');
}
} else if (this._type == ApplicationType.APPLICATION) {
if (this.app.state != Shell.AppState.STOPPED) {
if (!this._settings.get_boolean('shortcuts-panel-show-window-count-indicators')) {
this._dot.opacity = 255;
}
this._onCountChanged();
} else {
this._dot.opacity = 0;
this._onCountChanged();
}
}
}
_onCountChanged() {
if (!this._settings.get_boolean('shortcuts-panel-show-window-count-indicators'))
return;
let appWindows = this.app.get_windows().filter((w) => {
return !w.skip_taskbar;
});
let n = appWindows.length;
if (n > this._maxN)
n = this._maxN;
for (let i = 1; i <= this._maxN; i++) {
let className = 'workspacestodock-shortcut-button-windowcount-image-'+i;
if (i != n) {
this.remove_style_class_name(className);
} else {
this.add_style_class_name(className);
}
}
}
_setPopupTimeout() {
this._removeMenuTimeout();
this._menuTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, MENU_POPUP_TIMEOUT, () => {
this._menuTimeoutId = 0;
this.popupMenu();
return GLib.SOURCE_REMOVE;
});
GLib.Source.set_name_by_id(this._menuTimeoutId, '[gnome-shell] this.popupMenu');
}
vfunc_leave_event(crossingEvent) {
let ret = super.vfunc_leave_event(crossingEvent);
this.fake_release();
this._removeMenuTimeout();
return ret;
}
vfunc_button_press_event(buttonEvent) {
super.vfunc_button_press_event(buttonEvent);
if (this._type == ApplicationType.APPSBUTTON || this._type == ApplicationType.APPLICATION) {
if (buttonEvent.button == 1) {
this._setPopupTimeout();
} else if (buttonEvent.button == 3) {
this.popupMenu();
return Clutter.EVENT_STOP;
}
}
return Clutter.EVENT_PROPAGATE;
}
vfunc_touch_event(touchEvent) {
super.vfunc_touch_event(touchEvent);
if (touchEvent.type == Clutter.EventType.TOUCH_BEGIN)
this._setPopupTimeout();
return Clutter.EVENT_PROPAGATE;
}
vfunc_clicked(button) {
this._removeMenuTimeout();
this.activate(button);
}
_onKeyboardPopupMenu() {
this.popupMenu();
this._menu.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
}
getId() {
return this.app.get_id();
}
popupMenu() {
if (this._type != ApplicationType.APPSBUTTON && this._type != ApplicationType.APPLICATION)
return false;
this._removeMenuTimeout();
this.fake_release();
if (this._draggable)
this._draggable.fakeRelease();
if (!this._menu) {
this._menu = new ShortcutButtonMenu(this);
this._menu.connect('activate-window', (menu, window) => {
this.activateWindow(window);
});
this._menu.connect('open-state-changed', (menu, isPoppedUp) => {
if (!isPoppedUp)
this._onMenuPoppedDown();
});
let id = Main.overview.connect('hiding', () => {
this._menu.close();
});
this.connect('destroy', () => {
Main.overview.disconnect(id);
});
this._menuManager.addMenu(this._menu);
}
this.emit('menu-state-changed', true);
this._panel.setPopupMenuFlag(true);
this._panel.hideThumbnails();
this.set_hover(true);
this._menu.popup();
this._menuManager.ignoreRelease();
this.emit('sync-tooltip');
return false;
}
activateWindow(metaWindow) {
if (metaWindow)
Main.activateWindow(metaWindow);
else
Main.overview.hide();
}
_onMenuPoppedDown() {
this.sync_hover();
this.emit('menu-state-changed', false);
this._panel.setPopupMenuFlag(false);
this._panel.showThumbnails();
}
activate(button) {
let event = Clutter.get_current_event();
let modifiers = event ? event.get_state() : 0;
let isPrimaryButton = button && button == Clutter.BUTTON_PRIMARY;
let isMiddleButton = button && button == Clutter.BUTTON_MIDDLE;
let isCtrlPressed = (modifiers & Clutter.ModifierType.CONTROL_MASK) != 0;
let openNewWindow = false;
if (this._type == ApplicationType.APPLICATION) {
openNewWindow = this.app.can_open_new_window() &&
this.app.state == Shell.AppState.RUNNING &&
(isCtrlPressed || isMiddleButton);
}
if (isPrimaryButton) {
if (this._type == ApplicationType.APPLICATION) {
// let tracker = Shell.WindowTracker.get_default();
// if (this.app.state == Shell.AppState.RUNNING) {
// if (this.app == tracker.focus_app && !Main.overview._shown) {
// this._cycleThroughWindows();
// } else {
// // If we activate the app (this.app.activate), all app
// // windows will come to the foreground. We only want to
// // activate one window at a time
// let windows = getInterestingWindows(this.app);
// let w = windows[0];
// Main.activateWindow(w);
// }
// } else {
// this.app.open_new_window(-1);
// }
if (this.app.state == Shell.AppState.STOPPED || openNewWindow)
this.animateLaunch();
if (openNewWindow)
this.app.open_new_window(-1);
else
this.app.activate();
Main.overview.hide();
} else if (this._type == ApplicationType.PLACE) {
this.app.launch(global.get_current_time());
} else if (this._type == ApplicationType.RECENT) {
Gio.app_info_launch_default_for_uri(this.app.uri, global.create_app_launch_context());
} else if (this._type == ApplicationType.APPSBUTTON) {
if (Main.overview.visible) {
if (Main.overview.viewSelector._showAppsButton.checked) {
Main.overview.hide();
Main.overview.viewSelector._showAppsButton.checked = false;
} else {
Main.overview.viewSelector._showAppsButton.checked = true;
}
} else {
// passingthru67: ISSUES #49 & #50
// Workaround issue by detecting animation status
// Showing the overview after checking the showAppsButton fails
// to animate when Gnome animations are enabled. On the other hand,
// showing the overview before checking the showAppsButton fails
// to scroll when Gnome animations are disabled.
if (this._gDesktopInterfaceSettings.get_boolean('enable-animations')) {
Main.overview.show();
Main.overview.viewSelector._showAppsButton.checked = true;
} else {
Main.overview.viewSelector._showAppsButton.checked = true;
Main.overview.show();
}
}
}
} else if (isMiddleButton) {
if (this._type == ApplicationType.APPLICATION) {
this.app.open_new_window(-1);
}
}
return Clutter.EVENT_PROPAGATE;
}
_cycleThroughWindows() {
// Store for a little amount of time last time app was clicked
// since the order changes upon window interaction
let MEMORY_TIME = 3000;
let appWindows = getInterestingWindows(this.app);
if(recentlyClickedAppLoopId>0)
GLib.source_remove(recentlyClickedAppLoopId);
recentlyClickedAppLoopId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, MEMORY_TIME, this._resetClickedApp);
// If there isn't already a list of windows for the current app,
// or the stored list is outdated, use the current windows list.
if (!recentlyClickedApp ||
recentlyClickedApp.get_id() != this.app.get_id() ||
recentlyClickedAppWindows.length != appWindows.length
) {
recentlyClickedApp = this.app;
recentlyClickedAppWindows = appWindows;
recentlyClickedAppIndex = 0;
}
recentlyClickedAppIndex ++;
let index = recentlyClickedAppIndex % recentlyClickedAppWindows.length;
let window = recentlyClickedAppWindows[index];
Main.activateWindow(window);
}
_resetClickedApp() {
if(recentlyClickedAppLoopId>0)
GLib.source_remove(recentlyClickedAppLoopId);
recentlyClickedAppLoopId=0;
recentlyClickedApp =null;
recentlyClickedAppWindows = null;
recentlyClickedAppIndex = 0;
return false;
}
animateLaunch() {
this.icon.animateZoomOut();
}
animateLaunchAtPos(x, y) {
this.icon.animateZoomOutAtPos(x, y);
}
scaleIn() {
this.scale_x = 0;
this.scale_y = 0;
this.ease({
scale_x: 1,
scale_y: 1,
duration: APP_ICON_SCALE_IN_TIME,
delay: APP_ICON_SCALE_IN_DELAY,
mode: Clutter.AnimationMode.EASE_OUT_QUINT,
});
}
shellWorkspaceLaunch(params) {
let { stack } = new Error();
log('shellWorkspaceLaunch is deprecated, use app.open_new_window() instead\n%s'.format(stack));
params = Params.parse(params, { workspace: -1,
timestamp: 0 });
if (this._type == ApplicationType.APPLICATION) {
this.app.open_new_window(params.workspace);
} else if (this._type == ApplicationType.PLACE) {
this.app.launch(global.get_current_time(), params.workspace);
} else if (this._type == ApplicationType.RECENT) {
Gio.app_info_launch_default_for_uri(this.app.uri, global.create_app_launch_context());
} else if (this._type == ApplicationType.APPSBUTTON) {
if (Main.overview.visible) {
if (Main.overview.viewSelector._showAppsButton.checked) {
Main.overview.hide();
Main.overview.viewSelector._showAppsButton.checked = false;
} else {
Main.overview.viewSelector._showAppsButton.checked = true;
}
} else {
Main.overview.show();
Main.overview.viewSelector._showAppsButton.checked = true;
}
}
}
getDragActor() {
let appIcon;
if (this._type == ApplicationType.APPLICATION) {
appIcon = this.app.create_icon_texture(this._iconSize);
} else if (this._type == ApplicationType.PLACE) {
appIcon = new St.Icon({gicon: this.app.icon, icon_size: this._iconSize});
} else if (this._type == ApplicationType.RECENT) {
let gicon = Gio.content_type_get_icon(this.app.mime);
appIcon = new St.Icon({gicon: gicon, icon_size: this._iconSize});
} else if (this._type == ApplicationType.APPSBUTTON) {
appIcon = new St.Icon({icon_name: 'view-app-grid-symbolic', icon_size: iconSize});
}
return appIcon;
}
// Returns the original actor that should align with the actor
// we show as the item is being dragged.
getDragActorSource() {
return this.icon.icon;
}
shouldShowTooltip() {
return this.hover && (!this._menu || !this._menu.isOpen);
}
scaleAndFade() {
this.reactive = false;
this.ease({
scale_x: 0.75,
scale_y: 0.75,
opacity: 128,
});
}
undoScaleAndFade() {
this.reactive = true;
this.ease({
scale_x: 1.0,
scale_y: 1.0,
opacity: 255,
});
}
_canAccept(source) {
return false;
// let view = _getViewFromIcon(source);
//
// return source != this &&
// (source instanceof this.constructor) &&
// (view instanceof AllView);
}
_setHoveringByDnd(hovering) {
// if (hovering) {
// if (this._folderPreviewId > 0)
// return;
//
// this._folderPreviewId =
// GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
// this.add_style_pseudo_class('drop');
// this._showFolderPreview();
// this._folderPreviewId = 0;
// return GLib.SOURCE_REMOVE;
// });
// } else {
// if (this._folderPreviewId > 0) {
// GLib.source_remove(this._folderPreviewId);
// this._folderPreviewId = 0;
// }
// this._hideFolderPreview();
// this.remove_style_pseudo_class('drop');
// }
}
_onDragBegin() {
this._dragMonitor = {
dragMotion: this._onDragMotion.bind(this),
};
DND.addDragMonitor(this._dragMonitor);
}
_onDragMotion(dragEvent) {
let target = dragEvent.targetActor;
let isHovering = target == this || this.contains(target);
let canDrop = this._canAccept(dragEvent.source);
let hasDndHover = isHovering && canDrop;
if (this._hasDndHover != hasDndHover) {
// this._setHoveringByDnd(hasDndHover);
this._hasDndHover = hasDndHover;
}
return DND.DragMotionResult.CONTINUE;
}
_onDragEnd() {
this.remove_style_pseudo_class('drop');
DND.removeDragMonitor(this._dragMonitor);
}
handleDragOver(source) {
if (source == this)
return DND.DragMotionResult.NO_DROP;
if (!this._canAccept(source))
return DND.DragMotionResult.CONTINUE;
return DND.DragMotionResult.MOVE_DROP;
}
acceptDrop(source) {
// this._setHoveringByDnd(false);
if (!this._canAccept(source))
return false;
let view = _getViewFromIcon(this);
let apps = [this.id, source.id];
return view.createFolder(apps);
}
});
var ShortcutButtonMenu = class WorkspacesToDock_ShortcutButtonMenu extends PopupMenu.PopupMenu {
constructor(source) {
let settings = Convenience.getSettings('org.gnome.shell.extensions.workspaces-to-dock');
let side = getPosition(settings);
super(source, 0.5, side);
this._settings = settings;
// We want to keep the item hovered while the menu is up
this.blockSourceEvents = true;
this._source = source;
this.actor.add_style_class_name('app-well-menu');
// Chain our visibility and lifecycle to that of the source
this._sourceMappedId = source.connect('notify::mapped', () => {
if (!source.mapped)
this.close();
});
source.connect('destroy', () => {
source.disconnect(this._sourceMappedId);
this.destroy();
});
Main.uiGroup.add_actor(this.actor);
}
_redisplay() {
this.removeAll();
// passingthru67: appsbutton menu to show extension preferences
if (this._source._type == ApplicationType.APPSBUTTON) {
let item = this._appendMenuItem(_("Extension Preferences"));
item.connect('activate', () => {
if (typeof ExtensionUtils.openPrefs === 'function') {
ExtensionUtils.openPrefs();
} else {
Util.spawn(["gnome-shell-extension-prefs", Me.metadata.uuid]);
}
});
return;
}
let windows = this._source.app.get_windows().filter(
w => !w.skip_taskbar
);
if (windows.length > 0) {
this.addMenuItem(
/* Translators: This is the heading of a list of open windows */
new PopupMenu.PopupSeparatorMenuItem(_("Open Windows"))
);
}
windows.forEach(window => {
let title = window.title
? window.title : this._source.app.get_name();
let item = this._appendMenuItem(title);
item.connect('activate', () => {
this.emit('activate-window', window);
});
});
if (!this._source.app.is_window_backed()) {
this._appendSeparator();
let appInfo = this._source.app.get_app_info();
let actions = appInfo.list_actions();
if (this._source.app.can_open_new_window() &&
!actions.includes('new-window')) {
this._newWindowMenuItem = this._appendMenuItem(_("New Window"));
this._newWindowMenuItem.connect('activate', () => {
this._source.animateLaunch();
this._source.app.open_new_window(-1);
this.emit('activate-window', null);
});
this._appendSeparator();
}
// if (discreteGpuAvailable &&
// this._source.app.state == Shell.AppState.STOPPED) {
// this._onDiscreteGpuMenuItem = this._appendMenuItem(_("Launch using Dedicated Graphics Card"));
// this._onDiscreteGpuMenuItem.connect('activate', () => {
// this._source.animateLaunch();
// this._source.app.launch(0, -1, true);
// this.emit('activate-window', null);
// });
// }
for (let i = 0; i < actions.length; i++) {
let action = actions[i];
let item = this._appendMenuItem(appInfo.get_action_name(action));
item.connect('activate', (emitter, event) => {
if (action == 'new-window')
this._source.animateLaunch();
this._source.app.launch_action(action, event.get_time(), -1);
this.emit('activate-window', null);
});
}
let canFavorite = global.settings.is_writable('favorite-apps');
if (canFavorite) {
this._appendSeparator();
let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id());
if (isFavorite) {
let item = this._appendMenuItem(_("Remove from Favorites"));
item.connect('activate', () => {
let favs = AppFavorites.getAppFavorites();
favs.removeFavorite(this._source.app.get_id());
});
} else {
let item = this._appendMenuItem(_("Add to Favorites"));
item.connect('activate', () => {
let favs = AppFavorites.getAppFavorites();
favs.addFavorite(this._source.app.get_id());
});
}
}
if (Shell.AppSystem.get_default().lookup_app('org.gnome.Software.desktop')) {
this._appendSeparator();
let item = this._appendMenuItem(_("Show Details"));
item.connect('activate', () => {
let id = this._source.app.get_id();
let args = GLib.Variant.new('(ss)', [id, '']);
Gio.DBus.get(Gio.BusType.SESSION, null, (o, res) => {
let bus = Gio.DBus.get_finish(res);
bus.call('org.gnome.Software',
'/org/gnome/Software',
'org.gtk.Actions', 'Activate',
GLib.Variant.new('(sava{sv})',
['details', [args], null]),
null, 0, -1, null, null);
Main.overview.hide();
});
});
}
}
}
_appendSeparator() {
let separator = new PopupMenu.PopupSeparatorMenuItem();
this.addMenuItem(separator);
}
_appendMenuItem(labelText) {
// FIXME: app-well-menu-item style
let item = new PopupMenu.PopupMenuItem(labelText);
this.addMenuItem(item);
return item;
}
popup(_activatingButton) {
this._redisplay();
if (this._settings.get_boolean('shortcuts-panel-popupmenu-arrow-at-top')) {
this._arrowAlignment = 0.0;
} else {
this._arrowAlignment = 0.5;
}
this.open();
}
};
Signals.addSignalMethods(ShortcutButtonMenu.prototype);
var ShortcutsPanel = class WorkspacesToDock_ShortcutsPanel {
constructor(dock) {
this._dock = dock;
this._settings = Convenience.getSettings('org.gnome.shell.extensions.workspaces-to-dock');
this._position = getPosition(this._settings);
this._isHorizontal = (this._position == St.Side.TOP ||
this._position == St.Side.BOTTOM);
let packVertical = true;
if (this._isHorizontal)
packVertical = false;
// Set _centerContainer property
if (this._settings.get_boolean('customize-height') && this._settings.get_boolean('center-thumbnails-on-dock')) {
this._centerContainer = true;
} else {
this._centerContainer = false;
}
// Set _centerPanelsIndependently property
if (this._centerContainer && this._settings.get_int('center-thumbnails-option') == 0) {
this._centerPanelsIndependently = true;
} else {
this._centerPanelsIndependently = false;
}
this.actor = new St.BoxLayout({
style_class: 'workspace-thumbnails workspacestodock-shortcuts-panel',
vertical: packVertical,
clip_to_allocation: true,
x_align: (this._centerContainer && this._centerPanelsIndependently) ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.START,
y_align: (this._centerContainer && this._centerPanelsIndependently) ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.START
});
this.actor._delegate = this;
this._appSystem = Shell.AppSystem.get_default();
this._appFavorites = AppFavorites.getAppFavorites();
this._installedChangedId = this._appSystem.connect('installed-changed', () => {
this._appFavorites.reload();
this.refresh();
});
// Connect to AppSystem and listen for app state changes
this._appStateChangedId = this._appSystem.connect('app-state-changed', this._updateRunningApps.bind(this));
// Connect to AppFavorites and listen for favorites changes
this._favoritesChangedId = this._appFavorites.connect('changed', this._queueUpdateFavoriteApps.bind(this));
// Connect to item drag signals
this._dragPlaceholder = null;
this._dragPlaceholderPos = -1;
this._itemDragBeginId = Main.overview.connect('item-drag-begin', this._onDragBegin.bind(this));
this._itemDragEndId = Main.overview.connect('item-drag-end', this._onDragEnd.bind(this));
this._itemDragCancelId = Main.overview.connect('item-drag-cancelled', this._onDragCancelled.bind(this));
// Bind Preference Settings
this._bindSettingsChanges();
// Populate panel
this._populate();
}
destroy() {
// Disconnect global signals
if (this._itemDragBeginId > 0) Main.overview.disconnect(this._itemDragBeginId);
if (this._itemDragEndId > 0) Main.overview.disconnect(this._itemDragEndId);
if (this._itemDragCancelId > 0) Main.overview.disconnect(this._itemDragCancelId);
if (this._installedChangedId > 0) this._appSystem.disconnect(this._installedChangedId);
if (this._appStateChangedId > 0) this._appSystem.disconnect(this._appStateChangedId);
if (this._favoritesChangedId > 0) this._appFavorites.disconnect(this._favoritesChangedId);
// Destroy main clutter actor
this.actor.destroy_all_children();
this.actor.destroy();
// Disconnect GSettings signals
this._settings.run_dispose();
}
_bindSettingsChanges() {
this._settings.connect('changed::shortcuts-panel-show-running', () => {
this.refresh();
});
this._settings.connect('changed::shortcuts-panel-show-places', () => {
this.refresh();
});
this._settings.connect('changed::shortcuts-panel-show-window-count-indicators', () => {
this.refresh();
});
this._settings.connect('changed::shortcuts-panel-appsbutton-at-bottom', () => {
this.refresh();
});
}
_onDragBegin() {
this._dragCancelled = false;
this._dragMonitor = {
dragMotion: this._onDragMotion.bind(this)
};
DND.addDragMonitor(this._dragMonitor);
}
_onDragCancelled() {
this._dragCancelled = true;
this._endDrag();
}
_onDragEnd() {
if (this._dragCancelled)
return;
this._endDrag();
}
_endDrag() {
this._clearDragPlaceholder();
DND.removeDragMonitor(this._dragMonitor);
}
_onDragMotion(dragEvent) {
let app = getAppFromSource(dragEvent.source);
if (app == null)
return DND.DragMotionResult.CONTINUE;
if (!this.actor.contains(dragEvent.targetActor))
this._clearDragPlaceholder();
return DND.DragMotionResult.CONTINUE;
}
_clearDragPlaceholder() {
if (this._dragPlaceholder) {
this._dragPlaceholder.destroy();
this._dragPlaceholder = null;
}
this._dragPlaceholderPos = -1;
}
handleDragOver(source, actor, x, y, time) {
let app = getAppFromSource(source);
// Don't allow favoriting of transient apps
if (app == null || app.is_window_backed())
return DND.DragMotionResult.NO_DROP;
if (!global.settings.is_writable('favorite-apps'))
return DND.DragMotionResult.NO_DROP;
let favorites = AppFavorites.getAppFavorites().getFavorites();
let numFavorites = favorites.length;
let favPos = favorites.indexOf(app);
let children = this._favoriteAppsBox.get_children();
let numChildren = children.length;
let boxH;
let boxY;
if (this._isHorizontal) {
boxY = this._favoriteAppsBox.x;
boxH = this._favoriteAppsBox.width;
} else {
boxY = this._favoriteAppsBox.y;
boxH = this._favoriteAppsBox.height;
}
// Keep the placeholder out of the index calculation; assuming that
// the remove target has the same size as "normal" items, we don't
// need to do the same adjustment there.
if (this._dragPlaceholder) {
if (this._isHorizontal) {
boxH -= this._dragPlaceholder.width;
} else {
boxH -= this._dragPlaceholder.height;
}
numChildren--;
}
let pos;
let posY;
if (this._isHorizontal) {
posY = x - boxY;
} else {
posY = y - boxY;
}
pos = Math.floor(posY * numChildren / boxH);
if (pos != this._dragPlaceholderPos && pos <= numFavorites) {
this._dragPlaceholderPos = pos;
// Don't allow positioning before or after self
if (favPos != -1 && (pos == favPos || pos == favPos + 1)) {
this._clearDragPlaceholder();
return DND.DragMotionResult.CONTINUE;
}
// If the placeholder already exists, we just move
// it, but if we are adding it, expand its size in
// an animation
// Passingthru67 TODO:
// Need to convert the dragPlaceholder to an St.Widget so that
// we can use the fadein animation
let fadeIn;
if (this._dragPlaceholder) {
this._dragPlaceholder.destroy();
fadeIn = false;
} else {
fadeIn = true;
}
this._dragPlaceholder = new MyDragPlaceholderItem();
this._favoriteAppsBox.insert_child_at_index(this._dragPlaceholder,
this._dragPlaceholderPos);
// this._dragPlaceholder.show(fadeIn);
this._dragPlaceholder.show();
}
// Remove the drag placeholder if we are not in the
// "favorites zone"
if (pos > numFavorites)
this._clearDragPlaceholder();
if (!this._dragPlaceholder)
return DND.DragMotionResult.NO_DROP;
let srcIsFavorite = (favPos != -1);
if (srcIsFavorite) {
return DND.DragMotionResult.MOVE_DROP;
}
return DND.DragMotionResult.COPY_DROP;
}
// Draggable target interface
acceptDrop(source, actor, x, y, time) {
let app = getAppFromSource(source);
// Don't allow favoriting of transient apps
if (app == null || app.is_window_backed()) {
return false;
}
if (!global.settings.is_writable('favorite-apps'))
return false;
let id = app.get_id();
let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
let srcIsFavorite = (id in favorites);
let favPos = 0;
let children = this._favoriteAppsBox.get_children();
for (let i = 0; i < this._dragPlaceholderPos; i++) {
if (this._dragPlaceholder &&
children[i] == this._dragPlaceholder)
continue;
let childId = children[i]._delegate.app.get_id();
if (childId == id) {
continue;
}
if (childId in favorites) {
favPos++;
}
}
// No drag placeholder means we don't wan't to favorite the app
// and we are dragging it to its original position
if (!this._dragPlaceholder)
return true;
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
let appFavorites = AppFavorites.getAppFavorites();
if (srcIsFavorite)
appFavorites.moveFavoriteToPos(id, favPos);
else
appFavorites.addFavoriteAtPos(id, favPos);
return false;
});
this._clearDragPlaceholder();
return true;
}
setPopupMenuFlag(showing) {
this._dock.setPopupMenuFlag(showing);
}
hideThumbnails() {
if (this._settings.get_boolean('shortcuts-panel-popupmenu-hide-thumbnails')) {
if (this._settings.get_enum('shortcuts-panel-orientation') == ShortcutsPanelOrientation.OUTSIDE) {
this._dock._thumbnailsBox.actor.opacity = 0;
this.actor.remove_style_class_name('workspacestodock-shortcuts-panel');
this.actor.add_style_class_name('workspacestodock-shortcuts-panel-popupmenu');
// for (let i = 0; i < this._dock._thumbnailsBox._thumbnails.length; i++) {
// this._dock._thumbnailsBox._thumbnails[i].actor.opacity = 0;
// }
// this._dock._thumbnailsBox._indicator.opacity = 0;
}
}
}
showThumbnails() {
if (this._settings.get_boolean('shortcuts-panel-popupmenu-hide-thumbnails')) {
this._dock._thumbnailsBox.actor.opacity = 255;
this.actor.remove_style_class_name('workspacestodock-shortcuts-panel-popupmenu');
this.actor.add_style_class_name('workspacestodock-shortcuts-panel');
// for (let i = 0; i < this._dock._thumbnailsBox._thumbnails.length; i++) {
// this._dock._thumbnailsBox._thumbnails[i].actor.opacity = 255;
// }
// this._dock._thumbnailsBox._indicator.opacity = 255;
}
}
setReactiveState(state) {
if (state == null)
return;
// Deactive Apps button
this._appsButton.reactive = state;
// Deactivate favorites
if (this._favoriteAppsBox) {
let children = this._favoriteAppsBox.get_children();
for (let i = 0; i < children.length; i++) {
children[i].reactive = state;
}
}
// Deactivate running apps
if (this._runningAppsBox) {
let children = this._runningAppsBox.get_children();
for (let i = 0; i < children.length; i++) {
children[i].reactive = state;
}
}
// Deactivate places
if (this._placesBox) {
let children = this._placesBox.get_children();
for (let i = 0; i < children.length; i++) {
children[i].reactive = state;
}
}
}
refresh() {
this._clear();
this._populate();
}
_clear() {
this.actor.destroy_all_children();
}
_populate() {
let packVertical = true;
if (this._isHorizontal)
packVertical = false;
// Add Favorite Apps Box
this._favoriteAppsBox = new St.BoxLayout({
vertical: packVertical,
style_class: 'workspacestodock-shortcuts-panel workspacestodock-shortcuts-panel-favorites'
});
this.actor.add_actor(this._favoriteAppsBox);
this._favoriteAppsWorkId = Main.initializeDeferredWork(this._favoriteAppsBox, this._updateFavoriteApps.bind(this));
// Add Running Apps Box
if (this._settings.get_boolean('shortcuts-panel-show-running')) {
this._runningAppsBox = new St.BoxLayout({
vertical: packVertical,
style_class: 'workspacestodock-shortcuts-panel workspacestodock-shortcuts-panel-running'
});
this.actor.add_actor(this._runningAppsBox);
this._updateRunningApps();
}
if (this._settings.get_boolean('shortcuts-panel-show-places')) {
this._placesBox = new St.BoxLayout({
vertical: packVertical,
style_class: 'workspacestodock-shortcuts-panel workspacestodock-shortcuts-panel-places'
});
this.actor.add_actor(this._placesBox);
// Get places
let placesManager = new PlaceDisplay.PlacesManager();
let special = placesManager.get('special');
let allPlaces = [];
allPlaces = allPlaces.concat(special);
// Add places to Places Box
for (let i = 0; i < allPlaces.length; ++i) {
let app = allPlaces[i];
let shortcutButton = new ShortcutButton(app, ApplicationType.PLACE);
this._placesBox.add_actor(shortcutButton);
}
}
// Add Apps Button to top or bottom of shortcuts panel
this._appsButton = new ShortcutButton(null, ApplicationType.APPSBUTTON, this);
if (this._settings.get_boolean('shortcuts-panel-appsbutton-at-bottom')) {
let filler = new St.Widget({
style_class: 'popup-separator-menu-item workspacestodock-shortcut-panel-filler',
x_expand: true,
y_expand: true
});
this.actor.add_actor(filler);
this.actor.add_actor(this._appsButton);
} else {
this.actor.insert_child_at_index(this._appsButton, 0);
}
}
_queueUpdateFavoriteApps () {
Main.queueDeferredWork(this._favoriteAppsWorkId);
}
_updateFavoriteApps() {
if (!this._favoriteAppsBox)
return;
// Clear favorite apps box
this._favoriteAppsBox.destroy_all_children();
// Apps supposed to be in the favorite apps box
let newApps = [];
// Get favorites
let favorites = this._appFavorites.getFavoriteMap();
for (let id in favorites) {
newApps.push(favorites[id]);
}
// Populate shortcuts panel with favorites
for (let i = 0; i < newApps.length; ++i) {
let app = newApps[i];
let shortcutButton = new ShortcutButton(app, ApplicationType.APPLICATION, this);
this._favoriteAppsBox.add_actor(shortcutButton);
}
this.emit('update-favorite-apps');
this._updateRunningApps();
}
_updateRunningApps() {
if (!this._runningAppsBox)
return;
let children = this._runningAppsBox.get_children().filter((actor) => {
return actor &&
actor._delegate &&
actor._delegate.app && actor._delegate._type == ApplicationType.APPLICATION;
});
// Apps currently in running apps box
let oldApps = children.map(function(actor) {
return actor._delegate.app;
});
// Apps supposed to be in the running apps box
let newApps = [];
// Get favorites
let favorites = this._appFavorites.getFavoriteMap();
// Get running apps
let running = this._appSystem.get_running();
for (let i = 0; i < running.length; i++) {
let app = running[i];
if (app.get_id() in favorites)
continue;
newApps.push(app);
}
// Figure out the actual changes to the list of items; we iterate
// over both the list of items currently in the dash and the list
// of items expected there, and collect additions and removals.
// Moves are both an addition and a removal, where the order of
// the operations depends on whether we encounter the position
// where the item has been added first or the one from where it
// was removed.
// There is an assumption that only one item is moved at a given
// time; when moving several items at once, everything will still
// end up at the right position, but there might be additional
// additions/removals (e.g. it might remove all the launchers
// and add them back in the new order even if a smaller set of
// additions and removals is possible).
// If above assumptions turns out to be a problem, we might need
// to use a more sophisticated algorithm, e.g. Longest Common
// Subsequence as used by diff.
let addedItems = [];
let removedActors = [];
let newIndex = 0;
let oldIndex = 0;
while (newIndex < newApps.length || oldIndex < oldApps.length) {
let oldApp = oldApps.length > oldIndex ? oldApps[oldIndex] : null;
let newApp = newApps.length > newIndex ? newApps[newIndex] : null;
// No change at oldIndex/newIndex
if (oldApp == newApp) {
oldIndex++;
newIndex++;
continue;
}
// App removed at oldIndex
if (oldApp && newApps.indexOf(oldApp) == -1) {
removedActors.push(children[oldIndex]);
oldIndex++;
continue;
}
// App added at newIndex
if (newApp && oldApps.indexOf(newApp) == -1) {
addedItems.push({ app: newApp,
item: this._createShortcutButton(newApp, ApplicationType.APPLICATION),
pos: newIndex });
newIndex++;
continue;
}
// App moved
let nextApp = newApps.length > newIndex + 1 ? newApps[newIndex + 1]
: null;
let insertHere = nextApp && nextApp == oldApp;
let alreadyRemoved = removedActors.reduce((result, actor) => {
let removedApp = actor.child._delegate.app;
return result || removedApp == newApp;
}, false);
if (insertHere || alreadyRemoved) {
let newItem = this._createShortcutButton(newApp, shortcutType);
addedItems.push({ app: newApp,
item: newItem,
pos: newIndex + removedActors.length });
newIndex++;
} else {
removedActors.push(children[oldIndex]);
oldIndex++;
}
}
for (let i = 0; i < addedItems.length; i++)
this._runningAppsBox.insert_child_at_index(addedItems[i].item,
addedItems[i].pos);
for (let i = 0; i < removedActors.length; i++) {
let item = removedActors[i];
item.destroy();
}
this.emit('update-running-apps');
}
_createShortcutButton(app, appType) {
let shortcutType = app ? appType : ApplicationType.APPSBUTTON;
let shortcutButton = new ShortcutButton(app, shortcutType, this);
return shortcutButton;
}
};
Signals.addSignalMethods(ShortcutsPanel.prototype);