Fix HTML escaping for custom OSD notifications

Fixes #618
This commit is contained in:
Jonas Kvinge 2020-12-11 23:59:38 +01:00
parent 9149d1baa3
commit 497952611d
15 changed files with 219 additions and 112 deletions

View File

@ -486,7 +486,7 @@ void ContextView::UpdateFonts() {
void ContextView::SetSong() { void ContextView::SetSong() {
label_top_->setStyleSheet(QString("font: %2pt \"%1\"; font-weight: regular;").arg(font_headline_).arg(font_size_headline_)); label_top_->setStyleSheet(QString("font: %2pt \"%1\"; font-weight: regular;").arg(font_headline_).arg(font_size_headline_));
label_top_->setText(QString("<b>%1</b><br/>%2").arg(Utilities::ReplaceMessage(title_fmt_, song_playing_, "<br/>"), Utilities::ReplaceMessage(summary_fmt_, song_playing_, "<br/>"))); label_top_->setText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song_playing_, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song_playing_, "<br />", true)));
label_stop_summary_->clear(); label_stop_summary_->clear();
@ -644,7 +644,7 @@ void ContextView::SetSong() {
void ContextView::UpdateSong(const Song &song) { void ContextView::UpdateSong(const Song &song) {
label_top_->setText(QString("<b>%1</b><br/>%2").arg(Utilities::ReplaceMessage(title_fmt_, song, "<br/>"), Utilities::ReplaceMessage(summary_fmt_, song, "<br/>"))); label_top_->setText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song, "<br />", true)));
if (action_show_data_->isChecked()) { if (action_show_data_->isChecked()) {
if (song.filetype() != song_playing_.filetype()) label_filetype_->setText(song.TextForFiletype()); if (song.filetype() != song_playing_.filetype()) label_filetype_->setText(song.TextForFiletype());

View File

@ -1427,6 +1427,14 @@ QString Song::PrettyYear() const {
} }
QString Song::PrettyOriginalYear() const {
if (effective_originalyear() == -1) return QString();
return QString::number(effective_originalyear());
}
QString Song::TitleWithCompilationArtist() const { QString Song::TitleWithCompilationArtist() const {
QString title(d->title_); QString title(d->title_);

View File

@ -282,6 +282,7 @@ class Song {
QString PrettyTitleWithArtist() const; QString PrettyTitleWithArtist() const;
QString PrettyLength() const; QString PrettyLength() const;
QString PrettyYear() const; QString PrettyYear() const;
QString PrettyOriginalYear() const;
QString TitleWithCompilationArtist() const; QString TitleWithCompilationArtist() const;

View File

@ -924,7 +924,7 @@ QString MacAddress() {
} }
QString ReplaceMessage(const QString &message, const Song &song, const QString &newline) { QString ReplaceMessage(const QString &message, const Song &song, const QString &newline, const bool html_escaped) {
QRegularExpression variable_replacer("[%][a-z]+[%]"); QRegularExpression variable_replacer("[%][a-z]+[%]");
QString copy(message); QString copy(message);
@ -935,7 +935,7 @@ QString ReplaceMessage(const QString &message, const Song &song, const QString &
for (match = variable_replacer.match(message, pos) ; match.hasMatch() ; match = variable_replacer.match(message, pos)) { for (match = variable_replacer.match(message, pos) ; match.hasMatch() ; match = variable_replacer.match(message, pos)) {
pos = match.capturedStart(); pos = match.capturedStart();
QStringList captured = match.capturedTexts(); QStringList captured = match.capturedTexts();
copy.replace(captured[0], ReplaceVariable(captured[0], song, newline)); copy.replace(captured[0], ReplaceVariable(captured[0], song, newline, html_escaped));
pos += match.capturedLength(); pos += match.capturedLength();
} }
@ -943,62 +943,76 @@ QString ReplaceMessage(const QString &message, const Song &song, const QString &
if (index_of >= 0) copy = copy.remove(index_of, 3); if (index_of >= 0) copy = copy.remove(index_of, 3);
return copy; return copy;
} }
QString ReplaceVariable(const QString &variable, const Song &song, const QString &newline) { QString ReplaceVariable(const QString &variable, const Song &song, const QString &newline, const bool html_escaped) {
QString return_value; QString value = variable;
if (variable == "%artist%") {
return song.artist().toHtmlEscaped(); if (variable == "%title%") {
value = song.PrettyTitle();
} }
else if (variable == "%album%") { else if (variable == "%album%") {
return song.album().toHtmlEscaped(); value = song.album();
} }
else if (variable == "%title%") { else if (variable == "%artist%") {
return song.PrettyTitle().toHtmlEscaped(); value = song.artist();
} }
else if (variable == "%albumartist%") { else if (variable == "%albumartist%") {
return song.effective_albumartist().toHtmlEscaped(); value = song.effective_albumartist();
}
else if (variable == "%year%") {
return song.PrettyYear().toHtmlEscaped();
}
else if (variable == "%composer%") {
return song.composer().toHtmlEscaped();
}
else if (variable == "%performer%") {
return song.performer().toHtmlEscaped();
}
else if (variable == "%grouping%") {
return song.grouping().toHtmlEscaped();
}
else if (variable == "%length%") {
return song.PrettyLength().toHtmlEscaped();
}
else if (variable == "%disc%") {
return return_value.setNum(song.disc()).toHtmlEscaped();
} }
else if (variable == "%track%") { else if (variable == "%track%") {
return return_value.setNum(song.track()).toHtmlEscaped(); value.setNum(song.track());
}
else if (variable == "%disc%") {
value.setNum(song.disc());
}
else if (variable == "%year%") {
value = song.PrettyYear();
}
else if (variable == "%originalyear%") {
value = song.PrettyOriginalYear();
} }
else if (variable == "%genre%") { else if (variable == "%genre%") {
return song.genre().toHtmlEscaped(); value = song.genre();
} }
else if (variable == "%playcount%") { else if (variable == "%composer%") {
return return_value.setNum(song.playcount()).toHtmlEscaped(); value = song.composer();
} }
else if (variable == "%skipcount%") { else if (variable == "%performer%") {
return return_value.setNum(song.skipcount()).toHtmlEscaped(); value = song.performer();
}
else if (variable == "%grouping%") {
value = song.grouping();
}
else if (variable == "%length%") {
value = song.PrettyLength();
} }
else if (variable == "%filename%") { else if (variable == "%filename%") {
return song.basefilename().toHtmlEscaped(); value = song.basefilename();
}
else if (variable == "%url%") {
value = song.url().toString();
}
else if (variable == "%playcount%") {
value.setNum(song.playcount());
}
else if (variable == "%skipcount%") {
value.setNum(song.skipcount());
}
else if (variable == "%rating%") {
value = song.PrettyRating();
} }
else if (variable == "%newline%") { else if (variable == "%newline%") {
return QString(newline); return QString(newline); // No HTML escaping, return immediately.
} }
//if the variable is not recognized, just return it if (html_escaped) {
return variable; value = value.toHtmlEscaped();
}
return value;
} }
bool IsColorDark(const QColor &color) { bool IsColorDark(const QColor &color) {

View File

@ -147,8 +147,8 @@ QString UnicodeToAscii(const QString &unicode);
QString MacAddress(); QString MacAddress();
QString ReplaceMessage(const QString &message, const Song &song, const QString &newline); QString ReplaceMessage(const QString &message, const Song &song, const QString &newline, const bool html_escaped = false);
QString ReplaceVariable(const QString &variable, const Song &song, const QString &newline); QString ReplaceVariable(const QString &variable, const Song &song, const QString &newline, const bool html_escaped = false);
bool IsColorDark(const QColor &color); bool IsColorDark(const QColor &color);

View File

@ -279,10 +279,10 @@ int main(int argc, char* argv[]) {
// Create the tray icon and OSD // Create the tray icon and OSD
std::unique_ptr<SystemTrayIcon> tray_icon(SystemTrayIcon::CreateSystemTrayIcon()); std::unique_ptr<SystemTrayIcon> tray_icon(SystemTrayIcon::CreateSystemTrayIcon());
#ifdef HAVE_DBUS #if defined(Q_OS_MACOS)
OSDDBus osd(tray_icon.get(), &app);
#elif defined(Q_OS_MACOS)
OSDMac osd(tray_icon.get(), &app); OSDMac osd(tray_icon.get(), &app);
#elif defined(HAVE_DBUS)
OSDDBus osd(tray_icon.get(), &app);
#else #else
OSDBase osd(tray_icon.get(), &app); OSDBase osd(tray_icon.get(), &app);
#endif #endif

View File

@ -130,23 +130,38 @@ void OSDBase::ShowPlaying(const Song &song, const QUrl &cover_url, const QImage
QStringList message_parts; QStringList message_parts;
QString summary; QString summary;
bool html_escaped = false;
if (use_custom_text_) { if (use_custom_text_) {
summary = ReplaceMessage(custom_text1_, song); summary = ReplaceMessage(Type_Summary, custom_text1_, song);
message_parts << ReplaceMessage(custom_text2_, song); message_parts << ReplaceMessage(Type_Message, custom_text2_, song);
} }
else { else {
summary = song.PrettyTitle(); summary = song.PrettyTitle();
if (!song.artist().isEmpty()) if (!song.artist().isEmpty()) {
summary = QString("%1 - %2").arg(song.artist(), summary); summary = QString("%1 - %2").arg(song.artist(), summary);
if (!song.album().isEmpty()) }
if (!song.album().isEmpty()) {
message_parts << song.album(); message_parts << song.album();
if (song.disc() > 0) }
if (song.disc() > 0) {
message_parts << tr("disc %1").arg(song.disc()); message_parts << tr("disc %1").arg(song.disc());
if (song.track() > 0) }
if (song.track() > 0) {
message_parts << tr("track %1").arg(song.track()); message_parts << tr("track %1").arg(song.track());
}
if (behaviour_ == Pretty) {
summary = summary.toHtmlEscaped();
html_escaped = true;
}
#if defined(HAVE_DBUS) && !defined(Q_OS_MACOS)
else if (behaviour_ == Native) {
html_escaped = true;
}
#endif
} }
QString message = message_parts.join(", "); QString message = message_parts.join(", ");
if (html_escaped) message = message.toHtmlEscaped();
if (show_art_) { if (show_art_) {
ShowMessage(summary, message, "notification-audio-play", image); ShowMessage(summary, message, "notification-audio-play", image);
@ -172,7 +187,7 @@ void OSDBase::Paused() {
if (show_on_pause_) { if (show_on_pause_) {
QString summary; QString summary;
if (use_custom_text_) { if (use_custom_text_) {
summary = ReplaceMessage(custom_text1_, last_song_); summary = ReplaceMessage(Type_Summary, custom_text1_, last_song_);
} }
else { else {
summary = last_song_.PrettyTitle(); summary = last_song_.PrettyTitle();
@ -180,8 +195,10 @@ void OSDBase::Paused() {
summary.prepend(" - "); summary.prepend(" - ");
summary.prepend(last_song_.artist()); summary.prepend(last_song_.artist());
} }
if (behaviour_ == Pretty) {
summary = summary.toHtmlEscaped();
}
} }
summary = summary.remove('&').simplified();
if (show_art_) { if (show_art_) {
ShowMessage(summary, tr("Paused"), QString(), last_image_); ShowMessage(summary, tr("Paused"), QString(), last_image_);
} }
@ -215,7 +232,7 @@ void OSDBase::Stopped() {
QString summary; QString summary;
if (use_custom_text_) { if (use_custom_text_) {
summary = ReplaceMessage(custom_text1_, last_song_); summary = ReplaceMessage(Type_Summary, custom_text1_, last_song_);
} }
else { else {
summary = last_song_.PrettyTitle(); summary = last_song_.PrettyTitle();
@ -223,10 +240,11 @@ void OSDBase::Stopped() {
summary.prepend(" - "); summary.prepend(" - ");
summary.prepend(last_song_.artist()); summary.prepend(last_song_.artist());
} }
if (behaviour_ == Pretty) {
summary = summary.toHtmlEscaped();
}
} }
summary = summary.remove('&').simplified();
if (show_art_) { if (show_art_) {
ShowMessage(summary, tr("Stopped"), QString(), last_image_); ShowMessage(summary, tr("Stopped"), QString(), last_image_);
} }
@ -257,7 +275,17 @@ void OSDBase::VolumeChanged(int value) {
if (!show_on_volume_change_) return; if (!show_on_volume_change_) return;
ShowMessage(app_name_, tr("Volume %1%").arg(value)); QString message = tr("Volume %1%").arg(value);
if (behaviour_ == Pretty) {
message = message.toHtmlEscaped();
}
#if defined(HAVE_DBUS) && !defined(Q_OS_MACOS)
else if (behaviour_ == Native) {
message = message.toHtmlEscaped();
}
#endif
ShowMessage(app_name_, message);
} }
@ -330,39 +358,51 @@ void OSDBase::RepeatModeChanged(PlaylistSequence::RepeatMode mode) {
} }
QString OSDBase::ReplaceMessage(const QString &message, const Song &song) { QString OSDBase::ReplaceMessage(const MessageType type, const QString &message, const Song &song) {
QString newline = "<br/>"; bool html_escaped = false;
QString newline = "";
if (message.indexOf("%newline%") != -1) { // We need different strings depending on notification type
// We need different strings depending on notification type switch (behaviour_) {
switch (behaviour_) { case Native:
case Native: #if defined(Q_OS_MACOS)
#ifdef Q_OS_MACOS html_escaped = false;
newline = "\n"; newline = "\n";
break; break;
#elif defined(HAVE_DBUS)
switch (type) {
case Type_Summary:{
html_escaped = false;
newline = "";
break;
}
case Type_Message: {
html_escaped = true;
newline = "<br />";
break;
}
}
break;
#else
// Other OSes doesn't support native notifications.
qLog(Debug) << "Native notifications are not supported on this OS.";
break;
#endif #endif
#ifdef Q_OS_LINUX case TrayPopup:
break; qLog(Debug) << "New line not supported by this notification type.";
#endif html_escaped = false;
#ifdef Q_OS_WIN32 newline = "";
// Other OS don't support native notifications break;
qLog(Debug) << "New line not supported by this notification type under Windows"; case Disabled: // When notifications are disabled, we force the PrettyOSD
newline = ""; case Pretty:
break; html_escaped = true;
#endif newline = "<br />";
case TrayPopup: break;
qLog(Debug) << "New line not supported by this notification type";
newline = "";
break;
case Pretty:
default:
// When notifications are disabled, we force the PrettyOSD
break;
}
} }
return Utilities::ReplaceMessage(message, song, newline); return Utilities::ReplaceMessage(message, song, newline, html_escaped);
} }
void OSDBase::ShowPreview(const Behaviour type, const QString &line1, const QString &line2, const Song &song) { void OSDBase::ShowPreview(const Behaviour type, const QString &line1, const QString &line2, const Song &song) {
@ -390,5 +430,5 @@ bool OSDBase::SupportsTrayPopups() {
} }
void OSDBase::ShowMessageNative(const QString&, const QString&, const QString&, const QImage&) { void OSDBase::ShowMessageNative(const QString&, const QString&, const QString&, const QImage&) {
qLog(Warning) << "Not implemented"; qLog(Warning) << "Native notifications are not supported on this OS.";
} }

View File

@ -83,9 +83,13 @@ class OSDBase : public QObject {
void ShowPreview(const Behaviour type, const QString &line1, const QString &line2, const Song &song); void ShowPreview(const Behaviour type, const QString &line1, const QString &line2, const Song &song);
private: private:
enum MessageType {
Type_Summary,
Type_Message,
};
void ShowPlaying(const Song &song, const QUrl &cover_url, const QImage &image, const bool preview = false); void ShowPlaying(const Song &song, const QUrl &cover_url, const QImage &image, const bool preview = false);
void ShowMessage(const QString &summary, const QString &message = QString(), const QString icon = QString("strawberry"), const QImage &image = QImage()); void ShowMessage(const QString &summary, const QString &message = QString(), const QString icon = QString("strawberry"), const QImage &image = QImage());
QString ReplaceMessage(const QString &message, const Song &song); QString ReplaceMessage(const MessageType type, const QString &message, const Song &song);
virtual void ShowMessageNative(const QString &summary, const QString &message, const QString &icon = QString(), const QImage &image = QImage()); virtual void ShowMessageNative(const QString &summary, const QString &message, const QString &icon = QString(), const QImage &image = QImage());
private slots: private slots:

View File

@ -144,7 +144,6 @@ void OSDDBus::ShowMessageNative(const QString &summary, const QString &message,
QVariantMap hints; QVariantMap hints;
QString summary_stripped = summary; QString summary_stripped = summary;
summary_stripped = summary_stripped.remove(QRegularExpression("[&\"<>]")).simplified(); summary_stripped = summary_stripped.remove(QRegularExpression("[&\"<>]")).simplified();
QString message_stripped = message.toHtmlEscaped();
if (!image.isNull()) { if (!image.isNull()) {
if (version_ >= QVersionNumber(1, 2)) { if (version_ >= QVersionNumber(1, 2)) {
@ -167,7 +166,7 @@ void OSDDBus::ShowMessageNative(const QString &summary, const QString &message,
id = notification_id_; id = notification_id_;
} }
QDBusPendingReply<uint> reply = interface_->Notify(app_name(), id, icon, summary_stripped, message_stripped, QStringList(), hints, timeout_msec()); QDBusPendingReply<uint> reply = interface_->Notify(app_name(), id, icon, summary_stripped, message, QStringList(), hints, timeout_msec());
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(CallFinished(QDBusPendingCallWatcher*))); connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(CallFinished(QDBusPendingCallWatcher*)));

View File

@ -343,7 +343,7 @@ void OSDPretty::paintEvent(QPaintEvent *) {
} }
void OSDPretty::SetMessage(const QString& summary, const QString& message, const QImage &image) { void OSDPretty::SetMessage(const QString &summary, const QString& message, const QImage &image) {
if (!image.isNull()) { if (!image.isNull()) {
QImage scaled_image = image.scaled(kMaxIconSize, kMaxIconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); QImage scaled_image = image.scaled(kMaxIconSize, kMaxIconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);

View File

@ -53,6 +53,9 @@
<height>16777215</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -72,6 +75,9 @@
<height>16777215</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>

View File

@ -75,21 +75,23 @@ ContextSettingsPage::ContextSettingsPage(SettingsDialog* dialog) : SettingsPage(
// Create and populate the helper menus // Create and populate the helper menus
QMenu *menu = new QMenu(this); QMenu *menu = new QMenu(this);
menu->addAction(ui_->action_albumartist);
menu->addAction(ui_->action_artist);
menu->addAction(ui_->action_album);
menu->addAction(ui_->action_title); menu->addAction(ui_->action_title);
menu->addAction(ui_->action_album);
menu->addAction(ui_->action_artist);
menu->addAction(ui_->action_albumartist);
menu->addAction(ui_->action_track);
menu->addAction(ui_->action_disc);
menu->addAction(ui_->action_year); menu->addAction(ui_->action_year);
menu->addAction(ui_->action_originalyear);
menu->addAction(ui_->action_composer); menu->addAction(ui_->action_composer);
menu->addAction(ui_->action_performer); menu->addAction(ui_->action_performer);
menu->addAction(ui_->action_grouping); menu->addAction(ui_->action_grouping);
menu->addAction(ui_->action_filename);
menu->addAction(ui_->action_url);
menu->addAction(ui_->action_length); menu->addAction(ui_->action_length);
menu->addAction(ui_->action_disc);
menu->addAction(ui_->action_track);
menu->addAction(ui_->action_genre); menu->addAction(ui_->action_genre);
menu->addAction(ui_->action_playcount); menu->addAction(ui_->action_playcount);
menu->addAction(ui_->action_skipcount); menu->addAction(ui_->action_skipcount);
menu->addAction(ui_->action_filename);
menu->addSeparator(); menu->addSeparator();
menu->addAction(ui_->action_newline); menu->addAction(ui_->action_newline);
ui_->context_exp_chooser1->setMenu(menu); ui_->context_exp_chooser1->setMenu(menu);

View File

@ -473,6 +473,30 @@
<string>Add song filename</string> <string>Add song filename</string>
</property> </property>
</action> </action>
<action name="action_url">
<property name="text">
<string>%url%</string>
</property>
<property name="toolTip">
<string>Add song URL</string>
</property>
</action>
<action name="action_rating">
<property name="text">
<string>%rating%</string>
</property>
<property name="toolTip">
<string>Add song rating</string>
</property>
</action>
<action name="action_originalyear">
<property name="text">
<string>%originalyear%</string>
</property>
<property name="toolTip">
<string>Add song original year tag</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -56,7 +56,7 @@
class QHideEvent; class QHideEvent;
class QShowEvent; class QShowEvent;
NotificationsSettingsPage::NotificationsSettingsPage(SettingsDialog* dialog) NotificationsSettingsPage::NotificationsSettingsPage(SettingsDialog *dialog)
: SettingsPage(dialog), ui_(new Ui_NotificationsSettingsPage), pretty_popup_(new OSDPretty(OSDPretty::Mode_Draggable)) { : SettingsPage(dialog), ui_(new Ui_NotificationsSettingsPage), pretty_popup_(new OSDPretty(OSDPretty::Mode_Draggable)) {
ui_->setupUi(this); ui_->setupUi(this);
@ -69,23 +69,24 @@ NotificationsSettingsPage::NotificationsSettingsPage(SettingsDialog* dialog)
// Create and populate the helper menus // Create and populate the helper menus
QMenu *menu = new QMenu(this); QMenu *menu = new QMenu(this);
menu->addAction(ui_->action_artist);
menu->addAction(ui_->action_album);
menu->addAction(ui_->action_title); menu->addAction(ui_->action_title);
menu->addAction(ui_->action_album);
menu->addAction(ui_->action_artist);
menu->addAction(ui_->action_albumartist); menu->addAction(ui_->action_albumartist);
menu->addAction(ui_->action_disc);
menu->addAction(ui_->action_track);
menu->addAction(ui_->action_year); menu->addAction(ui_->action_year);
menu->addAction(ui_->action_originalyear);
menu->addAction(ui_->action_genre);
menu->addAction(ui_->action_composer); menu->addAction(ui_->action_composer);
menu->addAction(ui_->action_performer); menu->addAction(ui_->action_performer);
menu->addAction(ui_->action_grouping); menu->addAction(ui_->action_grouping);
menu->addAction(ui_->action_length); menu->addAction(ui_->action_length);
menu->addAction(ui_->action_disc); menu->addAction(ui_->action_filename);
menu->addAction(ui_->action_track); menu->addAction(ui_->action_url);
menu->addAction(ui_->action_genre);
menu->addAction(ui_->action_playcount); menu->addAction(ui_->action_playcount);
menu->addAction(ui_->action_skipcount); menu->addAction(ui_->action_skipcount);
menu->addAction(ui_->action_filename);
menu->addAction(ui_->action_rating); menu->addAction(ui_->action_rating);
menu->addAction(ui_->action_score);
menu->addSeparator(); menu->addSeparator();
menu->addAction(ui_->action_newline); menu->addAction(ui_->action_newline);
ui_->notifications_exp_chooser1->setMenu(menu); ui_->notifications_exp_chooser1->setMenu(menu);

View File

@ -482,14 +482,6 @@
<string>Add song rating</string> <string>Add song rating</string>
</property> </property>
</action> </action>
<action name="action_score">
<property name="text">
<string notr="true">%score%</string>
</property>
<property name="toolTip">
<string>Add song auto score</string>
</property>
</action>
<action name="action_newline"> <action name="action_newline">
<property name="text"> <property name="text">
<string notr="true">%newline%</string> <string notr="true">%newline%</string>
@ -506,6 +498,22 @@
<string>Add song filename</string> <string>Add song filename</string>
</property> </property>
</action> </action>
<action name="action_url">
<property name="text">
<string>%url%</string>
</property>
<property name="toolTip">
<string>Add song URL</string>
</property>
</action>
<action name="action_originalyear">
<property name="text">
<string>%originalyear%</string>
</property>
<property name="toolTip">
<string>Add song original year tag</string>
</property>
</action>
</widget> </widget>
<tabstops> <tabstops>
<tabstop>notifications_none</tabstop> <tabstop>notifications_none</tabstop>