Marble

MapThemeDownloadDialog.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2013 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
4// SPDX-FileCopyrightText: 2013 Dennis Nienhüser <nienhueser@kde.org>
5//
6
7#include "MapThemeDownloadDialog.h"
8#include "ui_MapThemeDownloadDialog.h"
9
10#include "MarbleDirs.h"
11#include "MarbleWidget.h"
12#include "NewstuffModel.h"
13
14#include <QAbstractTextDocumentLayout>
15#include <QPainter>
16#include <QStyledItemDelegate>
17#include <QTextDocument>
18
19namespace Marble
20{
21
22class MapItemDelegate : public QStyledItemDelegate
23{
25
26public:
27 MapItemDelegate(QListView *view, NewstuffModel *newstuffModel, MarbleWidget *marbleWidget);
28 void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
29 QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
30
31protected:
32 bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override;
33
34private:
35 enum Element {
36 Icon,
37 Text,
38 InstallButton,
39 UpgradeButton,
40 OpenButton,
41 CancelButton,
42 RemoveButton,
43 ProgressReport
44 };
45
46 int buttonWidth(const QStyleOptionViewItem &option) const;
47 QStyleOptionButton button(Element element, const QStyleOptionViewItem &option) const;
48 QRect position(Element element, const QStyleOptionViewItem &option) const;
49 static QString text(const QModelIndex &index);
50 QListView *m_view;
51 NewstuffModel *m_newstuffModel;
52 mutable int m_buttonWidth;
53 int const m_margin;
54 int const m_iconSize;
55 MarbleWidget *m_marbleWidget;
56};
57
58class Q_DECL_HIDDEN MapThemeDownloadDialog::Private : public Ui::MapThemeDownloadDialog
59{
60public:
61 Private()
62 : m_model()
63 {
64 }
65
66 NewstuffModel m_model;
67};
68
69MapThemeDownloadDialog::MapThemeDownloadDialog(MarbleWidget *marbleWidget)
70 : QDialog(marbleWidget)
71 , d(new Private)
72{
73 d->setupUi(this);
74
75 d->m_model.setTargetDirectory(MarbleDirs::localPath() + QLatin1StringView("/maps"));
76 d->m_model.setProvider("https://marble.kde.org/maps-v3.xml");
77 d->m_model.setRegistryFile(MarbleDirs::localPath() + QLatin1StringView("/newstuff/marble-map-themes.knsregistry"), Marble::NewstuffModel::NameTag);
78
79 d->listView->setIconSize(QSize(130, 130));
80 d->listView->setAlternatingRowColors(true);
81 d->listView->setUniformItemSizes(false);
82 d->listView->setResizeMode(QListView::Adjust);
83 d->listView->setItemDelegate(new MapItemDelegate(d->listView, &d->m_model, marbleWidget));
84 d->listView->setModel(&d->m_model);
85}
86
87MapThemeDownloadDialog::~MapThemeDownloadDialog()
88{
89 delete d;
90}
91
92MapItemDelegate::MapItemDelegate(QListView *view, NewstuffModel *newstuffModel, MarbleWidget *marbleWidget)
93 : m_view(view)
94 , m_newstuffModel(newstuffModel)
95 , m_buttonWidth(0)
96 , m_margin(5)
97 , m_iconSize(16)
98 , m_marbleWidget(marbleWidget)
99{
100 // nothing to do
101}
102
103void MapItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
104{
105 QStyleOptionViewItem styleOption = option;
106 styleOption.text = QString();
108
110 if (styleOption.state & QStyle::State_Selected) {
111 paintContext.palette.setColor(QPalette::Text, styleOption.palette.color(QPalette::Active, QPalette::HighlightedText));
112 }
113
114 // Draw the map preview icon
115 QRect const iconRect = position(Icon, option);
116 auto const icon = index.data(Qt::DecorationRole).value<QIcon>();
117 painter->drawPixmap(iconRect, icon.pixmap(iconRect.size()));
118
119 // Draw summary, author, and similar information
120 QTextDocument document;
121 QRect const textRect = position(Text, option);
122 document.setTextWidth(textRect.width());
123 document.setDefaultFont(option.font);
124 document.setHtml(text(index));
125
126 painter->save();
127 painter->translate(textRect.topLeft());
128 painter->setClipRect(0, 0, textRect.width(), textRect.height());
129 document.documentLayout()->draw(painter, paintContext);
130 painter->restore();
131
132 // Draw buttons and installation progress
133 if (index.data(NewstuffModel::IsTransitioning).toBool()) {
134 qint64 total = qMax(qint64(1), index.data(NewstuffModel::PayloadSize).value<qint64>());
135 auto progress = index.data(NewstuffModel::DownloadedSize).value<qint64>();
136
137 QStyleOptionProgressBar progressBarOption;
138 progressBarOption.rect = position(ProgressReport, option);
139 progressBarOption.minimum = 0;
140 progressBarOption.maximum = 100;
141 progressBarOption.progress = (100.0 * progress / total);
142 progressBarOption.text = QString::number(progressBarOption.progress) + QLatin1Char('%');
143 progressBarOption.textVisible = true;
144 QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter);
145
146 QStyleOptionButton cancelButton = button(CancelButton, option);
147 QRect installRect = position(CancelButton, option);
148 cancelButton.rect = installRect;
149 QApplication::style()->drawControl(QStyle::CE_PushButton, &cancelButton, painter);
150 QRect buttonTextRect(installRect);
151 buttonTextRect.adjust(cancelButton.iconSize.width() + 4, 0, 0, 0);
152 painter->drawText(buttonTextRect, Qt::AlignCenter, cancelButton.text);
153 } else {
154 bool const installed = index.data(NewstuffModel::IsInstalled).toBool();
155 bool const upgradable = index.data(NewstuffModel::IsUpgradable).toBool();
156 Element element = InstallButton;
157 if (installed) {
158 element = upgradable ? UpgradeButton : OpenButton;
159 }
160 QStyleOptionButton actionButton = button(element, option);
161 QRect installRect = position(element, option);
162 actionButton.rect = installRect;
163 QApplication::style()->drawControl(QStyle::CE_PushButton, &actionButton, painter);
164 QRect buttonTextRect(installRect);
165 buttonTextRect.adjust(actionButton.iconSize.width() + 4, 0, 0, 0);
166 painter->drawText(buttonTextRect, Qt::AlignCenter, actionButton.text);
167
168 if (installed) {
169 QStyleOptionButton removeButton = button(RemoveButton, option);
170 QRect removeRect = position(RemoveButton, option);
171 removeButton.rect = removeRect;
172 QApplication::style()->drawControl(QStyle::CE_PushButton, &removeButton, painter);
173 buttonTextRect = removeRect;
174 buttonTextRect.adjust(removeButton.iconSize.width() + 4, 0, 0, 0);
175 painter->drawText(buttonTextRect, Qt::AlignCenter, removeButton.text);
176 }
177 }
178}
179
180QSize MapItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
181{
182 if (index.column() == 0) {
183 QSize const iconSize = option.decorationSize;
184 QTextDocument doc;
185 doc.setDefaultFont(option.font);
186 doc.setTextWidth(qMax(200, m_view->contentsRect().width() - iconSize.width() - buttonWidth(option) - 3 * m_margin));
187 doc.setHtml(text(index));
188 return QSize(iconSize.width() + doc.size().width() + buttonWidth(option) + 3 * m_margin, 2 + qMax(iconSize.height(), qRound(doc.size().height())));
189 }
190
191 return {};
192}
193
194bool MapItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *, const QStyleOptionViewItem &option, const QModelIndex &index)
195{
196 if ((event->type() == QEvent::MouseButtonRelease)) {
197 auto mouseEvent = static_cast<QMouseEvent *>(event);
198 if (index.data(NewstuffModel::IsTransitioning).toBool()) {
199 QRect cancelRect = position(CancelButton, option);
200 if (cancelRect.contains(mouseEvent->pos())) {
201 m_newstuffModel->cancel(index.row());
202 return true;
203 }
204 } else {
205 bool const installed = index.data(NewstuffModel::IsInstalled).toBool();
206 bool const upgradable = index.data(NewstuffModel::IsUpgradable).toBool();
207
208 if (!installed || upgradable) {
209 QRect installRect = position(InstallButton, option);
210 if (installRect.contains(mouseEvent->pos())) {
211 m_newstuffModel->install(index.row());
212 return true;
213 }
214 }
215
216 if (installed && !upgradable && m_marbleWidget) {
217 QRect openRect = position(OpenButton, option);
218 if (openRect.contains(mouseEvent->pos())) {
219 QStringList const files = index.data(NewstuffModel::InstalledFiles).toStringList();
220 for (const QString &file : files) {
221 if (file.endsWith(QLatin1StringView(".dgml"))) {
222 QFileInfo dgmlFile(file);
223 QDir baseDir = dgmlFile.dir();
224 baseDir.cdUp();
225 baseDir.cdUp();
226 int const index = baseDir.absolutePath().size();
227 QString const mapTheme = dgmlFile.absoluteFilePath().mid(index + 1);
228 m_marbleWidget->setMapThemeId(mapTheme);
229 return true;
230 }
231 }
232 }
233 }
234
235 if (installed) {
236 QRect removeRect = position(RemoveButton, option);
237 if (removeRect.contains(mouseEvent->pos())) {
238 m_newstuffModel->uninstall(index.row());
239 return true;
240 }
241 }
242 }
243 }
244
245 return false;
246}
247
248int MapItemDelegate::buttonWidth(const QStyleOptionViewItem &option) const
249{
250 if (m_buttonWidth <= 0) {
251 int const installWidth = option.fontMetrics.size(0, tr("Install")).width();
252 int const removeWidth = option.fontMetrics.size(0, tr("Remove")).width();
253 int const cancelWidth = option.fontMetrics.size(0, tr("Cancel")).width();
254 int const upgradeWidth = option.fontMetrics.size(0, tr("Upgrade")).width();
255 m_buttonWidth = 2 * m_iconSize + qMax(qMax(installWidth, removeWidth), qMax(cancelWidth, upgradeWidth));
256 }
257
258 return m_buttonWidth;
259}
260
261QStyleOptionButton MapItemDelegate::button(Element element, const QStyleOptionViewItem &option) const
262{
263 QStyleOptionButton result;
264 result.state = option.state;
265 result.state &= ~QStyle::State_HasFocus;
266
267 result.palette = option.palette;
268 result.features = QStyleOptionButton::None;
269
270 switch (element) {
271 case InstallButton:
272 result.text = tr("Install");
273 result.icon = QIcon(QStringLiteral(":/marble/dialog-ok.png"));
274 result.iconSize = QSize(m_iconSize, m_iconSize);
275 break;
276 case UpgradeButton:
277 result.text = tr("Update");
278 result.icon = QIcon(QStringLiteral(":/marble/system-software-update.png"));
279 result.iconSize = QSize(m_iconSize, m_iconSize);
280 break;
281 case OpenButton:
282 result.text = tr("Open");
283 result.icon = QIcon(QStringLiteral(":/marble/document-open.png"));
284 result.iconSize = QSize(m_iconSize, m_iconSize);
285 break;
286 case CancelButton:
287 result.text = tr("Cancel");
288 break;
289 case RemoveButton:
290 result.text = tr("Remove");
291 result.icon = QIcon(QStringLiteral(":/marble/edit-delete.png"));
292 result.iconSize = QSize(m_iconSize, m_iconSize);
293 break;
294 default:
295 // ignored
296 break;
297 }
298
299 return result;
300}
301
302QRect MapItemDelegate::position(Element element, const QStyleOptionViewItem &option) const
303{
304 int const width = buttonWidth(option);
305 QPoint const topLeftCol1 = option.rect.topLeft() + QPoint(0, 2);
306 QPoint const topLeftCol2 = topLeftCol1 + QPoint(option.decorationSize.width(), 0);
307 QPoint const topLeftCol3 = topLeftCol2 + QPoint(option.rect.width() - 3 * m_margin - width - option.decorationSize.width(), 0);
308 switch (element) {
309 case Icon:
310 return QRect(topLeftCol1, option.decorationSize);
311 case Text:
312 return QRect(topLeftCol2, QSize(topLeftCol3.x() - topLeftCol2.x(), option.rect.height()));
313 case InstallButton:
314 case UpgradeButton:
315 case OpenButton: {
316 QStyleOptionButton optionButton = button(element, option);
317 QSize size = option.fontMetrics.size(0, optionButton.text) + QSize(4, 4);
318 QSize buttonSize = QApplication::style()->sizeFromContents(QStyle::CT_PushButton, &optionButton, size);
319 buttonSize.setWidth(width);
320 return QRect(topLeftCol3, buttonSize);
321 }
322 case RemoveButton:
323 case CancelButton: {
324 QStyleOptionButton optionButton = button(element, option);
325 QSize size = option.fontMetrics.size(0, optionButton.text) + QSize(4, 4);
326 QSize buttonSize = QApplication::style()->sizeFromContents(QStyle::CT_PushButton, &optionButton, size);
327 buttonSize.setWidth(width);
328 return QRect(topLeftCol3 + QPoint(0, option.fontMetrics.height() + 8 + m_margin), buttonSize);
329 }
330 case ProgressReport: {
331 QSize const progressSize = QSize(width, option.fontMetrics.height() + 4);
332 return QRect(topLeftCol3 + QPoint(0, m_margin), progressSize);
333 }
334 }
335
336 Q_ASSERT(false);
337 return {};
338}
339
340QString MapItemDelegate::text(const QModelIndex &index)
341{
342 qreal const size = index.data(NewstuffModel::PayloadSize).toLongLong() / 1024.0 / 1024.0;
343 // Fields are typically not longer than 200 characters. Prevent excessive long text here anyway
344 // due to bug 319542
345 int const maxEntrySize = 4096;
346 return QStringLiteral("<p><b>%1</b><br />%2</p><p>Author: %3<br />License: %4<br />Version %5 (%6) %7</p>")
347 .arg(index.data().toString())
348 .arg(index.data(NewstuffModel::Summary).toString().left(maxEntrySize))
349 .arg(index.data(NewstuffModel::Author).toString().left(maxEntrySize))
350 .arg(index.data(NewstuffModel::License).toString().left(maxEntrySize))
351 .arg(index.data(NewstuffModel::Version).toString().left(maxEntrySize))
352 .arg(index.data(NewstuffModel::ReleaseDate).toString().left(maxEntrySize))
353 .arg(size > 0 ? QStringLiteral("%1 MB").arg(size, 0, 'f', 1) : QString());
354}
355
356}
357
358#include "MapThemeDownloadDialog.moc" // needed for Q_OBJECT here in source
359
360#include "moc_MapThemeDownloadDialog.cpp"
This file contains the headers for MarbleWidget.
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
Binds a QML item to a specific geodetic location in screen coordinates.
virtual void draw(QPainter *painter, const PaintContext &context)=0
QStyle * style()
QString absolutePath() const const
bool cdUp()
MouseButtonRelease
int column() const const
QVariant data(int role) const const
int row() const const
Q_OBJECTQ_OBJECT
virtual bool event(QEvent *e)
void drawPixmap(const QPoint &point, const QPixmap &pixmap)
void drawText(const QPoint &position, const QString &text)
void restore()
void save()
void setClipRect(const QRect &rectangle, Qt::ClipOperation operation)
void translate(const QPoint &offset)
int x() const const
void adjust(int dx1, int dy1, int dx2, int dy2)
bool contains(const QPoint &point, bool proper) const const
int height() const const
QSize size() const const
QPoint topLeft() const const
int width() const const
int height() const const
void setWidth(int width)
int width() const const
QString left(qsizetype n) const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
qsizetype size() const const
virtual void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const const=0
virtual QSize sizeFromContents(ContentsType type, const QStyleOption *option, const QSize &contentsSize, const QWidget *widget) const const=0
AlignCenter
DecorationRole
QAbstractTextDocumentLayout * documentLayout() const const
void setDefaultFont(const QFont &font)
void setHtml(const QString &html)
void setTextWidth(qreal width)
bool toBool() const const
qlonglong toLongLong(bool *ok) const const
QString toString() const const
QStringList toStringList() const const
T value() const const
void setupUi(QWidget *widget)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 8 2024 12:02:44 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.