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

0001 /*
0002     SPDX-FileCopyrightText: 2009 Csaba Karai <cskarai@freemail.hu>
0003     SPDX-FileCopyrightText: 2009-2022 Krusader Krew <https://krusader.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "krinterbriefview.h"
0009 
0010 // QtCore
0011 #include <QDebug>
0012 #include <QDir>
0013 #include <QHashIterator>
0014 #include <QItemSelection>
0015 #include <QItemSelectionRange>
0016 // QtGui
0017 #include <QFontMetrics>
0018 #include <QPainter>
0019 #include <QRegion>
0020 // QtWidgets
0021 #include <QApplication>
0022 #include <QDirModel>
0023 #include <QMenu>
0024 #include <QScrollBar>
0025 
0026 #include <KConfigCore/KConfig>
0027 #include <KI18n/KLocalizedString>
0028 #include <KIOWidgets/KDirLister>
0029 
0030 #include "../FileSystem/krpermhandler.h"
0031 #include "../GUI/krstyleproxy.h"
0032 #include "../compat.h"
0033 #include "../defaults.h"
0034 #include "../krcolorcache.h"
0035 #include "krmousehandler.h"
0036 #include "krviewfactory.h"
0037 #include "krviewitem.h"
0038 #include "krviewitemdelegate.h"
0039 #include "listmodel.h"
0040 
0041 #define MAX_BRIEF_COLS 5
0042 
0043 KrInterBriefView::KrInterBriefView(QWidget *parent, KrViewInstance &instance, KConfig *cfg)
0044     : QAbstractItemView(parent)
0045     , KrInterView(instance, cfg, this)
0046     , _header(nullptr)
0047 {
0048     setWidget(this);
0049     setModel(_model);
0050 
0051     setSelectionMode(QAbstractItemView::NoSelection);
0052     setSelectionModel(new DummySelectionModel(_model, this));
0053 
0054     KConfigGroup grpSvr(_config, "Look&Feel");
0055     _viewFont = grpSvr.readEntry("Filelist Font", _FilelistFont);
0056 
0057     auto *style = new KrStyleProxy();
0058     style->setParent(this);
0059     setStyle(style);
0060     viewport()->setStyle(style); // for custom tooltip delay
0061     setItemDelegate(new KrViewItemDelegate());
0062     setMouseTracking(true);
0063     setAcceptDrops(true);
0064     setDropIndicatorShown(true);
0065 
0066     connect(_mouseHandler, &KrMouseHandler::renameCurrentItem, this, &KrInterBriefView::renameCurrentItem);
0067 
0068     _model->setExtensionEnabled(false);
0069     _model->setAlternatingTable(true);
0070     connect(_model, &ListModel::layoutChanged, this, &KrInterBriefView::updateGeometries);
0071 }
0072 
0073 KrInterBriefView::~KrInterBriefView()
0074 {
0075     delete _properties;
0076     _properties = nullptr;
0077 
0078     delete _operator;
0079     _operator = nullptr;
0080 }
0081 
0082 void KrInterBriefView::doRestoreSettings(KConfigGroup group)
0083 {
0084     _properties->numberOfColumns = group.readEntry("Number Of Brief Columns", _NumberOfBriefColumns);
0085     if (_properties->numberOfColumns < 1)
0086         _properties->numberOfColumns = 1;
0087     else if (_properties->numberOfColumns > MAX_BRIEF_COLS)
0088         _properties->numberOfColumns = MAX_BRIEF_COLS;
0089 
0090     _numOfColumns = _properties->numberOfColumns;
0091 
0092     KrInterView::doRestoreSettings(group);
0093 
0094     updateGeometries();
0095 }
0096 
0097 void KrInterBriefView::saveSettings(KConfigGroup grp, KrViewProperties::PropertyType properties)
0098 {
0099     KrInterView::saveSettings(grp, properties);
0100     if (properties & KrViewProperties::PropColumns)
0101         grp.writeEntry("Number Of Brief Columns", _numOfColumns);
0102 }
0103 
0104 int KrInterBriefView::itemsPerPage()
0105 {
0106     int height = getItemHeight();
0107     if (height == 0)
0108         height++;
0109     int numRows = viewport()->height() / height;
0110     return numRows;
0111 }
0112 void KrInterBriefView::updateView()
0113 {
0114 }
0115 
0116 void KrInterBriefView::setup()
0117 {
0118     _header = new QHeaderView(Qt::Horizontal, this);
0119     _header->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
0120     _header->setParent(this);
0121     _header->setModel(_model);
0122     _header->hideSection(KrViewProperties::Type);
0123     _header->hideSection(KrViewProperties::Permissions);
0124     _header->hideSection(KrViewProperties::KrPermissions);
0125     _header->hideSection(KrViewProperties::Owner);
0126     _header->hideSection(KrViewProperties::Group);
0127     _header->hideSection(KrViewProperties::Changed);
0128     _header->hideSection(KrViewProperties::Accessed);
0129     _header->setStretchLastSection(true);
0130     _header->setSectionResizeMode(QHeaderView::Fixed);
0131     _header->setSectionsClickable(true);
0132     _header->setSortIndicatorShown(true);
0133     connect(_header, &QHeaderView::sortIndicatorChanged, _model, QOverload<int, Qt::SortOrder>::of(&ListModel::sort));
0134     _header->installEventFilter(this);
0135 
0136     _numOfColumns = _properties->numberOfColumns;
0137 
0138     setSortMode(_properties->sortColumn, (_properties->sortOptions & KrViewProperties::Descending));
0139 }
0140 
0141 void KrInterBriefView::keyPressEvent(QKeyEvent *e)
0142 {
0143     if (!e || !_model->ready())
0144         return; // subclass bug
0145 
0146     if (handleKeyEvent(e))
0147         return;
0148 
0149     QAbstractItemView::keyPressEvent(e);
0150 }
0151 
0152 bool KrInterBriefView::handleKeyEvent(QKeyEvent *e)
0153 {
0154     if (((e->key() != Qt::Key_Left && e->key() != Qt::Key_Right) || (e->modifiers() == Qt::ControlModifier)) && (KrView::handleKeyEvent(e)))
0155         // did the view class handled the event?
0156         return true;
0157 
0158     switch (e->key()) {
0159     case Qt::Key_Right: {
0160         KrViewItem *i = getCurrentKrViewItem();
0161         KrViewItem *newCurrent = i;
0162 
0163         if (!i)
0164             break;
0165 
0166         int num = itemsPerPage() + 1;
0167 
0168         if (e->modifiers() & Qt::ShiftModifier)
0169             i->setSelected(!i->isSelected());
0170 
0171         while (i && num > 0) {
0172             if (e->modifiers() & Qt::ShiftModifier)
0173                 i->setSelected(!i->isSelected());
0174             newCurrent = i;
0175             i = getNext(i);
0176             num--;
0177         }
0178 
0179         if (newCurrent) {
0180             setCurrentKrViewItem(newCurrent);
0181             makeCurrentVisible();
0182         }
0183         if (e->modifiers() & Qt::ShiftModifier)
0184             op()->emitSelectionChanged();
0185         return true;
0186     }
0187     case Qt::Key_Left: {
0188         KrViewItem *i = getCurrentKrViewItem();
0189         KrViewItem *newCurrent = i;
0190 
0191         if (!i)
0192             break;
0193 
0194         int num = itemsPerPage() + 1;
0195 
0196         if (e->modifiers() & Qt::ShiftModifier)
0197             i->setSelected(!i->isSelected());
0198 
0199         while (i && num > 0) {
0200             if (e->modifiers() & Qt::ShiftModifier)
0201                 i->setSelected(!i->isSelected());
0202             newCurrent = i;
0203             i = getPrev(i);
0204             num--;
0205         }
0206 
0207         if (newCurrent) {
0208             setCurrentKrViewItem(newCurrent);
0209             makeCurrentVisible();
0210         }
0211         if (e->modifiers() & Qt::ShiftModifier)
0212             op()->emitSelectionChanged();
0213         return true;
0214     }
0215     }
0216 
0217     return false;
0218 }
0219 
0220 void KrInterBriefView::wheelEvent(QWheelEvent *ev)
0221 {
0222     if (!_mouseHandler->wheelEvent(ev))
0223         QApplication::sendEvent(horizontalScrollBar(), ev);
0224 }
0225 
0226 bool KrInterBriefView::eventFilter(QObject *object, QEvent *event)
0227 {
0228     if (object == _header) {
0229         if (event->type() == QEvent::ContextMenu) {
0230             auto *me = dynamic_cast<QContextMenuEvent *>(event);
0231             showContextMenu(me->globalPos());
0232             return true;
0233         }
0234     }
0235     return false;
0236 }
0237 
0238 void KrInterBriefView::showContextMenu(const QPoint &p)
0239 {
0240     QMenu popup(this);
0241     popup.setTitle(i18n("Columns"));
0242 
0243     int COL_ID = 14700;
0244 
0245     for (int i = 1; i <= MAX_BRIEF_COLS; i++) {
0246         QAction *act = popup.addAction(QString("%1").arg(i));
0247         act->setData(QVariant(COL_ID + i));
0248         act->setCheckable(true);
0249         act->setChecked(properties()->numberOfColumns == i);
0250     }
0251 
0252     QAction *res = popup.exec(p);
0253     int result = -1;
0254     if (res && res->data().canConvert<int>())
0255         result = res->data().toInt();
0256 
0257     if (result > COL_ID && result <= COL_ID + MAX_BRIEF_COLS) {
0258         _properties->numberOfColumns = result - COL_ID;
0259         _numOfColumns = _properties->numberOfColumns;
0260         updateGeometries();
0261         op()->settingsChanged(KrViewProperties::PropColumns);
0262     }
0263 }
0264 
0265 QRect KrInterBriefView::visualRect(const QModelIndex &ndx) const
0266 {
0267     int width = (viewport()->width()) / _numOfColumns;
0268     if ((viewport()->width()) % _numOfColumns)
0269         width++;
0270     int height = getItemHeight();
0271     int numRows = viewport()->height() / height;
0272     if (numRows == 0)
0273         numRows++;
0274     int x = width * (ndx.row() / numRows);
0275     int y = height * (ndx.row() % numRows);
0276     return mapToViewport(QRect(x, y, width, height));
0277 }
0278 
0279 void KrInterBriefView::scrollTo(const QModelIndex &ndx, QAbstractItemView::ScrollHint hint)
0280 {
0281     const QRect rect = visualRect(ndx);
0282     if (hint == EnsureVisible && viewport()->rect().contains(rect)) {
0283         setDirtyRegion(rect);
0284         return;
0285     }
0286 
0287     const QRect area = viewport()->rect();
0288 
0289     const bool leftOf = rect.left() < area.left();
0290     const bool rightOf = rect.right() > area.right();
0291 
0292     int horizontalValue = horizontalScrollBar()->value();
0293 
0294     if (leftOf)
0295         horizontalValue -= area.left() - rect.left();
0296     else if (rightOf)
0297         horizontalValue += rect.right() - area.right();
0298 
0299     horizontalScrollBar()->setValue(horizontalValue);
0300 }
0301 
0302 QModelIndex KrInterBriefView::indexAt(const QPoint &p) const
0303 {
0304     int x = p.x() + horizontalOffset();
0305     int y = p.y() + verticalOffset();
0306 
0307     int itemWidth = (viewport()->width()) / _numOfColumns;
0308     if ((viewport()->width()) % _numOfColumns)
0309         itemWidth++;
0310     int itemHeight = getItemHeight();
0311 
0312     int numRows = viewport()->height() / itemHeight;
0313     if (numRows == 0)
0314         numRows++;
0315 
0316     int row = y / itemHeight;
0317     int col = x / itemWidth;
0318 
0319     int numColsTotal = _model->rowCount() / numRows;
0320     if (_model->rowCount() % numRows)
0321         numColsTotal++;
0322 
0323     if (row < numRows && col < numColsTotal)
0324         return _model->index((col * numRows) + row, 0);
0325 
0326     return QModelIndex();
0327 }
0328 
0329 QModelIndex KrInterBriefView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers)
0330 {
0331     if (_model->rowCount() == 0)
0332         return QModelIndex();
0333 
0334     QModelIndex current = currentIndex();
0335     if (!current.isValid())
0336         return _model->index(0, 0);
0337 
0338     switch (cursorAction) {
0339     case MoveLeft:
0340     case MovePageDown: {
0341         int newRow = current.row() - itemsPerPage();
0342         if (newRow < 0)
0343             newRow = 0;
0344         return _model->index(newRow, 0);
0345     }
0346     case MoveRight:
0347     case MovePageUp: {
0348         int newRow = current.row() + itemsPerPage();
0349         if (newRow >= _model->rowCount())
0350             newRow = _model->rowCount() - 1;
0351         return _model->index(newRow, 0);
0352     }
0353     case MovePrevious:
0354     case MoveUp: {
0355         int newRow = current.row() - 1;
0356         if (newRow < 0)
0357             newRow = 0;
0358         return _model->index(newRow, 0);
0359     }
0360     case MoveNext:
0361     case MoveDown: {
0362         int newRow = current.row() + 1;
0363         if (newRow >= _model->rowCount())
0364             newRow = _model->rowCount() - 1;
0365         return _model->index(newRow, 0);
0366     }
0367     case MoveHome:
0368         return _model->index(0, 0);
0369     case MoveEnd:
0370         return _model->index(_model->rowCount() - 1, 0);
0371     }
0372 
0373     return current;
0374 }
0375 
0376 int KrInterBriefView::horizontalOffset() const
0377 {
0378     return horizontalScrollBar()->value();
0379 }
0380 
0381 int KrInterBriefView::verticalOffset() const
0382 {
0383     return 0;
0384 }
0385 
0386 bool KrInterBriefView::isIndexHidden(const QModelIndex &ndx) const
0387 {
0388     return ndx.column() != 0;
0389 }
0390 
0391 void KrInterBriefView::paintEvent(QPaintEvent *e)
0392 {
0393     QStyleOptionViewItem option = viewOptions();
0394     option.widget = this;
0395     option.decorationSize = QSize(_fileIconSize, _fileIconSize);
0396     option.decorationPosition = QStyleOptionViewItem::Left;
0397     QPainter painter(viewport());
0398 
0399     QModelIndex curr = currentIndex();
0400 
0401     QVector<QModelIndex> intersectVector;
0402     QRect area = e->rect();
0403     area.adjust(horizontalOffset(), verticalOffset(), horizontalOffset(), verticalOffset());
0404     intersectionSet(area, intersectVector);
0405 
0406     foreach (const QModelIndex &mndx, intersectVector) {
0407         option.rect = visualRect(mndx);
0408         painter.save();
0409 
0410         itemDelegate()->paint(&painter, option, mndx);
0411 
0412         // (always) draw dashed line border around current item row
0413         const bool isCurrent = curr.isValid() && curr.row() == mndx.row();
0414         if (isCurrent && drawCurrent()) {
0415             QStyleOptionFocusRect o;
0416             o.QStyleOption::operator=(option);
0417             QPalette::ColorGroup cg = QPalette::Normal;
0418             o.backgroundColor = option.palette.color(cg, QPalette::Background);
0419 
0420             style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, &painter);
0421         }
0422 
0423         painter.restore();
0424     }
0425 }
0426 
0427 int KrInterBriefView::getItemHeight() const
0428 {
0429     int textHeight = QFontMetrics(_viewFont).height();
0430     int height = textHeight;
0431     int iconSize = 0;
0432     if (properties()->displayIcons)
0433         iconSize = _fileIconSize;
0434     if (iconSize > textHeight)
0435         height = iconSize;
0436     if (height == 0)
0437         height++;
0438     return height;
0439 }
0440 
0441 void KrInterBriefView::updateGeometries()
0442 {
0443     if (_header) {
0444         QSize hint = _header->sizeHint();
0445         setViewportMargins(0, hint.height(), 0, 0);
0446         QRect vg = viewport()->geometry();
0447         QRect geometryRect(vg.left(), vg.top() - hint.height(), vg.width(), hint.height());
0448         _header->setGeometry(geometryRect);
0449 
0450         int items = 0;
0451         for (int i = 0; i != _header->count(); i++)
0452             if (!_header->isSectionHidden(i))
0453                 items++;
0454         if (items == 0)
0455             items++;
0456 
0457         int sectWidth = viewport()->width() / items;
0458         for (int i = 0; i != _header->count(); i++)
0459             if (!_header->isSectionHidden(i))
0460                 _header->resizeSection(i, sectWidth);
0461 
0462         QMetaObject::invokeMethod(_header, "updateGeometries");
0463     }
0464 
0465     if (_model->rowCount() <= 0)
0466         horizontalScrollBar()->setRange(0, 0);
0467     else {
0468         int itemsPerColumn = viewport()->height() / getItemHeight();
0469         if (itemsPerColumn <= 0)
0470             itemsPerColumn = 1;
0471         int columnWidth = (viewport()->width()) / _numOfColumns;
0472         if ((viewport()->width()) % _numOfColumns)
0473             columnWidth++;
0474         int maxWidth = _model->rowCount() / itemsPerColumn;
0475         if (_model->rowCount() % itemsPerColumn)
0476             maxWidth++;
0477         maxWidth *= columnWidth;
0478         if (maxWidth > viewport()->width()) {
0479             horizontalScrollBar()->setSingleStep(columnWidth);
0480             horizontalScrollBar()->setPageStep(columnWidth * _numOfColumns);
0481             horizontalScrollBar()->setRange(0, maxWidth - viewport()->width());
0482         } else {
0483             horizontalScrollBar()->setRange(0, 0);
0484         }
0485     }
0486 
0487     QAbstractItemView::updateGeometries();
0488 }
0489 
0490 void KrInterBriefView::setSortMode(KrViewProperties::ColumnType sortColumn, bool descending)
0491 {
0492     Qt::SortOrder sortDir = descending ? Qt::DescendingOrder : Qt::AscendingOrder;
0493     _header->setSortIndicator(sortColumn, sortDir);
0494 }
0495 
0496 int KrInterBriefView::elementWidth(const QModelIndex &index)
0497 {
0498     QString text = index.data(Qt::DisplayRole).toString();
0499 
0500     int textWidth = QFontMetrics(_viewFont).horizontalAdvance(text);
0501 
0502     const int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
0503     textWidth += 2 * textMargin;
0504 
0505     QVariant decor = index.data(Qt::DecorationRole);
0506     if (decor.isValid() && decor.type() == QVariant::Pixmap) {
0507         QPixmap p = decor.value<QPixmap>();
0508         textWidth += p.width() + 2 * textMargin;
0509     }
0510 
0511     return textWidth;
0512 }
0513 
0514 void KrInterBriefView::intersectionSet(const QRect &rect, QVector<QModelIndex> &ndxList)
0515 {
0516     int maxNdx = _model->rowCount();
0517     int width = (viewport()->width()) / _numOfColumns;
0518     if ((viewport()->width()) % _numOfColumns)
0519         width++;
0520 
0521     int height = getItemHeight();
0522     int items = viewport()->height() / height;
0523     if (items == 0)
0524         items++;
0525 
0526     int xmin = -1;
0527     int ymin = -1;
0528     int xmax = -1;
0529     int ymax = -1;
0530 
0531     xmin = rect.x() / width;
0532     ymin = rect.y() / height;
0533     xmax = (rect.x() + rect.width()) / width;
0534     if ((rect.x() + rect.width()) % width)
0535         xmax++;
0536     ymax = (rect.y() + rect.height()) / height;
0537     if ((rect.y() + rect.height()) % height)
0538         ymax++;
0539 
0540     for (int i = ymin; i < ymax; i++)
0541         for (int j = xmin; j < xmax; j++) {
0542             int ndx = j * items + i;
0543             if (ndx < maxNdx)
0544                 ndxList.append(_model->index(ndx, 0));
0545         }
0546 }
0547 
0548 QRect KrInterBriefView::itemRect(const FileItem *item)
0549 {
0550     return visualRect(_model->fileItemIndex(item));
0551 }
0552 
0553 void KrInterBriefView::copySettingsFrom(KrView *other)
0554 {
0555     if (other->instance() == instance()) { // the other view is of the same type
0556         auto *v = dynamic_cast<KrInterBriefView *>(other);
0557         int column = v->_model->lastSortOrder();
0558         Qt::SortOrder sortDir = v->_model->lastSortDir();
0559         _header->setSortIndicator(column, sortDir);
0560         _model->sort(column, sortDir);
0561         setFileIconSize(v->fileIconSize());
0562     }
0563 }
0564 
0565 void KrInterBriefView::setFileIconSize(int size)
0566 {
0567     KrView::setFileIconSize(size);
0568     setIconSize(QSize(fileIconSize(), fileIconSize()));
0569     updateGeometries();
0570 }
0571 
0572 void KrInterBriefView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
0573 {
0574     if (_model->ready()) {
0575         KrViewItem *item = getKrViewItem(currentIndex());
0576         op()->emitCurrentChanged(item);
0577     }
0578     QAbstractItemView::currentChanged(current, previous);
0579 }
0580 
0581 void KrInterBriefView::renameCurrentItem()
0582 {
0583     QModelIndex nameIndex = _model->index(currentIndex().row(), KrViewProperties::Name);
0584 
0585     // cycle through various text selections if we are in the editing mode already
0586     if (state() == QAbstractItemView::EditingState) {
0587         auto delegate = dynamic_cast<KrViewItemDelegate *>(itemDelegate(nameIndex));
0588         if (!delegate) {
0589             qWarning() << "KrInterView item delegate is not KrViewItemDelegate, selection is not updated";
0590             return;
0591         }
0592 
0593         delegate->cycleEditorSelection();
0594         return;
0595     }
0596 
0597     // create and show file name editor
0598     edit(nameIndex);
0599     updateEditorData();
0600     update(nameIndex);
0601 }
0602 
0603 bool KrInterBriefView::event(QEvent *e)
0604 {
0605     _mouseHandler->otherEvent(e);
0606     return QAbstractItemView::event(e);
0607 }
0608 
0609 void KrInterBriefView::mousePressEvent(QMouseEvent *ev)
0610 {
0611     if (!_mouseHandler->mousePressEvent(ev))
0612         QAbstractItemView::mousePressEvent(ev);
0613 }
0614 
0615 void KrInterBriefView::mouseReleaseEvent(QMouseEvent *ev)
0616 {
0617     if (!_mouseHandler->mouseReleaseEvent(ev))
0618         QAbstractItemView::mouseReleaseEvent(ev);
0619 }
0620 
0621 void KrInterBriefView::mouseDoubleClickEvent(QMouseEvent *ev)
0622 {
0623     if (!_mouseHandler->mouseDoubleClickEvent(ev))
0624         QAbstractItemView::mouseDoubleClickEvent(ev);
0625 }
0626 
0627 void KrInterBriefView::mouseMoveEvent(QMouseEvent *ev)
0628 {
0629     if (!_mouseHandler->mouseMoveEvent(ev))
0630         QAbstractItemView::mouseMoveEvent(ev);
0631 }
0632 
0633 void KrInterBriefView::dragEnterEvent(QDragEnterEvent *ev)
0634 {
0635     if (!_mouseHandler->dragEnterEvent(ev))
0636         QAbstractItemView::dragEnterEvent(ev);
0637 }
0638 
0639 void KrInterBriefView::dragMoveEvent(QDragMoveEvent *ev)
0640 {
0641     QAbstractItemView::dragMoveEvent(ev);
0642     _mouseHandler->dragMoveEvent(ev);
0643 }
0644 
0645 void KrInterBriefView::dragLeaveEvent(QDragLeaveEvent *ev)
0646 {
0647     if (!_mouseHandler->dragLeaveEvent(ev))
0648         QAbstractItemView::dragLeaveEvent(ev);
0649 }
0650 
0651 void KrInterBriefView::dropEvent(QDropEvent *ev)
0652 {
0653     if (!_mouseHandler->dropEvent(ev))
0654         QAbstractItemView::dropEvent(ev);
0655 }
0656 
0657 QRect KrInterBriefView::mapToViewport(const QRect &rect) const
0658 {
0659     if (!rect.isValid())
0660         return rect;
0661 
0662     QRect result = rect;
0663 
0664     int dx = -horizontalOffset();
0665     int dy = -verticalOffset();
0666     result.adjust(dx, dy, dx, dy);
0667     return result;
0668 }