From 81c0bb15b25bd2f907696b35e6dbb54a2aff2732 Mon Sep 17 00:00:00 2001
From: Friedemann Kleint <Friedemann.Kleint@nokia.com>
Date: Mon, 23 Mar 2009 12:03:20 +0100
Subject: [PATCH] Add a submit field widget modeled after mail client address
 controls

---
 src/libs/utils/images/removesubmitfield.png  | Bin 0 -> 578 bytes
 src/libs/utils/submiteditorwidget.cpp        |  85 ++---
 src/libs/utils/submiteditorwidget.h          |   9 +-
 src/libs/utils/submitfieldwidget.cpp         | 344 +++++++++++++++++++
 src/libs/utils/submitfieldwidget.h           |  71 ++++
 src/libs/utils/utils.pro                     |  10 +-
 src/libs/utils/utils.qrc                     |   5 +
 src/plugins/vcsbase/vcsbasesubmiteditor.cpp  |  39 ++-
 src/tools/qtcreatorwidgets/customwidgets.cpp |  12 +
 src/tools/qtcreatorwidgets/customwidgets.h   |  11 +
 10 files changed, 508 insertions(+), 78 deletions(-)
 create mode 100644 src/libs/utils/images/removesubmitfield.png
 create mode 100644 src/libs/utils/submitfieldwidget.cpp
 create mode 100644 src/libs/utils/submitfieldwidget.h
 create mode 100644 src/libs/utils/utils.qrc

diff --git a/src/libs/utils/images/removesubmitfield.png b/src/libs/utils/images/removesubmitfield.png
new file mode 100644
index 0000000000000000000000000000000000000000..e4139afc552b36d5d1aabf2fe2697d00d61b6d70
GIT binary patch
literal 578
zcmV-I0=@l-P)<h;3K|Lk000e1NJLTq000aC000aK1^@s6R&`wG00006VoOIv0RI60
z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru)d30+0y9lIP7DA50nbT9
zK~#9!RnoC*6JZzz@aKGELPHbpYR-JUTp(a+ONaLQeF33^1YE>H>R(_STm*;ep#Ow+
z)4?TMham0X>L68cXi`H%p$>8ey)&1T(4^@N*N@M|9Pypr;o*Vz$Eyh;^c%qTRif%|
zDc2i~#y3oj5W*=C9Y#{t0$_`l4dd;7DXS0X=i!dWcgeC^zVEyL!j)BE=cQ@tsnb*E
zM-(wyoSWOb-RWQ>2yio>*MCY`A<Oz?Sr%IbcAlnErRAd|c>O+FnT(^AId`u0db@W#
zuk>VQ2EC;vyh<bh0M9QkVV<1eO&H>%Ww~dj`G7p%|8sp|q2&=(RKKtDFa%Ghu@Obk
zT^IG?5Fc#YJu}Tm2hHZ!__#9X&StTw_KB2ACY4Gi)jpBhEEZLnb7zvfGFg<Sff)uE
zV+a9+02pInh5@B%aWPG6ydcu`ZWr&zW3;8j8WDIdhwnhFM?wf^qG2B&qdpv>Zrf;Q
zvp^8w)@X#qLLt`E?yan-XWcH=MkBnFGS&cqiQZ!(v@#jI&gVg9E?4?B7&z@P#OEoE
zX}!*1pnpju;AOKlO$edW#l^NJWmV43t{*g;|Gh?;bBE5)4;4{Orqf%00UKq>;&)>-
Q4*&oF07*qoM6N<$g03C?VE_OC

literal 0
HcmV?d00001

diff --git a/src/libs/utils/submiteditorwidget.cpp b/src/libs/utils/submiteditorwidget.cpp
index 687813c197b..0a283c62a09 100644
--- a/src/libs/utils/submiteditorwidget.cpp
+++ b/src/libs/utils/submiteditorwidget.cpp
@@ -28,17 +28,15 @@
 **************************************************************************/
 
 #include "submiteditorwidget.h"
+#include "submitfieldwidget.h"
 #include "ui_submiteditorwidget.h"
 
 #include <QtCore/QDebug>
 #include <QtCore/QPointer>
 #include <QtCore/QTimer>
-#include <QtCore/QSignalMapper>
 
 #include <QtGui/QPushButton>
 #include <QtGui/QMenu>
-#include <QtGui/QLineEdit>
-#include <QtGui/QFormLayout>
 #include <QtGui/QHBoxLayout>
 #include <QtGui/QToolButton>
 #include <QtGui/QSpacerItem>
@@ -127,11 +125,8 @@ struct SubmitEditorWidgetPrivate
     int m_activatedRow;
 
     QList<AdditionalContextMenuAction> descriptionEditContextMenuActions;
-    QFormLayout *m_fieldLayout;
-    // Field entries (label, line edits)
-    typedef QPair<QString, QLineEdit*> FieldEntry;
-    QList<FieldEntry> m_fieldEntries;
-    QSignalMapper *m_fieldSignalMapper;
+    QVBoxLayout *m_fieldLayout;
+    QList<SubmitFieldWidget *> m_fieldWidgets;
     int m_lineWidth;
 };
 
@@ -141,7 +136,6 @@ SubmitEditorWidgetPrivate::SubmitEditorWidgetPrivate() :
     m_fileNameColumn(1),
     m_activatedRow(-1),
     m_fieldLayout(0),
-    m_fieldSignalMapper(0),
     m_lineWidth(defaultLineWidth)
 {
 }
@@ -259,15 +253,8 @@ QString SubmitEditorWidget::descriptionText() const
 {
     QString rc = trimMessageText(lineWrap() ? wrappedText(m_d->m_ui.description) : m_d->m_ui.description->toPlainText());
     // append field entries
-    foreach(const SubmitEditorWidgetPrivate::FieldEntry &fe, m_d->m_fieldEntries) {
-        const QString fieldText = fe.second->text().trimmed();
-        if (!fieldText.isEmpty()) {
-            rc += fe.first;
-            rc += QLatin1Char(' ');
-            rc += fieldText;
-            rc += QLatin1Char('\n');
-        }
-    }
+    foreach(const SubmitFieldWidget *fw, m_d->m_fieldWidgets)
+        rc += fw->fieldValues();
     return rc;
 }
 
@@ -470,6 +457,27 @@ void SubmitEditorWidget::insertTopWidget(QWidget *w)
     m_d->m_ui.vboxLayout->insertWidget(0, w);
 }
 
+void SubmitEditorWidget::addSubmitFieldWidget(SubmitFieldWidget *f)
+{
+    if (!m_d->m_fieldLayout) {
+        // VBox with horizontal, expanding spacer
+        m_d->m_fieldLayout = new QVBoxLayout;
+        QHBoxLayout *outerLayout = new QHBoxLayout;
+        outerLayout->addLayout(m_d->m_fieldLayout);
+        outerLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored));
+        QBoxLayout *descrLayout = qobject_cast<QBoxLayout*>(m_d->m_ui.descriptionBox->layout());
+        Q_ASSERT(descrLayout);
+        descrLayout->addLayout(outerLayout);
+    }
+    m_d->m_fieldLayout->addWidget(f);
+    m_d->m_fieldWidgets.push_back(f);
+}
+
+QList<SubmitFieldWidget *> SubmitEditorWidget::submitFieldWidgets() const
+{
+    return m_d->m_fieldWidgets;
+}
+
 void SubmitEditorWidget::addDescriptionEditContextMenuAction(QAction *a)
 {
     m_d->descriptionEditContextMenuActions.push_back(SubmitEditorWidgetPrivate::AdditionalContextMenuAction(-1, a));
@@ -497,47 +505,6 @@ void SubmitEditorWidget::editorCustomContextMenuRequested(const QPoint &pos)
     delete menu;
 }
 
-QLineEdit *SubmitEditorWidget::addField(const QString &label, bool hasDialogButton)
-{
-    // Insert  the form layout below the editor
-    if (!m_d->m_fieldLayout) {
-        QHBoxLayout *outerLayout = new QHBoxLayout;
-        m_d->m_fieldLayout = new QFormLayout;
-        outerLayout->addLayout(m_d->m_fieldLayout);
-        outerLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored));
-        QBoxLayout *descrLayout = qobject_cast<QBoxLayout*>(m_d->m_ui.descriptionBox->layout());
-        Q_ASSERT(descrLayout);
-        descrLayout->addLayout(outerLayout);
-    }
-    if (hasDialogButton && !m_d->m_fieldSignalMapper) {
-        m_d->m_fieldSignalMapper = new QSignalMapper;
-        connect(m_d->m_fieldSignalMapper, SIGNAL(mapped(int)), this, SIGNAL(fieldDialogRequested(int)));
-    }
-    // Add a field row consisting of label and line edit
-    QLineEdit *lineEdit = new QLineEdit;
-    QHBoxLayout *fieldLayout = new QHBoxLayout;
-    fieldLayout->addWidget(lineEdit);
-    if (hasDialogButton) {
-        QToolButton *dialogButton = new QToolButton;
-        dialogButton->setText(tr("..."));
-        connect(dialogButton, SIGNAL(clicked()), m_d->m_fieldSignalMapper, SLOT(map()));
-        m_d->m_fieldSignalMapper->setMapping(dialogButton, m_d->m_fieldEntries.size());
-        fieldLayout->addWidget(dialogButton);
-    }
-    QToolButton *clearButton = new QToolButton;
-    clearButton->setText(tr("Clear"));
-    connect(clearButton, SIGNAL(clicked()), lineEdit, SLOT(clear()));    
-    fieldLayout->addWidget(clearButton);
-    m_d->m_fieldLayout->addRow(label, fieldLayout);
-    m_d->m_fieldEntries.push_back(SubmitEditorWidgetPrivate::FieldEntry(label, lineEdit));
-    return lineEdit;
-}
-
-QLineEdit *SubmitEditorWidget::fieldLineEdit(int i) const
-{
-    return m_d->m_fieldEntries.at(i).second;
-}
-
 } // namespace Utils
 } // namespace Core
 
diff --git a/src/libs/utils/submiteditorwidget.h b/src/libs/utils/submiteditorwidget.h
index 9ed5ecfb115..2cf9c096357 100644
--- a/src/libs/utils/submiteditorwidget.h
+++ b/src/libs/utils/submiteditorwidget.h
@@ -47,6 +47,7 @@ QT_END_NAMESPACE
 namespace Core {
 namespace Utils {
 
+class SubmitFieldWidget;
 struct SubmitEditorWidgetPrivate;
 
 /* The submit editor presents the commit message in a text editor and an
@@ -114,17 +115,13 @@ public:
     void addDescriptionEditContextMenuAction(QAction *a);
     void insertDescriptionEditContextMenuAction(int pos, QAction *a);
 
-    // Fields are additional fields consisting of a Label and a Line Edit.
-    // A field dialog is wired to a button labeled "..." that pops up a chooser
-    // resulting in text being set
-    QLineEdit *addField(const QString &label, bool hasDialogButton);
-    QLineEdit *fieldLineEdit(int i) const;
+    void addSubmitFieldWidget(SubmitFieldWidget *f);
+    QList<SubmitFieldWidget *> submitFieldWidgets() const;
 
 signals:
     void diffSelected(const QStringList &);
     void fileSelectionChanged(bool someFileSelected);
     void fileCheckStateChanged(bool someFileChecked);
-    void fieldDialogRequested(int);
 
 protected:
     virtual void changeEvent(QEvent *e);
diff --git a/src/libs/utils/submitfieldwidget.cpp b/src/libs/utils/submitfieldwidget.cpp
new file mode 100644
index 00000000000..d3715952180
--- /dev/null
+++ b/src/libs/utils/submitfieldwidget.cpp
@@ -0,0 +1,344 @@
+#include "submitfieldwidget.h"
+
+#include <QtGui/QComboBox>
+#include <QtGui/QHBoxLayout>
+#include <QtGui/QVBoxLayout>
+#include <QtGui/QLineEdit>
+#include <QtGui/QToolButton>
+#include <QtGui/QCompleter>
+#include <QtGui/QIcon>
+#include <QtGui/QToolBar>
+
+#include <QtCore/QList>
+#include <QtCore/QDebug>
+
+enum { debug = 0 };
+enum { spacing = 2 };
+
+static void inline setComboBlocked(QComboBox *cb, int index)
+{
+    const bool blocked = cb->blockSignals(true);
+    cb->setCurrentIndex(index);
+    cb->blockSignals(blocked);
+}
+
+namespace Core {
+    namespace Utils {
+
+// Field/Row entry
+struct FieldEntry {
+    FieldEntry();
+    void createGui(const QIcon &removeIcon);
+    void deleteGuiLater();
+
+    QComboBox *combo;
+    QHBoxLayout *layout;
+    QLineEdit *lineEdit;
+    QToolBar *toolBar;
+    QToolButton *clearButton;
+    QToolButton *browseButton;
+    int comboIndex;
+};
+
+FieldEntry::FieldEntry() :
+    combo(0),
+    layout(0),
+    lineEdit(0),
+    toolBar(0),
+    clearButton(0),
+    browseButton(0),
+    comboIndex(0)
+{
+}
+
+void FieldEntry::createGui(const QIcon &removeIcon)
+{
+    layout = new QHBoxLayout;
+    layout->setMargin(0);
+    layout ->setSpacing(spacing);
+    combo = new QComboBox;
+    layout->addWidget(combo);
+    lineEdit = new QLineEdit;
+    layout->addWidget(lineEdit);
+    toolBar = new QToolBar;
+    toolBar->setProperty("_q_custom_style_disabled", QVariant(true));
+    layout->addWidget(toolBar);
+    clearButton = new QToolButton;
+    clearButton->setIcon(removeIcon);
+    toolBar->addWidget(clearButton);
+    browseButton = new QToolButton;
+    browseButton->setText(QLatin1String("..."));
+    toolBar->addWidget(browseButton);
+}
+
+void FieldEntry::deleteGuiLater()
+{
+    clearButton->deleteLater();
+    browseButton->deleteLater();
+    toolBar->deleteLater();
+    lineEdit->deleteLater();    
+    combo->deleteLater();
+    layout->deleteLater();
+}
+
+// ------- SubmitFieldWidgetPrivate
+struct SubmitFieldWidgetPrivate {
+    SubmitFieldWidgetPrivate();
+
+    int findSender(const QObject *o) const;
+    int findField(const QString &f, int excluded = -1) const;
+    inline QString fieldText(int) const;
+    inline QString fieldValue(int) const;
+    inline void focusField(int);
+
+    const QIcon removeFieldIcon;
+    QStringList fields;
+    QCompleter *completer;
+    bool hasBrowseButton;
+    bool allowDuplicateFields;
+
+    QList <FieldEntry> fieldEntries;
+    QVBoxLayout *layout;
+};
+
+SubmitFieldWidgetPrivate::SubmitFieldWidgetPrivate() :
+        removeFieldIcon(QLatin1String(":/utils/images/removesubmitfield.png")),
+        completer(0),
+        hasBrowseButton(false),
+        allowDuplicateFields(false),
+        layout(0)
+{
+}
+
+int SubmitFieldWidgetPrivate::findSender(const QObject *o) const
+{
+    const int count = fieldEntries.size();
+    for (int i = 0; i < count; i++) {
+        const FieldEntry &fe = fieldEntries.at(i);
+        if (fe.combo == o || fe.browseButton == o || fe.clearButton == o || fe.lineEdit == o)
+            return i;
+    }
+    return -1;
+}
+
+int SubmitFieldWidgetPrivate::findField(const QString &ft, int excluded) const
+{
+    const int count = fieldEntries.size();
+    for (int i = 0; i < count; i++)
+        if (i != excluded && fieldText(i) == ft)
+            return i;
+    return -1;
+}
+
+QString SubmitFieldWidgetPrivate::fieldText(int pos) const
+{
+    return fieldEntries.at(pos).combo->currentText();
+}
+
+QString SubmitFieldWidgetPrivate::fieldValue(int pos) const
+{
+    return fieldEntries.at(pos).lineEdit->text();
+}
+
+void SubmitFieldWidgetPrivate::focusField(int pos)
+{
+    fieldEntries.at(pos).lineEdit->setFocus(Qt::TabFocusReason);
+}
+
+// SubmitFieldWidget
+SubmitFieldWidget::SubmitFieldWidget(QWidget *parent) :
+        QWidget(parent),
+        m_d(new SubmitFieldWidgetPrivate)
+{
+    m_d->layout = new QVBoxLayout;
+    m_d->layout->setMargin(0);
+    m_d->layout->setSpacing(spacing);
+    setLayout(m_d->layout);
+}
+
+SubmitFieldWidget::~SubmitFieldWidget()
+{
+    delete m_d;
+}
+
+void SubmitFieldWidget::setFields(const QStringList & f)
+{
+    // remove old fields
+    for (int i = m_d->fieldEntries.size() - 1 ; i >= 0 ; i--)
+        removeField(i);    
+
+    m_d->fields = f;
+    if (!f.empty())
+        createField(f.front());
+}
+
+QStringList SubmitFieldWidget::fields() const
+{
+    return m_d->fields;
+}
+
+bool SubmitFieldWidget::hasBrowseButton() const
+{
+    return m_d->hasBrowseButton;
+}
+
+void SubmitFieldWidget::setHasBrowseButton(bool d)
+{
+    if (m_d->hasBrowseButton == d)
+        return;
+    m_d->hasBrowseButton = d;
+    foreach(const FieldEntry &fe, m_d->fieldEntries)
+        fe.browseButton->setVisible(d);
+}
+
+bool SubmitFieldWidget::allowDuplicateFields() const
+{
+    return m_d->allowDuplicateFields;
+}
+
+void SubmitFieldWidget::setAllowDuplicateFields(bool v)
+{
+    m_d->allowDuplicateFields = v;
+}
+
+QCompleter *SubmitFieldWidget::completer() const
+{
+    return m_d->completer;
+}
+
+void SubmitFieldWidget::setCompleter(QCompleter *c)
+{
+    if (c == m_d->completer)
+        return;
+    m_d->completer = c;
+    foreach(const FieldEntry &fe, m_d->fieldEntries)
+        fe.lineEdit->setCompleter(c);
+}
+
+QString SubmitFieldWidget::fieldValue(int pos) const
+{
+    return m_d->fieldValue(pos);
+}
+
+void SubmitFieldWidget::setFieldValue(int pos, const QString &value)
+{
+    m_d->fieldEntries.at(pos).lineEdit->setText(value);
+}
+
+QString SubmitFieldWidget::fieldValues() const
+{
+    const QChar blank = QLatin1Char(' ');
+    const QChar newLine = QLatin1Char('\n');
+    // Format as "RevBy: value\nSigned-Off: value\n"
+    QString rc;
+    foreach(const FieldEntry &fe, m_d->fieldEntries) {
+        const QString value = fe.lineEdit->text().trimmed();
+        if (!value.isEmpty()) {
+            rc += fe.combo->currentText();
+            rc += blank;
+            rc += value;
+            rc += newLine;
+        }
+    }
+    return rc;
+}
+
+void SubmitFieldWidget::createField(const QString &f)
+{
+    FieldEntry fe;
+    fe.createGui(m_d->removeFieldIcon);
+    fe.combo->addItems(m_d->fields);
+    if (!f.isEmpty()) {
+        const int index = fe.combo->findText(f);
+        if (index != -1) {
+            setComboBlocked(fe.combo, index);
+            fe.comboIndex = index;
+        }
+    }
+
+    connect(fe.browseButton, SIGNAL(clicked()), this, SLOT(slotBrowseButtonClicked()));
+    if (!m_d->hasBrowseButton)
+        fe.browseButton->setVisible(false);
+
+    if (m_d->completer)
+        fe.lineEdit->setCompleter(m_d->completer);
+
+    connect(fe.combo, SIGNAL(currentIndexChanged(int)),
+            this, SLOT(slotComboIndexChanged(int)));
+    connect(fe.clearButton, SIGNAL(clicked()),
+            this, SLOT(slotRemove()));
+    m_d->layout->addLayout(fe.layout);
+    m_d->fieldEntries.push_back(fe);
+}
+
+void SubmitFieldWidget::slotRemove()
+{
+    // Never remove first entry
+    const int index = m_d->findSender(sender());
+    switch (index) {
+    case -1:
+        break;
+    case 0:
+        m_d->fieldEntries.front().lineEdit->clear();
+        break;
+    default:
+        removeField(index);
+        break;
+    }
+}
+
+void SubmitFieldWidget::removeField(int index)
+{
+    FieldEntry fe = m_d->fieldEntries.takeAt(index);
+    QLayoutItem * item = m_d->layout->takeAt(index);
+    fe.deleteGuiLater();
+    delete item;
+}
+
+void SubmitFieldWidget::slotComboIndexChanged(int comboIndex)
+{
+    const int pos = m_d->findSender(sender());
+    if (debug)
+        qDebug() << '>' << Q_FUNC_INFO << pos;
+    if (pos == -1)
+        return;
+    // Accept new index or reset combo to previous value?
+    int &previousIndex = m_d->fieldEntries[pos].comboIndex;
+    if (comboIndexChange(pos, comboIndex)) {
+        previousIndex = comboIndex;
+    } else {
+        setComboBlocked(m_d->fieldEntries.at(pos).combo, previousIndex);
+    }
+    if (debug)
+        qDebug() << '<' << Q_FUNC_INFO << pos;
+}
+
+// Handle change of a combo. Return "false" if the combo
+// is to be reset (refuse new field).
+bool SubmitFieldWidget::comboIndexChange(int pos, int index)
+{
+    const QString newField = m_d->fieldEntries.at(pos).combo->itemText(index);
+    // If the field is visible elsewhere: focus the existing one and refuse
+    if (!m_d->allowDuplicateFields) {
+        const int existingFieldIndex = m_d->findField(newField, pos);
+        if (existingFieldIndex != -1) {
+            m_d->focusField(existingFieldIndex);
+            return false;
+        }
+    }
+    // Empty value: just change the field
+    if (m_d->fieldValue(pos).isEmpty())
+        return true;
+    // Non-empty: Create a new field and reset the triggering combo
+    createField(newField);
+    return false;
+}
+
+void SubmitFieldWidget::slotBrowseButtonClicked()
+{
+    const int pos = m_d->findSender(sender());
+    emit browseButtonClicked(pos, m_d->fieldText(pos));
+}
+
+}
+}
diff --git a/src/libs/utils/submitfieldwidget.h b/src/libs/utils/submitfieldwidget.h
new file mode 100644
index 00000000000..34a959c5efa
--- /dev/null
+++ b/src/libs/utils/submitfieldwidget.h
@@ -0,0 +1,71 @@
+#ifndef SUBMITFIELDWIDGET_H
+#define SUBMITFIELDWIDGET_H
+
+#include "utils_global.h"
+
+#include  <QtGui/QWidget>
+
+QT_BEGIN_NAMESPACE
+class QCompleter;
+QT_END_NAMESPACE
+
+namespace Core {
+    namespace Utils {
+
+struct SubmitFieldWidgetPrivate;
+
+/* A widget for editing submit message fields like "reviewed-by:",
+ * "signed-off-by:". It displays them in a vertical row of combo/line edit fields
+ * that is modeled after the target address controls of mail clients.
+ * When choosing a different field in the combo, a new row is opened if text
+ * has been entered for the current field. Optionally, a "Browse..." button and
+ * completer can be added. */
+class QWORKBENCH_UTILS_EXPORT SubmitFieldWidget : public QWidget
+{
+    Q_OBJECT
+    Q_PROPERTY(QStringList fields READ fields WRITE setFields DESIGNABLE true)
+    Q_PROPERTY(bool hasBrowseButton READ hasBrowseButton WRITE setHasBrowseButton DESIGNABLE true)
+    Q_PROPERTY(bool allowDuplicateFields READ allowDuplicateFields WRITE setAllowDuplicateFields DESIGNABLE true)
+
+public:
+    explicit SubmitFieldWidget(QWidget *parent = 0);
+    virtual ~SubmitFieldWidget();
+
+    QStringList fields() const;
+    void setFields(const QStringList&);
+
+    bool hasBrowseButton() const;
+    void setHasBrowseButton(bool d);
+
+    // Allow several entries for fields ("reviewed-by: a", "reviewed-by: b")
+    bool allowDuplicateFields() const;
+    void setAllowDuplicateFields(bool);
+
+    QCompleter *completer() const;
+    void setCompleter(QCompleter *c);
+
+    QString fieldValue(int pos) const;
+    void setFieldValue(int pos, const QString &value);
+
+    QString fieldValues() const;
+
+signals:
+    void browseButtonClicked(int pos, const QString &field);
+
+private slots:
+    void slotRemove();
+    void slotComboIndexChanged(int);
+    void slotBrowseButtonClicked();
+
+private:
+    void removeField(int index);
+    bool comboIndexChange(int fieldNumber, int index);
+    void createField(const QString &f);
+
+    SubmitFieldWidgetPrivate *m_d;
+};
+
+}
+}
+
+#endif // SUBMITFIELDWIDGET_H
diff --git a/src/libs/utils/utils.pro b/src/libs/utils/utils.pro
index 7649ab87486..9d9e6c76a1d 100644
--- a/src/libs/utils/utils.pro
+++ b/src/libs/utils/utils.pro
@@ -1,6 +1,6 @@
 TEMPLATE = lib
 TARGET = Utils
-QT += network
+QT += gui network
 
 DEFINES += QWORKBENCH_UTILS_LIBRARY
 
@@ -24,7 +24,8 @@ SOURCES += \
     fancylineedit.cpp \
     qtcolorbutton.cpp \
     submiteditorwidget.cpp \
-    synchronousprocess.cpp
+    synchronousprocess.cpp \
+    submitfieldwidget.cpp
 
 win32 {
     SOURCES += abstractprocess_win.cpp \
@@ -57,9 +58,12 @@ HEADERS += \
     submiteditorwidget.h \
     abstractprocess.h \
     consoleprocess.h \
-    synchronousprocess.h
+    synchronousprocess.h \
+    submitfieldwidget.h
 
 FORMS += filewizardpage.ui \
          projectintropage.ui \
          newclasswidget.ui \
          submiteditorwidget.ui
+
+RESOURCES += utils.qrc
diff --git a/src/libs/utils/utils.qrc b/src/libs/utils/utils.qrc
new file mode 100644
index 00000000000..ef180b21fe0
--- /dev/null
+++ b/src/libs/utils/utils.qrc
@@ -0,0 +1,5 @@
+<RCC>
+    <qresource prefix="/utils" >
+        <file>images/removesubmitfield.png</file>
+    </qresource>
+</RCC>
diff --git a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp
index e696f46b126..62ea9417b82 100644
--- a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp
+++ b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp
@@ -39,6 +39,7 @@
 #include <coreplugin/uniqueidmanager.h>
 #include <coreplugin/actionmanager/actionmanager.h>
 #include <utils/submiteditorwidget.h>
+#include <utils/submitfieldwidget.h>
 #include <find/basetextfind.h>
 
 #include <projectexplorer/projectexplorer.h>
@@ -135,7 +136,6 @@ VCSBaseSubmitEditor::VCSBaseSubmitEditor(const VCSBaseSubmitEditorParameters *pa
     // Do we have user fields?
     if (!settings.nickNameFieldListFile.isEmpty())
         createUserFields(settings.nickNameFieldListFile);
-    connect(m_d->m_widget, SIGNAL(fieldDialogRequested(int)), this, SLOT(slotSetFieldNickName(int)));
 
     // wrapping. etc
     slotUpdateEditorSettings(settings);
@@ -161,6 +161,19 @@ void VCSBaseSubmitEditor::slotUpdateEditorSettings(const Internal::VCSBaseSettin
     setLineWrap(s.lineWrap);
 }
 
+// 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'));
+    foreach(const QString &field, rawFields) {
+        const QString trimmedField = field.trimmed();
+        if (!trimmedField.isEmpty())
+            rc.push_back(trimmedField);
+    }
+    return rc;
+}
+
 void VCSBaseSubmitEditor::createUserFields(const QString &fieldConfigFile)
 {
     QFile fieldFile(fieldConfigFile);
@@ -169,17 +182,21 @@ void VCSBaseSubmitEditor::createUserFields(const QString &fieldConfigFile)
         return;
     }
     // Parse into fields
-    const QStringList fields = QString::fromUtf8(fieldFile.readAll()).trimmed().split(QLatin1Char('\n'));
+    const QStringList fields = fieldTexts(QString::fromUtf8(fieldFile.readAll()));
     if (fields.empty())
         return;
     // Create a completer on user names
     const QStandardItemModel *nickNameModel = Internal::VCSBasePlugin::instance()->nickNameModel();
     QCompleter *completer = new QCompleter(Internal::NickNameDialog::nickNameList(nickNameModel), this);
-    foreach(const QString &field, fields) {
-        const QString trimmedField = field.trimmed();
-        if (!trimmedField.isEmpty())
-            m_d->m_widget->addField(trimmedField, true)->setCompleter(completer);
-    }
+
+    Core::Utils::SubmitFieldWidget *fieldWidget = new Core::Utils::SubmitFieldWidget;
+    connect(fieldWidget, SIGNAL(browseButtonClicked(int,QString)),
+            this, SLOT(slotSetFieldNickName(int)));    
+    fieldWidget->setCompleter(completer);
+    fieldWidget->setAllowDuplicateFields(true);
+    fieldWidget->setHasBrowseButton(true);
+    fieldWidget->setFields(fields);
+    m_d->m_widget->addSubmitFieldWidget(fieldWidget);
 }
 
 void VCSBaseSubmitEditor::registerActions(QAction *editorUndoAction,  QAction *editorRedoAction,
@@ -461,9 +478,11 @@ void VCSBaseSubmitEditor::slotInsertNickName()
 
 void VCSBaseSubmitEditor::slotSetFieldNickName(int i)
 {
-    const QString nick = promptForNickName();
-    if (!nick.isEmpty())
-        m_d->m_widget->fieldLineEdit(i)->setText(nick);
+    if (Core::Utils::SubmitFieldWidget *sfw  =m_d->m_widget->submitFieldWidgets().front()) {
+        const QString nick = promptForNickName();
+        if (!nick.isEmpty())
+            sfw->setFieldValue(i, nick);
+    }
 }
 
 void VCSBaseSubmitEditor::slotCheckSubmitMessage()
diff --git a/src/tools/qtcreatorwidgets/customwidgets.cpp b/src/tools/qtcreatorwidgets/customwidgets.cpp
index de1831cd237..758fcb1ed28 100644
--- a/src/tools/qtcreatorwidgets/customwidgets.cpp
+++ b/src/tools/qtcreatorwidgets/customwidgets.cpp
@@ -143,6 +143,17 @@ SubmitEditorWidget_CW::SubmitEditorWidget_CW(QObject *parent) :
 {
 }
 
+SubmitFieldWidget_CW::SubmitFieldWidget_CW(QObject *parent) :
+    QObject(parent),
+    CustomWidget<Core::Utils::SubmitFieldWidget>
+    (QLatin1String("<utils/submitfieldwidget.h>"),
+    false,
+    QLatin1String(groupC),
+    QIcon(),
+    tr("Show predefined fields of a submit message in a control based on mail address controls"))
+{
+}
+
 // -------------- WidgetCollection
 WidgetCollection::WidgetCollection(QObject *parent) :
     QObject(parent)
@@ -157,6 +168,7 @@ WidgetCollection::WidgetCollection(QObject *parent) :
     m_plugins.push_back(new FancyLineEdit_CW(this));
     m_plugins.push_back(new QtColorButton_CW(this));
     m_plugins.push_back(new SubmitEditorWidget_CW(this));
+    m_plugins.push_back(new SubmitFieldWidget_CW(this));
 }
 
 QList<QDesignerCustomWidgetInterface*> WidgetCollection::customWidgets() const
diff --git a/src/tools/qtcreatorwidgets/customwidgets.h b/src/tools/qtcreatorwidgets/customwidgets.h
index 191f3e1d3d5..7f9bd4efeb8 100644
--- a/src/tools/qtcreatorwidgets/customwidgets.h
+++ b/src/tools/qtcreatorwidgets/customwidgets.h
@@ -41,6 +41,7 @@
 #include <utils/fancylineedit.h>
 #include <utils/qtcolorbutton.h>
 #include <utils/submiteditorwidget.h>
+#include <utils/submitfieldwidget.h>
 
 #include <QtDesigner/QDesignerCustomWidgetCollectionInterface>
 
@@ -141,6 +142,16 @@ public:
     explicit SubmitEditorWidget_CW(QObject *parent = 0);
 };
 
+class SubmitFieldWidget_CW :
+    public QObject,
+    public CustomWidget<Core::Utils::SubmitFieldWidget>
+{
+    Q_OBJECT
+    Q_INTERFACES(QDesignerCustomWidgetInterface)
+public:
+    explicit SubmitFieldWidget_CW(QObject *parent = 0);
+};
+
 // Collection
 
 class WidgetCollection : public QObject, public QDesignerCustomWidgetCollectionInterface
-- 
GitLab