File indexing completed on 2025-01-12 04:35:48

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 "elementform.h"
0021 
0022 #include <QLayout>
0023 #include <QDockWidget>
0024 #include <QLabel>
0025 #include <QCheckBox>
0026 #include <QPushButton>
0027 
0028 #include <kwidgetsaddons_version.h>
0029 #include <KLocalizedString>
0030 #include <KIconLoader>
0031 #include <KConfigGroup>
0032 #include <KSharedConfig>
0033 #include <KMessageBox>
0034 
0035 #include <Entry>
0036 #include <element/ElementEditor>
0037 #include "mdiwidget.h"
0038 
0039 class ElementForm::ElementFormPrivate
0040 {
0041 private:
0042     ElementForm *p;
0043     QGridLayout *layout;
0044     const File *file;
0045 
0046 public:
0047     ElementEditor *elementEditor;
0048     MDIWidget *mdiWidget;
0049     QCheckBox *checkBoxAutoApply;
0050     QPushButton *buttonApply, *buttonReset;
0051     QWidget *widgetUnmodifiedChanges;
0052     bool gotModified;
0053     QSharedPointer<Element> element;
0054 
0055     KSharedConfigPtr config;
0056     /// Group name in configuration file for all settings for this form
0057     static const QString configGroupName;
0058     /// Key to store/retrieve setting whether changes in form should be automatically applied to element or not
0059     static const QString configKeyAutoApply;
0060 
0061     ElementFormPrivate(MDIWidget *_mdiWidget, ElementForm *parent)
0062             : p(parent), file(nullptr), mdiWidget(_mdiWidget), gotModified(false), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))) {
0063         KConfigGroup configGroup(config, configGroupName);
0064 
0065         layout = new QGridLayout(p);
0066         layout->setColumnStretch(0, 10);
0067         layout->setColumnStretch(1, 0);
0068         layout->setColumnStretch(2, 0);
0069         layout->setColumnStretch(3, 0);
0070 
0071         elementEditor = new ElementEditor(true, p);
0072         layout->addWidget(elementEditor, 0, 0, 1, 4);
0073         elementEditor->setEnabled(false);
0074         elementEditor->layout()->setContentsMargins(0, 0, 0, 0);
0075         connect(elementEditor, &ElementEditor::modified, p, &ElementForm::modified);
0076 
0077         /// Checkbox enabling/disabling setting to automatically apply changes in form to element
0078         checkBoxAutoApply = new QCheckBox(i18n("Automatically apply changes"), p);
0079         checkBoxAutoApply->setChecked(configGroup.readEntry(configKeyAutoApply, false));
0080         layout->addWidget(checkBoxAutoApply, 1, 0, 1, 1);
0081 
0082         /// Create a special widget that shows a small icon and a text
0083         /// stating that there are unsaved changes. It will be shown
0084         /// simultaneously when the Apply and Reset buttons are enabled.
0085         // TODO nearly identical code as in SearchResultsPrivate constructor, create common class
0086         widgetUnmodifiedChanges = new QWidget(p);
0087         layout->addWidget(widgetUnmodifiedChanges, 1, 1, 1, 1);
0088         QBoxLayout *layoutUnmodifiedChanges = new QHBoxLayout(widgetUnmodifiedChanges);
0089         layoutUnmodifiedChanges->addSpacing(32);
0090         QLabel *label = new QLabel(widgetUnmodifiedChanges);
0091         label->setAlignment(Qt::AlignCenter | Qt::AlignVCenter);
0092         label->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-information")).pixmap(KIconLoader::SizeSmall));
0093         layoutUnmodifiedChanges->addWidget(label);
0094         label = new QLabel(i18n("There are unsaved changes. Please press either 'Apply' or 'Reset'."), widgetUnmodifiedChanges);
0095         label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
0096         layoutUnmodifiedChanges->addWidget(label);
0097 
0098         buttonApply = new QPushButton(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")), i18n("Apply"), p);
0099         layout->addWidget(buttonApply, 1, 2, 1, 1);
0100 
0101         buttonReset = new QPushButton(QIcon::fromTheme(QStringLiteral("edit-undo")), i18n("Reset"), p);
0102         layout->addWidget(buttonReset, 1, 3, 1, 1);
0103 
0104         connect(checkBoxAutoApply, &QCheckBox::toggled, p, &ElementForm::autoApplyToggled);
0105         connect(buttonApply, &QPushButton::clicked, p, &ElementForm::validateAndOnlyThenApply);
0106         connect(buttonReset, &QPushButton::clicked, p, [this]() {
0107             reset();
0108         });
0109     }
0110 
0111     ~ElementFormPrivate() {
0112         delete elementEditor;
0113     }
0114 
0115     void refreshElement() {
0116         loadElement(element, file);
0117     }
0118 
0119     void loadElement(QSharedPointer<Element> element, const File *file) {
0120         /// store both element and file for later refresh
0121         this->element = element;
0122         this->file = file;
0123 
0124         /// skip whole process of loading an element if not visible
0125         if (isVisible())
0126             p->setEnabled(true);
0127         else {
0128             p->setEnabled(false);
0129             return;
0130         }
0131 
0132         elementEditor->setElement(element, file);
0133         elementEditor->setEnabled(!element.isNull());
0134 
0135         /// make apply and reset buttons aware of new element editor
0136         buttonApply->setEnabled(false);
0137         buttonReset->setEnabled(false);
0138         widgetUnmodifiedChanges->setVisible(false);
0139         gotModified = false;
0140     }
0141 
0142     bool isVisible() {
0143         /// get dock where this widget is inside
0144         /// static cast is save as constructor requires parent to be QDockWidget
0145         QDockWidget *pp = static_cast<QDockWidget *>(p->parent());
0146         return pp != nullptr && !pp->isHidden();
0147     }
0148 
0149     void apply() {
0150         elementEditor->apply();
0151         buttonApply->setEnabled(false);
0152         buttonReset->setEnabled(false);
0153         gotModified = false;
0154         widgetUnmodifiedChanges->setVisible(false);
0155     }
0156 
0157     void reset() {
0158         elementEditor->reset();
0159         buttonApply->setEnabled(false);
0160         buttonReset->setEnabled(false);
0161         gotModified = false;
0162         widgetUnmodifiedChanges->setVisible(false);
0163     }
0164 };
0165 
0166 const QString ElementForm::ElementFormPrivate::configGroupName = QStringLiteral("ElementForm");
0167 const QString ElementForm::ElementFormPrivate::configKeyAutoApply = QStringLiteral("AutoApply");
0168 
0169 ElementForm::ElementForm(MDIWidget *mdiWidget, QDockWidget *parent)
0170         : QWidget(parent), d(new ElementFormPrivate(mdiWidget, this))
0171 {
0172     connect(parent, &QDockWidget::visibilityChanged, this, [this]() {
0173         d->refreshElement();
0174     });
0175 }
0176 
0177 ElementForm::~ElementForm()
0178 {
0179     delete d;
0180 }
0181 
0182 void ElementForm::setElement(QSharedPointer<Element> element, const File *file)
0183 {
0184     /// Test if previous element (1) got modified, (2) the new element isn't
0185     /// the same as the new one, and (3) the user confirms to apply those
0186     /// changes rather than to discard them -> apply changes in previous element.
0187     /// FIXME If the previous element got delete from the file and therefore a different
0188     /// element gets set, changes will be still applied to the element to-be-deleted.
0189     if (d->gotModified && element != d->element &&
0190 #if KWIDGETSADDONS_VERSION < QT_VERSION_CHECK(5, 100, 0)
0191             KMessageBox::questionYesNo(this, i18n("The current element got modified.\nApply or discard changes?"), i18n("Element modified"), KGuiItem(i18n("Apply changes"), QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))), KGuiItem(i18n("Discard changes"), QIcon::fromTheme(QStringLiteral("edit-undo")))) == KMessageBox::Yes
0192 #else // >= 5.100.0
0193             KMessageBox::questionTwoActions(this, i18n("The current element got modified.\nApply or discard changes?"), i18n("Element modified"), KGuiItem(i18n("Apply changes"), QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))), KGuiItem(i18n("Discard changes"), QIcon::fromTheme(QStringLiteral("edit-undo")))) == KMessageBox::PrimaryAction
0194 #endif // KWIDGETSADDONS_VERSION < QT_VERSION_CHECK(5, 100, 0)
0195        ) {
0196         d->apply();
0197     }
0198     if (element != d->element) {
0199         /// Ignore loading the same element again
0200         d->loadElement(element, file);
0201     }
0202 }
0203 
0204 /**
0205  * Fetch the modified signal from the editing widget.
0206  * @param gotModified true if widget was modified by user, false if modified status was reset by e.g. apply operation
0207  */
0208 void ElementForm::modified(bool gotModified)
0209 {
0210     /// Only interested in modifications, not resets of modified status
0211     if (!gotModified) return;
0212 
0213     if (d->checkBoxAutoApply->isChecked()) {
0214         /// User wants to automatically apply changes, so do it
0215         // FIXME validateAndOnlyThenApply();
0216         apply();
0217     } else {
0218         /// No automatic apply, therefore enable buttons where user can
0219         /// apply or reset changes, plus show warning label about unsaved changes
0220         d->buttonApply->setEnabled(true);
0221         d->buttonReset->setEnabled(true);
0222         d->widgetUnmodifiedChanges->setVisible(true);
0223         d->gotModified = true;
0224     }
0225 }
0226 
0227 void ElementForm::apply()
0228 {
0229     d->apply();
0230 
0231     /// Notify rest of program (esp. main list) about changes
0232     Q_EMIT elementModified();
0233 
0234 }
0235 
0236 bool ElementForm::validateAndOnlyThenApply()
0237 {
0238     const bool isValid = d->elementEditor->validate();
0239     if (isValid)
0240         apply();
0241     return isValid;
0242 }
0243 
0244 /**
0245  * React on toggles of checkbox for auto-apply.
0246  * @param isChecked true if checkbox got checked, false if checkbox got unchecked
0247  */
0248 void ElementForm::autoApplyToggled(bool isChecked)
0249 {
0250     if (isChecked) {
0251         /// Got toggled to check state
0252         if (!d->element.isNull()) {
0253             validateAndOnlyThenApply();
0254         } else {
0255             /// The following settings would happen when calling apply(),
0256             /// but as no valid element is edited, perform settings here instead
0257             d->buttonApply->setEnabled(false);
0258             d->buttonReset->setEnabled(false);
0259             d->widgetUnmodifiedChanges->setVisible(false);
0260             d->gotModified = false;
0261         }
0262     }
0263 
0264     /// Save changed status of checkbox in configuration settings
0265     KConfigGroup configGroup(d->config, ElementFormPrivate::configGroupName);
0266     configGroup.writeEntry(ElementFormPrivate::configKeyAutoApply, d->checkBoxAutoApply->isChecked());
0267     configGroup.sync();
0268 }