diff --git a/src/settings/ProfileSettings.cpp b/src/settings/ProfileSettings.cpp index b46f05ff..2db4dadb 100644 --- a/src/settings/ProfileSettings.cpp +++ b/src/settings/ProfileSettings.cpp @@ -1,545 +1,545 @@ /* Copyright 2007-2008 by Robert Knight This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ // Own #include "ProfileSettings.h" #include // Qt #include #include #include #include // KDE #include #include #include // Konsole #include "EditProfileDialog.h" #include "ProfileManager.h" #include "Session.h" #include "TerminalDisplay.h" #include "SessionManager.h" #include "SessionController.h" using namespace Konsole; ProfileSettings::ProfileSettings(QWidget* aParent) : QWidget(aParent) , _sessionModel(new QStandardItemModel(this)) { setupUi(this); profilesList->setItemDelegateForColumn(ShortcutColumn, new ShortcutItemDelegate(this)); // double clicking the profile name opens the profile edit dialog connect(profilesList, &QAbstractItemView::doubleClicked, this, &Konsole::ProfileSettings::doubleClicked); // populate the table with profiles populateTable(); // listen for changes to profiles connect(ProfileManager::instance(), &Konsole::ProfileManager::profileAdded, this, &Konsole::ProfileSettings::addItems); connect(ProfileManager::instance(), &Konsole::ProfileManager::profileRemoved, this, &Konsole::ProfileSettings::removeItems); connect(ProfileManager::instance(), &Konsole::ProfileManager::profileChanged, this, &Konsole::ProfileSettings::updateItems); connect(ProfileManager::instance(), &Konsole::ProfileManager::favoriteStatusChanged, this, &Konsole::ProfileSettings::updateFavoriteStatus); // setup buttons connect(newProfileButton, &QPushButton::clicked, this, &Konsole::ProfileSettings::createProfile); connect(editProfileButton, &QPushButton::clicked, this, &Konsole::ProfileSettings::editSelected); connect(deleteProfileButton, &QPushButton::clicked, this, &Konsole::ProfileSettings::deleteSelected); connect(setAsDefaultButton, &QPushButton::clicked, this, &Konsole::ProfileSettings::setSelectedAsDefault); } ProfileSettings::~ProfileSettings() = default; void ProfileSettings::slotAccepted() { ProfileManager::instance()->saveSettings(); deleteLater(); } void ProfileSettings::itemDataChanged(QStandardItem* item) { if (item->column() == ShortcutColumn) { QKeySequence sequence = QKeySequence::fromString(item->text()); QStandardItem *idItem = item->model()->item(item->row(), ProfileColumn); ProfileManager::instance()->setShortcut(idItem->data(ProfilePtrRole).value(), sequence); } else if (item->column() == FavoriteStatusColumn) { QStandardItem *idItem = item->model()->item(item->row(), ProfileColumn); const bool isFavorite = item->checkState() == Qt::Checked; ProfileManager::instance()->setFavorite(idItem->data(ProfilePtrRole).value(), isFavorite); updateShortcutField(item->model()->item(item->row(), ShortcutColumn), isFavorite); } } void ProfileSettings::updateShortcutField(QStandardItem *item, bool isFavorite) const { if(isFavorite) { item->setToolTip(i18nc("@info:tooltip", "Double click to change shortcut")); item->setForeground(palette().color(QPalette::Normal, QPalette::Text)); } else { item->setToolTip(i18nc("@info:tooltip", "Shortcut won't work while the profile is not marked as visible.")); item->setForeground(palette().color(QPalette::Disabled, QPalette::Text)); } } int ProfileSettings::rowForProfile(const Profile::Ptr &profile) const { const int rowCount = _sessionModel->rowCount(); for (int i = 0; i < rowCount; i++) { if (_sessionModel->item(i, ProfileColumn)->data(ProfilePtrRole) .value() == profile) { return i; } } return -1; } void ProfileSettings::removeItems(const Profile::Ptr &profile) { int row = rowForProfile(profile); if (row < 0) { return; } _sessionModel->removeRow(row); } void ProfileSettings::updateItems(const Profile::Ptr &profile) { const int row = rowForProfile(profile); if (row < 0) { return; } const auto items = QList { _sessionModel->item(row, FavoriteStatusColumn), _sessionModel->item(row, ProfileNameColumn), _sessionModel->item(row, ShortcutColumn), _sessionModel->item(row, ProfileColumn), }; updateItemsForProfile(profile, items); } void ProfileSettings::updateItemsForProfile(const Profile::Ptr &profile, const QList& items) const { // "Enabled" checkbox const auto isEnabled = ProfileManager::instance()->findFavorites().contains(profile); items[FavoriteStatusColumn]->setCheckState(isEnabled ? Qt::Checked : Qt::Unchecked); items[FavoriteStatusColumn]->setCheckable(true); items[FavoriteStatusColumn]->setToolTip( i18nc("@info:tooltip List item's checkbox for making item (profile) visible in a menu", "Show profile in menu")); // Profile Name items[ProfileNameColumn]->setText(profile->name()); if (!profile->icon().isEmpty()) { items[ProfileNameColumn]->setIcon(QIcon::fromTheme(profile->icon())); } // only allow renaming the profile from the edit profile dialog // so as to use ProfileManager::checkProfileName() items[ProfileNameColumn]->setEditable(false); // Shortcut const auto shortcut = ProfileManager::instance()->shortcut(profile).toString(); items[ShortcutColumn]->setText(shortcut); updateShortcutField(items[ShortcutColumn], isEnabled); // Profile ID (pointer to profile) - intended to be hidden in a view items[ProfileColumn]->setData(QVariant::fromValue(profile), ProfilePtrRole); } void ProfileSettings::doubleClicked(const QModelIndex &index) { QStandardItem *item = _sessionModel->itemFromIndex(index); if (item->column() == ProfileNameColumn) { editSelected(); } } void ProfileSettings::addItems(const Profile::Ptr &profile) { if (profile->isHidden()) { return; } // each _sessionModel row has three items. const auto items = QList { new QStandardItem(), new QStandardItem(), new QStandardItem(), new QStandardItem(), }; updateItemsForProfile(profile, items); _sessionModel->appendRow(items); } void ProfileSettings::populateTable() { Q_ASSERT(!profilesList->model()); profilesList->setModel(_sessionModel); _sessionModel->clear(); // setup session table _sessionModel->setHorizontalHeaderLabels({ QString(), // set using header item below i18nc("@title:column Profile name", "Name"), i18nc("@title:column Profile keyboard shortcut", "Shortcut"), QString(), }); auto *favoriteColumnHeaderItem = new QStandardItem(); favoriteColumnHeaderItem->setIcon(QIcon::fromTheme(QStringLiteral("visibility"))); favoriteColumnHeaderItem->setToolTip( i18nc("@info:tooltip List item's checkbox for making item (profile) visible in a menu", "Show profile in menu")); _sessionModel->setHorizontalHeaderItem(FavoriteStatusColumn, favoriteColumnHeaderItem); // Calculate favorite column width. resizeColumnToContents() // is not used because it takes distance between checkbox and // text into account, but there is no text and it looks weird. const int headerMargin = style()->pixelMetric(QStyle::PM_HeaderMargin, nullptr, profilesList->header()); const int iconWidth = style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, profilesList->header()); const int favoriteHeaderWidth = headerMargin * 2 + iconWidth; QStyleOptionViewItem opt; opt.features = QStyleOptionViewItem::HasCheckIndicator | QStyleOptionViewItem::HasDecoration; const QRect checkBoxRect = style()->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &opt, profilesList); // When right edge is at x < 0 it is assumed the checkbox is // placed on the right item's side and the margin between right // checkbox edge and right item edge should be used. const int checkBoxMargin = checkBoxRect.right() >= 0 ? checkBoxRect.x() : 0 - checkBoxRect.right(); const int favoriteItemWidth = checkBoxMargin * 2 + checkBoxRect.width(); auto *listHeader = profilesList->header(); profilesList->setColumnWidth(FavoriteStatusColumn, qMax(favoriteHeaderWidth, favoriteItemWidth)); profilesList->resizeColumnToContents(ProfileNameColumn); listHeader->setSectionResizeMode(FavoriteStatusColumn, QHeaderView::ResizeMode::Fixed); listHeader->setSectionResizeMode(ProfileNameColumn, QHeaderView::ResizeMode::Stretch); listHeader->setSectionResizeMode(ShortcutColumn, QHeaderView::ResizeMode::ResizeToContents); listHeader->setStretchLastSection(false); listHeader->setSectionsMovable(false); profilesList->hideColumn(ProfileColumn); QList profiles = ProfileManager::instance()->allProfiles(); ProfileManager::instance()->sortProfiles(profiles); foreach(const Profile::Ptr& profile, profiles) { addItems(profile); } updateDefaultItem(); connect(_sessionModel, &QStandardItemModel::itemChanged, this, &Konsole::ProfileSettings::itemDataChanged); // listen for changes in the table selection and update the state of the form's buttons // accordingly. // // it appears that the selection model is changed when the model itself is replaced, // so the signals need to be reconnected each time the model is updated. connect(profilesList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &Konsole::ProfileSettings::tableSelectionChanged); } void ProfileSettings::updateDefaultItem() { Profile::Ptr defaultProfile = ProfileManager::instance()->defaultProfile(); - const QString defaultItemSuffix = i18nc("Default list item's name suffix (with separator)", " (default)"); + const QString defaultItemSuffix = i18nc("@item:intable Default list item's name suffix (with separator)", " (default)"); const int rowCount = _sessionModel->rowCount(); for (int i = 0; i < rowCount; i++) { QStandardItem* item = _sessionModel->item(i, ProfileNameColumn); QFont itemFont = item->font(); QStandardItem* profileIdItem = _sessionModel->item(i, ProfileColumn); auto profile = profileIdItem->data().value(); const bool isDefault = (defaultProfile == profile); const QString cleanItemName = profile != nullptr ? profile->name() : QString(); if (isDefault) { itemFont.setItalic(true); item->setFont(itemFont); item->setText(cleanItemName + defaultItemSuffix); } else if (!isDefault) { // FIXME: use default font itemFont.setItalic(false); item->setFont(itemFont); item->setText(cleanItemName); } } } void ProfileSettings::tableSelectionChanged(const QItemSelection&) { const ProfileManager* manager = ProfileManager::instance(); bool isNotDefault = true; bool isDeletable = true; const auto profiles = selectedProfiles(); for (const auto &profile: profiles) { isNotDefault = isNotDefault && (profile != manager->defaultProfile()); isDeletable = isDeletable && isProfileDeletable(profile); } newProfileButton->setEnabled(profiles.count() < 2); // FIXME: At some point editing 2+ profiles no longer works editProfileButton->setEnabled(profiles.count() == 1); // do not allow the default session type to be removed deleteProfileButton->setEnabled(isDeletable && isNotDefault && (profiles.count() > 0)); setAsDefaultButton->setEnabled(isNotDefault && (profiles.count() == 1)); } void ProfileSettings::deleteSelected() { foreach(const Profile::Ptr & profile, selectedProfiles()) { if (profile != ProfileManager::instance()->defaultProfile()) { ProfileManager::instance()->deleteProfile(profile); } } } void ProfileSettings::setSelectedAsDefault() { ProfileManager::instance()->setDefaultProfile(currentProfile()); // do not allow the new default session type to be removed deleteProfileButton->setEnabled(false); setAsDefaultButton->setEnabled(false); // update font of new default item updateDefaultItem(); } void ProfileSettings::createProfile() { // setup a temporary profile which is a clone of the selected profile // or the default if no profile is selected Profile::Ptr sourceProfile = currentProfile() ? currentProfile() : ProfileManager::instance()->defaultProfile(); Q_ASSERT(sourceProfile); auto newProfile = Profile::Ptr(new Profile(ProfileManager::instance()->fallbackProfile())); newProfile->clone(sourceProfile, true); // TODO: add number suffix when the name is taken newProfile->setProperty(Profile::Name, i18nc("@item This will be used as part of the file name", "New Profile")); newProfile->setProperty(Profile::UntranslatedName, QStringLiteral("New Profile")); newProfile->setProperty(Profile::MenuIndex, QStringLiteral("0")); // Consider https://blogs.kde.org/2009/03/26/how-crash-almost-every-qtkde-application-and-how-fix-it-0 before changing the below QPointer dialog = new EditProfileDialog(this); dialog.data()->setProfile(newProfile); dialog.data()->selectProfileName(); if (dialog.data()->exec() == QDialog::Accepted) { ProfileManager::instance()->addProfile(newProfile); ProfileManager::instance()->setFavorite(newProfile, true); ProfileManager::instance()->changeProfile(newProfile, newProfile->setProperties()); } delete dialog.data(); } void ProfileSettings::editSelected() { QList profiles(selectedProfiles()); foreach (Session* session, SessionManager::instance()->sessions()) { foreach (TerminalDisplay* terminal, session->views()) { // Searching for opened profiles if (terminal->sessionController()->profileDialogPointer() != nullptr) { foreach (const Profile::Ptr & profile, profiles) { if (profile->name() == terminal->sessionController()->profileDialogPointer()->lookupProfile()->name() && terminal->sessionController()->profileDialogPointer()->isVisible()) { // close opened edit dialog terminal->sessionController()->profileDialogPointer()->close(); } } } } } EditProfileDialog dialog(this); // the dialog will delete the profile group when it is destroyed ProfileGroup* group = new ProfileGroup; foreach (const Profile::Ptr & profile, profiles) { group->addProfile(profile); } group->updateValues(); dialog.setProfile(Profile::Ptr(group)); dialog.exec(); } QList ProfileSettings::selectedProfiles() const { QList list; QItemSelectionModel* selection = profilesList->selectionModel(); if (selection == nullptr) { return list; } foreach(const QModelIndex & index, selection->selectedIndexes()) { if (index.column() == ProfileColumn) { list << index.data(ProfilePtrRole).value(); } } return list; } Profile::Ptr ProfileSettings::currentProfile() const { QItemSelectionModel* selection = profilesList->selectionModel(); if ((selection == nullptr) || selection->selectedRows().count() != 1) { return Profile::Ptr(); } return selection-> selectedIndexes().at(ProfileColumn).data(ProfilePtrRole).value(); } bool ProfileSettings::isProfileDeletable(Profile::Ptr profile) const { if (!profile) { return false; } const QFileInfo fileInfo(profile->path()); if (!fileInfo.exists()) { return false; } const QFileInfo dirInfo(fileInfo.path()); return dirInfo.isWritable(); } void ProfileSettings::updateFavoriteStatus(const Profile::Ptr &profile, bool favorite) { Q_ASSERT(_sessionModel); const int rowCount = _sessionModel->rowCount(); for (int i = 0; i < rowCount; i++) { auto *item = _sessionModel->item(i, ProfileColumn); if (item->data(ProfilePtrRole).value() == profile) { auto *favoriteItem = _sessionModel->item(i, FavoriteStatusColumn); favoriteItem->setCheckState(favorite ? Qt::Checked : Qt::Unchecked); break; } } } void ProfileSettings::setShortcutEditorVisible(bool visible) { profilesList->setColumnHidden(ShortcutColumn, !visible); } void StyledBackgroundPainter::drawBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex&) { const auto* opt = qstyleoption_cast(&option); const QWidget* widget = opt != nullptr ? opt->widget : nullptr; QStyle* style = widget != nullptr ? widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, widget); } ShortcutItemDelegate::ShortcutItemDelegate(QObject* aParent) : QStyledItemDelegate(aParent), _modifiedEditors(QSet()), _itemsBeingEdited(QSet()) { } void ShortcutItemDelegate::editorModified() { auto* editor = qobject_cast(sender()); Q_ASSERT(editor); _modifiedEditors.insert(editor); emit commitData(editor); emit closeEditor(editor); } void ShortcutItemDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { _itemsBeingEdited.remove(index); if (!_modifiedEditors.contains(editor)) { return; } QString shortcut = qobject_cast(editor)->keySequence().toString(); model->setData(index, shortcut, Qt::DisplayRole); _modifiedEditors.remove(editor); } QWidget* ShortcutItemDelegate::createEditor(QWidget* aParent, const QStyleOptionViewItem&, const QModelIndex& index) const { _itemsBeingEdited.insert(index); auto editor = new FilteredKeySequenceEdit(aParent); QString shortcutString = index.data(Qt::DisplayRole).toString(); editor->setKeySequence(QKeySequence::fromString(shortcutString)); connect(editor, &QKeySequenceEdit::editingFinished, this, &Konsole::ShortcutItemDelegate::editorModified); editor->setFocus(Qt::FocusReason::MouseFocusReason); return editor; } void ShortcutItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { if (_itemsBeingEdited.contains(index)) { StyledBackgroundPainter::drawBackground(painter, option, index); } else { QStyledItemDelegate::paint(painter, option, index); } } QSize Konsole::ShortcutItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { const QString shortcutString = index.data(Qt::DisplayRole).toString(); QFontMetrics fm = option.fontMetrics; static const int editorMargins = 16; // chosen empirically const int width = fm.width(shortcutString + QStringLiteral(", ...")) + editorMargins; return QSize(width, QStyledItemDelegate::sizeHint(option, index).height()); } void Konsole::ShortcutItemDelegate::destroyEditor(QWidget *editor, const QModelIndex &index) const { _itemsBeingEdited.remove(index); _modifiedEditors.remove(editor); editor->deleteLater(); } void Konsole::FilteredKeySequenceEdit::keyPressEvent(QKeyEvent *event) { if(event->modifiers() == Qt::NoModifier) { switch(event->key()) { case Qt::Key_Enter: case Qt::Key_Return: emit editingFinished(); return; case Qt::Key_Backspace: case Qt::Key_Delete: clear(); emit editingFinished(); event->accept(); return; default: event->accept(); return; } } QKeySequenceEdit::keyPressEvent(event); }