1 /*
2     SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
3     SPDX-FileCopyrightText: 2018 Harald Sitter <sitter@kde.org>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 // Own
9 #include "EditProfileDialog.h"
10 
11 // Qt
12 #include <QDialog>
13 #include <QDialogButtonBox>
14 #include <QFileDialog>
15 #include <QIcon>
16 #include <QInputDialog>
17 #include <QPainter>
18 #include <QPushButton>
19 #include <QRegularExpressionValidator>
20 #include <QStandardItem>
21 #include <QStandardPaths>
22 #include <QStringListModel>
23 #include <QTextCodec>
24 #include <QTimer>
25 #include <QUrl>
26 
27 // KDE
28 #include <KCodecAction>
29 #include <KIconDialog>
30 #include <KLocalizedString>
31 #include <KMessageBox>
32 #include <KNSCore/Engine>
33 #include <KWindowSystem>
34 #include <kconfigwidgets_version.h>
35 
36 // Konsole
37 #include "ui_EditProfileAdvancedPage.h"
38 #include "ui_EditProfileAppearancePage.h"
39 #include "ui_EditProfileGeneralPage.h"
40 #include "ui_EditProfileKeyboardPage.h"
41 #include "ui_EditProfileMousePage.h"
42 #include "ui_EditProfileScrollingPage.h"
43 #include "ui_EditProfileTabsPage.h"
44 
45 #include "colorscheme/ColorSchemeManager.h"
46 
47 #include "keyboardtranslator/KeyboardTranslator.h"
48 
49 #include "KeyBindingEditor.h"
50 #include "ShellCommand.h"
51 #include "WindowSystemInfo.h"
52 #include "profile/ProfileManager.h"
53 
54 using namespace Konsole;
55 
EditProfileDialog(QWidget * parent)56 EditProfileDialog::EditProfileDialog(QWidget *parent)
57     : KPageDialog(parent)
58     , _generalUi(nullptr)
59     , _tabsUi(nullptr)
60     , _appearanceUi(nullptr)
61     , _scrollingUi(nullptr)
62     , _keyboardUi(nullptr)
63     , _mouseUi(nullptr)
64     , _advancedUi(nullptr)
65     , _tempProfile(nullptr)
66     , _profile(nullptr)
67     , _isDefault(false)
68     , _previewedProperties(QHash<int, QVariant>())
69     , _delayedPreviewProperties(QHash<int, QVariant>())
70     , _delayedPreviewTimer(new QTimer(this))
71     , _colorDialog(nullptr)
72     , _buttonBox(nullptr)
73     , _fontDialog(nullptr)
74 {
75     setWindowTitle(i18n("Edit Profile"));
76     setFaceType(KPageDialog::List);
77 
78     _buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply);
79     setButtonBox(_buttonBox);
80 
81     _buttonBox->button(QDialogButtonBox::Ok)->setDefault(true);
82 
83     auto *applyButton = _buttonBox->button(QDialogButtonBox::Apply);
84     // Disable it, since no modifications have been made yet
85     applyButton->setEnabled(false);
86     connect(applyButton, &QPushButton::clicked, this, [this]() {
87         if (isProfileNameValid()) {
88             save();
89         }
90     });
91 
92     connect(_delayedPreviewTimer, &QTimer::timeout, this, &Konsole::EditProfileDialog::delayedPreviewActivate);
93 
94     // Set a fallback icon for non-plasma desktops as this dialog looks
95     // terrible without all the icons on the left sidebar.  On GTK related
96     // desktops, this dialog look good enough without installing
97     // oxygen-icon-theme, qt5ct and setting export QT_QPA_PLATFORMTHEME=qt5ct
98     // Plain Xorg desktops still look terrible as there are no icons
99     // visible.
100     const auto defaultIcon = QIcon::fromTheme(QStringLiteral("utilities-terminal"));
101 
102     // General page
103 
104     const QString generalPageName = i18nc("@title:tab Generic, common options", "General");
105     auto *generalPageWidget = new QWidget(this);
106     _generalUi = new Ui::EditProfileGeneralPage();
107     _generalUi->setupUi(generalPageWidget);
108     _generalPageItem = addPage(generalPageWidget, generalPageName);
109     _generalPageItem->setHeader(generalPageName);
110     _generalPageItem->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal")));
111     _pages[_generalPageItem] = Page(&EditProfileDialog::setupGeneralPage);
112 
113     // Tabs page
114 
115     const QString tabsPageName = i18n("Tabs");
116     auto *tabsPageWidget = new QWidget(this);
117     _tabsUi = new Ui::EditProfileTabsPage();
118     _tabsUi->setupUi(tabsPageWidget);
119     auto *tabsPageItem = addPage(tabsPageWidget, tabsPageName);
120     tabsPageItem->setHeader(tabsPageName);
121     tabsPageItem->setIcon(QIcon::fromTheme(QStringLiteral("tab-duplicate"), defaultIcon));
122     _pages[tabsPageItem] = Page(&EditProfileDialog::setupTabsPage);
123 
124     LabelsAligner tabsAligner(tabsPageWidget);
125     tabsAligner.addLayout(dynamic_cast<QGridLayout *>(_tabsUi->tabMonitoringGroup->layout()));
126     tabsAligner.addLayout(dynamic_cast<QGridLayout *>(_tabsUi->renameTabWidget->layout()));
127     tabsAligner.updateLayouts();
128     tabsAligner.align();
129 
130     // Appearance page
131 
132     const QString appearancePageName = i18n("Appearance");
133     auto *appearancePageWidget = new QWidget(this);
134     _appearanceUi = new Ui::EditProfileAppearancePage();
135     _appearanceUi->setupUi(appearancePageWidget);
136     auto *appearancePageItem = addPage(appearancePageWidget, appearancePageName);
137     appearancePageItem->setHeader(appearancePageName);
138     appearancePageItem->setIcon(QIcon::fromTheme(QStringLiteral("kcolorchooser"), defaultIcon));
139     _pages[appearancePageItem] = Page(&EditProfileDialog::setupAppearancePage);
140 
141     LabelsAligner appearanceAligner(appearancePageWidget);
142     appearanceAligner.addLayout(dynamic_cast<QGridLayout *>(_appearanceUi->contentsGroup->layout()));
143     appearanceAligner.updateLayouts();
144     appearanceAligner.align();
145 
146     // Scrolling page
147 
148     const QString scrollingPageName = i18n("Scrolling");
149     auto *scrollingPageWidget = new QWidget(this);
150     _scrollingUi = new Ui::EditProfileScrollingPage();
151     _scrollingUi->setupUi(scrollingPageWidget);
152     auto *scrollingPageItem = addPage(scrollingPageWidget, scrollingPageName);
153     scrollingPageItem->setHeader(scrollingPageName);
154     scrollingPageItem->setIcon(QIcon::fromTheme(QStringLiteral("transform-move-vertical"), defaultIcon));
155     _pages[scrollingPageItem] = Page(&EditProfileDialog::setupScrollingPage);
156 
157     // adjust "history size" label height to match history size widget's first radio button
158     _scrollingUi->historySizeLabel->setFixedHeight(_scrollingUi->historySizeWidget->preferredLabelHeight());
159 
160     // Keyboard page
161 
162     const QString keyboardPageName = i18n("Keyboard");
163     const QString keyboardPageTitle = i18n("Key bindings");
164     auto *keyboardPageWidget = new QWidget(this);
165     _keyboardUi = new Ui::EditProfileKeyboardPage();
166     _keyboardUi->setupUi(keyboardPageWidget);
167     auto *keyboardPageItem = addPage(keyboardPageWidget, keyboardPageName);
168     keyboardPageItem->setHeader(keyboardPageTitle);
169     keyboardPageItem->setIcon(QIcon::fromTheme(QStringLiteral("input-keyboard"), defaultIcon));
170     _pages[keyboardPageItem] = Page(&EditProfileDialog::setupKeyboardPage);
171 
172     // Mouse page
173 
174     const QString mousePageName = i18n("Mouse");
175     auto *mousePageWidget = new QWidget(this);
176     _mouseUi = new Ui::EditProfileMousePage();
177     _mouseUi->setupUi(mousePageWidget);
178 
179     const auto regExp = QRegularExpression(QStringLiteral(R"(([a-z]*:\/\/;)*([A-Za-z*]:\/\/))"));
180     auto validator = new QRegularExpressionValidator(regExp, this);
181     _mouseUi->linkEscapeSequenceTexts->setValidator(validator);
182 
183     auto *mousePageItem = addPage(mousePageWidget, mousePageName);
184     mousePageItem->setHeader(mousePageName);
185     mousePageItem->setIcon(QIcon::fromTheme(QStringLiteral("input-mouse"), defaultIcon));
186     _pages[mousePageItem] = Page(&EditProfileDialog::setupMousePage);
187 
188     // Advanced page
189 
190     const QString advancedPageName = i18nc("@title:tab Complex options", "Advanced");
191     auto *advancedPageWidget = new QWidget(this);
192     _advancedUi = new Ui::EditProfileAdvancedPage();
193     _advancedUi->setupUi(advancedPageWidget);
194     auto *advancedPageItem = addPage(advancedPageWidget, advancedPageName);
195     advancedPageItem->setHeader(advancedPageName);
196     advancedPageItem->setIcon(QIcon::fromTheme(QStringLiteral("configure"), defaultIcon));
197     _pages[advancedPageItem] = Page(&EditProfileDialog::setupAdvancedPage);
198 
199     // there are various setupXYZPage() methods to load the items
200     // for each page and update their states to match the profile
201     // being edited.
202     //
203     // these are only called when needed ( ie. when the user clicks
204     // the tab to move to that page ).
205     //
206     // the _pageNeedsUpdate vector keeps track of the pages that have
207     // not been updated since the last profile change and will need
208     // to be refreshed when the user switches to them
209     connect(this, &KPageDialog::currentPageChanged, this, &Konsole::EditProfileDialog::preparePage);
210 
211     createTempProfile();
212 }
213 
~EditProfileDialog()214 EditProfileDialog::~EditProfileDialog()
215 {
216     delete _generalUi;
217     delete _tabsUi;
218     delete _appearanceUi;
219     delete _scrollingUi;
220     delete _keyboardUi;
221     delete _mouseUi;
222     delete _advancedUi;
223 }
224 
save()225 void EditProfileDialog::save()
226 {
227     const bool isNewProfile = _profileState == EditProfileDialog::NewProfile;
228 
229     if (isNewProfile) {
230         ProfileManager::instance()->addProfile(_profile);
231     }
232 
233     bool defaultChanged = _isDefault != _generalUi->setAsDefaultButton->isChecked();
234 
235     if (_tempProfile->isEmpty() && !defaultChanged) {
236         if (isNewProfile) {
237             // New profile, we need to save it to disk, even if no settings
238             // were changed and _tempProfile is empty
239             ProfileManager::instance()->changeProfile(_profile, _profile->setProperties());
240         }
241         // no changes since last save
242         return;
243     }
244 
245     ProfileManager::instance()->changeProfile(_profile, _tempProfile->setProperties());
246 
247     // ensure that these settings are not undone by a call
248     // to unpreview()
249     QHashIterator<Profile::Property, QVariant> iter(_tempProfile->setProperties());
250     while (iter.hasNext()) {
251         iter.next();
252         _previewedProperties.remove(iter.key());
253     }
254 
255     // Update the default profile if needed
256     if (defaultChanged) {
257         Q_ASSERT(_profile != ProfileManager::instance()->fallbackProfile());
258 
259         bool defaultChecked = _generalUi->setAsDefaultButton->isChecked();
260         Profile::Ptr newDefault = defaultChecked ? _profile : ProfileManager::instance()->fallbackProfile();
261         ProfileManager::instance()->setDefaultProfile(newDefault);
262         _isDefault = defaultChecked;
263     }
264 
265     createTempProfile();
266 
267     _buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
268 }
269 
reject()270 void EditProfileDialog::reject()
271 {
272     unpreviewAll();
273     QDialog::reject();
274 }
275 
accept()276 void EditProfileDialog::accept()
277 {
278     if (isProfileNameValid()) {
279         save();
280         unpreviewAll();
281         QDialog::accept();
282     }
283 }
284 
setMessageGeneralPage(const QString & msg)285 void EditProfileDialog::setMessageGeneralPage(const QString &msg)
286 {
287     _generalUi->generalPageMessageWidget->setText(msg);
288     _generalUi->generalPageMessageWidget->setMessageType(KMessageWidget::Error);
289     setCurrentPage(_generalPageItem);
290     _generalUi->generalPageMessageWidget->animatedShow();
291 }
292 
isProfileNameValid()293 bool EditProfileDialog::isProfileNameValid()
294 {
295     Q_ASSERT(_profile);
296     Q_ASSERT(_tempProfile);
297 
298     // check whether the user has enough permissions to save the profile
299     QFileInfo fileInfo(_profile->path());
300     if (fileInfo.exists() && !fileInfo.isWritable() && (!_tempProfile->isPropertySet(Profile::Name) || _tempProfile->name() == _profile->name())) {
301         setMessageGeneralPage(xi18nc("@info",
302                                      "Insufficient permissions to save settings to: <filename>%1</filename>.<nl/>"
303                                      "Either change the permissions of that file or set a different name to save "
304                                      "the settings to a new profile.",
305                                      _profile->path()));
306         return false;
307     }
308 
309     const QList<Profile::Ptr> existingProfiles = ProfileManager::instance()->allProfiles();
310     QStringList otherExistingProfileNames;
311 
312     for (const Profile::Ptr &existingProfile : existingProfiles) {
313         if (existingProfile->name() != _profile->name()) {
314             otherExistingProfileNames.append(existingProfile->name());
315         }
316     }
317 
318     if ((_tempProfile->isPropertySet(Profile::Name) && _tempProfile->name().isEmpty()) || (_profile->name().isEmpty() && _tempProfile->name().isEmpty())) {
319         setMessageGeneralPage(i18nc("@info", "Profile Name was empty; please set a name to be able to save settings."));
320         // Revert the name in the dialog
321         _generalUi->profileNameEdit->setText(_profile->name());
322         selectProfileName();
323         return false;
324     }
325 
326     if (!_tempProfile->name().isEmpty() && otherExistingProfileNames.contains(_tempProfile->name())) {
327         setMessageGeneralPage(i18nc("@info", "A profile with the name \"%1\" already exists.", _generalUi->profileNameEdit->text()));
328         // Revert the name in the dialog
329         _generalUi->profileNameEdit->setText(_profile->name());
330         selectProfileName();
331         return false;
332     }
333 
334     // Valid name
335     return true;
336 }
337 
groupProfileNames(const ProfileGroup::Ptr & group,int maxLength)338 QString EditProfileDialog::groupProfileNames(const ProfileGroup::Ptr &group, int maxLength)
339 {
340     QString caption;
341     int count = group->profiles().count();
342     for (int i = 0; i < count; i++) {
343         caption += group->profiles()[i]->name();
344         if (i < (count - 1)) {
345             caption += QLatin1Char(',');
346             // limit caption length to prevent very long window titles
347             if (maxLength > 0 && caption.length() > maxLength) {
348                 caption += QLatin1String("...");
349                 break;
350             }
351         }
352     }
353     return caption;
354 }
355 
updateCaption(const Profile::Ptr & profile)356 void EditProfileDialog::updateCaption(const Profile::Ptr &profile)
357 {
358     const int MAX_GROUP_CAPTION_LENGTH = 25;
359     ProfileGroup::Ptr group = profile->asGroup();
360     if (group && group->profiles().count() > 1) {
361         QString caption = groupProfileNames(group, MAX_GROUP_CAPTION_LENGTH);
362         setWindowTitle(i18np("Editing profile: %2", "Editing %1 profiles: %2", group->profiles().count(), caption));
363     } else {
364         if (_profileState == EditProfileDialog::NewProfile) {
365             setWindowTitle(i18n("Create New Profile"));
366         } else {
367             setWindowTitle(i18n("Edit Profile \"%1\"", profile->name()));
368         }
369     }
370 }
371 
setProfile(const Konsole::Profile::Ptr & profile,EditProfileDialog::InitialProfileState state)372 void EditProfileDialog::setProfile(const Konsole::Profile::Ptr &profile, EditProfileDialog::InitialProfileState state)
373 {
374     Q_ASSERT(profile);
375 
376     _profile = profile;
377 
378     _profileState = state;
379 
380     // update caption
381     updateCaption(profile);
382 
383     // mark each page of the dialog as out of date
384     // and force an update of the currently visible page
385     //
386     // the other pages will be updated as necessary
387     for (Page &page : _pages) {
388         page.needsUpdate = true;
389     }
390     preparePage(currentPage());
391 
392     if (_tempProfile) {
393         createTempProfile();
394     }
395 }
396 
lookupProfile() const397 const Profile::Ptr EditProfileDialog::lookupProfile() const
398 {
399     return _profile;
400 }
401 
currentColorSchemeName() const402 const QString EditProfileDialog::currentColorSchemeName() const
403 {
404     const QString &currentColorSchemeName = lookupProfile()->colorScheme();
405     return currentColorSchemeName;
406 }
407 
preparePage(KPageWidgetItem * current,KPageWidgetItem * before)408 void EditProfileDialog::preparePage(KPageWidgetItem *current, KPageWidgetItem *before)
409 {
410     Q_UNUSED(before)
411     Q_ASSERT(current);
412     Q_ASSERT(_pages.contains(current));
413 
414     const Profile::Ptr profile = lookupProfile();
415     auto setupPage = _pages[current].setupPage;
416     Q_ASSERT(profile);
417     Q_ASSERT(setupPage);
418 
419     if (_pages[current].needsUpdate) {
420         (*this.*setupPage)(profile);
421         _pages[current].needsUpdate = false;
422     }
423 }
424 
selectProfileName()425 void Konsole::EditProfileDialog::selectProfileName()
426 {
427     _generalUi->profileNameEdit->setFocus();
428     _generalUi->profileNameEdit->selectAll();
429 }
430 
setupGeneralPage(const Profile::Ptr & profile)431 void EditProfileDialog::setupGeneralPage(const Profile::Ptr &profile)
432 {
433     _generalUi->generalPageMessageWidget->setVisible(false);
434     _generalUi->generalPageMessageWidget->setWordWrap(true);
435     _generalUi->generalPageMessageWidget->setCloseButtonVisible(true);
436 
437     // basic profile options
438     {
439         ProfileGroup::Ptr group = profile->asGroup();
440         if (!group || group->profiles().count() < 2) {
441             _generalUi->profileNameEdit->setText(profile->name());
442             _generalUi->profileNameEdit->setClearButtonEnabled(true);
443         } else {
444             _generalUi->profileNameEdit->setText(groupProfileNames(group, -1));
445             _generalUi->profileNameEdit->setEnabled(false);
446         }
447     }
448 
449     ShellCommand command(profile->command(), profile->arguments());
450     _generalUi->commandEdit->setText(command.fullCommand());
451     // If a "completion" is requested, consider changing this to KLineEdit
452     // and using KCompletion.
453     _generalUi->initialDirEdit->setText(profile->defaultWorkingDirectory());
454     _generalUi->initialDirEdit->setClearButtonEnabled(true);
455     _generalUi->initialDirEdit->setPlaceholderText(QStandardPaths::standardLocations(QStandardPaths::HomeLocation).value(0));
456 
457     _generalUi->dirSelectButton->setIcon(QIcon::fromTheme(QStringLiteral("folder-open")));
458     _generalUi->iconSelectButton->setIcon(QIcon::fromTheme(profile->icon()));
459     _generalUi->environmentEditButton->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
460     _generalUi->startInSameDirButton->setChecked(profile->startInCurrentSessionDir());
461 
462     // initial terminal size
463     const auto colsSuffix =
464         ki18ncp("Suffix of the number of columns (N columns). The leading space is needed to separate it from the number value.", " column", " columns");
465     const auto rowsSuffix =
466         ki18ncp("Suffix of the number of rows (N rows). The leading space is needed to separate it from the number value.", " row", " rows");
467     _generalUi->terminalColumnsEntry->setValue(profile->terminalColumns());
468     _generalUi->terminalRowsEntry->setValue(profile->terminalRows());
469     _generalUi->terminalColumnsEntry->setSuffix(colsSuffix);
470     _generalUi->terminalRowsEntry->setSuffix(rowsSuffix);
471     // make width of initial terminal size spinboxes equal
472     const int sizeEntryWidth = qMax(maxSpinBoxWidth(_generalUi->terminalColumnsEntry, colsSuffix), maxSpinBoxWidth(_generalUi->terminalRowsEntry, rowsSuffix));
473     _generalUi->terminalColumnsEntry->setFixedWidth(sizeEntryWidth);
474     _generalUi->terminalRowsEntry->setFixedWidth(sizeEntryWidth);
475 
476     auto *bellModeModel = new QStringListModel({i18n("System Bell"), i18n("System Notifications"), i18n("Visual Bell"), i18n("Ignore Bell Events")}, this);
477     _generalUi->terminalBellCombo->setModel(bellModeModel);
478     _generalUi->terminalBellCombo->setCurrentIndex(profile->property<int>(Profile::BellMode));
479 
480     _isDefault = profile == ProfileManager::instance()->defaultProfile();
481     _generalUi->setAsDefaultButton->setChecked(_isDefault);
482     QString appName = QCoreApplication::applicationName();
483     if (!appName.isEmpty() && appName != QLatin1String("konsole")) {
484         appName[0] = appName.at(0).toUpper();
485         _generalUi->setAsDefaultButton->setText(i18n("Default profile for new terminal sessions in %1", appName));
486     } else {
487         _generalUi->setAsDefaultButton->setText(i18n("Default profile"));
488     }
489 
490     // signals and slots
491     connect(_generalUi->dirSelectButton, &QToolButton::clicked, this, &Konsole::EditProfileDialog::selectInitialDir);
492     connect(_generalUi->iconSelectButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::selectIcon);
493     connect(_generalUi->startInSameDirButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::startInSameDir);
494     connect(_generalUi->profileNameEdit, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::profileNameChanged);
495     connect(_generalUi->initialDirEdit, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::initialDirChanged);
496     connect(_generalUi->commandEdit, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::commandChanged);
497     connect(_generalUi->environmentEditButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::showEnvironmentEditor);
498 
499     connect(_generalUi->terminalColumnsEntry, QOverload<int>::of(&QSpinBox::valueChanged), this, &Konsole::EditProfileDialog::terminalColumnsEntryChanged);
500     connect(_generalUi->terminalRowsEntry, QOverload<int>::of(&QSpinBox::valueChanged), this, &Konsole::EditProfileDialog::terminalRowsEntryChanged);
501 
502     connect(_generalUi->terminalBellCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](const int index) {
503         updateTempProfileProperty(Profile::BellMode, index);
504     });
505 
506     connect(_generalUi->setAsDefaultButton, &QAbstractButton::toggled, this, &Konsole::EditProfileDialog::updateButtonApply);
507 }
508 
showEnvironmentEditor()509 void EditProfileDialog::showEnvironmentEditor()
510 {
511     bool ok;
512     const Profile::Ptr profile = lookupProfile();
513 
514     QStringList currentEnvironment;
515 
516     // The user could re-open the environment editor before clicking
517     // OK/Apply in the parent edit profile dialog, so we make sure
518     // to show the new environment vars
519     if (_tempProfile->isPropertySet(Profile::Environment)) {
520         currentEnvironment = _tempProfile->environment();
521     } else {
522         currentEnvironment = profile->environment();
523     }
524 
525     QString text = QInputDialog::getMultiLineText(this,
526                                                   i18n("Edit Environment"),
527                                                   i18n("One environment variable per line"),
528                                                   currentEnvironment.join(QStringLiteral("\n")),
529                                                   &ok);
530 
531     QStringList newEnvironment;
532 
533     if (ok) {
534         if (!text.isEmpty()) {
535             newEnvironment = text.split(QLatin1Char('\n'));
536             updateTempProfileProperty(Profile::Environment, newEnvironment);
537         } else {
538             // the user could have removed all entries so we return an empty list
539             updateTempProfileProperty(Profile::Environment, newEnvironment);
540         }
541     }
542 }
543 
setupTabsPage(const Profile::Ptr & profile)544 void EditProfileDialog::setupTabsPage(const Profile::Ptr &profile)
545 {
546     // tab title format
547     _tabsUi->renameTabWidget->setTabTitleText(profile->localTabTitleFormat());
548     _tabsUi->renameTabWidget->setRemoteTabTitleText(profile->remoteTabTitleFormat());
549     _tabsUi->renameTabWidget->setColor(profile->tabColor());
550 
551     connect(_tabsUi->renameTabWidget, &Konsole::RenameTabWidget::tabTitleFormatChanged, this, &Konsole::EditProfileDialog::tabTitleFormatChanged);
552     connect(_tabsUi->renameTabWidget, &Konsole::RenameTabWidget::remoteTabTitleFormatChanged, this, &Konsole::EditProfileDialog::remoteTabTitleFormatChanged);
553     connect(_tabsUi->renameTabWidget, &Konsole::RenameTabWidget::tabColorChanged, this, &Konsole::EditProfileDialog::tabColorChanged);
554 
555     // tab monitoring
556     const int silenceSeconds = profile->silenceSeconds();
557     _tabsUi->silenceSecondsSpinner->setValue(silenceSeconds);
558     auto suffix = ki18ncp("Unit of time", " second", " seconds");
559     _tabsUi->silenceSecondsSpinner->setSuffix(suffix);
560     int silenceCheckBoxWidth = maxSpinBoxWidth(_generalUi->terminalColumnsEntry, suffix);
561     _tabsUi->silenceSecondsSpinner->setFixedWidth(silenceCheckBoxWidth);
562 
563     connect(_tabsUi->silenceSecondsSpinner, QOverload<int>::of(&QSpinBox::valueChanged), this, &Konsole::EditProfileDialog::silenceSecondsChanged);
564 }
565 
terminalColumnsEntryChanged(int value)566 void EditProfileDialog::terminalColumnsEntryChanged(int value)
567 {
568     updateTempProfileProperty(Profile::TerminalColumns, value);
569 }
570 
terminalRowsEntryChanged(int value)571 void EditProfileDialog::terminalRowsEntryChanged(int value)
572 {
573     updateTempProfileProperty(Profile::TerminalRows, value);
574 }
575 
showTerminalSizeHint(bool value)576 void EditProfileDialog::showTerminalSizeHint(bool value)
577 {
578     updateTempProfileProperty(Profile::ShowTerminalSizeHint, value);
579 }
580 
setDimWhenInactive(bool value)581 void EditProfileDialog::setDimWhenInactive(bool value)
582 {
583     updateTempProfileProperty(Profile::DimWhenInactive, value);
584 }
585 
setDimValue(int value)586 void EditProfileDialog::setDimValue(int value)
587 {
588     updateTempProfileProperty(Profile::DimValue, value);
589 }
590 
tabTitleFormatChanged(const QString & format)591 void EditProfileDialog::tabTitleFormatChanged(const QString &format)
592 {
593     updateTempProfileProperty(Profile::LocalTabTitleFormat, format);
594 }
595 
remoteTabTitleFormatChanged(const QString & format)596 void EditProfileDialog::remoteTabTitleFormatChanged(const QString &format)
597 {
598     updateTempProfileProperty(Profile::RemoteTabTitleFormat, format);
599 }
600 
tabColorChanged(const QColor & color)601 void EditProfileDialog::tabColorChanged(const QColor &color)
602 {
603     updateTempProfileProperty(Profile::TabColor, color);
604 }
605 
silenceSecondsChanged(int seconds)606 void EditProfileDialog::silenceSecondsChanged(int seconds)
607 {
608     updateTempProfileProperty(Profile::SilenceSeconds, seconds);
609 }
610 
selectIcon()611 void EditProfileDialog::selectIcon()
612 {
613     const QString &icon = KIconDialog::getIcon(KIconLoader::Desktop, KIconLoader::Application, false, 0, false, this);
614     if (!icon.isEmpty()) {
615         _generalUi->iconSelectButton->setIcon(QIcon::fromTheme(icon));
616         updateTempProfileProperty(Profile::Icon, icon);
617     }
618 }
619 
profileNameChanged(const QString & name)620 void EditProfileDialog::profileNameChanged(const QString &name)
621 {
622     updateTempProfileProperty(Profile::Name, name);
623     updateTempProfileProperty(Profile::UntranslatedName, name);
624     updateCaption(_tempProfile);
625 }
626 
startInSameDir(bool sameDir)627 void EditProfileDialog::startInSameDir(bool sameDir)
628 {
629     updateTempProfileProperty(Profile::StartInCurrentSessionDir, sameDir);
630 }
631 
initialDirChanged(const QString & dir)632 void EditProfileDialog::initialDirChanged(const QString &dir)
633 {
634     updateTempProfileProperty(Profile::Directory, dir);
635 }
636 
commandChanged(const QString & command)637 void EditProfileDialog::commandChanged(const QString &command)
638 {
639     ShellCommand shellCommand(command);
640 
641     updateTempProfileProperty(Profile::Command, shellCommand.command());
642     updateTempProfileProperty(Profile::Arguments, shellCommand.arguments());
643 }
644 
selectInitialDir()645 void EditProfileDialog::selectInitialDir()
646 {
647     const QUrl url = QFileDialog::getExistingDirectoryUrl(this, i18n("Select Initial Directory"), QUrl::fromUserInput(_generalUi->initialDirEdit->text()));
648 
649     if (!url.isEmpty()) {
650         _generalUi->initialDirEdit->setText(url.path());
651     }
652 }
653 
setupAppearancePage(const Profile::Ptr & profile)654 void EditProfileDialog::setupAppearancePage(const Profile::Ptr &profile)
655 {
656     auto delegate = new ColorSchemeViewDelegate(this);
657     _appearanceUi->colorSchemeList->setItemDelegate(delegate);
658 
659     _appearanceUi->transparencyWarningWidget->setVisible(false);
660     _appearanceUi->transparencyWarningWidget->setWordWrap(true);
661     _appearanceUi->transparencyWarningWidget->setCloseButtonVisible(false);
662     _appearanceUi->transparencyWarningWidget->setMessageType(KMessageWidget::Warning);
663 
664     _appearanceUi->colorSchemeMessageWidget->setVisible(false);
665     _appearanceUi->colorSchemeMessageWidget->setWordWrap(true);
666     _appearanceUi->colorSchemeMessageWidget->setCloseButtonVisible(false);
667     _appearanceUi->colorSchemeMessageWidget->setMessageType(KMessageWidget::Warning);
668 
669     _appearanceUi->editColorSchemeButton->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
670     _appearanceUi->removeColorSchemeButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
671     _appearanceUi->newColorSchemeButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
672     _appearanceUi->chooseFontButton->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font")));
673     _appearanceUi->resetColorSchemeButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo")));
674 
675     _appearanceUi->editColorSchemeButton->setEnabled(false);
676     _appearanceUi->removeColorSchemeButton->setEnabled(false);
677     _appearanceUi->resetColorSchemeButton->setEnabled(false);
678 
679     _appearanceUi->downloadColorSchemeButton->setConfigFile(QStringLiteral("konsole.knsrc"));
680 
681     // setup color list
682     // select the colorScheme used in the current profile
683     updateColorSchemeList(currentColorSchemeName());
684 
685     _appearanceUi->colorSchemeList->setMouseTracking(true);
686     _appearanceUi->colorSchemeList->installEventFilter(this);
687     _appearanceUi->colorSchemeList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
688 
689     connect(_appearanceUi->colorSchemeList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &Konsole::EditProfileDialog::colorSchemeSelected);
690     connect(_appearanceUi->colorSchemeList, &QListView::entered, this, &Konsole::EditProfileDialog::previewColorScheme);
691 
692     updateColorSchemeButtons();
693 
694     connect(_appearanceUi->editColorSchemeButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::editColorScheme);
695     connect(_appearanceUi->removeColorSchemeButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::removeColorScheme);
696     connect(_appearanceUi->newColorSchemeButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::newColorScheme);
697     connect(_appearanceUi->downloadColorSchemeButton, &KNS3::Button::dialogFinished, this, &Konsole::EditProfileDialog::gotNewColorSchemes);
698 
699     connect(_appearanceUi->resetColorSchemeButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::resetColorScheme);
700 
701     connect(_appearanceUi->chooseFontButton, &QAbstractButton::clicked, this, &EditProfileDialog::showFontDialog);
702 
703     // setup font preview
704     const bool antialias = profile->antiAliasFonts();
705 
706     QFont profileFont = profile->font();
707     profileFont.setStyleStrategy(antialias ? QFont::PreferAntialias : QFont::NoAntialias);
708 
709     _appearanceUi->fontPreview->setFont(profileFont);
710     _appearanceUi->fontPreview->setText(QStringLiteral("%1 %2pt").arg(profileFont.family()).arg(profileFont.pointSize()));
711 
712     // setup font smoothing
713     _appearanceUi->antialiasTextButton->setChecked(antialias);
714     connect(_appearanceUi->antialiasTextButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setAntialiasText);
715 
716     _appearanceUi->boldIntenseButton->setChecked(profile->boldIntense());
717     connect(_appearanceUi->boldIntenseButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setBoldIntense);
718 
719     _appearanceUi->useFontLineCharactersButton->setChecked(profile->useFontLineCharacters());
720     connect(_appearanceUi->useFontLineCharactersButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::useFontLineCharacters);
721 
722     _mouseUi->enableMouseWheelZoomButton->setChecked(profile->mouseWheelZoomEnabled());
723     connect(_mouseUi->enableMouseWheelZoomButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::toggleMouseWheelZoom);
724 
725     // cursor options
726     _appearanceUi->enableBlinkingCursorButton->setChecked(profile->property<bool>(Profile::BlinkingCursorEnabled));
727     connect(_appearanceUi->enableBlinkingCursorButton, &QToolButton::toggled, this, &EditProfileDialog::toggleBlinkingCursor);
728 
729     if (profile->useCustomCursorColor()) {
730         _appearanceUi->customCursorColorButton->setChecked(true);
731     } else {
732         _appearanceUi->autoCursorColorButton->setChecked(true);
733     }
734 
735     _appearanceUi->customColorSelectButton->setColor(profile->customCursorColor());
736     _appearanceUi->customTextColorSelectButton->setColor(profile->customCursorTextColor());
737 
738     connect(_appearanceUi->customCursorColorButton, &QRadioButton::clicked, this, &Konsole::EditProfileDialog::customCursorColor);
739     connect(_appearanceUi->autoCursorColorButton, &QRadioButton::clicked, this, &Konsole::EditProfileDialog::autoCursorColor);
740     connect(_appearanceUi->customColorSelectButton, &KColorButton::changed, this, &Konsole::EditProfileDialog::customCursorColorChanged);
741     connect(_appearanceUi->customTextColorSelectButton, &KColorButton::changed, this, &Konsole::EditProfileDialog::customCursorTextColorChanged);
742 
743     const ButtonGroupOptions cursorShapeOptions = {
744         _appearanceUi->cursorShape, // group
745         Profile::CursorShape, // profileProperty
746         true, // preview
747         {
748             // buttons
749             {_appearanceUi->cursorShapeBlock, Enum::BlockCursor},
750             {_appearanceUi->cursorShapeIBeam, Enum::IBeamCursor},
751             {_appearanceUi->cursorShapeUnderline, Enum::UnderlineCursor},
752         },
753     };
754     setupButtonGroup(cursorShapeOptions, profile);
755 
756     _appearanceUi->marginsSpinner->setValue(profile->terminalMargin());
757     connect(_appearanceUi->marginsSpinner, QOverload<int>::of(&QSpinBox::valueChanged), this, &Konsole::EditProfileDialog::terminalMarginChanged);
758 
759     _appearanceUi->lineSpacingSpinner->setValue(profile->lineSpacing());
760     connect(_appearanceUi->lineSpacingSpinner, QOverload<int>::of(&QSpinBox::valueChanged), this, &Konsole::EditProfileDialog::lineSpacingChanged);
761 
762     _appearanceUi->alignToCenterButton->setChecked(profile->terminalCenter());
763     connect(_appearanceUi->alignToCenterButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setTerminalCenter);
764 
765     _appearanceUi->showTerminalSizeHintButton->setChecked(profile->showTerminalSizeHint());
766     connect(_appearanceUi->showTerminalSizeHintButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::showTerminalSizeHint);
767 
768     _appearanceUi->dimWhenInactiveCheckbox->setChecked(profile->dimWhenInactive());
769     connect(_appearanceUi->dimWhenInactiveCheckbox, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setDimWhenInactive);
770 
771     _appearanceUi->dimValue->setValue(profile->dimValue());
772     _appearanceUi->dimValue->setEnabled(profile->dimWhenInactive());
773     _appearanceUi->dimLabel->setEnabled(profile->dimWhenInactive());
774     connect(_appearanceUi->dimValue, &QSlider::valueChanged, this, &Konsole::EditProfileDialog::setDimValue);
775 
776     _appearanceUi->invertSelectionColorsCheckbox->setChecked(profile->property<bool>(Profile::InvertSelectionColors));
777     connect(_appearanceUi->invertSelectionColorsCheckbox, &QCheckBox::toggled, this, [this](bool checked) {
778         updateTempProfileProperty(Profile::InvertSelectionColors, checked);
779     });
780 
781     _appearanceUi->displayVerticalLine->setChecked(profile->verticalLine());
782     connect(_appearanceUi->displayVerticalLine, &QCheckBox::toggled, this, &EditProfileDialog::setVerticalLine);
783 
784     _appearanceUi->displayVerticalLineAtColumn->setValue(profile->verticalLineAtChar());
785     connect(_appearanceUi->displayVerticalLineAtColumn, QOverload<int>::of(&QSpinBox::valueChanged), this, &EditProfileDialog::setVerticalLineColumn);
786 }
787 
setAntialiasText(bool enable)788 void EditProfileDialog::setAntialiasText(bool enable)
789 {
790     preview(Profile::AntiAliasFonts, enable);
791     updateTempProfileProperty(Profile::AntiAliasFonts, enable);
792 
793     const QFont font = _profile->font();
794     updateFontPreview(font);
795 }
796 
toggleAllowLinkEscapeSequence(bool enable)797 void EditProfileDialog::toggleAllowLinkEscapeSequence(bool enable)
798 {
799     updateTempProfileProperty(Profile::AllowEscapedLinks, enable);
800 }
801 
linkEscapeSequenceTextsChanged()802 void EditProfileDialog::linkEscapeSequenceTextsChanged()
803 {
804     updateTempProfileProperty(Profile::EscapedLinksSchema, _mouseUi->linkEscapeSequenceTexts->text());
805 }
setVerticalLine(bool value)806 void EditProfileDialog::setVerticalLine(bool value)
807 {
808     updateTempProfileProperty(Profile::VerticalLine, value);
809 }
810 
setVerticalLineColumn(int value)811 void EditProfileDialog::setVerticalLineColumn(int value)
812 {
813     updateTempProfileProperty(Profile::VerticalLineAtChar, value);
814 }
815 
setBoldIntense(bool enable)816 void EditProfileDialog::setBoldIntense(bool enable)
817 {
818     preview(Profile::BoldIntense, enable);
819     updateTempProfileProperty(Profile::BoldIntense, enable);
820 }
821 
useFontLineCharacters(bool enable)822 void EditProfileDialog::useFontLineCharacters(bool enable)
823 {
824     preview(Profile::UseFontLineCharacters, enable);
825     updateTempProfileProperty(Profile::UseFontLineCharacters, enable);
826 }
827 
toggleBlinkingCursor(bool enable)828 void EditProfileDialog::toggleBlinkingCursor(bool enable)
829 {
830     preview(Profile::BlinkingCursorEnabled, enable);
831     updateTempProfileProperty(Profile::BlinkingCursorEnabled, enable);
832 }
833 
setCursorShape(int index)834 void EditProfileDialog::setCursorShape(int index)
835 {
836     preview(Profile::CursorShape, index);
837     updateTempProfileProperty(Profile::CursorShape, index);
838 }
839 
autoCursorColor()840 void EditProfileDialog::autoCursorColor()
841 {
842     preview(Profile::UseCustomCursorColor, false);
843     updateTempProfileProperty(Profile::UseCustomCursorColor, false);
844 }
845 
customCursorColor()846 void EditProfileDialog::customCursorColor()
847 {
848     preview(Profile::UseCustomCursorColor, true);
849     updateTempProfileProperty(Profile::UseCustomCursorColor, true);
850 }
851 
customCursorColorChanged(const QColor & color)852 void EditProfileDialog::customCursorColorChanged(const QColor &color)
853 {
854     preview(Profile::CustomCursorColor, color);
855     updateTempProfileProperty(Profile::CustomCursorColor, color);
856 
857     // ensure that custom cursor colors are enabled
858     _appearanceUi->customCursorColorButton->click();
859 }
860 
customCursorTextColorChanged(const QColor & color)861 void EditProfileDialog::customCursorTextColorChanged(const QColor &color)
862 {
863     preview(Profile::CustomCursorTextColor, color);
864     updateTempProfileProperty(Profile::CustomCursorTextColor, color);
865 
866     // ensure that custom cursor colors are enabled
867     _appearanceUi->customCursorColorButton->click();
868 }
869 
terminalMarginChanged(int margin)870 void EditProfileDialog::terminalMarginChanged(int margin)
871 {
872     preview(Profile::TerminalMargin, margin);
873     updateTempProfileProperty(Profile::TerminalMargin, margin);
874 }
875 
lineSpacingChanged(int spacing)876 void EditProfileDialog::lineSpacingChanged(int spacing)
877 {
878     preview(Profile::LineSpacing, spacing);
879     updateTempProfileProperty(Profile::LineSpacing, spacing);
880 }
881 
setTerminalCenter(bool enable)882 void EditProfileDialog::setTerminalCenter(bool enable)
883 {
884     preview(Profile::TerminalCenter, enable);
885     updateTempProfileProperty(Profile::TerminalCenter, enable);
886 }
887 
toggleMouseWheelZoom(bool enable)888 void EditProfileDialog::toggleMouseWheelZoom(bool enable)
889 {
890     updateTempProfileProperty(Profile::MouseWheelZoomEnabled, enable);
891 }
892 
toggleAlternateScrolling(bool enable)893 void EditProfileDialog::toggleAlternateScrolling(bool enable)
894 {
895     updateTempProfileProperty(Profile::AlternateScrolling, enable);
896 }
897 
toggleAllowColorFilter(bool enable)898 void EditProfileDialog::toggleAllowColorFilter(bool enable)
899 {
900     updateTempProfileProperty(Profile::ColorFilterEnabled, enable);
901 }
902 
updateColorSchemeList(const QString & selectedColorSchemeName)903 void EditProfileDialog::updateColorSchemeList(const QString &selectedColorSchemeName)
904 {
905     if (_appearanceUi->colorSchemeList->model() == nullptr) {
906         _appearanceUi->colorSchemeList->setModel(new QStandardItemModel(this));
907     }
908 
909     const ColorScheme *selectedColorScheme = ColorSchemeManager::instance()->findColorScheme(selectedColorSchemeName);
910 
911     auto *model = qobject_cast<QStandardItemModel *>(_appearanceUi->colorSchemeList->model());
912 
913     Q_ASSERT(model);
914 
915     model->clear();
916 
917     QStandardItem *selectedItem = nullptr;
918 
919     const QList<const ColorScheme *> schemeList = ColorSchemeManager::instance()->allColorSchemes();
920 
921     for (const ColorScheme *scheme : schemeList) {
922         QStandardItem *item = new QStandardItem(scheme->description());
923         item->setData(QVariant::fromValue(scheme), Qt::UserRole + 1);
924         item->setData(QVariant::fromValue(_profile->font()), Qt::UserRole + 2);
925         item->setFlags(item->flags());
926 
927         // if selectedColorSchemeName is not empty then select that scheme
928         // after saving the changes in the colorScheme editor
929         if (selectedColorScheme == scheme) {
930             selectedItem = item;
931         }
932 
933         model->appendRow(item);
934     }
935 
936     model->sort(0);
937 
938     if (selectedItem != nullptr) {
939         _appearanceUi->colorSchemeList->updateGeometry();
940         _appearanceUi->colorSchemeList->selectionModel()->setCurrentIndex(selectedItem->index(), QItemSelectionModel::Select);
941 
942         // update transparency warning label
943         updateTransparencyWarning();
944     }
945 }
946 
updateKeyBindingsList(const QString & selectKeyBindingsName)947 void EditProfileDialog::updateKeyBindingsList(const QString &selectKeyBindingsName)
948 {
949     if (_keyboardUi->keyBindingList->model() == nullptr) {
950         _keyboardUi->keyBindingList->setModel(new QStandardItemModel(this));
951     }
952 
953     auto *model = qobject_cast<QStandardItemModel *>(_keyboardUi->keyBindingList->model());
954 
955     Q_ASSERT(model);
956 
957     model->clear();
958 
959     QStandardItem *selectedItem = nullptr;
960 
961     const QStringList &translatorNames = _keyManager->allTranslators();
962     for (const QString &translatorName : translatorNames) {
963         const KeyboardTranslator *translator = _keyManager->findTranslator(translatorName);
964         if (translator == nullptr) {
965             continue;
966         }
967 
968         QStandardItem *item = new QStandardItem(translator->description());
969         item->setEditable(false);
970         item->setData(QVariant::fromValue(translator), Qt::UserRole + 1);
971         item->setData(QVariant::fromValue(_keyManager->findTranslatorPath(translatorName)), Qt::ToolTipRole);
972         item->setData(QVariant::fromValue(_profile->font()), Qt::UserRole + 2);
973         item->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-keyboard")));
974 
975         if (selectKeyBindingsName == translatorName) {
976             selectedItem = item;
977         }
978 
979         model->appendRow(item);
980     }
981 
982     model->sort(0);
983 
984     if (selectedItem != nullptr) {
985         _keyboardUi->keyBindingList->selectionModel()->setCurrentIndex(selectedItem->index(), QItemSelectionModel::Select);
986     }
987 }
988 
eventFilter(QObject * watched,QEvent * event)989 bool EditProfileDialog::eventFilter(QObject *watched, QEvent *event)
990 {
991     if (watched == _appearanceUi->colorSchemeList && event->type() == QEvent::Leave) {
992         if (_tempProfile->isPropertySet(Profile::ColorScheme)) {
993             preview(Profile::ColorScheme, _tempProfile->colorScheme());
994         } else {
995             unpreview(Profile::ColorScheme);
996         }
997     }
998 
999     return QDialog::eventFilter(watched, event);
1000 }
1001 
sizeHint() const1002 QSize EditProfileDialog::sizeHint() const
1003 {
1004     QFontMetrics fm(font());
1005     const int ch = fm.boundingRect(QLatin1Char('0')).width();
1006 
1007     // By default minimum size is used. Increase it to make text inputs
1008     // on "tabs" page wider and to add some whitespace on right side
1009     // of other pages. The window will not be wider than 2/3 of
1010     // the screen width (unless necessary to fit everything)
1011     return QDialog::sizeHint() + QSize(10 * ch, 0);
1012 }
1013 
unpreviewAll()1014 void EditProfileDialog::unpreviewAll()
1015 {
1016     _delayedPreviewTimer->stop();
1017     _delayedPreviewProperties.clear();
1018 
1019     QHash<Profile::Property, QVariant> map;
1020     QHashIterator<int, QVariant> iter(_previewedProperties);
1021     while (iter.hasNext()) {
1022         iter.next();
1023         map.insert(static_cast<Profile::Property>(iter.key()), iter.value());
1024     }
1025 
1026     // undo any preview changes
1027     if (!map.isEmpty()) {
1028         ProfileManager::instance()->changeProfile(_profile, map, false);
1029     }
1030 }
1031 
unpreview(int property)1032 void EditProfileDialog::unpreview(int property)
1033 {
1034     _delayedPreviewProperties.remove(property);
1035 
1036     if (!_previewedProperties.contains(property)) {
1037         return;
1038     }
1039 
1040     QHash<Profile::Property, QVariant> map;
1041     map.insert(static_cast<Profile::Property>(property), _previewedProperties[property]);
1042     ProfileManager::instance()->changeProfile(_profile, map, false);
1043 
1044     _previewedProperties.remove(property);
1045 }
1046 
delayedPreview(int property,const QVariant & value)1047 void EditProfileDialog::delayedPreview(int property, const QVariant &value)
1048 {
1049     _delayedPreviewProperties.insert(property, value);
1050 
1051     _delayedPreviewTimer->stop();
1052     _delayedPreviewTimer->start(300);
1053 }
1054 
delayedPreviewActivate()1055 void EditProfileDialog::delayedPreviewActivate()
1056 {
1057     Q_ASSERT(qobject_cast<QTimer *>(sender()));
1058 
1059     QMutableHashIterator<int, QVariant> iter(_delayedPreviewProperties);
1060     if (iter.hasNext()) {
1061         iter.next();
1062         preview(iter.key(), iter.value());
1063     }
1064 }
1065 
preview(int property,const QVariant & value)1066 void EditProfileDialog::preview(int property, const QVariant &value)
1067 {
1068     QHash<Profile::Property, QVariant> map;
1069     map.insert(static_cast<Profile::Property>(property), value);
1070 
1071     _delayedPreviewProperties.remove(property);
1072 
1073     const Profile::Ptr original = lookupProfile();
1074 
1075     // skip previews for profile groups if the profiles in the group
1076     // have conflicting original values for the property
1077     //
1078     // TODO - Save the original values for each profile and use to unpreview properties
1079     ProfileGroup::Ptr group = original->asGroup();
1080     if (group && group->profiles().count() > 1 && original->property<QVariant>(static_cast<Profile::Property>(property)).isNull()) {
1081         return;
1082     }
1083 
1084     if (!_previewedProperties.contains(property)) {
1085         _previewedProperties.insert(property, original->property<QVariant>(static_cast<Profile::Property>(property)));
1086     }
1087 
1088     // temporary change to color scheme
1089     ProfileManager::instance()->changeProfile(_profile, map, false);
1090 }
1091 
previewColorScheme(const QModelIndex & index)1092 void EditProfileDialog::previewColorScheme(const QModelIndex &index)
1093 {
1094     const QString &name = index.data(Qt::UserRole + 1).value<const ColorScheme *>()->name();
1095     delayedPreview(Profile::ColorScheme, name);
1096 }
1097 
showFontDialog()1098 void EditProfileDialog::showFontDialog()
1099 {
1100     if (_fontDialog == nullptr) {
1101         _fontDialog = new FontDialog(this);
1102         _fontDialog->setModal(true);
1103         connect(_fontDialog, &FontDialog::fontChanged, this, [this](const QFont &font) {
1104             preview(Profile::Font, font);
1105             updateFontPreview(font);
1106         });
1107         connect(_fontDialog, &FontDialog::accepted, this, [this]() {
1108             const QFont font = _fontDialog->font();
1109             preview(Profile::Font, font);
1110             updateTempProfileProperty(Profile::Font, font);
1111             updateFontPreview(font);
1112         });
1113         connect(_fontDialog, &FontDialog::rejected, this, [this]() {
1114             unpreview(Profile::Font);
1115             updateFontPreview(_profile->font());
1116         });
1117     }
1118 
1119     _fontDialog->setFont(_profile->font());
1120     _fontDialog->show();
1121 }
1122 
updateFontPreview(QFont font)1123 void EditProfileDialog::updateFontPreview(QFont font)
1124 {
1125     bool aa = _profile->antiAliasFonts();
1126     font.setStyleStrategy(aa ? QFont::PreferAntialias : QFont::NoAntialias);
1127 
1128     _appearanceUi->fontPreview->setFont(font);
1129     _appearanceUi->fontPreview->setText(QStringLiteral("%1 %2pt").arg(font.family()).arg(font.pointSize()));
1130 }
1131 
removeColorScheme()1132 void EditProfileDialog::removeColorScheme()
1133 {
1134     const QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes();
1135     if (selected.isEmpty()) {
1136         return;
1137     }
1138     const QString &name = selected.first().data(Qt::UserRole + 1).value<const ColorScheme *>()->name();
1139     Q_ASSERT(!name.isEmpty());
1140     if (ColorSchemeManager::instance()->deleteColorScheme(name)) {
1141         _appearanceUi->colorSchemeList->model()->removeRow(selected.first().row());
1142     }
1143 }
1144 
gotNewColorSchemes(const KNS3::Entry::List & changedEntries)1145 void EditProfileDialog::gotNewColorSchemes(const KNS3::Entry::List &changedEntries)
1146 {
1147     int failures = 0;
1148     for (auto &entry : qAsConst(changedEntries)) {
1149         switch (entry.status()) {
1150         case KNS3::Entry::Installed:
1151             for (const auto &file : entry.installedFiles()) {
1152                 if (ColorSchemeManager::instance()->loadColorScheme(file)) {
1153                     continue;
1154                 }
1155                 qWarning() << "Failed to load file" << file;
1156                 ++failures;
1157             }
1158             if (failures == entry.installedFiles().size()) {
1159                 _appearanceUi->colorSchemeMessageWidget->setText(xi18nc("@info", "Scheme <resource>%1</resource> failed to load.", entry.name()));
1160                 _appearanceUi->colorSchemeMessageWidget->animatedShow();
1161                 QTimer::singleShot(8000, _appearanceUi->colorSchemeMessageWidget, &KMessageWidget::animatedHide);
1162             }
1163             break;
1164         case KNS3::Entry::Deleted:
1165             for (const auto &file : entry.uninstalledFiles()) {
1166                 if (ColorSchemeManager::instance()->unloadColorScheme(file)) {
1167                     continue;
1168                 }
1169                 qWarning() << "Failed to unload file" << file;
1170                 // If unloading fails we do not care. If the scheme failed here
1171                 // it either wasn't loaded or was invalid to begin with.
1172             }
1173             break;
1174         case KNS3::Entry::Invalid:
1175         case KNS3::Entry::Installing:
1176         case KNS3::Entry::Downloadable:
1177         case KNS3::Entry::Updateable:
1178         case KNS3::Entry::Updating:
1179             // Not interesting.
1180             break;
1181         }
1182     }
1183     updateColorSchemeList(currentColorSchemeName());
1184 }
1185 
resetColorScheme()1186 void EditProfileDialog::resetColorScheme()
1187 {
1188     QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes();
1189 
1190     if (!selected.isEmpty()) {
1191         const QString &name = selected.first().data(Qt::UserRole + 1).value<const ColorScheme *>()->name();
1192 
1193         ColorSchemeManager::instance()->deleteColorScheme(name);
1194 
1195         // select the colorScheme used in the current profile
1196         updateColorSchemeList(currentColorSchemeName());
1197     }
1198 }
1199 
showColorSchemeEditor(bool isNewScheme)1200 void EditProfileDialog::showColorSchemeEditor(bool isNewScheme)
1201 {
1202     // Finding selected ColorScheme
1203     QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes();
1204     QAbstractItemModel *model = _appearanceUi->colorSchemeList->model();
1205     const ColorScheme *colors = nullptr;
1206     if (!selected.isEmpty()) {
1207         colors = model->data(selected.first(), Qt::UserRole + 1).value<const ColorScheme *>();
1208     } else {
1209         colors = ColorSchemeManager::instance()->defaultColorScheme();
1210     }
1211 
1212     Q_ASSERT(colors);
1213 
1214     // Setting up ColorSchemeEditor ui
1215     // close any running ColorSchemeEditor
1216     if (_colorDialog != nullptr) {
1217         closeColorSchemeEditor();
1218     }
1219     _colorDialog = new ColorSchemeEditor(this);
1220 
1221     connect(_colorDialog, &Konsole::ColorSchemeEditor::colorSchemeSaveRequested, this, &Konsole::EditProfileDialog::saveColorScheme);
1222     _colorDialog->setup(colors, isNewScheme);
1223 
1224     _colorDialog->show();
1225 }
1226 
closeColorSchemeEditor()1227 void EditProfileDialog::closeColorSchemeEditor()
1228 {
1229     if (_colorDialog != nullptr) {
1230         _colorDialog->close();
1231         delete _colorDialog;
1232     }
1233 }
1234 
newColorScheme()1235 void EditProfileDialog::newColorScheme()
1236 {
1237     showColorSchemeEditor(true);
1238 }
1239 
editColorScheme()1240 void EditProfileDialog::editColorScheme()
1241 {
1242     showColorSchemeEditor(false);
1243 }
1244 
saveColorScheme(const ColorScheme & scheme,bool isNewScheme)1245 void EditProfileDialog::saveColorScheme(const ColorScheme &scheme, bool isNewScheme)
1246 {
1247     auto newScheme = new ColorScheme(scheme);
1248 
1249     // if this is a new color scheme, pick a name based on the description
1250     if (isNewScheme) {
1251         newScheme->setName(newScheme->description());
1252     }
1253 
1254     ColorSchemeManager::instance()->addColorScheme(newScheme);
1255 
1256     const QString &selectedColorSchemeName = newScheme->name();
1257 
1258     // select the edited or the new colorScheme after saving the changes
1259     updateColorSchemeList(selectedColorSchemeName);
1260 
1261     preview(Profile::ColorScheme, newScheme->name());
1262 }
1263 
colorSchemeSelected()1264 void EditProfileDialog::colorSchemeSelected()
1265 {
1266     QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes();
1267 
1268     if (!selected.isEmpty()) {
1269         QAbstractItemModel *model = _appearanceUi->colorSchemeList->model();
1270         const auto *colors = model->data(selected.first(), Qt::UserRole + 1).value<const ColorScheme *>();
1271         if (colors != nullptr) {
1272             updateTempProfileProperty(Profile::ColorScheme, colors->name());
1273             previewColorScheme(selected.first());
1274 
1275             updateTransparencyWarning();
1276         }
1277     }
1278 
1279     updateColorSchemeButtons();
1280 }
1281 
updateColorSchemeButtons()1282 void EditProfileDialog::updateColorSchemeButtons()
1283 {
1284     enableIfNonEmptySelection(_appearanceUi->editColorSchemeButton, _appearanceUi->colorSchemeList->selectionModel());
1285 
1286     QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes();
1287 
1288     if (!selected.isEmpty()) {
1289         const QString &name = selected.first().data(Qt::UserRole + 1).value<const ColorScheme *>()->name();
1290 
1291         bool isResettable = ColorSchemeManager::instance()->canResetColorScheme(name);
1292         _appearanceUi->resetColorSchemeButton->setEnabled(isResettable);
1293 
1294         bool isDeletable = ColorSchemeManager::instance()->isColorSchemeDeletable(name);
1295         // if a colorScheme can be restored then it can't be deleted
1296         _appearanceUi->removeColorSchemeButton->setEnabled(isDeletable && !isResettable);
1297     } else {
1298         _appearanceUi->removeColorSchemeButton->setEnabled(false);
1299         _appearanceUi->resetColorSchemeButton->setEnabled(false);
1300     }
1301 }
1302 
updateKeyBindingsButtons()1303 void EditProfileDialog::updateKeyBindingsButtons()
1304 {
1305     QModelIndexList selected = _keyboardUi->keyBindingList->selectionModel()->selectedIndexes();
1306 
1307     if (!selected.isEmpty()) {
1308         _keyboardUi->editKeyBindingsButton->setEnabled(true);
1309 
1310         const QString &name = selected.first().data(Qt::UserRole + 1).value<const KeyboardTranslator *>()->name();
1311 
1312         bool isResettable = _keyManager->isTranslatorResettable(name);
1313         _keyboardUi->resetKeyBindingsButton->setEnabled(isResettable);
1314 
1315         bool isDeletable = _keyManager->isTranslatorDeletable(name);
1316 
1317         // if a key bindings scheme can be reset then it can't be deleted
1318         _keyboardUi->removeKeyBindingsButton->setEnabled(isDeletable && !isResettable);
1319     }
1320 }
1321 
enableIfNonEmptySelection(QWidget * widget,QItemSelectionModel * selectionModel)1322 void EditProfileDialog::enableIfNonEmptySelection(QWidget *widget, QItemSelectionModel *selectionModel)
1323 {
1324     widget->setEnabled(selectionModel->hasSelection());
1325 }
1326 
updateTransparencyWarning()1327 void EditProfileDialog::updateTransparencyWarning()
1328 {
1329     // zero or one indexes can be selected
1330     const QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes();
1331     for (const QModelIndex &index : selected) {
1332         bool needTransparency = index.data(Qt::UserRole + 1).value<const ColorScheme *>()->opacity() < 1.0;
1333 
1334         if (!needTransparency) {
1335             _appearanceUi->transparencyWarningWidget->setHidden(true);
1336         } else if (!KWindowSystem::compositingActive()) {
1337             _appearanceUi->transparencyWarningWidget->setText(
1338                 i18n("This color scheme uses a transparent background"
1339                      " which does not appear to be supported on your"
1340                      " desktop"));
1341             _appearanceUi->transparencyWarningWidget->setHidden(false);
1342         } else if (!WindowSystemInfo::HAVE_TRANSPARENCY) {
1343             _appearanceUi->transparencyWarningWidget->setText(
1344                 i18n("Konsole was started before desktop effects were enabled."
1345                      " You need to restart Konsole to see transparent background."));
1346             _appearanceUi->transparencyWarningWidget->setHidden(false);
1347         }
1348     }
1349 }
1350 
createTempProfile()1351 void EditProfileDialog::createTempProfile()
1352 {
1353     _tempProfile = Profile::Ptr(new Profile);
1354     _tempProfile->setHidden(true);
1355 }
1356 
updateTempProfileProperty(Profile::Property property,const QVariant & value)1357 void EditProfileDialog::updateTempProfileProperty(Profile::Property property, const QVariant &value)
1358 {
1359     _tempProfile->setProperty(property, value);
1360     updateButtonApply();
1361 }
1362 
updateButtonApply()1363 void EditProfileDialog::updateButtonApply()
1364 {
1365     bool userModified = false;
1366 
1367     QHashIterator<Profile::Property, QVariant> iter(_tempProfile->setProperties());
1368     while (iter.hasNext()) {
1369         iter.next();
1370 
1371         Profile::Property property = iter.key();
1372         QVariant value = iter.value();
1373 
1374         // for previewed property
1375         if (_previewedProperties.contains(static_cast<int>(property))) {
1376             if (value != _previewedProperties.value(static_cast<int>(property))) {
1377                 userModified = true;
1378                 break;
1379             }
1380             // for not-previewed property
1381             //
1382             // for the Profile::KeyBindings property, if it's set in the _tempProfile
1383             // then the user opened the edit key bindings dialog and clicked
1384             // OK, and could have add/removed a key bindings rule
1385         } else if (property == Profile::KeyBindings || (value != _profile->property<QVariant>(property))) {
1386             userModified = true;
1387             break;
1388         }
1389     }
1390 
1391     if (_generalUi->setAsDefaultButton->isChecked() != _isDefault) {
1392         userModified = true;
1393     }
1394 
1395     _buttonBox->button(QDialogButtonBox::Apply)->setEnabled(userModified);
1396 }
1397 
setupKeyboardPage(const Profile::Ptr &)1398 void EditProfileDialog::setupKeyboardPage(const Profile::Ptr & /* profile */)
1399 {
1400     // setup translator list
1401     updateKeyBindingsList(lookupProfile()->keyBindings());
1402 
1403     connect(_keyboardUi->keyBindingList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &Konsole::EditProfileDialog::keyBindingSelected);
1404     connect(_keyboardUi->newKeyBindingsButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::newKeyBinding);
1405 
1406     _keyboardUi->editKeyBindingsButton->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
1407     _keyboardUi->removeKeyBindingsButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
1408     _keyboardUi->newKeyBindingsButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
1409     _keyboardUi->resetKeyBindingsButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo")));
1410 
1411     _keyboardUi->editKeyBindingsButton->setEnabled(false);
1412     _keyboardUi->removeKeyBindingsButton->setEnabled(false);
1413     _keyboardUi->resetKeyBindingsButton->setEnabled(false);
1414 
1415     updateKeyBindingsButtons();
1416 
1417     connect(_keyboardUi->editKeyBindingsButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::editKeyBinding);
1418     connect(_keyboardUi->removeKeyBindingsButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::removeKeyBinding);
1419     connect(_keyboardUi->resetKeyBindingsButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::resetKeyBindings);
1420 }
1421 
keyBindingSelected()1422 void EditProfileDialog::keyBindingSelected()
1423 {
1424     QModelIndexList selected = _keyboardUi->keyBindingList->selectionModel()->selectedIndexes();
1425 
1426     if (!selected.isEmpty()) {
1427         QAbstractItemModel *model = _keyboardUi->keyBindingList->model();
1428         const auto *translator = model->data(selected.first(), Qt::UserRole + 1).value<const KeyboardTranslator *>();
1429         if (translator != nullptr) {
1430             updateTempProfileProperty(Profile::KeyBindings, translator->name());
1431         }
1432     }
1433 
1434     updateKeyBindingsButtons();
1435 }
1436 
removeKeyBinding()1437 void EditProfileDialog::removeKeyBinding()
1438 {
1439     QModelIndexList selected = _keyboardUi->keyBindingList->selectionModel()->selectedIndexes();
1440 
1441     if (!selected.isEmpty()) {
1442         const QString &name = selected.first().data(Qt::UserRole + 1).value<const KeyboardTranslator *>()->name();
1443         if (KeyboardTranslatorManager::instance()->deleteTranslator(name)) {
1444             _keyboardUi->keyBindingList->model()->removeRow(selected.first().row());
1445         }
1446     }
1447 }
1448 
showKeyBindingEditor(bool isNewTranslator)1449 void EditProfileDialog::showKeyBindingEditor(bool isNewTranslator)
1450 {
1451     QModelIndexList selected = _keyboardUi->keyBindingList->selectionModel()->selectedIndexes();
1452     QAbstractItemModel *model = _keyboardUi->keyBindingList->model();
1453 
1454     const KeyboardTranslator *translator = nullptr;
1455     if (!selected.isEmpty()) {
1456         translator = model->data(selected.first(), Qt::UserRole + 1).value<const KeyboardTranslator *>();
1457     } else {
1458         translator = _keyManager->defaultTranslator();
1459     }
1460 
1461     auto *editor = new KeyBindingEditor(this);
1462     editor->setAttribute(Qt::WA_DeleteOnClose);
1463     editor->setModal(true);
1464 
1465     if (translator != nullptr) {
1466         editor->setup(translator, lookupProfile()->keyBindings(), isNewTranslator);
1467     }
1468 
1469     connect(editor, &Konsole::KeyBindingEditor::updateKeyBindingsListRequest, this, &Konsole::EditProfileDialog::updateKeyBindingsList);
1470     connect(editor, &Konsole::KeyBindingEditor::updateTempProfileKeyBindingsRequest, this, &Konsole::EditProfileDialog::updateTempProfileProperty);
1471 
1472     editor->show();
1473 }
1474 
newKeyBinding()1475 void EditProfileDialog::newKeyBinding()
1476 {
1477     showKeyBindingEditor(true);
1478 }
1479 
editKeyBinding()1480 void EditProfileDialog::editKeyBinding()
1481 {
1482     showKeyBindingEditor(false);
1483 }
1484 
resetKeyBindings()1485 void EditProfileDialog::resetKeyBindings()
1486 {
1487     QModelIndexList selected = _keyboardUi->keyBindingList->selectionModel()->selectedIndexes();
1488 
1489     if (!selected.isEmpty()) {
1490         const QString &name = selected.first().data(Qt::UserRole + 1).value<const KeyboardTranslator *>()->name();
1491 
1492         _keyManager->deleteTranslator(name);
1493         // find and load the translator
1494         _keyManager->findTranslator(name);
1495 
1496         updateKeyBindingsList(name);
1497     }
1498 }
1499 
setupButtonGroup(const ButtonGroupOptions & options,const Profile::Ptr & profile)1500 void EditProfileDialog::setupButtonGroup(const ButtonGroupOptions &options, const Profile::Ptr &profile)
1501 {
1502     auto currentValue = profile->property<int>(options.profileProperty);
1503 
1504     for (auto option : options.buttons) {
1505         options.group->setId(option.button, option.value);
1506     }
1507 
1508     Q_ASSERT(options.buttons.count() > 0);
1509     auto *activeButton = options.group->button(currentValue);
1510     if (activeButton == nullptr) {
1511         activeButton = options.buttons[0].button;
1512     }
1513     activeButton->setChecked(true);
1514     connect(options.group, QOverload<int>::of(&QButtonGroup::idClicked), this, [this, options](int value) {
1515         if (options.preview) {
1516             preview(options.profileProperty, value);
1517         }
1518         updateTempProfileProperty(options.profileProperty, value);
1519     });
1520 }
1521 
setupScrollingPage(const Profile::Ptr & profile)1522 void EditProfileDialog::setupScrollingPage(const Profile::Ptr &profile)
1523 {
1524     // setup scrollbar radio
1525     const ButtonGroupOptions scrollBarPositionOptions = {
1526         _scrollingUi->scrollBarPosition, // group
1527         Profile::ScrollBarPosition, // profileProperty
1528         false, // preview
1529         {
1530             // buttons
1531             {_scrollingUi->scrollBarRightButton, Enum::ScrollBarRight},
1532             {_scrollingUi->scrollBarLeftButton, Enum::ScrollBarLeft},
1533             {_scrollingUi->scrollBarHiddenButton, Enum::ScrollBarHidden},
1534         },
1535     };
1536     setupButtonGroup(scrollBarPositionOptions, profile);
1537 
1538     // setup scrollback type radio
1539     auto scrollBackType = profile->property<int>(Profile::HistoryMode);
1540     _scrollingUi->historySizeWidget->setMode(Enum::HistoryModeEnum(scrollBackType));
1541     connect(_scrollingUi->historySizeWidget, &Konsole::HistorySizeWidget::historyModeChanged, this, &Konsole::EditProfileDialog::historyModeChanged);
1542 
1543     // setup scrollback line count spinner
1544     const int historySize = profile->historySize();
1545     _scrollingUi->historySizeWidget->setLineCount(historySize);
1546 
1547     // setup scrollpageamount type radio
1548     auto scrollFullPage = profile->property<int>(Profile::ScrollFullPage);
1549 
1550     _scrollingUi->scrollHalfPage->setChecked(Enum::ScrollPageHalf == scrollFullPage);
1551     connect(_scrollingUi->scrollHalfPage, &QPushButton::clicked, this, &EditProfileDialog::scrollFullPage);
1552 
1553     _scrollingUi->scrollFullPage->setChecked(Enum::ScrollPageFull == scrollFullPage);
1554     connect(_scrollingUi->scrollFullPage, &QPushButton::clicked, this, &EditProfileDialog::scrollFullPage);
1555 
1556     _scrollingUi->highlightScrolledLinesButton->setChecked(profile->property<bool>(Profile::HighlightScrolledLines));
1557     connect(_scrollingUi->highlightScrolledLinesButton, &QPushButton::clicked, this, &EditProfileDialog::toggleHighlightScrolledLines);
1558 
1559     _scrollingUi->reflowLinesButton->setChecked(profile->property<bool>(Profile::ReflowLines));
1560     connect(_scrollingUi->reflowLinesButton, &QPushButton::clicked, this, &EditProfileDialog::toggleReflowLines);
1561 
1562     // signals and slots
1563     connect(_scrollingUi->historySizeWidget, &Konsole::HistorySizeWidget::historySizeChanged, this, &Konsole::EditProfileDialog::historySizeChanged);
1564 }
1565 
historySizeChanged(int lineCount)1566 void EditProfileDialog::historySizeChanged(int lineCount)
1567 {
1568     updateTempProfileProperty(Profile::HistorySize, lineCount);
1569 }
1570 
historyModeChanged(Enum::HistoryModeEnum mode)1571 void EditProfileDialog::historyModeChanged(Enum::HistoryModeEnum mode)
1572 {
1573     updateTempProfileProperty(Profile::HistoryMode, mode);
1574 }
1575 
scrollFullPage()1576 void EditProfileDialog::scrollFullPage()
1577 {
1578     updateTempProfileProperty(Profile::ScrollFullPage, Enum::ScrollPageFull);
1579 }
1580 
scrollHalfPage()1581 void EditProfileDialog::scrollHalfPage()
1582 {
1583     updateTempProfileProperty(Profile::ScrollFullPage, Enum::ScrollPageHalf);
1584 }
1585 
toggleHighlightScrolledLines(bool enable)1586 void EditProfileDialog::toggleHighlightScrolledLines(bool enable)
1587 {
1588     updateTempProfileProperty(Profile::HighlightScrolledLines, enable);
1589 }
1590 
toggleReflowLines(bool enable)1591 void EditProfileDialog::toggleReflowLines(bool enable)
1592 {
1593     updateTempProfileProperty(Profile::ReflowLines, enable);
1594 }
1595 
setupMousePage(const Profile::Ptr & profile)1596 void EditProfileDialog::setupMousePage(const Profile::Ptr &profile)
1597 {
1598     _mouseUi->underlineLinksButton->setChecked(profile->property<bool>(Profile::UnderlineLinksEnabled));
1599     connect(_mouseUi->underlineLinksButton, &QPushButton::toggled, this, &EditProfileDialog::toggleUnderlineLinks);
1600     _mouseUi->underlineFilesButton->setChecked(profile->property<bool>(Profile::UnderlineFilesEnabled));
1601     connect(_mouseUi->underlineFilesButton, &QPushButton::toggled, this, &EditProfileDialog::toggleUnderlineFiles);
1602     _mouseUi->ctrlRequiredForDragButton->setChecked(profile->property<bool>(Profile::CtrlRequiredForDrag));
1603     connect(_mouseUi->ctrlRequiredForDragButton, &QPushButton::toggled, this, &EditProfileDialog::toggleCtrlRequiredForDrag);
1604     _mouseUi->copyTextAsHTMLButton->setChecked(profile->property<bool>(Profile::CopyTextAsHTML));
1605     connect(_mouseUi->copyTextAsHTMLButton, &QPushButton::toggled, this, &EditProfileDialog::toggleCopyTextAsHTML);
1606     _mouseUi->copyTextToClipboardButton->setChecked(profile->property<bool>(Profile::AutoCopySelectedText));
1607     connect(_mouseUi->copyTextToClipboardButton, &QPushButton::toggled, this, &EditProfileDialog::toggleCopyTextToClipboard);
1608     _mouseUi->trimLeadingSpacesButton->setChecked(profile->property<bool>(Profile::TrimLeadingSpacesInSelectedText));
1609     connect(_mouseUi->trimLeadingSpacesButton, &QPushButton::toggled, this, &EditProfileDialog::toggleTrimLeadingSpacesInSelectedText);
1610     _mouseUi->trimTrailingSpacesButton->setChecked(profile->property<bool>(Profile::TrimTrailingSpacesInSelectedText));
1611     connect(_mouseUi->trimTrailingSpacesButton, &QPushButton::toggled, this, &EditProfileDialog::toggleTrimTrailingSpacesInSelectedText);
1612     _mouseUi->openLinksByDirectClickButton->setChecked(profile->property<bool>(Profile::OpenLinksByDirectClickEnabled));
1613     connect(_mouseUi->openLinksByDirectClickButton, &QPushButton::toggled, this, &EditProfileDialog::toggleOpenLinksByDirectClick);
1614     _mouseUi->dropUrlsAsText->setChecked(profile->property<bool>(Profile::DropUrlsAsText));
1615     connect(_mouseUi->dropUrlsAsText, &QPushButton::toggled, this, &EditProfileDialog::toggleDropUrlsAsText);
1616     _mouseUi->enableAlternateScrollingButton->setChecked(profile->property<bool>(Profile::AlternateScrolling));
1617     connect(_mouseUi->enableAlternateScrollingButton, &QPushButton::toggled, this, &EditProfileDialog::toggleAlternateScrolling);
1618     _mouseUi->allowColorFilters->setChecked(profile->property<bool>(Profile::ColorFilterEnabled));
1619     connect(_mouseUi->allowColorFilters, &QPushButton::toggled, this, &EditProfileDialog::toggleAllowColorFilter);
1620 
1621     // setup middle click paste mode
1622     const auto middleClickPasteMode = profile->property<int>(Profile::MiddleClickPasteMode);
1623     _mouseUi->pasteFromX11SelectionButton->setChecked(Enum::PasteFromX11Selection == middleClickPasteMode);
1624     connect(_mouseUi->pasteFromX11SelectionButton, &QPushButton::clicked, this, &EditProfileDialog::pasteFromX11Selection);
1625     _mouseUi->pasteFromClipboardButton->setChecked(Enum::PasteFromClipboard == middleClickPasteMode);
1626     connect(_mouseUi->pasteFromClipboardButton, &QPushButton::clicked, this, &EditProfileDialog::pasteFromClipboard);
1627 
1628     _mouseUi->textEditorCustomBtn->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
1629 
1630     // interaction options
1631     _mouseUi->wordCharacterEdit->setText(profile->wordCharacters());
1632 
1633     connect(_mouseUi->wordCharacterEdit, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::wordCharactersChanged);
1634 
1635     const ButtonGroupOptions tripleClickModeOptions = {
1636         _mouseUi->tripleClickMode, // group
1637         Profile::TripleClickMode, // profileProperty
1638         false, // preview
1639         {
1640             // buttons
1641             {_mouseUi->tripleClickSelectsTheWholeLine, Enum::SelectWholeLine},
1642             {_mouseUi->tripleClickSelectsFromMousePosition, Enum::SelectForwardsFromCursor},
1643         },
1644     };
1645     setupButtonGroup(tripleClickModeOptions, profile);
1646 
1647     _mouseUi->openLinksByDirectClickButton->setEnabled(_mouseUi->underlineLinksButton->isChecked() || _mouseUi->underlineFilesButton->isChecked());
1648 
1649     _mouseUi->enableMouseWheelZoomButton->setChecked(profile->mouseWheelZoomEnabled());
1650     connect(_mouseUi->enableMouseWheelZoomButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::toggleMouseWheelZoom);
1651 
1652     _mouseUi->allowLinkEscapeSequenceButton->setChecked(profile->allowEscapedLinks());
1653     connect(_mouseUi->allowLinkEscapeSequenceButton, &QPushButton::clicked, this, &Konsole::EditProfileDialog::toggleAllowLinkEscapeSequence);
1654 
1655     _mouseUi->linkEscapeSequenceTexts->setEnabled(profile->allowEscapedLinks());
1656     _mouseUi->linkEscapeSequenceTexts->setText(profile->escapedLinksSchema().join(QLatin1Char(';')));
1657     connect(_mouseUi->linkEscapeSequenceTexts, &QLineEdit::textChanged, this, &Konsole::EditProfileDialog::linkEscapeSequenceTextsChanged);
1658 
1659     setTextEditorCombo(profile);
1660 }
1661 
setTextEditorCombo(const Profile::Ptr & profile)1662 void EditProfileDialog::setTextEditorCombo(const Profile::Ptr &profile)
1663 {
1664     static const Enum::TextEditorCmd editorsList[] =
1665         {Enum::Kate, Enum::KWrite, Enum::KDevelop, Enum::QtCreator, Enum::Gedit, Enum::gVim, Enum::CustomTextEditor};
1666 
1667     auto *editorCombo = _mouseUi->textEditorCombo;
1668 
1669     QStandardItemModel *model = static_cast<QStandardItemModel *>(editorCombo->model());
1670     Q_ASSERT(model);
1671 
1672     for (auto editor : editorsList) {
1673         QString exec;
1674         QString displayName;
1675         QIcon icon;
1676         switch (editor) {
1677         case Enum::Kate:
1678             exec = QStringLiteral("kate");
1679             displayName = QStringLiteral("Kate");
1680             icon = QIcon::fromTheme(exec);
1681             break;
1682         case Enum::KWrite:
1683             exec = QStringLiteral("kwrite");
1684             displayName = QStringLiteral("KWrite");
1685             icon = QIcon::fromTheme(exec);
1686             break;
1687         case Enum::KDevelop:
1688             exec = QStringLiteral("kdevelop");
1689             displayName = QStringLiteral("KDevelop");
1690             icon = QIcon::fromTheme(exec);
1691             break;
1692         case Enum::QtCreator:
1693             exec = QStringLiteral("qtcreator");
1694             displayName = QStringLiteral("Qt Creator");
1695             icon = QIcon::fromTheme(exec);
1696             break;
1697         case Enum::Gedit:
1698             exec = QStringLiteral("gedit");
1699             displayName = QStringLiteral("Gedit");
1700             QIcon::fromTheme(QStringLiteral("org.gnome.gedit"));
1701             break;
1702         case Enum::gVim:
1703             exec = QStringLiteral("gvim");
1704             displayName = QStringLiteral("gVim");
1705             icon = QIcon::fromTheme(exec);
1706             break;
1707         case Enum::CustomTextEditor:
1708             displayName = QStringLiteral("Custom");
1709             icon = QIcon::fromTheme(QStringLiteral("system-run"));
1710             break;
1711         default:
1712             break;
1713         }
1714 
1715         editorCombo->addItem(icon, displayName);
1716 
1717         // For "CustomTextEditor" we don't check if the binary exists
1718         const bool isAvailable = editor == Enum::CustomTextEditor || !QStandardPaths::findExecutable(exec).isEmpty();
1719         // Make un-available editors look disabled in the combobox
1720         model->item(static_cast<int>(editor))->setEnabled(isAvailable);
1721     }
1722 
1723     const auto currentEditor = profile->property<int>(Profile::TextEditorCmd);
1724     editorCombo->setCurrentIndex(currentEditor);
1725 
1726     connect(editorCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](const int index) {
1727         updateTempProfileProperty(Profile::TextEditorCmd, index);
1728         _mouseUi->textEditorCustomBtn->setEnabled(index == Enum::CustomTextEditor);
1729     });
1730 
1731     _mouseUi->textEditorCustomBtn->setEnabled(currentEditor == Enum::CustomTextEditor);
1732     connect(_mouseUi->textEditorCustomBtn, &QAbstractButton::clicked, this, [this, profile]() {
1733         auto *dlg = new QInputDialog(static_cast<QWidget *>(this));
1734         dlg->setLabelText(
1735             i18n("The format is e.g. 'editorExec PATH:LINE:COLUMN'\n\n"
1736                  "PATH    will be replaced by the path to the text file\n"
1737                  "LINE    will be replaced by the line number\n"
1738                  "COLUMN  (optional) will be replaced by the column number\n"
1739                  "Note: you will need to replace 'PATH:LINE:COLUMN' by the actual\n"
1740                  "syntax the editor you want to use supports; e.g.:\n"
1741                  "gedit +LINE:COLUMN PATH\n\n"
1742                  "If PATH or LINE aren't present in the command, this setting\n"
1743                  "will be ignored and the file will be opened by the default text\n"
1744                  "editor."));
1745         const QString cmd = profile->customTextEditorCmd();
1746         dlg->setTextValue(cmd);
1747         dlg->setAttribute(Qt::WA_DeleteOnClose);
1748         dlg->setWindowTitle(i18n("Text Editor Custom Command"));
1749 
1750         QFontMetrics fm(font());
1751         const int width = qMin(fm.averageCharWidth() * cmd.size(), this->width());
1752         dlg->resize(width, dlg->height());
1753 
1754         connect(dlg, &QDialog::accepted, this, [this, dlg]() {
1755             updateTempProfileProperty(Profile::TextEditorCmdCustom, dlg->textValue());
1756         });
1757 
1758         dlg->show();
1759     });
1760 }
1761 
setupAdvancedPage(const Profile::Ptr & profile)1762 void EditProfileDialog::setupAdvancedPage(const Profile::Ptr &profile)
1763 {
1764     _advancedUi->enableBlinkingTextButton->setChecked(profile->property<bool>(Profile::BlinkingTextEnabled));
1765     connect(_advancedUi->enableBlinkingTextButton, &QPushButton::toggled, this, &EditProfileDialog::toggleBlinkingText);
1766     _advancedUi->enableFlowControlButton->setChecked(profile->property<bool>(Profile::FlowControlEnabled));
1767     connect(_advancedUi->enableFlowControlButton, &QPushButton::toggled, this, &EditProfileDialog::toggleFlowControl);
1768     _appearanceUi->enableBlinkingCursorButton->setChecked(profile->property<bool>(Profile::BlinkingCursorEnabled));
1769     connect(_appearanceUi->enableBlinkingCursorButton, &QPushButton::toggled, this, &EditProfileDialog::toggleBlinkingCursor);
1770     _advancedUi->enableBidiRenderingButton->setChecked(profile->property<bool>(Profile::BidiRenderingEnabled));
1771     connect(_advancedUi->enableBidiRenderingButton, &QPushButton::toggled, this, &EditProfileDialog::togglebidiRendering);
1772     _advancedUi->enableReverseUrlHints->setChecked(profile->property<bool>(Profile::ReverseUrlHints));
1773     connect(_advancedUi->enableReverseUrlHints, &QPushButton::toggled, this, &EditProfileDialog::toggleReverseUrlHints);
1774 
1775     // Setup the URL hints modifier checkboxes
1776     {
1777         auto modifiers = profile->property<int>(Profile::UrlHintsModifiers);
1778         _advancedUi->urlHintsModifierShift->setChecked((modifiers & Qt::ShiftModifier) != 0U);
1779         _advancedUi->urlHintsModifierCtrl->setChecked((modifiers & Qt::ControlModifier) != 0U);
1780         _advancedUi->urlHintsModifierAlt->setChecked((modifiers & Qt::AltModifier) != 0U);
1781         _advancedUi->urlHintsModifierMeta->setChecked((modifiers & Qt::MetaModifier) != 0U);
1782         connect(_advancedUi->urlHintsModifierShift, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier);
1783         connect(_advancedUi->urlHintsModifierCtrl, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier);
1784         connect(_advancedUi->urlHintsModifierAlt, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier);
1785         connect(_advancedUi->urlHintsModifierMeta, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier);
1786     }
1787 
1788     // encoding options
1789     auto codecAction = new KCodecAction(this);
1790     codecAction->setCurrentCodec(profile->defaultEncoding());
1791     _advancedUi->selectEncodingButton->setMenu(codecAction->menu());
1792     connect(codecAction,
1793 #if KCONFIGWIDGETS_VERSION >= QT_VERSION_CHECK(5, 78, 0)
1794             QOverload<QTextCodec *>::of(&KCodecAction::codecTriggered),
1795             this,
1796 #else
1797             QOverload<QTextCodec *>::of(&KCodecAction::triggered),
1798             this,
1799 #endif
1800             &Konsole::EditProfileDialog::setDefaultCodec);
1801 
1802     _advancedUi->selectEncodingButton->setText(profile->defaultEncoding());
1803 
1804     _advancedUi->peekPrimaryWidget->setKeySequence(profile->peekPrimaryKeySequence());
1805     connect(_advancedUi->peekPrimaryWidget, &QKeySequenceEdit::editingFinished, this, &EditProfileDialog::peekPrimaryKeySequenceChanged);
1806 }
1807 
maxSpinBoxWidth(const KPluralHandlingSpinBox * spinBox,const KLocalizedString & suffix)1808 int EditProfileDialog::maxSpinBoxWidth(const KPluralHandlingSpinBox *spinBox, const KLocalizedString &suffix)
1809 {
1810     static const int cursorWidth = 2;
1811 
1812     const QFontMetrics fm(spinBox->fontMetrics());
1813     const QString plural = suffix.subs(2).toString();
1814     const QString singular = suffix.subs(1).toString();
1815     const QString min = QString::number(spinBox->minimum());
1816     const QString max = QString::number(spinBox->maximum());
1817     const int pluralWidth = fm.boundingRect(plural).width();
1818     const int singularWidth = fm.boundingRect(singular).width();
1819     const int minWidth = fm.boundingRect(min).width();
1820     const int maxWidth = fm.boundingRect(max).width();
1821     const int width = qMax(pluralWidth, singularWidth) + qMax(minWidth, maxWidth) + cursorWidth;
1822 
1823     // Based on QAbstractSpinBox::initStyleOption() from Qt
1824     QStyleOptionSpinBox opt;
1825     opt.initFrom(spinBox);
1826     opt.activeSubControls = QStyle::SC_None;
1827     opt.buttonSymbols = spinBox->buttonSymbols();
1828     // Assume all spinboxes have buttons
1829     opt.subControls = QStyle::SC_SpinBoxFrame | QStyle::SC_SpinBoxEditField | QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown;
1830     opt.frame = spinBox->hasFrame();
1831 
1832     const QSize hint(width, spinBox->sizeHint().height());
1833     const QSize spinBoxSize = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, spinBox).expandedTo(QApplication::globalStrut());
1834 
1835     return spinBoxSize.width();
1836 }
1837 
setDefaultCodec(QTextCodec * codec)1838 void EditProfileDialog::setDefaultCodec(QTextCodec *codec)
1839 {
1840     QString name = QString::fromLocal8Bit(codec->name());
1841 
1842     updateTempProfileProperty(Profile::DefaultEncoding, name);
1843     _advancedUi->selectEncodingButton->setText(name);
1844 }
1845 
wordCharactersChanged(const QString & text)1846 void EditProfileDialog::wordCharactersChanged(const QString &text)
1847 {
1848     updateTempProfileProperty(Profile::WordCharacters, text);
1849 }
1850 
togglebidiRendering(bool enable)1851 void EditProfileDialog::togglebidiRendering(bool enable)
1852 {
1853     updateTempProfileProperty(Profile::BidiRenderingEnabled, enable);
1854 }
1855 
toggleUnderlineLinks(bool enable)1856 void EditProfileDialog::toggleUnderlineLinks(bool enable)
1857 {
1858     updateTempProfileProperty(Profile::UnderlineLinksEnabled, enable);
1859 
1860     bool enableClick = _mouseUi->underlineFilesButton->isChecked() || enable;
1861     _mouseUi->openLinksByDirectClickButton->setEnabled(enableClick);
1862 }
1863 
toggleUnderlineFiles(bool enable)1864 void EditProfileDialog::toggleUnderlineFiles(bool enable)
1865 {
1866     updateTempProfileProperty(Profile::UnderlineFilesEnabled, enable);
1867 
1868     bool enableClick = _mouseUi->underlineLinksButton->isChecked() || enable;
1869     _mouseUi->openLinksByDirectClickButton->setEnabled(enableClick);
1870 }
1871 
textEditorCmdEditLineChanged(const QString & text)1872 void EditProfileDialog::textEditorCmdEditLineChanged(const QString &text)
1873 {
1874     updateTempProfileProperty(Profile::TextEditorCmd, text);
1875 }
1876 
toggleCtrlRequiredForDrag(bool enable)1877 void EditProfileDialog::toggleCtrlRequiredForDrag(bool enable)
1878 {
1879     updateTempProfileProperty(Profile::CtrlRequiredForDrag, enable);
1880 }
1881 
toggleDropUrlsAsText(bool enable)1882 void EditProfileDialog::toggleDropUrlsAsText(bool enable)
1883 {
1884     updateTempProfileProperty(Profile::DropUrlsAsText, enable);
1885 }
1886 
toggleOpenLinksByDirectClick(bool enable)1887 void EditProfileDialog::toggleOpenLinksByDirectClick(bool enable)
1888 {
1889     updateTempProfileProperty(Profile::OpenLinksByDirectClickEnabled, enable);
1890 }
1891 
toggleCopyTextAsHTML(bool enable)1892 void EditProfileDialog::toggleCopyTextAsHTML(bool enable)
1893 {
1894     updateTempProfileProperty(Profile::CopyTextAsHTML, enable);
1895 }
1896 
toggleCopyTextToClipboard(bool enable)1897 void EditProfileDialog::toggleCopyTextToClipboard(bool enable)
1898 {
1899     updateTempProfileProperty(Profile::AutoCopySelectedText, enable);
1900 }
1901 
toggleTrimLeadingSpacesInSelectedText(bool enable)1902 void EditProfileDialog::toggleTrimLeadingSpacesInSelectedText(bool enable)
1903 {
1904     updateTempProfileProperty(Profile::TrimLeadingSpacesInSelectedText, enable);
1905 }
1906 
toggleTrimTrailingSpacesInSelectedText(bool enable)1907 void EditProfileDialog::toggleTrimTrailingSpacesInSelectedText(bool enable)
1908 {
1909     updateTempProfileProperty(Profile::TrimTrailingSpacesInSelectedText, enable);
1910 }
1911 
pasteFromX11Selection()1912 void EditProfileDialog::pasteFromX11Selection()
1913 {
1914     updateTempProfileProperty(Profile::MiddleClickPasteMode, Enum::PasteFromX11Selection);
1915 }
1916 
pasteFromClipboard()1917 void EditProfileDialog::pasteFromClipboard()
1918 {
1919     updateTempProfileProperty(Profile::MiddleClickPasteMode, Enum::PasteFromClipboard);
1920 }
1921 
TripleClickModeChanged(int newValue)1922 void EditProfileDialog::TripleClickModeChanged(int newValue)
1923 {
1924     updateTempProfileProperty(Profile::TripleClickMode, newValue);
1925 }
1926 
updateUrlHintsModifier(bool)1927 void EditProfileDialog::updateUrlHintsModifier(bool)
1928 {
1929     Qt::KeyboardModifiers modifiers;
1930     if (_advancedUi->urlHintsModifierShift->isChecked()) {
1931         modifiers |= Qt::ShiftModifier;
1932     }
1933     if (_advancedUi->urlHintsModifierCtrl->isChecked()) {
1934         modifiers |= Qt::ControlModifier;
1935     }
1936     if (_advancedUi->urlHintsModifierAlt->isChecked()) {
1937         modifiers |= Qt::AltModifier;
1938     }
1939     if (_advancedUi->urlHintsModifierMeta->isChecked()) {
1940         modifiers |= Qt::MetaModifier;
1941     }
1942     updateTempProfileProperty(Profile::UrlHintsModifiers, int(modifiers));
1943 }
1944 
toggleReverseUrlHints(bool enable)1945 void EditProfileDialog::toggleReverseUrlHints(bool enable)
1946 {
1947     updateTempProfileProperty(Profile::ReverseUrlHints, enable);
1948 }
1949 
toggleBlinkingText(bool enable)1950 void EditProfileDialog::toggleBlinkingText(bool enable)
1951 {
1952     updateTempProfileProperty(Profile::BlinkingTextEnabled, enable);
1953 }
1954 
toggleFlowControl(bool enable)1955 void EditProfileDialog::toggleFlowControl(bool enable)
1956 {
1957     updateTempProfileProperty(Profile::FlowControlEnabled, enable);
1958 }
1959 
peekPrimaryKeySequenceChanged()1960 void EditProfileDialog::peekPrimaryKeySequenceChanged()
1961 {
1962     updateTempProfileProperty(Profile::PeekPrimaryKeySequence, _advancedUi->peekPrimaryWidget->keySequence().toString());
1963 }
1964