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