File indexing completed on 2024-11-24 04:34:19

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 "fileview.h"
0021 
0022 #include <QMimeData>
0023 #include <QDropEvent>
0024 #include <QTimer>
0025 #include <QDialog>
0026 #include <QDialogButtonBox>
0027 #include <QBoxLayout>
0028 #include <QPushButton>
0029 
0030 #include <KLocalizedString>
0031 #include <KMessageBox>
0032 #include <KGuiItem>
0033 #include <KConfigGroup>
0034 #include <KSharedConfig>
0035 #include <KWindowConfig>
0036 #include <KStandardGuiItem>
0037 
0038 #include <Entry>
0039 #include <Macro>
0040 #include <file/Clipboard>
0041 #include <models/FileModel>
0042 #include <FileExporterBibTeX>
0043 #include "element/elementeditor.h"
0044 #include "valuelistmodel.h"
0045 
0046 #include "logging_gui.h"
0047 
0048 /**
0049  * Specialized dialog for element editing. It will check if the used
0050  * element editor widget has unapplied changes and ask the user if
0051  * he/she actually wants to discard those changes before closing this
0052  * dialog.
0053  *
0054  * @author Thomas Fischer
0055  */
0056 class ElementEditorDialog : public QDialog
0057 {
0058     Q_OBJECT
0059 
0060 private:
0061     ElementEditor *elementEditor;
0062     static const QString configGroupNameWindowSize;
0063     KConfigGroup configGroup;
0064 
0065 public:
0066     ElementEditorDialog(QWidget *parent)
0067             : QDialog(parent), elementEditor(nullptr) {
0068         /// restore window size
0069         KSharedConfigPtr config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc")));
0070         configGroup = KConfigGroup(config, configGroupNameWindowSize);
0071         KWindowConfig::restoreWindowSize(windowHandle(), configGroup);
0072 
0073         setLayout(new QVBoxLayout(parent));
0074     }
0075 
0076     /**
0077      * Store element editor widget for future reference.
0078      */
0079     void setElementEditor(ElementEditor *elementEditor) {
0080         this->elementEditor = elementEditor;
0081         QBoxLayout *boxLayout = qobject_cast<QBoxLayout *>(layout());
0082         boxLayout->addWidget(this->elementEditor);
0083     }
0084 
0085 public:
0086     void reject() override {
0087         /// If there unapplied changes in the editor widget ask user for consent
0088         /// to discard changes; only then allow to close this dialog
0089         if (!elementEditor->elementUnapplied() || KMessageBox::warningContinueCancel(this, i18n("The current entry has been modified. Do you want to discard your changes?"), i18n("Discard changes?"), KStandardGuiItem::discard(), KGuiItem(i18n("Continue Editing"), QStringLiteral("edit-rename"))) == KMessageBox::Continue)
0090             QDialog::reject();
0091     }
0092 
0093 protected:
0094     void closeEvent(QCloseEvent *) override {
0095         /// Save window size
0096         KWindowConfig::saveWindowSize(windowHandle(), configGroup);
0097     }
0098 };
0099 
0100 const QString ElementEditorDialog::configGroupNameWindowSize = QStringLiteral("ElementEditorDialog");
0101 
0102 FileView::FileView(const QString &name, QWidget *parent)
0103         : BasicFileView(name, parent), m_isReadOnly(false), m_current(QSharedPointer<Element>()), m_filterBar(nullptr), m_clipboard(nullptr), m_lastEditorPage(nullptr), m_elementEditorDialog(nullptr), m_elementEditor(nullptr), m_dbb(nullptr)
0104 {
0105     connect(this, &FileView::doubleClicked, this, &FileView::itemActivated);
0106 }
0107 
0108 void FileView::viewCurrentElement()
0109 {
0110     viewElement(currentElement());
0111 }
0112 
0113 void FileView::viewElement(const QSharedPointer<Element> &element)
0114 {
0115     prepareEditorDialog(DialogType::View);
0116     FileModel *model = fileModel();
0117     File *bibliographyFile = model != nullptr ? model->bibliographyFile() : nullptr;
0118     m_elementEditor->setElement(element, bibliographyFile);
0119 
0120     m_elementEditor->setCurrentPage(m_lastEditorPage);
0121     m_elementEditorDialog->exec();
0122     m_lastEditorPage = m_elementEditor->currentPage();
0123 }
0124 
0125 void FileView::editCurrentElement()
0126 {
0127     editElement(currentElement());
0128 }
0129 
0130 bool FileView::editElement(QSharedPointer<Element> element)
0131 {
0132     prepareEditorDialog(DialogType::Edit);
0133     FileModel *model = fileModel();
0134     File *bibliographyFile = model != nullptr ? model->bibliographyFile() : nullptr;
0135     m_elementEditor->setElement(element, bibliographyFile);
0136 
0137     m_elementEditor->setCurrentPage(m_lastEditorPage);
0138     m_elementEditorDialog->exec(); ///< no need to take of result code, got handled in FileView::dialogButtonClicked
0139     m_lastEditorPage = m_elementEditor->currentPage();
0140 
0141     if (!isReadOnly()) {
0142         bool changed = m_elementEditor->elementChanged();
0143         if (changed) {
0144             FileModel *model = fileModel();
0145             const File *bibliographyFile = model != nullptr ? model->bibliographyFile() : nullptr;
0146             Q_EMIT currentElementChanged(currentElement(), bibliographyFile);
0147             Q_EMIT selectedElementsChanged();
0148             Q_EMIT modified(true);
0149         }
0150         return changed;
0151     } else
0152         return false;
0153 }
0154 
0155 const QList<QSharedPointer<Element> > &FileView::selectedElements() const
0156 {
0157     return m_selection;
0158 }
0159 
0160 void FileView::setSelectedElement(QSharedPointer<Element> element)
0161 {
0162     m_selection.clear();
0163     m_selection << element;
0164 
0165     QItemSelectionModel *selModel = selectionModel();
0166     selModel->clear();
0167     FileModel *model = fileModel();
0168     const int row = model != nullptr ? model->row(element) : -1;
0169     const QModelIndex sourceIdx = row >= 0 && model != nullptr ? model->index(row, 0) : QModelIndex();
0170     const QModelIndex idx = sortFilterProxyModel()->mapFromSource(sourceIdx);
0171     selModel->setCurrentIndex(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows);
0172 }
0173 
0174 QSharedPointer<Element> FileView::currentElement()
0175 {
0176     return m_current;
0177 }
0178 
0179 QSharedPointer<Element> FileView::elementAt(const QModelIndex &index)
0180 {
0181     FileModel *model = fileModel();
0182     return model != nullptr ? model->element(sortFilterProxyModel()->mapToSource(index).row()) : QSharedPointer<Element>();
0183 }
0184 
0185 void FileView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
0186 {
0187     QTreeView::currentChanged(current, previous); // FIXME necessary?
0188 
0189     m_current = elementAt(current);
0190     FileModel *model = fileModel();
0191     if (model != nullptr)
0192         Q_EMIT currentElementChanged(m_current, model->bibliographyFile());
0193 }
0194 
0195 void FileView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
0196 {
0197     QTreeView::selectionChanged(selected, deselected);
0198 
0199     const QModelIndexList selectedSet = selected.indexes();
0200     for (const QModelIndex &index : selectedSet) {
0201         if (index.column() != 0) continue; ///< consider only column-0 indices to avoid duplicate elements
0202         m_selection.append(elementAt(index));
0203     }
0204     if (m_current.isNull() && !selectedSet.isEmpty())
0205         m_current = elementAt(selectedSet.first());
0206 
0207     const QModelIndexList deselectedSet = deselected.indexes();
0208     for (const QModelIndex &index : deselectedSet) {
0209         if (index.column() != 0) continue; ///< consider only column-0 indices to avoid duplicate elements
0210         m_selection.removeOne(elementAt(index));
0211     }
0212 
0213     Q_EMIT selectedElementsChanged();
0214 }
0215 
0216 void FileView::selectionDelete()
0217 {
0218     const QModelIndexList mil = selectionModel()->selectedRows();
0219     QList<int> rows;
0220     rows.reserve(mil.size());
0221     for (const QModelIndex &idx : mil)
0222         rows << sortFilterProxyModel()->mapToSource(idx).row();
0223 
0224     FileModel *model = fileModel();
0225     if (model != nullptr) model->removeRowList(rows);
0226 
0227     Q_EMIT modified(true);
0228 }
0229 
0230 /// FIXME the existence of this function is basically just one big hack
0231 void FileView::externalModification()
0232 {
0233     Q_EMIT modified(true);
0234 }
0235 
0236 void FileView::setReadOnly(bool isReadOnly)
0237 {
0238     m_isReadOnly = isReadOnly;
0239 }
0240 
0241 bool FileView::isReadOnly() const
0242 {
0243     return m_isReadOnly;
0244 }
0245 
0246 ValueListModel *FileView::valueListModel(const QString &field)
0247 {
0248     FileModel *model = fileModel();
0249     if (model != nullptr) {
0250         ValueListModel *result = new ValueListModel(model->bibliographyFile(), field, this);
0251         /// Keep track of external changes through modifications in this ValueListModel instance
0252         connect(result, &ValueListModel::dataChanged, this, &FileView::externalModification);
0253         return result;
0254     }
0255 
0256     return nullptr;
0257 }
0258 
0259 void FileView::setFilterBar(FilterBar *filterBar)
0260 {
0261     m_filterBar = filterBar;
0262 }
0263 
0264 void FileView::setClipboard(Clipboard *clipboard)
0265 {
0266     m_clipboard = clipboard;
0267 }
0268 
0269 void FileView::setFilterBarFilter(const SortFilterFileModel::FilterQuery &fq)
0270 {
0271     if (m_filterBar != nullptr)
0272         m_filterBar->setFilter(fq);
0273 }
0274 
0275 void FileView::mouseMoveEvent(QMouseEvent *event)
0276 {
0277     if (m_clipboard != nullptr)
0278         m_clipboard->editorMouseEvent(event);
0279 }
0280 
0281 void FileView::dragEnterEvent(QDragEnterEvent *event)
0282 {
0283     if (m_clipboard != nullptr)
0284         m_clipboard->editorDragEnterEvent(event);
0285 }
0286 
0287 void FileView::dropEvent(QDropEvent *event)
0288 {
0289     if (event->source() != this && m_clipboard != nullptr)
0290         m_clipboard->editorDropEvent(event);
0291 }
0292 
0293 void FileView::dragMoveEvent(QDragMoveEvent *event)
0294 {
0295     if (m_clipboard != nullptr)
0296         m_clipboard->editorDragMoveEvent(event);
0297 }
0298 
0299 void FileView::contextMenuEvent(QContextMenuEvent *event)
0300 {
0301     Q_EMIT contextMenuTriggered(event);
0302 }
0303 
0304 void FileView::itemActivated(const QModelIndex &index)
0305 {
0306     Q_EMIT elementExecuted(elementAt(index));
0307 }
0308 
0309 void FileView::prepareEditorDialog(DialogType dialogType)
0310 {
0311     if (dialogType != DialogType::View && isReadOnly()) {
0312         qCWarning(LOG_KBIBTEX_GUI) << "In read-only mode, you may only view elements, not edit them";
0313         dialogType = DialogType::View;
0314     }
0315 
0316     /// Create both the dialog window and the editing widget only once
0317     if (m_elementEditorDialog == nullptr)
0318         m_elementEditorDialog = new ElementEditorDialog(this);
0319     if (m_elementEditor == nullptr) {
0320         m_elementEditor = new ElementEditor(false, m_elementEditorDialog);
0321         m_elementEditorDialog->setElementEditor(m_elementEditor);
0322     }
0323     if (m_dbb != nullptr) {
0324         delete m_dbb;
0325         m_dbb = nullptr;
0326     }
0327 
0328     if (dialogType == DialogType::View) {
0329         /// View mode, as use in read-only situations
0330         m_elementEditor->setReadOnly(true);
0331         m_elementEditorDialog->setWindowTitle(i18nc("@title:window", "View Element"));
0332         m_dbb = new QDialogButtonBox(QDialogButtonBox::Close, m_elementEditorDialog);
0333         QBoxLayout *boxLayout = qobject_cast<QBoxLayout *>(m_elementEditorDialog->layout());
0334         boxLayout->addWidget(m_dbb);
0335         connect(m_dbb, &QDialogButtonBox::clicked, this, &FileView::dialogButtonClicked);
0336     } else if (dialogType == DialogType::Edit) {
0337         /// Edit mode, used in normal operations
0338         m_elementEditor->setReadOnly(false);
0339         m_elementEditorDialog->setWindowTitle(i18nc("@title:window", "Edit Element"));
0340         m_dbb = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::Reset, m_elementEditorDialog);
0341         QBoxLayout *boxLayout = qobject_cast<QBoxLayout *>(m_elementEditorDialog->layout());
0342         boxLayout->addWidget(m_dbb);
0343         m_dbb->button(QDialogButtonBox::Apply)->setEnabled(false);
0344         m_dbb->button(QDialogButtonBox::Ok)->setDefault(true);
0345 
0346         /// Establish signal-slot connections for modification/editing events
0347         connect(m_elementEditor, &ElementEditor::modified, m_dbb->button(QDialogButtonBox::Apply), &QPushButton::setEnabled);
0348         connect(m_dbb, &QDialogButtonBox::clicked, this, &FileView::dialogButtonClicked);
0349     }
0350 }
0351 
0352 void FileView::dialogButtonClicked(QAbstractButton *button) {
0353     switch (m_dbb->standardButton(button)) {
0354     case QDialogButtonBox::Ok:
0355         if (m_elementEditor->validate()) {
0356             m_elementEditor->apply();
0357             m_elementEditorDialog->accept();
0358         }
0359         break;
0360     case QDialogButtonBox::Apply:
0361         if (m_elementEditor->validate())
0362             m_elementEditor->apply();
0363         break;
0364     case QDialogButtonBox::Close:
0365         /// Close button exists only in read-only mode. Reject/close immediately.
0366         m_elementEditorDialog->reject();
0367         break;
0368     case QDialogButtonBox::Cancel:
0369         /// Trigger ElementEditorDialog::reject which in its turn checks
0370         /// if there are unapplied modifications. If user does not want to
0371         /// discard changes, stop closing this dialog and do not reject changes.
0372         m_elementEditorDialog->reject();
0373         break;
0374     case QDialogButtonBox::Reset:
0375         m_elementEditor->reset();
0376         break;
0377     default:
0378         qCWarning(LOG_KBIBTEX_GUI) << "Default case should never get triggered in FileView::dialogButtonClicked";
0379     }
0380 }
0381 
0382 #include "fileview.moc"