1 /*****************************************************************************
2 * Copyright (C) 2000-2002 Shie Erlich <krusader@users.sourceforge.net> *
3 * Copyright (C) 2000-2002 Rafi Yanai <krusader@users.sourceforge.net> *
4 * Copyright (C) 2004-2019 Krusader Krew [https://krusader.org] *
5 * *
6 * This file is part of Krusader [https://krusader.org]. *
7 * *
8 * Krusader is free software: you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation, either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * Krusader is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with Krusader. If not, see [http://www.gnu.org/licenses/]. *
20 *****************************************************************************/
21
22 #include "krview.h"
23
24 #include "krselectionmode.h"
25 #include "krviewfactory.h"
26 #include "krviewitem.h"
27 #include "../FileSystem/dirlisterinterface.h"
28 #include "../FileSystem/fileitem.h"
29 #include "../FileSystem/krpermhandler.h"
30 #include "../Filter/filterdialog.h"
31 #include "../defaults.h"
32 #include "../filelisticon.h"
33 #include "../krcolorcache.h"
34 #include "../krglobal.h"
35 #include "../krpreviews.h"
36 #include "../viewactions.h"
37
38 // QtCore
39 #include <QDebug>
40 #include <QDir>
41 // QtGui
42 #include <QPixmapCache>
43 #include <QBitmap>
44 #include <QPainter>
45 #include <QPixmap>
46 // QtWidgets
47 #include <QAction>
48 #include <QInputDialog>
49 #include <QMimeDatabase>
50 #include <QMimeType>
51 #include <qnamespace.h>
52
53 #include <KConfigCore/KSharedConfig>
54 #include <KI18n/KLocalizedString>
55
56
57 #define FILEITEM getFileItem()
58
59 KrView *KrViewOperator::_changedView = 0;
60 KrViewProperties::PropertyType KrViewOperator::_changedProperties = KrViewProperties::NoProperty;
61
62
63 // ----------------------------- operator
KrViewOperator(KrView * view,QWidget * widget)64 KrViewOperator::KrViewOperator(KrView *view, QWidget *widget) :
65 _view(view), _widget(widget), _massSelectionUpdate(false)
66 {
67 _saveDefaultSettingsTimer.setSingleShot(true);
68 connect(&_saveDefaultSettingsTimer, SIGNAL(timeout()), SLOT(saveDefaultSettings()));
69 }
70
~KrViewOperator()71 KrViewOperator::~KrViewOperator()
72 {
73 if(_changedView == _view)
74 saveDefaultSettings();
75 }
76
startUpdate()77 void KrViewOperator::startUpdate()
78 {
79 _view->refresh();
80 }
81
cleared()82 void KrViewOperator::cleared()
83 {
84 _view->clear();
85 }
86
fileAdded(FileItem * fileitem)87 void KrViewOperator::fileAdded(FileItem *fileitem)
88 {
89 _view->addItem(fileitem);
90 }
91
fileUpdated(FileItem * newFileitem)92 void KrViewOperator::fileUpdated(FileItem *newFileitem)
93 {
94 _view->updateItem(newFileitem);
95 }
96
startDrag()97 void KrViewOperator::startDrag()
98 {
99 QStringList items;
100 _view->getSelectedItems(&items);
101 if (items.empty())
102 return ; // don't drag an empty thing
103 QPixmap px;
104 if (items.count() > 1 || _view->getCurrentKrViewItem() == 0)
105 px = FileListIcon("document-multiple").pixmap(); // we are dragging multiple items
106 else
107 px = _view->getCurrentKrViewItem()->icon();
108 emit letsDrag(items, px);
109 }
110
searchItem(const QString & text,bool caseSensitive,int direction)111 bool KrViewOperator::searchItem(const QString &text, bool caseSensitive, int direction)
112 {
113 KrViewItem * item = _view->getCurrentKrViewItem();
114 if (!item) {
115 return false;
116 }
117 const QRegExp regeEx(text, caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive,
118 QRegExp::Wildcard);
119 if (!direction) {
120 if (regeEx.indexIn(item->name()) == 0) {
121 return true;
122 }
123 direction = 1;
124 }
125 KrViewItem * startItem = item;
126 while (true) {
127 item = (direction > 0) ? _view->getNext(item) : _view->getPrev(item);
128 if (!item)
129 item = (direction > 0) ? _view->getFirst() : _view->getLast();
130 if (regeEx.indexIn(item->name()) == 0) {
131 _view->setCurrentKrViewItem(item);
132 _view->makeItemVisible(item);
133 return true;
134 }
135 if (item == startItem) {
136 return false;
137 }
138 }
139 }
140
filterSearch(const QString & text,bool caseSensitive)141 bool KrViewOperator::filterSearch(const QString &text, bool caseSensitive)
142 {
143 _view->_quickFilterMask = QRegExp(text,
144 caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive,
145 QRegExp::Wildcard);
146 _view->refresh();
147 return _view->_count || !_view->_files->numFileItems();
148 }
149
setMassSelectionUpdate(bool upd)150 void KrViewOperator::setMassSelectionUpdate(bool upd)
151 {
152 _massSelectionUpdate = upd;
153 if (!upd) {
154 emit selectionChanged();
155 _view->redraw();
156 }
157 }
158
settingsChanged(KrViewProperties::PropertyType properties)159 void KrViewOperator::settingsChanged(KrViewProperties::PropertyType properties)
160 {
161 if(!_view->_updateDefaultSettings || _view->_ignoreSettingsChange)
162 return;
163
164 if(_changedView != _view)
165 saveDefaultSettings();
166 _changedView = _view;
167 _changedProperties = static_cast<KrViewProperties::PropertyType>(_changedProperties | properties);
168 _saveDefaultSettingsTimer.start(100);
169 }
170
saveDefaultSettings()171 void KrViewOperator::saveDefaultSettings()
172 {
173 _saveDefaultSettingsTimer.stop();
174 if(_changedView)
175 _changedView->saveDefaultSettings(_changedProperties);
176 _changedProperties = KrViewProperties::NoProperty;
177 _changedView = 0;
178 }
179
180 // ----------------------------- krview
181
182 const KrView::IconSizes KrView::iconSizes;
183
KrView(KrViewInstance & instance,KConfig * cfg)184 KrView::KrView(KrViewInstance &instance, KConfig *cfg)
185 : _config(cfg), _properties(0), _focused(false), _fileIconSize(0),
186 _instance(instance), _files(0), _mainWindow(0), _widget(0), _nameToMakeCurrent(QString()),
187 _previews(0), _updateDefaultSettings(false), _ignoreSettingsChange(false), _count(0),
188 _numDirs(0), _dummyFileItem(0)
189 {
190 }
191
~KrView()192 KrView::~KrView()
193 {
194 _instance.m_objects.removeOne(this);
195 delete _previews;
196 _previews = 0;
197 delete _dummyFileItem;
198 _dummyFileItem = 0;
199 if (_properties)
200 qFatal("A class inheriting KrView didn't delete _properties!");
201 if (_operator)
202 qFatal("A class inheriting KrView didn't delete _operator!");
203 }
204
init(bool enableUpdateDefaultSettings)205 void KrView::init(bool enableUpdateDefaultSettings)
206 {
207 // sanity checks:
208 if (!_widget)
209 qFatal("_widget must be set during construction of KrView inheritors");
210 // ok, continue
211 initProperties();
212 _operator = createOperator();
213 setup();
214 restoreDefaultSettings();
215
216 _updateDefaultSettings = enableUpdateDefaultSettings &&
217 KConfigGroup(_config, "Startup").readEntry("Update Default Panel Settings", _RememberPos);
218
219 _instance.m_objects.append(this);
220 }
221
initProperties()222 void KrView::initProperties()
223 {
224 const KConfigGroup grpInstance(_config, _instance.name());
225 const bool displayIcons = grpInstance.readEntry("With Icons", _WithIcons);
226
227 const KConfigGroup grpSvr(_config, "Look&Feel");
228 const bool numericPermissions = grpSvr.readEntry("Numeric permissions", _NumericPermissions);
229
230 int sortOps = 0;
231 if (grpSvr.readEntry("Show Directories First", true))
232 sortOps |= KrViewProperties::DirsFirst;
233 if(grpSvr.readEntry("Always sort dirs by name", false))
234 sortOps |= KrViewProperties::AlwaysSortDirsByName;
235 if (!grpSvr.readEntry("Case Sensative Sort", _CaseSensativeSort))
236 sortOps |= KrViewProperties::IgnoreCase;
237 if (grpSvr.readEntry("Locale Aware Sort", true))
238 sortOps |= KrViewProperties::LocaleAwareSort;
239 KrViewProperties::SortOptions sortOptions = static_cast<KrViewProperties::SortOptions>(sortOps);
240
241 KrViewProperties::SortMethod sortMethod = static_cast<KrViewProperties::SortMethod>(
242 grpSvr.readEntry("Sort method", (int)_DefaultSortMethod));
243 const bool humanReadableSize = grpSvr.readEntry("Human Readable Size", _HumanReadableSize);
244
245 // see KDE bug #40131
246 const bool localeAwareCompareIsCaseSensitive = QString("a").localeAwareCompare("B") > 0;
247
248 QStringList defaultAtomicExtensions;
249 defaultAtomicExtensions += ".tar.gz";
250 defaultAtomicExtensions += ".tar.bz2";
251 defaultAtomicExtensions += ".tar.lzma";
252 defaultAtomicExtensions += ".tar.xz";
253 defaultAtomicExtensions += ".moc.cpp";
254 QStringList atomicExtensions = grpSvr.readEntry("Atomic Extensions", defaultAtomicExtensions);
255 for (QStringList::iterator i = atomicExtensions.begin(); i != atomicExtensions.end();) {
256 QString & ext = *i;
257 ext = ext.trimmed();
258 if (!ext.length()) {
259 i = atomicExtensions.erase(i);
260 continue;
261 }
262 if (!ext.startsWith('.'))
263 ext.insert(0, '.');
264 ++i;
265 }
266
267 _properties = new KrViewProperties(displayIcons, numericPermissions, sortOptions, sortMethod,
268 humanReadableSize, localeAwareCompareIsCaseSensitive,
269 atomicExtensions);
270 }
271
showPreviews(bool show)272 void KrView::showPreviews(bool show)
273 {
274 if(show) {
275 if(!_previews) {
276 _previews = new KrPreviews(this);
277 _previews->update();
278 }
279 } else {
280 delete _previews;
281 _previews = 0;
282 }
283 redraw();
284 // op()->settingsChanged(KrViewProperties::PropShowPreviews);
285 op()->emitRefreshActions();
286 }
287
updatePreviews()288 void KrView::updatePreviews()
289 {
290 if(_previews)
291 _previews->update();
292 }
293
processIcon(const QPixmap & icon,bool dim,const QColor & dimColor,int dimFactor,bool symlink)294 QPixmap KrView::processIcon(const QPixmap &icon, bool dim, const QColor & dimColor, int dimFactor, bool symlink)
295 {
296 QPixmap pixmap = icon;
297
298 if (symlink) {
299 const QStringList overlays = QStringList() << QString() << "emblem-symbolic-link";
300 Icon::applyOverlays(&pixmap, overlays);
301 }
302
303 if(!dim)
304 return pixmap;
305
306 QImage dimmed = pixmap.toImage();
307
308 QPainter p(&dimmed);
309 p.setCompositionMode(QPainter::CompositionMode_SourceIn);
310 p.fillRect(0, 0, icon.width(), icon.height(), dimColor);
311 p.setCompositionMode(QPainter::CompositionMode_SourceOver);
312 p.setOpacity((qreal)dimFactor / (qreal)100);
313 p.drawPixmap(0, 0, icon.width(), icon.height(), pixmap);
314
315 return QPixmap::fromImage(dimmed, Qt::ColorOnly | Qt::ThresholdDither |
316 Qt::ThresholdAlphaDither | Qt::NoOpaqueDetection );
317 }
318
getIcon(FileItem * fileitem,bool active,int size)319 QPixmap KrView::getIcon(FileItem *fileitem, bool active, int size/*, KRListItem::cmpColor color*/)
320 {
321 // KConfigGroup ag( krConfig, "Advanced");
322 //////////////////////////////
323 QPixmap icon;
324 QString iconName = fileitem->getIcon();
325 QString cacheName;
326
327 if(!size)
328 size = _FilelistIconSize.toInt();
329
330 QColor dimColor;
331 int dimFactor;
332 bool dim = !active && KrColorCache::getColorCache().getDimSettings(dimColor, dimFactor);
333
334 if (iconName.isNull())
335 iconName = "";
336
337 cacheName.append(QString::number(size));
338 if(fileitem->isSymLink())
339 cacheName.append("LINK_");
340 if(dim)
341 cacheName.append("DIM_");
342 cacheName.append(iconName);
343
344 //QPixmapCache::setCacheLimit( ag.readEntry("Icon Cache Size",_IconCacheSize) );
345
346 // first try the cache
347 if (!QPixmapCache::find(cacheName, icon)) {
348 icon = processIcon(Icon(iconName, Icon("unknown")).pixmap(size),
349 dim, dimColor, dimFactor, fileitem->isSymLink());
350 // insert it into the cache
351 QPixmapCache::insert(cacheName, icon);
352 }
353
354 return icon;
355 }
356
getIcon(FileItem * fileitem)357 QPixmap KrView::getIcon(FileItem *fileitem)
358 {
359 if(_previews) {
360 QPixmap icon;
361 if(_previews->getPreview(fileitem, icon, _focused))
362 return icon;
363 }
364 return getIcon(fileitem, _focused, _fileIconSize);
365 }
366
367 /**
368 * this function ADDs a list of selected item names into 'names'.
369 * it assumes the list is ready and doesn't initialize it, or clears it
370 */
getItemsByMask(QString mask,QStringList * names,bool dirs,bool files)371 void KrView::getItemsByMask(QString mask, QStringList* names, bool dirs, bool files)
372 {
373 for (KrViewItem * it = getFirst(); it != 0; it = getNext(it)) {
374 if ((it->name() == "..") || !QDir::match(mask, it->name())) continue;
375 // if we got here, than the item fits the mask
376 if (it->getFileItem()->isDir() && !dirs) continue; // do we need to skip folders?
377 if (!it->getFileItem()->isDir() && !files) continue; // do we need to skip files
378 names->append(it->name());
379 }
380 }
381
382 /**
383 * this function ADDs a list of selected item names into 'names'.
384 * it assumes the list is ready and doesn't initialize it, or clears it
385 */
getSelectedItems(QStringList * names,bool fallbackToFocused)386 void KrView::getSelectedItems(QStringList *names, bool fallbackToFocused)
387 {
388 for (KrViewItem *it = getFirst(); it != 0; it = getNext(it))
389 if (it->isSelected() && (it->name() != ".."))
390 names->append(it->name());
391
392 if (fallbackToFocused) {
393 // if all else fails, take the current item
394 const QString item = getCurrentItem();
395 if (names->empty() && !item.isEmpty() && item != "..") {
396 names->append(item);
397 }
398 }
399 }
400
getSelectedKrViewItems()401 KrViewItemList KrView::getSelectedKrViewItems()
402 {
403 KrViewItemList items;
404 for (KrViewItem * it = getFirst(); it != nullptr; it = getNext(it)) {
405 if (it->isSelected() && (it->name() != "..")) {
406 items.append(it);
407 }
408 }
409
410 // if all else fails, take the current item
411 if (items.empty()) {
412 KrViewItem *currentItem = getCurrentKrViewItem();
413 if (currentItem && !currentItem->isDummy()) {
414 items.append(getCurrentKrViewItem());
415 }
416 }
417
418 return items;
419 }
420
statistics()421 QString KrView::statistics()
422 {
423 KIO::filesize_t size = calcSize();
424 KIO::filesize_t selectedSize = calcSelectedSize();
425 QString tmp;
426 KConfigGroup grp(_config, "Look&Feel");
427 if(grp.readEntry("Show Size In Bytes", false)) {
428 tmp = i18nc("%1=number of selected items,%2=total number of items, \
429 %3=filesize of selected items,%4=filesize in Bytes, \
430 %5=filesize of all items in folder,%6=filesize in Bytes",
431 "%1 out of %2, %3 (%4) out of %5 (%6)",
432 numSelected(), _count, KIO::convertSize(selectedSize),
433 KRpermHandler::parseSize(selectedSize),
434 KIO::convertSize(size),
435 KRpermHandler::parseSize(size));
436 } else {
437 tmp = i18nc("%1=number of selected items,%2=total number of items, \
438 %3=filesize of selected items,%4=filesize of all items in folder",
439 "%1 out of %2, %3 out of %4",
440 numSelected(), _count, KIO::convertSize(selectedSize),
441 KIO::convertSize(size));
442 }
443
444 // notify if we're running a filtered view
445 if (filter() != KrViewProperties::All)
446 tmp = ">> [ " + filterMask().nameFilter() + " ] " + tmp;
447 return tmp;
448 }
449
changeSelection(const KRQuery & filter,bool select)450 bool KrView::changeSelection(const KRQuery& filter, bool select)
451 {
452 KConfigGroup grpSvr(_config, "Look&Feel");
453 return changeSelection(filter, select, grpSvr.readEntry("Mark Dirs", _MarkDirs), true);
454 }
455
changeSelection(const KRQuery & filter,bool select,bool includeDirs,bool makeVisible)456 bool KrView::changeSelection(const KRQuery& filter, bool select, bool includeDirs, bool makeVisible)
457 {
458 if (op()) op()->setMassSelectionUpdate(true);
459
460 KrViewItem *temp = getCurrentKrViewItem();
461 KrViewItem *firstMatch = 0;
462 for (KrViewItem * it = getFirst(); it != 0; it = getNext(it)) {
463 if (it->name() == "..")
464 continue;
465 if (it->getFileItem()->isDir() && !includeDirs)
466 continue;
467
468 FileItem * file = it->getMutableFileItem(); // filter::match calls getMimetype which isn't const
469 if (file == 0)
470 continue;
471
472 if (filter.match(file)) {
473 it->setSelected(select);
474 if (!firstMatch) firstMatch = it;
475 }
476 }
477
478 if (op()) op()->setMassSelectionUpdate(false);
479 updateView();
480 if (ensureVisibilityAfterSelect() && temp != 0) {
481 makeItemVisible(temp);
482 } else if (makeVisible && firstMatch != 0) {
483 // if no selected item is visible...
484 const KrViewItemList selectedItems = getSelectedKrViewItems();
485 bool anyVisible = false;
486 for (KrViewItem *item : selectedItems) {
487 if (isItemVisible(item)) {
488 anyVisible = true;
489 break;
490 }
491 }
492 if (!anyVisible) {
493 // ...scroll to fist selected item
494 makeItemVisible(firstMatch);
495 }
496 }
497 redraw();
498
499 return firstMatch != 0; // return if any file was selected
500 }
501
invertSelection()502 void KrView::invertSelection()
503 {
504 if (op()) op()->setMassSelectionUpdate(true);
505 KConfigGroup grpSvr(_config, "Look&Feel");
506 bool markDirs = grpSvr.readEntry("Mark Dirs", _MarkDirs);
507
508 KrViewItem *temp = getCurrentKrViewItem();
509 for (KrViewItem * it = getFirst(); it != 0; it = getNext(it)) {
510 if (it->name() == "..")
511 continue;
512 if (it->getFileItem()->isDir() && !markDirs && !it->isSelected())
513 continue;
514 it->setSelected(!it->isSelected());
515 }
516 if (op()) op()->setMassSelectionUpdate(false);
517 updateView();
518 if (ensureVisibilityAfterSelect() && temp != 0)
519 makeItemVisible(temp);
520 }
521
firstUnmarkedBelowCurrent(const bool skipCurrent)522 QString KrView::firstUnmarkedBelowCurrent(const bool skipCurrent)
523 {
524 if (getCurrentKrViewItem() == 0)
525 return QString();
526
527 KrViewItem *iterator = getCurrentKrViewItem();
528 if (skipCurrent)
529 iterator = getNext(iterator);
530 while (iterator && iterator->isSelected())
531 iterator = getNext(iterator);
532 if (!iterator) {
533 iterator = getPrev(getCurrentKrViewItem());
534 while (iterator && iterator->isSelected())
535 iterator = getPrev(iterator);
536 }
537 if (!iterator) return QString();
538 return iterator->name();
539 }
540
deleteItem(const QString & name,bool onUpdate)541 void KrView::deleteItem(const QString &name, bool onUpdate)
542 {
543 KrViewItem *viewItem = findItemByName(name);
544 if (!viewItem)
545 return;
546
547 if (_previews && !onUpdate)
548 _previews->deletePreview(viewItem);
549
550 preDeleteItem(viewItem);
551
552 if (viewItem->FILEITEM->isDir()) {
553 --_numDirs;
554 }
555
556 --_count;
557 delete viewItem;
558
559 if (!onUpdate)
560 op()->emitSelectionChanged();
561 }
562
addItem(FileItem * fileItem,bool onUpdate)563 void KrView::addItem(FileItem *fileItem, bool onUpdate)
564 {
565 if (isFiltered(fileItem))
566 return;
567
568 KrViewItem *viewItem = preAddItem(fileItem);
569 if (!viewItem)
570 return; // not added
571
572 if (_previews)
573 _previews->updatePreview(viewItem);
574
575 if (fileItem->isDir())
576 ++_numDirs;
577
578 ++_count;
579
580 if (!onUpdate) {
581 op()->emitSelectionChanged();
582 }
583 }
584
updateItem(FileItem * newFileItem)585 void KrView::updateItem(FileItem *newFileItem)
586 {
587 // file name did not change
588 const QString name = newFileItem->getName();
589
590 // preserve 'current' and 'selection'
591 const bool isCurrent = getCurrentItem() == name;
592 QStringList selectedNames;
593 getSelectedItems(&selectedNames, false);
594 const bool isSelected = selectedNames.contains(name);
595
596 // delete old file item
597 deleteItem(name, true);
598
599 if (!isFiltered(newFileItem)) {
600 addItem(newFileItem, true);
601 }
602
603 if (isCurrent)
604 setCurrentItem(name, false);
605 if (isSelected)
606 setSelected(newFileItem, true);
607
608 op()->emitSelectionChanged();
609 }
610
clear()611 void KrView::clear()
612 {
613 if(_previews)
614 _previews->clear();
615 _count = _numDirs = 0;
616 delete _dummyFileItem;
617 _dummyFileItem = 0;
618 redraw();
619 }
620
handleKeyEvent(QKeyEvent * e)621 bool KrView::handleKeyEvent(QKeyEvent *e)
622 {
623 qDebug() << "key event=" << e;
624 switch (e->key()) {
625 case Qt::Key_Enter :
626 case Qt::Key_Return : {
627 if (e->modifiers() & Qt::ControlModifier)
628 // let the panel handle it
629 e->ignore();
630 else {
631 KrViewItem * i = getCurrentKrViewItem();
632 if (i == 0)
633 return true;
634 QString tmp = i->name();
635 op()->emitExecuted(tmp);
636 }
637 return true;
638 }
639 case Qt::Key_QuoteLeft :
640 // Terminal Emulator bugfix
641 if (e->modifiers() == Qt::ControlModifier) {
642 // let the panel handle it
643 e->ignore();
644 } else {
645 // a normal click - do a lynx-like moving thing
646 // ask krusader to move to the home directory
647 op()->emitGoHome();
648 }
649 return true;
650 case Qt::Key_Delete :
651 // delete/trash the file (delete with alternative mode is a panel action)
652 // allow only no modifier or KeypadModifier (i.e. Del on a Numeric Keypad)
653 if ((e->modifiers() & ~Qt::KeypadModifier) == Qt::NoModifier) {
654 op()->emitDefaultDeleteFiles();
655 }
656 return true;
657 case Qt::Key_Insert: {
658 KrViewItem * i = getCurrentKrViewItem();
659 if (!i)
660 return true;
661 i->setSelected(!i->isSelected());
662 if (KrSelectionMode::getSelectionHandler()->insertMovesDown()) {
663 KrViewItem * next = getNext(i);
664 if (next) {
665 setCurrentKrViewItem(next);
666 makeItemVisible(next);
667 }
668 }
669 op()->emitSelectionChanged();
670 return true;
671 }
672 case Qt::Key_Space: {
673 KrViewItem * viewItem = getCurrentKrViewItem();
674 if (viewItem != 0) {
675 viewItem->setSelected(!viewItem->isSelected());
676
677 if (viewItem->getFileItem()->isDir() &&
678 KrSelectionMode::getSelectionHandler()->spaceCalculatesDiskSpace()) {
679 op()->emitQuickCalcSpace(viewItem);
680 }
681 if (KrSelectionMode::getSelectionHandler()->spaceMovesDown()) {
682 KrViewItem * next = getNext(viewItem);
683 if (next) {
684 setCurrentKrViewItem(next);
685 makeItemVisible(next);
686 }
687 }
688 op()->emitSelectionChanged();
689 }
690 return true;
691 }
692 case Qt::Key_Backspace :
693 // Terminal Emulator bugfix
694 case Qt::Key_Left :
695 if (e->modifiers() == Qt::ControlModifier || e->modifiers() == Qt::ShiftModifier ||
696 e->modifiers() == Qt::AltModifier) {
697 // let the panel handle it
698 e->ignore();
699 } else {
700 // a normal click - do a lynx-like moving thing
701 // ask krusader to move up a directory
702 op()->emitDirUp();
703 }
704 return true; // safety
705 case Qt::Key_Right :
706 if (e->modifiers() == Qt::ControlModifier || e->modifiers() == Qt::ShiftModifier ||
707 e->modifiers() == Qt::AltModifier) {
708 // let the panel handle it
709 e->ignore();
710 } else {
711 // just a normal click - do a lynx-like moving thing
712 KrViewItem *i = getCurrentKrViewItem();
713 if (i)
714 op()->emitGoInside(i->name());
715 }
716 return true;
717 case Qt::Key_Up :
718 if (e->modifiers() == Qt::ControlModifier) {
719 // let the panel handle it - jump to the Location Bar
720 e->ignore();
721 } else {
722 KrViewItem *item = getCurrentKrViewItem();
723 if (item) {
724 if (e->modifiers() == Qt::ShiftModifier) {
725 item->setSelected(!item->isSelected());
726 op()->emitSelectionChanged();
727 }
728 item = getPrev(item);
729 if (item) {
730 setCurrentKrViewItem(item);
731 makeItemVisible(item);
732 }
733 }
734 }
735 return true;
736 case Qt::Key_Down :
737 if (e->modifiers() == Qt::ControlModifier || e->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) {
738 // let the panel handle it - jump to command line
739 e->ignore();
740 } else {
741 KrViewItem *item = getCurrentKrViewItem();
742 if (item) {
743 if (e->modifiers() == Qt::ShiftModifier) {
744 item->setSelected(!item->isSelected());
745 op()->emitSelectionChanged();
746 }
747 item = getNext(item);
748 if (item) {
749 setCurrentKrViewItem(item);
750 makeItemVisible(item);
751 }
752 }
753 }
754 return true;
755 case Qt::Key_Home: {
756 if (e->modifiers() & Qt::ShiftModifier) {
757 bool select = true;
758 KrViewItem *pos = getCurrentKrViewItem();
759 if (pos == 0)
760 pos = getLast();
761 KrViewItem *item = getFirst();
762 op()->setMassSelectionUpdate(true);
763 while (item) {
764 item->setSelected(select);
765 if (item == pos)
766 select = false;
767 item = getNext(item);
768 }
769 op()->setMassSelectionUpdate(false);
770 }
771 KrViewItem * first = getFirst();
772 if (first) {
773 setCurrentKrViewItem(first);
774 makeItemVisible(first);
775 }
776 }
777 return true;
778 case Qt::Key_End:
779 if (e->modifiers() & Qt::ShiftModifier) {
780 bool select = false;
781 KrViewItem *pos = getCurrentKrViewItem();
782 if (pos == 0)
783 pos = getFirst();
784 op()->setMassSelectionUpdate(true);
785 KrViewItem *item = getFirst();
786 while (item) {
787 if (item == pos)
788 select = true;
789 item->setSelected(select);
790 item = getNext(item);
791 }
792 op()->setMassSelectionUpdate(false);
793 } else {
794 KrViewItem *last = getLast();
795 if (last) {
796 setCurrentKrViewItem(last);
797 makeItemVisible(last);
798 }
799 }
800 return true;
801 case Qt::Key_PageDown: {
802 KrViewItem * current = getCurrentKrViewItem();
803 int downStep = itemsPerPage();
804 while (downStep != 0 && current) {
805 KrViewItem * newCurrent = getNext(current);
806 if (newCurrent == 0)
807 break;
808 current = newCurrent;
809 downStep--;
810 }
811 if (current) {
812 setCurrentKrViewItem(current);
813 makeItemVisible(current);
814 }
815 return true;
816 }
817 case Qt::Key_PageUp: {
818 KrViewItem * current = getCurrentKrViewItem();
819 int upStep = itemsPerPage();
820 while (upStep != 0 && current) {
821 KrViewItem * newCurrent = getPrev(current);
822 if (newCurrent == 0)
823 break;
824 current = newCurrent;
825 upStep--;
826 }
827 if (current) {
828 setCurrentKrViewItem(current);
829 makeItemVisible(current);
830 }
831 return true;
832 }
833 case Qt::Key_Escape:
834 e->ignore();
835 return true; // otherwise the selection gets lost??!??
836 // also it is needed by the panel
837 case Qt::Key_A : // mark all
838 if (e->modifiers() == Qt::ControlModifier) {
839 //FIXME: shouldn't there also be a shortcut for unselecting everything ?
840 selectAllIncludingDirs();
841 return true;
842 }
843 #if __GNUC__ >= 7
844 [[gnu::fallthrough]];
845 #endif
846 default:
847 return false;
848 }
849 return false;
850 }
851
zoomIn()852 void KrView::zoomIn()
853 {
854 int idx = iconSizes.indexOf(_fileIconSize);
855 if(idx >= 0 && (idx+1) < iconSizes.count())
856 setFileIconSize(iconSizes[idx+1]);
857 }
858
zoomOut()859 void KrView::zoomOut()
860 {
861 int idx = iconSizes.indexOf(_fileIconSize);
862 if(idx > 0)
863 setFileIconSize(iconSizes[idx-1]);
864 }
865
setFileIconSize(int size)866 void KrView::setFileIconSize(int size)
867 {
868 if(iconSizes.indexOf(size) < 0)
869 return;
870 _fileIconSize = size;
871 if(_previews) {
872 _previews->clear();
873 _previews->update();
874 }
875 redraw();
876 op()->emitRefreshActions();
877 }
878
defaultFileIconSize()879 int KrView::defaultFileIconSize()
880 {
881 KConfigGroup grpSvr(_config, _instance.name());
882 return grpSvr.readEntry("IconSize", _FilelistIconSize).toInt();
883 }
884
saveDefaultSettings(KrViewProperties::PropertyType properties)885 void KrView::saveDefaultSettings(KrViewProperties::PropertyType properties)
886 {
887 saveSettings(KConfigGroup(_config, _instance.name()), properties);
888 op()->emitRefreshActions();
889 }
890
restoreDefaultSettings()891 void KrView::restoreDefaultSettings()
892 {
893 restoreSettings(KConfigGroup(_config, _instance.name()));
894 }
895
saveSettings(KConfigGroup group,KrViewProperties::PropertyType properties)896 void KrView::saveSettings(KConfigGroup group, KrViewProperties::PropertyType properties)
897 {
898 if(properties & KrViewProperties::PropIconSize)
899 group.writeEntry("IconSize", fileIconSize());
900 if(properties & KrViewProperties::PropShowPreviews)
901 group.writeEntry("ShowPreviews", previewsShown());
902 if(properties & KrViewProperties::PropSortMode)
903 saveSortMode(group);
904 if(properties & KrViewProperties::PropFilter) {
905 group.writeEntry("Filter", static_cast<int>(_properties->filter));
906 group.writeEntry("FilterApplysToDirs", _properties->filterApplysToDirs);
907 if(_properties->filterSettings.isValid())
908 _properties->filterSettings.save(KConfigGroup(&group, "FilterSettings"));
909 }
910 }
911
restoreSettings(KConfigGroup group)912 void KrView::restoreSettings(KConfigGroup group)
913 {
914 _ignoreSettingsChange = true;
915 doRestoreSettings(group);
916 _ignoreSettingsChange = false;
917 refresh();
918 }
919
doRestoreSettings(KConfigGroup group)920 void KrView::doRestoreSettings(KConfigGroup group)
921 {
922 restoreSortMode(group);
923 setFileIconSize(group.readEntry("IconSize", defaultFileIconSize()));
924 showPreviews(group.readEntry("ShowPreviews", false));
925 _properties->filter = static_cast<KrViewProperties::FilterSpec>(group.readEntry("Filter",
926 static_cast<int>(KrViewProperties::All)));
927 _properties->filterApplysToDirs = group.readEntry("FilterApplysToDirs", false);
928 _properties->filterSettings.load(KConfigGroup(&group, "FilterSettings"));
929 _properties->filterMask = _properties->filterSettings.toQuery();
930 }
931
applySettingsToOthers()932 void KrView::applySettingsToOthers()
933 {
934 for(int i = 0; i < _instance.m_objects.length(); i++) {
935 KrView *view = _instance.m_objects[i];
936 if(this != view) {
937 view->_ignoreSettingsChange = true;
938 view->copySettingsFrom(this);
939 view->_ignoreSettingsChange = false;
940 }
941 }
942 }
943
sortModeUpdated(KrViewProperties::ColumnType sortColumn,bool descending)944 void KrView::sortModeUpdated(KrViewProperties::ColumnType sortColumn, bool descending)
945 {
946 if(sortColumn == _properties->sortColumn && descending == (bool) (_properties->sortOptions & KrViewProperties::Descending))
947 return;
948
949 int options = _properties->sortOptions;
950 if(descending)
951 options |= KrViewProperties::Descending;
952 else
953 options &= ~KrViewProperties::Descending;
954 _properties->sortColumn = sortColumn;
955 _properties->sortOptions = static_cast<KrViewProperties::SortOptions>(options);
956
957 // op()->settingsChanged(KrViewProperties::PropSortMode);
958 }
959
drawCurrent() const960 bool KrView::drawCurrent() const
961 {
962 return isFocused() ||
963 KConfigGroup(_config, "Look&Feel")
964 .readEntry("Always Show Current Item", _AlwaysShowCurrentItem);
965 }
966
saveSortMode(KConfigGroup & group)967 void KrView::saveSortMode(KConfigGroup &group)
968 {
969 group.writeEntry("Sort Column", static_cast<int>(_properties->sortColumn));
970 group.writeEntry("Descending Sort Order", _properties->sortOptions & KrViewProperties::Descending);
971 }
972
restoreSortMode(KConfigGroup & group)973 void KrView::restoreSortMode(KConfigGroup &group)
974 {
975 int column = group.readEntry("Sort Column", static_cast<int>(KrViewProperties::Name));
976 bool isDescending = group.readEntry("Descending Sort Order", false);
977 setSortMode(static_cast<KrViewProperties::ColumnType>(column), isDescending);
978 }
979
krPermissionText(const FileItem * fileitem)980 QString KrView::krPermissionText(const FileItem * fileitem)
981 {
982 QString tmp;
983 switch (fileitem->isReadable()) {
984 case ALLOWED_PERM: tmp+='r'; break;
985 case UNKNOWN_PERM: tmp+='?'; break;
986 case NO_PERM: tmp+='-'; break;
987 }
988 switch (fileitem->isWriteable()) {
989 case ALLOWED_PERM: tmp+='w'; break;
990 case UNKNOWN_PERM: tmp+='?'; break;
991 case NO_PERM: tmp+='-'; break;
992 }
993 switch (fileitem->isExecutable()) {
994 case ALLOWED_PERM: tmp+='x'; break;
995 case UNKNOWN_PERM: tmp+='?'; break;
996 case NO_PERM: tmp+='-'; break;
997 }
998 return tmp;
999 }
1000
permissionsText(const KrViewProperties * properties,const FileItem * fileItem)1001 QString KrView::permissionsText(const KrViewProperties *properties, const FileItem *fileItem)
1002 {
1003 return properties->numericPermissions ?
1004 QString().asprintf("%.4o", fileItem->getMode() & (S_ISUID | S_ISGID | S_ISVTX |
1005 S_IRWXU | S_IRWXG | S_IRWXO)) :
1006 fileItem->getPerm();
1007 }
1008
sizeText(const KrViewProperties * properties,KIO::filesize_t size)1009 QString KrView::sizeText(const KrViewProperties *properties, KIO::filesize_t size)
1010 {
1011 return properties->humanReadableSize ? KIO::convertSize(size) : KRpermHandler::parseSize(size);
1012 }
1013
mimeTypeText(FileItem * fileItem)1014 QString KrView::mimeTypeText(FileItem *fileItem)
1015 {
1016 QMimeType mt = QMimeDatabase().mimeTypeForName(fileItem->getMime());
1017 return mt.isValid() ? mt.comment() : QString();
1018 }
1019
isFiltered(FileItem * fileitem)1020 bool KrView::isFiltered(FileItem *fileitem)
1021 {
1022 if (_quickFilterMask.isValid() && _quickFilterMask.indexIn(fileitem->getName()) == -1)
1023 return true;
1024
1025 bool filteredOut = false;
1026 bool isDir = fileitem->isDir();
1027 if (!isDir || (isDir && properties()->filterApplysToDirs)) {
1028 switch (properties()->filter) {
1029 case KrViewProperties::All :
1030 break;
1031 case KrViewProperties::Custom :
1032 if (!properties()->filterMask.match(fileitem))
1033 filteredOut = true;
1034 break;
1035 case KrViewProperties::Dirs:
1036 if (!isDir)
1037 filteredOut = true;
1038 break;
1039 case KrViewProperties::Files:
1040 if (isDir)
1041 filteredOut = true;
1042 break;
1043 default:
1044 break;
1045 }
1046 }
1047 return filteredOut;
1048 }
1049
setFiles(DirListerInterface * files)1050 void KrView::setFiles(DirListerInterface *files)
1051 {
1052 if(files != _files) {
1053 clear();
1054 if(_files)
1055 QObject::disconnect(_files, 0, op(), 0);
1056 _files = files;
1057 }
1058
1059 if(!_files)
1060 return;
1061
1062 QObject::disconnect(_files, 0, op(), 0);
1063 QObject::connect(_files, &DirListerInterface::scanDone, op(), &KrViewOperator::startUpdate);
1064 QObject::connect(_files, &DirListerInterface::cleared, op(), &KrViewOperator::cleared);
1065 QObject::connect(_files, &DirListerInterface::addedFileItem, op(), &KrViewOperator::fileAdded);
1066 QObject::connect(_files, &DirListerInterface::updatedFileItem, op(), &KrViewOperator::fileUpdated);
1067 }
1068
setFilter(KrViewProperties::FilterSpec filter,FilterSettings customFilter,bool applyToDirs)1069 void KrView::setFilter(KrViewProperties::FilterSpec filter, FilterSettings customFilter, bool applyToDirs)
1070 {
1071 _properties->filter = filter;
1072 _properties->filterSettings = customFilter;
1073 _properties->filterMask = customFilter.toQuery();
1074 _properties->filterApplysToDirs = applyToDirs;
1075 refresh();
1076 }
1077
setFilter(KrViewProperties::FilterSpec filter)1078 void KrView::setFilter(KrViewProperties::FilterSpec filter)
1079 {
1080
1081 KConfigGroup cfg(_config, "Look&Feel");
1082 bool rememberSettings = cfg.readEntry("FilterDialogRemembersSettings", _FilterDialogRemembersSettings);
1083 bool applyToDirs = rememberSettings ? _properties->filterApplysToDirs : false;
1084 switch (filter) {
1085 case KrViewProperties::All :
1086 break;
1087 case KrViewProperties::Custom :
1088 {
1089 FilterDialog dialog(_widget, i18n("Filter Files"), QStringList(i18n("Apply filter to folders")), false);
1090 dialog.checkExtraOption(i18n("Apply filter to folders"), applyToDirs);
1091 if(rememberSettings)
1092 dialog.applySettings(_properties->filterSettings);
1093 dialog.exec();
1094 FilterSettings s(dialog.getSettings());
1095 if(!s.isValid()) // if the user canceled - quit
1096 return;
1097 _properties->filterSettings = s;
1098 _properties->filterMask = s.toQuery();
1099 applyToDirs = dialog.isExtraOptionChecked(i18n("Apply filter to folders"));
1100 }
1101 break;
1102 default:
1103 return;
1104 }
1105 _properties->filterApplysToDirs = applyToDirs;
1106 _properties->filter = filter;
1107 refresh();
1108 }
1109
customSelection(bool select)1110 void KrView::customSelection(bool select)
1111 {
1112 KConfigGroup grpSvr(_config, "Look&Feel");
1113 bool includeDirs = grpSvr.readEntry("Mark Dirs", _MarkDirs);
1114
1115 FilterDialog dialog(0, i18n("Select Files"), QStringList(i18n("Apply selection to folders")), false);
1116 dialog.checkExtraOption(i18n("Apply selection to folders"), includeDirs);
1117 dialog.exec();
1118 KRQuery query = dialog.getQuery();
1119 // if the user canceled - quit
1120 if (query.isNull())
1121 return;
1122 includeDirs = dialog.isExtraOptionChecked(i18n("Apply selection to folders"));
1123
1124 changeSelection(query, select, includeDirs);
1125 }
1126
refresh()1127 void KrView::refresh()
1128 {
1129 const QString currentItem = !nameToMakeCurrent().isEmpty() ? //
1130 nameToMakeCurrent() :
1131 getCurrentItem();
1132 bool scrollToCurrent = !nameToMakeCurrent().isEmpty() || isItemVisible(getCurrentKrViewItem());
1133 setNameToMakeCurrent(QString());
1134
1135 const QModelIndex currentIndex = getCurrentIndex();
1136 const QList<QUrl> selection = selectedUrls();
1137
1138 clear();
1139
1140 if(!_files)
1141 return;
1142
1143 QList<FileItem*> fileItems;
1144
1145 // if we are not at the root add the ".." entry
1146 if(!_files->isRoot()) {
1147 _dummyFileItem = FileItem::createDummy();
1148 fileItems << _dummyFileItem;
1149 }
1150
1151 foreach(FileItem *fileitem, _files->fileItems()) {
1152 if(!fileitem || isFiltered(fileitem))
1153 continue;
1154 if(fileitem->isDir())
1155 _numDirs++;
1156 _count++;
1157 fileItems << fileitem;
1158 }
1159
1160 populate(fileItems, _dummyFileItem);
1161
1162 if(!selection.isEmpty())
1163 setSelectionUrls(selection);
1164
1165 if (!currentItem.isEmpty()) {
1166 if (currentItem == ".." && _count > 0 && //
1167 !_quickFilterMask.isEmpty() && _quickFilterMask.isValid()) {
1168 // In a filtered view we should never select the dummy entry if
1169 // there are real matches.
1170 setCurrentKrViewItem(getNext(getFirst()));
1171 } else {
1172 setCurrentItem(currentItem, scrollToCurrent, currentIndex);
1173 }
1174 } else {
1175 setCurrentKrViewItem(getFirst());
1176 }
1177
1178 updatePreviews();
1179 redraw();
1180
1181 op()->emitSelectionChanged();
1182 }
1183
setSelected(const FileItem * fileitem,bool select)1184 void KrView::setSelected(const FileItem* fileitem, bool select)
1185 {
1186 if (fileitem == _dummyFileItem)
1187 return;
1188
1189 if (select)
1190 clearSavedSelection();
1191 intSetSelected(fileitem, select);
1192 }
1193
saveSelection()1194 void KrView::saveSelection()
1195 {
1196 _savedSelection = selectedUrls();
1197 op()->emitRefreshActions();
1198 }
1199
restoreSelection()1200 void KrView::restoreSelection()
1201 {
1202 if(canRestoreSelection())
1203 setSelectionUrls(_savedSelection);
1204 }
1205
clearSavedSelection()1206 void KrView::clearSavedSelection() {
1207 _savedSelection.clear();
1208 op()->emitRefreshActions();
1209 }
1210
markSameBaseName()1211 void KrView::markSameBaseName()
1212 {
1213 KrViewItem* item = getCurrentKrViewItem();
1214 if (!item)
1215 return;
1216 KRQuery query(QString("%1.*").arg(item->name(false)));
1217 changeSelection(query, true, false);
1218 }
1219
markSameExtension()1220 void KrView::markSameExtension()
1221 {
1222 KrViewItem* item = getCurrentKrViewItem();
1223 if (!item)
1224 return;
1225 KRQuery query(QString("*.%1").arg(item->extension()));
1226 changeSelection(query, true, false);
1227 }
1228