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 ¤t, 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"