KDEGames

kgthemeselector.cpp
1 /***************************************************************************
2  * Copyright 2009-2012 Stefan Majewsky <[email protected]> *
3  * *
4  * This program is free software; you can redistribute it and/or modify *
5  * it under the terms of the GNU Library General Public License *
6  * version 2 as published by the Free Software Foundation *
7  * *
8  * This program is distributed in the hope that it will be useful, *
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
11  * GNU Library General Public License for more details. *
12  * *
13  * You should have received a copy of the GNU Library General Public *
14  * License along with this program; if not, write to the *
15  * Free Software Foundation, Inc., *
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
17  ***************************************************************************/
18 
19 #include "kgthemeselector.h"
20 #include "kgthemeselector_p.h"
21 
22 #include <QCloseEvent>
23 #include <QFont>
24 #include <QFontMetrics>
25 #include <QPainter>
26 #include <QAbstractItemView>
27 #include <QApplication>
28 #include <QListWidget>
29 #include <QPushButton>
30 #include <QScrollBar>
31 #include <QVBoxLayout>
32 #include <QDialogButtonBox>
33 #include <QDialog>
34 
35 #include <QIcon>
36 #include <KLocalizedString>
37 #include <KNS3/DownloadDialog>
38 
39 namespace Metrics
40 {
41  const int Padding = 6;
42  const QSize ThumbnailBaseSize(64, 64);
43 }
44 
45 //BEGIN KgThemeSelector
46 
47 class KgThemeSelector::Private
48 {
49  public:
50  KgThemeSelector* q;
51  KgThemeProvider* m_provider;
52  Options m_options;
53  QListWidget* m_list;
54  QPushButton* m_knsButton;
55 
56  void fillList();
57 
58  Private(KgThemeProvider* provider, Options options, KgThemeSelector* q) : q(q), m_provider(provider), m_options(options), m_knsButton(nullptr) {}
59 
60  void _k_updateListSelection(const KgTheme* theme);
61  void _k_updateProviderSelection();
62  void _k_showNewStuffDialog();
63 };
64 
65 KgThemeSelector::KgThemeSelector(KgThemeProvider* provider, Options options, QWidget* parent)
66  : QWidget(parent)
67  , d(new Private(provider, options, this))
68 {
69  d->m_list = new QListWidget(this);
70  d->m_list->setSelectionMode(QAbstractItemView::SingleSelection);
71  d->m_list->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
72  //load themes from provider
73  d->fillList();
74  //setup appearance of the theme list (min. size = 4 items)
75  KgThemeDelegate* delegate = new KgThemeDelegate(d->m_list);
76  const QSize itemSizeHint = delegate->sizeHint(QStyleOptionViewItem(), QModelIndex());
77  const QSize scrollBarSizeHint = d->m_list->verticalScrollBar()->sizeHint();
78  d->m_list->setMinimumSize(itemSizeHint.width() + 2 * scrollBarSizeHint.width(), 4.1 * itemSizeHint.height());
79  //monitor change selection in both directions
80  connect(d->m_provider, SIGNAL(currentThemeChanged(const KgTheme*)),
81  SLOT(_k_updateListSelection(const KgTheme*)));
82  connect(d->m_list, SIGNAL(itemSelectionChanged()),
83  SLOT(_k_updateProviderSelection()));
84  //setup main layout
85  QVBoxLayout* layout = new QVBoxLayout(this);
86  layout->setContentsMargins(0, 0, 0, 0);
87  layout->addWidget(d->m_list);
88  //setup KNS button
89  if (options & EnableNewStuffDownload)
90  {
91  d->m_knsButton = new QPushButton(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff")),
92  i18n("Get New Themes..."), this);
93  layout->addWidget(d->m_knsButton);
94  connect(d->m_knsButton, SIGNAL(clicked()), SLOT(_k_showNewStuffDialog()));
95  }
96 
97 }
98 
99 KgThemeSelector::~KgThemeSelector()
100 {
101  delete d;
102 }
103 
104 void KgThemeSelector::Private::fillList()
105 {
106  m_list->clear();
107  const auto themes = m_provider->themes();
108  for (const KgTheme* theme : themes) {
109  QListWidgetItem* item = new QListWidgetItem(theme->name(), m_list);
111  m_provider->generatePreview(theme, Metrics::ThumbnailBaseSize));
112  item->setData(KgThemeDelegate::DescriptionRole, theme->description());
113  item->setData(KgThemeDelegate::AuthorRole, theme->author());
114  item->setData(KgThemeDelegate::AuthorEmailRole, theme->authorEmail());
115  item->setData(KgThemeDelegate::IdRole, theme->identifier());
116  }
117  _k_updateListSelection(m_provider->currentTheme());
118 }
119 
120 void KgThemeSelector::Private::_k_updateListSelection(const KgTheme* theme)
121 {
122  for (int idx = 0; idx < m_list->count(); ++idx)
123  {
124  QListWidgetItem* item = m_list->item(idx);
125  const QByteArray thisId = item->data(KgThemeDelegate::IdRole).toByteArray();
126  if (thisId == theme->identifier())
127  {
128  m_list->setCurrentItem(item, QItemSelectionModel::ClearAndSelect);
129  return;
130  }
131  }
132  //make sure that something is selected
133  if (m_list->count() > 0)
134  {
135  m_list->setCurrentRow(0, QItemSelectionModel::ClearAndSelect);
136  }
137 }
138 
139 void KgThemeSelector::Private::_k_updateProviderSelection()
140 {
141  const QListWidgetItem* selItem = m_list->selectedItems().value(0);
142  if (!selItem)
143  {
144  return;
145  }
146  const QByteArray selId = selItem->data(KgThemeDelegate::IdRole).toByteArray();
147  //select the theme with this identifier
148  const auto themes = m_provider->themes();
149  for (const KgTheme* theme : themes) {
150  if (theme->identifier() == selId)
151  {
152  m_provider->setCurrentTheme(theme);
153  }
154  }
155 }
156 
157 void KgThemeSelector::Private::_k_showNewStuffDialog()
158 {
160  dialog->exec();
161  if (dialog && !dialog->changedEntries().isEmpty())
162  {
163  m_provider->rediscoverThemes();
164  fillList();
165  }
166  //restore previous selection
167  _k_updateListSelection(m_provider->currentTheme());
168  delete dialog;
169 }
170 
171 class KgThemeSelector::Dialog : public QDialog
172 {
173  public:
174  Dialog(KgThemeSelector* sel, const QString& caption)
175  : mSelector(sel)
176  {
177  QVBoxLayout *mainLayout = new QVBoxLayout;
178  setLayout(mainLayout);
179  mainLayout->addWidget(sel);
180 
181  //replace
182  QPushButton* btn = sel->d->m_knsButton;
183 
184  QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
185 
186  if (btn)
187  {
188  btn->hide();
189 
190  QPushButton *stuff = new QPushButton(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff")), btn->text());
191  buttonBox->addButton(stuff, QDialogButtonBox::ActionRole);
193 
195  connect(buttonBox, &QDialogButtonBox::rejected, this, &KgThemeSelector::Dialog::reject);
196  }
197  else
198  {
200  connect(buttonBox, &QDialogButtonBox::rejected, this, &KgThemeSelector::Dialog::reject);
201  }
202  //window caption
203  if (caption.isEmpty())
204  {
205  setWindowTitle(i18nc("@title:window config dialog", "Select theme"));
206  }
207  else
208  {
209  setWindowTitle(caption);
210  }
211 
212  mainLayout->addWidget(buttonBox);
213  show();
214  }
215  protected:
216  void closeEvent(QCloseEvent* event) override
217  {
218  event->accept();
219  //delete myself, but *not* the KgThemeSelector
220  mSelector->setParent(nullptr);
221  deleteLater();
222  //restore the KNS button
223  if (mSelector->d->m_knsButton)
224  {
225  mSelector->d->m_knsButton->show();
226  }
227  }
228  private:
229  KgThemeSelector* mSelector;
230 };
231 
233 {
234  if (!isVisible())
235  {
236  new KgThemeSelector::Dialog(this, caption);
237  }
238 }
239 
240 //END KgThemeSelector
241 //BEGIN KgThemeDelegate
242 
243 KgThemeDelegate::KgThemeDelegate(QObject* parent)
244  : QStyledItemDelegate(parent)
245 {
247  if (view)
248  view->setItemDelegate(this);
249 }
250 
251 QRect KgThemeDelegate::thumbnailRect(const QRect& baseRect) const
252 {
253  QRect thumbnailBaseRect(QPoint(Metrics::Padding + baseRect.left(), 0), Metrics::ThumbnailBaseSize);
254  thumbnailBaseRect.moveCenter(QPoint(thumbnailBaseRect.center().x(), baseRect.center().y()));
256  thumbnailBaseRect.moveRight(baseRect.right() - Metrics::Padding);
257  return thumbnailBaseRect;
258 }
259 
260 void KgThemeDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
261 {
262  const bool rtl = option.direction == Qt::RightToLeft;
263  QRect baseRect = option.rect;
264  //draw background
265  QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr);
266  //draw thumbnail
267  QRect thumbnailBaseRect = this->thumbnailRect(baseRect);
268  const QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
269  QApplication::style()->drawItemPixmap(painter, thumbnailBaseRect, Qt::AlignCenter, thumbnail);
270 
271  //find metrics: text
272  QStringList texts; QList<QFont> fonts;
273  {
275  if (name.isEmpty())
276  name = i18n("[No name]");
277  texts << name;
278  QFont theFont(painter->font()); theFont.setBold(true); fonts << theFont;
279  }{
280  QString comment = index.data(DescriptionRole).toString();
281  if (!comment.isEmpty())
282  {
283  texts << comment;
284  fonts << painter->font();
285  }
286  }{
287  QString author = index.data(AuthorRole).toString();
288  if (!author.isEmpty())
289  {
290  const QString authorString = ki18nc("Author attribution, e.g. \"by Jack\"", "by %1").subs(author).toString();
291  texts << authorString;
292  QFont theFont(painter->font()); theFont.setItalic(true); fonts << theFont;
293  }
294  }
295  //TODO: display AuthorEmailRole
296  QList<QRect> textRects; int totalTextHeight = 0;
297  for (int i = 0; i < texts.count(); ++i)
298  {
299  QFontMetrics fm(fonts[i]);
300  textRects << fm.boundingRect(texts[i]);
301  textRects[i].setHeight(qMax(textRects[i].height(), fm.lineSpacing()));
302  totalTextHeight += textRects[i].height();
303  }
304  QRect textBaseRect(baseRect);
305  if (rtl)
306  {
307  textBaseRect.setRight(thumbnailBaseRect.left() - Metrics::Padding);
308  textBaseRect.adjust(Metrics::Padding, Metrics::Padding, 0, -Metrics::Padding);
309  }
310  else
311  {
312  textBaseRect.setLeft(thumbnailBaseRect.right() + Metrics::Padding);
313  textBaseRect.adjust(0, Metrics::Padding, -Metrics::Padding, -Metrics::Padding);
314  }
315  textBaseRect.setHeight(totalTextHeight);
316  textBaseRect.moveTop(baseRect.top() + (baseRect.height() - textBaseRect.height()) / 2);
317  //draw texts
318  QRect currentTextRect(textBaseRect);
319  painter->save();
320  for (int i = 0; i < texts.count(); ++i)
321  {
322  painter->setFont(fonts[i]);
323  const QRect& textRect = textRects[i];
324  currentTextRect.setHeight(textRect.height());
325  const QFontMetrics fm(fonts[i]);
326  const QString text = fm.elidedText(texts[i], Qt::ElideRight, currentTextRect.width());
327  painter->drawText(currentTextRect, Qt::AlignLeft | Qt::AlignVCenter, text);
328  currentTextRect.moveTop(currentTextRect.bottom());
329  }
330  painter->restore();
331 }
332 
333 QSize KgThemeDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
334 {
335  Q_UNUSED(option) Q_UNUSED(index)
336  //TODO: take text size into account
337  return QSize(400, Metrics::ThumbnailBaseSize.height() + 2 * Metrics::Padding);
338 }
339 
340 //END KgThemeDelegate
341 
342 #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
QFlags< Option > Options
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
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 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)
A theme describes the visual appearance of a game.
Definition: kgtheme.h:72
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 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-2020 The KDE developers.
Generated on Thu Nov 26 2020 22:36:18 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.