From 868bf5838d5a669efb8799827ab817435306e4cb Mon Sep 17 00:00:00 2001 From: Hellyna Ng Date: Sat, 12 Feb 2011 03:43:01 +0800 Subject: [PATCH] MessageTray: add right click menu for summary items This provides straight forward controls for opening the corresponding application or removing the summary item. https://bugzilla.gnome.org/show_bug.cgi?id=617224 --- data/theme/gnome-shell.css | 10 +- js/ui/messageTray.js | 191 ++++++++++++++++++++------------ js/ui/notificationDaemon.js | 2 +- js/ui/popupMenu.js | 8 +- js/ui/telepathyClient.js | 2 +- js/ui/windowAttentionHandler.js | 2 +- 6 files changed, 139 insertions(+), 76 deletions(-) diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index b317e6eb0..da69a3e67 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -977,7 +977,7 @@ StTooltip StLabel { padding-bottom: 8px; } -.summary-notification-boxpointer { +.summary-boxpointer { -arrow-border-radius: 9px; -arrow-background-color: rgba(0,0,0,0.9); -arrow-border-width: 2px; @@ -986,12 +986,18 @@ StTooltip StLabel { -arrow-rise: 15px; } -.summary-notification-boxpointer #notification { +.summary-boxpointer #notification { border-radius: 9px; background: rgba(0,0,0,0) !important; padding-bottom: 12px; } +.summary-boxpointer #summary-right-click-menu { + font-size: 14px; + padding-top: 12px; + padding-bottom: 12px; +} + #notification-scrollview { max-height: 10em; } diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index d15bd62ce..29d882cd8 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -15,6 +15,7 @@ const St = imports.gi.St; const BoxPointer = imports.ui.boxpointer; const GnomeSession = imports.misc.gnomeSession; const Main = imports.ui.main; +const PopupMenu = imports.ui.popupMenu; const Params = imports.misc.params; const Tweener = imports.ui.tweener; const Util = imports.misc.util; @@ -853,7 +854,7 @@ Source.prototype = { this.notification = notification; - this._notificationClickedId = notification.connect('clicked', Lang.bind(this, this._notificationClicked)); + this._notificationClickedId = notification.connect('clicked', Lang.bind(this, this.open)); this._notificationDestroyedId = notification.connect('destroy', Lang.bind(this, function () { if (this.notification == notification) { @@ -881,7 +882,7 @@ Source.prototype = { }, // Default implementation is to do nothing, but subclasses can override - _notificationClicked: function(notification) { + open: function(notification) { }, // Default implementation is to destroy this source, but subclasses can override @@ -917,6 +918,27 @@ SummaryItem.prototype = { this._sourceBox.add_actor(this._sourceIcon); this._sourceBox.add_actor(this._sourceTitleBin, { expand: true }); this.actor.child = this._sourceBox; + this.rightClickMenu = new St.BoxLayout({ name: 'summary-right-click-menu', + vertical: true }); + + let item; + + item = new PopupMenu.PopupMenuItem(_("Open")); + item.connect('activate', Lang.bind(this, function() { + source.open(); + this.emit('right-click-menu-done-displaying'); + })); + this.rightClickMenu.add(item.actor); + + item = new PopupMenu.PopupMenuItem(_("Remove")); + item.connect('activate', Lang.bind(this, function() { + source.destroy(); + this.emit('right-click-menu-done-displaying'); + })); + this.rightClickMenu.add(item.actor); + + let focusManager = St.FocusManager.get_for_stage(global.stage); + focusManager.add_group(this.rightClickMenu); }, // getTitleNaturalWidth, getTitleWidth, and setTitleWidth include @@ -943,6 +965,7 @@ SummaryItem.prototype = { this._sourceTitle.clutter_text.ellipsize = mode; } }; +Signals.addSignalMethods(SummaryItem.prototype); function MessageTray() { this._init(); @@ -980,17 +1003,19 @@ MessageTray.prototype = { this._summaryMotionId = 0; - this._summaryNotificationBoxPointer = new BoxPointer.BoxPointer(St.Side.BOTTOM, - { reactive: true, - track_hover: true }); - this._summaryNotificationBoxPointer.actor.style_class = 'summary-notification-boxpointer'; - this.actor.add_actor(this._summaryNotificationBoxPointer.actor); - this._summaryNotificationBoxPointer.actor.lower_bottom(); - this._summaryNotificationBoxPointer.actor.hide(); + this._summaryBoxPointer = new BoxPointer.BoxPointer(St.Side.BOTTOM, + { reactive: true, + track_hover: true }); + this._summaryBoxPointer.actor.style_class = 'summary-boxpointer'; + this.actor.add_actor(this._summaryBoxPointer.actor); + this._summaryBoxPointer.actor.lower_bottom(); + this._summaryBoxPointer.actor.hide(); this._summaryNotification = null; this._summaryNotificationClickedId = 0; + this._summaryRightClickMenuClickedId = 0; this._clickedSummaryItem = null; + this._clickedSummaryItemMouseButton = -1; this._clickedSummaryItemAllocationChangedId = 0; this._expandedSummaryItem = null; this._summaryItemTitleWidth = 0; @@ -1004,7 +1029,7 @@ MessageTray.prototype = { this._focusGrabber = new FocusGrabber(); this._focusGrabber.connect('focus-grabbed', Lang.bind(this, function() { - if (this._summaryNotification) + if (this._summaryBoxPointer.bin.child) this._lock(); })); this._focusGrabber.connect('focus-ungrabbed', Lang.bind(this, this._unlock)); @@ -1027,7 +1052,7 @@ MessageTray.prototype = { this._notificationState = State.HIDDEN; this._notificationTimeoutId = 0; this._notificationExpandedId = 0; - this._summaryNotificationState = State.HIDDEN; + this._summaryBoxPointerState = State.HIDDEN; this._summaryNotificationTimeoutId = 0; this._summaryNotificationExpandedId = 0; this._overviewVisible = Main.overview.visible; @@ -1037,7 +1062,7 @@ MessageTray.prototype = { Main.chrome.addActor(this.actor, { affectsStruts: false, visibleInOverview: true }); Main.chrome.trackActor(this._notificationBin); - Main.chrome.trackActor(this._summaryNotificationBoxPointer.actor); + Main.chrome.trackActor(this._summaryBoxPointer.actor); global.gdk_screen.connect('monitors-changed', Lang.bind(this, this._setSizePosition)); @@ -1138,9 +1163,9 @@ MessageTray.prototype = { this._onSummaryItemHoverChanged(summaryItem); })); - summaryItem.actor.connect('clicked', Lang.bind(this, - function () { - this._onSummaryItemClicked(summaryItem); + summaryItem.actor.connect('button-press-event', Lang.bind(this, + function (actor, event) { + this._onSummaryItemClicked(summaryItem, event); })); source.connect('destroy', Lang.bind(this, this._onSourceDestroy)); @@ -1354,11 +1379,16 @@ MessageTray.prototype = { this._expandedSummaryItem.setEllipsization(Pango.EllipsizeMode.END); }, - _onSummaryItemClicked: function(summaryItem) { - if (!this._clickedSummaryItem || this._clickedSummaryItem != summaryItem) + _onSummaryItemClicked: function(summaryItem, event) { + let clickedButton = event.get_button(); + if (!this._clickedSummaryItem || + this._clickedSummaryItem != summaryItem || + this._clickedSummaryItemMouseButton != clickedButton) { this._clickedSummaryItem = summaryItem; - else + this._clickedSummaryItemMouseButton = clickedButton; + } else { this._unsetClickedSummaryItem(); + } this._updateState(); }, @@ -1385,7 +1415,7 @@ MessageTray.prototype = { // leaving the tray. The tray is locked when the summary notification is visible anyway, but we // should treat the mouse being over the summary notification as the tray being left for collapsing // any expanded summary item other than the one related to the notification. - if (this._summaryNotificationBoxPointer.bin.hover) + if (this._summaryBoxPointer.bin.hover) return; this._useLongerTrayLeftTimeout = false; @@ -1543,19 +1573,23 @@ MessageTray.prototype = { } // Summary notification - let haveSummaryNotification = this._clickedSummaryItem != null; - let summaryNotificationIsMainNotification = (haveSummaryNotification && + let haveClickedSummaryItem = this._clickedSummaryItem != null; + let summaryNotificationIsMainNotification = (haveClickedSummaryItem && this._clickedSummaryItem.source.notification == this._notification); - let canShowSummaryNotification = this._summaryState == State.SHOWN; - let wrongSummaryNotification = (haveSummaryNotification && + let canShowSummaryBoxPointer = this._summaryState == State.SHOWN; + let wrongSummaryNotification = (this._clickedSummaryItemMouseButton == 1 && this._summaryNotification != this._clickedSummaryItem.source.notification); + let wrongSummaryRightClickMenu = (this._clickedSummaryItemMouseButton == 3 && + this._summaryBoxPointer.bin.child != this._clickedSummaryItem.rightClickMenu); + let wrongSummaryBoxPointer = (haveClickedSummaryItem && + (wrongSummaryNotification || wrongSummaryRightClickMenu)); - if (this._summaryNotificationState == State.HIDDEN) { - if (haveSummaryNotification && !summaryNotificationIsMainNotification && canShowSummaryNotification) - this._showSummaryNotification(); - } else if (this._summaryNotificationState == State.SHOWN) { - if (!haveSummaryNotification || !canShowSummaryNotification || wrongSummaryNotification) - this._hideSummaryNotification(); + if (this._summaryBoxPointerState == State.HIDDEN) { + if (haveClickedSummaryItem && !summaryNotificationIsMainNotification && canShowSummaryBoxPointer) + this._showSummaryBoxPointer(); + } else if (this._summaryBoxPointerState == State.SHOWN) { + if (!haveClickedSummaryItem || !canShowSummaryBoxPointer || wrongSummaryBoxPointer) + this._hideSummaryBoxPointer(); } // Tray itself @@ -1813,44 +1847,53 @@ MessageTray.prototype = { this._expandedSummaryItemTitleWidth = this._summaryItemTitleWidth; }, - _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); + _showSummaryBoxPointer: function() { + if (this._clickedSummaryItemMouseButton == 1) { + let clickedSummaryItemNotification = this._clickedSummaryItem.source.notification; + let index = this._notificationQueue.indexOf(clickedSummaryItemNotification); + if (index != -1) + this._notificationQueue.splice(index, 1); - this._summaryNotificationBoxPointer.bin.child = this._summaryNotification.actor; - this._focusGrabber.grabFocus(this._summaryNotification.actor); + this._summaryNotification = clickedSummaryItemNotification; + this._summaryNotificationClickedId = this._summaryNotification.connect('done-displaying', + Lang.bind(this, this._escapeTray)); + this._summaryBoxPointer.bin.child = this._summaryNotification.actor; + if (!this._summaryNotificationExpandedId) + this._summaryNotificationExpandedId = this._summaryNotification.connect('expanded', + Lang.bind(this, this._onSummaryBoxPointerExpanded)); + this._summaryNotification.expand(false); + } else if (this._clickedSummaryItemMouseButton == 3) { + this._summaryRightClickMenuClickedId = this._clickedSummaryItem.connect('right-click-menu-done-displaying', + Lang.bind(this, this._escapeTray)); + this._summaryBoxPointer.bin.child = this._clickedSummaryItem.rightClickMenu; + } + + this._focusGrabber.grabFocus(this._summaryBoxPointer.bin.child); - if (!this._summaryNotificationExpandedId) - this._summaryNotificationExpandedId = this._summaryNotification.connect('expanded', Lang.bind(this, this._onSummaryNotificationExpanded)); - this._summaryNotification.expand(false); this._clickedSummaryItemAllocationChangedId = this._clickedSummaryItem.actor.connect('allocation-changed', - Lang.bind(this, this._adjustNotificationBoxPointerPosition)); + Lang.bind(this, this._adjustSummaryBoxPointerPosition)); // _clickedSummaryItem.actor can change absolute postiion without changing allocation this._summaryMotionId = this._summary.connect('allocation-changed', - Lang.bind(this, this._adjustNotificationBoxPointerPosition)); + Lang.bind(this, this._adjustSummaryBoxPointerPosition)); - this._summaryNotificationBoxPointer.actor.opacity = 0; - this._summaryNotificationBoxPointer.actor.show(); - this._adjustNotificationBoxPointerPosition(); + this._summaryBoxPointer.actor.opacity = 0; + this._summaryBoxPointer.actor.show(); + this._adjustSummaryBoxPointerPosition(); - this._summaryNotificationState = State.SHOWING; - this._summaryNotificationBoxPointer.show(true, Lang.bind(this, function() { - this._summaryNotificationState = State.SHOWN; + this._summaryBoxPointerState = State.SHOWING; + this._summaryBoxPointer.show(true, Lang.bind(this, function() { + this._summaryBoxPointerState = State.SHOWN; })); }, - _adjustNotificationBoxPointerPosition: function() { + _adjustSummaryBoxPointerPosition: function() { // The position of the arrow origin should be the same as center of this._clickedSummaryItem.actor if (!this._clickedSummaryItem) return; - this._summaryNotificationBoxPointer.setPosition(this._clickedSummaryItem.actor, 0, 0.5); + this._summaryBoxPointer.setPosition(this._clickedSummaryItem.actor, 0, 0.5); }, _unsetClickedSummaryItem: function() { @@ -1861,14 +1904,20 @@ MessageTray.prototype = { this._summaryMotionId = 0; } + if (this._summaryRightClickMenuClickedId) { + this._clickedSummaryItem.disconnect(this._summaryRightClickMenuClickedId); + this._summaryRightClickMenuClickedId = 0; + } + this._clickedSummaryItem = null; + this._clickedSummaryItemMouseButton = -1; }, - _onSummaryNotificationExpanded: function() { - this._adjustNotificationBoxPointerPosition(); + _onSummaryBoxPointerExpanded: function() { + this._adjustSummaryBoxPointerPosition(); }, - _hideSummaryNotification: function() { + _hideSummaryBoxPointer: function() { if (this._summaryNotificationExpandedId) { this._summaryNotification.disconnect(this._summaryNotificationExpandedId); this._summaryNotificationExpandedId = 0; @@ -1878,23 +1927,25 @@ MessageTray.prototype = { this._unsetClickedSummaryItem(); this._focusGrabber.ungrabFocus(); - this._summaryNotificationState = State.HIDING; - this._summaryNotificationBoxPointer.hide(true, Lang.bind(this, this._hideSummaryNotificationCompleted)); + this._summaryBoxPointerState = State.HIDING; + this._summaryBoxPointer.hide(true, Lang.bind(this, this._hideSummaryBoxPointerCompleted)); }, - _hideSummaryNotificationCompleted: function() { - 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(NotificationDestroyedReason.EXPIRED); - if (this._reNotifyWithSummaryNotificationAfterHide) { - this._onNotify(summaryNotification.source, summaryNotification); - this._reNotifyWithSummaryNotificationAfterHide = false; + _hideSummaryBoxPointerCompleted: function() { + this._summaryBoxPointerState = State.HIDDEN; + this._summaryBoxPointer.bin.child = null; + if (this._summaryNotification != 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(NotificationDestroyedReason.EXPIRED); + if (this._reNotifyWithSummaryNotificationAfterHide) { + this._onNotify(summaryNotification.source, summaryNotification); + this._reNotifyWithSummaryNotificationAfterHide = false; + } } if (this._clickedSummaryItem) this._updateState(); @@ -1920,7 +1971,7 @@ SystemNotificationSource.prototype = { icon_size: this.ICON_SIZE }); }, - _notificationClicked: function() { + open: function() { this.destroy(); } }; diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index 2285d588c..7953b85e5 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -473,7 +473,7 @@ Source.prototype = { this._isTrayIcon = true; }, - _notificationClicked: function(notification) { + open: function(notification) { this.openApp(); }, diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js index 6e753fd44..e16220c89 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -54,8 +54,10 @@ PopupBaseMenuItem.prototype = { } if (params.reactive && params.hover) this.actor.connect('notify::hover', Lang.bind(this, this._onHoverChanged)); - if (params.reactive) + if (params.reactive) { this.actor.connect('key-focus-in', Lang.bind(this, this._onKeyFocusIn)); + this.actor.connect('key-focus-out', Lang.bind(this, this._onKeyFocusOut)); + } }, _onStyleChanged: function (actor) { @@ -81,6 +83,10 @@ PopupBaseMenuItem.prototype = { this.setActive(true); }, + _onKeyFocusOut: function (actor) { + this.setActive(false); + }, + _onHoverChanged: function (actor) { this.setActive(actor.hover); }, diff --git a/js/ui/telepathyClient.js b/js/ui/telepathyClient.js index 5ddaaefe3..d835d3e2b 100644 --- a/js/ui/telepathyClient.js +++ b/js/ui/telepathyClient.js @@ -173,7 +173,7 @@ Source.prototype = { } }, - _notificationClicked: function(notification) { + open: function(notification) { let props = {}; props[Tp.PROP_CHANNEL_CHANNEL_TYPE] = Tp.IFACE_CHANNEL_TYPE_TEXT; [props[Tp.PROP_CHANNEL_TARGET_HANDLE], props[Tp.PROP_CHANNEL_TARGET_HANDLE_TYPE]] = this._channel.get_handle(); diff --git a/js/ui/windowAttentionHandler.js b/js/ui/windowAttentionHandler.js index fd80cd9cc..560e91577 100644 --- a/js/ui/windowAttentionHandler.js +++ b/js/ui/windowAttentionHandler.js @@ -101,7 +101,7 @@ Source.prototype = { return this._app.create_icon_texture(this.ICON_SIZE); }, - _notificationClicked : function(notification) { + open : function(notification) { Main.activateWindow(this._window); this.destroy(); }