1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "submiteditorwidget.h"
27 #include "submitfieldwidget.h"
28 #include "submitfilemodel.h"
29 #include "ui_submiteditorwidget.h"
30
31 #include <utils/algorithm.h>
32 #include <utils/theme/theme.h>
33
34 #include <QDebug>
35 #include <QPointer>
36 #include <QTextBlock>
37 #include <QTimer>
38 #include <QScopedPointer>
39
40 #include <QMenu>
41 #include <QHBoxLayout>
42 #include <QToolButton>
43 #include <QSpacerItem>
44 #include <QShortcut>
45
46 using namespace Utils;
47
48 enum { debug = 0 };
49 enum { defaultLineWidth = 72 };
50
51 /*!
52 \class VcsBase::SubmitEditorWidget
53
54 \brief The SubmitEditorWidget class presents a VCS commit message in a text
55 editor and a
56 checkable list of modified files in a list window.
57
58 The user can delete files from the list by unchecking them or diff the selection
59 by doubleclicking. A list model which contains state and file columns should be
60 set using setFileModel().
61
62 Additionally, standard creator actions can be registered:
63 Undo/redo will be set up to work with the description editor.
64 Submit will be set up to be enabled according to checkstate.
65 Diff will be set up to trigger diffSelected().
66
67 Note that the actions are connected by signals; in the rare event that there
68 are several instances of the SubmitEditorWidget belonging to the same
69 context active, the actions must be registered/unregistered in the editor
70 change event.
71 Care should be taken to ensure the widget is deleted properly when the
72 editor closes.
73 */
74
75 namespace VcsBase {
76
77 // QActionPushButton: A push button tied to an action
78 // (similar to a QToolButton)
79 class QActionPushButton : public QToolButton
80 {
81 Q_OBJECT
82 public:
83 explicit QActionPushButton(QAction *a);
84
85 private slots:
86 void actionChanged();
87 };
88
QActionPushButton(QAction * a)89 QActionPushButton::QActionPushButton(QAction *a) :
90 QToolButton()
91 {
92 setIcon(a->icon());
93 setText(a->text());
94 setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
95 connect(a, &QAction::changed, this, &QActionPushButton::actionChanged);
96 connect(this, &QAbstractButton::clicked, a, &QAction::trigger);
97 setEnabled(a->isEnabled());
98 }
99
actionChanged()100 void QActionPushButton::actionChanged()
101 {
102 if (const QAction *a = qobject_cast<QAction*>(sender())) {
103 setEnabled(a->isEnabled());
104 setText(a->text());
105 }
106 }
107
108
109 // Helpers to retrieve model data
110 // Convenience to extract a list of selected indexes
111 // ----------- SubmitEditorWidgetPrivate
112
113 struct SubmitEditorWidgetPrivate
114 {
115 // A pair of position/action to extend context menus
116 typedef QPair<int, QPointer<QAction> > AdditionalContextMenuAction;
117
118 Ui::SubmitEditorWidget m_ui;
119
120 QList<AdditionalContextMenuAction> descriptionEditContextMenuActions;
121 QVBoxLayout *m_fieldLayout = nullptr;
122 QList<SubmitFieldWidget *> m_fieldWidgets;
123 QShortcut *m_submitShortcut = nullptr;
124 QActionPushButton *m_submitButton = nullptr;
125 QString m_description;
126
127 int m_lineWidth = defaultLineWidth;
128 int m_activatedRow = -1;
129
130 bool m_filesSelected = false;
131 bool m_emptyFileListEnabled = false;
132 bool m_commitEnabled = false;
133 bool m_ignoreChange = false;
134 bool m_descriptionMandatory = true;
135 bool m_updateInProgress = false;
136 };
137
SubmitEditorWidget()138 SubmitEditorWidget::SubmitEditorWidget() :
139 d(new SubmitEditorWidgetPrivate)
140 {
141 d->m_ui.setupUi(this);
142 d->m_ui.description->setContextMenuPolicy(Qt::CustomContextMenu);
143 d->m_ui.description->setLineWrapMode(QTextEdit::NoWrap);
144 d->m_ui.description->setWordWrapMode(QTextOption::WordWrap);
145 connect(d->m_ui.description, &QWidget::customContextMenuRequested,
146 this, &SubmitEditorWidget::editorCustomContextMenuRequested);
147 connect(d->m_ui.description, &QTextEdit::textChanged,
148 this, &SubmitEditorWidget::descriptionTextChanged);
149
150 // File List
151 d->m_ui.fileView->setContextMenuPolicy(Qt::CustomContextMenu);
152 connect(d->m_ui.fileView, &QWidget::customContextMenuRequested,
153 this, &SubmitEditorWidget::fileListCustomContextMenuRequested);
154 d->m_ui.fileView->setSelectionMode(QAbstractItemView::ExtendedSelection);
155 d->m_ui.fileView->setRootIsDecorated(false);
156 connect(d->m_ui.fileView, &QAbstractItemView::doubleClicked,
157 this, &SubmitEditorWidget::diffActivated);
158
159 connect(d->m_ui.checkAllCheckBox, &QCheckBox::stateChanged,
160 this, &SubmitEditorWidget::checkAllToggled);
161
162 setFocusPolicy(Qt::StrongFocus);
163 setFocusProxy(d->m_ui.description);
164 }
165
~SubmitEditorWidget()166 SubmitEditorWidget::~SubmitEditorWidget()
167 {
168 delete d;
169 }
170
registerActions(QAction * editorUndoAction,QAction * editorRedoAction,QAction * submitAction,QAction * diffAction)171 void SubmitEditorWidget::registerActions(QAction *editorUndoAction, QAction *editorRedoAction,
172 QAction *submitAction, QAction *diffAction)
173 {
174 if (editorUndoAction) {
175 editorUndoAction->setEnabled(d->m_ui.description->document()->isUndoAvailable());
176 connect(d->m_ui.description, &QTextEdit::undoAvailable,
177 editorUndoAction, &QAction::setEnabled);
178 connect(editorUndoAction, &QAction::triggered, d->m_ui.description, &QTextEdit::undo);
179 }
180 if (editorRedoAction) {
181 editorRedoAction->setEnabled(d->m_ui.description->document()->isRedoAvailable());
182 connect(d->m_ui.description, &QTextEdit::redoAvailable,
183 editorRedoAction, &QAction::setEnabled);
184 connect(editorRedoAction, &QAction::triggered, d->m_ui.description, &QTextEdit::redo);
185 }
186
187 if (submitAction) {
188 if (debug) {
189 const SubmitFileModel *model = fileModel();
190 int count = model ? model->rowCount() : 0;
191 qDebug() << Q_FUNC_INFO << submitAction << count << "items";
192 }
193 d->m_commitEnabled = !canSubmit();
194 connect(this, &SubmitEditorWidget::submitActionEnabledChanged,
195 submitAction, &QAction::setEnabled);
196 connect(this, &SubmitEditorWidget::submitActionTextChanged,
197 submitAction, &QAction::setText);
198 d->m_submitButton = new QActionPushButton(submitAction);
199 d->m_ui.buttonLayout->addWidget(d->m_submitButton);
200 if (!d->m_submitShortcut)
201 d->m_submitShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return), this);
202 connect(d->m_submitShortcut, &QShortcut::activated,
203 submitAction, [submitAction] {
204 if (submitAction->isEnabled())
205 submitAction->trigger();
206 });
207 }
208 if (diffAction) {
209 if (debug)
210 qDebug() << diffAction << d->m_filesSelected;
211 diffAction->setEnabled(d->m_filesSelected);
212 connect(this, &SubmitEditorWidget::fileSelectionChanged, diffAction, &QAction::setEnabled);
213 connect(diffAction, &QAction::triggered, this, &SubmitEditorWidget::triggerDiffSelected);
214 d->m_ui.buttonLayout->addWidget(new QActionPushButton(diffAction));
215 }
216 }
217
218 // Make sure we have one terminating NL. Do not trim front as leading space might be
219 // required for some formattings.
trimDescription()220 void SubmitEditorWidget::trimDescription()
221 {
222 if (d->m_description.isEmpty())
223 return;
224 // Trim back of string.
225 const int last = d->m_description.size() - 1;
226 int lastWordCharacter = last;
227 for ( ; lastWordCharacter >= 0 && d->m_description.at(lastWordCharacter).isSpace() ;
228 lastWordCharacter--)
229 { }
230 if (lastWordCharacter != last)
231 d->m_description.truncate(lastWordCharacter + 1);
232 d->m_description += QLatin1Char('\n');
233 }
234
235 // Extract the wrapped text from a text edit, which performs
236 // the wrapping only optically.
wrapDescription()237 void SubmitEditorWidget::wrapDescription()
238 {
239 if (!lineWrap())
240 return;
241 const QChar newLine = QLatin1Char('\n');
242 QTextEdit e;
243 e.setVisible(false);
244 e.setMinimumWidth(1000);
245 e.setFontPointSize(1.0);
246 e.setLineWrapColumnOrWidth(d->m_ui.description->lineWrapColumnOrWidth());
247 e.setLineWrapMode(d->m_ui.description->lineWrapMode());
248 e.setWordWrapMode(d->m_ui.description->wordWrapMode());
249 e.setPlainText(d->m_description);
250 d->m_description.clear();
251 QTextCursor cursor(e.document());
252 cursor.movePosition(QTextCursor::Start);
253 while (!cursor.atEnd()) {
254 const QString block = cursor.block().text();
255 if (block.startsWith(QLatin1Char('\t'))) { // Don't wrap
256 d->m_description += block + newLine;
257 cursor.movePosition(QTextCursor::EndOfBlock);
258 } else {
259 forever {
260 cursor.select(QTextCursor::LineUnderCursor);
261 d->m_description += cursor.selectedText();
262 d->m_description += newLine;
263 cursor.clearSelection();
264 if (cursor.atBlockEnd())
265 break;
266 cursor.movePosition(QTextCursor::NextCharacter);
267 }
268 }
269 cursor.movePosition(QTextCursor::NextBlock);
270 }
271 }
272
descriptionText() const273 QString SubmitEditorWidget::descriptionText() const
274 {
275 return d->m_description;
276 }
277
setDescriptionText(const QString & text)278 void SubmitEditorWidget::setDescriptionText(const QString &text)
279 {
280 d->m_ui.description->setPlainText(text);
281 }
282
lineWrap() const283 bool SubmitEditorWidget::lineWrap() const
284 {
285 return d->m_ui.description->lineWrapMode() != QTextEdit::NoWrap;
286 }
287
setLineWrap(bool v)288 void SubmitEditorWidget::setLineWrap(bool v)
289 {
290 if (debug)
291 qDebug() << Q_FUNC_INFO << v;
292 if (v) {
293 d->m_ui.description->setLineWrapColumnOrWidth(d->m_lineWidth);
294 d->m_ui.description->setLineWrapMode(QTextEdit::FixedColumnWidth);
295 } else {
296 d->m_ui.description->setLineWrapMode(QTextEdit::NoWrap);
297 }
298 descriptionTextChanged();
299 }
300
lineWrapWidth() const301 int SubmitEditorWidget::lineWrapWidth() const
302 {
303 return d->m_lineWidth;
304 }
305
setLineWrapWidth(int v)306 void SubmitEditorWidget::setLineWrapWidth(int v)
307 {
308 if (debug)
309 qDebug() << Q_FUNC_INFO << v << lineWrap();
310 if (d->m_lineWidth == v)
311 return;
312 d->m_lineWidth = v;
313 if (lineWrap())
314 d->m_ui.description->setLineWrapColumnOrWidth(v);
315 descriptionTextChanged();
316 }
317
isDescriptionMandatory() const318 bool SubmitEditorWidget::isDescriptionMandatory() const
319 {
320 return d->m_descriptionMandatory;
321 }
322
setDescriptionMandatory(bool v)323 void SubmitEditorWidget::setDescriptionMandatory(bool v)
324 {
325 d->m_descriptionMandatory = v;
326 }
327
fileListSelectionMode() const328 QAbstractItemView::SelectionMode SubmitEditorWidget::fileListSelectionMode() const
329 {
330 return d->m_ui.fileView->selectionMode();
331 }
332
setFileListSelectionMode(QAbstractItemView::SelectionMode sm)333 void SubmitEditorWidget::setFileListSelectionMode(QAbstractItemView::SelectionMode sm)
334 {
335 d->m_ui.fileView->setSelectionMode(sm);
336 }
337
setFileModel(SubmitFileModel * model)338 void SubmitEditorWidget::setFileModel(SubmitFileModel *model)
339 {
340 d->m_ui.fileView->clearSelection(); // trigger the change signals
341
342 d->m_ui.fileView->setModel(model);
343
344 if (model->rowCount()) {
345 const int columnCount = model->columnCount();
346 for (int c = 0; c < columnCount; c++)
347 d->m_ui.fileView->resizeColumnToContents(c);
348 }
349
350 connect(model, &QAbstractItemModel::dataChanged,
351 this, &SubmitEditorWidget::updateSubmitAction);
352 connect(model, &QAbstractItemModel::modelReset,
353 this, &SubmitEditorWidget::updateSubmitAction);
354 connect(model, &QAbstractItemModel::dataChanged,
355 this, &SubmitEditorWidget::updateCheckAllComboBox);
356 connect(model, &QAbstractItemModel::modelReset,
357 this, &SubmitEditorWidget::updateCheckAllComboBox);
358 connect(model, &QAbstractItemModel::rowsInserted,
359 this, &SubmitEditorWidget::updateSubmitAction);
360 connect(model, &QAbstractItemModel::rowsRemoved,
361 this, &SubmitEditorWidget::updateSubmitAction);
362 connect(d->m_ui.fileView->selectionModel(), &QItemSelectionModel::selectionChanged,
363 this, &SubmitEditorWidget::updateDiffAction);
364 updateActions();
365 }
366
fileModel() const367 SubmitFileModel *SubmitEditorWidget::fileModel() const
368 {
369 return static_cast<SubmitFileModel *>(d->m_ui.fileView->model());
370 }
371
checkedFiles() const372 QStringList SubmitEditorWidget::checkedFiles() const
373 {
374 QStringList rc;
375 const SubmitFileModel *model = fileModel();
376 if (!model)
377 return rc;
378 const int count = model->rowCount();
379 for (int i = 0; i < count; i++)
380 if (model->checked(i))
381 rc.push_back(model->file(i));
382 return rc;
383 }
384
descriptionEdit() const385 CompletingTextEdit *SubmitEditorWidget::descriptionEdit() const
386 {
387 return d->m_ui.description;
388 }
389
triggerDiffSelected()390 void SubmitEditorWidget::triggerDiffSelected()
391 {
392 const QList<int> sel = selectedRows();
393 if (!sel.empty())
394 emit diffSelected(sel);
395 }
396
diffActivatedDelayed()397 void SubmitEditorWidget::diffActivatedDelayed()
398 {
399 emit diffSelected(QList<int>() << d->m_activatedRow);
400 }
401
diffActivated(const QModelIndex & index)402 void SubmitEditorWidget::diffActivated(const QModelIndex &index)
403 {
404 // We need to delay the signal, otherwise, the diff editor will not
405 // be in the foreground.
406 d->m_activatedRow = index.row();
407 QTimer::singleShot(0, this, &SubmitEditorWidget::diffActivatedDelayed);
408 }
409
updateActions()410 void SubmitEditorWidget::updateActions()
411 {
412 updateSubmitAction();
413 updateDiffAction();
414 updateCheckAllComboBox();
415 }
416
417 // Enable submit depending on having checked files
updateSubmitAction()418 void SubmitEditorWidget::updateSubmitAction()
419 {
420 const unsigned checkedCount = checkedFilesCount();
421 const bool newCommitState = canSubmit();
422 // Emit signal to update action
423 if (d->m_commitEnabled != newCommitState) {
424 d->m_commitEnabled = newCommitState;
425 emit submitActionEnabledChanged(d->m_commitEnabled);
426 }
427 if (d->m_ui.fileView && d->m_ui.fileView->model()) {
428 // Update button text.
429 const int fileCount = d->m_ui.fileView->model()->rowCount();
430 const QString msg = checkedCount ?
431 tr("%1 %2/%n File(s)", nullptr, fileCount)
432 .arg(commitName()).arg(checkedCount) :
433 commitName();
434 emit submitActionTextChanged(msg);
435 }
436 }
437
438 // Enable diff depending on selected files
updateDiffAction()439 void SubmitEditorWidget::updateDiffAction()
440 {
441 const bool filesSelected = hasSelection();
442 if (d->m_filesSelected != filesSelected) {
443 d->m_filesSelected = filesSelected;
444 emit fileSelectionChanged(d->m_filesSelected);
445 }
446 }
447
updateCheckAllComboBox()448 void SubmitEditorWidget::updateCheckAllComboBox()
449 {
450 d->m_ignoreChange = true;
451 int checkedCount = checkedFilesCount();
452 if (checkedCount == 0)
453 d->m_ui.checkAllCheckBox->setCheckState(Qt::Unchecked);
454 else if (checkedCount == d->m_ui.fileView->model()->rowCount())
455 d->m_ui.checkAllCheckBox->setCheckState(Qt::Checked);
456 else
457 d->m_ui.checkAllCheckBox->setCheckState(Qt::PartiallyChecked);
458 d->m_ignoreChange = false;
459 }
460
hasSelection() const461 bool SubmitEditorWidget::hasSelection() const
462 {
463 // Not present until model is set
464 if (const QItemSelectionModel *sm = d->m_ui.fileView->selectionModel())
465 return sm->hasSelection();
466 return false;
467 }
468
checkedFilesCount() const469 int SubmitEditorWidget::checkedFilesCount() const
470 {
471 int checkedCount = 0;
472 if (const SubmitFileModel *model = fileModel()) {
473 const int count = model->rowCount();
474 for (int i = 0; i < count; ++i)
475 if (model->checked(i))
476 ++checkedCount;
477 }
478 return checkedCount;
479 }
480
cleanupDescription(const QString & input) const481 QString SubmitEditorWidget::cleanupDescription(const QString &input) const
482 {
483 return input;
484 }
485
insertTopWidget(QWidget * w)486 void SubmitEditorWidget::insertTopWidget(QWidget *w)
487 {
488 d->m_ui.vboxLayout->insertWidget(0, w);
489 }
490
insertLeftWidget(QWidget * w)491 void SubmitEditorWidget::insertLeftWidget(QWidget *w)
492 {
493 d->m_ui.splitter->insertWidget(0, w);
494 }
495
addSubmitButtonMenu(QMenu * menu)496 void SubmitEditorWidget::addSubmitButtonMenu(QMenu *menu)
497 {
498 d->m_submitButton->setMenu(menu);
499 }
500
hideDescription()501 void SubmitEditorWidget::hideDescription()
502 {
503 d->m_ui.descriptionBox->hide();
504 setDescriptionMandatory(false);
505 }
506
verifyDescription()507 void SubmitEditorWidget::verifyDescription()
508 {
509 auto fontColor = [](Utils::Theme::Color color) {
510 return QString("<font color=\"%1\">")
511 .arg(Utils::creatorTheme()->color(color).name());
512 };
513 const QString hint = fontColor(Utils::Theme::OutputPanes_TestWarnTextColor);
514 const QString warning = fontColor(Utils::Theme::TextColorError);
515
516 const QChar newLine = '\n';
517 const int descriptionLength = d->m_description.length();
518 int subjectLength = d->m_description.indexOf(newLine);
519 int secondLineLength = 0;
520 if (subjectLength >= 0) {
521 const int secondLineStart = subjectLength + 1;
522 int secondLineEnd = d->m_description.indexOf(newLine, secondLineStart);
523 if (secondLineEnd == -1)
524 secondLineEnd = descriptionLength;
525 secondLineLength = secondLineEnd - secondLineStart;
526 } else {
527 subjectLength = descriptionLength;
528 }
529
530 enum { MaxSubjectLength = 72, WarningSubjectLength = 55 };
531 QStringList hints;
532 QStringList toolTips;
533 if (descriptionLength < 20)
534 hints.append(warning + tr("Warning: The commit message is very short."));
535
536 if (subjectLength > MaxSubjectLength)
537 hints.append(warning + tr("Warning: The commit subject is too long."));
538 else if (subjectLength > WarningSubjectLength)
539 hints.append(hint + tr("Hint: Aim for a shorter commit subject."));
540
541 if (secondLineLength > 0)
542 hints.append(hint + tr("Hint: The second line of a commit message should be empty."));
543
544 d->m_ui.descriptionHint->setText(hints.join("<br>"));
545 if (!d->m_ui.descriptionHint->text().isEmpty()) {
546 d->m_ui.descriptionHint->setToolTip(
547 tr("<p>Writing good commit messages</p>"
548 "<ul>"
549 "<li>Avoid very short commit messages.</li>"
550 "<li>Consider the first line as subject (like in email) "
551 "and keep it shorter than %n characters.</li>"
552 "<li>After an empty second line, a longer description can be added.</li>"
553 "<li>Describe why the change was done, not how it was done.</li>"
554 "</ul>", nullptr, MaxSubjectLength));
555 }
556 }
557
descriptionTextChanged()558 void SubmitEditorWidget::descriptionTextChanged()
559 {
560 d->m_description = cleanupDescription(d->m_ui.description->toPlainText());
561 verifyDescription();
562 wrapDescription();
563 trimDescription();
564 // append field entries
565 foreach (const SubmitFieldWidget *fw, d->m_fieldWidgets)
566 d->m_description += fw->fieldValues();
567 updateSubmitAction();
568 }
569
canSubmit(QString * whyNot) const570 bool SubmitEditorWidget::canSubmit(QString *whyNot) const
571 {
572 if (d->m_updateInProgress) {
573 if (whyNot)
574 *whyNot = tr("Update in progress");
575 return false;
576 }
577 if (isDescriptionMandatory() && d->m_description.trimmed().isEmpty()) {
578 if (whyNot)
579 *whyNot = tr("Description is empty");
580 return false;
581 }
582 const unsigned checkedCount = checkedFilesCount();
583 const bool res = d->m_emptyFileListEnabled || checkedCount > 0;
584 if (!res && whyNot)
585 *whyNot = tr("No files checked");
586 return res;
587 }
588
setUpdateInProgress(bool value)589 void SubmitEditorWidget::setUpdateInProgress(bool value)
590 {
591 d->m_updateInProgress = value;
592 updateSubmitAction();
593 }
594
updateInProgress() const595 bool SubmitEditorWidget::updateInProgress() const
596 {
597 return d->m_updateInProgress;
598 }
599
selectedRows() const600 QList<int> SubmitEditorWidget::selectedRows() const
601 {
602 return Utils::transform(d->m_ui.fileView->selectionModel()->selectedRows(0), &QModelIndex::row);
603 }
604
setSelectedRows(const QList<int> & rows)605 void SubmitEditorWidget::setSelectedRows(const QList<int> &rows)
606 {
607 if (const SubmitFileModel *model = fileModel()) {
608 QItemSelectionModel *selectionModel = d->m_ui.fileView->selectionModel();
609 for (int row : rows) {
610 selectionModel->select(model->index(row, 0),
611 QItemSelectionModel::Select | QItemSelectionModel::Rows);
612 }
613 }
614 }
615
commitName() const616 QString SubmitEditorWidget::commitName() const
617 {
618 return tr("&Commit");
619 }
620
addSubmitFieldWidget(SubmitFieldWidget * f)621 void SubmitEditorWidget::addSubmitFieldWidget(SubmitFieldWidget *f)
622 {
623 if (!d->m_fieldLayout) {
624 // VBox with horizontal, expanding spacer
625 d->m_fieldLayout = new QVBoxLayout;
626 auto outerLayout = new QHBoxLayout;
627 outerLayout->addLayout(d->m_fieldLayout);
628 outerLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored));
629 d->m_ui.descriptionLayout->addLayout(outerLayout);
630 }
631 d->m_fieldLayout->addWidget(f);
632 d->m_fieldWidgets.push_back(f);
633 }
634
submitFieldWidgets() const635 QList<SubmitFieldWidget *> SubmitEditorWidget::submitFieldWidgets() const
636 {
637 return d->m_fieldWidgets;
638 }
639
addDescriptionEditContextMenuAction(QAction * a)640 void SubmitEditorWidget::addDescriptionEditContextMenuAction(QAction *a)
641 {
642 d->descriptionEditContextMenuActions
643 .push_back(SubmitEditorWidgetPrivate::AdditionalContextMenuAction(-1, a));
644 }
645
insertDescriptionEditContextMenuAction(int pos,QAction * a)646 void SubmitEditorWidget::insertDescriptionEditContextMenuAction(int pos, QAction *a)
647 {
648 d->descriptionEditContextMenuActions
649 .push_back(SubmitEditorWidgetPrivate::AdditionalContextMenuAction(pos, a));
650 }
651
editorCustomContextMenuRequested(const QPoint & pos)652 void SubmitEditorWidget::editorCustomContextMenuRequested(const QPoint &pos)
653 {
654 QScopedPointer<QMenu> menu(d->m_ui.description->createStandardContextMenu());
655 // Extend
656 foreach (const SubmitEditorWidgetPrivate::AdditionalContextMenuAction &a,
657 d->descriptionEditContextMenuActions) {
658 if (a.second) {
659 if (a.first >= 0)
660 menu->insertAction(menu->actions().at(a.first), a.second);
661 else
662 menu->addAction(a.second);
663 }
664 }
665 menu->exec(d->m_ui.description->mapToGlobal(pos));
666 }
667
checkAllToggled()668 void SubmitEditorWidget::checkAllToggled()
669 {
670 if (d->m_ignoreChange)
671 return;
672 Qt::CheckState checkState = d->m_ui.checkAllCheckBox->checkState();
673 fileModel()->setAllChecked(checkState == Qt::Checked || checkState == Qt::PartiallyChecked);
674 // Reset that again, so that the user can't do it
675 d->m_ui.checkAllCheckBox->setTristate(false);
676 }
677
fileListCustomContextMenuRequested(const QPoint & pos)678 void SubmitEditorWidget::fileListCustomContextMenuRequested(const QPoint & pos)
679 {
680 // Execute menu offering to check/uncheck all
681 QMenu menu;
682 //: Check all for submit
683 QAction *checkAllAction = menu.addAction(tr("Select All"));
684 //: Uncheck all for submit
685 QAction *uncheckAllAction = menu.addAction(tr("Unselect All"));
686 QAction *action = menu.exec(d->m_ui.fileView->mapToGlobal(pos));
687 if (action == checkAllAction) {
688 fileModel()->setAllChecked(true);;
689 return;
690 }
691 if (action == uncheckAllAction) {
692 fileModel()->setAllChecked(false);
693 return;
694 }
695 }
696
isEmptyFileListEnabled() const697 bool SubmitEditorWidget::isEmptyFileListEnabled() const
698 {
699 return d->m_emptyFileListEnabled;
700 }
701
setEmptyFileListEnabled(bool e)702 void SubmitEditorWidget::setEmptyFileListEnabled(bool e)
703 {
704 if (e != d->m_emptyFileListEnabled) {
705 d->m_emptyFileListEnabled = e;
706 updateSubmitAction();
707 }
708 }
709
710 } // namespace VcsBase
711
712 #include "submiteditorwidget.moc"
713