Merge branch 'master' into message-tray

This commit is contained in:
Dan Winship
2009-12-02 17:03:48 -05:00
57 changed files with 2071 additions and 1675 deletions

View File

@ -4,8 +4,6 @@ dist_jsui_DATA = \
altTab.js \
appDisplay.js \
appFavorites.js \
appIcon.js \
button.js \
calendar.js \
chrome.js \
dash.js \

View File

@ -11,7 +11,6 @@ const Shell = imports.gi.Shell;
const Signals = imports.signals;
const St = imports.gi.St;
const AppIcon = imports.ui.appIcon;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;
@ -438,25 +437,20 @@ SwitcherList.prototype = {
},
addItem : function(item) {
// We want the St.Bin's padding to be clickable (since it will
// be part of the highlighted background color), so we put the
// bin inside the ButtonBox rather than vice versa.
let bin = new St.Bin({ style_class: 'item-box' });
let bbox = new Shell.ButtonBox({ reactive: true });
let bbox = new St.Clickable({ style_class: 'item-box',
reactive: true });
bin.add_actor(item);
bbox.append(bin, Big.BoxPackFlags.NONE);
bbox.set_child(item);
this._list.add_actor(bbox);
let n = this._items.length;
bbox.connect('activate', Lang.bind(this, function () {
bbox.connect('clicked', Lang.bind(this, function () {
this._itemActivated(n);
}));
bbox.connect('enter-event', Lang.bind(this, function () {
this._itemEntered(n);
}));
bbox._bin = bin;
this._items.push(bbox);
},
@ -468,15 +462,15 @@ SwitcherList.prototype = {
highlight: function(index, justOutline) {
if (this._highlighted != -1)
this._items[this._highlighted]._bin.style_class = 'item-box';
this._items[this._highlighted].style_class = 'item-box';
this._highlighted = index;
if (this._highlighted != -1) {
if (justOutline)
this._items[this._highlighted]._bin.style_class = 'outlined-item-box';
this._items[this._highlighted].style_class = 'outlined-item-box';
else
this._items[this._highlighted]._bin.style_class = 'selected-item-box';
this._items[this._highlighted].style_class = 'selected-item-box';
}
},
@ -588,6 +582,22 @@ SwitcherList.prototype = {
Signals.addSignalMethods(SwitcherList.prototype);
function AppIcon(app) {
this._init(app);
}
AppIcon.prototype = {
_init: function(app) {
this.app = app;
this.actor = new St.BoxLayout({ style_class: "alt-tab-app",
vertical: true });
this._icon = this.app.create_icon_texture(POPUP_APPICON_SIZE);
this.actor.add(this._icon, { x_fill: false, y_fill: false });
this._label = new St.Label({ text: this.app.get_name() });
this.actor.add(this._label, { x_fill: false });
}
}
function AppSwitcher(apps) {
this._init(apps);
}
@ -603,8 +613,7 @@ AppSwitcher.prototype = {
let workspaceIcons = [];
let otherIcons = [];
for (let i = 0; i < apps.length; i++) {
let appIcon = new AppIcon.AppIcon({ app: apps[i],
size: POPUP_APPICON_SIZE });
let appIcon = new AppIcon(apps[i]);
// Cache the window list now; we don't handle dynamic changes here,
// and we don't want to be continually retrieving it
appIcon.cachedWindows = appIcon.app.get_windows();
@ -681,12 +690,8 @@ AppSwitcher.prototype = {
this.icons.push(appIcon);
this.addItem(appIcon.actor);
// SwitcherList creates its own Shell.ButtonBox; we want to
// avoid intercepting the events it wants.
appIcon.actor.reactive = false;
let n = this._arrows.length;
let arrow = new Shell.DrawingArea();
let arrow = new St.DrawingArea();
arrow.connect('redraw', Lang.bind(this,
function (area, texture) {
Shell.draw_box_pointer(texture, Shell.PointerDirection.DOWN,

View File

@ -9,28 +9,19 @@ const Gtk = imports.gi.Gtk;
const Shell = imports.gi.Shell;
const Lang = imports.lang;
const Signals = imports.signals;
const St = imports.gi.St;
const Mainloop = imports.mainloop;
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;
const AppFavorites = imports.ui.appFavorites;
const AppIcon = imports.ui.appIcon;
const DND = imports.ui.dnd;
const GenericDisplay = imports.ui.genericDisplay;
const Main = imports.ui.main;
const Workspaces = imports.ui.workspaces;
const ENTERED_MENU_COLOR = new Clutter.Color();
ENTERED_MENU_COLOR.from_pixel(0x00ff0022);
const WELL_DEFAULT_COLUMNS = 4;
const WELL_ITEM_MIN_HSPACING = 4;
const WELL_ITEM_VSPACING = 4;
const MENU_ARROW_SIZE = 12;
const MENU_SPACING = 7;
const MAX_ITEMS = 30;
const APPICON_SIZE = 48;
const WELL_MAX_COLUMNS = 8;
/* This class represents a single display item containing information about an application.
*
@ -86,79 +77,6 @@ AppDisplayItem.prototype = {
}
};
const MENU_UNSELECTED = 0;
const MENU_SELECTED = 1;
const MENU_ENTERED = 2;
function MenuItem(name, id) {
this._init(name, id);
}
/**
* MenuItem:
* Shows the list of menus in the sidebar.
*/
MenuItem.prototype = {
_init: function(name, id) {
this.id = id;
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
spacing: 4,
corner_radius: 4,
padding_right: 4,
padding_left: 4,
reactive: true });
this.actor.connect('button-press-event', Lang.bind(this, function (a, e) {
this.setState(MENU_SELECTED);
}));
this._text = new Clutter.Text({ color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
font_name: "Sans 14px",
text: name });
// We use individual boxes for the label and the arrow to ensure that they
// are aligned vertically. Just setting y_align: Big.BoxAlignment.CENTER
// on this.actor does not seem to achieve that.
let labelBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER,
padding: 4 });
labelBox.append(this._text, Big.BoxPackFlags.NONE);
this.actor.append(labelBox, Big.BoxPackFlags.EXPAND);
let arrowBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
this._arrow = new Shell.Arrow({ surface_width: MENU_ARROW_SIZE,
surface_height: MENU_ARROW_SIZE,
direction: Gtk.ArrowType.RIGHT,
opacity: 0 });
arrowBox.append(this._arrow, Big.BoxPackFlags.NONE);
this.actor.append(arrowBox, Big.BoxPackFlags.NONE);
},
getState: function() {
return this._state;
},
setState: function (state) {
if (state == this._state)
return;
this._state = state;
if (this._state == MENU_UNSELECTED) {
this.actor.background_color = null;
this._arrow.set_opacity(0);
} else if (this._state == MENU_ENTERED) {
this.actor.background_color = ENTERED_MENU_COLOR;
this._arrow.set_opacity(0xFF/2);
} else {
this.actor.background_color = GenericDisplay.ITEM_DISPLAY_SELECTED_BACKGROUND_COLOR;
this._arrow.set_opacity(0xFF);
}
this.emit('state-changed')
}
}
Signals.addSignalMethods(MenuItem.prototype);
/* This class represents a display containing a collection of application items.
* The applications are sorted based on their popularity by default, and based on
* their name if some search filter is applied.
@ -302,42 +220,203 @@ AppDisplay.prototype = {
Signals.addSignalMethods(AppDisplay.prototype);
function BaseWellItem(app, isFavorite, hasMenu) {
this._init(app, isFavorite, hasMenu);
function BaseWellItem(app, isFavorite) {
this._init(app, isFavorite);
}
BaseWellItem.prototype = {
__proto__: AppIcon.AppIcon.prototype,
_init : function(app, isFavorite) {
this.app = app;
_init: function(app, isFavorite) {
AppIcon.AppIcon.prototype._init.call(this, { app: app,
menuType: AppIcon.MenuType.ON_RIGHT,
glow: true });
this._glowExtendVertical = 0;
this._glowShrinkHorizontal = 0;
this.isFavorite = isFavorite;
this.actor = new St.Clickable({ style_class: 'app-well-app',
reactive: true });
this.actor._delegate = this;
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this.actor.connect('notify::mapped', Lang.bind(this, this._onMapped));
let box = new St.BoxLayout({ vertical: true });
this.actor.set_child(box);
this.actor.connect('clicked', Lang.bind(this, this._onClicked));
this._menu = null;
this.icon = this.app.create_icon_texture(APPICON_SIZE);
box.add(this.icon, { expand: true, x_fill: false, y_fill: false });
let nameBox = new Shell.GenericContainer();
nameBox.connect('get-preferred-width', Lang.bind(this, this._nameBoxGetPreferredWidth));
nameBox.connect('get-preferred-height', Lang.bind(this, this._nameBoxGetPreferredHeight));
nameBox.connect('allocate', Lang.bind(this, this._nameBoxAllocate));
this._nameBox = nameBox;
this._name = new St.Label({ text: this.app.get_name() });
this._name.clutter_text.line_alignment = Pango.Alignment.CENTER;
nameBox.add_actor(this._name);
this._glowBox = new St.BoxLayout({ style_class: 'app-well-app-glow' });
this._glowBox.connect('style-changed', Lang.bind(this, this._onStyleChanged));
this._nameBox.add_actor(this._glowBox);
this._glowBox.lower(this._name);
this._appWindowChangedId = this.app.connect('windows-changed', Lang.bind(this, this._rerenderGlow));
this._rerenderGlow();
box.add(nameBox);
this._draggable = DND.makeDraggable(this.actor, true);
this._dragStartX = null;
this._dragStartY = null;
// Do these as anonymous functions to avoid conflict with handlers in subclasses
this.actor.connect('button-press-event', Lang.bind(this, function(actor, event) {
let [stageX, stageY] = event.get_coords();
this._dragStartX = stageX;
this._dragStartY = stageY;
return false;
}));
this.actor.connect('notify::hover', Lang.bind(this, function () {
let hover = this.actor.hover;
if (!hover) {
if (this.actor.pressed && this._dragStartX != null) {
this.actor.fake_release();
this._draggable.startDrag(this._dragStartX, this._dragStartY,
Main.currentTime());
} else {
this._dragStartX = null;
this._dragStartY = null;
}
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
this.actor.connect('notify::hover', Lang.bind(this, this._onHoverChange));
},
_nameBoxGetPreferredWidth: function (nameBox, forHeight, alloc) {
let [min, natural] = this._name.get_preferred_width(forHeight);
alloc.min_size = min;
alloc.natural_size = natural;
},
_nameBoxGetPreferredHeight: function (nameBox, forWidth, alloc) {
let [min, natural] = this._name.get_preferred_height(forWidth);
alloc.min_size = min + this._glowExtendVertical * 2;
alloc.natural_size = natural + this._glowExtendVertical * 2;
},
_nameBoxAllocate: function (nameBox, box, flags) {
let childBox = new Clutter.ActorBox();
let [minWidth, naturalWidth] = this._name.get_preferred_width(-1);
let [minHeight, naturalHeight] = this._name.get_preferred_height(-1);
let availWidth = box.x2 - box.x1;
let availHeight = box.y2 - box.y1;
let targetWidth = availWidth;
let xPadding = 0;
if (naturalWidth < availWidth) {
xPadding = Math.floor((availWidth - naturalWidth) / 2);
}
childBox.x1 = xPadding;
childBox.x2 = availWidth - xPadding;
childBox.y1 = this._glowExtendVertical;
childBox.y2 = availHeight - this._glowExtendVertical;
this._name.allocate(childBox, flags);
// Now the glow
let glowPaddingHoriz = Math.max(0, xPadding - this._glowShrinkHorizontal);
glowPaddingHoriz = Math.max(this._glowShrinkHorizontal, glowPaddingHoriz);
childBox.x1 = glowPaddingHoriz;
childBox.x2 = availWidth - glowPaddingHoriz;
childBox.y1 = 0;
childBox.y2 = availHeight;
this._glowBox.allocate(childBox, flags);
},
_onDestroy: function() {
if (this._appWindowChangedId > 0)
this.app.disconnect(this._appWindowChangedId);
},
_onMapped: function() {
if (!this._queuedGlowRerender)
return;
this._queuedGlowRerender = false;
this._rerenderGlow();
},
_rerenderGlow: function() {
if (!this.actor.mapped) {
this._queuedGlowRerender = true;
return;
}
this._glowBox.destroy_children();
let glowPath = GLib.filename_to_uri(global.imagedir + 'app-well-glow.png', '');
let windows = this.app.get_windows();
for (let i = 0; i < windows.length && i < 3; i++) {
let glow = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
glowPath, -1, -1);
glow.keep_aspect_ratio = false;
this._glowBox.add(glow);
}
},
_onButtonPress: function(actor, event) {
let [stageX, stageY] = event.get_coords();
this._dragStartX = stageX;
this._dragStartY = stageY;
},
_onHoverChange: function(actor) {
let hover = this.actor.hover;
if (!hover) {
if (this.actor.pressed && this._dragStartX != null) {
this.actor.fake_release();
this._draggable.startDrag(this._dragStartX, this._dragStartY,
Main.currentTime());
} else {
this._dragStartX = null;
this._dragStartY = null;
}
}));
}
},
_onClicked: function(actor, event) {
let button = event.get_button();
if (button == 1) {
this._onActivate(event);
} else if (button == 3) {
// Don't bind to the right click here; we want left click outside the
// area to deactivate as well.
this.popupMenu(0);
}
return false;
},
_onStyleChanged: function() {
let themeNode = this._glowBox.get_theme_node();
let success, len;
[success, len] = themeNode.get_length('-shell-glow-extend-vertical', false);
if (success)
this._glowExtendVertical = len;
[success, len] = themeNode.get_length('-shell-glow-shrink-horizontal', false);
if (success)
this._glowShrinkHorizontal = len;
this.actor.queue_relayout();
},
popupMenu: function(activatingButton) {
if (!this._menu) {
this._menu = new AppIconMenu(this);
this._menu.connect('highlight-window', Lang.bind(this, function (menu, window) {
this.highlightWindow(window);
}));
this._menu.connect('activate-window', Lang.bind(this, function (menu, window) {
this.activateWindow(window);
}));
this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) {
if (isPoppedUp) {
this._onMenuPoppedUp();
} else {
this._onMenuPoppedDown();
}
}));
}
this._menu.popup(activatingButton);
return false;
},
// Default implementations; AppDisplay.RunningWellItem overrides these
highlightWindow: function(window) {
this.emit('highlight-window', window);
},
activateWindow: function(window) {
this.emit('activate-window', window);
},
shellWorkspaceLaunch : function() {
@ -353,7 +432,7 @@ BaseWellItem.prototype = {
},
getDragActor: function() {
return this.createDragActor();
return this.app.create_icon_texture(APPICON_SIZE);
},
// Returns the original icon that is being used as a source for the cloned texture
@ -362,6 +441,305 @@ BaseWellItem.prototype = {
return this.actor;
}
}
Signals.addSignalMethods(BaseWellItem.prototype);
function AppIconMenu(source) {
this._init(source);
}
AppIconMenu.prototype = {
_init: function(source) {
this._source = source;
this._arrowSize = 4; // CSS default
this._spacing = 0; // CSS default
this._dragStartX = 0;
this._dragStartY = 0;
this.actor = new Shell.GenericContainer({ reactive: true });
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
this.actor.connect('allocate', Lang.bind(this, this._allocate));
this._windowContainerBox = new St.Bin({ style_class: 'app-well-menu' });
this._windowContainer = new Shell.Menu({ orientation: Big.BoxOrientation.VERTICAL,
width: Main.overview._dash.actor.width });
this._windowContainerBox.set_child(this._windowContainer);
this._windowContainer.connect('unselected', Lang.bind(this, this._onItemUnselected));
this._windowContainer.connect('selected', Lang.bind(this, this._onItemSelected));
this._windowContainer.connect('cancelled', Lang.bind(this, this._onWindowSelectionCancelled));
this._windowContainer.connect('activate', Lang.bind(this, this._onItemActivate));
this.actor.add_actor(this._windowContainerBox);
// Stay popped up on release over application icon
this._windowContainer.set_persistent_source(this._source.actor);
// Intercept events while the menu has the pointer grab to do window-related effects
this._windowContainer.connect('enter-event', Lang.bind(this, this._onMenuEnter));
this._windowContainer.connect('leave-event', Lang.bind(this, this._onMenuLeave));
this._windowContainer.connect('button-release-event', Lang.bind(this, this._onMenuButtonRelease));
this._borderColor = new Clutter.Color();
this._backgroundColor = new Clutter.Color();
this._windowContainerBox.connect('style-changed', Lang.bind(this, this._onStyleChanged));
this._arrow = new St.DrawingArea();
this._arrow.connect('redraw', Lang.bind(this, function (area, texture) {
Shell.draw_box_pointer(texture,
Shell.PointerDirection.LEFT,
this._borderColor,
this._backgroundColor);
}));
this.actor.add_actor(this._arrow);
// Chain our visibility and lifecycle to that of the source
source.actor.connect('notify::mapped', Lang.bind(this, function () {
if (!source.actor.mapped)
this._windowContainer.popdown();
}));
source.actor.connect('destroy', Lang.bind(this, function () { this.actor.destroy(); }));
global.stage.add_actor(this.actor);
},
_getPreferredWidth: function(actor, forHeight, alloc) {
let [min, natural] = this._windowContainerBox.get_preferred_width(forHeight);
min += this._arrowSize;
natural += this._arrowSize;
alloc.min_size = min;
alloc.natural_size = natural;
},
_getPreferredHeight: function(actor, forWidth, alloc) {
let [min, natural] = this._windowContainerBox.get_preferred_height(forWidth);
alloc.min_size = min;
alloc.natural_size = natural;
},
_allocate: function(actor, box, flags) {
let childBox = new Clutter.ActorBox();
let themeNode = this._windowContainerBox.get_theme_node();
let width = box.x2 - box.x1;
let height = box.y2 - box.y1;
childBox.x1 = 0;
childBox.x2 = this._arrowSize;
childBox.y1 = Math.floor((height / 2) - (this._arrowSize / 2));
childBox.y2 = childBox.y1 + this._arrowSize;
this._arrow.allocate(childBox, flags);
// Ensure the arrow is above the border area
let border = themeNode.get_border_width(St.Side.LEFT);
childBox.x1 = this._arrowSize - border;
childBox.x2 = width;
childBox.y1 = 0;
childBox.y2 = height;
this._windowContainerBox.allocate(childBox, flags);
},
_redisplay: function() {
this._windowContainer.remove_all();
let windows = this._source.app.get_windows();
this._windowContainer.show();
let iconsDiffer = false;
let texCache = Shell.TextureCache.get_default();
if (windows.length > 0) {
let firstIcon = windows[0].mini_icon;
for (let i = 1; i < windows.length; i++) {
if (!texCache.pixbuf_equal(windows[i].mini_icon, firstIcon)) {
iconsDiffer = true;
break;
}
}
}
// Display the app windows menu items and the separator between windows
// of the current desktop and other windows.
let activeWorkspace = global.screen.get_active_workspace();
let separatorShown = windows.length > 0 && windows[0].get_workspace() != activeWorkspace;
for (let i = 0; i < windows.length; i++) {
if (!separatorShown && windows[i].get_workspace() != activeWorkspace) {
this._appendSeparator();
separatorShown = true;
}
let box = this._appendMenuItem(windows[i].title);
box._window = windows[i];
}
if (windows.length > 0)
this._appendSeparator();
let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id());
this._newWindowMenuItem = windows.length > 0 ? this._appendMenuItem(_("New Window")) : null;
if (windows.length > 0)
this._appendSeparator();
this._toggleFavoriteMenuItem = this._appendMenuItem(isFavorite ? _("Remove from Favorites")
: _("Add to Favorites"));
this._highlightedItem = null;
},
_appendSeparator: function () {
let bin = new St.Bin({ style_class: "app-well-menu-separator" });
this._windowContainer.append_separator(bin, Big.BoxPackFlags.NONE);
},
_appendMenuItem: function(labelText) {
let box = new St.BoxLayout({ style_class: 'app-well-menu-item',
reactive: true });
let label = new St.Label({ text: labelText });
box.add(label);
this._windowContainer.append(box, Big.BoxPackFlags.NONE);
return box;
},
popup: function(activatingButton) {
let [stageX, stageY] = this._source.actor.get_transformed_position();
let [stageWidth, stageHeight] = this._source.actor.get_transformed_size();
this._redisplay();
this._windowContainer.popup(activatingButton, Main.currentTime());
this.emit('popup', true);
let x, y;
x = Math.floor(stageX + stageWidth);
y = Math.floor(stageY + (stageHeight / 2) - (this.actor.height / 2));
this.actor.set_position(x, y);
this.actor.show();
},
popdown: function() {
this._windowContainer.popdown();
this.emit('popup', false);
this.actor.hide();
},
selectWindow: function(metaWindow) {
this._selectMenuItemForWindow(metaWindow);
},
_findMetaWindowForActor: function (actor) {
if (actor._delegate instanceof Workspaces.WindowClone)
return actor._delegate.metaWindow;
else if (actor.get_meta_window)
return actor.get_meta_window();
return null;
},
// This function is called while the menu has a pointer grab; what we want
// to do is see if the mouse was released over a window representation
_onMenuButtonRelease: function (actor, event) {
let metaWindow = this._findMetaWindowForActor(event.get_source());
if (metaWindow) {
this.emit('activate-window', metaWindow);
}
},
_updateHighlight: function (item) {
if (this._highlightedItem) {
this._highlightedItem.set_style_pseudo_class(null);
this.emit('highlight-window', null);
}
this._highlightedItem = item;
if (this._highlightedItem) {
item.set_style_pseudo_class('hover');
let window = this._highlightedItem._window;
if (window)
this.emit('highlight-window', window);
}
},
_selectMenuItemForWindow: function (metaWindow) {
let children = this._windowContainer.get_children();
for (let i = 0; i < children.length; i++) {
let child = children[i];
let menuMetaWindow = child._window;
if (menuMetaWindow == metaWindow)
this._updateHighlight(child);
}
},
// Called while menu has a pointer grab
_onMenuEnter: function (actor, event) {
let metaWindow = this._findMetaWindowForActor(event.get_source());
if (metaWindow) {
this._selectMenuItemForWindow(metaWindow);
}
},
// Called while menu has a pointer grab
_onMenuLeave: function (actor, event) {
let metaWindow = this._findMetaWindowForActor(event.get_source());
if (metaWindow) {
this._updateHighlight(null);
}
},
_onItemUnselected: function (actor, child) {
this._updateHighlight(null);
},
_onItemSelected: function (actor, child) {
this._updateHighlight(child);
},
_onItemActivate: function (actor, child) {
if (child._window) {
let metaWindow = child._window;
this.emit('activate-window', metaWindow);
} else if (child == this._newWindowMenuItem) {
this._source.app.launch();
this.emit('activate-window', null);
} else if (child == this._toggleFavoriteMenuItem) {
let favs = AppFavorites.getAppFavorites();
let isFavorite = favs.isFavorite(this._source.app.get_id());
if (isFavorite)
favs.removeFavorite(this._source.app.get_id());
else
favs.addFavorite(this._source.app.get_id());
}
this.popdown();
},
_onWindowSelectionCancelled: function () {
this.emit('highlight-window', null);
this.popdown();
},
_onStyleChanged: function() {
let themeNode = this._windowContainerBox.get_theme_node();
let [success, len] = themeNode.get_length('-shell-arrow-size', false);
if (success) {
this._arrowSize = len;
this.actor.queue_relayout();
}
[success, len] = themeNode.get_length('-shell-menu-spacing', false)
if (success) {
this._windowContainer.spacing = len;
}
let color = new Clutter.Color();
if (themeNode.get_background_color(color)) {
this._backgroundColor = color;
color = new Clutter.Color();
}
if (themeNode.get_border_color(St.Side.LEFT, color)) {
this._borderColor = color;
}
this._arrow.emit_redraw();
}
};
Signals.addSignalMethods(AppIconMenu.prototype);
function RunningWellItem(app, isFavorite) {
this._init(app, isFavorite);
@ -372,14 +750,9 @@ RunningWellItem.prototype = {
_init: function(app, isFavorite) {
BaseWellItem.prototype._init.call(this, app, isFavorite);
this._dragStartX = 0;
this._dragStartY = 0;
this.actor.connect('activate', Lang.bind(this, this._onActivate));
},
_onActivate: function (actor, event) {
_onActivate: function (event) {
let modifiers = Shell.get_event_state(event);
if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
@ -406,11 +779,11 @@ RunningWellItem.prototype = {
Main.overview.hide();
},
menuPoppedUp: function() {
_onMenuPoppedUp: function() {
Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(this.app.get_id());
},
menuPoppedDown: function() {
_onMenuPoppedDown: function() {
if (this._didActivateWindow)
return;
@ -427,25 +800,18 @@ InactiveWellItem.prototype = {
_init : function(app, isFavorite) {
BaseWellItem.prototype._init.call(this, app, isFavorite);
this.actor.connect('notify::pressed', Lang.bind(this, this._onPressedChanged));
this.actor.connect('activate', Lang.bind(this, this._onActivate));
},
_onPressedChanged: function() {
this.setHighlight(this.actor.pressed);
},
_onActivate: function() {
_onActivate: function(event) {
this.app.launch();
Main.overview.hide();
return true;
},
menuPoppedUp: function() {
_onMenuPoppedUp: function() {
},
menuPoppedDown: function() {
_onMenuPoppedDown: function() {
}
};
@ -455,156 +821,123 @@ function WellGrid() {
WellGrid.prototype = {
_init: function() {
this.actor = new Shell.GenericContainer();
this.actor = new St.Bin({ name: "dashAppWell" });
// Pulled from CSS, but hardcode some defaults here
this._spacing = 0;
this._item_size = 48;
this._grid = new Shell.GenericContainer();
this.actor.set_child(this._grid);
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChanged));
this._separator = new Big.Box({ height: 1 });
this.actor.add_actor(this._separator);
this._separatorIndex = 0;
this._cachedSeparatorY = 0;
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
this.actor.connect('allocate', Lang.bind(this, this._allocate));
this._grid.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
this._grid.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
this._grid.connect('allocate', Lang.bind(this, this._allocate));
},
_getPreferredWidth: function (grid, forHeight, alloc) {
let [itemMin, itemNatural] = this._getItemPreferredWidth();
let children = this._getItemChildren();
let nColumns;
if (children.length < WELL_DEFAULT_COLUMNS)
nColumns = children.length;
else
nColumns = WELL_DEFAULT_COLUMNS;
alloc.min_size = itemMin;
alloc.natural_size = itemNatural * nColumns;
let children = this._grid.get_children();
let nColumns = children.length;
let totalSpacing = Math.max(0, nColumns - 1) * this._spacing;
// Kind of a lie, but not really an issue right now. If
// we wanted to support some sort of hidden/overflow that would
// need higher level design
alloc.min_size = this._item_size;
alloc.natural_size = nColumns * this._item_size + totalSpacing;
},
_getPreferredHeight: function (grid, forWidth, alloc) {
let [rows, columns, itemWidth, itemHeight] = this._computeLayout(forWidth);
let totalVerticalSpacing = Math.max(rows - 1, 0) * WELL_ITEM_VSPACING;
let [separatorMin, separatorNatural] = this._separator.get_preferred_height(forWidth);
alloc.min_size = alloc.natural_size = rows * itemHeight + totalVerticalSpacing + separatorNatural;
let children = this._grid.get_children();
let [nColumns, usedWidth] = this._computeLayout(forWidth);
let nRows;
if (nColumns > 0)
nRows = Math.ceil(children.length / nColumns);
else
nRows = 0;
let totalSpacing = Math.max(0, nRows - 1) * this._spacing;
let height = nRows * this._item_size + totalSpacing;
alloc.min_size = height;
alloc.natural_size = height;
},
_allocate: function (grid, box, flags) {
let children = this._getItemChildren();
let children = this._grid.get_children();
let availWidth = box.x2 - box.x1;
let availHeight = box.y2 - box.y1;
let [rows, columns, itemWidth, itemHeight] = this._computeLayout(availWidth);
let [nColumns, usedWidth] = this._computeLayout(availWidth);
let [separatorMin, separatorNatural] = this._separator.get_preferred_height(-1);
let overallPaddingX = Math.floor((availWidth - usedWidth) / 2);
let x = box.x1;
let x = box.x1 + overallPaddingX;
let y = box.y1;
let columnIndex = 0;
for (let i = 0; i < children.length; i++) {
let [childMinWidth, childNaturalWidth] = children[i].get_preferred_width(-1);
let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight]
= children[i].get_preferred_size();
/* Center the item in its allocation horizontally */
let width = Math.min(itemWidth, childNaturalWidth);
let horizSpacing = (itemWidth - width) / 2;
let width = Math.min(this._item_size, childNaturalWidth);
let childXSpacing = Math.max(0, width - childNaturalWidth) / 2;
let height = Math.min(this._item_size, childNaturalHeight);
let childYSpacing = Math.max(0, height - childNaturalHeight) / 2;
let childBox = new Clutter.ActorBox();
childBox.x1 = Math.floor(x + horizSpacing);
childBox.y1 = y;
childBox.x1 = Math.floor(x + childXSpacing);
childBox.y1 = Math.floor(y + childYSpacing);
childBox.x2 = childBox.x1 + width;
childBox.y2 = childBox.y1 + itemHeight;
childBox.y2 = childBox.y1 + height;
children[i].allocate(childBox, flags);
columnIndex++;
if (columnIndex == columns) {
if (columnIndex == nColumns) {
columnIndex = 0;
}
if (columnIndex == 0) {
y += itemHeight + WELL_ITEM_VSPACING;
x = box.x1;
y += this._item_size + this._spacing;
x = box.x1 + overallPaddingX;
} else {
x += itemWidth;
x += this._item_size + this._spacing;
}
}
},
removeAll: function () {
let itemChildren = this._getItemChildren();
for (let i = 0; i < itemChildren.length; i++) {
itemChildren[i].destroy();
}
},
_getItemChildren: function () {
let children = this.actor.get_children();
children.shift();
return children;
},
_computeLayout: function (forWidth) {
let [itemMinWidth, itemNaturalWidth] = this._getItemPreferredWidth();
let columnsNatural;
let i;
let children = this._getItemChildren();
if (children.length == 0)
return [0, WELL_DEFAULT_COLUMNS, 0, 0];
let children = this._grid.get_children();
let nColumns = 0;
let usedWidth = 0;
// Big.Box will allocate us at 0x0 if we are not visible; this is probably a
// Big.Box bug but it can't be fixed because if children are skipped in allocate()
// Clutter gets confused (see http://bugzilla.openedhand.com/show_bug.cgi?id=1831)
if (forWidth <= 0) {
nColumns = WELL_DEFAULT_COLUMNS;
} else {
while (nColumns < WELL_DEFAULT_COLUMNS &&
nColumns < children.length &&
usedWidth + itemMinWidth <= forWidth) {
// By including WELL_ITEM_MIN_HSPACING in usedWidth, we are ensuring
// that the number of columns we end up with will allow the spacing
// between the columns to be at least that value.
usedWidth += itemMinWidth + WELL_ITEM_MIN_HSPACING;
nColumns++;
}
while (nColumns < WELL_MAX_COLUMNS &&
nColumns < children.length &&
(usedWidth + this._item_size <= forWidth)) {
usedWidth += this._item_size + this._spacing;
nColumns += 1;
}
if (nColumns == 0) {
log("WellGrid: couldn't fit a column in width " + forWidth);
/* FIXME - fall back to smaller icon size */
}
if (nColumns > 0)
usedWidth -= this._spacing;
let minWidth = itemMinWidth * nColumns;
let lastColumnIndex = nColumns - 1;
let rows = Math.ceil(children.length / nColumns);
let itemWidth;
if (forWidth <= 0) {
itemWidth = itemNaturalWidth;
} else {
itemWidth = Math.floor(forWidth / nColumns);
}
let itemNaturalHeight = 0;
for (let i = 0; i < children.length; i++) {
let [childMin, childNatural] = children[i].get_preferred_height(itemWidth);
if (childNatural > itemNaturalHeight)
itemNaturalHeight = childNatural;
}
return [rows, nColumns, itemWidth, itemNaturalHeight];
return [nColumns, usedWidth];
},
_getItemPreferredWidth: function () {
let children = this._getItemChildren();
let minWidth = 0;
let naturalWidth = 0;
for (let i = 0; i < children.length; i++) {
let [childMin, childNatural] = children[i].get_preferred_width(-1);
if (childMin > minWidth)
minWidth = childMin;
if (childNatural > naturalWidth)
naturalWidth = childNatural;
}
return [minWidth, naturalWidth];
_onStyleChanged: function() {
let themeNode = this.actor.get_theme_node();
let [success, len] = themeNode.get_length('spacing', false);
if (success)
this._spacing = len;
[success, len] = themeNode.get_length('-shell-grid-item-size', false);
if (success)
this._item_size = len;
this._grid.queue_relayout();
},
removeAll: function () {
this._grid.get_children().forEach(Lang.bind(this, function (child) {
child.destroy();
}));
},
addItem: function(actor) {
this._grid.add_actor(actor);
}
}
@ -671,6 +1004,7 @@ AppWell.prototype = {
let running = this._tracker.get_running_apps(contextId);
let runningIds = this._appIdListToHash(running);
let nFavorites = 0;
for (let id in favorites) {
let app = favorites[id];
let display;
@ -679,7 +1013,8 @@ AppWell.prototype = {
} else {
display = new InactiveWellItem(app, true);
}
this._grid.actor.add_actor(display.actor);
this._grid.addItem(display.actor);
nFavorites++;
}
for (let i = 0; i < running.length; i++) {
@ -687,14 +1022,12 @@ AppWell.prototype = {
if (app.get_id() in favorites)
continue;
let display = new RunningWellItem(app, false);
this._grid.actor.add_actor(display.actor);
this._grid.addItem(display.actor);
}
if (this._grid.actor.get_n_children() == 1) {
let text = new Clutter.Text({ color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
font_name: "Sans 14px",
text: _("Drag here to add favorites")});
this._grid.actor.add_actor(text);
if (running.length == 0 && nFavorites == 0) {
let text = new St.Label({ text: _("Drag here to add favorites")});
this._grid.actor.set_child(text);
}
},

View File

@ -307,7 +307,7 @@ AppIconMenu.prototype = {
this._windowContainer.connect('leave-event', Lang.bind(this, this._onMenuLeave));
this._windowContainer.connect('button-release-event', Lang.bind(this, this._onMenuButtonRelease));
this._arrow = new Shell.DrawingArea();
this._arrow = new St.DrawingArea();
this._arrow.connect('redraw', Lang.bind(this, function (area, texture) {
Shell.draw_box_pointer(texture,
this._type == MenuType.ON_RIGHT ? Shell.PointerDirection.LEFT : Shell.PointerDirection.UP,

View File

@ -1,172 +0,0 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const Big = imports.gi.Big;
const Clutter = imports.gi.Clutter;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Shell = imports.gi.Shell;
const Signals = imports.signals;
const Tweener = imports.ui.tweener;
const DEFAULT_BUTTON_COLOR = new Clutter.Color();
DEFAULT_BUTTON_COLOR.from_pixel(0xeeddcc66);
const DEFAULT_PRESSED_BUTTON_COLOR = new Clutter.Color();
DEFAULT_PRESSED_BUTTON_COLOR.from_pixel(0xccbbaa66);
const DEFAULT_TEXT_COLOR = new Clutter.Color();
DEFAULT_TEXT_COLOR.from_pixel(0x000000ff);
const DEFAULT_FONT = 'Sans Bold 16px';
// Padding on the left and right side of the button.
const SIDE_PADDING = 14;
function Button(widget, buttonColor, pressedButtonColor, textColor, font) {
this._init(widget, buttonColor, pressedButtonColor, textColor, font);
}
Button.prototype = {
_init : function(widgetOrText, buttonColor, pressedButtonColor, textColor, font) {
this._buttonColor = buttonColor
if (buttonColor == null)
this._buttonColor = DEFAULT_BUTTON_COLOR;
this._pressedButtonColor = pressedButtonColor
if (pressedButtonColor == null)
this._pressedButtonColor = DEFAULT_PRESSED_BUTTON_COLOR;
this._textColor = textColor;
if (textColor == null)
this._textColor = DEFAULT_TEXT_COLOR;
this._font = font;
if (font == null)
this._font = DEFAULT_FONT;
this._isBetweenPressAndRelease = false;
this._mouseIsOverButton = false;
this.actor = new Shell.ButtonBox({ reactive: true,
corner_radius: 5,
padding_left: SIDE_PADDING,
padding_right: SIDE_PADDING,
orientation: Big.BoxOrientation.HORIZONTAL,
y_align: Big.BoxAlignment.CENTER
});
if (typeof widgetOrText == 'string') {
this._widget = new Clutter.Text({ font_name: this._font,
color: this._textColor,
text: widgetOrText });
} else {
this._widget = widgetOrText;
}
this.actor.append(this._widget, Big.BoxPackFlags.EXPAND);
this.actor.connect('notify::hover', Lang.bind(this, this._updateColors));
this.actor.connect('notify::pressed', Lang.bind(this, this._updateColors));
this.actor.connect('notify::active', Lang.bind(this, this._updateColors));
},
_updateColors : function() {
if (this.actor.active || this.actor.pressed)
this.actor.backgroundColor = this._pressedButtonColor;
else if (this.actor.hover)
this.actor.backgroundColor = this._buttonColor;
else
this.actor.backgroundColor = null;
}
};
Signals.addSignalMethods(Button.prototype);
/* Delay before the icon should appear, in seconds after the pointer has entered the parent */
const ANIMATION_TIME = 0.25;
/* This is an icon button that fades in/out when mouse enters/leaves the parent.
* A delay is used before the fading starts. You can force it to be shown if needed.
*
* parent -- used to show/hide the button depending on mouse entering/leaving it
* size -- size in pixels of both the button and the icon it contains
* texture -- optional, must be used if the texture for the icon is already created (else, use setIconFromName)
*/
function IconButton(parent, size, texture) {
this._init(parent, size, texture);
}
IconButton.prototype = {
_init : function(parent, size, texture) {
this._size = size;
if (texture)
this.actor = texture;
else
this.actor = new Clutter.Texture({ width: this._size, height: this._size });
this.actor.set_reactive(true);
this.actor.set_opacity(0);
parent.connect("enter-event", Lang.bind(this, function(actor, event) {
this._shouldHide = false;
// Nothing to do if the cursor has come back from a child of the parent actor
if (actor.get_children().indexOf(event.get_related()) != -1)
return;
this._fadeIn();
}));
parent.connect("leave-event", Lang.bind(this, function(actor, event) {
// Nothing to do if the cursor has merely entered a child of the parent actor
if (actor.get_children().indexOf(event.get_related()) != -1)
return;
// Remember that we should not be visible to hide the button if forceShow is unset
if (this._forceShow) {
this._shouldHide = true;
return;
}
this._fadeOut();
}));
},
/// Private methods ///
setIconFromName : function(iconName) {
let iconTheme = Gtk.IconTheme.get_default();
let iconInfo = iconTheme.lookup_icon(iconName, this._size, 0);
if (!iconInfo)
return;
let iconPath = iconInfo.get_filename();
this.actor.set_from_file(iconPath);
},
// Useful if we want to show the button immediately,
// e.g. in case the mouse is already in the parent when the button is created
show : function() {
this.actor.set_opacity(255);
},
// If show is true, prevents the button from fading out
forceShow : function(show) {
this._forceShow = show;
// Hide the button if it should have been hidden under normal conditions
if (!this._forceShow && this._shouldHide) {
this._fadeOut();
}
},
/// Private methods ///
_fadeIn : function() {
Tweener.removeTweens(this.actor);
Tweener.addTween(this.actor, { opacity: 255,
time: ANIMATION_TIME,
transition :"easeInQuad" });
},
_fadeOut : function() {
Tweener.removeTweens(this.actor);
Tweener.addTween(this.actor, { opacity: 0,
time: ANIMATION_TIME,
transition :"easeOutQuad" });
}
};

View File

@ -7,6 +7,7 @@ const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const Main = imports.ui.main;
const Params = imports.misc.params;
// This manages the shell "chrome"; the UI that's visible in the
// normal mode (ie, outside the Overview), that surrounds the main
@ -54,7 +55,7 @@ Chrome.prototype = {
// addActor:
// @actor: an actor to add to the chrome layer
// @shapeActor: optional "shape actor".
// @params: (optional) additional params
//
// Adds @actor to the chrome layer and extends the input region
// and window manager struts to include it. (Window manager struts
@ -64,59 +65,45 @@ Chrome.prototype = {
// in its visibility will affect the input region, but NOT the
// struts.
//
// If @shapeActor is provided, it will be used instead of @actor
// for the input region/strut shape. (This lets you have things like
// drop shadows in @actor that don't affect the struts.) It must
// be a child of @actor. Alternatively, you can pass %null for
// @shapeActor to indicate that @actor should not affect the input
// region or struts at all.
addActor: function(actor, shapeActor) {
if (shapeActor === undefined)
shapeActor = actor;
else if (shapeActor && !this._verifyAncestry(shapeActor, actor))
throw new Error('shapeActor is not a descendent of actor');
// If %visibleInOverview is %true in @params, @actor will remain
// visible when the overview is brought up. Otherwise it will
// automatically be hidden. If %affectsStruts or %affectsInputRegion
// is %false, the actor will not have the indicated effect.
addActor: function(actor, params) {
params = Params.parse(params, { visibleInOverview: false,
affectsStruts: true,
affectsInputRegion: true });
this.nonOverviewActor.add_actor(actor);
if (shapeActor)
this._trackActor(shapeActor, true, true);
},
// setVisibleInOverview:
// @actor: an actor in the chrome layer
// @visible: Overview visibility
//
// By default, actors in the chrome layer are automatically hidden
// when the Overview is shown. This can be used to override that
// behavior
setVisibleInOverview: function(actor, visible) {
if (!this._verifyAncestry(actor, this.actor))
throw new Error('actor is not a descendent of the chrome layer');
if (visible)
actor.reparent(this.actor);
if (params.visibleInOverview)
this.actor.add_actor(actor);
else
actor.reparent(this.nonOverviewActor);
this.nonOverviewActor.add_actor(actor);
this._trackActor(actor, params.affectsInputRegion, params.affectsStruts);
},
// addInputRegionActor:
// @actor: an actor to add to the stage input region
// trackActor:
// @actor: a descendant of the chrome to begin tracking
// @params: parameters describing how to track @actor
//
// Adds @actor to the stage input region, as with addActor(), but
// for actors that are already descendants of the chrome layer.
addInputRegionActor: function(actor) {
// Tells the chrome to track @actor, which must be a descendant
// of an actor added via addActor(). This can be used to extend the
// struts or input region to cover specific children.
trackActor: function(actor, params) {
if (!this._verifyAncestry(actor, this.actor))
throw new Error('actor is not a descendent of the chrome layer');
this._trackActor(actor, true, false);
params = Params.parse(params, { affectsStruts: true,
affectsInputRegion: true });
this._trackActor(actor, params.affectsInputRegion, params.affectsStruts);
},
// removeInputRegionActor:
// @actor: an actor previously added to the stage input region
// untrackActor:
// @actor: an actor previously tracked via trackActor()
//
// Undoes the effect of addInputRegionActor()
removeInputRegionActor: function(actor) {
this._untrackActor(actor, true, false);
// Undoes the effect of trackActor()
untrackActor: function(actor) {
this._untrackActor(actor);
},
// removeActor:
@ -128,7 +115,7 @@ Chrome.prototype = {
this.nonOverviewActor.remove_actor(actor);
else
this.actor.remove_actor(actor);
this._untrackActor(actor, true, true);
this._untrackActor(actor);
},
_findActor: function(actor) {
@ -142,23 +129,13 @@ Chrome.prototype = {
_trackActor: function(actor, inputRegion, strut) {
let actorData;
let i = this._findActor(actor);
if (i != -1) {
actorData = this._trackedActors[i];
if (inputRegion)
actorData.inputRegion++;
if (strut)
actorData.strut++;
if (!inputRegion && !strut)
actorData.children++;
return;
}
if (this._findActor(actor) != -1)
throw new Error('trying to re-track existing chrome actor');
actorData = { actor: actor,
inputRegion: inputRegion ? 1 : 0,
strut: strut ? 1 : 0,
children: 0 };
inputRegion: inputRegion,
strut: strut };
actorData.visibleId = actor.connect('notify::visible',
Lang.bind(this, this._queueUpdateRegions));
@ -166,54 +143,31 @@ Chrome.prototype = {
Lang.bind(this, this._queueUpdateRegions));
actorData.parentSetId = actor.connect('parent-set',
Lang.bind(this, this._actorReparented));
// Note that destroying actor will unset its parent, so we don't
// need to connect to 'destroy' too.
this._trackedActors.push(actorData);
actor = actor.get_parent();
if (actor != this.actor && actor != this.nonOverviewActor)
this._trackActor(actor, false, false);
if (inputRegion || strut)
this._queueUpdateRegions();
this._queueUpdateRegions();
},
_untrackActor: function(actor, inputRegion, strut) {
_untrackActor: function(actor) {
let i = this._findActor(actor);
if (i == -1)
return;
let actorData = this._trackedActors[i];
if (inputRegion)
actorData.inputRegion--;
if (strut)
actorData.strut--;
if (!inputRegion && !strut)
actorData.children--;
this._trackedActors.splice(i, 1);
actor.disconnect(actorData.visibleId);
actor.disconnect(actorData.allocationId);
actor.disconnect(actorData.parentSetId);
if (actorData.inputRegion <= 0 && actorData.strut <= 0 && actorData.children <= 0) {
this._trackedActors.splice(i, 1);
actor.disconnect(actorData.visibleId);
actor.disconnect(actorData.allocationId);
actor.disconnect(actorData.parentSetId);
actor = actor.get_parent();
if (actor && actor != this.actor && actor != this.nonOverviewActor)
this._untrackActor(actor, false, false);
}
if (inputRegion || strut)
this._queueUpdateRegions();
this._queueUpdateRegions();
},
_actorReparented: function(actor, oldParent) {
if (this._verifyAncestry(actor, this.actor)) {
let newParent = actor.get_parent();
if (newParent != this.actor && newParent != this.nonOverviewActor)
this._trackActor(newParent, false, false);
}
if (oldParent != this.actor && oldParent != this.nonOverviewActor)
this._untrackActor(oldParent, false, false);
if (!this._verifyAncestry(actor, this.actor))
this._untrackActor(actor);
},
_overviewShowing: function() {

View File

@ -16,7 +16,6 @@ const AppDisplay = imports.ui.appDisplay;
const DocDisplay = imports.ui.docDisplay;
const PlaceDisplay = imports.ui.placeDisplay;
const GenericDisplay = imports.ui.genericDisplay;
const Button = imports.ui.button;
const Main = imports.ui.main;
const DEFAULT_PADDING = 4;
@ -390,14 +389,6 @@ SectionHeader.prototype = {
this._innerBox = new St.BoxLayout({ style_class: "section-header-inner" });
this.actor.set_child(this._innerBox);
this._backgroundGradient = null;
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChanged));
this.actor.connect('notify::allocation', Lang.bind(this, function (actor) {
if (!this._backgroundGradient)
return;
this._onStyleChanged();
}));
this.backLink = new BackLink();
this._innerBox.add(this.backLink.actor);
this.backLink.actor.hide();
@ -422,33 +413,6 @@ SectionHeader.prototype = {
}
},
_onStyleChanged: function () {
if (this._backgroundGradient) {
this._backgroundGradient.destroy();
}
// Manually implement the gradient
let themeNode = this.actor.get_theme_node();
let gradientTopColor = new Clutter.Color();
if (!themeNode.get_color("-shell-gradient-top", false, gradientTopColor))
return;
let gradientBottomColor = new Clutter.Color();
if (!themeNode.get_color("-shell-gradient-bottom", false, gradientBottomColor))
return;
this._backgroundGradient = Shell.create_vertical_gradient(gradientTopColor,
gradientBottomColor);
let box = this.actor.allocation;
let contentBox = new Clutter.ActorBox();
themeNode.get_content_box(box, contentBox);
let width = contentBox.x2 - contentBox.x1;
let height = contentBox.y2 - contentBox.y1;
this._backgroundGradient.set_size(width, height);
// This will set a fixed position, which puts us outside of the normal box layout
this._backgroundGradient.set_position(0, 0);
this._innerBox.add_actor(this._backgroundGradient);
this._backgroundGradient.lower_bottom();
},
setTitle : function(title) {
this.text.text = title;
},

View File

@ -13,7 +13,6 @@ const Signals = imports.signals;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Button = imports.ui.button;
const DND = imports.ui.dnd;
const Link = imports.ui.link;
const Main = imports.ui.main;
@ -43,8 +42,6 @@ const PREVIEW_BOX_CORNER_RADIUS = 10;
const PREVIEW_PLACING = 3/4;
const PREVIEW_DETAILS_MIN_WIDTH = PREVIEW_ICON_SIZE * 2;
const INFORMATION_BUTTON_SIZE = 16;
/* This is a virtual class that represents a single display item containing
* a name, a description, and an icon. It allows selecting an item and represents
* it by highlighting it with a different background color than the default.
@ -68,44 +65,14 @@ GenericDisplayItem.prototype = {
}));
let draggable = DND.makeDraggable(this.actor);
draggable.connect('drag-begin', Lang.bind(this, this._onDragBegin));
this._iconBin = new St.Bin();
this.actor.add(this._iconBin);
this._infoText = new St.BoxLayout({ style_class: "generic-display-item-text",
this._infoText = new St.BoxLayout({ style_class: 'generic-display-item-text',
vertical: true });
this.actor.add(this._infoText, { expand: true, y_fill: false });
let infoIconUri = "file://" + global.imagedir + "info.svg";
let infoIcon = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
infoIconUri,
INFORMATION_BUTTON_SIZE,
INFORMATION_BUTTON_SIZE);
this._informationButton = new Button.IconButton(this.actor, INFORMATION_BUTTON_SIZE, infoIcon);
let buttonBox = new Big.Box({ width: INFORMATION_BUTTON_SIZE + 2 * DEFAULT_PADDING,
height: INFORMATION_BUTTON_SIZE,
padding_left: DEFAULT_PADDING, padding_right: DEFAULT_PADDING,
y_align: Big.BoxAlignment.CENTER });
buttonBox.append(this._informationButton.actor, Big.BoxPackFlags.NONE);
this.actor.add(buttonBox, { x_fill: false, x_align: St.Align.END });
// Connecting to the button-press-event for the information button ensures that the actor,
// which is a draggable actor, does not get the button-press-event and doesn't initiate
// the dragging, which then prevents us from getting the button-release-event for the button.
this._informationButton.actor.connect('button-press-event',
Lang.bind(this,
function() {
return true;
}));
this._informationButton.actor.connect('button-release-event',
Lang.bind(this,
function() {
// Selects the item by highlighting it and displaying its details
this.emit('show-details');
return true;
}));
this._name = null;
this._description = null;
this._icon = null;
@ -136,16 +103,10 @@ GenericDisplayItem.prototype = {
//// Public methods ////
// Shows the information button when the item was drawn under the mouse pointer.
onDrawnUnderPointer: function() {
this._informationButton.show();
},
// Highlights the item by setting a different background color than the default
// if isSelected is true, removes the highlighting otherwise.
markSelected: function(isSelected) {
this.actor.set_style_pseudo_class(isSelected ? "selected" : null);
this._informationButton.forceShow(isSelected)
},
/*
@ -271,16 +232,9 @@ GenericDisplayItem.prototype = {
// Returns a preview icon for the item.
_createPreviewIcon: function() {
throw new Error("Not implemented");
},
}
//// Private methods ////
// Hides the information button once the item starts being dragged.
_onDragBegin : function (draggable, time) {
// For some reason, we are not getting leave-event signal when we are dragging an item,
// so we should remove the link manually.
this._informationButton.actor.hide();
}
};
Signals.addSignalMethods(GenericDisplayItem.prototype);
@ -637,28 +591,9 @@ GenericDisplay.prototype = {
this.selectFirstItem();
}
Mainloop.idle_add(Lang.bind(this, this._checkInformationIcon),
Meta.PRIORITY_BEFORE_REDRAW);
this.emit('redisplayed');
},
// Check if the pointer is over one of the items and display the information button if it is.
// We want to do this between finishing our changes to the display and the point where
// the display is redrawn.
_checkInformationIcon: function() {
let [child, x, y, mask] = Gdk.Screen.get_default().get_root_window().get_pointer();
let actor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE,
x, y);
if (actor != null) {
let item = this._findDisplayedByActor(actor);
if (item != null) {
item.onDrawnUnderPointer();
}
}
return false;
},
//// Pure virtual protected methods ////
// Performs the steps needed to have the latest information about the items.

View File

@ -13,42 +13,16 @@ const Signals = imports.signals;
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;
const Button = imports.ui.button;
const Calendar = imports.ui.calendar;
const Main = imports.ui.main;
const StatusMenu = imports.ui.statusMenu;
const PANEL_HEIGHT = 26;
const TRAY_HEIGHT = PANEL_HEIGHT - 1;
const DEFAULT_PADDING = 4;
const PANEL_ICON_SIZE = 24;
const BACKGROUND_TOP = new Clutter.Color();
BACKGROUND_TOP.from_pixel(0x161616ff);
const BACKGROUND_BOTTOM = new Clutter.Color();
BACKGROUND_BOTTOM.from_pixel(0x000000ff);
const PANEL_FOREGROUND_COLOR = new Clutter.Color();
PANEL_FOREGROUND_COLOR.from_pixel(0xffffffff);
const SN_BACKGROUND_COLOR = new Clutter.Color();
SN_BACKGROUND_COLOR.from_pixel(0xffff00a0);
const TRANSPARENT_COLOR = new Clutter.Color();
TRANSPARENT_COLOR.from_pixel(0x00000000);
// Don't make the mouse hover effect visible to the user for a menu feel.
const PANEL_BUTTON_COLOR = new Clutter.Color();
PANEL_BUTTON_COLOR.from_pixel(0x00000000);
// Lighten pressed buttons; darkening has no effect on a black background.
const PRESSED_BUTTON_BACKGROUND_COLOR = new Clutter.Color();
PRESSED_BUTTON_BACKGROUND_COLOR.from_pixel(0x324c6ffa);
const DEFAULT_FONT = 'Sans 16px';
const TRAY_PADDING = 0;
// See comments around _recomputeTraySize
const TRAY_SPACING = 14;
const TRAY_SPACING_MIN = 8;
@ -84,24 +58,14 @@ AppPanelMenu.prototype = {
this._activeSequence = null;
this._startupSequences = {};
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
spacing: DEFAULT_PADDING,
y_align: Big.BoxAlignment.CENTER });
this._iconBox = new Big.Box({ width: PANEL_ICON_SIZE, height: PANEL_ICON_SIZE,
x_align: Big.BoxAlignment.CENTER,
y_align: Big.BoxAlignment.CENTER });
this.actor.append(this._iconBox, Big.BoxPackFlags.NONE);
let labelBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
y_align: Big.BoxAlignment.CENTER });
this._label = new Clutter.Text({ font_name: DEFAULT_FONT,
color: PANEL_FOREGROUND_COLOR,
text: "" });
labelBox.append(this._label, Big.BoxPackFlags.EXPAND);
this.actor.append(labelBox, Big.BoxPackFlags.NONE);
this.actor = new St.BoxLayout({ name: 'appMenu' });
this._iconBox = new St.Bin({ name: 'appMenuIcon' });
this.actor.add(this._iconBox);
this._label = new St.Label();
this.actor.add(this._label, { expand: true, y_fill: false });
this._startupBox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
y_align: Big.BoxAlignment.CENTER });
this.actor.append(this._startupBox, Big.BoxPackFlags.NONE);
this._startupBox = new St.BoxLayout();
this.actor.add(this._startupBox);
Main.overview.connect('hiding', Lang.bind(this, function () {
this.actor.opacity = 255;
@ -145,19 +109,20 @@ AppPanelMenu.prototype = {
this._focusedApp = focusedApp;
this._activeSequence = lastSequence;
this._iconBox.remove_all();
if (this._iconBox.child != null)
this._iconBox.child.destroy();
this._iconBox.hide();
this._label.set_text('');
if (this._focusedApp != null) {
let icon = this._focusedApp.create_icon_texture(PANEL_ICON_SIZE);
this._iconBox.append(icon, Big.BoxPackFlags.NONE);
this._iconBox.set_child(icon);
this._iconBox.show();
let appName = this._focusedApp.get_name();
// Use _set_text to work around http://bugzilla.openedhand.com/show_bug.cgi?id=1851
this._label.set_text(appName);
} else if (this._activeSequence != null) {
let icon = this._activeSequence.create_icon(PANEL_ICON_SIZE);
this._iconBox.append(icon, Big.BoxPackFlags.NONE);
this._iconBox.set_child(icon);
this._iconBox.show();
this._label.set_text(this._activeSequence.get_name());
}
@ -175,32 +140,17 @@ function Panel() {
Panel.prototype = {
_init : function() {
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL
});
this.actor = new St.BoxLayout({ name: 'panel' });
this.actor._delegate = this;
let backgroundGradient = Shell.create_vertical_gradient(BACKGROUND_TOP,
BACKGROUND_BOTTOM);
this.actor.connect('notify::allocation', Lang.bind(this, function () {
let [width, height] = this.actor.get_size();
backgroundGradient.set_size(width, height);
}));
this.actor.add_actor(backgroundGradient);
this._leftBox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
y_align: Big.BoxAlignment.CENTER,
spacing: DEFAULT_PADDING,
padding_right: DEFAULT_PADDING });
this._centerBox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
y_align: Big.BoxAlignment.CENTER });
this._rightBox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
y_align: Big.BoxAlignment.CENTER,
padding_left: DEFAULT_PADDING });
this._leftBox = new St.BoxLayout({ name: 'panelLeft' });
this._centerBox = new St.BoxLayout({ name: 'panelCenter' });
this._rightBox = new St.BoxLayout({ name: 'panelRight' });
/* This box container ensures that the centerBox is positioned in the *absolute*
* center, but can be pushed aside if necessary. */
this._boxContainer = new Shell.GenericContainer();
this.actor.append(this._boxContainer, Big.BoxPackFlags.EXPAND);
this.actor.add(this._boxContainer, { expand: true });
this._boxContainer.add_actor(this._leftBox);
this._boxContainer.add_actor(this._centerBox);
this._boxContainer.add_actor(this._rightBox);
@ -274,11 +224,14 @@ Panel.prototype = {
/* Button on the left side of the panel. */
/* Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". */
this.button = new Button.Button(_("Activities"), PANEL_BUTTON_COLOR, PRESSED_BUTTON_BACKGROUND_COLOR,
PANEL_FOREGROUND_COLOR, DEFAULT_FONT);
this.button.actor.height = PANEL_HEIGHT;
let label = new St.Label({ text: _("Activities") });
this.button = new St.Clickable({ name: 'panelActivities',
style_class: 'panel-button',
reactive: true });
this.button.set_child(label);
this.button.height = PANEL_HEIGHT;
this._leftBox.append(this.button.actor, Big.BoxPackFlags.NONE);
this._leftBox.add(this.button);
// We use this flag to mark the case where the user has entered the
// hot corner and has not left both the hot corner and a surrounding
@ -286,12 +239,16 @@ Panel.prototype = {
// multiple times due to an accidental jitter.
this._hotCornerEntered = false;
this._hotCornerEnvirons = new Clutter.Rectangle({ width: 3,
this._hotCornerEnvirons = new Clutter.Rectangle({ x: 0,
y: 0,
width: 3,
height: 3,
opacity: 0,
reactive: true });
this._hotCorner = new Clutter.Rectangle({ width: 1,
this._hotCorner = new Clutter.Rectangle({ x: 0,
y: 0,
width: 1,
height: 1,
opacity: 0,
reactive: true });
@ -315,23 +272,23 @@ Panel.prototype = {
this._hotCorner.connect('leave-event',
Lang.bind(this, this._onHotCornerLeft));
this._leftBox.append(this._hotCornerEnvirons, Big.BoxPackFlags.FIXED);
this._leftBox.append(this._hotCorner, Big.BoxPackFlags.FIXED);
this._leftBox.add(this._hotCornerEnvirons);
this._leftBox.add(this._hotCorner);
let appMenu = new AppPanelMenu();
this._leftBox.append(appMenu.actor, Big.BoxPackFlags.NONE);
this._leftBox.add(appMenu.actor);
/* center */
let clockButton = new St.Button({ style_class: "panel-button",
toggle_mode: true });
this._centerBox.append(clockButton, Big.BoxPackFlags.NONE);
toggle_mode: true,
x_fill: true,
y_fill: true });
this._centerBox.add(clockButton, { y_fill: false });
clockButton.connect('clicked', Lang.bind(this, this._toggleCalendar));
this._clock = new Clutter.Text({ font_name: DEFAULT_FONT,
color: PANEL_FOREGROUND_COLOR,
text: "" });
clockButton.add_actor(this._clock);
this._clock = new St.Label();
clockButton.set_child(this._clock);
this._calendarPopup = null;
@ -340,11 +297,10 @@ Panel.prototype = {
// The tray icons live in trayBox within trayContainer.
// The trayBox is hidden when there are no tray icons.
let trayContainer = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
y_align: Big.BoxAlignment.START });
this._rightBox.append(trayContainer, Big.BoxPackFlags.NONE);
y_align: Big.BoxAlignment.CENTER });
this._rightBox.add(trayContainer);
let trayBox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
height: TRAY_HEIGHT,
padding: TRAY_PADDING,
height: PANEL_ICON_SIZE,
spacing: TRAY_SPACING });
this._trayBox = trayBox;
@ -373,37 +329,31 @@ Panel.prototype = {
}));
this._traymanager.manage_stage(global.stage);
let statusbox = new Big.Box();
let statusmenu = this._statusmenu = new StatusMenu.StatusMenu();
statusbox.append(this._statusmenu.actor, Big.BoxPackFlags.NONE);
let statusbutton = new Button.Button(statusbox,
PANEL_BUTTON_COLOR,
PRESSED_BUTTON_BACKGROUND_COLOR,
PANEL_FOREGROUND_COLOR);
statusbutton.actor.height = PANEL_HEIGHT;
statusbutton.actor.connect('button-press-event', function (b, e) {
if (e.get_button() == 1 && e.get_click_count() == 1) {
statusmenu.toggle(e);
// The statusmenu might not pop up if it couldn't get a pointer grab
if (statusmenu.isActive())
statusbutton.actor.active = true;
return true;
} else {
return false;
}
let statusbutton = new St.Clickable({ name: 'panelStatus',
style_class: 'panel-button',
reactive: true });
statusbutton.set_child(statusmenu.actor);
statusbutton.height = PANEL_HEIGHT;
statusbutton.connect('clicked', function (b, event) {
statusmenu.toggle(event);
// The statusmenu might not pop up if it couldn't get a pointer grab
if (statusmenu.isActive())
statusbutton.active = true;
return true;
});
this._rightBox.append(statusbutton.actor, Big.BoxPackFlags.NONE);
this._rightBox.add(statusbutton);
// We get a deactivated event when the popup disappears
this._statusmenu.connect('deactivated', function (sm) {
statusbutton.actor.active = false;
statusbutton.active = false;
});
// TODO: decide what to do with the rest of the panel in the Overview mode (make it fade-out, become non-reactive, etc.)
// We get into the Overview mode on button-press-event as opposed to button-release-event because eventually we'll probably
// have the Overview act like a menu that allows the user to release the mouse on the activity the user wants
// to switch to.
this.button.actor.connect('button-press-event', Lang.bind(this, function(b, e) {
if (e.get_button() == 1 && e.get_click_count() == 1 && !Main.overview.animationInProgress) {
this.button.connect('clicked', Lang.bind(this, function(b, event) {
if (!Main.overview.animationInProgress) {
this._maybeToggleOverviewOnClick();
return true;
} else {
@ -414,14 +364,13 @@ Panel.prototype = {
// pressing the System key, Alt+F1 or Esc. We want the button to be pressed in when the Overview is entered
// and to be released when it is exited regardless of how it was triggered.
Main.overview.connect('showing', Lang.bind(this, function() {
this.button.actor.active = true;
this.button.active = true;
}));
Main.overview.connect('hiding', Lang.bind(this, function() {
this.button.actor.active = false;
this.button.active = false;
}));
Main.chrome.addActor(this.actor);
Main.chrome.setVisibleInOverview(this.actor, true);
Main.chrome.addActor(this.actor, { visibleInOverview: true });
// Start the clock
this._updateClock();
@ -558,12 +507,8 @@ CalendarPopup.prototype = {
this.calendar = new Calendar.Calendar();
this.actor.add(this.calendar.actor);
// Directly adding the actor to Main.chrome.actor is a hack to
// work around the fact that there is no way to add an actor that
// affects the input region but not the shape.
// See: https://bugzilla.gnome.org/show_bug.cgi?id=597044
Main.chrome.actor.add_actor(this.actor);
Main.chrome.addInputRegionActor(this.actor);
Main.chrome.addActor(this.actor, { visibleInOverview: true,
affectsStruts: false });
this.actor.y = (panelActor.y + panelActor.height - this.actor.height);
},

View File

@ -206,7 +206,7 @@ RunDialog.prototype = {
this._commandError = false;
this._group.hide();
this._entry.text = '';
this._entry.set_text('');
Main.popModal(this._group);
}

View File

@ -331,7 +331,7 @@ WidgetBox.prototype = {
onCompleteScope: this });
this.state = this._widget.state = Widget.STATE_POPPING_OUT;
Main.chrome.addInputRegionActor(this._hbox);
Main.chrome.trackActor(this._hbox, { affectsStruts: false });
},
_popOutComplete: function() {
@ -370,7 +370,7 @@ WidgetBox.prototype = {
this._egroup.hide();
this._ebox.x = 0;
Main.chrome.removeInputRegionActor(this._hbox);
Main.chrome.untrackActor(this._hbox);
},
destroy: function() {

View File

@ -3,12 +3,14 @@
const Big = imports.gi.Big;
const Clutter = imports.gi.Clutter;
const GdkPixbuf = imports.gi.GdkPixbuf;
const Gdk = imports.gi.Gdk;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const Pango = imports.gi.Pango;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Signals = imports.signals;
const DND = imports.ui.dnd;
@ -20,10 +22,6 @@ const Tweener = imports.ui.tweener;
const FOCUS_ANIMATION_TIME = 0.15;
const WINDOWCLONE_BG_COLOR = new Clutter.Color();
WINDOWCLONE_BG_COLOR.from_pixel(0x000000f0);
const WINDOWCLONE_TITLE_COLOR = new Clutter.Color();
WINDOWCLONE_TITLE_COLOR.from_pixel(0xffffffff);
const FRAME_COLOR = new Clutter.Color();
FRAME_COLOR.from_pixel(0xffffffff);
@ -113,8 +111,6 @@ WindowClone.prototype = {
this._stackAbove = null;
this._title = null;
this.actor.connect('button-release-event',
Lang.bind(this, this._onButtonRelease));
@ -135,18 +131,6 @@ WindowClone.prototype = {
this._zooming = false;
},
setVisibleWithChrome: function(visible) {
if (visible) {
this.actor.show();
if (this._title)
this._title.show();
} else {
this.actor.hide();
if (this._title)
this._title.hide();
}
},
setStackAbove: function (actor) {
this._stackAbove = actor;
if (this._inDrag || this._zooming)
@ -157,8 +141,6 @@ WindowClone.prototype = {
destroy: function () {
this.actor.destroy();
if (this._title)
this._title.destroy();
},
_onEnter: function (actor, event) {
@ -168,8 +150,6 @@ WindowClone.prototype = {
return;
this._havePointer = true;
this._updateTitle();
},
_onLeave: function (actor, event) {
@ -179,7 +159,6 @@ WindowClone.prototype = {
return;
this._havePointer = false;
this._updateTitle();
if (this._zoomStep)
this._zoomEnd();
@ -218,6 +197,7 @@ WindowClone.prototype = {
_zoomStart : function () {
this._zooming = true;
this.emit('zoom-start');
this._zoomLightbox = new Lightbox.Lightbox(global.stage);
@ -246,6 +226,7 @@ WindowClone.prototype = {
_zoomEnd : function () {
this._zooming = false;
this.emit('zoom-end');
this.actor.reparent(this._origParent);
this.actor.raise(this._stackAbove);
@ -253,8 +234,6 @@ WindowClone.prototype = {
[this.actor.x, this.actor.y] = this._zoomLocalOrig.getPosition();
[this.actor.scale_x, this.actor.scale_y] = this._zoomLocalOrig.getScale();
this._adjustTitle();
this._zoomLightbox.destroy();
Main.overview.disconnect(this._hideEventId);
@ -273,7 +252,6 @@ WindowClone.prototype = {
_onDragBegin : function (draggable, time) {
this._inDrag = true;
this._updateTitle();
this.emit('drag-begin');
},
@ -295,86 +273,6 @@ WindowClone.prototype = {
this.actor.raise(this._stackAbove);
this.emit('drag-end');
},
// Called by Tweener
onAnimationStart : function () {
this._updateTitle();
},
// Called by Tweener
onAnimationComplete : function () {
this._updateTitle();
},
_createTitle : function () {
let window = this.realWindow;
let box = new Big.Box({ background_color : WINDOWCLONE_BG_COLOR,
y_align: Big.BoxAlignment.CENTER,
corner_radius: 5,
padding: 4,
spacing: 4,
orientation: Big.BoxOrientation.HORIZONTAL });
let title = new Clutter.Text({ color: WINDOWCLONE_TITLE_COLOR,
font_name: "Sans 12",
text: this.metaWindow.title,
ellipsize: Pango.EllipsizeMode.END
});
box.append(title, Big.BoxPackFlags.EXPAND);
// Get and cache the expected width (just the icon), with spacing, plus title
box.fullWidth = box.width;
box.hide(); // Hidden by default, show on mouseover
this._title = box;
// Make the title a sibling of the window
this.actor.get_parent().add_actor(box);
},
_adjustTitle : function () {
let title = this._title;
if (!title)
return;
let [cloneScreenWidth, cloneScreenHeight] = this.actor.get_transformed_size();
let [titleScreenWidth, titleScreenHeight] = title.get_transformed_size();
// Titles are supposed to be "full-size", so adjust its
// scale to counteract the scaling of its ancestor actors.
title.set_scale(title.width / titleScreenWidth * title.scale_x,
title.height / titleScreenHeight * title.scale_y);
title.width = Math.min(title.fullWidth, cloneScreenWidth);
let xoff = ((cloneScreenWidth - title.width) / 2) * title.scale_x;
title.set_position(this.actor.x + xoff, this.actor.y);
},
_showTitle : function () {
if (!this._title)
this._createTitle();
this._adjustTitle();
this._title.show();
this._title.raise(this.actor);
},
_hideTitle : function () {
if (!this._title)
return;
this._title.hide();
},
_updateTitle : function () {
let shouldShow = (this._havePointer &&
!this._inDrag &&
!Tweener.isTweening(this.actor));
if (shouldShow)
this._showTitle();
else
this._hideTitle();
}
};
@ -409,6 +307,207 @@ DesktopClone.prototype = {
Signals.addSignalMethods(DesktopClone.prototype);
/**
* @windowClone: Corresponding window clone
* @parentActor: The actor which will be the parent of all overlay items
* such as app icon and window caption
*/
function WindowOverlay(windowClone, parentActor) {
this._init(windowClone, parentActor);
}
WindowOverlay.prototype = {
_init : function(windowClone, parentActor) {
let metaWindow = windowClone.metaWindow;
this._windowClone = windowClone;
this._parentActor = parentActor;
let title = new St.Label({ style_class: "window-caption",
text : metaWindow.title });
title.connect('style-changed',
Lang.bind(this, this._onStyleChanged));
title.clutter_text.ellipsize = Pango.EllipsizeMode.END;
title._spacing = 0;
let button = new St.Bin({ style_class: "window-close",
reactive: true });
button.connect('style-changed',
Lang.bind(this, this._onStyleChanged));
button._overlap = 0;
windowClone.actor.connect('destroy', Lang.bind(this, this._onDestroy));
windowClone.actor.connect('notify::allocation',
Lang.bind(this, this._positionItems));
windowClone.actor.connect('enter-event',
Lang.bind(this, this._onEnter));
windowClone.actor.connect('leave-event',
Lang.bind(this, this._onLeave));
this._idleToggleCloseId = 0;
button.connect('button-release-event',
Lang.bind(this, this._closeWindow));
this._windowAddedId = 0;
windowClone.connect('zoom-start', Lang.bind(this, this.hide));
windowClone.connect('zoom-end', Lang.bind(this, this.show));
button.hide();
this.title = title;
this.closeButton = button;
parentActor.add_actor(this.title);
parentActor.add_actor(this.closeButton);
},
hide: function() {
this.closeButton.hide();
this.title.hide();
},
show: function() {
let [child, x, y, mask] = Gdk.Screen.get_default().get_root_window().get_pointer();
let actor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE,
x, y);
if (actor == this._windowClone.actor) {
this.closeButton.show();
}
this.title.show();
},
fadeIn: function() {
this.title.opacity = 0;
this.title.show();
this.title.raise_top();
Tweener.addTween(this.title,
{ opacity: 255,
time: Overview.ANIMATION_TIME,
transition: "easeOutQuad" });
},
chromeWidth: function () {
return this.closeButton.width - this.closeButton._overlap;
},
chromeHeight: function () {
return this.closeButton.height - this.closeButton._overlap +
this.title.height + this.title._spacing;
},
_positionItems: function(win) {
let button = this.closeButton;
let title = this.title;
let [x, y] = win.get_transformed_position();
let [w, h] = win.get_transformed_size();
let buttonX = x + w - button._overlap;
let buttonY = y - button.height + button._overlap;
button.set_position(Math.floor(buttonX), Math.floor(buttonY));
if (!title.fullWidth)
title.fullWidth = title.width;
title.width = Math.min(title.fullWidth, w);
let titleX = x + (w - title.width) / 2;
let titleY = y + h + title._spacing;
title.set_position(Math.floor(titleX), Math.floor(titleY));
},
_closeWindow: function(actor, event) {
let metaWindow = this._windowClone.metaWindow;
this._workspace = metaWindow.get_workspace();
this._windowAddedId = this._workspace.connect('window-added',
Lang.bind(this,
this._onWindowAdded));
metaWindow.delete(event.get_time());
},
_onWindowAdded: function(workspace, win) {
let metaWindow = this._windowClone.metaWindow;
if (win.get_transient_for() == metaWindow) {
workspace.disconnect(this._windowAddedId);
this._windowAddedId = 0;
// use an idle handler to avoid mapping problems -
// see comment in Workspace._windowAdded
Mainloop.idle_add(Lang.bind(this,
function() {
this._windowClone.emit('selected');
return false;
}));
}
},
_onDestroy: function() {
if (this._windowAddedId > 0) {
this._workspace.disconnect(this._windowAddedId);
this._windowAddedId = 0;
}
if (this._idleToggleCloseId > 0) {
Mainloop.source_remove(this._idleToggleCloseId);
this._idleToggleCloseId = 0;
}
this.title.destroy();
this.closeButton.destroy();
},
_onEnter: function() {
this.closeButton.raise_top();
this.closeButton.show();
this.emit('show-close-button');
},
_onLeave: function() {
if (this._idleToggleCloseId == 0)
this._idleToggleCloseId = Mainloop.timeout_add(750, Lang.bind(this, this._idleToggleCloseButton));
},
_idleToggleCloseButton: function() {
this._idleToggleCloseId = 0;
let [child, x, y, mask] = Gdk.Screen.get_default().get_root_window().get_pointer();
let actor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE,
x, y);
if (actor != this._windowClone.actor && actor != this.closeButton) {
this.closeButton.hide();
}
return false;
},
hideCloseButton: function() {
if (this._idleToggleCloseId > 0) {
Mainloop.source_remove(this._idleToggleCloseId);
this._idleToggleCloseId = 0;
}
this.closeButton.hide();
},
_onStyleChanged: function() {
let titleNode = this.title.get_theme_node();
let [success, len] = titleNode.get_length('-shell-caption-spacing',
false);
if (success)
this.title._spacing = len;
let closeNode = this.closeButton.get_theme_node();
let [success, len] = closeNode.get_length('-shell-close-overlap',
false);
if (success)
this.closeButton._overlap = len;
this._parentActor.queue_relayout();
}
};
Signals.addSignalMethods(WindowOverlay.prototype);
/**
* @workspaceNum: Workspace index
* @parentActor: The actor which will be the parent of this workspace;
@ -460,7 +559,7 @@ Workspace.prototype = {
// Create clones for remaining windows that should be
// visible in the Overview
this._windows = [this._desktop];
this._windowIcons = [ null ];
this._windowOverlays = [ null ];
for (let i = 0; i < windows.length; i++) {
if (this._isOverviewWindow(windows[i])) {
this._addWindowClone(windows[i]);
@ -676,13 +775,13 @@ Workspace.prototype = {
_resetCloneVisibility: function () {
for (let i = 1; i < this._windows.length; i++) {
let clone = this._windows[i];
let icon = this._windowIcons[i];
let overlay = this._windowOverlays[i];
if (!this._isCloneVisible(clone)) {
clone.setVisibleWithChrome(false);
icon.hide();
clone.actor.hide();
overlay.hide();
} else {
clone.setVisibleWithChrome(true);
clone.actor.show();
}
}
},
@ -888,14 +987,17 @@ Workspace.prototype = {
let rect = new Meta.Rectangle();
metaWindow.get_outer_rect(rect);
let desiredWidth = global.screen_width * fraction;
let desiredHeight = global.screen_height * fraction;
let scale = Math.min(desiredWidth / rect.width,
desiredHeight / rect.height,
let chromeHeight = this._windowOverlays[1].chromeHeight() / this.scale;
let chromeWidth = this._windowOverlays[1].chromeWidth() / this.scale;
let desiredWidth = (global.screen_width - chromeWidth) * fraction;
let desiredHeight = (global.screen_height - chromeHeight) * fraction;
let scale = Math.min(desiredWidth / (rect.width + chromeWidth),
desiredHeight / (rect.height + chromeHeight),
1.0 / this.scale);
let x = xCenter - 0.5 * scale * rect.width;
let y = yCenter - 0.5 * scale * rect.height;
let x = xCenter - 0.5 * scale * (rect.width + chromeWidth);
let y = yCenter - 0.5 * scale * (rect.height + chromeHeight);
return [x, y, scale];
},
@ -918,11 +1020,11 @@ Workspace.prototype = {
let metaWindow = visibleWindows[i];
let mainIndex = this._lookupIndex(metaWindow);
let clone = metaWindow._delegate;
let icon = this._windowIcons[mainIndex];
let overlay = this._windowOverlays[mainIndex];
let [x, y, scale] = this._computeWindowRelativeLayout(metaWindow, slot);
icon.hide();
overlay.hide();
Tweener.addTween(clone.actor,
{ x: x,
y: y,
@ -932,7 +1034,7 @@ Workspace.prototype = {
time: Overview.ANIMATION_TIME,
transition: "easeOutQuad",
onComplete: Lang.bind(this, function() {
this._fadeInWindowIcon(clone, icon);
overlay.fadeIn();
})
});
}
@ -956,49 +1058,20 @@ Workspace.prototype = {
}
},
_fadeInWindowIcon: function (clone, icon) {
icon.opacity = 0;
icon.show();
// This is a little messy and complicated because when we
// start the fade-in we may not have done the final positioning
// of the workspaces. (Tweener doesn't necessarily finish
// all animations before calling onComplete callbacks.)
// So we need to manually compute where the window will
// be after the workspace animation finishes.
let [parentX, parentY] = icon.get_parent().get_position();
let [cloneX, cloneY] = clone.actor.get_position();
let [cloneWidth, cloneHeight] = clone.actor.get_size();
cloneX = this.gridX + this.scale * cloneX;
cloneY = this.gridY + this.scale * cloneY;
cloneWidth = this.scale * clone.actor.scale_x * cloneWidth;
cloneHeight = this.scale * clone.actor.scale_y * cloneHeight;
// Note we only round the first part, because we're still going to be
// positioned relative to the parent. By subtracting a possibly
// non-integral parent X/Y we cancel it out.
let x = Math.round(cloneX + cloneWidth - icon.width) - parentX;
let y = Math.round(cloneY + cloneHeight - icon.height) - parentY;
icon.set_position(x, y);
icon.raise(this.actor);
Tweener.addTween(icon,
{ opacity: 255,
time: Overview.ANIMATION_TIME,
transition: "easeOutQuad" });
},
_fadeInAllIcons: function () {
_fadeInAllOverlays: function() {
for (let i = 1; i < this._windows.length; i++) {
let clone = this._windows[i];
let icon = this._windowIcons[i];
let overlay = this._windowOverlays[i];
if (this._showOnlyWindows != null && !(clone.metaWindow in this._showOnlyWindows))
continue;
this._fadeInWindowIcon(clone, icon);
overlay.fadeIn();
}
},
_hideAllIcons: function () {
for (let i = 1; i < this._windows.length; i++) {
let icon = this._windowIcons[i];
icon.hide();
_hideAllOverlays: function() {
for (let i = 1; i< this._windows.length; i++) {
let overlay = this._windowOverlays[i];
overlay.hide();
}
},
@ -1012,10 +1085,9 @@ Workspace.prototype = {
return;
let clone = this._windows[index];
let icon = this._windowIcons[index];
this._windows.splice(index, 1);
this._windowIcons.splice(index, 1);
this._windowOverlays.splice(index, 1);
// If metaWin.get_compositor_private() returned non-NULL, that
// means the window still exists (and is just being moved to
@ -1033,7 +1105,6 @@ Workspace.prototype = {
};
}
clone.destroy();
icon.destroy();
this.positionWindows(false);
this.updateRemovable();
@ -1103,7 +1174,7 @@ Workspace.prototype = {
zoomFromOverview : function() {
this.leavingOverview = true;
this._hideAllIcons();
this._hideAllOverlays();
Main.overview.connect('hidden', Lang.bind(this,
this._doneLeavingOverview));
@ -1140,7 +1211,7 @@ Workspace.prototype = {
// Animates grid shrinking/expanding when a row or column
// of workspaces is added or removed
resizeToGrid : function (oldScale) {
this._hideAllIcons();
this._hideAllOverlays();
Tweener.addTween(this.actor,
{ x: this.gridX,
y: this.gridY,
@ -1148,7 +1219,7 @@ Workspace.prototype = {
scale_y: this.scale,
time: Overview.ANIMATION_TIME,
transition: "easeOutQuad",
onComplete: Lang.bind(this, this._fadeInAllIcons)
onComplete: Lang.bind(this, this._fadeInAllOverlays)
});
},
@ -1177,7 +1248,7 @@ Workspace.prototype = {
slideOut : function(onComplete) {
let destX = this.actor.x, destY = this.actor.y;
this._hideAllIcons();
this._hideAllOverlays();
if (this.gridCol > this.gridRow)
destX = global.screen_width;
@ -1226,50 +1297,41 @@ Workspace.prototype = {
return tracker.is_window_interesting(win.get_meta_window());
},
_createWindowIcon: function(window) {
let tracker = Shell.WindowTracker.get_default()
let app = tracker.get_window_app(window.metaWindow);
let iconTexture = null;
// The design is application based, so prefer the application
// icon here if we have it. FIXME - should move this fallback code
// into ShellAppMonitor.
if (app) {
iconTexture = app.create_icon_texture(48);
} else {
let icon = window.metaWindow.icon;
iconTexture = new Clutter.Texture({ width: 48,
height: 48,
keep_aspect_ratio: true });
Shell.clutter_texture_set_from_pixbuf(iconTexture, icon);
}
return iconTexture;
},
// Create a clone of a (non-desktop) window and add it to the window list
_addWindowClone : function(win) {
let icon = this._createWindowIcon(win);
this.parentActor.add_actor(icon);
let clone = new WindowClone(win);
let overlay = new WindowOverlay(clone, this.parentActor);
clone.connect('selected',
Lang.bind(this, this._onCloneSelected));
clone.connect('drag-begin',
Lang.bind(this, function() {
icon.hide();
overlay.hide();
}));
clone.connect('drag-end',
Lang.bind(this, function() {
icon.show();
overlay.show();
}));
this.actor.add_actor(clone.actor);
overlay.connect('show-close-button', Lang.bind(this, this._onShowOverlayClose));
this._windows.push(clone);
this._windowIcons.push(icon);
this._windowOverlays.push(overlay);
return clone;
},
_onShowOverlayClose: function (windowOverlay) {
for (let i = 1; i < this._windowOverlays.length; i++) {
let overlay = this._windowOverlays[i];
if (overlay == windowOverlay)
continue;
overlay.hideCloseButton();
}
},
_computeWindowSlot : function(windowIndex, numberOfWindows) {
if (numberOfWindows in POSITIONS)
return POSITIONS[numberOfWindows][windowIndex];
@ -1345,7 +1407,10 @@ function Workspaces(width, height, x, y) {
Workspaces.prototype = {
_init : function(width, height, x, y) {
this.actor = new Clutter.Group();
this.actor = new St.Bin({ style_class: "workspaces" });
this._actor = new Clutter.Group();
this.actor.add_actor(this._actor);
this._width = width;
this._height = height;
@ -1654,9 +1719,9 @@ Workspaces.prototype = {
},
_addWorkspaceActor : function(workspaceNum) {
let workspace = new Workspace(workspaceNum, this.actor);
let workspace = new Workspace(workspaceNum, this._actor);
this._workspaces[workspaceNum] = workspace;
this.actor.add_actor(workspace.actor);
this._actor.add_actor(workspace.actor);
},
_onRestacked: function() {