add more spacing
[personal-kdebase.git] / apps / plasma / applets / folderview / listview.cpp
blobc2bf88f2d4bfeef5ef195613b6a10851c812a9cc
1 /*
2 * Copyright © 2008 Fredrik Höglund <fredrik@kde.org>
3 * Copyright © 2008 Rafael Fernández López <ereslibre@kde.org>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
21 #include "listview.h"
23 #include <QApplication>
24 #include <QDebug>
25 #include <QGraphicsView>
26 #include <QGraphicsSceneHoverEvent>
27 #include <QGraphicsSceneMouseEvent>
28 #include <QItemSelectionModel>
29 #include <QPainter>
30 #include <QScrollBar>
31 #include <QStyleOptionGraphicsItem>
33 #include <KDirModel>
34 #include <KFileItemDelegate>
35 #include <KGlobalSettings>
37 #include "proxymodel.h"
39 #include "plasma/containment.h"
40 #include "plasma/corona.h"
41 #include "plasma/paintutils.h"
42 #include "plasma/theme.h"
45 ListView::ListView(QGraphicsWidget *parent)
46 : AbstractItemView(parent),
47 m_rowHeight(-1),
48 m_dragInProgress(false),
49 m_wordWrap(true),
50 m_drawShadows(true)
52 setAcceptHoverEvents(true);
53 setAcceptDrops(true);
54 setCacheMode(NoCache);
57 ListView::~ListView()
61 void ListView::setModel(QAbstractItemModel *model)
63 AbstractItemView::setModel(model);
64 updateSizeHint();
67 void ListView::setIconSize(const QSize &size)
69 if (size != m_iconSize) {
70 m_iconSize = size;
71 m_rowHeight = -1;
72 markAreaDirty(visibleArea());
76 void ListView::setWordWrap(bool on)
78 if (m_wordWrap != on) {
79 m_wordWrap = on;
80 m_rowHeight = -1;
81 markAreaDirty(visibleArea());
85 bool ListView::wordWrap() const
87 return m_wordWrap;
90 void ListView::setDrawShadows(bool on)
92 if (m_drawShadows != on) {
93 m_drawShadows = on;
94 markAreaDirty(visibleArea());
98 bool ListView::drawShadows() const
100 return m_drawShadows;
103 void ListView::rowsInserted(const QModelIndex &parent, int first, int last)
105 Q_UNUSED(parent)
106 Q_UNUSED(first)
107 Q_UNUSED(last)
109 markAreaDirty(visibleArea());
110 updateScrollBar();
111 updateSizeHint();
114 void ListView::rowsRemoved(const QModelIndex &parent, int first, int last)
116 Q_UNUSED(parent)
117 Q_UNUSED(first)
118 Q_UNUSED(last)
120 markAreaDirty(visibleArea());
121 updateScrollBar();
122 updateSizeHint();
125 void ListView::modelReset()
127 markAreaDirty(visibleArea());
128 updateScrollBar();
129 updateSizeHint();
132 void ListView::layoutChanged()
134 markAreaDirty(visibleArea());
135 updateScrollBar();
136 updateSizeHint();
139 void ListView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
141 markAreaDirty(visualRect(topLeft) | visualRect(bottomRight));
144 void ListView::updateScrollBar()
146 if (!m_model) {
147 return;
150 if (m_rowHeight == -1 && m_model->rowCount() > 0) {
151 // Use the height of the first item for all items
152 const QSize size = m_delegate->sizeHint(viewOptions(), m_model->index(0, 0));
153 m_rowHeight = size.height();
156 int max = int(m_rowHeight * m_model->rowCount() - contentsRect().height());
158 // Keep the scrollbar handle at the bottom if it was at the bottom and the viewport
159 // has grown vertically
160 bool updateValue = (m_scrollBar->minimum() != m_scrollBar->maximum()) &&
161 (max > m_scrollBar->maximum()) && (m_scrollBar->value() == m_scrollBar->maximum());
163 m_scrollBar->setRange(0, max);
164 m_scrollBar->setPageStep(contentsRect().height());
165 m_scrollBar->setSingleStep(m_rowHeight);
167 if (updateValue) {
168 m_scrollBar->setValue(max);
171 if (max > 0) {
172 m_scrollBar->show();
173 } else {
174 m_scrollBar->hide();
178 void ListView::updateSizeHint()
180 if (m_rowHeight == -1 && m_model->rowCount() > 0) {
181 // Use the height of the first item for all items
182 const QSize size = m_delegate->sizeHint(viewOptions(), m_model->index(0, 0));
183 m_rowHeight = size.height();
186 QFontMetrics fm(font());
187 setPreferredSize(m_iconSize.width() + fm.lineSpacing() * 18, m_rowHeight * m_model->rowCount());
190 QRect ListView::visualRect(const QModelIndex &index) const
192 if (!index.isValid() || index.row() >= m_model->rowCount()) {
193 return QRect();
196 QRectF cr = contentsRect();
197 return QRect(cr.left(), cr.top() + index.row() * m_rowHeight, cr.width(), m_rowHeight);
200 void ListView::updateTextShadows(const QColor &textColor)
202 if (!m_drawShadows) {
203 m_delegate->setShadowColor(Qt::transparent);
204 return;
207 QColor shadowColor;
209 // Use black shadows with bright text, and white shadows with dark text.
210 if (qGray(textColor.rgb()) > 192) {
211 shadowColor = Qt::black;
212 } else {
213 shadowColor = Qt::white;
216 if (m_delegate->shadowColor() != shadowColor)
218 m_delegate->setShadowColor(shadowColor);
220 // Center white shadows to create a halo effect, and offset dark shadows slightly.
221 if (shadowColor == Qt::white) {
222 m_delegate->setShadowOffset(QPoint(0, 0));
223 } else {
224 m_delegate->setShadowOffset(QPoint(layoutDirection() == Qt::RightToLeft ? -1 : 1, 1));
229 void ListView::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
231 Q_UNUSED(widget)
233 int offset = m_scrollBar->value();
234 const QRect cr = contentsRect().toRect();
235 if (!cr.isValid()) {
236 return;
239 QRect clipRect = cr & option->exposedRect.toAlignedRect();
240 if (clipRect.isEmpty()) {
241 return;
244 prepareBackBuffer();
246 painter->setClipRect(clipRect);
249 // Update the dirty region in the backbuffer
250 // =========================================
251 if (!m_dirtyRegion.isEmpty()) {
252 QStyleOptionViewItemV4 opt = viewOptions();
253 int width = m_scrollBar->isVisible() ? cr.width() - m_scrollBar->geometry().width() : cr.width();
255 if (m_rowHeight == -1 && m_model->rowCount() > 0) {
256 // Use the height of the first item for all items
257 const QSize size = m_delegate->sizeHint(opt, m_model->index(0, 0));
258 m_rowHeight = size.height();
261 QPainter p(&m_pixmap);
262 p.translate(-cr.topLeft() - QPoint(0, offset));
263 p.setClipRegion(m_dirtyRegion);
265 // Clear the dirty region
266 p.setCompositionMode(QPainter::CompositionMode_Source);
267 p.fillRect(mapToViewport(cr).toAlignedRect(), Qt::transparent);
268 p.setCompositionMode(QPainter::CompositionMode_SourceOver);
270 for (int i = 0; i < m_model->rowCount(); i++) {
271 opt.rect = QRect(cr.left(), cr.top() + i * m_rowHeight, width, m_rowHeight);
273 if (!m_dirtyRegion.intersects(opt.rect)) {
274 continue;
277 const QModelIndex index = m_model->index(i, 0);
278 opt.state &= ~(QStyle::State_HasFocus | QStyle::State_MouseOver | QStyle::State_Selected);
280 if (m_selectionModel->isSelected(index)) {
281 if (m_dragInProgress) {
282 continue;
284 updateTextShadows(palette().color(QPalette::HighlightedText));
285 opt.state |= QStyle::State_Selected | QStyle::State_MouseOver;
286 } else {
287 updateTextShadows(palette().color(QPalette::Text));
290 if (hasFocus() && index == m_selectionModel->currentIndex()) {
291 opt.state |= QStyle::State_HasFocus;
294 m_delegate->paint(&p, opt, index);
297 m_dirtyRegion = QRegion();
300 syncBackBuffer(painter, clipRect);
303 QModelIndex ListView::indexAt(const QPointF &pos) const
305 int row = pos.y() / m_rowHeight;
306 return row < m_model->rowCount() ? m_model->index(row, 0) : QModelIndex();
309 void ListView::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
311 const QPoint pos = mapToViewport(event->pos()).toPoint();
312 const QModelIndex index = indexAt(pos);
314 if (m_selectionModel->currentIndex().isValid()) {
315 markAreaDirty(visualRect(m_selectionModel->currentIndex()));
318 if (index.isValid()) {
319 m_selectionModel->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
320 markAreaDirty(visualRect(index));
324 void ListView::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
326 const QPoint pos = mapToViewport(event->pos()).toPoint();
327 const QModelIndex index = indexAt(pos);
329 if (index != m_selectionModel->currentIndex()) {
330 markAreaDirty(visualRect(index));
331 markAreaDirty(visualRect(m_selectionModel->currentIndex()));
332 m_selectionModel->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
336 void ListView::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
338 Q_UNUSED(event)
340 if (!m_pressedIndex.isValid() && m_selectionModel->currentIndex().isValid()) {
341 markAreaDirty(visualRect(m_selectionModel->currentIndex()));
342 m_selectionModel->clear();
347 void ListView::mousePressEvent(QGraphicsSceneMouseEvent *event)
349 const QPointF pos = mapToViewport(event->pos());
350 //setFocus(Qt::MouseFocusReason);
352 if (event->button() == Qt::RightButton) {
353 const QModelIndex index = indexAt(pos);
354 if (index.isValid()) {
355 if (!m_selectionModel->isSelected(index)) {
356 m_selectionModel->select(index, QItemSelectionModel::ClearAndSelect);
357 m_selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
358 markAreaDirty(visibleArea());
360 event->ignore(); // Causes contextMenuEvent() to get called
361 } else if (m_selectionModel->hasSelection()) {
362 m_selectionModel->clearSelection();
363 markAreaDirty(visibleArea());
365 m_pressedIndex = index;
366 return;
369 if (event->button() == Qt::LeftButton) {
370 const QModelIndex index = indexAt(pos);
372 // If an icon was pressed
373 if (index.isValid())
375 if (event->modifiers() & Qt::ControlModifier) {
376 m_selectionModel->select(index, QItemSelectionModel::Toggle);
377 m_selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
378 markAreaDirty(visualRect(index));
379 } else if (!m_selectionModel->isSelected(index)) {
380 m_selectionModel->select(index, QItemSelectionModel::ClearAndSelect);
381 m_selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
382 markAreaDirty(visibleArea());
384 m_pressedIndex = index;
385 return;
390 void ListView::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
392 if (event->button() == Qt::LeftButton)
394 const QPointF pos = mapToViewport(event->pos());
395 const QModelIndex index = indexAt(pos);
397 if (index.isValid() && index == m_pressedIndex && !(event->modifiers() & Qt::ControlModifier)) {
398 emit activated(index);
399 m_selectionModel->clearSelection();
400 markAreaDirty(visibleArea());
404 m_pressedIndex = QModelIndex();
407 void ListView::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
409 if (!(event->buttons() & Qt::LeftButton)) {
410 return;
413 // If an item is pressed
414 if (m_pressedIndex.isValid())
416 const QPointF buttonDownPos = event->buttonDownPos(Qt::LeftButton);
417 const QPointF point = event->pos() - buttonDownPos;
418 if (point.toPoint().manhattanLength() >= QApplication::startDragDistance())
420 startDrag(mapToViewport(buttonDownPos), event->widget());
422 return;
426 void ListView::wheelEvent(QGraphicsSceneWheelEvent *event)
428 if ((event->modifiers() & Qt::CTRL) || (event->orientation() == Qt::Horizontal)) {
429 // Let the event propagate to the parent widget
430 event->ignore();
431 return;
434 int pixels = 96 * event->delta() / 120;
435 smoothScroll(0, -pixels);
438 void ListView::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
440 const QPointF pos = mapToViewport(event->pos());
441 const QModelIndex index = indexAt(pos);
443 if (index.isValid()) {
444 emit contextMenuRequest(event->widget(), event->screenPos());
445 } else {
446 // Let the event propagate to the parent widget
447 event->ignore();
451 void ListView::dropEvent(QGraphicsSceneDragDropEvent *)
453 m_pressedIndex = QModelIndex();
456 void ListView::resizeEvent(QGraphicsSceneResizeEvent *)
458 const QRectF cr = contentsRect();
459 const QRectF r = QRectF(cr.right() - m_scrollBar->geometry().width(), cr.top(),
460 m_scrollBar->geometry().width(), cr.height());
462 if (m_scrollBar->geometry() != r) {
463 m_scrollBar->setGeometry(r);
466 updateScrollBar();
467 markAreaDirty(visibleArea());
470 // pos is the position where the mouse was clicked in the applet.
471 // widget is the widget that sent the mouse event that triggered the drag.
472 void ListView::startDrag(const QPointF &pos, QWidget *widget)
474 QModelIndexList indexes = m_selectionModel->selectedIndexes();
475 QRect boundingRect;
476 foreach (const QModelIndex &index, indexes) {
477 boundingRect |= visualRect(index);
480 QPixmap pixmap(boundingRect.size());
481 pixmap.fill(Qt::transparent);
483 QStyleOptionViewItemV4 option = viewOptions();
484 // ### We can't draw the items as selected or hovered since Qt doesn't
485 // use an ARGB window for the drag pixmap.
486 //option.state |= QStyle::State_Selected | QStyle::State_MouseOver;
487 option.state &= ~(QStyle::State_Selected | QStyle::State_MouseOver);
489 updateTextShadows(palette().color(QPalette::HighlightedText));
491 QPainter p(&pixmap);
492 foreach (const QModelIndex &index, indexes)
494 option.rect = visualRect(index).translated(-boundingRect.topLeft());
495 m_delegate->paint(&p, option, index);
497 p.end();
499 // Mark the area containing the about-to-be-dragged items as dirty, so they
500 // will be erased from the view on the next repaint. We have to do this
501 // before calling QDrag::exec(), since it's a blocking call.
502 markAreaDirty(boundingRect);
504 m_dragInProgress = true;
506 QDrag *drag = new QDrag(widget);
507 drag->setMimeData(m_model->mimeData(indexes));
508 drag->setPixmap(pixmap);
509 drag->setHotSpot((pos - boundingRect.topLeft()).toPoint());
510 drag->exec(m_model->supportedDragActions());
512 m_dragInProgress = false;
514 // Repaint the dragged icons in case the drag did not remove the file
515 markAreaDirty(boundingRect);
518 QStyleOptionViewItemV4 ListView::viewOptions() const
520 QStyleOptionViewItemV4 option;
521 initStyleOption(&option);
523 option.font = font();
524 option.decorationAlignment = Qt::AlignCenter;
525 option.decorationPosition = QStyleOptionViewItem::Left;
526 option.decorationSize = iconSize();
527 option.displayAlignment = Qt::AlignLeft | Qt::AlignVCenter;
528 option.textElideMode = Qt::ElideMiddle;
529 option.locale = QLocale::system();
530 option.widget = m_styleWidget;
531 option.viewItemPosition = QStyleOptionViewItemV4::OnlyOne;
533 if (m_wordWrap) {
534 option.features = QStyleOptionViewItemV2::WrapText;
537 return option;
540 #include "listview.moc"