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

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 "listmodel.h"
0010 
0011 #include "../FileSystem/fileitem.h"
0012 #include "../defaults.h"
0013 #include "../krcolorcache.h"
0014 #include "../krglobal.h"
0015 #include "../krpanel.h"
0016 #include "krinterview.h"
0017 #include "krviewproperties.h"
0018 
0019 #include <KConfigCore/KSharedConfig>
0020 #include <KI18n/KLocalizedString>
0021 
0022 ListModel::ListModel(KrInterView *view)
0023     : QAbstractListModel(nullptr)
0024     , _extensionEnabled(true)
0025     , _view(view)
0026     , _dummyFileItem(nullptr)
0027     , _ready(false)
0028     , _justForSizeHint(false)
0029     , _alternatingTable(false)
0030 {
0031     KConfigGroup grpSvr(krConfig, "Look&Feel");
0032     _defaultFont = grpSvr.readEntry("Filelist Font", _FilelistFont);
0033 }
0034 
0035 void ListModel::populate(const QList<FileItem *> &files, FileItem *dummy)
0036 {
0037     _fileItems = files;
0038     _dummyFileItem = dummy;
0039     _ready = true;
0040 
0041     if (lastSortOrder() != KrViewProperties::NoColumn)
0042         sort();
0043     else {
0044         emit layoutAboutToBeChanged();
0045         for (int i = 0; i < _fileItems.count(); i++) {
0046             updateIndices(_fileItems[i], i);
0047         }
0048         emit layoutChanged();
0049     }
0050 }
0051 
0052 ListModel::~ListModel() = default;
0053 
0054 void ListModel::clear(bool emitLayoutChanged)
0055 {
0056     if (!_fileItems.count())
0057         return;
0058 
0059     emit layoutAboutToBeChanged();
0060     // clear persistent indexes
0061     QModelIndexList oldPersistentList = persistentIndexList();
0062     QModelIndexList newPersistentList;
0063     newPersistentList.reserve(oldPersistentList.size());
0064     for (int i = 0; i < oldPersistentList.size(); ++i)
0065         newPersistentList.append(QModelIndex());
0066     changePersistentIndexList(oldPersistentList, newPersistentList);
0067 
0068     _fileItems.clear();
0069     _fileItemNdx.clear();
0070     _nameNdx.clear();
0071     _urlNdx.clear();
0072     _dummyFileItem = nullptr;
0073 
0074     if (emitLayoutChanged)
0075         emit layoutChanged();
0076 }
0077 
0078 int ListModel::rowCount(const QModelIndex & /*parent*/) const
0079 {
0080     return _fileItems.count();
0081 }
0082 
0083 int ListModel::columnCount(const QModelIndex & /*parent*/) const
0084 {
0085     return KrViewProperties::MAX_COLUMNS;
0086 }
0087 
0088 QVariant ListModel::data(const QModelIndex &index, int role) const
0089 {
0090     if (!index.isValid() || index.row() >= rowCount())
0091         return QVariant();
0092     FileItem *fileitem = _fileItems.at(index.row());
0093     if (fileitem == nullptr)
0094         return QVariant();
0095 
0096     switch (role) {
0097     case Qt::FontRole:
0098         return _defaultFont;
0099     case Qt::EditRole: {
0100         if (index.column() == 0) {
0101             return fileitem->getName();
0102         }
0103         return QVariant();
0104     }
0105     case Qt::UserRole: {
0106         if (index.column() == 0) {
0107             return nameWithoutExtension(fileitem, false);
0108         }
0109         return QVariant();
0110     }
0111     case Qt::ToolTipRole: {
0112         if (index.column() == KrViewProperties::Name) {
0113             return fileitem == _dummyFileItem ? QVariant() : toolTipText(fileitem);
0114         }
0115 #if __GNUC__ >= 7
0116         [[gnu::fallthrough]];
0117 #endif
0118     }
0119     case Qt::DisplayRole: {
0120         switch (index.column()) {
0121         case KrViewProperties::Name: {
0122             return nameWithoutExtension(fileitem);
0123         }
0124         case KrViewProperties::Ext: {
0125             QString nameOnly = nameWithoutExtension(fileitem);
0126             const QString &fileitemName = fileitem->getName();
0127             return fileitemName.mid(nameOnly.length() + 1);
0128         }
0129         case KrViewProperties::Size: {
0130             if (fileitem->getUISize() == (KIO::filesize_t)-1) {
0131                 // HACK add <> brackets AFTER translating - otherwise KUIT thinks it's a tag
0132                 static QString label = QString("<") + i18nc("Show the string 'DIR' instead of file size in detailed view (for folders)", "DIR") + '>';
0133                 return label;
0134             } else
0135                 return KrView::sizeText(properties(), fileitem->getUISize());
0136         }
0137         case KrViewProperties::Type: {
0138             if (fileitem == _dummyFileItem)
0139                 return QVariant();
0140             const QString mimeType = KrView::mimeTypeText(fileitem);
0141             return mimeType.isEmpty() ? QVariant() : mimeType;
0142         }
0143         case KrViewProperties::Modified: {
0144             return fileitem == _dummyFileItem ? QVariant() : dateText(fileitem->getModificationTime());
0145         }
0146         case KrViewProperties::Changed: {
0147             return fileitem == _dummyFileItem ? QVariant() : dateText(fileitem->getChangeTime());
0148         }
0149         case KrViewProperties::Accessed: {
0150             return fileitem == _dummyFileItem ? QVariant() : dateText(fileitem->getAccessTime());
0151         }
0152         case KrViewProperties::Permissions: {
0153             if (fileitem == _dummyFileItem)
0154                 return QVariant();
0155             return KrView::permissionsText(properties(), fileitem);
0156         }
0157         case KrViewProperties::KrPermissions: {
0158             if (fileitem == _dummyFileItem)
0159                 return QVariant();
0160             return KrView::krPermissionText(fileitem);
0161         }
0162         case KrViewProperties::Owner: {
0163             if (fileitem == _dummyFileItem)
0164                 return QVariant();
0165             return fileitem->getOwner();
0166         }
0167         case KrViewProperties::Group: {
0168             if (fileitem == _dummyFileItem)
0169                 return QVariant();
0170             return fileitem->getGroup();
0171         }
0172         default:
0173             return QString();
0174         }
0175         return QVariant();
0176     }
0177     case Qt::DecorationRole: {
0178         switch (index.column()) {
0179         case KrViewProperties::Name: {
0180             if (properties()->displayIcons) {
0181                 if (_justForSizeHint)
0182                     return QPixmap(_view->fileIconSize(), _view->fileIconSize());
0183                 return QIcon(_view->getIcon(fileitem));
0184             }
0185             break;
0186         }
0187         default:
0188             break;
0189         }
0190         return QVariant();
0191     }
0192     case Qt::TextAlignmentRole: {
0193         switch (index.column()) {
0194         case KrViewProperties::Size:
0195             return QVariant(Qt::AlignRight | Qt::AlignVCenter);
0196         default:
0197             return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
0198         }
0199         return QVariant();
0200     }
0201     case Qt::BackgroundRole:
0202     case Qt::ForegroundRole: {
0203         KrColorItemType colorItemType;
0204         colorItemType.m_activePanel = _view->isFocused();
0205         int actRow = index.row();
0206         if (_alternatingTable) {
0207             int itemNum = _view->itemsPerPage();
0208             if (itemNum == 0)
0209                 itemNum++;
0210             if ((itemNum & 1) == 0)
0211                 actRow += (actRow / itemNum);
0212         }
0213         colorItemType.m_alternateBackgroundColor = (actRow & 1);
0214         colorItemType.m_currentItem = _view->getCurrentIndex().row() == index.row();
0215         colorItemType.m_selectedItem = _view->isSelected(index);
0216         if (fileitem->isSymLink()) {
0217             if (fileitem->isBrokenLink())
0218                 colorItemType.m_fileType = KrColorItemType::InvalidSymlink;
0219             else
0220                 colorItemType.m_fileType = KrColorItemType::Symlink;
0221         } else if (fileitem->isDir())
0222             colorItemType.m_fileType = KrColorItemType::Directory;
0223         else if (fileitem->isExecutable())
0224             colorItemType.m_fileType = KrColorItemType::Executable;
0225         else
0226             colorItemType.m_fileType = KrColorItemType::File;
0227 
0228         KrColorGroup cols;
0229         KrColorCache::getColorCache().getColors(cols, colorItemType);
0230 
0231         if (colorItemType.m_selectedItem) {
0232             if (role == Qt::ForegroundRole)
0233                 return cols.highlightedText();
0234             else
0235                 return cols.highlight();
0236         }
0237         if (role == Qt::ForegroundRole)
0238             return cols.text();
0239         else
0240             return cols.background();
0241     }
0242     default:
0243         return QVariant();
0244     }
0245 }
0246 
0247 bool ListModel::setData(const QModelIndex &index, const QVariant &value, int role)
0248 {
0249     if (role == Qt::EditRole && index.isValid()) {
0250         if (index.row() < rowCount() && index.row() >= 0) {
0251             FileItem *fileitem = _fileItems.at(index.row());
0252             if (fileitem == nullptr)
0253                 return false;
0254             _view->op()->emitRenameItem(fileitem->getName(), value.toString());
0255         }
0256     }
0257     if (role == Qt::UserRole && index.isValid()) {
0258         _justForSizeHint = value.toBool();
0259     }
0260     return QAbstractListModel::setData(index, value, role);
0261 }
0262 
0263 void ListModel::sort(int column, Qt::SortOrder order)
0264 {
0265     _view->sortModeUpdated(column, order);
0266 
0267     if (lastSortOrder() == KrViewProperties::NoColumn)
0268         return;
0269 
0270     emit layoutAboutToBeChanged();
0271 
0272     QModelIndexList oldPersistentList = persistentIndexList();
0273 
0274     KrSort::Sorter sorter(createSorter());
0275     sorter.sort();
0276 
0277     _fileItems.clear();
0278     _fileItemNdx.clear();
0279     _nameNdx.clear();
0280     _urlNdx.clear();
0281 
0282     bool sortOrderChanged = false;
0283     QHash<int, int> changeMap;
0284     for (int i = 0; i < sorter.items().count(); ++i) {
0285         const KrSort::SortProps *props = sorter.items()[i];
0286         _fileItems.append(props->fileitem());
0287         changeMap[props->originalIndex()] = i;
0288         if (i != props->originalIndex())
0289             sortOrderChanged = true;
0290         updateIndices(props->fileitem(), i);
0291     }
0292 
0293     QModelIndexList newPersistentList;
0294     foreach (const QModelIndex &mndx, oldPersistentList)
0295         newPersistentList << index(changeMap[mndx.row()], mndx.column());
0296 
0297     changePersistentIndexList(oldPersistentList, newPersistentList);
0298 
0299     emit layoutChanged();
0300     if (sortOrderChanged)
0301         _view->makeItemVisible(_view->getCurrentKrViewItem());
0302 }
0303 
0304 QModelIndex ListModel::addItem(FileItem *fileitem)
0305 {
0306     emit layoutAboutToBeChanged();
0307 
0308     if (lastSortOrder() == KrViewProperties::NoColumn) {
0309         int idx = _fileItems.count();
0310         _fileItems.append(fileitem);
0311         updateIndices(fileitem, idx);
0312         emit layoutChanged();
0313         return index(idx, 0);
0314     }
0315 
0316     QModelIndexList oldPersistentList = persistentIndexList();
0317 
0318     KrSort::Sorter sorter(createSorter());
0319 
0320     const bool isDummy = fileitem == _dummyFileItem;
0321     const int insertIndex = sorter.insertIndex(fileitem, isDummy, customSortData(fileitem));
0322     if (insertIndex != _fileItems.count())
0323         _fileItems.insert(insertIndex, fileitem);
0324     else
0325         _fileItems.append(fileitem);
0326 
0327     for (int i = insertIndex; i < _fileItems.count(); ++i) {
0328         updateIndices(_fileItems[i], i);
0329     }
0330 
0331     QModelIndexList newPersistentList;
0332     foreach (const QModelIndex &mndx, oldPersistentList) {
0333         int newRow = mndx.row();
0334         if (newRow >= insertIndex)
0335             newRow++;
0336         newPersistentList << index(newRow, mndx.column());
0337     }
0338 
0339     changePersistentIndexList(oldPersistentList, newPersistentList);
0340     emit layoutChanged();
0341 
0342     return index(insertIndex, 0);
0343 }
0344 
0345 void ListModel::removeItem(FileItem *fileItem)
0346 {
0347     const int rowToRemove = _fileItems.indexOf(fileItem);
0348     if (rowToRemove < 0)
0349         return;
0350 
0351     beginRemoveRows(QModelIndex(), rowToRemove, rowToRemove);
0352 
0353     _fileItems.removeAt(rowToRemove);
0354 
0355     _fileItemNdx.remove(fileItem);
0356     _nameNdx.remove(fileItem->getName());
0357     _urlNdx.remove(fileItem->getUrl());
0358 
0359     // update indices for fileItems following the removed fileitem
0360     for (int i = rowToRemove; i < _fileItems.count(); i++) {
0361         updateIndices(_fileItems[i], i);
0362     }
0363 
0364     endRemoveRows();
0365 }
0366 
0367 QVariant ListModel::headerData(int section, Qt::Orientation orientation, int role) const
0368 {
0369     // ignore anything that's not display, and not horizontal
0370     if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
0371         return QVariant();
0372 
0373     switch (section) {
0374     case KrViewProperties::Name:
0375         return i18nc("File property", "Name");
0376     case KrViewProperties::Ext:
0377         return i18nc("File property", "Ext");
0378     case KrViewProperties::Size:
0379         return i18nc("File property", "Size");
0380     case KrViewProperties::Type:
0381         return i18nc("File property", "Type");
0382     case KrViewProperties::Modified:
0383         return i18nc("File property", "Modified");
0384     case KrViewProperties::Changed:
0385         return i18nc("File property", "Changed");
0386     case KrViewProperties::Accessed:
0387         return i18nc("File property", "Accessed");
0388     case KrViewProperties::Permissions:
0389         return i18nc("File property", "Perms");
0390     case KrViewProperties::KrPermissions:
0391         return i18nc("File property", "rwx");
0392     case KrViewProperties::Owner:
0393         return i18nc("File property", "Owner");
0394     case KrViewProperties::Group:
0395         return i18nc("File property", "Group");
0396     }
0397     return QString();
0398 }
0399 
0400 const KrViewProperties *ListModel::properties() const
0401 {
0402     return _view->properties();
0403 }
0404 
0405 FileItem *ListModel::fileItemAt(const QModelIndex &index)
0406 {
0407     if (!index.isValid() || index.row() < 0 || index.row() >= _fileItems.count())
0408         return nullptr;
0409     return _fileItems[index.row()];
0410 }
0411 
0412 const QModelIndex &ListModel::fileItemIndex(const FileItem *fileitem)
0413 {
0414     return _fileItemNdx[const_cast<FileItem *>(fileitem)];
0415 }
0416 
0417 const QModelIndex &ListModel::nameIndex(const QString &st)
0418 {
0419     return _nameNdx[st];
0420 }
0421 
0422 Qt::ItemFlags ListModel::flags(const QModelIndex &index) const
0423 {
0424     Qt::ItemFlags flags = QAbstractListModel::flags(index);
0425 
0426     if (!index.isValid())
0427         return flags;
0428 
0429     if (index.row() >= rowCount())
0430         return flags;
0431     FileItem *fileitem = _fileItems.at(index.row());
0432     if (fileitem == _dummyFileItem) {
0433         flags = (flags & (~Qt::ItemIsSelectable)) | Qt::ItemIsDropEnabled;
0434     } else
0435         flags = flags | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
0436     return flags;
0437 }
0438 
0439 Qt::SortOrder ListModel::lastSortDir() const
0440 {
0441     return (properties()->sortOptions & KrViewProperties::Descending) ? Qt::DescendingOrder : Qt::AscendingOrder;
0442 }
0443 
0444 int ListModel::lastSortOrder() const
0445 {
0446     return properties()->sortColumn;
0447 }
0448 
0449 QString ListModel::nameWithoutExtension(const FileItem *fileItem, bool checkEnabled) const
0450 {
0451     if ((checkEnabled && !_extensionEnabled) || fileItem->isDir())
0452         return fileItem->getName();
0453     // check if the file has an extension
0454     const QString &fileItemName = fileItem->getName();
0455     int loc = fileItemName.lastIndexOf('.');
0456     // avoid mishandling of .bashrc and friend
0457     // and virtfs / search result names like "/dir/.file" which would become "/dir/"
0458     if (loc > 0 && fileItemName.lastIndexOf('/') < loc) {
0459         // check if it has one of the predefined 'atomic extensions'
0460         for (const auto &atomicExtension : properties()->atomicExtensions) {
0461             if (fileItemName.endsWith(atomicExtension) && fileItemName != atomicExtension) {
0462                 loc = fileItemName.length() - atomicExtension.length();
0463                 break;
0464             }
0465         }
0466     } else
0467         return fileItemName;
0468     return fileItemName.left(loc);
0469 }
0470 
0471 const QModelIndex &ListModel::indexFromUrl(const QUrl &url)
0472 {
0473     return _urlNdx[url];
0474 }
0475 
0476 KrSort::Sorter ListModel::createSorter()
0477 {
0478     KrSort::Sorter sorter(_fileItems.count(), properties(), lessThanFunc(), greaterThanFunc());
0479     for (int i = 0; i < _fileItems.count(); i++)
0480         sorter.addItem(_fileItems[i], _fileItems[i] == _dummyFileItem, i, customSortData(_fileItems[i]));
0481     return sorter;
0482 }
0483 
0484 void ListModel::updateIndices(FileItem *file, int i)
0485 {
0486     _fileItemNdx[file] = index(i, 0);
0487     _nameNdx[file->getName()] = index(i, 0);
0488     _urlNdx[file->getUrl()] = index(i, 0);
0489 }
0490 
0491 QString ListModel::toolTipText(FileItem *fileItem) const
0492 {
0493     //"<p style='white-space:pre'>"; // disable automatic word-wrap
0494     QString text = "<b>" + fileItem->getName() + "</b><hr>";
0495     if (fileItem->getUISize() != (KIO::filesize_t)-1) {
0496         const QString size = KrView::sizeText(properties(), fileItem->getUISize());
0497         text += i18n("Size: %1", size) + "<br>";
0498     }
0499     text += i18nc("File property", "Type: %1", KrView::mimeTypeText(fileItem));
0500     text += "<br>" + i18nc("File property", "Modified: %1", dateText(fileItem->getModificationTime()));
0501     text += "<br>" + i18nc("File property", "Changed: %1", dateText(fileItem->getChangeTime()));
0502     text += "<br>" + i18nc("File property", "Last Access: %1", dateText(fileItem->getAccessTime()));
0503     text += "<br>" + i18nc("File property", "Permissions: %1", KrView::permissionsText(properties(), fileItem));
0504     text += "<br>" + i18nc("File property", "Owner: %1", fileItem->getOwner());
0505     text += "<br>" + i18nc("File property", "Group: %1", fileItem->getGroup());
0506     if (fileItem->isSymLink()) {
0507         KLocalizedString ls;
0508         if (fileItem->isBrokenLink())
0509             ls = ki18nc("File property; broken symbolic link", "Link to: %1 - (broken)");
0510         else
0511             ls = ki18nc("File property", "Link to: %1");
0512         text += "<br>" + ls.subs(fileItem->getSymDest()).toString();
0513     }
0514     return text;
0515 }
0516 
0517 QString ListModel::dateText(time_t time)
0518 {
0519     if (time == -1) {
0520         // unknown time
0521         return QString();
0522     }
0523     struct tm *t = localtime((time_t *)&time);
0524 
0525     const QDateTime dateTime(QDate(t->tm_year + 1900, t->tm_mon + 1, t->tm_mday), QTime(t->tm_hour, t->tm_min));
0526     return QLocale().toString(dateTime, QLocale::ShortFormat);
0527 }