Rewrite Dash, remove hardcoded width/height from GenericDisplay
This patch is a near-total rewrite of the Dash. First, the dash
code moves into a separate file, dash.js.
Inside dash.js, the components are more broken up into separate
classes; in particular there's now a Pane class and a MoreLink
class. Instead of each section of the dash, when activated,
attempting to close all N-1 other sections, instead there
is the concept of a single "active pane", and when e.g. activating
the More link for documents, if we know there's an active pane
which happens to be the apps, close it.
Many redundant containers were removed from the dash, and all
manual width, height and x/y offsets are entirely gone. We move
the visual apperance closer to the design by using the view-more.svg,
etc.
To complete the removal of height/width calculations from the dash,
we also had to do the same for GenericDisplay. Also clean up
the positioning inside overlay.js so calculation of children's
positioning is inside a single function that flows from screen.width
and screen.height, so in the future we can stop passing the width
into the Dash constructor and call this once and work on screen
resizing.
2009-07-31 22:12:01 -04:00
|
|
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
|
|
|
|
const Signals = imports.signals;
|
|
|
|
const Lang = imports.lang;
|
2010-11-07 20:51:02 -05:00
|
|
|
const Meta = imports.gi.Meta;
|
2010-07-21 19:29:02 -04:00
|
|
|
const Shell = imports.gi.Shell;
|
2009-10-22 13:33:09 -04:00
|
|
|
const St = imports.gi.St;
|
2009-08-14 09:30:48 -04:00
|
|
|
const Gettext = imports.gettext.domain('gnome-shell');
|
|
|
|
const _ = Gettext.gettext;
|
Rewrite Dash, remove hardcoded width/height from GenericDisplay
This patch is a near-total rewrite of the Dash. First, the dash
code moves into a separate file, dash.js.
Inside dash.js, the components are more broken up into separate
classes; in particular there's now a Pane class and a MoreLink
class. Instead of each section of the dash, when activated,
attempting to close all N-1 other sections, instead there
is the concept of a single "active pane", and when e.g. activating
the More link for documents, if we know there's an active pane
which happens to be the apps, close it.
Many redundant containers were removed from the dash, and all
manual width, height and x/y offsets are entirely gone. We move
the visual apperance closer to the design by using the view-more.svg,
etc.
To complete the removal of height/width calculations from the dash,
we also had to do the same for GenericDisplay. Also clean up
the positioning inside overlay.js so calculation of children's
positioning is inside a single function that flows from screen.width
and screen.height, so in the future we can stop passing the width
into the Dash constructor and call this once and work on screen
resizing.
2009-07-31 22:12:01 -04:00
|
|
|
|
|
|
|
const AppDisplay = imports.ui.appDisplay;
|
2010-07-21 19:29:02 -04:00
|
|
|
const AppFavorites = imports.ui.appFavorites;
|
2010-02-07 12:59:30 -05:00
|
|
|
const DND = imports.ui.dnd;
|
2010-11-07 20:51:02 -05:00
|
|
|
const IconGrid = imports.ui.iconGrid;
|
Rewrite Dash, remove hardcoded width/height from GenericDisplay
This patch is a near-total rewrite of the Dash. First, the dash
code moves into a separate file, dash.js.
Inside dash.js, the components are more broken up into separate
classes; in particular there's now a Pane class and a MoreLink
class. Instead of each section of the dash, when activated,
attempting to close all N-1 other sections, instead there
is the concept of a single "active pane", and when e.g. activating
the More link for documents, if we know there's an active pane
which happens to be the apps, close it.
Many redundant containers were removed from the dash, and all
manual width, height and x/y offsets are entirely gone. We move
the visual apperance closer to the design by using the view-more.svg,
etc.
To complete the removal of height/width calculations from the dash,
we also had to do the same for GenericDisplay. Also clean up
the positioning inside overlay.js so calculation of children's
positioning is inside a single function that flows from screen.width
and screen.height, so in the future we can stop passing the width
into the Dash constructor and call this once and work on screen
resizing.
2009-07-31 22:12:01 -04:00
|
|
|
const Main = imports.ui.main;
|
2010-07-21 19:29:02 -04:00
|
|
|
const Workspace = imports.ui.workspace;
|
2009-11-29 17:45:30 -05:00
|
|
|
|
2010-07-21 19:29:02 -04:00
|
|
|
|
2010-11-07 20:51:02 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2010-07-21 19:29:02 -04:00
|
|
|
function Dash() {
|
|
|
|
this._init();
|
|
|
|
}
|
|
|
|
|
|
|
|
Dash.prototype = {
|
|
|
|
_init : function() {
|
|
|
|
this._menus = [];
|
|
|
|
this._menuDisplays = [];
|
|
|
|
this._maxHeight = -1;
|
2010-11-07 20:51:02 -05:00
|
|
|
this._iconSize = 48;
|
|
|
|
|
|
|
|
this._dragPlaceholder = null;
|
|
|
|
this._dragPlaceholderPos = -1;
|
|
|
|
this._favRemoveTarget = null;
|
2010-07-21 19:29:02 -04:00
|
|
|
|
|
|
|
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));
|
|
|
|
},
|
|
|
|
|
2010-11-07 20:51:02 -05:00
|
|
|
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;
|
|
|
|
},
|
|
|
|
|
2010-07-21 19:29:02 -04:00
|
|
|
_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);
|
|
|
|
},
|
|
|
|
|
2010-11-07 20:51:02 -05:00
|
|
|
_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);
|
|
|
|
},
|
|
|
|
|
2010-07-21 19:29:02 -04:00
|
|
|
_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);
|
|
|
|
|
|
|
|
for (let id in favorites) {
|
|
|
|
let app = favorites[id];
|
2010-11-07 20:51:02 -05:00
|
|
|
this._addApp(app);
|
2010-07-21 19:29:02 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i < running.length; i++) {
|
|
|
|
let app = running[i];
|
|
|
|
if (app.get_id() in favorites)
|
|
|
|
continue;
|
2010-11-07 20:51:02 -05:00
|
|
|
this._addApp(app);
|
2010-07-21 19:29:02 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
let children = this._box.get_children();
|
|
|
|
if (children.length == 0) {
|
|
|
|
this._box.add_style_pseudo_class('empty');
|
|
|
|
} else {
|
|
|
|
this._box.remove_style_pseudo_class('empty');
|
|
|
|
|
|
|
|
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();
|
|
|
|
},
|
|
|
|
|
2010-11-07 20:51:02 -05:00
|
|
|
_clearDragPlaceholder: function() {
|
|
|
|
if (this._dragPlaceholder) {
|
|
|
|
this._dragPlaceholder.destroy();
|
|
|
|
this._dragPlaceholder = null;
|
|
|
|
this._dragPlaceholderPos = -1;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2010-07-21 19:29:02 -04:00
|
|
|
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;
|
|
|
|
|
2010-11-07 20:51:02 -05:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2010-07-21 19:29:02 -04:00
|
|
|
let id = app.get_id();
|
|
|
|
|
|
|
|
let favorites = AppFavorites.getAppFavorites().getFavoriteMap();
|
|
|
|
|
|
|
|
let srcIsFavorite = (id in favorites);
|
|
|
|
|
|
|
|
if (srcIsFavorite)
|
2010-11-07 20:51:02 -05:00
|
|
|
return DND.DragMotionResult.MOVE_DROP;
|
2010-07-21 19:29:02 -04:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2010-11-07 20:51:02 -05:00
|
|
|
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);
|
2010-07-21 19:29:02 -04:00
|
|
|
return false;
|
|
|
|
}));
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Signals.addSignalMethods(Dash.prototype);
|