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