Commit 81c0bb15 authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Add a submit field widget modeled after mail client address controls

parent 5cbd0679
......@@ -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
......
......@@ -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);
......
#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));
}
}
}
#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
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 \