Libkdepim

progressdialog.cpp
1/** -*- c++ -*-
2 * progressdialog.cpp
3 *
4 * SPDX-FileCopyrightText: 2004 Till Adam <adam@kde.org>
5 * SPDX-FileCopyrightText: 2004 David Faure <faure@kde.org>
6 *
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10#include "progressdialog.h"
11#include "ssllabel.h"
12
13#include <KLocalizedString>
14#include <QHBoxLayout>
15
16#include <QCloseEvent>
17#include <QFrame>
18#include <QLabel>
19#include <QLayout>
20#include <QObject>
21#include <QProgressBar>
22#include <QPushButton>
23#include <QScrollBar>
24#include <QTimer>
25#include <QVBoxLayout>
26#include <chrono>
27
28using namespace std::chrono_literals;
29
30using namespace KPIM;
31static const int MAX_LABEL_WIDTH = 650;
32
33class KPIM::OverlayWidgetPrivate
34{
35public:
36 OverlayWidgetPrivate() = default;
37
38 QWidget *mAlignWidget = nullptr;
39};
40
41OverlayWidget::OverlayWidget(QWidget *alignWidget, QWidget *parent)
42 : QFrame(parent)
43 , d(new KPIM::OverlayWidgetPrivate)
44{
45 setAlignWidget(alignWidget);
46 setLayout(new QHBoxLayout(this));
47}
48
49OverlayWidget::~OverlayWidget() = default;
50
51QWidget *OverlayWidget::alignWidget() const
52{
53 return d->mAlignWidget;
54}
55
56void OverlayWidget::reposition()
57{
58 if (!d->mAlignWidget) {
59 return;
60 }
61 // p is in the alignWidget's coordinates
62 QPoint p;
63 // We are always above the alignWidget, right-aligned with it for
64 // LTR locales, and left-aligned for RTL locales (default value=0).
66 p.setX(d->mAlignWidget->width() - width());
67 }
68 p.setY(-height());
69 // Position in the toplevelwidget's coordinates
70 QPoint pTopLevel = d->mAlignWidget->mapTo(topLevelWidget(), p);
71 // Position in the widget's parentWidget coordinates
72 QPoint pParent = parentWidget()->mapFrom(topLevelWidget(), pTopLevel);
73 // Move 'this' to that position.
74 move(pParent);
75}
76
77void OverlayWidget::setAlignWidget(QWidget *w)
78{
79 if (w == d->mAlignWidget) {
80 return;
81 }
82
83 if (d->mAlignWidget) {
84 d->mAlignWidget->removeEventFilter(this);
85 }
86
87 d->mAlignWidget = w;
88
89 if (d->mAlignWidget) {
90 d->mAlignWidget->installEventFilter(this);
91 }
92
93 reposition();
94}
95
96bool OverlayWidget::eventFilter(QObject *o, QEvent *e)
97{
98 if (o == d->mAlignWidget && (e->type() == QEvent::Move || e->type() == QEvent::Resize)) {
99 reposition();
100 }
101 return QFrame::eventFilter(o, e);
102}
103
104void OverlayWidget::resizeEvent(QResizeEvent *ev)
105{
106 reposition();
108}
109
110TransactionItemView::TransactionItemView(QWidget *parent, const QString &name)
111 : QScrollArea(parent)
112 , mBigBox(new QWidget(this))
113{
114 setObjectName(name);
115 setFrameStyle(NoFrame);
116 auto mBigBoxVBoxLayout = new QVBoxLayout(mBigBox);
117 mBigBoxVBoxLayout->setContentsMargins(0, 0, 0, 0);
118 setWidget(mBigBox);
119 setWidgetResizable(true);
121}
122
123TransactionItemView::~TransactionItemView()
124{
125 mBigBox = nullptr;
126}
127
128TransactionItem *TransactionItemView::addTransactionItem(ProgressItem *item, bool first)
129{
130 auto ti = new TransactionItem(mBigBox, item, first);
131 mBigBox->layout()->addWidget(ti);
132
133 resize(mBigBox->width(), mBigBox->height());
134
135 return ti;
136}
137
138void TransactionItemView::resizeEvent(QResizeEvent *event)
139{
140 // Tell the layout in the parent (progressdialog) that our size changed
142
143 const QSize sz = parentWidget()->sizeHint();
144 int currentWidth = parentWidget()->width();
145
146 // Don't resize to sz.width() every time when it only reduces a little bit
147 if (currentWidth < sz.width() || currentWidth > sz.width() + 100) {
148 currentWidth = sz.width();
149 }
150 parentWidget()->resize(currentWidth, sz.height());
151
153}
154
155QSize TransactionItemView::sizeHint() const
156{
157 return minimumSizeHint();
158}
159
160QSize TransactionItemView::minimumSizeHint() const
161{
162 const int f = 2 * frameWidth();
163 // Make room for a vertical scrollbar in all cases, to avoid a horizontal one
164 const int vsbExt = verticalScrollBar()->sizeHint().width();
165 const int minw = topLevelWidget()->width() / 3;
166 const int maxh = topLevelWidget()->height() / 2;
167 QSize sz(mBigBox->minimumSizeHint());
168 sz.setWidth(qMax(sz.width(), minw) + f + vsbExt);
169 sz.setHeight(qMin(sz.height(), maxh) + f);
170 return sz;
171}
172
173void TransactionItemView::slotLayoutFirstItem()
174{
175 if (!mBigBox)
176 return;
177 // This slot is called whenever a TransactionItem is deleted, so this is a
178 // good place to call updateGeometry(), so our parent takes the new size
179 // into account and resizes.
181
182 /*
183 The below relies on some details in Qt's behaviour regarding deleting
184 objects. This slot is called from the destroyed signal of an item just
185 going away. That item is at that point still in the list of children, but
186 since the vtable is already gone, it will have type QObject. The first
187 one with both the right name and the right class therefore is what will
188 be the first item very shortly. That's the one we want to remove the
189 hline for.
190 */
191 auto ti = mBigBox->findChild<KPIM::TransactionItem *>(QStringLiteral("TransactionItem"));
192 if (ti) {
193 ti->hideHLine();
194 }
195}
196
197// ----------------------------------------------------------------------------
198
199TransactionItem::TransactionItem(QWidget *parent, ProgressItem *item, bool first)
200 : QWidget(parent)
201 , mItem(item)
202{
203 auto vboxLayout = new QVBoxLayout(this);
204 vboxLayout->setSpacing(2);
205 vboxLayout->setContentsMargins(2, 2, 2, 2);
207
208 mFrame = new QFrame(this);
209 mFrame->setFrameShape(QFrame::HLine);
210 mFrame->setFrameShadow(QFrame::Raised);
211 mFrame->show();
212 layout()->addWidget(mFrame);
213
214 auto h = new QWidget(this);
215 auto hHBoxLayout = new QHBoxLayout(h);
216 hHBoxLayout->setContentsMargins(0, 0, 0, 0);
217 hHBoxLayout->setSpacing(5);
218 layout()->addWidget(h);
219
220 mItemLabel = new QLabel(fontMetrics().elidedText(item->label(), Qt::ElideRight, MAX_LABEL_WIDTH), h);
221 h->layout()->addWidget(mItemLabel);
223
224 mProgress = new QProgressBar(h);
225 hHBoxLayout->addWidget(mProgress);
226 mProgress->setFormat(i18nc("Percent value; %p is the value, % is the percent sign", "%p%"));
227 mProgress->setMaximum(100);
228 mProgress->setValue(item->progress());
229 h->layout()->addWidget(mProgress);
230
231 if (item->canBeCanceled()) {
232 mCancelButton = new QPushButton(QIcon::fromTheme(QStringLiteral("dialog-cancel")), QString(), h);
233 hHBoxLayout->addWidget(mCancelButton);
234 mCancelButton->setToolTip(i18nc("@info:tooltip", "Cancel this operation."));
235 connect(mCancelButton, &QAbstractButton::clicked, this, &TransactionItem::slotItemCanceled);
236 h->layout()->addWidget(mCancelButton);
237 }
238
239 h = new QWidget(this);
240 hHBoxLayout = new QHBoxLayout(h);
241 hHBoxLayout->setContentsMargins(0, 0, 0, 0);
242 hHBoxLayout->setSpacing(5);
244 layout()->addWidget(h);
245 mSSLLabel = new SSLLabel(h);
246 hHBoxLayout->addWidget(mSSLLabel);
247 mSSLLabel->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
248 h->layout()->addWidget(mSSLLabel);
249 mItemStatus = new QLabel(h);
250 hHBoxLayout->addWidget(mItemStatus);
251 mItemStatus->setTextFormat(Qt::RichText);
252 mItemStatus->setText(fontMetrics().elidedText(item->status(), Qt::ElideRight, MAX_LABEL_WIDTH));
253 h->layout()->addWidget(mItemStatus);
254 setCryptoStatus(item->cryptoStatus());
255 if (first) {
256 hideHLine();
257 }
258}
259
260KPIM::TransactionItem::~TransactionItem() = default;
261
262void TransactionItem::hideHLine()
263{
264 mFrame->hide();
265}
266
267void TransactionItem::setProgress(int progress)
268{
269 mProgress->setValue(progress);
270}
271
272void TransactionItem::setLabel(const QString &label)
273{
274 mItemLabel->setText(fontMetrics().elidedText(label, Qt::ElideRight, MAX_LABEL_WIDTH));
275}
276
277void TransactionItem::setStatus(const QString &status)
278{
279 mItemStatus->setText(fontMetrics().elidedText(status, Qt::ElideRight, MAX_LABEL_WIDTH));
280}
281
282void TransactionItem::setCryptoStatus(KPIM::ProgressItem::CryptoStatus status)
283{
284 switch (status) {
285 case KPIM::ProgressItem::Encrypted:
286 mSSLLabel->setEncrypted(SSLLabel::Encrypted);
287 break;
288 case KPIM::ProgressItem::Unencrypted:
289 mSSLLabel->setEncrypted(SSLLabel::Unencrypted);
290 break;
291 case KPIM::ProgressItem::Unknown:
292 mSSLLabel->setEncrypted(SSLLabel::Unknown);
293 break;
294 }
295 mSSLLabel->setState(mSSLLabel->lastState());
296}
297
298void TransactionItem::setTotalSteps(int totalSteps)
299{
300 mProgress->setMaximum(totalSteps);
301}
302
303void TransactionItem::slotItemCanceled()
304{
305 if (mItem) {
306 mItem->cancel();
307 }
308}
309
310void TransactionItem::addSubTransaction(ProgressItem *item)
311{
312 Q_UNUSED(item)
313}
314
315// ---------------------------------------------------------------------------
316
317ProgressDialog::ProgressDialog(QWidget *alignWidget, QWidget *parent)
318 : OverlayWidget(alignWidget, parent)
319{
320 // Qt Bug: Sunken is not applied for RTL layouts correctly (is not mirrored).
321 // For now let's just use Plain, which is fine for this.
322 if (layoutDirection() == Qt::LeftToRight) {
323 setFrameStyle(QFrame::Panel | QFrame::Sunken); // QFrame
324 } else {
325 setFrameStyle(QFrame::Panel | QFrame::Plain); // QFrame
326 }
327
328 setAutoFillBackground(true);
329
330 mScrollView = new TransactionItemView(this, QStringLiteral("ProgressScrollView"));
331 layout()->addWidget(mScrollView);
332 /*
333 * Get the singleton ProgressManager item which will inform us of
334 * appearing and vanishing items.
335 */
337 connect(pm, &ProgressManager::progressItemAdded, this, &ProgressDialog::slotTransactionAdded);
338 connect(pm, &ProgressManager::progressItemCompleted, this, &ProgressDialog::slotTransactionCompleted);
339 connect(pm, &ProgressManager::progressItemProgress, this, &ProgressDialog::slotTransactionProgress);
340 connect(pm, &ProgressManager::progressItemStatus, this, &ProgressDialog::slotTransactionStatus);
341 connect(pm, &ProgressManager::progressItemLabel, this, &ProgressDialog::slotTransactionLabel);
342 connect(pm, &ProgressManager::progressItemCryptoStatus, this, &ProgressDialog::slotTransactionCryptoStatus);
343 connect(pm, &ProgressManager::progressItemUsesBusyIndicator, this, &ProgressDialog::slotTransactionUsesBusyIndicator);
344 connect(pm, &ProgressManager::showProgressDialog, this, &ProgressDialog::slotShow);
345}
346
347void ProgressDialog::closeEvent(QCloseEvent *e)
348{
349 e->accept();
350 hide();
351}
352
353bool ProgressDialog::wasLastShown() const
354{
355 return mWasLastShown;
356}
357
358/*
359 * Destructor
360 */
361ProgressDialog::~ProgressDialog()
362{
363 // no need to delete child widgets.
364}
365
366void ProgressDialog::setShowTypeProgressItem(unsigned int type)
367{
368 mShowTypeProgressItem = type;
369}
370
371void ProgressDialog::slotTransactionAdded(ProgressItem *item)
372{
373 if (item->typeProgressItem() == mShowTypeProgressItem) {
374 if (item->parent()) {
375 if (TransactionItem *parent = mTransactionsToListviewItems.value(item->parent())) {
376 parent->addSubTransaction(item);
377 }
378 } else {
379 const bool first = mTransactionsToListviewItems.empty();
380 TransactionItem *ti = mScrollView->addTransactionItem(item, first);
381 if (ti) {
382 mTransactionsToListviewItems.insert(item, ti);
383 }
384 if (first && mWasLastShown) {
385 QTimer::singleShot(1s, this, &ProgressDialog::slotShow);
386 }
387 }
388 }
389}
390
391void ProgressDialog::slotTransactionCompleted(ProgressItem *item)
392{
393 if (TransactionItem *ti = mTransactionsToListviewItems.value(item)) {
394 mTransactionsToListviewItems.remove(item);
395 ti->setItemComplete();
397 // see the slot for comments as to why that works
398 connect(ti, &QObject::destroyed, mScrollView, &TransactionItemView::slotLayoutFirstItem);
399 }
400 // This was the last item, hide.
401 if (mTransactionsToListviewItems.empty()) {
402 QTimer::singleShot(3s, this, &ProgressDialog::slotHide);
403 }
404}
405
406void ProgressDialog::slotTransactionCanceled(ProgressItem *)
407{
408}
409
410void ProgressDialog::slotTransactionProgress(ProgressItem *item, unsigned int progress)
411{
412 if (TransactionItem *ti = mTransactionsToListviewItems.value(item)) {
413 ti->setProgress(progress);
414 }
415}
416
417void ProgressDialog::slotTransactionStatus(ProgressItem *item, const QString &status)
418{
419 if (TransactionItem *ti = mTransactionsToListviewItems.value(item)) {
420 ti->setStatus(status);
421 }
422}
423
424void ProgressDialog::slotTransactionLabel(ProgressItem *item, const QString &label)
425{
426 if (TransactionItem *ti = mTransactionsToListviewItems.value(item)) {
427 ti->setLabel(label);
428 }
429}
430
431void ProgressDialog::slotTransactionCryptoStatus(ProgressItem *item, KPIM::ProgressItem::CryptoStatus value)
432{
433 if (TransactionItem *ti = mTransactionsToListviewItems.value(item)) {
434 ti->setCryptoStatus(value);
435 }
436}
437
438void ProgressDialog::slotTransactionUsesBusyIndicator(KPIM::ProgressItem *item, bool value)
439{
440 if (TransactionItem *ti = mTransactionsToListviewItems.value(item)) {
441 if (value) {
442 ti->setTotalSteps(0);
443 } else {
444 ti->setTotalSteps(100);
445 }
446 }
447}
448
449void ProgressDialog::slotShow()
450{
451 setVisible(true);
452}
453
454void ProgressDialog::slotHide()
455{
456 // check if a new item showed up since we started the timer. If not, hide
457 if (mTransactionsToListviewItems.isEmpty()) {
458 setVisible(false);
459 }
460}
461
462void ProgressDialog::slotClose()
463{
464 mWasLastShown = false;
465 setVisible(false);
466}
467
468void ProgressDialog::setVisible(bool b)
469{
471 Q_EMIT visibilityChanged(b);
472}
473
474void ProgressDialog::slotToggleVisibility()
475{
476 /* Since we are only hiding with a timeout, there is a short period of
477 * time where the last item is still visible, but clicking on it in
478 * the statusbarwidget should not display the dialog, because there
479 * are no items to be shown anymore. Guard against that.
480 */
481 if (!isHidden() || !mTransactionsToListviewItems.isEmpty()) {
482 const bool showNow = isHidden();
483 setVisible(showNow);
484 mWasLastShown = showNow;
485 }
486}
487
488#include "moc_progressdialog.cpp"
The ProgressItem class.
CryptoStatus cryptoStatus() const
unsigned int progress() const
const QString & label() const
ProgressItem * parent() const
const QString & status() const
The ProgressManager singleton keeps track of all ongoing transactions and notifies observers (progres...
void progressItemAdded(KPIM::ProgressItem *)
void progressItemLabel(KPIM::ProgressItem *, const QString &)
static ProgressManager * instance()
void progressItemUsesBusyIndicator(KPIM::ProgressItem *, bool)
void progressItemStatus(KPIM::ProgressItem *, const QString &)
void progressItemCryptoStatus(KPIM::ProgressItem *, KPIM::ProgressItem::CryptoStatus)
void progressItemProgress(KPIM::ProgressItem *, unsigned int)
void progressItemCompleted(KPIM::ProgressItem *)
void showProgressDialog()
Emitted when an operation requests the listeners to be shown.
Q_SCRIPTABLE CaptureState status()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
Class KCheckComboBox::KCheckComboBoxPrivate.
void clicked(bool checked)
QScrollBar * verticalScrollBar() const const
void accept()
Type type() const const
QIcon fromTheme(const QString &name)
void setText(const QString &)
void addWidget(QWidget *w)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
void destroyed(QObject *obj)
virtual bool eventFilter(QObject *watched, QEvent *event)
T findChild(const QString &name, Qt::FindChildOptions options) const const
QObject * parent() const const
void setX(int x)
void setY(int y)
void setMaximum(int maximum)
void setValue(int value)
virtual bool event(QEvent *e) override
virtual void resizeEvent(QResizeEvent *) override
virtual QSize sizeHint() const const override
int height() const const
void setHeight(int height)
void setWidth(int width)
int width() const const
LeftToRight
ElideRight
RichText
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QWidget * topLevelWidget() const const
QFontMetrics fontMetrics() const const
void hide()
bool isHidden() const const
QLayout * layout() const const
QPoint mapFrom(const QWidget *parent, const QPoint &pos) const const
QWidget * parentWidget() const const
void move(const QPoint &)
virtual void resizeEvent(QResizeEvent *event)
void resize(const QSize &)
void updateGeometry()
virtual void setVisible(bool visible)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:58:12 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.