File indexing completed on 2024-05-19 05:05:50
0001 /*************************************************************************** 0002 * SPDX-License-Identifier: GPL-2.0-or-later 0003 * * 0004 * SPDX-FileCopyrightText: 2004-2023 Thomas Fischer <fischer@unix-ag.uni-kl.de> 0005 * * 0006 * This program is free software; you can redistribute it and/or modify * 0007 * it under the terms of the GNU General Public License as published by * 0008 * the Free Software Foundation; either version 2 of the License, or * 0009 * (at your option) any later version. * 0010 * * 0011 * This program is distributed in the hope that it will be useful, * 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0014 * GNU General Public License for more details. * 0015 * * 0016 * You should have received a copy of the GNU General Public License * 0017 * along with this program; if not, see <https://www.gnu.org/licenses/>. * 0018 ***************************************************************************/ 0019 0020 #include "documentlist.h" 0021 0022 #include <QStringListModel> 0023 #include <QTimer> 0024 #include <QGridLayout> 0025 #include <QLabel> 0026 #include <QPainter> 0027 #include <QPushButton> 0028 #include <QApplication> 0029 #include <QIcon> 0030 #include <QAction> 0031 0032 #include <kio_version.h> 0033 #include <kwidgetsaddons_version.h> 0034 #include <KIconLoader> 0035 #include <KLocalizedString> 0036 #include <KActionMenu> 0037 #include <KDirOperator> 0038 #include <KService> 0039 #include <KMessageBox> 0040 0041 #include <KBibTeX> 0042 0043 class DirOperatorWidget : public QWidget 0044 { 0045 Q_OBJECT 0046 0047 public: 0048 KDirOperator *dirOperator; 0049 0050 DirOperatorWidget(QWidget *parent) 0051 : QWidget(parent) { 0052 QGridLayout *layout = new QGridLayout(this); 0053 layout->setContentsMargins(0, 0, 0, 0); 0054 layout->setColumnStretch(0, 0); 0055 layout->setColumnStretch(1, 0); 0056 layout->setColumnStretch(2, 1); 0057 0058 QPushButton *buttonUp = new QPushButton(QIcon::fromTheme(QStringLiteral("go-up")), QString(), this); 0059 buttonUp->setToolTip(i18n("One level up")); 0060 layout->addWidget(buttonUp, 0, 0, 1, 1); 0061 0062 QPushButton *buttonHome = new QPushButton(QIcon::fromTheme(QStringLiteral("user-home")), QString(), this); 0063 buttonHome->setToolTip(i18n("Go to Home folder")); 0064 layout->addWidget(buttonHome, 0, 1, 1, 1); 0065 0066 dirOperator = new KDirOperator(QUrl(QStringLiteral("file:") + QDir::homePath()), this); 0067 layout->addWidget(dirOperator, 1, 0, 1, 3); 0068 #if KIO_VERSION < QT_VERSION_CHECK(5, 100, 0) 0069 dirOperator->setView(KFile::Detail); 0070 #else // >= 5.100.0 0071 dirOperator->setViewMode(KFile::Detail); 0072 #endif // KIO_VERSION < QT_VERSION_CHECK(5, 100, 0) 0073 0074 connect(buttonUp, &QPushButton::clicked, dirOperator, &KDirOperator::cdUp); 0075 connect(buttonHome, &QPushButton::clicked, dirOperator, &KDirOperator::home); 0076 } 0077 }; 0078 0079 DocumentListDelegate::DocumentListDelegate(QObject *parent) 0080 : QStyledItemDelegate(parent) 0081 { 0082 /// nothing 0083 } 0084 0085 void DocumentListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 0086 { 0087 int height = option.rect.height(); 0088 0089 QStyle *style = QApplication::style(); 0090 style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr); 0091 0092 painter->save(); 0093 0094 if (option.state & QStyle::State_Selected) { 0095 painter->setPen(QPen(option.palette.highlightedText().color())); 0096 } else { 0097 painter->setPen(QPen(option.palette.text().color())); 0098 } 0099 0100 OpenFileInfo *ofi = qvariant_cast<OpenFileInfo *>(index.data(Qt::UserRole)); 0101 0102 if (OpenFileInfoManager::instance().currentFile() == ofi) { 0103 /// for the currently open file, use a bold font to write file name 0104 QFont font = painter->font(); 0105 font.setBold(true); 0106 painter->setFont(font); 0107 } 0108 0109 QRect textRect = option.rect; 0110 textRect.setLeft(textRect.left() + height + 4); 0111 textRect.setHeight(height / 2); 0112 painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, index.data(Qt::DisplayRole).toString()); 0113 0114 textRect = option.rect; 0115 textRect.setLeft(textRect.left() + height + 4); 0116 textRect.setTop(textRect.top() + height / 2); 0117 textRect.setHeight(height * 3 / 8); 0118 QFont font = painter->font(); 0119 font.setPointSize(font.pointSize() * 7 / 8); 0120 painter->setFont(font); 0121 painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, ofi->fullCaption()); 0122 0123 QIcon icon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole)); 0124 painter->drawPixmap(option.rect.left() + 1, option.rect.top() + 1, height - 2, height - 2, icon.pixmap(height - 2, height - 2)); 0125 0126 painter->restore(); 0127 0128 } 0129 0130 QSize DocumentListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const 0131 { 0132 QSize size = QStyledItemDelegate::sizeHint(option, index); 0133 size.setHeight(size.height() * 9 / 4); 0134 return size; 0135 } 0136 0137 class DocumentListModel::DocumentListModelPrivate 0138 { 0139 public: 0140 OpenFileInfo::StatusFlag sf; 0141 OpenFileInfoManager::OpenFileInfoList ofiList; 0142 0143 public: 0144 DocumentListModelPrivate(OpenFileInfo::StatusFlag statusFlag, DocumentListModel *parent) 0145 : sf(statusFlag) 0146 { 0147 Q_UNUSED(parent) 0148 } 0149 }; 0150 0151 DocumentListModel::DocumentListModel(OpenFileInfo::StatusFlag statusFlag, QObject *parent) 0152 : QAbstractListModel(parent), d(new DocumentListModel::DocumentListModelPrivate(statusFlag, this)) 0153 { 0154 listsChanged(d->sf); 0155 0156 connect(&OpenFileInfoManager::instance(), &OpenFileInfoManager::flagsChanged, this, &DocumentListModel::listsChanged); 0157 } 0158 0159 DocumentListModel::~DocumentListModel() 0160 { 0161 delete d; 0162 } 0163 0164 int DocumentListModel::rowCount(const QModelIndex &parent) const 0165 { 0166 if (parent != QModelIndex()) return 0; 0167 return d->ofiList.count(); 0168 } 0169 0170 QVariant DocumentListModel::data(const QModelIndex &index, int role) const 0171 { 0172 if (index.row() < 0 || index.row() >= rowCount()) return QVariant(); 0173 0174 OpenFileInfo *openFileInfo = d->ofiList[index.row()]; 0175 const QString iconName = openFileInfo->mimeType().replace(QLatin1Char('/'), QLatin1Char('-')); 0176 0177 switch (role) { 0178 case Qt::DisplayRole: return openFileInfo->shortCaption(); 0179 case Qt::DecorationRole: { 0180 /// determine mime type-based icon and overlays (e.g. for modified files) 0181 QStringList overlays; 0182 if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::Favorite)) 0183 overlays << QStringLiteral("favorites"); 0184 else 0185 overlays << QString(); 0186 if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::RecentlyUsed)) 0187 overlays << QStringLiteral("document-open-recent"); 0188 else 0189 overlays << QString(); 0190 if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::Open)) 0191 overlays << QStringLiteral("folder-open"); 0192 else 0193 overlays << QString(); 0194 if (openFileInfo->isModified()) 0195 overlays << QStringLiteral("document-save"); 0196 else 0197 overlays << QString(); 0198 return KDE::icon(iconName, overlays, nullptr); 0199 } 0200 case Qt::ToolTipRole: { 0201 QString htmlText(QString(QStringLiteral("<qt><img src=\"%1\"> <b>%2</b>")).arg(KIconLoader::global()->iconPath(iconName, KIconLoader::Small), openFileInfo->shortCaption())); 0202 const QUrl url = openFileInfo->url(); 0203 if (url.isValid()) { 0204 QString path(QFileInfo(url.path()).path()); 0205 if (!path.endsWith(QLatin1Char('/'))) 0206 path.append(QLatin1Char('/')); 0207 htmlText.append(i18n("<br/><small>located in <b>%1</b></small>", path)); 0208 } 0209 QStringList flagListItems; 0210 if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::Favorite)) 0211 flagListItems << i18n("Favorite"); 0212 if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::RecentlyUsed)) 0213 flagListItems << i18n("Recently Used"); 0214 if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::Open)) 0215 flagListItems << i18n("Open"); 0216 if (openFileInfo->isModified()) 0217 flagListItems << i18n("Modified"); 0218 if (!flagListItems.empty()) { 0219 htmlText.append(QStringLiteral("<ul>")); 0220 for (const QString &flagListItem : const_cast<const QStringList &>(flagListItems)) { 0221 htmlText.append(QString(QStringLiteral("<li>%1</li>")).arg(flagListItem)); 0222 } 0223 htmlText.append(QStringLiteral("</ul>")); 0224 } 0225 htmlText.append(QStringLiteral("</qt>")); 0226 return htmlText; 0227 } 0228 case Qt::UserRole: return QVariant::fromValue(openFileInfo); 0229 default: return QVariant(); 0230 } 0231 } 0232 0233 QVariant DocumentListModel::headerData(int section, Qt::Orientation orientation, int role) const 0234 { 0235 if (orientation != Qt::Horizontal || section != 0 || role != Qt::DisplayRole) return QVariant(); 0236 return QVariant(QStringLiteral("List of Files")); 0237 } 0238 0239 void DocumentListModel::listsChanged(OpenFileInfo::StatusFlags statusFlags) 0240 { 0241 if (statusFlags.testFlag(d->sf)) { 0242 beginResetModel(); 0243 d->ofiList = OpenFileInfoManager::instance().filteredItems(d->sf); 0244 endResetModel(); 0245 } 0246 } 0247 0248 class DocumentListView::DocumentListViewPrivate 0249 { 0250 private: 0251 DocumentListView *p; 0252 0253 public: 0254 QAction *actionAddToFav, *actionRemFromFav; 0255 QAction *actionCloseFile, *actionOpenFile; 0256 KActionMenu *actionOpenMenu; 0257 QList<QAction *> openMenuActions; 0258 0259 DocumentListViewPrivate(DocumentListView *parent) 0260 : p(parent), actionAddToFav(nullptr), actionRemFromFav(nullptr), actionCloseFile(nullptr), actionOpenFile(nullptr), actionOpenMenu(nullptr) 0261 { 0262 /// nothing 0263 } 0264 0265 void openFileWithService(const KPluginMetaData &service) 0266 { 0267 const QModelIndex modelIndex = p->currentIndex(); 0268 if (modelIndex != QModelIndex()) { 0269 OpenFileInfo *ofi = qvariant_cast<OpenFileInfo *>(modelIndex.data(Qt::UserRole)); 0270 if (!ofi->isModified() || ( 0271 #if KWIDGETSADDONS_VERSION < QT_VERSION_CHECK(5, 100, 0) 0272 KMessageBox::questionYesNo(p, i18n("The current document has to be saved before switching the viewer/editor component."), i18n("Save before switching?"), KGuiItem(i18n("Save document"), QIcon::fromTheme(QStringLiteral("document-save"))), KGuiItem(i18n("Do not switch"), QIcon::fromTheme(QStringLiteral("dialog-cancel")))) == KMessageBox::Yes 0273 #else // >= 5.100.0 0274 KMessageBox::questionTwoActions(p, i18n("The current document has to be saved before switching the viewer/editor component."), i18n("Save before switching?"), KGuiItem(i18n("Save document"), QIcon::fromTheme(QStringLiteral("document-save"))), KGuiItem(i18n("Do not switch"), QIcon::fromTheme(QStringLiteral("dialog-cancel")))) == KMessageBox::PrimaryAction 0275 #endif // KWIDGETSADDONS_VERSION < QT_VERSION_CHECK(5, 100, 0) 0276 && ofi->save())) 0277 OpenFileInfoManager::instance().setCurrentFile(ofi, service); 0278 } 0279 } 0280 }; 0281 0282 DocumentListView::DocumentListView(OpenFileInfo::StatusFlag statusFlag, QWidget *parent) 0283 : QListView(parent), d(new DocumentListViewPrivate(this)) 0284 { 0285 setContextMenuPolicy(Qt::ActionsContextMenu); 0286 setItemDelegate(new DocumentListDelegate(this)); 0287 0288 if (statusFlag == OpenFileInfo::StatusFlag::Open) { 0289 d->actionCloseFile = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Close File"), this); 0290 connect(d->actionCloseFile, &QAction::triggered, this, &DocumentListView::closeFile); 0291 addAction(d->actionCloseFile); 0292 } else { 0293 d->actionOpenFile = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open File"), this); 0294 connect(d->actionOpenFile, &QAction::triggered, this, &DocumentListView::openFile); 0295 addAction(d->actionOpenFile); 0296 } 0297 0298 d->actionOpenMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open with"), this); 0299 addAction(d->actionOpenMenu); 0300 0301 if (statusFlag == OpenFileInfo::StatusFlag::Favorite) { 0302 d->actionRemFromFav = new QAction(QIcon::fromTheme(QStringLiteral("favorites")), i18n("Remove from Favorites"), this); 0303 connect(d->actionRemFromFav, &QAction::triggered, this, &DocumentListView::removeFromFavorites); 0304 addAction(d->actionRemFromFav); 0305 } else { 0306 d->actionAddToFav = new QAction(QIcon::fromTheme(QStringLiteral("favorites")), i18n("Add to Favorites"), this); 0307 connect(d->actionAddToFav, &QAction::triggered, this, &DocumentListView::addToFavorites); 0308 addAction(d->actionAddToFav); 0309 } 0310 0311 connect(this, &DocumentListView::activated, this, &DocumentListView::openFile); 0312 0313 currentChanged(QModelIndex(), QModelIndex()); 0314 } 0315 0316 DocumentListView::~DocumentListView() 0317 { 0318 delete d; 0319 } 0320 0321 void DocumentListView::addToFavorites() 0322 { 0323 QModelIndex modelIndex = currentIndex(); 0324 if (modelIndex != QModelIndex()) { 0325 OpenFileInfo *ofi = qvariant_cast<OpenFileInfo *>(modelIndex.data(Qt::UserRole)); 0326 ofi->addFlags(OpenFileInfo::StatusFlag::Favorite); 0327 } 0328 } 0329 0330 void DocumentListView::removeFromFavorites() 0331 { 0332 QModelIndex modelIndex = currentIndex(); 0333 if (modelIndex != QModelIndex()) { 0334 OpenFileInfo *ofi = qvariant_cast<OpenFileInfo *>(modelIndex.data(Qt::UserRole)); 0335 ofi->removeFlags(OpenFileInfo::StatusFlag::Favorite); 0336 } 0337 } 0338 0339 void DocumentListView::openFile() 0340 { 0341 QModelIndex modelIndex = currentIndex(); 0342 if (modelIndex != QModelIndex()) { 0343 OpenFileInfo *ofi = qvariant_cast<OpenFileInfo *>(modelIndex.data(Qt::UserRole)); 0344 OpenFileInfoManager::instance().setCurrentFile(ofi); 0345 } 0346 } 0347 0348 void DocumentListView::closeFile() 0349 { 0350 QModelIndex modelIndex = currentIndex(); 0351 if (modelIndex != QModelIndex()) { 0352 OpenFileInfo *ofi = qvariant_cast<OpenFileInfo *>(modelIndex.data(Qt::UserRole)); 0353 OpenFileInfoManager::instance().close(ofi); 0354 } 0355 } 0356 0357 void DocumentListView::currentChanged(const QModelIndex ¤t, const QModelIndex &) 0358 { 0359 bool hasCurrent = current != QModelIndex(); 0360 OpenFileInfo *ofi = hasCurrent ? qvariant_cast<OpenFileInfo *>(current.data(Qt::UserRole)) : nullptr; 0361 bool isOpen = hasCurrent ? ofi->flags().testFlag(OpenFileInfo::StatusFlag::Open) : false; 0362 bool isFavorite = hasCurrent ? ofi->flags().testFlag(OpenFileInfo::StatusFlag::Favorite) : false; 0363 bool hasName = hasCurrent ? ofi->flags().testFlag(OpenFileInfo::StatusFlag::HasName) : false; 0364 0365 if (d->actionOpenFile != nullptr) 0366 d->actionOpenFile->setEnabled(hasCurrent && !isOpen); 0367 if (d->actionCloseFile != nullptr) 0368 d->actionCloseFile->setEnabled(hasCurrent && isOpen); 0369 if (d->actionAddToFav != nullptr) 0370 d->actionAddToFav->setEnabled(hasCurrent && !isFavorite && hasName); 0371 if (d->actionRemFromFav != nullptr) 0372 d->actionRemFromFav->setEnabled(hasCurrent && isFavorite); 0373 0374 for (QAction *action : const_cast<const QList<QAction *> &>(d->openMenuActions)) { 0375 d->actionOpenMenu->removeAction(action); 0376 } 0377 if (ofi != nullptr) { 0378 const QVector<KPluginMetaData> services = ofi->listOfServices(); 0379 for (const KPluginMetaData &service : services) { 0380 QAction *menuItem = new QAction(QIcon::fromTheme(service.iconName()), service.name(), this); 0381 d->actionOpenMenu->addAction(menuItem); 0382 d->openMenuActions << menuItem; 0383 0384 connect(menuItem, &QAction::triggered, this, [this, service]() { 0385 d->openFileWithService(service); 0386 }); 0387 } 0388 } 0389 d->actionOpenMenu->setEnabled(!d->openMenuActions.isEmpty()); 0390 } 0391 0392 class DocumentList::DocumentListPrivate 0393 { 0394 public: 0395 DocumentListView *listOpenFiles; 0396 DocumentListView *listRecentFiles; 0397 DocumentListView *listFavorites; 0398 DirOperatorWidget *dirOperator; 0399 0400 DocumentListPrivate(DocumentList *p) { 0401 listOpenFiles = new DocumentListView(OpenFileInfo::StatusFlag::Open, p); 0402 DocumentListModel *model = new DocumentListModel(OpenFileInfo::StatusFlag::Open, listOpenFiles); 0403 listOpenFiles->setModel(model); 0404 p->addTab(listOpenFiles, QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open Files")); 0405 0406 listRecentFiles = new DocumentListView(OpenFileInfo::StatusFlag::RecentlyUsed, p); 0407 model = new DocumentListModel(OpenFileInfo::StatusFlag::RecentlyUsed, listRecentFiles); 0408 listRecentFiles->setModel(model); 0409 p->addTab(listRecentFiles, QIcon::fromTheme(QStringLiteral("document-open-recent")), i18n("Recently Used")); 0410 0411 listFavorites = new DocumentListView(OpenFileInfo::StatusFlag::Favorite, p); 0412 model = new DocumentListModel(OpenFileInfo::StatusFlag::Favorite, listFavorites); 0413 listFavorites->setModel(model); 0414 p->addTab(listFavorites, QIcon::fromTheme(QStringLiteral("favorites")), i18n("Favorites")); 0415 0416 dirOperator = new DirOperatorWidget(p); 0417 p->addTab(dirOperator, QIcon::fromTheme(QStringLiteral("system-file-manager")), i18n("Filesystem Browser")); 0418 connect(dirOperator->dirOperator, &KDirOperator::fileSelected, p, &DocumentList::fileSelected); 0419 } 0420 }; 0421 0422 DocumentList::DocumentList(QWidget *parent) 0423 : QTabWidget(parent), d(new DocumentListPrivate(this)) 0424 { 0425 setDocumentMode(true); 0426 } 0427 0428 DocumentList::~DocumentList() 0429 { 0430 delete d; 0431 } 0432 0433 void DocumentList::fileSelected(const KFileItem &item) 0434 { 0435 if (item.isFile() && item.isReadable()) 0436 Q_EMIT openFile(item.url()); 0437 } 0438 0439 #include "documentlist.moc"