File indexing completed on 2024-05-12 17:22:03

0001 /*
0002     SPDX-FileCopyrightText: 2000-2002 Shie Erlich <krusader@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2000-2002 Rafi Yanai <krusader@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "krview.h"
0010 
0011 #include "../FileSystem/dirlisterinterface.h"
0012 #include "../FileSystem/fileitem.h"
0013 #include "../FileSystem/krpermhandler.h"
0014 #include "../Filter/filterdialog.h"
0015 #include "../defaults.h"
0016 #include "../filelisticon.h"
0017 #include "../krcolorcache.h"
0018 #include "../krglobal.h"
0019 #include "../krpreviews.h"
0020 #include "../viewactions.h"
0021 #include "krselectionmode.h"
0022 #include "krviewfactory.h"
0023 #include "krviewitem.h"
0024 
0025 // QtCore
0026 #include <QDebug>
0027 #include <QDir>
0028 // QtGui
0029 #include <QBitmap>
0030 #include <QPainter>
0031 #include <QPixmap>
0032 #include <QPixmapCache>
0033 // QtWidgets
0034 #include <QAction>
0035 #include <QInputDialog>
0036 #include <QMimeDatabase>
0037 #include <QMimeType>
0038 #include <qnamespace.h>
0039 
0040 #include <KConfigCore/KSharedConfig>
0041 #include <KI18n/KLocalizedString>
0042 
0043 #define FILEITEM getFileItem()
0044 
0045 KrView *KrViewOperator::_changedView = nullptr;
0046 KrViewProperties::PropertyType KrViewOperator::_changedProperties = KrViewProperties::NoProperty;
0047 
0048 // ----------------------------- operator
0049 KrViewOperator::KrViewOperator(KrView *view, QWidget *widget)
0050     : _view(view)
0051     , _widget(widget)
0052     , _massSelectionUpdate(false)
0053 {
0054     _saveDefaultSettingsTimer.setSingleShot(true);
0055     connect(&_saveDefaultSettingsTimer, &QTimer::timeout, this, &KrViewOperator::saveDefaultSettings);
0056 }
0057 
0058 KrViewOperator::~KrViewOperator()
0059 {
0060     if (_changedView == _view)
0061         saveDefaultSettings();
0062 }
0063 
0064 void KrViewOperator::startUpdate()
0065 {
0066     _view->refresh();
0067 }
0068 
0069 void KrViewOperator::cleared()
0070 {
0071     _view->clear();
0072 }
0073 
0074 void KrViewOperator::fileAdded(FileItem *fileitem)
0075 {
0076     _view->addItem(fileitem);
0077 }
0078 
0079 void KrViewOperator::fileUpdated(FileItem *newFileitem)
0080 {
0081     _view->updateItem(newFileitem);
0082 }
0083 
0084 void KrViewOperator::startDrag()
0085 {
0086     QStringList items;
0087     _view->getSelectedItems(&items);
0088     if (items.empty())
0089         return; // don't drag an empty thing
0090     QPixmap px;
0091     if (items.count() > 1 || _view->getCurrentKrViewItem() == nullptr)
0092         px = FileListIcon("document-multiple").pixmap(); // we are dragging multiple items
0093     else
0094         px = _view->getCurrentKrViewItem()->icon();
0095     emit letsDrag(items, px);
0096 }
0097 
0098 bool KrViewOperator::searchItem(const QString &text, bool caseSensitive, int direction)
0099 {
0100     KrViewItem *item = _view->getCurrentKrViewItem();
0101     if (!item) {
0102         return false;
0103     }
0104     const QRegExp regeEx(text, caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive, QRegExp::Wildcard);
0105     if (!direction) {
0106         if (regeEx.indexIn(item->name()) == 0) {
0107             return true;
0108         }
0109         direction = 1;
0110     }
0111     KrViewItem *startItem = item;
0112     while (true) {
0113         item = (direction > 0) ? _view->getNext(item) : _view->getPrev(item);
0114         if (!item)
0115             item = (direction > 0) ? _view->getFirst() : _view->getLast();
0116         if (regeEx.indexIn(item->name()) == 0) {
0117             _view->setCurrentKrViewItem(item);
0118             _view->makeItemVisible(item);
0119             return true;
0120         }
0121         if (item == startItem) {
0122             return false;
0123         }
0124     }
0125 }
0126 
0127 bool KrViewOperator::filterSearch(const QString &text, bool caseSensitive)
0128 {
0129     _view->_quickFilterMask = QRegExp(text, caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive, QRegExp::Wildcard);
0130     _view->refresh();
0131     return _view->_count || !_view->_files->numFileItems();
0132 }
0133 
0134 void KrViewOperator::setMassSelectionUpdate(bool upd)
0135 {
0136     _massSelectionUpdate = upd;
0137     if (!upd) {
0138         emit selectionChanged();
0139         _view->redraw();
0140     }
0141 }
0142 
0143 void KrViewOperator::settingsChanged(KrViewProperties::PropertyType properties)
0144 {
0145     if (!_view->_updateDefaultSettings || _view->_ignoreSettingsChange)
0146         return;
0147 
0148     if (_changedView != _view)
0149         saveDefaultSettings();
0150     _changedView = _view;
0151     _changedProperties = static_cast<KrViewProperties::PropertyType>(_changedProperties | properties);
0152     _saveDefaultSettingsTimer.start(100);
0153 }
0154 
0155 void KrViewOperator::saveDefaultSettings()
0156 {
0157     _saveDefaultSettingsTimer.stop();
0158     if (_changedView)
0159         _changedView->saveDefaultSettings(_changedProperties);
0160     _changedProperties = KrViewProperties::NoProperty;
0161     _changedView = nullptr;
0162 }
0163 
0164 // ----------------------------- krview
0165 
0166 const KrView::IconSizes KrView::iconSizes;
0167 
0168 KrView::KrView(KrViewInstance &instance, KConfig *cfg)
0169     : _config(cfg)
0170     , _properties(nullptr)
0171     , _focused(false)
0172     , _fileIconSize(0)
0173     , _instance(instance)
0174     , _files(nullptr)
0175     , _mainWindow(nullptr)
0176     , _widget(nullptr)
0177     , _nameToMakeCurrent(QString())
0178     , _previews(nullptr)
0179     , _updateDefaultSettings(false)
0180     , _ignoreSettingsChange(false)
0181     , _count(0)
0182     , _numDirs(0)
0183     , _dummyFileItem(nullptr)
0184 {
0185 }
0186 
0187 KrView::~KrView()
0188 {
0189     _instance.m_objects.removeOne(this);
0190     delete _previews;
0191     _previews = nullptr;
0192     delete _dummyFileItem;
0193     _dummyFileItem = nullptr;
0194     if (_properties)
0195         qFatal("A class inheriting KrView didn't delete _properties!");
0196     if (_operator)
0197         qFatal("A class inheriting KrView didn't delete _operator!");
0198 }
0199 
0200 void KrView::init(bool enableUpdateDefaultSettings)
0201 {
0202     // sanity checks:
0203     if (!_widget)
0204         qFatal("_widget must be set during construction of KrView inheritors");
0205     // ok, continue
0206     initProperties();
0207     _operator = createOperator();
0208     setup();
0209     restoreDefaultSettings();
0210 
0211     _updateDefaultSettings = enableUpdateDefaultSettings && KConfigGroup(_config, "Startup").readEntry("Update Default Panel Settings", _RememberPos);
0212 
0213     _instance.m_objects.append(this);
0214 }
0215 
0216 void KrView::initProperties()
0217 {
0218     const KConfigGroup grpInstance(_config, _instance.name());
0219     const bool displayIcons = grpInstance.readEntry("With Icons", _WithIcons);
0220 
0221     const KConfigGroup grpSvr(_config, "Look&Feel");
0222     const bool numericPermissions = grpSvr.readEntry("Numeric permissions", _NumericPermissions);
0223 
0224     int sortOps = 0;
0225     if (grpSvr.readEntry("Show Directories First", true))
0226         sortOps |= KrViewProperties::DirsFirst;
0227     if (grpSvr.readEntry("Always sort dirs by name", false))
0228         sortOps |= KrViewProperties::AlwaysSortDirsByName;
0229     if (!grpSvr.readEntry("Case Sensative Sort", _CaseSensativeSort))
0230         sortOps |= KrViewProperties::IgnoreCase;
0231     if (grpSvr.readEntry("Locale Aware Sort", true))
0232         sortOps |= KrViewProperties::LocaleAwareSort;
0233     auto sortOptions = static_cast<KrViewProperties::SortOptions>(sortOps);
0234 
0235     KrViewProperties::SortMethod sortMethod = static_cast<KrViewProperties::SortMethod>(grpSvr.readEntry("Sort method", (int)_DefaultSortMethod));
0236     const bool humanReadableSize = grpSvr.readEntry("Human Readable Size", _HumanReadableSize);
0237 
0238     // see KDE bug #40131
0239     const bool localeAwareCompareIsCaseSensitive = QString("a").localeAwareCompare("B") > 0;
0240 
0241     QStringList defaultAtomicExtensions;
0242     defaultAtomicExtensions += ".tar.gz";
0243     defaultAtomicExtensions += ".tar.bz2";
0244     defaultAtomicExtensions += ".tar.lzma";
0245     defaultAtomicExtensions += ".tar.xz";
0246     defaultAtomicExtensions += ".moc.cpp";
0247     QStringList atomicExtensions = grpSvr.readEntry("Atomic Extensions", defaultAtomicExtensions);
0248     for (QStringList::iterator i = atomicExtensions.begin(); i != atomicExtensions.end();) {
0249         QString &ext = *i;
0250         ext = ext.trimmed();
0251         if (!ext.length()) {
0252             i = atomicExtensions.erase(i);
0253             continue;
0254         }
0255         if (!ext.startsWith('.'))
0256             ext.insert(0, '.');
0257         ++i;
0258     }
0259 
0260     _properties =
0261         new KrViewProperties(displayIcons, numericPermissions, sortOptions, sortMethod, humanReadableSize, localeAwareCompareIsCaseSensitive, atomicExtensions);
0262 }
0263 
0264 void KrView::showPreviews(bool show)
0265 {
0266     if (show) {
0267         if (!_previews) {
0268             _previews = new KrPreviews(this);
0269             _previews->update();
0270         }
0271     } else {
0272         delete _previews;
0273         _previews = nullptr;
0274     }
0275     redraw();
0276     //     op()->settingsChanged(KrViewProperties::PropShowPreviews);
0277     op()->emitRefreshActions();
0278 }
0279 
0280 void KrView::updatePreviews()
0281 {
0282     if (_previews)
0283         _previews->update();
0284 }
0285 
0286 QPixmap KrView::processIcon(const QPixmap &icon, bool dim, const QColor &dimColor, int dimFactor, bool symlink)
0287 {
0288     QPixmap pixmap = icon;
0289 
0290     if (symlink) {
0291         const QStringList overlays = QStringList() << QString() << "emblem-symbolic-link";
0292         Icon::applyOverlays(&pixmap, overlays);
0293     }
0294 
0295     if (!dim)
0296         return pixmap;
0297 
0298     QImage dimmed = pixmap.toImage();
0299 
0300     QPainter p(&dimmed);
0301     p.setCompositionMode(QPainter::CompositionMode_SourceIn);
0302     p.fillRect(0, 0, icon.width(), icon.height(), dimColor);
0303     p.setCompositionMode(QPainter::CompositionMode_SourceOver);
0304     p.setOpacity((qreal)dimFactor / (qreal)100);
0305     p.drawPixmap(0, 0, pixmap);
0306 
0307     return QPixmap::fromImage(dimmed, Qt::ColorOnly | Qt::ThresholdDither | Qt::ThresholdAlphaDither | Qt::NoOpaqueDetection);
0308 }
0309 
0310 QPixmap KrView::getIcon(FileItem *fileitem, bool active, int size /*, KRListItem::cmpColor color*/)
0311 {
0312     // KConfigGroup ag( krConfig, "Advanced");
0313     //////////////////////////////
0314     QPixmap icon;
0315     QString iconName = fileitem->getIcon();
0316     QString cacheName;
0317 
0318     if (!size)
0319         size = _FilelistIconSize.toInt();
0320 
0321     QColor dimColor;
0322     int dimFactor;
0323     bool dim = !active && KrColorCache::getColorCache().getDimSettings(dimColor, dimFactor);
0324 
0325     if (iconName.isNull())
0326         iconName = "";
0327 
0328     cacheName.append(QString::number(size));
0329     if (fileitem->isSymLink())
0330         cacheName.append("LINK_");
0331     if (dim)
0332         cacheName.append("DIM_");
0333     cacheName.append(iconName);
0334 
0335     // QPixmapCache::setCacheLimit( ag.readEntry("Icon Cache Size",_IconCacheSize) );
0336 
0337     // first try the cache
0338     if (!QPixmapCache::find(cacheName, &icon)) {
0339         icon = processIcon(Icon(iconName, Icon("unknown")).pixmap(size), dim, dimColor, dimFactor, fileitem->isSymLink());
0340         // insert it into the cache
0341         QPixmapCache::insert(cacheName, icon);
0342     }
0343 
0344     return icon;
0345 }
0346 
0347 QPixmap KrView::getIcon(FileItem *fileitem)
0348 {
0349     if (_previews) {
0350         QPixmap icon;
0351         if (_previews->getPreview(fileitem, icon, _focused))
0352             return icon;
0353     }
0354     return getIcon(fileitem, _focused, _fileIconSize);
0355 }
0356 
0357 /**
0358  * this function ADDs a list of selected item names into 'names'.
0359  * it assumes the list is ready and doesn't initialize it, or clears it
0360  */
0361 void KrView::getItemsByMask(const QString &mask, QStringList *names, bool dirs, bool files)
0362 {
0363     for (KrViewItem *it = getFirst(); it != nullptr; it = getNext(it)) {
0364         if ((it->name() == "..") || !QDir::match(mask, it->name()))
0365             continue;
0366         // if we got here, than the item fits the mask
0367         if (it->getFileItem()->isDir() && !dirs)
0368             continue; // do we need to skip folders?
0369         if (!it->getFileItem()->isDir() && !files)
0370             continue; // do we need to skip files
0371         names->append(it->name());
0372     }
0373 }
0374 
0375 /**
0376  * this function ADDs a list of selected item names into 'names'.
0377  * it assumes the list is ready and doesn't initialize it, or clears it
0378  */
0379 void KrView::getSelectedItems(QStringList *names, bool fallbackToFocused)
0380 {
0381     for (KrViewItem *it = getFirst(); it != nullptr; it = getNext(it))
0382         if (it->isSelected() && (it->name() != ".."))
0383             names->append(it->name());
0384 
0385     if (fallbackToFocused) {
0386         // if all else fails, take the current item
0387         const QString item = getCurrentItem();
0388         if (names->empty() && !item.isEmpty() && item != "..") {
0389             names->append(item);
0390         }
0391     }
0392 }
0393 
0394 KrViewItemList KrView::getSelectedKrViewItems()
0395 {
0396     KrViewItemList items;
0397     for (KrViewItem *it = getFirst(); it != nullptr; it = getNext(it)) {
0398         if (it->isSelected() && (it->name() != "..")) {
0399             items.append(it);
0400         }
0401     }
0402 
0403     // if all else fails, take the current item
0404     if (items.empty()) {
0405         KrViewItem *currentItem = getCurrentKrViewItem();
0406         if (currentItem && !currentItem->isDummy()) {
0407             items.append(getCurrentKrViewItem());
0408         }
0409     }
0410 
0411     return items;
0412 }
0413 
0414 QString KrView::statistics()
0415 {
0416     KIO::filesize_t size = calcSize();
0417     KIO::filesize_t selectedSize = calcSelectedSize();
0418     QString tmp;
0419     KConfigGroup grp(_config, "Look&Feel");
0420     if (grp.readEntry("Show Size In Bytes", false)) {
0421         tmp = i18nc(
0422             "%1=number of selected items,%2=total number of items, \
0423                     %3=filesize of selected items,%4=filesize in Bytes, \
0424                     %5=filesize of all items in folder,%6=filesize in Bytes",
0425             "%1 out of %2, %3 (%4) out of %5 (%6)",
0426             numSelected(),
0427             _count,
0428             KIO::convertSize(selectedSize),
0429             KrPermHandler::parseSize(selectedSize),
0430             KIO::convertSize(size),
0431             KrPermHandler::parseSize(size));
0432     } else {
0433         tmp = i18nc(
0434             "%1=number of selected items,%2=total number of items, \
0435                     %3=filesize of selected items,%4=filesize of all items in folder",
0436             "%1 out of %2, %3 out of %4",
0437             numSelected(),
0438             _count,
0439             KIO::convertSize(selectedSize),
0440             KIO::convertSize(size));
0441     }
0442 
0443     // notify if we're running a filtered view
0444     if (filter() != KrViewProperties::All)
0445         tmp = ">> [ " + filterMask().nameFilter() + " ]  " + tmp;
0446     return tmp;
0447 }
0448 
0449 bool KrView::changeSelection(const KrQuery &filter, bool select)
0450 {
0451     KConfigGroup grpSvr(_config, "Look&Feel");
0452     return changeSelection(filter, select, grpSvr.readEntry("Mark Dirs", _MarkDirs), true);
0453 }
0454 
0455 bool KrView::changeSelection(const KrQuery &filter, bool select, bool includeDirs, bool makeVisible)
0456 {
0457     if (op())
0458         op()->setMassSelectionUpdate(true);
0459 
0460     KrViewItem *temp = getCurrentKrViewItem();
0461     KrViewItem *firstMatch = nullptr;
0462     for (KrViewItem *it = getFirst(); it != nullptr; it = getNext(it)) {
0463         if (it->name() == "..")
0464             continue;
0465         if (it->getFileItem()->isDir() && !includeDirs)
0466             continue;
0467 
0468         FileItem *file = it->getMutableFileItem(); // filter::match calls getMimetype which isn't const
0469         if (file == nullptr)
0470             continue;
0471 
0472         if (filter.match(file)) {
0473             it->setSelected(select);
0474             if (!firstMatch)
0475                 firstMatch = it;
0476         }
0477     }
0478 
0479     if (op())
0480         op()->setMassSelectionUpdate(false);
0481     updateView();
0482     if (ensureVisibilityAfterSelect() && temp != nullptr) {
0483         makeItemVisible(temp);
0484     } else if (makeVisible && firstMatch != nullptr) {
0485         // if no selected item is visible...
0486         const KrViewItemList selectedItems = getSelectedKrViewItems();
0487         bool anyVisible = false;
0488         for (KrViewItem *item : selectedItems) {
0489             if (isItemVisible(item)) {
0490                 anyVisible = true;
0491                 break;
0492             }
0493         }
0494         if (!anyVisible) {
0495             // ...scroll to fist selected item
0496             makeItemVisible(firstMatch);
0497         }
0498     }
0499     redraw();
0500 
0501     return firstMatch != nullptr; // return if any file was selected
0502 }
0503 
0504 void KrView::invertSelection()
0505 {
0506     if (op())
0507         op()->setMassSelectionUpdate(true);
0508     KConfigGroup grpSvr(_config, "Look&Feel");
0509     bool markDirs = grpSvr.readEntry("Mark Dirs", _MarkDirs);
0510 
0511     KrViewItem *temp = getCurrentKrViewItem();
0512     for (KrViewItem *it = getFirst(); it != nullptr; it = getNext(it)) {
0513         if (it->name() == "..")
0514             continue;
0515         if (it->getFileItem()->isDir() && !markDirs && !it->isSelected())
0516             continue;
0517         it->setSelected(!it->isSelected());
0518     }
0519     if (op())
0520         op()->setMassSelectionUpdate(false);
0521     updateView();
0522     if (ensureVisibilityAfterSelect() && temp != nullptr)
0523         makeItemVisible(temp);
0524 }
0525 
0526 QString KrView::firstUnmarkedBelowCurrent(const bool skipCurrent)
0527 {
0528     if (getCurrentKrViewItem() == nullptr)
0529         return QString();
0530 
0531     KrViewItem *iterator = getCurrentKrViewItem();
0532     if (skipCurrent)
0533         iterator = getNext(iterator);
0534     while (iterator && iterator->isSelected())
0535         iterator = getNext(iterator);
0536     if (!iterator) {
0537         iterator = getPrev(getCurrentKrViewItem());
0538         while (iterator && iterator->isSelected())
0539             iterator = getPrev(iterator);
0540     }
0541     if (!iterator)
0542         return QString();
0543     return iterator->name();
0544 }
0545 
0546 void KrView::deleteItem(const QString &name, bool onUpdate)
0547 {
0548     KrViewItem *viewItem = findItemByName(name);
0549     if (!viewItem)
0550         return;
0551 
0552     if (_previews)
0553         _previews->deletePreview(viewItem);
0554 
0555     preDeleteItem(viewItem);
0556 
0557     if (viewItem->FILEITEM->isDir()) {
0558         --_numDirs;
0559     }
0560 
0561     --_count;
0562     delete viewItem;
0563 
0564     if (!onUpdate)
0565         op()->emitSelectionChanged();
0566 }
0567 
0568 void KrView::addItem(FileItem *fileItem, bool onUpdate)
0569 {
0570     if (isFiltered(fileItem))
0571         return;
0572 
0573     KrViewItem *viewItem = preAddItem(fileItem);
0574     if (!viewItem)
0575         return; // not added
0576 
0577     if (_previews)
0578         _previews->updatePreview(viewItem);
0579 
0580     if (fileItem->isDir())
0581         ++_numDirs;
0582 
0583     ++_count;
0584 
0585     if (!onUpdate) {
0586         op()->emitSelectionChanged();
0587     }
0588 }
0589 
0590 void KrView::updateItem(FileItem *newFileItem)
0591 {
0592     // file name did not change
0593     const QString name = newFileItem->getName();
0594 
0595     // preserve 'current' and 'selection'
0596     const bool isCurrent = getCurrentItem() == name;
0597     QStringList selectedNames;
0598     getSelectedItems(&selectedNames, false);
0599     const bool isSelected = selectedNames.contains(name);
0600 
0601     // delete old file item
0602     deleteItem(name, true);
0603 
0604     if (!isFiltered(newFileItem)) {
0605         addItem(newFileItem, true);
0606     }
0607 
0608     if (isCurrent)
0609         setCurrentItem(name, false);
0610     if (isSelected)
0611         setSelected(newFileItem, true);
0612 
0613     op()->emitSelectionChanged();
0614 }
0615 
0616 void KrView::clear()
0617 {
0618     if (_previews)
0619         _previews->clear();
0620     _count = _numDirs = 0;
0621     delete _dummyFileItem;
0622     _dummyFileItem = nullptr;
0623     redraw();
0624 }
0625 
0626 bool KrView::handleKeyEvent(QKeyEvent *e)
0627 {
0628     qDebug() << "key event=" << e;
0629     switch (e->key()) {
0630     case Qt::Key_Enter:
0631     case Qt::Key_Return: {
0632         if (e->modifiers() & Qt::ControlModifier)
0633             // let the panel handle it
0634             e->ignore();
0635         else {
0636             KrViewItem *i = getCurrentKrViewItem();
0637             if (i == nullptr)
0638                 return true;
0639             QString tmp = i->name();
0640             op()->emitExecuted(tmp);
0641         }
0642         return true;
0643     }
0644     case Qt::Key_QuoteLeft:
0645         // Terminal Emulator bugfix
0646         if (e->modifiers() == Qt::ControlModifier) {
0647             // let the panel handle it
0648             e->ignore();
0649         } else {
0650             // a normal click - do a lynx-like moving thing
0651             // ask krusader to move to the home directory
0652             op()->emitGoHome();
0653         }
0654         return true;
0655     case Qt::Key_Delete:
0656         // delete/trash the file (delete with alternative mode is a panel action)
0657         // allow only no modifier or KeypadModifier (i.e. Del on a Numeric Keypad)
0658         if ((e->modifiers() & ~Qt::KeypadModifier) == Qt::NoModifier) {
0659             op()->emitDefaultDeleteFiles();
0660         }
0661         return true;
0662     case Qt::Key_Insert: {
0663         KrViewItem *i = getCurrentKrViewItem();
0664         if (!i)
0665             return true;
0666         i->setSelected(!i->isSelected());
0667         if (KrSelectionMode::getSelectionHandler()->insertMovesDown()) {
0668             KrViewItem *next = getNext(i);
0669             if (next) {
0670                 setCurrentKrViewItem(next);
0671                 makeItemVisible(next);
0672             }
0673         }
0674         op()->emitSelectionChanged();
0675         return true;
0676     }
0677     case Qt::Key_Space: {
0678         KrViewItem *viewItem = getCurrentKrViewItem();
0679         if (viewItem != nullptr) {
0680             viewItem->setSelected(!viewItem->isSelected());
0681 
0682             if (viewItem->getFileItem()->isDir() && KrSelectionMode::getSelectionHandler()->spaceCalculatesDiskSpace()) {
0683                 op()->emitQuickCalcSpace(viewItem);
0684             }
0685             if (KrSelectionMode::getSelectionHandler()->spaceMovesDown()) {
0686                 KrViewItem *next = getNext(viewItem);
0687                 if (next) {
0688                     setCurrentKrViewItem(next);
0689                     makeItemVisible(next);
0690                 }
0691             }
0692             op()->emitSelectionChanged();
0693         }
0694         return true;
0695     }
0696     case Qt::Key_Backspace:
0697         // Terminal Emulator bugfix
0698     case Qt::Key_Left:
0699         if (e->modifiers() == Qt::ControlModifier || e->modifiers() == Qt::ShiftModifier || e->modifiers() == Qt::AltModifier) {
0700             // let the panel handle it
0701             e->ignore();
0702         } else {
0703             // a normal click - do a lynx-like moving thing
0704             // ask krusader to move up a directory
0705             op()->emitDirUp();
0706         }
0707         return true; // safety
0708     case Qt::Key_Right:
0709         if (e->modifiers() == Qt::ControlModifier || e->modifiers() == Qt::ShiftModifier || e->modifiers() == Qt::AltModifier) {
0710             // let the panel handle it
0711             e->ignore();
0712         } else {
0713             // just a normal click - do a lynx-like moving thing
0714             KrViewItem *i = getCurrentKrViewItem();
0715             if (i)
0716                 op()->emitGoInside(i->name());
0717         }
0718         return true;
0719     case Qt::Key_Up:
0720         if (e->modifiers() == Qt::ControlModifier) {
0721             // let the panel handle it - jump to the Location Bar
0722             e->ignore();
0723         } else {
0724             KrViewItem *item = getCurrentKrViewItem();
0725             if (item) {
0726                 if (e->modifiers() == Qt::ShiftModifier) {
0727                     item->setSelected(!item->isSelected());
0728                     op()->emitSelectionChanged();
0729                 }
0730                 item = getPrev(item);
0731                 if (item) {
0732                     setCurrentKrViewItem(item);
0733                     makeItemVisible(item);
0734                 }
0735             }
0736         }
0737         return true;
0738     case Qt::Key_Down:
0739         if (e->modifiers() == Qt::ControlModifier || e->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) {
0740             // let the panel handle it - jump to command line
0741             e->ignore();
0742         } else {
0743             KrViewItem *item = getCurrentKrViewItem();
0744             if (item) {
0745                 if (e->modifiers() == Qt::ShiftModifier) {
0746                     item->setSelected(!item->isSelected());
0747                     op()->emitSelectionChanged();
0748                 }
0749                 item = getNext(item);
0750                 if (item) {
0751                     setCurrentKrViewItem(item);
0752                     makeItemVisible(item);
0753                 }
0754             }
0755         }
0756         return true;
0757     case Qt::Key_Home: {
0758         if (e->modifiers() & Qt::ShiftModifier) {
0759             bool select = true;
0760             KrViewItem *pos = getCurrentKrViewItem();
0761             if (pos == nullptr)
0762                 pos = getLast();
0763             KrViewItem *item = getFirst();
0764             op()->setMassSelectionUpdate(true);
0765             while (item) {
0766                 item->setSelected(select);
0767                 if (item == pos)
0768                     select = false;
0769                 item = getNext(item);
0770             }
0771             op()->setMassSelectionUpdate(false);
0772         }
0773         KrViewItem *first = getFirst();
0774         if (first) {
0775             setCurrentKrViewItem(first);
0776             makeItemVisible(first);
0777         }
0778     }
0779         return true;
0780     case Qt::Key_End:
0781         if (e->modifiers() & Qt::ShiftModifier) {
0782             bool select = false;
0783             KrViewItem *pos = getCurrentKrViewItem();
0784             if (pos == nullptr)
0785                 pos = getFirst();
0786             op()->setMassSelectionUpdate(true);
0787             KrViewItem *item = getFirst();
0788             while (item) {
0789                 if (item == pos)
0790                     select = true;
0791                 item->setSelected(select);
0792                 item = getNext(item);
0793             }
0794             op()->setMassSelectionUpdate(false);
0795         } else {
0796             KrViewItem *last = getLast();
0797             if (last) {
0798                 setCurrentKrViewItem(last);
0799                 makeItemVisible(last);
0800             }
0801         }
0802         return true;
0803     case Qt::Key_PageDown: {
0804         KrViewItem *current = getCurrentKrViewItem();
0805         int downStep = itemsPerPage();
0806         while (downStep != 0 && current) {
0807             KrViewItem *newCurrent = getNext(current);
0808             if (newCurrent == nullptr)
0809                 break;
0810             current = newCurrent;
0811             downStep--;
0812         }
0813         if (current) {
0814             setCurrentKrViewItem(current);
0815             makeItemVisible(current);
0816         }
0817         return true;
0818     }
0819     case Qt::Key_PageUp: {
0820         KrViewItem *current = getCurrentKrViewItem();
0821         int upStep = itemsPerPage();
0822         while (upStep != 0 && current) {
0823             KrViewItem *newCurrent = getPrev(current);
0824             if (newCurrent == nullptr)
0825                 break;
0826             current = newCurrent;
0827             upStep--;
0828         }
0829         if (current) {
0830             setCurrentKrViewItem(current);
0831             makeItemVisible(current);
0832         }
0833         return true;
0834     }
0835     case Qt::Key_Escape:
0836         e->ignore();
0837         return true; // otherwise the selection gets lost??!??
0838                      // also it is needed by the panel
0839     case Qt::Key_A: // mark all
0840         if (e->modifiers() == Qt::ControlModifier) {
0841             // FIXME: shouldn't there also be a shortcut for unselecting everything ?
0842             selectAllIncludingDirs();
0843             return true;
0844         }
0845 #if __GNUC__ >= 7
0846         [[gnu::fallthrough]];
0847 #endif
0848     default:
0849         return false;
0850     }
0851     return false;
0852 }
0853 
0854 void KrView::zoomIn()
0855 {
0856     int idx = iconSizes.indexOf(_fileIconSize);
0857     if (idx >= 0 && (idx + 1) < iconSizes.count())
0858         setFileIconSize(iconSizes[idx + 1]);
0859 }
0860 
0861 void KrView::zoomOut()
0862 {
0863     int idx = iconSizes.indexOf(_fileIconSize);
0864     if (idx > 0)
0865         setFileIconSize(iconSizes[idx - 1]);
0866 }
0867 
0868 void KrView::setFileIconSize(int size)
0869 {
0870     if (iconSizes.indexOf(size) < 0)
0871         return;
0872     _fileIconSize = size;
0873     if (_previews) {
0874         _previews->clear();
0875         _previews->update();
0876     }
0877     redraw();
0878     op()->emitRefreshActions();
0879 }
0880 
0881 int KrView::defaultFileIconSize()
0882 {
0883     KConfigGroup grpSvr(_config, _instance.name());
0884     return grpSvr.readEntry("IconSize", _FilelistIconSize).toInt();
0885 }
0886 
0887 void KrView::saveDefaultSettings(KrViewProperties::PropertyType properties)
0888 {
0889     saveSettings(KConfigGroup(_config, _instance.name()), properties);
0890     op()->emitRefreshActions();
0891 }
0892 
0893 void KrView::restoreDefaultSettings()
0894 {
0895     restoreSettings(KConfigGroup(_config, _instance.name()));
0896 }
0897 
0898 void KrView::saveSettings(KConfigGroup group, KrViewProperties::PropertyType properties)
0899 {
0900     if (properties & KrViewProperties::PropIconSize)
0901         group.writeEntry("IconSize", fileIconSize());
0902     if (properties & KrViewProperties::PropShowPreviews)
0903         group.writeEntry("ShowPreviews", previewsShown());
0904     if (properties & KrViewProperties::PropSortMode)
0905         saveSortMode(group);
0906     if (properties & KrViewProperties::PropFilter) {
0907         group.writeEntry("Filter", static_cast<int>(_properties->filter));
0908         group.writeEntry("FilterApplysToDirs", _properties->filterApplysToDirs);
0909         if (_properties->filterSettings.isValid())
0910             _properties->filterSettings.save(KConfigGroup(&group, "FilterSettings"));
0911     }
0912 }
0913 
0914 void KrView::restoreSettings(const KConfigGroup &group)
0915 {
0916     _ignoreSettingsChange = true;
0917     doRestoreSettings(group);
0918     _ignoreSettingsChange = false;
0919     refresh();
0920 }
0921 
0922 void KrView::doRestoreSettings(KConfigGroup group)
0923 {
0924     restoreSortMode(group);
0925     setFileIconSize(group.readEntry("IconSize", defaultFileIconSize()));
0926     showPreviews(group.readEntry("ShowPreviews", false));
0927     _properties->filter = static_cast<KrViewProperties::FilterSpec>(group.readEntry("Filter", static_cast<int>(KrViewProperties::All)));
0928     _properties->filterApplysToDirs = group.readEntry("FilterApplysToDirs", false);
0929     _properties->filterSettings.load(KConfigGroup(&group, "FilterSettings"));
0930     _properties->filterMask = _properties->filterSettings.toQuery();
0931 }
0932 
0933 void KrView::applySettingsToOthers()
0934 {
0935     for (auto view : _instance.m_objects) {
0936         if (this != view) {
0937             view->_ignoreSettingsChange = true;
0938             view->copySettingsFrom(this);
0939             view->_ignoreSettingsChange = false;
0940         }
0941     }
0942 }
0943 
0944 void KrView::sortModeUpdated(KrViewProperties::ColumnType sortColumn, bool descending)
0945 {
0946     if (sortColumn == _properties->sortColumn && descending == (bool)(_properties->sortOptions & KrViewProperties::Descending))
0947         return;
0948 
0949     int options = _properties->sortOptions;
0950     if (descending)
0951         options |= KrViewProperties::Descending;
0952     else
0953         options &= ~KrViewProperties::Descending;
0954     _properties->sortColumn = sortColumn;
0955     _properties->sortOptions = static_cast<KrViewProperties::SortOptions>(options);
0956 }
0957 
0958 bool KrView::drawCurrent() const
0959 {
0960     return isFocused() || KConfigGroup(_config, "Look&Feel").readEntry("Always Show Current Item", _AlwaysShowCurrentItem);
0961 }
0962 
0963 void KrView::saveSortMode(KConfigGroup &group)
0964 {
0965     group.writeEntry("Sort Column", static_cast<int>(_properties->sortColumn));
0966     group.writeEntry("Descending Sort Order", _properties->sortOptions & KrViewProperties::Descending);
0967 }
0968 
0969 void KrView::restoreSortMode(KConfigGroup &group)
0970 {
0971     int column = group.readEntry("Sort Column", static_cast<int>(KrViewProperties::Name));
0972     bool isDescending = group.readEntry("Descending Sort Order", false);
0973     setSortMode(static_cast<KrViewProperties::ColumnType>(column), isDescending);
0974 }
0975 
0976 QString KrView::krPermissionText(const FileItem *fileitem)
0977 {
0978     QString tmp;
0979     switch (fileitem->isReadable()) {
0980     case ALLOWED_PERM:
0981         tmp += 'r';
0982         break;
0983     case UNKNOWN_PERM:
0984         tmp += '?';
0985         break;
0986     case NO_PERM:
0987         tmp += '-';
0988         break;
0989     }
0990     switch (fileitem->isWriteable()) {
0991     case ALLOWED_PERM:
0992         tmp += 'w';
0993         break;
0994     case UNKNOWN_PERM:
0995         tmp += '?';
0996         break;
0997     case NO_PERM:
0998         tmp += '-';
0999         break;
1000     }
1001     switch (fileitem->isExecutable()) {
1002     case ALLOWED_PERM:
1003         tmp += 'x';
1004         break;
1005     case UNKNOWN_PERM:
1006         tmp += '?';
1007         break;
1008     case NO_PERM:
1009         tmp += '-';
1010         break;
1011     }
1012     return tmp;
1013 }
1014 
1015 QString KrView::permissionsText(const KrViewProperties *properties, const FileItem *fileItem)
1016 {
1017     return properties->numericPermissions ? QString().asprintf("%.4o", fileItem->getMode() & (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO))
1018                                           : fileItem->getPerm();
1019 }
1020 
1021 QString KrView::sizeText(const KrViewProperties *properties, KIO::filesize_t size)
1022 {
1023     return properties->humanReadableSize ? KIO::convertSize(size) : KrPermHandler::parseSize(size);
1024 }
1025 
1026 QString KrView::mimeTypeText(FileItem *fileItem)
1027 {
1028     QMimeType mt = QMimeDatabase().mimeTypeForName(fileItem->getMime());
1029     return mt.isValid() ? mt.comment() : QString();
1030 }
1031 
1032 bool KrView::isFiltered(FileItem *fileitem)
1033 {
1034     if (_quickFilterMask.isValid() && _quickFilterMask.indexIn(fileitem->getName()) == -1)
1035         return true;
1036 
1037     bool filteredOut = false;
1038     bool isDir = fileitem->isDir();
1039     if (!isDir || (isDir && properties()->filterApplysToDirs)) {
1040         switch (properties()->filter) {
1041         case KrViewProperties::All:
1042             break;
1043         case KrViewProperties::Custom:
1044             if (!properties()->filterMask.match(fileitem))
1045                 filteredOut = true;
1046             break;
1047         case KrViewProperties::Dirs:
1048             if (!isDir)
1049                 filteredOut = true;
1050             break;
1051         case KrViewProperties::Files:
1052             if (isDir)
1053                 filteredOut = true;
1054             break;
1055         default:
1056             break;
1057         }
1058     }
1059     return filteredOut;
1060 }
1061 
1062 void KrView::setFiles(DirListerInterface *files)
1063 {
1064     if (files != _files) {
1065         clear();
1066         if (_files)
1067             QObject::disconnect(_files, nullptr, op(), nullptr);
1068         _files = files;
1069     }
1070 
1071     if (!_files)
1072         return;
1073 
1074     QObject::disconnect(_files, nullptr, op(), nullptr);
1075     QObject::connect(_files, &DirListerInterface::scanDone, op(), &KrViewOperator::startUpdate);
1076     QObject::connect(_files, &DirListerInterface::cleared, op(), &KrViewOperator::cleared);
1077     QObject::connect(_files, &DirListerInterface::addedFileItem, op(), &KrViewOperator::fileAdded);
1078     QObject::connect(_files, &DirListerInterface::updatedFileItem, op(), &KrViewOperator::fileUpdated);
1079 }
1080 
1081 void KrView::setFilter(KrViewProperties::FilterSpec filter, const FilterSettings &customFilter, bool applyToDirs)
1082 {
1083     _properties->filter = filter;
1084     _properties->filterSettings = customFilter;
1085     _properties->filterMask = customFilter.toQuery();
1086     _properties->filterApplysToDirs = applyToDirs;
1087     refresh();
1088 }
1089 
1090 void KrView::setFilter(KrViewProperties::FilterSpec filter)
1091 {
1092     KConfigGroup cfg(_config, "Look&Feel");
1093     bool rememberSettings = cfg.readEntry("FilterDialogRemembersSettings", _FilterDialogRemembersSettings);
1094     bool applyToDirs = rememberSettings ? _properties->filterApplysToDirs : false;
1095     switch (filter) {
1096     case KrViewProperties::All:
1097         break;
1098     case KrViewProperties::Custom: {
1099         QString applyFilterToFolders = i18n("Apply filter to folder&s");
1100         // Note: It has the same shortcut as "Apply &selection to folders" has
1101         // in a very similar dialog (which is aimed to select files/folders).
1102         // The "Alt+A" and "Alt+F" shortcuts were already taken
1103 
1104         FilterDialog dialog(_widget, i18n("Filter Files"), QStringList(applyFilterToFolders), false);
1105         dialog.checkExtraOption(applyFilterToFolders, applyToDirs);
1106         if (rememberSettings)
1107             dialog.applySettings(_properties->filterSettings);
1108         dialog.exec();
1109         FilterSettings s(dialog.getSettings());
1110         if (!s.isValid()) // if the user canceled -> quit
1111             return;
1112         _properties->filterSettings = s;
1113         _properties->filterMask = s.toQuery();
1114         applyToDirs = dialog.isExtraOptionChecked(applyFilterToFolders);
1115     } break;
1116     default:
1117         return;
1118     }
1119     _properties->filterApplysToDirs = applyToDirs;
1120     _properties->filter = filter;
1121     refresh();
1122 }
1123 
1124 void KrView::customSelection(bool select)
1125 {
1126     KConfigGroup grpSvr(_config, "Look&Feel");
1127     bool includeDirs = grpSvr.readEntry("Mark Dirs", _MarkDirs);
1128 
1129     QString applySelToFolders = i18n("Apply &selection to folders");
1130     FilterDialog dialog(nullptr, i18n("Select Files"), QStringList(applySelToFolders), false);
1131     dialog.checkExtraOption(applySelToFolders, includeDirs);
1132     dialog.exec();
1133     KrQuery query = dialog.getQuery();
1134     // if the user canceled -> quit
1135     if (query.isNull())
1136         return;
1137     includeDirs = dialog.isExtraOptionChecked(applySelToFolders);
1138 
1139     changeSelection(query, select, includeDirs);
1140 }
1141 
1142 void KrView::refresh()
1143 {
1144     const QString currentItem = !nameToMakeCurrent().isEmpty() ? //
1145         nameToMakeCurrent()
1146                                                                : getCurrentItem();
1147     bool scrollToCurrent = !nameToMakeCurrent().isEmpty() || isItemVisible(getCurrentKrViewItem());
1148     setNameToMakeCurrent(QString());
1149 
1150     const QModelIndex currentIndex = getCurrentIndex();
1151     const QList<QUrl> selection = selectedUrls();
1152 
1153     clear();
1154 
1155     if (!_files)
1156         return;
1157 
1158     QList<FileItem *> fileItems;
1159 
1160     // if we are not at the root add the ".." entry
1161     if (!_files->isRoot()) {
1162         _dummyFileItem = FileItem::createDummy();
1163         fileItems << _dummyFileItem;
1164     }
1165 
1166     foreach (FileItem *fileitem, _files->fileItems()) {
1167         if (!fileitem || isFiltered(fileitem))
1168             continue;
1169         if (fileitem->isDir())
1170             _numDirs++;
1171         _count++;
1172         fileItems << fileitem;
1173     }
1174 
1175     populate(fileItems, _dummyFileItem);
1176 
1177     if (!selection.isEmpty())
1178         setSelectionUrls(selection);
1179 
1180     if (!currentItem.isEmpty()) {
1181         if (currentItem == ".." && _count > 0 && //
1182             !_quickFilterMask.isEmpty() && _quickFilterMask.isValid()) {
1183             // In a filtered view we should never select the dummy entry if
1184             // there are real matches.
1185             setCurrentKrViewItem(getNext(getFirst()));
1186         } else {
1187             setCurrentItem(currentItem, scrollToCurrent, currentIndex);
1188         }
1189     } else {
1190         setCurrentKrViewItem(getFirst());
1191     }
1192 
1193     updatePreviews();
1194     redraw();
1195 
1196     op()->emitSelectionChanged();
1197 }
1198 
1199 void KrView::setSelected(const FileItem *fileitem, bool select)
1200 {
1201     if (fileitem == _dummyFileItem)
1202         return;
1203 
1204     if (select)
1205         clearSavedSelection();
1206     intSetSelected(fileitem, select);
1207 }
1208 
1209 void KrView::saveSelection()
1210 {
1211     _savedSelection = selectedUrls();
1212     op()->emitRefreshActions();
1213 }
1214 
1215 void KrView::restoreSelection()
1216 {
1217     if (canRestoreSelection())
1218         setSelectionUrls(_savedSelection);
1219 }
1220 
1221 void KrView::clearSavedSelection()
1222 {
1223     _savedSelection.clear();
1224     op()->emitRefreshActions();
1225 }
1226 
1227 void KrView::markSameBaseName()
1228 {
1229     KrViewItem *item = getCurrentKrViewItem();
1230     if (!item)
1231         return;
1232     KrQuery query(QString("%1.*").arg(item->name(false)));
1233     changeSelection(query, true, false);
1234 }
1235 
1236 void KrView::markSameExtension()
1237 {
1238     KrViewItem *item = getCurrentKrViewItem();
1239     if (!item)
1240         return;
1241     KrQuery query(QString("*.%1").arg(item->extension()));
1242     changeSelection(query, true, false);
1243 }