diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8c3948842..3de167544 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -86,9 +86,11 @@ set(keepassx_SOURCES gui/FileDialog.cpp gui/IconModels.cpp gui/KeePass1OpenWidget.cpp + gui/kmessagewidget.cpp gui/LineEdit.cpp gui/MainWindow.cpp gui/MessageBox.cpp + gui/MessageWidget.cpp gui/PasswordEdit.cpp gui/PasswordGeneratorWidget.cpp gui/PasswordComboBox.cpp diff --git a/src/gui/MessageWidget.cpp b/src/gui/MessageWidget.cpp new file mode 100644 index 000000000..1e6d06044 --- /dev/null +++ b/src/gui/MessageWidget.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 Felix Geyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "MessageWidget.h" + +MessageWidget::MessageWidget(QWidget* parent) + :KMessageWidget(parent) +{ + +} + +void MessageWidget::showMessageError(const QString& text) +{ + showMessage(text, MessageType::Error); +} + +void MessageWidget::showMessageWarning(const QString& text) +{ + showMessage(text, MessageType::Warning); +} + +void MessageWidget::showMessagePositive(const QString& text) +{ + showMessage(text, MessageType::Positive); +} + +void MessageWidget::showMessage(const QString& text, MessageType type) +{ + setMessageType(type); + setText(text); + animatedShow(); +} + + + diff --git a/src/gui/MessageWidget.h b/src/gui/MessageWidget.h new file mode 100644 index 000000000..6e8366fae --- /dev/null +++ b/src/gui/MessageWidget.h @@ -0,0 +1,21 @@ + + +#ifndef MESSAGEWIDGET_H +#define MESSAGEWIDGET_H + +#include "gui/kmessagewidget.h" + +class MessageWidget : public KMessageWidget +{ +public: + explicit MessageWidget(QWidget* parent = 0); + void showMessageError(const QString& text); + void showMessageWarning(const QString& text); + void showMessageInformation(const QString& text); + void showMessagePositive(const QString& text); + +private: + void showMessage(const QString& text, MessageType type); +}; + +#endif // MESSAGEWIDGET_H diff --git a/src/gui/kmessagewidget.cpp b/src/gui/kmessagewidget.cpp new file mode 100644 index 000000000..e5aafd61a --- /dev/null +++ b/src/gui/kmessagewidget.cpp @@ -0,0 +1,425 @@ +/* This file is part of the KDE libraries + * + * Copyright (c) 2011 Aurélien Gâteau + * + * This 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 + */ +#include "kmessagewidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void KMessageWidgetPrivate::init(KMessageWidget *q_ptr) +{ + q = q_ptr; + + q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + + timeLine = new QTimeLine(500, q); + QObject::connect(timeLine, SIGNAL(valueChanged(qreal)), q, SLOT(slotTimeLineChanged(qreal))); + QObject::connect(timeLine, SIGNAL(finished()), q, SLOT(slotTimeLineFinished())); + + content = new QFrame(q); + content->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + wordWrap = false; + + iconLabel = new QLabel(content); + iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + iconLabel->hide(); + + textLabel = new QLabel(content); + textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + QObject::connect(textLabel, SIGNAL(linkActivated(const QString &)), q, SIGNAL(linkActivated(const QString &))); + QObject::connect(textLabel, SIGNAL(linkHovered(const QString &)), q, SIGNAL(linkHovered(const QString &))); + + QAction *closeAction = new QAction(QObject::tr("Close"), q); + q->connect(closeAction, SIGNAL(triggered(bool)), q, SLOT(animatedHide())); + + closeButton = new QToolButton(content); + closeButton->setAutoRaise(true); + closeButton->setDefaultAction(closeAction); + closeButton->setVisible(true); + q->setMessageType(KMessageWidget::Information); +} + +void KMessageWidgetPrivate::createLayout() +{ + delete content->layout(); + + content->resize(q->size()); + + qDeleteAll(buttons); + buttons.clear(); + + Q_FOREACH (QAction *action, q->actions()) { + QToolButton *button = new QToolButton(content); + button->setDefaultAction(action); + button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + buttons.append(button); + } + + // AutoRaise reduces visual clutter, but we don't want to turn it on if + // there are other buttons, otherwise the close button will look different + // from the others. + closeButton->setAutoRaise(buttons.isEmpty()); + + if (wordWrap) { + QGridLayout *layout = new QGridLayout(content); + // Set alignment to make sure icon does not move down if text wraps + layout->addWidget(iconLabel, 0, 0, 1, 1, Qt::AlignHCenter | Qt::AlignTop); + layout->addWidget(textLabel, 0, 1); + + QDialogButtonBox *buttonLayout = new QDialogButtonBox(); + //buttonLayout->addStretch(); + Q_FOREACH (QToolButton *button, buttons) { + // For some reason, calling show() is necessary if wordwrap is true, + // otherwise the buttons do not show up. It is not needed if + // wordwrap is false. + button->show(); + buttonLayout->addButton(button, QDialogButtonBox::QDialogButtonBox::AcceptRole); + } + buttonLayout->addButton(closeButton, QDialogButtonBox::RejectRole); + layout->addWidget(buttonLayout, 1, 0, 1, 2, Qt::AlignHCenter | Qt::AlignTop); + } else { + bool closeButtonVisible = closeButton->isVisible(); + QHBoxLayout *layout = new QHBoxLayout(content); + layout->addWidget(iconLabel); + layout->addWidget(textLabel); + + QDialogButtonBox *buttonLayout = new QDialogButtonBox(); + Q_FOREACH (QToolButton *button, buttons) { + buttonLayout->addButton(button, QDialogButtonBox::QDialogButtonBox::AcceptRole); + } + + buttonLayout->addButton(closeButton, QDialogButtonBox::RejectRole); + // Something gets changed when added to the buttonLayout + closeButton->setVisible(closeButtonVisible); + layout->addWidget(buttonLayout); + }; + + if (q->isVisible()) { + q->setFixedHeight(content->sizeHint().height()); + } + + q->updateGeometry(); +} + +void KMessageWidgetPrivate::updateLayout() +{ + if (content->layout()) { + createLayout(); + } +} + +void KMessageWidgetPrivate::updateSnapShot() +{ + // Attention: updateSnapShot calls QWidget::render(), which causes the whole + // window layouts to be activated. Calling this method from resizeEvent() + // can lead to infinite recursion, see: + // https://bugs.kde.org/show_bug.cgi?id=311336 + contentSnapShot = QPixmap(content->size()); + contentSnapShot.fill(Qt::transparent); + content->render(&contentSnapShot, QPoint(), QRegion(), QWidget::DrawChildren); +} + +void KMessageWidgetPrivate::slotTimeLineChanged(qreal value) +{ + q->setFixedHeight(qMin(value * 2, qreal(1.0)) * content->height()); + q->update(); +} + +void KMessageWidgetPrivate::slotTimeLineFinished() +{ + if (timeLine->direction() == QTimeLine::Forward) { + // Show + // We set the whole geometry here, because it may be wrong if a + // KMessageWidget is shown right when the toplevel window is created. + content->setGeometry(0, 0, q->width(), bestContentHeight()); + } else { + // Hide + q->hide(); + } +} + +int KMessageWidgetPrivate::bestContentHeight() const +{ + int height = content->heightForWidth(q->width()); + + if (height == -1) { + height = content->sizeHint().height(); + } + + return height; +} + + +//--------------------------------------------------------------------- +// KMessageWidget +//--------------------------------------------------------------------- +KMessageWidget::KMessageWidget(QWidget *parent) : QFrame(parent), d(new KMessageWidgetPrivate) +{ + d->init(this); +} + +KMessageWidget::KMessageWidget(const QString &text, QWidget *parent) : QFrame(parent), d(new KMessageWidgetPrivate) +{ + d->init(this); + setText(text); +} + +KMessageWidget::~KMessageWidget() +{ + delete d; +} + +QString KMessageWidget::text() const +{ + return d->textLabel->text(); +} + +void KMessageWidget::setText(const QString &text) +{ + d->textLabel->setText(text); + updateGeometry(); +} + +int KMessageWidget::bestContentHeight() const +{ + return d->bestContentHeight(); +} + + +KMessageWidget::MessageType KMessageWidget::messageType() const +{ + return d->messageType; +} + +void KMessageWidget::setMessageType(KMessageWidget::MessageType type) +{ + d->messageType = type; + QColor bg0, bg1, bg2, border, fg; + + switch (type) { + case Positive: + bg1 = QColor("#72D594"); // nice green + fg = QColor(Qt::white); + break; + + case Information: + bg1 = QColor("#41A8E3"); // nice blue + fg = QColor(Qt::black); + break; + + case Warning: + bg1 = QColor("#FFCD0F"); // nice yellow + fg = QColor(Qt::black); + break; + + case Error: + bg1 = QColor("#FF7D7D"); // nice red. + fg = QColor(Qt::black); + break; + } + + // Colors + bg0 = bg1.lighter(110); + bg2 = bg1.darker(110); + border = bg2.darker(110); + d->content->setStyleSheet( + QString(".QFrame {" + "background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1," + " stop: 0 %1," + " stop: 0.1 %2," + " stop: 1.0 %3);" + "border-radius: 5px;" + "border: 1px solid %4;" + "margin: %5px;" + "}" + ".QLabel { color: %6; }").arg(bg0.name()) + .arg(bg1.name()) + .arg(bg2.name()) + .arg(border.name()) + /* + DefaultFrameWidth returns the size of the external margin + border width. + We know our border is 1px, so we subtract this from the frame + normal QStyle FrameWidth to get our margin + */ + .arg(style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, this) - 1) + .arg(fg.name())); +} + +QSize KMessageWidget::sizeHint() const +{ + ensurePolished(); + return d->content->sizeHint(); +} + +QSize KMessageWidget::minimumSizeHint() const +{ + ensurePolished(); + return d->content->minimumSizeHint(); +} + +bool KMessageWidget::event(QEvent *event) +{ + if (event->type() == QEvent::Polish && !d->content->layout()) { + d->createLayout(); + } + + return QFrame::event(event); +} + +void KMessageWidget::resizeEvent(QResizeEvent *event) +{ + QFrame::resizeEvent(event); + + if (d->timeLine->state() == QTimeLine::NotRunning) { + d->content->resize(width(), d->bestContentHeight()); + } +} + +int KMessageWidget::heightForWidth(int width) const +{ + ensurePolished(); + return d->content->heightForWidth(width); +} + +void KMessageWidget::paintEvent(QPaintEvent *event) +{ + QFrame::paintEvent(event); + + if (d->timeLine->state() == QTimeLine::Running) { + QPainter painter(this); + painter.setOpacity(d->timeLine->currentValue() * d->timeLine->currentValue()); + painter.drawPixmap(0, 0, d->contentSnapShot); + } +} + +void KMessageWidget::showEvent(QShowEvent *event) +{ + // Keep this method here to avoid breaking binary compatibility: + // QFrame::showEvent() used to be reimplemented. + QFrame::showEvent(event); +} + +bool KMessageWidget::wordWrap() const +{ + return d->wordWrap; +} + +void KMessageWidget::setWordWrap(bool wordWrap) +{ + d->wordWrap = wordWrap; + d->textLabel->setWordWrap(wordWrap); + QSizePolicy policy = sizePolicy(); + policy.setHeightForWidth(wordWrap); + setSizePolicy(policy); + d->updateLayout(); + + // Without this, when user does wordWrap -> !wordWrap -> wordWrap, a minimum + // height is set, causing the widget to be too high. + // Mostly visible in test programs. + if (wordWrap) { + setMinimumHeight(0); + } +} + +bool KMessageWidget::isCloseButtonVisible() const +{ + return d->closeButton->isVisible(); +} + +void KMessageWidget::setCloseButtonVisible(bool show) +{ + d->closeButton->setVisible(show); + updateGeometry(); +} + +void KMessageWidget::addAction(QAction *action) +{ + QFrame::addAction(action); + d->updateLayout(); +} + +void KMessageWidget::removeAction(QAction *action) +{ + QFrame::removeAction(action); + d->updateLayout(); +} + +void KMessageWidget::animatedShow() +{ + if (isVisible()) { + return; + } + + QFrame::show(); + setFixedHeight(0); + int wantedHeight = d->bestContentHeight(); + d->content->setGeometry(0, -wantedHeight, width(), wantedHeight); + + d->updateSnapShot(); + + d->timeLine->setDirection(QTimeLine::Forward); + + if (d->timeLine->state() == QTimeLine::NotRunning) { + d->timeLine->start(); + } +} + +void KMessageWidget::animatedHide() +{ + if (!isVisible()) { + hide(); + return; + } + + d->content->move(0, -d->content->height()); + d->updateSnapShot(); + + d->timeLine->setDirection(QTimeLine::Backward); + + if (d->timeLine->state() == QTimeLine::NotRunning) { + d->timeLine->start(); + } +} + +QIcon KMessageWidget::icon() const +{ + return d->icon; +} + +void KMessageWidget::setIcon(const QIcon &icon) +{ + d->icon = icon; + + if (d->icon.isNull()) { + d->iconLabel->hide(); + } else { + d->iconLabel->setPixmap(d->icon.pixmap(QSize(16, 16))); + d->iconLabel->show(); + } +} diff --git a/src/gui/kmessagewidget.h b/src/gui/kmessagewidget.h new file mode 100644 index 000000000..dbc694afa --- /dev/null +++ b/src/gui/kmessagewidget.h @@ -0,0 +1,239 @@ +/* This file is part of the KDE libraries + * + * Copyright (c) 2011 Aurélien Gâteau + * + * This 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 + */ +#ifndef KMESSAGEWIDGET_H +#define KMESSAGEWIDGET_H + +#include + +class KMessageWidgetPrivate; + +/** + * @short A widget to provide feedback or propose opportunistic interactions. + * + * KMessageWidget can be used to provide inline positive or negative + * feedback, or to implement opportunistic interactions. + * + * As a feedback widget, KMessageWidget provides a less intrusive alternative + * to "OK Only" message boxes. If you do not need the modalness of KMessageBox, + * consider using KMessageWidget instead. + * + * Negative feedback + * + * The KMessageWidget can be used as a secondary indicator of failure: the + * first indicator is usually the fact the action the user expected to happen + * did not happen. + * + * Example: User fills a form, clicks "Submit". + * + * @li Expected feedback: form closes + * @li First indicator of failure: form stays there + * @li Second indicator of failure: a KMessageWidget appears on top of the + * form, explaining the error condition + * + * When used to provide negative feedback, KMessageWidget should be placed + * close to its context. In the case of a form, it should appear on top of the + * form entries. + * + * KMessageWidget should get inserted in the existing layout. Space should not + * be reserved for it, otherwise it becomes "dead space", ignored by the user. + * KMessageWidget should also not appear as an overlay to prevent blocking + * access to elements the user needs to interact with to fix the failure. + * + * Positive feedback + * + * KMessageWidget can be used for positive feedback but it shouldn't be + * overused. It is often enough to provide feedback by simply showing the + * results of an action. + * + * Examples of acceptable uses: + * + * @li Confirm success of "critical" transactions + * @li Indicate completion of background tasks + * + * Example of inadapted uses: + * + * @li Indicate successful saving of a file + * @li Indicate a file has been successfully removed + * + * Opportunistic interaction + * + * Opportunistic interaction is the situation where the application suggests to + * the user an action he could be interested in perform, either based on an + * action the user just triggered or an event which the application noticed. + * + * Example of acceptable uses: + * + * @li A browser can propose remembering a recently entered password + * @li A music collection can propose ripping a CD which just got inserted + * @li A chat application may notify the user a "special friend" just connected + * + * @author Aurélien Gâteau + * @since 4.7 + */ +class KMessageWidget : public QFrame { + Q_OBJECT + Q_ENUMS(MessageType) + + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap) + Q_PROPERTY(bool closeButtonVisible READ isCloseButtonVisible WRITE setCloseButtonVisible) + Q_PROPERTY(MessageType messageType READ messageType WRITE setMessageType) + Q_PROPERTY(QIcon icon READ icon WRITE setIcon) +public: + enum MessageType { + Positive, + Information, + Warning, + Error + }; + + /** + * Constructs a KMessageWidget with the specified parent. + */ + explicit KMessageWidget(QWidget *parent = 0); + + explicit KMessageWidget(const QString &text, QWidget *parent = 0); + + ~KMessageWidget(); + + QString text() const; + + bool wordWrap() const; + + bool isCloseButtonVisible() const; + + MessageType messageType() const; + + void addAction(QAction *action); + + void removeAction(QAction *action); + + QSize sizeHint() const; + + QSize minimumSizeHint() const; + + int heightForWidth(int width) const; + + /** + * The icon shown on the left of the text. By default, no icon is shown. + * @since 4.11 + */ + QIcon icon() const; + +public +Q_SLOTS: + void setText(const QString &text); + + void setWordWrap(bool wordWrap); + + void setCloseButtonVisible(bool visible); + + void setMessageType(KMessageWidget::MessageType type); + + /** + * Show the widget using an animation, unless + * KGlobalSettings::graphicsEffectLevel() does not allow simple effects. + */ + void animatedShow(); + + /** + * Hide the widget using an animation, unless + * KGlobalSettings::graphicsEffectLevel() does not allow simple effects. + */ + void animatedHide(); + + /** + * Define an icon to be shown on the left of the text + * @since 4.11 + */ + void setIcon(const QIcon &icon); + + int bestContentHeight() const; +Q_SIGNALS: + /** + * This signal is emitted when the user clicks a link in the text label. + * The URL referred to by the href anchor is passed in contents. + * @param contents text of the href anchor + * @see QLabel::linkActivated() + * @since 4.10 + */ + void linkActivated(const QString &contents); + + /** + * This signal is emitted when the user hovers over a link in the text label. + * The URL referred to by the href anchor is passed in contents. + * @param contents text of the href anchor + * @see QLabel::linkHovered() + * @since 4.11 + */ + void linkHovered(const QString &contents); + +protected: + void paintEvent(QPaintEvent *event); + + bool event(QEvent *event); + + void resizeEvent(QResizeEvent *event); + + void showEvent(QShowEvent *event); + +private: + KMessageWidgetPrivate *const d; + friend class KMessageWidgetPrivate; + + Q_PRIVATE_SLOT(d, void slotTimeLineChanged(qreal)) + Q_PRIVATE_SLOT(d, void slotTimeLineFinished()) +}; + +//--------------------------------------------------------------------- +// KMessageWidgetPrivate +//--------------------------------------------------------------------- +class QLabel; +class QToolButton; +class QTimeLine; +#include + +class KMessageWidgetPrivate { +public: + void init(KMessageWidget *); + + KMessageWidget *q; + QFrame *content; + QLabel *iconLabel; + QLabel *textLabel; + QToolButton *closeButton; + QTimeLine *timeLine; + QIcon icon; + + KMessageWidget::MessageType messageType; + bool wordWrap; + QList buttons; + QPixmap contentSnapShot; + + void createLayout(); + void updateSnapShot(); + void updateLayout(); + void slotTimeLineChanged(qreal); + void slotTimeLineFinished(); + + int bestContentHeight() const; +}; + +#endif // KMESSAGEWIDGET_H