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