123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550 |
- // 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;
- }
- };
|