source: trunk/tools/designer/src/lib/shared/formlayoutmenu.cpp@ 944

Last change on this file since 944 was 846, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 18.8 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the Qt Designer of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "formlayoutmenu_p.h"
43#include "layoutinfo_p.h"
44#include "qdesigner_command_p.h"
45#include "qdesigner_utils_p.h"
46#include "qdesigner_propertycommand_p.h"
47#include "ui_formlayoutrowdialog.h"
48
49#include <QtDesigner/QDesignerFormWindowInterface>
50#include <QtDesigner/QDesignerFormEditorInterface>
51#include <QtDesigner/QDesignerWidgetFactoryInterface>
52#include <QtDesigner/QDesignerPropertySheetExtension>
53#include <QtDesigner/QExtensionManager>
54#include <QtDesigner/QDesignerWidgetDataBaseInterface>
55#include <QtDesigner/QDesignerLanguageExtension>
56
57#include <QtGui/QAction>
58#include <QtGui/QWidget>
59#include <QtGui/QFormLayout>
60#include <QtGui/QUndoStack>
61#include <QtGui/QDialog>
62#include <QtGui/QPushButton>
63#include <QtGui/QRegExpValidator>
64
65#include <QtCore/QPair>
66#include <QtCore/QCoreApplication>
67#include <QtCore/QRegExp>
68#include <QtCore/QMultiHash>
69#include <QtCore/QDebug>
70
71static const char *buddyPropertyC = "buddy";
72static const char *fieldWidgetBaseClasses[] = {
73 "QLineEdit", "QComboBox", "QSpinBox", "QDoubleSpinBox", "QCheckBox",
74 "QDateEdit", "QTimeEdit", "QDateTimeEdit", "QDial", "QWidget"
75};
76
77QT_BEGIN_NAMESPACE
78
79namespace qdesigner_internal {
80
81// Struct that describes a row of controls (descriptive label and control) to
82// be added to a form layout.
83struct FormLayoutRow {
84 FormLayoutRow() : buddy(false) {}
85
86 QString labelName;
87 QString labelText;
88 QString fieldClassName;
89 QString fieldName;
90 bool buddy;
91};
92
93// A Dialog to edit a FormLayoutRow. Lets the user input a label text, label
94// name, field widget type, field object name and buddy setting. As the
95// user types the label text; the object names to be used for label and field
96// are updated. It also checks the buddy setting depending on whether the
97// label text contains a buddy marker.
98class FormLayoutRowDialog : public QDialog {
99 Q_DISABLE_COPY(FormLayoutRowDialog)
100 Q_OBJECT
101public:
102 explicit FormLayoutRowDialog(QDesignerFormEditorInterface *core,
103 QWidget *parent);
104
105 FormLayoutRow formLayoutRow() const;
106
107 bool buddy() const;
108 void setBuddy(bool);
109
110 // Accessors for form layout row numbers using 0..[n-1] convention
111 int row() const;
112 void setRow(int);
113 void setRowRange(int, int);
114
115 QString fieldClass() const;
116 QString labelText() const;
117
118 static QStringList fieldWidgetClasses(QDesignerFormEditorInterface *core);
119
120private slots:
121 void labelTextEdited(const QString &text);
122 void labelNameEdited(const QString &text);
123 void fieldNameEdited(const QString &text);
124 void buddyClicked();
125 void fieldClassChanged(int);
126
127private:
128 bool isValid() const;
129 void updateObjectNames(bool updateLabel, bool updateField);
130 void updateOkButton();
131
132 // Check for buddy marker in string
133 const QRegExp m_buddyMarkerRegexp;
134
135 Ui::FormLayoutRowDialog m_ui;
136 bool m_labelNameEdited;
137 bool m_fieldNameEdited;
138 bool m_buddyClicked;
139};
140
141FormLayoutRowDialog::FormLayoutRowDialog(QDesignerFormEditorInterface *core,
142 QWidget *parent) :
143 QDialog(parent),
144 m_buddyMarkerRegexp(QLatin1String("\\&[^&]")),
145 m_labelNameEdited(false),
146 m_fieldNameEdited(false),
147 m_buddyClicked(false)
148{
149 Q_ASSERT(m_buddyMarkerRegexp.isValid());
150
151 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
152 setModal(true);
153 m_ui.setupUi(this);
154 connect(m_ui.labelTextLineEdit, SIGNAL(textEdited(QString)), this, SLOT(labelTextEdited(QString)));
155
156 QRegExpValidator *nameValidator = new QRegExpValidator(QRegExp(QLatin1String("^[a-zA-Z0-9_]+$")), this);
157 Q_ASSERT(nameValidator->regExp().isValid());
158
159 m_ui.labelNameLineEdit->setValidator(nameValidator);
160 connect(m_ui.labelNameLineEdit, SIGNAL(textEdited(QString)),
161 this, SLOT(labelNameEdited(QString)));
162
163 m_ui.fieldNameLineEdit->setValidator(nameValidator);
164 connect(m_ui.fieldNameLineEdit, SIGNAL(textEdited(QString)),
165 this, SLOT(fieldNameEdited(QString)));
166
167 connect(m_ui.buddyCheckBox, SIGNAL(clicked()), this, SLOT(buddyClicked()));
168
169 m_ui.fieldClassComboBox->addItems(fieldWidgetClasses(core));
170 m_ui.fieldClassComboBox->setCurrentIndex(0);
171 connect(m_ui.fieldClassComboBox, SIGNAL(currentIndexChanged(int)),
172 this, SLOT(fieldClassChanged(int)));
173
174 updateOkButton();
175}
176
177FormLayoutRow FormLayoutRowDialog::formLayoutRow() const
178{
179 FormLayoutRow rc;
180 rc.labelText = labelText();
181 rc.labelName = m_ui.labelNameLineEdit->text();
182 rc.fieldClassName = fieldClass();
183 rc.fieldName = m_ui.fieldNameLineEdit->text();
184 rc.buddy = buddy();
185 return rc;
186}
187
188bool FormLayoutRowDialog::buddy() const
189{
190 return m_ui.buddyCheckBox->checkState() == Qt::Checked;
191}
192
193void FormLayoutRowDialog::setBuddy(bool b)
194{
195 m_ui.buddyCheckBox->setCheckState(b ? Qt::Checked : Qt::Unchecked);
196}
197
198// Convert rows to 1..n convention for users
199int FormLayoutRowDialog::row() const
200{
201 return m_ui.rowSpinBox->value() - 1;
202}
203
204void FormLayoutRowDialog::setRow(int row)
205{
206 m_ui.rowSpinBox->setValue(row + 1);
207}
208
209void FormLayoutRowDialog::setRowRange(int from, int to)
210{
211 m_ui.rowSpinBox->setMinimum(from + 1);
212 m_ui.rowSpinBox->setMaximum(to + 1);
213 m_ui.rowSpinBox->setEnabled(to - from > 0);
214}
215
216QString FormLayoutRowDialog::fieldClass() const
217{
218 return m_ui.fieldClassComboBox->itemText(m_ui.fieldClassComboBox->currentIndex());
219}
220
221QString FormLayoutRowDialog::labelText() const
222{
223 return m_ui.labelTextLineEdit->text();
224}
225
226bool FormLayoutRowDialog::isValid() const
227{
228 // Check for non-empty names and presence of buddy marker if checked
229 const QString name = labelText();
230 if (name.isEmpty() || m_ui.labelNameLineEdit->text().isEmpty() || m_ui.fieldNameLineEdit->text().isEmpty())
231 return false;
232 if (buddy() && !name.contains(m_buddyMarkerRegexp))
233 return false;
234 return true;
235}
236
237void FormLayoutRowDialog::updateOkButton()
238{
239 m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(isValid());
240}
241
242void FormLayoutRowDialog::labelTextEdited(const QString &text)
243{
244 updateObjectNames(true, true);
245 // Set buddy if '&' is present unless the user changed it
246 if (!m_buddyClicked)
247 setBuddy(text.contains(m_buddyMarkerRegexp));
248
249 updateOkButton();
250}
251
252// Get a suitable object name postfix from a class name:
253// "namespace::QLineEdit"->"LineEdit"
254static inline QString postFixFromClassName(QString className)
255{
256 const int index = className.lastIndexOf(QLatin1String("::"));
257 if (index != -1)
258 className.remove(0, index + 2);
259 if (className.size() > 2)
260 if (className.at(0) == QLatin1Char('Q') || className.at(0) == QLatin1Char('K'))
261 if (className.at(1).isUpper())
262 className.remove(0, 1);
263 return className;
264}
265
266// Helper routines to filter out characters for converting texts into
267// class name prefixes. Only accepts ASCII characters/digits and underscores.
268
269enum PrefixCharacterKind { PC_Digit, PC_UpperCaseLetter, PC_LowerCaseLetter,
270 PC_Other, PC_Invalid };
271
272static inline PrefixCharacterKind prefixCharacterKind(const QChar &c)
273{
274 switch (c.category()) {
275 case QChar::Number_DecimalDigit:
276 return PC_Digit;
277 case QChar::Letter_Lowercase: {
278 const char a = c.toAscii();
279 if (a >= 'a' && a <= 'z')
280 return PC_LowerCaseLetter;
281 }
282 break;
283 case QChar::Letter_Uppercase: {
284 const char a = c.toAscii();
285 if (a >= 'A' && a <= 'Z')
286 return PC_UpperCaseLetter;
287 }
288 break;
289 case QChar::Punctuation_Connector:
290 if (c.toAscii() == '_')
291 return PC_Other;
292 break;
293 default:
294 break;
295 }
296 return PC_Invalid;
297}
298
299// Convert the text the user types into a usable class name prefix by filtering
300// characters, lower-casing the first character and camel-casing subsequent
301// words. ("zip code:") --> ("zipCode").
302
303static QString prefixFromLabel(const QString &prefix)
304{
305 QString rc;
306 const int length = prefix.size();
307 bool lastWasAcceptable = false;
308 for (int i = 0 ; i < length; i++) {
309 const QChar c = prefix.at(i);
310 const PrefixCharacterKind kind = prefixCharacterKind(c);
311 const bool acceptable = kind != PC_Invalid;
312 if (acceptable) {
313 if (rc.isEmpty()) {
314 // Lower-case first character
315 rc += kind == PC_UpperCaseLetter ? c.toLower() : c;
316 } else {
317 // Camel-case words
318 rc += !lastWasAcceptable && kind == PC_LowerCaseLetter ? c.toUpper() : c;
319 }
320 }
321 lastWasAcceptable = acceptable;
322 }
323 return rc;
324}
325
326void FormLayoutRowDialog::updateObjectNames(bool updateLabel, bool updateField)
327{
328 // Generate label + field object names from the label text, that is,
329 // "&Zip code:" -> "zipcodeLabel", "zipcodeLineEdit" unless the user
330 // edited it.
331 const bool doUpdateLabel = !m_labelNameEdited && updateLabel;
332 const bool doUpdateField = !m_fieldNameEdited && updateField;
333 if (!doUpdateLabel && !doUpdateField)
334 return;
335
336 const QString prefix = prefixFromLabel(labelText());
337 // Set names
338 if (doUpdateLabel)
339 m_ui.labelNameLineEdit->setText(prefix + QLatin1String("Label"));
340 if (doUpdateField)
341 m_ui.fieldNameLineEdit->setText(prefix + postFixFromClassName(fieldClass()));
342}
343
344void FormLayoutRowDialog::fieldClassChanged(int)
345{
346 updateObjectNames(false, true);
347}
348
349void FormLayoutRowDialog::labelNameEdited(const QString & /*text*/)
350{
351 m_labelNameEdited = true; // stop auto-updating after user change
352 updateOkButton();
353}
354
355void FormLayoutRowDialog::fieldNameEdited(const QString & /*text*/)
356{
357 m_fieldNameEdited = true; // stop auto-updating after user change
358 updateOkButton();
359}
360
361void FormLayoutRowDialog::buddyClicked()
362{
363 m_buddyClicked = true; // stop auto-updating after user change
364 updateOkButton();
365}
366
367/* Create a list of classes suitable for field widgets. Take the fixed base
368 * classes provided and look in the widget database for custom widgets derived
369 * from them ("QLineEdit", "CustomLineEdit", "QComboBox"...). */
370QStringList FormLayoutRowDialog::fieldWidgetClasses(QDesignerFormEditorInterface *core)
371{
372 // Base class -> custom widgets map
373 typedef QMultiHash<QString, QString> ClassMap;
374
375 static QStringList rc;
376 if (rc.empty()) {
377 const int fwCount = sizeof(fieldWidgetBaseClasses)/sizeof(const char*);
378 // Turn known base classes into list
379 QStringList baseClasses;
380 for (int i = 0; i < fwCount; i++)
381 baseClasses.push_back(QLatin1String(fieldWidgetBaseClasses[i]));
382 // Scan for custom widgets that inherit them and store them in a
383 // multimap of base class->custom widgets unless we have a language
384 // extension installed which might do funny things with custom widgets.
385 ClassMap customClassMap;
386 if (qt_extension<QDesignerLanguageExtension *>(core->extensionManager(), core) == 0) {
387 const QDesignerWidgetDataBaseInterface *wdb = core->widgetDataBase();
388 const int wdbCount = wdb->count();
389 for (int w = 0; w < wdbCount; ++w) {
390 // Check for non-container custom types that extend the
391 // respective base class.
392 const QDesignerWidgetDataBaseItemInterface *dbItem = wdb->item(w);
393 if (!dbItem->isPromoted() && !dbItem->isContainer() && dbItem->isCustom()) {
394 const int index = baseClasses.indexOf(dbItem->extends());
395 if (index != -1)
396 customClassMap.insert(baseClasses.at(index), dbItem->name());
397 }
398 }
399 }
400 // Compile final list, taking each base class and append custom widgets
401 // based on it.
402 for (int i = 0; i < fwCount; i++) {
403 rc.push_back(baseClasses.at(i));
404 rc += customClassMap.values(baseClasses.at(i));
405 }
406 }
407 return rc;
408}
409
410// ------------------ Utilities
411
412static QFormLayout *managedFormLayout(const QDesignerFormEditorInterface *core, const QWidget *w)
413{
414 QLayout *l = 0;
415 if (LayoutInfo::managedLayoutType(core, w, &l) == LayoutInfo::Form)
416 return qobject_cast<QFormLayout *>(l);
417 return 0;
418}
419
420// Create the widgets of a control row and apply text properties contained
421// in the struct, called by addFormLayoutRow()
422static QPair<QWidget *,QWidget *>
423 createWidgets(const FormLayoutRow &row, QWidget *parent,
424 QDesignerFormWindowInterface *formWindow)
425{
426 QDesignerFormEditorInterface *core = formWindow->core();
427 QDesignerWidgetFactoryInterface *wf = core->widgetFactory();
428
429 QPair<QWidget *,QWidget *> rc = QPair<QWidget *,QWidget *>(wf->createWidget(QLatin1String("QLabel"), parent),
430 wf->createWidget(row.fieldClassName, parent));
431 // Set up properties of the label
432 const QString objectNameProperty = QLatin1String("objectName");
433 QDesignerPropertySheetExtension *labelSheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), rc.first);
434 int nameIndex = labelSheet->indexOf(objectNameProperty);
435 labelSheet->setProperty(nameIndex, qVariantFromValue(PropertySheetStringValue(row.labelName)));
436 labelSheet->setChanged(nameIndex, true);
437 formWindow->ensureUniqueObjectName(rc.first);
438 const int textIndex = labelSheet->indexOf(QLatin1String("text"));
439 labelSheet->setProperty(textIndex, qVariantFromValue(PropertySheetStringValue(row.labelText)));
440 labelSheet->setChanged(textIndex, true);
441 // Set up properties of the control
442 QDesignerPropertySheetExtension *controlSheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), rc.second);
443 nameIndex = controlSheet->indexOf(objectNameProperty);
444 controlSheet->setProperty(nameIndex, qVariantFromValue(PropertySheetStringValue(row.fieldName)));
445 controlSheet->setChanged(nameIndex, true);
446 formWindow->ensureUniqueObjectName(rc.second);
447 return rc;
448}
449
450// Create a command sequence on the undo stack of the form window that creates
451// the widgets of the row and inserts them into the form layout.
452static void addFormLayoutRow(const FormLayoutRow &formLayoutRow, int row, QWidget *w,
453 QDesignerFormWindowInterface *formWindow)
454{
455 QFormLayout *formLayout = managedFormLayout(formWindow->core(), w);
456 Q_ASSERT(formLayout);
457 QUndoStack *undoStack = formWindow->commandHistory();
458 const QString macroName = QCoreApplication::translate("Command", "Add '%1' to '%2'").arg(formLayoutRow.labelText, formLayout->objectName());
459 undoStack->beginMacro(macroName);
460
461 // Create a list of widget insertion commands and pass them a cell position
462 const QPair<QWidget *,QWidget *> widgetPair = createWidgets(formLayoutRow, w, formWindow);
463
464 InsertWidgetCommand *labelCmd = new InsertWidgetCommand(formWindow);
465 labelCmd->init(widgetPair.first, false, row, 0);
466 undoStack->push(labelCmd);
467 InsertWidgetCommand *controlCmd = new InsertWidgetCommand(formWindow);
468 controlCmd->init(widgetPair.second, false, row, 1);
469 undoStack->push(controlCmd);
470 if (formLayoutRow.buddy) {
471 SetPropertyCommand *buddyCommand = new SetPropertyCommand(formWindow);
472 buddyCommand->init(widgetPair.first, QLatin1String(buddyPropertyC), widgetPair.second->objectName());
473 undoStack->push(buddyCommand);
474 }
475 undoStack->endMacro();
476}
477
478// ---------------- FormLayoutMenu
479FormLayoutMenu::FormLayoutMenu(QObject *parent) :
480 QObject(parent),
481 m_separator1(new QAction(this)),
482 m_populateFormAction(new QAction(tr("Add form layout row..."), this)),
483 m_separator2(new QAction(this))
484{
485 m_separator1->setSeparator(true);
486 connect(m_populateFormAction, SIGNAL(triggered()), this, SLOT(slotAddRow()));
487 m_separator2->setSeparator(true);
488}
489
490void FormLayoutMenu::populate(QWidget *w, QDesignerFormWindowInterface *fw, ActionList &actions)
491{
492 switch (LayoutInfo::managedLayoutType(fw->core(), w)) {
493 case LayoutInfo::Form:
494 if (!actions.empty() && !actions.back()->isSeparator())
495 actions.push_back(m_separator1);
496 actions.push_back(m_populateFormAction);
497 actions.push_back(m_separator2);
498 m_widget = w;
499 break;
500 default:
501 m_widget = 0;
502 break;
503 }
504}
505
506void FormLayoutMenu::slotAddRow()
507{
508 QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(m_widget);
509 Q_ASSERT(m_widget && fw);
510 const int rowCount = managedFormLayout(fw->core(), m_widget)->rowCount();
511
512 FormLayoutRowDialog dialog(fw->core(), fw);
513 dialog.setRowRange(0, rowCount);
514 dialog.setRow(rowCount);
515
516 if (dialog.exec() != QDialog::Accepted)
517 return;
518 addFormLayoutRow(dialog.formLayoutRow(), dialog.row(), m_widget, fw);
519}
520
521QAction *FormLayoutMenu::preferredEditAction(QWidget *w, QDesignerFormWindowInterface *fw)
522{
523 if (LayoutInfo::managedLayoutType(fw->core(), w) == LayoutInfo::Form) {
524 m_widget = w;
525 return m_populateFormAction;
526 }
527 return 0;
528}
529}
530
531QT_END_NAMESPACE
532
533#include "formlayoutmenu.moc"
534
Note: See TracBrowser for help on using the repository browser.