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 ¤tColorSchemeName = 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