KDEGames

kgthemeselector.cpp
1 /*
2  SPDX-FileCopyrightText: 2009-2012 Stefan Majewsky <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-only
5 */
6 
7 #include "kgthemeselector.h"
8 #include "kgthemeselector_p.h"
9 
10 // KF
11 #include <KLocalizedString>
12 #include <KNS3/DownloadDialog>
13 // Qt
14 #include <QCloseEvent>
15 #include <QFont>
16 #include <QFontMetrics>
17 #include <QPainter>
18 #include <QAbstractItemView>
19 #include <QApplication>
20 #include <QListWidget>
21 #include <QPushButton>
22 #include <QScreen>
23 #include <QScrollBar>
24 #include <QVBoxLayout>
25 #include <QDialogButtonBox>
26 #include <QDialog>
27 #include <QIcon>
28 
29 namespace Metrics
30 {
31  const int Padding = 6;
32  const QSize ThumbnailBaseSize(64, 64);
33 }
34 
35 //BEGIN KgThemeSelector
36 
37 class KgThemeSelectorPrivate
38 {
39  public:
40  KgThemeSelector* q;
41  KgThemeProvider* m_provider;
42  KgThemeSelector::Options m_options;
43  QListWidget* m_list;
44  QPushButton* m_knsButton;
45 
46  void fillList();
47 
48  KgThemeSelectorPrivate(KgThemeProvider* provider, KgThemeSelector::Options options, KgThemeSelector* q)
49  : q(q)
50  , m_provider(provider)
51  , m_options(options)
52  , m_knsButton(nullptr)
53  {}
54 
55  void _k_updateListSelection(const KgTheme* theme);
56  void _k_updateProviderSelection();
57  void _k_showNewStuffDialog();
58 };
59 
60 KgThemeSelector::KgThemeSelector(KgThemeProvider* provider, Options options, QWidget* parent)
61  : QWidget(parent)
62  , d(new KgThemeSelectorPrivate(provider, options, this))
63 {
64  d->m_list = new QListWidget(this);
65  d->m_list->setSelectionMode(QAbstractItemView::SingleSelection);
66  d->m_list->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
67  //load themes from provider
68  d->fillList();
69 
70  //setup appearance of the theme list
71  KgThemeDelegate* delegate = new KgThemeDelegate(d->m_list);
72  QScreen *screen = QWidget::screen();
73  QSize screenSize = screen->availableSize();
74  if (screenSize.width() < 650 || screenSize.height() < 650) {
75  d->m_list->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored );
76  if (parent){
77  d->m_list->setMinimumSize(0, 0);
78  } else {
79  //greater than zero to prevent zero sized dialog in some games
80  d->m_list->setMinimumSize(330, 200);
81  }
82  } else {
83  const QSize itemSizeHint = delegate->sizeHint(QStyleOptionViewItem(), QModelIndex());
84  const QSize scrollBarSizeHint = d->m_list->verticalScrollBar()->sizeHint();
85  d->m_list->setMinimumSize(itemSizeHint.width() + 2 * scrollBarSizeHint.width(), 4.1 * itemSizeHint.height());
86  }
87 
88  //monitor change selection in both directions
90  this, [this](const KgTheme* theme) { d->_k_updateListSelection(theme); });
92  this, [this]() { d->_k_updateProviderSelection(); });
93  //setup main layout
94  QVBoxLayout* layout = new QVBoxLayout(this);
95  layout->setContentsMargins(0, 0, 0, 0);
96  layout->addWidget(d->m_list);
97  //setup KNS button
98  if (options & EnableNewStuffDownload)
99  {
100  d->m_knsButton = new QPushButton(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff")),
101  i18n("Get New Themes..."), this);
102  QHBoxLayout * hLayout = new QHBoxLayout();
103  hLayout->addStretch( 1 );
104  hLayout->addWidget( d->m_knsButton );
105  layout->addLayout( hLayout );
106  connect(d->m_knsButton, &QAbstractButton::clicked,
107  this, [this]() { d->_k_showNewStuffDialog(); });
108  }
109 }
110 
111 KgThemeSelector::~KgThemeSelector() = default;
112 
113 void KgThemeSelectorPrivate::fillList()
114 {
115  m_list->clear();
116  const auto themes = m_provider->themes();
117  for (const KgTheme* theme : themes) {
118  QListWidgetItem* item = new QListWidgetItem(theme->name(), m_list);
120  m_provider->generatePreview(theme, Metrics::ThumbnailBaseSize));
121  item->setData(KgThemeDelegate::DescriptionRole, theme->description());
122  item->setData(KgThemeDelegate::AuthorRole, theme->author());
123  item->setData(KgThemeDelegate::AuthorEmailRole, theme->authorEmail());
124  item->setData(KgThemeDelegate::IdRole, theme->identifier());
125  }
126  _k_updateListSelection(m_provider->currentTheme());
127 }
128 
129 void KgThemeSelectorPrivate::_k_updateListSelection(const KgTheme* theme)
130 {
131  for (int idx = 0; idx < m_list->count(); ++idx)
132  {
133  QListWidgetItem* item = m_list->item(idx);
134  const QByteArray thisId = item->data(KgThemeDelegate::IdRole).toByteArray();
135  if (thisId == theme->identifier())
136  {
137  m_list->setCurrentItem(item, QItemSelectionModel::ClearAndSelect);
138  return;
139  }
140  }
141  //make sure that something is selected
142  if (m_list->count() > 0)
143  {
144  m_list->setCurrentRow(0, QItemSelectionModel::ClearAndSelect);
145  }
146 }
147 
148 void KgThemeSelectorPrivate::_k_updateProviderSelection()
149 {
150  const QListWidgetItem* selItem = m_list->selectedItems().value(0);
151  if (!selItem)
152  {
153  return;
154  }
155  const QByteArray selId = selItem->data(KgThemeDelegate::IdRole).toByteArray();
156  //select the theme with this identifier
157  const auto themes = m_provider->themes();
158  for (const KgTheme* theme : themes) {
159  if (theme->identifier() == selId)
160  {
161  m_provider->setCurrentTheme(theme);
162  }
163  }
164 }
165 
166 void KgThemeSelectorPrivate::_k_showNewStuffDialog()
167 {
169  dialog->exec();
170  if (dialog && !dialog->changedEntries().isEmpty())
171  {
172  m_provider->rediscoverThemes();
173  fillList();
174  }
175  //restore previous selection
176  _k_updateListSelection(m_provider->currentTheme());
177  delete dialog;
178 }
179 
180 class KgThemeSelector::Dialog : public QDialog
181 {
182  public:
183  Dialog(KgThemeSelector* sel, const QString& caption)
184  : mSelector(sel)
185  {
186  QVBoxLayout *mainLayout = new QVBoxLayout;
187  setLayout(mainLayout);
188  mainLayout->addWidget(sel);
189 
190  //replace
191  QPushButton* btn = sel->d->m_knsButton;
192 
193  QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
194 
195  if (btn)
196  {
197  btn->hide();
198 
199  QPushButton *stuff = new QPushButton(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff")), btn->text());
200  buttonBox->addButton(stuff, QDialogButtonBox::ActionRole);
202 
204  connect(buttonBox, &QDialogButtonBox::rejected, this, &KgThemeSelector::Dialog::reject);
205  }
206  else
207  {
209  connect(buttonBox, &QDialogButtonBox::rejected, this, &KgThemeSelector::Dialog::reject);
210  }
211  //window caption
212  if (caption.isEmpty())
213  {
214  setWindowTitle(i18nc("@title:window config dialog", "Select theme"));
215  }
216  else
217  {
218  setWindowTitle(caption);
219  }
220 
221  mainLayout->addWidget(buttonBox);
222  show();
223  }
224  protected:
225  void closeEvent(QCloseEvent* event) override
226  {
227  event->accept();
228  //delete myself, but *not* the KgThemeSelector
229  mSelector->setParent(nullptr);
230  deleteLater();
231  //restore the KNS button
232  if (mSelector->d->m_knsButton)
233  {
234  mSelector->d->m_knsButton->show();
235  }
236  }
237  private:
238  KgThemeSelector* mSelector;
239 };
240 
242 {
243  if (!isVisible())
244  {
245  new KgThemeSelector::Dialog(this, caption);
246  }
247 }
248 
249 //END KgThemeSelector
250 //BEGIN KgThemeDelegate
251 
252 KgThemeDelegate::KgThemeDelegate(QObject* parent)
253  : QStyledItemDelegate(parent)
254 {
256  if (view)
257  view->setItemDelegate(this);
258 }
259 
260 QRect KgThemeDelegate::thumbnailRect(const QRect& baseRect) const
261 {
262  QRect thumbnailBaseRect(QPoint(Metrics::Padding + baseRect.left(), 0), Metrics::ThumbnailBaseSize);
263  thumbnailBaseRect.moveCenter(QPoint(thumbnailBaseRect.center().x(), baseRect.center().y()));
265  thumbnailBaseRect.moveRight(baseRect.right() - Metrics::Padding);
266  return thumbnailBaseRect;
267 }
268 
269 void KgThemeDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
270 {
271  const bool rtl = option.direction == Qt::RightToLeft;
272  QRect baseRect = option.rect;
273  //draw background
274  QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr);
275  //draw thumbnail
276  QRect thumbnailBaseRect = this->thumbnailRect(baseRect);
277  const QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
278  QApplication::style()->drawItemPixmap(painter, thumbnailBaseRect, Qt::AlignCenter, thumbnail);
279 
280  //find metrics: text
281  QStringList texts; QList<QFont> fonts;
282  {
284  if (name.isEmpty())
285  name = i18n("[No name]");
286  texts << name;
287  QFont theFont(painter->font()); theFont.setBold(true); fonts << theFont;
288  }{
289  QString comment = index.data(DescriptionRole).toString();
290  if (!comment.isEmpty())
291  {
292  texts << comment;
293  fonts << painter->font();
294  }
295  }{
296  QString author = index.data(AuthorRole).toString();
297  if (!author.isEmpty())
298  {
299  const QString authorString = ki18nc("Author attribution, e.g. \"by Jack\"", "by %1").subs(author).toString();
300  texts << authorString;
301  QFont theFont(painter->font()); theFont.setItalic(true); fonts << theFont;
302  }
303  }
304  //TODO: display AuthorEmailRole
305  QList<QRect> textRects; int totalTextHeight = 0;
306  for (int i = 0; i < texts.count(); ++i)
307  {
308  QFontMetrics fm(fonts[i]);
309  textRects << fm.boundingRect(texts[i]);
310  textRects[i].setHeight(qMax(textRects[i].height(), fm.lineSpacing()));
311  totalTextHeight += textRects[i].height();
312  }
313  QRect textBaseRect(baseRect);
314  if (rtl)
315  {
316  textBaseRect.setRight(thumbnailBaseRect.left() - Metrics::Padding);
317  textBaseRect.adjust(Metrics::Padding, Metrics::Padding, 0, -Metrics::Padding);
318  }
319  else
320  {
321  textBaseRect.setLeft(thumbnailBaseRect.right() + Metrics::Padding);
322  textBaseRect.adjust(0, Metrics::Padding, -Metrics::Padding, -Metrics::Padding);
323  }
324  textBaseRect.setHeight(totalTextHeight);
325  textBaseRect.moveTop(baseRect.top() + (baseRect.height() - textBaseRect.height()) / 2);
326  //draw texts
327  QRect currentTextRect(textBaseRect);
328  painter->save();
329  for (int i = 0; i < texts.count(); ++i)
330  {
331  painter->setFont(fonts[i]);
332  const QRect& textRect = textRects[i];
333  currentTextRect.setHeight(textRect.height());
334  const QFontMetrics fm(fonts[i]);
335  const QString text = fm.elidedText(texts[i], Qt::ElideRight, currentTextRect.width());
336  painter->drawText(currentTextRect, Qt::AlignLeft | Qt::AlignVCenter, text);
337  currentTextRect.moveTop(currentTextRect.bottom());
338  }
339  painter->restore();
340 }
341 
342 QSize KgThemeDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
343 {
344  Q_UNUSED(option) Q_UNUSED(index)
345  //TODO: take text size into account
346  return QSize(400, Metrics::ThumbnailBaseSize.height() + 2 * Metrics::Padding);
347 }
348 
349 //END KgThemeDelegate
350 
351 #include "moc_kgthemeselector.cpp"
bool isRightToLeft()
QByteArray toByteArray() const const
void setContentsMargins(int left, int top, int right, int bottom)
int width() const const
QString toString() const
int right() const const
const QFont & font() const const
A theme provider manages KgTheme instances, and maintains a selection of the currentTheme().
void addButton(QAbstractButton *button, QDialogButtonBox::ButtonRole role)
void save()
T value() const const
int height() const const
QScreen * screen() const const
RightToLeft
AlignCenter
int x() const const
int y() const const
KLocalizedString subs(int a, int fieldWidth=0, int base=10, QChar fillChar=QLatin1Char(' ')) const
virtual bool event(QEvent *e)
void setBold(bool enable)
QString name() const
QRect boundingRect(QChar ch) const const
void setFont(const QFont &font)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
int count(const T &value) const const
QList< const KgTheme * > themes() const
Theme selection widget.
int top() const const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
int left() const const
DecorationRole
bool isEmpty() const const
void clicked(bool checked)
void setItemDelegate(QAbstractItemDelegate *delegate)
QString elidedText(const QString &text, Qt::TextElideMode mode, int width, int flags) const const
virtual QVariant data(int role) const const
void drawText(const QPointF &position, const QString &text)
QPoint center() const const
void deleteLater()
void hide()
PE_PanelItemViewItem
void moveTop(int y)
virtual void drawItemPixmap(QPainter *painter, const QRect &rectangle, int alignment, const QPixmap &pixmap) const const
virtual void setData(int role, const QVariant &value)
void setItalic(bool enable)
void restore()
QString i18n(const char *text, const TYPE &arg...)
void setRight(int x)
KLocalizedString KI18N_EXPORT ki18nc(const char *context, const char *text)
int width() const const
QVariant data(int role) const const
QStyle * style()
void setStandardButtons(QDialogButtonBox::StandardButtons buttons)
void addStretch(int stretch)
void setHeight(int height)
int height() const const
void adjust(int dx1, int dy1, int dx2, int dy2)
ElideRight
int bottom() const const
virtual void drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const const =0
QIcon fromTheme(const QString &name)
void itemSelectionChanged()
A theme describes the visual appearance of a game.
Definition: kgtheme.h:58
void currentThemeChanged(const KgTheme *theme)
Emitted when the current theme changes.
void moveRight(int x)
void moveCenter(const QPoint &position)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
T qobject_cast(QObject *object)
QObject * parent() const const
QString toString() const const
void setLeft(int x)
int lineSpacing() const const
void addLayout(QLayout *layout, int stretch)
void showAsDialog(const QString &caption=QString())
Create and show a non-modal dialog which displays this selector.
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Tue Dec 7 2021 22:34:15 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.