From 8ac97fe1a0a5530b9f09fbdf2b52cc23d46e2afc Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sat, 14 Nov 2009 18:00:54 -0500 Subject: [PATCH 01/20] [StThemeNode] Re-add erroneously deleted (out) annotations An earlier commit was overzealous in removing (out) annotations; introspection supports (out) for integral types just fine, we only need to remove them for (out) types where the caller needs to allocate a boxed type. https://bugzilla.gnome.org/show_bug.cgi?id=602131 --- src/st/st-theme-node.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/st/st-theme-node.c b/src/st/st-theme-node.c index cebd1afde..81eb6c24f 100644 --- a/src/st/st-theme-node.c +++ b/src/st/st-theme-node.c @@ -513,7 +513,7 @@ st_theme_node_get_color (StThemeNode *node, * parent's parent, and so forth. Note that if the property has a * value of 'inherit' it will be inherited even if %FALSE is passed * in for @inherit; this only affects the default behavior for inheritance. - * @value: location to store the value that was determined. + * @value: (out): location to store the value that was determined. * If the property is not found, the value in this location * will not be changed. * @@ -740,7 +740,7 @@ get_length_internal (StThemeNode *node, * parent's parent, and so forth. Note that if the property has a * value of 'inherit' it will be inherited even if %FALSE is passed * in for @inherit; this only affects the default behavior for inheritance. - * @length: location to store the length that was determined. + * @length: (out): location to store the length that was determined. * If the property is not found, the value in this location * will not be changed. The returned length is resolved * to pixels. From bb366f8fbee8174280f0921c1327d87d8a3d047e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Thu, 12 Nov 2009 01:26:52 +0100 Subject: [PATCH 02/20] Window selector refinements * Remove window icons * Add a close button to window previews * Position caption underneath the window * Port caption from Big.Box to St.Label https://bugzilla.gnome.org/show_bug.cgi?id=598324 --- data/Makefile.am | 1 + data/theme/close-window.svg | 76 ++++++++ data/theme/gnome-shell.css | 22 +++ js/ui/workspaces.js | 374 ++++++++++++++++++------------------ 4 files changed, 282 insertions(+), 191 deletions(-) create mode 100644 data/theme/close-window.svg diff --git a/data/Makefile.am b/data/Makefile.am index 912ad3dbe..765bef654 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -25,6 +25,7 @@ themedir = $(pkgdatadir)/theme dist_theme_DATA = \ theme/gnome-shell.css \ theme/close.svg \ + theme/close-window.svg \ theme/scroll-button-down.png \ theme/scroll-button-down-hover.png \ theme/scroll-button-up.png \ diff --git a/data/theme/close-window.svg b/data/theme/close-window.svg new file mode 100644 index 000000000..a15eade3b --- /dev/null +++ b/data/theme/close-window.svg @@ -0,0 +1,76 @@ + + +image/svg+xml + + \ No newline at end of file diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 351b4aadb..e161905c0 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -81,6 +81,28 @@ StTooltip { background-color: #314a6c; } +/* Overlay */ + +.workspaces { + color: white; +} + +.window-caption { + background: rgba(0,0,0,0.8); + border: 1px solid rgba(128,128,128,0.40); + border-radius: 10px; + font-size: 12px; + padding: 2px 8px; + -shell-caption-spacing: 4px; +} + +.window-close { + background-image: url("close-window.svg"); + height: 24px; + width: 24px; + -shell-close-overlap: 16px; +} + /* Dash */ #dash { diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js index 8e4a60d97..b236d8dfd 100644 --- a/js/ui/workspaces.js +++ b/js/ui/workspaces.js @@ -9,6 +9,7 @@ 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 +21,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 +110,6 @@ WindowClone.prototype = { this._stackAbove = null; - this._title = null; - this.actor.connect('button-release-event', Lang.bind(this, this._onButtonRelease)); @@ -135,18 +130,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 +140,6 @@ WindowClone.prototype = { destroy: function () { this.actor.destroy(); - if (this._title) - this._title.destroy(); }, _onEnter: function (actor, event) { @@ -168,8 +149,6 @@ WindowClone.prototype = { return; this._havePointer = true; - - this._updateTitle(); }, _onLeave: function (actor, event) { @@ -179,7 +158,6 @@ WindowClone.prototype = { return; this._havePointer = false; - this._updateTitle(); if (this._zoomStep) this._zoomEnd(); @@ -218,6 +196,7 @@ WindowClone.prototype = { _zoomStart : function () { this._zooming = true; + this.emit('zoom-start'); this._zoomLightbox = new Lightbox.Lightbox(global.stage); @@ -246,6 +225,7 @@ WindowClone.prototype = { _zoomEnd : function () { this._zooming = false; + this.emit('zoom-end'); this.actor.reparent(this._origParent); this.actor.raise(this._stackAbove); @@ -253,8 +233,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 +251,6 @@ WindowClone.prototype = { _onDragBegin : function (draggable, time) { this._inDrag = true; - this._updateTitle(); this.emit('drag-begin'); }, @@ -295,86 +272,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 +306,144 @@ 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._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('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)); + + button.connect('enter-event', Lang.bind(this, this._onEnter)); + button.connect('leave-event', Lang.bind(this, this._onLeave)); + button.connect('button-release-event', + Lang.bind(this, function(actor, event) { + metaWindow.delete(event.get_time()); + })); + + 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() { + 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" }); + }, + + destroy: function() { + this.title.destroy(); + this.closeButton.destroy(); + }, + + 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)); + }, + + _onEnter: function() { + this.closeButton.raise_top(); + this.closeButton.show(); + }, + + _onLeave: function() { + 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 +495,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 +711,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 +923,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 +956,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 +970,7 @@ Workspace.prototype = { time: Overview.ANIMATION_TIME, transition: "easeOutQuad", onComplete: Lang.bind(this, function() { - this._fadeInWindowIcon(clone, icon); + overlay.fadeIn(); }) }); } @@ -956,49 +994,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 +1021,10 @@ Workspace.prototype = { return; let clone = this._windows[index]; - let icon = this._windowIcons[index]; + let overlay = this._windowOverlays[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 +1042,7 @@ Workspace.prototype = { }; } clone.destroy(); - icon.destroy(); + overlay.destroy(); this.positionWindows(false); this.updateRemovable(); @@ -1103,7 +1112,7 @@ Workspace.prototype = { zoomFromOverview : function() { this.leavingOverview = true; - this._hideAllIcons(); + this._hideAllOverlays(); Main.overview.connect('hidden', Lang.bind(this, this._doneLeavingOverview)); @@ -1140,7 +1149,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 +1157,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 +1186,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,46 +1235,26 @@ 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); this._windows.push(clone); - this._windowIcons.push(icon); + this._windowOverlays.push(overlay); return clone; }, @@ -1345,7 +1334,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 +1646,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() { From 0bc578230ff78ef2d2b036e84b7ce09970ca7cb5 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 19 Nov 2009 19:39:00 -0500 Subject: [PATCH 03/20] [workspaces] Add an idle timeout for close button Add a grace period for the close button so if you happen to move your mouse outside the target it doesn't vanish and force a Z motion. --- js/ui/workspaces.js | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js index b236d8dfd..53fe4aa1b 100644 --- a/js/ui/workspaces.js +++ b/js/ui/workspaces.js @@ -3,6 +3,7 @@ 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; @@ -319,6 +320,7 @@ 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", @@ -341,8 +343,7 @@ WindowOverlay.prototype = { windowClone.actor.connect('leave-event', Lang.bind(this, this._onLeave)); - button.connect('enter-event', Lang.bind(this, this._onEnter)); - button.connect('leave-event', Lang.bind(this, this._onLeave)); + this._idleToggleCloseId = 0; button.connect('button-release-event', Lang.bind(this, function(actor, event) { metaWindow.delete(event.get_time()); @@ -380,6 +381,10 @@ WindowOverlay.prototype = { }, destroy: function() { + if (this._idleToggleCloseId > 0) { + Mainloop.source_remove(this._idleToggleCloseId); + this._idleToggleCloseId = 0; + } this.title.destroy(); this.closeButton.destroy(); }, @@ -416,9 +421,30 @@ WindowOverlay.prototype = { _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(); }, @@ -1253,12 +1279,23 @@ Workspace.prototype = { this.actor.add_actor(clone.actor); + overlay.connect('show-close-button', Lang.bind(this, this._onShowOverlayClose)); + this._windows.push(clone); 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]; From 7b9f5b76435c93cf7bb266737ef213046e540efb Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sat, 14 Nov 2009 16:39:22 -0500 Subject: [PATCH 04/20] Implement gradients for StWidget Rather than having gradients be individually implemented by higher level JS widgets, move basic gradient functionality into StWidget. There is prior art in WebKit for CSS gradients: http://webkit.org/blog/175/introducing-css-gradients/ However, implementing this would be quite a lot of work; all we need in the Shell design at the moment is basic horizontal/vertical linear gradients. So, the syntax now supported is: background-gradient-type: [vertical|horizontal] background-gradient-start: color; background-gradient-end: color; https://bugzilla.gnome.org/show_bug.cgi?id=602131 --- src/st/st-theme-node.c | 59 ++++++++++++++++ src/st/st-theme-node.h | 10 +++ src/st/st-widget.c | 153 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 218 insertions(+), 4 deletions(-) diff --git a/src/st/st-theme-node.c b/src/st/st-theme-node.c index 81eb6c24f..29acbd8e0 100644 --- a/src/st/st-theme-node.c +++ b/src/st/st-theme-node.c @@ -21,8 +21,13 @@ struct _StThemeNode { PangoFontDescription *font_desc; ClutterColor background_color; + /* If gradient is set, then background_color is the gradient start */ + StGradientType background_gradient_type; + ClutterColor background_gradient_end; + ClutterColor foreground_color; ClutterColor border_color[4]; + double border_width[4]; double border_radius[4]; guint padding[4]; @@ -1236,6 +1241,7 @@ ensure_background (StThemeNode *node) node->background_computed = TRUE; node->background_color = TRANSPARENT_COLOR; + node->background_gradient_type = ST_GRADIENT_NONE; ensure_properties (node); @@ -1331,6 +1337,31 @@ ensure_background (StThemeNode *node) node->background_image = NULL; } } + else if (strcmp (property_name, "-gradient-direction") == 0) + { + CRTerm *term = decl->value; + if (strcmp (term->content.str->stryng->str, "vertical") == 0) + { + node->background_gradient_type = ST_GRADIENT_VERTICAL; + } + else if (strcmp (term->content.str->stryng->str, "horizontal") == 0) + { + node->background_gradient_type = ST_GRADIENT_HORIZONTAL; + } + else + { + g_warning ("Unrecognized background-gradient-direction \"%s\"", + term->content.str->stryng->str); + } + } + else if (strcmp (property_name, "-gradient-start") == 0) + { + get_color_from_term (node, decl->value, &node->background_color); + } + else if (strcmp (property_name, "-gradient-end") == 0) + { + get_color_from_term (node, decl->value, &node->background_gradient_end); + } } } @@ -1393,6 +1424,34 @@ st_theme_node_get_foreground_color (StThemeNode *node, *color = node->foreground_color; } + +/** + * st_theme_node_get_background_gradient: + * @node: A #StThemeNode + * @type: (out): Type of gradient + * @start: Color at start of gradient + * @end: Color at end of gradient + * + * The @start and @end arguments will only be set if @type is not #ST_GRADIENT_NONE. + */ +void +st_theme_node_get_background_gradient (StThemeNode *node, + StGradientType *type, + ClutterColor *start, + ClutterColor *end) +{ + g_return_if_fail (ST_IS_THEME_NODE (node)); + + ensure_background (node); + + *type = node->background_gradient_type; + if (*type != ST_GRADIENT_NONE) + { + *start = node->background_color; + *end = node->background_gradient_end; + } +} + void st_theme_node_get_border_color (StThemeNode *node, StSide side, diff --git a/src/st/st-theme-node.h b/src/st/st-theme-node.h index abf5110a3..0f9221463 100644 --- a/src/st/st-theme-node.h +++ b/src/st/st-theme-node.h @@ -57,6 +57,12 @@ typedef enum { ST_TEXT_DECORATION_BLINK = 1 << 3 } StTextDecoration; +typedef enum { + ST_GRADIENT_NONE, + ST_GRADIENT_VERTICAL, + ST_GRADIENT_HORIZONTAL +} StGradientType; + GType st_theme_node_get_type (void) G_GNUC_CONST; StThemeNode *st_theme_node_new (StThemeContext *context, @@ -103,6 +109,10 @@ void st_theme_node_get_background_color (StThemeNode *node, ClutterColor *color); void st_theme_node_get_foreground_color (StThemeNode *node, ClutterColor *color); +void st_theme_node_get_background_gradient (StThemeNode *node, + StGradientType *type, + ClutterColor *start, + ClutterColor *end); const char *st_theme_node_get_background_image (StThemeNode *node); diff --git a/src/st/st-widget.c b/src/st/st-widget.c index 482d5c04a..ba2c7a63e 100644 --- a/src/st/st-widget.c +++ b/src/st/st-widget.c @@ -59,6 +59,9 @@ struct _StWidgetPrivate ClutterActor *background_image; ClutterColor bg_color; + StGradientType bg_gradient_type; + ClutterColor bg_gradient_end; + gboolean is_stylable : 1; gboolean has_tooltip : 1; gboolean is_style_dirty : 1; @@ -111,6 +114,7 @@ G_DEFINE_ABSTRACT_TYPE (StWidget, st_widget, CLUTTER_TYPE_ACTOR); static void st_widget_recompute_style (StWidget *widget, StThemeNode *old_theme_node); +static void st_widget_redraw_gradient (StWidget *widget); static void st_widget_set_property (GObject *gobject, @@ -258,10 +262,13 @@ st_widget_allocate (ClutterActor *actor, ClutterAllocationFlags flags) { StWidgetPrivate *priv = ST_WIDGET (actor)->priv; + StThemeNode *theme_node; ClutterActorClass *klass; ClutterGeometry area; ClutterVertex in_v, out_v; + theme_node = st_widget_get_theme_node ((StWidget*) actor); + klass = CLUTTER_ACTOR_CLASS (st_widget_parent_class); klass->allocate (actor, box, flags); @@ -298,7 +305,7 @@ st_widget_allocate (ClutterActor *actor, flags); } - if (priv->background_image) + if (priv->background_image && priv->bg_gradient_type == ST_GRADIENT_NONE) { ClutterActorBox frame_box = { 0, 0, box->x2 - box->x1, box->y2 - box->y1 @@ -353,6 +360,27 @@ st_widget_allocate (ClutterActor *actor, &frame_box, flags); } + else if (priv->bg_gradient_type != ST_GRADIENT_NONE) + { + float width, height; + ClutterActorBox frame_box, content_box; + + width = box->x2 - box->x1; + height = box->y2 - box->y1; + frame_box.x1 = frame_box.y1 = 0; + frame_box.x2 = width; + frame_box.y2 = height; + + st_theme_node_get_content_box (theme_node, &frame_box, &content_box); + + if (width > 0 && height > 0) + clutter_cairo_texture_set_surface_size (CLUTTER_CAIRO_TEXTURE (priv->background_image), + width, height); + st_widget_redraw_gradient ((StWidget*) actor); + clutter_actor_allocate (CLUTTER_ACTOR (priv->background_image), + &content_box, + flags); + } } static void @@ -526,6 +554,84 @@ st_widget_unmap (ClutterActor *actor) clutter_actor_unmap ((ClutterActor *) priv->tooltip); } +static void +draw_vertical_gradient (ClutterCairoTexture *texture, + ClutterColor *start, + ClutterColor *end) +{ + guint width, height; + cairo_t *cr; + cairo_pattern_t *pattern; + + clutter_cairo_texture_get_surface_size (texture, &width, &height); + clutter_cairo_texture_clear (texture); + cr = clutter_cairo_texture_create (texture); + + pattern = cairo_pattern_create_linear (0, 0, 0, height); + cairo_pattern_add_color_stop_rgba (pattern, 0, + start->red / 255., + start->green / 255., + start->blue / 255., + start->alpha / 255.); + cairo_pattern_add_color_stop_rgba (pattern, 1, + end->red / 255., + end->green / 255., + end->blue / 255., + end->alpha / 255.); + + cairo_rectangle (cr, 0, 0, width, height); + cairo_set_source (cr, pattern); + cairo_fill (cr); + + cairo_pattern_destroy (pattern); + cairo_destroy (cr); +} + +static void +draw_horizontal_gradient (ClutterCairoTexture *texture, + ClutterColor *start, + ClutterColor *end) +{ + guint width, height; + cairo_t *cr; + cairo_pattern_t *pattern; + + clutter_cairo_texture_get_surface_size (texture, &width, &height); + clutter_cairo_texture_clear (texture); + cr = clutter_cairo_texture_create (texture); + + pattern = cairo_pattern_create_linear (0, 0, width, 0); + cairo_pattern_add_color_stop_rgba (pattern, 0, + start->red / 255., + start->green / 255., + start->blue / 255., + start->alpha / 255.); + cairo_pattern_add_color_stop_rgba (pattern, 1, + end->red / 255., + end->green / 255., + end->blue / 255., + end->alpha / 255.); + cairo_rectangle (cr, 0, 0, width, height); + cairo_set_source (cr, pattern); + cairo_fill (cr); + + cairo_pattern_destroy (pattern); + cairo_destroy (cr); +} + +static void +st_widget_redraw_gradient (StWidget *widget) +{ + if (widget->priv->bg_gradient_type == ST_GRADIENT_VERTICAL) + draw_vertical_gradient ((ClutterCairoTexture*) widget->priv->background_image, + &widget->priv->bg_color, + &widget->priv->bg_gradient_end); + else if (widget->priv->bg_gradient_type == ST_GRADIENT_HORIZONTAL) + draw_horizontal_gradient ((ClutterCairoTexture*) widget->priv->background_image, + &widget->priv->bg_color, + &widget->priv->bg_gradient_end); +} + static void notify_children_of_style_change (ClutterContainer *container); static void @@ -562,6 +668,8 @@ st_widget_real_style_changed (StWidget *self) guint border_width = 0; guint border_radius = 0; ClutterColor border_color = { 0, }; + StGradientType gradient; + ClutterColor gradient_end; StSide side; StCorner corner; gboolean uniform_border_width; @@ -572,11 +680,29 @@ st_widget_real_style_changed (StWidget *self) theme_node = st_widget_get_theme_node (self); - st_theme_node_get_background_color (theme_node, &color); - if (!clutter_color_equal (&color, &priv->bg_color)) + st_theme_node_get_background_gradient (theme_node, &gradient, &color, &gradient_end); + + if (gradient == ST_GRADIENT_NONE) { + if (gradient != priv->bg_gradient_type) + has_changed = TRUE; + priv->bg_gradient_type = gradient; + st_theme_node_get_background_color (theme_node, &color); + if (!clutter_color_equal (&color, &priv->bg_color)) + { + priv->bg_color = color; + priv->draw_bg_color = color.alpha != 0; + has_changed = TRUE; + } + } + else if (gradient != priv->bg_gradient_type || + !clutter_color_equal (&color, &priv->bg_color) || + !clutter_color_equal (&gradient_end, &priv->bg_gradient_end)) + { + priv->bg_gradient_type = gradient; priv->bg_color = color; - priv->draw_bg_color = color.alpha != 0; + priv->bg_gradient_end = gradient_end; + priv->draw_bg_color = TRUE; has_changed = TRUE; } @@ -741,6 +867,15 @@ st_widget_real_style_changed (StWidget *self) has_changed = TRUE; relayout_needed = TRUE; } + else if (priv->bg_gradient_type != ST_GRADIENT_NONE) + { + texture = g_object_new (CLUTTER_TYPE_CAIRO_TEXTURE, NULL); + priv->background_image = CLUTTER_ACTOR (texture); + clutter_actor_set_parent (priv->background_image, + CLUTTER_ACTOR (self)); + has_changed = TRUE; + relayout_needed = TRUE; + } /* If there are any properties above that need to cause a relayout thay * should set this flag. @@ -1248,12 +1383,22 @@ static void st_widget_recompute_style (StWidget *widget, StThemeNode *old_theme_node) { + ClutterActorBox allocation_box; StThemeNode *new_theme_node = st_widget_get_theme_node (widget); + clutter_actor_get_allocation_box ((ClutterActor *) widget, &allocation_box); + if (!old_theme_node || !st_theme_node_geometry_equal (old_theme_node, new_theme_node)) clutter_actor_queue_relayout ((ClutterActor *) widget); + /* Could compare gradient values here if we hit a performance issue. + * Also, only redraw if we've been allocated. + */ + if (allocation_box.x2 - allocation_box.x1 > 0 && + allocation_box.y2 - allocation_box.y1 > 0) + st_widget_redraw_gradient (widget); + g_signal_emit (widget, signals[STYLE_CHANGED], 0); widget->priv->is_style_dirty = FALSE; } From 8c59bc71b0b6f4b68bc093977963971cb8f503a7 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 19 Nov 2009 17:04:05 -0500 Subject: [PATCH 05/20] [dash] Switch to using StWidget gradients https://bugzilla.gnome.org/show_bug.cgi?id=602131 --- data/theme/gnome-shell.css | 5 +++-- js/ui/dash.js | 35 ----------------------------------- 2 files changed, 3 insertions(+), 37 deletions(-) diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index e161905c0..16b6f76b0 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -141,8 +141,9 @@ StTooltip { .section-header { border: 1px solid #262626; - -shell-gradient-top: #161616; - -shell-gradient-bottom: #000000; + background-gradient-direction: vertical; + background-gradient-start: #161616; + background-gradient-end: #000000; font-weight: bold; font-size: 12px; } diff --git a/js/ui/dash.js b/js/ui/dash.js index 3398fce9e..3a2f5e4cb 100644 --- a/js/ui/dash.js +++ b/js/ui/dash.js @@ -390,14 +390,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 +414,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; }, From 1cc78fdf60285bc036a7c528e8e61dce63875f63 Mon Sep 17 00:00:00 2001 From: "ultrageek.lloyd" Date: Sat, 21 Nov 2009 15:50:57 -0500 Subject: [PATCH 06/20] Clear text when closing run dialogue Signed-off-by: Colin Walters --- js/ui/runDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/ui/runDialog.js b/js/ui/runDialog.js index 69c243426..487aeb11b 100644 --- a/js/ui/runDialog.js +++ b/js/ui/runDialog.js @@ -206,7 +206,7 @@ RunDialog.prototype = { this._commandError = false; this._group.hide(); - this._entry.text = ''; + this._entry.set_text(''); Main.popModal(this._group); } From efc291ac5b33677ffe25e70ecd319c0332b194ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Sat, 21 Nov 2009 06:46:02 +0100 Subject: [PATCH 07/20] Switch to confirm dialog when closing Some applications show a confirm dialog before closing, which the close button happily ignores. Detect newly created windows which are transient for the window we try to close and switch to them. https://bugzilla.gnome.org/show_bug.cgi?id=602532 --- js/ui/workspaces.js | 58 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js index 53fe4aa1b..565e557ff 100644 --- a/js/ui/workspaces.js +++ b/js/ui/workspaces.js @@ -336,6 +336,7 @@ WindowOverlay.prototype = { 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', @@ -345,10 +346,9 @@ WindowOverlay.prototype = { this._idleToggleCloseId = 0; button.connect('button-release-event', - Lang.bind(this, function(actor, event) { - metaWindow.delete(event.get_time()); - })); + 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)); @@ -380,15 +380,6 @@ WindowOverlay.prototype = { transition: "easeOutQuad" }); }, - destroy: function() { - if (this._idleToggleCloseId > 0) { - Mainloop.source_remove(this._idleToggleCloseId); - this._idleToggleCloseId = 0; - } - this.title.destroy(); - this.closeButton.destroy(); - }, - chromeWidth: function () { return this.closeButton.width - this.closeButton._overlap; }, @@ -418,6 +409,47 @@ WindowOverlay.prototype = { 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(); @@ -1047,7 +1079,6 @@ Workspace.prototype = { return; let clone = this._windows[index]; - let overlay = this._windowOverlays[index]; this._windows.splice(index, 1); this._windowOverlays.splice(index, 1); @@ -1068,7 +1099,6 @@ Workspace.prototype = { }; } clone.destroy(); - overlay.destroy(); this.positionWindows(false); this.updateRemovable(); From e9787c0f1e4a377749b2906397ccb5dac4d06e38 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Tue, 3 Nov 2009 16:05:54 -0500 Subject: [PATCH 08/20] Add params.js, for function param object parsing. --- js/misc/Makefile.am | 3 ++- js/misc/params.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 js/misc/params.js diff --git a/js/misc/Makefile.am b/js/misc/Makefile.am index 084a45cad..9118b1f5c 100644 --- a/js/misc/Makefile.am +++ b/js/misc/Makefile.am @@ -2,4 +2,5 @@ jsmiscdir = $(pkgdatadir)/js/misc dist_jsmisc_DATA = \ docInfo.js \ - format.js + format.js \ + params.js diff --git a/js/misc/params.js b/js/misc/params.js new file mode 100644 index 000000000..77c51b9e4 --- /dev/null +++ b/js/misc/params.js @@ -0,0 +1,33 @@ +/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + +// parse: +// @params: caller-provided parameter object, or %null +// @default: function-provided defaults object +// @allowExtras: whether or not to allow properties not in @default +// +// Examines @params and fills in default values from @defaults for +// any properties in @defaults that don't appear in @params. If +// @allowExtras is not %true, it will throw an error if @params +// contains any properties that aren't in @defaults. +// +// If @params is %null, this returns @defaults. +// +// Return value: the updated params +function parse(params, defaults, allowExtras) { + if (!params) + return defaults; + + if (!allowExtras) { + for (let prop in params) { + if (!(prop in defaults)) + throw new Error('Unrecognized parameter "' + prop + '"'); + } + } + + for (let prop in defaults) { + if (!(prop in params)) + params[prop] = defaults[prop]; + } + + return params; +} \ No newline at end of file From b0cb8fb85a8fac2f4d218c33b5741d306489f9cb Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Wed, 11 Nov 2009 13:11:34 -0500 Subject: [PATCH 09/20] [Chrome] clean up APIs and remove workarounds https://bugzilla.gnome.org/show_bug.cgi?id=597044 --- js/ui/chrome.js | 138 +++++++++++++++------------------------------ js/ui/panel.js | 11 +--- js/ui/widgetBox.js | 4 +- 3 files changed, 51 insertions(+), 102 deletions(-) diff --git a/js/ui/chrome.js b/js/ui/chrome.js index e5cf650c4..daa4c4dd1 100644 --- a/js/ui/chrome.js +++ b/js/ui/chrome.js @@ -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() { diff --git a/js/ui/panel.js b/js/ui/panel.js index 9e2fdb431..6d126a8c6 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -420,8 +420,7 @@ Panel.prototype = { this.button.actor.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 +557,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); }, diff --git a/js/ui/widgetBox.js b/js/ui/widgetBox.js index e12dd7376..b7dae7459 100644 --- a/js/ui/widgetBox.js +++ b/js/ui/widgetBox.js @@ -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() { From f815844eb4d9aa5bf7b53a87941cfc97921cf97f Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sat, 14 Nov 2009 16:39:22 -0500 Subject: [PATCH 10/20] Move ShellDrawingArea to StDrawingArea It's nicer to have ShellDrawingArea as a St widget so it can participate more cleanly in CSS styling, such as queuing a redraw automatically on style changes, and allowing subclasses to use CSS styling. https://bugzilla.gnome.org/show_bug.cgi?id=602131 --- js/ui/altTab.js | 2 +- js/ui/appIcon.js | 2 +- src/Makefile-st.am | 2 + src/Makefile.am | 2 - src/shell-drawing-area.c | 102 -------------------------------- src/shell-drawing-area.h | 38 ------------ src/shell-drawing.c | 96 ------------------------------ src/shell-drawing.h | 6 -- src/st/st-drawing-area.c | 125 +++++++++++++++++++++++++++++++++++++++ src/st/st-drawing-area.h | 39 ++++++++++++ 10 files changed, 168 insertions(+), 246 deletions(-) delete mode 100644 src/shell-drawing-area.c delete mode 100644 src/shell-drawing-area.h create mode 100644 src/st/st-drawing-area.c create mode 100644 src/st/st-drawing-area.h diff --git a/js/ui/altTab.js b/js/ui/altTab.js index dd7726631..add986fb8 100644 --- a/js/ui/altTab.js +++ b/js/ui/altTab.js @@ -686,7 +686,7 @@ AppSwitcher.prototype = { 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, diff --git a/js/ui/appIcon.js b/js/ui/appIcon.js index 571e05097..5202a1da6 100644 --- a/js/ui/appIcon.js +++ b/js/ui/appIcon.js @@ -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, diff --git a/src/Makefile-st.am b/src/Makefile-st.am index 9cb69f552..618e334f5 100644 --- a/src/Makefile-st.am +++ b/src/Makefile-st.am @@ -73,6 +73,7 @@ st_source_h = \ st/st-box-layout-child.h \ st/st-button.h \ st/st-clipboard.h \ + st/st-drawing-area.h \ st/st-entry.h \ st/st-im-text.h \ st/st-label.h \ @@ -109,6 +110,7 @@ st_source_c = \ st/st-box-layout-child.c \ st/st-button.c \ st/st-clipboard.c \ + st/st-drawing-area.c \ st/st-entry.c \ st/st-im-text.c \ st/st-label.c \ diff --git a/src/Makefile.am b/src/Makefile.am index 4b11e83f3..ed9ae6041 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -67,8 +67,6 @@ libgnome_shell_la_SOURCES = \ shell-button-box.h \ shell-drawing.c \ shell-drawing.h \ - shell-drawing-area.c \ - shell-drawing-area.h \ shell-embedded-window.c \ shell-embedded-window.h \ shell-embedded-window-private.h \ diff --git a/src/shell-drawing-area.c b/src/shell-drawing-area.c deleted file mode 100644 index 04505bb50..000000000 --- a/src/shell-drawing-area.c +++ /dev/null @@ -1,102 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ - -/** - * SECTION:shell-drawing-area - * @short_description: A dynamically-sized Cairo drawing area - * - * #ShellDrawingArea is similar to #ClutterCairoTexture in that - * it allows drawing via Cairo; the primary difference is that - * it is dynamically sized. To use, connect to the @redraw - * signal, and inside the signal handler, call - * clutter_cairo_texture_create() to begin drawing. - */ - -#include "shell-drawing-area.h" - -#include -#include -#include - -G_DEFINE_TYPE(ShellDrawingArea, shell_drawing_area, CLUTTER_TYPE_GROUP); - -struct _ShellDrawingAreaPrivate { - ClutterCairoTexture *texture; -}; - -/* Signals */ -enum -{ - REDRAW, - LAST_SIGNAL -}; - -static guint shell_drawing_area_signals [LAST_SIGNAL] = { 0 }; - -static void -shell_drawing_area_allocate (ClutterActor *self, - const ClutterActorBox *box, - ClutterAllocationFlags flags) -{ - ShellDrawingArea *area = SHELL_DRAWING_AREA (self); - int width = box->x2 - box->x1; - int height = box->y2 - box->y1; - ClutterActorBox child_box; - - /* Chain up directly to ClutterActor to set actor->allocation. We explicitly skip our parent class - * ClutterGroup here because we want to override the allocate function. */ - (CLUTTER_ACTOR_CLASS (g_type_class_peek (clutter_actor_get_type ())))->allocate (self, box, flags); - - child_box.x1 = 0; - child_box.x2 = width; - child_box.y1 = 0; - child_box.y2 = height; - - clutter_actor_allocate (CLUTTER_ACTOR (area->priv->texture), &child_box, flags); - if (width > 0 && height > 0) - { - clutter_cairo_texture_set_surface_size (area->priv->texture, - width, height); - g_signal_emit (G_OBJECT (self), shell_drawing_area_signals[REDRAW], 0, - area->priv->texture); - } -} - -static void -shell_drawing_area_class_init (ShellDrawingAreaClass *klass) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); - - actor_class->allocate = shell_drawing_area_allocate; - - shell_drawing_area_signals[REDRAW] = - g_signal_new ("redraw", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (ShellDrawingAreaClass, redraw), - NULL, NULL, - g_cclosure_marshal_VOID__OBJECT, - G_TYPE_NONE, 1, G_TYPE_OBJECT); - - g_type_class_add_private (gobject_class, sizeof (ShellDrawingAreaPrivate)); -} - -static void -shell_drawing_area_init (ShellDrawingArea *area) -{ - area->priv = G_TYPE_INSTANCE_GET_PRIVATE (area, SHELL_TYPE_DRAWING_AREA, - ShellDrawingAreaPrivate); - area->priv->texture = CLUTTER_CAIRO_TEXTURE (clutter_cairo_texture_new (1, 1)); - clutter_container_add_actor (CLUTTER_CONTAINER (area), CLUTTER_ACTOR (area->priv->texture)); -} - -/** - * shell_drawing_area_get_texture: - * - * Return Value: (transfer none): - */ -ClutterCairoTexture * -shell_drawing_area_get_texture (ShellDrawingArea *area) -{ - return area->priv->texture; -} diff --git a/src/shell-drawing-area.h b/src/shell-drawing-area.h deleted file mode 100644 index 9cc0f8b71..000000000 --- a/src/shell-drawing-area.h +++ /dev/null @@ -1,38 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -#ifndef __SHELL_DRAWING_AREA_H__ -#define __SHELL_DRAWING_AREA_H__ - -#include -#include - -#define SHELL_TYPE_DRAWING_AREA (shell_drawing_area_get_type ()) -#define SHELL_DRAWING_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_TYPE_DRAWING_AREA, ShellDrawingArea)) -#define SHELL_DRAWING_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_DRAWING_AREA, ShellDrawingAreaClass)) -#define SHELL_IS_DRAWING_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_TYPE_DRAWING_AREA)) -#define SHELL_IS_DRAWING_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_DRAWING_AREA)) -#define SHELL_DRAWING_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_DRAWING_AREA, ShellDrawingAreaClass)) - -typedef struct _ShellDrawingArea ShellDrawingArea; -typedef struct _ShellDrawingAreaClass ShellDrawingAreaClass; - -typedef struct _ShellDrawingAreaPrivate ShellDrawingAreaPrivate; - -struct _ShellDrawingArea -{ - ClutterGroup parent; - - ShellDrawingAreaPrivate *priv; -}; - -struct _ShellDrawingAreaClass -{ - ClutterGroupClass parent_class; - - void (*redraw) (ShellDrawingArea *area, ClutterCairoTexture *texture); -}; - -GType shell_drawing_area_get_type (void) G_GNUC_CONST; - -ClutterCairoTexture *shell_drawing_area_get_texture (ShellDrawingArea *area); - -#endif /* __SHELL_DRAWING_AREA_H__ */ diff --git a/src/shell-drawing.c b/src/shell-drawing.c index 69c47b940..78a7f54d5 100644 --- a/src/shell-drawing.c +++ b/src/shell-drawing.c @@ -3,102 +3,6 @@ #include "shell-drawing.h" #include -/** - * shell_create_vertical_gradient: - * @top: the color at the top - * @bottom: the color at the bottom - * - * Creates a vertical gradient actor. - * - * Return value: (transfer none): a #ClutterCairoTexture actor with the - * gradient. The texture actor is floating, hence (transfer none). - */ -ClutterCairoTexture * -shell_create_vertical_gradient (ClutterColor *top, - ClutterColor *bottom) -{ - ClutterCairoTexture *texture; - cairo_t *cr; - cairo_pattern_t *pattern; - - /* Draw the gradient on an 8x8 pixel texture. Because the gradient is drawn - * from the uppermost to the lowermost row, after stretching 1/16 of the - * texture height has the top color and 1/16 has the bottom color. The 8 - * pixel width is chosen for reasons related to graphics hardware internals. - */ - texture = CLUTTER_CAIRO_TEXTURE (clutter_cairo_texture_new (8, 8)); - cr = clutter_cairo_texture_create (texture); - - pattern = cairo_pattern_create_linear (0, 0, 0, 8); - cairo_pattern_add_color_stop_rgba (pattern, 0, - top->red / 255., - top->green / 255., - top->blue / 255., - top->alpha / 255.); - cairo_pattern_add_color_stop_rgba (pattern, 1, - bottom->red / 255., - bottom->green / 255., - bottom->blue / 255., - bottom->alpha / 255.); - - cairo_set_source (cr, pattern); - cairo_paint (cr); - - cairo_pattern_destroy (pattern); - cairo_destroy (cr); - - return texture; -} - -/** - * shell_create_horizontal_gradient: - * @left: the color on the left - * @right: the color on the right - * - * Creates a horizontal gradient actor. - * - * Return value: (transfer none): a #ClutterCairoTexture actor with the - * gradient. The texture actor is floating, hence (transfer none). - */ -ClutterCairoTexture * -shell_create_horizontal_gradient (ClutterColor *left, - ClutterColor *right) -{ - ClutterCairoTexture *texture; - cairo_t *cr; - cairo_pattern_t *pattern; - - /* Draw the gradient on an 8x1 pixel texture. Because the gradient is drawn - * from the left to the right column, after stretching 1/16 of the - * texture width has the left side color and 1/16 has the right side color. - * There is no reason to use the 8 pixel height that would be similar to the - * reason we are using the 8 pixel width for the vertical gradient, so we - * are just using the 1 pixel height instead. - */ - texture = CLUTTER_CAIRO_TEXTURE (clutter_cairo_texture_new (8, 1)); - cr = clutter_cairo_texture_create (texture); - - pattern = cairo_pattern_create_linear (0, 0, 8, 0); - cairo_pattern_add_color_stop_rgba (pattern, 0, - left->red / 255., - left->green / 255., - left->blue / 255., - left->alpha / 255.); - cairo_pattern_add_color_stop_rgba (pattern, 1, - right->red / 255., - right->green / 255., - right->blue / 255., - right->alpha / 255.); - - cairo_set_source (cr, pattern); - cairo_paint (cr); - - cairo_pattern_destroy (pattern); - cairo_destroy (cr); - - return texture; -} - void shell_draw_clock (ClutterCairoTexture *texture, int hour, diff --git a/src/shell-drawing.h b/src/shell-drawing.h index eae39fc38..4b48d11c2 100644 --- a/src/shell-drawing.h +++ b/src/shell-drawing.h @@ -7,12 +7,6 @@ G_BEGIN_DECLS -ClutterCairoTexture *shell_create_vertical_gradient (ClutterColor *top, - ClutterColor *bottom); - -ClutterCairoTexture *shell_create_horizontal_gradient (ClutterColor *left, - ClutterColor *right); - typedef enum { SHELL_POINTER_UP, SHELL_POINTER_DOWN, diff --git a/src/st/st-drawing-area.c b/src/st/st-drawing-area.c new file mode 100644 index 000000000..4391ac13e --- /dev/null +++ b/src/st/st-drawing-area.c @@ -0,0 +1,125 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * SECTION:st-drawing-area + * @short_description: A dynamically-sized Cairo drawing area + * + * #StDrawingArea is similar to #ClutterCairoTexture in that + * it allows drawing via Cairo; the primary difference is that + * it is dynamically sized. To use, connect to the #StDrawingArea::redraw + * signal, and inside the signal handler, call + * clutter_cairo_texture_create() to begin drawing. The + * #StDrawingArea::redraw signal will be emitted by default when the area is + * resized or the CSS style changes; you can use the + * st_drawing_area_emit_redraw() as well. + */ + +#include "st-drawing-area.h" + +#include + +G_DEFINE_TYPE(StDrawingArea, st_drawing_area, ST_TYPE_BIN); + +struct _StDrawingAreaPrivate { + ClutterCairoTexture *texture; +}; + +/* Signals */ +enum +{ + REDRAW, + LAST_SIGNAL +}; + +static guint st_drawing_area_signals [LAST_SIGNAL] = { 0 }; + +static void +st_drawing_area_allocate (ClutterActor *self, + const ClutterActorBox *box, + ClutterAllocationFlags flags) +{ + StThemeNode *theme_node; + ClutterActorBox content_box; + StDrawingArea *area = ST_DRAWING_AREA (self); + int width = box->x2 - box->x1; + int height = box->y2 - box->y1; + + (CLUTTER_ACTOR_CLASS (st_drawing_area_parent_class))->allocate (self, box, flags); + + theme_node = st_widget_get_theme_node (ST_WIDGET (self)); + + st_theme_node_get_content_box (theme_node, box, &content_box); + + if (width > 0 && height > 0) + { + clutter_cairo_texture_set_surface_size (area->priv->texture, + content_box.x2 - content_box.x1, + content_box.y2 - content_box.y1); + g_signal_emit (G_OBJECT (self), st_drawing_area_signals[REDRAW], 0, + area->priv->texture); + } +} + +static void +st_drawing_area_style_changed (StWidget *self) +{ + (ST_WIDGET_CLASS (st_drawing_area_parent_class))->style_changed (self); + + st_drawing_area_emit_redraw (ST_DRAWING_AREA (self)); +} + +static void +st_drawing_area_class_init (StDrawingAreaClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + StWidgetClass *widget_class = ST_WIDGET_CLASS (klass); + + actor_class->allocate = st_drawing_area_allocate; + widget_class->style_changed = st_drawing_area_style_changed; + + st_drawing_area_signals[REDRAW] = + g_signal_new ("redraw", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (StDrawingAreaClass, redraw), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, CLUTTER_TYPE_CAIRO_TEXTURE); + + g_type_class_add_private (gobject_class, sizeof (StDrawingAreaPrivate)); +} + +static void +st_drawing_area_init (StDrawingArea *area) +{ + area->priv = G_TYPE_INSTANCE_GET_PRIVATE (area, ST_TYPE_DRAWING_AREA, + StDrawingAreaPrivate); + area->priv->texture = CLUTTER_CAIRO_TEXTURE (clutter_cairo_texture_new (1, 1)); + clutter_container_add_actor (CLUTTER_CONTAINER (area), CLUTTER_ACTOR (area->priv->texture)); +} + +/** + * st_drawing_area_get_texture: + * + * Return Value: (transfer none): + */ +ClutterCairoTexture * +st_drawing_area_get_texture (StDrawingArea *area) +{ + return area->priv->texture; +} + +/** + * st_drawing_area_emit_redraw: + * @area: A #StDrawingArea + * + * Immediately emit a redraw signal. Useful if + * some parameters for the area being drawn other + * than the size or style have changed. + */ +void +st_drawing_area_emit_redraw (StDrawingArea *area) +{ + g_signal_emit ((GObject*)area, st_drawing_area_signals[REDRAW], 0, area->priv->texture); +} diff --git a/src/st/st-drawing-area.h b/src/st/st-drawing-area.h new file mode 100644 index 000000000..36083729e --- /dev/null +++ b/src/st/st-drawing-area.h @@ -0,0 +1,39 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#ifndef __ST_DRAWING_AREA_H__ +#define __ST_DRAWING_AREA_H__ + +#include "st-bin.h" + +#define ST_TYPE_DRAWING_AREA (st_drawing_area_get_type ()) +#define ST_DRAWING_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_DRAWING_AREA, StDrawingArea)) +#define ST_DRAWING_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_DRAWING_AREA, StDrawingAreaClass)) +#define ST_IS_DRAWING_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_DRAWING_AREA)) +#define ST_IS_DRAWING_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_DRAWING_AREA)) +#define ST_DRAWING_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_DRAWING_AREA, StDrawingAreaClass)) + +typedef struct _StDrawingArea StDrawingArea; +typedef struct _StDrawingAreaClass StDrawingAreaClass; + +typedef struct _StDrawingAreaPrivate StDrawingAreaPrivate; + +struct _StDrawingArea +{ + StBin parent; + + StDrawingAreaPrivate *priv; +}; + +struct _StDrawingAreaClass +{ + StBinClass parent_class; + + void (*redraw) (StDrawingArea *area, ClutterCairoTexture *texture); +}; + +GType st_drawing_area_get_type (void) G_GNUC_CONST; + +ClutterCairoTexture *st_drawing_area_get_texture (StDrawingArea *area); + +void st_drawing_area_emit_redraw (StDrawingArea *area); + +#endif /* __ST_DRAWING_AREA_H__ */ From 93f3412f70fb3cd00c7df04975d5b3ae875a8bb3 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Mon, 16 Nov 2009 13:28:51 -0500 Subject: [PATCH 11/20] Import ShellButtonBox as StClickable Now a StBin, and add hover/active style properties. Also, add the event to the CLICKED signal. Otherwise a straightforward namespace transformation. https://bugzilla.gnome.org/show_bug.cgi?id=602131 --- src/Makefile-st.am | 2 + src/st/st-clickable.c | 330 ++++++++++++++++++++++++++++++++++++++++++ src/st/st-clickable.h | 35 +++++ 3 files changed, 367 insertions(+) create mode 100644 src/st/st-clickable.c create mode 100644 src/st/st-clickable.h diff --git a/src/Makefile-st.am b/src/Makefile-st.am index 618e334f5..a6a85f12e 100644 --- a/src/Makefile-st.am +++ b/src/Makefile-st.am @@ -72,6 +72,7 @@ st_source_h = \ st/st-box-layout.h \ st/st-box-layout-child.h \ st/st-button.h \ + st/st-clickable.h \ st/st-clipboard.h \ st/st-drawing-area.h \ st/st-entry.h \ @@ -109,6 +110,7 @@ st_source_c = \ st/st-box-layout.c \ st/st-box-layout-child.c \ st/st-button.c \ + st/st-clickable.c \ st/st-clipboard.c \ st/st-drawing-area.c \ st/st-entry.c \ diff --git a/src/st/st-clickable.c b/src/st/st-clickable.c new file mode 100644 index 000000000..bc5ad4994 --- /dev/null +++ b/src/st/st-clickable.c @@ -0,0 +1,330 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * SECTION:st-clickable + * @short_description: A bin with methods and properties useful for implementing buttons + * + * A #StBin subclass which translates lower-level Clutter button events + * into higher level properties which are useful for implementing "button-like" + * actors. + */ + +#include "st-clickable.h" + +G_DEFINE_TYPE (StClickable, st_clickable, ST_TYPE_BIN); + +struct _StClickablePrivate { + gboolean active; + gboolean held; + gboolean hover; + gboolean pressed; + + guint initiating_button; +}; + +/* Signals */ +enum +{ + CLICKED, + LAST_SIGNAL +}; + +enum { + PROP_0, + + PROP_ACTIVE, + PROP_HOVER, + PROP_PRESSED, +}; + +static guint st_clickable_signals [LAST_SIGNAL] = { 0 }; + +static void +sync_pseudo_class (StClickable *self) +{ + st_widget_set_style_pseudo_class (ST_WIDGET (self), + (self->priv->pressed || self->priv->active) ? "pressed" : + (self->priv->hover ? "hover" : NULL)); +} + +static void +set_active (StClickable *self, + gboolean active) +{ + if (self->priv->active == active) + return; + self->priv->active = active; + sync_pseudo_class (self); + g_object_notify (G_OBJECT (self), "active"); +} + +static void +set_hover (StClickable *self, + gboolean hover) +{ + if (self->priv->hover == hover) + return; + self->priv->hover = hover; + sync_pseudo_class (self); + g_object_notify (G_OBJECT (self), "hover"); +} + +static void +set_pressed (StClickable *self, + gboolean pressed) +{ + if (self->priv->pressed == pressed) + return; + self->priv->pressed = pressed; + sync_pseudo_class (self); + g_object_notify (G_OBJECT (self), "pressed"); +} + +static gboolean +st_clickable_contains (StClickable *self, + ClutterActor *actor) +{ + while (actor != NULL && actor != (ClutterActor*)self) + { + actor = clutter_actor_get_parent (actor); + } + return actor != NULL; +} + +static gboolean +st_clickable_enter_event (ClutterActor *actor, + ClutterCrossingEvent *event) +{ + StClickable *self = ST_CLICKABLE (actor); + + if (st_clickable_contains (self, event->related)) + return TRUE; + if (!st_clickable_contains (self, event->source)) + return TRUE; + + g_object_freeze_notify (G_OBJECT (actor)); + + if (self->priv->held) + set_pressed (self, TRUE); + set_hover (self, TRUE); + + g_object_thaw_notify (G_OBJECT (actor)); + + return TRUE; +} + +static gboolean +st_clickable_leave_event (ClutterActor *actor, + ClutterCrossingEvent *event) +{ + StClickable *self = ST_CLICKABLE (actor); + + if (st_clickable_contains (self, event->related)) + return TRUE; + + set_hover (self, FALSE); + set_pressed (self, FALSE); + + return TRUE; +} + +static gboolean +st_clickable_button_press_event (ClutterActor *actor, + ClutterButtonEvent *event) +{ + StClickable *self = ST_CLICKABLE (actor); + + if (event->click_count != 1) + return FALSE; + + if (self->priv->held) + return TRUE; + + if (!st_clickable_contains (self, event->source)) + return FALSE; + + self->priv->held = TRUE; + self->priv->initiating_button = event->button; + clutter_grab_pointer (CLUTTER_ACTOR (self)); + + set_pressed (self, TRUE); + + return TRUE; +} + +static gboolean +st_clickable_button_release_event (ClutterActor *actor, + ClutterButtonEvent *event) +{ + StClickable *self = ST_CLICKABLE (actor); + + if (event->button != self->priv->initiating_button || event->click_count != 1) + return FALSE; + + if (!self->priv->held) + return TRUE; + + self->priv->held = FALSE; + clutter_ungrab_pointer (); + + if (!st_clickable_contains (self, event->source)) + return FALSE; + + set_pressed (self, FALSE); + + g_signal_emit (G_OBJECT (self), st_clickable_signals[CLICKED], 0, event); + + return TRUE; +} + +/** + * st_clickable_fake_release: + * @box: + * + * If this widget is holding a pointer grab, this function will + * will ungrab it, and reset the pressed state. The effect is + * similar to if the user had released the mouse button, but without + * emitting the clicked signal. + * + * This function is useful if for example you want to do something after the user + * is holding the mouse button for a given period of time, breaking the + * grab. + */ +void +st_clickable_fake_release (StClickable *self) +{ + if (!self->priv->held) + return; + + self->priv->held = FALSE; + clutter_ungrab_pointer (); + + set_pressed (self, FALSE); +} + +static void +st_clickable_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + StClickable *self = ST_CLICKABLE (object); + + switch (prop_id) + { + case PROP_ACTIVE: + set_active (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +st_clickable_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + StClickable *self = ST_CLICKABLE (object); + + switch (prop_id) + { + case PROP_ACTIVE: + g_value_set_boolean (value, self->priv->active); + break; + case PROP_PRESSED: + g_value_set_boolean (value, self->priv->pressed); + break; + case PROP_HOVER: + g_value_set_boolean (value, self->priv->hover); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +st_clickable_class_init (StClickableClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + gobject_class->get_property = st_clickable_get_property; + gobject_class->set_property = st_clickable_set_property; + + actor_class->enter_event = st_clickable_enter_event; + actor_class->leave_event = st_clickable_leave_event; + actor_class->button_press_event = st_clickable_button_press_event; + actor_class->button_release_event = st_clickable_button_release_event; + + /** + * StClickable::clicked + * @box: The #StClickable + * + * This signal is emitted when the button should take the action + * associated with button click+release. + */ + st_clickable_signals[CLICKED] = + g_signal_new ("clicked", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, CLUTTER_TYPE_EVENT); + + /** + * StClickable:active + * + * The property allows the button to be used as a "toggle button"; it's up to the + * application to update the active property in response to the activate signal; + * it doesn't happen automatically. + */ + g_object_class_install_property (gobject_class, + PROP_ACTIVE, + g_param_spec_boolean ("active", + "Active", + "Whether the button persistently active", + FALSE, + G_PARAM_READWRITE)); + + /** + * StClickable:hover + * + * This property tracks whether the mouse is over the button; note this + * state is independent of whether the button is pressed. + */ + g_object_class_install_property (gobject_class, + PROP_HOVER, + g_param_spec_boolean ("hover", + "Hovering state", + "Whether the mouse is over the button", + FALSE, + G_PARAM_READABLE)); + + /** + * StClickable:pressed + * + * This property tracks whether the button should have a "pressed in" + * effect. + */ + g_object_class_install_property (gobject_class, + PROP_PRESSED, + g_param_spec_boolean ("pressed", + "Pressed state", + "Whether the button is currently pressed", + FALSE, + G_PARAM_READABLE)); + + g_type_class_add_private (gobject_class, sizeof (StClickablePrivate)); +} + +static void +st_clickable_init (StClickable *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, ST_TYPE_CLICKABLE, + StClickablePrivate); +} diff --git a/src/st/st-clickable.h b/src/st/st-clickable.h new file mode 100644 index 000000000..1c59447d7 --- /dev/null +++ b/src/st/st-clickable.h @@ -0,0 +1,35 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#ifndef __ST_CLICKABLE_H__ +#define __ST_CLICKABLE_H__ + +#include "st-bin.h" + +#define ST_TYPE_CLICKABLE (st_clickable_get_type ()) +#define ST_CLICKABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_CLICKABLE, StClickable)) +#define ST_CLICKABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_CLICKABLE, StClickableClass)) +#define ST_IS_CLICKABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_CLICKABLE)) +#define ST_IS_CLICKABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_CLICKABLE)) +#define ST_CLICKABLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_CLICKABLE, StClickableClass)) + +typedef struct _StClickable StClickable; +typedef struct _StClickableClass StClickableClass; + +typedef struct _StClickablePrivate StClickablePrivate; + +struct _StClickable +{ + StBin parent; + + StClickablePrivate *priv; +}; + +struct _StClickableClass +{ + StBinClass parent_class; +}; + +GType st_clickable_get_type (void) G_GNUC_CONST; + +void st_clickable_fake_release (StClickable *box); + +#endif /* __ST_CLICKABLE_H__ */ From 2f1ca7bf28326073dd95d58215c300b890d0e417 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Mon, 16 Nov 2009 14:16:22 -0500 Subject: [PATCH 12/20] Remove ShellButtonBox, button.js; Use St.Clickable in panel.js StClickable replaces ShellButtonBox. Reduce the number of button-like things by deleting button.js. To do so, add CSS style for the actitivies button. https://bugzilla.gnome.org/show_bug.cgi?id=602131 --- data/theme/gnome-shell.css | 10 ++ js/ui/Makefile.am | 3 +- js/ui/altTab.js | 12 +- js/ui/button.js | 172 -------------------- js/ui/dash.js | 1 - js/ui/panel.js | 19 +-- src/Makefile.am | 2 - src/shell-button-box.c | 317 ------------------------------------- src/shell-button-box.h | 36 ----- 9 files changed, 28 insertions(+), 544 deletions(-) delete mode 100644 js/ui/button.js delete mode 100644 src/shell-button-box.c delete mode 100644 src/shell-button-box.h diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 16b6f76b0..ab2d8acce 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -287,6 +287,16 @@ StTooltip { spacing: 4px; } +/* Panel */ + +#panelActivities { + color: #ffffff; +} + +#panelActivities:pressed { + background-color: rgba(50,76,111,0.98); +} + /* Calendar popup */ #calendarPopup { diff --git a/js/ui/Makefile.am b/js/ui/Makefile.am index a74fb862e..4b6260ca2 100644 --- a/js/ui/Makefile.am +++ b/js/ui/Makefile.am @@ -4,8 +4,7 @@ dist_jsui_DATA = \ altTab.js \ appDisplay.js \ appFavorites.js \ - appIcon.js \ - button.js \ + appIcon.js \ calendar.js \ chrome.js \ dash.js \ diff --git a/js/ui/altTab.js b/js/ui/altTab.js index add986fb8..78f8262c7 100644 --- a/js/ui/altTab.js +++ b/js/ui/altTab.js @@ -440,16 +440,18 @@ 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. + // bin inside the Clickable 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({ reactive: true, + x_fill: true, + y_fill: true }); bin.add_actor(item); - bbox.append(bin, Big.BoxPackFlags.NONE); + bbox.set_child(bin); 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 () { @@ -681,7 +683,7 @@ AppSwitcher.prototype = { this.icons.push(appIcon); this.addItem(appIcon.actor); - // SwitcherList creates its own Shell.ButtonBox; we want to + // SwitcherList creates its own St.Clickable; we want to // avoid intercepting the events it wants. appIcon.actor.reactive = false; diff --git a/js/ui/button.js b/js/ui/button.js deleted file mode 100644 index 7cae476af..000000000 --- a/js/ui/button.js +++ /dev/null @@ -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" }); - } -}; diff --git a/js/ui/dash.js b/js/ui/dash.js index 3a2f5e4cb..1fc2184d6 100644 --- a/js/ui/dash.js +++ b/js/ui/dash.js @@ -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; diff --git a/js/ui/panel.js b/js/ui/panel.js index 6d126a8c6..9ae999b8e 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -13,7 +13,6 @@ 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; @@ -274,11 +273,12 @@ 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' }); + this.button.set_child(label); + this.button.height = PANEL_HEIGHT; - this._leftBox.append(this.button.actor, Big.BoxPackFlags.NONE); + this._leftBox.append(this.button, Big.BoxPackFlags.NONE); // 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 @@ -402,8 +402,9 @@ Panel.prototype = { // 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) { + let event = Clutter.get_current_event(); + if (!Main.overview.animationInProgress) { this._maybeToggleOverviewOnClick(); return true; } else { @@ -414,10 +415,10 @@ 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, { visibleInOverview: true }); diff --git a/src/Makefile.am b/src/Makefile.am index ed9ae6041..29c36cb80 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -63,8 +63,6 @@ libgnome_shell_la_SOURCES = \ shell-app-usage.h \ shell-arrow.c \ shell-arrow.h \ - shell-button-box.c \ - shell-button-box.h \ shell-drawing.c \ shell-drawing.h \ shell-embedded-window.c \ diff --git a/src/shell-button-box.c b/src/shell-button-box.c deleted file mode 100644 index bd74d732f..000000000 --- a/src/shell-button-box.c +++ /dev/null @@ -1,317 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ - -/** - * SECTION:shell-button-box - * @short_description: A box with properties useful for implementing buttons - * - * A #BigBox subclass which translates lower-level Clutter button events - * into higher level properties which are useful for implementing "button-like" - * actors. - */ - -#include "shell-button-box.h" - -G_DEFINE_TYPE(ShellButtonBox, shell_button_box, BIG_TYPE_BOX); - -struct _ShellButtonBoxPrivate { - gboolean active; - gboolean held; - gboolean hover; - gboolean pressed; -}; - -/* Signals */ -enum -{ - ACTIVATE, - LAST_SIGNAL -}; - -enum { - PROP_0, - - PROP_ACTIVE, - PROP_HOVER, - PROP_PRESSED, -}; - -static guint shell_button_box_signals [LAST_SIGNAL] = { 0 }; - -static void -set_active (ShellButtonBox *box, - gboolean active) -{ - if (box->priv->active == active) - return; - box->priv->active = active; - g_object_notify (G_OBJECT (box), "active"); -} - -static void -set_hover (ShellButtonBox *box, - gboolean hover) -{ - if (box->priv->hover == hover) - return; - box->priv->hover = hover; - g_object_notify (G_OBJECT (box), "hover"); -} - -static void -set_pressed (ShellButtonBox *box, - gboolean pressed) -{ - if (box->priv->pressed == pressed) - return; - box->priv->pressed = pressed; - g_object_notify (G_OBJECT (box), "pressed"); -} - -static gboolean -shell_button_box_contains (ShellButtonBox *box, - ClutterActor *actor) -{ - while (actor != NULL && actor != (ClutterActor*)box) - { - actor = clutter_actor_get_parent (actor); - } - return actor != NULL; -} - -static gboolean -shell_button_box_enter_event (ClutterActor *actor, - ClutterCrossingEvent *event) -{ - ShellButtonBox *box = SHELL_BUTTON_BOX (actor); - - if (shell_button_box_contains (box, event->related)) - return TRUE; - if (!shell_button_box_contains (box, event->source)) - return TRUE; - - g_object_freeze_notify (G_OBJECT (actor)); - - if (box->priv->held) - set_pressed (box, TRUE); - set_hover (box, TRUE); - - g_object_thaw_notify (G_OBJECT (actor)); - - return TRUE; -} - -static gboolean -shell_button_box_leave_event (ClutterActor *actor, - ClutterCrossingEvent *event) -{ - ShellButtonBox *box = SHELL_BUTTON_BOX (actor); - - if (shell_button_box_contains (box, event->related)) - return TRUE; - - set_hover (box, FALSE); - set_pressed (box, FALSE); - - return TRUE; -} - -static gboolean -shell_button_box_button_press_event (ClutterActor *actor, - ClutterButtonEvent *event) -{ - ShellButtonBox *box = SHELL_BUTTON_BOX (actor); - - if (event->button != 1 || event->click_count != 1) - return FALSE; - - if (box->priv->held) - return TRUE; - - if (!shell_button_box_contains (box, event->source)) - return FALSE; - - box->priv->held = TRUE; - clutter_grab_pointer (CLUTTER_ACTOR (box)); - - set_pressed (box, TRUE); - - return TRUE; -} - -static gboolean -shell_button_box_button_release_event (ClutterActor *actor, - ClutterButtonEvent *event) -{ - ShellButtonBox *box = SHELL_BUTTON_BOX (actor); - - if (event->button != 1 || event->click_count != 1) - return FALSE; - - if (!box->priv->held) - return TRUE; - - box->priv->held = FALSE; - clutter_ungrab_pointer (); - - if (!shell_button_box_contains (box, event->source)) - return FALSE; - - set_pressed (box, FALSE); - - g_signal_emit (G_OBJECT (box), shell_button_box_signals[ACTIVATE], 0, event); - - return TRUE; -} - -/** - * shell_button_box_fake_release: - * @box: - * - * If this button box is holding a pointer grab, this function will - * will ungrab it, and reset the pressed state. The effect is - * similar to if the user had released the mouse button, but without - * emitting the activate signal. - * - * This function is useful if for example you want to do something after the user - * is holding the mouse button for a given period of time, breaking the - * grab. - */ -void -shell_button_box_fake_release (ShellButtonBox *box) -{ - if (!box->priv->held) - return; - - box->priv->held = FALSE; - clutter_ungrab_pointer (); - - set_pressed (box, FALSE); -} - -static void -shell_button_box_set_property(GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - ShellButtonBox *box = SHELL_BUTTON_BOX (object); - - switch (prop_id) - { - case PROP_ACTIVE: - set_active (box, g_value_get_boolean (value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -shell_button_box_get_property(GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - ShellButtonBox *box = SHELL_BUTTON_BOX (object); - - switch (prop_id) - { - case PROP_ACTIVE: - g_value_set_boolean (value, box->priv->active); - break; - case PROP_PRESSED: - g_value_set_boolean (value, box->priv->pressed); - break; - case PROP_HOVER: - g_value_set_boolean (value, box->priv->hover); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -shell_button_box_class_init (ShellButtonBoxClass *klass) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); - - gobject_class->get_property = shell_button_box_get_property; - gobject_class->set_property = shell_button_box_set_property; - - actor_class->enter_event = shell_button_box_enter_event; - actor_class->leave_event = shell_button_box_leave_event; - actor_class->button_press_event = shell_button_box_button_press_event; - actor_class->button_release_event = shell_button_box_button_release_event; - - /** - * ShellButtonBox::activate - * @box: The #ShellButtonBox - * @event: Release event which triggered the activation - * - * This signal is emitted when the button should take the action - * associated with button click+release. - */ - shell_button_box_signals[ACTIVATE] = - g_signal_new ("activate", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 1, CLUTTER_TYPE_EVENT); - - /** - * ShellButtonBox:active - * - * The property allows the button to be used as a "toggle button"; it's up to the - * application to update the active property in response to the activate signal; - * it doesn't happen automatically. - */ - g_object_class_install_property (gobject_class, - PROP_ACTIVE, - g_param_spec_boolean ("active", - "Active", - "Whether the button persistently active", - FALSE, - G_PARAM_READWRITE)); - - /** - * ShellButtonBox:hover - * - * This property tracks whether the mouse is over the button; note this - * state is independent of whether the button is pressed. - */ - g_object_class_install_property (gobject_class, - PROP_HOVER, - g_param_spec_boolean ("hover", - "Hovering state", - "Whether the mouse is over the button", - FALSE, - G_PARAM_READABLE)); - - /** - * ShellButtonBox:pressed - * - * This property tracks whether the button should have a "pressed in" - * effect. - */ - g_object_class_install_property (gobject_class, - PROP_PRESSED, - g_param_spec_boolean ("pressed", - "Pressed state", - "Whether the button is currently pressed", - FALSE, - G_PARAM_READABLE)); - - g_type_class_add_private (gobject_class, sizeof (ShellButtonBoxPrivate)); -} - -static void -shell_button_box_init (ShellButtonBox *self) -{ - self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, SHELL_TYPE_BUTTON_BOX, - ShellButtonBoxPrivate); -} diff --git a/src/shell-button-box.h b/src/shell-button-box.h deleted file mode 100644 index 3ebe8d772..000000000 --- a/src/shell-button-box.h +++ /dev/null @@ -1,36 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ -#ifndef __SHELL_BUTTON_BOX_H__ -#define __SHELL_BUTTON_BOX_H__ - -#include -#include "big/box.h" - -#define SHELL_TYPE_BUTTON_BOX (shell_button_box_get_type ()) -#define SHELL_BUTTON_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_TYPE_BUTTON_BOX, ShellButtonBox)) -#define SHELL_BUTTON_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_BUTTON_BOX, ShellButtonBoxClass)) -#define SHELL_IS_BUTTON_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_TYPE_BUTTON_BOX)) -#define SHELL_IS_BUTTON_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_BUTTON_BOX)) -#define SHELL_BUTTON_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_BUTTON_BOX, ShellButtonBoxClass)) - -typedef struct _ShellButtonBox ShellButtonBox; -typedef struct _ShellButtonBoxClass ShellButtonBoxClass; - -typedef struct _ShellButtonBoxPrivate ShellButtonBoxPrivate; - -struct _ShellButtonBox -{ - BigBox parent; - - ShellButtonBoxPrivate *priv; -}; - -struct _ShellButtonBoxClass -{ - BigBoxClass parent_class; -}; - -GType shell_button_box_get_type (void) G_GNUC_CONST; - -void shell_button_box_fake_release (ShellButtonBox *box); - -#endif /* __SHELL_BUTTON_BOX_H__ */ From 907fc2f067c8c831ca16ef5722026ff481b8cb72 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 12 Nov 2009 17:46:59 -0500 Subject: [PATCH 13/20] 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 | 43 +- js/ui/appDisplay.js | 823 ++++++++++++++++++++++++++----------- 4 files changed, 647 insertions(+), 268 deletions(-) diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index ab2d8acce..cc5212f6e 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 4b6260ca2..350885ef8 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 78f8262c7..3ed39f8c5 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 }); + let bbox = new St.Clickable({ style_class: 'item-box', + reactive: true }); - bin.add_actor(item); - bbox.set_child(bin); + 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 c88191016..8bf359d08 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()); - } 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); } }, From 73cd9513bd6e49b38aea9dde4d7bd438760573de Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 17 Nov 2009 17:29:54 -0500 Subject: [PATCH 14/20] Remove information icon It wasn't really useful, not obviously in the current mockups, and depended on button.js. https://bugzilla.gnome.org/show_bug.cgi?id=602131 --- data/Makefile.am | 1 - data/info.svg | 74 ----------------------------------------- js/ui/genericDisplay.js | 69 ++------------------------------------ 3 files changed, 2 insertions(+), 142 deletions(-) delete mode 100644 data/info.svg diff --git a/data/Makefile.am b/data/Makefile.am index 765bef654..eef11bd01 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -17,7 +17,6 @@ dist_images_DATA = \ add-workspace.svg \ app-well-glow.png \ close-black.svg \ - info.svg \ magnifier.svg \ remove-workspace.svg diff --git a/data/info.svg b/data/info.svg deleted file mode 100644 index 41b5066a9..000000000 --- a/data/info.svg +++ /dev/null @@ -1,74 +0,0 @@ - - -image/svg+xml - - \ No newline at end of file diff --git a/js/ui/genericDisplay.js b/js/ui/genericDisplay.js index d5c3e3f38..939d98936 100644 --- a/js/ui/genericDisplay.js +++ b/js/ui/genericDisplay.js @@ -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. From 1d2dc09ede3df406ea83810811f011898b481f1f Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 17 Nov 2009 17:46:20 -0500 Subject: [PATCH 15/20] [panel] Port to CSS With some tweaks from JP St. Pierre to center things in case we ever have a larger panel. https://bugzilla.gnome.org/show_bug.cgi?id=602131 --- data/theme/gnome-shell.css | 55 +++++++++---- js/ui/panel.js | 155 +++++++++++++------------------------ 2 files changed, 90 insertions(+), 120 deletions(-) diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index cc5212f6e..8b4263064 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -70,17 +70,54 @@ StTooltip { /* Panel */ +#panel { + color: #ffffff; + font-size: 16px; + background-gradient-direction: vertical; + background-gradient-start: #161616; + background-gradient-end: #000000; +} + +#panelLeft, #panelCenter, #panelRight { + spacing: 4px; +} + +#panelLeft { + padding-right: 4px; +} + +#panelRight { + padding-left: 4px; +} + +.panel-button:pressed { + background-color: rgba(50,76,111,0.98); + border-radius: 4px; +} + +#appMenu { + spacing: 4px; +} + +.app-menu-icon { + width: 24px; + height: 24px; +} + .panel-button { padding: 4px 12px 3px; border-radius: 5px; font: 16px sans-serif; - color: white; } .panel-button:active, .panel-button:checked { background-color: #314a6c; } +#panelStatus { + spacing: 4px; +} + /* Overlay */ .workspaces { @@ -331,16 +368,6 @@ StTooltip { spacing: 4px; } -/* Panel */ - -#panelActivities { - color: #ffffff; -} - -#panelActivities:pressed { - background-color: rgba(50,76,111,0.98); -} - /* Calendar popup */ #calendarPopup { @@ -426,9 +453,3 @@ StTooltip { background: rgba(255,255,255,0.33); } -/* Status Menu */ -#StatusMenu { - spacing: 4px; - font: 16px sans-serif; - color: white; -} diff --git a/js/ui/panel.js b/js/ui/panel.js index 9ae999b8e..a07075d41 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -18,36 +18,11 @@ 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; @@ -83,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; @@ -144,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()); } @@ -174,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 +225,13 @@ 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". */ let label = new St.Label({ text: _("Activities") }); - this.button = new St.Clickable({ name: 'panelActivities' }); + 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, 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,30 @@ 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.connect('clicked', Lang.bind(this, function(b) { - let event = Clutter.get_current_event(); + this.button.connect('clicked', Lang.bind(this, function(b, event) { if (!Main.overview.animationInProgress) { this._maybeToggleOverviewOnClick(); return true; From f0e3b87330a542bfe8a9331bf880a952a31abea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Mon, 23 Nov 2009 22:42:02 +0100 Subject: [PATCH 16/20] Fix disappearing close button Show the close button after zooming a window in the overview when the window clone has the pointer focus. https://bugzilla.gnome.org/show_bug.cgi?id=602772 --- js/ui/workspaces.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js index 565e557ff..58e982fc0 100644 --- a/js/ui/workspaces.js +++ b/js/ui/workspaces.js @@ -367,6 +367,12 @@ WindowOverlay.prototype = { }, 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(); }, From dd8f05c81d472f247624a8f408ab5df02fdf5a65 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Tue, 24 Nov 2009 09:07:40 -0500 Subject: [PATCH 17/20] include "config.h" in all .c files Also, fix shell-global.c to actually compile with config.h included. Was supposed to fix bug 602802, but doesn't... --- src/gnome-shell-plugin.c | 2 ++ src/shell-app-system.c | 2 ++ src/shell-app-usage.c | 3 +++ src/shell-app.c | 2 ++ src/shell-arrow.c | 2 ++ src/shell-drawing.c | 2 ++ src/shell-embedded-window.c | 2 ++ src/shell-gconf.c | 2 ++ src/shell-generic-container.c | 2 ++ src/shell-global.c | 5 +++++ src/shell-gtk-embed.c | 2 ++ src/shell-menu.c | 2 ++ src/shell-overflow-list.c | 2 ++ src/shell-process.c | 2 ++ src/shell-recorder-src.c | 2 ++ src/shell-recorder.c | 2 ++ src/shell-stack.c | 2 ++ src/shell-texture-cache.c | 2 ++ src/shell-tray-manager.c | 2 ++ src/shell-uri-util.c | 2 ++ src/shell-window-tracker.c | 3 +++ src/shell-wm.c | 2 ++ 22 files changed, 49 insertions(+) diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c index bf6c6a04f..011f6ff82 100644 --- a/src/gnome-shell-plugin.c +++ b/src/gnome-shell-plugin.c @@ -23,6 +23,8 @@ * 02111-1307, USA. */ +#include "config.h" + #define MUTTER_BUILDING_PLUGIN 1 #include diff --git a/src/shell-app-system.c b/src/shell-app-system.c index 2ec877472..0f61576d5 100644 --- a/src/shell-app-system.c +++ b/src/shell-app-system.c @@ -1,5 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + #include "shell-app-system.h" #include diff --git a/src/shell-app-usage.c b/src/shell-app-usage.c index f02dd6503..c17c3d207 100644 --- a/src/shell-app-usage.c +++ b/src/shell-app-usage.c @@ -1,4 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +#include "config.h" + #include #include diff --git a/src/shell-app.c b/src/shell-app.c index a0c56e4f9..992ad8bd8 100644 --- a/src/shell-app.c +++ b/src/shell-app.c @@ -1,5 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + #include "shell-app-private.h" #include "shell-global.h" diff --git a/src/shell-arrow.c b/src/shell-arrow.c index 1d961f3b4..380ec44ca 100644 --- a/src/shell-arrow.c +++ b/src/shell-arrow.c @@ -1,5 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + #include "shell-arrow.h" #include diff --git a/src/shell-drawing.c b/src/shell-drawing.c index 78a7f54d5..21f7129af 100644 --- a/src/shell-drawing.c +++ b/src/shell-drawing.c @@ -1,5 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + #include "shell-drawing.h" #include diff --git a/src/shell-embedded-window.c b/src/shell-embedded-window.c index 80c64fff9..a12d7d6b4 100644 --- a/src/shell-embedded-window.c +++ b/src/shell-embedded-window.c @@ -1,5 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + #include #include diff --git a/src/shell-gconf.c b/src/shell-gconf.c index 2d065512b..7073bbdcf 100644 --- a/src/shell-gconf.c +++ b/src/shell-gconf.c @@ -1,5 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + #include "shell-gconf.h" #include diff --git a/src/shell-generic-container.c b/src/shell-generic-container.c index db74ecb62..68b3afde2 100644 --- a/src/shell-generic-container.c +++ b/src/shell-generic-container.c @@ -94,6 +94,8 @@ function runTestFixedBox() { } */ +#include "config.h" + #include "shell-generic-container.h" #include diff --git a/src/shell-global.c b/src/shell-global.c index 5bb7e69d4..a36753e9a 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -1,5 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + #include "shell-global-private.h" #include "shell-wm.h" @@ -19,6 +21,9 @@ #include #include #include +#ifdef HAVE_SYS_RESOURCE_H +#include +#endif #define SHELL_DBUS_SERVICE "org.gnome.Shell" diff --git a/src/shell-gtk-embed.c b/src/shell-gtk-embed.c index 0d5f2978e..35f004bc0 100644 --- a/src/shell-gtk-embed.c +++ b/src/shell-gtk-embed.c @@ -1,5 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + #include "shell-embedded-window-private.h" #include diff --git a/src/shell-menu.c b/src/shell-menu.c index 4fce08c5f..605a7ab74 100644 --- a/src/shell-menu.c +++ b/src/shell-menu.c @@ -8,6 +8,8 @@ * popup-menu like actors. */ +#include "config.h" + #include "shell-menu.h" G_DEFINE_TYPE(ShellMenu, shell_menu, BIG_TYPE_BOX); diff --git a/src/shell-overflow-list.c b/src/shell-overflow-list.c index a5dc8f96f..a398a854b 100644 --- a/src/shell-overflow-list.c +++ b/src/shell-overflow-list.c @@ -1,5 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + #include "shell-overflow-list.h" G_DEFINE_TYPE (ShellOverflowList, diff --git a/src/shell-process.c b/src/shell-process.c index 443c48258..afa3abdf6 100644 --- a/src/shell-process.c +++ b/src/shell-process.c @@ -1,5 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + #include "shell-process.h" #include diff --git a/src/shell-recorder-src.c b/src/shell-recorder-src.c index 21eebe44e..5fbdf7c7b 100644 --- a/src/shell-recorder-src.c +++ b/src/shell-recorder-src.c @@ -1,5 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + #include #include "shell-recorder-src.h" diff --git a/src/shell-recorder.c b/src/shell-recorder.c index 70f833eb2..4c0304e22 100644 --- a/src/shell-recorder.c +++ b/src/shell-recorder.c @@ -1,5 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + #include #include #include diff --git a/src/shell-stack.c b/src/shell-stack.c index ea3ec2c33..f1bdf76a3 100644 --- a/src/shell-stack.c +++ b/src/shell-stack.c @@ -12,6 +12,8 @@ * size, even if that would overflow the size allocated to the stack. */ +#include "config.h" + #include "shell-stack.h" G_DEFINE_TYPE (ShellStack, diff --git a/src/shell-texture-cache.c b/src/shell-texture-cache.c index 55bdc2c63..90402eec0 100644 --- a/src/shell-texture-cache.c +++ b/src/shell-texture-cache.c @@ -1,5 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + #include "shell-texture-cache.h" #include "shell-global.h" #include diff --git a/src/shell-tray-manager.c b/src/shell-tray-manager.c index 9940461a3..474c5c609 100644 --- a/src/shell-tray-manager.c +++ b/src/shell-tray-manager.c @@ -1,5 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + #include #include #include diff --git a/src/shell-uri-util.c b/src/shell-uri-util.c index ccb9591d6..69937859a 100644 --- a/src/shell-uri-util.c +++ b/src/shell-uri-util.c @@ -1,5 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + #include "shell-uri-util.h" #include #include diff --git a/src/shell-window-tracker.c b/src/shell-window-tracker.c index 57226b7d5..3c9bec64c 100644 --- a/src/shell-window-tracker.c +++ b/src/shell-window-tracker.c @@ -1,4 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +#include "config.h" + #include #include diff --git a/src/shell-wm.c b/src/shell-wm.c index 3b757cc0c..6ddd65560 100644 --- a/src/shell-wm.c +++ b/src/shell-wm.c @@ -1,5 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#include "config.h" + #include #include "shell-wm.h" From e2ac769fd133d8d1ae830e572ff3b911358f05cf Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 19 Nov 2009 20:15:46 -0500 Subject: [PATCH 18/20] Bump save timeout to 5 minutes, close output asynchronously The synchronous close causes us to block in fsync() which has extremely poor interactivity implications on ext3. Also, the 5 second timeout was an accidental commit from debugging, 5 minutes is fine. https://bugzilla.gnome.org/show_bug.cgi?id=602456 --- src/shell-app-usage.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shell-app-usage.c b/src/shell-app-usage.c index c17c3d207..00a6c2ee5 100644 --- a/src/shell-app-usage.c +++ b/src/shell-app-usage.c @@ -65,7 +65,7 @@ */ /* How often we save internally app data, in seconds */ -#define SAVE_APPS_TIMEOUT_SECONDS 5 /* leave this low for testing, we can bump later if need be */ +#define SAVE_APPS_TIMEOUT_SECONDS (5 * 60) /* With this value, an app goes from bottom to top of the * usage list in 50 hours of use */ @@ -714,7 +714,7 @@ idle_save_application_usage (gpointer data) out: if (!error) - g_output_stream_close (G_OUTPUT_STREAM(data_output), NULL, &error); + g_output_stream_close_async (G_OUTPUT_STREAM (data_output), 0, NULL, NULL, NULL); g_object_unref (data_output); if (error) { From 6b816fa7dc8cd65ba2c514ae84190628e4953da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Tue, 24 Nov 2009 16:41:00 +0100 Subject: [PATCH 19/20] Replace includes of glib/gi18n.h with glib/gi18n-lib.h https://bugzilla.gnome.org/show_bug.cgi?id=602802 --- src/shell-uri-util.c | 2 +- src/tray/na-tray-child.c | 2 +- src/tray/na-tray-manager.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shell-uri-util.c b/src/shell-uri-util.c index 69937859a..dcbe58bb5 100644 --- a/src/shell-uri-util.c +++ b/src/shell-uri-util.c @@ -3,7 +3,7 @@ #include "config.h" #include "shell-uri-util.h" -#include +#include #include #include diff --git a/src/tray/na-tray-child.c b/src/tray/na-tray-child.c index 61c3aea79..bfd0d88df 100644 --- a/src/tray/na-tray-child.c +++ b/src/tray/na-tray-child.c @@ -24,7 +24,7 @@ #include "na-tray-child.h" -#include +#include #include #include #include diff --git a/src/tray/na-tray-manager.c b/src/tray/na-tray-manager.c index 1bf54f1f6..82ebc2918 100644 --- a/src/tray/na-tray-manager.c +++ b/src/tray/na-tray-manager.c @@ -27,7 +27,7 @@ #include "na-tray-manager.h" #include -#include +#include #if defined (GDK_WINDOWING_X11) #include #include From 236a9b184f7e30f7b93b7edecfb3f6752fcf5147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amanda=20Magalh=C3=A3es?= Date: Sat, 14 Nov 2009 11:50:03 -0200 Subject: [PATCH 20/20] Updated Brazilian Portuguese translation --- po/pt_BR.po | 104 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 71 insertions(+), 33 deletions(-) diff --git a/po/pt_BR.po b/po/pt_BR.po index b62d8df6a..9af5a83b2 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -7,10 +7,11 @@ msgid "" msgstr "" "Project-Id-Version: \n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-09-21 08:39-0300\n" -"PO-Revision-Date: 2009-09-20 08:41-0300\n" -"Last-Translator: Rodrigo Flores \n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" +"shell&component=general\n" +"POT-Creation-Date: 2009-11-12 21:33+0000\n" +"PO-Revision-Date: 2009-11-14 11:53-0200\n" +"Last-Translator: Amanda Magalhães \n" "Language-Team: Brazilian Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -25,84 +26,115 @@ msgstr "GNOME Shell" msgid "Window management and application launching" msgstr "Gerenciamento de janelas e lançador de aplicações" -#. left side -#: ../js/ui/panel.js:269 -msgid "Activities" -msgstr "Atividades" +#: ../js/ui/appDisplay.js:696 +msgid "Drag here to add favorites" +msgstr "Arraste até aqui para adicionar aos favoritos" -#. Translators: This is a time format. -#: ../js/ui/panel.js:452 -msgid "%a %l:%M %p" -msgstr "%a %l:%M %p" +#: ../js/ui/appIcon.js:425 +msgid "New Window" +msgstr "Nova janela" -#: ../js/ui/dash.js:283 +#: ../js/ui/appIcon.js:429 +msgid "Remove from Favorites" +msgstr "Remover dos Favoritos" + +#: ../js/ui/appIcon.js:430 +msgid "Add to Favorites" +msgstr "Adicionar aos Favoritos" + +#: ../js/ui/dash.js:237 msgid "Find..." -msgstr "Encontre..." - -#: ../js/ui/dash.js:400 -msgid "Browse" -msgstr "Navegar" - -#: ../js/ui/dash.js:536 -msgid "(see all)" -msgstr "(veja todos)" +msgstr "Procurar..." #. **** Applications **** -#: ../js/ui/dash.js:753 ../js/ui/dash.js:809 +#: ../js/ui/dash.js:656 ../js/ui/dash.js:718 msgid "APPLICATIONS" msgstr "APLICATIVOS" #. **** Places **** #. Translators: This is in the sense of locations for documents, #. network locations, etc. -#: ../js/ui/dash.js:773 +#: ../js/ui/dash.js:676 ../js/ui/dash.js:733 msgid "PLACES" msgstr "LOCAIS" #. **** Documents **** -#: ../js/ui/dash.js:780 ../js/ui/dash.js:819 +#: ../js/ui/dash.js:683 ../js/ui/dash.js:728 msgid "RECENT DOCUMENTS" msgstr "DOCUMENTOS RECENTES" #. **** Search Results **** -#: ../js/ui/dash.js:799 ../js/ui/dash.js:931 +#: ../js/ui/dash.js:708 ../js/ui/dash.js:898 msgid "SEARCH RESULTS" msgstr "RESULTADOS DA BUSCA" -#: ../js/ui/dash.js:814 +#: ../js/ui/dash.js:723 msgid "PREFERENCES" msgstr "PREFERÊNCIAS" -#: ../js/ui/runDialog.js:101 +#. 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". +#: ../js/ui/panel.js:274 +msgid "Activities" +msgstr "Atividades" + +#. Translators: This is a time format. +#: ../js/ui/panel.js:491 +msgid "%a %l:%M %p" +msgstr "%a %l:%M %p" + +#: ../js/ui/placeDisplay.js:84 +msgid "Connect to..." +msgstr "Conectar ao..." + +#: ../js/ui/runDialog.js:96 msgid "Please enter a command:" msgstr "Por favor digite um comando:" -#: ../src/shell-global.c:799 +#: ../js/ui/runDialog.js:173 +#, c-format +msgid "Execution of '%s' failed:" +msgstr "A execução de \"%s\" falhou:" + +#. Translators: This is a time format. +#: ../js/ui/widget.js:163 +msgid "%H:%M" +msgstr "%H:%M" + +#: ../js/ui/widget.js:317 +msgid "Applications" +msgstr "Aplicações" + +#: ../js/ui/widget.js:339 +msgid "Recent Documents" +msgstr "Documentos Recentes" + +#: ../src/shell-global.c:821 msgid "Less than a minute ago" msgstr "Menos de um minuto atrás" -#: ../src/shell-global.c:802 +#: ../src/shell-global.c:824 #, c-format msgid "%d minute ago" msgid_plural "%d minutes ago" msgstr[0] "%d minuto atrás" msgstr[1] "%d minutos atrás" -#: ../src/shell-global.c:805 +#: ../src/shell-global.c:827 #, c-format msgid "%d hour ago" msgid_plural "%d hours ago" msgstr[0] "%d hora atrás" msgstr[1] "%d horas atrás" -#: ../src/shell-global.c:808 +#: ../src/shell-global.c:830 #, c-format msgid "%d day ago" msgid_plural "%d days ago" msgstr[0] "%d dia atrás" msgstr[1] "%d dias atrás" -#: ../src/shell-global.c:811 +#: ../src/shell-global.c:833 #, c-format msgid "%d week ago" msgid_plural "%d weeks ago" @@ -183,6 +215,12 @@ msgstr "Procurar" msgid "%1$s: %2$s" msgstr "%1$s: %2$s" +#~ msgid "Browse" +#~ msgstr "Navegar" + +#~ msgid "(see all)" +#~ msgstr "(veja todos)" + #~ msgid "Find apps or documents" #~ msgstr "Localizar aplicativos ou documentos"