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 | |
36 | namespace Metrics |
37 | { |
38 | const int Padding = 6; |
39 | const QSize ThumbnailBaseSize(64, 64); |
40 | } |
41 | |
42 | //BEGIN KgThemeSelector |
43 | |
44 | class 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 | |
62 | KgThemeSelector::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 | |
95 | KgThemeSelector::~KgThemeSelector() |
96 | { |
97 | delete d; |
98 | } |
99 | |
100 | void 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 | |
116 | void 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 | |
135 | void 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 | |
153 | void 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 | |
166 | class 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 | |
214 | void 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 | |
225 | KgThemeDelegate::KgThemeDelegate(QObject* parent) |
226 | : QStyledItemDelegate(parent) |
227 | { |
228 | QAbstractItemView* view = qobject_cast<QAbstractItemView*>(parent); |
229 | if (view) |
230 | view->setItemDelegate(this); |
231 | } |
232 | |
233 | QRect 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 | |
242 | void 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 = 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 | |
319 | QSize 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 | |