00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include "ui/urlitemview.h"
00022
00023
00024 #include <QtCore/QHash>
00025 #include <QtCore/QPersistentModelIndex>
00026
00027 #include <QtGui/QMouseEvent>
00028 #include <QtGui/QPainter>
00029 #include <QtGui/QPaintEvent>
00030 #include <QtGui/QScrollBar>
00031 #include <QtGui/QToolTip>
00032
00033
00034 #include <KDebug>
00035 #include <KGlobalSettings>
00036 #include <KIconLoader>
00037 #include <KColorScheme>
00038
00039
00040 #include "core/models.h"
00041 #include "core/kickoffmodel.h"
00042 #include "ui/itemdelegate.h"
00043
00044 using namespace Kickoff;
00045
00046 class UrlItemView::Private
00047 {
00048 public:
00049 Private(UrlItemView *parent)
00050 : q(parent)
00051 , contentsHeight(0)
00052 , itemStateProvider(0) {
00053 }
00054
00055 void doLayout() {
00056
00057 itemRects.clear();
00058 visualOrder.clear();
00059
00060 if (!q->model()) {
00061 return;
00062 }
00063
00064 int verticalOffset = ItemDelegate::TOP_OFFSET;
00065 int horizontalOffset = 0;
00066 int row = 0;
00067 int visualColumn = 0;
00068
00069 QModelIndex branch = currentRootIndex;
00070
00071 while (true) {
00072 if (itemChildOffsets[branch] + row >= q->model()->rowCount(branch) ||
00073 (branch != currentRootIndex && row > MAX_CHILD_ROWS)) {
00074
00075 if (branch.isValid()) {
00076 row = branch.row() + 1;
00077 branch = branch.parent();
00078 continue;
00079 } else {
00080 break;
00081 }
00082 }
00083
00084 QModelIndex child = q->model()->index(row + itemChildOffsets[branch], 0, branch);
00085
00086 if (q->model()->hasChildren(child)) {
00087 QSize childSize = calculateHeaderSize(child);
00088 QRect rect(QPoint(ItemDelegate::HEADER_LEFT_MARGIN, verticalOffset), childSize);
00089
00090 itemRects.insert(child, rect);
00091
00092 if (childSize.isValid()) {
00093
00094 verticalOffset += childSize.height();
00095 }
00096 horizontalOffset = 0;
00097 branch = child;
00098 row = 0;
00099 visualColumn = 0;
00100 } else {
00101 QSize childSize = calculateItemSize(child);
00102
00103
00104 itemRects.insert(child, QRect(QPoint(horizontalOffset, verticalOffset),
00105 childSize));
00106
00107 if (childSize.isValid()) {
00108 visualOrder << child;
00109 }
00110
00111 horizontalOffset += contentWidth() / MAX_COLUMNS;
00112
00113 visualColumn++;
00114 row++;
00115
00116 bool wasLastRow = row + itemChildOffsets[branch] >= q->model()->rowCount(branch);
00117 bool nextItemIsBranch = false;
00118 if (!wasLastRow) {
00119 QModelIndex nextIndex = q->model()->index(row + itemChildOffsets[branch], 0, branch);
00120 nextItemIsBranch = q->model()->hasChildren(nextIndex);
00121 }
00122
00123 if (visualColumn >= MAX_COLUMNS || wasLastRow || nextItemIsBranch) {
00124 horizontalOffset = 0;
00125 visualColumn = 0;
00126 }
00127
00128 if (childSize.isValid()) {
00129
00130 verticalOffset += childSize.height();
00131 }
00132 }
00133 }
00134 contentsHeight = verticalOffset;
00135
00136 updateScrollBarRange();
00137 }
00138
00139 void drawHeader(QPainter *painter,
00140 const QModelIndex& index,
00141 const QStyleOptionViewItem& option) {
00142 const bool first = isFirstHeader(index);
00143 const int rightMargin = q->style()->pixelMetric(QStyle::PM_ScrollBarExtent) + 6;
00144 const int dy = (first ? 4 : ItemDelegate::HEADER_TOP_MARGIN);
00145
00146 painter->save();
00147 painter->setRenderHint(QPainter::Antialiasing, false);
00148
00149 if (!first) {
00150 QLinearGradient gradient(option.rect.topLeft(), option.rect.topRight());
00151 gradient.setColorAt(0.0, Qt::transparent);
00152 gradient.setColorAt(0.1, option.palette.midlight().color());
00153 gradient.setColorAt(0.5, option.palette.mid().color());
00154 gradient.setColorAt(0.9, option.palette.midlight().color());
00155 gradient.setColorAt(1.0, Qt::transparent);
00156 painter->setPen(QPen(gradient, 1));
00157
00158 painter->drawLine(option.rect.x() + 6, option.rect.y() + dy + 2,
00159 option.rect.right() - rightMargin , option.rect.y() + dy + 2);
00160 }
00161
00162 painter->setFont(KGlobalSettings::smallestReadableFont());
00163 painter->setPen(QPen(KColorScheme(QPalette::Active).foreground(KColorScheme::InactiveText), 0));
00164 QString text = index.data(Qt::DisplayRole).value<QString>();
00165 painter->drawText(option.rect.adjusted(0, dy, -rightMargin, 0),
00166 Qt::AlignVCenter | Qt::AlignRight, text);
00167 painter->restore();
00168 }
00169
00170 void updateScrollBarRange() {
00171 int pageSize = q->height();
00172 q->verticalScrollBar()->setRange(0, contentsHeight - pageSize);
00173 q->verticalScrollBar()->setPageStep(pageSize);
00174 q->verticalScrollBar()->setSingleStep(q->sizeHintForRow(0));
00175 }
00176
00177 int contentWidth() const {
00178 return q->width() - q->style()->pixelMetric(QStyle::PM_ScrollBarExtent) + 2;
00179 }
00180
00181 QSize calculateItemSize(const QModelIndex& index) const {
00182 if (itemStateProvider && !itemStateProvider->isVisible(index)) {
00183 return QSize();
00184 } else {
00185 return QSize(contentWidth() / MAX_COLUMNS, q->sizeHintForIndex(index).height());
00186 }
00187 }
00188
00189 bool isFirstHeader(const QModelIndex &index) const {
00190 if (index.row() == 0) {
00191 return q->model()->hasChildren(index);
00192 }
00193
00194 QModelIndex prevHeader = index.sibling(index.row() - 1, index.column());
00195 while (prevHeader.isValid()) {
00196
00197 if (q->model()->hasChildren(prevHeader)) {
00198
00199 return false;
00200 }
00201
00202 prevHeader = prevHeader.sibling(prevHeader.row() - 1, prevHeader.column());
00203 }
00204
00205 return true;
00206 }
00207
00208 bool insertAbove(const QRect &itemRect, const QPoint &pos) const {
00209 return pos.y() < itemRect.top() + (itemRect.height() / 2);
00210 }
00211
00212 bool insertBelow(const QRect &itemRect, const QPoint &pos) const {
00213 return pos.y() >= itemRect.top() + (itemRect.height() / 2);
00214 }
00215
00216 QSize calculateHeaderSize(const QModelIndex& index) const {
00217 const QFontMetrics fm(KGlobalSettings::smallestReadableFont());
00218 int minHeight = ItemDelegate::HEADER_HEIGHT;
00219 const bool isFirst = isFirstHeader(index);
00220
00221 if (itemStateProvider && !itemStateProvider->isVisible(index)) {
00222 return QSize();
00223 } else if (isFirst) {
00224 minHeight = ItemDelegate::FIRST_HEADER_HEIGHT;
00225 }
00226
00227 return QSize(q->width() - ItemDelegate::HEADER_LEFT_MARGIN,
00228 qMax(fm.height() + (isFirst ? 4 : ItemDelegate::HEADER_TOP_MARGIN), minHeight)
00229 + ItemDelegate::HEADER_BOTTOM_MARGIN) ;
00230 }
00231
00232 QPoint mapFromViewport(const QPoint& point) const {
00233 return point + QPoint(0, q->verticalOffset());
00234 }
00235
00236 QPoint mapToViewport(const QPoint& point) const {
00237 return point - QPoint(0, q->verticalOffset());
00238 }
00239
00240 UrlItemView * const q;
00241 QPersistentModelIndex currentRootIndex;
00242 QPersistentModelIndex hoveredIndex;
00243 QPersistentModelIndex watchedIndexForDrag;
00244
00245 QHash<QModelIndex, int> itemChildOffsets;
00246 QHash<QModelIndex, QRect> itemRects;
00247 QList<QModelIndex> visualOrder;
00248
00249 QRect dropRect;
00250 int draggedRow;
00251 bool dragging;
00252
00253 int contentsHeight;
00254 ItemStateProvider *itemStateProvider;
00255
00256 static const int MAX_COLUMNS = 1;
00257
00258
00259
00260
00261
00262
00263
00264
00265
00266 static const int MAX_CHILD_ROWS = 1000;
00267 };
00268
00269 UrlItemView::UrlItemView(QWidget *parent)
00270 : QAbstractItemView(parent)
00271 , d(new Private(this))
00272 {
00273 d->dragging = false;
00274 setIconSize(QSize(KIconLoader::SizeMedium, KIconLoader::SizeMedium));
00275 setMouseTracking(true);
00276 QPalette viewPalette(palette());
00277 viewPalette.setColor(QPalette::Window, palette().color(QPalette::Active, QPalette::Base));
00278 setPalette(viewPalette);
00279 setAutoFillBackground(true);
00280 }
00281
00282 UrlItemView::~UrlItemView()
00283 {
00284 delete d;
00285 }
00286
00287 QModelIndex UrlItemView::indexAt(const QPoint& point) const
00288 {
00289
00290
00291 QHashIterator<QModelIndex, QRect> iter(d->itemRects);
00292 while (iter.hasNext()) {
00293 iter.next();
00294 if (iter.value().contains(d->mapFromViewport(point))) {
00295 return iter.key();
00296 }
00297 }
00298 return QModelIndex();
00299 }
00300
00301 void UrlItemView::setModel(QAbstractItemModel *model)
00302 {
00303 QAbstractItemView::setModel(model);
00304
00305 if (model) {
00306 connect(model, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(updateLayout()));
00307 connect(model, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(updateLayout()));
00308 connect(model, SIGNAL(modelReset()), this, SLOT(updateLayout()));
00309 }
00310
00311 d->currentRootIndex = QModelIndex();
00312 d->itemChildOffsets.clear();
00313 updateLayout();
00314 }
00315
00316 void UrlItemView::updateLayout()
00317 {
00318 d->doLayout();
00319
00320 if (viewport()->isVisible()) {
00321 viewport()->update();
00322 }
00323 }
00324
00325 void UrlItemView::scrollTo(const QModelIndex& index, ScrollHint hint)
00326 {
00327 QRect itemRect = d->itemRects[index];
00328 QRect viewedRect = QRect(d->mapFromViewport(QPoint(0, 0)),
00329 size());
00330 int topDifference = viewedRect.top() - itemRect.top();
00331 int bottomDifference = viewedRect.bottom() - itemRect.bottom();
00332 QScrollBar *scrollBar = verticalScrollBar();
00333
00334 if (!itemRect.isValid())
00335 return;
00336
00337 switch (hint) {
00338 case EnsureVisible: {
00339 if (!viewedRect.contains(itemRect)) {
00340
00341 if (topDifference < 0) {
00342
00343 scrollBar->setValue(scrollBar->value() - bottomDifference);
00344 } else {
00345
00346 scrollBar->setValue(scrollBar->value() - topDifference);
00347 }
00348 }
00349 }
00350 break;
00351 case PositionAtTop: {
00352
00353 }
00354 default:
00355 Q_ASSERT(false);
00356 }
00357 }
00358
00359 QRect UrlItemView::visualRect(const QModelIndex& index) const
00360 {
00361 QRect itemRect = d->itemRects[index];
00362 if (!itemRect.isValid()) {
00363 return itemRect;
00364 }
00365
00366 itemRect.moveTopLeft(d->mapToViewport(itemRect.topLeft()));
00367 return itemRect;
00368 }
00369
00370 int UrlItemView::horizontalOffset() const
00371 {
00372 return 0;
00373 }
00374
00375 bool UrlItemView::isIndexHidden(const QModelIndex&) const
00376 {
00377 return false;
00378 }
00379
00380 QModelIndex UrlItemView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers)
00381 {
00382 QModelIndex index = currentIndex();
00383
00384 int visualIndex = d->visualOrder.indexOf(index);
00385
00386 switch (cursorAction) {
00387 case MoveUp:
00388 if (!currentIndex().isValid()) {
00389 const QModelIndex root = model()->index(0, 0);
00390 index = model()->index(model()->rowCount(root) - 1, 0, root);
00391 } else {
00392 visualIndex = qMax(0, visualIndex - 1);
00393 }
00394 break;
00395 case MoveDown:
00396 if (!currentIndex().isValid()) {
00397 const QModelIndex root = model()->index(0, 0);
00398 index = model()->index(0, 0, root);
00399 } else {
00400 visualIndex = qMin(d->visualOrder.count() - 1, visualIndex + 1);
00401 }
00402 break;
00403 default:
00404
00405 break;
00406 }
00407
00408 d->hoveredIndex = QModelIndex();
00409
00410 return currentIndex().isValid() ? d->visualOrder.value(visualIndex, QModelIndex())
00411 : index;
00412 }
00413
00414 void UrlItemView::setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags flags)
00415 {
00416 QItemSelection selection;
00417 selection.select(indexAt(rect.topLeft()), indexAt(rect.bottomRight()));
00418 selectionModel()->select(selection, flags);
00419 }
00420
00421 int UrlItemView::verticalOffset() const
00422 {
00423 return verticalScrollBar()->value();
00424 }
00425
00426 QRegion UrlItemView::visualRegionForSelection(const QItemSelection& selection) const
00427 {
00428 QRegion region;
00429 foreach(const QModelIndex& index, selection.indexes()) {
00430 region |= visualRect(index);
00431 }
00432 return region;
00433 }
00434
00435 void UrlItemView::paintEvent(QPaintEvent *event)
00436 {
00437 if (!model()) {
00438 return;
00439 }
00440
00441 QPalette viewPalette(palette());
00442 viewPalette.setColor(QPalette::Window, palette().color(QPalette::Active, QPalette::Base));
00443 setPalette(viewPalette);
00444 setAutoFillBackground(true);
00445
00446 QPainter painter(viewport());
00447 painter.setRenderHint(QPainter::Antialiasing);
00448
00449 if (d->dragging && dragDropMode() == QAbstractItemView::DragDrop) {
00450 const int y = (d->dropRect.top() + d->dropRect.bottom()) / 2;
00451
00452 painter.save();
00453 QLinearGradient gr(d->dropRect.left(), y, d->dropRect.right(), y);
00454 gr.setColorAt(0, palette().base().color());
00455 gr.setColorAt(.35, palette().windowText().color());
00456 gr.setColorAt(.65, palette().windowText().color());
00457 gr.setColorAt(1, palette().base().color());
00458 painter.setPen(QPen(gr, 1));
00459 painter.drawLine(d->dropRect.left(), y, d->dropRect.right(), y);
00460 painter.restore();
00461 }
00462
00463 QHashIterator<QModelIndex, QRect> indexIter(d->itemRects);
00464 while (indexIter.hasNext()) {
00465 indexIter.next();
00466 const QRect itemRect = visualRect(indexIter.key());
00467 const QModelIndex index = indexIter.key();
00468
00469 if (event->region().contains(itemRect)) {
00470 QStyleOptionViewItem option = viewOptions();
00471 option.rect = itemRect;
00472
00473 if (selectionModel()->isSelected(index)) {
00474 option.state |= QStyle::State_Selected;
00475 }
00476 if (index == d->hoveredIndex) {
00477 option.state |= QStyle::State_MouseOver;
00478 }
00479 if (index == currentIndex()) {
00480 option.state |= QStyle::State_HasFocus;
00481 }
00482
00483 if (model()->hasChildren(index)) {
00484 d->drawHeader(&painter, index, option);
00485 } else {
00486 if (option.rect.left() == 0) {
00487 option.rect.setLeft(option.rect.left() + ItemDelegate::ITEM_LEFT_MARGIN);
00488 option.rect.setRight(option.rect.right() - ItemDelegate::ITEM_RIGHT_MARGIN);
00489 }
00490 itemDelegate(index)->paint(&painter, option, index);
00491 }
00492 }
00493 }
00494 }
00495
00496 void UrlItemView::resizeEvent(QResizeEvent *)
00497 {
00498 updateLayout();
00499 }
00500
00501
00502 void UrlItemView::mouseMoveEvent(QMouseEvent *event)
00503 {
00504 const QModelIndex itemUnderMouse = indexAt(event->pos());
00505 if (itemUnderMouse != d->hoveredIndex && state() == NoState) {
00506 update(itemUnderMouse);
00507 update(d->hoveredIndex);
00508
00509 d->hoveredIndex = itemUnderMouse;
00510 setCurrentIndex(d->hoveredIndex);
00511 }
00512
00513 Plasma::Delegate *hoveredItemDelegate =
00514 static_cast<Plasma::Delegate*>(itemDelegate(d->hoveredIndex));
00515 if (hoveredItemDelegate->showToolTip() == true) {
00516 QModelIndex index = d->hoveredIndex;
00517 QString titleText = index.data(Qt::DisplayRole).toString();
00518 QString subTitleText = index.data(Plasma::Delegate::SubTitleRole).toString();
00519 setToolTip(titleText + "\n" + subTitleText);
00520 } else {
00521 setToolTip("");
00522 }
00523
00524 QAbstractItemView::mouseMoveEvent(event);
00525 }
00526
00527 void UrlItemView::mousePressEvent(QMouseEvent *event)
00528 {
00529 d->watchedIndexForDrag = indexAt(event->pos());
00530 QAbstractItemView::mousePressEvent(event);
00531 }
00532
00533 void UrlItemView::mouseReleaseEvent(QMouseEvent *event)
00534 {
00535 Q_UNUSED(event)
00536
00537 d->watchedIndexForDrag = QModelIndex();
00538 }
00539
00540 void UrlItemView::setItemStateProvider(ItemStateProvider *provider)
00541 {
00542 d->itemStateProvider = provider;
00543 }
00544
00545 void UrlItemView::dragEnterEvent(QDragEnterEvent *event)
00546 {
00547 if (dragDropMode() != QAbstractItemView::DragDrop) {
00548 return;
00549 }
00550
00551 d->dragging = true;
00552 setDirtyRegion(d->dropRect);
00553
00554 event->accept();
00555 }
00556
00557 void UrlItemView::dragLeaveEvent(QDragLeaveEvent *event)
00558 {
00559 if (dragDropMode() != QAbstractItemView::DragDrop) {
00560 return;
00561 }
00562
00563 d->dragging = false;
00564 setDirtyRegion(d->dropRect);
00565
00566 event->accept();
00567 }
00568
00569 void UrlItemView::dragMoveEvent(QDragMoveEvent *event)
00570 {
00571 QAbstractItemView::dragMoveEvent(event);
00572
00573 const QPoint pos = event->pos();
00574 const QModelIndex index = indexAt(pos);
00575 setDirtyRegion(d->dropRect);
00576
00577
00578 if (d->isFirstHeader(index) && index.row() == 0) {
00579 event->ignore();
00580 return;
00581 }
00582
00583 if (index.isValid()) {
00584 const QRect rect = visualRect(index);
00585 const int gap = d->contentsHeight;
00586
00587 if (d->insertAbove(rect, pos)) {
00588 d->dropRect = QRect(rect.left(), rect.top() - gap / 2,
00589 rect.width(), gap);
00590 } else if (d->insertBelow(rect, pos)) {
00591 d->dropRect = QRect(rect.left(), rect.bottom() + 1 - gap / 2,
00592 rect.width(), gap);
00593 } else {
00594 d->dropRect = rect;
00595 }
00596 }
00597
00598 setDirtyRegion(d->dropRect);
00599 }
00600
00601
00602 void UrlItemView::startDrag(Qt::DropActions supportedActions)
00603 {
00604 kDebug() << "Starting UrlItemView drag with actions" << supportedActions;
00605
00606 if (!d->watchedIndexForDrag.isValid()) {
00607 return;
00608 }
00609
00610 QMimeData *mimeData = model()->mimeData(selectionModel()->selectedIndexes());
00611
00612 if (!mimeData || mimeData->text().isNull()) {
00613 return;
00614 }
00615
00616 QDrag *drag = new QDrag(this);
00617 drag->setMimeData(mimeData);
00618
00619 QModelIndex idx = selectionModel()->selectedIndexes().first();
00620 QIcon icon = idx.data(Qt::DecorationRole).value<QIcon>();
00621 d->draggedRow = idx.row();
00622 drag->setPixmap(icon.pixmap(IconSize(KIconLoader::Desktop)));
00623
00624 d->dropRect = QRect();
00625 drag->exec();
00626
00627 QAbstractItemView::startDrag(supportedActions);
00628 }
00629
00630 void UrlItemView::dropEvent(QDropEvent *event)
00631 {
00632 QAbstractItemView::dropEvent(event);
00633
00634 if (!d->dragging) {
00635 return;
00636 }
00637
00638
00639
00640
00641
00642
00643 if (dragDropMode() == QAbstractItemView::DragDrop) {
00644 int row;
00645 QPoint pos = event->pos();
00646 QModelIndex parent = indexAt(pos);
00647 const QRect rect = visualRect(parent);
00648
00649 row = parent.row();
00650
00651 if(d->insertBelow(rect, pos) && d->draggedRow > row) {
00652 row++;
00653 } else if(d->insertAbove(rect, pos) && d->draggedRow < row) {
00654 row--;
00655 }
00656
00657 model()->dropMimeData(event->mimeData(), event->dropAction(),
00658 row, 0, parent);
00659
00660 d->dragging = false;
00661
00662 event->accept();
00663 }
00664 }
00665 void UrlItemView::leaveEvent(QEvent *event)
00666 {
00667 Q_UNUSED(event)
00668
00669 kDebug() << "UrlItemView leave event";
00670
00671 d->hoveredIndex = QModelIndex();
00672 setCurrentIndex(QModelIndex());
00673 }
00674
00675 ItemStateProvider *UrlItemView::itemStateProvider() const
00676 {
00677 return d->itemStateProvider;
00678 }
00679 #include "urlitemview.moc"