1 /* This file is part of the KDE project
2 SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org>
3 SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org>
4 SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch>
5
6 SPDX-License-Identifier: LGPL-2.0-only
7 */
8
9 #include "kateconfigdialog.h"
10
11 #include "kateapp.h"
12 #include "kateconfigplugindialogpage.h"
13 #include "katedebug.h"
14 #include "katedocmanager.h"
15 #include "katemainwindow.h"
16 #include "katepluginmanager.h"
17 #include "katequickopenmodel.h"
18 #include "katesessionmanager.h"
19 #include "kateviewmanager.h"
20
21 #include <KConfigGroup>
22 #include <KLocalizedString>
23 #include <KMessageBox>
24 #include <KPluralHandlingSpinBox>
25 #include <KSharedConfig>
26 #include <KStandardAction>
27
28 #include <QCheckBox>
29 #include <QComboBox>
30 #include <QDesktopServices>
31 #include <QDialogButtonBox>
32 #include <QFrame>
33 #include <QGroupBox>
34 #include <QLabel>
35 #include <QVBoxLayout>
36
KateConfigDialog(KateMainWindow * parent)37 KateConfigDialog::KateConfigDialog(KateMainWindow *parent)
38 : KPageDialog(parent)
39 , m_mainWindow(parent)
40 {
41 setWindowTitle(i18n("Configure"));
42 setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::Help);
43
44 // first: add the KTextEditor config pages
45 // rational: most people want to alter e.g. the fonts, the colors or some other editor stuff first
46 addEditorPages();
47
48 // second: add out own config pages
49 // this includes all plugin config pages, added to the bottom
50 addBehaviorPage();
51 addSessionPage();
52 addFeedbackPage();
53 addPluginsPage();
54 addPluginPages();
55
56 // ensure no stray signals already set this!
57 m_dataChanged = false;
58 buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(false);
59
60 // handle dialog actions
61 connect(this, &KateConfigDialog::accepted, this, &KateConfigDialog::slotApply);
62 connect(buttonBox()->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &KateConfigDialog::slotApply);
63 connect(buttonBox()->button(QDialogButtonBox::Help), &QPushButton::clicked, this, &KateConfigDialog::slotHelp);
64 }
65
addBehaviorPage()66 void KateConfigDialog::addBehaviorPage()
67 {
68 KSharedConfig::Ptr config = KSharedConfig::openConfig();
69 KConfigGroup cgGeneral = KConfigGroup(config, "General");
70
71 QFrame *generalFrame = new QFrame;
72 KPageWidgetItem *item = addPage(generalFrame, i18n("Behavior"));
73 item->setHeader(i18n("Behavior Options"));
74 item->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-windows-behavior")));
75
76 QVBoxLayout *layout = new QVBoxLayout(generalFrame);
77 layout->setContentsMargins(0, 0, 0, 0);
78
79 // GROUP with the one below: "Behavior"
80 QGroupBox *buttonGroup = new QGroupBox(i18n("&Behavior"), generalFrame);
81 QVBoxLayout *vbox = new QVBoxLayout;
82 layout->addWidget(buttonGroup);
83
84 auto hlayout = new QHBoxLayout;
85 auto label = new QLabel(i18n("&Switch to output view upon message type:"), buttonGroup);
86 hlayout->addWidget(label);
87 m_messageTypes = new QComboBox(buttonGroup);
88 hlayout->addWidget(m_messageTypes);
89 label->setBuddy(m_messageTypes);
90 m_messageTypes->addItems({i18n("Never"), i18n("Error"), i18n("Warning"), i18n("Info"), i18n("Log")});
91 m_messageTypes->setCurrentIndex(cgGeneral.readEntry("Show output view for message type", 1));
92 connect(m_messageTypes, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KateConfigDialog::slotChanged);
93 vbox->addLayout(hlayout);
94
95 // modified files notification
96 m_modNotifications = new QCheckBox(i18n("Use a separate &dialog for handling externally modified files"), buttonGroup);
97 m_modNotifications->setChecked(m_mainWindow->modNotificationEnabled());
98 m_modNotifications->setWhatsThis(
99 i18n("If enabled, a modal dialog will be used to show all of the modified files. "
100 "If not enabled, you will be individually asked what to do for each modified file "
101 "only when that file's view receives focus."));
102 connect(m_modNotifications, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged);
103
104 vbox->addWidget(m_modNotifications);
105
106 buttonGroup->setLayout(vbox);
107
108 // tabbar => we allow to configure some limit on number of tabs to show
109 buttonGroup = new QGroupBox(i18n("&Tabs"), generalFrame);
110 vbox = new QVBoxLayout;
111 buttonGroup->setLayout(vbox);
112 hlayout = new QHBoxLayout;
113 label = new QLabel(i18n("&Limit number of tabs:"), buttonGroup);
114 hlayout->addWidget(label);
115 m_tabLimit = new QSpinBox(buttonGroup);
116 hlayout->addWidget(m_tabLimit);
117 label->setBuddy(m_tabLimit);
118 m_tabLimit->setRange(0, 256);
119 m_tabLimit->setSpecialValueText(i18n("Unlimited"));
120 m_tabLimit->setValue(cgGeneral.readEntry("Tabbar Tab Limit", 0));
121 connect(m_tabLimit, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KateConfigDialog::slotChanged);
122 vbox->addLayout(hlayout);
123
124 m_showTabCloseButton = new QCheckBox(i18n("&Show close button"), buttonGroup);
125 m_showTabCloseButton->setChecked(cgGeneral.readEntry("Show Tabs Close Button", true));
126 m_showTabCloseButton->setToolTip(i18n("When checked each tab will display a close button."));
127 connect(m_showTabCloseButton, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged);
128 vbox->addWidget(m_showTabCloseButton);
129
130 m_expandTabs = new QCheckBox(i18n("&Expand tabs"), buttonGroup);
131 m_expandTabs->setChecked(cgGeneral.readEntry("Expand Tabs", true));
132 m_expandTabs->setToolTip(i18n("When checked tabs take as much size as possible."));
133 connect(m_expandTabs, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged);
134 vbox->addWidget(m_expandTabs);
135
136 m_tabDoubleClickNewDocument = new QCheckBox(i18n("&Double click opens a new document"), buttonGroup);
137 m_tabDoubleClickNewDocument->setChecked(cgGeneral.readEntry("Tab Double Click New Document", true));
138 m_tabDoubleClickNewDocument->setToolTip(i18n("When checked double click opens a new document."));
139 connect(m_tabDoubleClickNewDocument, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged);
140 vbox->addWidget(m_tabDoubleClickNewDocument);
141
142 m_tabMiddleClickCloseDocument = new QCheckBox(i18n("&Middle click closes a document"), buttonGroup);
143 m_tabMiddleClickCloseDocument->setChecked(cgGeneral.readEntry("Tab Middle Click Close Document", true));
144 m_tabMiddleClickCloseDocument->setToolTip(i18n("When checked middle click closes a document."));
145 connect(m_tabMiddleClickCloseDocument, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged);
146 vbox->addWidget(m_tabMiddleClickCloseDocument);
147
148 layout->addWidget(buttonGroup);
149
150 layout->addStretch(1); // :-] works correct without autoadd
151 }
152
addSessionPage()153 void KateConfigDialog::addSessionPage()
154 {
155 KSharedConfig::Ptr config = KSharedConfig::openConfig();
156 KConfigGroup cgGeneral = KConfigGroup(config, "General");
157
158 QWidget *sessionsPage = new QWidget();
159 auto item = addPage(sessionsPage, i18n("Session"));
160 item->setHeader(i18n("Session Management"));
161 item->setIcon(QIcon::fromTheme(QStringLiteral("view-history")));
162
163 sessionConfigUi.setupUi(sessionsPage);
164
165 // save meta infos
166 sessionConfigUi.saveMetaInfos->setChecked(KateApp::self()->documentManager()->getSaveMetaInfos());
167 connect(sessionConfigUi.saveMetaInfos, &QGroupBox::toggled, this, &KateConfigDialog::slotChanged);
168
169 // meta infos days
170 sessionConfigUi.daysMetaInfos->setMaximum(180);
171 sessionConfigUi.daysMetaInfos->setSpecialValueText(i18nc("The special case of 'Delete unused meta-information after'", "(never)"));
172 sessionConfigUi.daysMetaInfos->setSuffix(ki18ncp("The suffix of 'Delete unused meta-information after'", " day", " days"));
173 sessionConfigUi.daysMetaInfos->setValue(KateApp::self()->documentManager()->getDaysMetaInfos());
174 connect(sessionConfigUi.daysMetaInfos,
175 static_cast<void (KPluralHandlingSpinBox::*)(int)>(&KPluralHandlingSpinBox::valueChanged),
176 this,
177 &KateConfigDialog::slotChanged);
178
179 // restore view config
180 sessionConfigUi.restoreVC->setChecked(cgGeneral.readEntry("Restore Window Configuration", true));
181 connect(sessionConfigUi.restoreVC, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged);
182
183 sessionConfigUi.spinBoxRecentFilesCount->setValue(recentFilesMaxCount());
184 connect(sessionConfigUi.spinBoxRecentFilesCount, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KateConfigDialog::slotChanged);
185
186 QString sesStart(cgGeneral.readEntry("Startup Session", "manual"));
187 if (sesStart == QLatin1String("new")) {
188 sessionConfigUi.startNewSessionRadioButton->setChecked(true);
189 } else if (sesStart == QLatin1String("last")) {
190 sessionConfigUi.loadLastUserSessionRadioButton->setChecked(true);
191 } else {
192 sessionConfigUi.manuallyChooseSessionRadioButton->setChecked(true);
193 }
194
195 connect(sessionConfigUi.startNewSessionRadioButton, &QRadioButton::toggled, this, &KateConfigDialog::slotChanged);
196 connect(sessionConfigUi.loadLastUserSessionRadioButton, &QRadioButton::toggled, this, &KateConfigDialog::slotChanged);
197 connect(sessionConfigUi.manuallyChooseSessionRadioButton, &QRadioButton::toggled, this, &KateConfigDialog::slotChanged);
198
199 // Closing last file closes Kate
200 sessionConfigUi.modCloseAfterLast->setChecked(m_mainWindow->modCloseAfterLast());
201 connect(sessionConfigUi.modCloseAfterLast, &QCheckBox::toggled, this, &KateConfigDialog::slotChanged);
202
203 // stash unsave changes
204 sessionConfigUi.stashNewUnsavedFiles->setChecked(KateApp::self()->stashManager()->stashNewUnsavedFiles());
205 sessionConfigUi.stashUnsavedFilesChanges->setChecked(KateApp::self()->stashManager()->stashUnsavedChanges());
206 connect(sessionConfigUi.stashNewUnsavedFiles, &QRadioButton::toggled, this, &KateConfigDialog::slotChanged);
207 connect(sessionConfigUi.stashUnsavedFilesChanges, &QRadioButton::toggled, this, &KateConfigDialog::slotChanged);
208 }
209
addPluginsPage()210 void KateConfigDialog::addPluginsPage()
211 {
212 QFrame *page = new QFrame(this);
213 QVBoxLayout *vlayout = new QVBoxLayout(page);
214 vlayout->setContentsMargins(0, 0, 0, 0);
215 vlayout->setSpacing(0);
216
217 KateConfigPluginPage *configPluginPage = new KateConfigPluginPage(page, this);
218 vlayout->addWidget(configPluginPage);
219 connect(configPluginPage, &KateConfigPluginPage::changed, this, &KateConfigDialog::slotChanged);
220
221 auto item = addPage(page, i18n("Plugins"));
222 item->setHeader(i18n("Plugin Manager"));
223 item->setIcon(QIcon::fromTheme(QStringLiteral("preferences-plugin")));
224 }
225
addFeedbackPage()226 void KateConfigDialog::addFeedbackPage()
227 {
228 #ifdef WITH_KUSERFEEDBACK
229 // KUserFeedback Config
230 auto page = new QFrame(this);
231 auto vlayout = new QVBoxLayout(page);
232 vlayout->setContentsMargins(0, 0, 0, 0);
233 vlayout->setSpacing(0);
234
235 m_userFeedbackWidget = new KUserFeedback::FeedbackConfigWidget(page);
236 m_userFeedbackWidget->setFeedbackProvider(&KateApp::self()->userFeedbackProvider());
237 connect(m_userFeedbackWidget, &KUserFeedback::FeedbackConfigWidget::configurationChanged, this, &KateConfigDialog::slotChanged);
238 vlayout->addWidget(m_userFeedbackWidget);
239
240 auto item = addPage(page, i18n("User Feedback"));
241 item->setHeader(i18n("User Feedback"));
242 item->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-locale")));
243 #endif
244 }
245
addPluginPages()246 void KateConfigDialog::addPluginPages()
247 {
248 const KatePluginList &pluginList(KateApp::self()->pluginManager()->pluginList());
249 for (const KatePluginInfo &plugin : pluginList) {
250 if (plugin.load) {
251 addPluginPage(plugin.plugin);
252 }
253 }
254 }
255
addEditorPages()256 void KateConfigDialog::addEditorPages()
257 {
258 for (int i = 0; i < KTextEditor::Editor::instance()->configPages(); ++i) {
259 KTextEditor::ConfigPage *page = KTextEditor::Editor::instance()->configPage(i, this);
260 connect(page, &KTextEditor::ConfigPage::changed, this, &KateConfigDialog::slotChanged);
261 m_editorPages.push_back(page);
262 KPageWidgetItem *item = addPage(page, page->name());
263 item->setHeader(page->fullName());
264 item->setIcon(page->icon());
265 }
266 }
267
addPluginPage(KTextEditor::Plugin * plugin)268 void KateConfigDialog::addPluginPage(KTextEditor::Plugin *plugin)
269 {
270 for (int i = 0; i < plugin->configPages(); i++) {
271 KTextEditor::ConfigPage *cp = plugin->configPage(i, this);
272 KPageWidgetItem *item = addPage(cp, cp->name());
273 item->setHeader(cp->fullName());
274 item->setIcon(cp->icon());
275
276 PluginPageListItem info;
277 info.plugin = plugin;
278 info.pluginPage = cp;
279 info.idInPlugin = i;
280 info.pageWidgetItem = item;
281 connect(info.pluginPage, &KTextEditor::ConfigPage::changed, this, &KateConfigDialog::slotChanged);
282 m_pluginPages.insert(item, info);
283 }
284 }
285
removePluginPage(KTextEditor::Plugin * plugin)286 void KateConfigDialog::removePluginPage(KTextEditor::Plugin *plugin)
287 {
288 QList<KPageWidgetItem *> remove;
289 for (QHash<KPageWidgetItem *, PluginPageListItem>::const_iterator it = m_pluginPages.constBegin(); it != m_pluginPages.constEnd(); ++it) {
290 const PluginPageListItem &pli = it.value();
291
292 if (pli.plugin == plugin) {
293 remove.append(it.key());
294 }
295 }
296
297 qCDebug(LOG_KATE) << remove.count();
298 while (!remove.isEmpty()) {
299 KPageWidgetItem *wItem = remove.takeLast();
300 PluginPageListItem item = m_pluginPages.take(wItem);
301 delete item.pluginPage;
302 removePage(wItem);
303 }
304 }
305
slotApply()306 void KateConfigDialog::slotApply()
307 {
308 KSharedConfig::Ptr config = KSharedConfig::openConfig();
309
310 // if data changed apply the kate app stuff
311 if (m_dataChanged) {
312 KConfigGroup cg = KConfigGroup(config, "General");
313
314 cg.writeEntry("Restore Window Configuration", sessionConfigUi.restoreVC->isChecked());
315
316 cg.writeEntry("Recent File List Entry Count", sessionConfigUi.spinBoxRecentFilesCount->value());
317
318 if (sessionConfigUi.startNewSessionRadioButton->isChecked()) {
319 cg.writeEntry("Startup Session", "new");
320 } else if (sessionConfigUi.loadLastUserSessionRadioButton->isChecked()) {
321 cg.writeEntry("Startup Session", "last");
322 } else {
323 cg.writeEntry("Startup Session", "manual");
324 }
325
326 cg.writeEntry("Save Meta Infos", sessionConfigUi.saveMetaInfos->isChecked());
327 KateApp::self()->documentManager()->setSaveMetaInfos(sessionConfigUi.saveMetaInfos->isChecked());
328
329 cg.writeEntry("Days Meta Infos", sessionConfigUi.daysMetaInfos->value());
330 KateApp::self()->documentManager()->setDaysMetaInfos(sessionConfigUi.daysMetaInfos->value());
331
332 cg.writeEntry("Modified Notification", m_modNotifications->isChecked());
333 m_mainWindow->setModNotificationEnabled(m_modNotifications->isChecked());
334
335 cg.writeEntry("Close After Last", sessionConfigUi.modCloseAfterLast->isChecked());
336 m_mainWindow->setModCloseAfterLast(sessionConfigUi.modCloseAfterLast->isChecked());
337
338 cg.writeEntry("Show output view for message type", m_messageTypes->currentIndex());
339
340 cg.writeEntry("Stash unsaved file changes", sessionConfigUi.stashUnsavedFilesChanges->isChecked());
341 KateApp::self()->stashManager()->setStashUnsavedChanges(sessionConfigUi.stashUnsavedFilesChanges->isChecked());
342 cg.writeEntry("Stash new unsaved files", sessionConfigUi.stashNewUnsavedFiles->isChecked());
343 KateApp::self()->stashManager()->setStashNewUnsavedFiles(sessionConfigUi.stashNewUnsavedFiles->isChecked());
344
345 cg.writeEntry("Tabbar Tab Limit", m_tabLimit->value());
346
347 cg.writeEntry("Show Tabs Close Button", m_showTabCloseButton->isChecked());
348
349 cg.writeEntry("Expand Tabs", m_expandTabs->isChecked());
350
351 cg.writeEntry("Tab Double Click New Document", m_tabDoubleClickNewDocument->isChecked());
352 cg.writeEntry("Tab Middle Click Close Document", m_tabMiddleClickCloseDocument->isChecked());
353
354 // patch document modified warn state
355 const QList<KTextEditor::Document *> &docs = KateApp::self()->documentManager()->documentList();
356 for (KTextEditor::Document *doc : docs) {
357 if (auto modIface = qobject_cast<KTextEditor::ModificationInterface *>(doc)) {
358 modIface->setModifiedOnDiskWarning(!m_modNotifications->isChecked());
359 }
360 }
361
362 m_mainWindow->saveOptions();
363
364 // save plugin config !!
365 KateSessionManager *sessionmanager = KateApp::self()->sessionManager();
366 KConfig *sessionConfig = sessionmanager->activeSession()->config();
367 KateApp::self()->pluginManager()->writeConfig(sessionConfig);
368
369 #ifdef WITH_KUSERFEEDBACK
370 // set current active mode + write back the config for future starts
371 KateApp::self()->userFeedbackProvider().setTelemetryMode(m_userFeedbackWidget->telemetryMode());
372 KateApp::self()->userFeedbackProvider().setSurveyInterval(m_userFeedbackWidget->surveyInterval());
373 #endif
374 }
375
376 for (const PluginPageListItem &plugin : qAsConst(m_pluginPages)) {
377 if (plugin.pluginPage) {
378 plugin.pluginPage->apply();
379 }
380 }
381
382 // apply ktexteditor pages
383 for (KTextEditor::ConfigPage *page : qAsConst(m_editorPages)) {
384 page->apply();
385 }
386
387 config->sync();
388
389 // emit config change
390 if (m_dataChanged) {
391 KateApp::self()->configurationChanged();
392 }
393
394 m_dataChanged = false;
395 buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(false);
396 }
397
slotChanged()398 void KateConfigDialog::slotChanged()
399 {
400 m_dataChanged = true;
401 buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(true);
402 }
403
showAppPluginPage(KTextEditor::Plugin * p,int id)404 void KateConfigDialog::showAppPluginPage(KTextEditor::Plugin *p, int id)
405 {
406 for (const PluginPageListItem &plugin : qAsConst(m_pluginPages)) {
407 if ((plugin.plugin == p) && (id == plugin.idInPlugin)) {
408 setCurrentPage(plugin.pageWidgetItem);
409 break;
410 }
411 }
412 }
413
slotHelp()414 void KateConfigDialog::slotHelp()
415 {
416 QDesktopServices::openUrl(QUrl(QStringLiteral("help:/")));
417 }
418
recentFilesMaxCount()419 int KateConfigDialog::recentFilesMaxCount()
420 {
421 int maxItems = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("Recent File List Entry Count", 10);
422 return maxItems;
423 }
424
closeEvent(QCloseEvent * event)425 void KateConfigDialog::closeEvent(QCloseEvent *event)
426 {
427 if (!m_dataChanged) {
428 event->accept();
429 return;
430 }
431
432 const auto response = KMessageBox::warningYesNoCancel(this,
433 i18n("You have unsaved changes. Do you want to apply the changes or discard them?"),
434 i18n("Warning"),
435 KStandardGuiItem::save(),
436 KStandardGuiItem::discard(),
437 KStandardGuiItem::cancel());
438 switch (response) {
439 case KMessageBox::Yes:
440 slotApply();
441 Q_FALLTHROUGH();
442 case KMessageBox::No:
443 event->accept();
444 break;
445 case KMessageBox::Cancel:
446 event->ignore();
447 break;
448 default:
449 break;
450 }
451 }
452