File indexing completed on 2024-06-02 05:09:12

0001 /***************************************************************************
0002  *   SPDX-License-Identifier: GPL-2.0-or-later
0003  *                                                                         *
0004  *   SPDX-FileCopyrightText: 2004-2022 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 "associatedfilesui.h"
0021 
0022 #include <QLayout>
0023 #include <QLabel>
0024 #include <QGroupBox>
0025 #include <QRadioButton>
0026 #include <QButtonGroup>
0027 #include <QDir>
0028 #include <QDialog>
0029 #include <QDialogButtonBox>
0030 #include <QPushButton>
0031 #include <QLineEdit>
0032 #include <QPointer>
0033 
0034 #include <KLocalizedString>
0035 
0036 class AssociatedFilesUI::Private
0037 {
0038 private:
0039     AssociatedFilesUI *p;
0040 
0041 public:
0042     QLabel *labelGreeting;
0043     QLineEdit *lineEditSourceUrl;
0044     QRadioButton *radioNoCopyMove, *radioCopyFile, *radioMoveFile;
0045     QLabel *labelMoveCopyLocation;
0046     QLineEdit *lineMoveCopyLocation;
0047     QGroupBox *groupBoxRename;
0048     QRadioButton *radioKeepFilename, *radioRenameToEntryId, *radioUserDefinedName;
0049     QLineEdit *lineEditUserDefinedName;
0050     QGroupBox *groupBoxPathType;
0051     QRadioButton *radioRelativePath, *radioAbsolutePath;
0052     QLineEdit *linePreview;
0053 
0054     QUrl sourceUrl;
0055     QSharedPointer<Entry> entry;
0056     QString entryId;
0057     const File *bibTeXfile;
0058 
0059     Private(AssociatedFilesUI *parent)
0060             : p(parent), entry(QSharedPointer<Entry>()), bibTeXfile(nullptr) {
0061         setupGUI();
0062     }
0063 
0064     void setupGUI() {
0065         QBoxLayout *layout = new QVBoxLayout(p);
0066 
0067         labelGreeting = new QLabel(p);
0068         layout->addWidget(labelGreeting);
0069         labelGreeting->setWordWrap(true);
0070 
0071         lineEditSourceUrl = new QLineEdit(p);
0072         layout->addWidget(lineEditSourceUrl);
0073         lineEditSourceUrl->setReadOnly(true);
0074 
0075         layout->addSpacing(8);
0076 
0077         QLabel *label = new QLabel(i18n("The following operations can be performed when associating the document with the entry:"), p);
0078         layout->addWidget(label);
0079         label->setWordWrap(true);
0080 
0081         QGroupBox *groupBox = new QGroupBox(i18n("File operation"), p);
0082         layout->addWidget(groupBox);
0083         QBoxLayout *groupBoxLayout = new QVBoxLayout(groupBox);
0084         QButtonGroup *buttonGroup = new QButtonGroup(groupBox);
0085         radioNoCopyMove = new QRadioButton(i18n("Do not copy or move document, only insert reference to it"), groupBox);
0086         groupBoxLayout->addWidget(radioNoCopyMove);
0087         buttonGroup->addButton(radioNoCopyMove);
0088         radioCopyFile = new QRadioButton(i18n("Copy document next to bibliography file"), groupBox);
0089         groupBoxLayout->addWidget(radioCopyFile);
0090         buttonGroup->addButton(radioCopyFile);
0091         radioMoveFile = new QRadioButton(i18n("Move document next to bibliography file"), groupBox);
0092         groupBoxLayout->addWidget(radioMoveFile);
0093         buttonGroup->addButton(radioMoveFile);
0094 #if QT_VERSION >= 0x050f00
0095         connect(buttonGroup, static_cast<void(QButtonGroup::*)(int)>(&QButtonGroup::idClicked), p, &AssociatedFilesUI::updateUIandPreview);
0096 #else // QT_VERSION < 0x050f00
0097         connect(buttonGroup, static_cast<void(QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), p, &AssociatedFilesUI::updateUIandPreview);
0098 #endif // QT_VERSION >= 0x050f00
0099         radioNoCopyMove->setChecked(true); /// by default
0100         groupBoxLayout->addSpacing(4);
0101         labelMoveCopyLocation = new QLabel(i18n("Path and filename of bibliography file:"), groupBox);
0102         groupBoxLayout->addWidget(labelMoveCopyLocation, 1);
0103         lineMoveCopyLocation = new QLineEdit(groupBox);
0104         lineMoveCopyLocation->setReadOnly(true);
0105         groupBoxLayout->addWidget(lineMoveCopyLocation, 1);
0106 
0107         groupBoxRename = new QGroupBox(i18n("Rename Document?"), p);
0108         layout->addWidget(groupBoxRename);
0109         QGridLayout *gridLayout = new QGridLayout(groupBoxRename);
0110         gridLayout->setColumnMinimumWidth(0, 16);
0111         gridLayout->setColumnStretch(0, 0);
0112         gridLayout->setColumnStretch(1, 1);
0113         buttonGroup = new QButtonGroup(groupBoxRename);
0114         radioKeepFilename = new QRadioButton(i18n("Keep document's original filename"), groupBoxRename);
0115         gridLayout->addWidget(radioKeepFilename, 0, 0, 1, 2);
0116         buttonGroup->addButton(radioKeepFilename);
0117         radioRenameToEntryId = new QRadioButton(groupBoxRename);
0118         gridLayout->addWidget(radioRenameToEntryId, 1, 0, 1, 2);
0119         buttonGroup->addButton(radioRenameToEntryId);
0120         radioUserDefinedName = new QRadioButton(i18n("User-defined name:"), groupBoxRename);
0121         gridLayout->addWidget(radioUserDefinedName, 2, 0, 1, 2);
0122         buttonGroup->addButton(radioUserDefinedName);
0123         lineEditUserDefinedName = new QLineEdit(groupBoxRename);
0124         gridLayout->addWidget(lineEditUserDefinedName, 3, 1, 1, 1);
0125         connect(lineEditUserDefinedName, &QLineEdit::textEdited, p, &AssociatedFilesUI::updateUIandPreview);
0126 #if QT_VERSION >= 0x050f00
0127         connect(buttonGroup, static_cast<void(QButtonGroup::*)(int)>(&QButtonGroup::idClicked), p, &AssociatedFilesUI::updateUIandPreview);
0128 #else // QT_VERSION < 0x050f00
0129         connect(buttonGroup, static_cast<void(QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), p, &AssociatedFilesUI::updateUIandPreview);
0130 #endif // QT_VERSION >= 0x050f00
0131         radioRenameToEntryId->setChecked(true); /// by default
0132 
0133         groupBoxPathType = new QGroupBox(i18n("Path as Inserted into Entry"), p);
0134         buttonGroup = new QButtonGroup(groupBoxPathType);
0135         layout->addWidget(groupBoxPathType);
0136         groupBoxLayout = new QVBoxLayout(groupBoxPathType);
0137         radioRelativePath = new QRadioButton(i18n("Relative Path"), groupBoxPathType);
0138         groupBoxLayout->addWidget(radioRelativePath);
0139         buttonGroup->addButton(radioRelativePath);
0140         radioAbsolutePath = new QRadioButton(i18n("Absolute Path"), groupBoxPathType);
0141         groupBoxLayout->addWidget(radioAbsolutePath);
0142         buttonGroup->addButton(radioAbsolutePath);
0143 #if QT_VERSION >= 0x050f00
0144         connect(buttonGroup, static_cast<void(QButtonGroup::*)(int)>(&QButtonGroup::idClicked), p, &AssociatedFilesUI::updateUIandPreview);
0145 #else // QT_VERSION < 0x050f00
0146         connect(buttonGroup, static_cast<void(QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), p, &AssociatedFilesUI::updateUIandPreview);
0147 #endif // QT_VERSION >= 0x050f00
0148         radioRelativePath->setChecked(true); /// by default
0149 
0150         layout->addSpacing(8);
0151 
0152         label = new QLabel(i18n("Preview of reference to be inserted:"), p);
0153         layout->addWidget(label);
0154 
0155         linePreview = new QLineEdit(p);
0156         layout->addWidget(linePreview);
0157         linePreview->setReadOnly(true);
0158 
0159         layout->addStretch(10);
0160     }
0161 };
0162 
0163 QString AssociatedFilesUI::associateUrl(const QUrl &url, QSharedPointer<Entry> &entry, const File *bibTeXfile, const bool doInsertUrl, QWidget *parent) {
0164     QPointer<QDialog> dlg = new QDialog(parent);
0165     dlg->setWindowTitle(i18nc("@title:window", "Associate Document with Entry"));
0166     QBoxLayout *layout = new QVBoxLayout(dlg);
0167     QPointer<AssociatedFilesUI> ui = new AssociatedFilesUI(entry->id(), bibTeXfile, dlg);
0168     layout->addWidget(ui);
0169     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, dlg);
0170     buttonBox->button(QDialogButtonBox::Ok)->setDefault(true);
0171     layout->addWidget(buttonBox);
0172     dlg->setLayout(layout);
0173 
0174     connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, dlg.data(), &QDialog::accept);
0175     connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, dlg.data(), &QDialog::reject);
0176 
0177     if (url.isLocalFile())
0178         ui->setupForLocalFile(url, entry->id());
0179     else
0180         ui->setupForRemoteUrl(url, entry->id());
0181 
0182     const bool accepted = dlg->exec() == QDialog::Accepted;
0183     bool success = false;
0184     QString referenceString;
0185     if (accepted) {
0186         const QUrl newUrl = AssociatedFiles::copyDocument(url, entry->id(), bibTeXfile, ui->renameOperation(), ui->moveCopyOperation(), dlg, ui->userDefinedFilename());
0187         success = newUrl.isValid();
0188         if (success) {
0189             referenceString = doInsertUrl ? AssociatedFiles::insertUrl(newUrl, entry, bibTeXfile, ui->pathType()) : AssociatedFiles::computeAssociateString(newUrl, bibTeXfile, ui->pathType());
0190             success &= !referenceString.isEmpty();
0191         }
0192     }
0193 
0194     delete dlg;
0195     return success ? referenceString : QString();
0196 }
0197 
0198 AssociatedFilesUI::AssociatedFilesUI(const QString &entryId, const File *bibTeXfile, QWidget *parent)
0199         : QWidget(parent), d(new AssociatedFilesUI::Private(this)) {
0200     d->entryId = entryId;
0201     d->bibTeXfile = bibTeXfile;
0202 }
0203 
0204 AssociatedFilesUI::~AssociatedFilesUI()
0205 {
0206     delete d;
0207 }
0208 
0209 AssociatedFiles::RenameOperation AssociatedFilesUI::renameOperation() const {
0210     if (d->radioRenameToEntryId->isChecked())
0211         return AssociatedFiles::RenameOperation::EntryId;
0212     else if (d->radioKeepFilename->isChecked() || d->lineEditUserDefinedName->text().isEmpty())
0213         return AssociatedFiles::RenameOperation::KeepName;
0214     else
0215         return AssociatedFiles::RenameOperation::UserDefined;
0216 }
0217 
0218 AssociatedFiles::MoveCopyOperation AssociatedFilesUI::moveCopyOperation() const {
0219     if (d->radioNoCopyMove->isChecked()) return AssociatedFiles::MoveCopyOperation::None;
0220     else if (d->radioMoveFile->isChecked()) return AssociatedFiles::MoveCopyOperation::Move;
0221     else return AssociatedFiles::MoveCopyOperation::Copy;
0222 }
0223 
0224 AssociatedFiles::PathType AssociatedFilesUI::pathType() const {
0225     return d->radioAbsolutePath->isChecked() ? AssociatedFiles::PathType::Absolute : AssociatedFiles::PathType::Relative;
0226 }
0227 
0228 QString AssociatedFilesUI::userDefinedFilename() const {
0229     QString text = d->lineEditUserDefinedName->text();
0230     const int p = qMax(text.lastIndexOf(QLatin1Char('/')), text.lastIndexOf(QDir::separator()));
0231     if (p > 0) text = text.mid(p + 1);
0232     return text;
0233 }
0234 
0235 void AssociatedFilesUI::updateUIandPreview() {
0236     QString preview = i18n("No preview available");
0237     const QString entryId = d->entryId.isEmpty() && !d->entry.isNull() ? d->entry->id() : d->entryId;
0238 
0239     const QUrl bibTeXfileUrl = d->bibTeXfile != nullptr && d->bibTeXfile->hasProperty(File::Url) ? d->bibTeXfile->property(File::Url).toUrl() : QUrl();
0240 
0241     if (entryId.isEmpty()) {
0242         /// If current entry has no identifier, then renaming after entry id is not possible
0243         d->radioRenameToEntryId->setEnabled(false);
0244         /// ... and the current filename should be kept
0245         d->radioKeepFilename->setChecked(true);
0246     } else
0247         /// But if the entry has an identifier, prefer to rename after it
0248         d->radioRenameToEntryId->setEnabled(true);
0249     if (!bibTeXfileUrl.isValid()) {
0250         /// If current file has no URL, e.g. because it hasn't been saved,
0251         /// no relative path can be assembled, i.e. only an absolute one is possible
0252         d->radioRelativePath->setEnabled(false);
0253         d->radioAbsolutePath->setChecked(true);
0254     } else if (bibTeXfileUrl.isValid() && !d->sourceUrl.isRelative() && (bibTeXfileUrl.scheme() != d->sourceUrl.scheme() || (bibTeXfileUrl.scheme() == d->sourceUrl.scheme() && bibTeXfileUrl.host() != d->sourceUrl.host()))) {
0255         /// If URL to be associated is not relative, i.e. has a scheme and this scheme is
0256         /// different from the current bibliography file's scheme or the scheme is the same
0257         /// but the host is different, then no relative path is possible
0258         d->radioRelativePath->setEnabled(false);
0259         d->radioAbsolutePath->setChecked(true);
0260     } else {
0261         /// If the file has a valid URL, prefer to use relative paths for association
0262         d->radioRelativePath->setEnabled(true);
0263     }
0264     if (bibTeXfileUrl.isValid()) {
0265         /// Show the URL of the current bibliography file
0266         d->lineMoveCopyLocation->setText(bibTeXfileUrl.path());
0267         d->lineMoveCopyLocation->setToolTip(d->lineMoveCopyLocation->text());
0268         d->labelMoveCopyLocation->show();
0269         d->lineMoveCopyLocation->show();
0270     } else {
0271         d->labelMoveCopyLocation->hide();
0272         d->lineMoveCopyLocation->hide();
0273     }
0274     /// Renaming is only possible if remote file is either copied or moved
0275     d->groupBoxRename->setEnabled(!d->radioNoCopyMove->isChecked());
0276     if (d->radioNoCopyMove->isChecked())
0277         /// Not moving/copying remote file implies that filename is kept
0278         d->radioKeepFilename->setChecked(true);
0279 
0280     if (d->radioMoveFile->isChecked() || d->radioCopyFile->isChecked()) {
0281         /// Assuming that the remote URL is to be copied next to the bibliography file,
0282         /// compute the destination a.k.a. target URL of the copied file
0283         const QPair<QUrl, QUrl> newURLs = AssociatedFiles::computeSourceDestinationUrls(d->sourceUrl, entryId, d->bibTeXfile, renameOperation(), d->lineEditUserDefinedName->text());
0284         if (newURLs.second.isValid())
0285             preview = AssociatedFiles::computeAssociateString(newURLs.second, d->bibTeXfile, pathType());
0286     } else if (d->radioNoCopyMove->isChecked()) {
0287         if (d->sourceUrl.isValid())
0288             preview = AssociatedFiles::computeAssociateString(d->sourceUrl, d->bibTeXfile, pathType());
0289     }
0290     d->linePreview->setText(preview);
0291 }
0292 
0293 void AssociatedFilesUI::setupForRemoteUrl(const QUrl &url, const QString &entryId) {
0294     d->sourceUrl = url;
0295     d->lineEditSourceUrl->setText(url.toDisplayString());
0296     if (entryId.isEmpty()) {
0297         d->labelGreeting->setText(i18n("The following remote document is about to be associated with the current entry:"));
0298         d->radioRenameToEntryId->setText(i18n("Rename after entry's id"));
0299     } else {
0300         d->labelGreeting->setText(i18n("The following remote document is about to be associated with entry '%1':", entryId));
0301         d->radioRenameToEntryId->setText(i18n("Rename after entry's id: '%1'", entryId));
0302     }
0303     updateUIandPreview();
0304 }
0305 
0306 void AssociatedFilesUI::setupForLocalFile(const QUrl &url, const QString &entryId) {
0307     d->sourceUrl = url;
0308     d->lineEditSourceUrl->setText(url.path());
0309     if (entryId.isEmpty()) {
0310         d->labelGreeting->setText(i18n("The following local document is about to be associated with the current entry:"));
0311         d->radioRenameToEntryId->setText(i18n("Rename after entry's id"));
0312     } else {
0313         d->labelGreeting->setText(i18n("The following local document is about to be associated with entry '%1':", entryId));
0314         d->radioRenameToEntryId->setText(i18n("Rename after entry's id: '%1'", entryId));
0315     }
0316     updateUIandPreview();
0317 }