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 2410a049b..d06087eef 100644 --- a/configure.ac +++ b/configure.ac @@ -94,9 +94,21 @@ 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_SUBST([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` @@ -167,6 +179,7 @@ AC_CONFIG_FILES([ Makefile data/Makefile js/Makefile + js/misc/config.js src/Makefile tests/Makefile po/Makefile.in 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 508e0abbe..7234874e9 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. */ @@ -470,26 +474,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 { @@ -1032,7 +1038,7 @@ StTooltip StLabel { color: #bbbbbb; } -.chat-response { +#notification StEntry { padding: 4px; border-radius: 4px; border: 1px solid #565656; @@ -1042,7 +1048,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 3e2e8ae6b..f496db5c0 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 \ @@ -48,6 +49,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/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(); 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; diff --git a/js/ui/main.js b/js/ui/main.js index d9219062b..db3a818f5 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/messageTray.js b/js/ui/messageTray.js index d2c10e66b..08ad5c20e 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -226,6 +226,9 @@ Notification.prototype = { _init: function(source, title, banner, params) { 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; @@ -257,7 +260,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 +487,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 +495,14 @@ Notification.prototype = { this.urgent = urgent; }, + setResident: function(resident) { + this.resident = resident; + }, + + setTransient: function(isTransient) { + this.isTransient = isTransient; + }, + setUseActionIcons: function(useIcons) { this._useActionIcons = useIcons; }, @@ -696,6 +707,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) { @@ -781,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). @@ -811,6 +849,7 @@ Source.prototype = { this.notification = null; this._notificationDestroyedId = 0; this._notificationClickedId = 0; + this._notificationRemoved(); } })); @@ -830,8 +869,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 +949,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 +971,7 @@ MessageTray.prototype = { this._summaryNotificationBoxPointer.actor.hide(); this._summaryNotification = null; + this._summaryNotificationClickedId = 0; this._clickedSummaryItem = null; this._clickedSummaryItemAllocationChangedId = 0; this._expandedSummaryItem = null; @@ -1033,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)); @@ -1465,6 +1519,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,7 +1618,12 @@ MessageTray.prototype = { this._notificationBin.hide(); this._notificationBin.child = null; 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) { @@ -1636,7 +1697,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,8 +1768,12 @@ 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 (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 0f2a6c9cd..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); })); @@ -291,7 +328,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 +345,10 @@ 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); @@ -352,17 +396,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', @@ -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); }, @@ -421,6 +459,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 +481,10 @@ Source.prototype = { }, _notificationClicked: function(notification) { - notification.destroy(); this.openApp(); - this.activated(); }, - activated: function() { + _notificationRemoved: function() { if (!this._isTrayIcon) this.destroy(); }, diff --git a/js/ui/overview.js b/js/ui/overview.js index 5599cd7c5..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', @@ -171,10 +155,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()); diff --git a/js/ui/panel.js b/js/ui/panel.js index 2f31d26be..0f23d716f 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; @@ -37,6 +38,14 @@ 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'; +const CLOCK_SHOW_SECONDS_KEY = 'show-seconds'; + function AnimatedIcon(name, size) { this._init(name, size); } @@ -809,25 +818,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 @@ -853,6 +846,28 @@ 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(); + }, + startupAnimation: function() { this.actor.y = -this.actor.height; Tweener.addTween(this.actor, 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 23c18c2ae..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,29 +132,16 @@ PopupBaseMenuItem.prototype = { this.emit('destroy'); }, - // true if non descendant content includes @actor - contains: function(actor) { - 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 +259,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 +328,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 +373,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 +540,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 +575,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); }, @@ -601,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); @@ -681,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) @@ -702,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() { @@ -723,81 +722,17 @@ PopupMenu.prototype = { } }, - setArrowOrigin: function(origin) { - this._boxPointer.setArrowOrigin(origin); - }, - 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(); @@ -805,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(); @@ -836,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); @@ -846,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: '>' })); + 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(); } }; @@ -1045,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))); }, 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/js/ui/status/power.js b/js/ui/status/power.js index 4d66302f2..3a909c4cd 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(); @@ -84,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'); }); @@ -232,10 +227,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) { diff --git a/js/ui/workspace.js b/js/ui/workspace.js index b7837e853..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; @@ -566,8 +566,6 @@ Workspace.prototype = { Lang.bind(this, this._windowRemoved)); this._repositionWindowsId = 0; - this._visible = false; - this.leavingOverview = false; }, @@ -948,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]; @@ -964,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 @@ -994,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); } } }, @@ -1021,7 +1022,7 @@ Workspace.prototype = { } }, - _fadeInWindowOverlay: function(clone, overlay) { + _showWindowOverlay: function(clone, overlay, fade) { if (clone.inDrag) return; @@ -1043,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); } }, @@ -1089,7 +1094,7 @@ Workspace.prototype = { showWindowsOverlays: function() { this._windowOverlaysGroup.show(); - this._fadeInAllOverlays(); + this._showAllOverlays(); }, hideWindowsOverlays: function() { @@ -1206,12 +1211,12 @@ Workspace.prototype = { this.positionWindows(WindowPositionFlags.ANIMATE | WindowPositionFlags.ZOOM); else this.positionWindows(WindowPositionFlags.ZOOM); - - this._visible = true; }, // Animates the return from Overview mode zoomFromOverview : function() { + let currentWorkspace = global.screen.get_active_workspace(); + this.leavingOverview = true; this._hideAllOverlays(); @@ -1223,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]; @@ -1253,7 +1261,6 @@ Workspace.prototype = { } } - this._visible = false; }, destroy : function() { diff --git a/po/es.po b/po/es.po index 91362e889..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-02 18:10+0000\n" -"PO-Revision-Date: 2010-12-11 16:42+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" @@ -467,63 +471,71 @@ 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 "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 +#: ../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" @@ -642,101 +654,87 @@ 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 -#| msgid "System Settings" +#: ../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 ago" -#| msgid_plural "%d hours ago" msgid "%d hour remaining" msgid_plural "%d hours remaining" 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 -#| msgid "%d minute ago" -#| msgid_plural "%d minutes ago" +#: ../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 ago" -#| msgid_plural "%d minutes ago" 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 -#| msgid "Mouse Keys" +#: ../js/ui/status/power.js:245 msgid "Mouse" msgstr "Ratón" -#: ../js/ui/status/power.js:254 -#| msgid "Screen Keyboard" +#: ../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 -#| msgid "Enabled" +#: ../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" @@ -749,10 +747,37 @@ 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 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:664 +#, no-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 +825,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" @@ -856,6 +881,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" @@ -899,9 +927,6 @@ msgstr "%1$s: %2$s" #~ msgid "%H:%M" #~ msgstr "%H:%M" -#~ msgid "Applications" -#~ msgstr "Aplicaciones" - #~ msgid "Recent Documents" #~ msgstr "Documentos recientes" 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..." 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 "מסמכים אחרונים" 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" 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 9d96303c0..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"); @@ -364,7 +368,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 */ 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); } /** 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(); 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 @@ + + + + + + + + +