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
42 QT_BEGIN_NAMESPACE
43
44 namespace qdesigner_internal {
45
46 class ItemPropertyBrowser : public QtTreePropertyBrowser
47 {
48 public:
ItemPropertyBrowser()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("ItemPropertyBrowser", "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
sizeHint() const59 QSize sizeHint() const override
60 {
61 return QSize(m_width, 1);
62 }
63
64 private:
65 int m_width;
66 };
67
68 ////////////////// Item editor ///////////////
AbstractItemEditor(QDesignerFormWindowInterface * form,QWidget * parent)69 AbstractItemEditor::AbstractItemEditor(QDesignerFormWindowInterface *form, QWidget *parent)
70 : QWidget(parent),
71 m_iconCache(qobject_cast<FormWindowBase *>(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(static_cast<QtVariantPropertyManager *>(m_propertyManager),
79 m_editorFactory);
80
81 connect(m_editorFactory, &DesignerEditorFactory::resetProperty,
82 this, &AbstractItemEditor::resetProperty);
83 connect(m_propertyManager, &DesignerPropertyManager::valueChanged,
84 this, &AbstractItemEditor::propertyChanged);
85 connect(iconCache(), &DesignerIconCache::reloaded, this, &AbstractItemEditor::cacheReloaded);
86 }
87
~AbstractItemEditor()88 AbstractItemEditor::~AbstractItemEditor()
89 {
90 m_propertyBrowser->unsetFactoryForManager(m_propertyManager);
91 }
92
93 static 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
104 static 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
c2qStringList(const char * const in[])111 static QStringList c2qStringList(const char * const in[])
112 {
113 QStringList out;
114 for (int i = 0; in[i]; i++)
115 out << AbstractItemEditor::tr(in[i]);
116 return out;
117 }
118
setupProperties(PropertyDefinition * propList)119 void AbstractItemEditor::setupProperties(PropertyDefinition *propList)
120 {
121 for (int i = 0; propList[i].name; i++) {
122 int type = propList[i].typeFunc ? propList[i].typeFunc() : propList[i].type;
123 int role = propList[i].role;
124 QtVariantProperty *prop = m_propertyManager->addProperty(type, QLatin1String(propList[i].name));
125 Q_ASSERT(prop);
126 if (role == Qt::ToolTipPropertyRole || role == Qt::WhatsThisPropertyRole)
127 prop->setAttribute(QStringLiteral("validationMode"), ValidationRichText);
128 else if (role == Qt::DisplayPropertyRole)
129 prop->setAttribute(QStringLiteral("validationMode"), ValidationMultiLine);
130 else if (role == Qt::StatusTipPropertyRole)
131 prop->setAttribute(QStringLiteral("validationMode"), ValidationSingleLine);
132 else if (role == ItemFlagsShadowRole)
133 prop->setAttribute(QStringLiteral("flagNames"), c2qStringList(itemFlagNames));
134 else if (role == Qt::CheckStateRole)
135 prop->setAttribute(QStringLiteral("enumNames"), c2qStringList(checkStateNames));
136 prop->setAttribute(QStringLiteral("resettable"), true);
137 m_properties.append(prop);
138 m_rootProperties.append(prop);
139 m_propertyToRole.insert(prop, role);
140 }
141 }
142
setupObject(QWidget * object)143 void AbstractItemEditor::setupObject(QWidget *object)
144 {
145 m_propertyManager->setObject(object);
146 QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(object);
147 FormWindowBase *fwb = qobject_cast<FormWindowBase *>(formWindow);
148 m_editorFactory->setFormWindowBase(fwb);
149 }
150
setupEditor(QWidget * object,PropertyDefinition * propList)151 void AbstractItemEditor::setupEditor(QWidget *object, PropertyDefinition *propList)
152 {
153 setupProperties(propList);
154 setupObject(object);
155 }
156
propertyChanged(QtProperty * property)157 void AbstractItemEditor::propertyChanged(QtProperty *property)
158 {
159 if (m_updatingBrowser)
160 return;
161
162
163 BoolBlocker block(m_updatingBrowser);
164 QtVariantProperty *prop = m_propertyManager->variantProperty(property);
165 int role;
166 if ((role = m_propertyToRole.value(prop, -1)) == -1)
167 // Subproperty
168 return;
169
170 if ((role == ItemFlagsShadowRole && prop->value().toInt() == defaultItemFlags())
171 || (role == Qt::DecorationPropertyRole && !qvariant_cast<PropertySheetIconValue>(prop->value()).mask())
172 || (role == Qt::FontRole && !qvariant_cast<QFont>(prop->value()).resolve())) {
173 prop->setModified(false);
174 setItemData(role, QVariant());
175 } else {
176 prop->setModified(true);
177 setItemData(role, prop->value());
178 }
179
180 switch (role) {
181 case Qt::DecorationPropertyRole:
182 setItemData(Qt::DecorationRole, QVariant::fromValue(iconCache()->icon(qvariant_cast<PropertySheetIconValue>(prop->value()))));
183 break;
184 case Qt::DisplayPropertyRole:
185 setItemData(Qt::EditRole, QVariant::fromValue(qvariant_cast<PropertySheetStringValue>(prop->value()).value()));
186 break;
187 case Qt::ToolTipPropertyRole:
188 setItemData(Qt::ToolTipRole, QVariant::fromValue(qvariant_cast<PropertySheetStringValue>(prop->value()).value()));
189 break;
190 case Qt::StatusTipPropertyRole:
191 setItemData(Qt::StatusTipRole, QVariant::fromValue(qvariant_cast<PropertySheetStringValue>(prop->value()).value()));
192 break;
193 case Qt::WhatsThisPropertyRole:
194 setItemData(Qt::WhatsThisRole, QVariant::fromValue(qvariant_cast<PropertySheetStringValue>(prop->value()).value()));
195 break;
196 default:
197 break;
198 }
199
200 prop->setValue(getItemData(role));
201 }
202
resetProperty(QtProperty * property)203 void AbstractItemEditor::resetProperty(QtProperty *property)
204 {
205 if (m_propertyManager->resetFontSubProperty(property))
206 return;
207
208 if (m_propertyManager->resetIconSubProperty(property))
209 return;
210
211 BoolBlocker block(m_updatingBrowser);
212
213 QtVariantProperty *prop = m_propertyManager->variantProperty(property);
214 int role = m_propertyToRole.value(prop);
215 if (role == ItemFlagsShadowRole)
216 prop->setValue(QVariant::fromValue(defaultItemFlags()));
217 else
218 prop->setValue(QVariant(prop->valueType(), nullptr));
219 prop->setModified(false);
220
221 setItemData(role, QVariant());
222 if (role == Qt::DecorationPropertyRole)
223 setItemData(Qt::DecorationRole, QVariant::fromValue(QIcon()));
224 if (role == Qt::DisplayPropertyRole)
225 setItemData(Qt::EditRole, QVariant::fromValue(QString()));
226 if (role == Qt::ToolTipPropertyRole)
227 setItemData(Qt::ToolTipRole, QVariant::fromValue(QString()));
228 if (role == Qt::StatusTipPropertyRole)
229 setItemData(Qt::StatusTipRole, QVariant::fromValue(QString()));
230 if (role == Qt::WhatsThisPropertyRole)
231 setItemData(Qt::WhatsThisRole, QVariant::fromValue(QString()));
232 }
233
cacheReloaded()234 void AbstractItemEditor::cacheReloaded()
235 {
236 BoolBlocker block(m_updatingBrowser);
237 m_propertyManager->reloadResourceProperties();
238 }
239
updateBrowser()240 void AbstractItemEditor::updateBrowser()
241 {
242 BoolBlocker block(m_updatingBrowser);
243 for (QtVariantProperty *prop : qAsConst(m_properties)) {
244 int role = m_propertyToRole.value(prop);
245 QVariant val = getItemData(role);
246 if (!val.isValid()) {
247 if (role == ItemFlagsShadowRole)
248 val = QVariant::fromValue(defaultItemFlags());
249 else
250 val = QVariant(int(prop->value().userType()), nullptr);
251 prop->setModified(false);
252 } else {
253 prop->setModified(true);
254 }
255 prop->setValue(val);
256 }
257
258 if (m_propertyBrowser->topLevelItems().isEmpty()) {
259 for (QtVariantProperty *prop : qAsConst(m_rootProperties))
260 m_propertyBrowser->addProperty(prop);
261 }
262 }
263
injectPropertyBrowser(QWidget * parent,QWidget * widget)264 void AbstractItemEditor::injectPropertyBrowser(QWidget *parent, QWidget *widget)
265 {
266 // It is impossible to design a splitter with just one widget, so we do it by hand.
267 m_propertySplitter = new QSplitter;
268 m_propertySplitter->addWidget(widget);
269 m_propertySplitter->addWidget(m_propertyBrowser);
270 m_propertySplitter->setStretchFactor(0, 1);
271 m_propertySplitter->setStretchFactor(1, 0);
272 parent->layout()->addWidget(m_propertySplitter);
273 }
274
275 ////////////////// List editor ///////////////
ItemListEditor(QDesignerFormWindowInterface * form,QWidget * parent)276 ItemListEditor::ItemListEditor(QDesignerFormWindowInterface *form, QWidget *parent)
277 : AbstractItemEditor(form, parent),
278 m_updating(false)
279 {
280 ui.setupUi(this);
281
282 injectPropertyBrowser(this, ui.widget);
283 connect(ui.showPropertiesButton, &QAbstractButton::clicked,
284 this, &ItemListEditor::togglePropertyBrowser);
285 setPropertyBrowserVisible(false);
286
287 QIcon upIcon = createIconSet(QString::fromUtf8("up.png"));
288 QIcon downIcon = createIconSet(QString::fromUtf8("down.png"));
289 QIcon minusIcon = createIconSet(QString::fromUtf8("minus.png"));
290 QIcon plusIcon = createIconSet(QString::fromUtf8("plus.png"));
291 ui.moveListItemUpButton->setIcon(upIcon);
292 ui.moveListItemDownButton->setIcon(downIcon);
293 ui.newListItemButton->setIcon(plusIcon);
294 ui.deleteListItemButton->setIcon(minusIcon);
295
296 connect(iconCache(), &DesignerIconCache::reloaded, this, &AbstractItemEditor::cacheReloaded);
297 }
298
setupEditor(QWidget * object,PropertyDefinition * propList)299 void ItemListEditor::setupEditor(QWidget *object, PropertyDefinition *propList)
300 {
301 AbstractItemEditor::setupEditor(object, propList);
302
303 if (ui.listWidget->count() > 0)
304 ui.listWidget->setCurrentRow(0);
305 else
306 updateEditor();
307 }
308
setCurrentIndex(int idx)309 void ItemListEditor::setCurrentIndex(int idx)
310 {
311 m_updating = true;
312 ui.listWidget->setCurrentRow(idx);
313 m_updating = false;
314 }
315
on_newListItemButton_clicked()316 void ItemListEditor::on_newListItemButton_clicked()
317 {
318 int row = ui.listWidget->currentRow() + 1;
319
320 QListWidgetItem *item = new QListWidgetItem(m_newItemText);
321 item->setData(Qt::DisplayPropertyRole, QVariant::fromValue(PropertySheetStringValue(m_newItemText)));
322 item->setFlags(item->flags() | Qt::ItemIsEditable);
323 if (row < ui.listWidget->count())
324 ui.listWidget->insertItem(row, item);
325 else
326 ui.listWidget->addItem(item);
327 emit itemInserted(row);
328
329 ui.listWidget->setCurrentItem(item);
330 ui.listWidget->editItem(item);
331 }
332
on_deleteListItemButton_clicked()333 void ItemListEditor::on_deleteListItemButton_clicked()
334 {
335 int row = ui.listWidget->currentRow();
336
337 if (row != -1) {
338 delete ui.listWidget->takeItem(row);
339 emit itemDeleted(row);
340 }
341
342 if (row == ui.listWidget->count())
343 row--;
344 if (row < 0)
345 updateEditor();
346 else
347 ui.listWidget->setCurrentRow(row);
348 }
349
on_moveListItemUpButton_clicked()350 void ItemListEditor::on_moveListItemUpButton_clicked()
351 {
352 int row = ui.listWidget->currentRow();
353 if (row <= 0)
354 return; // nothing to do
355
356 ui.listWidget->insertItem(row - 1, ui.listWidget->takeItem(row));
357 ui.listWidget->setCurrentRow(row - 1);
358 emit itemMovedUp(row);
359 }
360
on_moveListItemDownButton_clicked()361 void ItemListEditor::on_moveListItemDownButton_clicked()
362 {
363 int row = ui.listWidget->currentRow();
364 if (row == -1 || row == ui.listWidget->count() - 1)
365 return; // nothing to do
366
367 ui.listWidget->insertItem(row + 1, ui.listWidget->takeItem(row));
368 ui.listWidget->setCurrentRow(row + 1);
369 emit itemMovedDown(row);
370 }
371
on_listWidget_currentRowChanged()372 void ItemListEditor::on_listWidget_currentRowChanged()
373 {
374 updateEditor();
375 if (!m_updating)
376 emit indexChanged(ui.listWidget->currentRow());
377 }
378
on_listWidget_itemChanged(QListWidgetItem * item)379 void ItemListEditor::on_listWidget_itemChanged(QListWidgetItem *item)
380 {
381 if (m_updatingBrowser)
382 return;
383
384 PropertySheetStringValue val = qvariant_cast<PropertySheetStringValue>(item->data(Qt::DisplayPropertyRole));
385 val.setValue(item->text());
386 BoolBlocker block(m_updatingBrowser);
387 item->setData(Qt::DisplayPropertyRole, QVariant::fromValue(val));
388
389 // The checkState could change, too, but if this signal is connected,
390 // checkState is not in the list anyway, as we are editing a header item.
391 emit itemChanged(ui.listWidget->currentRow(), Qt::DisplayPropertyRole,
392 QVariant::fromValue(val));
393 updateBrowser();
394 }
395
togglePropertyBrowser()396 void ItemListEditor::togglePropertyBrowser()
397 {
398 setPropertyBrowserVisible(!m_propertyBrowser->isVisible());
399 }
400
setPropertyBrowserVisible(bool v)401 void ItemListEditor::setPropertyBrowserVisible(bool v)
402 {
403 ui.showPropertiesButton->setText(v ? tr("Properties &>>") : tr("Properties &<<"));
404 m_propertyBrowser->setVisible(v);
405 }
406
setItemData(int role,const QVariant & v)407 void ItemListEditor::setItemData(int role, const QVariant &v)
408 {
409 QListWidgetItem *item = ui.listWidget->currentItem();
410 bool reLayout = false;
411 if ((role == Qt::EditRole && (v.toString().count(QLatin1Char('\n')) != item->data(role).toString().count(QLatin1Char('\n'))))
412 || role == Qt::FontRole)
413 reLayout = true;
414 QVariant newValue = v;
415 if (role == Qt::FontRole && newValue.type() == QVariant::Font) {
416 QFont oldFont = ui.listWidget->font();
417 QFont newFont = qvariant_cast<QFont>(newValue).resolve(oldFont);
418 newValue = QVariant::fromValue(newFont);
419 item->setData(role, QVariant()); // force the right font with the current resolve mask is set (item view bug)
420 }
421 item->setData(role, newValue);
422 if (reLayout)
423 ui.listWidget->doItemsLayout();
424 emit itemChanged(ui.listWidget->currentRow(), role, newValue);
425 }
426
getItemData(int role) const427 QVariant ItemListEditor::getItemData(int role) const
428 {
429 return ui.listWidget->currentItem()->data(role);
430 }
431
defaultItemFlags() const432 int ItemListEditor::defaultItemFlags() const
433 {
434 static const int flags = QListWidgetItem().flags();
435 return flags;
436 }
437
cacheReloaded()438 void ItemListEditor::cacheReloaded()
439 {
440 reloadIconResources(iconCache(), ui.listWidget);
441 }
442
updateEditor()443 void ItemListEditor::updateEditor()
444 {
445 bool currentItemEnabled = false;
446
447 bool moveRowUpEnabled = false;
448 bool moveRowDownEnabled = false;
449
450 QListWidgetItem *item = ui.listWidget->currentItem();
451 if (item) {
452 currentItemEnabled = true;
453 int currentRow = ui.listWidget->currentRow();
454 if (currentRow > 0)
455 moveRowUpEnabled = true;
456 if (currentRow < ui.listWidget->count() - 1)
457 moveRowDownEnabled = true;
458 }
459
460 ui.moveListItemUpButton->setEnabled(moveRowUpEnabled);
461 ui.moveListItemDownButton->setEnabled(moveRowDownEnabled);
462 ui.deleteListItemButton->setEnabled(currentItemEnabled);
463
464 if (item)
465 updateBrowser();
466 else
467 m_propertyBrowser->clear();
468 }
469 } // namespace qdesigner_internal
470
471 QT_END_NAMESPACE
472