vcsbasesubmiteditor.cpp 27.4 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2014 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
** 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
Eike Ziller's avatar
Eike Ziller committed
13 14
** conditions see http://www.qt.io/licensing.  For further information
** use the contact form at http://www.qt.io/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18 19 20 21 22 23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24 25 26
**
** 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
27 28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
hjk's avatar
hjk committed
30

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

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

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

54
#include <projectexplorer/projecttree.h>
55
#include <projectexplorer/project.h>
56

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

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

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

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

// 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;
}

100
/*!
101
    \class VcsBase::VcsBaseSubmitEditorParameters
102

103 104
    \brief The VcsBaseSubmitEditorParameters class is a utility class
    to parametrize a VcsBaseSubmitEditor.
105 106 107
*/

/*!
hjk's avatar
hjk committed
108
    \class  VcsBase::VcsBaseSubmitEditor
109

110 111
    \brief The VcsBaseSubmitEditor class is the base class for a submit editor
    based on the SubmitEditorWidget.
112 113 114 115 116 117

    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.

Sergio Ahumada's avatar
Sergio Ahumada committed
118
    The action matching the ids (unless 0) of the parameter struct will be
119 120 121 122 123 124 125 126 127 128
    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
129 130
    signal and then asking the IDocument interface of the editor to save the file
    within a DocumentManager::blockFileChange() section
131 132 133 134 135
    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
136
namespace VcsBase {
con's avatar
con committed
137

hjk's avatar
hjk committed
138 139 140
using namespace Internal;
using namespace Utils;

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

hjk's avatar
hjk committed
146
struct VcsBaseSubmitEditorPrivate
147
{
hjk's avatar
hjk committed
148
    VcsBaseSubmitEditorPrivate(const VcsBaseSubmitEditorParameters *parameters,
hjk's avatar
hjk committed
149
                               SubmitEditorWidget *editorWidget,
150
                               VcsBaseSubmitEditor *q);
con's avatar
con committed
151

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

    QPointer<QAction> m_diffAction;
160
    QPointer<QAction> m_submitAction;
161

hjk's avatar
hjk committed
162
    NickNameDialog *m_nickNameDialog;
con's avatar
con committed
163 164
};

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

hjk's avatar
hjk committed
181
VcsBaseSubmitEditor::VcsBaseSubmitEditor(const VcsBaseSubmitEditorParameters *parameters,
hjk's avatar
hjk committed
182
                                         SubmitEditorWidget *editorWidget) :
hjk's avatar
hjk committed
183
    d(new VcsBaseSubmitEditorPrivate(parameters, editorWidget, this))
con's avatar
con committed
184
{
185
    setWidget(d->m_widget);
186
    document()->setDisplayName(QCoreApplication::translate("VCS", d->m_parameters->displayName));
187

188
    // Message font according to settings
189
    Utils::CompletingTextEdit *descriptionEdit = editorWidget->descriptionEdit();
190
    const TextEditor::FontSettings fs = TextEditor::TextEditorSettings::fontSettings();
191 192 193
    const QTextCharFormat tf = fs.toTextCharFormat(TextEditor::C_TEXT);
    descriptionEdit->setFont(tf.font());
    const QTextCharFormat selectionFormat = fs.toTextCharFormat(TextEditor::C_SELECTION);
Lorenz Haas's avatar
Lorenz Haas committed
194
    QPalette pal;
195 196 197 198 199 200 201
    pal.setColor(QPalette::Base, tf.background().color());
    pal.setColor(QPalette::Text, tf.foreground().color());
    pal.setColor(QPalette::Foreground, tf.foreground().color());
    if (selectionFormat.background().style() != Qt::NoBrush)
        pal.setColor(QPalette::Highlight, selectionFormat.background().color());
    pal.setBrush(QPalette::HighlightedText, selectionFormat.foreground());
    descriptionEdit->setPalette(pal);
202

203
    d->m_file->setModified(false);
con's avatar
con committed
204 205
    // We are always clean to prevent the editor manager from asking to save.

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

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

    // wrapping. etc
    slotUpdateEditorSettings(settings);
hjk's avatar
hjk committed
234 235 236
    connect(VcsPlugin::instance(),
            SIGNAL(settingsChanged(VcsBase::Internal::CommonVcsSettings)),
            this, SLOT(slotUpdateEditorSettings(VcsBase::Internal::CommonVcsSettings)));
237
    // Commit data refresh might lead to closing the editor, so use a queued connection
238
    connect(Core::EditorManager::instance(), SIGNAL(currentEditorChanged(Core::IEditor*)),
239 240 241
            this, SLOT(slotRefreshCommitData()), Qt::QueuedConnection);
    connect(Core::ICore::mainWindow(), SIGNAL(windowActivated()),
            this, SLOT(slotRefreshCommitData()), Qt::QueuedConnection);
242

con's avatar
con committed
243
    Aggregation::Aggregate *aggregate = new Aggregation::Aggregate;
244
    aggregate->add(new Core::BaseTextFind(descriptionEdit));
con's avatar
con committed
245 246 247
    aggregate->add(this);
}

hjk's avatar
hjk committed
248
VcsBaseSubmitEditor::~VcsBaseSubmitEditor()
con's avatar
con committed
249
{
250 251 252
    delete d->m_toolWidget;
    delete d->m_widget;
    delete d;
con's avatar
con committed
253 254
}

hjk's avatar
hjk committed
255
void VcsBaseSubmitEditor::slotUpdateEditorSettings(const CommonVcsSettings &s)
256 257 258 259 260
{
    setLineWrapWidth(s.lineWrapWidth);
    setLineWrap(s.lineWrap);
}

261 262 263 264 265 266
void VcsBaseSubmitEditor::slotRefreshCommitData()
{
    if (Core::EditorManager::currentEditor() == this)
        updateFileModel();
}

267 268 269 270 271
// 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
272
    foreach (const QString &field, rawFields) {
273 274 275 276 277 278 279
        const QString trimmedField = field.trimmed();
        if (!trimmedField.isEmpty())
            rc.push_back(trimmedField);
    }
    return rc;
}

hjk's avatar
hjk committed
280
void VcsBaseSubmitEditor::createUserFields(const QString &fieldConfigFile)
281
{
282
    Utils::FileReader reader;
hjk's avatar
hjk committed
283
    if (!reader.fetch(fieldConfigFile, QIODevice::Text, Core::ICore::mainWindow()))
284 285
        return;
    // Parse into fields
286
    const QStringList fields = fieldTexts(QString::fromUtf8(reader.data()));
287 288 289
    if (fields.empty())
        return;
    // Create a completer on user names
hjk's avatar
hjk committed
290
    const QStandardItemModel *nickNameModel = VcsPlugin::instance()->nickNameModel();
hjk's avatar
hjk committed
291
    QCompleter *completer = new QCompleter(NickNameDialog::nickNameList(nickNameModel), this);
292

hjk's avatar
hjk committed
293
    SubmitFieldWidget *fieldWidget = new SubmitFieldWidget;
294
    connect(fieldWidget, SIGNAL(browseButtonClicked(int,QString)),
295
            this, SLOT(slotSetFieldNickName(int)));
296 297 298 299
    fieldWidget->setCompleter(completer);
    fieldWidget->setAllowDuplicateFields(true);
    fieldWidget->setHasBrowseButton(true);
    fieldWidget->setFields(fields);
300
    d->m_widget->addSubmitFieldWidget(fieldWidget);
301 302
}

hjk's avatar
hjk committed
303
void VcsBaseSubmitEditor::registerActions(QAction *editorUndoAction, QAction *editorRedoAction,
hjk's avatar
hjk committed
304
                                          QAction *submitAction, QAction *diffAction)
305
{
306 307 308
    d->m_widget->registerActions(editorUndoAction, editorRedoAction, submitAction, diffAction);
    d->m_diffAction = diffAction;
    d->m_submitAction = submitAction;
309 310
}

hjk's avatar
hjk committed
311
void VcsBaseSubmitEditor::unregisterActions(QAction *editorUndoAction,  QAction *editorRedoAction,
312 313
                           QAction *submitAction, QAction *diffAction)
{
314 315
    d->m_widget->unregisterActions(editorUndoAction, editorRedoAction, submitAction, diffAction);
    d->m_diffAction = d->m_submitAction = 0;
316
}
317

hjk's avatar
hjk committed
318
QAbstractItemView::SelectionMode VcsBaseSubmitEditor::fileListSelectionMode() const
319
{
320
    return d->m_widget->fileListSelectionMode();
321 322
}

hjk's avatar
hjk committed
323
void VcsBaseSubmitEditor::setFileListSelectionMode(QAbstractItemView::SelectionMode sm)
324
{
325
    d->m_widget->setFileListSelectionMode(sm);
326 327
}

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

hjk's avatar
hjk committed
333
void VcsBaseSubmitEditor::setEmptyFileListEnabled(bool e)
334
{
335
    d->m_widget->setEmptyFileListEnabled(e);
336 337
}

hjk's avatar
hjk committed
338
bool VcsBaseSubmitEditor::lineWrap() const
339
{
340
    return d->m_widget->lineWrap();
341 342
}

hjk's avatar
hjk committed
343
void VcsBaseSubmitEditor::setLineWrap(bool w)
344
{
345
    d->m_widget->setLineWrap(w);
346 347
}

hjk's avatar
hjk committed
348
int VcsBaseSubmitEditor::lineWrapWidth() const
349
{
350
    return d->m_widget->lineWrapWidth();
351 352
}

hjk's avatar
hjk committed
353
void VcsBaseSubmitEditor::setLineWrapWidth(int w)
354
{
355
    d->m_widget->setLineWrapWidth(w);
356 357
}

hjk's avatar
hjk committed
358
void VcsBaseSubmitEditor::slotDescriptionChanged()
con's avatar
con committed
359 360 361
{
}

hjk's avatar
hjk committed
362
bool VcsBaseSubmitEditor::open(QString *errorString, const QString &fileName, const QString &realFileName)
con's avatar
con committed
363 364 365 366
{
    if (fileName.isEmpty())
        return false;

367
    Utils::FileReader reader;
368
    if (!reader.fetch(realFileName, QIODevice::Text, errorString))
con's avatar
con committed
369 370
        return false;

371
    const QString text = QString::fromLocal8Bit(reader.data());
372
    if (!setFileContents(text.toUtf8()))
con's avatar
con committed
373 374
        return false;

375
    d->m_file->setFilePath(Utils::FileName::fromString(fileName));
376
    d->m_file->setModified(fileName != realFileName);
con's avatar
con committed
377 378 379
    return true;
}

380
Core::IDocument *VcsBaseSubmitEditor::document()
con's avatar
con committed
381
{
382
    return d->m_file;
con's avatar
con committed
383 384
}

hjk's avatar
hjk committed
385
QString VcsBaseSubmitEditor::checkScriptWorkingDirectory() const
386
{
387
    return d->m_checkScriptWorkingDirectory;
388 389
}

hjk's avatar
hjk committed
390
void VcsBaseSubmitEditor::setCheckScriptWorkingDirectory(const QString &s)
391
{
392
    d->m_checkScriptWorkingDirectory = s;
393 394
}

395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
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
411
QWidget *VcsBaseSubmitEditor::toolBar()
con's avatar
con committed
412
{
413 414 415
    if (!wantToolBar)
        return 0;

416 417
    if (d->m_toolWidget)
        return d->m_toolWidget;
con's avatar
con committed
418

419
    if (!d->m_diffAction && !d->m_submitAction)
con's avatar
con committed
420 421 422
        return 0;

    // Create
423 424
    d->m_toolWidget = createToolBar(d->m_widget, d->m_submitAction, d->m_diffAction);
    return d->m_toolWidget;
con's avatar
con committed
425 426
}

hjk's avatar
hjk committed
427
QStringList VcsBaseSubmitEditor::checkedFiles() const
con's avatar
con committed
428
{
429
    return d->m_widget->checkedFiles();
con's avatar
con committed
430 431
}

432
void VcsBaseSubmitEditor::setFileModel(SubmitFileModel *model, const QString &repositoryDirectory)
con's avatar
con committed
433
{
434
    QTC_ASSERT(model, return);
435
    if (SubmitFileModel *oldModel = d->m_widget->fileModel()) {
436
        model->updateSelections(oldModel);
437 438
        delete oldModel;
    }
439
    d->m_widget->setFileModel(model);
440 441

    QSet<QString> uniqueSymbols;
442
    const CPlusPlus::Snapshot cppSnapShot = CppTools::CppModelManager::instance()->snapshot();
443 444

    // Iterate over the files and get interesting symbols
445 446
    for (int row = 0; row < model->rowCount(); ++row) {
        const QFileInfo fileInfo(repositoryDirectory, model->file(row));
447 448 449 450 451 452 453 454 455 456 457 458 459

        // 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;

460 461 462 463 464
                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())))
                {
465
                    uniqueSymbols.insert(QString::fromUtf8(symId->chars()));
466
                }
467 468 469 470 471

                // Handle specific case : get "Foo" in "void Foo::function() {}"
                if (sym->isFunction() && !sym->asFunction()->isDeclaration()) {
                    const char *className = belongingClassName(sym->asFunction());
                    if (acceptsWordForCompletion(className))
472
                        uniqueSymbols.insert(QString::fromUtf8(className));
473 474 475 476 477 478 479 480 481 482 483 484 485 486
                }

                ++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
487 488
}

489
SubmitFileModel *VcsBaseSubmitEditor::fileModel() const
con's avatar
con committed
490
{
491
    return d->m_widget->fileModel();
con's avatar
con committed
492 493
}

494
QStringList VcsBaseSubmitEditor::rowsToFiles(const QList<int> &rows) const
con's avatar
con committed
495
{
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
    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
513 514
}

hjk's avatar
hjk committed
515
QByteArray VcsBaseSubmitEditor::fileContents() const
con's avatar
con committed
516
{
517
    return d->m_widget->descriptionText().toLocal8Bit();
con's avatar
con committed
518 519
}

520
bool VcsBaseSubmitEditor::setFileContents(const QByteArray &contents)
con's avatar
con committed
521
{
522
    d->m_widget->setDescriptionText(QString::fromUtf8(contents));
con's avatar
con committed
523 524 525
    return true;
}

526 527 528 529 530 531 532 533 534 535
bool VcsBaseSubmitEditor::isDescriptionMandatory() const
{
    return d->m_widget->isDescriptionMandatory();
}

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

536 537
enum { checkDialogMinimumWidth = 500 };

hjk's avatar
hjk committed
538 539
VcsBaseSubmitEditor::PromptSubmitResult
        VcsBaseSubmitEditor::promptSubmit(const QString &title,
540 541
                                          const QString &question,
                                          const QString &checkFailureQuestion,
542
                                          bool *promptSetting,
543
                                          bool forcePrompt,
544
                                          bool canCommitOnFailure)
545
{
546
    SubmitEditorWidget *submitWidget = static_cast<SubmitEditorWidget *>(this->widget());
547

Eike Ziller's avatar
Eike Ziller committed
548
    Core::EditorManager::activateEditor(this, Core::EditorManager::IgnoreNavigationHistory);
549

550 551 552
    if (!submitWidget->isEnabled())
        return SubmitDiscarded;

553 554 555
    QString errorMessage;
    QMessageBox::StandardButton answer = QMessageBox::Yes;

556
    const bool prompt = forcePrompt || *promptSetting;
557

hjk's avatar
hjk committed
558
    QWidget *parent = Core::ICore::mainWindow();
559 560
    // Pop up a message depending on whether the check succeeded and the
    // user wants to be prompted
561 562
    bool canCommit = checkSubmitMessage(&errorMessage) && submitWidget->canSubmit();
    if (canCommit) {
563
        // Check ok, do prompt?
564
        if (prompt) {
565 566 567
            // Provide check box to turn off prompt ONLY if it was not forced
            if (*promptSetting && !forcePrompt) {
                const QDialogButtonBox::StandardButton danswer =
568
                        Utils::CheckableMessageBox::question(parent, title, question,
569
                                                                   tr("Prompt to submit"), promptSetting,
Tobias Hunger's avatar
Tobias Hunger committed
570 571
                                                                   QDialogButtonBox::Yes|QDialogButtonBox::No|
                                                                   QDialogButtonBox::Cancel,
572
                                                                   QDialogButtonBox::Yes);
573
                answer = Utils::CheckableMessageBox::dialogButtonBoxToMessageBoxButton(danswer);
574 575 576 577 578
            } else {
                answer = QMessageBox::question(parent, title, question,
                                               QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel,
                                               QMessageBox::Yes);
            }
579 580 581
        }
    } else {
        // Check failed.
582 583 584 585 586
        QMessageBox::StandardButtons buttons;
        if (canCommitOnFailure)
            buttons = QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel;
        else
            buttons = QMessageBox::Yes|QMessageBox::No;
587
        QMessageBox msgBox(QMessageBox::Question, title, checkFailureQuestion,
588
                           buttons, parent);
589 590 591 592 593
        msgBox.setDefaultButton(QMessageBox::Cancel);
        msgBox.setInformativeText(errorMessage);
        msgBox.setMinimumWidth(checkDialogMinimumWidth);
        answer = static_cast<QMessageBox::StandardButton>(msgBox.exec());
    }
594 595 596
    if (!canCommit && !canCommitOnFailure) {
        switch (answer) {
        case QMessageBox::No:
hjk's avatar
hjk committed
597
            return SubmitDiscarded;
598
        case QMessageBox::Yes:
hjk's avatar
hjk committed
599
            return SubmitCanceled;
600 601 602 603 604 605 606 607 608 609 610 611
        default:
            break;
        }
    } else {
        switch (answer) {
        case QMessageBox::No:
            return SubmitDiscarded;
        case QMessageBox::Yes:
            return SubmitConfirmed;
        default:
            break;
        }
612
    }
613

Tobias Hunger's avatar
Tobias Hunger committed
614
    return SubmitCanceled;
615 616
}

hjk's avatar
hjk committed
617
QString VcsBaseSubmitEditor::promptForNickName()
618
{
619
    if (!d->m_nickNameDialog)
hjk's avatar
hjk committed
620
        d->m_nickNameDialog = new NickNameDialog(VcsPlugin::instance()->nickNameModel(), d->m_widget);
621 622
    if (d->m_nickNameDialog->exec() == QDialog::Accepted)
       return d->m_nickNameDialog->nickName();
623 624 625
    return QString();
}

hjk's avatar
hjk committed
626
void VcsBaseSubmitEditor::slotInsertNickName()
627 628 629
{
    const QString nick = promptForNickName();
    if (!nick.isEmpty())
630
        d->m_widget->descriptionEdit()->textCursor().insertText(nick);
631 632
}

hjk's avatar
hjk committed
633
void VcsBaseSubmitEditor::slotSetFieldNickName(int i)
634
{
hjk's avatar
hjk committed
635
    if (SubmitFieldWidget *sfw = d->m_widget->submitFieldWidgets().front()) {
636 637 638 639
        const QString nick = promptForNickName();
        if (!nick.isEmpty())
            sfw->setFieldValue(i, nick);
    }
640 641
}

hjk's avatar
hjk committed
642
void VcsBaseSubmitEditor::slotCheckSubmitMessage()
643 644 645
{
    QString errorMessage;
    if (!checkSubmitMessage(&errorMessage)) {
Leena Miettinen's avatar
Leena Miettinen committed
646
        QMessageBox msgBox(QMessageBox::Warning, tr("Submit Message Check Failed"),
647
                           errorMessage, QMessageBox::Ok, d->m_widget);
648 649 650 651 652
        msgBox.setMinimumWidth(checkDialogMinimumWidth);
        msgBox.exec();
    }
}

hjk's avatar
hjk committed
653
bool VcsBaseSubmitEditor::checkSubmitMessage(QString *errorMessage) const
654 655 656 657
{
    const QString checkScript = submitMessageCheckScript();
    if (checkScript.isEmpty())
        return true;
658 659 660 661 662 663
    QApplication::setOverrideCursor(Qt::WaitCursor);
    const bool rc = runSubmitMessageCheckScript(checkScript, errorMessage);
    QApplication::restoreOverrideCursor();
    return rc;
}

664 665
static inline QString msgCheckScript(const QString &workingDir, const QString &cmd)
{
666
    const QString nativeCmd = QDir::toNativeSeparators(cmd);
667
    return workingDir.isEmpty() ?
hjk's avatar
hjk committed
668 669
           VcsBaseSubmitEditor::tr("Executing %1").arg(nativeCmd) :
           VcsBaseSubmitEditor::tr("Executing [%1] %2").
670
           arg(QDir::toNativeSeparators(workingDir), nativeCmd);
671 672
}

hjk's avatar
hjk committed
673
bool VcsBaseSubmitEditor::runSubmitMessageCheckScript(const QString &checkScript, QString *errorMessage) const
674
{
675 676 677 678 679
    // Write out message
    QString tempFilePattern = QDir::tempPath();
    if (!tempFilePattern.endsWith(QDir::separator()))
        tempFilePattern += QDir::separator();
    tempFilePattern += QLatin1String("msgXXXXXX.txt");
hjk's avatar
hjk committed
680
    TempFileSaver saver(tempFilePattern);
681
    saver.write(fileContents());
682
    if (!saver.finalize(errorMessage))
683 684
        return false;
    // Run check process
685 686
    VcsOutputWindow::appendShellCommandLine(msgCheckScript(d->m_checkScriptWorkingDirectory,
                                                           checkScript));
687
    QProcess checkProcess;
688 689
    if (!d->m_checkScriptWorkingDirectory.isEmpty())
        checkProcess.setWorkingDirectory(d->m_checkScriptWorkingDirectory);
690
    checkProcess.start(checkScript, QStringList(saver.fileName()));
691
    checkProcess.closeWriteChannel();
692
    if (!checkProcess.waitForStarted()) {
693
        *errorMessage = tr("The check script \"%1\" could not be started: %2").arg(checkScript, checkProcess.errorString());
694 695
        return false;
    }
696 697
    QByteArray stdOutData;
    QByteArray stdErrData;
hjk's avatar
hjk committed
698 699
    if (!SynchronousProcess::readDataFromProcess(checkProcess, 30000, &stdOutData, &stdErrData, false)) {
        SynchronousProcess::stopProcess(checkProcess);
700
        *errorMessage = tr("The check script \"%1\" timed out.").
701
                        arg(QDir::toNativeSeparators(checkScript));
702
        return false;
703
    }
704
    if (checkProcess.exitStatus() != QProcess::NormalExit) {
705
        *errorMessage = tr("The check script \"%1\" crashed.").
706
                        arg(QDir::toNativeSeparators(checkScript));
707 708
        return false;
    }
709
    if (!stdOutData.isEmpty())
710
        VcsOutputWindow::appendSilently(QString::fromLocal8Bit(stdOutData));
711
    const QString stdErr = QString::fromLocal8Bit(stdErrData);
712
    if (!stdErr.isEmpty())
713
        VcsOutputWindow::appendSilently(stdErr);
714 715
    const int exitCode = checkProcess.exitCode();
    if (exitCode != 0) {
716 717
        const QString exMessage = tr("The check script returned exit code %1.").
                                  arg(exitCode);
718
        VcsOutputWindow::appendError(exMessage);
719
        *errorMessage = stdErr;
720
        if (errorMessage->isEmpty())
721
            *errorMessage = exMessage;
722 723 724 725 726
        return false;
    }
    return true;
}

hjk's avatar
hjk committed
727
QIcon VcsBaseSubmitEditor::diffIcon()
728 729 730 731
{
    return QIcon(QLatin1String(":/vcsbase/images/diff.png"));
}

hjk's avatar
hjk committed
732
QIcon VcsBaseSubmitEditor::submitIcon()
733 734 735 736
{
    return QIcon(QLatin1String(":/vcsbase/images/submit.png"));
}

737
// Compile a list if files in the current projects. TODO: Recurse down qrc files?
hjk's avatar
hjk committed
738
QStringList VcsBaseSubmitEditor::currentProjectFiles(bool nativeSeparators, QString *name)
739 740 741
{
    if (name)
        name->clear();
742

743
    if (const ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectTree::currentProject()) {
744 745 746 747 748 749 750
        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);
751
        }
752
        return files;
753
    }
754
    return QStringList();
755
}
756

757 758
// 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
759
void VcsBaseSubmitEditor::filterUntrackedFilesOfProject(const QString &repositoryDirectory, QStringList *untrackedFiles)
760 761 762
{
    if (untrackedFiles->empty())
        return;
hjk's avatar
hjk committed
763
    const QStringList nativeProjectFiles = VcsBaseSubmitEditor::currentProjectFiles(true);
764 765 766 767 768
    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));
769
        if (nativeProjectFiles.contains(path))
770
            ++it;
771
        else
772 773 774 775
            it = untrackedFiles->erase(it);
    }
}

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