* use Backend term in place of Package as it suits better
[plasma-media-center:mainline.git] / applets / mediabrowser / abstractmediaitemview.cpp
1 /***************************************************************************
2  *   Copyright 2009 by Alessandro Diaferia <alediaferia@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 #include "abstractmediaitemview.h"
20 #include "viewitem.h"
21
22 // Plasma
23 #include <Plasma/ScrollBar>
24 #include <Plasma/Theme>
25 #include <Plasma/Animator>
26
27 // KDE
28 #include <KFileItem>
29 #include <KDirModel>
30 #include <KDirLister>
31 #include <QPainter>
32 #include <KIconLoader>
33 #include <KGlobalSettings>
34 #include <KDebug>
35
36 #include <Nepomuk/Resource>
37
38 // Qt
39 #include <QScrollBar>
40 #include <QAbstractItemModel>
41 #include <QLocale>
42 #include <QGraphicsSceneHoverEvent>
43 #include <QApplication>
44
45 AbstractMediaItemView::AbstractMediaItemView(QGraphicsItem *parent) : QGraphicsWidget(parent),
46 m_model(0),
47 m_scrollBar(new Plasma::ScrollBar(this)),
48 m_scrollMode(PerPixel),
49 m_hoveredItem(0),
50 m_blurred(true)
51
52 {
53     setFlags(QGraphicsItem::ItemClipsChildrenToShape | QGraphicsItem::ItemIsFocusable);
54     setAcceptsHoverEvents(true);
55     setIconSize(KIconLoader::SizeEnormous);
56     connect (m_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(layoutItems()));
57     m_scrollBar->setZValue(1000);
58
59     QStyleOptionViewItemV4 opt = m_option;
60     opt.state |= QStyle::State_MouseOver;
61     m_hoverIndicator = new ViewItem(opt, this);
62     m_hoverIndicator->setZValue(-1000);
63     m_hoverIndicator->setPos(0, -100);
64
65     m_hoverIndicator->setAcceptedMouseButtons(Qt::LeftButton);
66     connect (m_hoverIndicator, SIGNAL(ratingActivated(int)), this, SLOT(setRating(int)));
67     connect (m_hoverIndicator, SIGNAL(itemSelected()), this, SLOT(slotItemSelected()));
68 }
69
70 AbstractMediaItemView::~AbstractMediaItemView()
71 {
72     qDeleteAll(m_items);
73     m_items.clear();
74 }
75
76 void AbstractMediaItemView::setIconSize(int size)
77 {
78     m_iconSize = size;
79     update();
80 }
81
82 int AbstractMediaItemView::iconSize() const
83 {
84     return m_iconSize;
85 }
86
87 void AbstractMediaItemView::setModel(QAbstractItemModel *model)
88 {
89     if (m_model) {
90         disconnect(m_model);
91         reset();
92         delete m_model;
93     }
94
95     m_model = model;
96     m_rootIndex = QModelIndex();
97     if (!m_model) {
98         return;
99     }
100
101     qDeleteAll(m_items);
102     m_items.clear();
103
104     generateItems(m_rootIndex, 0, model->rowCount() - 1);
105     connect (m_model, SIGNAL(modelAboutToBeReset()), this, SLOT(reset()));
106     connect (m_model, SIGNAL(rowsInserted(const QModelIndex & , int, int)), this, SLOT(generateItems(const QModelIndex&, int, int)));
107     connect (m_model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(removeItems(const QModelIndex&, int, int)));
108 }
109
110 QAbstractItemModel* AbstractMediaItemView::model()
111 {
112     return m_model;
113 }
114
115 QScrollBar* AbstractMediaItemView::verticalScrollBar()
116 {
117     return m_scrollBar->nativeWidget();
118 }
119
120 QRect AbstractMediaItemView::contentsArea() const
121 {
122     if (!m_scrollBar->isVisible()) {
123         return contentsRect().toRect();
124     }
125     QRect contents = contentsRect().toRect();
126     contents.setWidth(contents.width() - m_scrollBar->size().width());
127
128     return contents;
129 }
130
131 void AbstractMediaItemView::hideVerticalScrollBar()
132 {
133     m_scrollBar->hide();
134 }
135
136 bool AbstractMediaItemView::isVerticalScrollBarHidden()
137 {
138     return !m_scrollBar->isVisible();
139 }
140
141 void AbstractMediaItemView::resizeEvent(QGraphicsSceneResizeEvent *event)
142 {
143     m_scrollBar->resize(m_scrollBar->size().width(), contentsRect().height());
144     m_scrollBar->setPos(contentsRect().width() - m_scrollBar->size().width(), 0);
145
146     layoutItems();
147 }
148
149 void AbstractMediaItemView::setupOptions()
150 {
151     m_option.palette.setColor(QPalette::Text, Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor));
152     m_option.font = Plasma::Theme::defaultTheme()->font(Plasma::Theme::DesktopFont);
153     m_option.fontMetrics = QFontMetrics(m_option.font);
154     m_option.decorationSize = QSize(iconSize(), iconSize());
155     m_option.locale = QLocale::system();
156     m_option.widget = 0;
157     m_option.state |= QStyle::State_Enabled;
158 }
159
160 void AbstractMediaItemView::setRootIndex(const QModelIndex &index)
161 {
162     m_rootIndex = index;
163 }
164
165 QModelIndex AbstractMediaItemView::rootIndex() const
166 {
167     return m_rootIndex;
168 }
169
170 void AbstractMediaItemView::setScrollMode(ScrollMode mode)
171 {
172     m_scrollMode = mode;
173 }
174
175 AbstractMediaItemView::ScrollMode AbstractMediaItemView::scrollMode()
176 {
177     return m_scrollMode;
178 }
179
180 void AbstractMediaItemView::invalidate()
181 {
182     qDeleteAll(m_items);
183     m_items.clear();
184 }
185
186 void AbstractMediaItemView::updateHoveredItem(ViewItem *item)
187 {
188     if (!item || !m_items.contains(item)) {
189         m_hoverIndicator->hide();
190         if (m_hoverIndicator->m_nepomuk) {
191             m_hoverIndicator->updateHoverRating(QPoint(-1, -1));
192         }
193         if (m_hoveredItem) {
194             update();
195         }
196         m_hoveredItem = 0;
197         return;
198     }
199
200     QModelIndex index = item->index();
201     KFileItem file = index.isValid() ? index.data(KDirModel::FileItemRole).value<KFileItem>() : KFileItem();
202     if (file.isNull() || file.isDir()) {
203         m_hoverIndicator->setIsNotFile(true);
204     } else {
205         m_hoverIndicator->setIsNotFile(false);
206     }
207
208     m_hoveredItem = item;
209     m_hoverIndicator->setSelected(m_hoveredItem->isSelected());
210     m_hoverIndicator->m_nepomuk = item->m_nepomuk;
211     m_hoverIndicator->show();
212
213     if (m_hoveredItem->size() != m_hoverIndicator->size()) {
214         m_hoverIndicator->resize(m_hoveredItem->size());
215     }
216
217     Plasma::Animator::self()->moveItem(m_hoverIndicator, Plasma::Animator::SlideInMovement, m_hoveredItem->pos().toPoint());
218 }
219
220 void AbstractMediaItemView::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
221 {
222     updateHoveredItem(itemFromPos(event->pos()));
223     if (m_hoveredItem) {
224         m_hoverIndicator->m_rating = m_hoveredItem->m_nepomuk ? m_hoveredItem->m_resource->rating() : 0;
225     }
226
227     if (m_hoverIndicator->m_nepomuk) {
228         m_hoverIndicator->updateHoverRating(mapToItem(m_hoverIndicator, event->pos()).toPoint());
229     }
230 }
231
232 void AbstractMediaItemView::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
233 {
234     updateHoveredItem(itemFromPos(event->pos()));
235     if (m_hoveredItem) {
236         m_hoverIndicator->m_rating = m_hoveredItem->m_nepomuk ? m_hoveredItem->m_resource->rating() : 0;
237     }
238     if (m_hoverIndicator->m_nepomuk) {
239         m_hoverIndicator->updateHoverRating(mapToItem(m_hoverIndicator, event->pos()).toPoint());
240     }
241 }
242
243 void AbstractMediaItemView::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
244 {
245     Q_UNUSED(event);
246     updateHoveredItem(0);
247 }
248
249 void AbstractMediaItemView::setRating(int rating)
250 {
251     if (!m_hoveredItem) {
252         return;
253     }
254
255     m_hoveredItem->m_resource->setRating(rating);
256 }
257
258 QModelIndex AbstractMediaItemView::indexFromPos(const QPointF &pos)
259 {
260     ViewItem *item = itemFromPos(pos);
261     if (item) {
262         return item->index();
263     }
264     return QModelIndex();
265 }
266
267 ViewItem* AbstractMediaItemView::itemFromPos(const QPointF &pos)
268 {
269     foreach (ViewItem *item, m_items) {
270         if (item->geometry().contains(pos)) {
271             return item;
272         }
273     }
274
275     return 0;
276 }
277
278 void AbstractMediaItemView::mousePressEvent(QGraphicsSceneMouseEvent *event)
279 {
280     event->accept();
281     setFocus(Qt::OtherFocusReason);
282 }
283
284 void AbstractMediaItemView::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
285 {
286     if (!(event->buttons() & Qt::LeftButton)) {
287         return;
288     }
289
290     if ((event->pos() - event->buttonDownPos(Qt::LeftButton)).toPoint().manhattanLength()
291         >= QApplication::startDragDistance()) {
292         tryDrag(event);
293     }
294 }
295
296 void AbstractMediaItemView::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
297 {
298     if (KGlobalSettings::singleClick()) {
299         itemClickEvent(event);
300     }
301 }
302
303 void AbstractMediaItemView::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
304 {
305     if (KGlobalSettings::singleClick()) {
306         return;
307     }
308
309     itemClickEvent(event);
310 }
311
312 void AbstractMediaItemView::itemClickEvent(QGraphicsSceneMouseEvent *event)
313 {
314     ViewItem *viewItem = itemFromPos(event->pos());
315     if (!viewItem) {
316         return;
317     }
318
319
320     QModelIndex index = viewItem->index();
321
322     KDirModel *model = qobject_cast<KDirModel*>(m_model);
323     if (model && model->dirLister()->isFinished()) {
324         KFileItem item = index.isValid() ? index.data(KDirModel::FileItemRole).value<KFileItem>() : KFileItem();
325         if (item.isNull()) {
326             return;
327         }
328         if (item.isDir()) {
329             model->dirLister()->stop();
330             m_history << model->dirLister()->url();
331             model->dirLister()->openUrl(item.url());
332             m_hoveredItem = 0;
333             m_rootIndex = model->indexForItem(item);
334             listMediaInDirectory();
335             emit directoryChanged();
336         } else if (item.isLocalFile()) {
337             MediaCenter::Media media;
338             media.first = MediaCenter::getType(item.url().path());
339             media.second = item.localPath();
340             emit mediasActivated(QList<MediaCenter::Media>() << media);
341             emit mediaActivated(media);
342         } // TODO check the chance to have remote files or
343     }
344
345     emit indexActivated(index);
346 }
347
348 void AbstractMediaItemView::listMediaInDirectory()
349 {
350     MediaCenter::Media media;
351     QList<MediaCenter::Media> list;
352     KFileItem item;
353     KFileItemList items;
354     KDirModel *model = qobject_cast<KDirModel*>(m_model);
355
356     if (model) {
357         if (!model->dirLister()->isFinished()) {
358             KDirLister *lister = model->dirLister();
359             //FIXME:This signal is emitted several times??? (Check what folerview does)
360             connect (lister, SIGNAL(completed()), this, SLOT(listMediaInDirectory()));
361         }
362
363         if (model && model->dirLister()->isFinished()) {
364             items = model->dirLister()->itemsForDir(model->dirLister()->url(), KDirLister::AllItems);
365         }
366
367         foreach (const KFileItem &item, items) {
368             if (MediaCenter::getType(item.url().path()) == MediaCenter::Picture) {
369                 media.first = MediaCenter::getType(item.url().path());
370                 media.second = item.url().path();
371                 list << media;
372             }
373         }
374         //At the moment I only want pictures. Maybe I can create a signal for each mediatype?
375         emit mediasListInDirectory(list);
376     }
377 }
378
379 void AbstractMediaItemView::tryDrag(QGraphicsSceneMouseEvent *event)
380 {
381     QDrag *drag = new QDrag(event->widget());
382     QMimeData *mime = new QMimeData;
383
384     // NOTE: for drag to work a KDirModel::FileItemRole is needed.
385     //       if none is found no drag can occur.
386     ViewItem *viewItem = itemFromPos(event->pos());
387     if (!viewItem) {
388         return;
389     }
390
391     QModelIndex index = viewItem->index();
392     if (!index.isValid()) {
393         return;
394     }
395     KFileItem item = index.data(KDirModel::FileItemRole).value<KFileItem>();
396     if (item.isNull()) {
397         return;
398     }
399     mime->setUrls(QList<QUrl>() << item.url());
400     mime->setText(item.url().pathOrUrl());
401     drag->setMimeData(mime);
402
403     QPixmap pixmap(viewItem->size().toSize());
404     pixmap.fill(Qt::transparent);
405     QPainter p(&pixmap);
406
407     QStyleOptionGraphicsItem o;
408     o.rect = pixmap.rect();
409
410     viewItem->paint(&p, &o, 0);
411
412     drag->setPixmap(pixmap);
413
414     drag->exec(Qt::MoveAction);
415 }
416
417 void AbstractMediaItemView::goPrevious()
418 {
419     if (m_history.isEmpty()) {
420         return;
421     }
422
423     KDirModel *model = qobject_cast<KDirModel*>(this->model());
424     if (!model) {
425         return;
426     }
427
428     if (!m_history.last().isValid()) {
429         return;
430     }
431     model->dirLister()->stop();
432     model->dirLister()->openUrl(m_history.last());
433     m_rootIndex = model->indexForUrl(m_history.last());
434     m_history.removeLast();
435     emit directoryChanged();
436 }
437
438 void AbstractMediaItemView::setDrawBlurredText(bool set)
439 {
440     if (m_blurred == set) {
441         return;
442     }
443
444     kDebug() << "setting blurred text to" << set;
445     m_blurred = set;
446     foreach (ViewItem *item, m_items) {
447         item->setDrawBlurredText(set);
448     }
449 }
450
451 bool AbstractMediaItemView::drawBlurredText()
452 {
453     return m_blurred;
454 }
455
456 void AbstractMediaItemView::reset()
457 {
458     qDeleteAll(m_items);
459     m_items.clear();
460     m_hoveredItem = 0;
461 }
462
463 void AbstractMediaItemView::removeItems(const QModelIndex &parent, int start, int end)
464 {
465     if (m_rootIndex != parent) {
466         return;
467     }
468
469     if (start < 0 || end >= m_items.count()) {
470         return;
471     }
472     m_hoveredItem = 0;
473     QList<ViewItem*>::iterator startIt = m_items.begin();
474     QList<ViewItem*>::iterator endIt = m_items.begin();
475     startIt += (start);
476     endIt += (end);
477     for (; startIt <= endIt; ++startIt) {
478         delete *startIt;
479         m_items.erase(startIt);
480     }
481     layoutItems();
482     updateScrollBar();
483 }
484
485 void AbstractMediaItemView::setBrowsingType(const AbstractBrowsingBackend::BrowsingType &type)
486 {
487     m_browsingType = type;
488 }
489
490 AbstractBrowsingBackend::BrowsingType AbstractMediaItemView::browsingType() const
491 {
492     return m_browsingType;
493 }
494
495 void AbstractMediaItemView::slotItemSelected()
496 {
497     QModelIndex index = m_hoveredItem->index();
498     MediaCenter::Media media;
499     KDirModel *model = qobject_cast<KDirModel*>(m_model);
500     if (model && model->dirLister()->isFinished()) {
501         KFileItem item = index.isValid() ? index.data(KDirModel::FileItemRole).value<KFileItem>() : KFileItem();
502         if (item.isNull()) {
503             return;
504         }
505         if (item.isDir()) {
506             return;
507         }
508         if (!item.isDir()) {
509             media.first = MediaCenter::getType(item.url().path());
510             media.second = item.url().path();
511         }
512     }
513     if (m_hoveredItem->isSelected()) {
514         m_hoveredItem->setSelected(false);
515         emit mediaUnselected(media);
516         return;
517     }
518     if (!m_hoveredItem->isSelected()) {
519         m_hoveredItem->setSelected(true);
520         emit mediaSelected(media);
521         return;
522     }
523 }