Compare commits
	
		
			12 Commits
		
	
	
		
			3.31.92
			...
			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 */ | ||||
|  | ||||
| .candidate-page-button, | ||||
| .notification-button, | ||||
| .notification-icon-button, | ||||
| .hotplug-notification-item, | ||||
| .hotplug-resident-eject-button, | ||||
| .modal-dialog-button, | ||||
| .app-view-control { | ||||
|     border: 1px solid #8b8b8b; | ||||
| @@ -431,17 +428,12 @@ StScrollBar StButton#vhandle:active { | ||||
| } | ||||
|  | ||||
| .candidate-page-button:hover, | ||||
| .notification-button:hover, | ||||
| .notification-icon-button:hover, | ||||
| .hotplug-notification-item:hover, | ||||
| .hotplug-resident-eject-button:hover, | ||||
| .modal-dialog-button:hover { | ||||
|     background-gradient-start: rgba(255, 255, 255, 0.3); | ||||
|     background-gradient-end: rgba(255, 255, 255, 0.1); | ||||
| } | ||||
|  | ||||
| .notification-button:focus, | ||||
| .notification-icon-button:focus, | ||||
| .hotplug-notification-item:focus, | ||||
| .modal-dialog-button:focus, | ||||
| .app-view-control:focus { | ||||
| @@ -455,10 +447,7 @@ StScrollBar StButton#vhandle:active { | ||||
|  | ||||
| .candidate-page-button:active, | ||||
| .candidate-page-button:pressed, | ||||
| .notification-button:active, | ||||
| .notification-icon-button:active, | ||||
| .hotplug-notification-item:active, | ||||
| .hotplug-resident-eject-button:active, | ||||
| .modal-dialog-button:active, | ||||
| .modal-dialog-button:pressed, | ||||
| .app-view-control:checked { | ||||
| @@ -467,8 +456,6 @@ StScrollBar StButton#vhandle:active { | ||||
| } | ||||
|  | ||||
| .candidate-page-button:insensitive, | ||||
| .notification-button:insensitive, | ||||
| .notification-icon-button:insensitive, | ||||
| .modal-dialog-button:insensitive { | ||||
|     border-color: #666666; | ||||
|     color: #9f9f9f; | ||||
| @@ -480,7 +467,6 @@ StScrollBar StButton#vhandle:active { | ||||
|  | ||||
| #searchEntry, | ||||
| .modal-dialog-button, | ||||
| .notification-button, | ||||
| .hotplug-notification-item, | ||||
| .app-view-controls, | ||||
| #screenShieldNotifications { | ||||
| @@ -1535,36 +1521,69 @@ StScrollBar StButton#vhandle:active { | ||||
|     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 { | ||||
|     font-size: 11pt; | ||||
|     width: 34em; | ||||
| } | ||||
|  | ||||
| .notification.multi-line-notification { | ||||
|     padding-bottom: 8px; | ||||
| .notification-main-button, | ||||
| .notification-button { | ||||
|     background: rgba(0,0,0,0.9); | ||||
| } | ||||
|  | ||||
| .notification-unexpanded { | ||||
|     /* We want to force the actor at a specific size, irrespective | ||||
|        of its minimum and preferred size, so we override both */ | ||||
|     min-height: 36px; | ||||
|     height: 36px; | ||||
| .notification-main-button { | ||||
|     border-radius: 10px 10px 0px 0px; | ||||
| } | ||||
|  | ||||
| /* We use row-span = 2 for the image cell, which prevents its height preferences to be | ||||
|    taken into account during allocation, so its height ends up being limited by the height | ||||
|    of the content in the other rows. To avoid showing a stretched image, we set the minimum | ||||
|    height of the table to be ICON_SIZE + IMAGE_SIZE + spacing-rows = 24 + 125 + 10 = 159 */ | ||||
| .notification-with-image { | ||||
|     min-height: 159px; | ||||
| .notification-main-content { | ||||
|     padding: 8px; | ||||
|     spacing: 8px; | ||||
| } | ||||
|  | ||||
| .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 { | ||||
| @@ -1606,47 +1625,6 @@ StScrollBar StButton#vhandle:active { | ||||
|     -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 { | ||||
|     icon-size: 1.09em; | ||||
| } | ||||
| @@ -1669,45 +1647,6 @@ StScrollBar StButton#vhandle:active { | ||||
|     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 { | ||||
|     color: #888888; | ||||
| } | ||||
| @@ -1747,7 +1686,11 @@ StScrollBar StButton#vhandle:active { | ||||
|     padding-right: 4px; | ||||
| } | ||||
|  | ||||
| .chat-notification-scrollview{ | ||||
| .chat-notification-body-box { | ||||
|     spacing: 5px; | ||||
| } | ||||
|  | ||||
| .chat-notification-scrollview { | ||||
|     max-height: 22em; | ||||
| } | ||||
|  | ||||
| @@ -2666,8 +2609,7 @@ StScrollBar StButton#vhandle:active { | ||||
|     padding-bottom: 0px; | ||||
| } | ||||
|  | ||||
| #screenShieldNotifications .notification-button, | ||||
| #screenShieldNotifications .notification-icon-button { | ||||
| #screenShieldNotifications .notification-button { | ||||
|     border: 1px rgba(255,255,255,0.5); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -170,17 +170,6 @@ const AutorunManager = new Lang.Class({ | ||||
|         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() { | ||||
|         this._scanMounts(); | ||||
|  | ||||
| @@ -189,17 +178,12 @@ const AutorunManager = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     disable: function() { | ||||
|         if (this._residentSource) | ||||
|             this._residentSource.destroy(); | ||||
|         this._volumeMonitor.disconnect(this._mountAddedId); | ||||
|         this._volumeMonitor.disconnect(this._mountRemovedId); | ||||
|     }, | ||||
|  | ||||
|     _processMount: function(mount, hotplug) { | ||||
|         let discoverer = new ContentTypeDiscoverer(Lang.bind(this, function(mount, apps, contentTypes) { | ||||
|             this._ensureResidentSource(); | ||||
|             this._residentSource.addMount(mount, apps); | ||||
|  | ||||
|             if (hotplug) | ||||
|                 this._transDispatcher.addMount(mount, apps, contentTypes); | ||||
|         })); | ||||
| @@ -224,8 +208,6 @@ const AutorunManager = new Lang.Class({ | ||||
|  | ||||
|     _onMountRemoved: function(monitor, mount) { | ||||
|         this._transDispatcher.removeMount(mount); | ||||
|         if (this._residentSource) | ||||
|             this._residentSource.removeMount(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({ | ||||
|     Name: 'AutorunTransientDispatcher', | ||||
|  | ||||
| @@ -559,12 +394,12 @@ const AutorunTransientNotification = new Lang.Class({ | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(manager, source) { | ||||
|         this.parent(source, source.title, null, { customContent: true }); | ||||
|         this.parent(source, source.title); | ||||
|  | ||||
|         this._manager = manager; | ||||
|         this._box = new St.BoxLayout({ style_class: 'hotplug-transient-box', | ||||
|                                        vertical: true }); | ||||
|         this.addActor(this._box); | ||||
|         this._bodyBin.child = this._box; | ||||
|  | ||||
|         this._mount = source.mount; | ||||
|  | ||||
|   | ||||
| @@ -102,15 +102,6 @@ const TelepathyClient = new Lang.Class({ | ||||
|         this._tpClient.set_handle_channels_func( | ||||
|             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 | ||||
|         // needed | ||||
|         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); | ||||
|         } | ||||
|  | ||||
|         this._accountManagerValidityChangedId = this._accountManager.connect('account-validity-changed', | ||||
|                                                                              Lang.bind(this, this._accountValidityChanged)); | ||||
|  | ||||
|         if (!this._accountManager.is_prepared(Tp.AccountManager.get_feature_quark_core())) | ||||
|             this._accountManager.prepare_async(null, Lang.bind(this, this._accountManagerPrepared)); | ||||
|     }, | ||||
|  | ||||
|     disable: function() { | ||||
|         this._tpClient.unregister(); | ||||
|         this._accountManager.disconnect(this._accountManagerValidityChangedId); | ||||
|         this._accountManagerValidityChangedId = 0; | ||||
|     }, | ||||
|  | ||||
|     _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, | ||||
|                                dispatchOp, context) { | ||||
|         let channel = channels[0]; | ||||
| @@ -259,10 +218,6 @@ const TelepathyClient = new Lang.Class({ | ||||
|  | ||||
|         if (chanType == Tp.IFACE_CHANNEL_TYPE_TEXT) | ||||
|             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 | ||||
|             context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT, | ||||
|                                         message: 'Unsupported channel type' })); | ||||
| @@ -283,45 +238,9 @@ const TelepathyClient = new Lang.Class({ | ||||
|                 }})); | ||||
|  | ||||
|             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) { | ||||
|         // Nothing to do as we don't make a distinction between observed and | ||||
|         // handled channels. | ||||
| @@ -329,105 +248,7 @@ const TelepathyClient = new Lang.Class({ | ||||
|  | ||||
|     _accountManagerPrepared: function(am, 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({ | ||||
| @@ -545,7 +366,7 @@ const ChatSource = new Lang.Class({ | ||||
|  | ||||
|     _updateAvatarIcon: function() { | ||||
|         this.iconUpdated(); | ||||
|         this._notification.update(this._notification.title, null, { customContent: true }); | ||||
|         this._notification.update(this._notification.title); | ||||
|     }, | ||||
|  | ||||
|     open: function() { | ||||
| @@ -737,7 +558,7 @@ const ChatSource = new Lang.Class({ | ||||
|  | ||||
|         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) | ||||
|             msg += ' <i>(' + GLib.markup_escape_text(message, -1) + ')</i>'; | ||||
| @@ -764,8 +585,7 @@ const ChatNotification = new Lang.Class({ | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(source) { | ||||
|         this.parent(source, source.title, null, { customContent: true, secondaryGIcon: source.getSecondaryIcon() }); | ||||
|         this.setResident(true); | ||||
|         this.parent(source, source.title, null, { secondaryGIcon: source.getSecondaryIcon() }); | ||||
|  | ||||
|         this._responseEntry = new St.Entry({ style_class: 'chat-response', | ||||
|                                              can_focus: true }); | ||||
| @@ -781,15 +601,17 @@ const ChatNotification = new Lang.Class({ | ||||
|             this.emit('unfocused'); | ||||
|         })); | ||||
|  | ||||
|         this._createScrollArea(); | ||||
|         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 | ||||
|         // force a scroll to the bottom if things change while we were at the | ||||
|         // bottom | ||||
|         this._oldMaxScrollValue = this._scrollArea.vscroll.adjustment.value; | ||||
|         this._scrollArea.add_style_class_name('chat-notification-scrollview'); | ||||
|         this._scrollArea.vscroll.adjustment.connect('changed', Lang.bind(this, function(adjustment) { | ||||
|         this._oldMaxScrollValue = this._bodyScrollArea.vscroll.adjustment.value; | ||||
|         this._bodyScrollArea.add_style_class_name('chat-notification-scrollview'); | ||||
|         this._bodyScrollArea.vscroll.adjustment.connect('changed', Lang.bind(this, function(adjustment) { | ||||
|             if (adjustment.value == this._oldMaxScrollValue) | ||||
|                 this.scrollTo(St.Side.BOTTOM); | ||||
|             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) { | ||||
|             this.update(this.source.title, messageBody, { customContent: true, | ||||
|                                                           bannerMarkup: true }); | ||||
|             this.update(this.source.title, messageBody, { bannerMarkup: true }); | ||||
|         } | ||||
|  | ||||
|         let group = (message.direction == NotificationDirection.RECEIVED ? | ||||
| @@ -864,7 +685,7 @@ const ChatNotification = new Lang.Class({ | ||||
|                 expired[i].actor.destroy(); | ||||
|         } | ||||
|  | ||||
|         let groups = this._contentArea.get_children(); | ||||
|         let groups = this._bodyBox.get_children(); | ||||
|         for (let i = 0; i < groups.length; i++) { | ||||
|             let group = groups[i]; | ||||
|             if (group.get_n_children() == 0) | ||||
| @@ -896,9 +717,9 @@ const ChatNotification = new Lang.Class({ | ||||
|         if (this._timestampTimeoutId) | ||||
|             Mainloop.source_remove(this._timestampTimeoutId); | ||||
|  | ||||
|         let highlighter = new MessageTray.URLHighlighter(props.body, | ||||
|                                                          true,  // line wrap? | ||||
|                                                          true); // allow markup? | ||||
|         let highlighter = new MessageTray.URLHighlighter(); | ||||
|         highlighter.actor.clutter_text.line_wrap = true; | ||||
|         highlighter.setMarkup(props.body, true); | ||||
|  | ||||
|         let body = highlighter.actor; | ||||
|  | ||||
| @@ -910,14 +731,12 @@ const ChatNotification = new Lang.Class({ | ||||
|         if (group != this._lastGroup) { | ||||
|             this._lastGroup = group; | ||||
|             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.add(body, props.childProps); | ||||
|         this.addActor(this._lastMessageBox); | ||||
|  | ||||
|         this.updated(); | ||||
|         this._bodyBox.add_child(this._lastMessageBox); | ||||
|  | ||||
|         let timestamp = props.timestamp; | ||||
|         this._history.unshift({ actor: body, time: timestamp, | ||||
| @@ -1052,7 +871,7 @@ const ChatNotification = new Lang.Class({ | ||||
|                                    group: 'meta', | ||||
|                                    styles: ['chat-meta-message'] }); | ||||
|  | ||||
|         this.update(newAlias, null, { customContent: true }); | ||||
|         this.update(newAlias); | ||||
|  | ||||
|         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; | ||||
|   | ||||
| @@ -111,7 +111,6 @@ const FocusGrabber = new Lang.Class({ | ||||
|         if (this._focused) | ||||
|             return; | ||||
|  | ||||
|         this._prevFocusedWindow = global.display.focus_window; | ||||
|         this._prevKeyFocusActor = global.stage.get_key_focus(); | ||||
|  | ||||
|         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) { | ||||
|         text = text ? _fixMarkup(text, allowMarkup) : ''; | ||||
|         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 | ||||
| // fit fully in the banner mode. When the notification is expanded, | ||||
| // the @banner text from the top line is always removed. The complete | ||||
| // @banner text is added as the first element in the content section, | ||||
| // unless 'customContent' parameter with the value 'true' is specified | ||||
| // in @params. | ||||
| // @banner text is added to the notification by default. You can change | ||||
| // what is displayed by setting the child of this._bodyBin. | ||||
| // | ||||
| // Additional notification content can be added with addActor() and | ||||
| // addBody() methods. The notification content is put inside a | ||||
| // 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. | ||||
| // You can also add buttons to the notification with addButton(), | ||||
| // and you can construct simple default buttons with addAction(). | ||||
| // | ||||
| // By default, the icon shown is the same as the source's. | ||||
| // 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 | ||||
| // 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 | ||||
| // 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({ | ||||
|     Name: 'Notification', | ||||
|  | ||||
|     ICON_SIZE: 24, | ||||
|  | ||||
|     IMAGE_SIZE: 125, | ||||
|     ICON_SIZE: 32, | ||||
|  | ||||
|     _init: function(source, title, banner, params) { | ||||
|         this.source = source; | ||||
|         this.title = title; | ||||
|         this.urgency = Urgency.NORMAL; | ||||
|         this.resident = false; | ||||
|         // 'transient' is a reserved keyword in JS, so we have to use an alternate variable name | ||||
|         this.isTransient = false; | ||||
|         this.isMusic = false; | ||||
| @@ -499,59 +484,89 @@ const Notification = new Lang.Class({ | ||||
|         this.focused = false; | ||||
|         this.acknowledged = 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._soundFile = null; | ||||
|         this._soundPlayed = false; | ||||
|  | ||||
|         this.actor = new St.Button({ accessible_role: Atk.Role.NOTIFICATION }); | ||||
|         this.actor.add_style_class_name('notification-unexpanded'); | ||||
|         // Let me draw you a picture. I am a bad artist: | ||||
|         // | ||||
|         //      ,. 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.connect('clicked', Lang.bind(this, this._onClicked)); | ||||
|         this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); | ||||
|  | ||||
|         this._table = new St.Table({ style_class: 'notification', | ||||
|                                      reactive: true }); | ||||
|         this._table.connect('style-changed', Lang.bind(this, this._styleChanged)); | ||||
|         this.actor.set_child(this._table); | ||||
|         this._mainButton = new St.Button({ style_class: 'notification-main-button', | ||||
|                                            can_focus: true, | ||||
|                                            x_fill: true, y_fill: true }); | ||||
|         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 | ||||
|         // banner text, but ellipsized if they won't both fit. We can't | ||||
|         // make St.Table or St.BoxLayout do this the way we want (don't | ||||
|         // 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 }); | ||||
|         // Separates the icon, title/body and close button | ||||
|         this._hbox = new St.BoxLayout({ style_class: 'notification-main-content' }); | ||||
|         this._mainButton.child = this._hbox; | ||||
|  | ||||
|         // This is an empty cell that overlaps with this._bannerBox cell to ensure | ||||
|         // that this._bannerBox cell expands horizontally, while not forcing the | ||||
|         // 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._iconBin = new St.Bin({ y_align: St.Align.START }); | ||||
|         this._hbox.add_child(this._iconBin); | ||||
|  | ||||
|         this._titleLabel = new St.Label(); | ||||
|         this._bannerBox.add_actor(this._titleLabel); | ||||
|         this._bannerUrlHighlighter = new URLHighlighter(); | ||||
|         this._bannerLabel = this._bannerUrlHighlighter.actor; | ||||
|         this._bannerBox.add_actor(this._bannerLabel); | ||||
|         this._titleBodyBox = new St.BoxLayout({ style_class: 'notification-title-body-box', | ||||
|                                                 vertical: true }); | ||||
|         this._titleBodyBox.set_x_expand(true); | ||||
|         this._hbox.add_child(this._titleBodyBox); | ||||
|  | ||||
|         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 | ||||
|         // will call .update() later on. This is the case of | ||||
| @@ -559,6 +574,31 @@ const Notification = new Lang.Class({ | ||||
|         // for new and updated notifications | ||||
|         if (arguments.length != 1) | ||||
|             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: | ||||
| @@ -570,52 +610,31 @@ const Notification = new Lang.Class({ | ||||
|     // the title/banner. If @params.clear is %true, it will also | ||||
|     // remove any additional actors/action buttons previously added. | ||||
|     update: function(title, banner, params) { | ||||
|         params = Params.parse(params, { customContent: false, | ||||
|                                         gicon: null, | ||||
|         params = Params.parse(params, { gicon: null, | ||||
|                                         secondaryGIcon: null, | ||||
|                                         bannerMarkup: false, | ||||
|                                         clear: false, | ||||
|                                         soundName: null, | ||||
|                                         soundFile: null }); | ||||
|  | ||||
|         this._customContent = params.customContent; | ||||
|  | ||||
|         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 (oldFocus && this._actionArea.contains(oldFocus)) | ||||
|                 this.actor.grab_key_focus(); | ||||
|  | ||||
|             this._actionArea.destroy(); | ||||
|             this._actionArea = null; | ||||
|             this._buttonBox = null; | ||||
|         } | ||||
|         if (params.clear) | ||||
|             this.unsetImage(); | ||||
|  | ||||
|         if (!this._scrollArea && !this._actionArea && !this._imageBin) | ||||
|             this._table.remove_style_class_name('multi-line-notification'); | ||||
|         if (params.clear) { | ||||
|             this._buttonBox.destroy_all_children(); | ||||
|         } | ||||
|  | ||||
|         if (this._icon && (params.gicon || params.clear)) { | ||||
|             this._icon.destroy(); | ||||
|             this._icon = null; | ||||
|         } | ||||
|  | ||||
|         if (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); | ||||
|         } | ||||
|  | ||||
|         if (this._icon) { | ||||
|             this._table.add(this._icon, { row: 0, | ||||
|                                           col: 0, | ||||
|                                           x_expand: false, | ||||
|                                           y_expand: false, | ||||
|                                           y_fill: false, | ||||
|                                           y_align: St.Align.START }); | ||||
|         if (this._icon) | ||||
|             this._iconBin.child = this._icon; | ||||
|  | ||||
|         if (this._secondaryIcon && (params.secondaryGIcon || params.clear)) { | ||||
|             this._secondaryIcon.destroy(); | ||||
|             this._secondaryIcon = null; | ||||
|         } | ||||
|  | ||||
|         if (params.secondaryGIcon) { | ||||
|             this._secondaryIcon = new St.Icon({ gicon: params.secondaryGIcon, | ||||
|                                                 style_class: 'secondary-icon' }); | ||||
|             this._bannerBox.add_actor(this._secondaryIcon); | ||||
|             this._secondaryIconBin.child = this._secondaryIcon; | ||||
|         } | ||||
|  | ||||
|         this.title = title; | ||||
|         title = title ? _fixMarkup(title.replace(/\n/g, ' '), false) : ''; | ||||
|         this._titleLabel.clutter_text.set_markup('<b>' + title + '</b>'); | ||||
|  | ||||
|         let titleDirection; | ||||
|         if (Pango.find_base_dir(title, -1) == Pango.Direction.RTL) | ||||
|             this._titleDirection = Clutter.TextDirection.RTL; | ||||
|             titleDirection = Clutter.TextDirection.RTL; | ||||
|         else | ||||
|             this._titleDirection = Clutter.TextDirection.LTR; | ||||
|             titleDirection = Clutter.TextDirection.LTR; | ||||
|  | ||||
|         // Let the title's text direction control the overall direction | ||||
|         // 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 | ||||
|         // will be allocated at the available width, so that their alignment | ||||
|         // 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 | ||||
|         // 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(); | ||||
|         this._bodyUrlHighlighter.setMarkup(banner, params.bannerMarkup); | ||||
|  | ||||
|         if (this._soundName != params.soundName || | ||||
|             this._soundFile != params.soundFile) { | ||||
| @@ -680,71 +684,18 @@ const Notification = new Lang.Class({ | ||||
|             this._soundPlayed = false; | ||||
|         } | ||||
|  | ||||
|         this.updated(); | ||||
|         this._sync(); | ||||
|     }, | ||||
|  | ||||
|     setIconVisible: function(visible) { | ||||
|         this._icon.visible = visible; | ||||
|         this._sync(); | ||||
|     }, | ||||
|  | ||||
|     enableScrolling: function(enableScrolling) { | ||||
|         this._scrollPolicy = enableScrolling ? Gtk.PolicyType.AUTOMATIC : Gtk.PolicyType.NEVER; | ||||
|         if (this._scrollArea) { | ||||
|             this._scrollArea.vscrollbar_policy = this._scrollPolicy; | ||||
|             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); | ||||
|         } | ||||
|         let scrollPolicy = enableScrolling ? Gtk.PolicyType.AUTOMATIC : Gtk.PolicyType.NEVER; | ||||
|         this._bodyScrollArea.vscrollbar_policy = scrollPolicy; | ||||
|         this._bodyScrollArea.enable_mouse_scrolling = enableScrolling; | ||||
|     }, | ||||
|  | ||||
|     // scrollTo: | ||||
| @@ -752,112 +703,32 @@ const Notification = new Lang.Class({ | ||||
|     // | ||||
|     // Scrolls the content area (if scrollable) to the indicated edge | ||||
|     scrollTo: function(side) { | ||||
|         let adjustment = this._scrollArea.vscroll.adjustment; | ||||
|         let adjustment = this._bodyScrollArea.vscroll.adjustment; | ||||
|         if (side == St.Side.TOP) | ||||
|             adjustment.value = adjustment.lower; | ||||
|         else if (side == St.Side.BOTTOM) | ||||
|             adjustment.value = adjustment.upper; | ||||
|     }, | ||||
|  | ||||
|     // setActionArea: | ||||
|     // @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 }); | ||||
|     setActionArea: function(actor) { | ||||
|         if (this._actionArea) | ||||
|             this._table.child_set(this._actionArea, { col: this._imageBin ? 2 : 1, | ||||
|                                                       col_span: this._imageBin ? 1 : 2 }); | ||||
|     }, | ||||
|             this._actionArea.destroy(); | ||||
|  | ||||
|     setImage: function(image) { | ||||
|         this.unsetImage(); | ||||
|  | ||||
|         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'); | ||||
|         } | ||||
|         this._actionArea = actor; | ||||
|         this._actionAreaBin.child = actor; | ||||
|         this._sync(); | ||||
|     }, | ||||
|  | ||||
|     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); | ||||
|         button.connect('clicked', Lang.bind(this, function() { | ||||
|             callback(); | ||||
|  | ||||
|             if (!this.resident) { | ||||
|                 // We don't hide a resident notification when the user invokes one of its actions, | ||||
|                 // because it is common for such notifications to update themselves with new | ||||
|                 // information based on the action. We'd like to display the updated information | ||||
|                 // in place, rather than pop-up a new notification. | ||||
|                 this.emit('done-displaying'); | ||||
|                 this.destroy(); | ||||
|             } | ||||
|             this.emit('done-displaying'); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|  | ||||
|         this.updated(); | ||||
|         this._sync(); | ||||
|         return button; | ||||
|     }, | ||||
|  | ||||
| @@ -870,8 +741,7 @@ const Notification = new Lang.Class({ | ||||
|     // the notification. | ||||
|     addAction: function(label, callback) { | ||||
|         let button = new St.Button({ style_class: 'notification-button', | ||||
|                                      label: label, | ||||
|                                      can_focus: true }); | ||||
|                                      x_expand: true, label: label, can_focus: true }); | ||||
|  | ||||
|         return this.addButton(button, callback); | ||||
|     }, | ||||
| @@ -880,10 +750,6 @@ const Notification = new Lang.Class({ | ||||
|         this.urgency = urgency; | ||||
|     }, | ||||
|  | ||||
|     setResident: function(resident) { | ||||
|         this.resident = resident; | ||||
|     }, | ||||
|  | ||||
|     setTransient: function(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() { | ||||
|         if (this._soundPlayed) | ||||
|             return; | ||||
| @@ -1050,83 +809,27 @@ const Notification = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     updated: function() { | ||||
|         if (this.expanded) | ||||
|             this.expand(false); | ||||
|     }, | ||||
|  | ||||
|     expand: function(animate) { | ||||
|         this.expanded = true; | ||||
|         this.actor.remove_style_class_name('notification-unexpanded'); | ||||
|  | ||||
|         // 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'); | ||||
|         this._sync(); | ||||
|     }, | ||||
|  | ||||
|     collapseCompleted: function() { | ||||
|         if (this._destroyed) | ||||
|             return; | ||||
|  | ||||
|         this.expanded = false; | ||||
|  | ||||
|         // 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'); | ||||
|         this._sync(); | ||||
|     }, | ||||
|  | ||||
|     _onClicked: function() { | ||||
|         this.emit('clicked'); | ||||
|         // We hide all types of notifications once the user clicks on them because the common | ||||
|         // outcome of clicking should be the relevant window being brought forward and the user's | ||||
|         // attention switching to the window. | ||||
|         this.emit('done-displaying'); | ||||
|         if (!this.resident) | ||||
|             this.destroy(); | ||||
|         this.destroy(); | ||||
|     }, | ||||
|  | ||||
|     _onCloseClicked: function() { | ||||
|         this.destroy(); | ||||
|     }, | ||||
|  | ||||
|     _onDestroy: function() { | ||||
| @@ -1294,7 +997,7 @@ const Source = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     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; | ||||
|     }, | ||||
|  | ||||
| @@ -1307,7 +1010,7 @@ const Source = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     get isClearable() { | ||||
|         return !this.trayIcon && !this.isChat && !this.resident; | ||||
|         return !this.trayIcon && !this.isChat; | ||||
|     }, | ||||
|  | ||||
|     countUpdated: function() { | ||||
| @@ -1451,10 +1154,9 @@ const Source = new Lang.Class({ | ||||
|     open: function() { | ||||
|     }, | ||||
|  | ||||
|     destroyNonResidentNotifications: function() { | ||||
|     destroyNotifications: function() { | ||||
|         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(); | ||||
|     }, | ||||
| @@ -1786,6 +1488,9 @@ const MessageTray = new Lang.Class({ | ||||
|                                                    layout_manager: new Clutter.BinLayout() }); | ||||
|         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::height', Lang.bind(this, function() { | ||||
|             this._notificationWidget.translation_y = -this._notificationWidget.height; | ||||
|         })); | ||||
|  | ||||
|         this._notificationBin = new St.Bin({ y_expand: true }); | ||||
|         this._notificationBin.set_y_align(Clutter.ActorAlign.START); | ||||
| @@ -1829,11 +1534,6 @@ const MessageTray = new Lang.Class({ | ||||
|         this._clickedSummaryItemMouseButton = -1; | ||||
|         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.idleMonitor = Meta.IdleMonitor.get_core(); | ||||
| @@ -1864,7 +1564,6 @@ const MessageTray = new Lang.Class({ | ||||
|         this._keyboardVisible = false; | ||||
|         this._notificationState = State.HIDDEN; | ||||
|         this._notificationTimeoutId = 0; | ||||
|         this._notificationExpandedId = 0; | ||||
|         this._summaryBoxPointerState = State.HIDDEN; | ||||
|         this._summaryBoxPointerTimeoutId = 0; | ||||
|         this._desktopCloneState = State.HIDDEN; | ||||
| @@ -1888,7 +1587,6 @@ const MessageTray = new Lang.Class({ | ||||
|         Main.layoutManager.trayBox.add_actor(this._notificationWidget); | ||||
|         Main.layoutManager.trackChrome(this.actor); | ||||
|         Main.layoutManager.trackChrome(this._notificationWidget); | ||||
|         Main.layoutManager.trackChrome(this._closeButton); | ||||
|  | ||||
|         global.screen.connect('in-fullscreen-changed', Lang.bind(this, this._updateState)); | ||||
|         Main.layoutManager.connect('hot-corners-changed', Lang.bind(this, this._hotCornersChanged)); | ||||
| @@ -2040,14 +1738,6 @@ const MessageTray = new Lang.Class({ | ||||
|         this._updateState(); | ||||
|     }, | ||||
|  | ||||
|     _closeNotification: function() { | ||||
|         if (this._notificationState == State.SHOWN) { | ||||
|             this._closeButton.hide(); | ||||
|             this._notification.emit('done-displaying'); | ||||
|             this._notification.destroy(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     contains: function(source) { | ||||
|         return this._sources.has(source); | ||||
|     }, | ||||
| @@ -2613,7 +2303,6 @@ const MessageTray = new Lang.Class({ | ||||
|         this._notificationBin.child = this._notification.actor; | ||||
|  | ||||
|         this._notificationWidget.opacity = 0; | ||||
|         this._notificationWidget.y = 0; | ||||
|         this._notificationWidget.show(); | ||||
|  | ||||
|         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 | ||||
|         // 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 | ||||
|         // notification is being shown. | ||||
|  | ||||
|         let tweenParams = { opacity: 255, | ||||
|                             y: -this._notificationWidget.height, | ||||
|                             time: ANIMATION_TIME, | ||||
|                             transition: 'easeOutQuad', | ||||
|                             onComplete: this._showNotificationCompleted, | ||||
|                             onCompleteScope: this | ||||
|                           }; | ||||
|  | ||||
|         this._tween(this._notificationWidget, '_notificationState', State.SHOWN, tweenParams); | ||||
|         this._tween(this._notificationWidget, '_notificationState', State.SHOWN, | ||||
|                     { opacity: 255, | ||||
|                       time: ANIMATION_TIME, | ||||
|                       transition: 'easeOutQuad', | ||||
|                       onComplete: this._showNotificationCompleted, | ||||
|                       onCompleteScope: this | ||||
|                     }); | ||||
|    }, | ||||
|  | ||||
|     _showNotificationCompleted: function() { | ||||
| @@ -2712,10 +2394,6 @@ const MessageTray = new Lang.Class({ | ||||
|     _hideNotification: function(animate) { | ||||
|         this._notificationFocusGrabber.ungrabFocus(); | ||||
|  | ||||
|         if (this._notificationExpandedId) { | ||||
|             this._notification.disconnect(this._notificationExpandedId); | ||||
|             this._notificationExpandedId = 0; | ||||
|         } | ||||
|         if (this._notificationClickedId) { | ||||
|             this._notification.disconnect(this._notificationClickedId); | ||||
|             this._notificationClickedId = 0; | ||||
| @@ -2729,8 +2407,7 @@ const MessageTray = new Lang.Class({ | ||||
|  | ||||
|         if (animate) { | ||||
|             this._tween(this._notificationWidget, '_notificationState', State.HIDDEN, | ||||
|                         { y: this.actor.height, | ||||
|                           opacity: 0, | ||||
|                         { opacity: 0, | ||||
|                           time: ANIMATION_TIME, | ||||
|                           transition: 'easeOutQuad', | ||||
|                           onComplete: this._hideNotificationCompleted, | ||||
| @@ -2738,7 +2415,6 @@ const MessageTray = new Lang.Class({ | ||||
|                         }); | ||||
|         } else { | ||||
|             Tweener.removeTweens(this._notificationWidget); | ||||
|             this._notificationWidget.y = this.actor.height; | ||||
|             this._notificationWidget.opacity = 0; | ||||
|             this._notificationState = State.HIDDEN; | ||||
|             this._hideNotificationCompleted(); | ||||
| @@ -2753,7 +2429,6 @@ const MessageTray = new Lang.Class({ | ||||
|         if (notification.isTransient) | ||||
|             notification.destroy(NotificationDestroyedReason.EXPIRED); | ||||
|  | ||||
|         this._closeButton.hide(); | ||||
|         this._pointerInNotification = false; | ||||
|         this._notificationRemoved = false; | ||||
|         this._notificationBin.child = null; | ||||
| @@ -2768,10 +2443,6 @@ const MessageTray = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _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. | ||||
|         this._notification.expand(!autoExpanding); | ||||
|  | ||||
| @@ -2780,31 +2451,6 @@ const MessageTray = new Lang.Class({ | ||||
|             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() { | ||||
|         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({ | ||||
|     Name: 'FdoNotificationDaemon', | ||||
|  | ||||
| @@ -334,13 +319,14 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _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'; | ||||
|  | ||||
|         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 }); | ||||
|             button.child = new St.Icon({ icon_name: iconName, icon_size: 16 }); | ||||
|         } else { | ||||
|             button.add_style_class_name('notification-button'); | ||||
|             button.label = label; | ||||
|         } | ||||
|         return button; | ||||
| @@ -379,8 +365,6 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|         let gicon = this._iconForNotificationData(icon, 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 | ||||
|         // 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 | ||||
| @@ -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 | ||||
|         // one of 'image-data' or 'image-path' are specified, we show both an icon and | ||||
|         // a large image. | ||||
|         if (gicon && gimage) | ||||
|             image = new St.Icon({ gicon: gimage, | ||||
|                                   icon_size: notification.IMAGE_SIZE }); | ||||
|         else if (!gicon && gimage) | ||||
|         if (!gicon && gimage) | ||||
|             gicon = gimage; | ||||
|         else if (!gicon) | ||||
|             gicon = this._fallbackIconForNotificationData(hints); | ||||
| @@ -402,7 +383,6 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|                                              clear: true, | ||||
|                                              soundFile: hints['sound-file'], | ||||
|                                              soundName: hints['sound-name'] }); | ||||
|         notification.setImage(image); | ||||
|  | ||||
|         let hasDefaultAction = false; | ||||
|  | ||||
| @@ -442,7 +422,6 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|                 notification.setUrgency(MessageTray.Urgency.CRITICAL); | ||||
|                 break; | ||||
|         } | ||||
|         notification.setResident(hints.resident == true); | ||||
|         // 'transient' is a reserved keyword in JS, so we have to retrieve the value | ||||
|         // of the 'transient' hint with hints['transient'] rather than hints.transient | ||||
|         notification.setTransient(hints['transient'] == true); | ||||
| @@ -470,7 +449,6 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|             'body-markup', | ||||
|             // 'icon-multi', | ||||
|             'icon-static', | ||||
|             'persistence', | ||||
|             'sound', | ||||
|         ]; | ||||
|     }, | ||||
| @@ -492,7 +470,7 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|         for (let i = 0; i < this._sources.length; i++) { | ||||
|             let source = this._sources[i]; | ||||
|             if (source.app == tracker.focus_app) { | ||||
|                 source.destroyNonResidentNotifications(); | ||||
|                 source.destroyNotifications(); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
| @@ -509,10 +487,6 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _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); | ||||
|     }, | ||||
|  | ||||
| @@ -584,7 +558,7 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|             this.iconUpdated(); | ||||
|  | ||||
|         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); | ||||
|         else | ||||
|             this.notify(notification); | ||||
| @@ -651,7 +625,7 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|  | ||||
|     open: function() { | ||||
|         this.openApp(); | ||||
|         this.destroyNonResidentNotifications(); | ||||
|         this.destroyNotifications(); | ||||
|     }, | ||||
|  | ||||
|     _lastNotificationRemoved: function() { | ||||
|   | ||||
| @@ -21,10 +21,6 @@ struct _ShellTpClientPrivate | ||||
|   ShellTpClientHandleChannelsImpl handle_channels_impl; | ||||
|   gpointer user_data_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. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * 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 | ||||
| shell_tp_client_init (ShellTpClient *self) | ||||
| { | ||||
| @@ -226,13 +212,6 @@ shell_tp_client_dispose (GObject *object) | ||||
|       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) | ||||
|     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->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, | ||||
|     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 | ||||
| #endif /* __SHELL_TP_CLIENT_H__ */ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user