1/***************************************************************************
2 * Copyright 2009-2012 Stefan Majewsky <majewsky@gmx.net> *
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 <QtGui/QAbstractItemView>
23#include <QtGui/QApplication>
24#include <QtGui/QCloseEvent>
25#include <QtGui/QFont>
26#include <QtGui/QFontMetrics>
27#include <QtGui/QListWidget>
28#include <QtGui/QPainter>
29#include <QtGui/QPushButton>
30#include <QtGui/QScrollBar>
31#include <QtGui/QVBoxLayout>
32#include <KDE/KIcon>
33#include <KDE/KLocalizedString>
34#include <KNS3/DownloadDialog>
35
36namespace Metrics
37{
38 const int Padding = 6;
39 const QSize ThumbnailBaseSize(64, 64);
40}
41
42//BEGIN KgThemeSelector
43
44class KgThemeSelector::Private
45{
46 public:
47 KgThemeSelector* q;
48 KgThemeProvider* m_provider;
49 Options m_options;
50 QListWidget* m_list;
51 QPushButton* m_knsButton;
52
53 void fillList();
54
55 Private(KgThemeProvider* provider, Options options, KgThemeSelector* q) : q(q), m_provider(provider), m_options(options), m_knsButton(0) {}
56
57 void _k_updateListSelection(const KgTheme* theme);
58 void _k_updateProviderSelection();
59 void _k_showNewStuffDialog();
60};
61
62KgThemeSelector::KgThemeSelector(KgThemeProvider* provider, Options options, QWidget* parent)
63 : QWidget(parent)
64 , d(new Private(provider, options, this))
65{
66 d->m_list = new QListWidget(this);
67 d->m_list->setSelectionMode(QAbstractItemView::SingleSelection);
68 d->m_list->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
69 //load themes from provider
70 d->fillList();
71 //setup appearance of the theme list (min. size = 4 items)
72 KgThemeDelegate* delegate = new KgThemeDelegate(d->m_list);
73 const QSize itemSizeHint = delegate->sizeHint(QStyleOptionViewItem(), QModelIndex());
74 const QSize scrollBarSizeHint = d->m_list->verticalScrollBar()->sizeHint();
75 d->m_list->setMinimumSize(itemSizeHint.width() + 2 * scrollBarSizeHint.width(), 4.1 * itemSizeHint.height());
76 //monitor change selection in both directions
77 connect(d->m_provider, SIGNAL(currentThemeChanged(const KgTheme*)),
78 SLOT(_k_updateListSelection(const KgTheme*)));
79 connect(d->m_list, SIGNAL(itemSelectionChanged()),
80 SLOT(_k_updateProviderSelection()));
81 //setup main layout
82 QVBoxLayout* layout = new QVBoxLayout(this);
83 layout->setMargin(0);
84 layout->addWidget(d->m_list);
85 //setup KNS button
86 if (options & EnableNewStuffDownload)
87 {
88 d->m_knsButton = new QPushButton(KIcon("get-hot-new-stuff"),
89 i18n("Get New Themes..."), this);
90 layout->addWidget(d->m_knsButton);
91 connect(d->m_knsButton, SIGNAL(clicked()), SLOT(_k_showNewStuffDialog()));
92 }
93}
94
95KgThemeSelector::~KgThemeSelector()
96{
97 delete d;
98}
99
100void KgThemeSelector::Private::fillList()
101{
102 m_list->clear();
103 foreach (const KgTheme* theme, m_provider->themes())
104 {
105 QListWidgetItem* item = new QListWidgetItem(theme->name(), m_list);
106 item->setData(Qt::DecorationRole,
107 m_provider->generatePreview(theme, Metrics::ThumbnailBaseSize));
108 item->setData(KgThemeDelegate::DescriptionRole, theme->description());
109 item->setData(KgThemeDelegate::AuthorRole, theme->author());
110 item->setData(KgThemeDelegate::AuthorEmailRole, theme->authorEmail());
111 item->setData(KgThemeDelegate::IdRole, theme->identifier());
112 }
113 _k_updateListSelection(m_provider->currentTheme());
114}
115
116void KgThemeSelector::Private::_k_updateListSelection(const KgTheme* theme)
117{
118 for (int idx = 0; idx < m_list->count(); ++idx)
119 {
120 QListWidgetItem* item = m_list->item(idx);
121 const QByteArray thisId = item->data(KgThemeDelegate::IdRole).toByteArray();
122 if (thisId == theme->identifier())
123 {
124 m_list->setCurrentItem(item, QItemSelectionModel::ClearAndSelect);
125 return;
126 }
127 }
128 //make sure that something is selected
129 if (m_list->count() > 0)
130 {
131 m_list->setCurrentRow(0, QItemSelectionModel::ClearAndSelect);
132 }
133}
134
135void KgThemeSelector::Private::_k_updateProviderSelection()
136{
137 const QListWidgetItem* selItem = m_list->selectedItems().value(0);
138 if (!selItem)
139 {
140 return;
141 }
142 const QByteArray selId = selItem->data(KgThemeDelegate::IdRole).toByteArray();
143 //select the theme with this identifier
144 foreach (const KgTheme* theme, m_provider->themes())
145 {
146 if (theme->identifier() == selId)
147 {
148 m_provider->setCurrentTheme(theme);
149 }
150 }
151}
152
153void KgThemeSelector::Private::_k_showNewStuffDialog()
154{
155 KNS3::DownloadDialog dialog(q);
156 dialog.exec();
157 if (!dialog.changedEntries().isEmpty())
158 {
159 m_provider->rediscoverThemes();
160 fillList();
161 }
162 //restore previous selection
163 _k_updateListSelection(m_provider->currentTheme());
164}
165
166class KgThemeSelector::Dialog : public KDialog
167{
168 public:
169 Dialog(KgThemeSelector* sel, const QString& caption)
170 {
171 setMainWidget(sel);
172 //replace
173 QPushButton* btn = sel->d->m_knsButton;
174 if (btn)
175 {
176 btn->hide();
177 setButtons(Close | User1);
178 setButtonText(User1, btn->text());
179 //cannot use btn->icon() because setButtonIcon() wants KIcon
180 setButtonIcon(User1, KIcon("get-hot-new-stuff"));
181 connect(this, SIGNAL(user1Clicked()), btn, SIGNAL(clicked()));
182 }
183 else
184 {
185 setButtons(Close);
186 }
187 //window caption
188 if (caption.isEmpty())
189 {
190 setCaption(i18nc("@title:window config dialog", "Select theme"));
191 }
192 else
193 {
194 setCaption(caption);
195 }
196 show();
197 }
198 protected:
199 virtual void closeEvent(QCloseEvent* event)
200 {
201 event->accept();
202 KgThemeSelector* sel = qobject_cast<KgThemeSelector*>(mainWidget());
203 //delete myself, but *not* the KgThemeSelector
204 sel->setParent(0);
205 deleteLater();
206 //restore the KNS button
207 if (sel->d->m_knsButton)
208 {
209 sel->d->m_knsButton->show();
210 }
211 }
212};
213
214void KgThemeSelector::showAsDialog(const QString& caption)
215{
216 if (!isVisible())
217 {
218 new KgThemeSelector::Dialog(this, caption);
219 }
220}
221
222//END KgThemeSelector
223//BEGIN KgThemeDelegate
224
225KgThemeDelegate::KgThemeDelegate(QObject* parent)
226 : QStyledItemDelegate(parent)
227{
228 QAbstractItemView* view = qobject_cast<QAbstractItemView*>(parent);
229 if (view)
230 view->setItemDelegate(this);
231}
232
233QRect KgThemeDelegate::thumbnailRect(const QRect& baseRect) const
234{
235 QRect thumbnailBaseRect(QPoint(Metrics::Padding + baseRect.left(), 0), Metrics::ThumbnailBaseSize);
236 thumbnailBaseRect.moveCenter(QPoint(thumbnailBaseRect.center().x(), baseRect.center().y()));
237 if (QApplication::isRightToLeft())
238 thumbnailBaseRect.moveRight(baseRect.right() - Metrics::Padding);
239 return thumbnailBaseRect;
240}
241
242void KgThemeDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
243{
244 const bool rtl = option.direction == Qt::RightToLeft;
245 QRect baseRect = option.rect;
246 //draw background
247 QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, 0);
248 //draw thumbnail
249 QRect thumbnailBaseRect = this->thumbnailRect(baseRect);
250 const QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>().scaled(Metrics::ThumbnailBaseSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
251 QRect thumbnailRect(thumbnailBaseRect.topLeft(), thumbnail.size());
252 thumbnailRect.translate( //center inside thumbnailBaseRect
253 (thumbnailBaseRect.width() - thumbnailRect.width()) / 2,
254 (thumbnailBaseRect.height() - thumbnailRect.height()) / 2
255 );
256 painter->drawPixmap(thumbnailRect.topLeft(), thumbnail);
257 //find metrics: text
258 QStringList texts; QList<QFont> fonts;
259 {
260 QString name = index.data(Qt::DisplayRole).toString();
261 if (name.isEmpty())
262 name = i18n("[No name]");
263 texts << name;
264 QFont theFont(painter->font()); theFont.setBold(true); fonts << theFont;
265 }{
266 QString comment = index.data(DescriptionRole).toString();
267 if (!comment.isEmpty())
268 {
269 texts << comment;
270 fonts << painter->font();
271 }
272 }{
273 QString author = index.data(AuthorRole).toString();
274 if (!author.isEmpty())
275 {
276 const QString authorString = ki18nc("Author attribution, e.g. \"by Jack\"", "by %1").subs(author).toString();
277 texts << authorString;
278 QFont theFont(painter->font()); theFont.setItalic(true); fonts << theFont;
279 }
280 }
281 //TODO: display AuthorEmailRole
282 QList<QRect> textRects; int totalTextHeight = 0;
283 for (int i = 0; i < texts.count(); ++i)
284 {
285 QFontMetrics fm(fonts[i]);
286 textRects << fm.boundingRect(texts[i]);
287 textRects[i].setHeight(qMax(textRects[i].height(), fm.lineSpacing()));
288 totalTextHeight += textRects[i].height();
289 }
290 QRect textBaseRect(baseRect);
291 if (rtl)
292 {
293 textBaseRect.setRight(thumbnailBaseRect.left() - Metrics::Padding);
294 textBaseRect.adjust(Metrics::Padding, Metrics::Padding, 0, -Metrics::Padding);
295 }
296 else
297 {
298 textBaseRect.setLeft(thumbnailBaseRect.right() + Metrics::Padding);
299 textBaseRect.adjust(0, Metrics::Padding, -Metrics::Padding, -Metrics::Padding);
300 }
301 textBaseRect.setHeight(totalTextHeight);
302 textBaseRect.moveTop(baseRect.top() + (baseRect.height() - textBaseRect.height()) / 2);
303 //draw texts
304 QRect currentTextRect(textBaseRect);
305 painter->save();
306 for (int i = 0; i < texts.count(); ++i)
307 {
308 painter->setFont(fonts[i]);
309 const QRect& textRect = textRects[i];
310 currentTextRect.setHeight(textRect.height());
311 const QFontMetrics fm(fonts[i]);
312 const QString text = fm.elidedText(texts[i], Qt::ElideRight, currentTextRect.width());
313 painter->drawText(currentTextRect, Qt::AlignLeft | Qt::AlignVCenter, text);
314 currentTextRect.moveTop(currentTextRect.bottom());
315 }
316 painter->restore();
317}
318
319QSize KgThemeDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
320{
321 Q_UNUSED(option) Q_UNUSED(index)
322 //TODO: take text size into account
323 return QSize(400, Metrics::ThumbnailBaseSize.height() + 2 * Metrics::Padding);
324}
325
326//END KgThemeDelegate
327
328#include "kgthemeselector.moc"
329