[gnome-shell] Port AppWell to CSS; delete appIcon.js
- From: Colin Walters <walters src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [gnome-shell] Port AppWell to CSS; delete appIcon.js
- Date: Mon, 23 Nov 2009 19:35:09 +0000 (UTC)
commit 907fc2f067c8c831ca16ef5722026ff481b8cb72
Author: Colin Walters <walters verbum org>
Date: Thu Nov 12 17:46:59 2009 -0500
Port AppWell to CSS; delete appIcon.js
The altTab.js and app well code weren't sharing really
any functionality anymore; un-merge the appIcon code back
into appWell, and have a simple icon + text display for
altTab.
Port AppWell to St and CSS.
https://bugzilla.gnome.org/show_bug.cgi?id=602131
data/theme/gnome-shell.css | 48 +++-
js/ui/Makefile.am | 1 -
js/ui/altTab.js | 45 ++--
js/ui/appDisplay.js | 819 +++++++++++++++++++++++++++++++-------------
4 files changed, 646 insertions(+), 267 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index ab2d8ac..cc5212f 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -236,12 +236,56 @@ StTooltip {
font-weight: bold;
}
-/* AppIcon */
+/* Apps */
-.app-icon-label {
+#dashAppWell {
+ spacing: 2px;
+ -shell-grid-item-size: 74px;
+}
+
+.app-well-app {
+ border: 1px solid #080808;
+ border-radius: 2px;
+ padding: 2px;
+ width: 74px;
+ height: 74px;
font-size: 12px;
}
+.app-well-app:hover {
+ border: 1px solid #202020;
+}
+
+.app-well-app:active {
+ background-color: #1e1e1e;
+ border: 1px solid #5f5f5f;
+}
+
+.app-well-app-glow {
+ -shell-glow-extend-vertical: 3px;
+ -shell-glow-shrink-horizontal: 3px;
+}
+
+.app-well-menu {
+ border: 1px solid #5f5f5f;
+ border-radius: 4px;
+ padding: 4px;
+ background-color: rgba(0,0,0,0.9);
+ color: #ffffff;
+ -shell-arrow-width: 12px;
+ -shell-menu-spacing: 4px;
+}
+
+.app-well-menu-item:hover {
+ background-color: #1e1e1e;
+}
+
+.app-well-menu-separator {
+ padding-top: 1px;
+ border-bottom: 1px solid #5f5f5f;
+ height: 1px;
+}
+
/* Places */
.places-actions {
diff --git a/js/ui/Makefile.am b/js/ui/Makefile.am
index 4b6260c..350885e 100644
--- a/js/ui/Makefile.am
+++ b/js/ui/Makefile.am
@@ -4,7 +4,6 @@ dist_jsui_DATA = \
altTab.js \
appDisplay.js \
appFavorites.js \
- appIcon.js \
calendar.js \
chrome.js \
dash.js \
diff --git a/js/ui/altTab.js b/js/ui/altTab.js
index 78f8262..3ed39f8 100644
--- a/js/ui/altTab.js
+++ b/js/ui/altTab.js
@@ -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,16 +437,10 @@ 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 Clickable rather than vice versa.
- let bin = new St.Bin({ style_class: 'item-box' });
- let bbox = new St.Clickable({ reactive: true,
- x_fill: true,
- y_fill: true });
-
- bin.add_actor(item);
- bbox.set_child(bin);
+ let bbox = new St.Clickable({ style_class: 'item-box',
+ reactive: true });
+
+ bbox.set_child(item);
this._list.add_actor(bbox);
let n = this._items.length;
@@ -458,7 +451,6 @@ SwitcherList.prototype = {
this._itemEntered(n);
}));
- bbox._bin = bin;
this._items.push(bbox);
},
@@ -470,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';
}
},
@@ -590,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);
}
@@ -605,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();
@@ -683,10 +690,6 @@ AppSwitcher.prototype = {
this.icons.push(appIcon);
this.addItem(appIcon.actor);
- // SwitcherList creates its own St.Clickable; we want to
- // avoid intercepting the events it wants.
- appIcon.actor.reactive = false;
-
let n = this._arrows.length;
let arrow = new St.DrawingArea();
arrow.connect('redraw', Lang.bind(this,
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index c881910..8bf359d 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -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());
+ 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._dragStartX = null;
- this._dragStartY = null;
+ 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._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.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._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 */
- }
-
- let minWidth = itemMinWidth * nColumns;
+ if (nColumns > 0)
+ usedWidth -= this._spacing;
- let lastColumnIndex = nColumns - 1;
- let rows = Math.ceil(children.length / nColumns);
-
- let itemWidth;
- if (forWidth <= 0) {
- itemWidth = itemNaturalWidth;
- } else {
- itemWidth = Math.floor(forWidth / nColumns);
- }
+ return [nColumns, usedWidth];
+ },
- 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;
- }
+ _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();
+ },
- return [rows, nColumns, itemWidth, itemNaturalHeight];
+ removeAll: function () {
+ this._grid.get_children().forEach(Lang.bind(this, function (child) {
+ child.destroy();
+ }));
},
- _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];
+ 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);
}
},
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]