diff options
-rw-r--r-- | src/libs/utils/submiteditorwidget.cpp | 107 | ||||
-rw-r--r-- | src/libs/utils/submiteditorwidget.h | 12 | ||||
-rw-r--r-- | src/plugins/git/gitplugin.cpp | 10 | ||||
-rw-r--r-- | src/plugins/perforce/perforceplugin.cpp | 19 | ||||
-rw-r--r-- | src/plugins/subversion/subversionplugin.cpp | 12 | ||||
-rw-r--r-- | src/plugins/vcsbase/nicknamedialog.cpp | 251 | ||||
-rw-r--r-- | src/plugins/vcsbase/nicknamedialog.h | 81 | ||||
-rw-r--r-- | src/plugins/vcsbase/nicknamedialog.ui | 117 | ||||
-rw-r--r-- | src/plugins/vcsbase/vcsbase.pro | 17 | ||||
-rw-r--r-- | src/plugins/vcsbase/vcsbaseconstants.h | 1 | ||||
-rw-r--r-- | src/plugins/vcsbase/vcsbaseplugin.cpp | 11 | ||||
-rw-r--r-- | src/plugins/vcsbase/vcsbaseplugin.h | 6 | ||||
-rw-r--r-- | src/plugins/vcsbase/vcsbasesettings.cpp | 78 | ||||
-rw-r--r-- | src/plugins/vcsbase/vcsbasesettings.h | 66 | ||||
-rw-r--r-- | src/plugins/vcsbase/vcsbasesettingspage.cpp | 145 | ||||
-rw-r--r-- | src/plugins/vcsbase/vcsbasesettingspage.h | 86 | ||||
-rw-r--r-- | src/plugins/vcsbase/vcsbasesettingspage.ui | 116 | ||||
-rw-r--r-- | src/plugins/vcsbase/vcsbasesubmiteditor.cpp | 178 | ||||
-rw-r--r-- | src/plugins/vcsbase/vcsbasesubmiteditor.h | 17 |
19 files changed, 1303 insertions, 27 deletions
diff --git a/src/libs/utils/submiteditorwidget.cpp b/src/libs/utils/submiteditorwidget.cpp index a047682b81..b9c8259020 100644 --- a/src/libs/utils/submiteditorwidget.cpp +++ b/src/libs/utils/submiteditorwidget.cpp @@ -33,8 +33,15 @@ #include <QtCore/QDebug> #include <QtCore/QPointer> #include <QtCore/QTimer> +#include <QtCore/QSignalMapper> #include <QtGui/QPushButton> +#include <QtGui/QMenu> +#include <QtGui/QLineEdit> +#include <QtGui/QFormLayout> +#include <QtGui/QHBoxLayout> +#include <QtGui/QToolButton> +#include <QtGui/QSpacerItem> enum { debug = 0 }; @@ -104,8 +111,12 @@ QList<int> selectedRows(const QAbstractItemView *view) } // ----------- SubmitEditorWidgetPrivate + struct SubmitEditorWidgetPrivate { + // A pair of position/action to extend context menus + typedef QPair<int, QPointer<QAction> > AdditionalContextMenuAction; + SubmitEditorWidgetPrivate(); Ui::SubmitEditorWidget m_ui; @@ -113,13 +124,22 @@ struct SubmitEditorWidgetPrivate bool m_filesChecked; int m_fileNameColumn; int m_activatedRow; + + QList<AdditionalContextMenuAction> descriptionEditContextMenuActions; + QFormLayout *m_fieldLayout; + // Field entries (label, line edits) + typedef QPair<QString, QLineEdit*> FieldEntry; + QList<FieldEntry> m_fieldEntries; + QSignalMapper *m_fieldSignalMapper; }; SubmitEditorWidgetPrivate::SubmitEditorWidgetPrivate() : m_filesSelected(false), m_filesChecked(false), m_fileNameColumn(1), - m_activatedRow(-1) + m_activatedRow(-1), + m_fieldLayout(0), + m_fieldSignalMapper(0) { } @@ -128,6 +148,10 @@ SubmitEditorWidget::SubmitEditorWidget(QWidget *parent) : m_d(new SubmitEditorWidgetPrivate) { m_d->m_ui.setupUi(this); + m_d->m_ui.description->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_d->m_ui.description, SIGNAL(customContextMenuRequested(QPoint)), + this, SLOT(editorCustomContextMenuRequested(QPoint))); + // File List m_d->m_ui.fileView->setSelectionMode(QAbstractItemView::ExtendedSelection); m_d->m_ui.fileView->setRootIsDecorated(false); @@ -212,7 +236,18 @@ QString SubmitEditorWidget::trimmedDescriptionText() const QString SubmitEditorWidget::descriptionText() const { - return m_d->m_ui.description->toPlainText(); + QString rc = m_d->m_ui.description->toPlainText(); + // append field entries + foreach(const SubmitEditorWidgetPrivate::FieldEntry &fe, m_d->m_fieldEntries) { + const QString fieldText = fe.second->text().trimmed(); + if (!fieldText.isEmpty()) { + rc += fe.first; + rc += QLatin1Char(' '); + rc += fieldText; + rc += QLatin1Char('\n'); + } + } + return rc; } void SubmitEditorWidget::setDescriptionText(const QString &text) @@ -381,6 +416,74 @@ void SubmitEditorWidget::insertTopWidget(QWidget *w) m_d->m_ui.vboxLayout->insertWidget(0, w); } +void SubmitEditorWidget::addDescriptionEditContextMenuAction(QAction *a) +{ + m_d->descriptionEditContextMenuActions.push_back(SubmitEditorWidgetPrivate::AdditionalContextMenuAction(-1, a)); +} + +void SubmitEditorWidget::insertDescriptionEditContextMenuAction(int pos, QAction *a) +{ + m_d->descriptionEditContextMenuActions.push_back(SubmitEditorWidgetPrivate::AdditionalContextMenuAction(pos, a)); +} + +void SubmitEditorWidget::editorCustomContextMenuRequested(const QPoint &pos) +{ + QMenu *menu = m_d->m_ui.description->createStandardContextMenu(); + // Extend + foreach (const SubmitEditorWidgetPrivate::AdditionalContextMenuAction &a, m_d->descriptionEditContextMenuActions) { + if (a.second) { + if (a.first >= 0) { + menu->insertAction(menu->actions().at(a.first), a.second); + } else { + menu->addAction(a.second); + } + } + } + menu->exec(m_d->m_ui.description->mapToGlobal(pos)); + delete menu; +} + +QLineEdit *SubmitEditorWidget::addField(const QString &label, bool hasDialogButton) +{ + // Insert the form layout below the editor + if (!m_d->m_fieldLayout) { + QHBoxLayout *outerLayout = new QHBoxLayout; + m_d->m_fieldLayout = new QFormLayout; + outerLayout->addLayout(m_d->m_fieldLayout); + outerLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); + QBoxLayout *descrLayout = qobject_cast<QBoxLayout*>(m_d->m_ui.descriptionBox->layout()); + Q_ASSERT(descrLayout); + descrLayout->addLayout(outerLayout); + } + if (hasDialogButton && !m_d->m_fieldSignalMapper) { + m_d->m_fieldSignalMapper = new QSignalMapper; + connect(m_d->m_fieldSignalMapper, SIGNAL(mapped(int)), this, SIGNAL(fieldDialogRequested(int))); + } + // Add a field row consisting of label and line edit + QLineEdit *lineEdit = new QLineEdit; + QHBoxLayout *fieldLayout = new QHBoxLayout; + fieldLayout->addWidget(lineEdit); + if (hasDialogButton) { + QToolButton *dialogButton = new QToolButton; + dialogButton->setText(tr("...")); + connect(dialogButton, SIGNAL(clicked()), m_d->m_fieldSignalMapper, SLOT(map())); + m_d->m_fieldSignalMapper->setMapping(dialogButton, m_d->m_fieldEntries.size()); + fieldLayout->addWidget(dialogButton); + } + QToolButton *clearButton = new QToolButton; + clearButton->setText(tr("Clear")); + connect(clearButton, SIGNAL(clicked()), lineEdit, SLOT(clear())); + fieldLayout->addWidget(clearButton); + m_d->m_fieldLayout->addRow(label, fieldLayout); + m_d->m_fieldEntries.push_back(SubmitEditorWidgetPrivate::FieldEntry(label, lineEdit)); + return lineEdit; +} + +QLineEdit *SubmitEditorWidget::fieldLineEdit(int i) const +{ + return m_d->m_fieldEntries.at(i).second; +} + } // namespace Utils } // namespace Core diff --git a/src/libs/utils/submiteditorwidget.h b/src/libs/utils/submiteditorwidget.h index 74dd56d006..e77da4379e 100644 --- a/src/libs/utils/submiteditorwidget.h +++ b/src/libs/utils/submiteditorwidget.h @@ -41,6 +41,7 @@ class QListWidgetItem; class QAction; class QAbstractItemModel; class QModelIndex; +class QLineEdit; QT_END_NAMESPACE namespace Core { @@ -104,10 +105,20 @@ public: QPlainTextEdit *descriptionEdit() const; + void addDescriptionEditContextMenuAction(QAction *a); + void insertDescriptionEditContextMenuAction(int pos, QAction *a); + + // Fields are additional fields consisting of a Label and a Line Edit. + // A field dialog is wired to a button labeled "..." that pops up a chooser + // resulting in text being set + QLineEdit *addField(const QString &label, bool hasDialogButton); + QLineEdit *fieldLineEdit(int i) const; + signals: void diffSelected(const QStringList &); void fileSelectionChanged(bool someFileSelected); void fileCheckStateChanged(bool someFileChecked); + void fieldDialogRequested(int); protected: virtual void changeEvent(QEvent *e); @@ -120,6 +131,7 @@ private slots: void updateActions(); void updateSubmitAction(); void updateDiffAction(); + void editorCustomContextMenuRequested(const QPoint &); private: bool hasSelection() const; diff --git a/src/plugins/git/gitplugin.cpp b/src/plugins/git/gitplugin.cpp index 29af542988..cb80541e0f 100644 --- a/src/plugins/git/gitplugin.cpp +++ b/src/plugins/git/gitplugin.cpp @@ -673,12 +673,14 @@ bool GitPlugin::editorAboutToClose(Core::IEditor *iEditor) if (editorFile.absoluteFilePath() != changeFile.absoluteFilePath()) return true; // Prompt user. - const QMessageBox::StandardButton answer = QMessageBox::question(m_core->mainWindow(), tr("Closing git editor"), tr("Do you want to commit the change?"), - QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel, QMessageBox::Yes); + const VCSBase::VCSBaseSubmitEditor::PromptSubmitResult answer = + editor->promptSubmit(tr("Closing git editor"), + tr("Do you want to commit the change?"), + tr("The commit message check failed. Do you want to commit the change?")); switch (answer) { - case QMessageBox::Cancel: + case VCSBase::VCSBaseSubmitEditor::SubmitCanceled: return false; // Keep editing and change file - case QMessageBox::No: + case VCSBase::VCSBaseSubmitEditor::SubmitDiscarded: cleanChangeTmpFile(); return true; // Cancel all default: diff --git a/src/plugins/perforce/perforceplugin.cpp b/src/plugins/perforce/perforceplugin.cpp index 65efbc977f..2b068e2982 100644 --- a/src/plugins/perforce/perforceplugin.cpp +++ b/src/plugins/perforce/perforceplugin.cpp @@ -961,21 +961,24 @@ bool PerforcePlugin::editorAboutToClose(Core::IEditor *editor) Core::IFile *fileIFace = editor->file(); if (!fileIFace) return true; + const PerforceSubmitEditor *perforceEditor = qobject_cast<PerforceSubmitEditor *>(editor); + if (!perforceEditor) + return true; QFileInfo editorFile(fileIFace->fileName()); QFileInfo changeFile(m_changeTmpFile->fileName()); - if (editorFile.absoluteFilePath() == changeFile.absoluteFilePath()) { - const QMessageBox::StandardButton answer = - QMessageBox::question(core->mainWindow(), - tr("Closing p4 Editor"), - tr("Do you want to submit this change list?"), - QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel, QMessageBox::Yes); - if (answer == QMessageBox::Cancel) + if (editorFile.absoluteFilePath() == changeFile.absoluteFilePath()) { + const VCSBase::VCSBaseSubmitEditor::PromptSubmitResult answer = + perforceEditor->promptSubmit(tr("Closing p4 Editor"), + tr("Do you want to submit this change list?"), + tr("The commit message check failed. Do you want to submit this change list")); + + if (answer == VCSBase::VCSBaseSubmitEditor::SubmitCanceled) return false; core->fileManager()->blockFileChange(fileIFace); fileIFace->save(); core->fileManager()->unblockFileChange(fileIFace); - if (answer == QMessageBox::Yes) { + if (answer == VCSBase::VCSBaseSubmitEditor::SubmitConfirmed) { QByteArray change = m_changeTmpFile->readAll(); m_changeTmpFile->close(); if (!checkP4Command()) { diff --git a/src/plugins/subversion/subversionplugin.cpp b/src/plugins/subversion/subversionplugin.cpp index 67e56eacf6..189b83e0e3 100644 --- a/src/plugins/subversion/subversionplugin.cpp +++ b/src/plugins/subversion/subversionplugin.cpp @@ -464,14 +464,14 @@ bool SubversionPlugin::editorAboutToClose(Core::IEditor *iEditor) return true; // Oops?! // Prompt user. - const QMessageBox::StandardButton answer = QMessageBox::question( - Core::ICore::instance()->mainWindow(), tr("Closing Subversion Editor"), - tr("Do you want to commit the change?"), - QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel, QMessageBox::Yes); + const VCSBase::VCSBaseSubmitEditor::PromptSubmitResult answer = + editor->promptSubmit(tr("Closing Subversion Editor"), + tr("Do you want to commit the change?"), + tr("The commit message check failed. Do you want to commit the change?")); switch (answer) { - case QMessageBox::Cancel: + case VCSBase::VCSBaseSubmitEditor::SubmitCanceled: return false; // Keep editing and change file - case QMessageBox::No: + case VCSBase::VCSBaseSubmitEditor::SubmitDiscarded: cleanChangeTmpFile(); return true; // Cancel all default: diff --git a/src/plugins/vcsbase/nicknamedialog.cpp b/src/plugins/vcsbase/nicknamedialog.cpp new file mode 100644 index 0000000000..d8578aa7e4 --- /dev/null +++ b/src/plugins/vcsbase/nicknamedialog.cpp @@ -0,0 +1,251 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +**************************************************************************/ + +#include "nicknamedialog.h" +#include "ui_nicknamedialog.h" + +#include <QtCore/QDebug> +#include <QtCore/QFile> +#include <QtGui/QPushButton> +#include <QtGui/QStandardItemModel> +#include <QtGui/QSortFilterProxyModel> + +namespace VCSBase { +namespace Internal { + +struct NickEntry { + void clear(); + bool parse(const QString &); + QString nickName() const; + + QString name; + QString email; + QString aliasName; + QString aliasEmail; +}; + +void NickEntry::clear() +{ + name.clear(); + email.clear(); + aliasName.clear(); + aliasEmail.clear(); +} + +// Parse "Hans Mustermann <HM@acme.de> [Alias [<alias@acme.de>]]" + +bool NickEntry::parse(const QString &l) +{ + clear(); + const QChar lessThan = QLatin1Char('<'); + const QChar greaterThan = QLatin1Char('>'); + // Get first name/mail pair + int mailPos = l.indexOf(lessThan); + if (mailPos == -1) + return false; + name = l.mid(0, mailPos).trimmed(); + mailPos++; + const int mailEndPos = l.indexOf(greaterThan, mailPos); + if (mailEndPos == -1) + return false; + email = l.mid(mailPos, mailEndPos - mailPos); + // get optional 2nd name/mail pair + const int aliasNameStart = mailEndPos + 1; + if (aliasNameStart >= l.size()) + return true; + int aliasMailPos = l.indexOf(lessThan, aliasNameStart); + if (aliasMailPos == -1) { + aliasName =l.mid(aliasNameStart, l.size() - aliasNameStart).trimmed(); + return true; + } + aliasName = l.mid(aliasNameStart, aliasMailPos - aliasNameStart).trimmed(); + aliasMailPos++; + const int aliasMailEndPos = l.indexOf(greaterThan, aliasMailPos); + if (aliasMailEndPos == -1) + return true; + aliasEmail = l.mid(aliasMailPos, aliasMailEndPos - aliasMailPos); + return true; +} + +// Format "Hans Mustermann <HM@acme.de>" +static inline QString formatNick(const QString &name, const QString &email) +{ + QString rc = name; + if (!email.isEmpty()) { + rc += QLatin1String(" <"); + rc += email; + rc += QLatin1Char('>'); + } + return rc; +} + +QString NickEntry::nickName() const +{ + return aliasName.isEmpty() ? formatNick(name, email) : formatNick(aliasName, aliasEmail); +} + +// Sort by name +bool operator<(const NickEntry &n1, const NickEntry &n2) +{ + return n1.name < n2.name; +} + +QDebug operator<<(QDebug d, const NickEntry &e) +{ + d.nospace() << "Name='" << e.name << "' Mail='" << e.email + << " Alias='" << e.aliasName << " AliasEmail='" << e.aliasEmail << "'\n"; + return d; +} + +// Globally cached list +static QList<NickEntry> &nickList() +{ + static QList<NickEntry> rc; + return rc; +} + +// Create a model populated with the names +static QStandardItemModel *createModel(QObject *parent) +{ + QStandardItemModel *rc = new QStandardItemModel(parent); + QStringList headers; + headers << NickNameDialog::tr("Name") + << NickNameDialog::tr("E-mail") + << NickNameDialog::tr("Alias") + << NickNameDialog::tr("Alias e-mail"); + rc->setHorizontalHeaderLabels(headers); + foreach(const NickEntry &ne, nickList()) { + QList<QStandardItem *> row; + row.push_back(new QStandardItem(ne.name)); + row.push_back(new QStandardItem(ne.email)); + row.push_back(new QStandardItem(ne.aliasName)); + row.push_back(new QStandardItem(ne.aliasEmail)); + rc->appendRow(row); + } + return rc; +} + +NickNameDialog::NickNameDialog(QWidget *parent) : + QDialog(parent), + m_ui(new Ui::NickNameDialog), + m_filterModel(new QSortFilterProxyModel(this)) +{ + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + m_ui->setupUi(this); + okButton()->setEnabled(false); + + // Populate model and grow tree to accommodate it + m_filterModel->setSourceModel(createModel(this)); + m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_ui->filterTreeView->setModel(m_filterModel); + const int columnCount = m_filterModel->columnCount(); + int treeWidth = 0; + for (int c = 0; c < columnCount; c++) { + m_ui->filterTreeView->resizeColumnToContents(c); + treeWidth += m_ui->filterTreeView->columnWidth(c); + } + m_ui->filterTreeView->setMinimumWidth(treeWidth + 20); + connect(m_ui->filterTreeView, SIGNAL(doubleClicked(QModelIndex)), this, + SLOT(slotDoubleClicked(QModelIndex))); + connect(m_ui->filterTreeView->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), + this, SLOT(slotCurrentItemChanged(QModelIndex))); + connect(m_ui->filterLineEdit, SIGNAL(textChanged(QString)), + m_filterModel, SLOT(setFilterFixedString(QString))); +} + +NickNameDialog::~NickNameDialog() +{ + delete m_ui; +} + +QPushButton *NickNameDialog::okButton() const +{ + return m_ui->buttonBox->button(QDialogButtonBox::Ok); +} + +void NickNameDialog::slotCurrentItemChanged(const QModelIndex &index) +{ + okButton()->setEnabled(index.isValid()); +} + +void NickNameDialog::slotDoubleClicked(const QModelIndex &) +{ + if (okButton()->isEnabled()) + okButton()->animateClick(); +} + +QString NickNameDialog::nickName() const +{ + const QModelIndex index = m_ui->filterTreeView->selectionModel()->currentIndex(); + if (index.isValid()) { + const QModelIndex sourceIndex = m_filterModel->mapToSource(index); + return nickList().at(sourceIndex.row()).nickName(); + } + return QString(); +} + +void NickNameDialog::clearNickNames() +{ + nickList().clear(); +} + +bool NickNameDialog::readNickNamesFromMailCapFile(const QString &fileName, QString *errorMessage) +{ + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly|QIODevice::Text)) { + *errorMessage = tr("Cannot open '%1': %2").arg(fileName, file.errorString()); + return false; + } + // Split into lines and read + QList<NickEntry> &nl = nickList(); + nl.clear(); + NickEntry entry; + const QStringList lines = QString::fromUtf8(file.readAll()).trimmed().split(QLatin1Char('\n')); + const int count = lines.size(); + for (int i = 0; i < count; i++) { + if (entry.parse(lines.at(i))) { + nl.push_back(entry); + } else { + qWarning("%s: Invalid mail cap entry at line %d: '%s'\n", qPrintable(fileName), i + 1, qPrintable(lines.at(i))); + } + } + qStableSort(nl); + return true; +} + +QStringList NickNameDialog::nickNameList() +{ + QStringList rc; + foreach(const NickEntry &ne, nickList()) + rc.push_back(ne.nickName()); + return rc; +} + +} +} diff --git a/src/plugins/vcsbase/nicknamedialog.h b/src/plugins/vcsbase/nicknamedialog.h new file mode 100644 index 0000000000..f8d32b7ee4 --- /dev/null +++ b/src/plugins/vcsbase/nicknamedialog.h @@ -0,0 +1,81 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +**************************************************************************/ + +#ifndef NICKNAMEDIALOG_H +#define NICKNAMEDIALOG_H + +#include <QtGui/QDialog> + +QT_BEGIN_NAMESPACE +namespace Ui { + class NickNameDialog; +} +class QSortFilterProxyModel; +class QModelIndex; +class QPushButton; +QT_END_NAMESPACE + +namespace VCSBase { +namespace Internal { + +/* Nick name dialog: Manages a list of users read from an extended + * mail cap file, consisting of 4 columns: + * "Name Mail [AliasName [AliasMail]]". + * The names can be used for insertion into "RevBy:" fields; aliases will + * be preferred. */ + +class NickNameDialog : public QDialog { + Q_OBJECT +public: + explicit NickNameDialog(QWidget *parent = 0); + virtual ~NickNameDialog(); + + QString nickName() const; + + // Fill/clear the global nick name cache + static bool readNickNamesFromMailCapFile(const QString &file, QString *errorMessage); + static void clearNickNames(); + // Return a list for a completer on the field line edits + static QStringList nickNameList(); + +private slots: + void slotCurrentItemChanged(const QModelIndex &); + void slotDoubleClicked(const QModelIndex &); + +private: + QPushButton *okButton() const; + + Ui::NickNameDialog *m_ui; + QSortFilterProxyModel *m_filterModel; +}; + +} // namespace Internal +} // namespace VCSBase + +#endif // NICKNAMEDIALOG_H diff --git a/src/plugins/vcsbase/nicknamedialog.ui b/src/plugins/vcsbase/nicknamedialog.ui new file mode 100644 index 0000000000..6d7217d700 --- /dev/null +++ b/src/plugins/vcsbase/nicknamedialog.ui @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>NickNameDialog</class> + <widget class="QDialog" name="NickNameDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>618</width> + <height>414</height> + </rect> + </property> + <property name="windowTitle"> + <string>Nick Names</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="filterLabel"> + <property name="text"> + <string>Filter:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="filterLineEdit"/> + </item> + <item> + <widget class="QToolButton" name="filterClearToolButton"> + <property name="text"> + <string>Clear</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QTreeView" name="filterTreeView"/> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>NickNameDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>252</x> + <y>405</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>NickNameDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>293</x> + <y>405</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>filterClearToolButton</sender> + <signal>clicked()</signal> + <receiver>filterLineEdit</receiver> + <slot>clear()</slot> + <hints> + <hint type="sourcelabel"> + <x>263</x> + <y>14</y> + </hint> + <hint type="destinationlabel"> + <x>198</x> + <y>19</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/plugins/vcsbase/vcsbase.pro b/src/plugins/vcsbase/vcsbase.pro index d34fe84e7c..58bbc0dd4e 100644 --- a/src/plugins/vcsbase/vcsbase.pro +++ b/src/plugins/vcsbase/vcsbase.pro @@ -14,7 +14,11 @@ HEADERS += vcsbase_global.h \ basevcseditorfactory.h \ submiteditorfile.h \ basevcssubmiteditorfactory.h \ - submitfilemodel.h + submitfilemodel.h \ + vcsbasesettings.h \ + vcsbasesettingspage.h \ + nicknamedialog.h + SOURCES += vcsbaseplugin.cpp \ baseannotationhighlighter.cpp \ diffhighlighter.cpp \ @@ -24,5 +28,12 @@ SOURCES += vcsbaseplugin.cpp \ basevcseditorfactory.cpp \ submiteditorfile.cpp \ basevcssubmiteditorfactory.cpp \ - submitfilemodel.cpp -RESOURCES = vcsbase.qrc + submitfilemodel.cpp \ + vcsbasesettings.cpp \ + vcsbasesettingspage.cpp \ + nicknamedialog.cpp + +RESOURCES += vcsbase.qrc + +FORMS += vcsbasesettingspage.ui \ + nicknamedialog.ui diff --git a/src/plugins/vcsbase/vcsbaseconstants.h b/src/plugins/vcsbase/vcsbaseconstants.h index 6ce3141cb6..bb180213fb 100644 --- a/src/plugins/vcsbase/vcsbaseconstants.h +++ b/src/plugins/vcsbase/vcsbaseconstants.h @@ -36,6 +36,7 @@ namespace VCSBase { namespace Constants { const char * const VCS_SETTINGS_CATEGORY = QT_TRANSLATE_NOOP("VCSBase", "Version Control System"); +const char * const VCS_COMMON_SETTINGS_ID = QT_TRANSLATE_NOOP("VCSBase", "Common"); namespace Internal { enum { debug = 0 }; diff --git a/src/plugins/vcsbase/vcsbaseplugin.cpp b/src/plugins/vcsbase/vcsbaseplugin.cpp index 1aa9a32ff6..0de678e80e 100644 --- a/src/plugins/vcsbase/vcsbaseplugin.cpp +++ b/src/plugins/vcsbase/vcsbaseplugin.cpp @@ -29,6 +29,7 @@ #include "vcsbaseplugin.h" #include "diffhighlighter.h" +#include "vcsbasesettingspage.h" #include <coreplugin/icore.h> #include <coreplugin/coreconstants.h> @@ -42,7 +43,8 @@ namespace Internal { VCSBasePlugin *VCSBasePlugin::m_instance = 0; -VCSBasePlugin::VCSBasePlugin() +VCSBasePlugin::VCSBasePlugin() : + m_settingsPage(0) { m_instance = this; } @@ -61,6 +63,8 @@ bool VCSBasePlugin::initialize(const QStringList &arguments, QString *errorMessa if (!core->mimeDatabase()->addMimeTypes(QLatin1String(":/vcsbase/VCSBase.mimetypes.xml"), errorMessage)) return false; + m_settingsPage = new VCSBaseSettingsPage; + addAutoReleasedObject(m_settingsPage); return true; } @@ -73,6 +77,11 @@ VCSBasePlugin *VCSBasePlugin::instance() return m_instance; } +VCSBaseSettings VCSBasePlugin::settings() const +{ + return m_settingsPage->settings(); +} + } // namespace Internal } // namespace VCSBase diff --git a/src/plugins/vcsbase/vcsbaseplugin.h b/src/plugins/vcsbase/vcsbaseplugin.h index 0adbd5b8a6..7b845a3055 100644 --- a/src/plugins/vcsbase/vcsbaseplugin.h +++ b/src/plugins/vcsbase/vcsbaseplugin.h @@ -37,6 +37,9 @@ namespace VCSBase { namespace Internal { +struct VCSBaseSettings; +class VCSBaseSettingsPage; + class VCSBasePlugin : public ExtensionSystem::IPlugin { Q_OBJECT @@ -51,8 +54,11 @@ public: static VCSBasePlugin *instance(); + VCSBaseSettings settings() const; + private: static VCSBasePlugin *m_instance; + VCSBaseSettingsPage *m_settingsPage; }; } // namespace Internal diff --git a/src/plugins/vcsbase/vcsbasesettings.cpp b/src/plugins/vcsbase/vcsbasesettings.cpp new file mode 100644 index 0000000000..d2f6951bbd --- /dev/null +++ b/src/plugins/vcsbase/vcsbasesettings.cpp @@ -0,0 +1,78 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +**************************************************************************/ + +#include "vcsbasesettings.h" + +#include <QtCore/QSettings> +#include <QtCore/QDebug> + +static const char *settingsGroupC = "VCS"; +static const char *nickNameMailMapKeyC = "NickNameMailMap"; +static const char *nickNameFieldListFileKeyC = "NickNameFieldListFile"; +static const char *promptForSubmitKeyC = "PromptForSubmit"; +static const char *submitMessageCheckScriptKeyC = "SubmitMessageCheckScript"; + +namespace VCSBase { +namespace Internal { + +VCSBaseSettings::VCSBaseSettings() : + promptForSubmit(true) +{ +} + +void VCSBaseSettings::toSettings(QSettings *s) const +{ + s->beginGroup(QLatin1String(settingsGroupC)); + s->setValue(QLatin1String(nickNameMailMapKeyC), nickNameMailMap); + s->setValue(QLatin1String(nickNameFieldListFileKeyC), nickNameFieldListFile); + s->setValue(QLatin1String(submitMessageCheckScriptKeyC), submitMessageCheckScript); + s->setValue(QLatin1String(promptForSubmitKeyC), promptForSubmit); + s->endGroup(); +} + +void VCSBaseSettings::fromSettings(QSettings *s) +{ + s->beginGroup(QLatin1String(settingsGroupC)); + nickNameMailMap = s->value(QLatin1String(nickNameMailMapKeyC), QString()).toString(); + nickNameFieldListFile = s->value(QLatin1String(nickNameFieldListFileKeyC), QString()).toString(); + submitMessageCheckScript = s->value(QLatin1String(submitMessageCheckScriptKeyC), QString()).toString(); + promptForSubmit = s->value(QLatin1String(promptForSubmitKeyC), QVariant(true)).toBool(); + s->endGroup(); +} + +bool VCSBaseSettings::equals(const VCSBaseSettings &rhs) const +{ + return promptForSubmit == rhs.promptForSubmit + && nickNameMailMap == rhs.nickNameMailMap + && nickNameFieldListFile == rhs.nickNameFieldListFile + && submitMessageCheckScript == rhs.submitMessageCheckScript; +} + +} +} diff --git a/src/plugins/vcsbase/vcsbasesettings.h b/src/plugins/vcsbase/vcsbasesettings.h new file mode 100644 index 0000000000..b4efd59d33 --- /dev/null +++ b/src/plugins/vcsbase/vcsbasesettings.h @@ -0,0 +1,66 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +**************************************************************************/ + +#ifndef VCSBASESETTINGS_H +#define VCSBASESETTINGS_H + +#include <QtCore/QString> +#include <QtGui/QWidget> + +QT_BEGIN_NAMESPACE +class QSettings; +QT_END_NAMESPACE + +namespace VCSBase { +namespace Internal { + +// Common VCS settings, message check script and user nick names. +struct VCSBaseSettings { + VCSBaseSettings(); + + bool promptForSubmit; + + QString nickNameMailMap; + QString nickNameFieldListFile; + + QString submitMessageCheckScript; + + void toSettings(QSettings *) const; + void fromSettings(QSettings *); + + bool equals(const VCSBaseSettings &rhs) const; +}; + +inline bool operator==(const VCSBaseSettings &s1, const VCSBaseSettings &s2) { return s1.equals(s2); } +inline bool operator!=(const VCSBaseSettings &s1, const VCSBaseSettings &s2) { return !s1.equals(s2); } + +} // namespace Internal +} // namespace VCSBase + +#endif // VCSBASESETTINGS_H diff --git a/src/plugins/vcsbase/vcsbasesettingspage.cpp b/src/plugins/vcsbase/vcsbasesettingspage.cpp new file mode 100644 index 0000000000..0cc528a2e7 --- /dev/null +++ b/src/plugins/vcsbase/vcsbasesettingspage.cpp @@ -0,0 +1,145 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +**************************************************************************/ + +#include "vcsbasesettingspage.h" +#include "vcsbaseconstants.h" +#include "nicknamedialog.h" + +#include "ui_vcsbasesettingspage.h" + +#include <coreplugin/icore.h> +#include <extensionsystem/pluginmanager.h> + +#include <QtCore/QDebug> +#include <QtCore/QCoreApplication> +#include <QtGui/QMessageBox> + +namespace VCSBase { +namespace Internal { + +// ------------------ VCSBaseSettingsWidget + +VCSBaseSettingsWidget::VCSBaseSettingsWidget(QWidget *parent) : + QWidget(parent), + m_ui(new Ui::VCSBaseSettingsPage) +{ + m_ui->setupUi(this); + m_ui->submitMessageCheckScriptChooser->setExpectedKind(Core::Utils::PathChooser::Command); + m_ui->nickNameFieldsFileChooser->setExpectedKind(Core::Utils::PathChooser::File); + m_ui->nickNameMailMapChooser->setExpectedKind(Core::Utils::PathChooser::File); +} + +VCSBaseSettingsWidget::~VCSBaseSettingsWidget() +{ + delete m_ui; +} + +VCSBaseSettings VCSBaseSettingsWidget::settings() const +{ + VCSBaseSettings rc; + rc.nickNameMailMap = m_ui->nickNameMailMapChooser->path(); + rc.nickNameFieldListFile = m_ui->nickNameFieldsFileChooser->path(); + rc.submitMessageCheckScript = m_ui->submitMessageCheckScriptChooser->path(); + rc.promptForSubmit = m_ui->promptForSubmitCheckBox->isChecked(); + return rc; +} + +void VCSBaseSettingsWidget::setSettings(const VCSBaseSettings &s) +{ + + m_ui->nickNameMailMapChooser->setPath(s.nickNameMailMap); + m_ui->nickNameFieldsFileChooser->setPath(s.nickNameFieldListFile); + m_ui->submitMessageCheckScriptChooser->setPath(s.submitMessageCheckScript); + m_ui->promptForSubmitCheckBox->setChecked(s.promptForSubmit); +} + +// --------------- VCSBaseSettingsPage +VCSBaseSettingsPage::VCSBaseSettingsPage(QObject *parent) : + Core::IOptionsPage(parent) +{ + m_settings.fromSettings(Core::ICore::instance()->settings()); + updateNickNames(); +} + +void VCSBaseSettingsPage::updateNickNames() +{ + if (m_settings.nickNameMailMap.isEmpty()) { + NickNameDialog::clearNickNames(); + } else { + QString errorMessage; + if (!NickNameDialog::readNickNamesFromMailCapFile(m_settings.nickNameMailMap, &errorMessage)) + qWarning("%s", qPrintable(errorMessage)); + } +} + +VCSBaseSettingsPage::~VCSBaseSettingsPage() +{ +} + +QString VCSBaseSettingsPage::id() const +{ + return QLatin1String(Constants::VCS_COMMON_SETTINGS_ID); +} + +QString VCSBaseSettingsPage::trName() const +{ + return QCoreApplication::translate("VCSBase", Constants::VCS_COMMON_SETTINGS_ID); +} + +QString VCSBaseSettingsPage::category() const +{ + return QLatin1String(Constants::VCS_SETTINGS_CATEGORY); +} + +QString VCSBaseSettingsPage::trCategory() const +{ + return QCoreApplication::translate("VCSBase", Constants::VCS_SETTINGS_CATEGORY); +} + +QWidget *VCSBaseSettingsPage::createPage(QWidget *parent) +{ + m_widget = new VCSBaseSettingsWidget(parent); + m_widget->setSettings(m_settings); + return m_widget; +} + +void VCSBaseSettingsPage::apply() +{ + if (m_widget) { + const VCSBaseSettings newSettings = m_widget->settings(); + if (newSettings != m_settings) { + m_settings = newSettings; + m_settings.toSettings(Core::ICore::instance()->settings()); + updateNickNames(); + } + } +} + +} +} diff --git a/src/plugins/vcsbase/vcsbasesettingspage.h b/src/plugins/vcsbase/vcsbasesettingspage.h new file mode 100644 index 0000000000..4492f66544 --- /dev/null +++ b/src/plugins/vcsbase/vcsbasesettingspage.h @@ -0,0 +1,86 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +**************************************************************************/ + +#ifndef VCSBASESETTINGSPAGE_H +#define VCSBASESETTINGSPAGE_H + +#include "vcsbasesettings.h" +#include <coreplugin/dialogs/ioptionspage.h> +#include <QtCore/QPointer> +#include <QtGui/QWidget> + +QT_BEGIN_NAMESPACE +namespace Ui { + class VCSBaseSettingsPage; +} +QT_END_NAMESPACE + +namespace VCSBase { +namespace Internal { + +class VCSBaseSettingsWidget : public QWidget { + Q_OBJECT +public: + explicit VCSBaseSettingsWidget(QWidget *parent = 0); + virtual ~VCSBaseSettingsWidget(); + + VCSBaseSettings settings() const; + void setSettings(const VCSBaseSettings &s); + +private: + Ui::VCSBaseSettingsPage *m_ui; +}; + +class VCSBaseSettingsPage : public Core::IOptionsPage +{ +public: + explicit VCSBaseSettingsPage(QObject *parent = 0); + virtual ~VCSBaseSettingsPage(); + + virtual QString id() const; + virtual QString trName() const; + virtual QString category() const; + virtual QString trCategory() const; + + virtual QWidget *createPage(QWidget *parent); + virtual void apply(); + virtual void finish() { } + + VCSBaseSettings settings() const { return m_settings; } + +private: + void updateNickNames(); + QPointer<VCSBaseSettingsWidget> m_widget; + VCSBaseSettings m_settings; +}; + +} // namespace Internal +} // namespace VCSBase + +#endif // VCSBASESETTINGSPAGE_H diff --git a/src/plugins/vcsbase/vcsbasesettingspage.ui b/src/plugins/vcsbase/vcsbasesettingspage.ui new file mode 100644 index 0000000000..5636d995ce --- /dev/null +++ b/src/plugins/vcsbase/vcsbasesettingspage.ui @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>VCSBaseSettingsPage</class> + <widget class="QWidget" name="VCSBaseSettingsPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>536</width> + <height>407</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QFormLayout" name="formLayout"> + <property name="fieldGrowthPolicy"> + <enum>QFormLayout::ExpandingFieldsGrow</enum> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="promptForSubmitLabel"> + <property name="text"> + <string>Prompt for submit:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QCheckBox" name="promptForSubmitCheckBox"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="submitMessageCheckScriptLabel"> + <property name="toolTip"> + <string>An executable which is called with the submit message in a temporary file as first argument. It should return with an exit != 0 and a message on standard error to indicate failure.</string> + </property> + <property name="text"> + <string>Submit message check script:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="Core::Utils::PathChooser" name="submitMessageCheckScriptChooser" native="true"/> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="nickNameMailMapLabel"> + <property name="toolTip"> + <string>A file listing user names in 2-column mailmap format: +name <email> alias <email></string> + </property> + <property name="text"> + <string>User name file:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="Core::Utils::PathChooser" name="nickNameMailMapChooser" native="true"/> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="nickNameFieldsFileLabel"> + <property name="toolTip"> + <string>A simple file containing lines with field names like "Reviewed-By:" which will be added below the submit editor.</string> + </property> + <property name="text"> + <string>User fields configuration file:</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="Core::Utils::PathChooser" name="nickNameFieldsFileChooser" native="true"/> + </item> + </layout> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>307</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>Core::Utils::PathChooser</class> + <extends>QWidget</extends> + <header location="global">utils/pathchooser.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp index 473abaf175..7f7c8365ed 100644 --- a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp +++ b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp @@ -28,10 +28,14 @@ **************************************************************************/ #include "vcsbasesubmiteditor.h" +#include "vcsbasesettings.h" +#include "vcsbaseplugin.h" +#include "nicknamedialog.h" #include "submiteditorfile.h" #include <aggregation/aggregate.h> #include <coreplugin/ifile.h> +#include <coreplugin/icore.h> #include <coreplugin/uniqueidmanager.h> #include <coreplugin/actionmanager/actionmanager.h> #include <utils/submiteditorwidget.h> @@ -42,18 +46,31 @@ #include <QtCore/QDebug> #include <QtCore/QDir> +#include <QtCore/QTemporaryFile> +#include <QtCore/QProcess> #include <QtCore/QFile> #include <QtCore/QFileInfo> #include <QtCore/QPointer> #include <QtCore/QTextStream> #include <QtGui/QStyle> #include <QtGui/QToolBar> +#include <QtGui/QAction> +#include <QtGui/QApplication> +#include <QtGui/QMessageBox> +#include <QtGui/QMainWindow> +#include <QtGui/QCompleter> +#include <QtGui/QLineEdit> enum { debug = 0 }; enum { wantToolBar = 0 }; namespace VCSBase { +static inline QString submitMessageCheckScript() +{ + return Internal::VCSBasePlugin::instance()->settings().submitMessageCheckScript; +} + struct VCSBaseSubmitEditorPrivate { VCSBaseSubmitEditorPrivate(const VCSBaseSubmitEditorParameters *parameters, @@ -69,6 +86,8 @@ struct VCSBaseSubmitEditorPrivate QPointer<QAction> m_diffAction; QPointer<QAction> m_submitAction; + + Internal::NickNameDialog *m_nickNameDialog; }; VCSBaseSubmitEditorPrivate::VCSBaseSubmitEditorPrivate(const VCSBaseSubmitEditorParameters *parameters, @@ -77,7 +96,8 @@ VCSBaseSubmitEditorPrivate::VCSBaseSubmitEditorPrivate(const VCSBaseSubmitEditor m_widget(editorWidget), m_toolWidget(0), m_parameters(parameters), - m_file(new VCSBase::Internal::SubmitEditorFile(QLatin1String(m_parameters->mimeType), q)) + m_file(new VCSBase::Internal::SubmitEditorFile(QLatin1String(m_parameters->mimeType), q)), + m_nickNameDialog(0) { m_contexts << Core::UniqueIDManager::instance()->uniqueIdentifier(m_parameters->context); } @@ -93,6 +113,29 @@ VCSBaseSubmitEditor::VCSBaseSubmitEditor(const VCSBaseSubmitEditorParameters *pa connect(m_d->m_widget, SIGNAL(diffSelected(QStringList)), this, SLOT(slotDiffSelectedVCSFiles(QStringList))); connect(m_d->m_widget->descriptionEdit(), SIGNAL(textChanged()), this, SLOT(slotDescriptionChanged())); + const Internal::VCSBaseSettings settings = Internal::VCSBasePlugin::instance()->settings(); + // Add additional context menu settings + if (!settings.submitMessageCheckScript.isEmpty() || !settings.nickNameFieldListFile.isEmpty()) { + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_d->m_widget->addDescriptionEditContextMenuAction(sep); + // Run check action + if (!settings.submitMessageCheckScript.isEmpty()) { + QAction *checkAction = new QAction(tr("Check message"), this); + connect(checkAction, SIGNAL(triggered()), this, SLOT(slotCheckSubmitMessage())); + m_d->m_widget->addDescriptionEditContextMenuAction(checkAction); + } + // Insert nick + if (!settings.nickNameFieldListFile.isEmpty()) { + QAction *insertAction = new QAction(tr("Insert name..."), this); + connect(insertAction, SIGNAL(triggered()), this, SLOT(slotInsertNickName())); + m_d->m_widget->addDescriptionEditContextMenuAction(insertAction); + } + } + // Do we have user fields? + if (!settings.nickNameFieldListFile.isEmpty()) + createUserFields(settings.nickNameFieldListFile); + connect(m_d->m_widget, SIGNAL(fieldDialogRequested(int)), this, SLOT(slotSetFieldNickName(int))); Aggregation::Aggregate *aggregate = new Aggregation::Aggregate; aggregate->add(new Find::BaseTextFind(m_d->m_widget->descriptionEdit())); aggregate->add(this); @@ -105,6 +148,26 @@ VCSBaseSubmitEditor::~VCSBaseSubmitEditor() delete m_d; } +void VCSBaseSubmitEditor::createUserFields(const QString &fieldConfigFile) +{ + QFile fieldFile(fieldConfigFile); + if (!fieldFile.open(QIODevice::ReadOnly|QIODevice::Text)) { + qWarning("%s: Unable to open %s: %s", Q_FUNC_INFO, qPrintable(fieldConfigFile), qPrintable(fieldFile.errorString())); + return; + } + // Parse into fields + const QStringList fields = QString::fromUtf8(fieldFile.readAll()).trimmed().split(QLatin1Char('\n')); + if (fields.empty()) + return; + // Create a completer on user names + QCompleter *completer = new QCompleter(Internal::NickNameDialog::nickNameList(), this); + foreach(const QString &field, fields) { + const QString trimmedField = field.trimmed(); + if (!trimmedField.isEmpty()) + m_d->m_widget->addField(trimmedField, true)->setCompleter(completer); + } +} + void VCSBaseSubmitEditor::registerActions(QAction *editorUndoAction, QAction *editorRedoAction, QAction *submitAction, QAction *diffAction)\ { @@ -139,7 +202,6 @@ void VCSBaseSubmitEditor::setFileListSelectionMode(QAbstractItemView::SelectionM m_d->m_widget->setFileListSelectionMode(sm); } - void VCSBaseSubmitEditor::slotDescriptionChanged() { } @@ -304,6 +366,118 @@ bool VCSBaseSubmitEditor::setFileContents(const QString &contents) return true; } +enum { checkDialogMinimumWidth = 500 }; + +VCSBaseSubmitEditor::PromptSubmitResult + VCSBaseSubmitEditor::promptSubmit(const QString &title, const QString &question, const QString &checkFailureQuestion) const +{ + QString errorMessage; + QMessageBox::StandardButton answer = QMessageBox::Yes; + + QWidget *parent = Core::ICore::instance()->mainWindow(); + // Pop up a message depending on whether the check succeeded and the + // user wants to be prompted + if (checkSubmitMessage(&errorMessage)) { + // Check ok, do prompt? + if (Internal::VCSBasePlugin::instance()->settings().promptForSubmit) { + answer = QMessageBox::question(parent, title, question, + QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel, + QMessageBox::Yes); + } + } else { + // Check failed. + QMessageBox msgBox(QMessageBox::Question, title, checkFailureQuestion, + QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel, parent); + msgBox.setDefaultButton(QMessageBox::Cancel); + msgBox.setInformativeText(errorMessage); + msgBox.setMinimumWidth(checkDialogMinimumWidth); + answer = static_cast<QMessageBox::StandardButton>(msgBox.exec()); + } + switch (answer) { + case QMessageBox::Cancel: + return SubmitCanceled; + case QMessageBox::No: + return SubmitDiscarded; + default: + break; + } + return SubmitConfirmed; +} + +QString VCSBaseSubmitEditor::promptForNickName() +{ + if (!m_d->m_nickNameDialog) + m_d->m_nickNameDialog = new Internal::NickNameDialog(m_d->m_widget); + if (m_d->m_nickNameDialog->exec() == QDialog::Accepted) + return m_d->m_nickNameDialog->nickName(); + return QString(); +} + +void VCSBaseSubmitEditor::slotInsertNickName() +{ + const QString nick = promptForNickName(); + if (!nick.isEmpty()) + m_d->m_widget->descriptionEdit()->textCursor().insertText(nick); +} + +void VCSBaseSubmitEditor::slotSetFieldNickName(int i) +{ + const QString nick = promptForNickName(); + if (!nick.isEmpty()) + m_d->m_widget->fieldLineEdit(i)->setText(nick); +} + +void VCSBaseSubmitEditor::slotCheckSubmitMessage() +{ + QString errorMessage; + if (!checkSubmitMessage(&errorMessage)) { + QMessageBox msgBox(QMessageBox::Warning, tr("Submit Message Check failed"), + errorMessage, QMessageBox::Ok, m_d->m_widget); + msgBox.setMinimumWidth(checkDialogMinimumWidth); + msgBox.exec(); + } +} + +bool VCSBaseSubmitEditor::checkSubmitMessage(QString *errorMessage) const +{ + const QString checkScript = submitMessageCheckScript(); + if (checkScript.isEmpty()) + return true; + // Write out message + QString tempFilePattern = QDir::tempPath(); + if (!tempFilePattern.endsWith(QDir::separator())) + tempFilePattern += QDir::separator(); + tempFilePattern += QLatin1String("msgXXXXXX.txt"); + QTemporaryFile messageFile(tempFilePattern); + messageFile.setAutoRemove(true); + if (!messageFile.open()) { + *errorMessage = tr("Unable to open '%1': %2").arg(messageFile.fileName(), messageFile.errorString()); + return false; + } + const QString messageFileName = messageFile.fileName(); + messageFile.write(fileContents().toUtf8()); + messageFile.close(); + // Run check process + QProcess checkProcess; + checkProcess.start(checkScript, QStringList(messageFileName)); + if (!checkProcess.waitForStarted()) { + *errorMessage = tr("The check script '%1' could not be started: %2").arg(checkScript, checkProcess.errorString()); + return false; + } + if (!checkProcess.waitForFinished()) { + *errorMessage = tr("The check script '%1' could not be run: %2").arg(checkScript, checkProcess.errorString()); + return false; + } + const int exitCode = checkProcess.exitCode(); + if (exitCode != 0) { + *errorMessage = QString::fromLocal8Bit(checkProcess.readAllStandardError()); + if (errorMessage->isEmpty()) + *errorMessage = tr("The check script returned exit code %1.").arg(exitCode); + return false; + } + return true; +} + QIcon VCSBaseSubmitEditor::diffIcon() { return QIcon(QLatin1String(":/vcsbase/images/diff.png")); diff --git a/src/plugins/vcsbase/vcsbasesubmiteditor.h b/src/plugins/vcsbase/vcsbasesubmiteditor.h index 3dcc4e4d29..7b3b3fa358 100644 --- a/src/plugins/vcsbase/vcsbasesubmiteditor.h +++ b/src/plugins/vcsbase/vcsbasesubmiteditor.h @@ -50,7 +50,9 @@ namespace Core { } namespace VCSBase { - +namespace Internal { + struct VCSBaseSettings; +} struct VCSBaseSubmitEditorPrivate; /* Utility struct to parametrize a VCSBaseSubmitEditor. */ @@ -104,6 +106,12 @@ public: virtual ~VCSBaseSubmitEditor(); + // A utility routine to be called when clsing a submit editor. + // Runs checks on the message and prompts according to configuration. + enum PromptSubmitResult { SubmitConfirmed, SubmitCanceled, SubmitDiscarded }; + PromptSubmitResult promptSubmit(const QString &title, const QString &question, + const QString &checkFailureQuestion) const; + int fileNameColumn() const; void setFileNameColumn(int c); @@ -147,6 +155,9 @@ private slots: void slotDiffSelectedVCSFiles(const QStringList &rawList); bool save(const QString &fileName); void slotDescriptionChanged(); + void slotCheckSubmitMessage(); + void slotInsertNickName(); + void slotSetFieldNickName(int); protected: /* These hooks allow for modifying the contents that goes to @@ -156,6 +167,10 @@ protected: virtual bool setFileContents(const QString &contents); private: + void createUserFields(const QString &fieldConfigFile); + bool checkSubmitMessage(QString *errorMessage) const; + QString promptForNickName(); + VCSBaseSubmitEditorPrivate *m_d; }; |