/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "snippetssettingspage.h" #include "snippeteditor.h" #include "snippetprovider.h" #include "snippet.h" #include "snippetscollection.h" #include "snippetssettings.h" #include "ui_snippetssettingspage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace TextEditor { namespace Internal { // SnippetsTableModel class SnippetsTableModel : public QAbstractTableModel { Q_OBJECT public: SnippetsTableModel(QObject *parent); ~SnippetsTableModel() override = default; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; Qt::ItemFlags flags(const QModelIndex &modelIndex) const override; QVariant data(const QModelIndex &modelIndex, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &modelIndex, const QVariant &value, int role = Qt::EditRole) override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QList groupIds() const; void load(const QString &groupId); QModelIndex createSnippet(); QModelIndex insertSnippet(const Snippet &snippet); void removeSnippet(const QModelIndex &modelIndex); const Snippet &snippetAt(const QModelIndex &modelIndex) const; void setSnippetContent(const QModelIndex &modelIndex, const QString &content); void revertBuitInSnippet(const QModelIndex &modelIndex); void restoreRemovedBuiltInSnippets(); void resetSnippets(); private: void replaceSnippet(const Snippet &snippet, const QModelIndex &modelIndex); SnippetsCollection* m_collection; QString m_activeGroupId; }; SnippetsTableModel::SnippetsTableModel(QObject *parent) : QAbstractTableModel(parent), m_collection(SnippetsCollection::instance()) {} int SnippetsTableModel::rowCount(const QModelIndex &) const { return m_collection->totalActiveSnippets(m_activeGroupId); } int SnippetsTableModel::columnCount(const QModelIndex &) const { return 2; } Qt::ItemFlags SnippetsTableModel::flags(const QModelIndex &index) const { Qt::ItemFlags itemFlags = QAbstractTableModel::flags(index); if (index.isValid()) itemFlags |= Qt::ItemIsEditable; return itemFlags; } QVariant SnippetsTableModel::data(const QModelIndex &modelIndex, int role) const { if (!modelIndex.isValid()) return QVariant(); if (role == Qt::DisplayRole || role == Qt::EditRole) { const Snippet &snippet = m_collection->snippet(modelIndex.row(), m_activeGroupId); if (modelIndex.column() == 0) return snippet.trigger(); else return snippet.complement(); } else { return QVariant(); } } bool SnippetsTableModel::setData(const QModelIndex &modelIndex, const QVariant &value, int role) { if (modelIndex.isValid() && role == Qt::EditRole) { Snippet snippet(m_collection->snippet(modelIndex.row(), m_activeGroupId)); if (modelIndex.column() == 0) { const QString &s = value.toString(); if (!Snippet::isValidTrigger(s)) { QMessageBox::critical( Core::ICore::dialogParent(), tr("Error"), tr("Not a valid trigger. A valid trigger can only contain letters, " "numbers, or underscores, where the first character is " "limited to letter or underscore.")); if (snippet.trigger().isEmpty()) removeSnippet(modelIndex); return false; } snippet.setTrigger(s); } else { snippet.setComplement(value.toString()); } replaceSnippet(snippet, modelIndex); return true; } return false; } QVariant SnippetsTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole || orientation != Qt::Horizontal) return QVariant(); if (section == 0) return tr("Trigger"); else return tr("Trigger Variant"); } void SnippetsTableModel::load(const QString &groupId) { beginResetModel(); m_activeGroupId = groupId; endResetModel(); } QList SnippetsTableModel::groupIds() const { return m_collection->groupIds(); } QModelIndex SnippetsTableModel::createSnippet() { Snippet snippet(m_activeGroupId); return insertSnippet(snippet); } QModelIndex SnippetsTableModel::insertSnippet(const Snippet &snippet) { const SnippetsCollection::Hint &hint = m_collection->computeInsertionHint(snippet); beginInsertRows(QModelIndex(), hint.index(), hint.index()); m_collection->insertSnippet(snippet, hint); endInsertRows(); return index(hint.index(), 0); } void SnippetsTableModel::removeSnippet(const QModelIndex &modelIndex) { beginRemoveRows(QModelIndex(), modelIndex.row(), modelIndex.row()); m_collection->removeSnippet(modelIndex.row(), m_activeGroupId); endRemoveRows(); } const Snippet &SnippetsTableModel::snippetAt(const QModelIndex &modelIndex) const { return m_collection->snippet(modelIndex.row(), m_activeGroupId); } void SnippetsTableModel::setSnippetContent(const QModelIndex &modelIndex, const QString &content) { m_collection->setSnippetContent(modelIndex.row(), m_activeGroupId, content); } void SnippetsTableModel::revertBuitInSnippet(const QModelIndex &modelIndex) { const Snippet &snippet = m_collection->revertedSnippet(modelIndex.row(), m_activeGroupId); if (snippet.id().isEmpty()) { QMessageBox::critical(Core::ICore::dialogParent(), tr("Error"), tr("Error reverting snippet.")); return; } replaceSnippet(snippet, modelIndex); } void SnippetsTableModel::restoreRemovedBuiltInSnippets() { beginResetModel(); m_collection->restoreRemovedSnippets(m_activeGroupId); endResetModel(); } void SnippetsTableModel::resetSnippets() { beginResetModel(); m_collection->reset(m_activeGroupId); endResetModel(); } void SnippetsTableModel::replaceSnippet(const Snippet &snippet, const QModelIndex &modelIndex) { const int row = modelIndex.row(); const SnippetsCollection::Hint &hint = m_collection->computeReplacementHint(row, snippet); if (modelIndex.row() == hint.index()) { m_collection->replaceSnippet(row, snippet, hint); if (modelIndex.column() == 0) emit dataChanged(modelIndex, modelIndex.sibling(row, 1)); else emit dataChanged(modelIndex.sibling(row, 0), modelIndex); } else { if (row < hint.index()) // Rows will be moved down. beginMoveRows(QModelIndex(), row, row, QModelIndex(), hint.index() + 1); else beginMoveRows(QModelIndex(), row, row, QModelIndex(), hint.index()); m_collection->replaceSnippet(row, snippet, hint); endMoveRows(); } } // SnippetsSettingsPagePrivate class SnippetsSettingsPagePrivate : public QObject { Q_DECLARE_TR_FUNCTIONS(TextEditor::Internal::SnippetsSettingsPage) public: SnippetsSettingsPagePrivate(); ~SnippetsSettingsPagePrivate() override { delete m_model; } void configureUi(QWidget *parent); void apply(); void finish(); QPointer m_widget; private: void loadSnippetGroup(int index); void markSnippetsCollection(); void addSnippet(); void removeSnippet(); void revertBuiltInSnippet(); void restoreRemovedBuiltInSnippets(); void resetAllSnippets(); void selectSnippet(const QModelIndex &parent, int row); void selectMovedSnippet(const QModelIndex &, int, int, const QModelIndex &, int row); void setSnippetContent(); void updateCurrentSnippetDependent(const QModelIndex &modelIndex = QModelIndex()); void decorateEditors(const TextEditor::FontSettings &fontSettings); SnippetEditorWidget *currentEditor() const; SnippetEditorWidget *editorAt(int i) const; void loadSettings(); bool settingsChanged() const; void writeSettings(); const QString m_settingsPrefix; SnippetsTableModel *m_model; bool m_snippetsCollectionChanged; SnippetsSettings m_settings; Ui::SnippetsSettingsPage m_ui; }; SnippetsSettingsPagePrivate::SnippetsSettingsPagePrivate() : m_settingsPrefix(QLatin1String("Text")), m_model(new SnippetsTableModel(nullptr)), m_snippetsCollectionChanged(false) {} SnippetEditorWidget *SnippetsSettingsPagePrivate::currentEditor() const { return editorAt(m_ui.snippetsEditorStack->currentIndex()); } SnippetEditorWidget *SnippetsSettingsPagePrivate::editorAt(int i) const { return static_cast(m_ui.snippetsEditorStack->widget(i)); } void SnippetsSettingsPagePrivate::configureUi(QWidget *w) { m_ui.setupUi(w); for (const SnippetProvider &provider : SnippetProvider::snippetProviders()) { m_ui.groupCombo->addItem(provider.displayName(), provider.groupId()); auto snippetEditor = new SnippetEditorWidget(w); SnippetProvider::decorateEditor(snippetEditor, provider.groupId()); m_ui.snippetsEditorStack->insertWidget(m_ui.groupCombo->count() - 1, snippetEditor); connect(snippetEditor, &SnippetEditorWidget::snippetContentChanged, this, &SnippetsSettingsPagePrivate::setSnippetContent); } m_ui.snippetsTable->setModel(m_model); new Utils::HeaderViewStretcher(m_ui.snippetsTable->header(), 1); m_ui.revertButton->setEnabled(false); loadSettings(); loadSnippetGroup(m_ui.groupCombo->currentIndex()); connect(m_model, &QAbstractItemModel::rowsInserted, this, &SnippetsSettingsPagePrivate::selectSnippet); connect(m_model, &QAbstractItemModel::rowsInserted, this, &SnippetsSettingsPagePrivate::markSnippetsCollection); connect(m_model, &QAbstractItemModel::rowsRemoved, this, &SnippetsSettingsPagePrivate::markSnippetsCollection); connect(m_model, &QAbstractItemModel::rowsMoved, this, &SnippetsSettingsPagePrivate::selectMovedSnippet); connect(m_model, &QAbstractItemModel::rowsMoved, this, &SnippetsSettingsPagePrivate::markSnippetsCollection); connect(m_model, &QAbstractItemModel::dataChanged, this, &SnippetsSettingsPagePrivate::markSnippetsCollection); connect(m_model, &QAbstractItemModel::modelReset, this, [this] { this->updateCurrentSnippetDependent(); }); connect(m_model, &QAbstractItemModel::modelReset, this, &SnippetsSettingsPagePrivate::markSnippetsCollection); connect(m_ui.groupCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &SnippetsSettingsPagePrivate::loadSnippetGroup); connect(m_ui.addButton, &QAbstractButton::clicked, this, &SnippetsSettingsPagePrivate::addSnippet); connect(m_ui.removeButton, &QAbstractButton::clicked, this, &SnippetsSettingsPagePrivate::removeSnippet); connect(m_ui.resetAllButton, &QAbstractButton::clicked, this, &SnippetsSettingsPagePrivate::resetAllSnippets); connect(m_ui.restoreRemovedButton, &QAbstractButton::clicked, this, &SnippetsSettingsPagePrivate::restoreRemovedBuiltInSnippets); connect(m_ui.revertButton, &QAbstractButton::clicked, this, &SnippetsSettingsPagePrivate::revertBuiltInSnippet); connect(m_ui.snippetsTable->selectionModel(), &QItemSelectionModel::currentChanged, this, &SnippetsSettingsPagePrivate::updateCurrentSnippetDependent); connect(TextEditorSettings::instance(), &TextEditorSettings::fontSettingsChanged, this, &SnippetsSettingsPagePrivate::decorateEditors); } void SnippetsSettingsPagePrivate::apply() { if (settingsChanged()) writeSettings(); if (currentEditor()->document()->isModified()) setSnippetContent(); if (m_snippetsCollectionChanged) { QString errorString; if (SnippetsCollection::instance()->synchronize(&errorString)) { m_snippetsCollectionChanged = false; } else { QMessageBox::critical(Core::ICore::dialogParent(), tr("Error While Saving Snippet Collection"), errorString); } } } void SnippetsSettingsPagePrivate::finish() { if (m_snippetsCollectionChanged) { SnippetsCollection::instance()->reload(); m_snippetsCollectionChanged = false; } disconnect(TextEditorSettings::instance(), nullptr, this, nullptr); } void SnippetsSettingsPagePrivate::loadSettings() { if (m_ui.groupCombo->count() == 0) return; m_settings.fromSettings(m_settingsPrefix, Core::ICore::settings()); const QString &lastGroupName = m_settings.lastUsedSnippetGroup(); const int index = m_ui.groupCombo->findText(lastGroupName); if (index != -1) m_ui.groupCombo->setCurrentIndex(index); else m_ui.groupCombo->setCurrentIndex(0); } void SnippetsSettingsPagePrivate::writeSettings() { if (m_ui.groupCombo->count() == 0) return; m_settings.setLastUsedSnippetGroup(m_ui.groupCombo->currentText()); m_settings.toSettings(m_settingsPrefix, Core::ICore::settings()); } bool SnippetsSettingsPagePrivate::settingsChanged() const { if (m_settings.lastUsedSnippetGroup() != m_ui.groupCombo->currentText()) return true; return false; } void SnippetsSettingsPagePrivate::loadSnippetGroup(int index) { if (index == -1) return; m_ui.snippetsEditorStack->setCurrentIndex(index); currentEditor()->clear(); m_model->load(m_ui.groupCombo->itemData(index).toString()); } void SnippetsSettingsPagePrivate::markSnippetsCollection() { if (!m_snippetsCollectionChanged) m_snippetsCollectionChanged = true; } void SnippetsSettingsPagePrivate::addSnippet() { const QModelIndex &modelIndex = m_model->createSnippet(); selectSnippet(QModelIndex(), modelIndex.row()); m_ui.snippetsTable->edit(modelIndex); } void SnippetsSettingsPagePrivate::removeSnippet() { const QModelIndex &modelIndex = m_ui.snippetsTable->selectionModel()->currentIndex(); if (!modelIndex.isValid()) { QMessageBox::critical(Core::ICore::dialogParent(), tr("Error"), tr("No snippet selected.")); return; } m_model->removeSnippet(modelIndex); } void SnippetsSettingsPagePrivate::restoreRemovedBuiltInSnippets() { m_model->restoreRemovedBuiltInSnippets(); } void SnippetsSettingsPagePrivate::revertBuiltInSnippet() { m_model->revertBuitInSnippet(m_ui.snippetsTable->selectionModel()->currentIndex()); } void SnippetsSettingsPagePrivate::resetAllSnippets() { m_model->resetSnippets(); } void SnippetsSettingsPagePrivate::selectSnippet(const QModelIndex &parent, int row) { QModelIndex topLeft = m_model->index(row, 0, parent); QModelIndex bottomRight = m_model->index(row, 1, parent); QItemSelection selection(topLeft, bottomRight); m_ui.snippetsTable->selectionModel()->select(selection, QItemSelectionModel::SelectCurrent); m_ui.snippetsTable->setCurrentIndex(topLeft); m_ui.snippetsTable->scrollTo(topLeft); } void SnippetsSettingsPagePrivate::selectMovedSnippet(const QModelIndex &, int sourceRow, int, const QModelIndex &destinationParent, int destinationRow) { QModelIndex modelIndex; if (sourceRow < destinationRow) modelIndex = m_model->index(destinationRow - 1, 0, destinationParent); else modelIndex = m_model->index(destinationRow, 0, destinationParent); m_ui.snippetsTable->scrollTo(modelIndex); currentEditor()->setPlainText(m_model->snippetAt(modelIndex).content()); } void SnippetsSettingsPagePrivate::updateCurrentSnippetDependent(const QModelIndex &modelIndex) { if (modelIndex.isValid()) { const Snippet &snippet = m_model->snippetAt(modelIndex); currentEditor()->setPlainText(snippet.content()); m_ui.revertButton->setEnabled(snippet.isBuiltIn()); } else { currentEditor()->clear(); m_ui.revertButton->setEnabled(false); } } void SnippetsSettingsPagePrivate::setSnippetContent() { const QModelIndex &modelIndex = m_ui.snippetsTable->selectionModel()->currentIndex(); if (modelIndex.isValid()) { m_model->setSnippetContent(modelIndex, currentEditor()->toPlainText()); markSnippetsCollection(); } } void SnippetsSettingsPagePrivate::decorateEditors(const TextEditor::FontSettings &fontSettings) { for (int i = 0; i < m_ui.groupCombo->count(); ++i) { SnippetEditorWidget *snippetEditor = editorAt(i); snippetEditor->textDocument()->setFontSettings(fontSettings); const QString &id = m_ui.groupCombo->itemData(i).toString(); // This list should be quite short... Re-iterating over it is ok. SnippetProvider::decorateEditor(snippetEditor, id); } } // SnippetsSettingsPage SnippetsSettingsPage::SnippetsSettingsPage() : d(new SnippetsSettingsPagePrivate) { setId(Constants::TEXT_EDITOR_SNIPPETS_SETTINGS); setDisplayName(SnippetsSettingsPagePrivate::tr("Snippets")); setCategory(TextEditor::Constants::TEXT_EDITOR_SETTINGS_CATEGORY); setDisplayCategory(QCoreApplication::translate("TextEditor", "Text Editor")); setCategoryIconPath(TextEditor::Constants::TEXT_EDITOR_SETTINGS_CATEGORY_ICON_PATH); } SnippetsSettingsPage::~SnippetsSettingsPage() { delete d; } QWidget *SnippetsSettingsPage::widget() { if (!d->m_widget) { d->m_widget = new QWidget; d->configureUi(d->m_widget); } return d->m_widget; } void SnippetsSettingsPage::apply() { d->apply(); } void SnippetsSettingsPage::finish() { d->finish(); delete d->m_widget; } } // Internal } // TextEditor #include "snippetssettingspage.moc"