File indexing completed on 2024-05-05 17:56:53

0001 /*
0002     SPDX-FileCopyrightText: 2004 Csaba Karai <krusader@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "diskusage.h"
0009 
0010 // QtCore
0011 #include <QDebug>
0012 #include <QEvent>
0013 #include <QHash>
0014 #include <QMimeDatabase>
0015 #include <QMimeType>
0016 #include <QPointer>
0017 // QtGui
0018 #include <QCursor>
0019 #include <QKeyEvent>
0020 #include <QPixmap>
0021 #include <QPixmapCache>
0022 #include <QResizeEvent>
0023 // QtWidgets
0024 #include <QApplication>
0025 #include <QFrame>
0026 #include <QGridLayout>
0027 #include <QGroupBox>
0028 #include <QLabel>
0029 #include <QLayout>
0030 #include <QMenu>
0031 #include <QPushButton>
0032 
0033 #include <KConfigCore/KSharedConfig>
0034 #include <KI18n/KLocalizedString>
0035 #include <KIO/DeleteJob>
0036 #include <KIO/Job>
0037 #include <KWidgetsAddons/KMessageBox>
0038 #include <KWidgetsAddons/KStandardGuiItem>
0039 #include <utility>
0040 
0041 #include "../FileSystem/fileitem.h"
0042 #include "../FileSystem/filesystemprovider.h"
0043 #include "../FileSystem/krpermhandler.h"
0044 #include "../Panel/krpanel.h"
0045 #include "../Panel/panelfunc.h"
0046 #include "../compat.h"
0047 #include "../defaults.h"
0048 #include "../filelisticon.h"
0049 #include "../krglobal.h"
0050 #include "dufilelight.h"
0051 #include "dulines.h"
0052 #include "dulistview.h"
0053 #include "filelightParts/Config.h"
0054 
0055 // these are the values that will exist in the menu
0056 #define DELETE_ID 90
0057 #define EXCLUDE_ID 91
0058 #define PARENT_DIR_ID 92
0059 #define NEW_SEARCH_ID 93
0060 #define REFRESH_ID 94
0061 #define STEP_INTO_ID 95
0062 #define INCLUDE_ALL_ID 96
0063 #define VIEW_POPUP_ID 97
0064 #define LINES_VIEW_ID 98
0065 #define DETAILED_VIEW_ID 99
0066 #define FILELIGHT_VIEW_ID 100
0067 #define NEXT_VIEW_ID 101
0068 #define PREVIOUS_VIEW_ID 102
0069 #define ADDITIONAL_POPUP_ID 103
0070 
0071 #define MAX_FILENUM 100
0072 
0073 LoaderWidget::LoaderWidget(QWidget *parent)
0074     : QScrollArea(parent)
0075     , cancelled(false)
0076 {
0077     QPalette palette = viewport()->palette();
0078     palette.setColor(viewport()->backgroundRole(), Qt::white);
0079     viewport()->setPalette(palette);
0080 
0081     widget = new QWidget(parent);
0082 
0083     auto *loaderLayout = new QGridLayout(widget);
0084     loaderLayout->setSpacing(0);
0085     loaderLayout->setContentsMargins(0, 0, 0, 0);
0086 
0087     QFrame *loaderBox = new QFrame(widget);
0088     loaderBox->setFrameShape(QFrame::Box);
0089     loaderBox->setFrameShadow(QFrame::Sunken);
0090     loaderBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0091     loaderBox->setFrameStyle(QFrame::Panel + QFrame::Raised);
0092     loaderBox->setLineWidth(2);
0093 
0094     auto *synchGrid = new QGridLayout(loaderBox);
0095     synchGrid->setSpacing(6);
0096     synchGrid->setContentsMargins(11, 11, 11, 11);
0097 
0098     QLabel *titleLabel = new QLabel(i18n("Loading Usage Information"), loaderBox);
0099     titleLabel->setAlignment(Qt::AlignHCenter);
0100     synchGrid->addWidget(titleLabel, 0, 0, 1, 2);
0101 
0102     QLabel *filesLabel = new QLabel(i18n("Files:"), loaderBox);
0103     filesLabel->setFrameShape(QLabel::StyledPanel);
0104     filesLabel->setFrameShadow(QLabel::Sunken);
0105     synchGrid->addWidget(filesLabel, 1, 0);
0106 
0107     QLabel *directoriesLabel = new QLabel(i18n("Folders:"), loaderBox);
0108     directoriesLabel->setFrameShape(QLabel::StyledPanel);
0109     directoriesLabel->setFrameShadow(QLabel::Sunken);
0110     synchGrid->addWidget(directoriesLabel, 2, 0);
0111 
0112     QLabel *totalSizeLabel = new QLabel(i18n("Total Size:"), loaderBox);
0113     totalSizeLabel->setFrameShape(QLabel::StyledPanel);
0114     totalSizeLabel->setFrameShadow(QLabel::Sunken);
0115     synchGrid->addWidget(totalSizeLabel, 3, 0);
0116 
0117     files = new QLabel(loaderBox);
0118     files->setFrameShape(QLabel::StyledPanel);
0119     files->setFrameShadow(QLabel::Sunken);
0120     files->setAlignment(Qt::AlignRight);
0121     synchGrid->addWidget(files, 1, 1);
0122 
0123     directories = new QLabel(loaderBox);
0124     directories->setFrameShape(QLabel::StyledPanel);
0125     directories->setFrameShadow(QLabel::Sunken);
0126     directories->setAlignment(Qt::AlignRight);
0127     synchGrid->addWidget(directories, 2, 1);
0128 
0129     totalSize = new QLabel(loaderBox);
0130     totalSize->setFrameShape(QLabel::StyledPanel);
0131     totalSize->setFrameShadow(QLabel::Sunken);
0132     totalSize->setAlignment(Qt::AlignRight);
0133     synchGrid->addWidget(totalSize, 3, 1);
0134 
0135     int width;
0136     searchedDirectory = new KSqueezedTextLabel(loaderBox);
0137     searchedDirectory->setFrameShape(QLabel::StyledPanel);
0138     searchedDirectory->setFrameShadow(QLabel::Sunken);
0139     searchedDirectory->setMinimumWidth(width = QFontMetrics(searchedDirectory->font()).horizontalAdvance("W") * 30);
0140     searchedDirectory->setMaximumWidth(width);
0141     synchGrid->addWidget(searchedDirectory, 4, 0, 1, 2);
0142 
0143     QFrame *line = new QFrame(loaderBox);
0144     line->setFrameStyle(QFrame::HLine | QFrame::Sunken);
0145     synchGrid->addWidget(line, 5, 0, 1, 2);
0146 
0147     QWidget *hboxWidget = new QWidget(loaderBox);
0148     auto *hbox = new QHBoxLayout(hboxWidget);
0149 
0150     auto *spacer = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
0151     hbox->addItem(spacer);
0152     auto *cancelButton = new QPushButton(hboxWidget);
0153     KStandardGuiItem::assign(cancelButton, KStandardGuiItem::Cancel);
0154     hbox->addWidget(cancelButton);
0155 
0156     synchGrid->addWidget(hboxWidget, 6, 1);
0157 
0158     loaderLayout->addWidget(loaderBox, 0, 0);
0159 
0160     setWidget(widget);
0161     setAlignment(Qt::AlignCenter);
0162 
0163     connect(cancelButton, &QPushButton::clicked, this, &LoaderWidget::slotCancelled);
0164 }
0165 
0166 void LoaderWidget::init()
0167 {
0168     cancelled = false;
0169 }
0170 
0171 void LoaderWidget::setCurrentURL(const QUrl &url)
0172 {
0173     searchedDirectory->setText(FileSystem::ensureTrailingSlash(url).toDisplayString(QUrl::PreferLocalFile));
0174 }
0175 
0176 void LoaderWidget::setValues(int fileNum, int dirNum, KIO::filesize_t total)
0177 {
0178     files->setText(QString("%1").arg(fileNum));
0179     directories->setText(QString("%1").arg(dirNum));
0180     totalSize->setText(QString("%1").arg(KrPermHandler::parseSize(total).trimmed()));
0181 }
0182 
0183 void LoaderWidget::slotCancelled()
0184 {
0185     cancelled = true;
0186 }
0187 
0188 DiskUsage::DiskUsage(QString confGroup, QWidget *parent)
0189     : QStackedWidget(parent)
0190     , currentDirectory(nullptr)
0191     , root(nullptr)
0192     , configGroup(std::move(confGroup))
0193     , loading(false)
0194     , abortLoading(false)
0195     , clearAfterAbort(false)
0196     , deleting(false)
0197     , searchFileSystem(nullptr)
0198 {
0199     listView = new DUListView(this);
0200     lineView = new DULines(this);
0201     filelightView = new DUFilelight(this);
0202     loaderView = new LoaderWidget(this);
0203 
0204     addWidget(listView);
0205     addWidget(lineView);
0206     addWidget(filelightView);
0207     addWidget(loaderView);
0208 
0209     setView(VIEW_LINES);
0210 
0211     Filelight::Config::read();
0212 
0213     connect(&loadingTimer, &QTimer::timeout, this, &DiskUsage::slotLoadDirectory);
0214 }
0215 
0216 DiskUsage::~DiskUsage()
0217 {
0218     if (listView) // don't remove these lines. The module will crash at exit if removed
0219         delete listView;
0220     if (lineView)
0221         delete lineView;
0222     if (filelightView)
0223         delete filelightView;
0224 
0225     if (root)
0226         delete root;
0227 
0228     QHashIterator<File *, Properties *> lit(propertyMap);
0229     while (lit.hasNext())
0230         delete lit.next().value();
0231 }
0232 
0233 void DiskUsage::load(const QUrl &baseDir)
0234 {
0235     fileNum = dirNum = 0;
0236     currentSize = 0;
0237 
0238     emit status(i18n("Loading the disk usage information..."));
0239 
0240     clear();
0241 
0242     baseURL = baseDir.adjusted(QUrl::StripTrailingSlash);
0243 
0244     root = new Directory(baseURL.fileName(), baseDir.toDisplayString(QUrl::PreferLocalFile));
0245 
0246     directoryStack.clear();
0247     parentStack.clear();
0248 
0249     directoryStack.push("");
0250     parentStack.push(root);
0251 
0252     if (searchFileSystem) {
0253         delete searchFileSystem;
0254         searchFileSystem = nullptr;
0255     }
0256     searchFileSystem = FileSystemProvider::instance().getFilesystem(baseDir);
0257     if (searchFileSystem == nullptr) {
0258         qWarning() << "could not get filesystem for directory=" << baseDir;
0259         loading = abortLoading = clearAfterAbort = false;
0260         emit loadFinished(false);
0261         return;
0262     }
0263 
0264     currentFileItem = nullptr;
0265 
0266     if (!loading) {
0267         viewBeforeLoad = activeView;
0268         setView(VIEW_LOADER);
0269     }
0270 
0271     loading = true;
0272 
0273     loaderView->init();
0274     loaderView->setCurrentURL(baseURL);
0275     loaderView->setValues(fileNum, dirNum, currentSize);
0276 
0277     loadingTimer.setSingleShot(true);
0278     loadingTimer.start(0);
0279 }
0280 
0281 void DiskUsage::slotLoadDirectory()
0282 {
0283     if ((currentFileItem == nullptr && directoryStack.isEmpty()) || loaderView->wasCancelled() || abortLoading) {
0284         if (searchFileSystem)
0285             delete searchFileSystem;
0286 
0287         searchFileSystem = nullptr;
0288         currentFileItem = nullptr;
0289 
0290         setView(viewBeforeLoad);
0291 
0292         if (clearAfterAbort)
0293             clear();
0294         else {
0295             calculateSizes();
0296             changeDirectory(root);
0297         }
0298 
0299         emit loadFinished(!(loaderView->wasCancelled() || abortLoading));
0300 
0301         loading = abortLoading = clearAfterAbort = false;
0302     } else if (loading) {
0303         for (int counter = 0; counter != MAX_FILENUM; counter++) {
0304             if (currentFileItem == nullptr) {
0305                 if (directoryStack.isEmpty())
0306                     break;
0307 
0308                 dirToCheck = directoryStack.pop();
0309                 currentParent = parentStack.pop();
0310 
0311                 contentMap.insert(dirToCheck, currentParent);
0312 
0313                 QUrl url = baseURL;
0314 
0315                 if (!dirToCheck.isEmpty()) {
0316                     url = url.adjusted(QUrl::StripTrailingSlash);
0317                     url.setPath(url.path() + '/' + (dirToCheck));
0318                 }
0319 
0320 #ifdef BSD
0321                 if (url.isLocalFile() && url.path().left(7) == "/procfs")
0322                     break;
0323 #else
0324                 if (url.isLocalFile() && url.path().left(5) == "/proc")
0325                     break;
0326 #endif
0327 
0328                 loaderView->setCurrentURL(url);
0329 
0330                 if (!searchFileSystem->scanDir(url))
0331                     break;
0332                 fileItems = searchFileSystem->fileItems();
0333 
0334                 dirNum++;
0335 
0336                 currentFileItem = fileItems.isEmpty() ? nullptr : fileItems.takeFirst();
0337             } else {
0338                 fileNum++;
0339                 File *newItem = nullptr;
0340 
0341                 QString mime = currentFileItem->getMime(); // fast == not using mimetype magic
0342 
0343                 if (currentFileItem->isDir() && !currentFileItem->isSymLink()) {
0344                     newItem = new Directory(currentParent,
0345                                             currentFileItem->getName(),
0346                                             dirToCheck,
0347                                             currentFileItem->getSize(),
0348                                             currentFileItem->getMode(),
0349                                             currentFileItem->getOwner(),
0350                                             currentFileItem->getGroup(),
0351                                             currentFileItem->getPerm(),
0352                                             currentFileItem->getModificationTime(),
0353                                             currentFileItem->isSymLink(),
0354                                             mime);
0355                     directoryStack.push((dirToCheck.isEmpty() ? "" : dirToCheck + '/') + currentFileItem->getName());
0356                     parentStack.push(dynamic_cast<Directory *>(newItem));
0357                 } else {
0358                     newItem = new File(currentParent,
0359                                        currentFileItem->getName(),
0360                                        dirToCheck,
0361                                        currentFileItem->getSize(),
0362                                        currentFileItem->getMode(),
0363                                        currentFileItem->getOwner(),
0364                                        currentFileItem->getGroup(),
0365                                        currentFileItem->getPerm(),
0366                                        currentFileItem->getModificationTime(),
0367                                        currentFileItem->isSymLink(),
0368                                        mime);
0369                     currentSize += currentFileItem->getSize();
0370                 }
0371                 currentParent->append(newItem);
0372 
0373                 currentFileItem = fileItems.isEmpty() ? nullptr : fileItems.takeFirst();
0374             }
0375         }
0376 
0377         loaderView->setValues(fileNum, dirNum, currentSize);
0378         loadingTimer.setSingleShot(true);
0379         loadingTimer.start(0);
0380     }
0381 }
0382 
0383 void DiskUsage::stopLoad()
0384 {
0385     abortLoading = true;
0386 }
0387 
0388 void DiskUsage::close()
0389 {
0390     if (loading) {
0391         abortLoading = true;
0392         clearAfterAbort = true;
0393     }
0394 }
0395 
0396 void DiskUsage::dirUp()
0397 {
0398     if (currentDirectory != nullptr) {
0399         if (currentDirectory->parent() != nullptr)
0400             changeDirectory(const_cast<Directory *>(currentDirectory->parent()));
0401         else {
0402             QUrl up = KIO::upUrl(baseURL);
0403 
0404             if (KMessageBox::questionYesNo(this,
0405                                            i18n("Stepping into the parent folder requires "
0406                                                 "loading the content of the \"%1\" URL. Do you wish "
0407                                                 "to continue?",
0408                                                 up.toDisplayString(QUrl::PreferLocalFile)),
0409                                            i18n("Krusader::DiskUsage"),
0410                                            KStandardGuiItem::cont(),
0411                                            KStandardGuiItem::cancel(),
0412                                            "DiskUsageLoadParentDir")
0413                 == KMessageBox::Yes)
0414                 load(up);
0415         }
0416     }
0417 }
0418 
0419 Directory *DiskUsage::getDirectory(QString dir)
0420 {
0421     while (dir.endsWith('/'))
0422         dir.truncate(dir.length() - 1);
0423 
0424     if (dir.isEmpty())
0425         return root;
0426 
0427     if (contentMap.find(dir) == contentMap.end())
0428         return nullptr;
0429     return contentMap[dir];
0430 }
0431 
0432 File *DiskUsage::getFile(const QString &path)
0433 {
0434     if (path.isEmpty())
0435         return root;
0436 
0437     QString dir = path;
0438 
0439     int ndx = path.lastIndexOf('/');
0440     QString file = path.mid(ndx + 1);
0441 
0442     if (ndx == -1)
0443         dir = "";
0444     else
0445         dir.truncate(ndx);
0446 
0447     Directory *dirEntry = getDirectory(dir);
0448     if (dirEntry == nullptr)
0449         return nullptr;
0450 
0451     for (Iterator<File> it = dirEntry->iterator(); it != dirEntry->end(); ++it)
0452         if ((*it)->name() == file)
0453             return *it;
0454 
0455     return nullptr;
0456 }
0457 
0458 void DiskUsage::clear()
0459 {
0460     baseURL = QUrl();
0461     emit clearing();
0462 
0463     QHashIterator<File *, Properties *> lit(propertyMap);
0464     while (lit.hasNext())
0465         delete lit.next().value();
0466 
0467     propertyMap.clear();
0468     contentMap.clear();
0469     if (root)
0470         delete root;
0471     root = currentDirectory = nullptr;
0472 }
0473 
0474 int DiskUsage::calculateSizes(Directory *dirEntry, bool emitSig, int depth)
0475 {
0476     int changeNr = 0;
0477 
0478     if (dirEntry == nullptr)
0479         dirEntry = root;
0480 
0481     KIO::filesize_t own = 0, total = 0;
0482 
0483     for (Iterator<File> it = dirEntry->iterator(); it != dirEntry->end(); ++it) {
0484         File *item = *it;
0485 
0486         if (!item->isExcluded()) {
0487             if (item->isDir())
0488                 changeNr += calculateSizes(dynamic_cast<Directory *>(item), emitSig, depth + 1);
0489             else
0490                 own += item->size();
0491 
0492             total += item->size();
0493         }
0494     }
0495 
0496     KIO::filesize_t oldOwn = dirEntry->ownSize(), oldTotal = dirEntry->size();
0497     dirEntry->setSizes(total, own);
0498 
0499     if (dirEntry == currentDirectory)
0500         currentSize = total;
0501 
0502     if (emitSig && (own != oldOwn || total != oldTotal)) {
0503         emit changed(dirEntry);
0504         changeNr++;
0505     }
0506 
0507     if (depth == 0 && changeNr != 0)
0508         emit changeFinished();
0509     return changeNr;
0510 }
0511 
0512 int DiskUsage::exclude(File *file, bool calcPercents, int depth)
0513 {
0514     int changeNr = 0;
0515 
0516     if (!file->isExcluded()) {
0517         file->exclude(true);
0518         emit changed(file);
0519         changeNr++;
0520 
0521         if (file->isDir()) {
0522             auto *dir = dynamic_cast<Directory *>(file);
0523             for (Iterator<File> it = dir->iterator(); it != dir->end(); ++it)
0524                 changeNr += exclude(*it, false, depth + 1);
0525         }
0526     }
0527 
0528     if (calcPercents) {
0529         calculateSizes(root, true);
0530         calculatePercents(true);
0531         createStatus();
0532     }
0533 
0534     if (depth == 0 && changeNr != 0)
0535         emit changeFinished();
0536 
0537     return changeNr;
0538 }
0539 
0540 int DiskUsage::include(Directory *dir, int depth)
0541 {
0542     int changeNr = 0;
0543 
0544     if (dir == nullptr)
0545         return 0;
0546 
0547     for (Iterator<File> it = dir->iterator(); it != dir->end(); ++it) {
0548         File *item = *it;
0549 
0550         if (item->isDir())
0551             changeNr += include(dynamic_cast<Directory *>(item), depth + 1);
0552 
0553         if (item->isExcluded()) {
0554             item->exclude(false);
0555             emit changed(item);
0556             changeNr++;
0557         }
0558     }
0559 
0560     if (depth == 0 && changeNr != 0)
0561         emit changeFinished();
0562 
0563     return changeNr;
0564 }
0565 
0566 void DiskUsage::includeAll()
0567 {
0568     include(root);
0569     calculateSizes(root, true);
0570     calculatePercents(true);
0571     createStatus();
0572 }
0573 
0574 int DiskUsage::del(File *file, bool calcPercents, int depth)
0575 {
0576     int deleteNr = 0;
0577 
0578     if (file == root)
0579         return 0;
0580 
0581     KConfigGroup gg(krConfig, "General");
0582     bool trash = gg.readEntry("Move To Trash", _MoveToTrash);
0583     QUrl url = QUrl::fromLocalFile(file->fullPath());
0584 
0585     if (calcPercents) {
0586         // now ask the user if he want to delete:
0587         KConfigGroup ga(krConfig, "Advanced");
0588         if (ga.readEntry("Confirm Delete", _ConfirmDelete)) {
0589             QString s;
0590             KGuiItem b;
0591             if (trash && url.isLocalFile()) {
0592                 s = i18nc("singularOnly", "Do you really want to move this item to the trash?");
0593                 b = KGuiItem(i18n("&Trash"));
0594             } else {
0595                 s = i18nc("singularOnly", "Do you really want to delete this item?");
0596                 b = KStandardGuiItem::del();
0597             }
0598 
0599             QStringList name;
0600             name.append(file->fullPath());
0601             // show message
0602             // note: i'm using continue and not yes/no because the yes/no has cancel as default button
0603             if (KMessageBox::warningContinueCancelList(krMainWindow, s, name, i18n("Warning"), b) != KMessageBox::Continue)
0604                 return 0;
0605         }
0606 
0607         emit status(i18n("Deleting %1...", file->name()));
0608     }
0609 
0610     if (file == currentDirectory)
0611         dirUp();
0612 
0613     if (file->isDir()) {
0614         auto *dir = dynamic_cast<Directory *>(file);
0615 
0616         Iterator<File> it;
0617         while ((it = dir->iterator()) != dir->end())
0618             deleteNr += del(*it, false, depth + 1);
0619 
0620         QString path;
0621         for (const Directory *d = dynamic_cast<Directory *>(file); d != root && d && d->parent() != nullptr; d = d->parent()) {
0622             if (!path.isEmpty())
0623                 path = '/' + path;
0624 
0625             path = d->name() + path;
0626         }
0627 
0628         contentMap.remove(path);
0629     }
0630 
0631     emit deleted(file);
0632     deleteNr++;
0633 
0634     KIO::Job *job;
0635 
0636     if (trash) {
0637         job = KIO::trash(url);
0638     } else {
0639         job = KIO::del(QUrl::fromLocalFile(file->fullPath()), KIO::HideProgressInfo);
0640     }
0641 
0642     deleting = true; // during qApp->processEvent strange things can occur
0643     grabMouse(); // that's why we disable the mouse and keyboard events
0644     grabKeyboard();
0645 
0646     job->exec();
0647     delete job;
0648 
0649     releaseMouse();
0650     releaseKeyboard();
0651     deleting = false;
0652 
0653     const_cast<Directory *>(file->parent())->remove(file);
0654     delete file;
0655 
0656     if (depth == 0)
0657         createStatus();
0658 
0659     if (calcPercents) {
0660         calculateSizes(root, true);
0661         calculatePercents(true);
0662         createStatus();
0663         emit enteringDirectory(currentDirectory);
0664     }
0665 
0666     if (depth == 0 && deleteNr != 0)
0667         emit deleteFinished();
0668 
0669     return deleteNr;
0670 }
0671 
0672 void *DiskUsage::getProperty(File *item, const QString &key)
0673 {
0674     QHash<File *, Properties *>::iterator itr = propertyMap.find(item);
0675     if (itr == propertyMap.end())
0676         return nullptr;
0677 
0678     QHash<QString, void *>::iterator it = (*itr)->find(key);
0679     if (it == (*itr)->end())
0680         return nullptr;
0681 
0682     return it.value();
0683 }
0684 
0685 void DiskUsage::addProperty(File *item, const QString &key, void *prop)
0686 {
0687     Properties *props;
0688     QHash<File *, Properties *>::iterator itr = propertyMap.find(item);
0689     if (itr == propertyMap.end()) {
0690         props = new Properties();
0691         propertyMap.insert(item, props);
0692     } else
0693         props = *itr;
0694 
0695     props->insert(key, prop);
0696 }
0697 
0698 void DiskUsage::removeProperty(File *item, const QString &key)
0699 {
0700     QHash<File *, Properties *>::iterator itr = propertyMap.find(item);
0701     if (itr == propertyMap.end())
0702         return;
0703     (*itr)->remove(key);
0704     if ((*itr)->count() == 0)
0705         propertyMap.remove(item);
0706 }
0707 
0708 void DiskUsage::createStatus()
0709 {
0710     Directory *dirEntry = currentDirectory;
0711 
0712     if (dirEntry == nullptr)
0713         return;
0714 
0715     QUrl url = baseURL;
0716     if (dirEntry != root) {
0717         url = url.adjusted(QUrl::StripTrailingSlash);
0718         url.setPath(url.path() + '/' + (dirEntry->directory()));
0719     }
0720 
0721     emit status(i18n("Current folder:%1,  Total size:%2,  Own size:%3",
0722                      url.toDisplayString(QUrl::PreferLocalFile | QUrl::StripTrailingSlash),
0723                      ' ' + KrPermHandler::parseSize(dirEntry->size()),
0724                      ' ' + KrPermHandler::parseSize(dirEntry->ownSize())));
0725 }
0726 
0727 void DiskUsage::changeDirectory(Directory *dir)
0728 {
0729     currentDirectory = dir;
0730 
0731     currentSize = dir->size();
0732     calculatePercents(true, dir);
0733 
0734     createStatus();
0735     emit enteringDirectory(dir);
0736 }
0737 
0738 Directory *DiskUsage::getCurrentDir()
0739 {
0740     return currentDirectory;
0741 }
0742 
0743 void DiskUsage::rightClickMenu(const QPoint &pos, File *fileItem, QMenu *addPopup, const QString &addPopupName)
0744 {
0745     QMenu popup(this);
0746 
0747     popup.setTitle(i18n("Disk Usage"));
0748 
0749     QHash<void *, int> actionHash;
0750 
0751     if (fileItem != nullptr) {
0752         QAction *actDelete = popup.addAction(i18n("Delete"));
0753         actionHash[actDelete] = DELETE_ID;
0754         actDelete->setShortcut(Qt::Key_Delete);
0755         QAction *actExclude = popup.addAction(i18n("Exclude"));
0756         actionHash[actExclude] = EXCLUDE_ID;
0757         actExclude->setShortcut(Qt::CTRL + Qt::Key_E);
0758         popup.addSeparator();
0759     }
0760 
0761     QAction *myAct = popup.addAction(i18n("Up one folder"));
0762     actionHash[myAct] = PARENT_DIR_ID;
0763     myAct->setShortcut(Qt::SHIFT + Qt::Key_Up);
0764 
0765     myAct = popup.addAction(i18n("New search"));
0766     actionHash[myAct] = NEW_SEARCH_ID;
0767     myAct->setShortcut(Qt::CTRL + Qt::Key_N);
0768 
0769     myAct = popup.addAction(i18n("Refresh"));
0770     actionHash[myAct] = REFRESH_ID;
0771     myAct->setShortcut(Qt::CTRL + Qt::Key_R);
0772 
0773     myAct = popup.addAction(i18n("Include all"));
0774     actionHash[myAct] = INCLUDE_ALL_ID;
0775     myAct->setShortcut(Qt::CTRL + Qt::Key_I);
0776 
0777     myAct = popup.addAction(i18n("Step into"));
0778     actionHash[myAct] = STEP_INTO_ID;
0779     myAct->setShortcut(Qt::SHIFT + Qt::Key_Down);
0780 
0781     popup.addSeparator();
0782 
0783     if (addPopup != nullptr) {
0784         QAction *menu = popup.addMenu(addPopup);
0785         menu->setText(addPopupName);
0786     }
0787 
0788     QMenu viewPopup;
0789 
0790     myAct = viewPopup.addAction(i18n("Lines"));
0791     actionHash[myAct] = LINES_VIEW_ID;
0792     myAct->setShortcut(Qt::CTRL + Qt::Key_L);
0793 
0794     myAct = viewPopup.addAction(i18n("Detailed"));
0795     actionHash[myAct] = DETAILED_VIEW_ID;
0796     myAct->setShortcut(Qt::CTRL + Qt::Key_D);
0797 
0798     myAct = viewPopup.addAction(i18n("Filelight"));
0799     actionHash[myAct] = FILELIGHT_VIEW_ID;
0800     myAct->setShortcut(Qt::CTRL + Qt::Key_F);
0801 
0802     viewPopup.addSeparator();
0803 
0804     myAct = viewPopup.addAction(i18n("Next"));
0805     actionHash[myAct] = NEXT_VIEW_ID;
0806     myAct->setShortcut(Qt::SHIFT + Qt::Key_Right);
0807 
0808     myAct = viewPopup.addAction(i18n("Previous"));
0809     actionHash[myAct] = PREVIOUS_VIEW_ID;
0810     myAct->setShortcut(Qt::SHIFT + Qt::Key_Left);
0811 
0812     QAction *menu = popup.addMenu(&viewPopup);
0813     menu->setText(i18n("View"));
0814 
0815     QAction *res = popup.exec(pos);
0816 
0817     if (actionHash.contains(res))
0818         executeAction(actionHash[res], fileItem);
0819 }
0820 
0821 void DiskUsage::executeAction(int action, File *fileItem)
0822 {
0823     // check out the user's option
0824     switch (action) {
0825     case DELETE_ID:
0826         if (fileItem)
0827             del(fileItem);
0828         break;
0829     case EXCLUDE_ID:
0830         if (fileItem)
0831             exclude(fileItem);
0832         break;
0833     case PARENT_DIR_ID:
0834         dirUp();
0835         break;
0836     case NEW_SEARCH_ID:
0837         emit newSearch();
0838         break;
0839     case REFRESH_ID:
0840         load(baseURL);
0841         break;
0842     case INCLUDE_ALL_ID:
0843         includeAll();
0844         break;
0845     case STEP_INTO_ID: {
0846         QString uri;
0847         if (fileItem && fileItem->isDir())
0848             uri = fileItem->fullPath();
0849         else
0850             uri = currentDirectory->fullPath();
0851         ACTIVE_FUNC->openUrl(QUrl::fromLocalFile(uri));
0852     } break;
0853     case LINES_VIEW_ID:
0854         setView(VIEW_LINES);
0855         break;
0856     case DETAILED_VIEW_ID:
0857         setView(VIEW_DETAILED);
0858         break;
0859     case FILELIGHT_VIEW_ID:
0860         setView(VIEW_FILELIGHT);
0861         break;
0862     case NEXT_VIEW_ID:
0863         setView((activeView + 1) % 3);
0864         break;
0865     case PREVIOUS_VIEW_ID:
0866         setView((activeView + 2) % 3);
0867         break;
0868     }
0869     //     currentWidget()->setFocus();
0870 }
0871 
0872 void DiskUsage::keyPressEvent(QKeyEvent *e)
0873 {
0874     if (activeView != VIEW_LOADER) {
0875         switch (e->key()) {
0876         case Qt::Key_E:
0877             if (e->modifiers() == Qt::ControlModifier) {
0878                 executeAction(EXCLUDE_ID, getCurrentFile());
0879                 return;
0880             }
0881             break;
0882         case Qt::Key_D:
0883             if (e->modifiers() == Qt::ControlModifier) {
0884                 executeAction(DETAILED_VIEW_ID);
0885                 return;
0886             }
0887             break;
0888         case Qt::Key_F:
0889             if (e->modifiers() == Qt::ControlModifier) {
0890                 executeAction(FILELIGHT_VIEW_ID);
0891                 return;
0892             }
0893             break;
0894         case Qt::Key_I:
0895             if (e->modifiers() == Qt::ControlModifier) {
0896                 executeAction(INCLUDE_ALL_ID);
0897                 return;
0898             }
0899             break;
0900         case Qt::Key_L:
0901             if (e->modifiers() == Qt::ControlModifier) {
0902                 executeAction(LINES_VIEW_ID);
0903                 return;
0904             }
0905             break;
0906         case Qt::Key_N:
0907             if (e->modifiers() == Qt::ControlModifier) {
0908                 executeAction(NEW_SEARCH_ID);
0909                 return;
0910             }
0911             break;
0912         case Qt::Key_R:
0913             if (e->modifiers() == Qt::ControlModifier) {
0914                 executeAction(REFRESH_ID);
0915                 return;
0916             }
0917             break;
0918         case Qt::Key_Up:
0919             if (e->modifiers() == Qt::ShiftModifier) {
0920                 executeAction(PARENT_DIR_ID);
0921                 return;
0922             }
0923             break;
0924         case Qt::Key_Down:
0925             if (e->modifiers() == Qt::ShiftModifier) {
0926                 executeAction(STEP_INTO_ID);
0927                 return;
0928             }
0929             break;
0930         case Qt::Key_Left:
0931             if (e->modifiers() == Qt::ShiftModifier) {
0932                 executeAction(PREVIOUS_VIEW_ID);
0933                 return;
0934             }
0935             break;
0936         case Qt::Key_Right:
0937             if (e->modifiers() == Qt::ShiftModifier) {
0938                 executeAction(NEXT_VIEW_ID);
0939                 return;
0940             }
0941             break;
0942         case Qt::Key_Delete:
0943             if (!e->modifiers()) {
0944                 executeAction(DELETE_ID, getCurrentFile());
0945                 return;
0946             }
0947             break;
0948         case Qt::Key_Plus:
0949             if (activeView == VIEW_FILELIGHT) {
0950                 filelightView->zoomIn();
0951                 return;
0952             }
0953             break;
0954         case Qt::Key_Minus:
0955             if (activeView == VIEW_FILELIGHT) {
0956                 filelightView->zoomOut();
0957                 return;
0958             }
0959             break;
0960         }
0961     }
0962     QStackedWidget::keyPressEvent(e);
0963 }
0964 
0965 QPixmap DiskUsage::getIcon(const QString &mime)
0966 {
0967     QPixmap icon;
0968 
0969     if (!QPixmapCache::find(mime, &icon)) {
0970         // get the icon.
0971         if (mime == "Broken Link !") // FIXME: this doesn't work anymore - the reported mimetype for a broken link is now "unknown"
0972             icon = FileListIcon("file-broken").pixmap();
0973         else {
0974             QMimeDatabase db;
0975             QMimeType mt = db.mimeTypeForName(mime);
0976             if (mt.isValid())
0977                 icon = FileListIcon(mt.iconName()).pixmap();
0978             else
0979                 icon = FileListIcon("file-broken").pixmap();
0980         }
0981 
0982         // insert it into the cache
0983         QPixmapCache::insert(mime, icon);
0984     }
0985     return icon;
0986 }
0987 
0988 int DiskUsage::calculatePercents(bool emitSig, Directory *dirEntry, int depth)
0989 {
0990     int changeNr = 0;
0991 
0992     if (dirEntry == nullptr)
0993         dirEntry = root;
0994 
0995     for (Iterator<File> it = dirEntry->iterator(); it != dirEntry->end(); ++it) {
0996         File *item = *it;
0997 
0998         if (!item->isExcluded()) {
0999             int newPerc;
1000 
1001             if (dirEntry->size() == 0 && item->size() == 0)
1002                 newPerc = 0;
1003             else if (dirEntry->size() == 0)
1004                 newPerc = -1;
1005             else
1006                 newPerc = (int)((double)item->size() / (double)currentSize * 10000. + 0.5);
1007 
1008             int oldPerc = item->intPercent();
1009             item->setPercent(newPerc);
1010 
1011             if (emitSig && newPerc != oldPerc) {
1012                 emit changed(item);
1013                 changeNr++;
1014             }
1015 
1016             if (item->isDir())
1017                 changeNr += calculatePercents(emitSig, dynamic_cast<Directory *>(item), depth + 1);
1018         }
1019     }
1020 
1021     if (depth == 0 && changeNr != 0)
1022         emit changeFinished();
1023     return changeNr;
1024 }
1025 
1026 QString DiskUsage::getToolTip(File *item)
1027 {
1028     QMimeDatabase db;
1029     QMimeType mt = db.mimeTypeForName(item->mime());
1030     QString mime;
1031     if (mt.isValid())
1032         mime = mt.comment();
1033 
1034     time_t tma = item->time();
1035     struct tm *t = localtime((time_t *)&tma);
1036     QDateTime tmp(QDate(t->tm_year + 1900, t->tm_mon + 1, t->tm_mday), QTime(t->tm_hour, t->tm_min));
1037     QString date = QLocale().toString(tmp, QLocale::ShortFormat);
1038 
1039     QString str = "<qt><h5><table><tr><td>" + i18n("Name:") + "</td><td>" + item->name() + "</td></tr>" + "<tr><td>" + i18n("Type:") + "</td><td>" + mime
1040         + "</td></tr>" + "<tr><td>" + i18n("Size:") + "</td><td>" + KrPermHandler::parseSize(item->size()) + "</td></tr>";
1041 
1042     if (item->isDir())
1043         str += "<tr><td>" + i18n("Own size:") + "</td><td>" + KrPermHandler::parseSize(item->ownSize()) + "</td></tr>";
1044 
1045     str += "<tr><td>" + i18n("Last modified:") + "</td><td>" + date + "</td></tr>" + "<tr><td>" + i18n("Permissions:") + "</td><td>" + item->perm()
1046         + "</td></tr>" + "<tr><td>" + i18n("Owner:") + "</td><td>" + item->owner() + " - " + item->group() + "</td></tr>" + "</table></h5></qt>";
1047     str.replace(' ', "&nbsp;");
1048     return str;
1049 }
1050 
1051 void DiskUsage::setView(int view)
1052 {
1053     switch (view) {
1054     case VIEW_LINES:
1055         setCurrentWidget(lineView);
1056         break;
1057     case VIEW_DETAILED:
1058         setCurrentWidget(listView);
1059         break;
1060     case VIEW_FILELIGHT:
1061         setCurrentWidget(filelightView);
1062         break;
1063     case VIEW_LOADER:
1064         setCurrentWidget(loaderView);
1065         break;
1066     }
1067 
1068     //     currentWidget()->setFocus();
1069     emit viewChanged(activeView = view);
1070 }
1071 
1072 File *DiskUsage::getCurrentFile()
1073 {
1074     File *file = nullptr;
1075 
1076     switch (activeView) {
1077     case VIEW_LINES:
1078         file = lineView->getCurrentFile();
1079         break;
1080     case VIEW_DETAILED:
1081         file = listView->getCurrentFile();
1082         break;
1083     case VIEW_FILELIGHT:
1084         file = filelightView->getCurrentFile();
1085         break;
1086     }
1087 
1088     return file;
1089 }
1090 
1091 bool DiskUsage::event(QEvent *e)
1092 {
1093     if (deleting) { // if we are deleting, disable the mouse and
1094         switch (e->type()) { // keyboard events
1095         case QEvent::MouseButtonPress:
1096         case QEvent::MouseButtonRelease:
1097         case QEvent::MouseButtonDblClick:
1098         case QEvent::MouseMove:
1099         case QEvent::KeyPress:
1100         case QEvent::KeyRelease:
1101             return true;
1102         default:
1103             break;
1104         }
1105     }
1106 
1107     if (e->type() == QEvent::ShortcutOverride) {
1108         auto *ke = dynamic_cast<QKeyEvent *>(e);
1109 
1110         if (ke->modifiers() == Qt::NoModifier || ke->modifiers() == Qt::KeypadModifier) {
1111             switch (ke->key()) {
1112             case Qt::Key_Delete:
1113             case Qt::Key_Plus:
1114             case Qt::Key_Minus:
1115                 ke->accept();
1116                 break;
1117             }
1118         } else if (ke->modifiers() == Qt::ShiftModifier) {
1119             switch (ke->key()) {
1120             case Qt::Key_Left:
1121             case Qt::Key_Right:
1122             case Qt::Key_Up:
1123             case Qt::Key_Down:
1124                 ke->accept();
1125                 break;
1126             }
1127         } else if (ke->modifiers() & Qt::ControlModifier) {
1128             switch (ke->key()) {
1129             case Qt::Key_D:
1130             case Qt::Key_E:
1131             case Qt::Key_F:
1132             case Qt::Key_I:
1133             case Qt::Key_L:
1134             case Qt::Key_N:
1135             case Qt::Key_R:
1136                 ke->accept();
1137                 break;
1138             }
1139         }
1140     }
1141     return QStackedWidget::event(e);
1142 }