/************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** ** GNU Lesser General Public License Usage ** ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this file. ** Please review the following information to ensure the GNU Lesser General ** Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** Other Usage ** ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** **************************************************************************/ #include "submitfieldwidget.h" #include #include #include #include #include #include #include #include #include #include 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); } /*! \class Utils::SubmitFieldWidget \brief 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. */ 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 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), d(new SubmitFieldWidgetPrivate) { d->layout = new QVBoxLayout; d->layout->setMargin(0); d->layout->setSpacing(spacing); setLayout(d->layout); } SubmitFieldWidget::~SubmitFieldWidget() { delete d; } void SubmitFieldWidget::setFields(const QStringList & f) { // remove old fields for (int i = d->fieldEntries.size() - 1 ; i >= 0 ; i--) removeField(i); d->fields = f; if (!f.empty()) createField(f.front()); } QStringList SubmitFieldWidget::fields() const { return d->fields; } bool SubmitFieldWidget::hasBrowseButton() const { return d->hasBrowseButton; } void SubmitFieldWidget::setHasBrowseButton(bool on) { if (d->hasBrowseButton == on) return; d->hasBrowseButton = on; foreach(const FieldEntry &fe, d->fieldEntries) fe.browseButton->setVisible(on); } bool SubmitFieldWidget::allowDuplicateFields() const { return d->allowDuplicateFields; } void SubmitFieldWidget::setAllowDuplicateFields(bool v) { d->allowDuplicateFields = v; } QCompleter *SubmitFieldWidget::completer() const { return d->completer; } void SubmitFieldWidget::setCompleter(QCompleter *c) { if (c == d->completer) return; d->completer = c; foreach(const FieldEntry &fe, d->fieldEntries) fe.lineEdit->setCompleter(c); } QString SubmitFieldWidget::fieldValue(int pos) const { return d->fieldValue(pos); } void SubmitFieldWidget::setFieldValue(int pos, const QString &value) { 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, 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(d->removeFieldIcon); fe.combo->addItems(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 (!d->hasBrowseButton) fe.browseButton->setVisible(false); if (d->completer) fe.lineEdit->setCompleter(d->completer); connect(fe.combo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotComboIndexChanged(int))); connect(fe.clearButton, SIGNAL(clicked()), this, SLOT(slotRemove())); d->layout->addLayout(fe.layout); d->fieldEntries.push_back(fe); } void SubmitFieldWidget::slotRemove() { // Never remove first entry const int index = d->findSender(sender()); switch (index) { case -1: break; case 0: d->fieldEntries.front().lineEdit->clear(); break; default: removeField(index); break; } } void SubmitFieldWidget::removeField(int index) { FieldEntry fe = d->fieldEntries.takeAt(index); QLayoutItem * item = d->layout->takeAt(index); fe.deleteGuiLater(); delete item; } void SubmitFieldWidget::slotComboIndexChanged(int comboIndex) { const int pos = 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 = d->fieldEntries[pos].comboIndex; if (comboIndexChange(pos, comboIndex)) { previousIndex = comboIndex; } else { setComboBlocked(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 = d->fieldEntries.at(pos).combo->itemText(index); // If the field is visible elsewhere: focus the existing one and refuse if (!d->allowDuplicateFields) { const int existingFieldIndex = d->findField(newField, pos); if (existingFieldIndex != -1) { d->focusField(existingFieldIndex); return false; } } // Empty value: just change the field if (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 = d->findSender(sender()); emit browseButtonClicked(pos, d->fieldText(pos)); } }