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 }