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 }