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

0001 /*
0002     SPDX-FileCopyrightText: 2002 Shie Erlich <erlich@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2002 Rafi Yanai <yanai@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "krinterdetailedview.h"
0010 
0011 // QtCore
0012 #include <QDebug>
0013 #include <QDir>
0014 #include <QHashIterator>
0015 // QtWidgets
0016 #include <QApplication>
0017 #include <QDirModel>
0018 #include <QHeaderView>
0019 #include <QMenu>
0020 #include <QToolTip>
0021 
0022 #include <KConfigCore/KSharedConfig>
0023 #include <KI18n/KLocalizedString>
0024 #include <KIOWidgets/KDirLister>
0025 
0026 #include "../FileSystem/krpermhandler.h"
0027 #include "../GUI/krstyleproxy.h"
0028 #include "../compat.h"
0029 #include "../defaults.h"
0030 #include "../krcolorcache.h"
0031 #include "../krglobal.h"
0032 #include "krmousehandler.h"
0033 #include "krviewfactory.h"
0034 #include "krviewitem.h"
0035 #include "krviewitemdelegate.h"
0036 #include "listmodel.h"
0037 
0038 KrInterDetailedView::KrInterDetailedView(QWidget *parent, KrViewInstance &instance, KConfig *cfg)
0039     : QTreeView(parent)
0040     , KrInterView(instance, cfg, this)
0041     , _autoResizeColumns(true)
0042 {
0043     connect(_mouseHandler, &KrMouseHandler::renameCurrentItem, this, &KrInterDetailedView::renameCurrentItem);
0044     setWidget(this);
0045 
0046     KConfigGroup grpSvr(_config, "Look&Feel");
0047     _viewFont = grpSvr.readEntry("Filelist Font", _FilelistFont);
0048 
0049     setModel(_model);
0050     setRootIsDecorated(false);
0051     setItemsExpandable(false);
0052     setAllColumnsShowFocus(true);
0053     setUniformRowHeights(true);
0054 
0055     setMouseTracking(true);
0056     setAcceptDrops(true);
0057     setDropIndicatorShown(true);
0058 
0059     setSelectionMode(QAbstractItemView::NoSelection);
0060     setSelectionModel(new DummySelectionModel(_model, this));
0061 
0062     header()->installEventFilter(this);
0063     header()->setSectionResizeMode(QHeaderView::Interactive);
0064     header()->setStretchLastSection(false);
0065 
0066     auto *style = new KrStyleProxy();
0067     style->setParent(this);
0068     setStyle(style);
0069     viewport()->setStyle(style); // for custom tooltip delay
0070 
0071     setItemDelegate(new KrViewItemDelegate(this));
0072 
0073     connect(header(), &QHeaderView::sectionResized, this, &KrInterDetailedView::sectionResized);
0074     connect(header(), &QHeaderView::sectionMoved, this, &KrInterDetailedView::sectionMoved);
0075 }
0076 
0077 KrInterDetailedView::~KrInterDetailedView()
0078 {
0079     delete _properties;
0080     _properties = nullptr;
0081     delete _operator;
0082     _operator = nullptr;
0083 }
0084 
0085 void KrInterDetailedView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
0086 {
0087     if (_model->ready()) {
0088         KrViewItem *item = getKrViewItem(currentIndex());
0089         op()->emitCurrentChanged(item);
0090     }
0091     QTreeView::currentChanged(current, previous);
0092 }
0093 
0094 void KrInterDetailedView::doRestoreSettings(KConfigGroup grp)
0095 {
0096     auto headerView = header();
0097 
0098     _autoResizeColumns = grp.readEntry("AutoResizeColumns", true);
0099 
0100     QByteArray savedState = grp.readEntry("Saved State", QByteArray());
0101     if (savedState.isEmpty()) {
0102         hideColumn(KrViewProperties::Type);
0103         hideColumn(KrViewProperties::Permissions);
0104         hideColumn(KrViewProperties::Owner);
0105         hideColumn(KrViewProperties::Group);
0106         hideColumn(KrViewProperties::Changed);
0107         hideColumn(KrViewProperties::Accessed);
0108         headerView->resizeSection(KrViewProperties::Ext, QFontMetrics(_viewFont).horizontalAdvance("tar.bz2  "));
0109         headerView->resizeSection(KrViewProperties::KrPermissions, QFontMetrics(_viewFont).horizontalAdvance("rwx  "));
0110         headerView->resizeSection(KrViewProperties::Size, QFontMetrics(_viewFont).horizontalAdvance("9") * 10);
0111 
0112         QDateTime tmp(QDate(2099, 12, 29), QTime(23, 59));
0113         QString desc = QLocale().toString(tmp, QLocale::ShortFormat) + "  ";
0114 
0115         headerView->resizeSection(KrViewProperties::Modified, QFontMetrics(_viewFont).horizontalAdvance(desc));
0116     } else {
0117         headerView->restoreState(savedState);
0118 
0119         // do not show new columns by default; restoreState() shows columns not saved
0120         if (KrGlobal::sCurrentConfigVersion < KrGlobal::sConfigVersion) {
0121             hideColumn(KrViewProperties::Changed);
0122             hideColumn(KrViewProperties::Accessed);
0123         }
0124 
0125         _model->setExtensionEnabled(!isColumnHidden(KrViewProperties::Ext));
0126     }
0127 
0128     // In case a column is assigned zero size for some reason, it's impossible to fix this from interface.
0129     // We correct this problem by enforcing the minimum width of the column.
0130     auto minSize = headerView->minimumSectionSize();
0131     for (int i = KrViewProperties::Ext; i < KrViewProperties::MAX_COLUMNS; i++) {
0132         if (!headerView->isSectionHidden(i) && headerView->sectionSize(i) < minSize) {
0133             headerView->resizeSection(i, minSize);
0134         }
0135     }
0136 
0137     KrInterView::doRestoreSettings(grp);
0138 }
0139 
0140 void KrInterDetailedView::saveSettings(KConfigGroup grp, KrViewProperties::PropertyType properties)
0141 {
0142     KrInterView::saveSettings(grp, properties);
0143 
0144     grp.writeEntry("AutoResizeColumns", _autoResizeColumns);
0145 
0146     if (properties & KrViewProperties::PropColumns) {
0147         QByteArray state = header()->saveState();
0148         grp.writeEntry("Saved State", state);
0149     }
0150 }
0151 
0152 int KrInterDetailedView::itemsPerPage()
0153 {
0154     QRect rect = visualRect(currentIndex());
0155     if (!rect.isValid()) {
0156         for (int i = 0; i != _model->rowCount(); i++) {
0157             rect = visualRect(_model->index(i, 0));
0158             if (rect.isValid())
0159                 break;
0160         }
0161     }
0162     if (!rect.isValid())
0163         return 0;
0164     int size = (height() - header()->height()) / rect.height();
0165     if (size < 0)
0166         size = 0;
0167     return size;
0168 }
0169 
0170 void KrInterDetailedView::updateView()
0171 {
0172 }
0173 
0174 void KrInterDetailedView::setup()
0175 {
0176     setSortMode(_properties->sortColumn, (_properties->sortOptions & KrViewProperties::Descending));
0177     setSortingEnabled(true);
0178 }
0179 
0180 void KrInterDetailedView::keyPressEvent(QKeyEvent *e)
0181 {
0182     if (!e || !_model->ready())
0183         return; // subclass bug
0184     if (handleKeyEvent(e)) // did the view class handled the event?
0185         return;
0186     QTreeView::keyPressEvent(e);
0187 }
0188 
0189 void KrInterDetailedView::mousePressEvent(QMouseEvent *ev)
0190 {
0191     if (!_mouseHandler->mousePressEvent(ev))
0192         QTreeView::mousePressEvent(ev);
0193 }
0194 
0195 void KrInterDetailedView::mouseReleaseEvent(QMouseEvent *ev)
0196 {
0197     if (!_mouseHandler->mouseReleaseEvent(ev))
0198         QTreeView::mouseReleaseEvent(ev);
0199 }
0200 
0201 void KrInterDetailedView::mouseDoubleClickEvent(QMouseEvent *ev)
0202 {
0203     if (!_mouseHandler->mouseDoubleClickEvent(ev))
0204         QTreeView::mouseDoubleClickEvent(ev);
0205 }
0206 
0207 void KrInterDetailedView::mouseMoveEvent(QMouseEvent *ev)
0208 {
0209     if (!_mouseHandler->mouseMoveEvent(ev))
0210         QTreeView::mouseMoveEvent(ev);
0211 }
0212 
0213 void KrInterDetailedView::wheelEvent(QWheelEvent *ev)
0214 {
0215     if (!_mouseHandler->wheelEvent(ev))
0216         QTreeView::wheelEvent(ev);
0217 }
0218 
0219 void KrInterDetailedView::dragEnterEvent(QDragEnterEvent *ev)
0220 {
0221     if (!_mouseHandler->dragEnterEvent(ev))
0222         QTreeView::dragEnterEvent(ev);
0223 }
0224 
0225 void KrInterDetailedView::dragMoveEvent(QDragMoveEvent *ev)
0226 {
0227     QTreeView::dragMoveEvent(ev);
0228     _mouseHandler->dragMoveEvent(ev);
0229 }
0230 
0231 void KrInterDetailedView::dragLeaveEvent(QDragLeaveEvent *ev)
0232 {
0233     if (!_mouseHandler->dragLeaveEvent(ev))
0234         QTreeView::dragLeaveEvent(ev);
0235 }
0236 
0237 void KrInterDetailedView::dropEvent(QDropEvent *ev)
0238 {
0239     if (!_mouseHandler->dropEvent(ev))
0240         QTreeView::dropEvent(ev);
0241 }
0242 
0243 bool KrInterDetailedView::event(QEvent *e)
0244 {
0245     _mouseHandler->otherEvent(e);
0246     return QTreeView::event(e);
0247 }
0248 
0249 void KrInterDetailedView::renameCurrentItem()
0250 {
0251     QModelIndex nameIndex = _model->index(currentIndex().row(), KrViewProperties::Name);
0252 
0253     // cycle through various text selections if we are in the editing mode already
0254     if (state() == QAbstractItemView::EditingState) {
0255         auto delegate = dynamic_cast<KrViewItemDelegate *>(itemDelegate(nameIndex));
0256         if (!delegate) {
0257             qWarning() << "KrInterView item delegate is not KrViewItemDelegate, selection is not updated";
0258             return;
0259         }
0260 
0261         delegate->cycleEditorSelection();
0262         return;
0263     }
0264 
0265     // create and show file name editor
0266     edit(nameIndex);
0267     updateEditorData();
0268     update(nameIndex);
0269 }
0270 
0271 bool KrInterDetailedView::eventFilter(QObject *object, QEvent *event)
0272 {
0273     if (object == header()) {
0274         if (event->type() == QEvent::ContextMenu) {
0275             auto *me = dynamic_cast<QContextMenuEvent *>(event);
0276             showContextMenu(me->globalPos());
0277             return true;
0278         } else if (event->type() == QEvent::Resize) {
0279             recalculateColumnSizes();
0280             return false;
0281         }
0282     }
0283     return false;
0284 }
0285 
0286 void KrInterDetailedView::showContextMenu(const QPoint &p)
0287 {
0288     QMenu popup(this);
0289     popup.setTitle(i18n("Columns"));
0290 
0291     QVector<QAction *> actions;
0292 
0293     for (int i = KrViewProperties::Ext; i < KrViewProperties::MAX_COLUMNS; i++) {
0294         QString text = (_model->headerData(i, Qt::Horizontal)).toString();
0295         QAction *act = popup.addAction(text);
0296         act->setCheckable(true);
0297         act->setChecked(!header()->isSectionHidden(i));
0298         act->setData(i);
0299         actions.append(act);
0300     }
0301 
0302     popup.addSeparator();
0303     QAction *actAutoResize = popup.addAction(i18n("Automatically Resize Columns"));
0304     actAutoResize->setCheckable(true);
0305     actAutoResize->setChecked(_autoResizeColumns);
0306 
0307     QAction *res = popup.exec(p);
0308     if (res == nullptr)
0309         return;
0310 
0311     if (res == actAutoResize) {
0312         _autoResizeColumns = actAutoResize->isChecked();
0313         recalculateColumnSizes();
0314     } else {
0315         int column = res->data().toInt();
0316 
0317         if (header()->isSectionHidden(column))
0318             header()->showSection(column);
0319         else
0320             header()->hideSection(column);
0321 
0322         if (KrViewProperties::Ext == column)
0323             _model->setExtensionEnabled(!header()->isSectionHidden(KrViewProperties::Ext));
0324     }
0325     op()->settingsChanged(KrViewProperties::PropColumns);
0326 }
0327 
0328 void KrInterDetailedView::sectionResized(int /*column*/, int oldSize, int newSize)
0329 {
0330     // *** taken from dolphin ***
0331     // If the user changes the size of the headers, the autoresize feature should be
0332     // turned off. As there is no dedicated interface to find out whether the header
0333     // section has been resized by the user or by a resize event, another approach is used.
0334     // Attention: Take care when changing the if-condition to verify that there is no
0335     // regression in combination with bug 178630 (see fix in comment #8).
0336     if ((QApplication::mouseButtons() & Qt::LeftButton) && header()->underMouse()) {
0337         _autoResizeColumns = false;
0338         op()->settingsChanged(KrViewProperties::PropColumns);
0339     }
0340 
0341     if (oldSize == newSize || !_model->ready())
0342         return;
0343 
0344     recalculateColumnSizes();
0345 }
0346 
0347 void KrInterDetailedView::sectionMoved(int /*logicalIndex*/, int /*oldVisualIndex*/, int /*newVisualIndex*/)
0348 {
0349     op()->settingsChanged(KrViewProperties::PropColumns);
0350 }
0351 
0352 void KrInterDetailedView::recalculateColumnSizes()
0353 {
0354     if (!_autoResizeColumns)
0355         return;
0356     int sum = 0;
0357     for (int i = 0; i != _model->columnCount(); i++) {
0358         if (!isColumnHidden(i))
0359             sum += header()->sectionSize(i);
0360     }
0361 
0362     if (sum != header()->width()) {
0363         int delta = sum - header()->width();
0364         int nameSize = header()->sectionSize(KrViewProperties::Name);
0365         if (nameSize - delta > 20)
0366             header()->resizeSection(KrViewProperties::Name, nameSize - delta);
0367     }
0368 }
0369 
0370 bool KrInterDetailedView::viewportEvent(QEvent *event)
0371 {
0372     if (event->type() == QEvent::ToolTip) {
0373         // only show tooltip if column is not wide enough to show all text. In this case the column
0374         // data text is abbreviated and the full text is shown as tooltip, see ListModel::data().
0375 
0376         auto *he = dynamic_cast<QHelpEvent *>(event);
0377         const QModelIndex index = indexAt(he->pos());
0378         // name column has a detailed tooltip
0379         if (index.isValid() && index.column() != KrViewProperties::Name) {
0380             int width = header()->sectionSize(index.column());
0381             QString text = index.data(Qt::DisplayRole).toString();
0382 
0383             int textWidth = QFontMetrics(_viewFont).horizontalAdvance(text);
0384 
0385             const int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
0386             textWidth += 2 * textMargin;
0387 
0388             QVariant decor = index.data(Qt::DecorationRole);
0389             if (decor.isValid() && decor.type() == QVariant::Pixmap) {
0390                 QPixmap p = decor.value<QPixmap>();
0391                 textWidth += p.width() + 2 * textMargin;
0392             }
0393 
0394             if (textWidth <= width) {
0395                 QToolTip::hideText();
0396                 event->accept();
0397                 return true;
0398             }
0399         }
0400     }
0401     return QTreeView::viewportEvent(event);
0402 }
0403 
0404 void KrInterDetailedView::drawRow(QPainter *painter, const QStyleOptionViewItem &options, const QModelIndex &index) const
0405 {
0406     QTreeView::drawRow(painter, options, index);
0407     // (may) draw dashed line border around current item row. This is done internally in
0408     // QTreeView::drawRow() only when panel is focused, we have to repeat it here.
0409     if (index == currentIndex() && drawCurrent()) {
0410         QStyleOptionFocusRect o;
0411         o.backgroundColor = options.palette.color(QPalette::Normal, QPalette::Background);
0412 
0413         const QRect focusRect(0, options.rect.y(), header()->length(), options.rect.height());
0414         o.rect = style()->visualRect(layoutDirection(), viewport()->rect(), focusRect);
0415 
0416         style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter);
0417     }
0418 }
0419 
0420 void KrInterDetailedView::setSortMode(KrViewProperties::ColumnType sortColumn, bool descending)
0421 {
0422     Qt::SortOrder sortDir = descending ? Qt::DescendingOrder : Qt::AscendingOrder;
0423     sortByColumn(sortColumn, sortDir);
0424 }
0425 
0426 void KrInterDetailedView::setFileIconSize(int size)
0427 {
0428     KrView::setFileIconSize(size);
0429     setIconSize(QSize(fileIconSize(), fileIconSize()));
0430 }
0431 
0432 QRect KrInterDetailedView::itemRect(const FileItem *item)
0433 {
0434     QRect r = visualRect(_model->fileItemIndex(item));
0435     r.setLeft(0);
0436     r.setWidth(header()->length());
0437     return r;
0438 }
0439 
0440 void KrInterDetailedView::copySettingsFrom(KrView *other)
0441 {
0442     if (other->instance() == instance()) { // the other view is of the same type
0443         auto *v = dynamic_cast<KrInterDetailedView *>(other);
0444         _autoResizeColumns = v->_autoResizeColumns;
0445         header()->restoreState(v->header()->saveState());
0446         _model->setExtensionEnabled(!isColumnHidden(KrViewProperties::Ext));
0447         recalculateColumnSizes();
0448         setFileIconSize(v->fileIconSize());
0449     }
0450 }