1 /*
2     This file is part of the KDE libraries
3     SPDX-FileCopyrightText: 2002, 2003 Anders Lund <anders.lund@lund.tdcadsl.dk>
4     SPDX-FileCopyrightText: 2003 Christoph Cullmann <cullmann@kde.org>
5     SPDX-FileCopyrightText: 2001 Joseph Wenninger <jowenn@kde.org>
6     SPDX-FileCopyrightText: 2006 Dominik Haumann <dhdev@gmx.de>
7     SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch>
8     SPDX-FileCopyrightText: 2009 Michel Ludwig <michel.ludwig@kdemail.net>
9     SPDX-FileCopyrightText: 2009 Erlend Hamberg <ehamberg@gmail.com>
10 
11     Based on work of:
12     SPDX-FileCopyrightText: 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de>
13 
14     SPDX-License-Identifier: LGPL-2.0-only
15 */
16 
17 // BEGIN Includes
18 #include "katedialogs.h"
19 
20 #include <ktexteditor/message.h>
21 #include <ktexteditor_version.h>
22 
23 #include "kateautoindent.h"
24 #include "katebuffer.h"
25 #include "kateconfig.h"
26 #include "katedocument.h"
27 #include "kateglobal.h"
28 #include "katemodeconfigpage.h"
29 #include "kateview.h"
30 #include "spellcheck/spellcheck.h"
31 
32 // auto generated ui files
33 #include "ui_bordersappearanceconfigwidget.h"
34 #include "ui_completionconfigtab.h"
35 #include "ui_editconfigwidget.h"
36 #include "ui_indentationconfigwidget.h"
37 #include "ui_navigationconfigwidget.h"
38 #include "ui_opensaveconfigadvwidget.h"
39 #include "ui_opensaveconfigwidget.h"
40 #include "ui_spellcheckconfigwidget.h"
41 #include "ui_textareaappearanceconfigwidget.h"
42 
43 #include <KIO/Job>
44 #include <KIO/JobUiDelegate>
45 #include <KIO/OpenUrlJob>
46 
47 #include "kateabstractinputmodefactory.h"
48 #include "katepartdebug.h"
49 #include <KActionCollection>
50 #include <KCharsets>
51 #include <KColorCombo>
52 #include <KFontRequester>
53 #include <KMessageBox>
54 #include <KProcess>
55 #include <KSeparator>
56 
57 #include <QCheckBox>
58 #include <QClipboard>
59 #include <QComboBox>
60 #include <QDomDocument>
61 #include <QFile>
62 #include <QFileDialog>
63 #include <QGroupBox>
64 #include <QKeyEvent>
65 #include <QLabel>
66 #include <QLayout>
67 #include <QMap>
68 #include <QPainter>
69 #include <QRadioButton>
70 #include <QSettings>
71 #include <QSlider>
72 #include <QSpinBox>
73 #include <QStringList>
74 #include <QTabBar>
75 #include <QTemporaryFile>
76 #include <QTextCodec>
77 #include <QTextStream>
78 #include <QToolButton>
79 #include <QWhatsThis>
80 
81 // END
82 
83 // BEGIN KateIndentConfigTab
KateIndentConfigTab(QWidget * parent)84 KateIndentConfigTab::KateIndentConfigTab(QWidget *parent)
85     : KateConfigPage(parent)
86 {
87     // This will let us have more separation between this page and
88     // the QTabWidget edge (ereslibre)
89     QVBoxLayout *layout = new QVBoxLayout(this);
90     QWidget *newWidget = new QWidget(this);
91 
92     ui = new Ui::IndentationConfigWidget();
93     ui->setupUi(newWidget);
94 
95     ui->cmbMode->addItems(KateAutoIndent::listModes());
96 
97     // FIXME Give ui->label a more descriptive name, it's these "More..." info about tab key action
98     ui->label->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard);
99     connect(ui->label, &QLabel::linkActivated, this, &KateIndentConfigTab::showWhatsThis);
100 
101     // "What's This?" help can be found in the ui file
102 
103     reload();
104 
105     observeChanges(ui->chkBackspaceUnindents);
106     observeChanges(ui->chkIndentPaste);
107     observeChanges(ui->chkKeepExtraSpaces);
108     observeChanges(ui->cmbMode);
109     observeChanges(ui->rbIndentMixed);
110     observeChanges(ui->rbIndentWithSpaces);
111     observeChanges(ui->rbIndentWithTabs);
112     connect(ui->rbIndentWithTabs, &QAbstractButton::toggled, ui->sbIndentWidth, &QWidget::setDisabled);
113     connect(ui->rbIndentWithTabs, &QAbstractButton::toggled, this, &KateIndentConfigTab::slotChanged); // FIXME See slot below
114     observeChanges(ui->rbTabAdvances);
115     observeChanges(ui->rbTabIndents);
116     observeChanges(ui->rbTabSmart);
117     observeChanges(ui->sbIndentWidth);
118     observeChanges(ui->sbTabWidth);
119 
120     layout->addWidget(newWidget);
121 }
122 
~KateIndentConfigTab()123 KateIndentConfigTab::~KateIndentConfigTab()
124 {
125     delete ui;
126 }
127 
slotChanged()128 void KateIndentConfigTab::slotChanged()
129 {
130     // FIXME Make it working without this quirk
131     // When the value is not copied it will silently set back to "Tabs & Spaces"
132     if (ui->rbIndentWithTabs->isChecked()) {
133         ui->sbIndentWidth->setValue(ui->sbTabWidth->value());
134     }
135 }
136 
137 // NOTE Should we have more use of such info stuff, consider to make it part
138 // of KateConfigPage and add a similar function like observeChanges(..)
showWhatsThis(const QString & text)139 void KateIndentConfigTab::showWhatsThis(const QString &text)
140 {
141     QWhatsThis::showText(QCursor::pos(), text);
142 }
143 
apply()144 void KateIndentConfigTab::apply()
145 {
146     // nothing changed, no need to apply stuff
147     if (!hasChanged()) {
148         return;
149     }
150     m_changed = false;
151 
152     KateDocumentConfig::global()->configStart();
153 
154     KateDocumentConfig::global()->setBackspaceIndents(ui->chkBackspaceUnindents->isChecked());
155     KateDocumentConfig::global()->setIndentPastedText(ui->chkIndentPaste->isChecked());
156     KateDocumentConfig::global()->setIndentationMode(KateAutoIndent::modeName(ui->cmbMode->currentIndex()));
157     KateDocumentConfig::global()->setIndentationWidth(ui->sbIndentWidth->value());
158     KateDocumentConfig::global()->setKeepExtraSpaces(ui->chkKeepExtraSpaces->isChecked());
159     KateDocumentConfig::global()->setReplaceTabsDyn(ui->rbIndentWithSpaces->isChecked());
160     KateDocumentConfig::global()->setTabWidth(ui->sbTabWidth->value());
161 
162     if (ui->rbTabAdvances->isChecked()) {
163         KateDocumentConfig::global()->setTabHandling(KateDocumentConfig::tabInsertsTab);
164     } else if (ui->rbTabIndents->isChecked()) {
165         KateDocumentConfig::global()->setTabHandling(KateDocumentConfig::tabIndents);
166     } else {
167         KateDocumentConfig::global()->setTabHandling(KateDocumentConfig::tabSmart);
168     }
169 
170     KateDocumentConfig::global()->configEnd();
171 }
172 
reload()173 void KateIndentConfigTab::reload()
174 {
175     ui->chkBackspaceUnindents->setChecked(KateDocumentConfig::global()->backspaceIndents());
176     ui->chkIndentPaste->setChecked(KateDocumentConfig::global()->indentPastedText());
177     ui->chkKeepExtraSpaces->setChecked(KateDocumentConfig::global()->keepExtraSpaces());
178 
179     ui->sbIndentWidth->setSuffix(ki18np(" character", " characters"));
180     ui->sbIndentWidth->setValue(KateDocumentConfig::global()->indentationWidth());
181     ui->sbTabWidth->setSuffix(ki18np(" character", " characters"));
182     ui->sbTabWidth->setValue(KateDocumentConfig::global()->tabWidth());
183 
184     ui->rbTabAdvances->setChecked(KateDocumentConfig::global()->tabHandling() == KateDocumentConfig::tabInsertsTab);
185     ui->rbTabIndents->setChecked(KateDocumentConfig::global()->tabHandling() == KateDocumentConfig::tabIndents);
186     ui->rbTabSmart->setChecked(KateDocumentConfig::global()->tabHandling() == KateDocumentConfig::tabSmart);
187 
188     ui->cmbMode->setCurrentIndex(KateAutoIndent::modeNumber(KateDocumentConfig::global()->indentationMode()));
189 
190     if (KateDocumentConfig::global()->replaceTabsDyn()) {
191         ui->rbIndentWithSpaces->setChecked(true);
192     } else {
193         if (KateDocumentConfig::global()->indentationWidth() == KateDocumentConfig::global()->tabWidth()) {
194             ui->rbIndentWithTabs->setChecked(true);
195         } else {
196             ui->rbIndentMixed->setChecked(true);
197         }
198     }
199 
200     ui->sbIndentWidth->setEnabled(!ui->rbIndentWithTabs->isChecked());
201 }
202 
name() const203 QString KateIndentConfigTab::name() const
204 {
205     return i18n("Indentation");
206 }
207 
208 // END KateIndentConfigTab
209 
210 // BEGIN KateCompletionConfigTab
KateCompletionConfigTab(QWidget * parent)211 KateCompletionConfigTab::KateCompletionConfigTab(QWidget *parent)
212     : KateConfigPage(parent)
213 {
214     // This will let us have more separation between this page and
215     // the QTabWidget edge (ereslibre)
216     QVBoxLayout *layout = new QVBoxLayout(this);
217     QWidget *newWidget = new QWidget(this);
218 
219     ui = new Ui::CompletionConfigTab();
220     ui->setupUi(newWidget);
221 
222     // "What's This?" help can be found in the ui file
223 
224     reload();
225 
226     observeChanges(ui->chkAutoCompletionEnabled);
227     observeChanges(ui->chkAutoSelectFirstEntry);
228     observeChanges(ui->gbKeywordCompletion);
229     observeChanges(ui->gbWordCompletion);
230     observeChanges(ui->minimalWordLength);
231     observeChanges(ui->removeTail);
232 
233     layout->addWidget(newWidget);
234 }
235 
~KateCompletionConfigTab()236 KateCompletionConfigTab::~KateCompletionConfigTab()
237 {
238     delete ui;
239 }
240 
showWhatsThis(const QString & text)241 void KateCompletionConfigTab::showWhatsThis(const QString &text) // NOTE Not used atm, remove? See also KateIndentConfigTab::showWhatsThis
242 {
243     QWhatsThis::showText(QCursor::pos(), text);
244 }
245 
apply()246 void KateCompletionConfigTab::apply()
247 {
248     // nothing changed, no need to apply stuff
249     if (!hasChanged()) {
250         return;
251     }
252     m_changed = false;
253 
254     KateViewConfig::global()->configStart();
255 
256     KateViewConfig::global()->setValue(KateViewConfig::AutomaticCompletionInvocation, ui->chkAutoCompletionEnabled->isChecked());
257     KateViewConfig::global()->setValue(KateViewConfig::AutomaticCompletionPreselectFirst, ui->chkAutoSelectFirstEntry->isChecked());
258     KateViewConfig::global()->setValue(KateViewConfig::KeywordCompletion, ui->gbKeywordCompletion->isChecked());
259     KateViewConfig::global()->setValue(KateViewConfig::WordCompletion, ui->gbWordCompletion->isChecked());
260     KateViewConfig::global()->setValue(KateViewConfig::WordCompletionMinimalWordLength, ui->minimalWordLength->value());
261     KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, ui->removeTail->isChecked());
262 
263     KateViewConfig::global()->configEnd();
264 }
265 
reload()266 void KateCompletionConfigTab::reload()
267 {
268     ui->chkAutoCompletionEnabled->setChecked(KateViewConfig::global()->automaticCompletionInvocation());
269     ui->chkAutoSelectFirstEntry->setChecked(KateViewConfig::global()->automaticCompletionPreselectFirst());
270 
271     ui->gbKeywordCompletion->setChecked(KateViewConfig::global()->keywordCompletion());
272     ui->gbWordCompletion->setChecked(KateViewConfig::global()->wordCompletion());
273 
274     ui->minimalWordLength->setValue(KateViewConfig::global()->wordCompletionMinimalWordLength());
275     ui->removeTail->setChecked(KateViewConfig::global()->wordCompletionRemoveTail());
276 }
277 
name() const278 QString KateCompletionConfigTab::name() const
279 {
280     return i18n("Auto Completion");
281 }
282 
283 // END KateCompletionConfigTab
284 
285 // BEGIN KateSpellCheckConfigTab
KateSpellCheckConfigTab(QWidget * parent)286 KateSpellCheckConfigTab::KateSpellCheckConfigTab(QWidget *parent)
287     : KateConfigPage(parent)
288 {
289     // This will let us have more separation between this page and
290     // the QTabWidget edge (ereslibre)
291     QVBoxLayout *layout = new QVBoxLayout(this);
292     QWidget *newWidget = new QWidget(this);
293 
294     ui = new Ui::SpellCheckConfigWidget();
295     ui->setupUi(newWidget);
296 
297     // "What's This?" help can be found in the ui file
298 
299     reload();
300 
301     m_sonnetConfigWidget = new Sonnet::ConfigWidget(this);
302     connect(m_sonnetConfigWidget, &Sonnet::ConfigWidget::configChanged, this, &KateSpellCheckConfigTab::slotChanged);
303     layout->addWidget(m_sonnetConfigWidget);
304 
305     layout->addWidget(newWidget);
306 }
307 
~KateSpellCheckConfigTab()308 KateSpellCheckConfigTab::~KateSpellCheckConfigTab()
309 {
310     delete ui;
311 }
312 
showWhatsThis(const QString & text)313 void KateSpellCheckConfigTab::showWhatsThis(const QString &text) // NOTE Not used atm, remove? See also KateIndentConfigTab::showWhatsThis
314 {
315     QWhatsThis::showText(QCursor::pos(), text);
316 }
317 
apply()318 void KateSpellCheckConfigTab::apply()
319 {
320     if (!hasChanged()) {
321         // nothing changed, no need to apply stuff
322         return;
323     }
324     m_changed = false;
325 
326     // WARNING: this is slightly hackish, but it's currently the only way to
327     //          do it, see also the KTextEdit class
328     KateDocumentConfig::global()->configStart();
329     m_sonnetConfigWidget->save();
330     QSettings settings(QStringLiteral("KDE"), QStringLiteral("Sonnet"));
331     KateDocumentConfig::global()->setOnTheFlySpellCheck(settings.value(QStringLiteral("checkerEnabledByDefault"), false).toBool());
332     KateDocumentConfig::global()->configEnd();
333 
334     const auto docs = KTextEditor::EditorPrivate::self()->kateDocuments();
335     for (KTextEditor::DocumentPrivate *doc : docs) {
336         doc->refreshOnTheFlyCheck();
337     }
338 }
339 
reload()340 void KateSpellCheckConfigTab::reload()
341 {
342     // does nothing
343 }
344 
name() const345 QString KateSpellCheckConfigTab::name() const
346 {
347     return i18n("Spellcheck");
348 }
349 
350 // END KateSpellCheckConfigTab
351 
352 // BEGIN KateNavigationConfigTab
KateNavigationConfigTab(QWidget * parent)353 KateNavigationConfigTab::KateNavigationConfigTab(QWidget *parent)
354     : KateConfigPage(parent)
355 {
356     // This will let us having more separation between this page and
357     // the QTabWidget edge (ereslibre)
358     QVBoxLayout *layout = new QVBoxLayout(this);
359     QWidget *newWidget = new QWidget(this);
360 
361     ui = new Ui::NavigationConfigWidget();
362     ui->setupUi(newWidget);
363 
364     // "What's This?" help can be found in the ui file
365 
366     reload();
367 
368     observeChanges(ui->cbTextSelectionMode);
369     observeChanges(ui->chkBackspaceRemoveComposed);
370     observeChanges(ui->chkPagingMovesCursor);
371     observeChanges(ui->chkScrollPastEnd);
372     observeChanges(ui->chkSmartHome);
373     observeChanges(ui->sbAutoCenterCursor);
374     observeChanges(ui->chkCamelCursor);
375 
376     layout->addWidget(newWidget);
377 }
378 
~KateNavigationConfigTab()379 KateNavigationConfigTab::~KateNavigationConfigTab()
380 {
381     delete ui;
382 }
383 
apply()384 void KateNavigationConfigTab::apply()
385 {
386     // nothing changed, no need to apply stuff
387     if (!hasChanged()) {
388         return;
389     }
390     m_changed = false;
391 
392     KateViewConfig::global()->configStart();
393     KateDocumentConfig::global()->configStart();
394 
395     KateDocumentConfig::global()->setPageUpDownMovesCursor(ui->chkPagingMovesCursor->isChecked());
396     KateDocumentConfig::global()->setSmartHome(ui->chkSmartHome->isChecked());
397     KateDocumentConfig::global()->setCamelCursor(ui->chkCamelCursor->isChecked());
398 
399     KateViewConfig::global()->setValue(KateViewConfig::AutoCenterLines, ui->sbAutoCenterCursor->value());
400     KateViewConfig::global()->setValue(KateViewConfig::BackspaceRemoveComposedCharacters, ui->chkBackspaceRemoveComposed->isChecked());
401     KateViewConfig::global()->setValue(KateViewConfig::PersistentSelection, ui->cbTextSelectionMode->currentIndex() == 1);
402     KateViewConfig::global()->setValue(KateViewConfig::ScrollPastEnd, ui->chkScrollPastEnd->isChecked());
403 
404     KateDocumentConfig::global()->configEnd();
405     KateViewConfig::global()->configEnd();
406 }
407 
reload()408 void KateNavigationConfigTab::reload()
409 {
410     ui->cbTextSelectionMode->setCurrentIndex(KateViewConfig::global()->persistentSelection() ? 1 : 0);
411 
412     ui->chkBackspaceRemoveComposed->setChecked(KateViewConfig::global()->backspaceRemoveComposed());
413     ui->chkPagingMovesCursor->setChecked(KateDocumentConfig::global()->pageUpDownMovesCursor());
414     ui->chkScrollPastEnd->setChecked(KateViewConfig::global()->scrollPastEnd());
415     ui->chkSmartHome->setChecked(KateDocumentConfig::global()->smartHome());
416     ui->chkCamelCursor->setChecked(KateDocumentConfig::global()->camelCursor());
417 
418     ui->sbAutoCenterCursor->setValue(KateViewConfig::global()->autoCenterLines());
419 }
420 
name() const421 QString KateNavigationConfigTab::name() const
422 {
423     return i18n("Text Navigation");
424 }
425 
426 // END KateNavigationConfigTab
427 
428 // BEGIN KateEditGeneralConfigTab
KateEditGeneralConfigTab(QWidget * parent)429 KateEditGeneralConfigTab::KateEditGeneralConfigTab(QWidget *parent)
430     : KateConfigPage(parent)
431 {
432     QVBoxLayout *layout = new QVBoxLayout(this);
433     QWidget *newWidget = new QWidget(this);
434     ui = new Ui::EditConfigWidget();
435     ui->setupUi(newWidget);
436 
437     for (const auto &fact : KTextEditor::EditorPrivate::self()->inputModeFactories()) {
438         ui->cmbInputMode->addItem(fact->name(), static_cast<int>(fact->inputMode()));
439     }
440 
441     // "What's This?" Help is in the ui-files
442 
443     reload();
444 
445     observeChanges(ui->chkAutoBrackets);
446     observeChanges(ui->chkMousePasteAtCursorPosition);
447     observeChanges(ui->chkShowStaticWordWrapMarker);
448     observeChanges(ui->chkTextDragAndDrop);
449     observeChanges(ui->chkSmartCopyCut);
450     observeChanges(ui->chkStaticWordWrap);
451     observeChanges(ui->cmbEncloseSelection);
452     ui->lblBracketHelp->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
453     connect(ui->cmbEncloseSelection->lineEdit(), &QLineEdit::editingFinished, [=] {
454         const int index = ui->cmbEncloseSelection->currentIndex();
455         const QString text = ui->cmbEncloseSelection->currentText();
456         // Text removed? Remove item, but don't remove default data!
457         if (index >= UserData && text.isEmpty()) {
458             ui->cmbEncloseSelection->removeItem(index);
459             slotChanged();
460 
461             // Not already there? Add new item! For whatever reason it isn't done automatically
462         } else if (ui->cmbEncloseSelection->findText(text) < 0) {
463             ui->cmbEncloseSelection->addItem(text);
464             slotChanged();
465         }
466         ui->cmbEncloseSelection->setCurrentIndex(ui->cmbEncloseSelection->findText(text));
467     });
468     observeChanges(ui->cmbInputMode);
469     observeChanges(ui->sbWordWrap);
470 
471     layout->addWidget(newWidget);
472 }
473 
~KateEditGeneralConfigTab()474 KateEditGeneralConfigTab::~KateEditGeneralConfigTab()
475 {
476     delete ui;
477 }
478 
apply()479 void KateEditGeneralConfigTab::apply()
480 {
481     // nothing changed, no need to apply stuff
482     if (!hasChanged()) {
483         return;
484     }
485     m_changed = false;
486 
487     KateViewConfig::global()->configStart();
488     KateDocumentConfig::global()->configStart();
489 
490     KateDocumentConfig::global()->setWordWrap(ui->chkStaticWordWrap->isChecked());
491     KateDocumentConfig::global()->setWordWrapAt(ui->sbWordWrap->value());
492 
493     KateRendererConfig::global()->setWordWrapMarker(ui->chkShowStaticWordWrapMarker->isChecked());
494 
495     KateViewConfig::global()->setValue(KateViewConfig::AutoBrackets, ui->chkAutoBrackets->isChecked());
496     KateViewConfig::global()->setValue(KateViewConfig::CharsToEncloseSelection, ui->cmbEncloseSelection->currentText());
497     QStringList userLetters;
498     for (int i = UserData; i < ui->cmbEncloseSelection->count(); ++i) {
499         userLetters.append(ui->cmbEncloseSelection->itemText(i));
500     }
501     KateViewConfig::global()->setValue(KateViewConfig::UserSetsOfCharsToEncloseSelection, userLetters);
502     KateViewConfig::global()->setValue(KateViewConfig::InputMode, ui->cmbInputMode->currentData().toInt());
503     KateViewConfig::global()->setValue(KateViewConfig::MousePasteAtCursorPosition, ui->chkMousePasteAtCursorPosition->isChecked());
504     KateViewConfig::global()->setValue(KateViewConfig::TextDragAndDrop, ui->chkTextDragAndDrop->isChecked());
505     KateViewConfig::global()->setValue(KateViewConfig::SmartCopyCut, ui->chkSmartCopyCut->isChecked());
506 
507     KateDocumentConfig::global()->configEnd();
508     KateViewConfig::global()->configEnd();
509 }
510 
reload()511 void KateEditGeneralConfigTab::reload()
512 {
513     ui->chkAutoBrackets->setChecked(KateViewConfig::global()->autoBrackets());
514     ui->chkMousePasteAtCursorPosition->setChecked(KateViewConfig::global()->mousePasteAtCursorPosition());
515     ui->chkShowStaticWordWrapMarker->setChecked(KateRendererConfig::global()->wordWrapMarker());
516     ui->chkTextDragAndDrop->setChecked(KateViewConfig::global()->textDragAndDrop());
517     ui->chkSmartCopyCut->setChecked(KateViewConfig::global()->smartCopyCut());
518     ui->chkStaticWordWrap->setChecked(KateDocumentConfig::global()->wordWrap());
519 
520     ui->sbWordWrap->setSuffix(ki18ncp("Wrap words at (value is at 20 or larger)", " character", " characters"));
521     ui->sbWordWrap->setValue(KateDocumentConfig::global()->wordWrapAt());
522 
523     ui->cmbEncloseSelection->clear();
524     ui->cmbEncloseSelection->lineEdit()->setClearButtonEnabled(true);
525     ui->cmbEncloseSelection->lineEdit()->setPlaceholderText(i18n("Feature is not active"));
526     ui->cmbEncloseSelection->addItem(QString(), None);
527     ui->cmbEncloseSelection->setItemData(0, i18n("Disable Feature"), Qt::ToolTipRole);
528     ui->cmbEncloseSelection->addItem(QStringLiteral("`*_~"), MarkDown);
529     ui->cmbEncloseSelection->setItemData(1, i18n("May be handy with Markdown"), Qt::ToolTipRole);
530     ui->cmbEncloseSelection->addItem(QStringLiteral("<>(){}[]'\""), MirrorChar);
531     ui->cmbEncloseSelection->setItemData(2, i18n("Mirror characters, similar but not exactly like auto brackets"), Qt::ToolTipRole);
532     ui->cmbEncloseSelection->addItem(QStringLiteral("´`_.:|#@~*!?$%/=,;-+^°§&"), NonLetters);
533     ui->cmbEncloseSelection->setItemData(3, i18n("Non letter character"), Qt::ToolTipRole);
534     const QStringList userLetters = KateViewConfig::global()->value(KateViewConfig::UserSetsOfCharsToEncloseSelection).toStringList();
535     for (int i = 0; i < userLetters.size(); ++i) {
536         ui->cmbEncloseSelection->addItem(userLetters.at(i), UserData + i);
537     }
538     ui->cmbEncloseSelection->setCurrentIndex(ui->cmbEncloseSelection->findText(KateViewConfig::global()->charsToEncloseSelection()));
539 
540     const int id = static_cast<int>(KateViewConfig::global()->inputMode());
541     ui->cmbInputMode->setCurrentIndex(ui->cmbInputMode->findData(id));
542 }
543 
name() const544 QString KateEditGeneralConfigTab::name() const
545 {
546     return i18n("General");
547 }
548 
549 // END KateEditGeneralConfigTab
550 
551 // BEGIN KateEditConfigTab
KateEditConfigTab(QWidget * parent)552 KateEditConfigTab::KateEditConfigTab(QWidget *parent)
553     : KateConfigPage(parent)
554     , editConfigTab(new KateEditGeneralConfigTab(this))
555     , navigationConfigTab(new KateNavigationConfigTab(this))
556     , indentConfigTab(new KateIndentConfigTab(this))
557     , completionConfigTab(new KateCompletionConfigTab(this))
558     , spellCheckConfigTab(new KateSpellCheckConfigTab(this))
559 {
560     QVBoxLayout *layout = new QVBoxLayout(this);
561     layout->setContentsMargins(0, 0, 0, 0);
562     QTabWidget *tabWidget = new QTabWidget(this);
563 
564     // add all tabs
565     tabWidget->insertTab(0, editConfigTab, editConfigTab->name());
566     tabWidget->insertTab(1, navigationConfigTab, navigationConfigTab->name());
567     tabWidget->insertTab(2, indentConfigTab, indentConfigTab->name());
568     tabWidget->insertTab(3, completionConfigTab, completionConfigTab->name());
569     tabWidget->insertTab(4, spellCheckConfigTab, spellCheckConfigTab->name());
570 
571     observeChanges(editConfigTab);
572     observeChanges(navigationConfigTab);
573     observeChanges(indentConfigTab);
574     observeChanges(completionConfigTab);
575     observeChanges(spellCheckConfigTab);
576 
577     int i = tabWidget->count();
578     for (const auto &factory : KTextEditor::EditorPrivate::self()->inputModeFactories()) {
579         KateConfigPage *tab = factory->createConfigPage(this);
580         if (tab) {
581             m_inputModeConfigTabs << tab;
582             tabWidget->insertTab(i, tab, tab->name());
583             observeChanges(tab);
584             i++;
585         }
586     }
587 
588     layout->addWidget(tabWidget);
589 }
590 
~KateEditConfigTab()591 KateEditConfigTab::~KateEditConfigTab()
592 {
593     qDeleteAll(m_inputModeConfigTabs);
594 }
595 
apply()596 void KateEditConfigTab::apply()
597 {
598     // try to update the rest of tabs
599     editConfigTab->apply();
600     navigationConfigTab->apply();
601     indentConfigTab->apply();
602     completionConfigTab->apply();
603     spellCheckConfigTab->apply();
604     for (KateConfigPage *tab : std::as_const(m_inputModeConfigTabs)) {
605         tab->apply();
606     }
607 }
608 
reload()609 void KateEditConfigTab::reload()
610 {
611     editConfigTab->reload();
612     navigationConfigTab->reload();
613     indentConfigTab->reload();
614     completionConfigTab->reload();
615     spellCheckConfigTab->reload();
616     for (KateConfigPage *tab : std::as_const(m_inputModeConfigTabs)) {
617         tab->reload();
618     }
619 }
620 
reset()621 void KateEditConfigTab::reset()
622 {
623     editConfigTab->reset();
624     navigationConfigTab->reset();
625     indentConfigTab->reset();
626     completionConfigTab->reset();
627     spellCheckConfigTab->reset();
628     for (KateConfigPage *tab : std::as_const(m_inputModeConfigTabs)) {
629         tab->reset();
630     }
631 }
632 
defaults()633 void KateEditConfigTab::defaults()
634 {
635     editConfigTab->defaults();
636     navigationConfigTab->defaults();
637     indentConfigTab->defaults();
638     completionConfigTab->defaults();
639     spellCheckConfigTab->defaults();
640     for (KateConfigPage *tab : std::as_const(m_inputModeConfigTabs)) {
641         tab->defaults();
642     }
643 }
644 
name() const645 QString KateEditConfigTab::name() const
646 {
647     return i18n("Editing");
648 }
649 
fullName() const650 QString KateEditConfigTab::fullName() const
651 {
652     return i18n("Editing Options");
653 }
654 
icon() const655 QIcon KateEditConfigTab::icon() const
656 {
657     return QIcon::fromTheme(QStringLiteral("accessories-text-editor"));
658 }
659 
660 // END KateEditConfigTab
661 
662 // BEGIN KateViewDefaultsConfig
KateViewDefaultsConfig(QWidget * parent)663 KateViewDefaultsConfig::KateViewDefaultsConfig(QWidget *parent)
664     : KateConfigPage(parent)
665     , textareaUi(new Ui::TextareaAppearanceConfigWidget())
666     , bordersUi(new Ui::BordersAppearanceConfigWidget())
667 {
668     QLayout *layout = new QVBoxLayout(this);
669     QTabWidget *tabWidget = new QTabWidget(this);
670     layout->addWidget(tabWidget);
671     layout->setContentsMargins(0, 0, 0, 0);
672 
673     QWidget *textareaTab = new QWidget(tabWidget);
674     textareaUi->setupUi(textareaTab);
675     tabWidget->addTab(textareaTab, i18n("General"));
676 
677     QWidget *bordersTab = new QWidget(tabWidget);
678     bordersUi->setupUi(bordersTab);
679     tabWidget->addTab(bordersTab, i18n("Borders"));
680 
681     textareaUi->cmbDynamicWordWrapIndicator->addItem(i18n("Off"));
682     textareaUi->cmbDynamicWordWrapIndicator->addItem(i18n("Follow Line Numbers"));
683     textareaUi->cmbDynamicWordWrapIndicator->addItem(i18n("Always On"));
684 
685     // "What's This?" help is in the ui-file
686 
687     reload();
688 
689     observeChanges(textareaUi->kfontrequester);
690 
691     observeChanges(textareaUi->chkAnimateBracketMatching);
692     observeChanges(textareaUi->chkDynWrapAnywhere);
693     observeChanges(textareaUi->chkDynWrapAtStaticMarker);
694     observeChanges(textareaUi->chkFoldFirstLine);
695     observeChanges(textareaUi->chkShowBracketMatchPreview);
696     observeChanges(textareaUi->chkShowIndentationLines);
697     observeChanges(textareaUi->chkShowLineCount);
698     observeChanges(textareaUi->chkShowTabs);
699     observeChanges(textareaUi->chkShowWholeBracketExpression);
700     observeChanges(textareaUi->chkShowWordCount);
701     observeChanges(textareaUi->cmbDynamicWordWrapIndicator);
702     observeChanges(textareaUi->cbxWordWrap);
703     auto a = [ui = textareaUi, cbx = textareaUi->cbxWordWrap]() {
704         ui->chkDynWrapAtStaticMarker->setEnabled(cbx->isChecked());
705         ui->chkDynWrapAnywhere->setEnabled(cbx->isChecked());
706         ui->cmbDynamicWordWrapIndicator->setEnabled(cbx->isChecked());
707         ui->sbDynamicWordWrapDepth->setEnabled(cbx->isChecked());
708     };
709     connect(textareaUi->cbxWordWrap, &QCheckBox::stateChanged, this, a);
710     a();
711     auto b = [cbx = textareaUi->cbxIndentWrappedLines, sb = textareaUi->sbDynamicWordWrapDepth]() {
712         sb->setEnabled(cbx->isChecked());
713     };
714     b();
715     connect(textareaUi->cbxIndentWrappedLines, &QCheckBox::stateChanged, this, b);
716     observeChanges(textareaUi->cbxIndentWrappedLines);
717     observeChanges(textareaUi->sbDynamicWordWrapDepth);
718     observeChanges(textareaUi->sliSetMarkerSize);
719     observeChanges(textareaUi->spacesComboBox);
720 
721     observeChanges(bordersUi->chkIconBorder);
722     observeChanges(bordersUi->chkLineNumbers);
723     observeChanges(bordersUi->chkScrollbarMarks);
724     observeChanges(bordersUi->chkScrollbarMiniMap);
725     observeChanges(bordersUi->chkScrollbarMiniMapAll);
726     bordersUi->chkScrollbarMiniMapAll->hide(); // this is temporary until the feature is done
727     observeChanges(bordersUi->chkScrollbarPreview);
728     observeChanges(bordersUi->chkShowFoldingMarkers);
729     observeChanges(bordersUi->chkShowFoldingPreview);
730     observeChanges(bordersUi->chkShowLineModification);
731     observeChanges(bordersUi->cmbShowScrollbars);
732     observeChanges(bordersUi->rbSortBookmarksByCreation);
733     observeChanges(bordersUi->rbSortBookmarksByPosition);
734     observeChanges(bordersUi->spBoxMiniMapWidth);
735 }
736 
~KateViewDefaultsConfig()737 KateViewDefaultsConfig::~KateViewDefaultsConfig()
738 {
739     delete bordersUi;
740     delete textareaUi;
741 }
742 
apply()743 void KateViewDefaultsConfig::apply()
744 {
745     // nothing changed, no need to apply stuff
746     if (!hasChanged()) {
747         return;
748     }
749     m_changed = false;
750 
751     KateViewConfig::global()->configStart();
752     KateRendererConfig::global()->configStart();
753 
754     KateDocumentConfig::global()->setMarkerSize(textareaUi->sliSetMarkerSize->value());
755     KateDocumentConfig::global()->setShowSpaces(KateDocumentConfig::WhitespaceRendering(textareaUi->spacesComboBox->currentIndex()));
756     KateDocumentConfig::global()->setShowTabs(textareaUi->chkShowTabs->isChecked());
757 
758     KateRendererConfig::global()->setFont(textareaUi->kfontrequester->font());
759     KateRendererConfig::global()->setAnimateBracketMatching(textareaUi->chkAnimateBracketMatching->isChecked());
760     KateRendererConfig::global()->setShowIndentationLines(textareaUi->chkShowIndentationLines->isChecked());
761     KateRendererConfig::global()->setShowWholeBracketExpression(textareaUi->chkShowWholeBracketExpression->isChecked());
762 
763     KateViewConfig::global()->setDynWordWrap(textareaUi->cbxWordWrap->isChecked());
764     KateViewConfig::global()->setShowWordCount(textareaUi->chkShowWordCount->isChecked());
765     KateViewConfig::global()->setValue(KateViewConfig::BookmarkSorting, bordersUi->rbSortBookmarksByPosition->isChecked() ? 0 : 1);
766     if (!textareaUi->cbxIndentWrappedLines->isChecked()) {
767         KateViewConfig::global()->setValue(KateViewConfig::DynWordWrapAlignIndent, 0);
768     } else {
769         KateViewConfig::global()->setValue(KateViewConfig::DynWordWrapAlignIndent, textareaUi->sbDynamicWordWrapDepth->value());
770     }
771     KateViewConfig::global()->setValue(KateViewConfig::DynWordWrapIndicators, textareaUi->cmbDynamicWordWrapIndicator->currentIndex());
772     KateViewConfig::global()->setValue(KateViewConfig::DynWrapAnywhere, textareaUi->chkDynWrapAnywhere->isChecked());
773     KateViewConfig::global()->setValue(KateViewConfig::DynWrapAtStaticMarker, textareaUi->chkDynWrapAtStaticMarker->isChecked());
774     KateViewConfig::global()->setValue(KateViewConfig::FoldFirstLine, textareaUi->chkFoldFirstLine->isChecked());
775     KateViewConfig::global()->setValue(KateViewConfig::ScrollBarMiniMapWidth, bordersUi->spBoxMiniMapWidth->value());
776     KateViewConfig::global()->setValue(KateViewConfig::ShowBracketMatchPreview, textareaUi->chkShowBracketMatchPreview->isChecked());
777     KateViewConfig::global()->setValue(KateViewConfig::ShowFoldingBar, bordersUi->chkShowFoldingMarkers->isChecked());
778     KateViewConfig::global()->setValue(KateViewConfig::ShowFoldingPreview, bordersUi->chkShowFoldingPreview->isChecked());
779     KateViewConfig::global()->setValue(KateViewConfig::ShowIconBar, bordersUi->chkIconBorder->isChecked());
780     KateViewConfig::global()->setValue(KateViewConfig::ShowLineCount, textareaUi->chkShowLineCount->isChecked());
781     KateViewConfig::global()->setValue(KateViewConfig::ShowLineModification, bordersUi->chkShowLineModification->isChecked());
782     KateViewConfig::global()->setValue(KateViewConfig::ShowLineNumbers, bordersUi->chkLineNumbers->isChecked());
783     KateViewConfig::global()->setValue(KateViewConfig::ShowScrollBarMarks, bordersUi->chkScrollbarMarks->isChecked());
784     KateViewConfig::global()->setValue(KateViewConfig::ShowScrollBarMiniMap, bordersUi->chkScrollbarMiniMap->isChecked());
785     KateViewConfig::global()->setValue(KateViewConfig::ShowScrollBarMiniMapAll, bordersUi->chkScrollbarMiniMapAll->isChecked());
786     KateViewConfig::global()->setValue(KateViewConfig::ShowScrollBarPreview, bordersUi->chkScrollbarPreview->isChecked());
787     KateViewConfig::global()->setValue(KateViewConfig::ShowScrollbars, bordersUi->cmbShowScrollbars->currentIndex());
788 
789     KateRendererConfig::global()->configEnd();
790     KateViewConfig::global()->configEnd();
791 }
792 
reload()793 void KateViewDefaultsConfig::reload()
794 {
795     bordersUi->chkIconBorder->setChecked(KateViewConfig::global()->iconBar());
796     bordersUi->chkLineNumbers->setChecked(KateViewConfig::global()->lineNumbers());
797     bordersUi->chkScrollbarMarks->setChecked(KateViewConfig::global()->scrollBarMarks());
798     bordersUi->chkScrollbarMiniMap->setChecked(KateViewConfig::global()->scrollBarMiniMap());
799     bordersUi->chkScrollbarMiniMapAll->setChecked(KateViewConfig::global()->scrollBarMiniMapAll());
800     bordersUi->chkScrollbarPreview->setChecked(KateViewConfig::global()->scrollBarPreview());
801     bordersUi->chkShowFoldingMarkers->setChecked(KateViewConfig::global()->foldingBar());
802     bordersUi->chkShowFoldingPreview->setChecked(KateViewConfig::global()->foldingPreview());
803     bordersUi->chkShowLineModification->setChecked(KateViewConfig::global()->lineModification());
804     bordersUi->cmbShowScrollbars->setCurrentIndex(KateViewConfig::global()->showScrollbars());
805     bordersUi->rbSortBookmarksByCreation->setChecked(KateViewConfig::global()->bookmarkSort() == 1);
806     bordersUi->rbSortBookmarksByPosition->setChecked(KateViewConfig::global()->bookmarkSort() == 0);
807     bordersUi->spBoxMiniMapWidth->setValue(KateViewConfig::global()->scrollBarMiniMapWidth());
808 
809     textareaUi->kfontrequester->setFont(KateRendererConfig::global()->baseFont());
810 
811     textareaUi->chkAnimateBracketMatching->setChecked(KateRendererConfig::global()->animateBracketMatching());
812     textareaUi->chkDynWrapAnywhere->setChecked(KateViewConfig::global()->dynWrapAnywhere());
813     textareaUi->chkDynWrapAtStaticMarker->setChecked(KateViewConfig::global()->dynWrapAtStaticMarker());
814     textareaUi->chkFoldFirstLine->setChecked(KateViewConfig::global()->foldFirstLine());
815     textareaUi->chkShowBracketMatchPreview->setChecked(KateViewConfig::global()->value(KateViewConfig::ShowBracketMatchPreview).toBool());
816     textareaUi->chkShowIndentationLines->setChecked(KateRendererConfig::global()->showIndentationLines());
817     textareaUi->chkShowLineCount->setChecked(KateViewConfig::global()->showLineCount());
818     textareaUi->chkShowTabs->setChecked(KateDocumentConfig::global()->showTabs());
819     textareaUi->chkShowWholeBracketExpression->setChecked(KateRendererConfig::global()->showWholeBracketExpression());
820     textareaUi->chkShowWordCount->setChecked(KateViewConfig::global()->showWordCount());
821     textareaUi->cmbDynamicWordWrapIndicator->setCurrentIndex(KateViewConfig::global()->dynWordWrapIndicators());
822     textareaUi->cbxWordWrap->setChecked(KateViewConfig::global()->dynWordWrap());
823     textareaUi->cbxIndentWrappedLines->setChecked(KateViewConfig::global()->dynWordWrapAlignIndent() != 0);
824     textareaUi->sbDynamicWordWrapDepth->setValue(KateViewConfig::global()->dynWordWrapAlignIndent());
825     textareaUi->sliSetMarkerSize->setValue(KateDocumentConfig::global()->markerSize());
826     textareaUi->spacesComboBox->setCurrentIndex(KateDocumentConfig::global()->showSpaces());
827 }
828 
reset()829 void KateViewDefaultsConfig::reset()
830 {
831 }
832 
defaults()833 void KateViewDefaultsConfig::defaults()
834 {
835 }
836 
name() const837 QString KateViewDefaultsConfig::name() const
838 {
839     return i18n("Appearance");
840 }
841 
fullName() const842 QString KateViewDefaultsConfig::fullName() const
843 {
844     return i18n("Appearance");
845 }
846 
icon() const847 QIcon KateViewDefaultsConfig::icon() const
848 {
849     return QIcon::fromTheme(QStringLiteral("preferences-desktop-theme"));
850 }
851 
852 // END KateViewDefaultsConfig
853 
854 // BEGIN KateSaveConfigTab
KateSaveConfigTab(QWidget * parent)855 KateSaveConfigTab::KateSaveConfigTab(QWidget *parent)
856     : KateConfigPage(parent)
857     , modeConfigPage(new ModeConfigPage(this))
858 {
859     // FIXME: Is really needed to move all this code below to another class,
860     // since it is another tab itself on the config dialog. This means we should
861     // initialize, add and work with as we do with modeConfigPage (ereslibre)
862     QVBoxLayout *layout = new QVBoxLayout(this);
863     layout->setContentsMargins(0, 0, 0, 0);
864     QTabWidget *tabWidget = new QTabWidget(this);
865 
866     QWidget *tmpWidget = new QWidget(tabWidget);
867     QVBoxLayout *internalLayout = new QVBoxLayout(tmpWidget);
868     QWidget *newWidget = new QWidget(tabWidget);
869     ui = new Ui::OpenSaveConfigWidget();
870     ui->setupUi(newWidget);
871 
872     QWidget *tmpWidget2 = new QWidget(tabWidget);
873     QVBoxLayout *internalLayout2 = new QVBoxLayout(tmpWidget2);
874     QWidget *newWidget2 = new QWidget(tabWidget);
875     uiadv = new Ui::OpenSaveConfigAdvWidget();
876     uiadv->setupUi(newWidget2);
877     uiadv->lblExplanatory->setText(i18n("%1 backs up unsaved files to \"swap files.\" Swap files allow %1 to recover your work in the case of a system crash. Disabling swap files may cause data loss in case of a system crash.", QCoreApplication::applicationName()));
878     uiadv->lblExplanatory->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
879 
880     // "What's This?" help can be found in the ui file
881 
882     reload();
883 
884     observeChanges(ui->cbRemoveTrailingSpaces);
885     observeChanges(ui->chkDetectEOL);
886     observeChanges(ui->chkEnableBOM);
887     observeChanges(ui->chkNewLineAtEof);
888     observeChanges(ui->cmbEOL);
889     observeChanges(ui->cmbEncoding);
890     observeChanges(ui->cmbEncodingDetection);
891     observeChanges(ui->cmbEncodingFallback);
892     observeChanges(ui->lineLengthLimit);
893 
894     observeChanges(uiadv->chkBackupLocalFiles);
895     observeChanges(uiadv->chkBackupRemoteFiles);
896     observeChanges(uiadv->cmbSwapFileMode);
897     connect(uiadv->cmbSwapFileMode, qOverload<int>(&QComboBox::currentIndexChanged), this, &KateSaveConfigTab::swapFileModeChanged);
898 
899     observeChanges(uiadv->edtBackupPrefix);
900     observeChanges(uiadv->edtBackupSuffix);
901     observeChanges(uiadv->kurlSwapDirectory);
902     observeChanges(uiadv->spbSwapFileSync);
903 
904     internalLayout->addWidget(newWidget);
905     internalLayout2->addWidget(newWidget2);
906 
907     // add all tabs
908     tabWidget->insertTab(0, tmpWidget, i18n("General"));
909     tabWidget->insertTab(1, tmpWidget2, i18n("Advanced"));
910     tabWidget->insertTab(2, modeConfigPage, modeConfigPage->name());
911 
912     observeChanges(modeConfigPage);
913 
914     layout->addWidget(tabWidget);
915 
916     // support variable expansion in backup prefix/suffix
917     KTextEditor::Editor::instance()->addVariableExpansion({uiadv->edtBackupPrefix, uiadv->edtBackupSuffix},
918                                                           {QStringLiteral("Date:Locale"),
919                                                            QStringLiteral("Date:ISO"),
920                                                            QStringLiteral("Date:"),
921                                                            QStringLiteral("Time:Locale"),
922                                                            QStringLiteral("Time:ISO"),
923                                                            QStringLiteral("Time:"),
924                                                            QStringLiteral("ENV:"),
925                                                            QStringLiteral("JS:"),
926                                                            QStringLiteral("UUID")});
927 }
928 
~KateSaveConfigTab()929 KateSaveConfigTab::~KateSaveConfigTab()
930 {
931     delete uiadv;
932     delete ui;
933 }
934 
swapFileModeChanged(int idx)935 void KateSaveConfigTab::swapFileModeChanged(int idx)
936 {
937     const KateDocumentConfig::SwapFileMode mode = static_cast<KateDocumentConfig::SwapFileMode>(idx);
938     switch (mode) {
939     case KateDocumentConfig::DisableSwapFile:
940         uiadv->lblSwapDirectory->setEnabled(false);
941         uiadv->kurlSwapDirectory->setEnabled(false);
942         uiadv->lblSwapFileSync->setEnabled(false);
943         uiadv->spbSwapFileSync->setEnabled(false);
944         break;
945     case KateDocumentConfig::EnableSwapFile:
946         uiadv->lblSwapDirectory->setEnabled(false);
947         uiadv->kurlSwapDirectory->setEnabled(false);
948         uiadv->lblSwapFileSync->setEnabled(true);
949         uiadv->spbSwapFileSync->setEnabled(true);
950         break;
951     case KateDocumentConfig::SwapFilePresetDirectory:
952         uiadv->lblSwapDirectory->setEnabled(true);
953         uiadv->kurlSwapDirectory->setEnabled(true);
954         uiadv->lblSwapFileSync->setEnabled(true);
955         uiadv->spbSwapFileSync->setEnabled(true);
956         break;
957     }
958 }
959 
apply()960 void KateSaveConfigTab::apply()
961 {
962     modeConfigPage->apply();
963 
964     // nothing changed, no need to apply stuff
965     if (!hasChanged()) {
966         return;
967     }
968     m_changed = false;
969 
970     KateGlobalConfig::global()->configStart();
971     KateDocumentConfig::global()->configStart();
972 
973     if (uiadv->edtBackupSuffix->text().isEmpty() && uiadv->edtBackupPrefix->text().isEmpty()) {
974         KMessageBox::information(this, i18n("You did not provide a backup suffix or prefix. Using default suffix: '~'"), i18n("No Backup Suffix or Prefix"));
975         uiadv->edtBackupSuffix->setText(QStringLiteral("~"));
976     }
977 
978     KateDocumentConfig::global()->setBackupOnSaveLocal(uiadv->chkBackupLocalFiles->isChecked());
979     KateDocumentConfig::global()->setBackupOnSaveRemote(uiadv->chkBackupRemoteFiles->isChecked());
980     KateDocumentConfig::global()->setBackupPrefix(uiadv->edtBackupPrefix->text());
981     KateDocumentConfig::global()->setBackupSuffix(uiadv->edtBackupSuffix->text());
982 
983     KateDocumentConfig::global()->setSwapFileMode(uiadv->cmbSwapFileMode->currentIndex());
984     KateDocumentConfig::global()->setSwapDirectory(uiadv->kurlSwapDirectory->url().toLocalFile());
985     KateDocumentConfig::global()->setSwapSyncInterval(uiadv->spbSwapFileSync->value());
986 
987     KateDocumentConfig::global()->setRemoveSpaces(ui->cbRemoveTrailingSpaces->currentIndex());
988 
989     KateDocumentConfig::global()->setNewLineAtEof(ui->chkNewLineAtEof->isChecked());
990 
991     // set both standard and fallback encoding
992     KateDocumentConfig::global()->setEncoding(KCharsets::charsets()->encodingForName(ui->cmbEncoding->currentText()));
993 
994     KateGlobalConfig::global()->setProberType((KEncodingProber::ProberType)ui->cmbEncodingDetection->currentIndex());
995     KateGlobalConfig::global()->setFallbackEncoding(KCharsets::charsets()->encodingForName(ui->cmbEncodingFallback->currentText()));
996 
997     KateDocumentConfig::global()->setEol(ui->cmbEOL->currentIndex());
998     KateDocumentConfig::global()->setAllowEolDetection(ui->chkDetectEOL->isChecked());
999     KateDocumentConfig::global()->setBom(ui->chkEnableBOM->isChecked());
1000 
1001     KateDocumentConfig::global()->setLineLengthLimit(ui->lineLengthLimit->value());
1002 
1003     KateDocumentConfig::global()->configEnd();
1004     KateGlobalConfig::global()->configEnd();
1005 }
1006 
reload()1007 void KateSaveConfigTab::reload()
1008 {
1009     modeConfigPage->reload();
1010 
1011     // encodings
1012     ui->cmbEncoding->clear();
1013     ui->cmbEncodingFallback->clear();
1014     QStringList encodings(KCharsets::charsets()->descriptiveEncodingNames());
1015     int insert = 0;
1016     for (int i = 0; i < encodings.count(); i++) {
1017         bool found = false;
1018         QTextCodec *codecForEnc = KCharsets::charsets()->codecForName(KCharsets::charsets()->encodingForName(encodings[i]), found);
1019 
1020         if (found) {
1021             ui->cmbEncoding->addItem(encodings[i]);
1022             ui->cmbEncodingFallback->addItem(encodings[i]);
1023 
1024             if (codecForEnc == KateDocumentConfig::global()->codec()) {
1025                 ui->cmbEncoding->setCurrentIndex(insert);
1026             }
1027 
1028             if (codecForEnc == KateGlobalConfig::global()->fallbackCodec()) {
1029                 // adjust index for fallback config, has no default!
1030                 ui->cmbEncodingFallback->setCurrentIndex(insert);
1031             }
1032 
1033             insert++;
1034         }
1035     }
1036 
1037     // encoding detection
1038     ui->cmbEncodingDetection->clear();
1039     bool found = false;
1040     for (int i = 0; !KEncodingProber::nameForProberType((KEncodingProber::ProberType)i).isEmpty(); ++i) {
1041         ui->cmbEncodingDetection->addItem(KEncodingProber::nameForProberType((KEncodingProber::ProberType)i));
1042         if (i == KateGlobalConfig::global()->proberType()) {
1043             ui->cmbEncodingDetection->setCurrentIndex(ui->cmbEncodingDetection->count() - 1);
1044             found = true;
1045         }
1046     }
1047     if (!found) {
1048         ui->cmbEncodingDetection->setCurrentIndex(KEncodingProber::Universal);
1049     }
1050 
1051     // eol
1052     ui->cmbEOL->setCurrentIndex(KateDocumentConfig::global()->eol());
1053     ui->chkDetectEOL->setChecked(KateDocumentConfig::global()->allowEolDetection());
1054     ui->chkEnableBOM->setChecked(KateDocumentConfig::global()->bom());
1055     ui->lineLengthLimit->setValue(KateDocumentConfig::global()->lineLengthLimit());
1056 
1057     ui->cbRemoveTrailingSpaces->setCurrentIndex(KateDocumentConfig::global()->removeSpaces());
1058     ui->chkNewLineAtEof->setChecked(KateDocumentConfig::global()->newLineAtEof());
1059 
1060     // other stuff
1061     uiadv->chkBackupLocalFiles->setChecked(KateDocumentConfig::global()->backupOnSaveLocal());
1062     uiadv->chkBackupRemoteFiles->setChecked(KateDocumentConfig::global()->backupOnSaveRemote());
1063     uiadv->edtBackupPrefix->setText(KateDocumentConfig::global()->backupPrefix());
1064     uiadv->edtBackupSuffix->setText(KateDocumentConfig::global()->backupSuffix());
1065 
1066     uiadv->cmbSwapFileMode->setCurrentIndex(KateDocumentConfig::global()->swapFileMode());
1067     uiadv->kurlSwapDirectory->setUrl(QUrl::fromLocalFile(KateDocumentConfig::global()->swapDirectory()));
1068     uiadv->spbSwapFileSync->setValue(KateDocumentConfig::global()->swapSyncInterval());
1069     swapFileModeChanged(KateDocumentConfig::global()->swapFileMode());
1070 }
1071 
reset()1072 void KateSaveConfigTab::reset()
1073 {
1074     modeConfigPage->reset();
1075 }
1076 
defaults()1077 void KateSaveConfigTab::defaults()
1078 {
1079     modeConfigPage->defaults();
1080 
1081     ui->cbRemoveTrailingSpaces->setCurrentIndex(0);
1082 
1083     uiadv->chkBackupLocalFiles->setChecked(true);
1084     uiadv->chkBackupRemoteFiles->setChecked(false);
1085     uiadv->edtBackupPrefix->setText(QString());
1086     uiadv->edtBackupSuffix->setText(QStringLiteral("~"));
1087 
1088     uiadv->cmbSwapFileMode->setCurrentIndex(1);
1089     uiadv->kurlSwapDirectory->setDisabled(true);
1090     uiadv->lblSwapDirectory->setDisabled(true);
1091     uiadv->spbSwapFileSync->setValue(15);
1092 }
1093 
name() const1094 QString KateSaveConfigTab::name() const
1095 {
1096     return i18n("Open/Save");
1097 }
1098 
fullName() const1099 QString KateSaveConfigTab::fullName() const
1100 {
1101     return i18n("File Opening & Saving");
1102 }
1103 
icon() const1104 QIcon KateSaveConfigTab::icon() const
1105 {
1106     return QIcon::fromTheme(QStringLiteral("document-save"));
1107 }
1108 
1109 // END KateSaveConfigTab
1110 
1111 // BEGIN KateGotoBar
KateGotoBar(KTextEditor::View * view,QWidget * parent)1112 KateGotoBar::KateGotoBar(KTextEditor::View *view, QWidget *parent)
1113     : KateViewBarWidget(true, parent)
1114     , m_view(view)
1115 {
1116     Q_ASSERT(m_view != nullptr); // this bar widget is pointless w/o a view
1117 
1118     QHBoxLayout *topLayout = new QHBoxLayout(centralWidget());
1119     topLayout->setContentsMargins(0, 0, 0, 0);
1120 
1121     QToolButton *btn = new QToolButton(this);
1122     btn->setAutoRaise(true);
1123     btn->setMinimumSize(QSize(1, btn->minimumSizeHint().height()));
1124     btn->setText(i18n("&Line:"));
1125     btn->setToolTip(i18n("Go to line number from clipboard"));
1126     connect(btn, &QToolButton::clicked, this, &KateGotoBar::gotoClipboard);
1127     topLayout->addWidget(btn);
1128 
1129     m_gotoRange = new QSpinBox(this);
1130     topLayout->addWidget(m_gotoRange, 1);
1131     topLayout->setStretchFactor(m_gotoRange, 0);
1132 
1133     btn = new QToolButton(this);
1134     btn->setAutoRaise(true);
1135     btn->setMinimumSize(QSize(1, btn->minimumSizeHint().height()));
1136     btn->setText(i18n("Go to"));
1137     btn->setIcon(QIcon::fromTheme(QStringLiteral("go-jump")));
1138     btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
1139     connect(btn, &QToolButton::clicked, this, &KateGotoBar::gotoLine);
1140     topLayout->addWidget(btn);
1141 
1142     btn = m_modifiedUp = new QToolButton(this);
1143     btn->setAutoRaise(true);
1144     btn->setMinimumSize(QSize(1, btn->minimumSizeHint().height()));
1145     btn->setDefaultAction(m_view->action("modified_line_up"));
1146     btn->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search")));
1147     btn->setText(QString());
1148     btn->installEventFilter(this);
1149     topLayout->addWidget(btn);
1150 
1151     btn = m_modifiedDown = new QToolButton(this);
1152     btn->setAutoRaise(true);
1153     btn->setMinimumSize(QSize(1, btn->minimumSizeHint().height()));
1154     btn->setDefaultAction(m_view->action("modified_line_down"));
1155     btn->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search")));
1156     btn->setText(QString());
1157     btn->installEventFilter(this);
1158     topLayout->addWidget(btn);
1159 
1160     topLayout->addStretch();
1161 
1162     setFocusProxy(m_gotoRange);
1163 }
1164 
showEvent(QShowEvent * event)1165 void KateGotoBar::showEvent(QShowEvent *event)
1166 {
1167     Q_UNUSED(event)
1168     // Catch rare cases where the bar is visible while document is edited
1169     connect(m_view->document(), &KTextEditor::Document::textChanged, this, &KateGotoBar::updateData);
1170 }
1171 
closed()1172 void KateGotoBar::closed()
1173 {
1174     disconnect(m_view->document(), &KTextEditor::Document::textChanged, this, &KateGotoBar::updateData);
1175 }
1176 
eventFilter(QObject * object,QEvent * event)1177 bool KateGotoBar::eventFilter(QObject *object, QEvent *event)
1178 {
1179     if (object == m_modifiedUp || object == m_modifiedDown) {
1180         if (event->type() != QEvent::Wheel) {
1181             return false;
1182         }
1183 
1184         int delta = static_cast<QWheelEvent *>(event)->angleDelta().y();
1185         // Reset m_wheelDelta when scroll direction change
1186         if (m_wheelDelta != 0 && (m_wheelDelta < 0) != (delta < 0)) {
1187             m_wheelDelta = 0;
1188         }
1189 
1190         m_wheelDelta += delta;
1191 
1192         if (m_wheelDelta >= 120) {
1193             m_wheelDelta = 0;
1194             m_modifiedUp->click();
1195         } else if (m_wheelDelta <= -120) {
1196             m_wheelDelta = 0;
1197             m_modifiedDown->click();
1198         }
1199     }
1200 
1201     return false;
1202 }
1203 
gotoClipboard()1204 void KateGotoBar::gotoClipboard()
1205 {
1206     static const QRegularExpression rx(QStringLiteral("-?\\d+"));
1207     const int lineNo = rx.match(QApplication::clipboard()->text(QClipboard::Selection)).captured().toInt();
1208     if (lineNo >= m_gotoRange->minimum() && lineNo <= m_gotoRange->maximum()) {
1209         m_gotoRange->setValue(lineNo);
1210         gotoLine();
1211     } else {
1212         QPointer<KTextEditor::Message> message = new KTextEditor::Message(i18n("No valid line number found in clipboard"));
1213         message->setWordWrap(true);
1214         message->setAutoHide(2000);
1215         message->setPosition(KTextEditor::Message::BottomInView);
1216         message->setView(m_view), m_view->document()->postMessage(message);
1217     }
1218 }
1219 
updateData()1220 void KateGotoBar::updateData()
1221 {
1222     int lines = m_view->document()->lines();
1223     m_gotoRange->setMinimum(-lines);
1224     m_gotoRange->setMaximum(lines);
1225     if (!isVisible()) {
1226         m_gotoRange->setValue(m_view->cursorPosition().line() + 1);
1227         m_gotoRange->adjustSize(); // ### does not respect the range :-(
1228     }
1229 
1230     m_gotoRange->selectAll();
1231 }
1232 
keyPressEvent(QKeyEvent * event)1233 void KateGotoBar::keyPressEvent(QKeyEvent *event)
1234 {
1235     int key = event->key();
1236     if (key == Qt::Key_Return || key == Qt::Key_Enter) {
1237         gotoLine();
1238         return;
1239     }
1240     KateViewBarWidget::keyPressEvent(event);
1241 }
1242 
gotoLine()1243 void KateGotoBar::gotoLine()
1244 {
1245     KTextEditor::ViewPrivate *kv = qobject_cast<KTextEditor::ViewPrivate *>(m_view);
1246     if (kv && kv->selection() && !kv->config()->persistentSelection()) {
1247         kv->clearSelection();
1248     }
1249 
1250     int gotoValue = m_gotoRange->value();
1251     if (gotoValue < 0) {
1252         gotoValue += m_view->document()->lines();
1253     } else if (gotoValue > 0) {
1254         gotoValue -= 1;
1255     }
1256 
1257     m_view->setCursorPosition(KTextEditor::Cursor(gotoValue, 0));
1258     m_view->setFocus();
1259     Q_EMIT hideMe();
1260 }
1261 // END KateGotoBar
1262 
1263 // BEGIN KateDictionaryBar
KateDictionaryBar(KTextEditor::ViewPrivate * view,QWidget * parent)1264 KateDictionaryBar::KateDictionaryBar(KTextEditor::ViewPrivate *view, QWidget *parent)
1265     : KateViewBarWidget(true, parent)
1266     , m_view(view)
1267 {
1268     Q_ASSERT(m_view != nullptr); // this bar widget is pointless w/o a view
1269 
1270     QHBoxLayout *topLayout = new QHBoxLayout(centralWidget());
1271     topLayout->setContentsMargins(0, 0, 0, 0);
1272     // topLayout->setSpacing(spacingHint());
1273     m_dictionaryComboBox = new Sonnet::DictionaryComboBox(centralWidget());
1274     connect(m_dictionaryComboBox, &Sonnet::DictionaryComboBox::dictionaryChanged, this, &KateDictionaryBar::dictionaryChanged);
1275     connect(view->doc(), &KTextEditor::DocumentPrivate::defaultDictionaryChanged, this, &KateDictionaryBar::updateData);
1276     QLabel *label = new QLabel(i18n("Dictionary:"), centralWidget());
1277     label->setBuddy(m_dictionaryComboBox);
1278 
1279     topLayout->addWidget(label);
1280     topLayout->addWidget(m_dictionaryComboBox, 1);
1281     topLayout->setStretchFactor(m_dictionaryComboBox, 0);
1282     topLayout->addStretch();
1283 }
1284 
~KateDictionaryBar()1285 KateDictionaryBar::~KateDictionaryBar()
1286 {
1287 }
1288 
updateData()1289 void KateDictionaryBar::updateData()
1290 {
1291     KTextEditor::DocumentPrivate *document = m_view->doc();
1292     QString dictionary = document->defaultDictionary();
1293     if (dictionary.isEmpty()) {
1294         dictionary = Sonnet::Speller().defaultLanguage();
1295     }
1296     m_dictionaryComboBox->setCurrentByDictionary(dictionary);
1297 }
1298 
dictionaryChanged(const QString & dictionary)1299 void KateDictionaryBar::dictionaryChanged(const QString &dictionary)
1300 {
1301     const KTextEditor::Range selection = m_view->selectionRange();
1302     if (selection.isValid() && !selection.isEmpty()) {
1303         const bool blockmode = m_view->blockSelection();
1304         m_view->doc()->setDictionary(dictionary, selection, blockmode);
1305     } else {
1306         m_view->doc()->setDefaultDictionary(dictionary);
1307     }
1308 }
1309 
1310 // END KateGotoBar
1311 
1312 // BEGIN KateModOnHdPrompt
KateModOnHdPrompt(KTextEditor::DocumentPrivate * doc,KTextEditor::ModificationInterface::ModifiedOnDiskReason modtype,const QString & reason)1313 KateModOnHdPrompt::KateModOnHdPrompt(KTextEditor::DocumentPrivate *doc, KTextEditor::ModificationInterface::ModifiedOnDiskReason modtype, const QString &reason)
1314     : QObject(doc)
1315     , m_doc(doc)
1316     , m_modtype(modtype)
1317     , m_proc(nullptr)
1318     , m_diffFile(nullptr)
1319     , m_diffAction(nullptr)
1320 {
1321     m_message = new KTextEditor::Message(reason, KTextEditor::Message::Information);
1322     m_message->setPosition(KTextEditor::Message::AboveView);
1323     m_message->setWordWrap(true);
1324 
1325     // If the file isn't deleted, present a diff button
1326     const bool onDiskDeleted = modtype == KTextEditor::ModificationInterface::OnDiskDeleted;
1327     if (!onDiskDeleted) {
1328         QAction *aAutoReload = new QAction(i18n("Enable Auto Reload"), this);
1329         aAutoReload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
1330         aAutoReload->setToolTip(i18n("Will never again warn about on disk changes but always reload."));
1331         m_message->addAction(aAutoReload, false);
1332         connect(aAutoReload, &QAction::triggered, this, &KateModOnHdPrompt::autoReloadTriggered);
1333 
1334         if (!QStandardPaths::findExecutable(QStringLiteral("diff")).isEmpty()) {
1335             m_diffAction = new QAction(i18n("View &Difference"), this);
1336             m_diffAction->setIcon(QIcon::fromTheme(QStringLiteral("document-multiple")));
1337             m_diffAction->setToolTip(i18n("Shows a diff of the changes"));
1338             m_message->addAction(m_diffAction, false);
1339             connect(m_diffAction, &QAction::triggered, this, &KateModOnHdPrompt::slotDiff);
1340         }
1341 
1342         QAction *aReload = new QAction(i18n("&Reload"), this);
1343         aReload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
1344         aReload->setToolTip(i18n("Reload the file from disk. Unsaved changes will be lost."));
1345         m_message->addAction(aReload);
1346         connect(aReload, &QAction::triggered, this, &KateModOnHdPrompt::reloadTriggered);
1347     } else {
1348         QAction *closeFile = new QAction(i18nc("@action:button closes the opened file", "&Close File"), this);
1349         closeFile->setIcon(QIcon::fromTheme(QStringLiteral("document-close")));
1350         closeFile->setToolTip(i18n("Close the file, discarding its content."));
1351         m_message->addAction(closeFile, false);
1352         connect(closeFile, &QAction::triggered, this, &KateModOnHdPrompt::closeTriggered);
1353 
1354         QAction *aSaveAs = new QAction(i18n("&Save As..."), this);
1355         aSaveAs->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as")));
1356         aSaveAs->setToolTip(i18n("Lets you select a location and save the file again."));
1357         m_message->addAction(aSaveAs, false);
1358         connect(aSaveAs, &QAction::triggered, this, &KateModOnHdPrompt::saveAsTriggered);
1359     }
1360 
1361     QAction *aIgnore = new QAction(i18n("&Ignore"), this);
1362     aIgnore->setToolTip(i18n("Ignores the changes on disk without any action."));
1363     aIgnore->setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel")));
1364     m_message->addAction(aIgnore);
1365     connect(aIgnore, &QAction::triggered, this, &KateModOnHdPrompt::ignoreTriggered);
1366 
1367     m_doc->postMessage(m_message);
1368 }
1369 
~KateModOnHdPrompt()1370 KateModOnHdPrompt::~KateModOnHdPrompt()
1371 {
1372     delete m_proc;
1373     m_proc = nullptr;
1374     if (m_diffFile) {
1375         m_diffFile->setAutoRemove(true);
1376         delete m_diffFile;
1377         m_diffFile = nullptr;
1378     }
1379     delete m_message;
1380 }
1381 
slotDiff()1382 void KateModOnHdPrompt::slotDiff()
1383 {
1384     if (m_diffFile) {
1385         return;
1386     }
1387 
1388     m_diffFile = new QTemporaryFile(QLatin1String("XXXXXX.diff"));
1389     m_diffFile->open();
1390 
1391     // Start a KProcess that creates a diff
1392     m_proc = new KProcess(this);
1393     m_proc->setOutputChannelMode(KProcess::MergedChannels);
1394     *m_proc << QStringLiteral("diff") << QStringLiteral("-u") << QStringLiteral("-") << m_doc->url().toLocalFile();
1395     connect(m_proc, &KProcess::readyRead, this, &KateModOnHdPrompt::slotDataAvailable);
1396     connect(m_proc, &KProcess::finished, this, &KateModOnHdPrompt::slotPDone);
1397 
1398     // disable the diff button, to hinder the user to run it twice.
1399     m_diffAction->setEnabled(false);
1400 
1401     m_proc->start();
1402 
1403     QTextStream ts(m_proc);
1404     int lastln = m_doc->lines() - 1;
1405     for (int l = 0; l < lastln; ++l) {
1406         ts << m_doc->line(l) << '\n';
1407     }
1408     ts << m_doc->line(lastln);
1409     ts.flush();
1410     m_proc->closeWriteChannel();
1411 }
1412 
slotDataAvailable()1413 void KateModOnHdPrompt::slotDataAvailable()
1414 {
1415     m_diffFile->write(m_proc->readAll());
1416 }
1417 
slotPDone()1418 void KateModOnHdPrompt::slotPDone()
1419 {
1420     m_diffAction->setEnabled(true);
1421 
1422     const QProcess::ExitStatus es = m_proc->exitStatus();
1423     delete m_proc;
1424     m_proc = nullptr;
1425 
1426     if (es != QProcess::NormalExit) {
1427         KMessageBox::sorry(nullptr,
1428                            i18n("The diff command failed. Please make sure that "
1429                                 "diff(1) is installed and in your PATH."),
1430                            i18n("Error Creating Diff"));
1431         delete m_diffFile;
1432         m_diffFile = nullptr;
1433         return;
1434     }
1435 
1436     if (m_diffFile->size() == 0) {
1437         KMessageBox::information(nullptr, i18n("The files are identical."), i18n("Diff Output"));
1438         delete m_diffFile;
1439         m_diffFile = nullptr;
1440         return;
1441     }
1442 
1443     m_diffFile->setAutoRemove(false);
1444     QUrl url = QUrl::fromLocalFile(m_diffFile->fileName());
1445     delete m_diffFile;
1446     m_diffFile = nullptr;
1447 
1448     KIO::OpenUrlJob *job = new KIO::OpenUrlJob(url, QStringLiteral("text/x-patch"));
1449     job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr /*TODO window*/));
1450     job->setDeleteTemporaryFile(true); // delete the file, once the client exits
1451     job->start();
1452 }
1453 
1454 // END KateModOnHdPrompt
1455