From 86021b24d98c385b10ccc2fefab5cf22f3a03b54 Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Fri, 17 May 2013 21:58:49 -0300 Subject: Borrowed the KMessageWidget code to better show passive information. Borrowed the code from KMessageWidget from Aurelian Gateau, Kdelibs, to better show passive information and notifications. instead of a popup blowing in the user's face, a nice, animated and well designed widget will gracefully fade-in, show the notes, and fade out when not needed anymore. Signed-off-by: Tomaz Canabrava --- qt-ui/globe.cpp | 24 ++- qt-ui/globe.h | 3 + qt-ui/kmessagewidget.cpp | 421 +++++++++++++++++++++++++++++++++++++++++++++++ qt-ui/kmessagewidget.h | 241 +++++++++++++++++++++++++++ qt-ui/mainwindow.cpp | 8 +- qt-ui/mainwindow.ui | 23 ++- 6 files changed, 710 insertions(+), 10 deletions(-) create mode 100644 qt-ui/kmessagewidget.cpp create mode 100644 qt-ui/kmessagewidget.h (limited to 'qt-ui') diff --git a/qt-ui/globe.cpp b/qt-ui/globe.cpp index 93d1ab7c4..43763e338 100644 --- a/qt-ui/globe.cpp +++ b/qt-ui/globe.cpp @@ -1,4 +1,5 @@ #include "globe.h" +#include "kmessagewidget.h" #include "../dive.h" #include @@ -40,10 +41,13 @@ void GlobeGPS::reload() model()->treeModel()->removeDocument(loadedDives); delete loadedDives; } + if (editingDiveCoords){ editingDiveCoords = 0; } + messageWidget->animatedHide(); + loadedDives = new GeoDataDocument; int idx = 0; @@ -64,6 +68,13 @@ void GlobeGPS::reload() void GlobeGPS::centerOn(dive* dive) { + // dive has changed, if we had the 'editingDive', hide it. + if(messageWidget->isVisible()){ + messageWidget->animatedHide(); + } + + editingDiveCoords = 0; + qreal longitude = dive->longitude.udeg / 1000000.0; qreal latitude = dive->latitude.udeg / 1000000.0; @@ -77,9 +88,9 @@ void GlobeGPS::centerOn(dive* dive) void GlobeGPS::prepareForGetDiveCoordinates(dive* dive) { - QMessageBox::warning(parentWidget(), - tr("This dive has no location!"), - tr("Move the planet to the desired position, then \n double-click to set the new location of this dive.")); + messageWidget->setMessageType(KMessageWidget::Warning); + messageWidget->setText(QObject::tr("This dive has no location! Please move the planet to the desired position, then double-click to set the new location for this dive.")); + messageWidget->animatedShow(); editingDiveCoords = dive; } @@ -101,6 +112,7 @@ void GlobeGPS::changeDiveGeoPosition(qreal lon, qreal lat, GeoDataCoordinates::U centerOn(lon, lat, true); reload(); editingDiveCoords = 0; + messageWidget->animatedHide(); } void GlobeGPS::mousePressEvent(QMouseEvent* event) @@ -111,3 +123,9 @@ void GlobeGPS::mousePressEvent(QMouseEvent* event) } } +void GlobeGPS::setMessageWidget(KMessageWidget* globeMessage) +{ + messageWidget = globeMessage; +} + + diff --git a/qt-ui/globe.h b/qt-ui/globe.h index 5f207a502..650d55716 100644 --- a/qt-ui/globe.h +++ b/qt-ui/globe.h @@ -6,6 +6,7 @@ #include +class KMessageWidget; using namespace Marble; struct dive; @@ -25,9 +26,11 @@ private: GeoDataDocument *loadedDives; QStringList diveLocations; struct dive* editingDiveCoords; + KMessageWidget* messageWidget; public Q_SLOTS: void changeDiveGeoPosition(qreal lon,qreal lat,GeoDataCoordinates::Unit); + void setMessageWidget(KMessageWidget* globeMessage); }; diff --git a/qt-ui/kmessagewidget.cpp b/qt-ui/kmessagewidget.cpp new file mode 100644 index 000000000..653d52f4f --- /dev/null +++ b/qt-ui/kmessagewidget.cpp @@ -0,0 +1,421 @@ +/* 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(toggled(bool)), q, SLOT(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); + + 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); + + Q_FOREACH(QToolButton* button, buttons) { + layout->addWidget(button); + } + + layout->addWidget(closeButton); + }; + + 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(); +} + +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(Qt::green); + fg = QColor(Qt::white); + break; + + case Information: + bg1 = QColor(Qt::blue); + fg = QColor(Qt::black); + break; + + case Warning: + bg1 = QColor(Qt::yellow); + fg = QColor(Qt::black); + break; + + case Error: + bg1 = QColor(Qt::red); + fg = QColor(Qt::black); + break; + } + + // Colors + bg0 = bg1.lighter(110); + bg2 = bg1.darker(110); + border = QColor(Qt::black); + 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()) { + 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/qt-ui/kmessagewidget.h b/qt-ui/kmessagewidget.h new file mode 100644 index 000000000..e35676a59 --- /dev/null +++ b/qt-ui/kmessagewidget.h @@ -0,0 +1,241 @@ +/* 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 + +#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); + +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 */ diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index c5a4e5ce3..e5cf5ed37 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -32,16 +32,18 @@ MainWindow::MainWindow() : ui(new Ui::MainWindow()) readSettings(); setWindowIcon(QIcon(":subsurface-icon")); connect(ui->ListWidget, SIGNAL(currentDiveChanged(int)), this, SLOT(current_dive_changed(int))); + ui->globeMessage->hide(); + ui->globe->setMessageWidget(ui->globeMessage); ui->ProfileWidget->setFocusProxy(ui->ListWidget); ui->ListWidget->reload(); ui->ListWidget->setFocus(); - ui->widget->reload(); + ui->globe->reload(); } void MainWindow::current_dive_changed(int divenr) { select_dive(divenr); - ui->widget->centerOn(get_dive(selected_dive)); + ui->globe->centerOn(get_dive(selected_dive)); redrawProfile(); ui->InfoWidget->updateDiveInfo(divenr); } @@ -79,7 +81,7 @@ void MainWindow::on_actionOpen_triggered() process_dives(FALSE, FALSE); ui->InfoWidget->reload(); - ui->widget->reload(); + ui->globe->reload(); ui->ListWidget->reload(); ui->ListWidget->setFocus(); } diff --git a/qt-ui/mainwindow.ui b/qt-ui/mainwindow.ui index ab0cd5f49..36f594d2d 100644 --- a/qt-ui/mainwindow.ui +++ b/qt-ui/mainwindow.ui @@ -14,8 +14,8 @@ MainWindow - - + + Qt::Vertical @@ -27,7 +27,7 @@ - + Qt::Horizontal @@ -82,7 +82,16 @@ true - + + + + + + + + + + @@ -377,6 +386,12 @@
globe.h
1 + + KMessageWidget + QWidget +
kmessagewidget.h
+ 1 +
-- cgit v1.2.3-70-g09d2