diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,6 +110,7 @@ find_package(KF5WidgetsAddons ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5WindowSystem ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5XmlGui ${KF5_VERSION} CONFIG REQUIRED) +find_package(KF5Plasma ${KF5_VERSION} CONFIG REQUIRED) # Find KdepimLibs Package find_package(KF5Akonadi ${AKONADI_VERSION} CONFIG REQUIRED) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -144,6 +144,7 @@ widgets/statusbarlabeltoggledstate.cpp widgets/kactionmenutransport.cpp widgets/kactionmenuaccount.cpp + widgets/busyindicator.cpp ) set(kmailprivate_tag_LIB_SRCS tag/tagactionmanager.cpp @@ -324,6 +325,7 @@ KF5::AkonadiSearchPIM KF5::WebEngineViewer KF5::SyntaxHighlighting + KF5::Plasma ) target_include_directories(kmailprivate PUBLIC $) target_include_directories(kmailprivate PUBLIC $) diff --git a/src/kmreaderwin.h b/src/kmreaderwin.h --- a/src/kmreaderwin.h +++ b/src/kmreaderwin.h @@ -33,6 +33,7 @@ class QAction; class KToggleAction; class QMenu; +class QStackedWidget; namespace MessageViewer { class CSSHelper; } @@ -43,6 +44,9 @@ class KJob; +class BusyWidget; +class OfflineWidget; + /** This class implements a "reader window", that is a window used for reading or viewing messages. @@ -243,6 +247,9 @@ QMenu *mViewHtmlOptions; + QStackedWidget *mStack; + BusyWidget *mBusyWidget; + OfflineWidget *mOfflineWidget; MessageViewer::Viewer *mViewer; }; diff --git a/src/kmreaderwin.cpp b/src/kmreaderwin.cpp --- a/src/kmreaderwin.cpp +++ b/src/kmreaderwin.cpp @@ -28,6 +28,7 @@ #include "mailcommon/mailkernel.h" #include "dialog/addemailtoexistingcontactdialog.h" #include "job/addemailtoexistingcontactjob.h" +#include "widgets/busyindicator.h" #include "kmail-version.h" #include @@ -73,8 +74,12 @@ #include #include #include +#include +#include +#include #include +#include // X headers... #undef Never @@ -85,6 +90,109 @@ using namespace KMail; using namespace MailCommon; +class BusyWidget : public QWidget { + Q_OBJECT +public: + explicit BusyWidget(QWidget *parent = nullptr) + : QWidget(parent) + { + mLabel = new QLabel; + mLabel->setAlignment(Qt::AlignCenter); + + mBusy = new BusyIndicator; + mBusy->setMinimumWidth(64); + mBusy->setMinimumHeight(64); + + auto l = new QGridLayout(this); + l->addWidget(mBusy, 1, 0, Qt::AlignCenter); + l->addWidget(mLabel, 2, 0, Qt::AlignCenter); + l->setRowStretch(0, 2); + l->setRowStretch(3, 2); + } + + void setText(const QString &text) + { + mLabel->setText(text); + } + +protected: + void showEvent(QShowEvent *event) override + { + mBusy->setActive(true); + QWidget::showEvent(event); + } + + void hideEvent(QHideEvent *event) override + { + mBusy->setActive(false); + QWidget::hideEvent(event); + } + +private: + BusyIndicator *mBusy; + QLabel *mLabel; +}; + + +class OfflineWidget : public QWidget { + Q_OBJECT +public: + explicit OfflineWidget(QWidget *parent = nullptr) + : QWidget(parent) + , mOnlineMode(MessageViewer::Viewer::AllResources) + { + auto l = new QVBoxLayout; + setLayout(l); + l->addStretch(2); + + auto h = new QHBoxLayout; + h->addStretch(2); + auto icon = new QLabel; + icon->setPixmap(QIcon::fromTheme(QStringLiteral("offline")).pixmap(42)); + h->addWidget(icon); + + mLabel = new QLabel; + mLabel->setAlignment(Qt::AlignCenter); + h->addWidget(mLabel); + h->addStretch(2); + + l->addLayout(h); + + mOnlineBtn = new QPushButton; + connect(mOnlineBtn, &QPushButton::clicked, + this, [this]() { + Q_EMIT goOnline(mOnlineMode); + }); + l->addWidget(mOnlineBtn, 0, Qt::AlignCenter); + + l->addStretch(2); + } + + void setText(const QString &labelText, const QString &buttonText, + MessageViewer::Viewer::ResourceOnlineMode onlineMode) + { + mLabel->setText(labelText); + mOnlineBtn->setText(buttonText); + mOnlineMode = onlineMode; + } + +Q_SIGNALS: + void goOnline(MessageViewer::Viewer::ResourceOnlineMode onlineMode); + +private: + QLabel *mLabel; + QPushButton *mOnlineBtn; + MessageViewer::Viewer::ResourceOnlineMode mOnlineMode; +}; + +namespace { + +static const int Stack_ViewerIndex = 0; +static const int Stack_BusyIndex = 1; +static const int Stack_OfflineIndex = 2; + +} + KMReaderWin::KMReaderWin(QWidget *aParent, QWidget *mainWindow, KActionCollection *actionCollection) : QWidget(aParent) , mMainWindow(mainWindow) @@ -101,10 +209,13 @@ createActions(); QVBoxLayout *vlay = new QVBoxLayout(this); vlay->setContentsMargins(0, 4, 0, 0); + + mStack = new QStackedWidget(this); + vlay->addWidget(mStack); + mViewer = new Viewer(this, mainWindow, mActionCollection); connect(mViewer, SIGNAL(urlClicked(Akonadi::Item,QUrl)), this, SLOT(slotUrlClicked(Akonadi::Item,QUrl))); connect(mViewer, &Viewer::requestConfigSync, kmkernel, &KMKernel::slotRequestConfigSync, Qt::QueuedConnection); // happens anyway on shutdown, so we can skip it there with using a queued connection - connect(mViewer, &Viewer::makeResourceOnline, kmkernel, &KMKernel::makeResourceOnline); connect(mViewer, &MessageViewer::Viewer::showReader, this, &KMReaderWin::slotShowReader); connect(mViewer, &MessageViewer::Viewer::showMessage, this, &KMReaderWin::slotShowMessage); connect(mViewer, &MessageViewer::Viewer::showStatusBarMessage, this, &KMReaderWin::showStatusBarMessage); @@ -114,7 +225,18 @@ mViewer->addMessageLoadedHandler(new MessageViewer::MarkMessageReadHandler(this)); mViewer->addMessageLoadedHandler(new MailCommon::SendMdnHandler(kmkernel, this)); - vlay->addWidget(mViewer); + mStack->addWidget(mViewer); + + mBusyWidget = new BusyWidget(this); + mStack->addWidget(mBusyWidget); + + mOfflineWidget = new OfflineWidget(this); + connect(mOfflineWidget, &OfflineWidget::goOnline, + kmkernel, &KMKernel::makeResourceOnline); + mStack->addWidget(mOfflineWidget); + + mStack->setCurrentIndex(Stack_ViewerIndex); + readConfig(); } @@ -313,32 +435,26 @@ void KMReaderWin::displayBusyPage() { - displaySplashPage(QStringLiteral("status.html"), { - { QStringLiteral("title"), i18n("Retrieving Folder Contents") }, - { QStringLiteral("subtext"), i18n("Please wait . . .") } - }); + mBusyWidget->setText(i18n("Loading message...")); + mStack->setCurrentIndex(Stack_BusyIndex); + mViewer->clear(MimeTreeParser::Delayed); } void KMReaderWin::displayOfflinePage() { - displaySplashPage(QStringLiteral("status.html"), { - { QStringLiteral("title"), i18n("Offline") }, - { - QStringLiteral("subtext"), i18n("KMail is currently in offline mode. " - "Click here to go online . . .

") - } - }); + mOfflineWidget->setText(i18n("KMail is currently in offline mode."), + i18n("Go online"), MessageViewer::Viewer::AllResources); + mStack->setCurrentIndex(Stack_OfflineIndex); + mViewer->clear(MimeTreeParser::Delayed); } void KMReaderWin::displayResourceOfflinePage() { - displaySplashPage(QStringLiteral("status.html"), { - { QStringLiteral("title"), i18n("Offline") }, - { - QStringLiteral("subtext"), i18n("Account is currently in offline mode. " - "Click here to go online . . .

") - } - }); + mOfflineWidget->setText(i18n("Account is currently in offline mode."), + i18n("Switch account online"), + MessageViewer::Viewer::SelectedResource); + mStack->setCurrentIndex(Stack_OfflineIndex); + mViewer->clear(MimeTreeParser::Delayed); } void KMReaderWin::displayAboutPage() @@ -661,17 +777,20 @@ void KMReaderWin::clear(bool force) { mViewer->clear(force ? MimeTreeParser::Force : MimeTreeParser::Delayed); + mStack->setCurrentIndex(Stack_ViewerIndex); } void KMReaderWin::setMessage(const Akonadi::Item &item, MimeTreeParser::UpdateMode updateMode) { qCDebug(KMAIL_LOG) << Q_FUNC_INFO << parentWidget(); mViewer->setMessageItem(item, updateMode); + mStack->setCurrentIndex(Stack_ViewerIndex); } void KMReaderWin::setMessage(const KMime::Message::Ptr &message) { mViewer->setMessage(message); + mStack->setCurrentIndex(Stack_ViewerIndex); } QUrl KMReaderWin::urlClicked() const @@ -890,3 +1009,5 @@ deleteLater(); } } + +#include "kmreaderwin.moc" diff --git a/src/widgets/busyindicator.h b/src/widgets/busyindicator.h new file mode 100644 --- /dev/null +++ b/src/widgets/busyindicator.h @@ -0,0 +1,35 @@ +#ifndef BUSYINDICATOR_H_ +#define BUSYINDICATOR_H_ + +#include + +namespace Plasma { +class Svg; +} + +class QTimer; + +class BusyIndicator : public QWidget +{ + Q_OBJECT + +public: + explicit BusyIndicator(QWidget *parent = nullptr); + ~BusyIndicator(); + + void setActive(bool active); + bool active() const; + +protected: + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + + int heightForWidth(int width) const override; + +private: + Plasma::Svg *mSvg; + QTimer *mTimer; + int mAngle; +}; + +#endif diff --git a/src/widgets/busyindicator.cpp b/src/widgets/busyindicator.cpp new file mode 100644 --- /dev/null +++ b/src/widgets/busyindicator.cpp @@ -0,0 +1,73 @@ +#include "busyindicator.h" + +#include +#include +#include + +#include + +namespace { + +static const int FPS = 20; +static const int StepAngle = 30; + +} + +BusyIndicator::BusyIndicator(QWidget *parent) + : QWidget(parent) + , mSvg(new Plasma::Svg(this)) + , mTimer(new QTimer(this)) + , mAngle(0) +{ + mSvg->setImagePath(QStringLiteral("widgets/busywidget")); + mSvg->setUsingRenderingCache(true); + mSvg->setContainsMultipleImages(true); + + connect(mTimer, &QTimer::timeout, + this, static_cast(&QWidget::repaint)); + mTimer->setInterval(1000 / FPS); +} + +BusyIndicator::~BusyIndicator() +{ + mTimer->stop(); +} + +void BusyIndicator::setActive(bool active) +{ + if (active && !mTimer->isActive()) { + mTimer->start(); + } else if (!active) { + mTimer->stop(); + } +} + +bool BusyIndicator::active() const +{ + return mTimer->isActive(); +} + +int BusyIndicator::heightForWidth(int width) const +{ + // always square + return width; +} + +void BusyIndicator::resizeEvent(QResizeEvent *event) +{ + mSvg->resize(event->size()); + QWidget::resizeEvent(event); +} + +void BusyIndicator::paintEvent(QPaintEvent *event) +{ + QWidget::paintEvent(event); + + QPainter painter(this); + mAngle = (mAngle + StepAngle) % 360; + const int w = width() / 2; + const int h = height() / 2; + painter.translate(w, h); + painter.rotate(mAngle); + mSvg->paint(&painter, -w , -h, width(), height()); +}