]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphindetailsview.cpp
svn merge -r764536:HEAD from 4.0 branch: refactor "additional info" actions and provi...
[dolphin.git] / src / dolphindetailsview.cpp
1 /***************************************************************************
2 * Copyright (C) 2006 by Peter Penz *
3 * peter.penz@gmx.at *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program 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 *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
19 ***************************************************************************/
20
21 #include "dolphindetailsview.h"
22
23 #include "dolphinmodel.h"
24 #include "dolphincontroller.h"
25 #include "dolphinsettings.h"
26 #include "dolphinsortfilterproxymodel.h"
27 #include "draganddrophelper.h"
28 #include "viewproperties.h"
29
30 #include "dolphin_detailsmodesettings.h"
31
32 #include <kdirmodel.h>
33 #include <klocale.h>
34 #include <kmenu.h>
35
36 #include <QAbstractProxyModel>
37 #include <QAction>
38 #include <QApplication>
39 #include <QHeaderView>
40 #include <QRubberBand>
41 #include <QPainter>
42 #include <QScrollBar>
43
44 DolphinDetailsView::DolphinDetailsView(QWidget* parent, DolphinController* controller) :
45 QTreeView(parent),
46 m_autoResize(true),
47 m_controller(controller),
48 m_font(),
49 m_decorationSize(),
50 m_dragging(false),
51 m_showElasticBand(false),
52 m_elasticBandOrigin(),
53 m_elasticBandDestination()
54 {
55 Q_ASSERT(controller != 0);
56
57 setAcceptDrops(true);
58 setRootIsDecorated(false);
59 setSortingEnabled(true);
60 setUniformRowHeights(true);
61 setSelectionBehavior(SelectItems);
62 setDragDropMode(QAbstractItemView::DragDrop);
63 setDropIndicatorShown(false);
64 setAlternatingRowColors(true);
65 setItemsExpandable(false);
66
67 setMouseTracking(true);
68 viewport()->setAttribute(Qt::WA_Hover);
69
70 const ViewProperties props(controller->url());
71 setSortIndicatorSection(props.sorting());
72 setSortIndicatorOrder(props.sortOrder());
73
74 QHeaderView* headerView = header();
75 connect(headerView, SIGNAL(sectionClicked(int)),
76 this, SLOT(synchronizeSortingState(int)));
77 headerView->setContextMenuPolicy(Qt::CustomContextMenu);
78 connect(headerView, SIGNAL(customContextMenuRequested(const QPoint&)),
79 this, SLOT(configureColumns(const QPoint&)));
80 connect(headerView, SIGNAL(sectionResized(int, int, int)),
81 this, SLOT(slotHeaderSectionResized(int, int, int)));
82 connect(headerView, SIGNAL(sectionHandleDoubleClicked(int)),
83 this, SLOT(disableAutoResizing()));
84
85 connect(parent, SIGNAL(sortingChanged(DolphinView::Sorting)),
86 this, SLOT(setSortIndicatorSection(DolphinView::Sorting)));
87 connect(parent, SIGNAL(sortOrderChanged(Qt::SortOrder)),
88 this, SLOT(setSortIndicatorOrder(Qt::SortOrder)));
89
90 // TODO: Connecting to the signal 'activated()' is not possible, as kstyle
91 // does not forward the single vs. doubleclick to it yet (KDE 4.1?). Hence it is
92 // necessary connecting the signal 'singleClick()' or 'doubleClick' and to handle the
93 // RETURN-key in keyPressEvent().
94 if (KGlobalSettings::singleClick()) {
95 connect(this, SIGNAL(clicked(const QModelIndex&)),
96 this, SLOT(triggerItem(const QModelIndex&)));
97 } else {
98 connect(this, SIGNAL(doubleClicked(const QModelIndex&)),
99 this, SLOT(triggerItem(const QModelIndex&)));
100 }
101 connect(this, SIGNAL(entered(const QModelIndex&)),
102 this, SLOT(slotEntered(const QModelIndex&)));
103 connect(this, SIGNAL(viewportEntered()),
104 controller, SLOT(emitViewportEntered()));
105 connect(controller, SIGNAL(zoomIn()),
106 this, SLOT(zoomIn()));
107 connect(controller, SIGNAL(zoomOut()),
108 this, SLOT(zoomOut()));
109 connect(controller->dolphinView(), SIGNAL(additionalInfoChanged()),
110 this, SLOT(updateColumnVisibility()));
111
112 // apply the details mode settings to the widget
113 const DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings();
114 Q_ASSERT(settings != 0);
115
116 m_font = QFont(settings->fontFamily(), settings->fontSize());
117
118 // TODO: Remove this check when 4.3.2 is released and KDE requires it... this
119 // check avoids a division by zero happening on versions before 4.3.1.
120 // Right now KDE in theory can be shipped with Qt 4.3.0 and above.
121 // ereslibre
122 #if (QT_VERSION >= QT_VERSION_CHECK(4, 3, 2) || defined(QT_KDE_QT_COPY))
123 setVerticalScrollMode(QTreeView::ScrollPerPixel);
124 setHorizontalScrollMode(QTreeView::ScrollPerPixel);
125 #endif
126
127 updateDecorationSize();
128
129 setFocus();
130 }
131
132 DolphinDetailsView::~DolphinDetailsView()
133 {
134 }
135
136 bool DolphinDetailsView::event(QEvent* event)
137 {
138 if (event->type() == QEvent::Polish) {
139 QHeaderView* headerView = header();
140 headerView->setResizeMode(QHeaderView::Interactive);
141 headerView->setMovable(false);
142
143 updateColumnVisibility();
144
145 hideColumn(DolphinModel::Rating);
146 hideColumn(DolphinModel::Tags);
147 }
148 // TODO: Remove this check when 4.3.2 is released and KDE requires it... this
149 // check avoids a division by zero happening on versions before 4.3.1.
150 // Right now KDE in theory can be shipped with Qt 4.3.0 and above.
151 // ereslibre
152 #if (QT_VERSION >= QT_VERSION_CHECK(4, 3, 2) || defined(QT_KDE_QT_COPY))
153 else if (event->type() == QEvent::UpdateRequest) {
154 // a wheel movement will scroll 4 items
155 if (model()->rowCount() > 0) {
156 verticalScrollBar()->setSingleStep((sizeHintForRow(0) / 3) * 4);
157 }
158 }
159 #endif
160
161 return QTreeView::event(event);
162 }
163
164 QStyleOptionViewItem DolphinDetailsView::viewOptions() const
165 {
166 QStyleOptionViewItem viewOptions = QTreeView::viewOptions();
167 viewOptions.font = m_font;
168 viewOptions.showDecorationSelected = true;
169 viewOptions.decorationSize = m_decorationSize;
170 return viewOptions;
171 }
172
173 void DolphinDetailsView::contextMenuEvent(QContextMenuEvent* event)
174 {
175 QTreeView::contextMenuEvent(event);
176 m_controller->triggerContextMenuRequest(event->pos());
177 }
178
179 void DolphinDetailsView::mousePressEvent(QMouseEvent* event)
180 {
181 m_controller->requestActivation();
182
183 QTreeView::mousePressEvent(event);
184
185 const QModelIndex index = indexAt(event->pos());
186 if (!index.isValid() || (index.column() != DolphinModel::Name)) {
187 const Qt::KeyboardModifiers modifier = QApplication::keyboardModifiers();
188 if (!(modifier & Qt::ShiftModifier) && !(modifier & Qt::ControlModifier)) {
189 clearSelection();
190 }
191 }
192
193 if (event->button() == Qt::LeftButton) {
194 m_showElasticBand = true;
195
196 const QPoint pos(contentsPos());
197 m_elasticBandOrigin = event->pos();
198 m_elasticBandOrigin.setX(m_elasticBandOrigin.x() + pos.x());
199 m_elasticBandOrigin.setY(m_elasticBandOrigin.y() + pos.y());
200 m_elasticBandDestination = event->pos();
201 }
202 }
203
204 void DolphinDetailsView::mouseMoveEvent(QMouseEvent* event)
205 {
206 if (m_showElasticBand) {
207 const QPoint mousePos = event->pos();
208 const QModelIndex index = indexAt(mousePos);
209 if (!index.isValid()) {
210 // the destination of the selection rectangle is above the viewport. In this
211 // case QTreeView does no selection at all, which is not the wanted behavior
212 // in Dolphin -> select all items within the elastic band rectangle
213 clearSelection();
214
215 const int nameColumnWidth = header()->sectionSize(DolphinModel::Name);
216 QRect selRect = QRect(m_elasticBandOrigin, m_elasticBandDestination).normalized();
217 const QRect nameColumnsRect(0, 0, nameColumnWidth, viewport()->height());
218 selRect = nameColumnsRect.intersected(selRect);
219
220 setSelection(selRect, QItemSelectionModel::Select);
221 }
222
223 QTreeView::mouseMoveEvent(event);
224 updateElasticBand();
225 } else {
226 QTreeView::mouseMoveEvent(event);
227 }
228 }
229
230 void DolphinDetailsView::mouseReleaseEvent(QMouseEvent* event)
231 {
232 QTreeView::mouseReleaseEvent(event);
233 if (m_showElasticBand) {
234 updateElasticBand();
235 m_showElasticBand = false;
236 }
237 }
238
239 void DolphinDetailsView::startDrag(Qt::DropActions supportedActions)
240 {
241 DragAndDropHelper::startDrag(this, supportedActions);
242 }
243
244 void DolphinDetailsView::dragEnterEvent(QDragEnterEvent* event)
245 {
246 if (event->mimeData()->hasUrls()) {
247 event->acceptProposedAction();
248 }
249
250 if (m_showElasticBand) {
251 updateElasticBand();
252 m_showElasticBand = false;
253 }
254 m_dragging = true;
255 }
256
257 void DolphinDetailsView::dragLeaveEvent(QDragLeaveEvent* event)
258 {
259 QTreeView::dragLeaveEvent(event);
260
261 // TODO: remove this code when the issue #160611 is solved in Qt 4.4
262 m_dragging = false;
263 setDirtyRegion(m_dropRect);
264 }
265
266 void DolphinDetailsView::dragMoveEvent(QDragMoveEvent* event)
267 {
268 QTreeView::dragMoveEvent(event);
269
270 // TODO: remove this code when the issue #160611 is solved in Qt 4.4
271 setDirtyRegion(m_dropRect);
272 const QModelIndex index = indexAt(event->pos());
273 if (!index.isValid() || (index.column() != DolphinModel::Name)) {
274 m_dragging = false;
275 } else {
276 m_dragging = true;
277 const KFileItem item = itemForIndex(index);
278 if (!item.isNull() && item.isDir()) {
279 m_dropRect = visualRect(index);
280 } else {
281 m_dropRect.setSize(QSize()); // set as invalid
282 }
283 setDirtyRegion(m_dropRect);
284 }
285
286 if (event->mimeData()->hasUrls()) {
287 // accept url drops, independently from the destination item
288 event->acceptProposedAction();
289 }
290 }
291
292 void DolphinDetailsView::dropEvent(QDropEvent* event)
293 {
294 const KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
295 if (!urls.isEmpty()) {
296 event->acceptProposedAction();
297 const QModelIndex index = indexAt(event->pos());
298 KFileItem item;
299 if (index.isValid() && (index.column() == DolphinModel::Name)) {
300 item = itemForIndex(index);
301 }
302 m_controller->indicateDroppedUrls(urls,
303 m_controller->url(),
304 item);
305 }
306 QTreeView::dropEvent(event);
307 m_dragging = false;
308 }
309
310 void DolphinDetailsView::paintEvent(QPaintEvent* event)
311 {
312 QTreeView::paintEvent(event);
313 if (m_showElasticBand) {
314 // The following code has been taken from QListView
315 // and adapted to DolphinDetailsView.
316 // (C) 1992-2007 Trolltech ASA
317 QStyleOptionRubberBand opt;
318 opt.initFrom(this);
319 opt.shape = QRubberBand::Rectangle;
320 opt.opaque = false;
321 opt.rect = elasticBandRect();
322
323 QPainter painter(viewport());
324 painter.save();
325 style()->drawControl(QStyle::CE_RubberBand, &opt, &painter);
326 painter.restore();
327 }
328
329 // TODO: remove this code when the issue #160611 is solved in Qt 4.4
330 if (m_dragging) {
331 const QBrush& brush = viewOptions().palette.brush(QPalette::Normal, QPalette::Highlight);
332 DragAndDropHelper::drawHoverIndication(this, m_dropRect, brush);
333 }
334 }
335
336 void DolphinDetailsView::keyPressEvent(QKeyEvent* event)
337 {
338 QTreeView::keyPressEvent(event);
339
340 const QItemSelectionModel* selModel = selectionModel();
341 const QModelIndex currentIndex = selModel->currentIndex();
342 const bool trigger = currentIndex.isValid()
343 && (event->key() == Qt::Key_Return)
344 && (selModel->selectedIndexes().count() <= 1);
345 if (trigger) {
346 triggerItem(currentIndex);
347 }
348 }
349
350 void DolphinDetailsView::resizeEvent(QResizeEvent* event)
351 {
352 QTreeView::resizeEvent(event);
353 if (m_autoResize) {
354 resizeColumns();
355 }
356 }
357
358 void DolphinDetailsView::setSortIndicatorSection(DolphinView::Sorting sorting)
359 {
360 QHeaderView* headerView = header();
361 headerView->setSortIndicator(sorting, headerView->sortIndicatorOrder());
362 }
363
364 void DolphinDetailsView::setSortIndicatorOrder(Qt::SortOrder sortOrder)
365 {
366 QHeaderView* headerView = header();
367 headerView->setSortIndicator(headerView->sortIndicatorSection(), sortOrder);
368 }
369
370 void DolphinDetailsView::synchronizeSortingState(int column)
371 {
372 // The sorting has already been changed in QTreeView if this slot is
373 // invoked, but Dolphin is not informed about this.
374 DolphinView::Sorting sorting = DolphinSortFilterProxyModel::sortingForColumn(column);
375 const Qt::SortOrder sortOrder = header()->sortIndicatorOrder();
376 m_controller->indicateSortingChange(sorting);
377 m_controller->indicateSortOrderChange(sortOrder);
378 }
379
380 void DolphinDetailsView::slotEntered(const QModelIndex& index)
381 {
382 const QPoint pos = viewport()->mapFromGlobal(QCursor::pos());
383 const int nameColumnWidth = header()->sectionSize(DolphinModel::Name);
384 if (pos.x() < nameColumnWidth) {
385 m_controller->emitItemEntered(itemForIndex(index));
386 }
387 else {
388 m_controller->emitViewportEntered();
389 }
390 }
391
392 void DolphinDetailsView::updateElasticBand()
393 {
394 if (m_showElasticBand) {
395 QRect dirtyRegion(elasticBandRect());
396 m_elasticBandDestination = viewport()->mapFromGlobal(QCursor::pos());
397 dirtyRegion = dirtyRegion.united(elasticBandRect());
398 setDirtyRegion(dirtyRegion);
399 }
400 }
401
402 QRect DolphinDetailsView::elasticBandRect() const
403 {
404 const QPoint pos(contentsPos());
405 const QPoint topLeft(m_elasticBandOrigin.x() - pos.x(), m_elasticBandOrigin.y() - pos.y());
406 return QRect(topLeft, m_elasticBandDestination).normalized();
407 }
408
409 void DolphinDetailsView::zoomIn()
410 {
411 if (isZoomInPossible()) {
412 DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings();
413 switch (settings->iconSize()) {
414 case KIconLoader::SizeSmall: settings->setIconSize(KIconLoader::SizeMedium); break;
415 case KIconLoader::SizeMedium: settings->setIconSize(KIconLoader::SizeLarge); break;
416 default: Q_ASSERT(false); break;
417 }
418 updateDecorationSize();
419 }
420 }
421
422 void DolphinDetailsView::zoomOut()
423 {
424 if (isZoomOutPossible()) {
425 DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings();
426 switch (settings->iconSize()) {
427 case KIconLoader::SizeLarge: settings->setIconSize(KIconLoader::SizeMedium); break;
428 case KIconLoader::SizeMedium: settings->setIconSize(KIconLoader::SizeSmall); break;
429 default: Q_ASSERT(false); break;
430 }
431 updateDecorationSize();
432 }
433 }
434
435 void DolphinDetailsView::triggerItem(const QModelIndex& index)
436 {
437 const KFileItem item = itemForIndex(index);
438 if (index.isValid() && (index.column() == KDirModel::Name)) {
439 m_controller->triggerItem(item);
440 } else {
441 clearSelection();
442 m_controller->emitItemEntered(item);
443 }
444 }
445
446 void DolphinDetailsView::configureColumns(const QPoint& pos)
447 {
448 KMenu popup(this);
449 popup.addTitle(i18nc("@title:menu", "Columns"));
450
451 QHeaderView* headerView = header();
452 for (int i = DolphinModel::Size; i <= DolphinModel::Type; ++i) {
453 const int logicalIndex = headerView->logicalIndex(i);
454 const QString text = model()->headerData(i, Qt::Horizontal).toString();
455 QAction* action = popup.addAction(text);
456 action->setCheckable(true);
457 action->setChecked(!headerView->isSectionHidden(logicalIndex));
458 action->setData(i);
459 }
460
461 QAction* activatedAction = popup.exec(header()->mapToGlobal(pos));
462 if (activatedAction != 0) {
463 const bool show = activatedAction->isChecked();
464 const int columnIndex = activatedAction->data().toInt();
465
466 KFileItemDelegate::InformationList list = m_controller->dolphinView()->additionalInfo();
467 const KFileItemDelegate::Information info = infoForColumn(columnIndex);
468 if (show) {
469 Q_ASSERT(!list.contains(info));
470 list.append(info);
471 } else {
472 Q_ASSERT(list.contains(info));
473 const int index = list.indexOf(info);
474 list.removeAt(index);
475 }
476
477 m_controller->indicateAdditionalInfoChange(list);
478 setColumnHidden(columnIndex, !show);
479 }
480 }
481
482 void DolphinDetailsView::updateColumnVisibility()
483 {
484 const KFileItemDelegate::InformationList list = m_controller->dolphinView()->additionalInfo();
485 for (int i = DolphinModel::Size; i <= DolphinModel::Type; ++i) {
486 const KFileItemDelegate::Information info = infoForColumn(i);
487 const bool hide = !list.contains(info);
488 if (isColumnHidden(i) != hide) {
489 setColumnHidden(i, hide);
490 }
491 }
492
493 resizeColumns();
494 }
495
496 void DolphinDetailsView::slotHeaderSectionResized(int logicalIndex, int oldSize, int newSize)
497 {
498 Q_UNUSED(logicalIndex);
499 Q_UNUSED(oldSize);
500 Q_UNUSED(newSize);
501 if (QApplication::mouseButtons() & Qt::LeftButton) {
502 disableAutoResizing();
503 }
504 }
505
506 void DolphinDetailsView::disableAutoResizing()
507 {
508 m_autoResize = false;
509 }
510
511 bool DolphinDetailsView::isZoomInPossible() const
512 {
513 DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings();
514 return settings->iconSize() < KIconLoader::SizeLarge;
515 }
516
517 bool DolphinDetailsView::isZoomOutPossible() const
518 {
519 DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings();
520 return settings->iconSize() > KIconLoader::SizeSmall;
521 }
522
523 void DolphinDetailsView::updateDecorationSize()
524 {
525 DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings();
526 const int iconSize = settings->iconSize();
527 m_decorationSize = QSize(iconSize, iconSize);
528
529 m_controller->setZoomInPossible(isZoomInPossible());
530 m_controller->setZoomOutPossible(isZoomOutPossible());
531
532 doItemsLayout();
533 }
534
535 QPoint DolphinDetailsView::contentsPos() const
536 {
537 // implementation note: the horizonal position is ignored currently, as no
538 // horizontal scrolling is done anyway during a selection
539 const QScrollBar* scrollbar = verticalScrollBar();
540 Q_ASSERT(scrollbar != 0);
541
542 const int maxHeight = maximumViewportSize().height();
543 const int height = scrollbar->maximum() - scrollbar->minimum() + 1;
544 const int visibleHeight = model()->rowCount() + 1 - height;
545 if (visibleHeight <= 0) {
546 return QPoint(0, 0);
547 }
548
549 const int y = scrollbar->sliderPosition() * maxHeight / visibleHeight;
550 return QPoint(0, y);
551 }
552
553 KFileItem DolphinDetailsView::itemForIndex(const QModelIndex& index) const
554 {
555 QAbstractProxyModel* proxyModel = static_cast<QAbstractProxyModel*>(model());
556 KDirModel* dirModel = static_cast<KDirModel*>(proxyModel->sourceModel());
557 const QModelIndex dirIndex = proxyModel->mapToSource(index);
558 return dirModel->itemForIndex(dirIndex);
559 }
560
561 KFileItemDelegate::Information DolphinDetailsView::infoForColumn(int columnIndex) const
562 {
563 KFileItemDelegate::Information info = KFileItemDelegate::NoInformation;
564
565 switch (columnIndex) {
566 case DolphinModel::Size: info = KFileItemDelegate::Size; break;
567 case DolphinModel::ModifiedTime: info = KFileItemDelegate::ModificationTime; break;
568 case DolphinModel::Permissions: info = KFileItemDelegate::Permissions; break;
569 case DolphinModel::Owner: info = KFileItemDelegate::Owner; break;
570 case DolphinModel::Group: info = KFileItemDelegate::OwnerAndGroup; break;
571 case DolphinModel::Type: info = KFileItemDelegate::FriendlyMimeType; break;
572 default: break;
573 }
574
575 return info;
576 }
577
578 void DolphinDetailsView::resizeColumns()
579 {
580 // Using the resize mode QHeaderView::ResizeToContents is too slow (it takes
581 // around 3 seconds for each (!) resize operation when having > 10000 items).
582 // This gets a problem especially when opening large directories, where several
583 // resize operations are received for showing the currently available items during
584 // loading (the application hangs around 20 seconds when loading > 10000 items).
585
586 QHeaderView* headerView = header();
587 QFontMetrics fontMetrics(viewport()->font());
588
589 int columnWidth[KDirModel::ColumnCount];
590 columnWidth[KDirModel::Size] = fontMetrics.width("00000 Items");
591 columnWidth[KDirModel::ModifiedTime] = fontMetrics.width("0000-00-00 00:00");
592 columnWidth[KDirModel::Permissions] = fontMetrics.width("xxxxxxxxxx");
593 columnWidth[KDirModel::Owner] = fontMetrics.width("xxxxxxxxxx");
594 columnWidth[KDirModel::Group] = fontMetrics.width("xxxxxxxxxx");
595 columnWidth[KDirModel::Type] = fontMetrics.width("XXXX Xxxxxxx");
596
597 int requiredWidth = 0;
598 for (int i = KDirModel::Size; i <= KDirModel::Type; ++i) {
599 if (!isColumnHidden(i)) {
600 columnWidth[i] += 20; // provide a default gap
601 requiredWidth += columnWidth[i];
602 headerView->resizeSection(i, columnWidth[i]);
603 }
604 }
605
606 // resize the name column in a way that the whole available width is used
607 columnWidth[KDirModel::Name] = viewport()->width() - requiredWidth;
608 if (columnWidth[KDirModel::Name] < 120) {
609 columnWidth[KDirModel::Name] = 120;
610 }
611 headerView->resizeSection(KDirModel::Name, columnWidth[KDirModel::Name]);
612 }
613
614 #include "dolphindetailsview.moc"