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"