1 /***************************************************************************
2 * Copyright (C) 2004-2020 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 "elementeditor.h"
19
20 #include <typeinfo>
21
22 #include <QCheckBox>
23 #include <QLabel>
24 #include <QLayout>
25 #include <QBuffer>
26 #include <QTextStream>
27 #include <QApplication>
28 #include <QFileInfo>
29 #include <QMenu>
30 #include <QScrollArea>
31 #include <QPushButton>
32
33 #include <KMessageBox>
34 #include <KLocalizedString>
35 #include <KSharedConfig>
36 #include <KConfigGroup>
37
38 #include "menulineedit.h"
39 #include "entry.h"
40 #include "comment.h"
41 #include "macro.h"
42 #include "preamble.h"
43 #include "element.h"
44 #include "file.h"
45 #include "elementwidgets.h"
46 #include "checkbibtex.h"
47 #include "hidingtabwidget.h"
48
49 class ElementEditor::ElementEditorPrivate : public ElementEditor::ApplyElementInterface
50 {
51 private:
52 typedef QVector<ElementWidget *> WidgetList;
53 WidgetList widgets;
54 const File *file;
55 QSharedPointer<Entry> internalEntry;
56 QSharedPointer<Macro> internalMacro;
57 QSharedPointer<Preamble> internalPreamble;
58 QSharedPointer<Comment> internalComment;
59 ElementEditor *p;
60 ElementWidget *previousWidget;
61 ReferenceWidget *referenceWidget;
62 SourceWidget *sourceWidget;
63 QPushButton *buttonCheckWithBibTeX;
64
65 /// Settings management through a push button with menu
66 KSharedConfigPtr config;
67 QPushButton *buttonOptions;
68 QAction *actionForceShowAllWidgets, *actionLimitKeyboardTabStops;
69
70 public:
71 QSharedPointer<Element> element;
72 HidingTabWidget *tab;
73 bool elementChanged, elementUnapplied;
74
ElementEditorPrivate(bool scrollable,ElementEditor * parent)75 ElementEditorPrivate(bool scrollable, ElementEditor *parent)
76 : file(nullptr), p(parent), previousWidget(nullptr), config(KSharedConfig::openConfig(QStringLiteral("kbibtexrc"))), elementChanged(false), elementUnapplied(false) {
77 internalEntry = QSharedPointer<Entry>();
78 internalMacro = QSharedPointer<Macro>();
79 internalComment = QSharedPointer<Comment>();
80 internalPreamble = QSharedPointer<Preamble>();
81
82 createGUI(scrollable);
83 }
84
~ElementEditorPrivate()85 ~ElementEditorPrivate() override {
86 clearWidgets();
87 }
88
clearWidgets()89 void clearWidgets() {
90 for (int i = widgets.count() - 1; i >= 0; --i) {
91 QWidget *w = widgets[i];
92 w->deleteLater();
93 }
94 widgets.clear();
95 }
96
setElement(QSharedPointer<Element> element,const File * file)97 void setElement(QSharedPointer<Element> element, const File *file) {
98 this->element = element;
99 this->file = file;
100 referenceWidget->setOriginalElement(element);
101 updateTabVisibility();
102 }
103
addTabWidgets()104 void addTabWidgets() {
105 for (const auto &etl : EntryLayout::instance()) {
106 ElementWidget *widget = new EntryConfiguredWidget(etl, tab);
107 connect(widget, &ElementWidget::modified, p, &ElementEditor::childModified);
108 widgets << widget;
109 if (previousWidget == nullptr)
110 previousWidget = widget; ///< memorize the first tab
111 int index = tab->addTab(widget, widget->icon(), widget->label());
112 tab->hideTab(index);
113 }
114
115 ElementWidget *widget = new PreambleWidget(tab);
116 connect(widget, &ElementWidget::modified, p, &ElementEditor::childModified);
117 widgets << widget;
118 int index = tab->addTab(widget, widget->icon(), widget->label());
119 tab->hideTab(index);
120
121 widget = new MacroWidget(tab);
122 connect(widget, &ElementWidget::modified, p, &ElementEditor::childModified);
123 widgets << widget;
124 index = tab->addTab(widget, widget->icon(), widget->label());
125 tab->hideTab(index);
126
127 FilesWidget *filesWidget = new FilesWidget(tab);
128 connect(filesWidget, &FilesWidget::modified, p, &ElementEditor::childModified);
129 widgets << filesWidget;
130 index = tab->addTab(filesWidget, filesWidget->icon(), filesWidget->label());
131 tab->hideTab(index);
132
133 QStringList blacklistedFields;
134
135 /// blacklist fields covered by EntryConfiguredWidget
136 for (const auto &etl : EntryLayout::instance())
137 for (const auto &sfl : const_cast<const QList<SingleFieldLayout> &>(etl->singleFieldLayouts))
138 blacklistedFields << sfl.bibtexLabel;
139
140 /// blacklist fields covered by FilesWidget
141 blacklistedFields << QString(Entry::ftUrl) << QString(Entry::ftLocalFile) << QString(Entry::ftFile) << QString(Entry::ftDOI) << QStringLiteral("ee") << QStringLiteral("biburl") << QStringLiteral("postscript");
142 for (int i = 2; i < 256; ++i) // FIXME replace number by constant
143 blacklistedFields << QString(Entry::ftUrl) + QString::number(i) << QString(Entry::ftLocalFile) + QString::number(i) << QString(Entry::ftFile) + QString::number(i) << QString(Entry::ftDOI) + QString::number(i) << QStringLiteral("ee") + QString::number(i) << QStringLiteral("postscript") + QString::number(i);
144
145 widget = new OtherFieldsWidget(blacklistedFields, tab);
146 connect(widget, &ElementWidget::modified, p, &ElementEditor::childModified);
147 widgets << widget;
148 index = tab->addTab(widget, widget->icon(), widget->label());
149 tab->hideTab(index);
150
151 sourceWidget = new SourceWidget(tab);
152 connect(sourceWidget, &ElementWidget::modified, p, &ElementEditor::childModified);
153 widgets << sourceWidget;
154 index = tab->addTab(sourceWidget, sourceWidget->icon(), sourceWidget->label());
155 tab->hideTab(index);
156 }
157
createGUI(bool scrollable)158 void createGUI(bool scrollable) {
159 /// load configuration for options push button
160 static const QString configGroupName = QStringLiteral("User Interface");
161 static const QString keyEnableAllWidgets = QStringLiteral("EnableAllWidgets");
162 KConfigGroup configGroup(config, configGroupName);
163 const bool showAll = configGroup.readEntry(keyEnableAllWidgets, true);
164 const bool limitKeyboardTabStops = configGroup.readEntry(MenuLineEdit::keyLimitKeyboardTabStops, false);
165
166 QBoxLayout *vLayout = new QVBoxLayout(p);
167
168 referenceWidget = new ReferenceWidget(p);
169 referenceWidget->setApplyElementInterface(this);
170 connect(referenceWidget, &ElementWidget::modified, p, &ElementEditor::childModified);
171 connect(referenceWidget, &ReferenceWidget::entryTypeChanged, p, &ElementEditor::updateReqOptWidgets);
172 vLayout->addWidget(referenceWidget, 0);
173 widgets << referenceWidget;
174
175 if (scrollable) {
176 QScrollArea *sa = new QScrollArea(p);
177 tab = new HidingTabWidget(sa);
178 sa->setFrameStyle(0);
179 sa->setWidget(tab);
180 sa->setWidgetResizable(true);
181 sa->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
182 sa->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
183 vLayout->addWidget(sa, 10);
184 } else {
185 tab = new HidingTabWidget(p);
186 vLayout->addWidget(tab, 10);
187 }
188
189 QBoxLayout *hLayout = new QHBoxLayout();
190 vLayout->addLayout(hLayout, 0);
191
192 /// Push button with menu to toggle various options
193 buttonOptions = new QPushButton(QIcon::fromTheme(QStringLiteral("configure")), i18n("Options"), p);
194 hLayout->addWidget(buttonOptions, 0);
195 QMenu *menuOptions = new QMenu(buttonOptions);
196 buttonOptions->setMenu(menuOptions);
197
198 /// Option to show all fields or only those require for current entry type
199 actionForceShowAllWidgets = menuOptions->addAction(i18n("Show all fields"), p, SLOT(updateReqOptWidgets()));
200 actionForceShowAllWidgets->setCheckable(true);
201 actionForceShowAllWidgets->setChecked(showAll);
202
203 /// Option to disable tab key focus to reach/visit various non-editable widgets
204 actionLimitKeyboardTabStops = menuOptions->addAction(i18n("Tab key visits only editable fields"), p, SLOT(limitKeyboardTabStops()));
205 actionLimitKeyboardTabStops->setCheckable(true);
206 actionLimitKeyboardTabStops->setChecked(limitKeyboardTabStops);
207
208 hLayout->addStretch(10);
209
210 buttonCheckWithBibTeX = new QPushButton(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18n("Check with BibTeX"), p);
211 hLayout->addWidget(buttonCheckWithBibTeX, 0);
212 connect(buttonCheckWithBibTeX, &QPushButton::clicked, p, &ElementEditor::checkBibTeX);
213
214 addTabWidgets();
215 }
216
updateTabVisibility()217 void updateTabVisibility() {
218 disconnect(tab, &HidingTabWidget::currentChanged, p, &ElementEditor::tabChanged);
219 if (element.isNull()) {
220 p->setEnabled(false);
221 } else {
222 p->setEnabled(true);
223 int firstEnabledTab = 1024;
224
225 for (ElementWidget *widget : const_cast<const WidgetList &>(widgets)) {
226 const int index = tab->indexOf(widget);
227 const bool canEdit = widget->canEdit(element.data());
228
229 if (widget == referenceWidget) {
230 /// Reference widget
231 widget->setVisible(canEdit);
232 widget->setEnabled(canEdit);
233 } else {
234 if (canEdit) tab->showTab(widget);
235 else if (index >= 0) tab->hideTab(index);
236 if (canEdit && index >= 0 && index < firstEnabledTab)
237 firstEnabledTab = index;
238 }
239 }
240 if (firstEnabledTab < 1024)
241 tab->setCurrentIndex(firstEnabledTab);
242 }
243 connect(tab, &HidingTabWidget::currentChanged, p, &ElementEditor::tabChanged);
244 }
245
246 /**
247 * If this element editor makes use of a reference widget
248 * (e.g. where entry type and entry id/macro key can be edited),
249 * then return the current value of the entry id/macro key
250 * editing widget.
251 * Otherwise, return an empty string.
252 *
253 * @return Current value of entry id/macro key if any, otherwise empty string
254 */
currentId() const255 QString currentId() const {
256 if (referenceWidget != nullptr)
257 return referenceWidget->currentId();
258 return QString();
259 }
260
setCurrentId(const QString & newId)261 void setCurrentId(const QString &newId) {
262 if (referenceWidget != nullptr)
263 return referenceWidget->setCurrentId(newId);
264 }
265
266 /**
267 * Return the current File object set for this element editor.
268 * May be NULL if nothing has been set or if it has been cleared.
269 *
270 * @return Current File object, may be nullptr
271 */
currentFile() const272 const File *currentFile() const {
273 return file;
274 }
275
apply()276 void apply() {
277 elementChanged = true;
278 elementUnapplied = false;
279 apply(element);
280 }
281
apply(QSharedPointer<Element> element)282 void apply(QSharedPointer<Element> element) override {
283 QSharedPointer<Entry> e = element.dynamicCast<Entry>();
284 QSharedPointer<Macro> m = e.isNull() ? element.dynamicCast<Macro>() : QSharedPointer<Macro>();
285 QSharedPointer<Comment> c = e.isNull() && m.isNull() ? element.dynamicCast<Comment>() : QSharedPointer<Comment>();
286 QSharedPointer<Preamble> p = e.isNull() && m.isNull() && c.isNull() ? element.dynamicCast<Preamble>() : QSharedPointer<Preamble>();
287
288 if (tab->currentWidget() == sourceWidget) {
289 /// Very simple if source view is active: BibTeX code contains
290 /// all necessary data
291 if (!e.isNull())
292 sourceWidget->setElementClass(SourceWidget::elementEntry);
293 else if (!m.isNull())
294 sourceWidget->setElementClass(SourceWidget::elementMacro);
295 else if (!p.isNull())
296 sourceWidget->setElementClass(SourceWidget::elementPreamble);
297 else if (!c.isNull())
298 sourceWidget->setElementClass(SourceWidget::elementComment);
299 else
300 sourceWidget->setElementClass(SourceWidget::elementInvalid);
301 sourceWidget->apply(element);
302 } else {
303 /// Start by assigning the current internal element's
304 /// data to the output element
305 if (!e.isNull())
306 *e = *internalEntry;
307 else {
308 if (!m.isNull())
309 *m = *internalMacro;
310 else {
311 if (!c.isNull())
312 *c = *internalComment;
313 else {
314 if (!p.isNull())
315 *p = *internalPreamble;
316 else
317 Q_ASSERT_X(element.isNull(), "ElementEditor::ElementEditorPrivate::apply(QSharedPointer<Element> element)", "element is not NULL but could not be cast on a valid Element sub-class");
318 }
319 }
320 }
321
322 /// The internal element may be outdated (only updated on tab switch),
323 /// so apply the reference widget's data on the output element
324 if (referenceWidget != nullptr)
325 referenceWidget->apply(element);
326 /// The internal element may be outdated (only updated on tab switch),
327 /// so apply the current widget's data on the output element
328 ElementWidget *currentElementWidget = qobject_cast<ElementWidget *>(tab->currentWidget());
329 if (currentElementWidget != nullptr)
330 currentElementWidget->apply(element);
331 }
332 }
333
validate(QWidget ** widgetWithIssue,QString & message) const334 bool validate(QWidget **widgetWithIssue, QString &message) const override {
335 if (tab->currentWidget() == sourceWidget) {
336 /// Source widget must check its textual content for being valid BibTeX code
337 return sourceWidget->validate(widgetWithIssue, message);
338 } else {
339 /// All widgets except for the source widget must validate their values
340 for (WidgetList::ConstIterator it = widgets.begin(); it != widgets.end(); ++it) {
341 if ((*it) == sourceWidget) continue;
342 const bool v = (*it)->validate(widgetWithIssue, message);
343 /// A single widget failing to validate lets the whole validation fail
344 if (!v) return false;
345 }
346 return true;
347 }
348 }
349
reset()350 void reset() {
351 elementChanged = false;
352 elementUnapplied = false;
353 reset(element);
354
355 /// show checkbox to enable all fields only if editing an entry
356 actionForceShowAllWidgets->setVisible(!internalEntry.isNull());
357 /// Disable widgets if necessary
358 if (!actionForceShowAllWidgets->isChecked())
359 updateReqOptWidgets();
360 }
361
reset(QSharedPointer<const Element> element)362 void reset(QSharedPointer<const Element> element) {
363 for (WidgetList::Iterator it = widgets.begin(); it != widgets.end(); ++it) {
364 (*it)->setFile(file);
365 (*it)->reset(element);
366 (*it)->setModified(false);
367 }
368
369 QSharedPointer<const Entry> e = element.dynamicCast<const Entry>();
370 if (!e.isNull()) {
371 internalEntry = QSharedPointer<Entry>(new Entry(*e.data()));
372 sourceWidget->setElementClass(SourceWidget::elementEntry);
373 } else {
374 QSharedPointer<const Macro> m = element.dynamicCast<const Macro>();
375 if (!m.isNull()) {
376 internalMacro = QSharedPointer<Macro>(new Macro(*m.data()));
377 sourceWidget->setElementClass(SourceWidget::elementMacro);
378 } else {
379 QSharedPointer<const Comment> c = element.dynamicCast<const Comment>();
380 if (!c.isNull()) {
381 internalComment = QSharedPointer<Comment>(new Comment(*c.data()));
382 sourceWidget->setElementClass(SourceWidget::elementComment);
383 } else {
384 QSharedPointer<const Preamble> p = element.dynamicCast<const Preamble>();
385 if (!p.isNull()) {
386 internalPreamble = QSharedPointer<Preamble>(new Preamble(*p.data()));
387 sourceWidget->setElementClass(SourceWidget::elementPreamble);
388 } else
389 Q_ASSERT_X(element.isNull(), "ElementEditor::ElementEditorPrivate::reset(QSharedPointer<const Element> element)", "element is not NULL but could not be cast on a valid Element sub-class");
390 }
391 }
392 }
393
394 buttonCheckWithBibTeX->setEnabled(!internalEntry.isNull());
395 }
396
setReadOnly(bool isReadOnly)397 void setReadOnly(bool isReadOnly) {
398 for (WidgetList::Iterator it = widgets.begin(); it != widgets.end(); ++it)
399 (*it)->setReadOnly(isReadOnly);
400 }
401
updateReqOptWidgets()402 void updateReqOptWidgets() {
403 /// this function is only relevant if editing an entry (and not e.g. a comment)
404 if (internalEntry.isNull()) return; /// quick-and-dirty test if editing an entry
405
406 /// make a temporary snapshot of the current state
407 QSharedPointer<Entry> tempEntry = QSharedPointer<Entry>(new Entry());
408 apply(tempEntry);
409
410 /// update the enabled/disabled state of required and optional widgets/fields
411 bool forceVisible = actionForceShowAllWidgets->isChecked();
412 for (ElementWidget *elementWidget : const_cast<const WidgetList &>(widgets)) {
413 elementWidget->showReqOptWidgets(forceVisible, tempEntry->type());
414 }
415
416 /// save configuration
417 static const QString configGroupName = QStringLiteral("User Interface");
418 static const QString keyEnableAllWidgets = QStringLiteral("EnableAllWidgets");
419 KConfigGroup configGroup(config, configGroupName);
420 configGroup.writeEntry(keyEnableAllWidgets, actionForceShowAllWidgets->isChecked());
421 config->sync();
422 }
423
limitKeyboardTabStops()424 void limitKeyboardTabStops() {
425 /// save configuration
426 static const QString configGroupName = QStringLiteral("User Interface");
427 KConfigGroup configGroup(config, configGroupName);
428 configGroup.writeEntry(MenuLineEdit::keyLimitKeyboardTabStops, actionLimitKeyboardTabStops->isChecked());
429 config->sync();
430
431 /// notify all listening MenuLineEdit widgets to change their behavior
432 NotificationHub::publishEvent(MenuLineEdit::MenuLineConfigurationChangedEvent);
433 }
434
switchTo(QWidget * futureTab)435 void switchTo(QWidget *futureTab) {
436 /// Switched from source widget to another widget?
437 const bool isToSourceWidget = futureTab == sourceWidget;
438 /// Switch from some widget to the source widget?
439 const bool isFromSourceWidget = previousWidget == sourceWidget;
440 /// Interprete future widget as an ElementWidget
441 ElementWidget *futureWidget = qobject_cast<ElementWidget *>(futureTab);
442 /// Past and future ElementWidget values are valid?
443 if (previousWidget != nullptr && futureWidget != nullptr) {
444 /// Assign to temp wihch internal variable holds current state
445 QSharedPointer<Element> temp;
446 if (!internalEntry.isNull())
447 temp = internalEntry;
448 else if (!internalMacro.isNull())
449 temp = internalMacro;
450 else if (!internalComment.isNull())
451 temp = internalComment;
452 else if (!internalPreamble.isNull())
453 temp = internalPreamble;
454 Q_ASSERT_X(!temp.isNull(), "void ElementEditor::ElementEditorPrivate::switchTo(QWidget *newTab)", "temp is NULL");
455
456 /// Past widget writes its state to the internal state
457 previousWidget->apply(temp);
458 /// Before switching to source widget, store internally reference widget's state
459 if (isToSourceWidget && referenceWidget != nullptr) referenceWidget->apply(temp);
460 /// Tell future widget to initialize itself based on internal state
461 futureWidget->reset(temp);
462 /// When switchin from source widget to another widget, initialize reference widget
463 if (isFromSourceWidget && referenceWidget != nullptr)
464 referenceWidget->reset(temp);
465 }
466 previousWidget = futureWidget;
467
468 /// Enable/disable tabs
469 for (WidgetList::Iterator it = widgets.begin(); it != widgets.end(); ++it)
470 (*it)->setEnabled(!isToSourceWidget || *it == futureTab);
471 }
472
473 /**
474 * Test current entry if it compiles with BibTeX.
475 * Show warnings and errors in message box.
476 */
checkBibTeX()477 void checkBibTeX() {
478 /// disable GUI under process
479 p->setEnabled(false);
480 QSharedPointer<Entry> entry = QSharedPointer<Entry>(new Entry());
481 apply(entry);
482 CheckBibTeX::checkBibTeX(entry, file, p);
483 p->setEnabled(true);
484 }
485
setModified(bool newIsModified)486 void setModified(bool newIsModified) {
487 for (WidgetList::Iterator it = widgets.begin(); it != widgets.end(); ++it)
488 (*it)->setModified(newIsModified);
489 }
490
referenceWidgetSetEntryIdByDefault()491 void referenceWidgetSetEntryIdByDefault() {
492 referenceWidget->setEntryIdByDefault();
493 }
494 };
495
ElementEditor(bool scrollable,QWidget * parent)496 ElementEditor::ElementEditor(bool scrollable, QWidget *parent)
497 : QWidget(parent), d(new ElementEditorPrivate(scrollable, this))
498 {
499 connect(d->tab, &HidingTabWidget::currentChanged, this, &ElementEditor::tabChanged);
500 }
501
~ElementEditor()502 ElementEditor::~ElementEditor()
503 {
504 disconnect(d->tab, &HidingTabWidget::currentChanged, this, &ElementEditor::tabChanged);
505 delete d;
506 }
507
apply()508 void ElementEditor::apply()
509 {
510 /// The prime problem to tackle in this function is to cope with
511 /// invalid/problematic entry ids or macro keys, respectively:
512 /// - empty ids/keys
513 /// - ids/keys that are duplicates of already used ids/keys
514
515 QSharedPointer<Entry> entry = d->element.dynamicCast<Entry>();
516 QSharedPointer<Macro> macro = d->element.dynamicCast<Macro>();
517 /// Only for entry or macro bother with duplicate ids (but not for preamble or comment)
518 if (!entry.isNull() || !macro.isNull()) {
519 /// Determine id/key as it was set before the current editing started
520 const QString originalId = !entry.isNull() ? entry->id() : (!macro.isNull() ? macro->key() : QString());
521 /// Get the id/key as it is in the editing widget right now
522 const QString newId = d->currentId();
523 /// Keep track whether the 'original' id/key or the 'new' id/key will eventually be used
524 enum IdToUse {UseOriginalId, UseNewId};
525 IdToUse idToUse = UseNewId;
526
527 if (newId.isEmpty() && !originalId.isEmpty()) {
528 /// New id/key is empty (invalid by definition), so just notify use and revert back to original id/key
529 /// (assuming that original id/key is valid)
530 KMessageBox::sorry(this, i18n("No id was entered, so the previous id '%1' will be restored.", originalId), i18n("No id given"));
531 idToUse = UseOriginalId;
532 } else if (!newId.isEmpty()) { // FIXME test if !originalId.isEmpty() ?
533 /// If new id/key is not empty, then check if it is identical to another entry/macro in the current file
534 const QSharedPointer<Element> knownElementWithSameId = d->currentFile() != nullptr ? d->currentFile()->containsKey(newId) : QSharedPointer<Element>();
535 if (!knownElementWithSameId.isNull() && d->element != knownElementWithSameId) {
536 /// Some other, different element (entry or macro) uses same id/key, so ask user how to proceed
537 const int msgBoxResult = KMessageBox::warningContinueCancel(this, i18n("The entered id '%1' is already in use for another element.\n\nKeep original id '%2' instead?", newId, originalId), i18n("Id already in use"), KGuiItem(i18n("Keep duplicate ids")), KGuiItem(i18n("Restore original id")));
538 idToUse = msgBoxResult == KMessageBox::Continue ? UseNewId : UseOriginalId;
539 }
540 }
541
542 if (idToUse == UseOriginalId) {
543 /// As 'apply()' above set the 'new' id/key but the 'original' id/key is to be used,
544 /// now UI must be updated accordingly. Changes will propagate to the entry id or
545 /// macro key, respectively, when invoking apply() further down
546 d->setCurrentId(originalId);
547 }
548 /// Case idToUse == UseNewId does not need to get handled as newId == d->currentId()
549 }
550
551 d->apply();
552 d->setModified(false);
553 emit modified(false);
554 }
555
reset()556 void ElementEditor::reset()
557 {
558 d->reset();
559 emit modified(false);
560 }
561
validate()562 bool ElementEditor::validate() {
563 QWidget *widgetWithIssue = nullptr;
564 QString message;
565 if (!validate(&widgetWithIssue, message)) {
566 const QString msgBoxMessage = message.isEmpty() ? i18n("Validation for the current element failed.") : i18n("Validation for the current element failed:\n%1", message);
567 KMessageBox::error(this, msgBoxMessage, i18n("Element validation failed"));
568 if (widgetWithIssue != nullptr) {
569 /// Probe if widget with issue is inside a QTabWiget; if yes, make parenting tab the current tab
570 QWidget *cur = widgetWithIssue;
571 do {
572 QTabWidget *tabWidget = cur->parent() != nullptr && cur->parent()->parent() != nullptr ? qobject_cast<QTabWidget *>(cur->parent()->parent()) : nullptr;
573 if (tabWidget != nullptr) {
574 tabWidget->setCurrentWidget(cur);
575 break;
576 }
577 cur = qobject_cast<QWidget *>(cur->parent());
578 } while (cur != nullptr);
579 /// Set focus to widget with issue
580 widgetWithIssue->setFocus();
581 }
582 return false;
583 }
584 return true;
585 }
586
setElement(QSharedPointer<Element> element,const File * file)587 void ElementEditor::setElement(QSharedPointer<Element> element, const File *file)
588 {
589 d->setElement(element, file);
590 d->reset();
591 emit modified(false);
592 }
593
setElement(QSharedPointer<const Element> element,const File * file)594 void ElementEditor::setElement(QSharedPointer<const Element> element, const File *file)
595 {
596 QSharedPointer<Element> clone;
597 QSharedPointer<const Entry> entry = element.dynamicCast<const Entry>();
598 if (!entry.isNull())
599 clone = QSharedPointer<Entry>(new Entry(*entry.data()));
600 else {
601 QSharedPointer<const Macro> macro = element.dynamicCast<const Macro>();
602 if (!macro.isNull())
603 clone = QSharedPointer<Macro>(new Macro(*macro.data()));
604 else {
605 QSharedPointer<const Preamble> preamble = element.dynamicCast<const Preamble>();
606 if (!preamble.isNull())
607 clone = QSharedPointer<Preamble>(new Preamble(*preamble.data()));
608 else {
609 QSharedPointer<const Comment> comment = element.dynamicCast<const Comment>();
610 if (!comment.isNull())
611 clone = QSharedPointer<Comment>(new Comment(*comment.data()));
612 else
613 Q_ASSERT_X(element == nullptr, "ElementEditor::ElementEditor(const Element *element, QWidget *parent)", "element is not NULL but could not be cast on a valid Element sub-class");
614 }
615 }
616 }
617
618 d->setElement(clone, file);
619 d->reset();
620 }
621
setReadOnly(bool isReadOnly)622 void ElementEditor::setReadOnly(bool isReadOnly)
623 {
624 d->setReadOnly(isReadOnly);
625 }
626
elementChanged()627 bool ElementEditor::elementChanged()
628 {
629 return d->elementChanged;
630 }
631
elementUnapplied()632 bool ElementEditor::elementUnapplied()
633 {
634 return d->elementUnapplied;
635 }
636
validate(QWidget ** widgetWithIssue,QString & message)637 bool ElementEditor::validate(QWidget **widgetWithIssue, QString &message)
638 {
639 return d->validate(widgetWithIssue, message);
640 }
641
currentPage() const642 QWidget *ElementEditor::currentPage() const
643 {
644 return d->tab->currentWidget();
645 }
646
setCurrentPage(QWidget * page)647 void ElementEditor::setCurrentPage(QWidget *page)
648 {
649 if (d->tab->indexOf(page) >= 0)
650 d->tab->setCurrentWidget(page);
651 }
652
tabChanged()653 void ElementEditor::tabChanged()
654 {
655 d->switchTo(d->tab->currentWidget());
656 }
657
checkBibTeX()658 void ElementEditor::checkBibTeX()
659 {
660 d->checkBibTeX();
661 }
662
childModified(bool m)663 void ElementEditor::childModified(bool m)
664 {
665 if (m) {
666 d->elementUnapplied = true;
667 d->referenceWidgetSetEntryIdByDefault();
668 }
669 emit modified(m);
670 }
671
updateReqOptWidgets()672 void ElementEditor::updateReqOptWidgets()
673 {
674 d->updateReqOptWidgets();
675 }
676
limitKeyboardTabStops()677 void ElementEditor::limitKeyboardTabStops()
678 {
679 d->limitKeyboardTabStops();
680 }
681