/**************************************************************************** ** ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/ ** ** This file is part of the Qt Designer of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** 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. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** 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. ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "formlayoutmenu_p.h" #include "layoutinfo_p.h" #include "qdesigner_command_p.h" #include "qdesigner_utils_p.h" #include "qdesigner_propertycommand_p.h" #include "ui_formlayoutrowdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char *buddyPropertyC = "buddy"; static const char *fieldWidgetBaseClasses[] = { "QLineEdit", "QComboBox", "QSpinBox", "QDoubleSpinBox", "QCheckBox", "QDateEdit", "QTimeEdit", "QDateTimeEdit", "QDial", "QWidget" }; QT_BEGIN_NAMESPACE namespace qdesigner_internal { // Struct that describes a row of controls (descriptive label and control) to // be added to a form layout. struct FormLayoutRow { FormLayoutRow() : buddy(false) {} QString labelName; QString labelText; QString fieldClassName; QString fieldName; bool buddy; }; // A Dialog to edit a FormLayoutRow. Lets the user input a label text, label // name, field widget type, field object name and buddy setting. As the // user types the label text; the object names to be used for label and field // are updated. It also checks the buddy setting depending on whether the // label text contains a buddy marker. class FormLayoutRowDialog : public QDialog { Q_DISABLE_COPY(FormLayoutRowDialog) Q_OBJECT public: explicit FormLayoutRowDialog(QDesignerFormEditorInterface *core, QWidget *parent); FormLayoutRow formLayoutRow() const; bool buddy() const; void setBuddy(bool); // Accessors for form layout row numbers using 0..[n-1] convention int row() const; void setRow(int); void setRowRange(int, int); QString fieldClass() const; QString labelText() const; static QStringList fieldWidgetClasses(QDesignerFormEditorInterface *core); private slots: void labelTextEdited(const QString &text); void labelNameEdited(const QString &text); void fieldNameEdited(const QString &text); void buddyClicked(); void fieldClassChanged(int); private: bool isValid() const; void updateObjectNames(bool updateLabel, bool updateField); void updateOkButton(); // Check for buddy marker in string const QRegExp m_buddyMarkerRegexp; Ui::FormLayoutRowDialog m_ui; bool m_labelNameEdited; bool m_fieldNameEdited; bool m_buddyClicked; }; FormLayoutRowDialog::FormLayoutRowDialog(QDesignerFormEditorInterface *core, QWidget *parent) : QDialog(parent), m_buddyMarkerRegexp(QLatin1String("\\&[^&]")), m_labelNameEdited(false), m_fieldNameEdited(false), m_buddyClicked(false) { Q_ASSERT(m_buddyMarkerRegexp.isValid()); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setModal(true); m_ui.setupUi(this); connect(m_ui.labelTextLineEdit, SIGNAL(textEdited(QString)), this, SLOT(labelTextEdited(QString))); QRegExpValidator *nameValidator = new QRegExpValidator(QRegExp(QLatin1String("^[a-zA-Z0-9_]+$")), this); Q_ASSERT(nameValidator->regExp().isValid()); m_ui.labelNameLineEdit->setValidator(nameValidator); connect(m_ui.labelNameLineEdit, SIGNAL(textEdited(QString)), this, SLOT(labelNameEdited(QString))); m_ui.fieldNameLineEdit->setValidator(nameValidator); connect(m_ui.fieldNameLineEdit, SIGNAL(textEdited(QString)), this, SLOT(fieldNameEdited(QString))); connect(m_ui.buddyCheckBox, SIGNAL(clicked()), this, SLOT(buddyClicked())); m_ui.fieldClassComboBox->addItems(fieldWidgetClasses(core)); m_ui.fieldClassComboBox->setCurrentIndex(0); connect(m_ui.fieldClassComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(fieldClassChanged(int))); updateOkButton(); } FormLayoutRow FormLayoutRowDialog::formLayoutRow() const { FormLayoutRow rc; rc.labelText = labelText(); rc.labelName = m_ui.labelNameLineEdit->text(); rc.fieldClassName = fieldClass(); rc.fieldName = m_ui.fieldNameLineEdit->text(); rc.buddy = buddy(); return rc; } bool FormLayoutRowDialog::buddy() const { return m_ui.buddyCheckBox->checkState() == Qt::Checked; } void FormLayoutRowDialog::setBuddy(bool b) { m_ui.buddyCheckBox->setCheckState(b ? Qt::Checked : Qt::Unchecked); } // Convert rows to 1..n convention for users int FormLayoutRowDialog::row() const { return m_ui.rowSpinBox->value() - 1; } void FormLayoutRowDialog::setRow(int row) { m_ui.rowSpinBox->setValue(row + 1); } void FormLayoutRowDialog::setRowRange(int from, int to) { m_ui.rowSpinBox->setMinimum(from + 1); m_ui.rowSpinBox->setMaximum(to + 1); m_ui.rowSpinBox->setEnabled(to - from > 0); } QString FormLayoutRowDialog::fieldClass() const { return m_ui.fieldClassComboBox->itemText(m_ui.fieldClassComboBox->currentIndex()); } QString FormLayoutRowDialog::labelText() const { return m_ui.labelTextLineEdit->text(); } bool FormLayoutRowDialog::isValid() const { // Check for non-empty names and presence of buddy marker if checked const QString name = labelText(); if (name.isEmpty() || m_ui.labelNameLineEdit->text().isEmpty() || m_ui.fieldNameLineEdit->text().isEmpty()) return false; if (buddy() && !name.contains(m_buddyMarkerRegexp)) return false; return true; } void FormLayoutRowDialog::updateOkButton() { m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(isValid()); } void FormLayoutRowDialog::labelTextEdited(const QString &text) { updateObjectNames(true, true); // Set buddy if '&' is present unless the user changed it if (!m_buddyClicked) setBuddy(text.contains(m_buddyMarkerRegexp)); updateOkButton(); } // Get a suitable object name postfix from a class name: // "namespace::QLineEdit"->"LineEdit" static inline QString postFixFromClassName(QString className) { const int index = className.lastIndexOf(QLatin1String("::")); if (index != -1) className.remove(0, index + 2); if (className.size() > 2) if (className.at(0) == QLatin1Char('Q') || className.at(0) == QLatin1Char('K')) if (className.at(1).isUpper()) className.remove(0, 1); return className; } // Helper routines to filter out characters for converting texts into // class name prefixes. Only accepts ASCII characters/digits and underscores. enum PrefixCharacterKind { PC_Digit, PC_UpperCaseLetter, PC_LowerCaseLetter, PC_Other, PC_Invalid }; static inline PrefixCharacterKind prefixCharacterKind(const QChar &c) { switch (c.category()) { case QChar::Number_DecimalDigit: return PC_Digit; case QChar::Letter_Lowercase: { const char a = c.toAscii(); if (a >= 'a' && a <= 'z') return PC_LowerCaseLetter; } break; case QChar::Letter_Uppercase: { const char a = c.toAscii(); if (a >= 'A' && a <= 'Z') return PC_UpperCaseLetter; } break; case QChar::Punctuation_Connector: if (c.toAscii() == '_') return PC_Other; break; default: break; } return PC_Invalid; } // Convert the text the user types into a usable class name prefix by filtering // characters, lower-casing the first character and camel-casing subsequent // words. ("zip code:") --> ("zipCode"). static QString prefixFromLabel(const QString &prefix) { QString rc; const int length = prefix.size(); bool lastWasAcceptable = false; for (int i = 0 ; i < length; i++) { const QChar c = prefix.at(i); const PrefixCharacterKind kind = prefixCharacterKind(c); const bool acceptable = kind != PC_Invalid; if (acceptable) { if (rc.isEmpty()) { // Lower-case first character rc += kind == PC_UpperCaseLetter ? c.toLower() : c; } else { // Camel-case words rc += !lastWasAcceptable && kind == PC_LowerCaseLetter ? c.toUpper() : c; } } lastWasAcceptable = acceptable; } return rc; } void FormLayoutRowDialog::updateObjectNames(bool updateLabel, bool updateField) { // Generate label + field object names from the label text, that is, // "&Zip code:" -> "zipcodeLabel", "zipcodeLineEdit" unless the user // edited it. const bool doUpdateLabel = !m_labelNameEdited && updateLabel; const bool doUpdateField = !m_fieldNameEdited && updateField; if (!doUpdateLabel && !doUpdateField) return; const QString prefix = prefixFromLabel(labelText()); // Set names if (doUpdateLabel) m_ui.labelNameLineEdit->setText(prefix + QLatin1String("Label")); if (doUpdateField) m_ui.fieldNameLineEdit->setText(prefix + postFixFromClassName(fieldClass())); } void FormLayoutRowDialog::fieldClassChanged(int) { updateObjectNames(false, true); } void FormLayoutRowDialog::labelNameEdited(const QString & /*text*/) { m_labelNameEdited = true; // stop auto-updating after user change updateOkButton(); } void FormLayoutRowDialog::fieldNameEdited(const QString & /*text*/) { m_fieldNameEdited = true; // stop auto-updating after user change updateOkButton(); } void FormLayoutRowDialog::buddyClicked() { m_buddyClicked = true; // stop auto-updating after user change updateOkButton(); } /* Create a list of classes suitable for field widgets. Take the fixed base * classes provided and look in the widget database for custom widgets derived * from them ("QLineEdit", "CustomLineEdit", "QComboBox"...). */ QStringList FormLayoutRowDialog::fieldWidgetClasses(QDesignerFormEditorInterface *core) { // Base class -> custom widgets map typedef QMultiHash ClassMap; static QStringList rc; if (rc.empty()) { const int fwCount = sizeof(fieldWidgetBaseClasses)/sizeof(const char*); // Turn known base classes into list QStringList baseClasses; for (int i = 0; i < fwCount; i++) baseClasses.push_back(QLatin1String(fieldWidgetBaseClasses[i])); // Scan for custom widgets that inherit them and store them in a // multimap of base class->custom widgets unless we have a language // extension installed which might do funny things with custom widgets. ClassMap customClassMap; if (qt_extension(core->extensionManager(), core) == 0) { const QDesignerWidgetDataBaseInterface *wdb = core->widgetDataBase(); const int wdbCount = wdb->count(); for (int w = 0; w < wdbCount; ++w) { // Check for non-container custom types that extend the // respective base class. const QDesignerWidgetDataBaseItemInterface *dbItem = wdb->item(w); if (!dbItem->isPromoted() && !dbItem->isContainer() && dbItem->isCustom()) { const int index = baseClasses.indexOf(dbItem->extends()); if (index != -1) customClassMap.insert(baseClasses.at(index), dbItem->name()); } } } // Compile final list, taking each base class and append custom widgets // based on it. for (int i = 0; i < fwCount; i++) { rc.push_back(baseClasses.at(i)); rc += customClassMap.values(baseClasses.at(i)); } } return rc; } // ------------------ Utilities static QFormLayout *managedFormLayout(const QDesignerFormEditorInterface *core, const QWidget *w) { QLayout *l = 0; if (LayoutInfo::managedLayoutType(core, w, &l) == LayoutInfo::Form) return qobject_cast(l); return 0; } // Create the widgets of a control row and apply text properties contained // in the struct, called by addFormLayoutRow() static QPair createWidgets(const FormLayoutRow &row, QWidget *parent, QDesignerFormWindowInterface *formWindow) { QDesignerFormEditorInterface *core = formWindow->core(); QDesignerWidgetFactoryInterface *wf = core->widgetFactory(); QPair rc = QPair(wf->createWidget(QLatin1String("QLabel"), parent), wf->createWidget(row.fieldClassName, parent)); // Set up properties of the label const QString objectNameProperty = QLatin1String("objectName"); QDesignerPropertySheetExtension *labelSheet = qt_extension(core->extensionManager(), rc.first); int nameIndex = labelSheet->indexOf(objectNameProperty); labelSheet->setProperty(nameIndex, QVariant::fromValue(PropertySheetStringValue(row.labelName))); labelSheet->setChanged(nameIndex, true); formWindow->ensureUniqueObjectName(rc.first); const int textIndex = labelSheet->indexOf(QLatin1String("text")); labelSheet->setProperty(textIndex, QVariant::fromValue(PropertySheetStringValue(row.labelText))); labelSheet->setChanged(textIndex, true); // Set up properties of the control QDesignerPropertySheetExtension *controlSheet = qt_extension(core->extensionManager(), rc.second); nameIndex = controlSheet->indexOf(objectNameProperty); controlSheet->setProperty(nameIndex, QVariant::fromValue(PropertySheetStringValue(row.fieldName))); controlSheet->setChanged(nameIndex, true); formWindow->ensureUniqueObjectName(rc.second); return rc; } // Create a command sequence on the undo stack of the form window that creates // the widgets of the row and inserts them into the form layout. static void addFormLayoutRow(const FormLayoutRow &formLayoutRow, int row, QWidget *w, QDesignerFormWindowInterface *formWindow) { QFormLayout *formLayout = managedFormLayout(formWindow->core(), w); Q_ASSERT(formLayout); QUndoStack *undoStack = formWindow->commandHistory(); const QString macroName = QCoreApplication::translate("Command", "Add '%1' to '%2'").arg(formLayoutRow.labelText, formLayout->objectName()); undoStack->beginMacro(macroName); // Create a list of widget insertion commands and pass them a cell position const QPair widgetPair = createWidgets(formLayoutRow, w, formWindow); InsertWidgetCommand *labelCmd = new InsertWidgetCommand(formWindow); labelCmd->init(widgetPair.first, false, row, 0); undoStack->push(labelCmd); InsertWidgetCommand *controlCmd = new InsertWidgetCommand(formWindow); controlCmd->init(widgetPair.second, false, row, 1); undoStack->push(controlCmd); if (formLayoutRow.buddy) { SetPropertyCommand *buddyCommand = new SetPropertyCommand(formWindow); buddyCommand->init(widgetPair.first, QLatin1String(buddyPropertyC), widgetPair.second->objectName()); undoStack->push(buddyCommand); } undoStack->endMacro(); } // ---------------- FormLayoutMenu FormLayoutMenu::FormLayoutMenu(QObject *parent) : QObject(parent), m_separator1(new QAction(this)), m_populateFormAction(new QAction(tr("Add form layout row..."), this)), m_separator2(new QAction(this)) { m_separator1->setSeparator(true); connect(m_populateFormAction, SIGNAL(triggered()), this, SLOT(slotAddRow())); m_separator2->setSeparator(true); } void FormLayoutMenu::populate(QWidget *w, QDesignerFormWindowInterface *fw, ActionList &actions) { switch (LayoutInfo::managedLayoutType(fw->core(), w)) { case LayoutInfo::Form: if (!actions.empty() && !actions.back()->isSeparator()) actions.push_back(m_separator1); actions.push_back(m_populateFormAction); actions.push_back(m_separator2); m_widget = w; break; default: m_widget = 0; break; } } void FormLayoutMenu::slotAddRow() { QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(m_widget); Q_ASSERT(m_widget && fw); const int rowCount = managedFormLayout(fw->core(), m_widget)->rowCount(); FormLayoutRowDialog dialog(fw->core(), fw); dialog.setRowRange(0, rowCount); dialog.setRow(rowCount); if (dialog.exec() != QDialog::Accepted) return; addFormLayoutRow(dialog.formLayoutRow(), dialog.row(), m_widget, fw); } QAction *FormLayoutMenu::preferredEditAction(QWidget *w, QDesignerFormWindowInterface *fw) { if (LayoutInfo::managedLayoutType(fw->core(), w) == LayoutInfo::Form) { m_widget = w; return m_populateFormAction; } return 0; } } QT_END_NAMESPACE #include "formlayoutmenu.moc"