not quite so much needs to be delayed to the init() function
[personal-kdebase.git] / workspace / plasma / applets / kickoff / ui / urlitemview.cpp
blob1366fef4288006669c91db7fc64c4e17593d0504
1 /*
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.
20 // Own
21 #include "ui/urlitemview.h"
23 // Qt
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>
33 // KDE
34 #include <KDebug>
35 #include <KGlobalSettings>
36 #include <KIconLoader>
37 #include <KColorScheme>
39 // Local
40 #include "core/models.h"
41 #include "core/kickoffmodel.h"
42 #include "ui/itemdelegate.h"
44 using namespace Kickoff;
46 class UrlItemView::Private
48 public:
49 Private(UrlItemView *parent)
50 : q(parent)
51 , contentsHeight(0)
52 , itemStateProvider(0) {
55 void doLayout() {
56 // clear existing layout information
57 itemRects.clear();
58 visualOrder.clear();
60 if (!q->model()) {
61 return;
64 int verticalOffset = ItemDelegate::TOP_OFFSET;
65 int horizontalOffset = 0;
66 int row = 0;
67 int visualColumn = 0;
69 QModelIndex branch = currentRootIndex;
71 while (true) {
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();
78 continue;
79 } else {
80 break;
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()) {
93 // don't subtract 1
94 verticalOffset += childSize.height();
96 horizontalOffset = 0;
97 branch = child;
98 row = 0;
99 visualColumn = 0;
100 } else {
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),
105 childSize));
107 if (childSize.isValid()) {
108 visualOrder << child;
111 horizontalOffset += contentWidth() / MAX_COLUMNS;
113 visualColumn++;
114 row++;
116 bool wasLastRow = row + itemChildOffsets[branch] >= q->model()->rowCount(branch);
117 bool nextItemIsBranch = false;
118 if (!wasLastRow) {
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;
125 visualColumn = 0;
128 if (childSize.isValid()) {
129 // don't subtract 1
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);
146 painter->save();
147 painter->setRenderHint(QPainter::Antialiasing, false);
149 if (!first) {
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);
167 painter->restore();
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)) {
183 return QSize();
184 } else {
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";
199 return false;
202 prevHeader = prevHeader.sibling(prevHeader.row() - 1, prevHeader.column());
205 return true;
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)) {
222 return QSize();
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;
249 QRect dropRect;
250 int draggedRow;
251 bool dragging;
253 int contentsHeight;
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
261 // eg.
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))
273 d->dragging = false;
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()
284 delete d;
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()) {
293 iter.next();
294 if (iter.value().contains(d->mapFromViewport(point))) {
295 return iter.key();
298 return QModelIndex();
301 void UrlItemView::setModel(QAbstractItemModel *model)
303 QAbstractItemView::setModel(model);
305 if (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();
313 updateLayout();
316 void UrlItemView::updateLayout()
318 d->doLayout();
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)),
329 size());
330 int topDifference = viewedRect.top() - itemRect.top();
331 int bottomDifference = viewedRect.bottom() - itemRect.bottom();
332 QScrollBar *scrollBar = verticalScrollBar();
334 if (!itemRect.isValid())
335 return;
337 switch (hint) {
338 case EnsureVisible: {
339 if (!viewedRect.contains(itemRect)) {
341 if (topDifference < 0) {
342 // scroll view down
343 scrollBar->setValue(scrollBar->value() - bottomDifference);
344 } else {
345 // scroll view up
346 scrollBar->setValue(scrollBar->value() - topDifference);
350 break;
351 case PositionAtTop: {
352 //d->viewportOffset = itemRect.top();
354 default:
355 Q_ASSERT(false); // Not implemented
359 QRect UrlItemView::visualRect(const QModelIndex& index) const
361 QRect itemRect = d->itemRects[index];
362 if (!itemRect.isValid()) {
363 return itemRect;
366 itemRect.moveTopLeft(d->mapToViewport(itemRect.topLeft()));
367 return itemRect;
370 int UrlItemView::horizontalOffset() const
372 return 0;
375 bool UrlItemView::isIndexHidden(const QModelIndex&) const
377 return false;
380 QModelIndex UrlItemView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers)
382 QModelIndex index = currentIndex();
384 int visualIndex = d->visualOrder.indexOf(index);
386 switch (cursorAction) {
387 case MoveUp:
388 if (!currentIndex().isValid()) {
389 const QModelIndex root = model()->index(0, 0);
390 index = model()->index(model()->rowCount(root) - 1, 0, root);
391 } else {
392 visualIndex = qMax(0, visualIndex - 1);
394 break;
395 case MoveDown:
396 if (!currentIndex().isValid()) {
397 const QModelIndex root = model()->index(0, 0);
398 index = model()->index(0, 0, root);
399 } else {
400 visualIndex = qMin(d->visualOrder.count() - 1, visualIndex + 1);
402 break;
403 default:
404 // Do nothing
405 break;
408 d->hoveredIndex = QModelIndex();
410 return currentIndex().isValid() ? d->visualOrder.value(visualIndex, QModelIndex())
411 : index;
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
428 QRegion region;
429 foreach(const QModelIndex& index, selection.indexes()) {
430 region |= visualRect(index);
432 return region;
435 void UrlItemView::paintEvent(QPaintEvent *event)
437 if (!model()) {
438 return;
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;
452 painter.save();
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);
460 painter.restore();
463 QHashIterator<QModelIndex, QRect> indexIter(d->itemRects);
464 while (indexIter.hasNext()) {
465 indexIter.next();
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);
485 } else {
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 *)
498 updateLayout();
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);
520 } else {
521 setToolTip("");
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)
535 Q_UNUSED(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) {
548 return;
551 d->dragging = true;
552 setDirtyRegion(d->dropRect);
554 event->accept();
557 void UrlItemView::dragLeaveEvent(QDragLeaveEvent *event)
559 if (dragDropMode() != QAbstractItemView::DragDrop) {
560 return;
563 d->dragging = false;
564 setDirtyRegion(d->dropRect);
566 event->accept();
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) {
579 event->ignore();
580 return;
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,
589 rect.width(), gap);
590 } else if (d->insertBelow(rect, pos)) {
591 d->dropRect = QRect(rect.left(), rect.bottom() + 1 - gap / 2,
592 rect.width(), gap);
593 } else {
594 d->dropRect = rect;
598 setDirtyRegion(d->dropRect);
602 void UrlItemView::startDrag(Qt::DropActions supportedActions)
604 kDebug() << "Starting UrlItemView drag with actions" << supportedActions;
606 if (!d->watchedIndexForDrag.isValid()) {
607 return;
610 QMimeData *mimeData = model()->mimeData(selectionModel()->selectedIndexes());
612 if (!mimeData || mimeData->text().isNull()) {
613 return;
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();
625 drag->exec();
627 QAbstractItemView::startDrag(supportedActions);
630 void UrlItemView::dropEvent(QDropEvent *event)
632 QAbstractItemView::dropEvent(event);
634 if (!d->dragging) {
635 return;
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
642 // item.
643 if (dragDropMode() == QAbstractItemView::DragDrop) {
644 int row;
645 QPoint pos = event->pos();
646 QModelIndex parent = indexAt(pos);
647 const QRect rect = visualRect(parent);
649 row = parent.row();
651 if(d->insertBelow(rect, pos) && d->draggedRow > row) {
652 row++;
653 } else if(d->insertAbove(rect, pos) && d->draggedRow < row) {
654 row--;
657 model()->dropMimeData(event->mimeData(), event->dropAction(),
658 row, 0, parent);
660 d->dragging = false;
662 event->accept();
665 void UrlItemView::leaveEvent(QEvent *event)
667 Q_UNUSED(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"