messageTray: fix handling of markup vs non-markup notifications

NotificationDaemon-based notifications have markup in the banner/body,
but Telepathy-based notifications don't. (Eg, an XMPP message
containing "<b>foo</b>" should show up angle brackets and all, not as
bold.) Fix MessageTray.Notification to allow explicitly specifying
where there should and shouldn't be markup, and use that
appropriately.

https://bugzilla.gnome.org/show_bug.cgi?id=610219
This commit is contained in:
Dan Winship 2010-11-24 02:31:55 +03:00 committed by Maxim Ermilov
parent 65f0b483f8
commit d6f1c10b1b
2 changed files with 43 additions and 23 deletions

View File

@ -38,21 +38,27 @@ const State = {
HIDING: 3 HIDING: 3
}; };
function _cleanMarkup(text) { function _fixMarkup(text, allowMarkup) {
if (allowMarkup) {
// Support &amp;, &quot;, &apos;, &lt; and &gt;, escape all other // Support &amp;, &quot;, &apos;, &lt; and &gt;, escape all other
// occurrences of '&'. // occurrences of '&'.
let _text = text.replace(/&(?!amp;|quot;|apos;|lt;|gt;)/g, '&amp;'); let _text = text.replace(/&(?!amp;|quot;|apos;|lt;|gt;)/g, '&amp;');
// Support <b>, <i>, and <u>, escape anything else // Support <b>, <i>, and <u>, escape anything else
// so it displays as raw markup. // so it displays as raw markup.
return _text.replace(/<(\/?[^biu]>|[^>\/][^>])/g, '&lt;$1'); return _text.replace(/<(\/?[^biu]>|[^>\/][^>])/g, '&lt;$1');
} else {
// Escape everything
let _text = text.replace(/&/g, '&amp;');
return _text.replace(/</g, '&lt;');
}
} }
function URLHighlighter(text, lineWrap) { function URLHighlighter(text, lineWrap, allowMarkup) {
this._init(text, lineWrap); this._init(text, lineWrap, allowMarkup);
} }
URLHighlighter.prototype = { URLHighlighter.prototype = {
_init: function(text, lineWrap) { _init: function(text, lineWrap, allowMarkup) {
if (!text) if (!text)
text = ''; text = '';
this.actor = new St.Label({ reactive: true, style_class: 'url-highlighter' }); this.actor = new St.Label({ reactive: true, style_class: 'url-highlighter' });
@ -74,7 +80,7 @@ URLHighlighter.prototype = {
this.actor.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; this.actor.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
} }
this.setMarkup(text); this.setMarkup(text, allowMarkup);
this.actor.connect('button-release-event', Lang.bind(this, function (actor, event) { this.actor.connect('button-release-event', Lang.bind(this, function (actor, event) {
let urlId = this._findUrlAtPos(event); let urlId = this._findUrlAtPos(event);
if (urlId != -1) { if (urlId != -1) {
@ -112,8 +118,8 @@ URLHighlighter.prototype = {
})); }));
}, },
setMarkup: function(text) { setMarkup: function(text, allowMarkup) {
text = text ? _cleanMarkup(text) : ''; text = text ? _fixMarkup(text, allowMarkup) : '';
this._text = text; this._text = text;
this.actor.clutter_text.set_markup(text); this.actor.clutter_text.set_markup(text);
@ -187,7 +193,8 @@ URLHighlighter.prototype = {
// area. // area.
// //
// @params can contain values for 'customContent', 'body', 'icon', // @params can contain values for 'customContent', 'body', 'icon',
// and 'clear' parameters. // 'titleMarkup', 'bannerMarkup', 'bodyMarkup', and 'clear'
// parameters.
// //
// If @params contains a 'customContent' parameter with the value %true, // If @params contains a 'customContent' parameter with the value %true,
// then @banner will not be shown in the body of the notification when the // then @banner will not be shown in the body of the notification when the
@ -201,6 +208,12 @@ URLHighlighter.prototype = {
// source.createNotificationIcon(). However, if @params contains an 'icon' // source.createNotificationIcon(). However, if @params contains an 'icon'
// parameter, the passed in icon will be used. // parameter, the passed in icon will be used.
// //
// If @params contains a 'titleMarkup', 'bannerMarkup', or
// 'bodyMarkup' parameter with the value %true, then the corresponding
// element is assumed to use pango markup. If the parameter is not
// present for an element, then anything that looks like markup in
// that element will appear literally in the output.
//
// 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 // The content area is also always cleared if 'customContent' is false
@ -217,6 +230,7 @@ Notification.prototype = {
this._useActionIcons = false; this._useActionIcons = false;
this._customContent = false; this._customContent = false;
this._bannerBodyText = null; this._bannerBodyText = null;
this._bannerBodyMarkup = false;
this._titleFitsInBannerMode = true; this._titleFitsInBannerMode = true;
this._spacing = 0; this._spacing = 0;
@ -290,6 +304,9 @@ Notification.prototype = {
params = Params.parse(params, { customContent: false, params = Params.parse(params, { customContent: false,
body: null, body: null,
icon: null, icon: null,
titleMarkup: false,
bannerMarkup: false,
bodyMarkup: false,
clear: false }); clear: false });
this._customContent = params.customContent; this._customContent = params.customContent;
@ -320,7 +337,7 @@ Notification.prototype = {
y_fill: false, y_fill: false,
y_align: St.Align.START }); y_align: St.Align.START });
title = title ? _cleanMarkup(title.replace(/\n/g, ' ')) : ''; title = title ? _fixMarkup(title.replace(/\n/g, ' '), params.titleMarkup) : '';
this._titleLabel.clutter_text.set_markup('<b>' + title + '</b>'); this._titleLabel.clutter_text.set_markup('<b>' + title + '</b>');
// Unless the notification has custom content, we save this._bannerBodyText // Unless the notification has custom content, we save this._bannerBodyText
@ -328,10 +345,11 @@ Notification.prototype = {
// expandable due to other elements in its content area or due to the banner // expandable due to other elements in its content area or due to the banner
// not fitting fully in the single-line mode. // not fitting fully in the single-line mode.
this._bannerBodyText = this._customContent ? null : banner; this._bannerBodyText = this._customContent ? null : banner;
this._bannerBodyMarkup = params.bannerMarkup;
banner = banner ? banner.replace(/\n/g, ' ') : ''; banner = banner ? banner.replace(/\n/g, ' ') : '';
this._bannerUrlHighlighter.setMarkup(banner); this._bannerUrlHighlighter.setMarkup(banner, params.bannerMarkup);
this._bannerLabel.queue_relayout(); this._bannerLabel.queue_relayout();
// Add the bannerBody now if we know for sure we'll need it // Add the bannerBody now if we know for sure we'll need it
@ -339,7 +357,7 @@ Notification.prototype = {
this._addBannerBody(); this._addBannerBody();
if (params.body) if (params.body)
this.addBody(params.body); this.addBody(params.body, params.bodyMarkup);
this._updated(); this._updated();
}, },
@ -370,12 +388,13 @@ Notification.prototype = {
// addBody: // addBody:
// @text: the text // @text: the text
// @markup: %true if @text contains pango markup
// //
// Adds a multi-line label containing @text to the notification. // Adds a multi-line label containing @text to the notification.
// //
// Return value: the newly-added label // Return value: the newly-added label
addBody: function(text) { addBody: function(text, markup) {
let label = new URLHighlighter(text, true); let label = new URLHighlighter(text, true, markup);
this.addActor(label.actor); this.addActor(label.actor);
return label.actor; return label.actor;
@ -385,7 +404,7 @@ Notification.prototype = {
if (this._bannerBodyText) { if (this._bannerBodyText) {
let text = this._bannerBodyText; let text = this._bannerBodyText;
this._bannerBodyText = null; this._bannerBodyText = null;
this.addBody(text); this.addBody(text, this._bannerBodyMarkup);
} }
}, },

View File

@ -205,8 +205,6 @@ NotificationDaemon.prototype = {
return id; return id;
} }
summary = GLib.markup_escape_text(summary, -1);
let rewrites = rewriteRules[appName]; let rewrites = rewriteRules[appName];
if (rewrites) { if (rewrites) {
for (let i = 0; i < rewrites.length; i++) { for (let i = 0; i < rewrites.length; i++) {
@ -281,7 +279,9 @@ NotificationDaemon.prototype = {
let iconActor = this._iconForNotificationData(icon, hints, source.ICON_SIZE); let iconActor = this._iconForNotificationData(icon, hints, source.ICON_SIZE);
if (notification == null) { if (notification == null) {
notification = new MessageTray.Notification(source, summary, body, { icon: iconActor }); notification = new MessageTray.Notification(source, summary, body,
{ icon: iconActor,
bannerMarkup: true });
ndata.notification = notification; ndata.notification = notification;
notification.connect('clicked', Lang.bind(this, notification.connect('clicked', Lang.bind(this,
function(n) { function(n) {
@ -294,6 +294,7 @@ NotificationDaemon.prototype = {
notification.connect('action-invoked', Lang.bind(this, this._actionInvoked, source, id)); notification.connect('action-invoked', Lang.bind(this, this._actionInvoked, source, id));
} else { } else {
notification.update(summary, body, { icon: iconActor, notification.update(summary, body, { icon: iconActor,
bannerMarkup: true,
clear: true }); clear: true });
} }