1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Designer of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "itemlisteditor.h"
30#include <abstractformbuilder.h>
31#include <iconloader_p.h>
32#include <formwindowbase_p.h>
33#include <designerpropertymanager.h>
34
35#include <QtDesigner/abstractformwindow.h>
36
37#include <qttreepropertybrowser.h>
38
39#include <QtWidgets/qsplitter.h>
40#include <QtCore/qcoreapplication.h>
41
42QT_BEGIN_NAMESPACE
43
44namespace qdesigner_internal {
45
46class ItemPropertyBrowser : public QtTreePropertyBrowser
47{
48public:
49 ItemPropertyBrowser()
50 {
51 setResizeMode(Interactive);
52 //: Sample string to determinate the width for the first column of the list item property browser
53 const QString widthSampleString = QCoreApplication::translate(context: "ItemPropertyBrowser", key: "XX Icon Selected off");
54 m_width = fontMetrics().horizontalAdvance(widthSampleString);
55 setSplitterPosition(m_width);
56 m_width += fontMetrics().horizontalAdvance(QStringLiteral("/this/is/some/random/path"));
57 }
58
59 QSize sizeHint() const override
60 {
61 return QSize(m_width, 1);
62 }
63
64private:
65 int m_width;
66};
67
68////////////////// Item editor ///////////////
69AbstractItemEditor::AbstractItemEditor(QDesignerFormWindowInterface *form, QWidget *parent)
70 : QWidget(parent),
71 m_iconCache(qobject_cast<FormWindowBase *>(object: form)->iconCache())
72{
73 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
74 m_propertyManager = new DesignerPropertyManager(form->core(), this);
75 m_editorFactory = new DesignerEditorFactory(form->core(), this);
76 m_editorFactory->setSpacing(0);
77 m_propertyBrowser = new ItemPropertyBrowser;
78 m_propertyBrowser->setFactoryForManager(manager: static_cast<QtVariantPropertyManager *>(m_propertyManager),
79 factory: m_editorFactory);
80
81 connect(sender: m_editorFactory, signal: &DesignerEditorFactory::resetProperty,
82 receiver: this, slot: &AbstractItemEditor::resetProperty);
83 connect(sender: m_propertyManager, signal: &DesignerPropertyManager::valueChanged,
84 receiver: this, slot: &AbstractItemEditor::propertyChanged);
85 connect(sender: iconCache(), signal: &DesignerIconCache::reloaded, receiver: this, slot: &AbstractItemEditor::cacheReloaded);
86}
87
88AbstractItemEditor::~AbstractItemEditor()
89{
90 m_propertyBrowser->unsetFactoryForManager(manager: m_propertyManager);
91}
92
93static const char * const itemFlagNames[] = {
94 QT_TRANSLATE_NOOP("AbstractItemEditor", "Selectable"),
95 QT_TRANSLATE_NOOP("AbstractItemEditor", "Editable"),
96 QT_TRANSLATE_NOOP("AbstractItemEditor", "DragEnabled"),
97 QT_TRANSLATE_NOOP("AbstractItemEditor", "DropEnabled"),
98 QT_TRANSLATE_NOOP("AbstractItemEditor", "UserCheckable"),
99 QT_TRANSLATE_NOOP("AbstractItemEditor", "Enabled"),
100 QT_TRANSLATE_NOOP("AbstractItemEditor", "Tristate"),
101 nullptr
102};
103
104static const char * const checkStateNames[] = {
105 QT_TRANSLATE_NOOP("AbstractItemEditor", "Unchecked"),
106 QT_TRANSLATE_NOOP("AbstractItemEditor", "PartiallyChecked"),
107 QT_TRANSLATE_NOOP("AbstractItemEditor", "Checked"),
108 nullptr
109};
110
111static QStringList c2qStringList(const char * const in[])
112{
113 QStringList out;
114 for (int i = 0; in[i]; i++)
115 out << AbstractItemEditor::tr(s: in[i]);
116 return out;
117}
118
119void AbstractItemEditor::setupProperties(const PropertyDefinition *propList,
120 Qt::Alignment alignDefault)
121{
122 for (int i = 0; propList[i].name; i++) {
123 int type = propList[i].typeFunc ? propList[i].typeFunc() : propList[i].type;
124 int role = propList[i].role;
125 QtVariantProperty *prop = m_propertyManager->addProperty(propertyType: type, name: QLatin1String(propList[i].name));
126 if (role == Qt::TextAlignmentRole) {
127 prop->setAttribute(attribute: DesignerPropertyManager::alignDefaultAttribute(),
128 value: QVariant(uint(alignDefault)));
129 }
130 Q_ASSERT(prop);
131 if (role == Qt::ToolTipPropertyRole || role == Qt::WhatsThisPropertyRole)
132 prop->setAttribute(QStringLiteral("validationMode"), value: ValidationRichText);
133 else if (role == Qt::DisplayPropertyRole)
134 prop->setAttribute(QStringLiteral("validationMode"), value: ValidationMultiLine);
135 else if (role == Qt::StatusTipPropertyRole)
136 prop->setAttribute(QStringLiteral("validationMode"), value: ValidationSingleLine);
137 else if (role == ItemFlagsShadowRole)
138 prop->setAttribute(QStringLiteral("flagNames"), value: c2qStringList(in: itemFlagNames));
139 else if (role == Qt::CheckStateRole)
140 prop->setAttribute(QStringLiteral("enumNames"), value: c2qStringList(in: checkStateNames));
141 prop->setAttribute(QStringLiteral("resettable"), value: true);
142 m_properties.append(t: prop);
143 m_rootProperties.append(t: prop);
144 m_propertyToRole.insert(akey: prop, avalue: role);
145 }
146}
147
148void AbstractItemEditor::setupObject(QWidget *object)
149{
150 m_propertyManager->setObject(object);
151 QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(w: object);
152 FormWindowBase *fwb = qobject_cast<FormWindowBase *>(object: formWindow);
153 m_editorFactory->setFormWindowBase(fwb);
154}
155
156void AbstractItemEditor::setupEditor(QWidget *object,
157 const PropertyDefinition *propList,
158 Qt::Alignment alignDefault)
159{
160 setupProperties(propList, alignDefault);
161 setupObject(object);
162}
163
164void AbstractItemEditor::propertyChanged(QtProperty *property)
165{
166 if (m_updatingBrowser)
167 return;
168
169
170 BoolBlocker block(m_updatingBrowser);
171 QtVariantProperty *prop = m_propertyManager->variantProperty(property);
172 int role;
173 if ((role = m_propertyToRole.value(akey: prop, adefaultValue: -1)) == -1)
174 // Subproperty
175 return;
176
177 if ((role == ItemFlagsShadowRole && prop->value().toInt() == defaultItemFlags())
178 || (role == Qt::DecorationPropertyRole && !qvariant_cast<PropertySheetIconValue>(v: prop->value()).mask())
179 || (role == Qt::FontRole && !qvariant_cast<QFont>(v: prop->value()).resolve())) {
180 prop->setModified(false);
181 setItemData(role, v: QVariant());
182 } else {
183 prop->setModified(true);
184 setItemData(role, v: prop->value());
185 }
186
187 switch (role) {
188 case Qt::DecorationPropertyRole:
189 setItemData(role: Qt::DecorationRole, v: QVariant::fromValue(value: iconCache()->icon(value: qvariant_cast<PropertySheetIconValue>(v: prop->value()))));
190 break;
191 case Qt::DisplayPropertyRole:
192 setItemData(role: Qt::EditRole, v: QVariant::fromValue(value: qvariant_cast<PropertySheetStringValue>(v: prop->value()).value()));
193 break;
194 case Qt::ToolTipPropertyRole:
195 setItemData(role: Qt::ToolTipRole, v: QVariant::fromValue(value: qvariant_cast<PropertySheetStringValue>(v: prop->value()).value()));
196 break;
197 case Qt::StatusTipPropertyRole:
198 setItemData(role: Qt::StatusTipRole, v: QVariant::fromValue(value: qvariant_cast<PropertySheetStringValue>(v: prop->value()).value()));
199 break;
200 case Qt::WhatsThisPropertyRole:
201 setItemData(role: Qt::WhatsThisRole, v: QVariant::fromValue(value: qvariant_cast<PropertySheetStringValue>(v: prop->value()).value()));
202 break;
203 default:
204 break;
205 }
206
207 prop->setValue(getItemData(role));
208}
209
210void AbstractItemEditor::resetProperty(QtProperty *property)
211{
212 if (m_propertyManager->resetFontSubProperty(property))
213 return;
214
215 if (m_propertyManager->resetIconSubProperty(subProperty: property))
216 return;
217
218 if (m_propertyManager->resetTextAlignmentProperty(property))
219 return;
220
221 BoolBlocker block(m_updatingBrowser);
222
223 QtVariantProperty *prop = m_propertyManager->variantProperty(property);
224 int role = m_propertyToRole.value(akey: prop);
225 if (role == ItemFlagsShadowRole)
226 prop->setValue(QVariant::fromValue(value: defaultItemFlags()));
227 else
228 prop->setValue(QVariant(prop->valueType(), nullptr));
229 prop->setModified(false);
230
231 setItemData(role, v: QVariant());
232 if (role == Qt::DecorationPropertyRole)
233 setItemData(role: Qt::DecorationRole, v: QVariant::fromValue(value: QIcon()));
234 if (role == Qt::DisplayPropertyRole)
235 setItemData(role: Qt::EditRole, v: QVariant::fromValue(value: QString()));
236 if (role == Qt::ToolTipPropertyRole)
237 setItemData(role: Qt::ToolTipRole, v: QVariant::fromValue(value: QString()));
238 if (role == Qt::StatusTipPropertyRole)
239 setItemData(role: Qt::StatusTipRole, v: QVariant::fromValue(value: QString()));
240 if (role == Qt::WhatsThisPropertyRole)
241 setItemData(role: Qt::WhatsThisRole, v: QVariant::fromValue(value: QString()));
242}
243
244void AbstractItemEditor::cacheReloaded()
245{
246 BoolBlocker block(m_updatingBrowser);
247 m_propertyManager->reloadResourceProperties();
248}
249
250void AbstractItemEditor::updateBrowser()
251{
252 BoolBlocker block(m_updatingBrowser);
253 for (QtVariantProperty *prop : qAsConst(t&: m_properties)) {
254 int role = m_propertyToRole.value(akey: prop);
255 QVariant val = getItemData(role);
256
257 bool modified = false;
258 if (!val.isValid()) {
259 if (role == ItemFlagsShadowRole)
260 val = QVariant::fromValue(value: defaultItemFlags());
261 else
262 val = QVariant(int(prop->value().userType()), nullptr);
263 } else {
264 modified = role != Qt::TextAlignmentRole
265 || val.toUInt() != DesignerPropertyManager::alignDefault(prop);
266 }
267 prop->setModified(modified);
268 prop->setValue(val);
269 }
270
271 if (m_propertyBrowser->topLevelItems().isEmpty()) {
272 for (QtVariantProperty *prop : qAsConst(t&: m_rootProperties))
273 m_propertyBrowser->addProperty(property: prop);
274 }
275}
276
277void AbstractItemEditor::injectPropertyBrowser(QWidget *parent, QWidget *widget)
278{
279 // It is impossible to design a splitter with just one widget, so we do it by hand.
280 m_propertySplitter = new QSplitter;
281 m_propertySplitter->addWidget(widget);
282 m_propertySplitter->addWidget(widget: m_propertyBrowser);
283 m_propertySplitter->setStretchFactor(index: 0, stretch: 1);
284 m_propertySplitter->setStretchFactor(index: 1, stretch: 0);
285 parent->layout()->addWidget(w: m_propertySplitter);
286}
287
288////////////////// List editor ///////////////
289ItemListEditor::ItemListEditor(QDesignerFormWindowInterface *form, QWidget *parent)
290 : AbstractItemEditor(form, parent),
291 m_updating(false)
292{
293 ui.setupUi(this);
294
295 injectPropertyBrowser(parent: this, widget: ui.widget);
296 connect(sender: ui.showPropertiesButton, signal: &QAbstractButton::clicked,
297 receiver: this, slot: &ItemListEditor::togglePropertyBrowser);
298 setPropertyBrowserVisible(false);
299
300 QIcon upIcon = createIconSet(name: QString::fromUtf8(str: "up.png"));
301 QIcon downIcon = createIconSet(name: QString::fromUtf8(str: "down.png"));
302 QIcon minusIcon = createIconSet(name: QString::fromUtf8(str: "minus.png"));
303 QIcon plusIcon = createIconSet(name: QString::fromUtf8(str: "plus.png"));
304 ui.moveListItemUpButton->setIcon(upIcon);
305 ui.moveListItemDownButton->setIcon(downIcon);
306 ui.newListItemButton->setIcon(plusIcon);
307 ui.deleteListItemButton->setIcon(minusIcon);
308
309 connect(sender: iconCache(), signal: &DesignerIconCache::reloaded, receiver: this, slot: &AbstractItemEditor::cacheReloaded);
310}
311
312void ItemListEditor::setupEditor(QWidget *object,
313 const PropertyDefinition *propList,
314 Qt::Alignment alignDefault)
315{
316 AbstractItemEditor::setupEditor(object, propList, alignDefault);
317
318 if (ui.listWidget->count() > 0)
319 ui.listWidget->setCurrentRow(0);
320 else
321 updateEditor();
322}
323
324void ItemListEditor::setCurrentIndex(int idx)
325{
326 m_updating = true;
327 ui.listWidget->setCurrentRow(idx);
328 m_updating = false;
329}
330
331void ItemListEditor::on_newListItemButton_clicked()
332{
333 int row = ui.listWidget->currentRow() + 1;
334
335 QListWidgetItem *item = new QListWidgetItem(m_newItemText);
336 item->setData(role: Qt::DisplayPropertyRole, value: QVariant::fromValue(value: PropertySheetStringValue(m_newItemText)));
337 if (m_alignDefault != 0)
338 item->setTextAlignment(Qt::Alignment(m_alignDefault));
339 item->setFlags(item->flags() | Qt::ItemIsEditable);
340 if (row < ui.listWidget->count())
341 ui.listWidget->insertItem(row, item);
342 else
343 ui.listWidget->addItem(aitem: item);
344 emit itemInserted(idx: row);
345
346 ui.listWidget->setCurrentItem(item);
347 ui.listWidget->editItem(item);
348}
349
350void ItemListEditor::on_deleteListItemButton_clicked()
351{
352 int row = ui.listWidget->currentRow();
353
354 if (row != -1) {
355 delete ui.listWidget->takeItem(row);
356 emit itemDeleted(idx: row);
357 }
358
359 if (row == ui.listWidget->count())
360 row--;
361 if (row < 0)
362 updateEditor();
363 else
364 ui.listWidget->setCurrentRow(row);
365}
366
367void ItemListEditor::on_moveListItemUpButton_clicked()
368{
369 int row = ui.listWidget->currentRow();
370 if (row <= 0)
371 return; // nothing to do
372
373 ui.listWidget->insertItem(row: row - 1, item: ui.listWidget->takeItem(row));
374 ui.listWidget->setCurrentRow(row - 1);
375 emit itemMovedUp(idx: row);
376}
377
378void ItemListEditor::on_moveListItemDownButton_clicked()
379{
380 int row = ui.listWidget->currentRow();
381 if (row == -1 || row == ui.listWidget->count() - 1)
382 return; // nothing to do
383
384 ui.listWidget->insertItem(row: row + 1, item: ui.listWidget->takeItem(row));
385 ui.listWidget->setCurrentRow(row + 1);
386 emit itemMovedDown(idx: row);
387}
388
389void ItemListEditor::on_listWidget_currentRowChanged()
390{
391 updateEditor();
392 if (!m_updating)
393 emit indexChanged(idx: ui.listWidget->currentRow());
394}
395
396void ItemListEditor::on_listWidget_itemChanged(QListWidgetItem *item)
397{
398 if (m_updatingBrowser)
399 return;
400
401 PropertySheetStringValue val = qvariant_cast<PropertySheetStringValue>(v: item->data(role: Qt::DisplayPropertyRole));
402 val.setValue(item->text());
403 BoolBlocker block(m_updatingBrowser);
404 item->setData(role: Qt::DisplayPropertyRole, value: QVariant::fromValue(value: val));
405
406 // The checkState could change, too, but if this signal is connected,
407 // checkState is not in the list anyway, as we are editing a header item.
408 emit itemChanged(idx: ui.listWidget->currentRow(), role: Qt::DisplayPropertyRole,
409 v: QVariant::fromValue(value: val));
410 updateBrowser();
411}
412
413void ItemListEditor::togglePropertyBrowser()
414{
415 setPropertyBrowserVisible(!m_propertyBrowser->isVisible());
416}
417
418void ItemListEditor::setPropertyBrowserVisible(bool v)
419{
420 ui.showPropertiesButton->setText(v ? tr(s: "Properties &>>") : tr(s: "Properties &<<"));
421 m_propertyBrowser->setVisible(v);
422}
423
424void ItemListEditor::setItemData(int role, const QVariant &v)
425{
426 QListWidgetItem *item = ui.listWidget->currentItem();
427 bool reLayout = false;
428 if ((role == Qt::EditRole && (v.toString().count(c: QLatin1Char('\n')) != item->data(role).toString().count(c: QLatin1Char('\n'))))
429 || role == Qt::FontRole)
430 reLayout = true;
431 QVariant newValue = v;
432 if (role == Qt::FontRole && newValue.type() == QVariant::Font) {
433 QFont oldFont = ui.listWidget->font();
434 QFont newFont = qvariant_cast<QFont>(v: newValue).resolve(oldFont);
435 newValue = QVariant::fromValue(value: newFont);
436 item->setData(role, value: QVariant()); // force the right font with the current resolve mask is set (item view bug)
437 }
438 item->setData(role, value: newValue);
439 if (reLayout)
440 ui.listWidget->doItemsLayout();
441 emit itemChanged(idx: ui.listWidget->currentRow(), role, v: newValue);
442}
443
444QVariant ItemListEditor::getItemData(int role) const
445{
446 return ui.listWidget->currentItem()->data(role);
447}
448
449int ItemListEditor::defaultItemFlags() const
450{
451 static const int flags = QListWidgetItem().flags();
452 return flags;
453}
454
455void ItemListEditor::cacheReloaded()
456{
457 reloadIconResources(iconCache: iconCache(), object: ui.listWidget);
458}
459
460void ItemListEditor::updateEditor()
461{
462 bool currentItemEnabled = false;
463
464 bool moveRowUpEnabled = false;
465 bool moveRowDownEnabled = false;
466
467 QListWidgetItem *item = ui.listWidget->currentItem();
468 if (item) {
469 currentItemEnabled = true;
470 int currentRow = ui.listWidget->currentRow();
471 if (currentRow > 0)
472 moveRowUpEnabled = true;
473 if (currentRow < ui.listWidget->count() - 1)
474 moveRowDownEnabled = true;
475 }
476
477 ui.moveListItemUpButton->setEnabled(moveRowUpEnabled);
478 ui.moveListItemDownButton->setEnabled(moveRowDownEnabled);
479 ui.deleteListItemButton->setEnabled(currentItemEnabled);
480
481 if (item)
482 updateBrowser();
483 else
484 m_propertyBrowser->clear();
485}
486
487uint ItemListEditor::alignDefault() const
488{
489 return m_alignDefault;
490}
491
492void ItemListEditor::setAlignDefault(uint newAlignDefault)
493{
494 m_alignDefault = newAlignDefault;
495}
496} // namespace qdesigner_internal
497
498QT_END_NAMESPACE
499

source code of qttools/src/designer/src/components/taskmenu/itemlisteditor.cpp