diff --git a/.gitignore b/.gitignore index 0b9b16727..b16e70a67 100644 --- a/.gitignore +++ b/.gitignore @@ -171,3 +171,4 @@ CPackConfig.cmake CPackOptions.cmake CPackSourceConfig.cmake +compile_commands.json diff --git a/src/3rdparty/kmessagewidget/kmessagewidget.cpp b/src/3rdparty/kmessagewidget/kmessagewidget.cpp new file mode 100644 index 000000000..9e9a7ebb0 --- /dev/null +++ b/src/3rdparty/kmessagewidget/kmessagewidget.cpp @@ -0,0 +1,512 @@ +/* This file is part of the KDE libraries + * + * Copyright (c) 2011 Aurélien Gâteau + * Copyright (c) 2014 Dominik Haumann + * + * 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 +#include + +//--------------------------------------------------------------------- +// KMessageWidgetPrivate +//--------------------------------------------------------------------- +class KMessageWidgetPrivate +{ +public: + void init(KMessageWidget *); + + KMessageWidget *q; + QFrame *content = nullptr; + QLabel *iconLabel = nullptr; + QLabel *textLabel = nullptr; + QToolButton *closeButton = nullptr; + QTimeLine *timeLine = nullptr; + QIcon icon; + bool ignoreShowEventDoingAnimatedShow = false; + + KMessageWidget::MessageType messageType; + bool wordWrap; + QList buttons; + QPixmap contentSnapShot; + + void createLayout(); + void applyStyleSheet(); + void updateSnapShot(); + void updateLayout(); + void slotTimeLineChanged(qreal); + void slotTimeLineFinished(); + + int bestContentHeight() const; +}; + +void KMessageWidgetPrivate::init(KMessageWidget *q_ptr) +{ + q = q_ptr; + + q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + + // Note: when changing the value 500, also update KMessageWidgetTest + 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->setObjectName(QStringLiteral("contentWidget")); + 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, &QLabel::linkActivated, q, &KMessageWidget::linkActivated); + QObject::connect(textLabel, &QLabel::linkHovered, q, &KMessageWidget::linkHovered); + + QAction *closeAction = new QAction(q); + closeAction->setText(KMessageWidget::tr("&Close")); + closeAction->setToolTip(KMessageWidget::tr("Close message")); + closeAction->setIcon(q->style()->standardIcon(QStyle::SP_DialogCloseButton)); + + QObject::connect(closeAction, &QAction::triggered, q, &KMessageWidget::animatedHide); + + closeButton = new QToolButton(content); + closeButton->setAutoRaise(true); + closeButton->setDefaultAction(closeAction); + + 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); + + if (buttons.isEmpty()) { + // Use top-vertical alignment like the icon does. + layout->addWidget(closeButton, 0, 2, 1, 1, Qt::AlignHCenter | Qt::AlignTop); + } else { + // Use an additional layout in row 1 for the buttons. + QHBoxLayout *buttonLayout = new QHBoxLayout; + 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->addWidget(button); + } + buttonLayout->addWidget(closeButton); + layout->addItem(buttonLayout, 1, 0, 1, 2); + } + } else { + QHBoxLayout *layout = new QHBoxLayout(content); + layout->addWidget(iconLabel); + layout->addWidget(textLabel); + + for (QToolButton *button : qAsConst(buttons)) { + layout->addWidget(button); + } + + layout->addWidget(closeButton); + }; + + if (q->isVisible()) { + q->setFixedHeight(content->sizeHint().height()); + } + q->updateGeometry(); +} + +void KMessageWidgetPrivate::applyStyleSheet() +{ + QColor bgBaseColor; + + // We have to hardcode colors here because KWidgetsAddons is a tier 1 framework + // and therefore can't depend on any other KDE Frameworks + // The following RGB color values come from the "default" scheme in kcolorscheme.cpp + switch (messageType) { + case KMessageWidget::Positive: + bgBaseColor.setRgb(39, 174, 96); // Window: ForegroundPositive + break; + case KMessageWidget::Information: + bgBaseColor.setRgb(61, 174, 233); // Window: ForegroundActive + break; + case KMessageWidget::Warning: + bgBaseColor.setRgb(246, 116, 0); // Window: ForegroundNeutral + break; + case KMessageWidget::Error: + bgBaseColor.setRgb(218, 68, 83); // Window: ForegroundNegative + break; + } + const qreal bgBaseColorAlpha = 0.2; + bgBaseColor.setAlphaF(bgBaseColorAlpha); + + const QPalette palette = QGuiApplication::palette(); + const QColor windowColor = palette.window().color(); + const QColor textColor = palette.text().color(); + const QColor border = bgBaseColor; + + // Generate a final background color from overlaying bgBaseColor over windowColor + const int newRed = (bgBaseColor.red() * bgBaseColorAlpha) + (windowColor.red() * (1 - bgBaseColorAlpha)); + const int newGreen = (bgBaseColor.green() * bgBaseColorAlpha) + (windowColor.green() * (1 - bgBaseColorAlpha)); + const int newBlue = (bgBaseColor.blue() * bgBaseColorAlpha) + (windowColor.blue() * (1 - bgBaseColorAlpha)); + + const QColor bgFinalColor = QColor(newRed, newGreen, newBlue); + + content->setStyleSheet( + QString::fromLatin1(".QFrame {" + "background-color: %1;" + "border-radius: 4px;" + "border: 2px solid %2;" + "margin: %3px;" + "}" + ".QLabel { color: %4; }" + ) + .arg(bgFinalColor.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(q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, q) - 1) + .arg(textColor.name()) + ); +} + +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() * q->devicePixelRatio()); + contentSnapShot.setDevicePixelRatio(q->devicePixelRatio()); + 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()); + + // notify about finished animation + emit q->showAnimationFinished(); + } else { + // hide and notify about finished animation + q->hide(); + emit q->hideAnimationFinished(); + } +} + +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(); +} + +KMessageWidget::MessageType KMessageWidget::messageType() const +{ + return d->messageType; +} + +void KMessageWidget::setMessageType(KMessageWidget::MessageType type) +{ + d->messageType = type; + d->applyStyleSheet(); +} + +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(); + } else if (event->type() == QEvent::PaletteChange) { + d->applyStyleSheet(); + } else if (event->type() == QEvent::Show && !d->ignoreShowEventDoingAnimatedShow) { + if ((height() != d->content->height()) || (d->content->pos().y() != 0)) { + d->content->move(0, 0); + setFixedHeight(d->content->height()); + } + } + 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); + } +} + +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() +{ + // Test before styleHint, as there might have been a style change while animation was running + if (isHideAnimationRunning()) { + d->timeLine->stop(); + emit hideAnimationFinished(); + } + + if (!style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this) + || (parentWidget() && !parentWidget()->isVisible())) { + show(); + emit showAnimationFinished(); + return; + } + + if (isVisible() && (d->timeLine->state() == QTimeLine::NotRunning) && (height() == d->bestContentHeight()) && (d->content->pos().y() == 0)) { + emit showAnimationFinished(); + return; + } + + d->ignoreShowEventDoingAnimatedShow = true; + show(); + d->ignoreShowEventDoingAnimatedShow = false; + 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() +{ + // test this before isVisible, as animatedShow might have been called directly before, + // so the first timeline event is not yet done and the widget is still hidden + // And before styleHint, as there might have been a style change while animation was running + if (isShowAnimationRunning()) { + d->timeLine->stop(); + emit showAnimationFinished(); + } + + if (!style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this)) { + hide(); + emit hideAnimationFinished(); + return; + } + + if (!isVisible()) { + // explicitly hide it, so it stays hidden in case it is only not visible due to the parents + hide(); + emit hideAnimationFinished(); + return; + } + + d->content->move(0, -d->content->height()); + d->updateSnapShot(); + + d->timeLine->setDirection(QTimeLine::Backward); + if (d->timeLine->state() == QTimeLine::NotRunning) { + d->timeLine->start(); + } +} + +bool KMessageWidget::isHideAnimationRunning() const +{ + return (d->timeLine->direction() == QTimeLine::Backward) + && (d->timeLine->state() == QTimeLine::Running); +} + +bool KMessageWidget::isShowAnimationRunning() const +{ + return (d->timeLine->direction() == QTimeLine::Forward) + && (d->timeLine->state() == QTimeLine::Running); +} + +QIcon KMessageWidget::icon() const +{ + return d->icon; +} + +void KMessageWidget::setIcon(const QIcon &icon) +{ + d->icon = icon; + if (d->icon.isNull()) { + d->iconLabel->hide(); + } else { + const int size = style()->pixelMetric(QStyle::PM_ToolBarIconSize); + d->iconLabel->setPixmap(d->icon.pixmap(size)); + d->iconLabel->show(); + } +} + +#include "moc_kmessagewidget.cpp" + diff --git a/src/3rdparty/kmessagewidget/kmessagewidget.h b/src/3rdparty/kmessagewidget/kmessagewidget.h new file mode 100644 index 000000000..cc2767bf9 --- /dev/null +++ b/src/3rdparty/kmessagewidget/kmessagewidget.h @@ -0,0 +1,346 @@ +/* This file is part of the KDE libraries + * + * Copyright (c) 2011 Aurélien Gâteau + * Copyright (c) 2014 Dominik Haumann + * + * 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; + +/** + * @class KMessageWidget kmessagewidget.h KMessageWidget + * + * @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 want to avoid a modal KMessageBox, + * consider using KMessageWidget instead. + * + * Examples of KMessageWidget look as follows, all of them having an icon set + * with setIcon(), and the first three show a close button: + * + * \image html kmessagewidget.png "KMessageWidget with different message types" + * + * 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 unadapted 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_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: + + /** + * Available message types. + * The background colors are chosen depending on the message type. + */ + enum MessageType { + Positive, + Information, + Warning, + Error + }; + Q_ENUM(MessageType) + + /** + * Constructs a KMessageWidget with the specified @p parent. + */ + explicit KMessageWidget(QWidget *parent = nullptr); + + /** + * Constructs a KMessageWidget with the specified @p parent and + * contents @p text. + */ + explicit KMessageWidget(const QString &text, QWidget *parent = nullptr); + + /** + * Destructor. + */ + ~KMessageWidget() override; + + /** + * Get the text of this message widget. + * @see setText() + */ + QString text() const; + + /** + * Check whether word wrap is enabled. + * + * If word wrap is enabled, the message widget wraps the displayed text + * as required to the available width of the widget. This is useful to + * avoid breaking widget layouts. + * + * @see setWordWrap() + */ + bool wordWrap() const; + + /** + * Check whether the close button is visible. + * + * @see setCloseButtonVisible() + */ + bool isCloseButtonVisible() const; + + /** + * Get the type of this message. + * By default, the type is set to KMessageWidget::Information. + * + * @see KMessageWidget::MessageType, setMessageType() + */ + MessageType messageType() const; + + /** + * Add @p action to the message widget. + * For each action a button is added to the message widget in the + * order the actions were added. + * + * @param action the action to add + * @see removeAction(), QWidget::actions() + */ + void addAction(QAction *action); + + /** + * Remove @p action from the message widget. + * + * @param action the action to remove + * @see KMessageWidget::MessageType, addAction(), setMessageType() + */ + void removeAction(QAction *action); + + /** + * Returns the preferred size of the message widget. + */ + QSize sizeHint() const override; + + /** + * Returns the minimum size of the message widget. + */ + QSize minimumSizeHint() const override; + + /** + * Returns the required height for @p width. + * @param width the width in pixels + */ + int heightForWidth(int width) const override; + + /** + * The icon shown on the left of the text. By default, no icon is shown. + * @since 4.11 + */ + QIcon icon() const; + + /** + * Check whether the hide animation started by calling animatedHide() + * is still running. If animations are disabled, this function always + * returns @e false. + * + * @see animatedHide(), hideAnimationFinished() + * @since 5.0 + */ + bool isHideAnimationRunning() const; + + /** + * Check whether the show animation started by calling animatedShow() + * is still running. If animations are disabled, this function always + * returns @e false. + * + * @see animatedShow(), showAnimationFinished() + * @since 5.0 + */ + bool isShowAnimationRunning() const; + +public Q_SLOTS: + /** + * Set the text of the message widget to @p text. + * If the message widget is already visible, the text changes on the fly. + * + * @param text the text to display, rich text is allowed + * @see text() + */ + void setText(const QString &text); + + /** + * Set word wrap to @p wordWrap. If word wrap is enabled, the text() + * of the message widget is wrapped to fit the available width. + * If word wrap is disabled, the message widget's minimum size is + * such that the entire text fits. + * + * @param wordWrap disable/enable word wrap + * @see wordWrap() + */ + void setWordWrap(bool wordWrap); + + /** + * Set the visibility of the close button. If @p visible is @e true, + * a close button is shown that calls animatedHide() if clicked. + * + * @see closeButtonVisible(), animatedHide() + */ + void setCloseButtonVisible(bool visible); + + /** + * Set the message type to @p type. + * By default, the message type is set to KMessageWidget::Information. + * Appropriate colors are chosen to mimic the appearance of Kirigami's + * InlineMessage. + * + * @see messageType(), KMessageWidget::MessageType + */ + void setMessageType(KMessageWidget::MessageType type); + + /** + * Show the widget using an animation. + */ + void animatedShow(); + + /** + * Hide the widget using an animation. + */ + void animatedHide(); + + /** + * Define an icon to be shown on the left of the text + * @since 4.11 + */ + void setIcon(const QIcon &icon); + +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); + + /** + * This signal is emitted when the hide animation is finished, started by + * calling animatedHide(). If animations are disabled, this signal is + * emitted immediately after the message widget got hidden. + * + * @note This signal is @e not emitted if the widget was hidden by + * calling hide(), so this signal is only useful in conjunction + * with animatedHide(). + * + * @see animatedHide() + * @since 5.0 + */ + void hideAnimationFinished(); + + /** + * This signal is emitted when the show animation is finished, started by + * calling animatedShow(). If animations are disabled, this signal is + * emitted immediately after the message widget got shown. + * + * @note This signal is @e not emitted if the widget was shown by + * calling show(), so this signal is only useful in conjunction + * with animatedShow(). + * + * @see animatedShow() + * @since 5.0 + */ + void showAnimationFinished(); + +protected: + void paintEvent(QPaintEvent *event) override; + + bool event(QEvent *event) override; + + void resizeEvent(QResizeEvent *event) override; + +private: + KMessageWidgetPrivate *const d; + friend class KMessageWidgetPrivate; + + Q_PRIVATE_SLOT(d, void slotTimeLineChanged(qreal)) + Q_PRIVATE_SLOT(d, void slotTimeLineFinished()) +}; + +#endif /* KMESSAGEWIDGET_H */ diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 4e739e25a..167d1727f 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -165,6 +165,7 @@ set(3rdparty_SRC ../3rdparty/qtsingleapplication/qtlocalpeer.cpp ../3rdparty/qtsingleapplication/qtsingleapplication.cpp ../3rdparty/qtsingleapplication/qtsinglecoreapplication.cpp + ../3rdparty/kmessagewidget/kmessagewidget.cpp ) if (APPLE) @@ -329,6 +330,7 @@ target_include_directories(${APPLICATION_EXECUTABLE} PRIVATE ${CMAKE_SOURCE_DIR}/src/3rdparty/qtlockedfile ${CMAKE_SOURCE_DIR}/src/3rdparty/qtmacgoodies/src ${CMAKE_SOURCE_DIR}/src/3rdparty/qtsingleapplication + ${CMAKE_SOURCE_DIR}/src/3rdparty/kmessagewidget ${CMAKE_CURRENT_BINARY_DIR} ) diff --git a/src/gui/accountmanager.cpp b/src/gui/accountmanager.cpp index 8824ac7c7..ca00a4e17 100644 --- a/src/gui/accountmanager.cpp +++ b/src/gui/accountmanager.cpp @@ -342,9 +342,6 @@ AccountPtr AccountManager::createAccount() connect(acc.data(), &Account::proxyAuthenticationRequired, ProxyAuthHandler::instance(), &ProxyAuthHandler::handleProxyAuthenticationRequired); - connect(acc.data()->e2e(), &ClientSideEncryption::mnemonicGenerated, - &AccountManager::displayMnemonic); - return acc; } @@ -364,6 +361,7 @@ void AccountManager::displayMnemonic(const QString& mnemonic) widget->exec(); widget->resize(widget->sizeHint()); } + void AccountManager::shutdown() { auto accountsCopy = _accounts; diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index 81f441f22..4a554a196 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -194,6 +194,14 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) // Connect E2E stuff connect(this, &AccountSettings::requesetMnemonic, _accountState->account()->e2e(), &ClientSideEncryption::slotRequestMnemonic); connect(_accountState->account()->e2e(), &ClientSideEncryption::showMnemonic, this, &AccountSettings::slotShowMnemonic); + + connect(_accountState->account()->e2e(), &ClientSideEncryption::mnemonicGenerated, this, &AccountSettings::slotNewMnemonicGenerated); + if (_accountState->account()->e2e()->newMnemonicGenerated()) + { + slotNewMnemonicGenerated(); + } else { + ui->encryptionMessage->hide(); + } } @@ -222,6 +230,19 @@ void AccountSettings::createAccountToolbox() slotAccountAdded(_accountState); } + +void AccountSettings::slotNewMnemonicGenerated() +{ + ui->encryptionMessage->setText(tr("This account supports end to end encryption")); + + QAction *mnemonic = new QAction(tr("Setup"), this); + connect(mnemonic, &QAction::triggered, this, &AccountSettings::requesetMnemonic); + connect(mnemonic, &QAction::triggered, ui->encryptionMessage, &KMessageWidget::hide); + + ui->encryptionMessage->addAction(mnemonic); + ui->encryptionMessage->show(); +} + void AccountSettings::slotMenuBeforeShow() { if (_menuShown) { return; diff --git a/src/gui/accountsettings.h b/src/gui/accountsettings.h index e0132ae2f..eb6a78b17 100644 --- a/src/gui/accountsettings.h +++ b/src/gui/accountsettings.h @@ -99,6 +99,7 @@ protected slots: // Encryption Related Stuff. void slotShowMnemonic(const QString &mnemonic); + void slotNewMnemonicGenerated(); void slotEncryptionFlagSuccess(const QByteArray &folderId); void slotEncryptionFlagError(const QByteArray &folderId, int httpReturnCode); diff --git a/src/gui/accountsettings.ui b/src/gui/accountsettings.ui index 705f1bdad..fcdd8365a 100644 --- a/src/gui/accountsettings.ui +++ b/src/gui/accountsettings.ui @@ -6,7 +6,7 @@ 0 0 - 575 + 582 557 @@ -14,128 +14,7 @@ Form - - - - - - - - 0 - 0 - - - - - - - - - - - - 0 - 0 - - - - Connected with <server> as <user> - - - Qt::RichText - - - true - - - true - - - - - - - ... - - - - - - - - - - - - - 0 - 0 - - - - - - - Storage space: ... - - - Qt::PlainText - - - false - - - - - - - false - - - - 0 - 0 - - - - - 16777215 - 7 - - - - 100 - - - -1 - - - false - - - - - - - - - - 0 - 5 - - - - Qt::CustomContextMenu - - - QAbstractItemView::NoEditTriggers - - - true - - - - + @@ -267,6 +146,130 @@ + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + Connected with <server> as <user> + + + Qt::RichText + + + true + + + true + + + + + + + ... + + + + + + + + + + + + + 0 + 0 + + + + + + + Storage space: ... + + + Qt::PlainText + + + false + + + + + + + false + + + + 0 + 0 + + + + + 16777215 + 7 + + + + 100 + + + -1 + + + false + + + + + + + + + + 0 + 5 + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + true + + + + + + @@ -280,6 +283,12 @@ QTreeView
folderstatusview.h
+ + KMessageWidget + QWidget +
kmessagewidget.h
+ 1 +
diff --git a/src/libsync/clientsideencryption.cpp b/src/libsync/clientsideencryption.cpp index f0d3bc9dc..5c5f23c9e 100644 --- a/src/libsync/clientsideencryption.cpp +++ b/src/libsync/clientsideencryption.cpp @@ -958,6 +958,7 @@ void ClientSideEncryption::encryptPrivateKey() { QStringList list = WordList::getRandomWords(12); _mnemonic = list.join(' '); + _newMnemonicGenerated = true; qCInfo(lcCse()) << "mnemonic Generated:" << _mnemonic; emit mnemonicGenerated(_mnemonic); @@ -989,6 +990,11 @@ void ClientSideEncryption::encryptPrivateKey() job->start(); } +bool ClientSideEncryption::newMnemonicGenerated() const +{ + return _newMnemonicGenerated; +} + void ClientSideEncryption::decryptPrivateKey(const QByteArray &key) { QString msg = tr("Please enter your end to end encryption passphrase:
" "
" diff --git a/src/libsync/clientsideencryption.h b/src/libsync/clientsideencryption.h index 400260706..05425a26f 100644 --- a/src/libsync/clientsideencryption.h +++ b/src/libsync/clientsideencryption.h @@ -87,6 +87,8 @@ public: void forgetSensitiveData(); + bool newMnemonicGenerated() const; + public slots: void slotRequestMnemonic(); @@ -127,6 +129,7 @@ public: QSslKey _publicKey; QSslCertificate _certificate; QString _mnemonic; + bool _newMnemonicGenerated = false; }; /* Generates the Metadata for the folder */