1 | /*************************************************************************** |
2 | * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> * |
3 | * * |
4 | * This program is free software; you can redistribute it and/or modify * |
5 | * it under the terms of the GNU General Public License as published by * |
6 | * the Free Software Foundation; either version 2 of the License, or * |
7 | * (at your option) any later version. * |
8 | * * |
9 | * This program is distributed in the hope that it will be useful, * |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
12 | * GNU General Public License for more details. * |
13 | * * |
14 | * You should have received a copy of the GNU General Public License * |
15 | * along with this program; if not, write to the * |
16 | * Free Software Foundation, Inc., * |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * |
18 | ***************************************************************************/ |
19 | |
20 | #include "kfileitemlistview.h" |
21 | |
22 | #include "kfileitemmodelrolesupdater.h" |
23 | #include "kfileitemlistwidget.h" |
24 | #include "kfileitemmodel.h" |
25 | #include <KLocale> |
26 | #include <KStringHandler> |
27 | #include "private/kpixmapmodifier.h" |
28 | |
29 | #include <KDebug> |
30 | #include <KIcon> |
31 | #include <KTextEdit> |
32 | |
33 | #include <QPainter> |
34 | #include <QTextLine> |
35 | #include <QTimer> |
36 | |
37 | // #define KFILEITEMLISTVIEW_DEBUG |
38 | |
39 | namespace { |
40 | // If the visible index range changes, KFileItemModelRolesUpdater is not |
41 | // informed immediatetly, but with a short delay. This ensures that scrolling |
42 | // always feels smooth and is not interrupted by icon loading (which can be |
43 | // quite expensive if a disk access is required to determine the final icon). |
44 | const int ShortInterval = 50; |
45 | |
46 | // If the icon size changes, a longer delay is used. This prevents that |
47 | // the expensive re-generation of all previews is triggered repeatedly when |
48 | // chaning the zoom level. |
49 | const int LongInterval = 300; |
50 | } |
51 | |
52 | KFileItemListView::KFileItemListView(QGraphicsWidget* parent) : |
53 | KStandardItemListView(parent), |
54 | m_modelRolesUpdater(0), |
55 | m_updateVisibleIndexRangeTimer(0), |
56 | m_updateIconSizeTimer(0) |
57 | { |
58 | setAcceptDrops(true); |
59 | |
60 | setScrollOrientation(Qt::Vertical); |
61 | |
62 | m_updateVisibleIndexRangeTimer = new QTimer(this); |
63 | m_updateVisibleIndexRangeTimer->setSingleShot(true); |
64 | m_updateVisibleIndexRangeTimer->setInterval(ShortInterval); |
65 | connect(m_updateVisibleIndexRangeTimer, SIGNAL(timeout()), this, SLOT(updateVisibleIndexRange())); |
66 | |
67 | m_updateIconSizeTimer = new QTimer(this); |
68 | m_updateIconSizeTimer->setSingleShot(true); |
69 | m_updateIconSizeTimer->setInterval(LongInterval); |
70 | connect(m_updateIconSizeTimer, SIGNAL(timeout()), this, SLOT(updateIconSize())); |
71 | |
72 | setVisibleRoles(QList<QByteArray>() << "text" ); |
73 | } |
74 | |
75 | KFileItemListView::~KFileItemListView() |
76 | { |
77 | } |
78 | |
79 | void KFileItemListView::setPreviewsShown(bool show) |
80 | { |
81 | if (!m_modelRolesUpdater) { |
82 | return; |
83 | } |
84 | |
85 | if (m_modelRolesUpdater->previewsShown() != show) { |
86 | beginTransaction(); |
87 | m_modelRolesUpdater->setPreviewsShown(show); |
88 | onPreviewsShownChanged(show); |
89 | endTransaction(); |
90 | } |
91 | } |
92 | |
93 | bool KFileItemListView::previewsShown() const |
94 | { |
95 | return m_modelRolesUpdater ? m_modelRolesUpdater->previewsShown() : false; |
96 | } |
97 | |
98 | void KFileItemListView::setEnlargeSmallPreviews(bool enlarge) |
99 | { |
100 | if (m_modelRolesUpdater) { |
101 | m_modelRolesUpdater->setEnlargeSmallPreviews(enlarge); |
102 | } |
103 | } |
104 | |
105 | bool KFileItemListView::enlargeSmallPreviews() const |
106 | { |
107 | return m_modelRolesUpdater ? m_modelRolesUpdater->enlargeSmallPreviews() : false; |
108 | } |
109 | |
110 | void KFileItemListView::setEnabledPlugins(const QStringList& list) |
111 | { |
112 | if (m_modelRolesUpdater) { |
113 | m_modelRolesUpdater->setEnabledPlugins(list); |
114 | } |
115 | } |
116 | |
117 | QStringList KFileItemListView::enabledPlugins() const |
118 | { |
119 | return m_modelRolesUpdater ? m_modelRolesUpdater->enabledPlugins() : QStringList(); |
120 | } |
121 | |
122 | QPixmap KFileItemListView::createDragPixmap(const KItemSet& indexes) const |
123 | { |
124 | if (!model()) { |
125 | return QPixmap(); |
126 | } |
127 | |
128 | const int itemCount = indexes.count(); |
129 | Q_ASSERT(itemCount > 0); |
130 | if (itemCount == 1) { |
131 | return KItemListView::createDragPixmap(indexes); |
132 | } |
133 | |
134 | // If more than one item is dragged, align the items inside a |
135 | // rectangular grid. The maximum grid size is limited to 5 x 5 items. |
136 | int xCount; |
137 | int size; |
138 | if (itemCount > 16) { |
139 | xCount = 5; |
140 | size = KIconLoader::SizeSmall; |
141 | } else if (itemCount > 9) { |
142 | xCount = 4; |
143 | size = KIconLoader::SizeSmallMedium; |
144 | } else { |
145 | xCount = 3; |
146 | size = KIconLoader::SizeMedium; |
147 | } |
148 | |
149 | if (itemCount < xCount) { |
150 | xCount = itemCount; |
151 | } |
152 | |
153 | int yCount = itemCount / xCount; |
154 | if (itemCount % xCount != 0) { |
155 | ++yCount; |
156 | } |
157 | if (yCount > xCount) { |
158 | yCount = xCount; |
159 | } |
160 | |
161 | // Draw the selected items into the grid cells. |
162 | QPixmap dragPixmap(xCount * size + xCount, yCount * size + yCount); |
163 | dragPixmap.fill(Qt::transparent); |
164 | |
165 | QPainter painter(&dragPixmap); |
166 | int x = 0; |
167 | int y = 0; |
168 | |
169 | foreach (int index, indexes) { |
170 | QPixmap pixmap = model()->data(index).value("iconPixmap" ).value<QPixmap>(); |
171 | if (pixmap.isNull()) { |
172 | KIcon icon(model()->data(index).value("iconName" ).toString()); |
173 | pixmap = icon.pixmap(size, size); |
174 | } else { |
175 | KPixmapModifier::scale(pixmap, QSize(size, size)); |
176 | } |
177 | |
178 | painter.drawPixmap(x, y, pixmap); |
179 | |
180 | x += size + 1; |
181 | if (x >= dragPixmap.width()) { |
182 | x = 0; |
183 | y += size + 1; |
184 | } |
185 | |
186 | if (y >= dragPixmap.height()) { |
187 | break; |
188 | } |
189 | } |
190 | |
191 | return dragPixmap; |
192 | } |
193 | |
194 | KItemListWidgetCreatorBase* KFileItemListView::defaultWidgetCreator() const |
195 | { |
196 | return new KItemListWidgetCreator<KFileItemListWidget>(); |
197 | } |
198 | |
199 | void KFileItemListView::initializeItemListWidget(KItemListWidget* item) |
200 | { |
201 | KStandardItemListView::initializeItemListWidget(item); |
202 | |
203 | // Make sure that the item has an icon. |
204 | QHash<QByteArray, QVariant> data = item->data(); |
205 | if (!data.contains("iconName" ) && data["iconPixmap" ].value<QPixmap>().isNull()) { |
206 | Q_ASSERT(qobject_cast<KFileItemModel*>(model())); |
207 | KFileItemModel* fileItemModel = static_cast<KFileItemModel*>(model()); |
208 | |
209 | const KFileItem fileItem = fileItemModel->fileItem(item->index()); |
210 | data.insert("iconName" , fileItem.iconName()); |
211 | item->setData(data, QSet<QByteArray>() << "iconName" ); |
212 | } |
213 | } |
214 | |
215 | void KFileItemListView::onPreviewsShownChanged(bool shown) |
216 | { |
217 | Q_UNUSED(shown); |
218 | } |
219 | |
220 | void KFileItemListView::onItemLayoutChanged(ItemLayout current, ItemLayout previous) |
221 | { |
222 | KStandardItemListView::onItemLayoutChanged(current, previous); |
223 | triggerVisibleIndexRangeUpdate(); |
224 | } |
225 | |
226 | void KFileItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous) |
227 | { |
228 | Q_ASSERT(qobject_cast<KFileItemModel*>(current)); |
229 | KStandardItemListView::onModelChanged(current, previous); |
230 | |
231 | delete m_modelRolesUpdater; |
232 | m_modelRolesUpdater = 0; |
233 | |
234 | if (current) { |
235 | m_modelRolesUpdater = new KFileItemModelRolesUpdater(static_cast<KFileItemModel*>(current), this); |
236 | m_modelRolesUpdater->setIconSize(availableIconSize()); |
237 | |
238 | applyRolesToModel(); |
239 | } |
240 | } |
241 | |
242 | void KFileItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous) |
243 | { |
244 | KStandardItemListView::onScrollOrientationChanged(current, previous); |
245 | triggerVisibleIndexRangeUpdate(); |
246 | } |
247 | |
248 | void KFileItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous) |
249 | { |
250 | Q_UNUSED(current); |
251 | Q_UNUSED(previous); |
252 | triggerVisibleIndexRangeUpdate(); |
253 | } |
254 | |
255 | void KFileItemListView::onScrollOffsetChanged(qreal current, qreal previous) |
256 | { |
257 | KStandardItemListView::onScrollOffsetChanged(current, previous); |
258 | triggerVisibleIndexRangeUpdate(); |
259 | } |
260 | |
261 | void KFileItemListView::onVisibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous) |
262 | { |
263 | KStandardItemListView::onVisibleRolesChanged(current, previous); |
264 | applyRolesToModel(); |
265 | } |
266 | |
267 | void KFileItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous) |
268 | { |
269 | KStandardItemListView::onStyleOptionChanged(current, previous); |
270 | triggerIconSizeUpdate(); |
271 | } |
272 | |
273 | void KFileItemListView::onSupportsItemExpandingChanged(bool supportsExpanding) |
274 | { |
275 | applyRolesToModel(); |
276 | KStandardItemListView::onSupportsItemExpandingChanged(supportsExpanding); |
277 | triggerVisibleIndexRangeUpdate(); |
278 | } |
279 | |
280 | void KFileItemListView::onTransactionBegin() |
281 | { |
282 | if (m_modelRolesUpdater) { |
283 | m_modelRolesUpdater->setPaused(true); |
284 | } |
285 | } |
286 | |
287 | void KFileItemListView::onTransactionEnd() |
288 | { |
289 | if (!m_modelRolesUpdater) { |
290 | return; |
291 | } |
292 | |
293 | // Only unpause the model-roles-updater if no timer is active. If one |
294 | // timer is still active the model-roles-updater will be unpaused later as |
295 | // soon as the timer has been exceeded. |
296 | const bool timerActive = m_updateVisibleIndexRangeTimer->isActive() || |
297 | m_updateIconSizeTimer->isActive(); |
298 | if (!timerActive) { |
299 | m_modelRolesUpdater->setPaused(false); |
300 | } |
301 | } |
302 | |
303 | void KFileItemListView::resizeEvent(QGraphicsSceneResizeEvent* event) |
304 | { |
305 | KStandardItemListView::resizeEvent(event); |
306 | triggerVisibleIndexRangeUpdate(); |
307 | } |
308 | |
309 | void KFileItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) |
310 | { |
311 | KStandardItemListView::slotItemsRemoved(itemRanges); |
312 | } |
313 | |
314 | void KFileItemListView::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous) |
315 | { |
316 | const QByteArray sortRole = model()->sortRole(); |
317 | if (!visibleRoles().contains(sortRole)) { |
318 | applyRolesToModel(); |
319 | } |
320 | |
321 | KStandardItemListView::slotSortRoleChanged(current, previous); |
322 | } |
323 | |
324 | void KFileItemListView::triggerVisibleIndexRangeUpdate() |
325 | { |
326 | if (!model()) { |
327 | return; |
328 | } |
329 | m_modelRolesUpdater->setPaused(true); |
330 | |
331 | // If the icon size has been changed recently, wait until |
332 | // m_updateIconSizeTimer expires. |
333 | if (!m_updateIconSizeTimer->isActive()) { |
334 | m_updateVisibleIndexRangeTimer->start(); |
335 | } |
336 | } |
337 | |
338 | void KFileItemListView::updateVisibleIndexRange() |
339 | { |
340 | if (!m_modelRolesUpdater) { |
341 | return; |
342 | } |
343 | |
344 | const int index = firstVisibleIndex(); |
345 | const int count = lastVisibleIndex() - index + 1; |
346 | m_modelRolesUpdater->setMaximumVisibleItems(maximumVisibleItems()); |
347 | m_modelRolesUpdater->setVisibleIndexRange(index, count); |
348 | m_modelRolesUpdater->setPaused(isTransactionActive()); |
349 | } |
350 | |
351 | void KFileItemListView::triggerIconSizeUpdate() |
352 | { |
353 | if (!model()) { |
354 | return; |
355 | } |
356 | m_modelRolesUpdater->setPaused(true); |
357 | m_updateIconSizeTimer->start(); |
358 | |
359 | // The visible index range will be updated when m_updateIconSizeTimer expires. |
360 | // Stop m_updateVisibleIndexRangeTimer to prevent an expensive re-generation |
361 | // of all previews (note that the user might change the icon size again soon). |
362 | m_updateVisibleIndexRangeTimer->stop(); |
363 | } |
364 | |
365 | void KFileItemListView::updateIconSize() |
366 | { |
367 | if (!m_modelRolesUpdater) { |
368 | return; |
369 | } |
370 | |
371 | m_modelRolesUpdater->setIconSize(availableIconSize()); |
372 | |
373 | // Update the visible index range (which has most likely changed after the |
374 | // icon size change) before unpausing m_modelRolesUpdater. |
375 | const int index = firstVisibleIndex(); |
376 | const int count = lastVisibleIndex() - index + 1; |
377 | m_modelRolesUpdater->setVisibleIndexRange(index, count); |
378 | |
379 | m_modelRolesUpdater->setPaused(isTransactionActive()); |
380 | } |
381 | |
382 | void KFileItemListView::applyRolesToModel() |
383 | { |
384 | if (!model()) { |
385 | return; |
386 | } |
387 | |
388 | Q_ASSERT(qobject_cast<KFileItemModel*>(model())); |
389 | KFileItemModel* fileItemModel = static_cast<KFileItemModel*>(model()); |
390 | |
391 | // KFileItemModel does not distinct between "visible" and "invisible" roles. |
392 | // Add all roles that are mandatory for having a working KFileItemListView: |
393 | QSet<QByteArray> roles = visibleRoles().toSet(); |
394 | roles.insert("iconPixmap" ); |
395 | roles.insert("iconName" ); |
396 | roles.insert("text" ); |
397 | roles.insert("isDir" ); |
398 | roles.insert("isLink" ); |
399 | if (supportsItemExpanding()) { |
400 | roles.insert("isExpanded" ); |
401 | roles.insert("isExpandable" ); |
402 | roles.insert("expandedParentsCount" ); |
403 | } |
404 | |
405 | // Assure that the role that is used for sorting will be determined |
406 | roles.insert(fileItemModel->sortRole()); |
407 | |
408 | fileItemModel->setRoles(roles); |
409 | m_modelRolesUpdater->setRoles(roles); |
410 | } |
411 | |
412 | QSize KFileItemListView::availableIconSize() const |
413 | { |
414 | const KItemListStyleOption& option = styleOption(); |
415 | const int iconSize = option.iconSize; |
416 | if (itemLayout() == IconsLayout) { |
417 | const int maxIconWidth = itemSize().width() - 2 * option.padding; |
418 | return QSize(maxIconWidth, iconSize); |
419 | } |
420 | |
421 | return QSize(iconSize, iconSize); |
422 | } |
423 | |
424 | #include "kfileitemlistview.moc" |
425 | |