1 /*************************************************************************** 2 * Copyright (C) 2004-2018 by Thomas Fischer <fischer@unix-ag.uni-kl.de> * 3 * * 4 * This program is free software; you can redistribute it and/or modify * 5 * it under the terms of the GNU General Public License as published by * 6 * the Free Software Foundation; either version 2 of the License, or * 7 * (at your option) any later version. * 8 * * 9 * This program is distributed in the hope that it will be useful, * 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 * GNU General Public License for more details. * 13 * * 14 * You should have received a copy of the GNU General Public License * 15 * along with this program; if not, see <https://www.gnu.org/licenses/>. * 16 ***************************************************************************/ 17 18 #include "elementform.h" 19 20 #include <QLayout> 21 #include <QDockWidget> 22 #include <QLabel> 23 #include <QCheckBox> 24 #include <QPushButton> 25 26 #include <KLocalizedString> 27 #include <KIconLoader> 28 #include <KConfigGroup> 29 #include <KSharedConfig> 30 #include <KMessageBox> 31 32 #include "entry.h" 33 #include "elementeditor.h" 34 #include "mdiwidget.h" 35 36 class ElementForm::ElementFormPrivate 37 { 38 private: 39 ElementForm *p; 40 QGridLayout *layout; 41 const File *file; 42 43 public: 44 ElementEditor *elementEditor; 45 MDIWidget *mdiWidget; 46 QCheckBox *checkBoxAutoApply; 47 QPushButton *buttonApply, *buttonReset; 48 QWidget *widgetUnmodifiedChanges; 49 bool gotModified; 50 QSharedPointer<Element> element; 51 52 KSharedConfigPtr config; 53 /// Group name in configuration file for all settings for this form 54 static const QString configGroupName; 55 /// Key to store/retrieve setting whether changes in form should be automatically applied to element or not 56 static const QString configKeyAutoApply; 57 58 ElementFormPrivate(MDIWidget *_mdiWidget, ElementForm *parent) 59 : p(parent), file(nullptr), mdiWidget(_mdiWidget), gotModified(false), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))) { 60 KConfigGroup configGroup(config, configGroupName); 61 62 layout = new QGridLayout(p); 63 layout->setColumnStretch(0, 10); 64 layout->setColumnStretch(1, 0); 65 layout->setColumnStretch(2, 0); 66 layout->setColumnStretch(3, 0); 67 68 elementEditor = new ElementEditor(true, p); 69 layout->addWidget(elementEditor, 0, 0, 1, 4); 70 elementEditor->setEnabled(false); 71 elementEditor->layout()->setMargin(0); 72 connect(elementEditor, &ElementEditor::modified, p, &ElementForm::modified); 73 74 /// Checkbox enabling/disabling setting to automatically apply changes in form to element 75 checkBoxAutoApply = new QCheckBox(i18n("Automatically apply changes"), p); 76 checkBoxAutoApply->setChecked(configGroup.readEntry(configKeyAutoApply, false)); 77 layout->addWidget(checkBoxAutoApply, 1, 0, 1, 1); 78 79 /// Create a special widget that shows a small icon and a text 80 /// stating that there are unsaved changes. It will be shown 81 /// simultaneously when the Apply and Reset buttons are enabled. 82 // TODO nearly identical code as in SearchResultsPrivate constructor, create common class 83 widgetUnmodifiedChanges = new QWidget(p); 84 layout->addWidget(widgetUnmodifiedChanges, 1, 1, 1, 1); 85 QBoxLayout *layoutUnmodifiedChanges = new QHBoxLayout(widgetUnmodifiedChanges); 86 layoutUnmodifiedChanges->addSpacing(32); 87 QLabel *label = new QLabel(widgetUnmodifiedChanges); 88 label->setAlignment(Qt::AlignCenter | Qt::AlignVCenter); 89 label->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-information"), KIconLoader::Dialog, KIconLoader::SizeSmall)); 90 layoutUnmodifiedChanges->addWidget(label); 91 label = new QLabel(i18n("There are unsaved changes. Please press either 'Apply' or 'Reset'."), widgetUnmodifiedChanges); 92 label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); 93 layoutUnmodifiedChanges->addWidget(label); 94 95 buttonApply = new QPushButton(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")), i18n("Apply"), p); 96 layout->addWidget(buttonApply, 1, 2, 1, 1); 97 98 buttonReset = new QPushButton(QIcon::fromTheme(QStringLiteral("edit-undo")), i18n("Reset"), p); 99 layout->addWidget(buttonReset, 1, 3, 1, 1); 100 101 connect(checkBoxAutoApply, &QCheckBox::toggled, p, &ElementForm::autoApplyToggled); 102 connect(buttonApply, &QPushButton::clicked, p, &ElementForm::validateAndOnlyThenApply); 103 connect(buttonReset, &QPushButton::clicked, p, &ElementForm::reset); 104 } 105 106 ~ElementFormPrivate() { 107 delete elementEditor; 108 } 109 110 void refreshElement() { 111 loadElement(element, file); 112 } 113 114 void loadElement(QSharedPointer<Element> element, const File *file) { 115 /// store both element and file for later refresh 116 this->element = element; 117 this->file = file; 118 119 /// skip whole process of loading an element if not visible 120 if (isVisible()) 121 p->setEnabled(true); 122 else { 123 p->setEnabled(false); 124 return; 125 } 126 127 elementEditor->setElement(element, file); 128 elementEditor->setEnabled(!element.isNull()); 129 130 /// make apply and reset buttons aware of new element editor 131 buttonApply->setEnabled(false); 132 buttonReset->setEnabled(false); 133 widgetUnmodifiedChanges->setVisible(false); 134 gotModified = false; 135 } 136 137 bool isVisible() { 138 /// get dock where this widget is inside 139 /// static cast is save as constructor requires parent to be QDockWidget 140 QDockWidget *pp = static_cast<QDockWidget *>(p->parent()); 141 return pp != nullptr && !pp->isHidden(); 142 } 143 144 void apply() { 145 elementEditor->apply(); 146 buttonApply->setEnabled(false); 147 buttonReset->setEnabled(false); 148 gotModified = false; 149 widgetUnmodifiedChanges->setVisible(false); 150 } 151 152 void reset() { 153 elementEditor->reset(); 154 buttonApply->setEnabled(false); 155 buttonReset->setEnabled(false); 156 gotModified = false; 157 widgetUnmodifiedChanges->setVisible(false); 158 } 159 }; 160 161 const QString ElementForm::ElementFormPrivate::configGroupName = QStringLiteral("ElementForm"); 162 const QString ElementForm::ElementFormPrivate::configKeyAutoApply = QStringLiteral("AutoApply"); 163 164 ElementForm::ElementForm(MDIWidget *mdiWidget, QDockWidget *parent) 165 : QWidget(parent), d(new ElementFormPrivate(mdiWidget, this)) 166 { 167 connect(parent, &QDockWidget::visibilityChanged, this, &ElementForm::visibilityChanged); 168 } 169 170 ElementForm::~ElementForm() 171 { 172 delete d; 173 } 174 175 void ElementForm::setElement(QSharedPointer<Element> element, const File *file) 176 { 177 /// Test if previous element (1) got modified, (2) the new element isn't 178 /// the same as the new one, and (3) the user confirms to apply those 179 /// changes rather than to discard them -> apply changes in previous element. 180 /// FIXME If the previous element got delete from the file and therefore a different 181 /// element gets set, changes will be still applied to the element to-be-deleted. 182 if (d->gotModified && element != d->element && 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) { 183 d->apply(); 184 } 185 if (element != d->element) { 186 /// Ignore loading the same element again 187 d->loadElement(element, file); 188 } 189 } 190 191 void ElementForm::refreshElement() 192 { 193 d->refreshElement(); 194 } 195 196 /** 197 * Fetch the modified signal from the editing widget. 198 * @param gotModified true if widget was modified by user, false if modified status was reset by e.g. apply operation 199 */ 200 void ElementForm::modified(bool gotModified) 201 { 202 /// Only interested in modifications, not resets of modified status 203 if (!gotModified) return; 204 205 if (d->checkBoxAutoApply->isChecked()) { 206 /// User wants to automatically apply changes, so do it 207 // FIXME validateAndOnlyThenApply(); 208 apply(); 209 } else { 210 /// No automatic apply, therefore enable buttons where user can 211 /// apply or reset changes, plus show warning label about unsaved changes 212 d->buttonApply->setEnabled(true); 213 d->buttonReset->setEnabled(true); 214 d->widgetUnmodifiedChanges->setVisible(true); 215 d->gotModified = true; 216 } 217 } 218 219 void ElementForm::apply() 220 { 221 d->apply(); 222 223 /// Notify rest of program (esp. main list) about changes 224 emit elementModified(); 225 226 } 227 228 bool ElementForm::validateAndOnlyThenApply() 229 { 230 const bool isValid = d->elementEditor->validate(); 231 if (isValid) 232 apply(); 233 return isValid; 234 } 235 236 void ElementForm::reset() 237 { 238 d->reset(); 239 } 240 241 void ElementForm::visibilityChanged(bool) 242 { 243 d->refreshElement(); 244 } 245 246 /** 247 * React on toggles of checkbox for auto-apply. 248 * @param isChecked true if checkbox got checked, false if checkbox got unchecked 249 */ 250 void ElementForm::autoApplyToggled(bool isChecked) 251 { 252 if (isChecked) { 253 /// Got toggled to check state 254 if (!d->element.isNull()) { 255 validateAndOnlyThenApply(); 256 } else { 257 /// The following settings would happen when calling apply(), 258 /// but as no valid element is edited, perform settings here instead 259 d->buttonApply->setEnabled(false); 260 d->buttonReset->setEnabled(false); 261 d->widgetUnmodifiedChanges->setVisible(false); 262 d->gotModified = false; 263 } 264 } 265 266 /// Save changed status of checkbox in configuration settings 267 KConfigGroup configGroup(d->config, ElementFormPrivate::configGroupName); 268 configGroup.writeEntry(ElementFormPrivate::configKeyAutoApply, d->checkBoxAutoApply->isChecked()); 269 configGroup.sync(); 270 } 271