/* BEGIN_COMMON_COPYRIGHT_HEADER * (c)LGPL2+ * * LXDE-Qt - a lightweight, Qt based, desktop toolset * http://razor-qt.org * * Copyright: 2012 Razor team * Authors: * Petr Vanek * * This program or library is free software; you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #include #include #include #include #include #include #include #include #include #include #include "notification.h" #include "notificationwidgets.h" #define ICONSIZE QSize(32, 32) Notification::Notification(const QString &application, const QString &summary, const QString &body, const QString &icon, int timeout, const QStringList& actions, const QVariantMap& hints, QWidget *parent) : QWidget(parent), m_timer(0), m_linkHovered(false), m_actionWidget(0) { setupUi(this); setObjectName("Notification"); setMouseTracking(true); setMaximumWidth(parent->width()); setMinimumWidth(parent->width()); setValues(application, summary, body, icon, timeout, actions, hints); connect(closeButton, &QPushButton::clicked, this, &Notification::closeButton_clicked); for (QLabel *label : {bodyLabel, summaryLabel}) { connect(label, &QLabel::linkHovered, this, &Notification::linkHovered); label->installEventFilter(this); } } void Notification::setValues(const QString &application, const QString &summary, const QString &body, const QString &icon, int timeout, const QStringList& actions, const QVariantMap& hints) { // Basic properties ********************* // Notifications spec set real order here: // An implementation which only displays one image or icon must // choose which one to display using the following order: // - "image-data" // - "image-path" // - app_icon parameter // - for compatibility reason, "icon_data" if (!hints["image_data"].isNull()) { m_pixmap = getPixmapFromHint(hints["image_data"]); } else if (!hints["image_path"].isNull()) { m_pixmap = getPixmapFromString(hints["image_path"].toString()); } else if (!icon.isEmpty()) { m_pixmap = getPixmapFromString(icon); } else if (!hints["icon_data"].isNull()) { m_pixmap = getPixmapFromHint(hints["icon_data"]); } // issue #325: Do not display icon if it's not found... if (m_pixmap.isNull()) { iconLabel->hide(); } else { if (m_pixmap.size().width() > ICONSIZE.width() || m_pixmap.size().height() > ICONSIZE.height()) { m_pixmap = m_pixmap.scaled(ICONSIZE, Qt::KeepAspectRatio, Qt::SmoothTransformation); } iconLabel->setPixmap(m_pixmap); iconLabel->show(); } //XXX: workaround to properly set text labels widths (for correct sizeHints after setText) adjustSize(); // application appLabel->setVisible(!application.isEmpty()); appLabel->setFixedWidth(appLabel->width()); appLabel->setText(application); // summary summaryLabel->setVisible(!summary.isEmpty() && application != summary); summaryLabel->setFixedWidth(summaryLabel->width()); summaryLabel->setText(summary); // body bodyLabel->setVisible(!body.isEmpty()); bodyLabel->setFixedWidth(bodyLabel->width()); //https://developer.gnome.org/notification-spec //Body - This is a multi-line body of text. Each line is a paragraph, server implementations are free to word wrap them as they see fit. //XXX: remove all unsupported tags?!? (supported , , , , ) QString formatted(body); bodyLabel->setText(formatted.replace('\n', QStringLiteral("
"))); // Timeout // Special values: // < 0: server decides timeout // 0: infifite if (m_timer) { m_timer->stop(); m_timer->deleteLater(); } // -1 for server decides is handled in notifyd to save QSettings instance if (timeout > 0) { m_timer = new NotificationTimer(this); connect(m_timer, &NotificationTimer::timeout, this, &Notification::timeout); m_timer->start(timeout); } // Categories ********************* if (!hints["category"].isNull()) { // TODO/FIXME: Categories - how to handle it? } // Urgency Levels ********************* // Type Description // 0 Low // 1 Normal // 2 Critical if (!hints["urgency"].isNull()) { // TODO/FIXME: Urgencies - how to handle it? } // Actions if (actions.count() && m_actionWidget == 0) { if (actions.count()/2 < 4) m_actionWidget = new NotificationActionsButtonsWidget(actions, this); else m_actionWidget = new NotificationActionsComboWidget(actions, this); connect(m_actionWidget, &NotificationActionsWidget::actionTriggered, this, &Notification::actionTriggered); actionsLayout->addWidget(m_actionWidget); m_actionWidget->show(); } adjustSize(); // ensure layout expansion setMinimumHeight(qMax(rect().height(), childrenRect().height())); } QString Notification::application() const { return appLabel->text(); } QString Notification::summary() const { return summaryLabel->text(); } QString Notification::body() const { return bodyLabel->text(); } void Notification::closeButton_clicked() { if (m_timer) m_timer->stop(); emit userCanceled(); } void Notification::paintEvent(QPaintEvent *) { QStyleOption opt; opt.init(this); QPainter p(this); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } QPixmap Notification::getPixmapFromHint(const QVariant &argument) const { int width, height, rowstride, bitsPerSample, channels; bool hasAlpha; QByteArray data; const QDBusArgument arg = argument.value(); arg.beginStructure(); arg >> width; arg >> height; arg >> rowstride; arg >> hasAlpha; arg >> bitsPerSample; arg >> channels; arg >> data; arg.endStructure(); bool rgb = !hasAlpha && channels == 3 && bitsPerSample == 8; QImage::Format imageFormat = rgb ? QImage::Format_RGB888 : QImage::Format_ARGB32; QImage img = QImage((uchar*)data.constData(), width, height, imageFormat); if (!rgb) img = img.rgbSwapped(); return QPixmap::fromImage(img); } QPixmap Notification::getPixmapFromString(const QString &str) const { QUrl url(str); if (url.isValid() && QFile::exists(url.toLocalFile())) { // qDebug() << " getPixmapFromString by URL" << url; return QPixmap(url.toLocalFile()); } else { // qDebug() << " getPixmapFromString by XdgIcon theme" << str << ICONSIZE << XdgIcon::themeName(); // qDebug() << " " << XdgIcon::fromTheme(str) << "isnull:" << XdgIcon::fromTheme(str).isNull(); // They say: do not display an icon if it;s not found - see #325 return XdgIcon::fromTheme(str/*, XdgIcon::defaultApplicationIcon()*/).pixmap(ICONSIZE); } } void Notification::enterEvent(QEvent * event) { if (m_timer) m_timer->pause(); } void Notification::leaveEvent(QEvent * event) { if (m_timer) m_timer->resume(); } bool Notification::eventFilter(QObject *obj, QEvent *event) { // Catch mouseReleaseEvent on child labels if a link is not currently being hovered. // // This workarounds QTBUG-49025 where clicking on text does not propagate the mouseReleaseEvent // to the parent even though the text is not selectable and no link is being clicked. if (event->type() == QEvent::MouseButtonRelease && !m_linkHovered) { mouseReleaseEvent(static_cast(event)); return true; } return false; } void Notification::linkHovered(QString link) { m_linkHovered = !link.isEmpty(); } void Notification::mouseReleaseEvent(QMouseEvent * event) { // qDebug() << "CLICKED" << event; QString appName; QString windowTitle; if (m_actionWidget && m_actionWidget->hasDefaultAction()) { emit actionTriggered(m_actionWidget->defaultAction()); return; } foreach (WId i, KWindowSystem::stackingOrder()) { KWindowInfo info = KWindowInfo(i, NET::WMName | NET::WMVisibleName); appName = info.name(); windowTitle = info.visibleName(); // qDebug() << " " << i << "APPNAME" << appName << "TITLE" << windowTitle; if (appName.isEmpty()) { QWidget::mouseReleaseEvent(event); return; } if (appName == appLabel->text() || windowTitle == appLabel->text()) { KWindowSystem::raiseWindow(i); closeButton_clicked(); return; } } } NotificationTimer::NotificationTimer(QObject *parent) : QTimer(parent), m_intervalMsec(-1) { } void NotificationTimer::start(int msec) { m_startTime = QDateTime::currentDateTime(); m_intervalMsec = msec; QTimer::start(msec); } void NotificationTimer::pause() { if (!isActive()) return; stop(); m_intervalMsec = m_startTime.msecsTo(QDateTime::currentDateTime()); } void NotificationTimer::resume() { if (isActive()) return; start(m_intervalMsec); }