2 Copyright 2007 Robert Knight <robertknight@gmail.com>
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
9 This library 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 GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
21 #include "ui/urlitemview.h"
24 #include <QtCore/QHash>
25 #include <QtCore/QPersistentModelIndex>
27 #include <QtGui/QMouseEvent>
28 #include <QtGui/QPainter>
29 #include <QtGui/QPaintEvent>
30 #include <QtGui/QScrollBar>
31 #include <QtGui/QToolTip>
35 #include <KGlobalSettings>
36 #include <KIconLoader>
37 #include <KColorScheme>
40 #include "core/models.h"
41 #include "core/kickoffmodel.h"
42 #include "ui/itemdelegate.h"
44 using namespace Kickoff
;
46 class UrlItemView::Private
49 Private(UrlItemView
*parent
)
52 , itemStateProvider(0) {
56 // clear existing layout information
64 int verticalOffset
= ItemDelegate::TOP_OFFSET
;
65 int horizontalOffset
= 0;
69 QModelIndex branch
= currentRootIndex
;
72 if (itemChildOffsets
[branch
] + row
>= q
->model()->rowCount(branch
) ||
73 (branch
!= currentRootIndex
&& row
> MAX_CHILD_ROWS
)) {
75 if (branch
.isValid()) {
76 row
= branch
.row() + 1;
77 branch
= branch
.parent();
84 QModelIndex child
= q
->model()->index(row
+ itemChildOffsets
[branch
], 0, branch
);
86 if (q
->model()->hasChildren(child
)) {
87 QSize childSize
= calculateHeaderSize(child
);
88 QRect
rect(QPoint(ItemDelegate::HEADER_LEFT_MARGIN
, verticalOffset
), childSize
);
89 //kDebug() << "header rect for" << child.data(Qt::DisplayRole) << "is" << rect;
90 itemRects
.insert(child
, rect
);
92 if (childSize
.isValid()) {
94 verticalOffset
+= childSize
.height();
101 QSize childSize
= calculateItemSize(child
);
102 //kDebug() << "item rect for" << child.data(Qt::DisplayRole) << "is" << QRect(QPoint(horizontalOffset,verticalOffset), childSize);
104 itemRects
.insert(child
, QRect(QPoint(horizontalOffset
, verticalOffset
),
107 if (childSize
.isValid()) {
108 visualOrder
<< child
;
111 horizontalOffset
+= contentWidth() / MAX_COLUMNS
;
116 bool wasLastRow
= row
+ itemChildOffsets
[branch
] >= q
->model()->rowCount(branch
);
117 bool nextItemIsBranch
= false;
119 QModelIndex nextIndex
= q
->model()->index(row
+ itemChildOffsets
[branch
], 0, branch
);
120 nextItemIsBranch
= q
->model()->hasChildren(nextIndex
);
123 if (visualColumn
>= MAX_COLUMNS
|| wasLastRow
|| nextItemIsBranch
) {
124 horizontalOffset
= 0;
128 if (childSize
.isValid()) {
130 verticalOffset
+= childSize
.height();
134 contentsHeight
= verticalOffset
;
136 updateScrollBarRange();
139 void drawHeader(QPainter
*painter
,
140 const QModelIndex
& index
,
141 const QStyleOptionViewItem
& option
) {
142 const bool first
= isFirstHeader(index
);
143 const int rightMargin
= q
->style()->pixelMetric(QStyle::PM_ScrollBarExtent
) + 6;
144 const int dy
= (first
? 4 : ItemDelegate::HEADER_TOP_MARGIN
);
147 painter
->setRenderHint(QPainter::Antialiasing
, false);
150 QLinearGradient
gradient(option
.rect
.topLeft(), option
.rect
.topRight());
151 gradient
.setColorAt(0.0, Qt::transparent
);
152 gradient
.setColorAt(0.1, option
.palette
.midlight().color());
153 gradient
.setColorAt(0.5, option
.palette
.mid().color());
154 gradient
.setColorAt(0.9, option
.palette
.midlight().color());
155 gradient
.setColorAt(1.0, Qt::transparent
);
156 painter
->setPen(QPen(gradient
, 1));
158 painter
->drawLine(option
.rect
.x() + 6, option
.rect
.y() + dy
+ 2,
159 option
.rect
.right() - rightMargin
, option
.rect
.y() + dy
+ 2);
162 painter
->setFont(KGlobalSettings::smallestReadableFont());
163 painter
->setPen(QPen(KColorScheme(QPalette::Active
).foreground(KColorScheme::InactiveText
), 0));
164 QString text
= index
.data(Qt::DisplayRole
).value
<QString
>();
165 painter
->drawText(option
.rect
.adjusted(0, dy
, -rightMargin
, 0),
166 Qt::AlignVCenter
| Qt::AlignRight
, text
);
170 void updateScrollBarRange() {
171 int pageSize
= q
->height();
172 q
->verticalScrollBar()->setRange(0, contentsHeight
- pageSize
);
173 q
->verticalScrollBar()->setPageStep(pageSize
);
174 q
->verticalScrollBar()->setSingleStep(q
->sizeHintForRow(0));
177 int contentWidth() const {
178 return q
->width() - q
->style()->pixelMetric(QStyle::PM_ScrollBarExtent
) + 2;
181 QSize
calculateItemSize(const QModelIndex
& index
) const {
182 if (itemStateProvider
&& !itemStateProvider
->isVisible(index
)) {
185 return QSize(contentWidth() / MAX_COLUMNS
, q
->sizeHintForIndex(index
).height());
189 bool isFirstHeader(const QModelIndex
&index
) const {
190 if (index
.row() == 0) {
191 return q
->model()->hasChildren(index
);
194 QModelIndex prevHeader
= index
.sibling(index
.row() - 1, index
.column());
195 while (prevHeader
.isValid()) {
196 //kDebug() << "checking" << prevHeader.data(Qt::DisplayRole).value<QString>();
197 if (q
->model()->hasChildren(prevHeader
)) {
198 //kDebug() << "it has children";
202 prevHeader
= prevHeader
.sibling(prevHeader
.row() - 1, prevHeader
.column());
208 bool insertAbove(const QRect
&itemRect
, const QPoint
&pos
) const {
209 return pos
.y() < itemRect
.top() + (itemRect
.height() / 2);
212 bool insertBelow(const QRect
&itemRect
, const QPoint
&pos
) const {
213 return pos
.y() >= itemRect
.top() + (itemRect
.height() / 2);
216 QSize
calculateHeaderSize(const QModelIndex
& index
) const {
217 const QFontMetrics
fm(KGlobalSettings::smallestReadableFont());
218 int minHeight
= ItemDelegate::HEADER_HEIGHT
;
219 const bool isFirst
= isFirstHeader(index
);
221 if (itemStateProvider
&& !itemStateProvider
->isVisible(index
)) {
223 } else if (isFirst
) {
224 minHeight
= ItemDelegate::FIRST_HEADER_HEIGHT
;
227 return QSize(q
->width() - ItemDelegate::HEADER_LEFT_MARGIN
,
228 qMax(fm
.height() + (isFirst
? 4 : ItemDelegate::HEADER_TOP_MARGIN
), minHeight
)
229 + ItemDelegate::HEADER_BOTTOM_MARGIN
) ;
232 QPoint
mapFromViewport(const QPoint
& point
) const {
233 return point
+ QPoint(0, q
->verticalOffset());
236 QPoint
mapToViewport(const QPoint
& point
) const {
237 return point
- QPoint(0, q
->verticalOffset());
240 UrlItemView
* const q
;
241 QPersistentModelIndex currentRootIndex
;
242 QPersistentModelIndex hoveredIndex
;
243 QPersistentModelIndex watchedIndexForDrag
;
245 QHash
<QModelIndex
, int> itemChildOffsets
;
246 QHash
<QModelIndex
, QRect
> itemRects
;
247 QList
<QModelIndex
> visualOrder
;
254 ItemStateProvider
*itemStateProvider
;
256 static const int MAX_COLUMNS
= 1;
258 // TODO Eventually it will be possible to restrict each branch to only showing
259 // a given number of children, with Next/Previous arrows to view more children
263 // Recent Documents [1-10 of 100] Next
264 // Recent Applications [10-20 of 30] Previous|Next
266 static const int MAX_CHILD_ROWS
= 1000;
269 UrlItemView::UrlItemView(QWidget
*parent
)
270 : QAbstractItemView(parent
)
271 , d(new Private(this))
274 setIconSize(QSize(KIconLoader::SizeMedium
, KIconLoader::SizeMedium
));
275 setMouseTracking(true);
276 QPalette
viewPalette(palette());
277 viewPalette
.setColor(QPalette::Window
, palette().color(QPalette::Active
, QPalette::Base
));
278 setPalette(viewPalette
);
279 setAutoFillBackground(true);
282 UrlItemView::~UrlItemView()
287 QModelIndex
UrlItemView::indexAt(const QPoint
& point
) const
289 // simple linear search through the item rects, this will
290 // be inefficient when the viewport is large
291 QHashIterator
<QModelIndex
, QRect
> iter(d
->itemRects
);
292 while (iter
.hasNext()) {
294 if (iter
.value().contains(d
->mapFromViewport(point
))) {
298 return QModelIndex();
301 void UrlItemView::setModel(QAbstractItemModel
*model
)
303 QAbstractItemView::setModel(model
);
306 connect(model
, SIGNAL(rowsRemoved(QModelIndex
, int, int)), this, SLOT(updateLayout()));
307 connect(model
, SIGNAL(rowsInserted(QModelIndex
, int, int)), this, SLOT(updateLayout()));
308 connect(model
, SIGNAL(modelReset()), this, SLOT(updateLayout()));
311 d
->currentRootIndex
= QModelIndex();
312 d
->itemChildOffsets
.clear();
316 void UrlItemView::updateLayout()
320 if (viewport()->isVisible()) {
321 viewport()->update();
325 void UrlItemView::scrollTo(const QModelIndex
& index
, ScrollHint hint
)
327 QRect itemRect
= d
->itemRects
[index
];
328 QRect viewedRect
= QRect(d
->mapFromViewport(QPoint(0, 0)),
330 int topDifference
= viewedRect
.top() - itemRect
.top();
331 int bottomDifference
= viewedRect
.bottom() - itemRect
.bottom();
332 QScrollBar
*scrollBar
= verticalScrollBar();
334 if (!itemRect
.isValid())
338 case EnsureVisible
: {
339 if (!viewedRect
.contains(itemRect
)) {
341 if (topDifference
< 0) {
343 scrollBar
->setValue(scrollBar
->value() - bottomDifference
);
346 scrollBar
->setValue(scrollBar
->value() - topDifference
);
351 case PositionAtTop
: {
352 //d->viewportOffset = itemRect.top();
355 Q_ASSERT(false); // Not implemented
359 QRect
UrlItemView::visualRect(const QModelIndex
& index
) const
361 QRect itemRect
= d
->itemRects
[index
];
362 if (!itemRect
.isValid()) {
366 itemRect
.moveTopLeft(d
->mapToViewport(itemRect
.topLeft()));
370 int UrlItemView::horizontalOffset() const
375 bool UrlItemView::isIndexHidden(const QModelIndex
&) const
380 QModelIndex
UrlItemView::moveCursor(CursorAction cursorAction
, Qt::KeyboardModifiers
)
382 QModelIndex index
= currentIndex();
384 int visualIndex
= d
->visualOrder
.indexOf(index
);
386 switch (cursorAction
) {
388 if (!currentIndex().isValid()) {
389 const QModelIndex root
= model()->index(0, 0);
390 index
= model()->index(model()->rowCount(root
) - 1, 0, root
);
392 visualIndex
= qMax(0, visualIndex
- 1);
396 if (!currentIndex().isValid()) {
397 const QModelIndex root
= model()->index(0, 0);
398 index
= model()->index(0, 0, root
);
400 visualIndex
= qMin(d
->visualOrder
.count() - 1, visualIndex
+ 1);
408 d
->hoveredIndex
= QModelIndex();
410 return currentIndex().isValid() ? d
->visualOrder
.value(visualIndex
, QModelIndex())
414 void UrlItemView::setSelection(const QRect
& rect
, QItemSelectionModel::SelectionFlags flags
)
416 QItemSelection selection
;
417 selection
.select(indexAt(rect
.topLeft()), indexAt(rect
.bottomRight()));
418 selectionModel()->select(selection
, flags
);
421 int UrlItemView::verticalOffset() const
423 return verticalScrollBar()->value();
426 QRegion
UrlItemView::visualRegionForSelection(const QItemSelection
& selection
) const
429 foreach(const QModelIndex
& index
, selection
.indexes()) {
430 region
|= visualRect(index
);
435 void UrlItemView::paintEvent(QPaintEvent
*event
)
441 QPalette
viewPalette(palette());
442 viewPalette
.setColor(QPalette::Window
, palette().color(QPalette::Active
, QPalette::Base
));
443 setPalette(viewPalette
);
444 setAutoFillBackground(true);
446 QPainter
painter(viewport());
447 painter
.setRenderHint(QPainter::Antialiasing
);
449 if (d
->dragging
&& dragDropMode() == QAbstractItemView::DragDrop
) {
450 const int y
= (d
->dropRect
.top() + d
->dropRect
.bottom()) / 2;
453 QLinearGradient
gr(d
->dropRect
.left(), y
, d
->dropRect
.right(), y
);
454 gr
.setColorAt(0, palette().base().color());
455 gr
.setColorAt(.35, palette().windowText().color());
456 gr
.setColorAt(.65, palette().windowText().color());
457 gr
.setColorAt(1, palette().base().color());
458 painter
.setPen(QPen(gr
, 1));
459 painter
.drawLine(d
->dropRect
.left(), y
, d
->dropRect
.right(), y
);
463 QHashIterator
<QModelIndex
, QRect
> indexIter(d
->itemRects
);
464 while (indexIter
.hasNext()) {
466 const QRect itemRect
= visualRect(indexIter
.key());
467 const QModelIndex index
= indexIter
.key();
469 if (event
->region().contains(itemRect
)) {
470 QStyleOptionViewItem option
= viewOptions();
471 option
.rect
= itemRect
;
473 if (selectionModel()->isSelected(index
)) {
474 option
.state
|= QStyle::State_Selected
;
476 if (index
== d
->hoveredIndex
) {
477 option
.state
|= QStyle::State_MouseOver
;
479 if (index
== currentIndex()) {
480 option
.state
|= QStyle::State_HasFocus
;
483 if (model()->hasChildren(index
)) {
484 d
->drawHeader(&painter
, index
, option
);
486 if (option
.rect
.left() == 0) {
487 option
.rect
.setLeft(option
.rect
.left() + ItemDelegate::ITEM_LEFT_MARGIN
);
488 option
.rect
.setRight(option
.rect
.right() - ItemDelegate::ITEM_RIGHT_MARGIN
);
490 itemDelegate(index
)->paint(&painter
, option
, index
);
496 void UrlItemView::resizeEvent(QResizeEvent
*)
502 void UrlItemView::mouseMoveEvent(QMouseEvent
*event
)
504 const QModelIndex itemUnderMouse
= indexAt(event
->pos());
505 if (itemUnderMouse
!= d
->hoveredIndex
&& state() == NoState
) {
506 update(itemUnderMouse
);
507 update(d
->hoveredIndex
);
509 d
->hoveredIndex
= itemUnderMouse
;
510 setCurrentIndex(d
->hoveredIndex
);
513 Plasma::Delegate
*hoveredItemDelegate
=
514 static_cast<Plasma::Delegate
*>(itemDelegate(d
->hoveredIndex
));
515 if (hoveredItemDelegate
->showToolTip() == true) {
516 QModelIndex index
= d
->hoveredIndex
;
517 QString titleText
= index
.data(Qt::DisplayRole
).toString();
518 QString subTitleText
= index
.data(Plasma::Delegate::SubTitleRole
).toString();
519 setToolTip(titleText
+ "\n" + subTitleText
);
524 QAbstractItemView::mouseMoveEvent(event
);
527 void UrlItemView::mousePressEvent(QMouseEvent
*event
)
529 d
->watchedIndexForDrag
= indexAt(event
->pos());
530 QAbstractItemView::mousePressEvent(event
);
533 void UrlItemView::mouseReleaseEvent(QMouseEvent
*event
)
537 d
->watchedIndexForDrag
= QModelIndex();
540 void UrlItemView::setItemStateProvider(ItemStateProvider
*provider
)
542 d
->itemStateProvider
= provider
;
545 void UrlItemView::dragEnterEvent(QDragEnterEvent
*event
)
547 if (dragDropMode() != QAbstractItemView::DragDrop
) {
552 setDirtyRegion(d
->dropRect
);
557 void UrlItemView::dragLeaveEvent(QDragLeaveEvent
*event
)
559 if (dragDropMode() != QAbstractItemView::DragDrop
) {
564 setDirtyRegion(d
->dropRect
);
569 void UrlItemView::dragMoveEvent(QDragMoveEvent
*event
)
571 QAbstractItemView::dragMoveEvent(event
);
573 const QPoint pos
= event
->pos();
574 const QModelIndex index
= indexAt(pos
);
575 setDirtyRegion(d
->dropRect
);
577 // check to see if it's the header
578 if (d
->isFirstHeader(index
) && index
.row() == 0) {
583 if (index
.isValid()) {
584 const QRect rect
= visualRect(index
);
585 const int gap
= d
->contentsHeight
;
587 if (d
->insertAbove(rect
, pos
)) {
588 d
->dropRect
= QRect(rect
.left(), rect
.top() - gap
/ 2,
590 } else if (d
->insertBelow(rect
, pos
)) {
591 d
->dropRect
= QRect(rect
.left(), rect
.bottom() + 1 - gap
/ 2,
598 setDirtyRegion(d
->dropRect
);
602 void UrlItemView::startDrag(Qt::DropActions supportedActions
)
604 kDebug() << "Starting UrlItemView drag with actions" << supportedActions
;
606 if (!d
->watchedIndexForDrag
.isValid()) {
610 QMimeData
*mimeData
= model()->mimeData(selectionModel()->selectedIndexes());
612 if (!mimeData
|| mimeData
->text().isNull()) {
616 QDrag
*drag
= new QDrag(this);
617 drag
->setMimeData(mimeData
);
619 QModelIndex idx
= selectionModel()->selectedIndexes().first();
620 QIcon icon
= idx
.data(Qt::DecorationRole
).value
<QIcon
>();
621 d
->draggedRow
= idx
.row();
622 drag
->setPixmap(icon
.pixmap(IconSize(KIconLoader::Desktop
)));
624 d
->dropRect
= QRect();
627 QAbstractItemView::startDrag(supportedActions
);
630 void UrlItemView::dropEvent(QDropEvent
*event
)
632 QAbstractItemView::dropEvent(event
);
638 // This special code is necessary in order to be able to
639 // inidcate to the model WHERE the item should be dropped,
640 // Since the model cannot tell where the user dropped the
641 // item. The model itself handles the actual moving of the
643 if (dragDropMode() == QAbstractItemView::DragDrop
) {
645 QPoint pos
= event
->pos();
646 QModelIndex parent
= indexAt(pos
);
647 const QRect rect
= visualRect(parent
);
651 if(d
->insertBelow(rect
, pos
) && d
->draggedRow
> row
) {
653 } else if(d
->insertAbove(rect
, pos
) && d
->draggedRow
< row
) {
657 model()->dropMimeData(event
->mimeData(), event
->dropAction(),
665 void UrlItemView::leaveEvent(QEvent
*event
)
669 kDebug() << "UrlItemView leave event";
671 d
->hoveredIndex
= QModelIndex();
672 setCurrentIndex(QModelIndex());
675 ItemStateProvider
*UrlItemView::itemStateProvider() const
677 return d
->itemStateProvider
;
679 #include "urlitemview.moc"