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

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 "dulines.h"
0009 
0010 #include "../FileSystem/krpermhandler.h"
0011 #include "../icon.h"
0012 #include "../krglobal.h"
0013 
0014 // QtCore
0015 #include <QTimer>
0016 // QtGui
0017 #include <QFontMetrics>
0018 #include <QKeyEvent>
0019 #include <QMouseEvent>
0020 #include <QPainter>
0021 #include <QPen>
0022 #include <QPixmap>
0023 // QtWidgets
0024 #include <QApplication>
0025 #include <QHeaderView>
0026 #include <QItemDelegate>
0027 #include <QMenu>
0028 #include <QToolTip>
0029 
0030 #include <KConfigCore/KSharedConfig>
0031 #include <KI18n/KLocalizedString>
0032 
0033 #include "../compat.h"
0034 
0035 class DULinesItemDelegate : public QItemDelegate
0036 {
0037 public:
0038     explicit DULinesItemDelegate(QObject *parent = nullptr)
0039         : QItemDelegate(parent)
0040     {
0041     }
0042 
0043     void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
0044     {
0045         QItemDelegate::paint(painter, option, index);
0046 
0047         QVariant value = index.data(Qt::UserRole);
0048         if (value.isValid()) {
0049             QString text = value.toString();
0050 
0051             value = index.data(Qt::DisplayRole);
0052             QString display;
0053             if (value.isValid())
0054                 display = value.toString();
0055 
0056             QSize iconSize;
0057             value = index.data(Qt::DecorationRole);
0058             if (value.isValid())
0059                 iconSize = qvariant_cast<QIcon>(value).actualSize(option.decorationSize);
0060 
0061             painter->save();
0062             painter->setClipRect(option.rect);
0063 
0064             QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled;
0065             if (cg == QPalette::Normal && !(option.state & QStyle::State_Active))
0066                 cg = QPalette::Inactive;
0067             if (option.state & QStyle::State_Selected) {
0068                 painter->setPen(option.palette.color(cg, QPalette::HighlightedText));
0069             } else {
0070                 painter->setPen(option.palette.color(cg, QPalette::Text));
0071             }
0072 
0073             QFont fnt = option.font;
0074             fnt.setItalic(true);
0075             painter->setFont(fnt);
0076 
0077             QFontMetrics fm(fnt);
0078             QString renderedText = text;
0079 
0080             int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin);
0081             int pos = 3 * textMargin + option.fontMetrics.horizontalAdvance(display) + iconSize.width();
0082 
0083             bool truncd = false;
0084 
0085             QRect rct = option.rect;
0086             if (rct.width() > pos) {
0087                 rct.setX(rct.x() + pos);
0088 
0089                 if (fm.horizontalAdvance(renderedText) > rct.width()) {
0090                     truncd = true;
0091 
0092                     int points = fm.horizontalAdvance("...");
0093 
0094                     while (!renderedText.isEmpty() && (fm.horizontalAdvance(renderedText) + points > rct.width()))
0095                         renderedText.truncate(renderedText.length() - 1);
0096 
0097                     renderedText += "...";
0098                 }
0099 
0100                 painter->drawText(rct, Qt::AlignLeft, renderedText);
0101             } else
0102                 truncd = true;
0103 
0104             if (truncd)
0105                 const_cast<QAbstractItemModel *>(index.model())->setData(index, QVariant(display + "  " + text), Qt::ToolTipRole);
0106             else
0107                 const_cast<QAbstractItemModel *>(index.model())->setData(index, QVariant(), Qt::ToolTipRole);
0108 
0109             painter->restore();
0110         }
0111     }
0112 };
0113 
0114 class DULinesItem : public QTreeWidgetItem
0115 {
0116 public:
0117     DULinesItem(File *fileItem, QTreeWidget *parent, const QString &label1, const QString &label2, const QString &label3)
0118         : QTreeWidgetItem(parent)
0119         , file(fileItem)
0120     {
0121         setText(0, label1);
0122         setText(1, label2);
0123         setText(2, label3);
0124 
0125         setTextAlignment(1, Qt::AlignRight);
0126     }
0127     DULinesItem(File *fileItem, QTreeWidget *parent, QTreeWidgetItem *after, const QString &label1, const QString &label2, const QString &label3)
0128         : QTreeWidgetItem(parent, after)
0129         , file(fileItem)
0130     {
0131         setText(0, label1);
0132         setText(1, label2);
0133         setText(2, label3);
0134 
0135         setTextAlignment(1, Qt::AlignRight);
0136     }
0137 
0138     bool operator<(const QTreeWidgetItem &other) const override
0139     {
0140         int column = treeWidget() ? treeWidget()->sortColumn() : 0;
0141 
0142         if (text(0) == "..")
0143             return true;
0144 
0145         const auto *compWith = dynamic_cast<const DULinesItem *>(&other);
0146         if (compWith == nullptr)
0147             return false;
0148 
0149         switch (column) {
0150         case 0:
0151         case 1:
0152             return file->size() > compWith->file->size();
0153         default:
0154             return text(column) < other.text(column);
0155         }
0156     }
0157 
0158     inline File *getFile()
0159     {
0160         return file;
0161     }
0162 
0163 private:
0164     File *file;
0165 };
0166 
0167 DULines::DULines(DiskUsage *usage)
0168     : KrTreeWidget(usage)
0169     , diskUsage(usage)
0170     , refreshNeeded(false)
0171     , started(false)
0172 {
0173     setItemDelegate(itemDelegate = new DULinesItemDelegate());
0174 
0175     setAllColumnsShowFocus(true);
0176     setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0177     setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0178     setIndentation(10);
0179 
0180     int defaultSize = QFontMetrics(font()).horizontalAdvance("W");
0181 
0182     QStringList labels;
0183     labels << i18n("Line View");
0184     labels << i18n("Percent");
0185     labels << i18n("Name");
0186     setHeaderLabels(labels);
0187 
0188     header()->setSectionResizeMode(QHeaderView::Interactive);
0189 
0190     KConfigGroup group(krConfig, diskUsage->getConfigGroup());
0191 
0192     showFileSize = group.readEntry("L Show File Size", true);
0193 
0194     if (group.hasKey("L State"))
0195         header()->restoreState(group.readEntry("L State", QByteArray()));
0196     else {
0197         setColumnWidth(0, defaultSize * 20);
0198         setColumnWidth(1, defaultSize * 6);
0199         setColumnWidth(2, defaultSize * 20);
0200     }
0201 
0202     setStretchingColumn(0);
0203 
0204     header()->setSortIndicatorShown(true);
0205     sortItems(1, Qt::AscendingOrder);
0206 
0207     connect(diskUsage, &DiskUsage::enteringDirectory, this, &DULines::slotDirChanged);
0208     connect(diskUsage, &DiskUsage::clearing, this, &DULines::clear);
0209 
0210     connect(header(), &QHeaderView::sectionResized, this, &DULines::sectionResized);
0211 
0212     connect(this, &DULines::itemRightClicked, this, &DULines::slotRightClicked);
0213     connect(diskUsage, &DiskUsage::changed, this, &DULines::slotChanged);
0214     connect(diskUsage, &DiskUsage::deleted, this, &DULines::slotDeleted);
0215 
0216     started = true;
0217 }
0218 
0219 DULines::~DULines()
0220 {
0221     KConfigGroup group(krConfig, diskUsage->getConfigGroup());
0222     group.writeEntry("L State", header()->saveState());
0223 
0224     delete itemDelegate;
0225 }
0226 
0227 bool DULines::event(QEvent *event)
0228 {
0229     switch (event->type()) {
0230     case QEvent::ToolTip: {
0231         auto *he = dynamic_cast<QHelpEvent *>(event);
0232 
0233         if (viewport()) {
0234             QPoint pos = viewport()->mapFromGlobal(he->globalPos());
0235 
0236             QTreeWidgetItem *item = itemAt(pos);
0237 
0238             int column = columnAt(pos.x());
0239 
0240             if (item && column == 1) {
0241                 File *fileItem = (dynamic_cast<DULinesItem *>(item))->getFile();
0242                 QToolTip::showText(he->globalPos(), diskUsage->getToolTip(fileItem), this);
0243                 return true;
0244             }
0245         }
0246     } break;
0247     default:
0248         break;
0249     }
0250     return KrTreeWidget::event(event);
0251 }
0252 
0253 void DULines::slotDirChanged(Directory *dirEntry)
0254 {
0255     clear();
0256 
0257     QTreeWidgetItem *lastItem = nullptr;
0258 
0259     if (!(dirEntry->parent() == nullptr)) {
0260         lastItem = new QTreeWidgetItem(this);
0261         lastItem->setText(0, "..");
0262         lastItem->setIcon(0, Icon("go-up"));
0263         lastItem->setFlags(lastItem->flags() & (~Qt::ItemIsSelectable));
0264     }
0265 
0266     int maxPercent = -1;
0267     for (Iterator<File> it = dirEntry->iterator(); it != dirEntry->end(); ++it) {
0268         File *item = *it;
0269         if (!item->isExcluded() && item->intPercent() > maxPercent)
0270             maxPercent = item->intPercent();
0271     }
0272 
0273     for (Iterator<File> it = dirEntry->iterator(); it != dirEntry->end(); ++it) {
0274         File *item = *it;
0275 
0276         QString fileName = item->name();
0277 
0278         if (lastItem == nullptr)
0279             lastItem = new DULinesItem(item, this, "", item->percent() + "  ", fileName);
0280         else
0281             lastItem = new DULinesItem(item, this, lastItem, "", item->percent() + "  ", fileName);
0282 
0283         if (item->isExcluded())
0284             lastItem->setHidden(true);
0285 
0286         int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
0287 
0288         lastItem->setIcon(2, diskUsage->getIcon(item->mime()));
0289         lastItem->setData(0, Qt::DecorationRole, createPixmap(item->intPercent(), maxPercent, header()->sectionSize(0) - 2 * textMargin));
0290 
0291         if (showFileSize)
0292             lastItem->setData(2, Qt::UserRole, "  [" + KIO::convertSize(item->size()) + ']');
0293 
0294         QSize size = lastItem->sizeHint(0);
0295         size.setWidth(16);
0296         lastItem->setSizeHint(0, size);
0297     }
0298 
0299     if (topLevelItemCount() > 0) {
0300         setCurrentItem(topLevelItem(0));
0301     }
0302 }
0303 
0304 QPixmap DULines::createPixmap(int percent, int maxPercent, int maxWidth)
0305 {
0306     if (percent < 0 || percent > maxPercent || maxWidth < 2 || maxPercent == 0)
0307         return QPixmap();
0308     maxWidth -= 2;
0309 
0310     int actualWidth = maxWidth * percent / maxPercent;
0311     if (actualWidth == 0)
0312         return QPixmap();
0313 
0314     QPen pen;
0315     pen.setColor(Qt::black);
0316     QPainter painter;
0317 
0318     int size = QFontMetrics(font()).height() - 2;
0319     QRect rect(0, 0, actualWidth, size);
0320     QRect frameRect(0, 0, actualWidth - 1, size - 1);
0321     QPixmap pixmap(rect.width(), rect.height());
0322 
0323     painter.begin(&pixmap);
0324     painter.setPen(pen);
0325 
0326     for (int i = 1; i < actualWidth - 1; i++) {
0327         int color = (511 * i / (maxWidth - 1));
0328         if (color < 256)
0329             pen.setColor(QColor(255 - color, 255, 0));
0330         else
0331             pen.setColor(QColor(color - 256, 511 - color, 0));
0332 
0333         painter.setPen(pen);
0334         painter.drawLine(i, 1, i, size - 1);
0335     }
0336 
0337     pen.setColor(Qt::black);
0338     painter.setPen(pen);
0339 
0340     if (actualWidth != 1)
0341         painter.drawRect(frameRect);
0342     else
0343         painter.drawLine(0, 0, 0, size);
0344 
0345     painter.end();
0346     pixmap.detach();
0347     return pixmap;
0348 }
0349 
0350 void DULines::resizeEvent(QResizeEvent *re)
0351 {
0352     KrTreeWidget::resizeEvent(re);
0353 
0354     if (started && (re->oldSize() != re->size()))
0355         sectionResized(0);
0356 }
0357 
0358 void DULines::sectionResized(int column)
0359 {
0360     if (topLevelItemCount() == 0 || column != 0)
0361         return;
0362 
0363     Directory *currentDir = diskUsage->getCurrentDir();
0364     if (currentDir == nullptr)
0365         return;
0366 
0367     int maxPercent = -1;
0368     for (Iterator<File> it = currentDir->iterator(); it != currentDir->end(); ++it) {
0369         File *item = *it;
0370 
0371         if (!item->isExcluded() && item->intPercent() > maxPercent)
0372             maxPercent = item->intPercent();
0373     }
0374 
0375     QTreeWidgetItemIterator it2(this);
0376     while (*it2) {
0377         QTreeWidgetItem *lvitem = *it2;
0378         if (lvitem->text(0) != "..") {
0379             auto *duItem = dynamic_cast<DULinesItem *>(lvitem);
0380             if (duItem) {
0381                 int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
0382                 duItem->setData(0, Qt::DecorationRole, createPixmap(duItem->getFile()->intPercent(), maxPercent, header()->sectionSize(0) - 2 * textMargin));
0383                 QSize size = duItem->sizeHint(0);
0384                 size.setWidth(16);
0385                 duItem->setSizeHint(0, size);
0386             }
0387         }
0388         it2++;
0389     }
0390 }
0391 
0392 bool DULines::doubleClicked(QTreeWidgetItem *item)
0393 {
0394     if (item) {
0395         if (item->text(0) != "..") {
0396             File *fileItem = (dynamic_cast<DULinesItem *>(item))->getFile();
0397             if (fileItem->isDir())
0398                 diskUsage->changeDirectory(dynamic_cast<Directory *>(fileItem));
0399             return true;
0400         } else {
0401             auto *upDir = const_cast<Directory *>(diskUsage->getCurrentDir()->parent());
0402 
0403             if (upDir)
0404                 diskUsage->changeDirectory(upDir);
0405             return true;
0406         }
0407     }
0408     return false;
0409 }
0410 
0411 void DULines::mouseDoubleClickEvent(QMouseEvent *e)
0412 {
0413     if (e || e->button() == Qt::LeftButton) {
0414         QPoint vp = viewport()->mapFromGlobal(e->globalPos());
0415         QTreeWidgetItem *item = itemAt(vp);
0416 
0417         if (doubleClicked(item))
0418             return;
0419     }
0420     KrTreeWidget::mouseDoubleClickEvent(e);
0421 }
0422 
0423 void DULines::keyPressEvent(QKeyEvent *e)
0424 {
0425     switch (e->key()) {
0426     case Qt::Key_Return:
0427     case Qt::Key_Enter:
0428         if (doubleClicked(currentItem()))
0429             return;
0430         break;
0431     case Qt::Key_Left:
0432     case Qt::Key_Right:
0433     case Qt::Key_Up:
0434     case Qt::Key_Down:
0435         if (e->modifiers() == Qt::ShiftModifier) {
0436             e->ignore();
0437             return;
0438         }
0439         break;
0440     case Qt::Key_Delete:
0441         e->ignore();
0442         return;
0443     }
0444     KrTreeWidget::keyPressEvent(e);
0445 }
0446 
0447 void DULines::slotRightClicked(QTreeWidgetItem *item, const QPoint &pos)
0448 {
0449     File *file = nullptr;
0450 
0451     if (item && item->text(0) != "..")
0452         file = (dynamic_cast<DULinesItem *>(item))->getFile();
0453 
0454     QMenu linesPopup;
0455     QAction *act = linesPopup.addAction(i18n("Show file sizes"), this, SLOT(slotShowFileSizes()));
0456     act->setChecked(showFileSize);
0457 
0458     diskUsage->rightClickMenu(pos, file, &linesPopup, i18n("Lines"));
0459 }
0460 
0461 void DULines::slotShowFileSizes()
0462 {
0463     showFileSize = !showFileSize;
0464     slotDirChanged(diskUsage->getCurrentDir());
0465 }
0466 
0467 File *DULines::getCurrentFile()
0468 {
0469     QTreeWidgetItem *item = currentItem();
0470 
0471     if (item == nullptr || item->text(0) == "..")
0472         return nullptr;
0473 
0474     return (dynamic_cast<DULinesItem *>(item))->getFile();
0475 }
0476 
0477 void DULines::slotChanged(File *item)
0478 {
0479     QTreeWidgetItemIterator it(this);
0480     while (*it) {
0481         QTreeWidgetItem *lvitem = *it;
0482         it++;
0483 
0484         if (lvitem->text(0) != "..") {
0485             auto *duItem = dynamic_cast<DULinesItem *>(lvitem);
0486             if (duItem->getFile() == item) {
0487                 setSortingEnabled(false);
0488                 duItem->setHidden(item->isExcluded());
0489                 duItem->setText(1, item->percent());
0490                 if (!refreshNeeded) {
0491                     refreshNeeded = true;
0492                     QTimer::singleShot(0, this, &DULines::slotRefresh);
0493                 }
0494                 break;
0495             }
0496         }
0497     }
0498 }
0499 
0500 void DULines::slotDeleted(File *item)
0501 {
0502     QTreeWidgetItemIterator it(this);
0503     while (*it) {
0504         QTreeWidgetItem *lvitem = *it;
0505         it++;
0506 
0507         if (lvitem->text(0) != "..") {
0508             auto *duItem = dynamic_cast<DULinesItem *>(lvitem);
0509             if (duItem->getFile() == item) {
0510                 delete duItem;
0511                 break;
0512             }
0513         }
0514     }
0515 }
0516 
0517 void DULines::slotRefresh()
0518 {
0519     if (refreshNeeded) {
0520         refreshNeeded = false;
0521         setSortingEnabled(true);
0522         sortItems(1, Qt::AscendingOrder);
0523     }
0524 }