From be59876c601b082256b52cc3e25da579c393f013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Gonz=C3=A1lez?= Date: Wed, 15 Dec 2010 21:59:37 +0100 Subject: [PATCH 01/22] Updated Spanish translation --- po/es.po | 54 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/po/es.po b/po/es.po index 91362e889..1ada5d455 100644 --- a/po/es.po +++ b/po/es.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: gnome-shell.master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" "shell&component=general\n" -"POT-Creation-Date: 2010-12-02 18:10+0000\n" -"PO-Revision-Date: 2010-12-11 16:42+0100\n" +"POT-Creation-Date: 2010-12-13 20:48+0000\n" +"PO-Revision-Date: 2010-12-15 21:38+0100\n" "Last-Translator: Jorge González \n" "Language-Team: Español \n" "MIME-Version: 1.0\n" @@ -647,14 +647,11 @@ msgid "What's using power..." msgstr "Lo que está usando energía…" #: ../js/ui/status/power.js:90 -#| msgid "System Settings" msgid "Power Settings" msgstr "Configuración de energía" #: ../js/ui/status/power.js:117 #, c-format -#| msgid "%d hour ago" -#| msgid_plural "%d hours ago" msgid "%d hour remaining" msgid_plural "%d hours remaining" msgstr[0] "Queda %d hora" @@ -673,8 +670,6 @@ msgstr[0] "hora" msgstr[1] "horas" #: ../js/ui/status/power.js:122 -#| msgid "%d minute ago" -#| msgid_plural "%d minutes ago" msgid "minute" msgid_plural "minutes" msgstr[0] "miuto" @@ -682,8 +677,6 @@ msgstr[1] "minutos" #: ../js/ui/status/power.js:125 #, c-format -#| msgid "%d minute ago" -#| msgid_plural "%d minutes ago" msgid "%d minute remaining" msgid_plural "%d minutes remaining" msgstr[0] "Queda %d minuto" @@ -706,12 +699,10 @@ msgid "Monitor" msgstr "Monitor" #: ../js/ui/status/power.js:252 -#| msgid "Mouse Keys" msgid "Mouse" msgstr "Ratón" #: ../js/ui/status/power.js:254 -#| msgid "Screen Keyboard" msgid "Keyboard" msgstr "Teclado" @@ -728,7 +719,6 @@ msgid "Media player" msgstr "Reproductor multimedia" #: ../js/ui/status/power.js:262 -#| msgid "Enabled" msgid "Tablet" msgstr "Tableta" @@ -749,10 +739,38 @@ msgid "Microphone" msgstr "Micrófono" #: ../js/ui/status/volume.js:62 -#| msgid "System Settings" msgid "Sound Settings" msgstr "Configuración del sonido" +#: ../js/ui/telepathyClient.js:560 +#, c-format +msgid "%s is online." +msgstr "%s está conectado/a." + +#: ../js/ui/telepathyClient.js:565 +#, c-format +msgid "%s is offline." +msgstr "%s está desconectado/a." + +#: ../js/ui/telepathyClient.js:568 +#, c-format +#| msgid "'%s' is ready" +msgid "%s is away." +msgstr "%s está ausente." + +#: ../js/ui/telepathyClient.js:571 +#, c-format +msgid "%s is busy." +msgstr "%s está ocupado/a." + +#. Translators: this is a time format string followed by a date. +#. If applicable, replace %X with a strftime format valid for your +#. locale, without seconds. +#: ../js/ui/telepathyClient.js:663 +#, c-format +msgid "Sent at %X on %A" +msgstr "Enviado a las %X el %A" + #: ../js/ui/viewSelector.js:26 msgid "Search your computer" msgstr "Buscar en su equipo" @@ -800,32 +818,32 @@ msgstr[1] "%u entradas" msgid "System Sounds" msgstr "Sonidos del sistema" -#: ../src/shell-global.c:1163 +#: ../src/shell-global.c:1155 msgid "Less than a minute ago" msgstr "Hace menos de un minuto" -#: ../src/shell-global.c:1167 +#: ../src/shell-global.c:1159 #, c-format msgid "%d minute ago" msgid_plural "%d minutes ago" msgstr[0] "Hace %d minuto" msgstr[1] "Hace %d minutos" -#: ../src/shell-global.c:1172 +#: ../src/shell-global.c:1164 #, c-format msgid "%d hour ago" msgid_plural "%d hours ago" msgstr[0] "Hace %d hora" msgstr[1] "Hace %d horas" -#: ../src/shell-global.c:1177 +#: ../src/shell-global.c:1169 #, c-format msgid "%d day ago" msgid_plural "%d days ago" msgstr[0] "Hace %d día" msgstr[1] "Hace %d días" -#: ../src/shell-global.c:1182 +#: ../src/shell-global.c:1174 #, c-format msgid "%d week ago" msgid_plural "%d weeks ago" From 3262b6332564e66051d1eeb0e1be8638438c53ed Mon Sep 17 00:00:00 2001 From: Luca Ferretti Date: Thu, 16 Dec 2010 00:29:43 +0100 Subject: [PATCH 02/22] overview: mark Windows and Application as translatable --- js/ui/overview.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/ui/overview.js b/js/ui/overview.js index 5599cd7c5..21ca95179 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -171,10 +171,10 @@ Overview.prototype = { this._group.add_actor(this.viewSelector.actor); this._workspacesDisplay = new WorkspacesView.WorkspacesDisplay(); - this.viewSelector.addViewTab("Windows", this._workspacesDisplay.actor); + this.viewSelector.addViewTab(_("Windows"), this._workspacesDisplay.actor); let appView = new AppDisplay.AllAppDisplay(); - this.viewSelector.addViewTab("Applications", appView.actor); + this.viewSelector.addViewTab(_("Applications"), appView.actor); // Default search providers this.viewSelector.addSearchProvider(new AppDisplay.AppSearchProvider()); From 7eec8a899a1e77e3c6543b64105894f93c5da422 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Wed, 17 Nov 2010 14:10:12 -0500 Subject: [PATCH 03/22] popupMenu: Improve menu item layout Mixing submenu menuitems and toggle menuitems results in poor layout. The fix is to right-align the submenu arrows. Since we already need to right-align the battery percentages as well, add alignment support to PopupBaseMenuItem.addActor(), and update stuff for that. Also remove the "column" param from addActor() since it hadn't actually been implemented correctly before. https://bugzilla.gnome.org/show_bug.cgi?id=633476 --- js/ui/popupMenu.js | 81 +++++++++++++++++++++++-------------------- js/ui/status/power.js | 8 ++--- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js index 23c18c2ae..728c1714c 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -137,24 +137,16 @@ PopupBaseMenuItem.prototype = { return false; }, - // adds an actor to the menu item; @column defaults to the next - // open column, @span defaults to 1. If @span is -1, the actor - // will span the width of the menu item. Children are not - // allowed to overlap each other. - addActor: function(child, column, span) { - if (column == null) { - if (this._children.length) { - let lastChild = this._children[this._children.length - 1]; - column = lastChild.column + lastChild.span; - } else - column = 0; - span = 1; - } else if (span == null) - span = 1; - - this._children.push({ actor: child, - column: column, - span: span }); + // adds an actor to the menu item; @params can contain %span + // (column span; defaults to 1, -1 means "all the remaining width"), + // %expand (defaults to #false), and %align (defaults to + // #St.Align.START) + addActor: function(child, params) { + params = Params.parse(params, { span: 1, + expand: false, + align: St.Align.START }); + params.actor = child; + this._children.push(params); this.actor.connect('destroy', Lang.bind(this, function () { this._removeChild(child); })); this.actor.add_actor(child); }, @@ -272,25 +264,44 @@ PopupBaseMenuItem.prototype = { for (let i = 0, col = 0; i < this._children.length; i++) { let child = this._children[i]; let childBox = new Clutter.ActorBox(); - childBox.x1 = x; + + let [minWidth, naturalWidth] = child.actor.get_preferred_width(-1); + let availWidth, extraWidth; if (this._columnWidths) { if (child.span == -1) - childBox.x2 = box.x2; + availWidth = box.x2 - x; else { - childBox.x2 = x; + availWidth = 0; for (let j = 0; j < child.span; j++) - childBox.x2 += this._columnWidths[col++]; + availWidth += this._columnWidths[col++]; } + extraWidth = availWidth - naturalWidth; } else { - let [min, natural] = child.actor.get_preferred_width(-1); - childBox.x2 = x + natural; + availWidth = naturalWidth; + extraWidth = 0; } - let [min, natural] = child.actor.get_preferred_height(-1); - childBox.y1 = Math.round(box.y1 + (height - natural) / 2); - childBox.y2 = childBox.y1 + natural; + + if (child.expand) { + childBox.x1 = x; + childBox.x2 = x + availWidth; + } else if (child.align === St.Align.CENTER) { + childBox.x1 = x + Math.round(extraWidth / 2); + childBox.x2 = childBox.x1 + naturalWidth; + } else if (child.align === St.Align.END) { + childBox.x2 = x + availWidth; + childBox.x1 = childBox.x2 - naturalWidth; + } else { + childBox.x1 = x; + childBox.x2 = x + naturalWidth; + } + + let [minHeight, naturalHeight] = child.actor.get_preferred_height(-1); + childBox.y1 = Math.round(box.y1 + (height - naturalHeight) / 2); + childBox.y2 = childBox.y1 + naturalHeight; + child.actor.allocate(childBox, flags); - x = childBox.x2 + this._spacing; + x += availWidth + this._spacing; } } }; @@ -322,7 +333,7 @@ PopupSeparatorMenuItem.prototype = { PopupBaseMenuItem.prototype._init.call(this, { reactive: false }); this._drawingArea = new St.DrawingArea({ style_class: 'popup-separator-menu-item' }); - this.addActor(this._drawingArea, 0, -1); + this.addActor(this._drawingArea, { span: -1, expand: true }); this._drawingArea.connect('repaint', Lang.bind(this, this._onRepaint)); }, @@ -367,7 +378,7 @@ PopupSliderMenuItem.prototype = { this._value = Math.max(Math.min(value, 1), 0); this._slider = new St.DrawingArea({ style_class: 'popup-slider-menu-item', reactive: true }); - this.addActor(this._slider, 0, -1); + this.addActor(this._slider, { span: -1, expand: true }); this._slider.connect('repaint', Lang.bind(this, this._sliderRepaint)); this._slider.connect('button-press-event', Lang.bind(this, this._startDragging)); this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); @@ -534,7 +545,7 @@ PopupSwitchMenuItem.prototype = { this._switch = new Switch(active); this.addActor(this.label); - this.addActor(this._switch.actor); + this.addActor(this._switch.actor, { align: St.Align.END }); this.connect('activate', Lang.bind(this,function(from) { this.toggle(); @@ -569,7 +580,7 @@ PopupImageMenuItem.prototype = { this.label = new St.Label({ text: text }); this.addActor(this.label); this._icon = new St.Icon({ style_class: 'popup-menu-icon' }); - this.addActor(this._icon); + this.addActor(this._icon, { align: St.Align.END }); this.setIcon(iconName); }, @@ -723,10 +734,6 @@ PopupMenu.prototype = { } }, - setArrowOrigin: function(origin) { - this._boxPointer.setArrowOrigin(origin); - }, - activateFirst: function() { let children = this._box.get_children(); for (let i = 0; i < children.length; i++) { @@ -852,7 +859,7 @@ PopupSubMenuMenuItem.prototype = { this.label = new St.Label({ text: text }); this.addActor(this.label); - this.addActor(new St.Label({ text: '>' })); + this.addActor(new St.Label({ text: '>' }), { align: St.Align.END }); this.menu = new PopupMenu(this.actor, St.Align.MIDDLE, St.Side.LEFT, 0, true); Main.chrome.addActor(this.menu.actor, { visibleInOverview: true, diff --git a/js/ui/status/power.js b/js/ui/status/power.js index 4d66302f2..3b395d553 100644 --- a/js/ui/status/power.js +++ b/js/ui/status/power.js @@ -74,9 +74,7 @@ Indicator.prototype = { this._batteryItem = new PopupMenu.PopupMenuItem(''); this._primaryPercentage = new St.Label(); - let percentBin = new St.Bin(); - percentBin.set_child(this._primaryPercentage, { x_align: St.Align.END }); - this._batteryItem.addActor(percentBin); + this._batteryItem.addActor(this._primaryPercentage, { align: St.Align.END }); this.menu.addMenuItem(this._batteryItem); this._deviceSep = new PopupMenu.PopupSeparatorMenuItem(); @@ -232,10 +230,8 @@ DeviceItem.prototype = { this._box.add_actor(this._label); this.addActor(this._box); - let percentBin = new St.Bin({ x_align: St.Align.END }); let percentLabel = new St.Label({ text: '%d%%'.format(Math.round(percentage)) }); - percentBin.child = percentLabel; - this.addActor(percentBin); + this.addActor(percentLabel, { align: St.Align.END }); }, _deviceTypeToString: function(type) { From e3aab2a90feb8ccb0a3da95c7bd6f02ac283c550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Thu, 9 Dec 2010 13:20:09 +0100 Subject: [PATCH 04/22] tests: Fix scrolling in border.js and border-radius.js In both tests the scrolled actor's width was fixed to the stage width, so that the scroll bars ended up outside the visible area. Fix by adding an outer container with a fixed width and expanding the scroll view to fill the available area. --- tests/interactive/border-radius.js | 27 +++++----- tests/interactive/borders.js | 81 ++++++++++++++++-------------- 2 files changed, 57 insertions(+), 51 deletions(-) diff --git a/tests/interactive/border-radius.js b/tests/interactive/border-radius.js index 3d5314cd5..670eaf004 100644 --- a/tests/interactive/border-radius.js +++ b/tests/interactive/border-radius.js @@ -10,15 +10,18 @@ let stage = Clutter.Stage.get_default(); stage.width = 640; stage.height = 480; -let vbox = new St.BoxLayout({ vertical: true, - width: stage.width, +let vbox = new St.BoxLayout({ width: stage.width, height: stage.height, - style: 'padding: 10px;' - + 'spacing: 20px;' - + 'background: #ffee88;' }); + style: 'background: #ffee88;' }); +stage.add_actor(vbox); + let scroll = new St.ScrollView(); -scroll.add_actor(vbox); -stage.add_actor(scroll); +vbox.add(scroll, { expand: true }); + +let box = new St.BoxLayout({ vertical: true, + style: 'padding: 10px;' + + 'spacing: 20px;' }); +scroll.add_actor(box); function addTestCase(radii, useGradient) { let background; @@ -29,11 +32,11 @@ function addTestCase(radii, useGradient) { else background = 'background: white;'; - vbox.add(new St.Label({ text: "border-radius: " + radii + ";", - style: 'border: 1px solid black; ' - + 'border-radius: ' + radii + ';' - + 'padding: 5px;' + background }), - { x_fill: false }); + box.add(new St.Label({ text: "border-radius: " + radii + ";", + style: 'border: 1px solid black; ' + + 'border-radius: ' + radii + ';' + + 'padding: 5px;' + background }), + { x_fill: false }); } // uniform backgrounds diff --git a/tests/interactive/borders.js b/tests/interactive/borders.js index 66ee1886a..7c5cda13a 100644 --- a/tests/interactive/borders.js +++ b/tests/interactive/borders.js @@ -10,63 +10,66 @@ let stage = Clutter.Stage.get_default(); stage.width = 640; stage.height = 480; -let vbox = new St.BoxLayout({ vertical: true, - width: stage.width, +let vbox = new St.BoxLayout({ width: stage.width, height: stage.height, - style: 'padding: 10px;' - + 'spacing: 20px;' - + 'background: #ffee88;' }); + style: 'background: #ffee88;' }); +stage.add_actor(vbox); + let scroll = new St.ScrollView(); -scroll.add_actor(vbox); -stage.add_actor(scroll); +vbox.add(scroll, { expand: true }); -vbox.add(new St.Label({ text: "Hello World", - style: 'border: 1px solid black; ' - + 'padding: 5px;' })); +let box = new St.BoxLayout({ vertical: true, + style: 'padding: 10px;' + + 'spacing: 20px;' }); +scroll.add_actor(box); -vbox.add(new St.Label({ text: "Hello Round World", - style: 'border: 3px solid green; ' - + 'border-radius: 8px; ' - + 'padding: 5px;' })); +box.add(new St.Label({ text: "Hello World", + style: 'border: 1px solid black; ' + + 'padding: 5px;' })); -vbox.add(new St.Label({ text: "Hello Background", - style: 'border: 3px solid green; ' - + 'border-radius: 8px; ' - + 'background: white; ' - + 'padding: 5px;' })); +box.add(new St.Label({ text: "Hello Round World", + style: 'border: 3px solid green; ' + + 'border-radius: 8px; ' + + 'padding: 5px;' })); -vbox.add(new St.Label({ text: "Hello Translucent Black Border", - style: 'border: 3px solid rgba(0, 0, 0, 0.4); ' - + 'background: white; ' })); - -vbox.add(new St.Label({ text: "Hello Translucent Background", - style: 'background: rgba(255, 255, 255, 0.3);' })); +box.add(new St.Label({ text: "Hello Background", + style: 'border: 3px solid green; ' + + 'border-radius: 8px; ' + + 'background: white; ' + + 'padding: 5px;' })); -vbox.add(new St.Label({ text: "Border, Padding, Content: 20px" })); +box.add(new St.Label({ text: "Hello Translucent Black Border", + style: 'border: 3px solid rgba(0, 0, 0, 0.4); ' + + 'background: white; ' })); + +box.add(new St.Label({ text: "Hello Translucent Background", + style: 'background: rgba(255, 255, 255, 0.3);' })); + +box.add(new St.Label({ text: "Border, Padding, Content: 20px" })); let b1 = new St.BoxLayout({ vertical: true, style: 'border: 20px solid black; ' + 'background: white; ' + 'padding: 20px;' }); -vbox.add(b1); +box.add(b1); b1.add(new St.BoxLayout({ width: 20, height: 20, style: 'background: black' })); -vbox.add(new St.Label({ text: "Translucent big blue border, with rounding", - style: 'border: 20px solid rgba(0, 0, 255, 0.2); ' - + 'border-radius: 10px; ' - + 'background: white; ' - + 'padding: 10px;' })); +box.add(new St.Label({ text: "Translucent big blue border, with rounding", + style: 'border: 20px solid rgba(0, 0, 255, 0.2); ' + + 'border-radius: 10px; ' + + 'background: white; ' + + 'padding: 10px;' })); -vbox.add(new St.Label({ text: "Transparent border", - style: 'border: 20px solid transparent; ' - + 'background: white; ' - + 'padding: 10px;' })); +box.add(new St.Label({ text: "Transparent border", + style: 'border: 20px solid transparent; ' + + 'background: white; ' + + 'padding: 10px;' })); -vbox.add(new St.Label({ text: "Border Image", - style_class: "border-image", - style: "padding: 10px;" })); +box.add(new St.Label({ text: "Border Image", + style_class: "border-image", + style: "padding: 10px;" })); stage.show(); Clutter.main(); From e41b0bc16d297f3ad06f0cf5cf44bc2b8c2efc08 Mon Sep 17 00:00:00 2001 From: Adel Gadllah Date: Thu, 16 Dec 2010 20:20:07 +0100 Subject: [PATCH 05/22] Remove unused workspace._visible property --- js/ui/workspace.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/js/ui/workspace.js b/js/ui/workspace.js index b7837e853..0f53380af 100644 --- a/js/ui/workspace.js +++ b/js/ui/workspace.js @@ -566,8 +566,6 @@ Workspace.prototype = { Lang.bind(this, this._windowRemoved)); this._repositionWindowsId = 0; - this._visible = false; - this.leavingOverview = false; }, @@ -1206,8 +1204,6 @@ Workspace.prototype = { this.positionWindows(WindowPositionFlags.ANIMATE | WindowPositionFlags.ZOOM); else this.positionWindows(WindowPositionFlags.ZOOM); - - this._visible = true; }, // Animates the return from Overview mode @@ -1253,7 +1249,6 @@ Workspace.prototype = { } } - this._visible = false; }, destroy : function() { From 7279cc1db861c996949d60d295cc4749289685ca Mon Sep 17 00:00:00 2001 From: Adel Gadllah Date: Thu, 16 Dec 2010 20:21:12 +0100 Subject: [PATCH 06/22] Overview: Remove invisible animations Given that the grid view is gone there is no point in animating the window previews on all workspaces anymore so just do it for the current one avoid taking a slow down caused by animating windows on other workspaces. https://bugzilla.gnome.org/show_bug.cgi?id=637353 --- js/ui/workspace.js | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/js/ui/workspace.js b/js/ui/workspace.js index 0f53380af..600249294 100644 --- a/js/ui/workspace.js +++ b/js/ui/workspace.js @@ -396,7 +396,7 @@ WindowOverlay.prototype = { // These parameters are not the values retrieved with // get_transformed_position() and get_transformed_size(), // as windowClone might be moving. - // See Workspace._fadeInWindowOverlay + // See Workspace._showWindowOverlay updatePositions: function(cloneX, cloneY, cloneWidth, cloneHeight) { let button = this.closeButton; let title = this.title; @@ -946,6 +946,9 @@ Workspace.prototype = { let slots = this._computeAllWindowSlots(visibleClones.length); visibleClones = this._orderWindowsByMotionAndStartup(visibleClones, slots); + let currentWorkspace = global.screen.get_active_workspace(); + let isOnCurrentWorkspace = this.metaWorkspace == currentWorkspace; + for (let i = 0; i < visibleClones.length; i++) { let slot = slots[i]; let clone = visibleClones[i]; @@ -962,7 +965,7 @@ Workspace.prototype = { if (overlay) overlay.hide(); - if (animate) { + if (animate && isOnCurrentWorkspace) { if (!metaWindow.showing_on_its_workspace()) { /* Hidden windows should fade in and grow * therefore we need to resize them now so they @@ -992,13 +995,13 @@ Workspace.prototype = { time: Overview.ANIMATION_TIME, transition: 'easeOutQuad', onComplete: Lang.bind(this, function() { - this._fadeInWindowOverlay(clone, overlay); + this._showWindowOverlay(clone, overlay, true); }) }); } else { clone.actor.set_position(x, y); clone.actor.set_scale(scale, scale); - this._fadeInWindowOverlay(clone, overlay); + this._showWindowOverlay(clone, overlay, isOnCurrentWorkspace); } } }, @@ -1019,7 +1022,7 @@ Workspace.prototype = { } }, - _fadeInWindowOverlay: function(clone, overlay) { + _showWindowOverlay: function(clone, overlay, fade) { if (clone.inDrag) return; @@ -1041,17 +1044,21 @@ Workspace.prototype = { if (overlay) { overlay.updatePositions(cloneX, cloneY, cloneWidth, cloneHeight); - overlay.fadeIn(); + if (fade) + overlay.fadeIn(); + else + overlay.show(); } }, - _fadeInAllOverlays: function() { + _showAllOverlays: function() { + let currentWorkspace = global.screen.get_active_workspace(); for (let i = 0; i < this._windows.length; i++) { let clone = this._windows[i]; let overlay = this._windowOverlays[i]; if (this._showOnlyWindows != null && !(clone.metaWindow in this._showOnlyWindows)) continue; - this._fadeInWindowOverlay(clone, overlay); + this._showWindowOverlay(clone, overlay, this.metaWorkspace == currentWorkspace); } }, @@ -1087,7 +1094,7 @@ Workspace.prototype = { showWindowsOverlays: function() { this._windowOverlaysGroup.show(); - this._fadeInAllOverlays(); + this._showAllOverlays(); }, hideWindowsOverlays: function() { @@ -1208,6 +1215,8 @@ Workspace.prototype = { // Animates the return from Overview mode zoomFromOverview : function() { + let currentWorkspace = global.screen.get_active_workspace(); + this.leavingOverview = true; this._hideAllOverlays(); @@ -1219,6 +1228,9 @@ Workspace.prototype = { this._overviewHiddenId = Main.overview.connect('hidden', Lang.bind(this, this._doneLeavingOverview)); + if (this._metaWorkspace == currentWorkspace) + return; + // Position and scale the windows. for (let i = 0; i < this._windows.length; i++) { let clone = this._windows[i]; From 2a19d5f143dbebb23dc9723a9f295556c54153e9 Mon Sep 17 00:00:00 2001 From: Marina Zhurakhinskaya Date: Thu, 16 Dec 2010 15:49:47 -0500 Subject: [PATCH 07/22] NotificationDaemon: add support for resident notifications Resident notifications don't get removed when they are clicked or one of their actions is invoked, and are only removed when the app that created them requests them to be removed or sends another notification. Remove the source when a notification associated with it is removed. Except if the source is a tray icon. Make sure that we pop down the tray when a notification is clicked or one of the actions of a non-resident notification is selected. Based on the initial patch by Jonathan Matthew. https://bugzilla.gnome.org/show_bug.cgi?id=633412 --- js/ui/messageTray.js | 50 ++++++++++++++++++++++++++++++++++--- js/ui/notificationDaemon.js | 21 ++++++++-------- 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index d2c10e66b..50a2f5f3b 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -226,6 +226,7 @@ Notification.prototype = { _init: function(source, title, banner, params) { this.source = source; this.urgent = false; + this.resident = false; this.expanded = false; this._useActionIcons = false; this._customContent = false; @@ -257,7 +258,7 @@ Notification.prototype = { function (actor, event) { if (!this._actionArea || !this._actionArea.contains(event.get_source())) - this.emit('clicked'); + this._onClicked(); })); // The first line should have the title, followed by the @@ -484,7 +485,7 @@ Notification.prototype = { } this._buttonBox.add(button); - button.connect('clicked', Lang.bind(this, function() { this.emit('action-invoked', id); })); + button.connect('clicked', Lang.bind(this, this._onActionInvoked, id)); this._updated(); }, @@ -492,6 +493,10 @@ Notification.prototype = { this.urgent = urgent; }, + setResident: function(resident) { + this.resident = resident; + }, + setUseActionIcons: function(useIcons) { this._useActionIcons = useIcons; }, @@ -696,6 +701,28 @@ Notification.prototype = { return false; }, + _onActionInvoked: function(actor, id) { + this.emit('action-invoked', id); + if (!this.resident) { + // We don't hide a resident notification when the user invokes one of its actions, + // because it is common for such notifications to update themselves with new + // information based on the action. We'd like to display the updated information + // in place, rather than pop-up a new notification. + this.emit('done-displaying'); + this.destroy(); + } + }, + + _onClicked: function() { + this.emit('clicked'); + // We hide all types of notifications once the user clicks on them because the common + // outcome of clicking should be the relevant window being brought forward and the user's + // attention switching to the window. + this.emit('done-displaying'); + if (!this.resident) + this.destroy(); + }, + _onKeyPress: function(actor, event) { let symbol = event.get_key_symbol(); if (symbol == Clutter.Escape) { @@ -811,6 +838,7 @@ Source.prototype = { this.notification = null; this._notificationDestroyedId = 0; this._notificationClickedId = 0; + this._notificationRemoved(); } })); @@ -830,8 +858,13 @@ Source.prototype = { this._iconBin.child = icon; }, - // Default implementation is to do nothing, but subclass can override + // Default implementation is to do nothing, but subclasses can override _notificationClicked: function(notification) { + }, + + // Default implementation is to destroy this source, but subclasses can override + _notificationRemoved: function() { + this.destroy(); } }; Signals.addSignalMethods(Source.prototype); @@ -905,6 +938,7 @@ MessageTray.prototype = { this._notificationBin.hide(); this._notificationQueue = []; this._notification = null; + this._notificationClickedId = 0; this._summaryBin = new St.Bin({ anchor_gravity: Clutter.Gravity.NORTH_EAST }); this.actor.add_actor(this._summaryBin); @@ -926,6 +960,7 @@ MessageTray.prototype = { this._summaryNotificationBoxPointer.actor.hide(); this._summaryNotification = null; + this._summaryNotificationClickedId = 0; this._clickedSummaryItem = null; this._clickedSummaryItemAllocationChangedId = 0; this._expandedSummaryItem = null; @@ -1465,6 +1500,8 @@ MessageTray.prototype = { _showNotification: function() { this._notification = this._notificationQueue.shift(); + this._notificationClickedId = this._notification.connect('done-displaying', + Lang.bind(this, this.escapeTray)); this._notificationBin.child = this._notification.actor; this._notificationBin.opacity = 0; @@ -1562,6 +1599,8 @@ MessageTray.prototype = { this._notificationBin.hide(); this._notificationBin.child = null; this._notification.collapseCompleted(); + this._notification.disconnect(this._notificationClickedId); + this._notificationClickedId = 0; this._notification = null; }, @@ -1636,7 +1675,8 @@ MessageTray.prototype = { _showSummaryNotification: function() { this._summaryNotification = this._clickedSummaryItem.source.notification; - + this._summaryNotificationClickedId = this._summaryNotification.connect('done-displaying', + Lang.bind(this, this.escapeTray)); let index = this._notificationQueue.indexOf(this._summaryNotification); if (index != -1) this._notificationQueue.splice(index, 1); @@ -1706,6 +1746,8 @@ MessageTray.prototype = { this._summaryNotificationState = State.HIDDEN; this._summaryNotificationBoxPointer.bin.child = null; this._summaryNotification.collapseCompleted(); + this._summaryNotification.disconnect(this._summaryNotificationClickedId); + this._summaryNotificationClickedId = 0; let summaryNotification = this._summaryNotification; this._summaryNotification = null; if (this._reNotifyWithSummaryNotificationAfterHide) { diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index 0f2a6c9cd..58f678349 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -291,7 +291,10 @@ NotificationDaemon.prototype = { function(n) { delete this._notifications[id]; })); - notification.connect('action-invoked', Lang.bind(this, this._actionInvoked, source, id)); + notification.connect('action-invoked', Lang.bind(this, + function(n, actionId) { + this._emitActionInvoked(id, actionId); + })); } else { notification.update(summary, body, { icon: iconActor, bannerMarkup: true, @@ -305,6 +308,7 @@ NotificationDaemon.prototype = { } notification.setUrgent(hints.urgency == Urgency.CRITICAL); + notification.setResident(hints.resident == true); let sourceIconActor = source.useNotificationIcon ? this._iconForNotificationData(icon, hints, source.ICON_SIZE) : null; source.notify(notification, sourceIconActor); @@ -352,17 +356,13 @@ NotificationDaemon.prototype = { for (let id in this._sources) { let source = this._sources[id]; if (source.app == tracker.focus_app) { - source.activated(); + if (source.notification && !source.notification.resident) + source.notification.destroy(); return; } } }, - _actionInvoked: function(notification, action, source, id) { - source.activated(); - this._emitActionInvoked(id, action); - }, - _emitNotificationClosed: function(id, reason) { DBus.session.emit_signal('/org/freedesktop/Notifications', 'org.freedesktop.Notifications', @@ -421,6 +421,9 @@ Source.prototype = { }, _setApp: function() { + if (this.app) + return; + this.app = Shell.WindowTracker.get_default().get_app_from_pid(this._pid); if (!this.app) return; @@ -440,12 +443,10 @@ Source.prototype = { }, _notificationClicked: function(notification) { - notification.destroy(); this.openApp(); - this.activated(); }, - activated: function() { + _notificationRemoved: function() { if (!this._isTrayIcon) this.destroy(); }, From 22f4aabadf464b262876c9108ff0b52e3d875552 Mon Sep 17 00:00:00 2001 From: Marina Zhurakhinskaya Date: Wed, 15 Dec 2010 16:30:50 -0500 Subject: [PATCH 08/22] NotificationDaemon: add support for transient notifications Transient notifications are removed after being shown. If the summary is being shown while they appear, they are represented in it by a new source icon. We always create a new source for new transient notifications to ensure that they don't replace the latest persistent notification associated with the source. Because we generally don't want any new or resident notifications to be replaced by others, associating multiple notifications with a source is the next thing we will implement. https://bugzilla.gnome.org/show_bug.cgi?id=633412 --- js/ui/messageTray.js | 26 ++++++++++++- js/ui/notificationDaemon.js | 78 +++++++++++++++++++++++++++---------- 2 files changed, 83 insertions(+), 21 deletions(-) diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index 50a2f5f3b..08ad5c20e 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -227,6 +227,8 @@ Notification.prototype = { this.source = source; this.urgent = false; this.resident = false; + // 'transient' is a reserved keyword in JS, so we have to use an alternate variable name + this.isTransient = false; this.expanded = false; this._useActionIcons = false; this._customContent = false; @@ -497,6 +499,10 @@ Notification.prototype = { this.resident = resident; }, + setTransient: function(isTransient) { + this.isTransient = isTransient; + }, + setUseActionIcons: function(useIcons) { this._useActionIcons = useIcons; }, @@ -808,6 +814,11 @@ Source.prototype = { this.title = title; this._iconBin = new St.Bin({ width: this.ICON_SIZE, height: this.ICON_SIZE }); + this.isTransient = false; + }, + + setTransient: function(isTransient) { + this.isTransient = isTransient; }, // Called to create a new icon actor (of size this.ICON_SIZE). @@ -1068,7 +1079,15 @@ MessageTray.prototype = { } this._summaryItems.push(summaryItem); - this._newSummaryItems.push(summaryItem); + + // We keep this._newSummaryItems to track any new sources that were added to the + // summary and show the summary with them to the user for a short period of time + // after notifications are done showing. However, we don't want that to happen for + // transient sources, which are removed after the notification is shown, but are + // not removed fast enough because of the callbacks to avoid the summary popping up. + // So we just don't add transient sources to this._newSummaryItems . + if (!source.isTransient) + this._newSummaryItems.push(summaryItem); source.connect('notify', Lang.bind(this, this._onNotify)); @@ -1601,7 +1620,10 @@ MessageTray.prototype = { this._notification.collapseCompleted(); this._notification.disconnect(this._notificationClickedId); this._notificationClickedId = 0; + let notification = this._notification; this._notification = null; + if (notification.isTransient) + notification.destroy(); }, _expandNotification: function(autoExpanding) { @@ -1750,6 +1772,8 @@ MessageTray.prototype = { this._summaryNotificationClickedId = 0; let summaryNotification = this._summaryNotification; this._summaryNotification = null; + if (summaryNotification.isTransient && !this._reNotifyWithSummaryNotificationAfterHide) + summaryNotification.destroy(); if (this._reNotifyWithSummaryNotificationAfterHide) { this._onNotify(summaryNotification.source, summaryNotification); this._reNotifyWithSummaryNotificationAfterHide = false; diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index 58f678349..0b2833df2 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -175,14 +175,43 @@ NotificationDaemon.prototype = { } }, - _newSource: function(title, pid) { - let source = new Source(title, pid); - this._sources[pid] = source; + // Returns the source associated with ndata.notification if it is set. + // Otherwise, returns the source associated with the pid if one is + // stored in this._sources and the notification is not transient. + // Otherwise, creates a new source as long as pid is provided. + // + // Either a pid or ndata.notification is needed to retrieve or + // create a source. + _getSource: function(title, pid, ndata) { + if (!pid && !(ndata && ndata.notification)) + return null; - source.connect('destroy', Lang.bind(this, - function() { - delete this._sources[pid]; - })); + // We use notification's source for the notifications we still have + // around that are getting replaced because we don't keep sources + // for transient notifications in this._sources, but we still want + // the notification associated with them to get replaced correctly. + if (ndata && ndata.notification) + return ndata.notification.source; + + let isForTransientNotification = (ndata && ndata.hints['transient'] == true); + + // We don't want to override a persistent notification + // with a transient one from the same sender, so we + // always create a new source object for new transient notifications + // and never add it to this._sources . + if (!isForTransientNotification && this._sources[pid]) + return this._sources[pid]; + + let source = new Source(title, pid); + source.setTransient(isForTransientNotification); + + if (!isForTransientNotification) { + this._sources[pid] = source; + source.connect('destroy', Lang.bind(this, + function() { + delete this._sources[pid]; + })); + } Main.messageTray.add(source); return source; @@ -234,7 +263,8 @@ NotificationDaemon.prototype = { let sender = DBus.getCurrentMessageContext().sender; let pid = this._senderToPid[sender]; - let source = pid ? this._sources[pid] : null; + + let source = this._getSource(appName, pid, ndata); if (source) { this._notifyForSource(source, ndata); @@ -255,16 +285,23 @@ NotificationDaemon.prototype = { if (!ndata) return; - this._senderToPid[sender] = pid; - source = this._sources[pid]; - - if (!source) - source = this._newSource(appName, pid); - source.connect('destroy', Lang.bind(this, - function() { - delete this._senderToPid[sender]; - })); + source = this._getSource(appName, pid, ndata); + // We only store sender-pid entries for persistent sources. + // Removing the entries once the source is destroyed + // would result in the entries associated with transient + // sources removed once the notification is shown anyway. + // However, keeping these pairs would mean that we would + // possibly remove an entry associated with a persistent + // source when a transient source for the same sender is + // distroyed. + if (!source.isTransient) { + this._senderToPid[sender] = pid; + source.connect('destroy', Lang.bind(this, + function() { + delete this._senderToPid[sender]; + })); + } this._notifyForSource(source, ndata); })); @@ -309,6 +346,9 @@ NotificationDaemon.prototype = { notification.setUrgent(hints.urgency == Urgency.CRITICAL); notification.setResident(hints.resident == true); + // 'transient' is a reserved keyword in JS, so we have to retrieve the value + // of the 'transient' hint with hints['transient'] rather than hints.transient + notification.setTransient(hints['transient'] == true); let sourceIconActor = source.useNotificationIcon ? this._iconForNotificationData(icon, hints, source.ICON_SIZE) : null; source.notify(notification, sourceIconActor); @@ -378,9 +418,7 @@ NotificationDaemon.prototype = { }, _onTrayIconAdded: function(o, icon) { - let source = this._sources[icon.pid]; - if (!source) - source = this._newSource(icon.title || icon.wm_class || _("Unknown"), icon.pid); + let source = this._getSource(icon.title || icon.wm_class || _("Unknown"), icon.pid, null); source.setTrayIcon(icon); }, From eef194c3aac3a66f93bf7cb677fdad51661faed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Thu, 16 Dec 2010 22:34:39 +0100 Subject: [PATCH 09/22] shell-info: Use transient notifications According to the designers, system notifications should be transient; so now that transient notifications are supported properly, make use of it instead of using a timeout to remove the associated source. --- js/ui/overview.js | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/js/ui/overview.js b/js/ui/overview.js index 21ca95179..4c3e21cb1 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -28,7 +28,6 @@ const ANIMATION_TIME = 0.25; // We split the screen vertically between the dash and the view selector. const DASH_SPLIT_FRACTION = 0.1; -const SHELL_INFO_HIDE_TIMEOUT = 10; function Source() { this._init(); @@ -61,14 +60,10 @@ function ShellInfo() { ShellInfo.prototype = { _init: function() { this._source = null; - this._timeoutId = 0; this._undoCallback = null; }, _onUndoClicked: function() { - Mainloop.source_remove(this._timeoutId); - this._timeoutId = 0; - if (this._undoCallback) this._undoCallback(); this._undoCallback = null; @@ -77,20 +72,7 @@ ShellInfo.prototype = { this._source.destroy(); }, - _onTimeout: function() { - this._timeoutId = 0; - if (this._source) - this._source.destroy(); - return false; - }, - setMessage: function(text, undoCallback, undoLabel) { - if (this._timeoutId) - Mainloop.source_remove(this._timeoutId); - - this._timeoutId = Mainloop.timeout_add_seconds(SHELL_INFO_HIDE_TIMEOUT, - Lang.bind(this, this._onTimeout)); - if (this._source == null) { this._source = new Source(); this._source.connect('destroy', Lang.bind(this, @@ -106,6 +88,8 @@ ShellInfo.prototype = { else notification.update(text, null, { clear: true }); + notification.setTransient(true); + this._undoCallback = undoCallback; if (undoCallback) { notification.addButton('system-undo', From 9f39ce5d278e30db5f6f88103a84b734eb3ee3a7 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 10 Dec 2010 15:03:26 -0500 Subject: [PATCH 10/22] Explicitly specify JS version See bug 636652 for rationale. https://bugzilla.gnome.org/show_bug.cgi?id=636983 --- src/gnome-shell-plugin.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c index 9d96303c0..69e63a272 100644 --- a/src/gnome-shell-plugin.c +++ b/src/gnome-shell-plugin.c @@ -364,7 +364,10 @@ gnome_shell_plugin_start (MetaPlugin *plugin) shell_js = JSDIR; search_path = g_strsplit(shell_js, ":", -1); - shell_plugin->gjs_context = gjs_context_new_with_search_path(search_path); + shell_plugin->gjs_context = g_object_new (GJS_TYPE_CONTEXT, + "search-path", search_path, + "js-version", "1.8", + NULL); g_strfreev(search_path); /* Disable the gnome-volume-control debug */ From d66207bc68fce03ec07f9a843144a43b4315a31c Mon Sep 17 00:00:00 2001 From: Kjartan Maraas Date: Fri, 17 Dec 2010 14:19:35 +0100 Subject: [PATCH 11/22] =?UTF-8?q?Updated=20Norwegian=20bokm=C3=A5l=20trans?= =?UTF-8?q?lation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- po/nb.po | 90 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 27 deletions(-) diff --git a/po/nb.po b/po/nb.po index a8afe8c23..18e0e0a5b 100644 --- a/po/nb.po +++ b/po/nb.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: gnome-shell 2.91.x\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-12-06 19:04+0100\n" -"PO-Revision-Date: 2010-12-06 19:06+0100\n" +"POT-Creation-Date: 2010-12-17 14:18+0100\n" +"PO-Revision-Date: 2010-12-17 14:19+0100\n" "Last-Translator: Kjartan Maraas \n" "Language-Team: Norwegian bokmål \n" "Language: \n" @@ -393,10 +393,18 @@ msgstr "Vis kildekode" msgid "Web Page" msgstr "Nettside" -#: ../js/ui/overview.js:112 +#: ../js/ui/overview.js:96 msgid "Undo" msgstr "Angre" +#: ../js/ui/overview.js:158 +msgid "Windows" +msgstr "Vinduer" + +#: ../js/ui/overview.js:161 +msgid "Applications" +msgstr "Programmer" + #. TODO - _quit() doesn't really work on apps in state STARTING yet #: ../js/ui/panel.js:470 #, c-format @@ -568,15 +576,15 @@ msgstr "Høy kontrast" msgid "Large Text" msgstr "Stor tekst" -#: ../js/ui/status/power.js:87 +#: ../js/ui/status/power.js:85 msgid "What's using power..." msgstr "Hva bruker strøm..." -#: ../js/ui/status/power.js:90 +#: ../js/ui/status/power.js:88 msgid "Power Settings" msgstr "Innstillinger for strøm" -#: ../js/ui/status/power.js:117 +#: ../js/ui/status/power.js:115 #, c-format msgid "%d hour remaining" msgid_plural "%d hours remaining" @@ -584,75 +592,75 @@ msgstr[0] "%d time gjenstår" msgstr[1] "%d timer gjenstår" #. TRANSLATORS: this is a time string, as in "%d hours %d minutes remaining" -#: ../js/ui/status/power.js:120 +#: ../js/ui/status/power.js:118 #, c-format msgid "%d %s %d %s remaining" msgstr "%d %s %d %s gjenstår" -#: ../js/ui/status/power.js:122 +#: ../js/ui/status/power.js:120 msgid "hour" msgid_plural "hours" msgstr[0] "time" msgstr[1] "timer" -#: ../js/ui/status/power.js:122 +#: ../js/ui/status/power.js:120 msgid "minute" msgid_plural "minutes" msgstr[0] "minutt" msgstr[1] "minutter" -#: ../js/ui/status/power.js:125 +#: ../js/ui/status/power.js:123 #, c-format msgid "%d minute remaining" msgid_plural "%d minutes remaining" msgstr[0] "%d minutt gjenstår" msgstr[1] "%d minutter gjenstår" -#: ../js/ui/status/power.js:244 +#: ../js/ui/status/power.js:240 msgid "AC adapter" msgstr "Strømadapter" -#: ../js/ui/status/power.js:246 +#: ../js/ui/status/power.js:242 msgid "Laptop battery" msgstr "Batteri på bærbar" -#: ../js/ui/status/power.js:248 +#: ../js/ui/status/power.js:244 msgid "UPS" msgstr "UPS" -#: ../js/ui/status/power.js:250 +#: ../js/ui/status/power.js:246 msgid "Monitor" msgstr "Skjerm" -#: ../js/ui/status/power.js:252 +#: ../js/ui/status/power.js:248 msgid "Mouse" msgstr "Mus" -#: ../js/ui/status/power.js:254 +#: ../js/ui/status/power.js:250 msgid "Keyboard" msgstr "Tastatur" -#: ../js/ui/status/power.js:256 +#: ../js/ui/status/power.js:252 msgid "PDA" msgstr "PDA" -#: ../js/ui/status/power.js:258 +#: ../js/ui/status/power.js:254 msgid "Cell phone" msgstr "Mobiltelefon" -#: ../js/ui/status/power.js:260 +#: ../js/ui/status/power.js:256 msgid "Media player" msgstr "Medieavspiller" -#: ../js/ui/status/power.js:262 +#: ../js/ui/status/power.js:258 msgid "Tablet" msgstr "Nettbrett" -#: ../js/ui/status/power.js:264 +#: ../js/ui/status/power.js:260 msgid "Computer" msgstr "Datamaskin" -#: ../js/ui/status/power.js:266 ../src/shell-app-system.c:1012 +#: ../js/ui/status/power.js:262 ../src/shell-app-system.c:1012 msgid "Unknown" msgstr "Ukjent" @@ -668,6 +676,34 @@ msgstr "Mikrofon" msgid "Sound Settings" msgstr "Innstillinger for lyd" +#: ../js/ui/telepathyClient.js:560 +#, c-format +msgid "%s is online." +msgstr "%s er tilkoblet." + +#: ../js/ui/telepathyClient.js:565 +#, c-format +msgid "%s is offline." +msgstr "%s er frakoblet." + +#: ../js/ui/telepathyClient.js:568 +#, c-format +msgid "%s is away." +msgstr "«%s» er borte." + +#: ../js/ui/telepathyClient.js:571 +#, c-format +msgid "%s is busy." +msgstr "%s er opptatt." + +#. Translators: this is a time format string followed by a date. +#. If applicable, replace %X with a strftime format valid for your +#. locale, without seconds. +#: ../js/ui/telepathyClient.js:664 +#, no-c-format +msgid "Sent at %X on %A" +msgstr "Sendt %X på %A" + #: ../js/ui/viewSelector.js:26 msgid "Search your computer" msgstr "Søk på din datamaskin" @@ -715,32 +751,32 @@ msgstr[1] "%u innganger" msgid "System Sounds" msgstr "Systemlyder" -#: ../src/shell-global.c:1163 +#: ../src/shell-global.c:1155 msgid "Less than a minute ago" msgstr "Mindre enn ett minutt siden" -#: ../src/shell-global.c:1167 +#: ../src/shell-global.c:1159 #, c-format msgid "%d minute ago" msgid_plural "%d minutes ago" msgstr[0] "%d minutt siden" msgstr[1] "%d minutter siden" -#: ../src/shell-global.c:1172 +#: ../src/shell-global.c:1164 #, c-format msgid "%d hour ago" msgid_plural "%d hours ago" msgstr[0] "%d time siden" msgstr[1] "%d timer siden" -#: ../src/shell-global.c:1177 +#: ../src/shell-global.c:1169 #, c-format msgid "%d day ago" msgid_plural "%d days ago" msgstr[0] "%d dag siden" msgstr[1] "%d dager siden" -#: ../src/shell-global.c:1182 +#: ../src/shell-global.c:1174 #, c-format msgid "%d week ago" msgid_plural "%d weeks ago" From 2d55eab62ec5b08bb476673e19f2dbe48ec23758 Mon Sep 17 00:00:00 2001 From: Bill Nottingham Date: Fri, 17 Dec 2010 14:16:49 -0500 Subject: [PATCH 12/22] Remove 'What's using power...' menu option The dialog brought up by the "what's using power.." option is currently more confusing than useful to the user. n https://bugzilla.gnome.org/show_bug.cgi?id=636982 --- js/ui/status/power.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/js/ui/status/power.js b/js/ui/status/power.js index 3b395d553..3a909c4cd 100644 --- a/js/ui/status/power.js +++ b/js/ui/status/power.js @@ -82,9 +82,6 @@ Indicator.prototype = { this._otherDevicePosition = 2; this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); - this.menu.addAction(_("What's using power..."),function() { - GLib.spawn_command_line_async('gnome-power-statistics --device wakeups'); - }); this.menu.addAction(_("Power Settings"),function() { GLib.spawn_command_line_async('gnome-control-center power'); }); From bc83890c3980e62a0109e9e7794b87f66e7ab62e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Gonz=C3=A1lez?= Date: Sat, 18 Dec 2010 14:37:27 +0100 Subject: [PATCH 13/22] Updated Spanish translation --- po/es.po | 66 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/po/es.po b/po/es.po index 1ada5d455..5caf174d1 100644 --- a/po/es.po +++ b/po/es.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: gnome-shell.master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" "shell&component=general\n" -"POT-Creation-Date: 2010-12-13 20:48+0000\n" -"PO-Revision-Date: 2010-12-15 21:38+0100\n" +"POT-Creation-Date: 2010-12-17 19:22+0000\n" +"PO-Revision-Date: 2010-12-18 14:23+0100\n" "Last-Translator: Jorge González \n" "Language-Team: Español \n" "MIME-Version: 1.0\n" @@ -467,10 +467,19 @@ msgstr "Ver fuente" msgid "Web Page" msgstr "Página web" -#: ../js/ui/overview.js:112 +#: ../js/ui/overview.js:96 msgid "Undo" msgstr "Deshacer" +#: ../js/ui/overview.js:158 +#| msgid "New Window" +msgid "Windows" +msgstr "Ventanas" + +#: ../js/ui/overview.js:161 +msgid "Applications" +msgstr "Aplicaciones" + #. TODO - _quit() doesn't really work on apps in state STARTING yet #: ../js/ui/panel.js:470 #, c-format @@ -642,15 +651,11 @@ msgstr "Contraste alto" msgid "Large Text" msgstr "Texto:" -#: ../js/ui/status/power.js:87 -msgid "What's using power..." -msgstr "Lo que está usando energía…" - -#: ../js/ui/status/power.js:90 +#: ../js/ui/status/power.js:85 msgid "Power Settings" msgstr "Configuración de energía" -#: ../js/ui/status/power.js:117 +#: ../js/ui/status/power.js:112 #, c-format msgid "%d hour remaining" msgid_plural "%d hours remaining" @@ -658,75 +663,75 @@ msgstr[0] "Queda %d hora" msgstr[1] "Queda %d horas" #. TRANSLATORS: this is a time string, as in "%d hours %d minutes remaining" -#: ../js/ui/status/power.js:120 +#: ../js/ui/status/power.js:115 #, c-format msgid "%d %s %d %s remaining" msgstr "Quedan %d %s %d %s" -#: ../js/ui/status/power.js:122 +#: ../js/ui/status/power.js:117 msgid "hour" msgid_plural "hours" msgstr[0] "hora" msgstr[1] "horas" -#: ../js/ui/status/power.js:122 +#: ../js/ui/status/power.js:117 msgid "minute" msgid_plural "minutes" msgstr[0] "miuto" msgstr[1] "minutos" -#: ../js/ui/status/power.js:125 +#: ../js/ui/status/power.js:120 #, c-format msgid "%d minute remaining" msgid_plural "%d minutes remaining" msgstr[0] "Queda %d minuto" msgstr[1] "Queda %d minutos" -#: ../js/ui/status/power.js:244 +#: ../js/ui/status/power.js:237 msgid "AC adapter" msgstr "Adaptador de corriente" -#: ../js/ui/status/power.js:246 +#: ../js/ui/status/power.js:239 msgid "Laptop battery" msgstr "Batería del portátil" -#: ../js/ui/status/power.js:248 +#: ../js/ui/status/power.js:241 msgid "UPS" msgstr "SAI" -#: ../js/ui/status/power.js:250 +#: ../js/ui/status/power.js:243 msgid "Monitor" msgstr "Monitor" -#: ../js/ui/status/power.js:252 +#: ../js/ui/status/power.js:245 msgid "Mouse" msgstr "Ratón" -#: ../js/ui/status/power.js:254 +#: ../js/ui/status/power.js:247 msgid "Keyboard" msgstr "Teclado" -#: ../js/ui/status/power.js:256 +#: ../js/ui/status/power.js:249 msgid "PDA" msgstr "PDA" -#: ../js/ui/status/power.js:258 +#: ../js/ui/status/power.js:251 msgid "Cell phone" msgstr "Teléfono móvil" -#: ../js/ui/status/power.js:260 +#: ../js/ui/status/power.js:253 msgid "Media player" msgstr "Reproductor multimedia" -#: ../js/ui/status/power.js:262 +#: ../js/ui/status/power.js:255 msgid "Tablet" msgstr "Tableta" -#: ../js/ui/status/power.js:264 +#: ../js/ui/status/power.js:257 msgid "Computer" msgstr "Equipo" -#: ../js/ui/status/power.js:266 ../src/shell-app-system.c:1012 +#: ../js/ui/status/power.js:259 ../src/shell-app-system.c:1012 msgid "Unknown" msgstr "Desconocido" @@ -754,7 +759,6 @@ msgstr "%s está desconectado/a." #: ../js/ui/telepathyClient.js:568 #, c-format -#| msgid "'%s' is ready" msgid "%s is away." msgstr "%s está ausente." @@ -766,8 +770,8 @@ msgstr "%s está ocupado/a." #. Translators: this is a time format string followed by a date. #. If applicable, replace %X with a strftime format valid for your #. locale, without seconds. -#: ../js/ui/telepathyClient.js:663 -#, c-format +#: ../js/ui/telepathyClient.js:664 +#, no-c-format msgid "Sent at %X on %A" msgstr "Enviado a las %X el %A" @@ -874,6 +878,9 @@ msgstr "Buscar" msgid "%1$s: %2$s" msgstr "%1$s: %2$s" +#~ msgid "What's using power..." +#~ msgstr "Lo que está usando energía…" + #~ msgid "Overview workspace view mode" #~ msgstr "Modo de visualización de la vista previa del área de trabajo" @@ -917,9 +924,6 @@ msgstr "%1$s: %2$s" #~ msgid "%H:%M" #~ msgstr "%H:%M" -#~ msgid "Applications" -#~ msgstr "Aplicaciones" - #~ msgid "Recent Documents" #~ msgstr "Documentos recientes" From 9e99a8c25a60183b9a7985bff2c77b3270c65ab2 Mon Sep 17 00:00:00 2001 From: Giovanni Campagna Date: Sat, 24 Jul 2010 13:57:53 +0200 Subject: [PATCH 14/22] Bluetooth status indicator Introduce the new Bluetooth indicator in the System Status area. It is written in JS with St and uses the new GnomeBluetoothApplet library. https://bugzilla.gnome.org/show_bug.cgi?id=618312 --- .gitignore | 1 + configure.ac | 14 +- data/theme/gnome-shell.css | 4 +- js/Makefile.am | 2 + js/misc/config.js.in | 3 + js/ui/main.js | 1 + js/ui/panel.js | 38 +-- js/ui/status/bluetooth.js | 446 ++++++++++++++++++++++++++++++++ src/Makefile.am | 2 + src/gnome-shell-plugin.c | 4 + tools/build/gnome-shell.modules | 10 + 11 files changed, 506 insertions(+), 19 deletions(-) create mode 100644 js/misc/config.js.in create mode 100644 js/ui/status/bluetooth.js diff --git a/.gitignore b/.gitignore index f717e24bb..5a035eb4c 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ data/org.gnome.shell.gschema.xml data/org.gnome.shell.gschema.valid data/org.gnome.accessibility.magnifier.gschema.xml data/org.gnome.accessibility.magnifier.gschema.valid +js/misc/config.js intltool-extract.in intltool-merge.in intltool-update.in diff --git a/configure.ac b/configure.ac index daf833cfb..353ac946c 100644 --- a/configure.ac +++ b/configure.ac @@ -92,9 +92,20 @@ PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-3.0 libcroco-0.6 gnome-desktop-3.0 >= 2.9 PKG_CHECK_MODULES(GDMUSER, dbus-glib-1 gtk+-3.0) PKG_CHECK_MODULES(TRAY, gtk+-3.0) PKG_CHECK_MODULES(GVC, libpulse libpulse-mainloop-glib gobject-2.0) - PKG_CHECK_MODULES(JS_TEST, clutter-x11-1.0 gjs-1.0 gobject-introspection-1.0 gtk+-3.0) +AC_MSG_CHECKING([for bluetooth support]) +PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 2.90.0], + [BLUETOOTH_DIR=`$PKG_CONFIG --variable=libdir gnome-bluetooth-1.0`/gnome-bluetooth + BLUETOOTH_LIBS="-L'$BLUETOOTH_DIR' -lgnome-bluetooth-applet" + AC_SUBST([BLUETOOTH_LIBS],["$BLUETOOTH_LIBS"]) + AC_DEFINE_UNQUOTED([BLUETOOTH_DIR],["$BLUETOOTH_DIR"],[Path to installed GnomeBluetooth typelib and library]) + AC_DEFINE([HAVE_BLUETOOTH],[1],[Define if you have libgnome-bluetooth-applet]) + AC_SUBST([HAVE_BLUETOOTH],[1]) + AC_MSG_RESULT([yes])], + [AC_DEFINE([HAVE_BLUETOOTH],[0]) + AC_MSG_RESULT([no])]) + MUTTER_BIN_DIR=`$PKG_CONFIG --variable=exec_prefix mutter-plugins`/bin # FIXME: metacity-plugins.pc should point directly to its .gir file MUTTER_LIB_DIR=`$PKG_CONFIG --variable=libdir mutter-plugins` @@ -165,6 +176,7 @@ AC_CONFIG_FILES([ Makefile data/Makefile js/Makefile + js/misc/config.js src/Makefile tests/Makefile po/Makefile.in diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 1eaefbfdd..e6b483b8e 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -883,7 +883,7 @@ StTooltip StLabel { color: #bbbbbb; } -.chat-response { +#notification StEntry { padding: 4px; border-radius: 4px; border: 1px solid #565656; @@ -893,7 +893,7 @@ StTooltip StLabel { caret-size: 1px; } -.chat-response:focus { +#notification StEntry:focus { border: 1px solid #3a3a3a; color: #545454; background-color: #e8e8e8; diff --git a/js/Makefile.am b/js/Makefile.am index 98ec73751..64c9ef97e 100644 --- a/js/Makefile.am +++ b/js/Makefile.am @@ -2,6 +2,7 @@ jsdir = $(pkgdatadir)/js nobase_dist_js_DATA = \ + misc/config.js \ misc/docInfo.js \ misc/fileUtils.js \ misc/format.js \ @@ -47,6 +48,7 @@ nobase_dist_js_DATA = \ ui/status/accessibility.js \ ui/status/power.js \ ui/status/volume.js \ + ui/status/bluetooth.js \ ui/telepathyClient.js \ ui/tweener.js \ ui/viewSelector.js \ diff --git a/js/misc/config.js.in b/js/misc/config.js.in new file mode 100644 index 000000000..db8c6da5e --- /dev/null +++ b/js/misc/config.js.in @@ -0,0 +1,3 @@ +/* mode: js2; indent-tabs-mode: nil; tab-size: 4 */ +const HAVE_BLUETOOTH = @HAVE_BLUETOOTH@; + diff --git a/js/ui/main.js b/js/ui/main.js index 82172cf0f..056baab31 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -135,6 +135,7 @@ function start() { notificationDaemon = new NotificationDaemon.NotificationDaemon(); windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler(); telepathyClient = new TelepathyClient.Client(); + panel.startStatusArea(); _startDate = new Date(); diff --git a/js/ui/panel.js b/js/ui/panel.js index 690d992c7..fc4614dc5 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -12,6 +12,7 @@ const Gettext = imports.gettext.domain('gnome-shell'); const _ = Gettext.gettext; const Calendar = imports.ui.calendar; +const Config = imports.misc.config; const Overview = imports.ui.overview; const PopupMenu = imports.ui.popupMenu; const PanelMenu = imports.ui.panelMenu; @@ -36,6 +37,9 @@ const STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION = { 'battery': imports.ui.status.power.Indicator }; +if (Config.HAVE_BLUETOOTH) + STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION['bluetooth'] = imports.ui.status.bluetooth.Indicator; + const CLOCK_FORMAT_KEY = 'format'; const CLOCK_CUSTOM_FORMAT_KEY = 'custom-format'; const CLOCK_SHOW_DATE_KEY = 'show-date'; @@ -815,25 +819,9 @@ Panel.prototype = { this._rightBox.add(trayBox); this._rightBox.add(statusBox); - for (let i = 0; i < STANDARD_TRAY_ICON_ORDER.length; i++) { - let role = STANDARD_TRAY_ICON_ORDER[i]; - let constructor = STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION[role]; - if (!constructor) { - // This icon is not implemented (this is a bug) - continue; - } - let indicator = new constructor(); - statusBox.add(indicator.actor); - this._menus.addMenu(indicator.menu); - } - Main.statusIconDispatcher.connect('status-icon-added', Lang.bind(this, this._onTrayIconAdded)); Main.statusIconDispatcher.connect('status-icon-removed', Lang.bind(this, this._onTrayIconRemoved)); - this._statusmenu = new StatusMenu.StatusMenuButton(); - this._menus.addMenu(this._statusmenu.menu); - this._rightBox.add(this._statusmenu.actor); - // 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 @@ -859,6 +847,24 @@ Panel.prototype = { Main.chrome.addActor(this.actor, { visibleInOverview: true }); }, + startStatusArea: function() { + for (let i = 0; i < STANDARD_TRAY_ICON_ORDER.length; i++) { + let role = STANDARD_TRAY_ICON_ORDER[i]; + let constructor = STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION[role]; + if (!constructor) { + // This icon is not implemented (this is a bug) + continue; + } + let indicator = new constructor(); + this._statusBox.add(indicator.actor); + this._menus.addMenu(indicator.menu); + } + + this._statusmenu = new StatusMenu.StatusMenuButton(); + this._menus.addMenu(this._statusmenu.menu); + this._rightBox.add(this._statusmenu.actor); + }, + hideCalendar: function() { this._clockButton.closeCalendar(); }, diff --git a/js/ui/status/bluetooth.js b/js/ui/status/bluetooth.js new file mode 100644 index 000000000..ead3b1f88 --- /dev/null +++ b/js/ui/status/bluetooth.js @@ -0,0 +1,446 @@ +/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + +const Clutter = imports.gi.Clutter; +const Gdk = imports.gi.Gdk; +const GLib = imports.gi.GLib; +const Gio = imports.gi.Gio; +const GnomeBluetoothApplet = imports.gi.GnomeBluetoothApplet; +const Gtk = imports.gi.Gtk; +const Lang = imports.lang; +const Mainloop = imports.mainloop; +const St = imports.gi.St; +const Shell = imports.gi.Shell; + +const Main = imports.ui.main; +const MessageTray = imports.ui.messageTray; +const PanelMenu = imports.ui.panelMenu; +const PopupMenu = imports.ui.popupMenu; + +const Gettext = imports.gettext.domain('gnome-shell'); +const _ = Gettext.gettext; + +const ConnectionState = { + DISCONNECTED: 0, + CONNECTED: 1, + DISCONNECTING: 2, + CONNECTING: 3 +} + +function Indicator() { + this._init.apply(this, arguments); +} + +Indicator.prototype = { + __proto__: PanelMenu.SystemStatusButton.prototype, + + _init: function() { + PanelMenu.SystemStatusButton.prototype._init.call(this, 'bluetooth-disabled', null); + + GLib.spawn_command_line_sync ('pkill -f "^bluetooth-applet$"'); + this._applet = new GnomeBluetoothApplet.Applet(); + + this._killswitch = new PopupMenu.PopupSwitchMenuItem(_("Bluetooth"), false); + this._applet.connect('notify::killswitch-state', Lang.bind(this, this._updateKillswitch)); + this._killswitch.connect('toggled', Lang.bind(this, function() { + let current_state = this._applet.killswitch_state; + if (current_state != GnomeBluetoothApplet.KillswitchState.HARD_BLOCKED && + current_state != GnomeBluetoothApplet.KillswitchState.NO_ADAPTER) { + this._applet.killswitch_state = this._killswitch.state ? + GnomeBluetoothApplet.KillswitchState.UNBLOCKED: + GnomeBluetoothApplet.KillswitchState.SOFT_BLOCKED; + } else + this._killswitch.setToggleState(false); + })); + + this._discoverable = new PopupMenu.PopupSwitchMenuItem(_("Visibility"), this._applet.discoverable); + this._applet.connect('notify::discoverable', Lang.bind(this, function() { + this._discoverable.setToggleState(this._applet.discoverable); + })); + this._discoverable.connect('toggled', Lang.bind(this, function() { + this._applet.discoverable = this._discoverable.state; + })); + + this._updateKillswitch(); + this.menu.addMenuItem(this._killswitch); + this.menu.addMenuItem(this._discoverable); + this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + + this._fullMenuItems = [new PopupMenu.PopupMenuItem(_("Send Files to Device...")), + new PopupMenu.PopupSeparatorMenuItem(), + new PopupMenu.PopupSeparatorMenuItem(), + new PopupMenu.PopupMenuItem(_("Setup a New Device..."))]; + this._deviceSep = this._fullMenuItems[1]; // hidden if no device exists + + this._fullMenuItems[0].connect('activate', function() { + GLib.spawn_command_line_async('bluetooth-sendto'); + }); + this._fullMenuItems[3].connect('activate', function() { + GLib.spawn_command_line_async('bluetooth-wizard'); + }); + + for (let i = 0; i < this._fullMenuItems.length; i++) { + let item = this._fullMenuItems[i]; + this.menu.addMenuItem(item); + } + + this._deviceItemPosition = 5; + this._deviceItems = []; + this._applet.connect('devices-changed', Lang.bind(this, this._updateDevices)); + this._updateDevices(); + + this._applet.connect('notify::show-full-menu', Lang.bind(this, this._updateFullMenu)); + this._updateFullMenu(); + + this.menu.addAction(_("Bluetooth Settings"), function() { + GLib.spawn_command_line_async('gnome-control-center bluetooth'); + }); + + this._applet.connect('pincode-request', Lang.bind(this, this._pinRequest)); + this._applet.connect('confirm-request', Lang.bind(this, this._confirmRequest)); + this._applet.connect('auth-request', Lang.bind(this, this._authRequest)); + this._applet.connect('cancel-request', Lang.bind(this, this._cancelRequest)); + }, + + _updateKillswitch: function() { + let current_state = this._applet.killswitch_state; + let on = current_state == GnomeBluetoothApplet.KillswitchState.UNBLOCKED; + let can_toggle = current_state != GnomeBluetoothApplet.KillswitchState.NO_ADAPTER && + current_state != GnomeBluetoothApplet.KillswitchState.HARD_BLOCKED; + + this._killswitch.setToggleState(on); + this._killswitch.actor.reactive = can_toggle; + + if (on) { + this._discoverable.actor.show(); + this.setIcon('bluetooth-active'); + } else { + this._discoverable.actor.hide(); + this.setIcon('bluetooth-disabled'); + } + }, + + _updateDevices: function() { + this._destroyAll(this._deviceItems); + this._deviceItems = []; + + let devices = this._applet.get_devices(); + if (devices.length == 0) + this._deviceSep.actor.hide(); + else + this._deviceSep.actor.show(); + for (let i = 0; i < devices.length; i++) { + let d = devices[i]; + let item = this._createDeviceItem(d); + if (item) { + this.menu.addMenuItem(item, this._deviceItemPosition + this._deviceItems.length); + this._deviceItems.push(item); + } + } + }, + + _createDeviceItem: function(device) { + if (!device.can_connect && device.capabilities == GnomeBluetoothApplet.Capabilities.NONE) + return null; + let item = new PopupMenu.PopupSubMenuMenuItem(device.alias); + item._device = device; + + if (device.can_connect) { + item._connected = device.connected; + let menuitem = new PopupMenu.PopupSwitchMenuItem(_("Connection"), device.connected); + + menuitem.connect('toggled', Lang.bind(this, function() { + if (item._connected > ConnectionState.CONNECTED) { + // operation already in progress, revert + menuitem.setToggleState(menuitem.state); + } + if (item._connected) { + item._connected = ConnectionState.DISCONNECTING; + this._applet.disconnect_device(item._device.device_path, function(applet, success) { + if (success) { // apply + item._connected = ConnectionState.DISCONNECTED; + menuitem.setToggleState(false); + } else { // revert + item._connected = ConnectionState.CONNECTED; + menuitem.setToggleState(true); + } + }); + } else { + item._connected = ConnectionState.CONNECTING; + this._applet.connect_device(item._device.device_path, function(applet, success) { + if (success) { // apply + item._connected = ConnectionState.CONNECTED; + menuitem.setToggleState(true); + } else { // revert + item._connected = ConnectionState.DISCONNECTED; + menuitem.setToggleState(false); + } + }); + } + })); + + item.menu.addMenuItem(menuitem); + } + + if (device.capabilities & GnomeBluetoothApplet.Capabilities.OBEX_PUSH) { + item.menu.addAction(_("Send Files..."), Lang.bind(this, function() { + this._applet.send_to_address(device.bdaddr, device.alias); + })); + } + if (device.capabilities & GnomeBluetoothApplet.Capabilities.OBEX_FILE_TRANSFER) { + item.menu.addAction(_("Browse Files..."), Lang.bind(this, function(event) { + this._applet.browse_address(device.bdaddr, event.get_time(), + Lang.bind(this, function(applet, result) { + try { + applet.browse_address_finish(result); + } catch (e) { + this._ensureSource(); + this._source.notify(new MessageTray.Notification(this._source, + _("Bluetooth"), + _("Error browsing device"), + { body: _("The requested device cannot be browsed, error is '%s'").format(e) })); + } + })); + })); + } + + switch (device.type) { + case GnomeBluetoothApplet.Type.KEYBOARD: + item.menu.addAction(_("Keyboard Settings"), function() { + GLib.spawn_command_line_async('gnome-control-center keyboard'); + }); + break; + case GnomeBluetoothApplet.Type.MOUSE: + item.menu.addAction(_("Mouse Settings"), function() { + GLib.spawn_command_line_async('gnome-control-center mouse'); + }); + break; + case GnomeBluetoothApplet.Type.HEADSET: + case GnomeBluetoothApplet.Type.HEADPHONES: + case GnomeBluetoothApplet.Type.OTHER_AUDIO: + item.menu.addAction(_("Sound Settings"), function() { + GLib.spawn_command_line_async('gnome-control-center sound'); + }); + break; + default: + break; + } + + return item; + }, + + _updateFullMenu: function() { + if (this._applet.show_full_menu) { + this._showAll(this._fullMenuItems); + this._showAll(this._deviceItems); + } else { + this._hideAll(this._fullMenuItems); + this._hideAll(this._deviceItems); + } + }, + + _showAll: function(items) { + for (let i = 0; i < items.length; i++) + items[i].actor.show(); + }, + + _hideAll: function(items) { + for (let i = 0; i < items.length; i++) + items[i].actor.hide(); + }, + + _destroyAll: function(items) { + for (let i = 0; i < items.length; i++) + items[i].destroy(); + }, + + _ensureSource: function() { + if (!this._source) { + this._source = new Source(); + Main.messageTray.add(this._source); + } + }, + + _authRequest: function(applet, device_path, name, long_name, uuid) { + this._ensureSource(); + this._source.notify(new AuthNotification(this._source, this._applet, device_path, name, long_name, uuid)); + }, + + _confirmRequest: function(applet, device_path, name, long_name, pin) { + this._ensureSource(); + this._source.notify(new ConfirmNotification(this._source, this._applet, device_path, name, long_name, pin)); + }, + + _pinRequest: function(applet, device_path, name, long_name, numeric) { + this._ensureSource(); + this._source.notify(new PinNotification(this._source, this._applet, device_path, name, long_name, numeric)); + }, + + _cancelRequest: function() { + this._source.destroy(); + } +} + +function Source() { + this._init.apply(this, arguments); +} + +Source.prototype = { + __proto__: MessageTray.Source.prototype, + + _init: function() { + MessageTray.Source.prototype._init.call(this, _("Bluetooth Agent")); + + this._setSummaryIcon(this.createNotificationIcon()); + }, + + notify: function(notification) { + this._private_destroyId = notification.connect('destroy', Lang.bind(this, function(notification) { + if (this.notification == notification) { + // the destroyed notification is the last for this source + this.notification.disconnect(this._private_destroyId); + this.destroy(); + } + })); + + MessageTray.Source.prototype.notify.call(this, notification); + }, + + createNotificationIcon: function() { + return new St.Icon({ icon_name: 'bluetooth-active', + icon_type: St.IconType.SYMBOLIC, + icon_size: this.ICON_SIZE }); + } +} + +function AuthNotification() { + this._init.apply(this, arguments); +} + +AuthNotification.prototype = { + __proto__: MessageTray.Notification.prototype, + + _init: function(source, applet, device_path, name, long_name, uuid) { + MessageTray.Notification.prototype._init.call(this, + source, + _("Bluetooth Agent"), + _("Authorization request from %s").format(name), + { customContent: true }); + this.setResident(true); + + this._applet = applet; + this._devicePath = device_path; + this.addBody(_("Device %s wants access to the service '%s'").format(long_name, uuid)); + + this.addButton('always-grant', _("Always grant access")); + this.addButton('grant', _("Grant this time only")); + this.addButton('reject', _("Reject")); + + this.connect('action-invoked', Lang.bind(this, function(self, action) { + switch (action) { + case 'always-grant': + this._applet.agent_reply_auth(this._devicePath, true, true); + break; + case 'grant': + this._applet.agent_reply_auth(this._devicePath, true, false); + break; + case 'reject': + default: + this._applet.agent_reply_auth(this._devicePath, false, false); + } + this.destroy(); + })); + } +} + +function ConfirmNotification() { + this._init.apply(this, arguments); +} + +ConfirmNotification.prototype = { + __proto__: MessageTray.Notification.prototype, + + _init: function(source, applet, device_path, name, long_name, pin) { + MessageTray.Notification.prototype._init.call(this, + source, + _("Bluetooth Agent"), + _("Pairing confirmation for %s").format(name), + { customContent: true }); + this.setResident(true); + + this._applet = applet; + this._devicePath = device_path; + this.addBody(_("Device %s wants to pair with this computer").format(long_name)); + this.addBody(_("Please confirm whether the PIN '%s' matches the one on the device.").format(pin)); + + this.addButton('matches', _("Matches")); + this.addButton('does-not-match', _("Does not match")); + + this.connect('action-invoked', Lang.bind(this, function(self, action) { + if (action == 'matches') + this._applet.agent_reply_confirm(this._devicePath, true); + else + this._applet.agent_reply_confirm(this._devicePath, false); + this.destroy(); + })); + } +} + +function PinNotification() { + this._init.apply(this, arguments); +} + +PinNotification.prototype = { + __proto__: MessageTray.Notification.prototype, + + _init: function(source, applet, device_path, name, long_name, numeric) { + MessageTray.Notification.prototype._init.call(this, + source, + _("Bluetooth Agent"), + _("Pairing request for %s").format(name), + { customContent: true }); + this.setResident(true); + + this._applet = applet; + this._devicePath = device_path; + this._numeric = numeric; + this.addBody(_("Device %s wants to pair with this computer").format(long_name)); + this.addBody(_("Please enter the PIN mentioned on the device.")); + + this._entry = new St.Entry(); + this._entry.connect('key-release-event', Lang.bind(this, function(entry, event) { + let key = event.get_key_symbol(); + if (key == Clutter.KEY_Return) { + this.emit('action-invoked', 'ok'); + return true; + } else if (key == Clutter.KEY_Escape) { + this.emit('action-invoked', 'cancel'); + return true; + } + return false; + })); + this.addActor(this._entry); + + this.addButton('ok', _("Ok")); + this.addButton('cancel', _("Cancel")); + + this.connect('action-invoked', Lang.bind(this, function(self, action) { + if (action == 'ok') { + if (this._numeric) + this._applet.agent_reply_passkey(this._devicePath, parseInt(this._entry.text)); + else + this._applet.agent_reply_pincode(this._devicePath, this._entry.text); + } else { + if (this._numeric) + this._applet.agent_reply_passkey(this._devicePath, -1); + else + this._applet.agent_reply_pincode(this._devicePath, null); + } + this.destroy(); + })); + }, + + grabFocus: function(lockTray) { + MessageTray.Notification.prototype.grabFocus.call(this, lockTray); + global.stage.set_key_focus(this._entry); + } +} diff --git a/src/Makefile.am b/src/Makefile.am index ee42b33b9..61b58260a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -218,10 +218,12 @@ libgnome_shell_la_LIBADD = \ -lm \ $(MUTTER_PLUGIN_LIBS) \ $(LIBGNOMEUI_LIBS) \ + $(BLUETOOTH_LIBS) \ libst-1.0.la \ libgdmuser-1.0.la \ libtray.la \ libgvc.la + libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags) typelibdir = $(pkglibdir) diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c index 69e63a272..ec23bf317 100644 --- a/src/gnome-shell-plugin.c +++ b/src/gnome-shell-plugin.c @@ -357,6 +357,10 @@ gnome_shell_plugin_start (MetaPlugin *plugin) "GL buffer swap complete event received (with timestamp of completion)", "x"); +#if HAVE_BLUETOOTH + g_irepository_prepend_search_path (BLUETOOTH_DIR); +#endif + g_irepository_prepend_search_path (GNOME_SHELL_PKGLIBDIR); shell_js = g_getenv("GNOME_SHELL_JS"); diff --git a/tools/build/gnome-shell.modules b/tools/build/gnome-shell.modules index 109cc6ac2..94828aa98 100644 --- a/tools/build/gnome-shell.modules +++ b/tools/build/gnome-shell.modules @@ -230,6 +230,7 @@ + @@ -251,6 +252,15 @@ + + + + + + + + + Date: Sat, 18 Dec 2010 13:37:07 -0500 Subject: [PATCH 15/22] configure: Fix case if HAVE_BLUETOOTH is false We still need to AC_SUBST here. --- configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.ac b/configure.ac index 353ac946c..827920a1a 100644 --- a/configure.ac +++ b/configure.ac @@ -104,6 +104,7 @@ PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 2.90.0], AC_SUBST([HAVE_BLUETOOTH],[1]) AC_MSG_RESULT([yes])], [AC_DEFINE([HAVE_BLUETOOTH],[0]) + AC_SUBST([HAVE_BLUETOOTH],[0]) AC_MSG_RESULT([no])]) MUTTER_BIN_DIR=`$PKG_CONFIG --variable=exec_prefix mutter-plugins`/bin From bdfc516715201ab934e2a166acf9a1265aafa4be Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sun, 5 Dec 2010 16:46:48 -0500 Subject: [PATCH 16/22] StTextureCache: Plug leaks in tiled image loading We weren't freeing the whole pixbuf, nor were we the GList of subpixbufs. Plug both of these leaks in the error handling path and in the default case. All of the unreffing/cleanup should happen in the GDestroyNotify for the result, not some in the handler. https://bugzilla.gnome.org/show_bug.cgi?id=636489 --- src/st/st-texture-cache.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c index 35931da36..520b7d750 100644 --- a/src/st/st-texture-cache.c +++ b/src/st/st-texture-cache.c @@ -1183,9 +1183,7 @@ on_sliced_image_loaded (GObject *source_object, { ClutterActor *actor = load_from_pixbuf (GDK_PIXBUF (list->data)); clutter_actor_hide (actor); - clutter_container_add_actor (CLUTTER_CONTAINER (data->group), actor); - g_object_unref (list->data); } } @@ -1198,6 +1196,17 @@ on_data_destroy (gpointer data) g_free (d); } +static void +free_glist_unref_gobjects (gpointer p) +{ + GList *list = p; + GList *iter; + + for (iter = list; iter; iter = iter->next) + g_object_unref (iter->data); + g_list_free (list); +} + static void load_sliced_image (GSimpleAsyncResult *result, GObject *object, @@ -1223,17 +1232,14 @@ load_sliced_image (GSimpleAsyncResult *result, for (x = 0; x < width; x += data->grid_height) { GdkPixbuf *pixbuf = gdk_pixbuf_new_subpixbuf (pix, x, y, data->grid_width, data->grid_height); - if (!pixbuf) - { - g_simple_async_result_set_error (result, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, - "Has failed thumbnail"); - break; - } + g_assert (pixbuf != NULL); res = g_list_append (res, pixbuf); } } - if (res) - g_simple_async_result_set_op_res_gpointer (result, res, (GDestroyNotify)g_list_free); + /* We don't need the original pixbuf anymore, though the subpixbufs + will hold a reference. */ + g_object_unref (pix); + g_simple_async_result_set_op_res_gpointer (result, res, free_glist_unref_gobjects); } /** From 4fd24da4e4d61eabda496a9db4e3b5bfb7d8d600 Mon Sep 17 00:00:00 2001 From: Maxim Ermilov Date: Sat, 18 Dec 2010 22:18:10 +0300 Subject: [PATCH 17/22] app-display: Implement filtering applications by category Add a list of filters to the application view of the view selector, as in the latest mockups https://bugzilla.gnome.org/show_bug.cgi?id=631537 --- data/Makefile.am | 1 + data/gs-applications.menu | 70 ++++++++++++++--- data/theme/filter-selected.svg | 81 +++++++++++++++++++ data/theme/gnome-shell.css | 22 +++--- js/ui/appDisplay.js | 140 +++++++++++++++++++-------------- 5 files changed, 231 insertions(+), 83 deletions(-) create mode 100644 data/theme/filter-selected.svg diff --git a/data/Makefile.am b/data/Makefile.am index 8c0419811..d8cb9064c 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -27,6 +27,7 @@ dist_theme_DATA = \ theme/corner-ripple.png \ theme/dash-placeholder.svg \ theme/dialog-error.svg \ + theme/filter-selected.svg \ theme/gnome-shell.css \ theme/mosaic-view-active.svg \ theme/mosaic-view.svg \ diff --git a/data/gs-applications.menu b/data/gs-applications.menu index 1d0bfdc03..cb4db03a8 100644 --- a/data/gs-applications.menu +++ b/data/gs-applications.menu @@ -1,12 +1,30 @@ - - Apps - Games - Tools - + + Accessories + Games + Graphics + Internet + Multimedia + Office + Other + + Applications /usr/local/share/applications + + + Accessories + + + Utility + + System + + + + + Games @@ -15,21 +33,47 @@ + - Tools + Graphics - Development - System - - Settings - + Graphics - Utility + - Apps + Internet + + + Network + Settings + + + + + + Multimedia + + + AudioVideo + Settings + + + + + + Office + + + Office + + + + + + Other diff --git a/data/theme/filter-selected.svg b/data/theme/filter-selected.svg new file mode 100644 index 000000000..62c8e5b7f --- /dev/null +++ b/data/theme/filter-selected.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index e6b483b8e..98899a2f2 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -470,26 +470,28 @@ StTooltip StLabel { /* Apps */ -.overview-pane { - width: 440px; -} - .icon-grid { spacing: 36px; -shell-grid-item-size: 70px; } .all-app { - padding: 16px 250px 10px 16px; + padding: 16px 25px 16px 16px; + spacing: 20px; } -.app-section-divider-container { - padding-top: 36px; - padding-bottom: 36px; +.app-filter { + font-size: 14px; + font-weight: bold; + height: 40px; + color: #aaa; + width: 200px; } -.app-section-divider { - height: 2px; +.app-filter:selected { + color: #ffffff; + background-image: url("filter-selected.svg"); + background-position: 190px 10px; } #dash > .app-well-app { diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 0cdb5dda6..eaeeb1590 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -29,10 +29,20 @@ function AlphabeticalView() { AlphabeticalView.prototype = { _init: function() { - this.actor = new St.BoxLayout({ vertical: true }); this._grid = new IconGrid.IconGrid({ xAlign: St.Align.START }); this._appSystem = Shell.AppSystem.get_default(); - this.actor.add(this._grid.actor, { y_align: St.Align.START, expand: true }); + + this._filterApp = null; + + let box = new St.BoxLayout({ vertical: true }); + box.add(this._grid.actor, { y_align: St.Align.START, expand: true }); + + this.actor = new St.ScrollView({ x_fill: true, + y_fill: false, + y_align: St.Align.START, + vshadows: true }); + this.actor.add_actor(box); + this.actor.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); }, _removeAll: function() { @@ -40,20 +50,24 @@ AlphabeticalView.prototype = { this._apps = []; }, - _addApp: function(app) { - let appIcon = new AppWellIcon(this._appSystem.get_app(app.get_id())); - appIcon.connect('launching', Lang.bind(this, function() { - this.emit('launching'); - })); - appIcon._draggable.connect('drag-begin', Lang.bind(this, function() { - this.emit('drag-begin'); - })); + _addApp: function(appInfo) { + let appIcon = new AppWellIcon(this._appSystem.get_app(appInfo.get_id())); this._grid.addItem(appIcon.actor); + appIcon._appInfo = appInfo; + if (this._filterApp && !this._filterApp(appInfo)) + appIcon.actor.hide(); + this._apps.push(appIcon); }, + setFilter: function(filter) { + this._filterApp = filter; + for (let i = 0; i < this._apps.length; i++) + this._apps[i].actor.visible = filter(this._apps[i]._appInfo); + }, + refresh: function(apps) { let ids = []; for (let i in apps) @@ -70,8 +84,6 @@ AlphabeticalView.prototype = { } }; -Signals.addSignalMethods(AlphabeticalView.prototype); - function ViewByCategories() { this._init(); } @@ -79,59 +91,78 @@ function ViewByCategories() { ViewByCategories.prototype = { _init: function() { this._appSystem = Shell.AppSystem.get_default(); - this.actor = new St.BoxLayout({ vertical: true }); + this.actor = new St.BoxLayout({ style_class: 'all-app' }); this.actor._delegate = this; + + this._view = new AlphabeticalView(); + + this._filters = new St.BoxLayout({ vertical: true }); + this.actor.add(this._view.actor, { expand: true, x_fill: true, y_fill: true }); + this.actor.add(this._filters, { expand: false, y_fill: false, y_align: St.Align.START }); + this._sections = []; }, - _updateSections: function(apps) { - this._removeAll(); + _selectCategory: function(num) { + if (num != -1) + this._allFilter.remove_style_pseudo_class('selected'); + else + this._allFilter.add_style_pseudo_class('selected'); - let sections = this._appSystem.get_sections(); - if (!sections) - return; - for (let i = 0; i < sections.length; i++) { - if (i) { - let actor = new St.Bin({ style_class: 'app-section-divider' }); - let divider = new St.Bin({ style_class: 'app-section-divider-container', - child: actor, - x_fill: true }); + this._view.setFilter(Lang.bind(this, function(app) { + if (num == -1) + return true; + return this._sections[num].name == app.get_section(); + })); - this.actor.add(divider, { y_fill: false, expand: true }); - } - let _apps = apps.filter(function(app) { - return app.get_section() == sections[i]; - }); - this._sections[i] = { view: new AlphabeticalView(), - apps: _apps, - name: sections[i] }; - this._sections[i].view.connect('launching', Lang.bind(this, function() { - this.emit('launching'); - })); - this._sections[i].view.connect('drag-begin', Lang.bind(this, function() { - this.emit('drag-begin'); - })); - this.actor.add(this._sections[i].view.actor, { y_align: St.Align.START, expand: true }); + for (let i = 0; i < this._sections.length; i++) { + if (i == num) + this._sections[i].filterActor.add_style_pseudo_class('selected'); + else + this._sections[i].filterActor.remove_style_pseudo_class('selected'); } }, + _addFilter: function(name, num) { + let button = new St.Button({ label: name, + style_class: 'app-filter', + x_align: St.Align.START }); + this._filters.add(button, { expand: true, x_fill: true, y_fill: false }); + button.connect('clicked', Lang.bind(this, function() { + this._selectCategory(num); + })); + + if (num != -1) + this._sections[num] = { filterActor: button, + name: name }; + else + this._allFilter = button; + }, + _removeAll: function() { - this.actor.destroy_children(); - this._sections.forEach(function (section) { section.view.disconnectAll(); }); - this._sections = []; + this._filters.destroy_children(); }, refresh: function(apps) { - this._updateSections(apps); - for (let i = 0; i < this._sections.length; i++) { - this._sections[i].view.refresh(this._sections[i].apps); - } + this._removeAll(); + + let sections = this._appSystem.get_sections(); + this._apps = apps; + this._view.refresh(apps); + + this._addFilter(_("All"), -1); + + if (!sections) + return; + + for (let i = 0; i < sections.length; i++) + this._addFilter(sections[i], i); + + this._selectCategory(-1); } }; -Signals.addSignalMethods(ViewByCategories.prototype); - /* This class represents a display containing a collection of application items. * The applications are sorted based on their name. */ @@ -146,17 +177,8 @@ AllAppDisplay.prototype = { Main.queueDeferredWork(this._workId); })); - this._scrollView = new St.ScrollView({ x_fill: true, - y_fill: false, - vshadows: true }); - this.actor = new St.Bin({ style_class: 'all-app', - y_align: St.Align.START, - child: this._scrollView }); - this._appView = new ViewByCategories(); - this._scrollView.add_actor(this._appView.actor); - - this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); + this.actor = new St.Bin({ child: this._appView.actor, x_fill: true, y_fill: true }); this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay)); }, @@ -169,8 +191,6 @@ AllAppDisplay.prototype = { this._appView.refresh(apps); } }; -Signals.addSignalMethods(AllAppDisplay.prototype); - function BaseAppSearchProvider() { this._init(); From dafaab66b171dadb276928b9174e7f553cfc2f21 Mon Sep 17 00:00:00 2001 From: Maxim Ermilov Date: Sat, 18 Dec 2010 22:09:58 +0300 Subject: [PATCH 18/22] iconGrid: Exclude hidden children from the layout As all children were considered for the grid's layout, hidden items showed up as empty space. Instead, exclude hidden children from the layout, so that the grid is only made up of visible items. https://bugzilla.gnome.org/show_bug.cgi?id=631537 --- js/ui/iconGrid.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js index ff6a60b97..e8ac3aca1 100644 --- a/js/ui/iconGrid.js +++ b/js/ui/iconGrid.js @@ -165,8 +165,16 @@ IconGrid.prototype = { alloc.natural_size = nColumns * this._item_size + totalSpacing; }, - _getPreferredHeight: function (grid, forWidth, alloc) { + _getVisibleChildren: function() { let children = this._grid.get_children(); + children = children.filter(function(actor) { + return actor.visible; + }); + return children; + }, + + _getPreferredHeight: function (grid, forWidth, alloc) { + let children = this._getVisibleChildren(); let [nColumns, usedWidth] = this._computeLayout(forWidth); let nRows; if (nColumns > 0) @@ -182,7 +190,7 @@ IconGrid.prototype = { }, _allocate: function (grid, box, flags) { - let children = this._grid.get_children(); + let children = this._getVisibleChildren(); let availWidth = box.x2 - box.x1; let availHeight = box.y2 - box.y1; From 67c05e07b149ed8f82e90e513316894e32aa7c96 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Sun, 19 Dec 2010 01:04:51 +0200 Subject: [PATCH 19/22] Updated Hebrew translation. --- po/he.po | 100 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 46 deletions(-) diff --git a/po/he.po b/po/he.po index 667fa15d3..630aa81c1 100644 --- a/po/he.po +++ b/po/he.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gnome-shell master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-12-13 23:50+0200\n" -"PO-Revision-Date: 2010-12-13 23:52+0200\n" +"POT-Creation-Date: 2010-12-19 01:04+0200\n" +"PO-Revision-Date: 2010-12-19 01:04+0200\n" "Last-Translator: Yaron Shahrabani \n" "Language-Team: Hebrew \n" "MIME-Version: 1.0\n" @@ -390,23 +390,27 @@ msgstr "מבנה _12 שעות" msgid "_24 hour format" msgstr "מבנה _24 שעות" -#: ../js/ui/appDisplay.js:215 +#: ../js/ui/appDisplay.js:154 +msgid "All" +msgstr "הכול" + +#: ../js/ui/appDisplay.js:235 msgid "APPLICATIONS" msgstr "יישומים" -#: ../js/ui/appDisplay.js:245 +#: ../js/ui/appDisplay.js:265 msgid "PREFERENCES" msgstr "העדפות" -#: ../js/ui/appDisplay.js:542 +#: ../js/ui/appDisplay.js:562 msgid "New Window" msgstr "חלון חדש" -#: ../js/ui/appDisplay.js:546 +#: ../js/ui/appDisplay.js:566 msgid "Remove from Favorites" msgstr "הסרה מהמועדפים" -#: ../js/ui/appDisplay.js:547 +#: ../js/ui/appDisplay.js:567 msgid "Add to Favorites" msgstr "הוספה למועדפים" @@ -458,63 +462,71 @@ msgstr "צפייה במקור" msgid "Web Page" msgstr "דף אינטרנט" -#: ../js/ui/overview.js:112 +#: ../js/ui/overview.js:96 msgid "Undo" msgstr "ביטול" +#: ../js/ui/overview.js:158 +msgid "Windows" +msgstr "חלונות" + +#: ../js/ui/overview.js:161 +msgid "Applications" +msgstr "יישומים" + #. TODO - _quit() doesn't really work on apps in state STARTING yet -#: ../js/ui/panel.js:470 +#: ../js/ui/panel.js:474 #, c-format msgid "Quit %s" msgstr "יציאה מ־%s" -#: ../js/ui/panel.js:495 +#: ../js/ui/panel.js:499 msgid "Preferences" msgstr "העדפות" #. Translators: This is the time format with date used #. in 24-hour mode. -#: ../js/ui/panel.js:581 +#: ../js/ui/panel.js:585 msgid "%a %b %e, %R:%S" msgstr "%a %b %e, %R:%S" -#: ../js/ui/panel.js:582 +#: ../js/ui/panel.js:586 msgid "%a %b %e, %R" msgstr "%a %b %e, %R" #. Translators: This is the time format without date used #. in 24-hour mode. -#: ../js/ui/panel.js:586 +#: ../js/ui/panel.js:590 msgid "%a %R:%S" msgstr "%a %R:%S" -#: ../js/ui/panel.js:587 +#: ../js/ui/panel.js:591 msgid "%a %R" msgstr "%a %R" #. Translators: This is a time format with date used #. for AM/PM. -#: ../js/ui/panel.js:594 +#: ../js/ui/panel.js:598 msgid "%a %b %e, %l:%M:%S %p" msgstr "%a %b %e, %l:%M:%S %p" -#: ../js/ui/panel.js:595 +#: ../js/ui/panel.js:599 msgid "%a %b %e, %l:%M %p" msgstr "%a %b %e, %l:%M %p" #. Translators: This is a time format without date used #. for AM/PM. -#: ../js/ui/panel.js:599 +#: ../js/ui/panel.js:603 msgid "%a %l:%M:%S %p" msgstr "%a %l:%M:%S %p" -#: ../js/ui/panel.js:600 +#: ../js/ui/panel.js:604 msgid "%a %l:%M %p" msgstr "%a %l:%M %p" #. 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:745 +#: ../js/ui/panel.js:749 msgid "Activities" msgstr "פעילויות" @@ -633,15 +645,11 @@ msgstr "ניגודיות גבוהה" msgid "Large Text" msgstr "טקסט גדול" -#: ../js/ui/status/power.js:87 -msgid "What's using power..." -msgstr "מה צורך חשמל..." - -#: ../js/ui/status/power.js:90 +#: ../js/ui/status/power.js:85 msgid "Power Settings" msgstr "הגדרות צריכת החשמל" -#: ../js/ui/status/power.js:117 +#: ../js/ui/status/power.js:112 #, c-format msgid "%d hour remaining" msgid_plural "%d hours remaining" @@ -650,26 +658,26 @@ msgstr[1] "נותרו %d שעות" msgstr[2] "נותרו שעתיים" #. TRANSLATORS: this is a time string, as in "%d hours %d minutes remaining" -#: ../js/ui/status/power.js:120 +#: ../js/ui/status/power.js:115 #, c-format msgid "%d %s %d %s remaining" msgstr "%d %s %d %s נותרו" -#: ../js/ui/status/power.js:122 +#: ../js/ui/status/power.js:117 msgid "hour" msgid_plural "hours" msgstr[0] "שעה" msgstr[1] "שעות" msgstr[2] "שעתיים" -#: ../js/ui/status/power.js:122 +#: ../js/ui/status/power.js:117 msgid "minute" msgid_plural "minutes" msgstr[0] "דקה" msgstr[1] "דקות" msgstr[2] "דקות" -#: ../js/ui/status/power.js:125 +#: ../js/ui/status/power.js:120 #, c-format msgid "%d minute remaining" msgid_plural "%d minutes remaining" @@ -677,51 +685,51 @@ msgstr[0] "דקה אחת נותרה" msgstr[1] "%d דקות נותרו" msgstr[2] "שתי דקות נותרו" -#: ../js/ui/status/power.js:244 +#: ../js/ui/status/power.js:237 msgid "AC adapter" msgstr "מתאם חשמל" -#: ../js/ui/status/power.js:246 +#: ../js/ui/status/power.js:239 msgid "Laptop battery" msgstr "סוללת נייד" -#: ../js/ui/status/power.js:248 +#: ../js/ui/status/power.js:241 msgid "UPS" msgstr "אל־פסק" -#: ../js/ui/status/power.js:250 +#: ../js/ui/status/power.js:243 msgid "Monitor" msgstr "צג" -#: ../js/ui/status/power.js:252 +#: ../js/ui/status/power.js:245 msgid "Mouse" msgstr "עכבר" -#: ../js/ui/status/power.js:254 +#: ../js/ui/status/power.js:247 msgid "Keyboard" msgstr "מקלדת" -#: ../js/ui/status/power.js:256 +#: ../js/ui/status/power.js:249 msgid "PDA" msgstr "מחשב כף יד" -#: ../js/ui/status/power.js:258 +#: ../js/ui/status/power.js:251 msgid "Cell phone" msgstr "טלפון סלולרי" -#: ../js/ui/status/power.js:260 +#: ../js/ui/status/power.js:253 msgid "Media player" msgstr "נגן מדיה" -#: ../js/ui/status/power.js:262 +#: ../js/ui/status/power.js:255 msgid "Tablet" msgstr "טבלת שליטה" -#: ../js/ui/status/power.js:264 +#: ../js/ui/status/power.js:257 msgid "Computer" msgstr "מחשב" -#: ../js/ui/status/power.js:266 ../src/shell-app-system.c:1012 +#: ../js/ui/status/power.js:259 ../src/shell-app-system.c:1012 msgid "Unknown" msgstr "לא ידוע" @@ -760,8 +768,8 @@ msgstr "%s עסוק/ה." #. Translators: this is a time format string followed by a date. #. If applicable, replace %X with a strftime format valid for your #. locale, without seconds. -#: ../js/ui/telepathyClient.js:663 -#, c-format +#: ../js/ui/telepathyClient.js:664 +#, no-c-format msgid "Sent at %X on %A" msgstr "נשלח ב־%X בשעה %A" @@ -872,6 +880,9 @@ msgstr "חיפוש" msgid "%1$s: %2$s" msgstr "%1$s: %2$s" +#~ msgid "What's using power..." +#~ msgstr "מה צורך חשמל..." + #~ msgid "Overview workspace view mode" #~ msgstr "Overview workspace view mode" @@ -918,9 +929,6 @@ msgstr "%1$s: %2$s" #~ msgid "%H:%M" #~ msgstr "%H:%M" -#~ msgid "Applications" -#~ msgstr "יישומים" - #~ msgid "Recent Documents" #~ msgstr "מסמכים אחרונים" From f96bc5c0520b0888e7cbc7e5d8b5e88e59e2c10f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Gonz=C3=A1lez?= Date: Sun, 19 Dec 2010 13:14:51 +0100 Subject: [PATCH 20/22] Updated Spanish translation --- po/es.po | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/po/es.po b/po/es.po index 5caf174d1..2e10fd726 100644 --- a/po/es.po +++ b/po/es.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: gnome-shell.master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" "shell&component=general\n" -"POT-Creation-Date: 2010-12-17 19:22+0000\n" -"PO-Revision-Date: 2010-12-18 14:23+0100\n" +"POT-Creation-Date: 2010-12-18 19:25+0000\n" +"PO-Revision-Date: 2010-12-19 13:12+0100\n" "Last-Translator: Jorge González \n" "Language-Team: Español \n" "MIME-Version: 1.0\n" @@ -399,23 +399,27 @@ msgstr "Formato _12 horas" msgid "_24 hour format" msgstr "Formato _24 horas" -#: ../js/ui/appDisplay.js:215 +#: ../js/ui/appDisplay.js:154 +msgid "All" +msgstr "Todas" + +#: ../js/ui/appDisplay.js:235 msgid "APPLICATIONS" msgstr "APLICACIONES" -#: ../js/ui/appDisplay.js:245 +#: ../js/ui/appDisplay.js:265 msgid "PREFERENCES" msgstr "PREFERENCIAS" -#: ../js/ui/appDisplay.js:542 +#: ../js/ui/appDisplay.js:562 msgid "New Window" msgstr "Ventana nueva" -#: ../js/ui/appDisplay.js:546 +#: ../js/ui/appDisplay.js:566 msgid "Remove from Favorites" msgstr "Quitar de los favoritos" -#: ../js/ui/appDisplay.js:547 +#: ../js/ui/appDisplay.js:567 msgid "Add to Favorites" msgstr "Añadir a los favoritos" @@ -472,7 +476,6 @@ msgid "Undo" msgstr "Deshacer" #: ../js/ui/overview.js:158 -#| msgid "New Window" msgid "Windows" msgstr "Ventanas" @@ -481,58 +484,58 @@ msgid "Applications" msgstr "Aplicaciones" #. TODO - _quit() doesn't really work on apps in state STARTING yet -#: ../js/ui/panel.js:470 +#: ../js/ui/panel.js:474 #, c-format msgid "Quit %s" msgstr "Salir de %s" -#: ../js/ui/panel.js:495 +#: ../js/ui/panel.js:499 msgid "Preferences" msgstr "Preferencias" #. Translators: This is the time format with date used #. in 24-hour mode. -#: ../js/ui/panel.js:581 +#: ../js/ui/panel.js:585 msgid "%a %b %e, %R:%S" msgstr "%a %e de %b, %R:%S" -#: ../js/ui/panel.js:582 +#: ../js/ui/panel.js:586 msgid "%a %b %e, %R" msgstr "%a %e de %b, %R" #. Translators: This is the time format without date used #. in 24-hour mode. -#: ../js/ui/panel.js:586 +#: ../js/ui/panel.js:590 msgid "%a %R:%S" msgstr "%a %R:%S" -#: ../js/ui/panel.js:587 +#: ../js/ui/panel.js:591 msgid "%a %R" msgstr "%a %R" #. Translators: This is a time format with date used #. for AM/PM. -#: ../js/ui/panel.js:594 +#: ../js/ui/panel.js:598 msgid "%a %b %e, %l:%M:%S %p" msgstr "%a %e de %b, %H:%M:%S" -#: ../js/ui/panel.js:595 +#: ../js/ui/panel.js:599 msgid "%a %b %e, %l:%M %p" msgstr "%a %e de %b, %H:%M" #. Translators: This is a time format without date used #. for AM/PM. -#: ../js/ui/panel.js:599 +#: ../js/ui/panel.js:603 msgid "%a %l:%M:%S %p" msgstr "%a %H:%M:%S" -#: ../js/ui/panel.js:600 +#: ../js/ui/panel.js:604 msgid "%a %l:%M %p" msgstr "%a %H:%M" #. 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:745 +#: ../js/ui/panel.js:749 msgid "Activities" msgstr "Actividades" From 59b1aa26bbd7dc5738aaff3573d909720ad20487 Mon Sep 17 00:00:00 2001 From: Ivar Smolin Date: Mon, 20 Dec 2010 14:30:54 +0200 Subject: [PATCH 21/22] [l10n] Updated Estonian translation --- po/et.po | 236 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 198 insertions(+), 38 deletions(-) diff --git a/po/et.po b/po/et.po index b2ef72cfa..84c9b65ed 100644 --- a/po/et.po +++ b/po/et.po @@ -8,9 +8,9 @@ msgstr "" "Project-Id-Version: gnome-shell master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" "shell&component=general\n" -"POT-Creation-Date: 2010-10-16 19:24+0000\n" -"PO-Revision-Date: 2010-10-17 17:16+0300\n" -"Last-Translator: Mattias Põldaru \n" +"POT-Creation-Date: 2010-12-18 23:05+0000\n" +"PO-Revision-Date: 2010-12-18 11:47+0200\n" +"Last-Translator: Ivar Smolin \n" "Language-Team: Estonian \n" "Language: et\n" "MIME-Version: 1.0\n" @@ -83,9 +83,6 @@ msgstr "Kui tõene, kuvatakse kalendris kuupäeva ISO nädalate järgi." msgid "List of desktop file IDs for favorite applications" msgstr "Lemmikrakenduste töölauafailide ID-de loend" -msgid "Overview workspace view mode" -msgstr "" - msgid "" "Sets the GStreamer pipeline used to encode recordings. It follows the syntax " "used for gst-launch. The pipeline should have an unconnected sink pad where " @@ -125,11 +122,6 @@ msgstr "" msgid "The gstreamer pipeline used to encode the screencast" msgstr "" -msgid "" -"The selected workspace view mode in the overview. Supported values are " -"\"single\" and \"grid\"." -msgstr "" - msgid "" "The shell normally monitors active applications in order to present the most " "used ones (e.g. in launchers). While this data will be kept private, you may " @@ -270,7 +262,7 @@ msgid "Width of the vertical and horizontal lines that make up the crosshairs." msgstr "Niitristi moodustavate püst- ja rõhtjoone laius" msgid "Clock Format" -msgstr "Kella formaat" +msgstr "Kellaaja vorming" msgid "Clock Preferences" msgstr "Kella eelistused" @@ -290,7 +282,9 @@ msgstr "_12 tunni vorming" msgid "_24 hour format" msgstr "_24 tunni vorming" -#. **** Applications **** +msgid "All" +msgstr "" + msgid "APPLICATIONS" msgstr "Rakendused" @@ -306,9 +300,6 @@ msgstr "Eemalda lemmikutest" msgid "Add to Favorites" msgstr "Lisa lemmikutesse" -msgid "Drag here to add favorites" -msgstr "Lemmikute lisamiseks lohista need siia" - #, c-format msgid "%s has been added to your favorites." msgstr "%s lisati lemmikutesse." @@ -317,22 +308,9 @@ msgstr "%s lisati lemmikutesse." msgid "%s has been removed from your favorites." msgstr "%s eemaldati lemmikutest." -msgid "Find" -msgstr "Otsi" +msgid "Remove" +msgstr "" -msgid "Searching..." -msgstr "Otsimine..." - -msgid "No matching results." -msgstr "Tulemused puuduvad." - -#. **** Places **** -#. Translators: This is in the sense of locations for documents, -#. network locations, etc. -msgid "PLACES & DEVICES" -msgstr "Asukohad ja seadmed" - -#. **** Documents **** msgid "RECENT ITEMS" msgstr "Hiljutised dokumendid" @@ -342,6 +320,8 @@ msgstr "Ühtegi laiendust pole paigaldatud" msgid "Enabled" msgstr "Lubatud" +#. translators: +#. * The device has been disabled msgid "Disabled" msgstr "Keelatud" @@ -360,6 +340,12 @@ msgstr "Veebileht" msgid "Undo" msgstr "Võta tagasi" +msgid "Windows" +msgstr "Aknad" + +msgid "Applications" +msgstr "Rakendused" + #. TODO - _quit() doesn't really work on apps in state STARTING yet #, c-format msgid "Quit %s" @@ -415,6 +401,9 @@ msgstr "Proovi uuesti" msgid "Connect to..." msgstr "Ühendumine..." +msgid "PLACES & DEVICES" +msgstr "Asukohad ja seadmed" + #. Translators: this MUST be either "toggle-switch-us" #. (for toggle switches containing the English words #. "ON" and "OFF") or "toggle-switch-intl" (for toggle @@ -436,14 +425,11 @@ msgstr "Saadaval" msgid "Busy" msgstr "Hõivatud" -msgid "Invisible" -msgstr "Nähtamatu" +msgid "My Account" +msgstr "Minu konto" -msgid "Account Information..." -msgstr "Konto andmed..." - -msgid "System Settings..." -msgstr "Süsteemi sätted..." +msgid "System Settings" +msgstr "Süsteemi sätted" msgid "Lock Screen" msgstr "Lukusta ekraan" @@ -454,9 +440,146 @@ msgstr "Vaheta kasutajat" msgid "Log Out..." msgstr "Logi välja..." +msgid "Suspend..." +msgstr "" + msgid "Shut Down..." msgstr "Lülita välja..." +msgid "Zoom" +msgstr "" + +msgid "Screen Reader" +msgstr "Ekraanilugeja" + +msgid "Screen Keyboard" +msgstr "Ekraaniklaviatuur" + +msgid "Visual Alerts" +msgstr "Visuaalsed märguanded" + +msgid "Sticky Keys" +msgstr "Kleepuvad klahvid" + +msgid "Slow Keys" +msgstr "Aeglased klahvid" + +msgid "Bounce Keys" +msgstr "" + +msgid "Mouse Keys" +msgstr "Hiireklahvid" + +msgid "Universal Access Settings" +msgstr "Universaalse ligipääsu sätted" + +msgid "High Contrast" +msgstr "" + +msgid "Large Text" +msgstr "" + +msgid "Power Settings" +msgstr "Toitesätted..." + +#, c-format +msgid "%d hour remaining" +msgid_plural "%d hours remaining" +msgstr[0] "" +msgstr[1] "" + +#. TRANSLATORS: this is a time string, as in "%d hours %d minutes remaining" +#, c-format +msgid "%d %s %d %s remaining" +msgstr "" + +msgid "hour" +msgid_plural "hours" +msgstr[0] "tund" +msgstr[1] "tundi" + +msgid "minute" +msgid_plural "minutes" +msgstr[0] "minut" +msgstr[1] "minutit" + +#, c-format +msgid "%d minute remaining" +msgid_plural "%d minutes remaining" +msgstr[0] "" +msgstr[1] "" + +msgid "AC adapter" +msgstr "Võrgutoite adapter" + +msgid "Laptop battery" +msgstr "Sülearvuti aku" + +msgid "UPS" +msgstr "UPS" + +msgid "Monitor" +msgstr "Monitor" + +msgid "Mouse" +msgstr "Hiir" + +msgid "Keyboard" +msgstr "Klaviatuur" + +msgid "PDA" +msgstr "" + +msgid "Cell phone" +msgstr "Mobiiltelefon" + +msgid "Media player" +msgstr "Meediaesitaja" + +msgid "Tablet" +msgstr "" + +msgid "Computer" +msgstr "Arvuti" + +msgid "Unknown" +msgstr "Tundmatu" + +msgid "Volume" +msgstr "Helivaljus" + +msgid "Microphone" +msgstr "Mikrofon" + +msgid "Sound Settings" +msgstr "Helisätted" + +#, c-format +msgid "%s is online." +msgstr "%s on ühendatud." + +#, c-format +msgid "%s is offline." +msgstr "%s on ühendamata." + +#, c-format +msgid "%s is away." +msgstr "%s on eemal." + +#, c-format +msgid "%s is busy." +msgstr "%s on hõivatud." + +#. Translators: this is a time format string followed by a date. +#. If applicable, replace %X with a strftime format valid for your +#. locale, without seconds. +#, no-c-format +msgid "Sent at %X on %A" +msgstr "" + +msgid "Search your computer" +msgstr "" + #, c-format msgid "%s has finished starting" msgstr "%s läks käima" @@ -472,6 +595,25 @@ msgstr "Pole võimalik uut tööala lisada, kuna tööalade piir on saavutatud." msgid "Can't remove the first workspace." msgstr "Esimest tööala pole võimalik eemaldada." +#. translators: +#. * The number of sound outputs on a particular device +#, c-format +msgid "%u Output" +msgid_plural "%u Outputs" +msgstr[0] "" +msgstr[1] "" + +#. translators: +#. * The number of sound inputs on a particular device +#, c-format +msgid "%u Input" +msgid_plural "%u Inputs" +msgstr[0] "" +msgstr[1] "" + +msgid "System Sounds" +msgstr "Süsteemi helid" + msgid "Less than a minute ago" msgstr "Vähem kui minuti eest" @@ -518,3 +660,21 @@ msgstr "Otsing" #, c-format msgid "%1$s: %2$s" msgstr "%1$s: %2$s" + +#~ msgid "Drag here to add favorites" +#~ msgstr "Lemmikute lisamiseks lohista need siia" + +#~ msgid "Find" +#~ msgstr "Otsi" + +#~ msgid "Searching..." +#~ msgstr "Otsimine..." + +#~ msgid "No matching results." +#~ msgstr "Tulemused puuduvad." + +#~ msgid "Invisible" +#~ msgstr "Nähtamatu" + +#~ msgid "Account Information..." +#~ msgstr "Konto andmed..." From 6024b87d27b4d52a4d3d3d681d4a3ed09d6acbfa Mon Sep 17 00:00:00 2001 From: Giovanni Campagna Date: Mon, 1 Nov 2010 16:03:28 +0100 Subject: [PATCH 22/22] PopupMenu: handle submenus inline Instead of showing submenus on the left side, make PopupSubMenuMenuItem act like an expander. The sub menu is toggled on click, opened on right/enter/space on the parent item, closed on left on any item or when closing the parent menu. https://bugzilla.gnome.org/show_bug.cgi?id=633476 --- data/theme/gnome-shell.css | 4 + js/ui/panelMenu.js | 2 +- js/ui/popupMenu.js | 470 ++++++++++++++++++++----------------- 3 files changed, 266 insertions(+), 210 deletions(-) diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 98899a2f2..6e777fdea 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -105,6 +105,10 @@ StTooltip StLabel { min-width: 200px; } +.popup-sub-menu { + background-color: #606060; +} + /* The remaining popup-menu sizing is all done in ems, so that if you * override .popup-menu.font-size, everything else will scale with it. */ diff --git a/js/ui/panelMenu.js b/js/ui/panelMenu.js index b0dad5eed..07a7220c8 100644 --- a/js/ui/panelMenu.js +++ b/js/ui/panelMenu.js @@ -50,7 +50,7 @@ Button.prototype = { if (open) { this.actor.add_style_pseudo_class('pressed'); let focus = global.stage.get_key_focus(); - if (!focus || (focus != this.actor && !menu.contains(focus))) + if (!focus || (focus != this.actor && !menu.actor.contains(focus))) this.actor.grab_key_focus(); } else this.actor.remove_style_pseudo_class('pressed'); diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js index 728c1714c..d493c75b9 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -87,7 +87,7 @@ PopupBaseMenuItem.prototype = { }, _onButtonReleaseEvent: function (actor, event) { - this.emit('activate', event); + this.activate(event); return true; }, @@ -95,7 +95,7 @@ PopupBaseMenuItem.prototype = { let symbol = event.get_key_symbol(); if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) { - this.emit('activate', event); + this.activate(event); return true; } return false; @@ -132,11 +132,6 @@ PopupBaseMenuItem.prototype = { this.emit('destroy'); }, - // true if non descendant content includes @actor - contains: function(actor) { - return false; - }, - // adds an actor to the menu item; @params can contain %span // (column span; defaults to 1, -1 means "all the remaining width"), // %expand (defaults to #false), and %align (defaults to @@ -612,76 +607,21 @@ function findNextInCycle(items, current, direction) { return items[mod(cur + direction, items.length)]; } -function PopupMenu() { - this._init.apply(this, arguments); +function PopupMenuBase() { + throw new TypeError('Trying to instantiate abstract class PopupMenuBase'); } -PopupMenu.prototype = { - _init: function(sourceActor, alignment, arrowSide, gap) { +PopupMenuBase.prototype = { + _init: function(sourceActor, styleClass) { this.sourceActor = sourceActor; - this._alignment = alignment; - this._arrowSide = arrowSide; - this._gap = gap; - this._boxPointer = new BoxPointer.BoxPointer(arrowSide, - { x_fill: true, - y_fill: true, - x_align: St.Align.START }); - this.actor = this._boxPointer.actor; - this.actor.style_class = 'popup-menu-boxpointer'; - this._boxWrapper = new Shell.GenericContainer(); - this._boxWrapper.connect('get-preferred-width', Lang.bind(this, this._boxGetPreferredWidth)); - this._boxWrapper.connect('get-preferred-height', Lang.bind(this, this._boxGetPreferredHeight)); - this._boxWrapper.connect('allocate', Lang.bind(this, this._boxAllocate)); - this._boxPointer.bin.set_child(this._boxWrapper); - - this._box = new St.BoxLayout({ style_class: 'popup-menu-content', - vertical: true }); - this._boxWrapper.add_actor(this._box); - this.actor.add_style_class_name('popup-menu'); - - global.focus_manager.add_group(this.actor); - - if (sourceActor._delegate instanceof PopupSubMenuMenuItem) { - this._isSubMenu = true; - this.actor.reactive = true; - this.actor.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent)); - } + this.box = new St.BoxLayout({ style_class: styleClass, + vertical: true }); this.isOpen = false; this._activeMenuItem = null; }, - _boxGetPreferredWidth: function (actor, forHeight, alloc) { - // Update the menuitem column widths - let columnWidths = []; - let items = this._box.get_children(); - for (let i = 0; i < items.length; i++) { - if (items[i]._delegate instanceof PopupBaseMenuItem) { - let itemColumnWidths = items[i]._delegate.getColumnWidths(); - for (let j = 0; j < itemColumnWidths.length; j++) { - if (j >= columnWidths.length || itemColumnWidths[j] > columnWidths[j]) - columnWidths[j] = itemColumnWidths[j]; - } - } - } - for (let i = 0; i < items.length; i++) { - if (items[i]._delegate instanceof PopupBaseMenuItem) - items[i]._delegate.setColumnWidths(columnWidths); - } - - // Now they will request the right sizes - [alloc.min_size, alloc.natural_size] = this._box.get_preferred_width(forHeight); - }, - - _boxGetPreferredHeight: function (actor, forWidth, alloc) { - [alloc.min_size, alloc.natural_size] = this._box.get_preferred_height(forWidth); - }, - - _boxAllocate: function (actor, box, flags) { - this._box.allocate(box, flags); - }, - addAction: function(title, callback) { var menuItem = new PopupMenuItem(title); this.addMenuItem(menuItem); @@ -692,9 +632,29 @@ PopupMenu.prototype = { addMenuItem: function(menuItem, position) { if (position == undefined) - this._box.add(menuItem.actor); + this.box.add(menuItem.actor); else - this._box.insert_actor(menuItem.actor, position); + this.box.insert_actor(menuItem.actor, position); + if (menuItem instanceof PopupSubMenuMenuItem) { + if (position == undefined) + this.box.add(menuItem.menu.actor); + else + this.box.insert_actor(menuItem.menu.actor, position + 1); + menuItem._subMenuActivateId = menuItem.menu.connect('activate', Lang.bind(this, function() { + this.emit('activate'); + this.close(); + })); + menuItem._subMenuActiveChangeId = menuItem.menu.connect('active-changed', Lang.bind(this, function(submenu, submenuItem) { + if (this._activeMenuItem && this._activeMenuItem != submenuItem) + this._activeMenuItem.setActive(false); + this._activeMenuItem = submenuItem; + this.emit('active-changed', submenuItem); + })); + menuItem._closingId = this.connect('open-state-changed', function(self, open) { + if (!open) + menuItem.menu.immediateClose(); + }); + } menuItem._activeChangeId = menuItem.connect('active-changed', Lang.bind(this, function (menuItem, active) { if (active && this._activeMenuItem != menuItem) { if (this._activeMenuItem) @@ -713,17 +673,45 @@ PopupMenu.prototype = { menuItem.connect('destroy', Lang.bind(this, function(emitter) { menuItem.disconnect(menuItem._activateId); menuItem.disconnect(menuItem._activeChangeId); + if (menuItem.menu) { + menuItem.menu.disconnect(menuItem._subMenuActivateId); + menuItem.menu.disconnect(menuItem._subMenuActiveChangeId); + this.disconnect(menuItem._closingId); + } if (menuItem == this._activeMenuItem) this._activeMenuItem = null; })); }, + getColumnWidths: function() { + let columnWidths = []; + let items = this.box.get_children(); + for (let i = 0; i < items.length; i++) { + if (items[i]._delegate instanceof PopupBaseMenuItem || items[i]._delegate instanceof PopupMenuBase) { + let itemColumnWidths = items[i]._delegate.getColumnWidths(); + for (let j = 0; j < itemColumnWidths.length; j++) { + if (j >= columnWidths.length || itemColumnWidths[j] > columnWidths[j]) + columnWidths[j] = itemColumnWidths[j]; + } + } + } + return columnWidths; + }, + + setColumnWidths: function(widths) { + let items = this.box.get_children(); + for (let i = 0; i < items.length; i++) { + if (items[i]._delegate instanceof PopupBaseMenuItem || items[i]._delegate instanceof PopupMenuBase) + items[i]._delegate.setColumnWidths(widths); + } + }, + addActor: function(actor) { - this._box.add(actor); + this.box.add(actor); }, getMenuItems: function() { - return this._box.get_children().map(function (actor) { return actor._delegate; }); + return this.box.get_children().map(function (actor) { return actor._delegate; }).filter(function(item) { return item instanceof PopupBaseMenuItem; }); }, removeAll: function() { @@ -735,76 +723,16 @@ PopupMenu.prototype = { }, activateFirst: function() { - let children = this._box.get_children(); + let children = this.box.get_children(); for (let i = 0; i < children.length; i++) { let actor = children[i]; - if (actor._delegate && actor.visible && actor.reactive) { + if (actor._delegate && actor._delegate instanceof PopupBaseMenuItem && actor.visible && actor.reactive) { actor._delegate.setActive(true); break; } } }, - open: function() { - if (this.isOpen) - return; - - let primary = global.get_primary_monitor(); - - // We need to show it now to force an allocation, - // so that we can query the correct size. - this.actor.show(); - - // Position correctly relative to the sourceActor - let [sourceX, sourceY] = this.sourceActor.get_transformed_position(); - let [sourceWidth, sourceHeight] = this.sourceActor.get_transformed_size(); - - let [minWidth, minHeight, natWidth, natHeight] = this.actor.get_preferred_size(); - - let menuWidth = natWidth, menuHeight = natHeight; - - // Position the non-pointing axis - if (this._isSubmenu) { - if (this._arrowSide == St.Side.TOP || this._arrowSide == St.Side.BOTTOM) { - // vertical submenu - if (sourceY + sourceHeigth + menuHeight + this._gap < primary.y + primary.height) - this._boxPointer._arrowSide = this._arrowSide = St.Side.TOP; - else if (primary.y + menuHeight + this._gap < sourceY) - this._boxPointer._arrowSide = this._arrowSide = St.Side.BOTTOM; - else - this._boxPointer._arrowSide = this._arrowSide = St.Side.TOP; - } else { - // horizontal submenu - if (sourceX + sourceWidth + menuWidth + this._gap < primary.x + primary.width) - this._boxPointer._arrowSide = this._arrowSide = St.Side.LEFT; - else if (primary.x + menuWidth + this._gap < sourceX) - this._boxPointer._arrowSide = this._arrowSide = St.Side.RIGHT; - else - this._boxPointer._arrowSide = this._arrowSide = St.Side.LEFT; - } - } - - this._boxPointer.setPosition(this.sourceActor, this._gap, this._alignment); - - // Now show it - this.actor.reactive = true; - this._boxPointer.animateAppear(); - this.isOpen = true; - this.emit('open-state-changed', true); - }, - - close: function() { - if (!this.isOpen) - return; - - if (this._activeMenuItem) - this._activeMenuItem.setActive(false); - this.actor.reactive = false; - this._boxPointer.animateDisappear(); - this.isOpen = false; - this.emit('open-state-changed', false); - }, - toggle: function() { if (this.isOpen) this.close(); @@ -812,30 +740,6 @@ PopupMenu.prototype = { this.open(); }, - _onKeyPressEvent: function(actor, event) { - // Move focus back to parent menu if the user types Left. - // (This handler is only connected if the PopupMenu is a - // submenu.) - if (this.isOpen && - this._activeMenuItem && - event.get_key_symbol() == Clutter.KEY_Left) { - this._activeMenuItem.setActive(false); - return true; - } - - return false; - }, - - // return true if the actor is inside the menu or - // any actor related to the active submenu - contains: function(actor) { - if (this.actor.contains(actor)) - return true; - if (this._activeMenuItem) - return this._activeMenuItem.contains(actor); - return false; - }, - destroy: function() { this.removeAll(); this.actor.destroy(); @@ -843,7 +747,186 @@ PopupMenu.prototype = { this.emit('destroy'); } }; -Signals.addSignalMethods(PopupMenu.prototype); +Signals.addSignalMethods(PopupMenuBase.prototype); + +function PopupMenu() { + this._init.apply(this, arguments); +} + +PopupMenu.prototype = { + __proto__: PopupMenuBase.prototype, + + _init: function(sourceActor, alignment, arrowSide, gap) { + PopupMenuBase.prototype._init.call (this, sourceActor, 'popup-menu-content'); + + this._alignment = alignment; + this._arrowSide = arrowSide; + this._gap = gap; + + this._boxPointer = new BoxPointer.BoxPointer(arrowSide, + { x_fill: true, + y_fill: true, + x_align: St.Align.START }); + this.actor = this._boxPointer.actor; + this.actor._delegate = this; + this.actor.style_class = 'popup-menu-boxpointer'; + this._boxWrapper = new Shell.GenericContainer(); + this._boxWrapper.connect('get-preferred-width', Lang.bind(this, this._boxGetPreferredWidth)); + this._boxWrapper.connect('get-preferred-height', Lang.bind(this, this._boxGetPreferredHeight)); + this._boxWrapper.connect('allocate', Lang.bind(this, this._boxAllocate)); + this._boxPointer.bin.set_child(this._boxWrapper); + this._boxWrapper.add_actor(this.box); + this.actor.add_style_class_name('popup-menu'); + + global.focus_manager.add_group(this.actor); + this.actor.reactive = true; + }, + + _boxGetPreferredWidth: function (actor, forHeight, alloc) { + let columnWidths = this.getColumnWidths(); + this.setColumnWidths(columnWidths); + + // Now they will request the right sizes + [alloc.min_size, alloc.natural_size] = this.box.get_preferred_width(forHeight); + }, + + _boxGetPreferredHeight: function (actor, forWidth, alloc) { + [alloc.min_size, alloc.natural_size] = this.box.get_preferred_height(forWidth); + }, + + _boxAllocate: function (actor, box, flags) { + this.box.allocate(box, flags); + }, + + setArrowOrigin: function(origin) { + this._boxPointer.setArrowOrigin(origin); + }, + + open: function() { + if (this.isOpen) + return; + + this.isOpen = true; + + this._boxPointer.setPosition(this.sourceActor, this._gap, this._alignment); + this._boxPointer.animateAppear(); + + this.emit('open-state-changed', true); + }, + + close: function() { + if (!this.isOpen) + return; + + if (this._activeMenuItem) + this._activeMenuItem.setActive(false); + + this._boxPointer.animateDisappear(); + + this.isOpen = false; + this.emit('open-state-changed', false); + } +}; + +function PopupSubMenu() { + this._init.apply(this, arguments); +} + +PopupSubMenu.prototype = { + __proto__: PopupMenuBase.prototype, + + _init: function(sourceActor, sourceArrow) { + PopupMenuBase.prototype._init.call(this, sourceActor, 'popup-sub-menu'); + + this._arrow = sourceArrow; + this._arrow.rotation_center_z_gravity = Clutter.Gravity.CENTER; + + this.actor = this.box; + this.actor._delegate = this; + this.actor.clip_to_allocation = true; + this.actor.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent)); + this.actor.hide(); + }, + + open: function() { + if (this.isOpen) + return; + + this.isOpen = true; + + let [naturalHeight, minHeight] = this.actor.get_preferred_height(-1); + this.actor.height = 0; + this.actor.show(); + this.actor._arrow_rotation = this._arrow.rotation_angle_z; + Tweener.addTween(this.actor, + { _arrow_rotation: 90, + height: naturalHeight, + time: 0.25, + onUpdateScope: this, + onUpdate: function() { + this._arrow.rotation_angle_z = this.actor._arrow_rotation; + }, + onCompleteScope: this, + onComplete: function() { + this.actor.set_height(-1); + this.emit('open-state-changed', true); + } + }); + }, + + close: function() { + if (!this.isOpen) + return; + + this.isOpen = false; + + if (this._activeMenuItem) + this._activeMenuItem.setActive(false); + + this.actor._arrow_rotation = this._arrow.rotation_angle_z; + Tweener.addTween(this.actor, + { _arrow_rotation: 0, + height: 0, + time: 0.25, + onCompleteScope: this, + onComplete: function() { + this.actor.hide(); + this.actor.set_height(-1); + + this.emit('open-state-changed', false); + }, + onUpdateScope: this, + onUpdate: function() { + this._arrow.rotation_angle_z = this.actor._arrow_rotation; + } + }); + }, + + immediateClose: function() { + if (!this.isOpen) + return; + + if (this._activeMenuItem) + this._activeMenuItem.setActive(false); + + this.actor.hide(); + + this.isOpen = false; + this.emit('open-state-changed', false); + }, + + _onKeyPressEvent: function(actor, event) { + // Move focus back to parent menu if the user types Left. + + if (this.isOpen && event.get_key_symbol() == Clutter.KEY_Left) { + this.close(); + this.sourceActor._delegate.setActive(true); + return true; + } + + return false; + } +}; function PopupSubMenuMenuItem() { this._init.apply(this, arguments); @@ -853,77 +936,46 @@ PopupSubMenuMenuItem.prototype = { __proto__: PopupBaseMenuItem.prototype, _init: function(text) { - PopupBaseMenuItem.prototype._init.call(this, { activate: false, hover: false }); - this.actor.connect('enter-event', Lang.bind(this, this._mouseEnter)); - this.actor.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent)); + PopupBaseMenuItem.prototype._init.call(this); + + this.actor.add_style_class_name('popup-submenu-menu-item'); this.label = new St.Label({ text: text }); this.addActor(this.label); - this.addActor(new St.Label({ text: '>' }), { align: St.Align.END }); + this._triangle = new St.Label({ text: '\u25B8' }); + this.addActor(this._triangle, { align: St.Align.END }); - this.menu = new PopupMenu(this.actor, St.Align.MIDDLE, St.Side.LEFT, 0, true); - Main.chrome.addActor(this.menu.actor, { visibleInOverview: true, - affectsStruts: false }); - this.menu.actor.hide(); - - this._openStateChangedId = this.menu.connect('open-state-changed', Lang.bind(this, this._subMenuOpenStateChanged)); - this._activateId = this.menu.connect('activate', Lang.bind(this, this._subMenuActivate)); + this.menu = new PopupSubMenu(this.actor, this._triangle); + this.menu.connect('open-state-changed', Lang.bind(this, this._subMenuOpenStateChanged)); }, _subMenuOpenStateChanged: function(menu, open) { - PopupBaseMenuItem.prototype.setActive.call(this, open); - }, - - _subMenuActivate: function(menu, menuItem) { - this.emit('activate', null); - }, - - setMenu: function(newmenu) { - if (this.menu) { - this.menu.close(); - this.menu.disconnect(this._openStateChangedId); - this.menu.disconnect(this._activateId); - } - if (newmenu) { - this._openStateChangedId = newmenu.connect('open-state-changed', Lang.bind(this, this._subMenuOpenStateChanged)); - this._activateId = newmenu.connect('activate', Lang.bind(this, this._subMenuActivate)); - } - this.menu = newmenu; + if (open) + this.actor.add_style_pseudo_class('open'); + else + this.actor.remove_style_pseudo_class('open'); }, destroy: function() { - if (this.menu) - this.menu.destroy(); + this.menu.destroy(); PopupBaseMenuItem.prototype.destroy.call(this); }, - setActive: function(active) { - if (this.menu) { - if (active) - this.menu.open(); - else - this.menu.close(); - } - - PopupBaseMenuItem.prototype.setActive.call(this, active); - }, - _onKeyPressEvent: function(actor, event) { - if (!this.menu) - return false; if (event.get_key_symbol() == Clutter.KEY_Right) { + this.menu.open(); this.menu.activateFirst(); return true; } - return false; + return PopupBaseMenuItem.prototype._onKeyPressEvent.call(this, actor, event); }, - contains: function(actor) { - return this.menu && this.menu.contains(actor); + activate: function(event) { + this.menu.open(); }, - _mouseEnter: function(event) { - this.setActive(true); + _onButtonReleaseEvent: function(actor) { + this.menu.toggle(); } }; @@ -1052,7 +1104,7 @@ PopupMenuManager.prototype = { _eventIsOnActiveMenu: function(event) { let src = event.get_source(); return this._activeMenu != null - && (this._activeMenu.contains(src) || + && (this._activeMenu.actor.contains(src) || (this._activeMenu.sourceActor && this._activeMenu.sourceActor.contains(src))); },