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