Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
widgetboxcategorylistview.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant reason:default
4
6
7#include <QtDesigner/abstractformeditor.h>
8#include <QtDesigner/abstractwidgetdatabase.h>
9
10#include <QtXml/qdom.h>
11
12#include <QtGui/qicon.h>
13#include <QtGui/qvalidator.h>
14#include <QtWidgets/qlistview.h>
15#include <QtWidgets/qlineedit.h>
16#include <QtWidgets/qstyleditemdelegate.h>
17#include <QtCore/qsortfilterproxymodel.h>
18
19#include <QtCore/qabstractitemmodel.h>
20#include <QtCore/qiodevice.h>
21#include <QtCore/qlist.h>
22#include <QtCore/qtextstream.h>
23#include <QtCore/qregularexpression.h>
24
26
27using namespace Qt::StringLiterals;
28
29static constexpr auto widgetElementC = "widget"_L1;
30static constexpr auto nameAttributeC = "name"_L1;
31static constexpr auto uiOpeningTagC = "<ui>"_L1;
32static constexpr auto uiClosingTagC = "</ui>"_L1;
33
34enum { FilterRole = Qt::UserRole + 11 };
35
36static QString domToString(const QDomElement &elt)
37{
38 QString result;
39 QTextStream stream(&result, QIODevice::WriteOnly);
40 elt.save(stream, 2);
41 stream.flush();
42 return result;
43}
44
45static QDomDocument stringToDom(const QString &xml)
46{
47 QDomDocument result;
48 result.setContent(xml);
49 return result;
50}
51
52namespace qdesigner_internal {
53
54// Entry of the model list
55
58 explicit WidgetBoxCategoryEntry(const QDesignerWidgetBoxInterface::Widget &widget,
59 const QString &filter,
60 const QIcon &icon,
61 bool editable);
62
68 bool editable{false};
69};
70
71WidgetBoxCategoryEntry::WidgetBoxCategoryEntry(const QDesignerWidgetBoxInterface::Widget &w,
72 const QString &filterIn,
73 const QIcon &i, bool e) :
74 widget(w),
76 icon(i),
77 editable(e)
78{
79}
80
81/* WidgetBoxCategoryModel, representing a list of category entries. Uses a
82 * QAbstractListModel since the behaviour depends on the view mode of the list
83 * view, it does not return text in the case of IconMode. */
84
86public:
87 explicit WidgetBoxCategoryModel(QDesignerFormEditorInterface *core, QObject *parent = nullptr);
88
89 // QAbstractListModel
90 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
91 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
92 bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) override;
93 Qt::ItemFlags flags (const QModelIndex & index ) const override;
94 bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
95
96 // The model returns no text in icon mode, so, it also needs to know it
98 void setViewMode(QListView::ViewMode vm);
99
100 void addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon, bool editable);
101
102 QDesignerWidgetBoxInterface::Widget widgetAt(const QModelIndex & index) const;
104
105 int indexOfWidget(const QString &name);
106
109
110private:
111 QDesignerFormEditorInterface *m_core;
112 QList<WidgetBoxCategoryEntry> m_items;
113 QListView::ViewMode m_viewMode;
114};
115
116WidgetBoxCategoryModel::WidgetBoxCategoryModel(QDesignerFormEditorInterface *core, QObject *parent) :
118 m_core(core),
120{
121}
122
124{
125 return m_viewMode;
126}
127
128void WidgetBoxCategoryModel::setViewMode(QListView::ViewMode vm)
129{
130 if (m_viewMode == vm)
131 return;
132 const bool empty = m_items.isEmpty();
133 if (!empty)
134 beginResetModel();
135 m_viewMode = vm;
136 if (!empty)
137 endResetModel();
138}
139
140int WidgetBoxCategoryModel::indexOfWidget(const QString &name)
141{
142 for (qsizetype i = 0, count = m_items.size(); i < count; ++i)
143 if (m_items.at(i).widget.name() == name)
144 return i;
145 return -1;
146}
147
149{
150 QDesignerWidgetBoxInterface::Category rc;
151 for (const auto &c : m_items)
152 rc.addWidget(c.widget);
153 return rc;
154}
155
157{
158 // Typically, we are a whole category of custom widgets, so, remove all
159 // and do reset.
160 bool changed = false;
161 for (auto it = m_items.begin(); it != m_items.end(); )
162 if (it->widget.type() == QDesignerWidgetBoxInterface::Widget::Custom) {
163 if (!changed)
164 beginResetModel();
165 it = m_items.erase(it);
166 changed = true;
167 } else {
168 ++it;
169 }
170 if (changed)
171 endResetModel();
172 return changed;
173}
174
175void WidgetBoxCategoryModel::addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon,bool editable)
176{
177 static const QRegularExpression classNameRegExp(QStringLiteral("<widget +class *= *\"([^\"]+)\""));
178 Q_ASSERT(classNameRegExp.isValid());
179 const auto match = classNameRegExp.match(widget.domXml());
180 const QString className = match.hasMatch() ? match.captured(1) : QString{};
181
182 // Filter on name + class name if it is different and not a layout.
183 QString filter = widget.name();
184 if (!className.isEmpty() && !filter.contains("Layout"_L1) && !filter.contains(className))
185 filter += className;
186
187 WidgetBoxCategoryEntry item(widget, filter, icon, editable);
188 const QDesignerWidgetDataBaseInterface *db = m_core->widgetDataBase();
189 int dbIndex = className.isEmpty() ? -1 : db->indexOfClassName(className);
190 if (dbIndex == -1)
191 dbIndex = db->indexOfClassName(widget.name());
192 if (dbIndex != -1) {
193 const QDesignerWidgetDataBaseItemInterface *dbItem = db->item(dbIndex);
194 const QString toolTip = dbItem->toolTip();
195 if (!toolTip.isEmpty())
196 item.toolTip = toolTip;
197 const QString whatsThis = dbItem->whatsThis();
198 if (!whatsThis.isEmpty())
199 item.whatsThis = whatsThis;
200 }
201 // insert
202 const int row = m_items.size();
203 beginInsertRows(QModelIndex(), row, row);
204 m_items.push_back(item);
205 endInsertRows();
206}
207
208QVariant WidgetBoxCategoryModel::data(const QModelIndex &index, int role) const
209{
210 const int row = index.row();
211 if (row < 0 || row >= m_items.size())
212 return QVariant();
213
214 const WidgetBoxCategoryEntry &item = m_items.at(row);
215 switch (role) {
216 case Qt::DisplayRole:
217 // No text in icon mode
218 return QVariant(m_viewMode == QListView::ListMode ? item.widget.name() : QString());
219 case Qt::DecorationRole:
220 return QVariant(item.icon);
221 case Qt::EditRole:
222 return QVariant(item.widget.name());
223 case Qt::ToolTipRole: {
224 if (m_viewMode == QListView::ListMode)
225 return QVariant(item.toolTip);
226 // Icon mode tooltip should contain the class name
227 QString tt = item.widget.name();
228 if (!item.toolTip.isEmpty())
229 tt += u'\n' + item.toolTip;
230 return QVariant(tt);
231
232 }
233 case Qt::WhatsThisRole:
234 return QVariant(item.whatsThis);
235 case FilterRole:
236 return item.filter;
237 }
238 return QVariant();
239}
240
241bool WidgetBoxCategoryModel::setData(const QModelIndex &index, const QVariant &value, int role)
242{
243 const int row = index.row();
244 if (role != Qt::EditRole || row < 0 || row >= m_items.size()
245 || value.metaType().id() != QMetaType::QString) {
246 return false;
247 }
248 // Set name and adapt Xml
249 WidgetBoxCategoryEntry &item = m_items[row];
250 const QString newName = value.toString();
251 item.widget.setName(newName);
252
253 const QDomDocument doc = stringToDom(WidgetBoxCategoryListView::widgetDomXml(item.widget));
254 QDomElement widget_elt = doc.firstChildElement(widgetElementC);
255 if (!widget_elt.isNull()) {
256 widget_elt.setAttribute(nameAttributeC, newName);
257 item.widget.setDomXml(domToString(widget_elt));
258 }
259 emit dataChanged(index, index);
260 return true;
261}
262
263Qt::ItemFlags WidgetBoxCategoryModel::flags(const QModelIndex &index) const
264{
265 Qt::ItemFlags rc = Qt::ItemIsEnabled;
266 const int row = index.row();
267 if (row >= 0 && row < m_items.size())
268 if (m_items.at(row).editable) {
269 rc |= Qt::ItemIsSelectable;
270 // Can change name in list mode only
271 if (m_viewMode == QListView::ListMode)
272 rc |= Qt::ItemIsEditable;
273 }
274 return rc;
275}
276
277int WidgetBoxCategoryModel::rowCount(const QModelIndex & /*parent*/) const
278{
279 return m_items.size();
280}
281
282bool WidgetBoxCategoryModel::removeRows(int row, int count, const QModelIndex & parent)
283{
284 if (row < 0 || count < 1)
285 return false;
286 const int size = m_items.size();
287 const int last = row + count - 1;
288 if (row >= size || last >= size)
289 return false;
290 beginRemoveRows(parent, row, last);
291 for (int r = last; r >= row; r--)
292 m_items.removeAt(r);
293 endRemoveRows();
294 return true;
295}
296
298{
299 return widgetAt(index.row());
300}
301
303{
304 if (row < 0 || row >= m_items.size())
305 return QDesignerWidgetBoxInterface::Widget();
306 return m_items.at(row).widget;
307}
308
309/* WidgetSubBoxItemDelegate, ensures a valid name using a regexp validator */
310
312{
313public:
314 explicit WidgetBoxCategoryEntryDelegate(QWidget *parent = nullptr) : QStyledItemDelegate(parent) {}
315 QWidget *createEditor(QWidget *parent,
316 const QStyleOptionViewItem &option,
317 const QModelIndex &index) const override;
318};
319
321 const QStyleOptionViewItem &option,
322 const QModelIndex &index) const
323{
324 QWidget *result = QStyledItemDelegate::createEditor(parent, option, index);
325 if (QLineEdit *line_edit = qobject_cast<QLineEdit*>(result)) {
326 static const QRegularExpression re(u"^[_a-zA-Z][_a-zA-Z0-9]*$"_s);
327 Q_ASSERT(re.isValid());
328 line_edit->setValidator(new QRegularExpressionValidator(re, line_edit));
329 }
330 return result;
331}
332
333// ---------------------- WidgetBoxCategoryListView
334
335WidgetBoxCategoryListView::WidgetBoxCategoryListView(QDesignerFormEditorInterface *core, QWidget *parent) :
339{
340 setFocusPolicy(Qt::NoFocus);
341 setFrameShape(QFrame::NoFrame);
342 setIconSize(QSize(22, 22));
343 setSpacing(1);
344 setTextElideMode(Qt::ElideMiddle);
345 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
346 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
347 setResizeMode(QListView::Adjust);
348 setUniformItemSizes(true);
349
350 setItemDelegate(new WidgetBoxCategoryEntryDelegate(this));
351
352 connect(this, &QListView::pressed, this,
353 &WidgetBoxCategoryListView::slotPressed);
354 setEditTriggers(QAbstractItemView::AnyKeyPressed);
355
356 m_proxyModel->setSourceModel(m_model);
357 m_proxyModel->setFilterRole(FilterRole);
358 setModel(m_proxyModel);
359 connect(m_model, &QAbstractItemModel::dataChanged,
360 this, &WidgetBoxCategoryListView::scratchPadChanged);
361}
362
364{
365 QListView::setViewMode(vm);
366 m_model->setViewMode(vm);
367}
368
369void WidgetBoxCategoryListView::setCurrentItem(AccessMode am, int row)
370{
371 const QModelIndex index = am == FilteredAccess ?
372 m_proxyModel->index(row, 0) :
373 m_proxyModel->mapFromSource(m_model->index(row, 0));
374
375 if (index.isValid())
376 setCurrentIndex(index);
377}
378
379void WidgetBoxCategoryListView::slotPressed(const QModelIndex &index)
380{
381 const QDesignerWidgetBoxInterface::Widget wgt = m_model->widgetAt(m_proxyModel->mapToSource(index));
382 if (wgt.isNull())
383 return;
384 emit widgetBoxPressed(wgt.name(), widgetDomXml(wgt), QCursor::pos());
385}
386
388{
389 const QModelIndex index = currentIndex();
390 if (!index.isValid() || !m_proxyModel->removeRow(index.row()))
391 return;
392
393 // We check the unfiltered item count here, we don't want to get removed if the
394 // filtered view is empty
395 if (m_model->rowCount()) {
396 emit itemRemoved();
397 } else {
398 emit lastItemRemoved();
399 }
400}
401
403{
404 const QModelIndex index = currentIndex();
405 if (index.isValid())
406 edit(index);
407}
408
409int WidgetBoxCategoryListView::count(AccessMode am) const
410{
411 return am == FilteredAccess ? m_proxyModel->rowCount() : m_model->rowCount();
412}
413
414int WidgetBoxCategoryListView::mapRowToSource(int filterRow) const
415{
416 const QModelIndex filterIndex = m_proxyModel->index(filterRow, 0);
417 return m_proxyModel->mapToSource(filterIndex).row();
418}
419
420QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryListView::widgetAt(AccessMode am, const QModelIndex & index) const
421{
422 const QModelIndex unfilteredIndex = am == FilteredAccess ? m_proxyModel->mapToSource(index) : index;
423 return m_model->widgetAt(unfilteredIndex);
424}
425
427{
428 return m_model->widgetAt(am == UnfilteredAccess ? row : mapRowToSource(row));
429}
430
431void WidgetBoxCategoryListView::removeRow(AccessMode am, int row)
432{
433 m_model->removeRow(am == UnfilteredAccess ? row : mapRowToSource(row));
434}
435
437{
438 return m_model->indexOfWidget(name) != -1;
439}
440
441void WidgetBoxCategoryListView::addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon, bool editable)
442{
443 m_model->addWidget(widget, icon, editable);
444}
445
446QString WidgetBoxCategoryListView::widgetDomXml(const QDesignerWidgetBoxInterface::Widget &widget)
447{
448 QString domXml = widget.domXml();
449
450 if (domXml.isEmpty())
451 domXml = uiOpeningTagC + "<widget class=\""_L1 + widget.name() +"\"/>"_L1 + uiClosingTagC;
452 return domXml;
453}
454
455void WidgetBoxCategoryListView::filter(const QString &needle, Qt::CaseSensitivity caseSensitivity)
456{
457 m_proxyModel->setFilterFixedString(needle);
458 m_proxyModel->setFilterCaseSensitivity(caseSensitivity);
459}
460
462{
463 return m_model->category();
464}
465
470} // namespace qdesigner_internal
471
472QT_END_NAMESPACE
QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override
Returns the widget used to edit the item specified by index for editing.
QDesignerWidgetBoxInterface::Category category() const
QDesignerWidgetBoxInterface::Widget widgetAt(AccessMode am, const QModelIndex &index) const
WidgetBoxCategoryListView(QDesignerFormEditorInterface *core, QWidget *parent=nullptr)
void addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon, bool editable)
QDesignerWidgetBoxInterface::Widget widgetAt(AccessMode am, int row) const
void addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon, bool editable)
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Returns the data stored under the given role for the item referred to by the index.
QDesignerWidgetBoxInterface::Widget widgetAt(const QModelIndex &index) const
Qt::ItemFlags flags(const QModelIndex &index) const override
\reimp
QDesignerWidgetBoxInterface::Category category() const
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Returns the number of rows under the given parent.
WidgetBoxCategoryModel(QDesignerFormEditorInterface *core, QObject *parent=nullptr)
bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
Sets the role data for the item at index to value.
QDesignerWidgetBoxInterface::Widget widgetAt(int row) const
Combined button and popup list for selecting options.
Auxiliary methods to store/retrieve settings.
WidgetBoxCategoryEntry(const QDesignerWidgetBoxInterface::Widget &widget, const QString &filter, const QIcon &icon, bool editable)
static QDomDocument stringToDom(const QString &xml)
static QString domToString(const QDomElement &elt)
static constexpr auto uiClosingTagC
static constexpr auto uiOpeningTagC
static constexpr auto widgetElementC
static constexpr auto nameAttributeC