submiteditorwidget.cpp 21.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 "submiteditorwidget.h"
31
#include "submitfieldwidget.h"
32
#include "submitfilemodel.h"
con's avatar
con committed
33
34
#include "ui_submiteditorwidget.h"

35
36
37
38
39
40
41
42
43
44
#include <QDebug>
#include <QPointer>
#include <QTimer>
#include <QScopedPointer>

#include <QMenu>
#include <QHBoxLayout>
#include <QToolButton>
#include <QSpacerItem>
#include <QShortcut>
45

hjk's avatar
hjk committed
46
enum { debug = 0 };
47
enum { defaultLineWidth = 72 };
con's avatar
con committed
48

49
/*!
50
    \class VcsBase::SubmitEditorWidget
51

52
53
    \brief The SubmitEditorWidget class presents a VCS commit message in a text
    editor and a
54
55
56
     checkable list of modified files in a list window.

    The user can delete files from the list by unchecking them or diff the selection
57
58
    by doubleclicking. A list model which contains state and file columns should be
    set using setFileModel().
59
60
61
62
63
64
65
66
67
68
69
70
71
72

    Additionally, standard creator actions  can be registered:
    Undo/redo will be set up to work with the description editor.
    Submit will be set up to be enabled according to checkstate.
    Diff will be set up to trigger diffSelected().

    Note that the actions are connected by signals; in the rare event that there
    are several instances of the SubmitEditorWidget belonging to the same
    context active, the actions must be registered/unregistered in the editor
    change event.
    Care should be taken to ensure the widget is deleted properly when the
    editor closes.
*/

73
namespace VcsBase {
con's avatar
con committed
74

75
76
// QActionPushButton: A push button tied to an action
// (similar to a QToolButton)
77
class QActionPushButton : public QToolButton
hjk's avatar
hjk committed
78
{
79
80
81
82
83
84
85
86
87
    Q_OBJECT
public:
    explicit QActionPushButton(QAction *a);

private slots:
    void actionChanged();
};

QActionPushButton::QActionPushButton(QAction *a) :
88
     QToolButton()
89
{
90
91
92
    setIcon(a->icon());
    setText(a->text());
    setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
93
94
95
96
97
98
99
    connect(a, SIGNAL(changed()), this, SLOT(actionChanged()));
    connect(this, SIGNAL(clicked()), a, SLOT(trigger()));
    setEnabled(a->isEnabled());
}

void QActionPushButton::actionChanged()
{
100
    if (const QAction *a = qobject_cast<QAction*>(sender())) {
101
        setEnabled(a->isEnabled());
102
103
        setText(a->text());
    }
104
105
}

106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// A helper parented on a QAction,
// making QAction::setText() a slot (which it currently is not).
class QActionSetTextSlotHelper : public QObject
{
    Q_OBJECT
public:
    explicit QActionSetTextSlotHelper(QAction *a) : QObject(a) {}

public slots:
    void setText(const QString &t) {
        if (QAction *action = qobject_cast<QAction *>(parent()))
            action->setText(t);
    }
};

121
122
123
124
125
126
127
128
129
130
131
132
133
134
// Helpers to retrieve model data
// Convenience to extract a list of selected indexes
QList<int> selectedRows(const QAbstractItemView *view)
{
    const QModelIndexList indexList = view->selectionModel()->selectedRows(0);
    if (indexList.empty())
        return QList<int>();
    QList<int> rc;
    const QModelIndexList::const_iterator cend = indexList.constEnd();
    for (QModelIndexList::const_iterator it = indexList.constBegin(); it != cend; ++it)
        rc.push_back(it->row());
    return rc;
}

135
// -----------  SubmitEditorWidgetPrivate
136

hjk's avatar
hjk committed
137
138
struct SubmitEditorWidgetPrivate
{
139
140
141
    // A pair of position/action to extend context menus
    typedef QPair<int, QPointer<QAction> > AdditionalContextMenuAction;

con's avatar
con committed
142
143
144
145
    SubmitEditorWidgetPrivate();

    Ui::SubmitEditorWidget m_ui;
    bool m_filesSelected;
146
    int m_activatedRow;
147
    bool m_emptyFileListEnabled;
148
149

    QList<AdditionalContextMenuAction> descriptionEditContextMenuActions;
150
151
    QVBoxLayout *m_fieldLayout;
    QList<SubmitFieldWidget *> m_fieldWidgets;
152
    QShortcut *m_submitShortcut;
153
    int m_lineWidth;
154
155

    bool m_commitEnabled;
dt's avatar
dt committed
156
    bool m_ignoreChange;
157
    bool m_descriptionMandatory;
158
159

    QActionPushButton *m_submitButton;
con's avatar
con committed
160
161
162
163
};

SubmitEditorWidgetPrivate::SubmitEditorWidgetPrivate() :
    m_filesSelected(false),
164
    m_activatedRow(-1),
165
    m_emptyFileListEnabled(false),
166
    m_fieldLayout(0),
167
    m_submitShortcut(0),
168
    m_lineWidth(defaultLineWidth),
dt's avatar
dt committed
169
    m_commitEnabled(false),
170
    m_ignoreChange(false),
171
172
    m_descriptionMandatory(true),
    m_submitButton(0)
con's avatar
con committed
173
174
175
176
177
{
}

SubmitEditorWidget::SubmitEditorWidget(QWidget *parent) :
    QWidget(parent),
hjk's avatar
hjk committed
178
    d(new SubmitEditorWidgetPrivate)
con's avatar
con committed
179
{
hjk's avatar
hjk committed
180
181
182
183
184
    d->m_ui.setupUi(this);
    d->m_ui.description->setContextMenuPolicy(Qt::CustomContextMenu);
    d->m_ui.description->setLineWrapMode(QTextEdit::NoWrap);
    d->m_ui.description->setWordWrapMode(QTextOption::WordWrap);
    connect(d->m_ui.description, SIGNAL(customContextMenuRequested(QPoint)),
185
            this, SLOT(editorCustomContextMenuRequested(QPoint)));
hjk's avatar
hjk committed
186
    connect(d->m_ui.description, SIGNAL(textChanged()),
Yuchen Deng's avatar
Yuchen Deng committed
187
            this, SLOT(descriptionTextChanged()));
188

con's avatar
con committed
189
    // File List
hjk's avatar
hjk committed
190
191
    d->m_ui.fileView->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(d->m_ui.fileView, SIGNAL(customContextMenuRequested(QPoint)),
192
            this, SLOT(fileListCustomContextMenuRequested(QPoint)));
hjk's avatar
hjk committed
193
194
195
    d->m_ui.fileView->setSelectionMode(QAbstractItemView::ExtendedSelection);
    d->m_ui.fileView->setRootIsDecorated(false);
    connect(d->m_ui.fileView, SIGNAL(doubleClicked(QModelIndex)),
196
            this, SLOT(diffActivated(QModelIndex)));
con's avatar
con committed
197

hjk's avatar
hjk committed
198
    connect(d->m_ui.checkAllCheckBox, SIGNAL(stateChanged(int)),
dt's avatar
dt committed
199
200
            this, SLOT(checkAllToggled()));

con's avatar
con committed
201
    setFocusPolicy(Qt::StrongFocus);
hjk's avatar
hjk committed
202
    setFocusProxy(d->m_ui.description);
con's avatar
con committed
203
204
205
206
}

SubmitEditorWidget::~SubmitEditorWidget()
{
hjk's avatar
hjk committed
207
    delete d;
con's avatar
con committed
208
209
}

210
void SubmitEditorWidget::registerActions(QAction *editorUndoAction, QAction *editorRedoAction,
con's avatar
con committed
211
212
213
                         QAction *submitAction, QAction *diffAction)
{
    if (editorUndoAction) {
hjk's avatar
hjk committed
214
215
216
        editorUndoAction->setEnabled(d->m_ui.description->document()->isUndoAvailable());
        connect(d->m_ui.description, SIGNAL(undoAvailable(bool)), editorUndoAction, SLOT(setEnabled(bool)));
        connect(editorUndoAction, SIGNAL(triggered()), d->m_ui.description, SLOT(undo()));
con's avatar
con committed
217
218
    }
    if (editorRedoAction) {
hjk's avatar
hjk committed
219
220
221
        editorRedoAction->setEnabled(d->m_ui.description->document()->isRedoAvailable());
        connect(d->m_ui.description, SIGNAL(redoAvailable(bool)), editorRedoAction, SLOT(setEnabled(bool)));
        connect(editorRedoAction, SIGNAL(triggered()), d->m_ui.description, SLOT(redo()));
con's avatar
con committed
222
223
224
    }

    if (submitAction) {
225
        if (debug) {
226
227
            const SubmitFileModel *model = fileModel();
            int count = model ? model->rowCount() : 0;
228
            qDebug() << Q_FUNC_INFO << submitAction << count << "items";
229
        }
hjk's avatar
hjk committed
230
        d->m_commitEnabled = !canSubmit();
231
        connect(this, SIGNAL(submitActionEnabledChanged(bool)), submitAction, SLOT(setEnabled(bool)));
232
233
234
235
236
        // Wire setText via QActionSetTextSlotHelper.
        QActionSetTextSlotHelper *actionSlotHelper = submitAction->findChild<QActionSetTextSlotHelper *>();
        if (!actionSlotHelper)
            actionSlotHelper = new QActionSetTextSlotHelper(submitAction);
        connect(this, SIGNAL(submitActionTextChanged(QString)), actionSlotHelper, SLOT(setText(QString)));
237
238
        d->m_submitButton = new QActionPushButton(submitAction);
        d->m_ui.buttonLayout->addWidget(d->m_submitButton);
hjk's avatar
hjk committed
239
240
241
        if (!d->m_submitShortcut)
            d->m_submitShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return), this);
        connect(d->m_submitShortcut, SIGNAL(activated()), submitAction, SLOT(trigger()));
con's avatar
con committed
242
243
244
    }
    if (diffAction) {
        if (debug)
hjk's avatar
hjk committed
245
246
            qDebug() << diffAction << d->m_filesSelected;
        diffAction->setEnabled(d->m_filesSelected);
247
248
        connect(this, SIGNAL(fileSelectionChanged(bool)), diffAction, SLOT(setEnabled(bool)));
        connect(diffAction, SIGNAL(triggered()), this, SLOT(triggerDiffSelected()));
hjk's avatar
hjk committed
249
        d->m_ui.buttonLayout->addWidget(new QActionPushButton(diffAction));
con's avatar
con committed
250
251
252
253
254
255
256
    }
}

void SubmitEditorWidget::unregisterActions(QAction *editorUndoAction,  QAction *editorRedoAction,
                                           QAction *submitAction, QAction *diffAction)
{
    if (editorUndoAction) {
hjk's avatar
hjk committed
257
258
        disconnect(d->m_ui.description, SIGNAL(undoAvailableChanged(bool)), editorUndoAction, SLOT(setEnabled(bool)));
        disconnect(editorUndoAction, SIGNAL(triggered()), d->m_ui.description, SLOT(undo()));
con's avatar
con committed
259
260
    }
    if (editorRedoAction) {
hjk's avatar
hjk committed
261
262
        disconnect(d->m_ui.description, SIGNAL(redoAvailableChanged(bool)), editorRedoAction, SLOT(setEnabled(bool)));
        disconnect(editorRedoAction, SIGNAL(triggered()), d->m_ui.description, SLOT(redo()));
con's avatar
con committed
263
264
    }

265
    if (submitAction) {
266
        disconnect(this, SIGNAL(submitActionEnabledChanged(bool)), submitAction, SLOT(setEnabled(bool)));
267
268
269
        // Just deactivate the QActionSetTextSlotHelper on the action
        disconnect(this, SIGNAL(submitActionTextChanged(QString)), 0, 0);
    }
con's avatar
con committed
270
271
272
273
274
275
276

    if (diffAction) {
         disconnect(this, SIGNAL(fileSelectionChanged(bool)), diffAction, SLOT(setEnabled(bool)));
         disconnect(diffAction, SIGNAL(triggered()), this, SLOT(triggerDiffSelected()));
    }
}

277
278
279
// Make sure we have one terminating NL. Do not trim front as leading space might be
// required for some formattings.
static inline QString trimMessageText(QString t)
con's avatar
con committed
280
{
281
282
283
284
285
286
287
288
289
290
    if (t.isEmpty())
        return t;
    // Trim back of string.
    const int last = t.size() - 1;
    int lastWordCharacter = last;
    for ( ; lastWordCharacter >= 0 && t.at(lastWordCharacter).isSpace() ; lastWordCharacter--) ;
    if (lastWordCharacter != last)
        t.truncate(lastWordCharacter + 1);
    t += QLatin1Char('\n');
    return t;
291
292
293
294
295
296
297
298
299
}

// Extract the wrapped text from a text edit, which performs
// the wrapping only optically.
static QString wrappedText(const QTextEdit *e)
{
    const QChar newLine = QLatin1Char('\n');
    QString rc;
    QTextCursor cursor(e->document());
300
    cursor.movePosition(QTextCursor::Start);
301
302
303
304
    while (!cursor.atEnd()) {
        cursor.select(QTextCursor::LineUnderCursor);
        rc += cursor.selectedText();
        rc += newLine;
305
        cursor.movePosition(QTextCursor::EndOfLine); // Mac needs it
Daniel Teske's avatar
Daniel Teske committed
306
        cursor.movePosition(QTextCursor::NextCharacter);
307
308
    }
    return rc;
con's avatar
con committed
309
310
311
312
}

QString SubmitEditorWidget::descriptionText() const
{
hjk's avatar
hjk committed
313
314
    QString rc = trimMessageText(lineWrap() ? wrappedText(d->m_ui.description) :
                                              d->m_ui.description->toPlainText());
315
    // append field entries
316
    foreach (const SubmitFieldWidget *fw, d->m_fieldWidgets)
317
        rc += fw->fieldValues();
318
    return cleanupDescription(rc);
con's avatar
con committed
319
320
321
322
}

void SubmitEditorWidget::setDescriptionText(const QString &text)
{
hjk's avatar
hjk committed
323
    d->m_ui.description->setPlainText(text);
con's avatar
con committed
324
325
}

326
327
bool SubmitEditorWidget::lineWrap() const
{
hjk's avatar
hjk committed
328
    return d->m_ui.description->lineWrapMode() != QTextEdit::NoWrap;
329
330
331
332
333
334
335
}

void SubmitEditorWidget::setLineWrap(bool v)
{
    if (debug)
        qDebug() << Q_FUNC_INFO << v;
    if (v) {
hjk's avatar
hjk committed
336
337
        d->m_ui.description->setLineWrapColumnOrWidth(d->m_lineWidth);
        d->m_ui.description->setLineWrapMode(QTextEdit::FixedColumnWidth);
338
    } else {
hjk's avatar
hjk committed
339
        d->m_ui.description->setLineWrapMode(QTextEdit::NoWrap);
340
341
342
343
344
    }
}

int SubmitEditorWidget::lineWrapWidth() const
{
hjk's avatar
hjk committed
345
    return d->m_lineWidth;
346
347
348
349
350
351
}

void SubmitEditorWidget::setLineWrapWidth(int v)
{
    if (debug)
        qDebug() << Q_FUNC_INFO << v << lineWrap();
hjk's avatar
hjk committed
352
    if (d->m_lineWidth == v)
353
        return;
hjk's avatar
hjk committed
354
    d->m_lineWidth = v;
355
    if (lineWrap())
hjk's avatar
hjk committed
356
        d->m_ui.description->setLineWrapColumnOrWidth(v);
357
358
}

359
360
361
362
363
364
365
366
367
368
bool SubmitEditorWidget::isDescriptionMandatory() const
{
    return d->m_descriptionMandatory;
}

void SubmitEditorWidget::setDescriptionMandatory(bool v)
{
    d->m_descriptionMandatory = v;
}

369
370
QAbstractItemView::SelectionMode SubmitEditorWidget::fileListSelectionMode() const
{
hjk's avatar
hjk committed
371
    return d->m_ui.fileView->selectionMode();
372
373
374
375
}

void SubmitEditorWidget::setFileListSelectionMode(QAbstractItemView::SelectionMode sm)
{
hjk's avatar
hjk committed
376
    d->m_ui.fileView->setSelectionMode(sm);
377
378
}

379
void SubmitEditorWidget::setFileModel(SubmitFileModel *model)
con's avatar
con committed
380
{
hjk's avatar
hjk committed
381
    d->m_ui.fileView->clearSelection(); // trigger the change signals
con's avatar
con committed
382

hjk's avatar
hjk committed
383
    d->m_ui.fileView->setModel(model);
con's avatar
con committed
384

385
386
387
    if (model->rowCount()) {
        const int columnCount = model->columnCount();
        for (int c = 0;  c < columnCount; c++)
hjk's avatar
hjk committed
388
            d->m_ui.fileView->resizeColumnToContents(c);
con's avatar
con committed
389
    }
390
391
392
393
394

    connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
            this, SLOT(updateSubmitAction()));
    connect(model, SIGNAL(modelReset()),
            this, SLOT(updateSubmitAction()));
dt's avatar
dt committed
395
396
397
398
    connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
            this, SLOT(updateCheckAllComboBox()));
    connect(model, SIGNAL(modelReset()),
            this, SLOT(updateCheckAllComboBox()));
399
400
401
402
    connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
            this, SLOT(updateSubmitAction()));
    connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
            this, SLOT(updateSubmitAction()));
Robert Loehning's avatar
Robert Loehning committed
403
    connect(d->m_ui.fileView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
404
405
            this, SLOT(updateDiffAction()));
    updateActions();
con's avatar
con committed
406
407
}

408
SubmitFileModel *SubmitEditorWidget::fileModel() const
con's avatar
con committed
409
{
410
    return static_cast<SubmitFileModel *>(d->m_ui.fileView->model());
con's avatar
con committed
411
412
413
414
415
}

QStringList SubmitEditorWidget::checkedFiles() const
{
    QStringList rc;
416
    const SubmitFileModel *model = fileModel();
417
418
419
420
    if (!model)
        return rc;
    const int count = model->rowCount();
    for (int i = 0; i < count; i++)
421
422
        if (model->checked(i))
            rc.push_back(model->file(i));
con's avatar
con committed
423
424
425
    return rc;
}

426
Utils::CompletingTextEdit *SubmitEditorWidget::descriptionEdit() const
con's avatar
con committed
427
{
hjk's avatar
hjk committed
428
    return d->m_ui.description;
con's avatar
con committed
429
430
431
432
}

void SubmitEditorWidget::triggerDiffSelected()
{
433
    const QList<int> sel = selectedRows(d->m_ui.fileView);
con's avatar
con committed
434
435
436
437
    if (!sel.empty())
        emit diffSelected(sel);
}

438
439
void SubmitEditorWidget::diffActivatedDelayed()
{
440
    emit diffSelected(QList<int>() << d->m_activatedRow);
441
442
443
444
445
446
}

void SubmitEditorWidget::diffActivated(const QModelIndex &index)
{
    // We need to delay the signal, otherwise, the diff editor will not
    // be in the foreground.
hjk's avatar
hjk committed
447
    d->m_activatedRow = index.row();
448
449
450
451
452
453
454
    QTimer::singleShot(0, this, SLOT(diffActivatedDelayed()));
}

void SubmitEditorWidget::updateActions()
{
    updateSubmitAction();
    updateDiffAction();
dt's avatar
dt committed
455
    updateCheckAllComboBox();
456
457
458
459
460
}

// Enable submit depending on having checked files
void SubmitEditorWidget::updateSubmitAction()
{
461
    const unsigned checkedCount = checkedFilesCount();
462
    const bool newCommitState = canSubmit();
463
    // Emit signal to update action
hjk's avatar
hjk committed
464
465
466
    if (d->m_commitEnabled != newCommitState) {
        d->m_commitEnabled = newCommitState;
        emit submitActionEnabledChanged(d->m_commitEnabled);
467
    }
hjk's avatar
hjk committed
468
    if (d->m_ui.fileView && d->m_ui.fileView->model()) {
469
        // Update button text.
hjk's avatar
hjk committed
470
        const int fileCount = d->m_ui.fileView->model()->rowCount();
471
        const QString msg = checkedCount ?
472
473
474
                            tr("%1 %2/%n File(s)", 0, fileCount)
                            .arg(commitName()).arg(checkedCount) :
                            commitName();
475
        emit submitActionTextChanged(msg);
con's avatar
con committed
476
477
478
    }
}

479
480
// Enable diff depending on selected files
void SubmitEditorWidget::updateDiffAction()
con's avatar
con committed
481
{
482
    const bool filesSelected = hasSelection();
hjk's avatar
hjk committed
483
484
485
    if (d->m_filesSelected != filesSelected) {
        d->m_filesSelected = filesSelected;
        emit fileSelectionChanged(d->m_filesSelected);
con's avatar
con committed
486
487
488
    }
}

dt's avatar
dt committed
489
490
void SubmitEditorWidget::updateCheckAllComboBox()
{
hjk's avatar
hjk committed
491
    d->m_ignoreChange = true;
492
    int checkedCount = checkedFilesCount();
dt's avatar
dt committed
493
    if (checkedCount == 0)
hjk's avatar
hjk committed
494
495
496
        d->m_ui.checkAllCheckBox->setCheckState(Qt::Unchecked);
    else if (checkedCount == d->m_ui.fileView->model()->rowCount())
        d->m_ui.checkAllCheckBox->setCheckState(Qt::Checked);
dt's avatar
dt committed
497
    else
hjk's avatar
hjk committed
498
499
        d->m_ui.checkAllCheckBox->setCheckState(Qt::PartiallyChecked);
    d->m_ignoreChange = false;
dt's avatar
dt committed
500
501
}

502
503
504
bool SubmitEditorWidget::hasSelection() const
{
    // Not present until model is set
hjk's avatar
hjk committed
505
    if (const QItemSelectionModel *sm = d->m_ui.fileView->selectionModel())
506
507
508
509
        return sm->hasSelection();
    return false;
}

510
int SubmitEditorWidget::checkedFilesCount() const
511
{
512
    int checkedCount = 0;
513
    if (const SubmitFileModel *model = fileModel()) {
514
        const int count = model->rowCount();
515
        for (int i = 0; i < count; ++i)
516
            if (model->checked(i))
517
                ++checkedCount;
518
519
    }
    return checkedCount;
520
521
}

522
523
524
525
526
QString SubmitEditorWidget::cleanupDescription(const QString &input) const
{
    return input;
}

con's avatar
con committed
527
528
void SubmitEditorWidget::insertTopWidget(QWidget *w)
{
hjk's avatar
hjk committed
529
    d->m_ui.vboxLayout->insertWidget(0, w);
con's avatar
con committed
530
531
}

532
533
534
535
536
void SubmitEditorWidget::addSubmitButtonMenu(QMenu *menu)
{
    d->m_submitButton->setMenu(menu);
}

537
538
539
540
541
542
void SubmitEditorWidget::hideDescription()
{
    d->m_ui.descriptionBox->hide();
    setDescriptionMandatory(false);
}

Yuchen Deng's avatar
Yuchen Deng committed
543
544
545
546
547
548
549
550
551
552
553
554
555
void SubmitEditorWidget::descriptionTextChanged()
{
#if QT_VERSION < 0x050000 // Fix Qt-Bug, see QTCREATORBUG-5633 && QTCREATORBUG-6082
    static QString lastText;
    const QString text = d->m_ui.description->toPlainText();
    if (lastText != text)
        lastText = text;
    else
        return;
#endif
    updateSubmitAction();
}

556
557
bool SubmitEditorWidget::canSubmit() const
{
558
    if (isDescriptionMandatory() && cleanupDescription(descriptionText()).trimmed().isEmpty())
559
        return false;
560
    const unsigned checkedCount = checkedFilesCount();
hjk's avatar
hjk committed
561
    return d->m_emptyFileListEnabled || checkedCount > 0;
562
563
}

564
565
566
567
568
QString SubmitEditorWidget::commitName() const
{
    return tr("&Commit");
}

569
570
void SubmitEditorWidget::addSubmitFieldWidget(SubmitFieldWidget *f)
{
hjk's avatar
hjk committed
571
    if (!d->m_fieldLayout) {
572
        // VBox with horizontal, expanding spacer
hjk's avatar
hjk committed
573
        d->m_fieldLayout = new QVBoxLayout;
574
        QHBoxLayout *outerLayout = new QHBoxLayout;
hjk's avatar
hjk committed
575
        outerLayout->addLayout(d->m_fieldLayout);
576
        outerLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored));
Orgad Shaneh's avatar
Orgad Shaneh committed
577
        d->m_ui.descriptionLayout->addLayout(outerLayout);
578
    }
hjk's avatar
hjk committed
579
580
    d->m_fieldLayout->addWidget(f);
    d->m_fieldWidgets.push_back(f);
581
582
583
584
}

QList<SubmitFieldWidget *> SubmitEditorWidget::submitFieldWidgets() const
{
hjk's avatar
hjk committed
585
    return d->m_fieldWidgets;
586
587
}

588
589
void SubmitEditorWidget::addDescriptionEditContextMenuAction(QAction *a)
{
hjk's avatar
hjk committed
590
    d->descriptionEditContextMenuActions.push_back(SubmitEditorWidgetPrivate::AdditionalContextMenuAction(-1, a));
591
592
593
594
}

void SubmitEditorWidget::insertDescriptionEditContextMenuAction(int pos, QAction *a)
{
hjk's avatar
hjk committed
595
    d->descriptionEditContextMenuActions.push_back(SubmitEditorWidgetPrivate::AdditionalContextMenuAction(pos, a));
596
597
598
599
}

void SubmitEditorWidget::editorCustomContextMenuRequested(const QPoint &pos)
{
hjk's avatar
hjk committed
600
    QScopedPointer<QMenu> menu(d->m_ui.description->createStandardContextMenu());
601
    // Extend
hjk's avatar
hjk committed
602
    foreach (const SubmitEditorWidgetPrivate::AdditionalContextMenuAction &a, d->descriptionEditContextMenuActions) {
603
        if (a.second) {
604
            if (a.first >= 0)
605
                menu->insertAction(menu->actions().at(a.first), a.second);
606
            else
607
608
609
                menu->addAction(a.second);
        }
    }
hjk's avatar
hjk committed
610
    menu->exec(d->m_ui.description->mapToGlobal(pos));
611
612
}

dt's avatar
dt committed
613
614
void SubmitEditorWidget::checkAllToggled()
{
hjk's avatar
hjk committed
615
    if (d->m_ignoreChange)
dt's avatar
dt committed
616
        return;
617
618
    Qt::CheckState checkState = d->m_ui.checkAllCheckBox->checkState();
    fileModel()->setAllChecked(checkState == Qt::Checked || checkState == Qt::PartiallyChecked);
dt's avatar
dt committed
619
    // Reset that again, so that the user can't do it
hjk's avatar
hjk committed
620
    d->m_ui.checkAllCheckBox->setTristate(false);
dt's avatar
dt committed
621
622
}

623
624
void SubmitEditorWidget::checkAll()
{
625
    fileModel()->setAllChecked(true);
626
627
628
629
}

void SubmitEditorWidget::uncheckAll()
{
630
    fileModel()->setAllChecked(false);
631
632
633
634
635
636
}

void SubmitEditorWidget::fileListCustomContextMenuRequested(const QPoint & pos)
{
    // Execute menu offering to check/uncheck all
    QMenu menu;
Friedemann Kleint's avatar
Friedemann Kleint committed
637
    //: Check all for submit
638
    QAction *checkAllAction = menu.addAction(tr("Check All"));
Friedemann Kleint's avatar
Friedemann Kleint committed
639
    //: Uncheck all for submit
640
    QAction *uncheckAllAction = menu.addAction(tr("Uncheck All"));
hjk's avatar
hjk committed
641
    QAction *action = menu.exec(d->m_ui.fileView->mapToGlobal(pos));
642
643
644
645
646
647
648
649
    if (action == checkAllAction) {
        checkAll();
        return;
    }
    if (action == uncheckAllAction) {
        uncheckAll();
        return;
    }
650
651
}

652
653
bool SubmitEditorWidget::isEmptyFileListEnabled() const
{
hjk's avatar
hjk committed
654
    return d->m_emptyFileListEnabled;
655
656
657
658
}

void SubmitEditorWidget::setEmptyFileListEnabled(bool e)
{
hjk's avatar
hjk committed
659
660
    if (e != d->m_emptyFileListEnabled) {
        d->m_emptyFileListEnabled = e;
661
662
663
664
        updateSubmitAction();
    }
}

665
} // namespace VcsBase
666
667

#include "submiteditorwidget.moc"