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 ¤t, 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 }