]> cloud.milkyroute.net Git - dolphin.git/blob - src/views/dolphincolumnview.cpp
Details view: Fix jumping column-widths
[dolphin.git] / src / views / dolphincolumnview.cpp
1 /***************************************************************************
2 * Copyright (C) 2007-2009 by Peter Penz <peter.penz19@gmail.com> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program 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 *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
19
20 #include "dolphincolumnview.h"
21
22 #include "dolphinmodel.h"
23 #include "dolphincolumnviewcontainer.h"
24 #include "dolphinviewcontroller.h"
25 #include "dolphindirlister.h"
26 #include "dolphinfileitemdelegate.h"
27 #include "dolphinsortfilterproxymodel.h"
28 #include "settings/dolphinsettings.h"
29 #include "dolphinviewautoscroller.h"
30 #include "dolphin_columnmodesettings.h"
31 #include "dolphin_generalsettings.h"
32 #include "draganddrophelper.h"
33 #include "folderexpander.h"
34 #include "tooltips/tooltipmanager.h"
35 #include "viewextensionsfactory.h"
36 #include "viewmodecontroller.h"
37 #include "zoomlevelinfo.h"
38
39 #include <KColorScheme>
40 #include <KDirLister>
41 #include <KFileItem>
42 #include <KIO/PreviewJob>
43 #include <KIcon>
44 #include <KIconEffect>
45 #include <KJob>
46 #include <KLocale>
47 #include <konqmimedata.h>
48
49 #include <QApplication>
50 #include <QClipboard>
51 #include <QHeaderView>
52 #include <QLabel>
53 #include <QPainter>
54 #include <QPoint>
55 #include <QScrollBar>
56
57 DolphinColumnView::DolphinColumnView(QWidget* parent,
58 DolphinColumnViewContainer* container,
59 const KUrl& url) :
60 DolphinTreeView(parent),
61 m_active(false),
62 m_container(container),
63 m_extensionsFactory(0),
64 m_url(url),
65 m_childUrl(),
66 m_font(),
67 m_decorationSize(),
68 m_dirLister(0),
69 m_dolphinModel(0),
70 m_proxyModel(0),
71 m_resizeWidget(0),
72 m_resizeXOrigin(-1)
73 {
74 setMouseTracking(true);
75 setAcceptDrops(true);
76 setUniformRowHeights(true);
77 setSelectionBehavior(SelectItems);
78 setSelectionMode(QAbstractItemView::ExtendedSelection);
79 setDragDropMode(QAbstractItemView::DragDrop);
80 setDropIndicatorShown(false);
81 setRootIsDecorated(false);
82 setItemsExpandable(false);
83 setEditTriggers(QAbstractItemView::NoEditTriggers);
84 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
85 setVerticalScrollMode(QTreeView::ScrollPerPixel);
86
87 m_resizeWidget = new QLabel(this);
88 m_resizeWidget->setPixmap(KIcon("transform-move").pixmap(KIconLoader::SizeSmall));
89 m_resizeWidget->setToolTip(i18nc("@info:tooltip", "Resize column"));
90 setCornerWidget(m_resizeWidget);
91 m_resizeWidget->installEventFilter(this);
92
93 const ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
94 Q_ASSERT(settings);
95
96 if (settings->useSystemFont()) {
97 m_font = KGlobalSettings::generalFont();
98 } else {
99 m_font = QFont(settings->fontFamily(),
100 qRound(settings->fontSize()),
101 settings->fontWeight(),
102 settings->italicFont());
103 m_font.setPointSizeF(settings->fontSize());
104 }
105
106 setMinimumWidth(settings->fontSize() * 10);
107 setMaximumWidth(settings->columnWidth());
108
109 connect(this, SIGNAL(viewportEntered()),
110 m_container->m_dolphinViewController, SLOT(emitViewportEntered()));
111 connect(this, SIGNAL(entered(const QModelIndex&)),
112 this, SLOT(slotEntered(const QModelIndex&)));
113
114 const DolphinView* dolphinView = m_container->m_dolphinViewController->view();
115 connect(dolphinView, SIGNAL(showPreviewChanged()),
116 this, SLOT(slotShowPreviewChanged()));
117
118 m_dirLister = new DolphinDirLister();
119 m_dirLister->setAutoUpdate(true);
120 m_dirLister->setMainWindow(window());
121 m_dirLister->setDelayedMimeTypes(true);
122 const bool showHiddenFiles = m_container->m_dolphinViewController->view()->showHiddenFiles();
123 m_dirLister->setShowingDotFiles(showHiddenFiles);
124 connect(m_dirLister, SIGNAL(completed()), this, SLOT(slotDirListerCompleted()));
125
126 m_dolphinModel = new DolphinModel(this);
127 m_dolphinModel->setDirLister(m_dirLister);
128 m_dolphinModel->setDropsAllowed(DolphinModel::DropOnDirectory);
129
130 m_proxyModel = new DolphinSortFilterProxyModel(this);
131 m_proxyModel->setSourceModel(m_dolphinModel);
132 m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
133
134 m_proxyModel->setSorting(dolphinView->sorting());
135 m_proxyModel->setSortOrder(dolphinView->sortOrder());
136 m_proxyModel->setSortFoldersFirst(dolphinView->sortFoldersFirst());
137
138 setModel(m_proxyModel);
139
140 connect(KGlobalSettings::self(), SIGNAL(kdisplayFontChanged()),
141 this, SLOT(updateFont()));
142
143 const ViewModeController* viewModeController = m_container->m_viewModeController;
144 connect(viewModeController, SIGNAL(zoomLevelChanged(int)),
145 this, SLOT(setZoomLevel(int)));
146 const QString nameFilter = viewModeController->nameFilter();
147 if (!nameFilter.isEmpty()) {
148 m_proxyModel->setFilterFixedString(nameFilter);
149 }
150
151 updateDecorationSize(dolphinView->showPreview());
152 updateBackground();
153
154 DolphinViewController* dolphinViewController = m_container->m_dolphinViewController;
155 m_extensionsFactory = new ViewExtensionsFactory(this, dolphinViewController, viewModeController);
156 m_extensionsFactory->fileItemDelegate()->setMinimizedNameColumn(true);
157
158 m_dirLister->openUrl(url, KDirLister::NoFlags);
159 }
160
161 DolphinColumnView::~DolphinColumnView()
162 {
163 delete m_proxyModel;
164 m_proxyModel = 0;
165 delete m_dolphinModel;
166 m_dolphinModel = 0;
167 m_dirLister = 0; // deleted by m_dolphinModel
168 }
169
170
171 void DolphinColumnView::setActive(bool active)
172 {
173 if (m_active != active) {
174 m_active = active;
175
176 if (active) {
177 activate();
178 } else {
179 deactivate();
180 }
181 }
182 }
183
184 bool DolphinColumnView::isActive() const
185 {
186 return m_active;
187 }
188
189 void DolphinColumnView::setChildUrl(const KUrl& url)
190 {
191 m_childUrl = url;
192 }
193
194 KUrl DolphinColumnView::childUrl() const
195 {
196 return m_childUrl;
197 }
198
199 void DolphinColumnView::setUrl(const KUrl& url)
200 {
201 if (url != m_url) {
202 m_url = url;
203 m_dirLister->openUrl(url, KDirLister::NoFlags);
204 }
205 }
206
207 KUrl DolphinColumnView::url() const
208 {
209 return m_url;
210 }
211
212 void DolphinColumnView::updateBackground()
213 {
214 // TODO: The alpha-value 150 is copied from DolphinView::setActive(). When
215 // cleaning up the cut-indication of DolphinColumnView with the code from
216 // DolphinView a common helper-class should be available which can be shared
217 // by all view implementations -> no hardcoded value anymore
218 const QPalette::ColorRole role = viewport()->backgroundRole();
219 QColor color = viewport()->palette().color(role);
220 color.setAlpha((m_active && m_container->m_active) ? 255 : 150);
221
222 QPalette palette = viewport()->palette();
223 palette.setColor(role, color);
224 viewport()->setPalette(palette);
225
226 update();
227 }
228
229 KFileItem DolphinColumnView::itemAt(const QPoint& pos) const
230 {
231 KFileItem item;
232 const QModelIndex index = indexAt(pos);
233 if (index.isValid() && (index.column() == DolphinModel::Name)) {
234 const QModelIndex dolphinModelIndex = m_proxyModel->mapToSource(index);
235 item = m_dolphinModel->itemForIndex(dolphinModelIndex);
236 }
237 return item;
238 }
239
240 void DolphinColumnView::setSelectionModel(QItemSelectionModel* model)
241 {
242 // If a change of the selection is done although the view is not active
243 // (e. g. by the selection markers), the column must be activated. This
244 // is done by listening to the current selectionChanged() signal.
245 if (selectionModel()) {
246 disconnect(selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
247 this, SLOT(requestActivation()));
248 }
249
250 DolphinTreeView::setSelectionModel(model);
251
252 connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
253 this, SLOT(requestActivation()));
254 }
255
256 QStyleOptionViewItem DolphinColumnView::viewOptions() const
257 {
258 QStyleOptionViewItem viewOptions = DolphinTreeView::viewOptions();
259 viewOptions.font = m_font;
260 viewOptions.fontMetrics = QFontMetrics(m_font);
261 viewOptions.decorationSize = m_decorationSize;
262 viewOptions.showDecorationSelected = true;
263 return viewOptions;
264 }
265
266 bool DolphinColumnView::event(QEvent* event)
267 {
268 if (event->type() == QEvent::Polish) {
269 // Hide all columns except of the 'Name' column
270 for (int i = DolphinModel::Name + 1; i < DolphinModel::ExtraColumnCount; ++i) {
271 hideColumn(i);
272 }
273 header()->hide();
274 }
275
276 return DolphinTreeView::event(event);
277 }
278
279 void DolphinColumnView::startDrag(Qt::DropActions supportedActions)
280 {
281 DragAndDropHelper::instance().startDrag(this, supportedActions, m_container->m_dolphinViewController);
282 DolphinTreeView::startDrag(supportedActions);
283 }
284
285 void DolphinColumnView::dragEnterEvent(QDragEnterEvent* event)
286 {
287 event->acceptProposedAction();
288 requestActivation();
289 DolphinTreeView::dragEnterEvent(event);
290 }
291
292 void DolphinColumnView::dragMoveEvent(QDragMoveEvent* event)
293 {
294 DolphinTreeView::dragMoveEvent(event);
295 event->acceptProposedAction();
296 }
297
298 void DolphinColumnView::dropEvent(QDropEvent* event)
299 {
300 const QModelIndex index = indexAt(event->pos());
301 m_container->m_dolphinViewController->setItemView(this);
302 const QModelIndex dolphinModelIndex = m_proxyModel->mapToSource(index);
303 const KFileItem item = m_dolphinModel->itemForIndex(dolphinModelIndex);
304 m_container->m_dolphinViewController->indicateDroppedUrls(item, event);
305 DolphinTreeView::dropEvent(event);
306 }
307
308 void DolphinColumnView::paintEvent(QPaintEvent* event)
309 {
310 if (!m_childUrl.isEmpty()) {
311 // Indicate the shown URL of the next column by highlighting the shown folder item
312 const QModelIndex dirIndex = m_dolphinModel->indexForUrl(m_childUrl);
313 const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex);
314 if (proxyIndex.isValid() && !selectionModel()->isSelected(proxyIndex)) {
315 QPainter painter(viewport());
316
317 QStyleOptionViewItemV4 option;
318 option.initFrom(this);
319 option.rect = visualRect(proxyIndex);
320 option.state = QStyle::State_Enabled | QStyle::State_HasFocus;
321 option.viewItemPosition = QStyleOptionViewItemV4::OnlyOne;
322 style()->drawPrimitive(QStyle::PE_FrameFocusRect, &option, &painter, this);
323 }
324 }
325
326 DolphinTreeView::paintEvent(event);
327 }
328
329 void DolphinColumnView::mousePressEvent(QMouseEvent* event)
330 {
331 requestActivation();
332 if (!indexAt(event->pos()).isValid() && (QApplication::mouseButtons() & Qt::MidButton)) {
333 m_container->m_dolphinViewController->replaceUrlByClipboard();
334 }
335
336 DolphinTreeView::mousePressEvent(event);
337 }
338
339 void DolphinColumnView::keyPressEvent(QKeyEvent* event)
340 {
341 DolphinTreeView::keyPressEvent(event);
342
343 DolphinViewController* controller = m_container->m_dolphinViewController;
344 controller->handleKeyPressEvent(event);
345 switch (event->key()) {
346 case Qt::Key_Right: {
347 // Special key handling for the column: A Key_Right should
348 // open a new column for the currently selected folder.
349 const QModelIndex dolphinModelIndex = m_proxyModel->mapToSource(currentIndex());
350 const KFileItem item = m_dolphinModel->itemForIndex(dolphinModelIndex);
351 if (!item.isNull() && item.isDir()) {
352 controller->emitItemTriggered(item);
353 }
354 break;
355 }
356
357 case Qt::Key_Escape:
358 selectionModel()->setCurrentIndex(selectionModel()->currentIndex(),
359 QItemSelectionModel::Current |
360 QItemSelectionModel::Clear);
361 break;
362
363 default:
364 break;
365 }
366 }
367
368 void DolphinColumnView::contextMenuEvent(QContextMenuEvent* event)
369 {
370 requestActivation();
371 DolphinTreeView::contextMenuEvent(event);
372 m_container->m_dolphinViewController->triggerContextMenuRequest(event->pos());
373 }
374
375 void DolphinColumnView::wheelEvent(QWheelEvent* event)
376 {
377 const int step = m_decorationSize.height();
378 verticalScrollBar()->setSingleStep(step);
379 DolphinTreeView::wheelEvent(event);
380 }
381
382 void DolphinColumnView::leaveEvent(QEvent* event)
383 {
384 DolphinTreeView::leaveEvent(event);
385 // if the mouse is above an item and moved very fast outside the widget,
386 // no viewportEntered() signal might be emitted although the mouse has been moved
387 // above the viewport
388 m_container->m_dolphinViewController->emitViewportEntered();
389 }
390
391 void DolphinColumnView::currentChanged(const QModelIndex& current, const QModelIndex& previous)
392 {
393 DolphinTreeView::currentChanged(current, previous);
394 m_extensionsFactory->handleCurrentIndexChange(current, previous);
395 }
396
397 QRect DolphinColumnView::visualRect(const QModelIndex& index) const
398 {
399 QRect rect = DolphinTreeView::visualRect(index);
400
401 const QModelIndex dolphinModelIndex = m_proxyModel->mapToSource(index);
402 const KFileItem item = m_dolphinModel->itemForIndex(dolphinModelIndex);
403 if (!item.isNull()) {
404 const int width = DolphinFileItemDelegate::nameColumnWidth(item.text(), viewOptions());
405 rect.setWidth(width);
406 }
407
408 return rect;
409 }
410
411 bool DolphinColumnView::acceptsDrop(const QModelIndex& index) const
412 {
413 if (index.isValid() && (index.column() == DolphinModel::Name)) {
414 // Accept drops above directories
415 const QModelIndex dolphinModelIndex = m_proxyModel->mapToSource(index);
416 const KFileItem item = m_dolphinModel->itemForIndex(dolphinModelIndex);
417 return !item.isNull() && item.isDir();
418 }
419
420 return false;
421 }
422
423 bool DolphinColumnView::eventFilter(QObject* watched, QEvent* event)
424 {
425 if (watched == m_resizeWidget) {
426 switch (event->type()) {
427 case QEvent::MouseButtonPress: {
428 // Initiate the resizing of the column
429 QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
430 m_resizeXOrigin = mouseEvent->globalX();
431 m_resizeWidget->setMouseTracking(true);
432 event->accept();
433 return true;
434 }
435
436 case QEvent::MouseButtonDblClick: {
437 // Reset the column width to the default value
438 const ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
439 setMaximumWidth(settings->columnWidth());
440 m_container->layoutColumns();
441 m_resizeWidget->setMouseTracking(false);
442 m_resizeXOrigin = -1;
443 event->accept();
444 return true;
445 }
446
447 case QEvent::MouseMove: {
448 // Resize the column and trigger a relayout of the container
449 QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
450 int requestedWidth = maximumWidth() - m_resizeXOrigin + mouseEvent->globalX();;
451 if (requestedWidth < minimumWidth()) {
452 requestedWidth = minimumWidth();
453 }
454 setMaximumWidth(requestedWidth);
455
456 m_container->layoutColumns();
457
458 m_resizeXOrigin = mouseEvent->globalX();
459
460 event->accept();
461 return true;
462 }
463
464 case QEvent::MouseButtonRelease: {
465 // The resizing has been finished
466 m_resizeWidget->setMouseTracking(false);
467 m_resizeXOrigin = -1;
468 event->accept();
469 return true;
470 }
471
472 default:
473 break;
474 }
475 }
476 return DolphinTreeView::eventFilter(watched, event);
477 }
478 void DolphinColumnView::setZoomLevel(int level)
479 {
480 const int size = ZoomLevelInfo::iconSizeForZoomLevel(level);
481 ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
482
483 const bool showPreview = m_container->m_dolphinViewController->view()->showPreview();
484 if (showPreview) {
485 settings->setPreviewSize(size);
486 } else {
487 settings->setIconSize(size);
488 }
489
490 updateDecorationSize(showPreview);
491 }
492
493 void DolphinColumnView::slotEntered(const QModelIndex& index)
494 {
495 m_container->m_dolphinViewController->setItemView(this);
496 m_container->m_dolphinViewController->emitItemEntered(index);
497 }
498
499 void DolphinColumnView::requestActivation()
500 {
501 m_container->m_dolphinViewController->requestActivation();
502 if (!m_active) {
503 m_container->requestActivation(this);
504 selectionModel()->clear();
505 }
506 }
507
508 void DolphinColumnView::updateFont()
509 {
510 const ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
511 Q_ASSERT(settings);
512
513 if (settings->useSystemFont()) {
514 m_font = KGlobalSettings::generalFont();
515 }
516 }
517
518 void DolphinColumnView::slotShowPreviewChanged()
519 {
520 const DolphinView* view = m_container->m_dolphinViewController->view();
521 updateDecorationSize(view->showPreview());
522 }
523
524 void DolphinColumnView::slotDirListerCompleted()
525 {
526 if (!m_childUrl.isEmpty()) {
527 return;
528 }
529
530 // Try to optimize the width of the column, so that no name gets clipped
531 const int requiredWidth = sizeHintForColumn(DolphinModel::Name);
532
533 const ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
534 if (requiredWidth > settings->columnWidth()) {
535 int frameAroundContents = 0;
536 if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) {
537 // TODO: Using 2 PM_DefaultFrameWidths are not sufficient. Check Qt-code
538 // for other pixelmetrics that should be added...
539 frameAroundContents = style()->pixelMetric(QStyle::PM_DefaultFrameWidth) * 4;
540 }
541
542 const int scrollBarWidth = style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, verticalScrollBar());
543
544 setMaximumWidth(requiredWidth + frameAroundContents + scrollBarWidth);
545 m_container->layoutColumns();
546 if (m_active) {
547 m_container->assureVisibleActiveColumn();
548 }
549 }
550 }
551
552 void DolphinColumnView::activate()
553 {
554 setFocus(Qt::OtherFocusReason);
555
556 connect(this, SIGNAL(clicked(const QModelIndex&)),
557 m_container->m_dolphinViewController, SLOT(requestTab(const QModelIndex&)));
558 if (KGlobalSettings::singleClick()) {
559 connect(this, SIGNAL(clicked(const QModelIndex&)),
560 m_container->m_dolphinViewController, SLOT(triggerItem(const QModelIndex&)));
561 } else {
562 connect(this, SIGNAL(doubleClicked(const QModelIndex&)),
563 m_container->m_dolphinViewController, SLOT(triggerItem(const QModelIndex&)));
564 }
565
566 if (selectionModel() && selectionModel()->currentIndex().isValid()) {
567 selectionModel()->setCurrentIndex(selectionModel()->currentIndex(), QItemSelectionModel::SelectCurrent);
568 }
569
570 updateBackground();
571 }
572
573 void DolphinColumnView::deactivate()
574 {
575 clearFocus();
576
577 disconnect(this, SIGNAL(clicked(const QModelIndex&)),
578 m_container->m_dolphinViewController, SLOT(requestTab(const QModelIndex&)));
579 if (KGlobalSettings::singleClick()) {
580 disconnect(this, SIGNAL(clicked(const QModelIndex&)),
581 m_container->m_dolphinViewController, SLOT(triggerItem(const QModelIndex&)));
582 } else {
583 disconnect(this, SIGNAL(doubleClicked(const QModelIndex&)),
584 m_container->m_dolphinViewController, SLOT(triggerItem(const QModelIndex&)));
585 }
586
587 // It is important to disconnect the connection to requestActivation() temporary, otherwise the internal
588 // clearing of the selection would result in activating the column again.
589 disconnect(selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
590 this, SLOT(requestActivation()));
591 const QModelIndex current = selectionModel()->currentIndex();
592 selectionModel()->clear();
593 selectionModel()->setCurrentIndex(current, QItemSelectionModel::NoUpdate);
594 connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
595 this, SLOT(requestActivation()));
596
597 updateBackground();
598 }
599
600 void DolphinColumnView::updateDecorationSize(bool showPreview)
601 {
602 ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
603 const int iconSize = showPreview ? settings->previewSize() : settings->iconSize();
604 const QSize size(iconSize, iconSize);
605 setIconSize(size);
606
607 m_decorationSize = size;
608
609 doItemsLayout();
610 }
611
612 #include "dolphincolumnview.moc"