gconf-settings/skel/.local/share/gnome-shell/extensions/appfolders-manager@maestros.../dragAndDrop.js

551 lines
14 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// dragAndDrop.js
// GPLv3
const DND = imports.ui.dnd;
const AppDisplay = imports.ui.appDisplay;
const Clutter = imports.gi.Clutter;
const St = imports.gi.St;
const Main = imports.ui.main;
const Mainloop = imports.mainloop;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Convenience = Me.imports.convenience;
const Extension = Me.imports.extension;
const CHANGE_PAGE_TIMEOUT = 400;
const Gettext = imports.gettext.domain('appfolders-manager');
const _ = Gettext.gettext;
//-------------------------------------------------
var OVERLAY_MANAGER;
/* This method is called by extension.js' enable function. It does code injections
* to AppDisplay.AppIcon, connecting it to DND-related signals.
*/
function initDND () {
OVERLAY_MANAGER = new OverlayManager();
}
//--------------------------------------------------------------
/* Amazing! A singleton! It allows easy (and safer?) access to general methods,
* managing other objects: it creates/updates/deletes all overlays (for folders,
* pages, creation, removing).
*/
class OverlayManager {
constructor () {
this.addActions = [];
this.removeAction = new FolderActionArea('remove');
this.createAction = new FolderActionArea('create');
this.upAction = new NavigationArea('up');
this.downAction = new NavigationArea('down');
this.next_drag_should_recompute = true;
this.current_width = 0;
}
on_drag_begin () {
this.ensurePopdowned();
this.ensureFolderOverlayActors();
this.updateFoldersVisibility();
this.updateState(true);
}
on_drag_end () {
// force to compute new positions if a drop occurs
this.next_drag_should_recompute = true;
this.updateState(false);
}
on_drag_cancelled () {
this.updateState(false);
}
updateArrowVisibility () {
let grid = Main.overview.viewSelector.appDisplay._views[1].view._grid;
if (grid.currentPage == 0) {
this.upAction.setActive(false);
} else {
this.upAction.setActive(true);
}
if (grid.currentPage == grid._nPages -1) {
this.downAction.setActive(false);
} else {
this.downAction.setActive(true);
}
this.upAction.show();
this.downAction.show();
}
updateState (isDragging) {
if (isDragging) {
this.removeAction.show();
if (this.openedFolder == null) {
this.removeAction.setActive(false);
} else {
this.removeAction.setActive(true);
}
this.createAction.show();
this.updateArrowVisibility();
} else {
this.hideAll();
}
}
hideAll () {
this.removeAction.hide();
this.createAction.hide();
this.upAction.hide();
this.downAction.hide();
this.hideAllFolders();
}
hideAllFolders () {
for (var i = 0; i < this.addActions.length; i++) {
this.addActions[i].hide();
}
}
updateActorsPositions () {
let monitor = Main.layoutManager.primaryMonitor;
this.topOfTheGrid = Main.overview.viewSelector.actor.get_parent().get_parent().get_allocation_box().y1;
let temp = Main.overview.viewSelector.appDisplay._views[1].view.actor.get_parent();
let bottomOfTheGrid = this.topOfTheGrid + temp.get_allocation_box().y2;
let _availHeight = bottomOfTheGrid - this.topOfTheGrid;
let _availWidth = Main.overview.viewSelector.appDisplay._views[1].view._grid.actor.width;
let sideMargin = (monitor.width - _availWidth) / 2;
let xMiddle = ( monitor.x + monitor.width ) / 2;
let yMiddle = ( monitor.y + monitor.height ) / 2;
// Positions of areas
this.removeAction.setPosition( xMiddle , bottomOfTheGrid );
this.createAction.setPosition( xMiddle, Main.overview._panelGhost.height );
this.upAction.setPosition( 0, Main.overview._panelGhost.height );
this.downAction.setPosition( 0, bottomOfTheGrid );
// Sizes of areas
this.removeAction.setSize(xMiddle, monitor.height - bottomOfTheGrid);
this.createAction.setSize(xMiddle, this.topOfTheGrid - Main.overview._panelGhost.height);
this.upAction.setSize(xMiddle, this.topOfTheGrid - Main.overview._panelGhost.height);
this.downAction.setSize(xMiddle, monitor.height - bottomOfTheGrid);
this.updateArrowVisibility();
}
ensureFolderOverlayActors () {
// A folder was opened, and just closed.
if (this.openedFolder != null) {
this.updateActorsPositions();
this.computeFolderOverlayActors();
this.next_drag_should_recompute = true;
return;
}
// The grid "moved" or the whole shit needs forced updating
let allAppsGrid = Main.overview.viewSelector.appDisplay._views[1].view._grid;
let new_width = allAppsGrid.actor.allocation.get_width();
if (new_width != this.current_width || this.next_drag_should_recompute) {
this.next_drag_should_recompute = false;
this.updateActorsPositions();
this.computeFolderOverlayActors();
}
}
computeFolderOverlayActors () {
let monitor = Main.layoutManager.primaryMonitor;
let xMiddle = ( monitor.x + monitor.width ) / 2;
let yMiddle = ( monitor.y + monitor.height ) / 2;
let allAppsGrid = Main.overview.viewSelector.appDisplay._views[1].view._grid;
let nItems = 0;
let indexes = [];
let folders = [];
let x, y;
Main.overview.viewSelector.appDisplay._views[1].view._allItems.forEach(function(icon) {
if (icon.actor.visible) {
if (icon instanceof AppDisplay.FolderIcon) {
indexes.push(nItems);
folders.push(icon);
}
nItems++;
}
});
this.current_width = allAppsGrid.actor.allocation.get_width();
let x_correction = (monitor.width - this.current_width)/2;
let availHeightPerPage = (allAppsGrid.actor.height)/(allAppsGrid._nPages);
for (var i = 0; i < this.addActions.length; i++) {
this.addActions[i].actor.destroy();
}
for (var i = 0; i < indexes.length; i++) {
let inPageIndex = indexes[i] % allAppsGrid._childrenPerPage;
let page = Math.floor(indexes[i] / allAppsGrid._childrenPerPage);
x = folders[i].actor.get_allocation_box().x1;
y = folders[i].actor.get_allocation_box().y1;
// Invalid coords (example: when dragging out of the folder) should
// not produce a visible overlay, a negative page number is an easy
// way to be sure it stays hidden.
if (x == 0) {
page = -1;
}
x = Math.floor(x + x_correction);
y = y + this.topOfTheGrid;
y = y - (page * availHeightPerPage);
this.addActions[i] = new FolderArea(folders[i].id, x, y, page);
}
}
updateFoldersVisibility () {
let appView = Main.overview.viewSelector.appDisplay._views[1].view;
for (var i = 0; i < this.addActions.length; i++) {
if ((this.addActions[i].page == appView._grid.currentPage) && (!appView._currentPopup)) {
this.addActions[i].show();
} else {
this.addActions[i].hide();
}
}
}
ensurePopdowned () {
let appView = Main.overview.viewSelector.appDisplay._views[1].view;
if (appView._currentPopup) {
this.openedFolder = appView._currentPopup._source.id;
appView._currentPopup.popdown();
} else {
this.openedFolder = null;
}
}
goToPage (nb) {
Main.overview.viewSelector.appDisplay._views[1].view.goToPage( nb );
this.updateArrowVisibility();
this.hideAllFolders();
this.updateFoldersVisibility(); //load folders of the new page
}
destroy () {
for (let i = 0; i > this.addActions.length; i++) {
this.addActions[i].destroy();
}
this.removeAction.destroy();
this.createAction.destroy();
this.upAction.destroy();
this.downAction.destroy();
//log('OverlayManager destroyed');
}
};
//-------------------------------------------------------
// Abstract overlay with very generic methods
class DroppableArea {
constructor (id) {
this.id = id;
this.styleClass = 'folderArea';
this.actor = new St.BoxLayout ({
width: 10,
height: 10,
visible: false,
});
this.actor._delegate = this;
this.lock = true;
this.use_frame = Convenience.getSettings('org.gnome.shell.extensions.appfolders-manager').get_boolean('debug');
}
setPosition (x, y) {
let monitor = Main.layoutManager.primaryMonitor;
this.actor.set_position(monitor.x + x, monitor.y + y);
}
setSize (w, h) {
this.actor.width = w;
this.actor.height = h;
}
hide () {
this.actor.visible = false;
this.lock = true;
}
show () {
this.actor.visible = true;
}
setActive (active) {
this._active = active;
if (this._active) {
this.actor.style_class = this.styleClass;
} else {
this.actor.style_class = 'insensitiveArea';
}
}
destroy () {
this.actor.destroy();
}
}
/* Overlay representing an "action". Actions can be creating a folder, or
* removing an app from a folder. These areas accept drop, and display a label.
*/
class FolderActionArea extends DroppableArea {
constructor (id) {
super(id);
let x, y, label;
switch (this.id) {
case 'create':
label = _("Create a new folder");
this.styleClass = 'shadowedAreaTop';
break;
case 'remove':
label = '';
this.styleClass = 'shadowedAreaBottom';
break;
default:
label = 'invalid id';
break;
}
if (this.use_frame) {
this.styleClass = 'framedArea';
}
this.actor.style_class = this.styleClass;
this.label = new St.Label({
text: label,
style_class: 'dropAreaLabel',
x_expand: true,
y_expand: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
});
this.actor.add(this.label);
this.setPosition(10, 10);
Main.layoutManager.overviewGroup.add_actor(this.actor);
}
getRemoveLabel () {
let label;
if (OVERLAY_MANAGER.openedFolder == null) {
label = '…';
} else {
let folder_schema = Extension.folderSchema (OVERLAY_MANAGER.openedFolder);
label = folder_schema.get_string('name');
}
return (_("Remove from %s")).replace('%s', label);
}
setActive (active) {
super.setActive(active);
if (this.id == 'remove') {
this.label.text = this.getRemoveLabel();
}
}
handleDragOver (source, actor, x, y, time) {
if (source instanceof AppDisplay.AppIcon && this._active) {
return DND.DragMotionResult.MOVE_DROP;
}
Main.overview.endItemDrag(this);
return DND.DragMotionResult.NO_DROP;
}
acceptDrop (source, actor, x, y, time) {
if ((source instanceof AppDisplay.AppIcon) && (this.id == 'create')) {
Extension.createNewFolder(source);
Main.overview.endItemDrag(this);
return true;
}
if ((source instanceof AppDisplay.AppIcon) && (this.id == 'remove')) {
this.removeApp(source);
Main.overview.endItemDrag(this);
return true;
}
Main.overview.endItemDrag(this);
return false;
}
removeApp (source) {
let id = source.app.get_id();
Extension.removeFromFolder(id, OVERLAY_MANAGER.openedFolder);
OVERLAY_MANAGER.updateState(false);
Main.overview.viewSelector.appDisplay._views[1].view._redisplay();
}
destroy () {
this.label.destroy();
super.destroy();
}
};
/* Overlay reacting to hover, but isn't droppable. The goal is to go to an other
* page of the grid while dragging an app.
*/
class NavigationArea extends DroppableArea {
constructor (id) {
super(id);
let x, y, i;
switch (this.id) {
case 'up':
i = 'pan-up-symbolic';
this.styleClass = 'shadowedAreaTop';
break;
case 'down':
i = 'pan-down-symbolic';
this.styleClass = 'shadowedAreaBottom';
break;
default:
i = 'dialog-error-symbolic';
break;
}
if (this.use_frame) {
this.styleClass = 'framedArea';
}
this.actor.style_class = this.styleClass;
this.actor.add(new St.Icon({
icon_name: i,
icon_size: 24,
style_class: 'system-status-icon',
x_expand: true,
y_expand: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
}));
this.setPosition(x, y);
Main.layoutManager.overviewGroup.add_actor(this.actor);
}
handleDragOver (source, actor, x, y, time) {
if (this.id == 'up' && this._active) {
this.pageUp();
return DND.DragMotionResult.CONTINUE;
}
if (this.id == 'down' && this._active) {
this.pageDown();
return DND.DragMotionResult.CONTINUE;
}
Main.overview.endItemDrag(this);
return DND.DragMotionResult.NO_DROP;
}
pageUp () {
if(this.lock && !this.timeoutSet) {
this._timeoutId = Mainloop.timeout_add(CHANGE_PAGE_TIMEOUT, this.unlock.bind(this));
this.timeoutSet = true;
}
if(!this.lock) {
let currentPage = Main.overview.viewSelector.appDisplay._views[1].view._grid.currentPage;
this.lock = true;
OVERLAY_MANAGER.goToPage(currentPage - 1);
}
}
pageDown () {
if(this.lock && !this.timeoutSet) {
this._timeoutId = Mainloop.timeout_add(CHANGE_PAGE_TIMEOUT, this.unlock.bind(this));
this.timeoutSet = true;
}
if(!this.lock) {
let currentPage = Main.overview.viewSelector.appDisplay._views[1].view._grid.currentPage;
this.lock = true;
OVERLAY_MANAGER.goToPage(currentPage + 1);
}
}
acceptDrop (source, actor, x, y, time) {
Main.overview.endItemDrag(this);
return false;
}
unlock () {
this.lock = false;
this.timeoutSet = false;
Mainloop.source_remove(this._timeoutId);
}
};
/* This overlay is the area upon a folder. Position and visibility of the actor
* is handled by exterior functions.
* "this.id" is the folder's id, a string, as written in the gsettings key.
* Dropping an app on this folder will add it to the folder
*/
class FolderArea extends DroppableArea {
constructor (id, asked_x, asked_y, page) {
super(id);
this.page = page;
let grid = Main.overview.viewSelector.appDisplay._views[1].view._grid;
this.actor.width = grid._getHItemSize();
this.actor.height = grid._getVItemSize();
if (this.use_frame) {
this.styleClass = 'framedArea';
this.actor.add(new St.Label({
text: this.id,
x_expand: true,
y_expand: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
}));
} else {
this.styleClass = 'folderArea';
this.actor.add(new St.Icon({
icon_name: 'list-add-symbolic',
icon_size: 24,
style_class: 'system-status-icon',
x_expand: true,
y_expand: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
}));
}
if (this.use_frame) {
this.styleClass = 'framedArea';
}
this.actor.style_class = this.styleClass;
this.setPosition(asked_x, asked_y);
Main.layoutManager.overviewGroup.add_actor(this.actor);
}
handleDragOver (source, actor, x, y, time) {
if (source instanceof AppDisplay.AppIcon) {
return DND.DragMotionResult.MOVE_DROP;
}
Main.overview.endItemDrag(this);
return DND.DragMotionResult.NO_DROP;
}
acceptDrop (source, actor, x, y, time) { //FIXME recharger la vue ou au minimum les miniatures des dossiers
if ((source instanceof AppDisplay.AppIcon) &&
!Extension.isInFolder(source.id, this.id)) {
Extension.addToFolder(source, this.id);
Main.overview.endItemDrag(this);
return true;
}
Main.overview.endItemDrag(this);
return false;
}
};