File indexing completed on 2024-05-19 05:05:51

0001 /***************************************************************************
0002  *   SPDX-License-Identifier: GPL-2.0-or-later
0003  *                                                                         *
0004  *   SPDX-FileCopyrightText: 2004-2019 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 "mdiwidget.h"
0021 
0022 #include <QVector>
0023 #include <QPair>
0024 #include <QLabel>
0025 #include <QApplication>
0026 #include <QLayout>
0027 #include <QTreeView>
0028 #include <QAbstractItemModel>
0029 #include <QSortFilterProxyModel>
0030 #include <QHeaderView>
0031 #include <QPushButton>
0032 #include <QIcon>
0033 
0034 #include <KParts/Part>
0035 #include <KParts/ReadOnlyPart>
0036 #include <KConfigGroup>
0037 #include <KSharedConfig>
0038 #include <KLocalizedString>
0039 #include <KMessageBox>
0040 
0041 #include <KBibTeX>
0042 #include <file/PartWidget>
0043 #include "logging_program.h"
0044 
0045 class LRUItemModel : public QAbstractItemModel
0046 {
0047     Q_OBJECT
0048 
0049 public:
0050     static const int URLRole = Qt::UserRole + 235;
0051     static const int SortRole = Qt::UserRole + 236;
0052 
0053     LRUItemModel(QObject *parent = nullptr)
0054             : QAbstractItemModel(parent)
0055     {
0056         /// nothing
0057     }
0058 
0059     void reset() {
0060         beginResetModel();
0061         endResetModel();
0062     }
0063 
0064     int columnCount(const QModelIndex &) const override {
0065         return 3;
0066     }
0067 
0068     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
0069         OpenFileInfoManager::OpenFileInfoList ofiList = OpenFileInfoManager::instance().filteredItems(OpenFileInfo::StatusFlag::RecentlyUsed);
0070         if (index.row() < ofiList.count()) {
0071             OpenFileInfo *ofiItem = ofiList[index.row()];
0072             if (index.column() == 0) {
0073                 if (role == Qt::DisplayRole || role == SortRole) {
0074                     const QUrl url = ofiItem->url();
0075                     const QString fileName = url.fileName();
0076                     return fileName.isEmpty() ? squeeze_text(url.url(QUrl::PreferLocalFile), 32) : fileName;
0077                 } else if (role == Qt::DecorationRole)
0078                     return QIcon::fromTheme(ofiItem->mimeType().replace(QLatin1Char('/'), QLatin1Char('-')));
0079                 else if (role == Qt::ToolTipRole)
0080                     return squeeze_text(ofiItem->url().url(QUrl::PreferLocalFile), 64);
0081             } else if (index.column() == 1) {
0082                 if (role == Qt::DisplayRole || role == Qt::ToolTipRole)
0083                     return ofiItem->lastAccess().toString(Qt::TextDate);
0084                 else if (role == SortRole)
0085                     return ofiItem->lastAccess().toSecsSinceEpoch();
0086             } else if (index.column() == 2) {
0087                 if (role == Qt::DisplayRole || role == Qt::ToolTipRole || role == SortRole)
0088                     return ofiItem->url().url(QUrl::PreferLocalFile);
0089             }
0090             if (role == URLRole)
0091                 return ofiItem->url();
0092         }
0093 
0094         return QVariant();
0095     }
0096 
0097     QVariant headerData(int section, Qt::Orientation, int role = Qt::DisplayRole) const override {
0098         if (role != Qt::DisplayRole || section > 2) return QVariant();
0099         else if (section == 0) return i18n("Filename");
0100         else if (section == 1) return i18n("Date/time of last use");
0101         else return i18n("Full filename");
0102     }
0103 
0104     QModelIndex index(int row, int column, const QModelIndex &) const override {
0105         return createIndex(row, column, static_cast<quintptr>(row));
0106     }
0107 
0108     QModelIndex parent(const QModelIndex &) const override {
0109         return QModelIndex();
0110     }
0111 
0112     int rowCount(const QModelIndex &parent = QModelIndex()) const override {
0113         if (parent == QModelIndex())
0114             return OpenFileInfoManager::instance().filteredItems(OpenFileInfo::StatusFlag::RecentlyUsed).count();
0115         else
0116             return 0;
0117     }
0118 };
0119 
0120 class MDIWidget::MDIWidgetPrivate
0121 {
0122 private:
0123     QTreeView *listLRU;
0124     LRUItemModel *modelLRU;
0125     QVector<QWidget *> welcomeWidgets;
0126 
0127     static const QString configGroupName;
0128     static const QString configHeaderState;
0129 
0130     void createWelcomeWidget() {
0131         welcomeWidget = new QWidget(p);
0132         QGridLayout *layout = new QGridLayout(welcomeWidget);
0133         layout->setRowStretch(0, 1);
0134         layout->setRowStretch(1, 0);
0135         layout->setRowStretch(2, 0);
0136         layout->setRowStretch(3, 0);
0137         layout->setRowStretch(4, 10);
0138         layout->setRowStretch(5, 1);
0139         layout->setColumnStretch(0, 1);
0140         layout->setColumnStretch(1, 10);
0141         layout->setColumnStretch(2, 1);
0142         layout->setColumnStretch(3, 0);
0143         layout->setColumnStretch(4, 1);
0144         layout->setColumnStretch(5, 10);
0145         layout->setColumnStretch(6, 1);
0146 
0147         QLabel *label = new QLabel(i18n("<qt>Welcome to <b>KBibTeX</b></qt>"), welcomeWidget);
0148         layout->addWidget(label, 1, 2, 1, 3, Qt::AlignHCenter | Qt::AlignTop);
0149 
0150         QPushButton *buttonNew = new QPushButton(QIcon::fromTheme(QStringLiteral("document-new")), i18n("New"), welcomeWidget);
0151         layout->addWidget(buttonNew, 2, 2, 1, 1, Qt::AlignLeft | Qt::AlignBottom);
0152         connect(buttonNew, &QPushButton::clicked, p, &MDIWidget::documentNew);
0153 
0154         QPushButton *buttonOpen = new QPushButton(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open..."), welcomeWidget);
0155         layout->addWidget(buttonOpen, 2, 4, 1, 1, Qt::AlignRight | Qt::AlignBottom);
0156         connect(buttonOpen, &QPushButton::clicked, p, &MDIWidget::documentOpen);
0157 
0158         label = new QLabel(i18n("List of recently used files:"), welcomeWidget);
0159         layout->addWidget(label, 3, 1, 1, 5, Qt::AlignLeft | Qt::AlignBottom);
0160         listLRU = new QTreeView(p);
0161         listLRU->setRootIsDecorated(false);
0162         listLRU->setSortingEnabled(true);
0163         listLRU->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
0164         layout->addWidget(listLRU, 4, 1, 1, 5);
0165         connect(listLRU, &QTreeView::activated, p, &MDIWidget::slotOpenLRU);
0166         label->setBuddy(listLRU);
0167 
0168         p->addWidget(welcomeWidget);
0169     }
0170 
0171     void saveColumnsState() {
0172         QByteArray headerState = listLRU->header()->saveState();
0173         KConfigGroup configGroup(config, configGroupName);
0174         configGroup.writeEntry(configHeaderState, headerState);
0175         config->sync();
0176     }
0177 
0178     void restoreColumnsState() {
0179         KConfigGroup configGroup(config, configGroupName);
0180         QByteArray headerState = configGroup.readEntry(configHeaderState, QByteArray());
0181         if (!headerState.isEmpty())
0182             listLRU->header()->restoreState(headerState);
0183     }
0184 
0185 public:
0186     MDIWidget *p;
0187     OpenFileInfo *currentFile;
0188     QWidget *welcomeWidget;
0189 
0190     KSharedConfigPtr config;
0191 
0192     MDIWidgetPrivate(MDIWidget *parent)
0193             : p(parent), currentFile(nullptr), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))) {
0194         createWelcomeWidget();
0195 
0196         modelLRU = new LRUItemModel(listLRU);
0197         QSortFilterProxyModel *sfpm = new QSortFilterProxyModel(listLRU);
0198         sfpm->setSourceModel(modelLRU);
0199         sfpm->setSortRole(LRUItemModel::SortRole);
0200         listLRU->setModel(sfpm);
0201 
0202         restoreColumnsState();
0203     }
0204 
0205     ~MDIWidgetPrivate() {
0206         saveColumnsState();
0207         delete welcomeWidget;
0208     }
0209 
0210     void addToMapper(OpenFileInfo *openFileInfo) {
0211         KParts::ReadOnlyPart *part = openFileInfo->part(p);
0212         connect(part, static_cast<void(KParts::ReadOnlyPart::*)()>(&KParts::ReadOnlyPart::completed), p, [this, openFileInfo]() {
0213             loadingFinished(openFileInfo);
0214         });
0215     }
0216 
0217     void updateLRU() {
0218         modelLRU->reset();
0219     }
0220 
0221     void loadingFinished(OpenFileInfo *ofi)
0222     {
0223         const QUrl oldUrl = ofi->url();
0224         const QUrl newUrl = ofi->part(p)->url();
0225 
0226         if (oldUrl != newUrl) {
0227             qCDebug(LOG_KBIBTEX_PROGRAM) << "Url changed from " << oldUrl.url(QUrl::PreferLocalFile) << " to " << newUrl.url(QUrl::PreferLocalFile);
0228             OpenFileInfoManager::instance().changeUrl(ofi, newUrl);
0229 
0230             /// completely opened or saved files should be marked as "recently used"
0231             ofi->addFlags(OpenFileInfo::StatusFlag::RecentlyUsed);
0232 
0233             /// Instead of an 'emit' ...
0234 #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
0235             QMetaObject::invokeMethod(p, "setCaption", Qt::DirectConnection, QGenericReturnArgument(), Q_ARG(QString, QString(QStringLiteral("%1 [%2]")).arg(ofi->shortCaption(), squeeze_text(ofi->fullCaption(), 64))));
0236 #else // QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
0237             QMetaObject::invokeMethod(p, "setCaption", Qt::DirectConnection, QMetaMethodReturnArgument(), Q_ARG(QString, QString(QStringLiteral("%1 [%2]")).arg(ofi->shortCaption(), squeeze_text(ofi->fullCaption(), 64))));
0238 #endif //
0239         }
0240     }
0241 };
0242 
0243 const QString MDIWidget::MDIWidgetPrivate::configGroupName = QStringLiteral("WelcomeWidget");
0244 const QString MDIWidget::MDIWidgetPrivate::configHeaderState = QStringLiteral("LRUlistHeaderState");
0245 
0246 MDIWidget::MDIWidget(QWidget *parent)
0247         : QStackedWidget(parent), d(new MDIWidgetPrivate(this))
0248 {
0249     connect(&OpenFileInfoManager::instance(), &OpenFileInfoManager::flagsChanged, this, &MDIWidget::slotStatusFlagsChanged);
0250 }
0251 
0252 MDIWidget::~MDIWidget()
0253 {
0254     delete d;
0255 }
0256 
0257 void MDIWidget::setFile(OpenFileInfo *openFileInfo, const KPluginMetaData &service)
0258 {
0259     KParts::Part *part = openFileInfo == nullptr ? nullptr : openFileInfo->part(this, service);
0260     /// 'part' will only be NULL if no OpenFileInfo object was given,
0261     /// or if the OpenFileInfo could not locate a KPart
0262     QWidget *widget = d->welcomeWidget; ///< by default use Welcome widget
0263     if (part != nullptr) {
0264         /// KPart object was located, use its object (instead of Welcome widget)
0265         widget = part->widget();
0266     } else if (openFileInfo != nullptr) {
0267         /// A valid OpenFileInfo was given, but no KPart could be located
0268 
0269         OpenFileInfoManager::instance().close(openFileInfo); // FIXME does not close correctly if file is new
0270         const QString filename = openFileInfo->url().fileName();
0271         if (filename.isEmpty())
0272             KMessageBox::error(this, i18n("No part available for file of mime type '%1'.", openFileInfo->mimeType()), i18n("No Part Available"));
0273         else
0274             KMessageBox::error(this, i18n("No part available for file '%1'.", filename), i18n("No Part Available"));
0275         return;
0276     }
0277 
0278     FileView *oldEditor = nullptr;
0279     bool hasChanged = true;
0280     if (indexOf(widget) >= 0) {
0281         /// Chosen widget is already known (Welcome widget or a previously used KPart)
0282 
0283         /// In case previous (still current) widget was a KBibTeX Part, remember its editor
0284         PartWidget *currentPartWidget = qobject_cast<PartWidget *>(currentWidget());
0285         oldEditor = currentPartWidget == nullptr ? nullptr : currentPartWidget->fileView();
0286         /// Record if set widget is different from previous (still current) widget
0287         hasChanged = widget != currentWidget();
0288     } else if (openFileInfo != nullptr) {
0289         /// Widget was not known previously, but a valid (new?) OpenFileInfo was given
0290         addWidget(widget);
0291         d->addToMapper(openFileInfo);
0292     }
0293     setCurrentWidget(widget);
0294     d->currentFile = openFileInfo;
0295 
0296     if (hasChanged) {
0297         /// This signal gets forwarded to KParts::MainWindow::createGUI(KParts::Part*)
0298         Q_EMIT activePartChanged(part);
0299         /// If new widget comes from a KBibTeX Part, retrieve its editor
0300         PartWidget *newPartWidget = qobject_cast<PartWidget *>(widget);
0301         FileView *newEditor = newPartWidget == nullptr ? nullptr : newPartWidget->fileView();
0302         Q_EMIT documentSwitched(oldEditor, newEditor);
0303     }
0304 
0305     /// Notify main window about a change of current file,
0306     /// so that the title may contain the current file's URL.
0307     /// This signal will be connected to KMainWindow::setCaption.
0308     if (openFileInfo != nullptr) {
0309         QUrl url = openFileInfo->url();
0310         if (url.isValid())
0311             Q_EMIT setCaption(QString(QStringLiteral("%1 [%2]")).arg(openFileInfo->shortCaption(), squeeze_text(openFileInfo->fullCaption(), 64)));
0312         else
0313             Q_EMIT setCaption(openFileInfo->shortCaption());
0314     } else
0315         Q_EMIT setCaption(QString());
0316 }
0317 
0318 FileView *MDIWidget::fileView()
0319 {
0320     OpenFileInfo *ofi = OpenFileInfoManager::instance().currentFile();
0321     return qobject_cast<PartWidget *>(ofi->part(this)->widget())->fileView();
0322 }
0323 
0324 OpenFileInfo *MDIWidget::currentFile()
0325 {
0326     return d->currentFile;
0327 }
0328 
0329 void MDIWidget::slotStatusFlagsChanged(OpenFileInfo::StatusFlags statusFlags)
0330 {
0331     if (statusFlags.testFlag(OpenFileInfo::StatusFlag::RecentlyUsed))
0332         d->updateLRU();
0333 }
0334 
0335 void MDIWidget::slotOpenLRU(const QModelIndex &index)
0336 {
0337     QUrl url = index.data(LRUItemModel::URLRole).toUrl();
0338     if (url.isValid())
0339         Q_EMIT documentOpenURL(url);
0340 }
0341 
0342 #include "mdiwidget.moc"