blob: 1bfaaa5849a03a4b2fa9e46d545d737071f7f870 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** 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 <QtDesigner/QDesignerFormWindowInterface>
#include <QtDesigner/QDesignerFormEditorInterface>
#include <QtDesigner/QDesignerWidgetFactoryInterface>
#include <QtDesigner/QDesignerPropertySheetExtension>
#include <QtDesigner/QExtensionManager>
#include <QtDesigner/QDesignerWidgetDataBaseInterface>
#include <QtDesigner/QDesignerLanguageExtension>
#include <QtGui/QAction>
#include <QtGui/QWidget>
#include <QtGui/QFormLayout>
#include <QtGui/QUndoStack>
#include <QtGui/QDialog>
#include <QtGui/QPushButton>
#include <QtGui/QRegExpValidator>
#include <QtCore/QPair>
#include <QtCore/QCoreApplication>
#include <QtCore/QRegExp>
#include <QtCore/QMultiHash>
#include <QtCore/QDebug>
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<QString, QString> 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<QDesignerLanguageExtension *>(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<QFormLayout *>(l);
return 0;
}
// Create the widgets of a control row and apply text properties contained
// in the struct, called by addFormLayoutRow()
static QPair<QWidget *,QWidget *>
createWidgets(const FormLayoutRow &row, QWidget *parent,
QDesignerFormWindowInterface *formWindow)
{
QDesignerFormEditorInterface *core = formWindow->core();
QDesignerWidgetFactoryInterface *wf = core->widgetFactory();
QPair<QWidget *,QWidget *> rc = QPair<QWidget *,QWidget *>(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<QDesignerPropertySheetExtension*>(core->extensionManager(), rc.first);
int nameIndex = labelSheet->indexOf(objectNameProperty);
labelSheet->setProperty(nameIndex, qVariantFromValue(PropertySheetStringValue(row.labelName)));
labelSheet->setChanged(nameIndex, true);
formWindow->ensureUniqueObjectName(rc.first);
const int textIndex = labelSheet->indexOf(QLatin1String("text"));
labelSheet->setProperty(textIndex, qVariantFromValue(PropertySheetStringValue(row.labelText)));
labelSheet->setChanged(textIndex, true);
// Set up properties of the control
QDesignerPropertySheetExtension *controlSheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), rc.second);
nameIndex = controlSheet->indexOf(objectNameProperty);
controlSheet->setProperty(nameIndex, qVariantFromValue(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<QWidget *,QWidget *> 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"