vcsbasesubmiteditor.cpp 28.4 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8 9 10 11 12 13 14
** 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 Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17 18 19 20 21 22 23 24 25
** 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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26 27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
#include "vcsbasesubmiteditor.h"
31 32

#include "commonvcssettings.h"
33
#include "nicknamedialog.h"
con's avatar
con committed
34
#include "submiteditorfile.h"
35 36
#include "submiteditorwidget.h"
#include "submitfieldwidget.h"
37
#include "submitfilemodel.h"
38 39
#include "vcsbaseoutputwindow.h"
#include "vcsplugin.h"
con's avatar
con committed
40

41
#include <aggregation/aggregate.h>
42
#include <cpptools/cppmodelmanagerinterface.h>
43
#include <coreplugin/icore.h>
44
#include <coreplugin/editormanager/editormanager.h>
45
#include <utils/checkablemessagebox.h>
46
#include <utils/synchronousprocess.h>
47
#include <utils/fileutils.h>
48
#include <utils/qtcassert.h>
con's avatar
con committed
49
#include <find/basetextfind.h>
50 51
#include <texteditor/fontsettings.h>
#include <texteditor/texteditorsettings.h>
con's avatar
con committed
52

53
#include <projectexplorer/projectexplorer.h>
54
#include <projectexplorer/project.h>
55

56 57 58 59 60 61 62 63 64 65 66 67
#include <QDebug>
#include <QDir>
#include <QProcess>
#include <QFileInfo>
#include <QPointer>
#include <QStringListModel>
#include <QStyle>
#include <QToolBar>
#include <QAction>
#include <QApplication>
#include <QMessageBox>
#include <QCompleter>
68

69
#include <cstring>
con's avatar
con committed
70 71

enum { debug = 0 };
72
enum { wantToolBar = 0 };
con's avatar
con committed
73

74 75 76
// Return true if word is meaningful and can be added to a completion model
static bool acceptsWordForCompletion(const char *word)
{
77 78 79 80
    if (word == 0)
        return false;
    static const std::size_t minWordLength = 7;
    return std::strlen(word) >= minWordLength;
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
}

// Return the class name which function belongs to
static const char *belongingClassName(const CPlusPlus::Function *function)
{
    if (function == 0)
        return 0;

    const CPlusPlus::Name *funcName = function->name();
    if (funcName != 0 && funcName->asQualifiedNameId() != 0) {
        const CPlusPlus::Name *funcBaseName = funcName->asQualifiedNameId()->base();
        if (funcBaseName != 0 && funcBaseName->identifier() != 0)
            return funcBaseName->identifier()->chars();
    }

    return 0;
}

99
/*!
hjk's avatar
hjk committed
100
    \struct VcsBase::VcsBaseSubmitEditorParameters
101

hjk's avatar
hjk committed
102
    \brief Utility struct to parametrize a VcsBaseSubmitEditor.
103 104 105
*/

/*!
hjk's avatar
hjk committed
106
    \class  VcsBase::VcsBaseSubmitEditor
107

108
    \brief Base class for a submit editor based on the SubmitEditorWidget.
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125

    Presents the commit message in a text editor and an
    checkable list of modified files in a list window. The user can delete
    files from the list by pressing unchecking them or diff the selection
    by doubleclicking.

    The action matching the the ids (unless 0) of the parameter struct will be
    registered with the EditorWidget and submit/diff actions will be added to
    a toolbar.

    For the given context, there must be only one instance of the editor
    active.
    To start a submit, set the submit template on the editor and the output
    of the VCS status command listing the modified files as fileList and open
    it.

    The submit process is started by listening on the editor close
126 127
    signal and then asking the IDocument interface of the editor to save the file
    within a DocumentManager::blockFileChange() section
128 129 130 131 132
    and to launch the submit process. In addition, the action registered
    for submit should be connected to a slot triggering the close of the
    current editor in the editor manager.
*/

hjk's avatar
hjk committed
133
namespace VcsBase {
con's avatar
con committed
134

hjk's avatar
hjk committed
135 136 137
using namespace Internal;
using namespace Utils;

138 139
static inline QString submitMessageCheckScript()
{
hjk's avatar
hjk committed
140
    return VcsPlugin::instance()->settings().submitMessageCheckScript;
141 142
}

hjk's avatar
hjk committed
143
struct VcsBaseSubmitEditorPrivate
144
{
hjk's avatar
hjk committed
145
    VcsBaseSubmitEditorPrivate(const VcsBaseSubmitEditorParameters *parameters,
hjk's avatar
hjk committed
146
                               SubmitEditorWidget *editorWidget,
con's avatar
con committed
147 148
                               QObject *q);

hjk's avatar
hjk committed
149
    SubmitEditorWidget *m_widget;
con's avatar
con committed
150
    QToolBar *m_toolWidget;
hjk's avatar
hjk committed
151
    const VcsBaseSubmitEditorParameters *m_parameters;
con's avatar
con committed
152
    QString m_displayName;
153
    QString m_checkScriptWorkingDirectory;
hjk's avatar
hjk committed
154
    SubmitEditorFile *m_file;
con's avatar
con committed
155 156

    QPointer<QAction> m_diffAction;
157
    QPointer<QAction> m_submitAction;
158

hjk's avatar
hjk committed
159
    NickNameDialog *m_nickNameDialog;
con's avatar
con committed
160 161
};

hjk's avatar
hjk committed
162
VcsBaseSubmitEditorPrivate::VcsBaseSubmitEditorPrivate(const VcsBaseSubmitEditorParameters *parameters,
hjk's avatar
hjk committed
163
                                                       SubmitEditorWidget *editorWidget,
con's avatar
con committed
164 165 166 167
                                                       QObject *q) :
    m_widget(editorWidget),
    m_toolWidget(0),
    m_parameters(parameters),
hjk's avatar
hjk committed
168
    m_file(new SubmitEditorFile(QLatin1String(parameters->mimeType), q)),
169
    m_nickNameDialog(0)
con's avatar
con committed
170
{
171
    QCompleter *completer = new QCompleter(q);
172
    completer->setCaseSensitivity(Qt::CaseSensitive);
173 174
    completer->setModelSorting(QCompleter::CaseSensitivelySortedModel);
    m_widget->descriptionEdit()->setCompleter(completer);
175
    m_widget->descriptionEdit()->setCompletionLengthThreshold(4);
con's avatar
con committed
176 177
}

hjk's avatar
hjk committed
178
VcsBaseSubmitEditor::VcsBaseSubmitEditor(const VcsBaseSubmitEditorParameters *parameters,
hjk's avatar
hjk committed
179
                                         SubmitEditorWidget *editorWidget) :
hjk's avatar
hjk committed
180
    d(new VcsBaseSubmitEditorPrivate(parameters, editorWidget, this))
con's avatar
con committed
181
{
182
    setContext(Core::Context(parameters->context));
183
    setWidget(d->m_widget);
184

185
    // Message font according to settings
186 187 188
    const TextEditor::FontSettings fs = TextEditor::TextEditorSettings::instance()->fontSettings();
    QFont font = editorWidget->descriptionEdit()->font();
    font.setFamily(fs.family());
189
    font.setPointSize(fs.fontSize());
190 191
    editorWidget->descriptionEdit()->setFont(font);

192
    d->m_file->setModified(false);
con's avatar
con committed
193
    // We are always clean to prevent the editor manager from asking to save.
hjk's avatar
hjk committed
194 195
    connect(d->m_file, SIGNAL(saveMe(QString*,QString,bool)),
            this, SLOT(save(QString*,QString,bool)));
con's avatar
con committed
196

197
    connect(d->m_widget, SIGNAL(diffSelected(QList<int>)), this, SLOT(slotDiffSelectedVcsFiles(QList<int>)));
198
    connect(d->m_widget->descriptionEdit(), SIGNAL(textChanged()), this, SLOT(slotDescriptionChanged()));
con's avatar
con committed
199

hjk's avatar
hjk committed
200
    const CommonVcsSettings settings = VcsPlugin::instance()->settings();
201
    // Add additional context menu settings
202
    if (!settings.submitMessageCheckScript.isEmpty() || !settings.nickNameMailMap.isEmpty()) {
203 204
        QAction *sep = new QAction(this);
        sep->setSeparator(true);
205
        d->m_widget->addDescriptionEditContextMenuAction(sep);
206 207
        // Run check action
        if (!settings.submitMessageCheckScript.isEmpty()) {
Leena Miettinen's avatar
Leena Miettinen committed
208
            QAction *checkAction = new QAction(tr("Check Message"), this);
209
            connect(checkAction, SIGNAL(triggered()), this, SLOT(slotCheckSubmitMessage()));
210
            d->m_widget->addDescriptionEditContextMenuAction(checkAction);
211 212
        }
        // Insert nick
213
        if (!settings.nickNameMailMap.isEmpty()) {
Leena Miettinen's avatar
Leena Miettinen committed
214
            QAction *insertAction = new QAction(tr("Insert Name..."), this);
215
            connect(insertAction, SIGNAL(triggered()), this, SLOT(slotInsertNickName()));
216
            d->m_widget->addDescriptionEditContextMenuAction(insertAction);
217 218 219 220 221
        }
    }
    // Do we have user fields?
    if (!settings.nickNameFieldListFile.isEmpty())
        createUserFields(settings.nickNameFieldListFile);
222 223 224

    // wrapping. etc
    slotUpdateEditorSettings(settings);
hjk's avatar
hjk committed
225 226 227
    connect(VcsPlugin::instance(),
            SIGNAL(settingsChanged(VcsBase::Internal::CommonVcsSettings)),
            this, SLOT(slotUpdateEditorSettings(VcsBase::Internal::CommonVcsSettings)));
228 229 230
    connect(Core::EditorManager::instance(), SIGNAL(currentEditorChanged(Core::IEditor*)),
            this, SLOT(slotRefreshCommitData()));
    connect(Core::ICore::mainWindow(), SIGNAL(windowActivated()), this, SLOT(slotRefreshCommitData()));
231

con's avatar
con committed
232
    Aggregation::Aggregate *aggregate = new Aggregation::Aggregate;
233
    aggregate->add(new Find::BaseTextFind(d->m_widget->descriptionEdit()));
con's avatar
con committed
234 235 236
    aggregate->add(this);
}

hjk's avatar
hjk committed
237
VcsBaseSubmitEditor::~VcsBaseSubmitEditor()
con's avatar
con committed
238
{
239 240 241
    delete d->m_toolWidget;
    delete d->m_widget;
    delete d;
con's avatar
con committed
242 243
}

hjk's avatar
hjk committed
244
void VcsBaseSubmitEditor::slotUpdateEditorSettings(const CommonVcsSettings &s)
245 246 247 248 249
{
    setLineWrapWidth(s.lineWrapWidth);
    setLineWrap(s.lineWrap);
}

250 251 252 253 254 255
void VcsBaseSubmitEditor::slotRefreshCommitData()
{
    if (Core::EditorManager::currentEditor() == this)
        updateFileModel();
}

256 257 258 259 260
// 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'));
hjk's avatar
hjk committed
261
    foreach (const QString &field, rawFields) {
262 263 264 265 266 267 268
        const QString trimmedField = field.trimmed();
        if (!trimmedField.isEmpty())
            rc.push_back(trimmedField);
    }
    return rc;
}

hjk's avatar
hjk committed
269
void VcsBaseSubmitEditor::createUserFields(const QString &fieldConfigFile)
270
{
271
    Utils::FileReader reader;
hjk's avatar
hjk committed
272
    if (!reader.fetch(fieldConfigFile, QIODevice::Text, Core::ICore::mainWindow()))
273 274
        return;
    // Parse into fields
275
    const QStringList fields = fieldTexts(QString::fromUtf8(reader.data()));
276 277 278
    if (fields.empty())
        return;
    // Create a completer on user names
hjk's avatar
hjk committed
279
    const QStandardItemModel *nickNameModel = VcsPlugin::instance()->nickNameModel();
hjk's avatar
hjk committed
280
    QCompleter *completer = new QCompleter(NickNameDialog::nickNameList(nickNameModel), this);
281

hjk's avatar
hjk committed
282
    SubmitFieldWidget *fieldWidget = new SubmitFieldWidget;
283
    connect(fieldWidget, SIGNAL(browseButtonClicked(int,QString)),
284
            this, SLOT(slotSetFieldNickName(int)));
285 286 287 288
    fieldWidget->setCompleter(completer);
    fieldWidget->setAllowDuplicateFields(true);
    fieldWidget->setHasBrowseButton(true);
    fieldWidget->setFields(fields);
289
    d->m_widget->addSubmitFieldWidget(fieldWidget);
290 291
}

hjk's avatar
hjk committed
292
void VcsBaseSubmitEditor::registerActions(QAction *editorUndoAction, QAction *editorRedoAction,
hjk's avatar
hjk committed
293
                                          QAction *submitAction, QAction *diffAction)
294
{
295 296 297
    d->m_widget->registerActions(editorUndoAction, editorRedoAction, submitAction, diffAction);
    d->m_diffAction = diffAction;
    d->m_submitAction = submitAction;
298 299
}

hjk's avatar
hjk committed
300
void VcsBaseSubmitEditor::unregisterActions(QAction *editorUndoAction,  QAction *editorRedoAction,
301 302
                           QAction *submitAction, QAction *diffAction)
{
303 304
    d->m_widget->unregisterActions(editorUndoAction, editorRedoAction, submitAction, diffAction);
    d->m_diffAction = d->m_submitAction = 0;
305
}
306

hjk's avatar
hjk committed
307
QAbstractItemView::SelectionMode VcsBaseSubmitEditor::fileListSelectionMode() const
308
{
309
    return d->m_widget->fileListSelectionMode();
310 311
}

hjk's avatar
hjk committed
312
void VcsBaseSubmitEditor::setFileListSelectionMode(QAbstractItemView::SelectionMode sm)
313
{
314
    d->m_widget->setFileListSelectionMode(sm);
315 316
}

hjk's avatar
hjk committed
317
bool VcsBaseSubmitEditor::isEmptyFileListEnabled() const
318
{
319
    return d->m_widget->isEmptyFileListEnabled();
320 321
}

hjk's avatar
hjk committed
322
void VcsBaseSubmitEditor::setEmptyFileListEnabled(bool e)
323
{
324
    d->m_widget->setEmptyFileListEnabled(e);
325 326
}

hjk's avatar
hjk committed
327
bool VcsBaseSubmitEditor::lineWrap() const
328
{
329
    return d->m_widget->lineWrap();
330 331
}

hjk's avatar
hjk committed
332
void VcsBaseSubmitEditor::setLineWrap(bool w)
333
{
334
    d->m_widget->setLineWrap(w);
335 336
}

hjk's avatar
hjk committed
337
int VcsBaseSubmitEditor::lineWrapWidth() const
338
{
339
    return d->m_widget->lineWrapWidth();
340 341
}

hjk's avatar
hjk committed
342
void VcsBaseSubmitEditor::setLineWrapWidth(int w)
343
{
344
    d->m_widget->setLineWrapWidth(w);
345 346
}

hjk's avatar
hjk committed
347
void VcsBaseSubmitEditor::slotDescriptionChanged()
con's avatar
con committed
348 349 350
{
}

hjk's avatar
hjk committed
351
bool VcsBaseSubmitEditor::createNew(const QString &contents)
con's avatar
con committed
352 353 354 355 356
{
    setFileContents(contents);
    return true;
}

hjk's avatar
hjk committed
357
bool VcsBaseSubmitEditor::open(QString *errorString, const QString &fileName, const QString &realFileName)
con's avatar
con committed
358 359 360 361
{
    if (fileName.isEmpty())
        return false;

362
    Utils::FileReader reader;
363
    if (!reader.fetch(realFileName, QIODevice::Text, errorString))
con's avatar
con committed
364 365
        return false;

366
    const QString text = QString::fromLocal8Bit(reader.data());
con's avatar
con committed
367 368 369
    if (!createNew(text))
        return false;

370 371
    d->m_file->setFileName(QFileInfo(fileName).absoluteFilePath());
    d->m_file->setModified(fileName != realFileName);
con's avatar
con committed
372 373 374
    return true;
}

375
Core::IDocument *VcsBaseSubmitEditor::document()
con's avatar
con committed
376
{
377
    return d->m_file;
con's avatar
con committed
378 379
}

hjk's avatar
hjk committed
380
QString VcsBaseSubmitEditor::displayName() const
con's avatar
con committed
381
{
382 383 384
    if (d->m_displayName.isEmpty())
        d->m_displayName = QCoreApplication::translate("VCS", d->m_parameters->displayName);
    return d->m_displayName;
con's avatar
con committed
385 386
}

hjk's avatar
hjk committed
387
void VcsBaseSubmitEditor::setDisplayName(const QString &title)
con's avatar
con committed
388
{
389
    d->m_displayName = title;
390
    emit changed();
con's avatar
con committed
391 392
}

hjk's avatar
hjk committed
393
QString VcsBaseSubmitEditor::checkScriptWorkingDirectory() const
394
{
395
    return d->m_checkScriptWorkingDirectory;
396 397
}

hjk's avatar
hjk committed
398
void VcsBaseSubmitEditor::setCheckScriptWorkingDirectory(const QString &s)
399
{
400
    d->m_checkScriptWorkingDirectory = s;
401 402
}

hjk's avatar
hjk committed
403
bool VcsBaseSubmitEditor::duplicateSupported() const
con's avatar
con committed
404 405 406 407
{
    return false;
}

hjk's avatar
hjk committed
408
Core::IEditor *VcsBaseSubmitEditor::duplicate(QWidget * /*parent*/)
con's avatar
con committed
409 410 411 412
{
    return 0;
}

hjk's avatar
hjk committed
413
Core::Id VcsBaseSubmitEditor::id() const
con's avatar
con committed
414
{
415
    return d->m_parameters->id;
con's avatar
con committed
416 417
}

418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
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;
}

hjk's avatar
hjk committed
434
QWidget *VcsBaseSubmitEditor::toolBar()
con's avatar
con committed
435
{
436 437 438
    if (!wantToolBar)
        return 0;

439 440
    if (d->m_toolWidget)
        return d->m_toolWidget;
con's avatar
con committed
441

442
    if (!d->m_diffAction && !d->m_submitAction)
con's avatar
con committed
443 444 445
        return 0;

    // Create
446 447
    d->m_toolWidget = createToolBar(d->m_widget, d->m_submitAction, d->m_diffAction);
    return d->m_toolWidget;
con's avatar
con committed
448 449
}

hjk's avatar
hjk committed
450
QByteArray VcsBaseSubmitEditor::saveState() const
con's avatar
con committed
451 452 453 454
{
    return QByteArray();
}

hjk's avatar
hjk committed
455
bool VcsBaseSubmitEditor::restoreState(const QByteArray &/*state*/)
con's avatar
con committed
456 457 458 459
{
    return true;
}

hjk's avatar
hjk committed
460
QStringList VcsBaseSubmitEditor::checkedFiles() const
con's avatar
con committed
461
{
462
    return d->m_widget->checkedFiles();
con's avatar
con committed
463 464
}

465
void VcsBaseSubmitEditor::setFileModel(SubmitFileModel *model, const QString &repositoryDirectory)
con's avatar
con committed
466
{
467
    QTC_ASSERT(model, return);
468
    if (SubmitFileModel *oldModel = d->m_widget->fileModel()) {
469
        model->updateSelections(oldModel);
470 471
        delete oldModel;
    }
472
    d->m_widget->setFileModel(model);
473 474

    QSet<QString> uniqueSymbols;
475
    const CPlusPlus::Snapshot cppSnapShot = CppTools::CppModelManagerInterface::instance()->snapshot();
476 477

    // Iterate over the files and get interesting symbols
478 479
    for (int row = 0; row < model->rowCount(); ++row) {
        const QFileInfo fileInfo(repositoryDirectory, model->file(row));
480 481 482 483 484 485 486 487 488 489 490 491 492

        // Add file name
        uniqueSymbols.insert(fileInfo.fileName());

        const QString filePath = fileInfo.absoluteFilePath();
        // Add symbols from the C++ code model
        const CPlusPlus::Document::Ptr doc = cppSnapShot.document(filePath);
        if (!doc.isNull() && doc->control() != 0) {
            const CPlusPlus::Control *ctrl = doc->control();
            CPlusPlus::Symbol **symPtr = ctrl->firstSymbol(); // Read-only
            while (symPtr != ctrl->lastSymbol()) {
                const CPlusPlus::Symbol *sym = *symPtr;

493 494 495 496 497
                const CPlusPlus::Identifier *symId = sym->identifier();
                // Add any class, function or namespace identifiers
                if ((sym->isClass() || sym->isFunction() || sym->isNamespace())
                        && (symId != 0 && acceptsWordForCompletion(symId->chars())))
                {
498
                    uniqueSymbols.insert(QString::fromUtf8(symId->chars()));
499
                }
500 501 502 503 504

                // Handle specific case : get "Foo" in "void Foo::function() {}"
                if (sym->isFunction() && !sym->asFunction()->isDeclaration()) {
                    const char *className = belongingClassName(sym->asFunction());
                    if (acceptsWordForCompletion(className))
505
                        uniqueSymbols.insert(QString::fromUtf8(className));
506 507 508 509 510 511 512 513 514 515 516 517 518 519
                }

                ++symPtr;
            }
        }
    }

    // Populate completer with symbols
    if (!uniqueSymbols.isEmpty()) {
        QCompleter *completer = d->m_widget->descriptionEdit()->completer();
        QStringList symbolsList = uniqueSymbols.toList();
        symbolsList.sort();
        completer->setModel(new QStringListModel(symbolsList, completer));
    }
con's avatar
con committed
520 521
}

522
SubmitFileModel *VcsBaseSubmitEditor::fileModel() const
con's avatar
con committed
523
{
524
    return d->m_widget->fileModel();
con's avatar
con committed
525 526
}

527
QStringList VcsBaseSubmitEditor::rowsToFiles(const QList<int> &rows) const
con's avatar
con committed
528
{
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
    if (rows.empty())
        return QStringList();

    QStringList rc;
    const SubmitFileModel *model = fileModel();
    const int count = rows.size();
    for (int i = 0; i < count; i++)
        rc.push_back(model->file(rows.at(i)));
    return rc;
}

void VcsBaseSubmitEditor::slotDiffSelectedVcsFiles(const QList<int> &rawList)
{
    if (d->m_parameters->diffType == VcsBaseSubmitEditorParameters::DiffRows)
        emit diffSelectedFiles(rawList);
    else
        emit diffSelectedFiles(rowsToFiles(rawList));
con's avatar
con committed
546 547
}

hjk's avatar
hjk committed
548
bool VcsBaseSubmitEditor::save(QString *errorString, const QString &fileName, bool autoSave)
con's avatar
con committed
549
{
550
    const QString fName = fileName.isEmpty() ? d->m_file->fileName() : fileName;
551
    Utils::FileSaver saver(fName, QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text);
552
    saver.write(fileContents());
553
    if (!saver.finalize(errorString))
con's avatar
con committed
554
        return false;
555 556
    if (autoSave)
        return true;
con's avatar
con committed
557
    const QFileInfo fi(fName);
558 559
    d->m_file->setFileName(fi.absoluteFilePath());
    d->m_file->setModified(false);
con's avatar
con committed
560 561 562
    return true;
}

hjk's avatar
hjk committed
563
QByteArray VcsBaseSubmitEditor::fileContents() const
con's avatar
con committed
564
{
565
    return d->m_widget->descriptionText().toLocal8Bit();
con's avatar
con committed
566 567
}

hjk's avatar
hjk committed
568
bool VcsBaseSubmitEditor::setFileContents(const QString &contents)
con's avatar
con committed
569
{
570
    d->m_widget->setDescriptionText(contents);
con's avatar
con committed
571 572 573
    return true;
}

574 575 576 577 578 579 580 581 582 583
bool VcsBaseSubmitEditor::isDescriptionMandatory() const
{
    return d->m_widget->isDescriptionMandatory();
}

void VcsBaseSubmitEditor::setDescriptionMandatory(bool v)
{
    d->m_widget->setDescriptionMandatory(v);
}

584 585
enum { checkDialogMinimumWidth = 500 };

hjk's avatar
hjk committed
586 587
VcsBaseSubmitEditor::PromptSubmitResult
        VcsBaseSubmitEditor::promptSubmit(const QString &title,
588 589
                                          const QString &question,
                                          const QString &checkFailureQuestion,
590
                                          bool *promptSetting,
591 592
                                          bool forcePrompt,
                                          bool canCommitOnFailure) const
593
{
hjk's avatar
hjk committed
594
    SubmitEditorWidget *submitWidget =
hjk's avatar
hjk committed
595
            static_cast<SubmitEditorWidget *>(const_cast<VcsBaseSubmitEditor *>(this)->widget());
596

597 598
    raiseSubmitEditor();

599 600 601
    QString errorMessage;
    QMessageBox::StandardButton answer = QMessageBox::Yes;

602
    const bool prompt = forcePrompt || *promptSetting;
603

hjk's avatar
hjk committed
604
    QWidget *parent = Core::ICore::mainWindow();
605 606
    // Pop up a message depending on whether the check succeeded and the
    // user wants to be prompted
607 608
    bool canCommit = checkSubmitMessage(&errorMessage) && submitWidget->canSubmit();
    if (canCommit) {
609
        // Check ok, do prompt?
610
        if (prompt) {
611 612 613
            // Provide check box to turn off prompt ONLY if it was not forced
            if (*promptSetting && !forcePrompt) {
                const QDialogButtonBox::StandardButton danswer =
614
                        Utils::CheckableMessageBox::question(parent, title, question,
615
                                                                   tr("Prompt to submit"), promptSetting,
Tobias Hunger's avatar
Tobias Hunger committed
616 617
                                                                   QDialogButtonBox::Yes|QDialogButtonBox::No|
                                                                   QDialogButtonBox::Cancel,
618
                                                                   QDialogButtonBox::Yes);
619
                answer = Utils::CheckableMessageBox::dialogButtonBoxToMessageBoxButton(danswer);
620 621 622 623 624
            } else {
                answer = QMessageBox::question(parent, title, question,
                                               QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel,
                                               QMessageBox::Yes);
            }
625 626 627 628 629 630 631 632 633 634
        }
    } 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());
    }
635 636 637
    if (!canCommit && !canCommitOnFailure) {
        switch (answer) {
        case QMessageBox::No:
hjk's avatar
hjk committed
638
            return SubmitDiscarded;
639
        case QMessageBox::Yes:
hjk's avatar
hjk committed
640
            return SubmitCanceled;
641 642 643 644 645 646 647 648 649 650 651 652
        default:
            break;
        }
    } else {
        switch (answer) {
        case QMessageBox::No:
            return SubmitDiscarded;
        case QMessageBox::Yes:
            return SubmitConfirmed;
        default:
            break;
        }
653
    }
654

Tobias Hunger's avatar
Tobias Hunger committed
655
    return SubmitCanceled;
656 657
}

hjk's avatar
hjk committed
658
QString VcsBaseSubmitEditor::promptForNickName()
659
{
660
    if (!d->m_nickNameDialog)
hjk's avatar
hjk committed
661
        d->m_nickNameDialog = new NickNameDialog(VcsPlugin::instance()->nickNameModel(), d->m_widget);
662 663
    if (d->m_nickNameDialog->exec() == QDialog::Accepted)
       return d->m_nickNameDialog->nickName();
664 665 666
    return QString();
}

hjk's avatar
hjk committed
667
void VcsBaseSubmitEditor::slotInsertNickName()
668 669 670
{
    const QString nick = promptForNickName();
    if (!nick.isEmpty())
671
        d->m_widget->descriptionEdit()->textCursor().insertText(nick);
672 673
}

hjk's avatar
hjk committed
674
void VcsBaseSubmitEditor::slotSetFieldNickName(int i)
675
{
hjk's avatar
hjk committed
676
    if (SubmitFieldWidget *sfw = d->m_widget->submitFieldWidgets().front()) {
677 678 679 680
        const QString nick = promptForNickName();
        if (!nick.isEmpty())
            sfw->setFieldValue(i, nick);
    }
681 682
}

hjk's avatar
hjk committed
683
void VcsBaseSubmitEditor::slotCheckSubmitMessage()
684 685 686
{
    QString errorMessage;
    if (!checkSubmitMessage(&errorMessage)) {
Leena Miettinen's avatar
Leena Miettinen committed
687
        QMessageBox msgBox(QMessageBox::Warning, tr("Submit Message Check Failed"),
688
                           errorMessage, QMessageBox::Ok, d->m_widget);
689 690 691 692 693
        msgBox.setMinimumWidth(checkDialogMinimumWidth);
        msgBox.exec();
    }
}

hjk's avatar
hjk committed
694
bool VcsBaseSubmitEditor::checkSubmitMessage(QString *errorMessage) const
695 696 697 698
{
    const QString checkScript = submitMessageCheckScript();
    if (checkScript.isEmpty())
        return true;
699 700 701 702 703 704
    QApplication::setOverrideCursor(Qt::WaitCursor);
    const bool rc = runSubmitMessageCheckScript(checkScript, errorMessage);
    QApplication::restoreOverrideCursor();
    return rc;
}

705 706
static inline QString msgCheckScript(const QString &workingDir, const QString &cmd)
{
707
    const QString nativeCmd = QDir::toNativeSeparators(cmd);
708
    return workingDir.isEmpty() ?
hjk's avatar
hjk committed
709 710
           VcsBaseSubmitEditor::tr("Executing %1").arg(nativeCmd) :
           VcsBaseSubmitEditor::tr("Executing [%1] %2").
711
           arg(QDir::toNativeSeparators(workingDir), nativeCmd);
712 713
}

hjk's avatar
hjk committed
714
bool VcsBaseSubmitEditor::runSubmitMessageCheckScript(const QString &checkScript, QString *errorMessage) const
715
{
716 717 718 719 720
    // Write out message
    QString tempFilePattern = QDir::tempPath();
    if (!tempFilePattern.endsWith(QDir::separator()))
        tempFilePattern += QDir::separator();
    tempFilePattern += QLatin1String("msgXXXXXX.txt");
hjk's avatar
hjk committed
721
    TempFileSaver saver(tempFilePattern);
722
    saver.write(fileContents());
723
    if (!saver.finalize(errorMessage))
724 725
        return false;
    // Run check process
hjk's avatar
hjk committed
726
    VcsBaseOutputWindow *outputWindow = VcsBaseOutputWindow::instance();
727
    outputWindow->appendCommand(msgCheckScript(d->m_checkScriptWorkingDirectory, checkScript));
728
    QProcess checkProcess;
729 730
    if (!d->m_checkScriptWorkingDirectory.isEmpty())
        checkProcess.setWorkingDirectory(d->m_checkScriptWorkingDirectory);
731
    checkProcess.start(checkScript, QStringList(saver.fileName()));
732
    checkProcess.closeWriteChannel();
733 734 735 736
    if (!checkProcess.waitForStarted()) {
        *errorMessage = tr("The check script '%1' could not be started: %2").arg(checkScript, checkProcess.errorString());
        return false;
    }
737 738
    QByteArray stdOutData;
    QByteArray stdErrData;
hjk's avatar
hjk committed
739 740
    if (!SynchronousProcess::readDataFromProcess(checkProcess, 30000, &stdOutData, &stdErrData, false)) {
        SynchronousProcess::stopProcess(checkProcess);
741 742
        *errorMessage = tr("The check script '%1' timed out.").
                        arg(QDir::toNativeSeparators(checkScript));
743
        return false;
744
    }
745
    if (checkProcess.exitStatus() != QProcess::NormalExit) {
746
        *errorMessage = tr("The check script '%1' crashed.").
747
                        arg(QDir::toNativeSeparators(checkScript));
748 749
        return false;
    }
750 751 752
    if (!stdOutData.isEmpty())
        outputWindow->appendSilently(QString::fromLocal8Bit(stdOutData));
    const QString stdErr = QString::fromLocal8Bit(stdErrData);
753 754
    if (!stdErr.isEmpty())
        outputWindow->appendSilently(stdErr);
755 756
    const int exitCode = checkProcess.exitCode();
    if (exitCode != 0) {
757 758
        const QString exMessage = tr("The check script returned exit code %1.").
                                  arg(exitCode);
759 760
        outputWindow->appendError(exMessage);
        *errorMessage = stdErr;
761
        if (errorMessage->isEmpty())
762
            *errorMessage = exMessage;
763 764 765 766 767
        return false;
    }
    return true;
}

hjk's avatar
hjk committed
768
QIcon VcsBaseSubmitEditor::diffIcon()
769 770 771 772
{
    return QIcon(QLatin1String(":/vcsbase/images/diff.png"));
}

hjk's avatar
hjk committed
773
QIcon VcsBaseSubmitEditor::submitIcon()
774 775 776 777
{
    return QIcon(QLatin1String(":/vcsbase/images/submit.png"));
}

778
// Compile a list if files in the current projects. TODO: Recurse down qrc files?
hjk's avatar
hjk committed
779
QStringList VcsBaseSubmitEditor::currentProjectFiles(bool nativeSeparators, QString *name)
780 781 782
{
    if (name)
        name->clear();
783

784 785 786 787 788 789 790 791
    if (const ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectExplorerPlugin::currentProject()) {
        QStringList files = currentProject->files(ProjectExplorer::Project::ExcludeGeneratedFiles);
        if (name)
            *name = currentProject->displayName();
        if (nativeSeparators && !files.empty()) {
            const QStringList::iterator end = files.end();
            for (QStringList::iterator it = files.begin(); it != end; ++it)
                *it = QDir::toNativeSeparators(*it);
792
        }
793
        return files;
794
    }
795
    return QStringList();
796
}
797

798 799
// Reduce a list of untracked files reported by a VCS down to the files
// that are actually part of the current project(s).
hjk's avatar
hjk committed
800
void VcsBaseSubmitEditor::filterUntrackedFilesOfProject(const QString &repositoryDirectory, QStringList *untrackedFiles)
801 802 803
{
    if (untrackedFiles->empty())
        return;
hjk's avatar
hjk committed
804
    const QStringList nativeProjectFiles = VcsBaseSubmitEditor::currentProjectFiles(true);
805 806 807 808 809
    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));
810
        if (nativeProjectFiles.contains(path))
811
            ++it;
812
        else
813 814 815 816
            it = untrackedFiles->erase(it);
    }
}

817
// Helper to raise an already open submit editor to prevent opening twice.
hjk's avatar
hjk committed
818
bool VcsBaseSubmitEditor::raiseSubmitEditor()
819 820
{
    // Nothing to do?
hjk's avatar
hjk committed
821
    if (Core::IEditor *ce = Core::EditorManager::currentEditor())
hjk's avatar
hjk committed
822
        if (qobject_cast<VcsBaseSubmitEditor*>(ce))
823 824
            return true;
    // Try to activate a hidden one
hjk's avatar
hjk committed
825
    Core::EditorManager *em = Core::EditorManager::instance();
826
    foreach (Core::IEditor *e, em->openedEditors()) {
hjk's avatar
hjk committed
827
        if (qobject_cast<VcsBaseSubmitEditor*>(e)) {
hjk's avatar
hjk committed
828 829
            Core::EditorManager::activateEditor(e,
                Core::EditorManager::IgnoreNavigationHistory | Core::EditorManager::ModeSwitch);
830 831 832 833 834 835
            return true;
        }
    }
    return false;
}

hjk's avatar
hjk committed
836
} // namespace VcsBase