1bbc138ed2
Show a positional indicator where a new favorite will be added and make the favorites re-orderable. Also allow the removal of favorites using drag-and-drop according to the mockups. https://bugzilla.gnome.org/show_bug.cgi?id=634948
358 lines
12 KiB
JavaScript
358 lines
12 KiB
JavaScript
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
const Signals = imports.signals;
|
|
const Lang = imports.lang;
|
|
const Meta = imports.gi.Meta;
|
|
const Shell = imports.gi.Shell;
|
|
const St = imports.gi.St;
|
|
const Gettext = imports.gettext.domain('gnome-shell');
|
|
const _ = Gettext.gettext;
|
|
|
|
const AppDisplay = imports.ui.appDisplay;
|
|
const AppFavorites = imports.ui.appFavorites;
|
|
const DND = imports.ui.dnd;
|
|
const IconGrid = imports.ui.iconGrid;
|
|
const Main = imports.ui.main;
|
|
const Workspace = imports.ui.workspace;
|
|
|
|
|
|
function RemoveFavoriteIcon() {
|
|
this._init();
|
|
}
|
|
|
|
RemoveFavoriteIcon.prototype = {
|
|
_init: function() {
|
|
this.actor = new St.Bin({ style_class: 'remove-favorite' });
|
|
this._iconActor = null;
|
|
this.icon = new IconGrid.BaseIcon(_("Remove"),
|
|
{ setSizeManually: true,
|
|
createIcon: Lang.bind(this, this._createIcon) });
|
|
this.actor.set_child(this.icon.actor);
|
|
this.actor._delegate = this;
|
|
},
|
|
|
|
_createIcon: function(size) {
|
|
this._iconActor = new St.Icon({ icon_name: 'user-trash',
|
|
style_class: 'remove-favorite-icon',
|
|
icon_size: size });
|
|
return this._iconActor;
|
|
},
|
|
|
|
setHover: function(hovered) {
|
|
this.actor.set_hover(hovered);
|
|
if (this._iconActor)
|
|
this._iconActor.set_hover(hovered);
|
|
},
|
|
|
|
// Rely on the dragged item being a favorite
|
|
handleDragOver: function(source, actor, x, y, time) {
|
|
return DND.DragMotionResult.MOVE_DROP;
|
|
},
|
|
|
|
acceptDrop: function(source, actor, x, y, time) {
|
|
let app = null;
|
|
if (source instanceof AppDisplay.AppWellIcon) {
|
|
let appSystem = Shell.AppSystem.get_default();
|
|
app = appSystem.get_app(source.getId());
|
|
} else if (source instanceof Workspace.WindowClone) {
|
|
let tracker = Shell.WindowTracker.get_default();
|
|
app = tracker.get_window_app(source.metaWindow);
|
|
}
|
|
|
|
let id = app.get_id();
|
|
|
|
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this,
|
|
function () {
|
|
AppFavorites.getAppFavorites().removeFavorite(id);
|
|
return false;
|
|
}));
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
|
|
function Dash() {
|
|
this._init();
|
|
}
|
|
|
|
Dash.prototype = {
|
|
_init : function() {
|
|
this._placeholderText = null;
|
|
this._menus = [];
|
|
this._menuDisplays = [];
|
|
this._maxHeight = -1;
|
|
this._iconSize = 48;
|
|
|
|
this._dragPlaceholder = null;
|
|
this._dragPlaceholderPos = -1;
|
|
this._favRemoveTarget = null;
|
|
|
|
this._favorites = [];
|
|
|
|
this._box = new St.BoxLayout({ name: 'dash',
|
|
vertical: true,
|
|
clip_to_allocation: true });
|
|
this._box._delegate = this;
|
|
|
|
this.actor = new St.Bin({ y_align: St.Align.START, child: this._box });
|
|
this.actor.connect('notify::height', Lang.bind(this,
|
|
function() {
|
|
if (this._maxHeight != this.actor.height)
|
|
this._queueRedisplay();
|
|
this._maxHeight = this.actor.height;
|
|
}));
|
|
|
|
this._workId = Main.initializeDeferredWork(this._box, Lang.bind(this, this._redisplay));
|
|
|
|
this._tracker = Shell.WindowTracker.get_default();
|
|
this._appSystem = Shell.AppSystem.get_default();
|
|
|
|
this._appSystem.connect('installed-changed', Lang.bind(this, this._queueRedisplay));
|
|
AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._queueRedisplay));
|
|
this._tracker.connect('app-state-changed', Lang.bind(this, this._queueRedisplay));
|
|
},
|
|
|
|
show: function() {
|
|
this._itemDragBeginId = Main.overview.connect('item-drag-begin',
|
|
Lang.bind(this, this._onDragBegin));
|
|
this._itemDragEndId = Main.overview.connect('item-drag-end',
|
|
Lang.bind(this, this._onDragEnd));
|
|
this._windowDragBeginId = Main.overview.connect('window-drag-begin',
|
|
Lang.bind(this, this._onDragBegin));
|
|
this._windowDragEndId = Main.overview.connect('window-drag-end',
|
|
Lang.bind(this, this._onDragEnd));
|
|
},
|
|
|
|
hide: function() {
|
|
Main.overview.disconnect(this._itemDragBeginId);
|
|
Main.overview.disconnect(this._itemDragEndId);
|
|
Main.overview.disconnect(this._windowDragBeginId);
|
|
Main.overview.disconnect(this._windowDragEndId);
|
|
},
|
|
|
|
_onDragBegin: function() {
|
|
this._dragMonitor = {
|
|
dragMotion: Lang.bind(this, this._onDragMotion)
|
|
};
|
|
DND.addDragMonitor(this._dragMonitor);
|
|
},
|
|
|
|
_onDragEnd: function() {
|
|
this._clearDragPlaceholder();
|
|
if (this._favRemoveTarget) {
|
|
this._favRemoveTarget.actor.destroy();
|
|
this._favRemoveTarget = null;
|
|
}
|
|
DND.removeMonitor(this._dragMonitor);
|
|
},
|
|
|
|
_onDragMotion: function(dragEvent) {
|
|
let app = null;
|
|
if (dragEvent.source instanceof AppDisplay.AppWellIcon)
|
|
app = this._appSystem.get_app(dragEvent.source.getId());
|
|
else if (dragEvent.source instanceof Workspace.WindowClone)
|
|
app = this._tracker.get_window_app(dragEvent.source.metaWindow);
|
|
else
|
|
return DND.DragMotionResult.CONTINUE;
|
|
|
|
let id = app.get_id();
|
|
|
|
let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
|
|
|
|
let srcIsFavorite = (id in favorites);
|
|
|
|
if (srcIsFavorite && this._favRemoveTarget == null) {
|
|
this._favRemoveTarget = new RemoveFavoriteIcon();
|
|
this._favRemoveTarget.icon.setIconSize(this._iconSize);
|
|
this._box.add(this._favRemoveTarget.actor);
|
|
}
|
|
|
|
let favRemoveHovered = false;
|
|
if (this._favRemoveTarget)
|
|
favRemoveHovered =
|
|
this._favRemoveTarget.actor.contains(dragEvent.targetActor);
|
|
|
|
if (!this._box.contains(dragEvent.targetActor) || favRemoveHovered)
|
|
this._clearDragPlaceholder();
|
|
|
|
if (this._favRemoveTarget)
|
|
this._favRemoveTarget.setHover(favRemoveHovered);
|
|
|
|
return DND.DragMotionResult.CONTINUE;
|
|
},
|
|
|
|
_appIdListToHash: function(apps) {
|
|
let ids = {};
|
|
for (let i = 0; i < apps.length; i++)
|
|
ids[apps[i].get_id()] = apps[i];
|
|
return ids;
|
|
},
|
|
|
|
_queueRedisplay: function () {
|
|
Main.queueDeferredWork(this._workId);
|
|
},
|
|
|
|
_addApp: function(app) {
|
|
let display = new AppDisplay.AppWellIcon(app);
|
|
display._draggable.connect('drag-begin',
|
|
Lang.bind(this, function() {
|
|
display.actor.opacity = 50;
|
|
}));
|
|
display._draggable.connect('drag-end',
|
|
Lang.bind(this, function() {
|
|
display.actor.opacity = 255;
|
|
}));
|
|
this._box.add(display.actor);
|
|
},
|
|
|
|
_redisplay: function () {
|
|
this._box.hide();
|
|
this._box.remove_all();
|
|
|
|
let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
|
|
|
|
/* hardcode here pending some design about how exactly desktop contexts behave */
|
|
let contextId = '';
|
|
|
|
let running = this._tracker.get_running_apps(contextId);
|
|
let runningIds = this._appIdListToHash(running);
|
|
|
|
for (let id in favorites) {
|
|
let app = favorites[id];
|
|
this._addApp(app);
|
|
}
|
|
|
|
for (let i = 0; i < running.length; i++) {
|
|
let app = running[i];
|
|
if (app.get_id() in favorites)
|
|
continue;
|
|
this._addApp(app);
|
|
}
|
|
if (this._placeholderText) {
|
|
this._placeholderText.destroy();
|
|
this._placeholderText = null;
|
|
}
|
|
|
|
let children = this._box.get_children();
|
|
if (children.length == 0) {
|
|
this._placeholderText = new St.Label({ text: _("Drag here to add favorites") });
|
|
this._box.add_actor(this._placeholderText);
|
|
} else if (this._maxHeight > -1) {
|
|
let iconSizes = [ 48, 32, 24, 22, 16 ];
|
|
|
|
for (let i = 0; i < iconSizes.length; i++) {
|
|
let minHeight, natHeight;
|
|
|
|
this._iconSize = iconSizes[i];
|
|
for (let j = 0; j < children.length; j++)
|
|
children[j]._delegate.icon.setIconSize(this._iconSize);
|
|
|
|
[minHeight, natHeight] = this.actor.get_preferred_height(-1);
|
|
|
|
if (natHeight <= this._maxHeight)
|
|
break;
|
|
}
|
|
}
|
|
this._box.show();
|
|
},
|
|
|
|
_clearDragPlaceholder: function() {
|
|
if (this._dragPlaceholder) {
|
|
this._dragPlaceholder.destroy();
|
|
this._dragPlaceholder = null;
|
|
this._dragPlaceholderPos = -1;
|
|
}
|
|
},
|
|
|
|
handleDragOver : function(source, actor, x, y, time) {
|
|
let app = null;
|
|
if (source instanceof AppDisplay.AppWellIcon)
|
|
app = this._appSystem.get_app(source.getId());
|
|
else if (source instanceof Workspace.WindowClone)
|
|
app = this._tracker.get_window_app(source.metaWindow);
|
|
|
|
// Don't allow favoriting of transient apps
|
|
if (app == null || app.is_transient())
|
|
return DND.DragMotionResult.NO_DROP;
|
|
|
|
let numFavorites = AppFavorites.getAppFavorites().getFavorites().length;
|
|
let numChildren = this._box.get_children().length;
|
|
let boxHeight = this._box.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) {
|
|
boxHeight -= this._dragPlaceholder.height;
|
|
numChildren--;
|
|
}
|
|
|
|
let pos = Math.round(y * numChildren / boxHeight);
|
|
|
|
if (pos != this._dragPlaceholderPos && pos <= numFavorites) {
|
|
this._dragPlaceholderPos = pos;
|
|
if (this._dragPlaceholder)
|
|
this._dragPlaceholder.destroy();
|
|
this._dragPlaceholder = new St.Bin({ style_class: 'dash-placeholder' });
|
|
this._box.insert_actor(this._dragPlaceholder, pos);
|
|
}
|
|
|
|
let id = app.get_id();
|
|
|
|
let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
|
|
|
|
let srcIsFavorite = (id in favorites);
|
|
|
|
if (srcIsFavorite)
|
|
return DND.DragMotionResult.MOVE_DROP;
|
|
|
|
return DND.DragMotionResult.COPY_DROP;
|
|
},
|
|
|
|
// Draggable target interface
|
|
acceptDrop : function(source, actor, x, y, time) {
|
|
let app = null;
|
|
if (source instanceof AppDisplay.AppWellIcon) {
|
|
app = this._appSystem.get_app(source.getId());
|
|
} else if (source instanceof Workspace.WindowClone) {
|
|
app = this._tracker.get_window_app(source.metaWindow);
|
|
}
|
|
|
|
// Don't allow favoriting of transient apps
|
|
if (app == null || app.is_transient()) {
|
|
return false;
|
|
}
|
|
|
|
let id = app.get_id();
|
|
|
|
let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
|
|
|
|
let srcIsFavorite = (id in favorites);
|
|
|
|
let favPos = 0;
|
|
let children = this._box.get_children();
|
|
for (let i = 0; i < this._dragPlaceholderPos; i++) {
|
|
let childId = children[i]._delegate.app.get_id();
|
|
if (childId == id)
|
|
continue;
|
|
if (childId in favorites)
|
|
favPos++;
|
|
}
|
|
|
|
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this,
|
|
function () {
|
|
let appFavorites = AppFavorites.getAppFavorites();
|
|
if (srcIsFavorite)
|
|
appFavorites.moveFavoriteToPos(id, favPos);
|
|
else
|
|
appFavorites.addFavoriteAtPos(id, favPos);
|
|
return false;
|
|
}));
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
Signals.addSignalMethods(Dash.prototype);
|