Files
qt-creator/src/plugins/vcsbase/vcsbasesubmiteditor.cpp

649 lines
22 KiB
C++
Raw Normal View History

/**************************************************************************
2008-12-02 12:01:29 +01:00
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
2008-12-02 12:01:29 +01:00
**
** Contact: Nokia Corporation (qt-info@nokia.com)
2008-12-02 12:01:29 +01:00
**
** 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
2009-08-14 09:30:56 +02:00
** contact the sales department at http://qt.nokia.com/contact.
2008-12-02 12:01:29 +01:00
**
**************************************************************************/
2008-12-02 16:19:05 +01:00
2008-12-02 12:01:29 +01:00
#include "vcsbasesubmiteditor.h"
#include "vcsbasesettings.h"
#include "vcsbaseplugin.h"
#include "nicknamedialog.h"
2008-12-02 12:01:29 +01:00
#include "submiteditorfile.h"
#include <aggregation/aggregate.h>
2008-12-02 12:01:29 +01:00
#include <coreplugin/ifile.h>
#include <coreplugin/icore.h>
#include <coreplugin/editormanager/editormanager.h>
2008-12-02 12:01:29 +01:00
#include <coreplugin/uniqueidmanager.h>
#include <coreplugin/actionmanager/actionmanager.h>
2008-12-02 12:01:29 +01:00
#include <utils/submiteditorwidget.h>
#include <utils/checkablemessagebox.h>
#include <utils/submitfieldwidget.h>
2008-12-02 12:01:29 +01:00
#include <find/basetextfind.h>
#include <texteditor/fontsettings.h>
#include <texteditor/texteditorsettings.h>
2008-12-02 12:01:29 +01:00
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/session.h>
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QTemporaryFile>
#include <QtCore/QProcess>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QPointer>
2008-12-02 12:01:29 +01:00
#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>
2008-12-02 12:01:29 +01:00
enum { debug = 0 };
enum { wantToolBar = 0 };
2008-12-02 12:01:29 +01:00
namespace VCSBase {
static inline QString submitMessageCheckScript()
{
return Internal::VCSBasePlugin::instance()->settings().submitMessageCheckScript;
}
struct VCSBaseSubmitEditorPrivate
{
2008-12-02 12:01:29 +01:00
VCSBaseSubmitEditorPrivate(const VCSBaseSubmitEditorParameters *parameters,
Core::Utils::SubmitEditorWidget *editorWidget,
QObject *q);
Core::Utils::SubmitEditorWidget *m_widget;
QToolBar *m_toolWidget;
const VCSBaseSubmitEditorParameters *m_parameters;
QString m_displayName;
VCSBase::Internal::SubmitEditorFile *m_file;
QList<int> m_contexts;
QPointer<QAction> m_diffAction;
QPointer<QAction> m_submitAction;
Internal::NickNameDialog *m_nickNameDialog;
2008-12-02 12:01:29 +01:00
};
VCSBaseSubmitEditorPrivate::VCSBaseSubmitEditorPrivate(const VCSBaseSubmitEditorParameters *parameters,
Core::Utils::SubmitEditorWidget *editorWidget,
QObject *q) :
m_widget(editorWidget),
m_toolWidget(0),
m_parameters(parameters),
m_file(new VCSBase::Internal::SubmitEditorFile(QLatin1String(m_parameters->mimeType), q)),
m_nickNameDialog(0)
2008-12-02 12:01:29 +01:00
{
m_contexts << Core::UniqueIDManager::instance()->uniqueIdentifier(m_parameters->context);
2008-12-02 12:01:29 +01:00
}
VCSBaseSubmitEditor::VCSBaseSubmitEditor(const VCSBaseSubmitEditorParameters *parameters,
Core::Utils::SubmitEditorWidget *editorWidget) :
m_d(new VCSBaseSubmitEditorPrivate(parameters, editorWidget, this))
{
// Message font according to settings
const TextEditor::FontSettings fs = TextEditor::TextEditorSettings::instance()->fontSettings();
QFont font = editorWidget->descriptionEdit()->font();
font.setFamily(fs.family());
font.setPointSize(fs.fontSize());
editorWidget->descriptionEdit()->setFont(font);
2008-12-02 12:01:29 +01:00
m_d->m_file->setModified(false);
// We are always clean to prevent the editor manager from asking to save.
connect(m_d->m_file, SIGNAL(saveMe(QString)), this, SLOT(save(QString)));
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.nickNameMailMap.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.nickNameMailMap.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);
2009-03-20 14:22:20 +01:00
// wrapping. etc
slotUpdateEditorSettings(settings);
connect(Internal::VCSBasePlugin::instance(),
SIGNAL(settingsChanged(VCSBase::Internal::VCSBaseSettings)),
this, SLOT(slotUpdateEditorSettings(VCSBase::Internal::VCSBaseSettings)));
2008-12-02 12:01:29 +01:00
Aggregation::Aggregate *aggregate = new Aggregation::Aggregate;
aggregate->add(new Find::BaseTextFind(m_d->m_widget->descriptionEdit()));
aggregate->add(this);
}
VCSBaseSubmitEditor::~VCSBaseSubmitEditor()
{
delete m_d->m_toolWidget;
delete m_d->m_widget;
delete m_d;
}
2009-03-20 14:22:20 +01:00
void VCSBaseSubmitEditor::slotUpdateEditorSettings(const Internal::VCSBaseSettings &s)
{
setLineWrapWidth(s.lineWrapWidth);
setLineWrap(s.lineWrap);
}
// Return a trimmed list of non-empty field texts
static inline QStringList fieldTexts(const QString &fileContents)
{
QStringList rc;
const QStringList rawFields = fileContents.trimmed().split(QLatin1Char('\n'));
foreach(const QString &field, rawFields) {
const QString trimmedField = field.trimmed();
if (!trimmedField.isEmpty())
rc.push_back(trimmedField);
}
return rc;
}
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 = fieldTexts(QString::fromUtf8(fieldFile.readAll()));
if (fields.empty())
return;
// Create a completer on user names
2009-03-20 14:22:20 +01:00
const QStandardItemModel *nickNameModel = Internal::VCSBasePlugin::instance()->nickNameModel();
QCompleter *completer = new QCompleter(Internal::NickNameDialog::nickNameList(nickNameModel), this);
Core::Utils::SubmitFieldWidget *fieldWidget = new Core::Utils::SubmitFieldWidget;
connect(fieldWidget, SIGNAL(browseButtonClicked(int,QString)),
this, SLOT(slotSetFieldNickName(int)));
fieldWidget->setCompleter(completer);
fieldWidget->setAllowDuplicateFields(true);
fieldWidget->setHasBrowseButton(true);
fieldWidget->setFields(fields);
m_d->m_widget->addSubmitFieldWidget(fieldWidget);
}
void VCSBaseSubmitEditor::registerActions(QAction *editorUndoAction, QAction *editorRedoAction,
QAction *submitAction, QAction *diffAction)\
{
m_d->m_widget->registerActions(editorUndoAction, editorRedoAction, submitAction, diffAction);
m_d->m_diffAction = diffAction;
m_d->m_submitAction = submitAction;
}
void VCSBaseSubmitEditor::unregisterActions(QAction *editorUndoAction, QAction *editorRedoAction,
QAction *submitAction, QAction *diffAction)
{
m_d->m_widget->unregisterActions(editorUndoAction, editorRedoAction, submitAction, diffAction);
m_d->m_diffAction = m_d->m_submitAction = 0;
}
int VCSBaseSubmitEditor::fileNameColumn() const
{
return m_d->m_widget->fileNameColumn();
}
void VCSBaseSubmitEditor::setFileNameColumn(int c)
{
m_d->m_widget->setFileNameColumn(c);
}
QAbstractItemView::SelectionMode VCSBaseSubmitEditor::fileListSelectionMode() const
{
return m_d->m_widget->fileListSelectionMode();
}
void VCSBaseSubmitEditor::setFileListSelectionMode(QAbstractItemView::SelectionMode sm)
{
m_d->m_widget->setFileListSelectionMode(sm);
}
2009-03-20 14:22:20 +01:00
bool VCSBaseSubmitEditor::lineWrap() const
{
return m_d->m_widget->lineWrap();
}
void VCSBaseSubmitEditor::setLineWrap(bool w)
{
m_d->m_widget->setLineWrap(w);
}
int VCSBaseSubmitEditor::lineWrapWidth() const
{
return m_d->m_widget->lineWrapWidth();
}
void VCSBaseSubmitEditor::setLineWrapWidth(int w)
{
m_d->m_widget->setLineWrapWidth(w);
}
2008-12-02 12:01:29 +01:00
void VCSBaseSubmitEditor::slotDescriptionChanged()
{
}
bool VCSBaseSubmitEditor::createNew(const QString &contents)
{
setFileContents(contents);
return true;
}
bool VCSBaseSubmitEditor::open(const QString &fileName)
{
if (fileName.isEmpty())
return false;
const QFileInfo fi(fileName);
if (!fi.isFile() || !fi.isReadable())
return false;
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly|QIODevice::Text)) {
qWarning("Unable to open %s: %s", qPrintable(fileName), qPrintable(file.errorString()));
return false;
}
const QString text = QString::fromLocal8Bit(file.readAll());
if (!createNew(text))
return false;
m_d->m_file->setFileName(fi.absoluteFilePath());
return true;
}
Core::IFile *VCSBaseSubmitEditor::file()
{
return m_d->m_file;
}
QString VCSBaseSubmitEditor::displayName() const
{
return m_d->m_displayName;
}
void VCSBaseSubmitEditor::setDisplayName(const QString &title)
{
m_d->m_displayName = title;
}
bool VCSBaseSubmitEditor::duplicateSupported() const
{
return false;
}
Core::IEditor *VCSBaseSubmitEditor::duplicate(QWidget * /*parent*/)
{
return 0;
}
const char *VCSBaseSubmitEditor::kind() const
{
return m_d->m_parameters->kind;
}
static QToolBar *createToolBar(const QWidget *someWidget, QAction *submitAction, QAction *diffAction)
{
// Create
QToolBar *toolBar = new QToolBar;
toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
const int size = someWidget->style()->pixelMetric(QStyle::PM_SmallIconSize);
toolBar->setIconSize(QSize(size, size));
toolBar->addSeparator();
if (submitAction)
toolBar->addAction(submitAction);
if (diffAction)
toolBar->addAction(diffAction);
return toolBar;
}
2009-07-15 16:23:07 +02:00
QWidget *VCSBaseSubmitEditor::toolBar()
2008-12-02 12:01:29 +01:00
{
if (!wantToolBar)
return 0;
2008-12-02 12:01:29 +01:00
if (m_d->m_toolWidget)
return m_d->m_toolWidget;
if (!m_d->m_diffAction && !m_d->m_submitAction)
return 0;
// Create
m_d->m_toolWidget = createToolBar(m_d->m_widget, m_d->m_submitAction, m_d->m_diffAction);
return m_d->m_toolWidget;
2008-12-02 12:01:29 +01:00
}
QList<int> VCSBaseSubmitEditor::context() const
{
return m_d->m_contexts;
}
QWidget *VCSBaseSubmitEditor::widget()
{
return m_d->m_widget;
}
QByteArray VCSBaseSubmitEditor::saveState() const
{
return QByteArray();
}
bool VCSBaseSubmitEditor::restoreState(const QByteArray &/*state*/)
{
return true;
}
QStringList VCSBaseSubmitEditor::checkedFiles() const
2008-12-02 12:01:29 +01:00
{
return m_d->m_widget->checkedFiles();
2008-12-02 12:01:29 +01:00
}
void VCSBaseSubmitEditor::setFileModel(QAbstractItemModel *m)
2008-12-02 12:01:29 +01:00
{
m_d->m_widget->setFileModel(m);
2008-12-02 12:01:29 +01:00
}
QAbstractItemModel *VCSBaseSubmitEditor::fileModel() const
2008-12-02 12:01:29 +01:00
{
return m_d->m_widget->fileModel();
2008-12-02 12:01:29 +01:00
}
void VCSBaseSubmitEditor::slotDiffSelectedVCSFiles(const QStringList &rawList)
2008-12-02 12:01:29 +01:00
{
emit diffSelectedFiles(rawList);
2008-12-02 12:01:29 +01:00
}
bool VCSBaseSubmitEditor::save(const QString &fileName)
{
const QString fName = fileName.isEmpty() ? m_d->m_file->fileName() : fileName;
QFile file(fName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
qWarning("Unable to open %s: %s", qPrintable(fName), qPrintable(file.errorString()));
return false;
}
file.write(fileContents().toLocal8Bit());
if (!file.flush())
return false;
file.close();
const QFileInfo fi(fName);
m_d->m_file->setFileName(fi.absoluteFilePath());
m_d->m_file->setModified(false);
return true;
}
QString VCSBaseSubmitEditor::fileContents() const
{
return m_d->m_widget->descriptionText();
2008-12-02 12:01:29 +01:00
}
bool VCSBaseSubmitEditor::setFileContents(const QString &contents)
{
m_d->m_widget->setDescriptionText(contents);
return true;
}
enum { checkDialogMinimumWidth = 500 };
VCSBaseSubmitEditor::PromptSubmitResult
VCSBaseSubmitEditor::promptSubmit(const QString &title,
const QString &question,
const QString &checkFailureQuestion,
bool *promptSetting,
bool forcePrompt) const
{
QString errorMessage;
QMessageBox::StandardButton answer = QMessageBox::Yes;
const bool prompt = forcePrompt || *promptSetting;
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 (prompt) {
// Provide check box to turn off prompt ONLY if it was not forced
if (*promptSetting && !forcePrompt) {
const QDialogButtonBox::StandardButton danswer =
Core::Utils::CheckableMessageBox::question(parent, title, question,
tr("Prompt to submit"), promptSetting,
QDialogButtonBox::Yes|QDialogButtonBox::No|QDialogButtonBox::Cancel,
QDialogButtonBox::Yes);
answer = Core::Utils::CheckableMessageBox::dialogButtonBoxToMessageBoxButton(danswer);
} else {
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)
2009-03-20 14:22:20 +01:00
m_d->m_nickNameDialog = new Internal::NickNameDialog(Internal::VCSBasePlugin::instance()->nickNameModel(), 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)
{
if (Core::Utils::SubmitFieldWidget *sfw =m_d->m_widget->submitFieldWidgets().front()) {
const QString nick = promptForNickName();
if (!nick.isEmpty())
sfw->setFieldValue(i, 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;
QApplication::setOverrideCursor(Qt::WaitCursor);
const bool rc = runSubmitMessageCheckScript(checkScript, errorMessage);
QApplication::restoreOverrideCursor();
return rc;
}
bool VCSBaseSubmitEditor::runSubmitMessageCheckScript(const QString &checkScript, QString *errorMessage) const
{
// 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"));
}
QIcon VCSBaseSubmitEditor::submitIcon()
{
return QIcon(QLatin1String(":/vcsbase/images/submit.png"));
}
// Compile a list if files in the current projects. TODO: Recurse down qrc files?
QStringList VCSBaseSubmitEditor::currentProjectFiles(bool nativeSeparators, QString *name)
{
using namespace ProjectExplorer;
if (name)
name->clear();
ProjectExplorerPlugin *pe = ProjectExplorerPlugin::instance();
if (!pe)
return QStringList();
QStringList files;
if (const Project *currentProject = pe->currentProject()) {
files << currentProject->files(Project::ExcludeGeneratedFiles);
if (name)
*name = currentProject->name();
} else {
if (const SessionManager *session = pe->session()) {
if (name)
*name = session->file()->fileName();
const QList<Project *> projects = session->projects();
foreach (Project *project, projects)
files << project->files(Project::ExcludeGeneratedFiles);
}
}
if (nativeSeparators && !files.empty()) {
const QStringList::iterator end = files.end();
for (QStringList::iterator it = files.begin(); it != end; ++it)
*it = QDir::toNativeSeparators(*it);
}
return files;
}
// Reduce a list of untracked files reported by a VCS down to the files
// that are actually part of the current project(s).
void VCSBaseSubmitEditor::filterUntrackedFilesOfProject(const QString &repositoryDirectory, QStringList *untrackedFiles)
{
if (untrackedFiles->empty())
return;
const QStringList nativeProjectFiles = VCSBase::VCSBaseSubmitEditor::currentProjectFiles(true);
if (nativeProjectFiles.empty())
return;
const QDir repoDir(repositoryDirectory);
for (QStringList::iterator it = untrackedFiles->begin(); it != untrackedFiles->end(); ) {
const QString path = QDir::toNativeSeparators(repoDir.absoluteFilePath(*it));
if (nativeProjectFiles.contains(path)) {
++it;
} else {
it = untrackedFiles->erase(it);
}
}
}
// Helper to raise an already open submit editor to prevent opening twice.
bool VCSBaseSubmitEditor::raiseSubmitEditor()
{
Core::EditorManager *em = Core::EditorManager::instance();
// Nothing to do?
if (Core::IEditor *ce = em->currentEditor())
if (qobject_cast<VCSBaseSubmitEditor*>(ce))
return true;
// Try to activate a hidden one
foreach (Core::IEditor *e, em->openedEditors()) {
if (qobject_cast<VCSBaseSubmitEditor*>(e)) {
em->activateEditor(e, Core::EditorManager::IgnoreNavigationHistory);
return true;
}
}
return false;
}
} // namespace VCSBase