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