Compare commits
	
		
			12 Commits
		
	
	
		
			wip/jimmac
			...
			wip/notif-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 672a4a56bd | ||
|   | 50421ede37 | ||
|   | 826a7fa02a | ||
|   | d7844bc7f4 | ||
|   | 485444a451 | ||
|   | e5318d5235 | ||
|   | e6e15489aa | ||
|   | 0951d36e8f | ||
|   | c38939902f | ||
|   | 97974a3f2a | ||
|   | 8b6cab741d | ||
|   | 2aad0eac31 | 
| @@ -414,10 +414,7 @@ StScrollBar StButton#vhandle:active { | |||||||
| /* Buttons */ | /* Buttons */ | ||||||
|  |  | ||||||
| .candidate-page-button, | .candidate-page-button, | ||||||
| .notification-button, |  | ||||||
| .notification-icon-button, |  | ||||||
| .hotplug-notification-item, | .hotplug-notification-item, | ||||||
| .hotplug-resident-eject-button, |  | ||||||
| .modal-dialog-button, | .modal-dialog-button, | ||||||
| .app-view-control { | .app-view-control { | ||||||
|     border: 1px solid #8b8b8b; |     border: 1px solid #8b8b8b; | ||||||
| @@ -431,17 +428,12 @@ StScrollBar StButton#vhandle:active { | |||||||
| } | } | ||||||
|  |  | ||||||
| .candidate-page-button:hover, | .candidate-page-button:hover, | ||||||
| .notification-button:hover, |  | ||||||
| .notification-icon-button:hover, |  | ||||||
| .hotplug-notification-item:hover, | .hotplug-notification-item:hover, | ||||||
| .hotplug-resident-eject-button:hover, |  | ||||||
| .modal-dialog-button:hover { | .modal-dialog-button:hover { | ||||||
|     background-gradient-start: rgba(255, 255, 255, 0.3); |     background-gradient-start: rgba(255, 255, 255, 0.3); | ||||||
|     background-gradient-end: rgba(255, 255, 255, 0.1); |     background-gradient-end: rgba(255, 255, 255, 0.1); | ||||||
| } | } | ||||||
|  |  | ||||||
| .notification-button:focus, |  | ||||||
| .notification-icon-button:focus, |  | ||||||
| .hotplug-notification-item:focus, | .hotplug-notification-item:focus, | ||||||
| .modal-dialog-button:focus, | .modal-dialog-button:focus, | ||||||
| .app-view-control:focus { | .app-view-control:focus { | ||||||
| @@ -455,10 +447,7 @@ StScrollBar StButton#vhandle:active { | |||||||
|  |  | ||||||
| .candidate-page-button:active, | .candidate-page-button:active, | ||||||
| .candidate-page-button:pressed, | .candidate-page-button:pressed, | ||||||
| .notification-button:active, |  | ||||||
| .notification-icon-button:active, |  | ||||||
| .hotplug-notification-item:active, | .hotplug-notification-item:active, | ||||||
| .hotplug-resident-eject-button:active, |  | ||||||
| .modal-dialog-button:active, | .modal-dialog-button:active, | ||||||
| .modal-dialog-button:pressed, | .modal-dialog-button:pressed, | ||||||
| .app-view-control:checked { | .app-view-control:checked { | ||||||
| @@ -467,8 +456,6 @@ StScrollBar StButton#vhandle:active { | |||||||
| } | } | ||||||
|  |  | ||||||
| .candidate-page-button:insensitive, | .candidate-page-button:insensitive, | ||||||
| .notification-button:insensitive, |  | ||||||
| .notification-icon-button:insensitive, |  | ||||||
| .modal-dialog-button:insensitive { | .modal-dialog-button:insensitive { | ||||||
|     border-color: #666666; |     border-color: #666666; | ||||||
|     color: #9f9f9f; |     color: #9f9f9f; | ||||||
| @@ -480,7 +467,6 @@ StScrollBar StButton#vhandle:active { | |||||||
|  |  | ||||||
| #searchEntry, | #searchEntry, | ||||||
| .modal-dialog-button, | .modal-dialog-button, | ||||||
| .notification-button, |  | ||||||
| .hotplug-notification-item, | .hotplug-notification-item, | ||||||
| .app-view-controls, | .app-view-controls, | ||||||
| #screenShieldNotifications { | #screenShieldNotifications { | ||||||
| @@ -1535,36 +1521,69 @@ StScrollBar StButton#vhandle:active { | |||||||
|     color: #999999; |     color: #999999; | ||||||
| } | } | ||||||
|  |  | ||||||
| .notification { |  | ||||||
|     border-radius: 10px 10px 0px 0px; |  | ||||||
|     background: rgba(0,0,0,0.9); |  | ||||||
|     padding: 8px 8px 4px 8px; |  | ||||||
|     spacing-rows: 4px; |  | ||||||
|     spacing-columns: 10px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .notification, #notification-container { | .notification, #notification-container { | ||||||
|     font-size: 11pt; |     font-size: 11pt; | ||||||
|     width: 34em; |     width: 34em; | ||||||
| } | } | ||||||
|  |  | ||||||
| .notification.multi-line-notification { | .notification-main-button, | ||||||
|     padding-bottom: 8px; | .notification-button { | ||||||
|  |     background: rgba(0,0,0,0.9); | ||||||
| } | } | ||||||
|  |  | ||||||
| .notification-unexpanded { | .notification-main-button { | ||||||
|     /* We want to force the actor at a specific size, irrespective |     border-radius: 10px 10px 0px 0px; | ||||||
|        of its minimum and preferred size, so we override both */ |  | ||||||
|     min-height: 36px; |  | ||||||
|     height: 36px; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /* We use row-span = 2 for the image cell, which prevents its height preferences to be | .notification-main-content { | ||||||
|    taken into account during allocation, so its height ends up being limited by the height |     padding: 8px; | ||||||
|    of the content in the other rows. To avoid showing a stretched image, we set the minimum |     spacing: 8px; | ||||||
|    height of the table to be ICON_SIZE + IMAGE_SIZE + spacing-rows = 24 + 125 + 10 = 159 */ | } | ||||||
| .notification-with-image { |  | ||||||
|     min-height: 159px; | .notification-close-button { | ||||||
|  |     padding: 8px; | ||||||
|  |     border-radius: 4px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .notification-action-area { | ||||||
|  |     padding: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .notification-action-area, | ||||||
|  | .notification-button { | ||||||
|  |     border-top: 1px solid #666; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .notification-button { | ||||||
|  |     padding: 8px 0px; | ||||||
|  |     border-right: 1px solid #666; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .notification-main-button:hover, | ||||||
|  | .notification-button:hover, | ||||||
|  | .notification-close-button:hover { | ||||||
|  |     background: rgba(100,100,100,0.9); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .notification-main-button:active, | ||||||
|  | .notification-button:active { | ||||||
|  |     background: rgba(255,255,255,0.1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .notification-button:last-child { | ||||||
|  |     border-right-width: 0px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .notification-title-box { | ||||||
|  |     spacing: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .notification-scrollview:ltr > StScrollBar { | ||||||
|  |     padding-left: 6px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .notification-scrollview:rtl > StScrollBar { | ||||||
|  |     padding-right: 6px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .summary-boxpointer { | .summary-boxpointer { | ||||||
| @@ -1606,47 +1625,6 @@ StScrollBar StButton#vhandle:active { | |||||||
|     -st-vfade-offset: 24px; |     -st-vfade-offset: 24px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .notification-scrollview:ltr > StScrollBar { |  | ||||||
|     padding-left: 6px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .notification-scrollview:rtl > StScrollBar { |  | ||||||
|     padding-right: 6px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .notification-body { |  | ||||||
|     spacing: 5px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .notification-actions { |  | ||||||
|     padding-top: 18px; |  | ||||||
|     spacing: 10px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .notification-button { |  | ||||||
|     -st-natural-width: 140px; |  | ||||||
|     padding: 4px 4px 5px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .notification-button:focus { |  | ||||||
|     -st-natural-width: 138px; |  | ||||||
|     padding: 3px 4px 4px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .notification-icon-button { |  | ||||||
|     border-radius: 5px; |  | ||||||
|     padding: 5px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .notification-icon-button:focus { |  | ||||||
|     padding: 4px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .notification-icon-button > StIcon { |  | ||||||
|     icon-size: 16px; |  | ||||||
|     padding: 8px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .secondary-icon { | .secondary-icon { | ||||||
|     icon-size: 1.09em; |     icon-size: 1.09em; | ||||||
| } | } | ||||||
| @@ -1669,45 +1647,6 @@ StScrollBar StButton#vhandle:active { | |||||||
|     padding: 2px 5px; |     padding: 2px 5px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .hotplug-resident-box { |  | ||||||
|     spacing: 8px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .hotplug-resident-mount { |  | ||||||
|     spacing: 8px; |  | ||||||
|     border-radius: 4px; |  | ||||||
|  |  | ||||||
|     color: #ccc; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .hotplug-resident-mount:hover { |  | ||||||
|     background-gradient-direction: horizontal; |  | ||||||
|     background-gradient-start: rgba(255, 255, 255, 0.1); |  | ||||||
|     background-gradient-end: rgba(255, 255, 255, 0); |  | ||||||
|  |  | ||||||
|     color: #fff; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .hotplug-resident-mount-label { |  | ||||||
|     color: inherit; |  | ||||||
|     padding-left: 6px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .hotplug-resident-mount-icon { |  | ||||||
|     icon-size: 24px; |  | ||||||
|     padding-left: 6px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .hotplug-resident-eject-icon { |  | ||||||
|     icon-size: 16px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .hotplug-resident-eject-button { |  | ||||||
|     padding: 7px; |  | ||||||
|     border-radius: 5px; |  | ||||||
|     color: #ccc; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .chat-log-message { | .chat-log-message { | ||||||
|     color: #888888; |     color: #888888; | ||||||
| } | } | ||||||
| @@ -1747,7 +1686,11 @@ StScrollBar StButton#vhandle:active { | |||||||
|     padding-right: 4px; |     padding-right: 4px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .chat-notification-scrollview{ | .chat-notification-body-box { | ||||||
|  |     spacing: 5px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .chat-notification-scrollview { | ||||||
|     max-height: 22em; |     max-height: 22em; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -2666,8 +2609,7 @@ StScrollBar StButton#vhandle:active { | |||||||
|     padding-bottom: 0px; |     padding-bottom: 0px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #screenShieldNotifications .notification-button, | #screenShieldNotifications .notification-button { | ||||||
| #screenShieldNotifications .notification-icon-button { |  | ||||||
|     border: 1px rgba(255,255,255,0.5); |     border: 1px rgba(255,255,255,0.5); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -170,17 +170,6 @@ const AutorunManager = new Lang.Class({ | |||||||
|         this._transDispatcher = new AutorunTransientDispatcher(this); |         this._transDispatcher = new AutorunTransientDispatcher(this); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _ensureResidentSource: function() { |  | ||||||
|         if (this._residentSource) |  | ||||||
|             return; |  | ||||||
|  |  | ||||||
|         this._residentSource = new AutorunResidentSource(this); |  | ||||||
|         let destroyId = this._residentSource.connect('destroy', Lang.bind(this, function() { |  | ||||||
|             this._residentSource.disconnect(destroyId); |  | ||||||
|             this._residentSource = null; |  | ||||||
|         })); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     enable: function() { |     enable: function() { | ||||||
|         this._scanMounts(); |         this._scanMounts(); | ||||||
|  |  | ||||||
| @@ -189,17 +178,12 @@ const AutorunManager = new Lang.Class({ | |||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     disable: function() { |     disable: function() { | ||||||
|         if (this._residentSource) |  | ||||||
|             this._residentSource.destroy(); |  | ||||||
|         this._volumeMonitor.disconnect(this._mountAddedId); |         this._volumeMonitor.disconnect(this._mountAddedId); | ||||||
|         this._volumeMonitor.disconnect(this._mountRemovedId); |         this._volumeMonitor.disconnect(this._mountRemovedId); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _processMount: function(mount, hotplug) { |     _processMount: function(mount, hotplug) { | ||||||
|         let discoverer = new ContentTypeDiscoverer(Lang.bind(this, function(mount, apps, contentTypes) { |         let discoverer = new ContentTypeDiscoverer(Lang.bind(this, function(mount, apps, contentTypes) { | ||||||
|             this._ensureResidentSource(); |  | ||||||
|             this._residentSource.addMount(mount, apps); |  | ||||||
|  |  | ||||||
|             if (hotplug) |             if (hotplug) | ||||||
|                 this._transDispatcher.addMount(mount, apps, contentTypes); |                 this._transDispatcher.addMount(mount, apps, contentTypes); | ||||||
|         })); |         })); | ||||||
| @@ -224,8 +208,6 @@ const AutorunManager = new Lang.Class({ | |||||||
|  |  | ||||||
|     _onMountRemoved: function(monitor, mount) { |     _onMountRemoved: function(monitor, mount) { | ||||||
|         this._transDispatcher.removeMount(mount); |         this._transDispatcher.removeMount(mount); | ||||||
|         if (this._residentSource) |  | ||||||
|             this._residentSource.removeMount(mount); |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     ejectMount: function(mount) { |     ejectMount: function(mount) { | ||||||
| @@ -288,153 +270,6 @@ const AutorunManager = new Lang.Class({ | |||||||
|     }, |     }, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const AutorunResidentSource = new Lang.Class({ |  | ||||||
|     Name: 'AutorunResidentSource', |  | ||||||
|     Extends: MessageTray.Source, |  | ||||||
|  |  | ||||||
|     _init: function(manager) { |  | ||||||
|         this.parent(_("Removable Devices"), 'media-removable'); |  | ||||||
|         this.resident = true; |  | ||||||
|  |  | ||||||
|         this._mounts = []; |  | ||||||
|  |  | ||||||
|         this._manager = manager; |  | ||||||
|         this._notification = new AutorunResidentNotification(this._manager, this); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _createPolicy: function() { |  | ||||||
|         return new MessageTray.NotificationPolicy({ showInLockScreen: false }); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     buildRightClickMenu: function() { |  | ||||||
|         return null; |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     addMount: function(mount, apps) { |  | ||||||
|         if (!shouldAutorunMount(mount, false)) |  | ||||||
|             return; |  | ||||||
|  |  | ||||||
|         let filtered = this._mounts.filter(function (element) { |  | ||||||
|             return (element.mount == mount); |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         if (filtered.length != 0) |  | ||||||
|             return; |  | ||||||
|  |  | ||||||
|         let element = { mount: mount, apps: apps }; |  | ||||||
|         this._mounts.push(element); |  | ||||||
|         this._redisplay(); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     removeMount: function(mount) { |  | ||||||
|         this._mounts = |  | ||||||
|             this._mounts.filter(function (element) { |  | ||||||
|                 return (element.mount != mount); |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|         this._redisplay(); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _redisplay: function() { |  | ||||||
|         if (this._mounts.length == 0) { |  | ||||||
|             this._notification.destroy(); |  | ||||||
|             this.destroy(); |  | ||||||
|  |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         this._notification.updateForMounts(this._mounts); |  | ||||||
|  |  | ||||||
|         // add ourselves as a source, and push the notification |  | ||||||
|         if (!Main.messageTray.contains(this)) { |  | ||||||
|             Main.messageTray.add(this); |  | ||||||
|             this.pushNotification(this._notification); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| const AutorunResidentNotification = new Lang.Class({ |  | ||||||
|     Name: 'AutorunResidentNotification', |  | ||||||
|     Extends: MessageTray.Notification, |  | ||||||
|  |  | ||||||
|     _init: function(manager, source) { |  | ||||||
|         this.parent(source, source.title, null, { customContent: true }); |  | ||||||
|  |  | ||||||
|         // set the notification as resident |  | ||||||
|         this.setResident(true); |  | ||||||
|  |  | ||||||
|         this._layout = new St.BoxLayout ({ style_class: 'hotplug-resident-box', |  | ||||||
|                                            vertical: true }); |  | ||||||
|         this._manager = manager; |  | ||||||
|  |  | ||||||
|         this.addActor(this._layout, |  | ||||||
|                       { x_expand: true, |  | ||||||
|                         x_fill: true }); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     updateForMounts: function(mounts) { |  | ||||||
|         // remove all the layout content |  | ||||||
|         this._layout.destroy_all_children(); |  | ||||||
|  |  | ||||||
|         for (let idx = 0; idx < mounts.length; idx++) { |  | ||||||
|             let element = mounts[idx]; |  | ||||||
|  |  | ||||||
|             let actor = this._itemForMount(element.mount, element.apps); |  | ||||||
|             this._layout.add(actor, { x_fill: true, |  | ||||||
|                                       expand: true }); |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _itemForMount: function(mount, apps) { |  | ||||||
|         let item = new St.BoxLayout(); |  | ||||||
|  |  | ||||||
|         // prepare the mount button content |  | ||||||
|         let mountLayout = new St.BoxLayout(); |  | ||||||
|  |  | ||||||
|         let mountIcon = new St.Icon({ gicon: mount.get_icon(), |  | ||||||
|                                       style_class: 'hotplug-resident-mount-icon' }); |  | ||||||
|         mountLayout.add_actor(mountIcon); |  | ||||||
|  |  | ||||||
|         let labelBin = new St.Bin({ y_align: St.Align.MIDDLE }); |  | ||||||
|         let mountLabel = |  | ||||||
|             new St.Label({ text: mount.get_name(), |  | ||||||
|                            style_class: 'hotplug-resident-mount-label', |  | ||||||
|                            track_hover: true, |  | ||||||
|                            reactive: true }); |  | ||||||
|         labelBin.add_actor(mountLabel); |  | ||||||
|         mountLayout.add_actor(labelBin); |  | ||||||
|  |  | ||||||
|         let mountButton = new St.Button({ child: mountLayout, |  | ||||||
|                                           x_align: St.Align.START, |  | ||||||
|                                           x_fill: true, |  | ||||||
|                                           style_class: 'hotplug-resident-mount', |  | ||||||
|                                           button_mask: St.ButtonMask.ONE }); |  | ||||||
|         item.add(mountButton, { x_align: St.Align.START, |  | ||||||
|                                 expand: true }); |  | ||||||
|  |  | ||||||
|         let ejectIcon =  |  | ||||||
|             new St.Icon({ icon_name: 'media-eject-symbolic', |  | ||||||
|                           style_class: 'hotplug-resident-eject-icon' }); |  | ||||||
|  |  | ||||||
|         let ejectButton = |  | ||||||
|             new St.Button({ style_class: 'hotplug-resident-eject-button', |  | ||||||
|                             button_mask: St.ButtonMask.ONE, |  | ||||||
|                             child: ejectIcon }); |  | ||||||
|         item.add(ejectButton, { x_align: St.Align.END }); |  | ||||||
|  |  | ||||||
|         // now connect signals |  | ||||||
|         mountButton.connect('clicked', Lang.bind(this, function(actor, event) { |  | ||||||
|             startAppForMount(apps[0], mount); |  | ||||||
|         })); |  | ||||||
|  |  | ||||||
|         ejectButton.connect('clicked', Lang.bind(this, function() { |  | ||||||
|             this._manager.ejectMount(mount); |  | ||||||
|         })); |  | ||||||
|  |  | ||||||
|         return item; |  | ||||||
|     }, |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| const AutorunTransientDispatcher = new Lang.Class({ | const AutorunTransientDispatcher = new Lang.Class({ | ||||||
|     Name: 'AutorunTransientDispatcher', |     Name: 'AutorunTransientDispatcher', | ||||||
|  |  | ||||||
| @@ -559,12 +394,12 @@ const AutorunTransientNotification = new Lang.Class({ | |||||||
|     Extends: MessageTray.Notification, |     Extends: MessageTray.Notification, | ||||||
|  |  | ||||||
|     _init: function(manager, source) { |     _init: function(manager, source) { | ||||||
|         this.parent(source, source.title, null, { customContent: true }); |         this.parent(source, source.title); | ||||||
|  |  | ||||||
|         this._manager = manager; |         this._manager = manager; | ||||||
|         this._box = new St.BoxLayout({ style_class: 'hotplug-transient-box', |         this._box = new St.BoxLayout({ style_class: 'hotplug-transient-box', | ||||||
|                                        vertical: true }); |                                        vertical: true }); | ||||||
|         this.addActor(this._box); |         this._bodyBin.child = this._box; | ||||||
|  |  | ||||||
|         this._mount = source.mount; |         this._mount = source.mount; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -102,15 +102,6 @@ const TelepathyClient = new Lang.Class({ | |||||||
|         this._tpClient.set_handle_channels_func( |         this._tpClient.set_handle_channels_func( | ||||||
|             Lang.bind(this, this._handleChannels)); |             Lang.bind(this, this._handleChannels)); | ||||||
|  |  | ||||||
|         // Watch subscription requests and connection errors |  | ||||||
|         this._subscriptionSource = null; |  | ||||||
|         this._accountSource = null; |  | ||||||
|  |  | ||||||
|         // Workaround for gjs not supporting GPtrArray in signals. |  | ||||||
|         // See BGO bug #653941 for context. |  | ||||||
|         this._tpClient.set_contact_list_changed_func( |  | ||||||
|             Lang.bind(this, this._contactListChanged)); |  | ||||||
|  |  | ||||||
|         // Allow other clients (such as Empathy) to pre-empt our channels if |         // Allow other clients (such as Empathy) to pre-empt our channels if | ||||||
|         // needed |         // needed | ||||||
|         this._tpClient.set_delegated_channels_callback( |         this._tpClient.set_delegated_channels_callback( | ||||||
| @@ -124,17 +115,12 @@ const TelepathyClient = new Lang.Class({ | |||||||
|             throw new Error('Couldn\'t register Telepathy client. Error: \n' + e); |             throw new Error('Couldn\'t register Telepathy client. Error: \n' + e); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this._accountManagerValidityChangedId = this._accountManager.connect('account-validity-changed', |  | ||||||
|                                                                              Lang.bind(this, this._accountValidityChanged)); |  | ||||||
|  |  | ||||||
|         if (!this._accountManager.is_prepared(Tp.AccountManager.get_feature_quark_core())) |         if (!this._accountManager.is_prepared(Tp.AccountManager.get_feature_quark_core())) | ||||||
|             this._accountManager.prepare_async(null, Lang.bind(this, this._accountManagerPrepared)); |             this._accountManager.prepare_async(null, Lang.bind(this, this._accountManagerPrepared)); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     disable: function() { |     disable: function() { | ||||||
|         this._tpClient.unregister(); |         this._tpClient.unregister(); | ||||||
|         this._accountManager.disconnect(this._accountManagerValidityChangedId); |  | ||||||
|         this._accountManagerValidityChangedId = 0; |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _observeChannels: function(observer, account, conn, channels, |     _observeChannels: function(observer, account, conn, channels, | ||||||
| @@ -219,33 +205,6 @@ const TelepathyClient = new Lang.Class({ | |||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _displayRoomInvitation: function(conn, channel, dispatchOp, context) { |  | ||||||
|         // We can only approve the rooms if we have been invited to it |  | ||||||
|         let selfContact = channel.group_get_self_contact(); |  | ||||||
|         if (selfContact == null) { |  | ||||||
|             context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT, |  | ||||||
|                                         message: 'Not invited to the room' })); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         let [invited, inviter, reason, msg] = channel.group_get_local_pending_contact_info(selfContact); |  | ||||||
|         if (!invited) { |  | ||||||
|             context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT, |  | ||||||
|                                         message: 'Not invited to the room' })); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // FIXME: We don't have a 'chat room' icon (bgo #653737) use |  | ||||||
|         // system-users for now as Empathy does. |  | ||||||
|         let source = new ApproverSource(dispatchOp, _("Invitation"), |  | ||||||
|                                         Gio.icon_new_for_string('system-users')); |  | ||||||
|         Main.messageTray.add(source); |  | ||||||
|  |  | ||||||
|         let notif = new RoomInviteNotification(source, dispatchOp, channel, inviter); |  | ||||||
|         source.notify(notif); |  | ||||||
|         context.accept(); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _approveChannels: function(approver, account, conn, channels, |     _approveChannels: function(approver, account, conn, channels, | ||||||
|                                dispatchOp, context) { |                                dispatchOp, context) { | ||||||
|         let channel = channels[0]; |         let channel = channels[0]; | ||||||
| @@ -259,10 +218,6 @@ const TelepathyClient = new Lang.Class({ | |||||||
|  |  | ||||||
|         if (chanType == Tp.IFACE_CHANNEL_TYPE_TEXT) |         if (chanType == Tp.IFACE_CHANNEL_TYPE_TEXT) | ||||||
|             this._approveTextChannel(account, conn, channel, dispatchOp, context); |             this._approveTextChannel(account, conn, channel, dispatchOp, context); | ||||||
|         else if (chanType == Tp.IFACE_CHANNEL_TYPE_CALL) |  | ||||||
|             this._approveCall(account, conn, channel, dispatchOp, context); |  | ||||||
|         else if (chanType == Tp.IFACE_CHANNEL_TYPE_FILE_TRANSFER) |  | ||||||
|             this._approveFileTransfer(account, conn, channel, dispatchOp, context); |  | ||||||
|         else |         else | ||||||
|             context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT, |             context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT, | ||||||
|                                         message: 'Unsupported channel type' })); |                                         message: 'Unsupported channel type' })); | ||||||
| @@ -283,45 +238,9 @@ const TelepathyClient = new Lang.Class({ | |||||||
|                 }})); |                 }})); | ||||||
|  |  | ||||||
|             context.accept(); |             context.accept(); | ||||||
|         } else { |  | ||||||
|             this._displayRoomInvitation(conn, channel, dispatchOp, context); |  | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _approveCall: function(account, conn, channel, dispatchOp, context) { |  | ||||||
|         let isVideo = false; |  | ||||||
|  |  | ||||||
|         let props = channel.borrow_immutable_properties(); |  | ||||||
|  |  | ||||||
|         if (props[Tp.PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO]) |  | ||||||
|           isVideo = true; |  | ||||||
|  |  | ||||||
|         // We got the TpContact |  | ||||||
|         let source = new ApproverSource(dispatchOp, _("Call"), isVideo ? |  | ||||||
|                                         Gio.icon_new_for_string('camera-web') : |  | ||||||
|                                         Gio.icon_new_for_string('audio-input-microphone')); |  | ||||||
|         Main.messageTray.add(source); |  | ||||||
|  |  | ||||||
|         let notif = new AudioVideoNotification(source, dispatchOp, channel, |  | ||||||
|             channel.get_target_contact(), isVideo); |  | ||||||
|         source.notify(notif); |  | ||||||
|         context.accept(); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _approveFileTransfer: function(account, conn, channel, dispatchOp, context) { |  | ||||||
|         // Use the icon of the file being transferred |  | ||||||
|         let gicon = Gio.content_type_get_icon(channel.get_mime_type()); |  | ||||||
|  |  | ||||||
|         // We got the TpContact |  | ||||||
|         let source = new ApproverSource(dispatchOp, _("File Transfer"), gicon); |  | ||||||
|         Main.messageTray.add(source); |  | ||||||
|  |  | ||||||
|         let notif = new FileTransferNotification(source, dispatchOp, channel, |  | ||||||
|             channel.get_target_contact()); |  | ||||||
|         source.notify(notif); |  | ||||||
|         context.accept(); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _delegatedChannelsCb: function(client, channels) { |     _delegatedChannelsCb: function(client, channels) { | ||||||
|         // Nothing to do as we don't make a distinction between observed and |         // Nothing to do as we don't make a distinction between observed and | ||||||
|         // handled channels. |         // handled channels. | ||||||
| @@ -329,105 +248,7 @@ const TelepathyClient = new Lang.Class({ | |||||||
|  |  | ||||||
|     _accountManagerPrepared: function(am, result) { |     _accountManagerPrepared: function(am, result) { | ||||||
|         am.prepare_finish(result); |         am.prepare_finish(result); | ||||||
|  |  | ||||||
|         let accounts = am.get_valid_accounts(); |  | ||||||
|         for (let i = 0; i < accounts.length; i++) { |  | ||||||
|             this._accountValidityChanged(am, accounts[i], true); |  | ||||||
|         } |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _accountValidityChanged: function(am, account, valid) { |  | ||||||
|         if (!valid) |  | ||||||
|             return; |  | ||||||
|  |  | ||||||
|         // It would be better to connect to "status-changed" but we cannot. |  | ||||||
|         // See discussion in https://bugzilla.gnome.org/show_bug.cgi?id=654159 |  | ||||||
|         account.connect("notify::connection-status", |  | ||||||
|                         Lang.bind(this, this._accountConnectionStatusNotifyCb)); |  | ||||||
|  |  | ||||||
|         account.connect('notify::connection', |  | ||||||
|                         Lang.bind(this, this._connectionChanged)); |  | ||||||
|         this._connectionChanged(account); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _connectionChanged: function(account) { |  | ||||||
|         let conn = account.get_connection(); |  | ||||||
|         if (conn == null) |  | ||||||
|             return; |  | ||||||
|  |  | ||||||
|         this._tpClient.grab_contact_list_changed(conn); |  | ||||||
|         if (conn.get_contact_list_state() == Tp.ContactListState.SUCCESS) { |  | ||||||
|             this._contactListChanged(conn, conn.dup_contact_list(), []); |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _contactListChanged: function(conn, added, removed) { |  | ||||||
|         for (let i = 0; i < added.length; i++) { |  | ||||||
|             let contact = added[i]; |  | ||||||
|  |  | ||||||
|             contact.connect('subscription-states-changed', |  | ||||||
|                             Lang.bind(this, this._subscriptionStateChanged)); |  | ||||||
|             this._subscriptionStateChanged(contact); |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _subscriptionStateChanged: function(contact) { |  | ||||||
|         if (contact.get_publish_state() != Tp.SubscriptionState.ASK) |  | ||||||
|             return; |  | ||||||
|  |  | ||||||
|         /* Implicitly accept publish requests if contact is already subscribed */ |  | ||||||
|         if (contact.get_subscribe_state() == Tp.SubscriptionState.YES || |  | ||||||
|             contact.get_subscribe_state() == Tp.SubscriptionState.ASK) { |  | ||||||
|  |  | ||||||
|             contact.authorize_publication_async(function(src, result) { |  | ||||||
|                 src.authorize_publication_finish(result)}); |  | ||||||
|  |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /* Display notification to ask user to accept/reject request */ |  | ||||||
|         let source = this._ensureAppSource(); |  | ||||||
|  |  | ||||||
|         let notif = new SubscriptionRequestNotification(source, contact); |  | ||||||
|         source.notify(notif); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _accountConnectionStatusNotifyCb: function(account) { |  | ||||||
|         let connectionError = account.connection_error; |  | ||||||
|  |  | ||||||
|         if (account.connection_status != Tp.ConnectionStatus.DISCONNECTED || |  | ||||||
|             connectionError == Tp.error_get_dbus_name(Tp.Error.CANCELLED)) { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         let notif = this._accountNotifications[account.get_object_path()]; |  | ||||||
|         if (notif) |  | ||||||
|             return; |  | ||||||
|  |  | ||||||
|         /* Display notification that account failed to connect */ |  | ||||||
|         let source = this._ensureAppSource(); |  | ||||||
|  |  | ||||||
|         notif = new AccountNotification(source, account, connectionError); |  | ||||||
|         this._accountNotifications[account.get_object_path()] = notif; |  | ||||||
|         notif.connect('destroy', Lang.bind(this, function() { |  | ||||||
|             delete this._accountNotifications[account.get_object_path()]; |  | ||||||
|         })); |  | ||||||
|         source.notify(notif); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _ensureAppSource: function() { |  | ||||||
|         if (this._appSource == null) { |  | ||||||
|             this._appSource = new MessageTray.Source(_("Chat"), 'empathy'); |  | ||||||
|             this._appSource.policy = new MessageTray.NotificationApplicationPolicy('empathy'); |  | ||||||
|  |  | ||||||
|             Main.messageTray.add(this._appSource); |  | ||||||
|             this._appSource.connect('destroy', Lang.bind(this, function () { |  | ||||||
|                 this._appSource = null; |  | ||||||
|             })); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return this._appSource; |  | ||||||
|     } |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const ChatSource = new Lang.Class({ | const ChatSource = new Lang.Class({ | ||||||
| @@ -545,7 +366,7 @@ const ChatSource = new Lang.Class({ | |||||||
|  |  | ||||||
|     _updateAvatarIcon: function() { |     _updateAvatarIcon: function() { | ||||||
|         this.iconUpdated(); |         this.iconUpdated(); | ||||||
|         this._notification.update(this._notification.title, null, { customContent: true }); |         this._notification.update(this._notification.title); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     open: function() { |     open: function() { | ||||||
| @@ -737,7 +558,7 @@ const ChatSource = new Lang.Class({ | |||||||
|  |  | ||||||
|         title = GLib.markup_escape_text(this.title, -1); |         title = GLib.markup_escape_text(this.title, -1); | ||||||
|  |  | ||||||
|         this._notification.update(this._notification.title, null, { customContent: true, secondaryGIcon: this.getSecondaryIcon() }); |         this._notification.update(this._notification.title, null, { secondaryGIcon: this.getSecondaryIcon() }); | ||||||
|  |  | ||||||
|         if (message) |         if (message) | ||||||
|             msg += ' <i>(' + GLib.markup_escape_text(message, -1) + ')</i>'; |             msg += ' <i>(' + GLib.markup_escape_text(message, -1) + ')</i>'; | ||||||
| @@ -764,8 +585,7 @@ const ChatNotification = new Lang.Class({ | |||||||
|     Extends: MessageTray.Notification, |     Extends: MessageTray.Notification, | ||||||
|  |  | ||||||
|     _init: function(source) { |     _init: function(source) { | ||||||
|         this.parent(source, source.title, null, { customContent: true, secondaryGIcon: source.getSecondaryIcon() }); |         this.parent(source, source.title, null, { secondaryGIcon: source.getSecondaryIcon() }); | ||||||
|         this.setResident(true); |  | ||||||
|  |  | ||||||
|         this._responseEntry = new St.Entry({ style_class: 'chat-response', |         this._responseEntry = new St.Entry({ style_class: 'chat-response', | ||||||
|                                              can_focus: true }); |                                              can_focus: true }); | ||||||
| @@ -781,15 +601,17 @@ const ChatNotification = new Lang.Class({ | |||||||
|             this.emit('unfocused'); |             this.emit('unfocused'); | ||||||
|         })); |         })); | ||||||
|  |  | ||||||
|         this._createScrollArea(); |  | ||||||
|         this._lastGroup = null; |         this._lastGroup = null; | ||||||
|  |  | ||||||
|  |         this._bodyBox = new St.BoxLayout({ style_class: 'chat-notification-body-box' }); | ||||||
|  |         this._bodyBin.child = this._bodyBox; | ||||||
|  |  | ||||||
|         // Keep track of the bottom position for the current adjustment and |         // Keep track of the bottom position for the current adjustment and | ||||||
|         // force a scroll to the bottom if things change while we were at the |         // force a scroll to the bottom if things change while we were at the | ||||||
|         // bottom |         // bottom | ||||||
|         this._oldMaxScrollValue = this._scrollArea.vscroll.adjustment.value; |         this._oldMaxScrollValue = this._bodyScrollArea.vscroll.adjustment.value; | ||||||
|         this._scrollArea.add_style_class_name('chat-notification-scrollview'); |         this._bodyScrollArea.add_style_class_name('chat-notification-scrollview'); | ||||||
|         this._scrollArea.vscroll.adjustment.connect('changed', Lang.bind(this, function(adjustment) { |         this._bodyScrollArea.vscroll.adjustment.connect('changed', Lang.bind(this, function(adjustment) { | ||||||
|             if (adjustment.value == this._oldMaxScrollValue) |             if (adjustment.value == this._oldMaxScrollValue) | ||||||
|                 this.scrollTo(St.Side.BOTTOM); |                 this.scrollTo(St.Side.BOTTOM); | ||||||
|             this._oldMaxScrollValue = Math.max(adjustment.lower, adjustment.upper - adjustment.page_size); |             this._oldMaxScrollValue = Math.max(adjustment.lower, adjustment.upper - adjustment.page_size); | ||||||
| @@ -826,8 +648,7 @@ const ChatNotification = new Lang.Class({ | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (message.direction == NotificationDirection.RECEIVED) { |         if (message.direction == NotificationDirection.RECEIVED) { | ||||||
|             this.update(this.source.title, messageBody, { customContent: true, |             this.update(this.source.title, messageBody, { bannerMarkup: true }); | ||||||
|                                                           bannerMarkup: true }); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         let group = (message.direction == NotificationDirection.RECEIVED ? |         let group = (message.direction == NotificationDirection.RECEIVED ? | ||||||
| @@ -864,7 +685,7 @@ const ChatNotification = new Lang.Class({ | |||||||
|                 expired[i].actor.destroy(); |                 expired[i].actor.destroy(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         let groups = this._contentArea.get_children(); |         let groups = this._bodyBox.get_children(); | ||||||
|         for (let i = 0; i < groups.length; i++) { |         for (let i = 0; i < groups.length; i++) { | ||||||
|             let group = groups[i]; |             let group = groups[i]; | ||||||
|             if (group.get_n_children() == 0) |             if (group.get_n_children() == 0) | ||||||
| @@ -896,9 +717,9 @@ const ChatNotification = new Lang.Class({ | |||||||
|         if (this._timestampTimeoutId) |         if (this._timestampTimeoutId) | ||||||
|             Mainloop.source_remove(this._timestampTimeoutId); |             Mainloop.source_remove(this._timestampTimeoutId); | ||||||
|  |  | ||||||
|         let highlighter = new MessageTray.URLHighlighter(props.body, |         let highlighter = new MessageTray.URLHighlighter(); | ||||||
|                                                          true,  // line wrap? |         highlighter.actor.clutter_text.line_wrap = true; | ||||||
|                                                          true); // allow markup? |         highlighter.setMarkup(props.body, true); | ||||||
|  |  | ||||||
|         let body = highlighter.actor; |         let body = highlighter.actor; | ||||||
|  |  | ||||||
| @@ -910,14 +731,12 @@ const ChatNotification = new Lang.Class({ | |||||||
|         if (group != this._lastGroup) { |         if (group != this._lastGroup) { | ||||||
|             this._lastGroup = group; |             this._lastGroup = group; | ||||||
|             let emptyLine = new St.Label({ style_class: 'chat-empty-line' }); |             let emptyLine = new St.Label({ style_class: 'chat-empty-line' }); | ||||||
|             this.addActor(emptyLine); |             this._bodyBox.add_child(emptyLine); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this._lastMessageBox = new St.BoxLayout({ vertical: false }); |         this._lastMessageBox = new St.BoxLayout({ vertical: false }); | ||||||
|         this._lastMessageBox.add(body, props.childProps); |         this._lastMessageBox.add(body, props.childProps); | ||||||
|         this.addActor(this._lastMessageBox); |         this._bodyBox.add_child(this._lastMessageBox); | ||||||
|  |  | ||||||
|         this.updated(); |  | ||||||
|  |  | ||||||
|         let timestamp = props.timestamp; |         let timestamp = props.timestamp; | ||||||
|         this._history.unshift({ actor: body, time: timestamp, |         this._history.unshift({ actor: body, time: timestamp, | ||||||
| @@ -1052,7 +871,7 @@ const ChatNotification = new Lang.Class({ | |||||||
|                                    group: 'meta', |                                    group: 'meta', | ||||||
|                                    styles: ['chat-meta-message'] }); |                                    styles: ['chat-meta-message'] }); | ||||||
|  |  | ||||||
|         this.update(newAlias, null, { customContent: true }); |         this.update(newAlias); | ||||||
|  |  | ||||||
|         this._filterMessages(); |         this._filterMessages(); | ||||||
|     }, |     }, | ||||||
| @@ -1105,359 +924,4 @@ const ChatNotification = new Lang.Class({ | |||||||
|     } |     } | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const ApproverSource = new Lang.Class({ |  | ||||||
|     Name: 'ApproverSource', |  | ||||||
|     Extends: MessageTray.Source, |  | ||||||
|  |  | ||||||
|     _init: function(dispatchOp, text, gicon) { |  | ||||||
|         this._gicon = gicon; |  | ||||||
|  |  | ||||||
|         this.parent(text); |  | ||||||
|  |  | ||||||
|         this._dispatchOp = dispatchOp; |  | ||||||
|  |  | ||||||
|         // Destroy the source if the channel dispatch operation is invalidated |  | ||||||
|         // as we can't approve any more. |  | ||||||
|         this._invalidId = dispatchOp.connect('invalidated', |  | ||||||
|                                              Lang.bind(this, function(domain, code, msg) { |  | ||||||
|             this.destroy(); |  | ||||||
|         })); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _createPolicy: function() { |  | ||||||
|         return new MessageTray.NotificationApplicationPolicy('empathy'); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     destroy: function() { |  | ||||||
|         if (this._invalidId != 0) { |  | ||||||
|             this._dispatchOp.disconnect(this._invalidId); |  | ||||||
|             this._invalidId = 0; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         this.parent(); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     getIcon: function() { |  | ||||||
|         return this._gicon; |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| const RoomInviteNotification = new Lang.Class({ |  | ||||||
|     Name: 'RoomInviteNotification', |  | ||||||
|     Extends: MessageTray.Notification, |  | ||||||
|  |  | ||||||
|     _init: function(source, dispatchOp, channel, inviter) { |  | ||||||
|         this.parent(source, |  | ||||||
|                     /* translators: argument is a room name like |  | ||||||
|                      * room@jabber.org for example. */ |  | ||||||
|                     _("Invitation to %s").format(channel.get_identifier()), |  | ||||||
|                     null, |  | ||||||
|                     { customContent: true }); |  | ||||||
|         this.setResident(true); |  | ||||||
|  |  | ||||||
|         /* translators: first argument is the name of a contact and the second |  | ||||||
|          * one the name of a room. "Alice is inviting you to join room@jabber.org |  | ||||||
|          * for example. */ |  | ||||||
|         this.addBody(_("%s is inviting you to join %s").format(inviter.get_alias(), channel.get_identifier())); |  | ||||||
|  |  | ||||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { |  | ||||||
|             dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, '', function(src, result) { |  | ||||||
|                 src.leave_channels_finish(result); |  | ||||||
|             }); |  | ||||||
|             this.destroy(); |  | ||||||
|         })); |  | ||||||
|         this.addAction(_("Accept"), Lang.bind(this, function() { |  | ||||||
|             dispatchOp.handle_with_time_async('', global.get_current_time(), function(src, result) { |  | ||||||
|                 src.handle_with_time_finish(result); |  | ||||||
|             }); |  | ||||||
|             this.destroy(); |  | ||||||
|         })); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| // Audio Video |  | ||||||
| const AudioVideoNotification = new Lang.Class({ |  | ||||||
|     Name: 'AudioVideoNotification', |  | ||||||
|     Extends: MessageTray.Notification, |  | ||||||
|  |  | ||||||
|     _init: function(source, dispatchOp, channel, contact, isVideo) { |  | ||||||
|         let title = ''; |  | ||||||
|  |  | ||||||
|         if (isVideo) |  | ||||||
|              /* translators: argument is a contact name like Alice for example. */ |  | ||||||
|             title = _("Video call from %s").format(contact.get_alias()); |  | ||||||
|         else |  | ||||||
|              /* translators: argument is a contact name like Alice for example. */ |  | ||||||
|             title = _("Call from %s").format(contact.get_alias()); |  | ||||||
|  |  | ||||||
|         this.parent(source, title, null, { customContent: true }); |  | ||||||
|         this.setResident(true); |  | ||||||
|  |  | ||||||
|         this.setUrgency(MessageTray.Urgency.CRITICAL); |  | ||||||
|  |  | ||||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { |  | ||||||
|             dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, '', function(src, result) { |  | ||||||
|                 src.leave_channels_finish(result); |  | ||||||
|             }); |  | ||||||
|             this.destroy(); |  | ||||||
|         })); |  | ||||||
|         /* translators: this is a button label (verb), not a noun */ |  | ||||||
|         this.addAction(_("Answer"), Lang.bind(this, function() { |  | ||||||
|             dispatchOp.handle_with_time_async('', global.get_current_time(), function(src, result) { |  | ||||||
|                 src.handle_with_time_finish(result); |  | ||||||
|             }); |  | ||||||
|             this.destroy(); |  | ||||||
|         })); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| // File Transfer |  | ||||||
| const FileTransferNotification = new Lang.Class({ |  | ||||||
|     Name: 'FileTransferNotification', |  | ||||||
|     Extends: MessageTray.Notification, |  | ||||||
|  |  | ||||||
|     _init: function(source, dispatchOp, channel, contact) { |  | ||||||
|         this.parent(source, |  | ||||||
|                     /* To translators: The first parameter is |  | ||||||
|                      * the contact's alias and the second one is the |  | ||||||
|                      * file name. The string will be something |  | ||||||
|                      * like: "Alice is sending you test.ogg" |  | ||||||
|                      */ |  | ||||||
|                     _("%s is sending you %s").format(contact.get_alias(), |  | ||||||
|                                                      channel.get_filename()), |  | ||||||
|                     null, |  | ||||||
|                     { customContent: true }); |  | ||||||
|         this.setResident(true); |  | ||||||
|  |  | ||||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { |  | ||||||
|             dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, '', function(src, result) { |  | ||||||
|                 src.leave_channels_finish(result); |  | ||||||
|             }); |  | ||||||
|             this.destroy(); |  | ||||||
|         })); |  | ||||||
|         this.addAction(_("Accept"), Lang.bind(this, function() { |  | ||||||
|             dispatchOp.handle_with_time_async('', global.get_current_time(), function(src, result) { |  | ||||||
|                 src.handle_with_time_finish(result); |  | ||||||
|             }); |  | ||||||
|             this.destroy(); |  | ||||||
|         })); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| // Subscription request |  | ||||||
| const SubscriptionRequestNotification = new Lang.Class({ |  | ||||||
|     Name: 'SubscriptionRequestNotification', |  | ||||||
|     Extends: MessageTray.Notification, |  | ||||||
|  |  | ||||||
|     _init: function(source, contact) { |  | ||||||
|         this.parent(source, |  | ||||||
|                     /* To translators: The parameter is the contact's alias */ |  | ||||||
|                     _("%s would like permission to see when you are online").format(contact.get_alias()), |  | ||||||
|                     null, { customContent: true }); |  | ||||||
|  |  | ||||||
|         this._contact = contact; |  | ||||||
|         this._connection = contact.get_connection(); |  | ||||||
|  |  | ||||||
|         let layout = new St.BoxLayout({ vertical: false }); |  | ||||||
|  |  | ||||||
|         // Display avatar |  | ||||||
|         let iconBox = new St.Bin({ style_class: 'avatar-box' }); |  | ||||||
|         iconBox._size = 48; |  | ||||||
|  |  | ||||||
|         let textureCache = St.TextureCache.get_default(); |  | ||||||
|         let file = contact.get_avatar_file(); |  | ||||||
|  |  | ||||||
|         if (file) { |  | ||||||
|             let uri = file.get_uri(); |  | ||||||
|             let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; |  | ||||||
|             iconBox.child = textureCache.load_uri_async(uri, iconBox._size, iconBox._size, scaleFactor); |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             iconBox.child = new St.Icon({ icon_name: 'avatar-default', |  | ||||||
|                                           icon_size: iconBox._size }); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         layout.add(iconBox); |  | ||||||
|  |  | ||||||
|         // subscription request message |  | ||||||
|         let label = new St.Label({ style_class: 'subscription-message', |  | ||||||
|                                    text: contact.get_publish_request() }); |  | ||||||
|  |  | ||||||
|         layout.add(label); |  | ||||||
|  |  | ||||||
|         this.addActor(layout); |  | ||||||
|  |  | ||||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { |  | ||||||
|             contact.remove_async(function(src, result) { |  | ||||||
|                 src.remove_finish(result); |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
|         this.addAction(_("Accept"), Lang.bind(this, function() { |  | ||||||
|             // Authorize the contact and request to see his status as well |  | ||||||
|             contact.authorize_publication_async(function(src, result) { |  | ||||||
|                 src.authorize_publication_finish(result); |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|             contact.request_subscription_async('', function(src, result) { |  | ||||||
|                 src.request_subscription_finish(result); |  | ||||||
|             }); |  | ||||||
|         })); |  | ||||||
|  |  | ||||||
|         this._changedId = contact.connect('subscription-states-changed', |  | ||||||
|             Lang.bind(this, this._subscriptionStatesChangedCb)); |  | ||||||
|         this._invalidatedId = this._connection.connect('invalidated', |  | ||||||
|             Lang.bind(this, this.destroy)); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     destroy: function() { |  | ||||||
|         if (this._changedId != 0) { |  | ||||||
|             this._contact.disconnect(this._changedId); |  | ||||||
|             this._changedId = 0; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (this._invalidatedId != 0) { |  | ||||||
|             this._connection.disconnect(this._invalidatedId); |  | ||||||
|             this._invalidatedId = 0; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         this.parent(); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _subscriptionStatesChangedCb: function(contact, subscribe, publish, msg) { |  | ||||||
|         // Destroy the notification if the subscription request has been |  | ||||||
|         // answered |  | ||||||
|         if (publish != Tp.SubscriptionState.ASK) |  | ||||||
|             this.destroy(); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| // Messages from empathy/libempathy/empathy-utils.c |  | ||||||
| // create_errors_to_message_hash() |  | ||||||
|  |  | ||||||
| /* Translator note: these should be the same messages that are |  | ||||||
|  * used in Empathy, so just copy and paste from there. */ |  | ||||||
| let _connectionErrorMessages = {}; |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.NETWORK_ERROR)] |  | ||||||
|   = _("Network error"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.AUTHENTICATION_FAILED)] |  | ||||||
|   = _("Authentication failed"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.ENCRYPTION_ERROR)] |  | ||||||
|   = _("Encryption error"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_NOT_PROVIDED)] |  | ||||||
|   = _("Certificate not provided"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_UNTRUSTED)] |  | ||||||
|   = _("Certificate untrusted"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_EXPIRED)] |  | ||||||
|   = _("Certificate expired"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_NOT_ACTIVATED)] |  | ||||||
|   = _("Certificate not activated"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_HOSTNAME_MISMATCH)] |  | ||||||
|   = _("Certificate hostname mismatch"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_FINGERPRINT_MISMATCH)] |  | ||||||
|   = _("Certificate fingerprint mismatch"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_SELF_SIGNED)] |  | ||||||
|   = _("Certificate self-signed"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CANCELLED)] |  | ||||||
|   = _("Status is set to offline"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.ENCRYPTION_NOT_AVAILABLE)] |  | ||||||
|   = _("Encryption is not available"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_INVALID)] |  | ||||||
|   = _("Certificate is invalid"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CONNECTION_REFUSED)] |  | ||||||
|   = _("Connection has been refused"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CONNECTION_FAILED)] |  | ||||||
|   = _("Connection can't be established"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CONNECTION_LOST)] |  | ||||||
|   = _("Connection has been lost"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.ALREADY_CONNECTED)] |  | ||||||
|   = _("This account is already connected to the server"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CONNECTION_REPLACED)] |  | ||||||
|   = _("Connection has been replaced by a new connection using the same resource"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.REGISTRATION_EXISTS)] |  | ||||||
|   = _("The account already exists on the server"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.SERVICE_BUSY)] |  | ||||||
|   = _("Server is currently too busy to handle the connection"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_REVOKED)] |  | ||||||
|   = _("Certificate has been revoked"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_INSECURE)] |  | ||||||
|   = _("Certificate uses an insecure cipher algorithm or is cryptographically weak"); |  | ||||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_LIMIT_EXCEEDED)] |  | ||||||
|   = _("The length of the server certificate, or the depth of the server certificate chain, exceed the limits imposed by the cryptography library"); |  | ||||||
| _connectionErrorMessages['org.freedesktop.DBus.Error.NoReply'] |  | ||||||
|   = _("Internal error"); |  | ||||||
|  |  | ||||||
| const AccountNotification = new Lang.Class({ |  | ||||||
|     Name: 'AccountNotification', |  | ||||||
|     Extends: MessageTray.Notification, |  | ||||||
|  |  | ||||||
|     _init: function(source, account, connectionError) { |  | ||||||
|         this.parent(source, |  | ||||||
|                     /* translators: argument is the account name, like |  | ||||||
|                      * name@jabber.org for example. */ |  | ||||||
|                     _("Unable to connect to %s").format(account.get_display_name()), |  | ||||||
|                     this._getMessage(connectionError)); |  | ||||||
|  |  | ||||||
|         this._account = account; |  | ||||||
|  |  | ||||||
|         this.addAction(_("View account"), Lang.bind(this, function() { |  | ||||||
|             let cmd = 'empathy-accounts --select-account=' + |  | ||||||
|                 account.get_path_suffix(); |  | ||||||
|             let app_info = Gio.app_info_create_from_commandline(cmd, null, 0); |  | ||||||
|             app_info.launch([], global.create_app_launch_context(0, -1)); |  | ||||||
|         })); |  | ||||||
|  |  | ||||||
|         this._enabledId = account.connect('notify::enabled', |  | ||||||
|                                           Lang.bind(this, function() { |  | ||||||
|                                               if (!account.is_enabled()) |  | ||||||
|                                                   this.destroy(); |  | ||||||
|                                           })); |  | ||||||
|  |  | ||||||
|         this._invalidatedId = account.connect('invalidated', |  | ||||||
|                                               Lang.bind(this, this.destroy)); |  | ||||||
|  |  | ||||||
|         this._connectionStatusId = account.connect('notify::connection-status', |  | ||||||
|             Lang.bind(this, function() { |  | ||||||
|                 let status = account.connection_status; |  | ||||||
|                 if (status == Tp.ConnectionStatus.CONNECTED) { |  | ||||||
|                     this.destroy(); |  | ||||||
|                 } else if (status == Tp.ConnectionStatus.DISCONNECTED) { |  | ||||||
|                     let connectionError = account.connection_error; |  | ||||||
|  |  | ||||||
|                     if (connectionError == Tp.error_get_dbus_name(Tp.Error.CANCELLED)) |  | ||||||
|                         this.destroy(); |  | ||||||
|                     else |  | ||||||
|                         this.update(this.title, this._getMessage(connectionError)); |  | ||||||
|                 } |  | ||||||
|             })); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _getMessage: function(connectionError) { |  | ||||||
|         let message; |  | ||||||
|         if (connectionError in _connectionErrorMessages) { |  | ||||||
|             message = _connectionErrorMessages[connectionError]; |  | ||||||
|         } else { |  | ||||||
|             message = _("Unknown reason"); |  | ||||||
|         } |  | ||||||
|         return message; |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     destroy: function() { |  | ||||||
|         if (this._enabledId != 0) { |  | ||||||
|             this._account.disconnect(this._enabledId); |  | ||||||
|             this._enabledId = 0; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (this._invalidatedId != 0) { |  | ||||||
|             this._account.disconnect(this._invalidatedId); |  | ||||||
|             this._invalidatedId = 0; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (this._connectionStatusId != 0) { |  | ||||||
|             this._account.disconnect(this._connectionStatusId); |  | ||||||
|             this._connectionStatusId = 0; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         this.parent(); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
| const Component = TelepathyClient; | const Component = TelepathyClient; | ||||||
|   | |||||||
| @@ -111,7 +111,6 @@ const FocusGrabber = new Lang.Class({ | |||||||
|         if (this._focused) |         if (this._focused) | ||||||
|             return; |             return; | ||||||
|  |  | ||||||
|         this._prevFocusedWindow = global.display.focus_window; |  | ||||||
|         this._prevKeyFocusActor = global.stage.get_key_focus(); |         this._prevKeyFocusActor = global.stage.get_key_focus(); | ||||||
|  |  | ||||||
|         this._focusActorChangedId = global.stage.connect('notify::key-focus', Lang.bind(this, this._focusActorChanged)); |         this._focusActorChangedId = global.stage.connect('notify::key-focus', Lang.bind(this, this._focusActorChanged)); | ||||||
| @@ -234,6 +233,10 @@ const URLHighlighter = new Lang.Class({ | |||||||
|         })); |         })); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  |     hasText: function() { | ||||||
|  |         return !!this._text; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|     setMarkup: function(text, allowMarkup) { |     setMarkup: function(text, allowMarkup) { | ||||||
|         text = text ? _fixMarkup(text, allowMarkup) : ''; |         text = text ? _fixMarkup(text, allowMarkup) : ''; | ||||||
|         this._text = text; |         this._text = text; | ||||||
| @@ -440,24 +443,11 @@ const NotificationApplicationPolicy = new Lang.Class({ | |||||||
| // elements that were added to it or if the @banner text did not | // elements that were added to it or if the @banner text did not | ||||||
| // fit fully in the banner mode. When the notification is expanded, | // fit fully in the banner mode. When the notification is expanded, | ||||||
| // the @banner text from the top line is always removed. The complete | // the @banner text from the top line is always removed. The complete | ||||||
| // @banner text is added as the first element in the content section, | // @banner text is added to the notification by default. You can change | ||||||
| // unless 'customContent' parameter with the value 'true' is specified | // what is displayed by setting the child of this._bodyBin. | ||||||
| // in @params. |  | ||||||
| // | // | ||||||
| // Additional notification content can be added with addActor() and | // You can also add buttons to the notification with addButton(), | ||||||
| // addBody() methods. The notification content is put inside a | // and you can construct simple default buttons with addAction(). | ||||||
| // scrollview, so if it gets too tall, the notification will scroll |  | ||||||
| // rather than continue to grow. In addition to this main content |  | ||||||
| // area, there is also a single-row action area, which is not |  | ||||||
| // scrolled and can contain a single actor. The action area can |  | ||||||
| // be set by calling setActionArea() method. There is also a |  | ||||||
| // convenience method addButton() for adding a button to the action |  | ||||||
| // area. |  | ||||||
| // |  | ||||||
| // If @params contains a 'customContent' parameter with the value %true, |  | ||||||
| // then @banner will not be shown in the body of the notification when the |  | ||||||
| // notification is expanded and calls to update() will not clear the content |  | ||||||
| // unless 'clear' parameter with value %true is explicitly specified. |  | ||||||
| // | // | ||||||
| // By default, the icon shown is the same as the source's. | // By default, the icon shown is the same as the source's. | ||||||
| // However, if @params contains a 'gicon' parameter, the passed in gicon | // However, if @params contains a 'gicon' parameter, the passed in gicon | ||||||
| @@ -473,8 +463,6 @@ const NotificationApplicationPolicy = new Lang.Class({ | |||||||
| // | // | ||||||
| // If @params contains a 'clear' parameter with the value %true, then | // If @params contains a 'clear' parameter with the value %true, then | ||||||
| // the content and the action area of the notification will be cleared. | // the content and the action area of the notification will be cleared. | ||||||
| // The content area is also always cleared if 'customContent' is false |  | ||||||
| // because it might contain the @banner that didn't fit in the banner mode. |  | ||||||
| // | // | ||||||
| // If @params contains 'soundName' or 'soundFile', the corresponding | // If @params contains 'soundName' or 'soundFile', the corresponding | ||||||
| // event sound is played when the notification is shown (if the policy for | // event sound is played when the notification is shown (if the policy for | ||||||
| @@ -482,15 +470,12 @@ const NotificationApplicationPolicy = new Lang.Class({ | |||||||
| const Notification = new Lang.Class({ | const Notification = new Lang.Class({ | ||||||
|     Name: 'Notification', |     Name: 'Notification', | ||||||
|  |  | ||||||
|     ICON_SIZE: 24, |     ICON_SIZE: 32, | ||||||
|  |  | ||||||
|     IMAGE_SIZE: 125, |  | ||||||
|  |  | ||||||
|     _init: function(source, title, banner, params) { |     _init: function(source, title, banner, params) { | ||||||
|         this.source = source; |         this.source = source; | ||||||
|         this.title = title; |         this.title = title; | ||||||
|         this.urgency = Urgency.NORMAL; |         this.urgency = Urgency.NORMAL; | ||||||
|         this.resident = false; |  | ||||||
|         // 'transient' is a reserved keyword in JS, so we have to use an alternate variable name |         // 'transient' is a reserved keyword in JS, so we have to use an alternate variable name | ||||||
|         this.isTransient = false; |         this.isTransient = false; | ||||||
|         this.isMusic = false; |         this.isMusic = false; | ||||||
| @@ -499,59 +484,89 @@ const Notification = new Lang.Class({ | |||||||
|         this.focused = false; |         this.focused = false; | ||||||
|         this.acknowledged = false; |         this.acknowledged = false; | ||||||
|         this._destroyed = false; |         this._destroyed = false; | ||||||
|         this._customContent = false; |  | ||||||
|         this.bannerBodyText = null; |  | ||||||
|         this.bannerBodyMarkup = false; |  | ||||||
|         this._bannerBodyAdded = false; |  | ||||||
|         this._titleFitsInBannerMode = true; |  | ||||||
|         this._titleDirection = Clutter.TextDirection.DEFAULT; |  | ||||||
|         this._spacing = 0; |  | ||||||
|         this._scrollPolicy = Gtk.PolicyType.AUTOMATIC; |  | ||||||
|         this._imageBin = null; |  | ||||||
|         this._soundName = null; |         this._soundName = null; | ||||||
|         this._soundFile = null; |         this._soundFile = null; | ||||||
|         this._soundPlayed = false; |         this._soundPlayed = false; | ||||||
|  |  | ||||||
|         this.actor = new St.Button({ accessible_role: Atk.Role.NOTIFICATION }); |         // Let me draw you a picture. I am a bad artist: | ||||||
|         this.actor.add_style_class_name('notification-unexpanded'); |         // | ||||||
|  |         //      ,. this._iconBin         ,. this._titleLabel | ||||||
|  |         //      |        ,-- this._second|ryIconBin | ||||||
|  |         // .----|--------|---------------|-----------. | ||||||
|  |         // | .----. | .----.-------------------. | X | | ||||||
|  |         // | |    | | |    |                   |-|----- this._titleBox | ||||||
|  |         // | '....' | '....'...................' |   | | ||||||
|  |         // |        |                            |   |- this._hbox | ||||||
|  |         // |        |        this._bodyBin       |   |-. | ||||||
|  |         // |________|____________________________|___| |- this.actor | ||||||
|  |         // | this._actionArea                        |-' | ||||||
|  |         // |_________________________________________| | ||||||
|  |         // | this._buttonBox                         | | ||||||
|  |         // |_________________________________________| | ||||||
|  |  | ||||||
|  |         this.actor = new St.BoxLayout({ vertical: true, | ||||||
|  |                                         style_class: 'notification', | ||||||
|  |                                         accessible_role: Atk.Role.NOTIFICATION }); | ||||||
|         this.actor._delegate = this; |         this.actor._delegate = this; | ||||||
|         this.actor.connect('clicked', Lang.bind(this, this._onClicked)); |  | ||||||
|         this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); |         this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); | ||||||
|  |  | ||||||
|         this._table = new St.Table({ style_class: 'notification', |         this._mainButton = new St.Button({ style_class: 'notification-main-button', | ||||||
|                                      reactive: true }); |                                            can_focus: true, | ||||||
|         this._table.connect('style-changed', Lang.bind(this, this._styleChanged)); |                                            x_fill: true, y_fill: true }); | ||||||
|         this.actor.set_child(this._table); |         this._mainButton.connect('clicked', Lang.bind(this, this._onClicked)); | ||||||
|  |         this.actor.add_child(this._mainButton); | ||||||
|  |  | ||||||
|         // The first line should have the title, followed by the |         // Separates the icon, title/body and close button | ||||||
|         // banner text, but ellipsized if they won't both fit. We can't |         this._hbox = new St.BoxLayout({ style_class: 'notification-main-content' }); | ||||||
|         // make St.Table or St.BoxLayout do this the way we want (don't |         this._mainButton.child = this._hbox; | ||||||
|         // show banner at all if title needs to be ellipsized), so we |  | ||||||
|         // use Shell.GenericContainer. |  | ||||||
|         this._bannerBox = new Shell.GenericContainer(); |  | ||||||
|         this._bannerBox.connect('get-preferred-width', Lang.bind(this, this._bannerBoxGetPreferredWidth)); |  | ||||||
|         this._bannerBox.connect('get-preferred-height', Lang.bind(this, this._bannerBoxGetPreferredHeight)); |  | ||||||
|         this._bannerBox.connect('allocate', Lang.bind(this, this._bannerBoxAllocate)); |  | ||||||
|         this._table.add(this._bannerBox, { row: 0, |  | ||||||
|                                            col: 1, |  | ||||||
|                                            col_span: 2, |  | ||||||
|                                            x_expand: false, |  | ||||||
|                                            y_expand: false, |  | ||||||
|                                            y_fill: false }); |  | ||||||
|  |  | ||||||
|         // This is an empty cell that overlaps with this._bannerBox cell to ensure |         this._iconBin = new St.Bin({ y_align: St.Align.START }); | ||||||
|         // that this._bannerBox cell expands horizontally, while not forcing the |         this._hbox.add_child(this._iconBin); | ||||||
|         // this._imageBin that is also in col: 2 to expand horizontally. |  | ||||||
|         this._table.add(new St.Bin(), { row: 0, |  | ||||||
|                                         col: 2, |  | ||||||
|                                         y_expand: false, |  | ||||||
|                                         y_fill: false }); |  | ||||||
|  |  | ||||||
|         this._titleLabel = new St.Label(); |         this._titleBodyBox = new St.BoxLayout({ style_class: 'notification-title-body-box', | ||||||
|         this._bannerBox.add_actor(this._titleLabel); |                                                 vertical: true }); | ||||||
|         this._bannerUrlHighlighter = new URLHighlighter(); |         this._titleBodyBox.set_x_expand(true); | ||||||
|         this._bannerLabel = this._bannerUrlHighlighter.actor; |         this._hbox.add_child(this._titleBodyBox); | ||||||
|         this._bannerBox.add_actor(this._bannerLabel); |  | ||||||
|  |         this._closeButton = new St.Button({ style_class: 'notification-close-button', | ||||||
|  |                                             can_focus: true }); | ||||||
|  |         this._closeButton.set_y_align(Clutter.ActorAlign.START); | ||||||
|  |         this._closeButton.set_y_expand(true); | ||||||
|  |         this._closeButton.child = new St.Icon({ icon_name: 'window-close-symbolic', icon_size: 16 }); | ||||||
|  |         this._closeButton.connect('clicked', Lang.bind(this, this._onCloseClicked)); | ||||||
|  |         this._hbox.add_child(this._closeButton); | ||||||
|  |  | ||||||
|  |         this._titleBox = new St.BoxLayout({ style_class: 'notification-title-box', | ||||||
|  |                                             x_expand: true, x_align: Clutter.ActorAlign.START }); | ||||||
|  |         this._secondaryIconBin = new St.Bin(); | ||||||
|  |         this._titleBox.add_child(this._secondaryIconBin); | ||||||
|  |         this._titleLabel = new St.Label({ x_expand: true }); | ||||||
|  |         this._titleBox.add_child(this._titleLabel); | ||||||
|  |         this._titleBodyBox.add(this._titleBox); | ||||||
|  |  | ||||||
|  |         this._bodyScrollArea = new St.ScrollView({ style_class: 'notification-scrollview', | ||||||
|  |                                                    hscrollbar_policy: Gtk.PolicyType.NEVER }); | ||||||
|  |         this._titleBodyBox.add(this._bodyScrollArea); | ||||||
|  |  | ||||||
|  |         this._bodyScrollable = new St.BoxLayout(); | ||||||
|  |         this._bodyScrollArea.add_actor(this._bodyScrollable); | ||||||
|  |  | ||||||
|  |         this._bodyBin = new St.Bin(); | ||||||
|  |         this._bodyScrollable.add_actor(this._bodyBin); | ||||||
|  |  | ||||||
|  |         // By default, this._bodyBin contains a URL highlighter. Subclasses | ||||||
|  |         // can override this to provide custom content if they want to. | ||||||
|  |         this._bodyUrlHighlighter = new URLHighlighter(); | ||||||
|  |         this._bodyBin.child = this._bodyUrlHighlighter.actor; | ||||||
|  |  | ||||||
|  |         this._actionAreaBin = new St.Bin({ style_class: 'notification-action-area', | ||||||
|  |                                            x_expand: true, y_expand: true }); | ||||||
|  |         this.actor.add_child(this._actionAreaBin); | ||||||
|  |  | ||||||
|  |         this._buttonBox = new St.BoxLayout({ style_class: 'notification-button-box', | ||||||
|  |                                              x_expand: true, y_expand: true }); | ||||||
|  |         global.focus_manager.add_group(this._buttonBox); | ||||||
|  |         this.actor.add_child(this._buttonBox); | ||||||
|  |  | ||||||
|         // If called with only one argument we assume the caller |         // If called with only one argument we assume the caller | ||||||
|         // will call .update() later on. This is the case of |         // will call .update() later on. This is the case of | ||||||
| @@ -559,6 +574,31 @@ const Notification = new Lang.Class({ | |||||||
|         // for new and updated notifications |         // for new and updated notifications | ||||||
|         if (arguments.length != 1) |         if (arguments.length != 1) | ||||||
|             this.update(title, banner, params); |             this.update(title, banner, params); | ||||||
|  |  | ||||||
|  |         this._sync(); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     _sync: function() { | ||||||
|  |         this._actionAreaBin.visible = this.expanded && (this._actionArea != null); | ||||||
|  |         this._buttonBox.visible = this.expanded && (this._buttonBox.get_n_children() > 0); | ||||||
|  |  | ||||||
|  |         this._iconBin.visible = (this._icon != null && this._icon.visible); | ||||||
|  |         this._secondaryIconBin.visible = (this._secondaryIcon != null); | ||||||
|  |  | ||||||
|  |         if (this.expanded) { | ||||||
|  |             this._titleLabel.clutter_text.line_wrap = true; | ||||||
|  |             this._titleLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||||
|  |             this._bodyUrlHighlighter.actor.clutter_text.line_wrap = true; | ||||||
|  |             this._bodyUrlHighlighter.actor.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; | ||||||
|  |         } else { | ||||||
|  |             this._titleLabel.clutter_text.line_wrap = false; | ||||||
|  |             this._titleLabel.clutter_text.ellipsize = Pango.EllipsizeMode.END; | ||||||
|  |             this._bodyUrlHighlighter.actor.clutter_text.line_wrap = false; | ||||||
|  |             this._bodyUrlHighlighter.actor.clutter_text.ellipsize = Pango.EllipsizeMode.END; | ||||||
|  |         } | ||||||
|  |         this.enableScrolling(this.expanded); | ||||||
|  |  | ||||||
|  |         this._bodyUrlHighlighter.actor.visible = this._bodyUrlHighlighter.hasText(); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     // update: |     // update: | ||||||
| @@ -570,52 +610,31 @@ const Notification = new Lang.Class({ | |||||||
|     // the title/banner. If @params.clear is %true, it will also |     // the title/banner. If @params.clear is %true, it will also | ||||||
|     // remove any additional actors/action buttons previously added. |     // remove any additional actors/action buttons previously added. | ||||||
|     update: function(title, banner, params) { |     update: function(title, banner, params) { | ||||||
|         params = Params.parse(params, { customContent: false, |         params = Params.parse(params, { gicon: null, | ||||||
|                                         gicon: null, |  | ||||||
|                                         secondaryGIcon: null, |                                         secondaryGIcon: null, | ||||||
|                                         bannerMarkup: false, |                                         bannerMarkup: false, | ||||||
|                                         clear: false, |                                         clear: false, | ||||||
|                                         soundName: null, |                                         soundName: null, | ||||||
|                                         soundFile: null }); |                                         soundFile: null }); | ||||||
|  |  | ||||||
|         this._customContent = params.customContent; |  | ||||||
|  |  | ||||||
|         let oldFocus = global.stage.key_focus; |         let oldFocus = global.stage.key_focus; | ||||||
|  |  | ||||||
|         if (this._icon && (params.gicon || params.clear)) { |  | ||||||
|             this._icon.destroy(); |  | ||||||
|             this._icon = null; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (this._secondaryIcon && (params.secondaryGIcon || params.clear)) { |  | ||||||
|             this._secondaryIcon.destroy(); |  | ||||||
|             this._secondaryIcon = null; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // We always clear the content area if we don't have custom |  | ||||||
|         // content because it might contain the @banner that didn't |  | ||||||
|         // fit in the banner mode. |  | ||||||
|         if (this._scrollArea && (!this._customContent || params.clear)) { |  | ||||||
|             if (oldFocus && this._scrollArea.contains(oldFocus)) |  | ||||||
|                 this.actor.grab_key_focus(); |  | ||||||
|  |  | ||||||
|             this._scrollArea.destroy(); |  | ||||||
|             this._scrollArea = null; |  | ||||||
|             this._contentArea = null; |  | ||||||
|         } |  | ||||||
|         if (this._actionArea && params.clear) { |         if (this._actionArea && params.clear) { | ||||||
|             if (oldFocus && this._actionArea.contains(oldFocus)) |             if (oldFocus && this._actionArea.contains(oldFocus)) | ||||||
|                 this.actor.grab_key_focus(); |                 this.actor.grab_key_focus(); | ||||||
|  |  | ||||||
|             this._actionArea.destroy(); |             this._actionArea.destroy(); | ||||||
|             this._actionArea = null; |             this._actionArea = null; | ||||||
|             this._buttonBox = null; |  | ||||||
|         } |         } | ||||||
|         if (params.clear) |  | ||||||
|             this.unsetImage(); |  | ||||||
|  |  | ||||||
|         if (!this._scrollArea && !this._actionArea && !this._imageBin) |         if (params.clear) { | ||||||
|             this._table.remove_style_class_name('multi-line-notification'); |             this._buttonBox.destroy_all_children(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (this._icon && (params.gicon || params.clear)) { | ||||||
|  |             this._icon.destroy(); | ||||||
|  |             this._icon = null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (params.gicon) { |         if (params.gicon) { | ||||||
|             this._icon = new St.Icon({ gicon: params.gicon, |             this._icon = new St.Icon({ gicon: params.gicon, | ||||||
| @@ -624,29 +643,29 @@ const Notification = new Lang.Class({ | |||||||
|             this._icon = this.source.createIcon(this.ICON_SIZE); |             this._icon = this.source.createIcon(this.ICON_SIZE); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (this._icon) { |         if (this._icon) | ||||||
|             this._table.add(this._icon, { row: 0, |             this._iconBin.child = this._icon; | ||||||
|                                           col: 0, |  | ||||||
|                                           x_expand: false, |         if (this._secondaryIcon && (params.secondaryGIcon || params.clear)) { | ||||||
|                                           y_expand: false, |             this._secondaryIcon.destroy(); | ||||||
|                                           y_fill: false, |             this._secondaryIcon = null; | ||||||
|                                           y_align: St.Align.START }); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (params.secondaryGIcon) { |         if (params.secondaryGIcon) { | ||||||
|             this._secondaryIcon = new St.Icon({ gicon: params.secondaryGIcon, |             this._secondaryIcon = new St.Icon({ gicon: params.secondaryGIcon, | ||||||
|                                                 style_class: 'secondary-icon' }); |                                                 style_class: 'secondary-icon' }); | ||||||
|             this._bannerBox.add_actor(this._secondaryIcon); |             this._secondaryIconBin.child = this._secondaryIcon; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this.title = title; |         this.title = title; | ||||||
|         title = title ? _fixMarkup(title.replace(/\n/g, ' '), false) : ''; |         title = title ? _fixMarkup(title.replace(/\n/g, ' '), false) : ''; | ||||||
|         this._titleLabel.clutter_text.set_markup('<b>' + title + '</b>'); |         this._titleLabel.clutter_text.set_markup('<b>' + title + '</b>'); | ||||||
|  |  | ||||||
|  |         let titleDirection; | ||||||
|         if (Pango.find_base_dir(title, -1) == Pango.Direction.RTL) |         if (Pango.find_base_dir(title, -1) == Pango.Direction.RTL) | ||||||
|             this._titleDirection = Clutter.TextDirection.RTL; |             titleDirection = Clutter.TextDirection.RTL; | ||||||
|         else |         else | ||||||
|             this._titleDirection = Clutter.TextDirection.LTR; |             titleDirection = Clutter.TextDirection.LTR; | ||||||
|  |  | ||||||
|         // Let the title's text direction control the overall direction |         // Let the title's text direction control the overall direction | ||||||
|         // of the notification - in case where different scripts are used |         // of the notification - in case where different scripts are used | ||||||
| @@ -654,24 +673,9 @@ const Notification = new Lang.Class({ | |||||||
|         // arguably for action buttons as well. Labels other than the title |         // arguably for action buttons as well. Labels other than the title | ||||||
|         // will be allocated at the available width, so that their alignment |         // will be allocated at the available width, so that their alignment | ||||||
|         // is done correctly automatically. |         // is done correctly automatically. | ||||||
|         this._table.set_text_direction(this._titleDirection); |         this.actor.set_text_direction(titleDirection); | ||||||
|  |  | ||||||
|         // Unless the notification has custom content, we save this.bannerBodyText |         this._bodyUrlHighlighter.setMarkup(banner, params.bannerMarkup); | ||||||
|         // to add it to the content of the notification if the notification is |  | ||||||
|         // expandable due to other elements in its content area or due to the banner |  | ||||||
|         // not fitting fully in the single-line mode. |  | ||||||
|         this.bannerBodyText = this._customContent ? null : banner; |  | ||||||
|         this.bannerBodyMarkup = params.bannerMarkup; |  | ||||||
|         this._bannerBodyAdded = false; |  | ||||||
|  |  | ||||||
|         banner = banner ? banner.replace(/\n/g, '  ') : ''; |  | ||||||
|  |  | ||||||
|         this._bannerUrlHighlighter.setMarkup(banner, params.bannerMarkup); |  | ||||||
|         this._bannerLabel.queue_relayout(); |  | ||||||
|  |  | ||||||
|         // Add the bannerBody now if we know for sure we'll need it |  | ||||||
|         if (this.bannerBodyText && this.bannerBodyText.indexOf('\n') > -1) |  | ||||||
|             this._addBannerBody(); |  | ||||||
|  |  | ||||||
|         if (this._soundName != params.soundName || |         if (this._soundName != params.soundName || | ||||||
|             this._soundFile != params.soundFile) { |             this._soundFile != params.soundFile) { | ||||||
| @@ -680,71 +684,18 @@ const Notification = new Lang.Class({ | |||||||
|             this._soundPlayed = false; |             this._soundPlayed = false; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this.updated(); |         this._sync(); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     setIconVisible: function(visible) { |     setIconVisible: function(visible) { | ||||||
|         this._icon.visible = visible; |         this._icon.visible = visible; | ||||||
|  |         this._sync(); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     enableScrolling: function(enableScrolling) { |     enableScrolling: function(enableScrolling) { | ||||||
|         this._scrollPolicy = enableScrolling ? Gtk.PolicyType.AUTOMATIC : Gtk.PolicyType.NEVER; |         let scrollPolicy = enableScrolling ? Gtk.PolicyType.AUTOMATIC : Gtk.PolicyType.NEVER; | ||||||
|         if (this._scrollArea) { |         this._bodyScrollArea.vscrollbar_policy = scrollPolicy; | ||||||
|             this._scrollArea.vscrollbar_policy = this._scrollPolicy; |         this._bodyScrollArea.enable_mouse_scrolling = enableScrolling; | ||||||
|             this._scrollArea.enable_mouse_scrolling = enableScrolling; |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _createScrollArea: function() { |  | ||||||
|         this._table.add_style_class_name('multi-line-notification'); |  | ||||||
|         this._scrollArea = new St.ScrollView({ style_class: 'notification-scrollview', |  | ||||||
|                                                vscrollbar_policy: this._scrollPolicy, |  | ||||||
|                                                hscrollbar_policy: Gtk.PolicyType.NEVER, |  | ||||||
|                                                visible: this.expanded }); |  | ||||||
|         this._table.add(this._scrollArea, { row: 1, |  | ||||||
|                                             col: 2 }); |  | ||||||
|         this._updateLastColumnSettings(); |  | ||||||
|         this._contentArea = new St.BoxLayout({ style_class: 'notification-body', |  | ||||||
|                                                vertical: true }); |  | ||||||
|         this._scrollArea.add_actor(this._contentArea); |  | ||||||
|         // If we know the notification will be expandable, we need to add |  | ||||||
|         // the banner text to the body as the first element. |  | ||||||
|         this._addBannerBody(); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     // addActor: |  | ||||||
|     // @actor: actor to add to the body of the notification |  | ||||||
|     // |  | ||||||
|     // Appends @actor to the notification's body |  | ||||||
|     addActor: function(actor, style) { |  | ||||||
|         if (!this._scrollArea) { |  | ||||||
|             this._createScrollArea(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         this._contentArea.add(actor, style ? style : {}); |  | ||||||
|         this.updated(); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     // addBody: |  | ||||||
|     // @text: the text |  | ||||||
|     // @markup: %true if @text contains pango markup |  | ||||||
|     // @style: style to use when adding the actor containing the text |  | ||||||
|     // |  | ||||||
|     // Adds a multi-line label containing @text to the notification. |  | ||||||
|     // |  | ||||||
|     // Return value: the newly-added label |  | ||||||
|     addBody: function(text, markup, style) { |  | ||||||
|         let label = new URLHighlighter(text, true, markup); |  | ||||||
|  |  | ||||||
|         this.addActor(label.actor, style); |  | ||||||
|         return label.actor; |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _addBannerBody: function() { |  | ||||||
|         if (this.bannerBodyText && !this._bannerBodyAdded) { |  | ||||||
|             this._bannerBodyAdded = true; |  | ||||||
|             this.addBody(this.bannerBodyText, this.bannerBodyMarkup); |  | ||||||
|         } |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     // scrollTo: |     // scrollTo: | ||||||
| @@ -752,112 +703,32 @@ const Notification = new Lang.Class({ | |||||||
|     // |     // | ||||||
|     // Scrolls the content area (if scrollable) to the indicated edge |     // Scrolls the content area (if scrollable) to the indicated edge | ||||||
|     scrollTo: function(side) { |     scrollTo: function(side) { | ||||||
|         let adjustment = this._scrollArea.vscroll.adjustment; |         let adjustment = this._bodyScrollArea.vscroll.adjustment; | ||||||
|         if (side == St.Side.TOP) |         if (side == St.Side.TOP) | ||||||
|             adjustment.value = adjustment.lower; |             adjustment.value = adjustment.lower; | ||||||
|         else if (side == St.Side.BOTTOM) |         else if (side == St.Side.BOTTOM) | ||||||
|             adjustment.value = adjustment.upper; |             adjustment.value = adjustment.upper; | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     // setActionArea: |     setActionArea: function(actor) { | ||||||
|     // @actor: the actor |  | ||||||
|     // @props: (option) St.Table child properties |  | ||||||
|     // |  | ||||||
|     // Puts @actor into the action area of the notification, replacing |  | ||||||
|     // the previous contents |  | ||||||
|     setActionArea: function(actor, props) { |  | ||||||
|         if (this._actionArea) { |  | ||||||
|             this._actionArea.destroy(); |  | ||||||
|             this._actionArea = null; |  | ||||||
|             if (this._buttonBox) |  | ||||||
|                 this._buttonBox = null; |  | ||||||
|         } else { |  | ||||||
|             this._addBannerBody(); |  | ||||||
|         } |  | ||||||
|         this._actionArea = actor; |  | ||||||
|         this._actionArea.visible = this.expanded; |  | ||||||
|  |  | ||||||
|         if (!props) |  | ||||||
|             props = {}; |  | ||||||
|         props.row = 2; |  | ||||||
|         props.col = 2; |  | ||||||
|  |  | ||||||
|         this._table.add_style_class_name('multi-line-notification'); |  | ||||||
|         this._table.add(this._actionArea, props); |  | ||||||
|         this._updateLastColumnSettings(); |  | ||||||
|         this.updated(); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _updateLastColumnSettings: function() { |  | ||||||
|         if (this._scrollArea) |  | ||||||
|             this._table.child_set(this._scrollArea, { col: this._imageBin ? 2 : 1, |  | ||||||
|                                                       col_span: this._imageBin ? 1 : 2 }); |  | ||||||
|         if (this._actionArea) |         if (this._actionArea) | ||||||
|             this._table.child_set(this._actionArea, { col: this._imageBin ? 2 : 1, |             this._actionArea.destroy(); | ||||||
|                                                       col_span: this._imageBin ? 1 : 2 }); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     setImage: function(image) { |         this._actionArea = actor; | ||||||
|         this.unsetImage(); |         this._actionAreaBin.child = actor; | ||||||
|  |         this._sync(); | ||||||
|         if (!image) |  | ||||||
|             return; |  | ||||||
|  |  | ||||||
|         this._imageBin = new St.Bin({ opacity: 230, |  | ||||||
|                                       child: image, |  | ||||||
|                                       visible: this.expanded }); |  | ||||||
|  |  | ||||||
|         this._table.add_style_class_name('multi-line-notification'); |  | ||||||
|         this._table.add_style_class_name('notification-with-image'); |  | ||||||
|         this._addBannerBody(); |  | ||||||
|         this._updateLastColumnSettings(); |  | ||||||
|         this._table.add(this._imageBin, { row: 1, |  | ||||||
|                                           col: 1, |  | ||||||
|                                           row_span: 2, |  | ||||||
|                                           x_expand: false, |  | ||||||
|                                           y_expand: false, |  | ||||||
|                                           x_fill: false, |  | ||||||
|                                           y_fill: false }); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     unsetImage: function() { |  | ||||||
|         if (this._imageBin) { |  | ||||||
|             this._table.remove_style_class_name('notification-with-image'); |  | ||||||
|             this._table.remove_actor(this._imageBin); |  | ||||||
|             this._imageBin = null; |  | ||||||
|             this._updateLastColumnSettings(); |  | ||||||
|             if (!this._scrollArea && !this._actionArea) |  | ||||||
|                 this._table.remove_style_class_name('multi-line-notification'); |  | ||||||
|         } |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     addButton: function(button, callback) { |     addButton: function(button, callback) { | ||||||
|         if (!this._buttonBox) { |  | ||||||
|             let box = new St.BoxLayout({ style_class: 'notification-actions' }); |  | ||||||
|             this.setActionArea(box, { x_expand: false, |  | ||||||
|                                       y_expand: false, |  | ||||||
|                                       x_fill: false, |  | ||||||
|                                       y_fill: false, |  | ||||||
|                                       x_align: St.Align.END }); |  | ||||||
|             this._buttonBox = box; |  | ||||||
|             global.focus_manager.add_group(this._buttonBox); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         this._buttonBox.add(button); |         this._buttonBox.add(button); | ||||||
|         button.connect('clicked', Lang.bind(this, function() { |         button.connect('clicked', Lang.bind(this, function() { | ||||||
|             callback(); |             callback(); | ||||||
|  |  | ||||||
|             if (!this.resident) { |             this.emit('done-displaying'); | ||||||
|                 // We don't hide a resident notification when the user invokes one of its actions, |             this.destroy(); | ||||||
|                 // 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(); |  | ||||||
|             } |  | ||||||
|         })); |         })); | ||||||
|  |  | ||||||
|         this.updated(); |         this._sync(); | ||||||
|         return button; |         return button; | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
| @@ -870,8 +741,7 @@ const Notification = new Lang.Class({ | |||||||
|     // the notification. |     // the notification. | ||||||
|     addAction: function(label, callback) { |     addAction: function(label, callback) { | ||||||
|         let button = new St.Button({ style_class: 'notification-button', |         let button = new St.Button({ style_class: 'notification-button', | ||||||
|                                      label: label, |                                      x_expand: true, label: label, can_focus: true }); | ||||||
|                                      can_focus: true }); |  | ||||||
|  |  | ||||||
|         return this.addButton(button, callback); |         return this.addButton(button, callback); | ||||||
|     }, |     }, | ||||||
| @@ -880,10 +750,6 @@ const Notification = new Lang.Class({ | |||||||
|         this.urgency = urgency; |         this.urgency = urgency; | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     setResident: function(resident) { |  | ||||||
|         this.resident = resident; |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     setTransient: function(isTransient) { |     setTransient: function(isTransient) { | ||||||
|         this.isTransient = isTransient; |         this.isTransient = isTransient; | ||||||
|     }, |     }, | ||||||
| @@ -911,113 +777,6 @@ const Notification = new Lang.Class({ | |||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _bannerBoxGetPreferredHeight: function(actor, forWidth, alloc) { |  | ||||||
|         [alloc.min_size, alloc.natural_size] = |  | ||||||
|             this._titleLabel.get_preferred_height(forWidth); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _bannerBoxAllocate: function(actor, box, flags) { |  | ||||||
|         let availWidth = box.x2 - box.x1; |  | ||||||
|  |  | ||||||
|         let [titleMinW, titleNatW] = this._titleLabel.get_preferred_width(-1); |  | ||||||
|         let [titleMinH, titleNatH] = this._titleLabel.get_preferred_height(availWidth); |  | ||||||
|         let [bannerMinW, bannerNatW] = this._bannerLabel.get_preferred_width(availWidth); |  | ||||||
|  |  | ||||||
|         let rtl = (this._titleDirection == Clutter.TextDirection.RTL); |  | ||||||
|         let x = rtl ? availWidth : 0; |  | ||||||
|  |  | ||||||
|         if (this._secondaryIcon) { |  | ||||||
|             let [iconMinW, iconNatW] = this._secondaryIcon.get_preferred_width(-1); |  | ||||||
|             let [iconMinH, iconNatH] = this._secondaryIcon.get_preferred_height(availWidth); |  | ||||||
|  |  | ||||||
|             let secondaryIconBox = new Clutter.ActorBox(); |  | ||||||
|             let secondaryIconBoxW = Math.min(iconNatW, availWidth); |  | ||||||
|  |  | ||||||
|             // allocate secondary icon box |  | ||||||
|             if (rtl) { |  | ||||||
|                 secondaryIconBox.x1 = x - secondaryIconBoxW; |  | ||||||
|                 secondaryIconBox.x2 = x; |  | ||||||
|                 x = x - (secondaryIconBoxW + this._spacing); |  | ||||||
|             } else { |  | ||||||
|                 secondaryIconBox.x1 = x; |  | ||||||
|                 secondaryIconBox.x2 = x + secondaryIconBoxW; |  | ||||||
|                 x = x + secondaryIconBoxW + this._spacing; |  | ||||||
|             } |  | ||||||
|             secondaryIconBox.y1 = 0; |  | ||||||
|             // Using titleNatH ensures that the secondary icon is centered vertically |  | ||||||
|             secondaryIconBox.y2 = titleNatH; |  | ||||||
|  |  | ||||||
|             availWidth = availWidth - (secondaryIconBoxW + this._spacing); |  | ||||||
|             this._secondaryIcon.allocate(secondaryIconBox, flags); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         let titleBox = new Clutter.ActorBox(); |  | ||||||
|         let titleBoxW = Math.min(titleNatW, availWidth); |  | ||||||
|         if (rtl) { |  | ||||||
|             titleBox.x1 = availWidth - titleBoxW; |  | ||||||
|             titleBox.x2 = availWidth; |  | ||||||
|         } else { |  | ||||||
|             titleBox.x1 = x; |  | ||||||
|             titleBox.x2 = titleBox.x1 + titleBoxW; |  | ||||||
|         } |  | ||||||
|         titleBox.y1 = 0; |  | ||||||
|         titleBox.y2 = titleNatH; |  | ||||||
|         this._titleLabel.allocate(titleBox, flags); |  | ||||||
|         this._titleFitsInBannerMode = (titleNatW <= availWidth); |  | ||||||
|  |  | ||||||
|         let bannerFits = true; |  | ||||||
|         if (titleBoxW + this._spacing > availWidth) { |  | ||||||
|             this._bannerLabel.opacity = 0; |  | ||||||
|             bannerFits = false; |  | ||||||
|         } else { |  | ||||||
|             let bannerBox = new Clutter.ActorBox(); |  | ||||||
|  |  | ||||||
|             if (rtl) { |  | ||||||
|                 bannerBox.x1 = 0; |  | ||||||
|                 bannerBox.x2 = titleBox.x1 - this._spacing; |  | ||||||
|  |  | ||||||
|                 bannerFits = (bannerBox.x2 - bannerNatW >= 0); |  | ||||||
|             } else { |  | ||||||
|                 bannerBox.x1 = titleBox.x2 + this._spacing; |  | ||||||
|                 bannerBox.x2 = availWidth; |  | ||||||
|  |  | ||||||
|                 bannerFits = (bannerBox.x1 + bannerNatW <= availWidth); |  | ||||||
|             } |  | ||||||
|             bannerBox.y1 = 0; |  | ||||||
|             bannerBox.y2 = titleNatH; |  | ||||||
|             this._bannerLabel.allocate(bannerBox, flags); |  | ||||||
|  |  | ||||||
|             // Make _bannerLabel visible if the entire notification |  | ||||||
|             // fits on one line, or if the notification is currently |  | ||||||
|             // unexpanded and only showing one line anyway. |  | ||||||
|             if (!this.expanded || (bannerFits && this._table.row_count == 1)) |  | ||||||
|                 this._bannerLabel.opacity = 255; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // If the banner doesn't fully fit in the banner box, we possibly need to add the |  | ||||||
|         // banner to the body. We can't do that from here though since that will force a |  | ||||||
|         // relayout, so we add it to the main loop. |  | ||||||
|         if (!bannerFits && this._canExpandContent()) |  | ||||||
|             Meta.later_add(Meta.LaterType.BEFORE_REDRAW, |  | ||||||
|                            Lang.bind(this, |  | ||||||
|                                      function() { |  | ||||||
|                                          if (this._destroyed) |  | ||||||
|                                              return false; |  | ||||||
|  |  | ||||||
|                                         if (this._canExpandContent()) { |  | ||||||
|                                             this._addBannerBody(); |  | ||||||
|                                             this._table.add_style_class_name('multi-line-notification'); |  | ||||||
|                                             this.updated(); |  | ||||||
|                                         } |  | ||||||
|                                         return false; |  | ||||||
|                                      })); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _canExpandContent: function() { |  | ||||||
|         return (this.bannerBodyText && !this._bannerBodyAdded) || |  | ||||||
|                (!this._titleFitsInBannerMode && !this._table.has_style_class_name('multi-line-notification')); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     playSound: function() { |     playSound: function() { | ||||||
|         if (this._soundPlayed) |         if (this._soundPlayed) | ||||||
|             return; |             return; | ||||||
| @@ -1050,83 +809,27 @@ const Notification = new Lang.Class({ | |||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     updated: function() { |  | ||||||
|         if (this.expanded) |  | ||||||
|             this.expand(false); |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     expand: function(animate) { |     expand: function(animate) { | ||||||
|         this.expanded = true; |         this.expanded = true; | ||||||
|         this.actor.remove_style_class_name('notification-unexpanded'); |         this._sync(); | ||||||
|  |  | ||||||
|         // Show additional content that we keep hidden in banner mode |  | ||||||
|         if (this._imageBin) |  | ||||||
|             this._imageBin.show(); |  | ||||||
|         if (this._actionArea) |  | ||||||
|             this._actionArea.show(); |  | ||||||
|         if (this._scrollArea) |  | ||||||
|             this._scrollArea.show(); |  | ||||||
|  |  | ||||||
|         // The banner is never shown when the title did not fit, so this |  | ||||||
|         // can be an if-else statement. |  | ||||||
|         if (!this._titleFitsInBannerMode) { |  | ||||||
|             // Remove ellipsization from the title label and make it wrap so that |  | ||||||
|             // we show the full title when the notification is expanded. |  | ||||||
|             this._titleLabel.clutter_text.line_wrap = true; |  | ||||||
|             this._titleLabel.clutter_text.line_wrap_mode = Pango.WrapMode.WORD_CHAR; |  | ||||||
|             this._titleLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; |  | ||||||
|         } else if (this._table.row_count > 1 && this._bannerLabel.opacity != 0) { |  | ||||||
|             // We always hide the banner if the notification has additional content. |  | ||||||
|             // |  | ||||||
|             // We don't need to wrap the banner that doesn't fit the way we wrap the |  | ||||||
|             // title that doesn't fit because we won't have a notification with |  | ||||||
|             // row_count=1 that has a banner that doesn't fully fit. We'll either add |  | ||||||
|             // that banner to the content of the notification in _bannerBoxAllocate() |  | ||||||
|             // or the notification will have custom content. |  | ||||||
|             if (animate) |  | ||||||
|                 Tweener.addTween(this._bannerLabel, |  | ||||||
|                                  { opacity: 0, |  | ||||||
|                                    time: ANIMATION_TIME, |  | ||||||
|                                    transition: 'easeOutQuad' }); |  | ||||||
|             else |  | ||||||
|                 this._bannerLabel.opacity = 0; |  | ||||||
|         } |  | ||||||
|         this.emit('expanded'); |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     collapseCompleted: function() { |     collapseCompleted: function() { | ||||||
|         if (this._destroyed) |         if (this._destroyed) | ||||||
|             return; |             return; | ||||||
|  |  | ||||||
|         this.expanded = false; |         this.expanded = false; | ||||||
|  |         this._sync(); | ||||||
|         // Hide additional content that we keep hidden in banner mode |  | ||||||
|         if (this._imageBin) |  | ||||||
|             this._imageBin.hide(); |  | ||||||
|         if (this._actionArea) |  | ||||||
|             this._actionArea.hide(); |  | ||||||
|         if (this._scrollArea) |  | ||||||
|             this._scrollArea.hide(); |  | ||||||
|  |  | ||||||
|         // Make sure we don't line wrap the title, and ellipsize it instead. |  | ||||||
|         this._titleLabel.clutter_text.line_wrap = false; |  | ||||||
|         this._titleLabel.clutter_text.ellipsize = Pango.EllipsizeMode.END; |  | ||||||
|  |  | ||||||
|         // Restore banner opacity in case the notification is shown in the |  | ||||||
|         // banner mode again on update. |  | ||||||
|         this._bannerLabel.opacity = 255; |  | ||||||
|  |  | ||||||
|         // Restore height requisition |  | ||||||
|         this.actor.add_style_class_name('notification-unexpanded'); |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _onClicked: function() { |     _onClicked: function() { | ||||||
|         this.emit('clicked'); |         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'); |         this.emit('done-displaying'); | ||||||
|         if (!this.resident) |         this.destroy(); | ||||||
|             this.destroy(); |     }, | ||||||
|  |  | ||||||
|  |     _onCloseClicked: function() { | ||||||
|  |         this.destroy(); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _onDestroy: function() { |     _onDestroy: function() { | ||||||
| @@ -1294,7 +997,7 @@ const Source = new Lang.Class({ | |||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     get indicatorCount() { |     get indicatorCount() { | ||||||
|         let notifications = this.notifications.filter(function(n) { return !n.isTransient && !n.resident; }); |         let notifications = this.notifications.filter(function(n) { return !n.isTransient; }); | ||||||
|         return notifications.length; |         return notifications.length; | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
| @@ -1307,7 +1010,7 @@ const Source = new Lang.Class({ | |||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     get isClearable() { |     get isClearable() { | ||||||
|         return !this.trayIcon && !this.isChat && !this.resident; |         return !this.trayIcon && !this.isChat; | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     countUpdated: function() { |     countUpdated: function() { | ||||||
| @@ -1451,10 +1154,9 @@ const Source = new Lang.Class({ | |||||||
|     open: function() { |     open: function() { | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     destroyNonResidentNotifications: function() { |     destroyNotifications: function() { | ||||||
|         for (let i = this.notifications.length - 1; i >= 0; i--) |         for (let i = this.notifications.length - 1; i >= 0; i--) | ||||||
|             if (!this.notifications[i].resident) |             this.notifications[i].destroy(); | ||||||
|                 this.notifications[i].destroy(); |  | ||||||
|  |  | ||||||
|         this.countUpdated(); |         this.countUpdated(); | ||||||
|     }, |     }, | ||||||
| @@ -1786,6 +1488,9 @@ const MessageTray = new Lang.Class({ | |||||||
|                                                    layout_manager: new Clutter.BinLayout() }); |                                                    layout_manager: new Clutter.BinLayout() }); | ||||||
|         this._notificationWidget.connect('key-release-event', Lang.bind(this, this._onNotificationKeyRelease)); |         this._notificationWidget.connect('key-release-event', Lang.bind(this, this._onNotificationKeyRelease)); | ||||||
|         this._notificationWidget.connect('notify::hover', Lang.bind(this, this._onNotificationHoverChanged)); |         this._notificationWidget.connect('notify::hover', Lang.bind(this, this._onNotificationHoverChanged)); | ||||||
|  |         this._notificationWidget.connect('notify::height', Lang.bind(this, function() { | ||||||
|  |             this._notificationWidget.translation_y = -this._notificationWidget.height; | ||||||
|  |         })); | ||||||
|  |  | ||||||
|         this._notificationBin = new St.Bin({ y_expand: true }); |         this._notificationBin = new St.Bin({ y_expand: true }); | ||||||
|         this._notificationBin.set_y_align(Clutter.ActorAlign.START); |         this._notificationBin.set_y_align(Clutter.ActorAlign.START); | ||||||
| @@ -1829,11 +1534,6 @@ const MessageTray = new Lang.Class({ | |||||||
|         this._clickedSummaryItemMouseButton = -1; |         this._clickedSummaryItemMouseButton = -1; | ||||||
|         this._clickedSummaryItemAllocationChangedId = 0; |         this._clickedSummaryItemAllocationChangedId = 0; | ||||||
|  |  | ||||||
|         this._closeButton = Util.makeCloseButton(); |  | ||||||
|         this._closeButton.hide(); |  | ||||||
|         this._closeButton.connect('clicked', Lang.bind(this, this._closeNotification)); |  | ||||||
|         this._notificationWidget.add_actor(this._closeButton); |  | ||||||
|  |  | ||||||
|         this._userActiveWhileNotificationShown = false; |         this._userActiveWhileNotificationShown = false; | ||||||
|  |  | ||||||
|         this.idleMonitor = Meta.IdleMonitor.get_core(); |         this.idleMonitor = Meta.IdleMonitor.get_core(); | ||||||
| @@ -1864,7 +1564,6 @@ const MessageTray = new Lang.Class({ | |||||||
|         this._keyboardVisible = false; |         this._keyboardVisible = false; | ||||||
|         this._notificationState = State.HIDDEN; |         this._notificationState = State.HIDDEN; | ||||||
|         this._notificationTimeoutId = 0; |         this._notificationTimeoutId = 0; | ||||||
|         this._notificationExpandedId = 0; |  | ||||||
|         this._summaryBoxPointerState = State.HIDDEN; |         this._summaryBoxPointerState = State.HIDDEN; | ||||||
|         this._summaryBoxPointerTimeoutId = 0; |         this._summaryBoxPointerTimeoutId = 0; | ||||||
|         this._desktopCloneState = State.HIDDEN; |         this._desktopCloneState = State.HIDDEN; | ||||||
| @@ -1888,7 +1587,6 @@ const MessageTray = new Lang.Class({ | |||||||
|         Main.layoutManager.trayBox.add_actor(this._notificationWidget); |         Main.layoutManager.trayBox.add_actor(this._notificationWidget); | ||||||
|         Main.layoutManager.trackChrome(this.actor); |         Main.layoutManager.trackChrome(this.actor); | ||||||
|         Main.layoutManager.trackChrome(this._notificationWidget); |         Main.layoutManager.trackChrome(this._notificationWidget); | ||||||
|         Main.layoutManager.trackChrome(this._closeButton); |  | ||||||
|  |  | ||||||
|         global.screen.connect('in-fullscreen-changed', Lang.bind(this, this._updateState)); |         global.screen.connect('in-fullscreen-changed', Lang.bind(this, this._updateState)); | ||||||
|         Main.layoutManager.connect('hot-corners-changed', Lang.bind(this, this._hotCornersChanged)); |         Main.layoutManager.connect('hot-corners-changed', Lang.bind(this, this._hotCornersChanged)); | ||||||
| @@ -2040,14 +1738,6 @@ const MessageTray = new Lang.Class({ | |||||||
|         this._updateState(); |         this._updateState(); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _closeNotification: function() { |  | ||||||
|         if (this._notificationState == State.SHOWN) { |  | ||||||
|             this._closeButton.hide(); |  | ||||||
|             this._notification.emit('done-displaying'); |  | ||||||
|             this._notification.destroy(); |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     contains: function(source) { |     contains: function(source) { | ||||||
|         return this._sources.has(source); |         return this._sources.has(source); | ||||||
|     }, |     }, | ||||||
| @@ -2613,7 +2303,6 @@ const MessageTray = new Lang.Class({ | |||||||
|         this._notificationBin.child = this._notification.actor; |         this._notificationBin.child = this._notification.actor; | ||||||
|  |  | ||||||
|         this._notificationWidget.opacity = 0; |         this._notificationWidget.opacity = 0; | ||||||
|         this._notificationWidget.y = 0; |  | ||||||
|         this._notificationWidget.show(); |         this._notificationWidget.show(); | ||||||
|  |  | ||||||
|         this._updateShowingNotification(); |         this._updateShowingNotification(); | ||||||
| @@ -2648,23 +2337,16 @@ const MessageTray = new Lang.Class({ | |||||||
|         // We tween all notifications to full opacity. This ensures that both new notifications and |         // We tween all notifications to full opacity. This ensures that both new notifications and | ||||||
|         // notifications that might have been in the process of hiding get full opacity. |         // notifications that might have been in the process of hiding get full opacity. | ||||||
|         // |         // | ||||||
|         // We tween any notification showing in the banner mode to the appropriate height |  | ||||||
|         // (which is banner height or expanded height, depending on the notification state) |  | ||||||
|         // This ensures that both new notifications and notifications in the banner mode that might |  | ||||||
|         // have been in the process of hiding are shown with the correct height. |  | ||||||
|         // |  | ||||||
|         // We use this._showNotificationCompleted() onComplete callback to extend the time the updated |         // We use this._showNotificationCompleted() onComplete callback to extend the time the updated | ||||||
|         // notification is being shown. |         // notification is being shown. | ||||||
|  |  | ||||||
|         let tweenParams = { opacity: 255, |         this._tween(this._notificationWidget, '_notificationState', State.SHOWN, | ||||||
|                             y: -this._notificationWidget.height, |                     { opacity: 255, | ||||||
|                             time: ANIMATION_TIME, |                       time: ANIMATION_TIME, | ||||||
|                             transition: 'easeOutQuad', |                       transition: 'easeOutQuad', | ||||||
|                             onComplete: this._showNotificationCompleted, |                       onComplete: this._showNotificationCompleted, | ||||||
|                             onCompleteScope: this |                       onCompleteScope: this | ||||||
|                           }; |                     }); | ||||||
|  |  | ||||||
|         this._tween(this._notificationWidget, '_notificationState', State.SHOWN, tweenParams); |  | ||||||
|    }, |    }, | ||||||
|  |  | ||||||
|     _showNotificationCompleted: function() { |     _showNotificationCompleted: function() { | ||||||
| @@ -2712,10 +2394,6 @@ const MessageTray = new Lang.Class({ | |||||||
|     _hideNotification: function(animate) { |     _hideNotification: function(animate) { | ||||||
|         this._notificationFocusGrabber.ungrabFocus(); |         this._notificationFocusGrabber.ungrabFocus(); | ||||||
|  |  | ||||||
|         if (this._notificationExpandedId) { |  | ||||||
|             this._notification.disconnect(this._notificationExpandedId); |  | ||||||
|             this._notificationExpandedId = 0; |  | ||||||
|         } |  | ||||||
|         if (this._notificationClickedId) { |         if (this._notificationClickedId) { | ||||||
|             this._notification.disconnect(this._notificationClickedId); |             this._notification.disconnect(this._notificationClickedId); | ||||||
|             this._notificationClickedId = 0; |             this._notificationClickedId = 0; | ||||||
| @@ -2729,8 +2407,7 @@ const MessageTray = new Lang.Class({ | |||||||
|  |  | ||||||
|         if (animate) { |         if (animate) { | ||||||
|             this._tween(this._notificationWidget, '_notificationState', State.HIDDEN, |             this._tween(this._notificationWidget, '_notificationState', State.HIDDEN, | ||||||
|                         { y: this.actor.height, |                         { opacity: 0, | ||||||
|                           opacity: 0, |  | ||||||
|                           time: ANIMATION_TIME, |                           time: ANIMATION_TIME, | ||||||
|                           transition: 'easeOutQuad', |                           transition: 'easeOutQuad', | ||||||
|                           onComplete: this._hideNotificationCompleted, |                           onComplete: this._hideNotificationCompleted, | ||||||
| @@ -2738,7 +2415,6 @@ const MessageTray = new Lang.Class({ | |||||||
|                         }); |                         }); | ||||||
|         } else { |         } else { | ||||||
|             Tweener.removeTweens(this._notificationWidget); |             Tweener.removeTweens(this._notificationWidget); | ||||||
|             this._notificationWidget.y = this.actor.height; |  | ||||||
|             this._notificationWidget.opacity = 0; |             this._notificationWidget.opacity = 0; | ||||||
|             this._notificationState = State.HIDDEN; |             this._notificationState = State.HIDDEN; | ||||||
|             this._hideNotificationCompleted(); |             this._hideNotificationCompleted(); | ||||||
| @@ -2753,7 +2429,6 @@ const MessageTray = new Lang.Class({ | |||||||
|         if (notification.isTransient) |         if (notification.isTransient) | ||||||
|             notification.destroy(NotificationDestroyedReason.EXPIRED); |             notification.destroy(NotificationDestroyedReason.EXPIRED); | ||||||
|  |  | ||||||
|         this._closeButton.hide(); |  | ||||||
|         this._pointerInNotification = false; |         this._pointerInNotification = false; | ||||||
|         this._notificationRemoved = false; |         this._notificationRemoved = false; | ||||||
|         this._notificationBin.child = null; |         this._notificationBin.child = null; | ||||||
| @@ -2768,10 +2443,6 @@ const MessageTray = new Lang.Class({ | |||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _expandNotification: function(autoExpanding) { |     _expandNotification: function(autoExpanding) { | ||||||
|         if (!this._notificationExpandedId) |  | ||||||
|             this._notificationExpandedId = |  | ||||||
|                 this._notification.connect('expanded', |  | ||||||
|                                            Lang.bind(this, this._onNotificationExpanded)); |  | ||||||
|         // Don't animate changes in notifications that are auto-expanding. |         // Don't animate changes in notifications that are auto-expanding. | ||||||
|         this._notification.expand(!autoExpanding); |         this._notification.expand(!autoExpanding); | ||||||
|  |  | ||||||
| @@ -2780,31 +2451,6 @@ const MessageTray = new Lang.Class({ | |||||||
|             this._ensureNotificationFocused(); |             this._ensureNotificationFocused(); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _onNotificationExpanded: function() { |  | ||||||
|         let expandedY = - this._notificationWidget.height; |  | ||||||
|         this._closeButton.show(); |  | ||||||
|  |  | ||||||
|         // Don't animate the notification to its new position if it has shrunk: |  | ||||||
|         // there will be a very visible "gap" that breaks the illusion. |  | ||||||
|         if (this._notificationWidget.y < expandedY) { |  | ||||||
|             this._notificationWidget.y = expandedY; |  | ||||||
|         } else if (this._notification.y != expandedY) { |  | ||||||
|             // Tween also opacity here, to override a possible tween that's |  | ||||||
|             // currently hiding the notification. |  | ||||||
|             Tweener.addTween(this._notificationWidget, |  | ||||||
|                              { y: expandedY, |  | ||||||
|                                opacity: 255, |  | ||||||
|                                time: ANIMATION_TIME, |  | ||||||
|                                transition: 'easeOutQuad', |  | ||||||
|                                // HACK: Drive the state machine here better, |  | ||||||
|                                // instead of overwriting tweens |  | ||||||
|                                onComplete: Lang.bind(this, function() { |  | ||||||
|                                    this._notificationState = State.SHOWN; |  | ||||||
|                                }), |  | ||||||
|                              }); |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     _ensureNotificationFocused: function() { |     _ensureNotificationFocused: function() { | ||||||
|         this._notificationFocusGrabber.grabFocus(); |         this._notificationFocusGrabber.grabFocus(); | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -91,21 +91,6 @@ const rewriteRules = { | |||||||
|     ] |     ] | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const STANDARD_TRAY_ICON_IMPLEMENTATIONS = { |  | ||||||
|     'bluetooth-applet': 'bluetooth', |  | ||||||
|     'gnome-volume-control-applet': 'volume', // renamed to gnome-sound-applet |  | ||||||
|                                              // when moved to control center |  | ||||||
|     'gnome-sound-applet': 'volume', |  | ||||||
|     'nm-applet': 'network', |  | ||||||
|     'gnome-power-manager': 'battery', |  | ||||||
|     'keyboard': 'keyboard', |  | ||||||
|     'a11y-keyboard': 'a11y', |  | ||||||
|     'kbd-scrolllock': 'keyboard', |  | ||||||
|     'kbd-numlock': 'keyboard', |  | ||||||
|     'kbd-capslock': 'keyboard', |  | ||||||
|     'ibus-ui-gtk': 'keyboard' |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const FdoNotificationDaemon = new Lang.Class({ | const FdoNotificationDaemon = new Lang.Class({ | ||||||
|     Name: 'FdoNotificationDaemon', |     Name: 'FdoNotificationDaemon', | ||||||
|  |  | ||||||
| @@ -334,13 +319,14 @@ const FdoNotificationDaemon = new Lang.Class({ | |||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _makeButton: function(id, label, useActionIcons) { |     _makeButton: function(id, label, useActionIcons) { | ||||||
|         let button = new St.Button({ can_focus: true }); |         let button = new St.Button({ can_focus: true, | ||||||
|  |                                      x_expand: true, | ||||||
|  |                                      style_class: 'notification-button' }); | ||||||
|         let iconName = id.endsWith('-symbolic') ? id : id + '-symbolic'; |         let iconName = id.endsWith('-symbolic') ? id : id + '-symbolic'; | ||||||
|  |  | ||||||
|         if (useActionIcons && Gtk.IconTheme.get_default().has_icon(iconName)) { |         if (useActionIcons && Gtk.IconTheme.get_default().has_icon(iconName)) { | ||||||
|             button.add_style_class_name('notification-icon-button'); |             button.child = new St.Icon({ icon_name: iconName, icon_size: 16 }); | ||||||
|             button.child = new St.Icon({ icon_name: iconName }); |  | ||||||
|         } else { |         } else { | ||||||
|             button.add_style_class_name('notification-button'); |  | ||||||
|             button.label = label; |             button.label = label; | ||||||
|         } |         } | ||||||
|         return button; |         return button; | ||||||
| @@ -379,8 +365,6 @@ const FdoNotificationDaemon = new Lang.Class({ | |||||||
|         let gicon = this._iconForNotificationData(icon, hints); |         let gicon = this._iconForNotificationData(icon, hints); | ||||||
|         let gimage = this._imageForNotificationData(hints); |         let gimage = this._imageForNotificationData(hints); | ||||||
|  |  | ||||||
|         let image = null; |  | ||||||
|  |  | ||||||
|         // If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon |         // If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon | ||||||
|         // and don't show a large image. There are currently many applications that use |         // and don't show a large image. There are currently many applications that use | ||||||
|         // notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets |         // notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets | ||||||
| @@ -389,10 +373,7 @@ const FdoNotificationDaemon = new Lang.Class({ | |||||||
|         // So the logic here does the right thing for this case. If both an icon and either |         // So the logic here does the right thing for this case. If both an icon and either | ||||||
|         // one of 'image-data' or 'image-path' are specified, we show both an icon and |         // one of 'image-data' or 'image-path' are specified, we show both an icon and | ||||||
|         // a large image. |         // a large image. | ||||||
|         if (gicon && gimage) |         if (!gicon && gimage) | ||||||
|             image = new St.Icon({ gicon: gimage, |  | ||||||
|                                   icon_size: notification.IMAGE_SIZE }); |  | ||||||
|         else if (!gicon && gimage) |  | ||||||
|             gicon = gimage; |             gicon = gimage; | ||||||
|         else if (!gicon) |         else if (!gicon) | ||||||
|             gicon = this._fallbackIconForNotificationData(hints); |             gicon = this._fallbackIconForNotificationData(hints); | ||||||
| @@ -402,7 +383,6 @@ const FdoNotificationDaemon = new Lang.Class({ | |||||||
|                                              clear: true, |                                              clear: true, | ||||||
|                                              soundFile: hints['sound-file'], |                                              soundFile: hints['sound-file'], | ||||||
|                                              soundName: hints['sound-name'] }); |                                              soundName: hints['sound-name'] }); | ||||||
|         notification.setImage(image); |  | ||||||
|  |  | ||||||
|         let hasDefaultAction = false; |         let hasDefaultAction = false; | ||||||
|  |  | ||||||
| @@ -442,7 +422,6 @@ const FdoNotificationDaemon = new Lang.Class({ | |||||||
|                 notification.setUrgency(MessageTray.Urgency.CRITICAL); |                 notification.setUrgency(MessageTray.Urgency.CRITICAL); | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
|         notification.setResident(hints.resident == true); |  | ||||||
|         // 'transient' is a reserved keyword in JS, so we have to retrieve the value |         // '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 |         // of the 'transient' hint with hints['transient'] rather than hints.transient | ||||||
|         notification.setTransient(hints['transient'] == true); |         notification.setTransient(hints['transient'] == true); | ||||||
| @@ -470,7 +449,6 @@ const FdoNotificationDaemon = new Lang.Class({ | |||||||
|             'body-markup', |             'body-markup', | ||||||
|             // 'icon-multi', |             // 'icon-multi', | ||||||
|             'icon-static', |             'icon-static', | ||||||
|             'persistence', |  | ||||||
|             'sound', |             'sound', | ||||||
|         ]; |         ]; | ||||||
|     }, |     }, | ||||||
| @@ -492,7 +470,7 @@ const FdoNotificationDaemon = new Lang.Class({ | |||||||
|         for (let i = 0; i < this._sources.length; i++) { |         for (let i = 0; i < this._sources.length; i++) { | ||||||
|             let source = this._sources[i]; |             let source = this._sources[i]; | ||||||
|             if (source.app == tracker.focus_app) { |             if (source.app == tracker.focus_app) { | ||||||
|                 source.destroyNonResidentNotifications(); |                 source.destroyNotifications(); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -509,10 +487,6 @@ const FdoNotificationDaemon = new Lang.Class({ | |||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _onTrayIconAdded: function(o, icon) { |     _onTrayIconAdded: function(o, icon) { | ||||||
|         let wmClass = icon.wm_class ? icon.wm_class.toLowerCase() : ''; |  | ||||||
|         if (STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass] !== undefined) |  | ||||||
|             return; |  | ||||||
|  |  | ||||||
|         let source = this._getSource(icon.title || icon.wm_class || C_("program", "Unknown"), icon.pid, null, null, icon); |         let source = this._getSource(icon.title || icon.wm_class || C_("program", "Unknown"), icon.pid, null, null, icon); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
| @@ -584,7 +558,7 @@ const FdoNotificationDaemonSource = new Lang.Class({ | |||||||
|             this.iconUpdated(); |             this.iconUpdated(); | ||||||
|  |  | ||||||
|         let tracker = Shell.WindowTracker.get_default(); |         let tracker = Shell.WindowTracker.get_default(); | ||||||
|         if (notification.resident && this.app && tracker.focus_app == this.app) |         if (this.app && tracker.focus_app == this.app) | ||||||
|             this.pushNotification(notification); |             this.pushNotification(notification); | ||||||
|         else |         else | ||||||
|             this.notify(notification); |             this.notify(notification); | ||||||
| @@ -651,7 +625,7 @@ const FdoNotificationDaemonSource = new Lang.Class({ | |||||||
|  |  | ||||||
|     open: function() { |     open: function() { | ||||||
|         this.openApp(); |         this.openApp(); | ||||||
|         this.destroyNonResidentNotifications(); |         this.destroyNotifications(); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _lastNotificationRemoved: function() { |     _lastNotificationRemoved: function() { | ||||||
|   | |||||||
| @@ -21,10 +21,6 @@ struct _ShellTpClientPrivate | |||||||
|   ShellTpClientHandleChannelsImpl handle_channels_impl; |   ShellTpClientHandleChannelsImpl handle_channels_impl; | ||||||
|   gpointer user_data_handle_channels; |   gpointer user_data_handle_channels; | ||||||
|   GDestroyNotify destroy_handle_channels; |   GDestroyNotify destroy_handle_channels; | ||||||
|  |  | ||||||
|   ShellTpClientContactListChangedImpl contact_list_changed_impl; |  | ||||||
|   gpointer user_data_contact_list_changed; |  | ||||||
|   GDestroyNotify destroy_contact_list_changed; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -83,16 +79,6 @@ struct _ShellTpClientPrivate | |||||||
|  * Signature of the implementation of the HandleChannels method. |  * Signature of the implementation of the HandleChannels method. | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * ShellTpClientContactListChangedImpl: |  | ||||||
|  * @connection: a #TpConnection having %TP_CONNECTION_FEATURE_CORE prepared |  | ||||||
|  * if possible |  | ||||||
|  * @added: (element-type TelepathyGLib.Contact): a #GPtrArray of added #TpContact |  | ||||||
|  * @removed: (element-type TelepathyGLib.Contact): a #GPtrArray of removed #TpContact |  | ||||||
|  * |  | ||||||
|  * Signature of the implementation of the ContactListChanged method. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| static void | static void | ||||||
| shell_tp_client_init (ShellTpClient *self) | shell_tp_client_init (ShellTpClient *self) | ||||||
| { | { | ||||||
| @@ -226,13 +212,6 @@ shell_tp_client_dispose (GObject *object) | |||||||
|       self->priv->user_data_handle_channels = NULL; |       self->priv->user_data_handle_channels = NULL; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   if (self->priv->destroy_contact_list_changed != NULL) |  | ||||||
|     { |  | ||||||
|       self->priv->destroy_contact_list_changed (self->priv->user_data_contact_list_changed); |  | ||||||
|       self->priv->destroy_contact_list_changed = NULL; |  | ||||||
|       self->priv->user_data_contact_list_changed = NULL; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|   if (dispose != NULL) |   if (dispose != NULL) | ||||||
|     dispose (object); |     dispose (object); | ||||||
| } | } | ||||||
| @@ -290,40 +269,3 @@ shell_tp_client_set_handle_channels_func (ShellTpClient *self, | |||||||
|   self->priv->user_data_handle_channels = user_data; |   self->priv->user_data_handle_channels = user_data; | ||||||
|   self->priv->destroy_handle_channels = destroy; |   self->priv->destroy_handle_channels = destroy; | ||||||
| } | } | ||||||
|  |  | ||||||
| void |  | ||||||
| shell_tp_client_set_contact_list_changed_func (ShellTpClient *self, |  | ||||||
|     ShellTpClientContactListChangedImpl contact_list_changed_impl, |  | ||||||
|     gpointer user_data, |  | ||||||
|     GDestroyNotify destroy) |  | ||||||
| { |  | ||||||
|   g_assert (self->priv->contact_list_changed_impl == NULL); |  | ||||||
|  |  | ||||||
|   self->priv->contact_list_changed_impl = contact_list_changed_impl; |  | ||||||
|   self->priv->user_data_handle_channels = user_data; |  | ||||||
|   self->priv->destroy_handle_channels = destroy; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static void |  | ||||||
| on_contact_list_changed (TpConnection *conn, |  | ||||||
|                          GPtrArray *added, |  | ||||||
|                          GPtrArray *removed, |  | ||||||
|                          gpointer user_data) |  | ||||||
| { |  | ||||||
|   ShellTpClient *self = (ShellTpClient *) user_data; |  | ||||||
|  |  | ||||||
|   g_assert (self->priv->contact_list_changed_impl != NULL); |  | ||||||
|  |  | ||||||
|   self->priv->contact_list_changed_impl (conn, |  | ||||||
|       added, removed, |  | ||||||
|       self->priv->user_data_contact_list_changed); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void |  | ||||||
| shell_tp_client_grab_contact_list_changed (ShellTpClient *self, |  | ||||||
|                                            TpConnection *conn) |  | ||||||
| { |  | ||||||
|   g_signal_connect (conn, "contact-list-changed", |  | ||||||
|                     G_CALLBACK (on_contact_list_changed), |  | ||||||
|                     self); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -86,19 +86,5 @@ void shell_tp_client_set_handle_channels_func (ShellTpClient *self, | |||||||
|     gpointer user_data, |     gpointer user_data, | ||||||
|     GDestroyNotify destroy); |     GDestroyNotify destroy); | ||||||
|  |  | ||||||
| typedef void (*ShellTpClientContactListChangedImpl) ( |  | ||||||
|     TpConnection *connection, |  | ||||||
|     GPtrArray *added, |  | ||||||
|     GPtrArray *removed, |  | ||||||
|     gpointer user_data); |  | ||||||
|  |  | ||||||
| void shell_tp_client_set_contact_list_changed_func (ShellTpClient *self, |  | ||||||
|     ShellTpClientContactListChangedImpl contact_list_changed_impl, |  | ||||||
|     gpointer user_data, |  | ||||||
|     GDestroyNotify destroy); |  | ||||||
|  |  | ||||||
| void shell_tp_client_grab_contact_list_changed (ShellTpClient *self, |  | ||||||
|     TpConnection *conn); |  | ||||||
|  |  | ||||||
| G_END_DECLS | G_END_DECLS | ||||||
| #endif /* __SHELL_TP_CLIENT_H__ */ | #endif /* __SHELL_TP_CLIENT_H__ */ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user