messageTray: Implement new notification designs
Rather than use an StTable, a custom ShellGenericContainer, and plenty of hacky style classes, replace them all with standard BoxLayouts and Bins. Remove the customContent parameter in favor of subclasses setting the child of this._bodyBin instead. With this comes a whole new notification implementation to implement the new notification designs. We lose a few of the fancy features like showing the first part of the body, ellipsized, next the banner when it will fit, and some other layout logic. But since the design of notifications is changing substantially anyways, I don't feel too bad...
This commit is contained in:
parent
ae74dbd1bb
commit
fa350bf41e
@ -414,8 +414,6 @@ StScrollBar StButton#vhandle:active {
|
|||||||
/* Buttons */
|
/* Buttons */
|
||||||
|
|
||||||
.candidate-page-button,
|
.candidate-page-button,
|
||||||
.notification-button,
|
|
||||||
.notification-icon-button,
|
|
||||||
.hotplug-notification-item,
|
.hotplug-notification-item,
|
||||||
.modal-dialog-button,
|
.modal-dialog-button,
|
||||||
.app-view-control {
|
.app-view-control {
|
||||||
@ -430,16 +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,
|
||||||
.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 {
|
||||||
@ -453,8 +447,6 @@ 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,
|
||||||
.modal-dialog-button:active,
|
.modal-dialog-button:active,
|
||||||
.modal-dialog-button:pressed,
|
.modal-dialog-button:pressed,
|
||||||
@ -464,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;
|
||||||
@ -477,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 {
|
||||||
@ -1532,28 +1521,63 @@ 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;
|
.notification-main-content {
|
||||||
|
padding: 8px;
|
||||||
|
spacing: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
||||||
|
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 {
|
||||||
@ -1595,47 +1619,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;
|
||||||
}
|
}
|
||||||
@ -1697,7 +1680,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2616,8 +2603,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,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;
|
||||||
|
|
||||||
|
@ -366,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() {
|
||||||
@ -558,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>';
|
||||||
@ -585,7 +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._responseEntry = new St.Entry({ style_class: 'chat-response',
|
this._responseEntry = new St.Entry({ style_class: 'chat-response',
|
||||||
can_focus: true });
|
can_focus: true });
|
||||||
@ -601,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);
|
||||||
@ -646,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 ?
|
||||||
@ -684,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)
|
||||||
@ -716,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;
|
||||||
|
|
||||||
@ -730,12 +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);
|
||||||
|
|
||||||
let timestamp = props.timestamp;
|
let timestamp = props.timestamp;
|
||||||
this._history.unshift({ actor: body, time: timestamp,
|
this._history.unshift({ actor: body, time: timestamp,
|
||||||
@ -870,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();
|
||||||
},
|
},
|
||||||
|
@ -233,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;
|
||||||
@ -439,23 +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(). The
|
// You can also add buttons to the notification with addButton(),
|
||||||
// notification content is put inside a scrollview, so if it gets too
|
// and you can construct simple default buttons with addAction().
|
||||||
// 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
|
||||||
@ -471,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
|
||||||
@ -480,7 +470,7 @@ 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,
|
||||||
|
|
||||||
_init: function(source, title, banner, params) {
|
_init: function(source, title, banner, params) {
|
||||||
this.source = source;
|
this.source = source;
|
||||||
@ -494,49 +484,80 @@ 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._spacing = 0;
|
|
||||||
this._scrollPolicy = Gtk.PolicyType.AUTOMATIC;
|
|
||||||
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
|
||||||
|
// .----|--------|---------------|-----------.
|
||||||
|
// | .----. | .----.-----------------------. |
|
||||||
|
// | | | | | | |--- 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 and title/body
|
||||||
// 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._titleLabel = new St.Label();
|
this._iconBin = new St.Bin({ y_align: St.Align.START });
|
||||||
this._bannerBox.add_actor(this._titleLabel);
|
this._hbox.add_child(this._iconBin);
|
||||||
this._bannerUrlHighlighter = new URLHighlighter();
|
|
||||||
this._bannerLabel = this._bannerUrlHighlighter.actor;
|
this._titleBodyBox = new St.BoxLayout({ style_class: 'notification-title-body-box',
|
||||||
this._bannerBox.add_actor(this._bannerLabel);
|
vertical: true });
|
||||||
|
this._hbox.add_child(this._titleBodyBox);
|
||||||
|
|
||||||
|
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
|
||||||
@ -544,6 +565,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:
|
||||||
@ -555,50 +601,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 (!this._scrollArea && !this._actionArea)
|
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,
|
||||||
@ -607,19 +634,18 @@ 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;
|
||||||
@ -638,24 +664,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(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) {
|
||||||
@ -664,56 +675,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._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();
|
|
||||||
},
|
|
||||||
|
|
||||||
_addBannerBody: function() {
|
|
||||||
if (this.bannerBodyText && !this._bannerBodyAdded) {
|
|
||||||
let label = new URLHighlighter(this.bannerBodyText, true, this.bannerBodyMarkup);
|
|
||||||
this.addActor(label.actor);
|
|
||||||
this._bannerBodyAdded = true;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// scrollTo:
|
// scrollTo:
|
||||||
@ -721,53 +694,23 @@ 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
|
if (this._actionArea)
|
||||||
// @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.destroy();
|
||||||
this._actionArea = null;
|
|
||||||
if (this._buttonBox)
|
|
||||||
this._buttonBox = null;
|
|
||||||
} else {
|
|
||||||
this._addBannerBody();
|
|
||||||
}
|
|
||||||
this._actionArea = actor;
|
this._actionArea = actor;
|
||||||
this._actionArea.visible = this.expanded;
|
this._actionAreaBin.child = actor;
|
||||||
|
this._sync();
|
||||||
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.updated();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
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();
|
||||||
@ -776,7 +719,7 @@ const Notification = new Lang.Class({
|
|||||||
this.destroy();
|
this.destroy();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.updated();
|
this._sync();
|
||||||
return button;
|
return button;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -789,8 +732,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);
|
||||||
},
|
},
|
||||||
@ -826,113 +768,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;
|
||||||
@ -965,69 +800,18 @@ 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._actionArea)
|
|
||||||
this._actionArea.show();
|
|
||||||
if (this._scrollArea)
|
|
||||||
this._scrollArea.show();
|
|
||||||
|
|
||||||
// The banner is never shown when the title did not fit, so this
|
|
||||||
// can be an if-else statement.
|
|
||||||
if (!this._titleFitsInBannerMode) {
|
|
||||||
// Remove ellipsization from the title label and make it wrap so that
|
|
||||||
// we show the full title when the notification is expanded.
|
|
||||||
this._titleLabel.clutter_text.line_wrap = true;
|
|
||||||
this._titleLabel.clutter_text.line_wrap_mode = Pango.WrapMode.WORD_CHAR;
|
|
||||||
this._titleLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
|
||||||
} else if (this._table.row_count > 1 && this._bannerLabel.opacity != 0) {
|
|
||||||
// We always hide the banner if the notification has additional content.
|
|
||||||
//
|
|
||||||
// We don't need to wrap the banner that doesn't fit the way we wrap the
|
|
||||||
// title that doesn't fit because we won't have a notification with
|
|
||||||
// row_count=1 that has a banner that doesn't fully fit. We'll either add
|
|
||||||
// that banner to the content of the notification in _bannerBoxAllocate()
|
|
||||||
// or the notification will have custom content.
|
|
||||||
if (animate)
|
|
||||||
Tweener.addTween(this._bannerLabel,
|
|
||||||
{ opacity: 0,
|
|
||||||
time: ANIMATION_TIME,
|
|
||||||
transition: 'easeOutQuad' });
|
|
||||||
else
|
|
||||||
this._bannerLabel.opacity = 0;
|
|
||||||
}
|
|
||||||
this.emit('expanded');
|
this.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._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() {
|
||||||
|
@ -319,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;
|
||||||
|
Loading…
Reference in New Issue
Block a user