submiteditorwidget.cpp 22.9 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
Eike Ziller's avatar
Eike Ziller committed
3
4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
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
** 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
Eike Ziller's avatar
Eike Ziller committed
12
13
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
14
** 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
**
Eike Ziller's avatar
Eike Ziller committed
25
26
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company 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 "submiteditorwidget.h"
32
#include "submitfieldwidget.h"
33
#include "submitfilemodel.h"
con's avatar
con committed
34
35
#include "ui_submiteditorwidget.h"

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

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

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

51
/*!
52
    \class VcsBase::SubmitEditorWidget
53

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

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

    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.
*/

75
namespace VcsBase {
con's avatar
con committed
76

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

private slots:
    void actionChanged();
};

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

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

108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// 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);
    }
};

123
124
125
126
127
128
129
130
131
132
133
134
135
136
// 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;
}

137
// -----------  SubmitEditorWidgetPrivate
138

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

con's avatar
con committed
144
145
146
147
    SubmitEditorWidgetPrivate();

    Ui::SubmitEditorWidget m_ui;
    bool m_filesSelected;
148
    int m_activatedRow;
149
    bool m_emptyFileListEnabled;
150
151

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

    bool m_commitEnabled;
dt's avatar
dt committed
158
    bool m_ignoreChange;
159
    bool m_descriptionMandatory;
160
    bool m_updateInProgress;
161
    QString m_description;
162
163

    QActionPushButton *m_submitButton;
con's avatar
con committed
164
165
166
167
};

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

181
SubmitEditorWidget::SubmitEditorWidget() :
hjk's avatar
hjk committed
182
    d(new SubmitEditorWidgetPrivate)
con's avatar
con committed
183
{
hjk's avatar
hjk committed
184
185
186
187
    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);
188
189
190
191
    connect(d->m_ui.description, &QWidget::customContextMenuRequested,
            this, &SubmitEditorWidget::editorCustomContextMenuRequested);
    connect(d->m_ui.description, &QTextEdit::textChanged,
            this, &SubmitEditorWidget::descriptionTextChanged);
192

con's avatar
con committed
193
    // File List
hjk's avatar
hjk committed
194
    d->m_ui.fileView->setContextMenuPolicy(Qt::CustomContextMenu);
195
196
    connect(d->m_ui.fileView, &QWidget::customContextMenuRequested,
            this, &SubmitEditorWidget::fileListCustomContextMenuRequested);
hjk's avatar
hjk committed
197
198
    d->m_ui.fileView->setSelectionMode(QAbstractItemView::ExtendedSelection);
    d->m_ui.fileView->setRootIsDecorated(false);
199
200
    connect(d->m_ui.fileView, &QAbstractItemView::doubleClicked,
            this, &SubmitEditorWidget::diffActivated);
con's avatar
con committed
201

202
203
    connect(d->m_ui.checkAllCheckBox, &QCheckBox::stateChanged,
            this, &SubmitEditorWidget::checkAllToggled);
dt's avatar
dt committed
204

con's avatar
con committed
205
    setFocusPolicy(Qt::StrongFocus);
hjk's avatar
hjk committed
206
    setFocusProxy(d->m_ui.description);
con's avatar
con committed
207
208
209
210
}

SubmitEditorWidget::~SubmitEditorWidget()
{
hjk's avatar
hjk committed
211
    delete d;
con's avatar
con committed
212
213
}

214
void SubmitEditorWidget::registerActions(QAction *editorUndoAction, QAction *editorRedoAction,
con's avatar
con committed
215
216
217
                         QAction *submitAction, QAction *diffAction)
{
    if (editorUndoAction) {
hjk's avatar
hjk committed
218
        editorUndoAction->setEnabled(d->m_ui.description->document()->isUndoAvailable());
219
220
221
        connect(d->m_ui.description, &QTextEdit::undoAvailable,
                editorUndoAction, &QAction::setEnabled);
        connect(editorUndoAction, &QAction::triggered, d->m_ui.description, &QTextEdit::undo);
con's avatar
con committed
222
223
    }
    if (editorRedoAction) {
hjk's avatar
hjk committed
224
        editorRedoAction->setEnabled(d->m_ui.description->document()->isRedoAvailable());
225
226
227
        connect(d->m_ui.description, &QTextEdit::redoAvailable,
                editorRedoAction, &QAction::setEnabled);
        connect(editorRedoAction, &QAction::triggered, d->m_ui.description, &QTextEdit::redo);
con's avatar
con committed
228
229
230
    }

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

void SubmitEditorWidget::unregisterActions(QAction *editorUndoAction,  QAction *editorRedoAction,
                                           QAction *submitAction, QAction *diffAction)
{
    if (editorUndoAction) {
266
267
268
269
        disconnect(d->m_ui.description, &Utils::CompletingTextEdit::undoAvailable,
                   editorUndoAction, &QAction::setEnabled);
        disconnect(editorUndoAction, &QAction::triggered,
                   d->m_ui.description, &Utils::CompletingTextEdit::undo);
con's avatar
con committed
270
271
    }
    if (editorRedoAction) {
272
273
274
275
        disconnect(d->m_ui.description, &Utils::CompletingTextEdit::redoAvailable,
                   editorRedoAction, &QAction::setEnabled);
        disconnect(editorRedoAction, &QAction::triggered,
                   d->m_ui.description, &Utils::CompletingTextEdit::redo);
con's avatar
con committed
276
277
    }

278
    if (submitAction) {
279
280
        disconnect(this, &SubmitEditorWidget::submitActionEnabledChanged,
                   submitAction, &QAction::setEnabled);
281
        // Just deactivate the QActionSetTextSlotHelper on the action
282
        disconnect(this, &SubmitEditorWidget::submitActionTextChanged, 0, 0);
283
    }
con's avatar
con committed
284
285

    if (diffAction) {
286
287
288
289
         disconnect(this, &SubmitEditorWidget::fileSelectionChanged,
                    diffAction, &QAction::setEnabled);
         disconnect(diffAction, &QAction::triggered,
                    this, &SubmitEditorWidget::triggerDiffSelected);
con's avatar
con committed
290
291
292
    }
}

293
294
// Make sure we have one terminating NL. Do not trim front as leading space might be
// required for some formattings.
295
void SubmitEditorWidget::trimDescription()
con's avatar
con committed
296
{
297
298
    if (d->m_description.isEmpty())
        return;
299
    // Trim back of string.
300
    const int last = d->m_description.size() - 1;
301
    int lastWordCharacter = last;
302
303
304
    for ( ; lastWordCharacter >= 0 && d->m_description.at(lastWordCharacter).isSpace() ;
          lastWordCharacter--)
    { }
305
    if (lastWordCharacter != last)
306
307
        d->m_description.truncate(lastWordCharacter + 1);
    d->m_description += QLatin1Char('\n');
308
309
310
311
}

// Extract the wrapped text from a text edit, which performs
// the wrapping only optically.
312
void SubmitEditorWidget::wrapDescription()
313
{
314
315
    if (!lineWrap())
        return;
316
    const QChar newLine = QLatin1Char('\n');
317
318
319
320
321
322
    QTextEdit e;
    e.setVisible(false);
    e.setMinimumWidth(1000);
    e.setFontPointSize(1.0);
    e.setLineWrapColumnOrWidth(d->m_ui.description->lineWrapColumnOrWidth());
    e.setLineWrapMode(d->m_ui.description->lineWrapMode());
323
    e.setWordWrapMode(d->m_ui.description->wordWrapMode());
324
325
326
    e.setPlainText(d->m_description);
    d->m_description.clear();
    QTextCursor cursor(e.document());
327
    cursor.movePosition(QTextCursor::Start);
328
    while (!cursor.atEnd()) {
329
330
        const QString block = cursor.block().text();
        if (block.startsWith(QLatin1Char('\t'))) { // Don't wrap
331
            d->m_description += block + newLine;
332
            cursor.movePosition(QTextCursor::EndOfBlock);
333
334
335
        } else {
            forever {
                cursor.select(QTextCursor::LineUnderCursor);
336
337
                d->m_description += cursor.selectedText();
                d->m_description += newLine;
338
                cursor.clearSelection();
339
340
341
342
343
344
                if (cursor.atBlockEnd())
                    break;
                cursor.movePosition(QTextCursor::NextCharacter);
            }
        }
        cursor.movePosition(QTextCursor::NextBlock);
345
    }
con's avatar
con committed
346
347
348
349
}

QString SubmitEditorWidget::descriptionText() const
{
350
    return d->m_description;
con's avatar
con committed
351
352
353
354
}

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

358
359
bool SubmitEditorWidget::lineWrap() const
{
hjk's avatar
hjk committed
360
    return d->m_ui.description->lineWrapMode() != QTextEdit::NoWrap;
361
362
363
364
365
366
367
}

void SubmitEditorWidget::setLineWrap(bool v)
{
    if (debug)
        qDebug() << Q_FUNC_INFO << v;
    if (v) {
hjk's avatar
hjk committed
368
369
        d->m_ui.description->setLineWrapColumnOrWidth(d->m_lineWidth);
        d->m_ui.description->setLineWrapMode(QTextEdit::FixedColumnWidth);
370
    } else {
hjk's avatar
hjk committed
371
        d->m_ui.description->setLineWrapMode(QTextEdit::NoWrap);
372
373
374
375
376
    }
}

int SubmitEditorWidget::lineWrapWidth() const
{
hjk's avatar
hjk committed
377
    return d->m_lineWidth;
378
379
380
381
382
383
}

void SubmitEditorWidget::setLineWrapWidth(int v)
{
    if (debug)
        qDebug() << Q_FUNC_INFO << v << lineWrap();
hjk's avatar
hjk committed
384
    if (d->m_lineWidth == v)
385
        return;
hjk's avatar
hjk committed
386
    d->m_lineWidth = v;
387
    if (lineWrap())
hjk's avatar
hjk committed
388
        d->m_ui.description->setLineWrapColumnOrWidth(v);
389
390
}

391
392
393
394
395
396
397
398
399
400
bool SubmitEditorWidget::isDescriptionMandatory() const
{
    return d->m_descriptionMandatory;
}

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

401
402
QAbstractItemView::SelectionMode SubmitEditorWidget::fileListSelectionMode() const
{
hjk's avatar
hjk committed
403
    return d->m_ui.fileView->selectionMode();
404
405
406
407
}

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

411
void SubmitEditorWidget::setFileModel(SubmitFileModel *model)
con's avatar
con committed
412
{
hjk's avatar
hjk committed
413
    d->m_ui.fileView->clearSelection(); // trigger the change signals
con's avatar
con committed
414

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

417
418
419
    if (model->rowCount()) {
        const int columnCount = model->columnCount();
        for (int c = 0;  c < columnCount; c++)
hjk's avatar
hjk committed
420
            d->m_ui.fileView->resizeColumnToContents(c);
con's avatar
con committed
421
    }
422

423
424
425
426
427
428
429
430
431
432
433
434
435
436
    connect(model, &QAbstractItemModel::dataChanged,
            this, &SubmitEditorWidget::updateSubmitAction);
    connect(model, &QAbstractItemModel::modelReset,
            this, &SubmitEditorWidget::updateSubmitAction);
    connect(model, &QAbstractItemModel::dataChanged,
            this, &SubmitEditorWidget::updateCheckAllComboBox);
    connect(model, &QAbstractItemModel::modelReset,
            this, &SubmitEditorWidget::updateCheckAllComboBox);
    connect(model, &QAbstractItemModel::rowsInserted,
            this, &SubmitEditorWidget::updateSubmitAction);
    connect(model, &QAbstractItemModel::rowsRemoved,
            this, &SubmitEditorWidget::updateSubmitAction);
    connect(d->m_ui.fileView->selectionModel(), &QItemSelectionModel::selectionChanged,
            this, &SubmitEditorWidget::updateDiffAction);
437
    updateActions();
con's avatar
con committed
438
439
}

440
SubmitFileModel *SubmitEditorWidget::fileModel() const
con's avatar
con committed
441
{
442
    return static_cast<SubmitFileModel *>(d->m_ui.fileView->model());
con's avatar
con committed
443
444
445
446
447
}

QStringList SubmitEditorWidget::checkedFiles() const
{
    QStringList rc;
448
    const SubmitFileModel *model = fileModel();
449
450
451
452
    if (!model)
        return rc;
    const int count = model->rowCount();
    for (int i = 0; i < count; i++)
453
454
        if (model->checked(i))
            rc.push_back(model->file(i));
con's avatar
con committed
455
456
457
    return rc;
}

458
Utils::CompletingTextEdit *SubmitEditorWidget::descriptionEdit() const
con's avatar
con committed
459
{
hjk's avatar
hjk committed
460
    return d->m_ui.description;
con's avatar
con committed
461
462
463
464
}

void SubmitEditorWidget::triggerDiffSelected()
{
465
    const QList<int> sel = selectedRows(d->m_ui.fileView);
con's avatar
con committed
466
467
468
469
    if (!sel.empty())
        emit diffSelected(sel);
}

470
471
void SubmitEditorWidget::diffActivatedDelayed()
{
472
    emit diffSelected(QList<int>() << d->m_activatedRow);
473
474
475
476
477
478
}

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
479
    d->m_activatedRow = index.row();
480
481
482
483
484
485
486
    QTimer::singleShot(0, this, SLOT(diffActivatedDelayed()));
}

void SubmitEditorWidget::updateActions()
{
    updateSubmitAction();
    updateDiffAction();
dt's avatar
dt committed
487
    updateCheckAllComboBox();
488
489
490
491
492
}

// Enable submit depending on having checked files
void SubmitEditorWidget::updateSubmitAction()
{
493
    const unsigned checkedCount = checkedFilesCount();
494
    const bool newCommitState = canSubmit();
495
    // Emit signal to update action
hjk's avatar
hjk committed
496
497
498
    if (d->m_commitEnabled != newCommitState) {
        d->m_commitEnabled = newCommitState;
        emit submitActionEnabledChanged(d->m_commitEnabled);
499
    }
hjk's avatar
hjk committed
500
    if (d->m_ui.fileView && d->m_ui.fileView->model()) {
501
        // Update button text.
hjk's avatar
hjk committed
502
        const int fileCount = d->m_ui.fileView->model()->rowCount();
503
        const QString msg = checkedCount ?
504
505
506
                            tr("%1 %2/%n File(s)", 0, fileCount)
                            .arg(commitName()).arg(checkedCount) :
                            commitName();
507
        emit submitActionTextChanged(msg);
con's avatar
con committed
508
509
510
    }
}

511
512
// Enable diff depending on selected files
void SubmitEditorWidget::updateDiffAction()
con's avatar
con committed
513
{
514
    const bool filesSelected = hasSelection();
hjk's avatar
hjk committed
515
516
517
    if (d->m_filesSelected != filesSelected) {
        d->m_filesSelected = filesSelected;
        emit fileSelectionChanged(d->m_filesSelected);
con's avatar
con committed
518
519
520
    }
}

dt's avatar
dt committed
521
522
void SubmitEditorWidget::updateCheckAllComboBox()
{
hjk's avatar
hjk committed
523
    d->m_ignoreChange = true;
524
    int checkedCount = checkedFilesCount();
dt's avatar
dt committed
525
    if (checkedCount == 0)
hjk's avatar
hjk committed
526
527
528
        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
529
    else
hjk's avatar
hjk committed
530
531
        d->m_ui.checkAllCheckBox->setCheckState(Qt::PartiallyChecked);
    d->m_ignoreChange = false;
dt's avatar
dt committed
532
533
}

534
535
536
bool SubmitEditorWidget::hasSelection() const
{
    // Not present until model is set
hjk's avatar
hjk committed
537
    if (const QItemSelectionModel *sm = d->m_ui.fileView->selectionModel())
538
539
540
541
        return sm->hasSelection();
    return false;
}

542
int SubmitEditorWidget::checkedFilesCount() const
543
{
544
    int checkedCount = 0;
545
    if (const SubmitFileModel *model = fileModel()) {
546
        const int count = model->rowCount();
547
        for (int i = 0; i < count; ++i)
548
            if (model->checked(i))
549
                ++checkedCount;
550
551
    }
    return checkedCount;
552
553
}

554
555
556
557
558
QString SubmitEditorWidget::cleanupDescription(const QString &input) const
{
    return input;
}

con's avatar
con committed
559
560
void SubmitEditorWidget::insertTopWidget(QWidget *w)
{
hjk's avatar
hjk committed
561
    d->m_ui.vboxLayout->insertWidget(0, w);
con's avatar
con committed
562
563
}

564
565
566
567
568
void SubmitEditorWidget::insertLeftWidget(QWidget *w)
{
    d->m_ui.splitter->insertWidget(0, w);
}

569
570
571
572
573
void SubmitEditorWidget::addSubmitButtonMenu(QMenu *menu)
{
    d->m_submitButton->setMenu(menu);
}

574
575
576
577
578
579
void SubmitEditorWidget::hideDescription()
{
    d->m_ui.descriptionBox->hide();
    setDescriptionMandatory(false);
}

Yuchen Deng's avatar
Yuchen Deng committed
580
581
void SubmitEditorWidget::descriptionTextChanged()
{
582
583
584
    d->m_description = cleanupDescription(d->m_ui.description->toPlainText());
    wrapDescription();
    trimDescription();
Orgad Shaneh's avatar
Orgad Shaneh committed
585
    // append field entries
586
    foreach (const SubmitFieldWidget *fw, d->m_fieldWidgets)
587
        d->m_description += fw->fieldValues();
Yuchen Deng's avatar
Yuchen Deng committed
588
589
590
    updateSubmitAction();
}

591
592
bool SubmitEditorWidget::canSubmit() const
{
593
594
    if (d->m_updateInProgress)
        return false;
595
    if (isDescriptionMandatory() && d->m_description.trimmed().isEmpty())
596
        return false;
597
    const unsigned checkedCount = checkedFilesCount();
hjk's avatar
hjk committed
598
    return d->m_emptyFileListEnabled || checkedCount > 0;
599
600
}

601
602
603
604
605
606
void SubmitEditorWidget::setUpdateInProgress(bool value)
{
    d->m_updateInProgress = value;
    updateSubmitAction();
}

607
608
609
610
611
bool SubmitEditorWidget::updateInProgress() const
{
    return d->m_updateInProgress;
}

612
613
614
615
616
QString SubmitEditorWidget::commitName() const
{
    return tr("&Commit");
}

617
618
void SubmitEditorWidget::addSubmitFieldWidget(SubmitFieldWidget *f)
{
hjk's avatar
hjk committed
619
    if (!d->m_fieldLayout) {
620
        // VBox with horizontal, expanding spacer
hjk's avatar
hjk committed
621
        d->m_fieldLayout = new QVBoxLayout;
Tobias Hunger's avatar
Tobias Hunger committed
622
        auto outerLayout = new QHBoxLayout;
hjk's avatar
hjk committed
623
        outerLayout->addLayout(d->m_fieldLayout);
624
        outerLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored));
Orgad Shaneh's avatar
Orgad Shaneh committed
625
        d->m_ui.descriptionLayout->addLayout(outerLayout);
626
    }
hjk's avatar
hjk committed
627
628
    d->m_fieldLayout->addWidget(f);
    d->m_fieldWidgets.push_back(f);
629
630
631
632
}

QList<SubmitFieldWidget *> SubmitEditorWidget::submitFieldWidgets() const
{
hjk's avatar
hjk committed
633
    return d->m_fieldWidgets;
634
635
}

636
637
void SubmitEditorWidget::addDescriptionEditContextMenuAction(QAction *a)
{
638
639
    d->descriptionEditContextMenuActions
            .push_back(SubmitEditorWidgetPrivate::AdditionalContextMenuAction(-1, a));
640
641
642
643
}

void SubmitEditorWidget::insertDescriptionEditContextMenuAction(int pos, QAction *a)
{
644
645
    d->descriptionEditContextMenuActions
            .push_back(SubmitEditorWidgetPrivate::AdditionalContextMenuAction(pos, a));
646
647
648
649
}

void SubmitEditorWidget::editorCustomContextMenuRequested(const QPoint &pos)
{
hjk's avatar
hjk committed
650
    QScopedPointer<QMenu> menu(d->m_ui.description->createStandardContextMenu());
651
    // Extend
652
653
    foreach (const SubmitEditorWidgetPrivate::AdditionalContextMenuAction &a,
             d->descriptionEditContextMenuActions) {
654
        if (a.second) {
655
            if (a.first >= 0)
656
                menu->insertAction(menu->actions().at(a.first), a.second);
657
            else
658
659
660
                menu->addAction(a.second);
        }
    }
hjk's avatar
hjk committed
661
    menu->exec(d->m_ui.description->mapToGlobal(pos));
662
663
}

dt's avatar
dt committed
664
665
void SubmitEditorWidget::checkAllToggled()
{
hjk's avatar
hjk committed
666
    if (d->m_ignoreChange)
dt's avatar
dt committed
667
        return;
668
669
    Qt::CheckState checkState = d->m_ui.checkAllCheckBox->checkState();
    fileModel()->setAllChecked(checkState == Qt::Checked || checkState == Qt::PartiallyChecked);
dt's avatar
dt committed
670
    // Reset that again, so that the user can't do it
hjk's avatar
hjk committed
671
    d->m_ui.checkAllCheckBox->setTristate(false);
dt's avatar
dt committed
672
673
}

674
675
void SubmitEditorWidget::checkAll()
{
676
    fileModel()->setAllChecked(true);
677
678
679
680
}

void SubmitEditorWidget::uncheckAll()
{
681
    fileModel()->setAllChecked(false);
682
683
684
685
686
687
}

void SubmitEditorWidget::fileListCustomContextMenuRequested(const QPoint & pos)
{
    // Execute menu offering to check/uncheck all
    QMenu menu;
Friedemann Kleint's avatar
Friedemann Kleint committed
688
    //: Check all for submit
689
    QAction *checkAllAction = menu.addAction(tr("Select All"));
Friedemann Kleint's avatar
Friedemann Kleint committed
690
    //: Uncheck all for submit
691
    QAction *uncheckAllAction = menu.addAction(tr("Unselect All"));
hjk's avatar
hjk committed
692
    QAction *action = menu.exec(d->m_ui.fileView->mapToGlobal(pos));
693
694
695
696
697
698
699
700
    if (action == checkAllAction) {
        checkAll();
        return;
    }
    if (action == uncheckAllAction) {
        uncheckAll();
        return;
    }
701
702
}

703
704
bool SubmitEditorWidget::isEmptyFileListEnabled() const
{
hjk's avatar
hjk committed
705
    return d->m_emptyFileListEnabled;
706
707
708
709
}

void SubmitEditorWidget::setEmptyFileListEnabled(bool e)
{
hjk's avatar
hjk committed
710
711
    if (e != d->m_emptyFileListEnabled) {
        d->m_emptyFileListEnabled = e;
712
713
714
715
        updateSubmitAction();
    }
}

716
} // namespace VcsBase
717
718

#include "submiteditorwidget.moc"