]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphindetailsview.cpp
57d5eb81c3f09707701841ce26e757e54ed4392a
[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 "viewproperties.h"
28
29 #include "dolphin_detailsmodesettings.h"
30
31 #include <kdirmodel.h>
32 #include <klocale.h>
33 #include <kmenu.h>
34
35 #include <QAbstractProxyModel>
36 #include <QAction>
37 #include <QApplication>
38 #include <QHeaderView>
39 #include <QRubberBand>
40 #include <QPainter>
41 #include <QScrollBar>
42
43 DolphinDetailsView::DolphinDetailsView(QWidget* parent, DolphinController* controller) :
44 QTreeView(parent),
45 m_controller(controller),
46 m_dragging(false),
47 m_showElasticBand(false),
48 m_elasticBandOrigin(),
49 m_elasticBandDestination()
50 {
51 Q_ASSERT(controller != 0);
52
53 setAcceptDrops(true);
54 setRootIsDecorated(false);
55 setSortingEnabled(true);
56 setUniformRowHeights(true);
57 setSelectionBehavior(SelectItems);
58 setDragDropMode(QAbstractItemView::DragDrop);
59 setDropIndicatorShown(false);
60 setAlternatingRowColors(true);
61
62 setMouseTracking(true);
63 viewport()->setAttribute(Qt::WA_Hover);
64
65 const ViewProperties props(controller->url());
66 setSortIndicatorSection(props.sorting());
67 setSortIndicatorOrder(props.sortOrder());
68
69 QHeaderView* headerView = header();
70 connect(headerView, SIGNAL(sectionClicked(int)),
71 this, SLOT(synchronizeSortingState(int)));
72 headerView->setContextMenuPolicy(Qt::CustomContextMenu);
73 connect(headerView, SIGNAL(customContextMenuRequested(const QPoint&)),
74 this, SLOT(configureColumns(const QPoint&)));
75
76 connect(parent, SIGNAL(sortingChanged(DolphinView::Sorting)),
77 this, SLOT(setSortIndicatorSection(DolphinView::Sorting)));
78 connect(parent, SIGNAL(sortOrderChanged(Qt::SortOrder)),
79 this, SLOT(setSortIndicatorOrder(Qt::SortOrder)));
80
81 // TODO: Connecting to the signal 'activated()' is not possible, as kstyle
82 // does not forward the single vs. doubleclick to it yet (KDE 4.1?). Hence it is
83 // necessary connecting the signal 'singleClick()' or 'doubleClick' and to handle the
84 // RETURN-key in keyPressEvent().
85 if (KGlobalSettings::singleClick()) {
86 connect(this, SIGNAL(clicked(const QModelIndex&)),
87 this, SLOT(triggerItem(const QModelIndex&)));
88 } else {
89 connect(this, SIGNAL(doubleClicked(const QModelIndex&)),
90 this, SLOT(triggerItem(const QModelIndex&)));
91 }
92 connect(this, SIGNAL(entered(const QModelIndex&)),
93 this, SLOT(slotEntered(const QModelIndex&)));
94 connect(this, SIGNAL(viewportEntered()),
95 controller, SLOT(emitViewportEntered()));
96 connect(controller, SIGNAL(zoomIn()),
97 this, SLOT(zoomIn()));
98 connect(controller, SIGNAL(zoomOut()),
99 this, SLOT(zoomOut()));
100
101 // apply the details mode settings to the widget
102 const DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings();
103 Q_ASSERT(settings != 0);
104
105 m_viewOptions = QTreeView::viewOptions();
106
107 QFont font(settings->fontFamily(), settings->fontSize());
108 font.setItalic(settings->italicFont());
109 font.setBold(settings->boldFont());
110 m_viewOptions.font = font;
111 m_viewOptions.showDecorationSelected = true;
112
113 // TODO: Remove this check when 4.3.2 is released and KDE requires it... this
114 // check avoids a division by zero happening on versions before 4.3.1.
115 // Right now KDE in theory can be shipped with Qt 4.3.0 and above.
116 // ereslibre
117 #if (QT_VERSION >= QT_VERSION_CHECK(4, 3, 2) || defined(QT_KDE_QT_COPY))
118 setVerticalScrollMode(QTreeView::ScrollPerPixel);
119 setHorizontalScrollMode(QTreeView::ScrollPerPixel);
120 #endif
121
122 updateDecorationSize();
123
124 setFocus();
125 }
126
127 DolphinDetailsView::~DolphinDetailsView()
128 {
129 }
130
131 bool DolphinDetailsView::event(QEvent* event)
132 {
133 if (event->type() == QEvent::Polish) {
134 // Assure that by respecting the available width that:
135 // - the 'Name' column is stretched as large as possible
136 // - the remaining columns are as small as possible
137 QHeaderView* headerView = header();
138 headerView->setStretchLastSection(false);
139 headerView->setResizeMode(QHeaderView::ResizeToContents);
140 headerView->setResizeMode(0, QHeaderView::Stretch);
141 headerView->setMovable(false);
142
143 // hide columns if this is indicated by the settings
144 const DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings();
145 Q_ASSERT(settings != 0);
146 if (!settings->showDate()) {
147 hideColumn(DolphinModel::ModifiedTime);
148 }
149
150 if (!settings->showPermissions()) {
151 hideColumn(DolphinModel::Permissions);
152 }
153
154 if (!settings->showOwner()) {
155 hideColumn(DolphinModel::Owner);
156 }
157
158 if (!settings->showGroup()) {
159 hideColumn(DolphinModel::Group);
160 }
161
162 if (!settings->showType()) {
163 hideColumn(DolphinModel::Type);
164 }
165
166 hideColumn(DolphinModel::Rating);
167 hideColumn(DolphinModel::Tags);
168 }
169 // TODO: Remove this check when 4.3.2 is released and KDE requires it... this
170 // check avoids a division by zero happening on versions before 4.3.1.
171 // Right now KDE in theory can be shipped with Qt 4.3.0 and above.
172 // ereslibre
173 #if (QT_VERSION >= QT_VERSION_CHECK(4, 3, 2) || defined(QT_KDE_QT_COPY))
174 else if (event->type() == QEvent::UpdateRequest) {
175 // a wheel movement will scroll 4 items
176 if (model()->rowCount() > 0) {
177 verticalScrollBar()->setSingleStep((sizeHintForRow(0) / 3) * 4);
178 }
179 }
180 #endif
181
182 return QTreeView::event(event);
183 }
184
185 QStyleOptionViewItem DolphinDetailsView::viewOptions() const
186 {
187 return m_viewOptions;
188 }
189
190 void DolphinDetailsView::contextMenuEvent(QContextMenuEvent* event)
191 {
192 QTreeView::contextMenuEvent(event);
193 m_controller->triggerContextMenuRequest(event->pos());
194 }
195
196 void DolphinDetailsView::mousePressEvent(QMouseEvent* event)
197 {
198 m_controller->requestActivation();
199
200 QTreeView::mousePressEvent(event);
201
202 const QModelIndex index = indexAt(event->pos());
203 if (!index.isValid() || (index.column() != DolphinModel::Name)) {
204 const Qt::KeyboardModifiers modifier = QApplication::keyboardModifiers();
205 if (!(modifier & Qt::ShiftModifier) && !(modifier & Qt::ControlModifier)) {
206 clearSelection();
207 }
208 }
209
210 if (event->button() == Qt::LeftButton) {
211 m_showElasticBand = true;
212
213 const QPoint pos(contentsPos());
214 m_elasticBandOrigin = event->pos();
215 m_elasticBandOrigin.setX(m_elasticBandOrigin.x() + pos.x());
216 m_elasticBandOrigin.setY(m_elasticBandOrigin.y() + pos.y());
217 m_elasticBandDestination = event->pos();
218 }
219 }
220
221 void DolphinDetailsView::mouseMoveEvent(QMouseEvent* event)
222 {
223 QTreeView::mouseMoveEvent(event);
224 if (m_showElasticBand) {
225 updateElasticBand();
226 }
227 }
228
229 void DolphinDetailsView::mouseReleaseEvent(QMouseEvent* event)
230 {
231 QTreeView::mouseReleaseEvent(event);
232 if (m_showElasticBand) {
233 updateElasticBand();
234 m_showElasticBand = false;
235 }
236 }
237
238 void DolphinDetailsView::dragEnterEvent(QDragEnterEvent* event)
239 {
240 if (event->mimeData()->hasUrls()) {
241 event->acceptProposedAction();
242 }
243
244 if (m_showElasticBand) {
245 updateElasticBand();
246 m_showElasticBand = false;
247 }
248 m_dragging = true;
249 }
250
251 void DolphinDetailsView::dragLeaveEvent(QDragLeaveEvent* event)
252 {
253 QTreeView::dragLeaveEvent(event);
254
255 // TODO: remove this code when the issue #160611 is solved in Qt 4.4
256 m_dragging = false;
257 setDirtyRegion(m_dropRect);
258 }
259
260 void DolphinDetailsView::dragMoveEvent(QDragMoveEvent* event)
261 {
262 QTreeView::dragMoveEvent(event);
263
264 // TODO: remove this code when the issue #160611 is solved in Qt 4.4
265 setDirtyRegion(m_dropRect);
266 const QModelIndex index = indexAt(event->pos());
267 if (!index.isValid() || (index.column() != DolphinModel::Name)) {
268 m_dragging = false;
269 } else {
270 m_dragging = true;
271 m_dropRect = visualRect(index);
272 setDirtyRegion(m_dropRect);
273 }
274 }
275
276 void DolphinDetailsView::dropEvent(QDropEvent* event)
277 {
278 const KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
279 if (!urls.isEmpty()) {
280 event->acceptProposedAction();
281 m_controller->indicateDroppedUrls(urls,
282 m_controller->url(),
283 indexAt(event->pos()),
284 event->source());
285 }
286 QTreeView::dropEvent(event);
287 m_dragging = false;
288 }
289
290 void DolphinDetailsView::paintEvent(QPaintEvent* event)
291 {
292 QTreeView::paintEvent(event);
293 if (m_showElasticBand) {
294 // The following code has been taken from QListView
295 // and adapted to DolphinDetailsView.
296 // (C) 1992-2007 Trolltech ASA
297 QStyleOptionRubberBand opt;
298 opt.initFrom(this);
299 opt.shape = QRubberBand::Rectangle;
300 opt.opaque = false;
301 opt.rect = elasticBandRect();
302
303 QPainter painter(viewport());
304 painter.save();
305 style()->drawControl(QStyle::CE_RubberBand, &opt, &painter);
306 painter.restore();
307 }
308
309 // TODO: remove this code when the issue #160611 is solved in Qt 4.4
310 if (m_dragging) {
311 const QBrush& brush = m_viewOptions.palette.brush(QPalette::Normal, QPalette::Highlight);
312 DolphinController::drawHoverIndication(viewport(), m_dropRect, brush);
313 }
314 }
315
316 void DolphinDetailsView::keyPressEvent(QKeyEvent* event)
317 {
318 QTreeView::keyPressEvent(event);
319
320 const QItemSelectionModel* selModel = selectionModel();
321 const QModelIndex currentIndex = selModel->currentIndex();
322 const bool trigger = currentIndex.isValid()
323 && (event->key() == Qt::Key_Return)
324 && (selModel->selectedIndexes().count() <= 1);
325 if (trigger) {
326 triggerItem(currentIndex);
327 }
328 }
329
330 void DolphinDetailsView::resizeEvent(QResizeEvent* event)
331 {
332 QTreeView::resizeEvent(event);
333
334 // assure that the width of the name-column does not get too small
335 const int minWidth = 120;
336 QHeaderView* headerView = header();
337 bool useFixedWidth = (headerView->sectionSize(KDirModel::Name) <= minWidth)
338 && (headerView->resizeMode(0) != QHeaderView::Fixed);
339 if (useFixedWidth) {
340 // the current width of the name-column is too small, hence
341 // use a fixed size
342 headerView->setResizeMode(QHeaderView::Fixed);
343 headerView->setResizeMode(0, QHeaderView::Fixed);
344 headerView->resizeSection(KDirModel::Name, minWidth);
345 } else if (headerView->resizeMode(0) != QHeaderView::Stretch) {
346 // check whether there is enough available viewport width
347 // to automatically resize the columns
348 const int availableWidth = viewport()->width();
349
350 int headerWidth = 0;
351 const int count = headerView->count();
352 for (int i = 0; i < count; ++i) {
353 headerWidth += headerView->sectionSize(i);
354 }
355
356 if (headerWidth < availableWidth) {
357 headerView->setResizeMode(QHeaderView::ResizeToContents);
358 headerView->setResizeMode(0, QHeaderView::Stretch);
359 }
360 }
361 }
362
363 void DolphinDetailsView::setSortIndicatorSection(DolphinView::Sorting sorting)
364 {
365 QHeaderView* headerView = header();
366 headerView->setSortIndicator(sorting, headerView->sortIndicatorOrder());
367 }
368
369 void DolphinDetailsView::setSortIndicatorOrder(Qt::SortOrder sortOrder)
370 {
371 QHeaderView* headerView = header();
372 headerView->setSortIndicator(headerView->sortIndicatorSection(), sortOrder);
373 }
374
375 void DolphinDetailsView::synchronizeSortingState(int column)
376 {
377 // The sorting has already been changed in QTreeView if this slot is
378 // invoked, but Dolphin is not informed about this.
379 DolphinView::Sorting sorting = DolphinSortFilterProxyModel::sortingForColumn(column);
380 const Qt::SortOrder sortOrder = header()->sortIndicatorOrder();
381 m_controller->indicateSortingChange(sorting);
382 m_controller->indicateSortOrderChange(sortOrder);
383 }
384
385 void DolphinDetailsView::slotEntered(const QModelIndex& index)
386 {
387 const QPoint pos = viewport()->mapFromGlobal(QCursor::pos());
388 const int nameColumnWidth = header()->sectionSize(DolphinModel::Name);
389 if (pos.x() < nameColumnWidth) {
390 m_controller->emitItemEntered(itemForIndex(index));
391 }
392 else {
393 m_controller->emitViewportEntered();
394 }
395 }
396
397 void DolphinDetailsView::updateElasticBand()
398 {
399 Q_ASSERT(m_showElasticBand);
400 QRect dirtyRegion(elasticBandRect());
401 m_elasticBandDestination = viewport()->mapFromGlobal(QCursor::pos());
402 dirtyRegion = dirtyRegion.united(elasticBandRect());
403 setDirtyRegion(dirtyRegion);
404 }
405
406 QRect DolphinDetailsView::elasticBandRect() const
407 {
408 const QPoint pos(contentsPos());
409 const QPoint topLeft(m_elasticBandOrigin.x() - pos.x(), m_elasticBandOrigin.y() - pos.y());
410 return QRect(topLeft, m_elasticBandDestination).normalized();
411 }
412
413 void DolphinDetailsView::zoomIn()
414 {
415 if (isZoomInPossible()) {
416 DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings();
417 switch (settings->iconSize()) {
418 case KIconLoader::SizeSmall: settings->setIconSize(KIconLoader::SizeMedium); break;
419 case KIconLoader::SizeMedium: settings->setIconSize(KIconLoader::SizeLarge); break;
420 default: Q_ASSERT(false); break;
421 }
422 updateDecorationSize();
423 }
424 }
425
426 void DolphinDetailsView::zoomOut()
427 {
428 if (isZoomOutPossible()) {
429 DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings();
430 switch (settings->iconSize()) {
431 case KIconLoader::SizeLarge: settings->setIconSize(KIconLoader::SizeMedium); break;
432 case KIconLoader::SizeMedium: settings->setIconSize(KIconLoader::SizeSmall); break;
433 default: Q_ASSERT(false); break;
434 }
435 updateDecorationSize();
436 }
437 }
438
439 void DolphinDetailsView::triggerItem(const QModelIndex& index)
440 {
441 const KFileItem item = itemForIndex(index);
442 if (index.isValid() && (index.column() == KDirModel::Name)) {
443 m_controller->triggerItem(item);
444 } else {
445 clearSelection();
446 m_controller->emitItemEntered(item);
447 }
448 }
449
450 void DolphinDetailsView::configureColumns(const QPoint& pos)
451 {
452 KMenu popup(this);
453 popup.addTitle(i18nc("@title:menu", "Columns"));
454
455 QHeaderView* headerView = header();
456 for (int i = DolphinModel::ModifiedTime; i <= DolphinModel::Type; ++i) {
457 const int logicalIndex = headerView->logicalIndex(i);
458 const QString text = model()->headerData(i, Qt::Horizontal).toString();
459 QAction* action = popup.addAction(text);
460 action->setCheckable(true);
461 action->setChecked(!headerView->isSectionHidden(logicalIndex));
462 action->setData(i);
463 }
464
465 QAction* activatedAction = popup.exec(header()->mapToGlobal(pos));
466 if (activatedAction != 0) {
467 const bool show = activatedAction->isChecked();
468 DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings();
469 Q_ASSERT(settings != 0);
470
471 // remember the changed column visibility in the settings
472 const int columnIndex = activatedAction->data().toInt();
473 switch (columnIndex) {
474 case DolphinModel::ModifiedTime: settings->setShowDate(show); break;
475 case DolphinModel::Permissions: settings->setShowPermissions(show); break;
476 case DolphinModel::Owner: settings->setShowOwner(show); break;
477 case DolphinModel::Group: settings->setShowGroup(show); break;
478 case DolphinModel::Type: settings->setShowType(show); break;
479 default: break;
480 }
481
482 // apply the changed column visibility
483 if (show) {
484 showColumn(columnIndex);
485 } else {
486 hideColumn(columnIndex);
487 }
488 }
489 }
490
491 bool DolphinDetailsView::isZoomInPossible() const
492 {
493 DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings();
494 return settings->iconSize() < KIconLoader::SizeLarge;
495 }
496
497 bool DolphinDetailsView::isZoomOutPossible() const
498 {
499 DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings();
500 return settings->iconSize() > KIconLoader::SizeSmall;
501 }
502
503 void DolphinDetailsView::updateDecorationSize()
504 {
505 DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings();
506 const int iconSize = settings->iconSize();
507 m_viewOptions.decorationSize = QSize(iconSize, iconSize);
508
509 m_controller->setZoomInPossible(isZoomInPossible());
510 m_controller->setZoomOutPossible(isZoomOutPossible());
511
512 doItemsLayout();
513 }
514
515 QPoint DolphinDetailsView::contentsPos() const
516 {
517 // implementation note: the horizonal position is ignored currently, as no
518 // horizontal scrolling is done anyway during a selection
519 const QScrollBar* scrollbar = verticalScrollBar();
520 Q_ASSERT(scrollbar != 0);
521
522 const int maxHeight = maximumViewportSize().height();
523 const int height = scrollbar->maximum() - scrollbar->minimum() + 1;
524 const int visibleHeight = model()->rowCount() + 1 - height;
525 if (visibleHeight <= 0) {
526 return QPoint(0, 0);
527 }
528
529 const int y = scrollbar->sliderPosition() * maxHeight / visibleHeight;
530 return QPoint(0, y);
531 }
532
533 KFileItem DolphinDetailsView::itemForIndex(const QModelIndex& index) const
534 {
535 QAbstractProxyModel* proxyModel = static_cast<QAbstractProxyModel*>(model());
536 KDirModel* dirModel = static_cast<KDirModel*>(proxyModel->sourceModel());
537 const QModelIndex dirIndex = proxyModel->mapToSource(index);
538 return dirModel->itemForIndex(dirIndex);
539 }
540
541 #include "dolphindetailsview.moc"