File indexing completed on 2024-04-28 04:05:05

0001 /*
0002     SPDX-FileCopyrightText: 2009-2012 Stefan Majewsky <majewsky@gmx.net>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "kgamethemeselector.h"
0008 #include "kgamethemeselector_p.h"
0009 
0010 // KF
0011 #include <KLocalizedString>
0012 #include <KNSWidgets/Button>
0013 // Qt
0014 #include <QAbstractItemView>
0015 #include <QApplication>
0016 #include <QCloseEvent>
0017 #include <QDialog>
0018 #include <QDialogButtonBox>
0019 #include <QFont>
0020 #include <QFontMetrics>
0021 #include <QIcon>
0022 #include <QListWidget>
0023 #include <QPainter>
0024 #include <QPushButton>
0025 #include <QScreen>
0026 #include <QScrollBar>
0027 #include <QVBoxLayout>
0028 
0029 namespace Metrics
0030 {
0031 const int Padding = 6;
0032 const QSize ThumbnailBaseSize(64, 64);
0033 }
0034 
0035 // BEGIN KGameThemeSelector
0036 
0037 class KGameThemeSelectorPrivate
0038 {
0039 public:
0040     KGameThemeSelector *const q;
0041 
0042     KGameThemeProvider *m_provider;
0043     KGameThemeSelector::Options m_options;
0044     QListWidget *m_list;
0045     KNSWidgets::Button *m_knsButton = nullptr;
0046     QString m_configFileName;
0047 
0048 public:
0049     KGameThemeSelectorPrivate(KGameThemeProvider *provider, KGameThemeSelector::Options options, KGameThemeSelector *q)
0050         : q(q)
0051         , m_provider(provider)
0052         , m_options(options)
0053     {
0054     }
0055 
0056     void fillList();
0057 
0058     void _k_updateListSelection(const KGameTheme *theme);
0059     void _k_updateProviderSelection();
0060 };
0061 
0062 KGameThemeSelector::KGameThemeSelector(KGameThemeProvider *provider, Options options, QWidget *parent)
0063     : QWidget(parent)
0064     , d_ptr(new KGameThemeSelectorPrivate(provider, options, this))
0065 {
0066     Q_D(KGameThemeSelector);
0067 
0068     d->m_list = new QListWidget(this);
0069     d->m_list->setSelectionMode(QAbstractItemView::SingleSelection);
0070     d->m_list->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
0071     // load themes from provider
0072     d->fillList();
0073 
0074     // setup appearance of the theme list
0075     KGameThemeDelegate *delegate = new KGameThemeDelegate(d->m_list);
0076     QScreen *screen = QWidget::screen();
0077     QSize screenSize = screen->availableSize();
0078     if (screenSize.width() < 650 || screenSize.height() < 650) {
0079         d->m_list->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
0080         if (parent) {
0081             d->m_list->setMinimumSize(0, 0);
0082         } else {
0083             // greater than zero to prevent zero sized dialog in some games
0084             d->m_list->setMinimumSize(330, 200);
0085         }
0086     } else {
0087         const QSize itemSizeHint = delegate->sizeHint(QStyleOptionViewItem(), QModelIndex());
0088         const QSize scrollBarSizeHint = d->m_list->verticalScrollBar()->sizeHint();
0089         d->m_list->setMinimumSize(itemSizeHint.width() + 2 * scrollBarSizeHint.width(), 4.1 * itemSizeHint.height());
0090     }
0091 
0092     // monitor change selection in both directions
0093     connect(d->m_provider, &KGameThemeProvider::currentThemeChanged, this, [this](const KGameTheme *theme) {
0094         Q_D(KGameThemeSelector);
0095         d->_k_updateListSelection(theme);
0096     });
0097     connect(d->m_list, &QListWidget::itemSelectionChanged, this, [this]() {
0098         Q_D(KGameThemeSelector);
0099         d->_k_updateProviderSelection();
0100     });
0101     // setup main layout
0102     QVBoxLayout *layout = new QVBoxLayout(this);
0103     layout->setContentsMargins(0, 0, 0, 0);
0104     layout->addWidget(d->m_list);
0105     // setup KNS button
0106     if (options & EnableNewStuffDownload) {
0107         QString name = QCoreApplication::applicationName() + QStringLiteral(".knsrc");
0108         d->m_knsButton = new KNSWidgets::Button(i18n("Get New Themes..."), name, this);
0109         QHBoxLayout *hLayout = new QHBoxLayout();
0110         hLayout->addStretch(1);
0111         hLayout->addWidget(d->m_knsButton);
0112         layout->addLayout(hLayout);
0113         connect(d->m_knsButton, &KNSWidgets::Button::dialogFinished, [this](const QList<KNSCore::Entry> &changedEntries) {
0114             Q_D(KGameThemeSelector);
0115             if (!changedEntries.isEmpty()) {
0116                 d->m_provider->rediscoverThemes();
0117                 d->fillList();
0118             }
0119             // restore previous selection
0120             d->_k_updateListSelection(d->m_provider->currentTheme());
0121         });
0122     }
0123 }
0124 
0125 void KGameThemeSelector::setNewStuffConfigFileName(const QString &configName)
0126 {
0127     Q_D(KGameThemeSelector);
0128     d->m_configFileName = configName;
0129 }
0130 
0131 KGameThemeSelector::~KGameThemeSelector() = default;
0132 
0133 void KGameThemeSelectorPrivate::fillList()
0134 {
0135     m_list->clear();
0136     const auto themes = m_provider->themes();
0137     for (const KGameTheme *theme : themes) {
0138         QListWidgetItem *item = new QListWidgetItem(theme->name(), m_list);
0139         item->setData(Qt::DecorationRole, m_provider->generatePreview(theme, Metrics::ThumbnailBaseSize));
0140         item->setData(KGameThemeDelegate::DescriptionRole, theme->description());
0141         item->setData(KGameThemeDelegate::AuthorRole, theme->author());
0142         item->setData(KGameThemeDelegate::AuthorEmailRole, theme->authorEmail());
0143         item->setData(KGameThemeDelegate::IdRole, theme->identifier());
0144     }
0145     _k_updateListSelection(m_provider->currentTheme());
0146 }
0147 
0148 void KGameThemeSelectorPrivate::_k_updateListSelection(const KGameTheme *theme)
0149 {
0150     for (int idx = 0; idx < m_list->count(); ++idx) {
0151         QListWidgetItem *item = m_list->item(idx);
0152         const QByteArray thisId = item->data(KGameThemeDelegate::IdRole).toByteArray();
0153         if (thisId == theme->identifier()) {
0154             m_list->setCurrentItem(item, QItemSelectionModel::ClearAndSelect);
0155             return;
0156         }
0157     }
0158     // make sure that something is selected
0159     if (m_list->count() > 0) {
0160         m_list->setCurrentRow(0, QItemSelectionModel::ClearAndSelect);
0161     }
0162 }
0163 
0164 void KGameThemeSelectorPrivate::_k_updateProviderSelection()
0165 {
0166     const QListWidgetItem *selItem = m_list->selectedItems().value(0);
0167     if (!selItem) {
0168         return;
0169     }
0170     const QByteArray selId = selItem->data(KGameThemeDelegate::IdRole).toByteArray();
0171     // select the theme with this identifier
0172     const auto themes = m_provider->themes();
0173     for (const KGameTheme *theme : themes) {
0174         if (theme->identifier() == selId) {
0175             m_provider->setCurrentTheme(theme);
0176         }
0177     }
0178 }
0179 
0180 class Q_DECL_HIDDEN KGameThemeSelector::Dialog : public QDialog
0181 {
0182 public:
0183     Dialog(KGameThemeSelector *sel, const QString &caption)
0184         : mSelector(sel)
0185     {
0186         QVBoxLayout *mainLayout = new QVBoxLayout;
0187         setLayout(mainLayout);
0188         mainLayout->addWidget(sel);
0189 
0190         // replace
0191         QPushButton *btn = sel->d_ptr->m_knsButton;
0192 
0193         QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
0194 
0195         if (btn) {
0196             btn->hide();
0197 
0198             QPushButton *stuff = new QPushButton(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff")), btn->text());
0199             buttonBox->addButton(stuff, QDialogButtonBox::ActionRole);
0200             buttonBox->addButton(QDialogButtonBox::Close);
0201 
0202             connect(stuff, &QAbstractButton::clicked, btn, &QAbstractButton::clicked);
0203             connect(buttonBox, &QDialogButtonBox::rejected, this, &KGameThemeSelector::Dialog::reject);
0204         } else {
0205             buttonBox->setStandardButtons(QDialogButtonBox::Close);
0206             connect(buttonBox, &QDialogButtonBox::rejected, this, &KGameThemeSelector::Dialog::reject);
0207         }
0208         // window caption
0209         if (caption.isEmpty()) {
0210             setWindowTitle(i18nc("@title:window config dialog", "Select theme"));
0211         } else {
0212             setWindowTitle(caption);
0213         }
0214 
0215         mainLayout->addWidget(buttonBox);
0216         show();
0217     }
0218 
0219 protected:
0220     void closeEvent(QCloseEvent *event) override
0221     {
0222         event->accept();
0223         // delete myself, but *not* the KGameThemeSelector
0224         mSelector->setParent(nullptr);
0225         deleteLater();
0226         // restore the KNS button
0227         if (mSelector->d_ptr->m_knsButton) {
0228             mSelector->d_ptr->m_knsButton->show();
0229         }
0230     }
0231 
0232 private:
0233     KGameThemeSelector *mSelector;
0234 };
0235 
0236 void KGameThemeSelector::showAsDialog(const QString &caption)
0237 {
0238     if (!isVisible()) {
0239         new KGameThemeSelector::Dialog(this, caption);
0240     }
0241 }
0242 
0243 // END KGameThemeSelector
0244 // BEGIN KGameThemeDelegate
0245 
0246 KGameThemeDelegate::KGameThemeDelegate(QObject *parent)
0247     : QStyledItemDelegate(parent)
0248 {
0249     QAbstractItemView *view = qobject_cast<QAbstractItemView *>(parent);
0250     if (view)
0251         view->setItemDelegate(this);
0252 }
0253 
0254 QRect KGameThemeDelegate::thumbnailRect(QRect baseRect) const
0255 {
0256     QRect thumbnailBaseRect(QPoint(Metrics::Padding + baseRect.left(), 0), Metrics::ThumbnailBaseSize);
0257     thumbnailBaseRect.moveCenter(QPoint(thumbnailBaseRect.center().x(), baseRect.center().y()));
0258     if (QApplication::isRightToLeft())
0259         thumbnailBaseRect.moveRight(baseRect.right() - Metrics::Padding);
0260     return thumbnailBaseRect;
0261 }
0262 
0263 void KGameThemeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
0264 {
0265     const bool rtl = option.direction == Qt::RightToLeft;
0266     QRect baseRect = option.rect;
0267     // draw background
0268     QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr);
0269     // draw thumbnail
0270     QRect thumbnailBaseRect = this->thumbnailRect(baseRect);
0271     const QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
0272     QApplication::style()->drawItemPixmap(painter, thumbnailBaseRect, Qt::AlignCenter, thumbnail);
0273 
0274     // find metrics: text
0275     QStringList texts;
0276     QList<QFont> fonts;
0277     texts.reserve(3);
0278     fonts.reserve(3);
0279     {
0280         QString name = index.data(Qt::DisplayRole).toString();
0281         if (name.isEmpty())
0282             name = i18n("[No name]");
0283         texts << name;
0284         QFont theFont(painter->font());
0285         theFont.setBold(true);
0286         fonts << theFont;
0287     }
0288     {
0289         QString comment = index.data(DescriptionRole).toString();
0290         if (!comment.isEmpty()) {
0291             texts << comment;
0292             fonts << painter->font();
0293         }
0294     }
0295     {
0296         QString author = index.data(AuthorRole).toString();
0297         if (!author.isEmpty()) {
0298             const QString authorString = ki18nc("Author attribution, e.g. \"by Jack\"", "by %1").subs(author).toString();
0299             texts << authorString;
0300             QFont theFont(painter->font());
0301             theFont.setItalic(true);
0302             fonts << theFont;
0303         }
0304     }
0305     // TODO: display AuthorEmailRole
0306     QList<QRect> textRects;
0307     textRects.reserve(texts.size());
0308     int totalTextHeight = 0;
0309     for (int i = 0; i < texts.count(); ++i) {
0310         QFontMetrics fm(fonts[i]);
0311         textRects << fm.boundingRect(texts[i]);
0312         textRects[i].setHeight(qMax(textRects[i].height(), fm.lineSpacing()));
0313         totalTextHeight += textRects[i].height();
0314     }
0315     QRect textBaseRect(baseRect);
0316     if (rtl) {
0317         textBaseRect.setRight(thumbnailBaseRect.left() - Metrics::Padding);
0318         textBaseRect.adjust(Metrics::Padding, Metrics::Padding, 0, -Metrics::Padding);
0319     } else {
0320         textBaseRect.setLeft(thumbnailBaseRect.right() + Metrics::Padding);
0321         textBaseRect.adjust(0, Metrics::Padding, -Metrics::Padding, -Metrics::Padding);
0322     }
0323     textBaseRect.setHeight(totalTextHeight);
0324     textBaseRect.moveTop(baseRect.top() + (baseRect.height() - textBaseRect.height()) / 2);
0325     // draw texts
0326     QRect currentTextRect(textBaseRect);
0327     painter->save();
0328     for (int i = 0; i < texts.count(); ++i) {
0329         painter->setFont(fonts[i]);
0330         const QRect &textRect = textRects[i];
0331         currentTextRect.setHeight(textRect.height());
0332         const QFontMetrics fm(fonts[i]);
0333         const QString text = fm.elidedText(texts[i], Qt::ElideRight, currentTextRect.width());
0334         painter->drawText(currentTextRect, Qt::AlignLeft | Qt::AlignVCenter, text);
0335         currentTextRect.moveTop(currentTextRect.bottom());
0336     }
0337     painter->restore();
0338 }
0339 
0340 QSize KGameThemeDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
0341 {
0342     Q_UNUSED(option)
0343     Q_UNUSED(index)
0344     // TODO: take text size into account
0345     return QSize(400, Metrics::ThumbnailBaseSize.height() + 2 * Metrics::Padding);
0346 }
0347 
0348 // END KGameThemeDelegate
0349 
0350 #include "moc_kgamethemeselector.cpp"