1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Designer of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
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 http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file. Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "widgetboxcategorylistview.h"
43
44 #include <QtDesigner/QDesignerFormEditorInterface>
45 #include <QtDesigner/QDesignerWidgetDataBaseInterface>
46
47 #include <QtXml/QDomDocument>
48
49 #include <QtGui/QIcon>
50 #include <QtGui/QListView>
51 #include <QtGui/QLineEdit>
52 #include <QtGui/QItemDelegate>
53 #include <QtGui/QSortFilterProxyModel>
54
55 #include <QtCore/QAbstractListModel>
56 #include <QtCore/QList>
57 #include <QtCore/QTextStream>
58 #include <QtCore/QRegExp>
59
60 static const char *widgetElementC = "widget";
61 static const char *nameAttributeC = "name";
62 static const char *uiOpeningTagC = "<ui>";
63 static const char *uiClosingTagC = "</ui>";
64
65 QT_BEGIN_NAMESPACE
66
67 enum { FilterRole = Qt::UserRole + 11 };
68
domToString(const QDomElement & elt)69 static QString domToString(const QDomElement &elt)
70 {
71 QString result;
72 QTextStream stream(&result, QIODevice::WriteOnly);
73 elt.save(stream, 2);
74 stream.flush();
75 return result;
76 }
77
stringToDom(const QString & xml)78 static QDomDocument stringToDom(const QString &xml)
79 {
80 QDomDocument result;
81 result.setContent(xml);
82 return result;
83 }
84
85 namespace qdesigner_internal {
86
87 // Entry of the model list
88
89 struct WidgetBoxCategoryEntry {
90 WidgetBoxCategoryEntry();
91 explicit WidgetBoxCategoryEntry(const QDesignerWidgetBoxInterface::Widget &widget,
92 const QString &filter,
93 const QIcon &icon,
94 bool editable);
95
96 QDesignerWidgetBoxInterface::Widget widget;
97 QString toolTip;
98 QString whatsThis;
99 QString filter;
100 QIcon icon;
101 bool editable;
102 };
103
104
WidgetBoxCategoryEntry()105 WidgetBoxCategoryEntry::WidgetBoxCategoryEntry() :
106 editable(false)
107 {
108 }
109
WidgetBoxCategoryEntry(const QDesignerWidgetBoxInterface::Widget & w,const QString & filterIn,const QIcon & i,bool e)110 WidgetBoxCategoryEntry::WidgetBoxCategoryEntry(const QDesignerWidgetBoxInterface::Widget &w,
111 const QString &filterIn,
112 const QIcon &i, bool e) :
113 widget(w),
114 filter(filterIn),
115 icon(i),
116 editable(e)
117 {
118 }
119
120 /* WidgetBoxCategoryModel, representing a list of category entries. Uses a
121 * QAbstractListModel since the behaviour depends on the view mode of the list
122 * view, it does not return text in the case of IconMode. */
123
124 class WidgetBoxCategoryModel : public QAbstractListModel {
125 public:
126 explicit WidgetBoxCategoryModel(QDesignerFormEditorInterface *core, QObject *parent = 0);
127
128 // QAbstractListModel
129 virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
130 virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
131 virtual bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole);
132 virtual Qt::ItemFlags flags (const QModelIndex & index ) const;
133 virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
134
135 // The model returns no text in icon mode, so, it also needs to know it
136 QListView::ViewMode viewMode() const;
137 void setViewMode(QListView::ViewMode vm);
138
139 void addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon, bool editable);
140
141 QDesignerWidgetBoxInterface::Widget widgetAt(const QModelIndex & index) const;
142 QDesignerWidgetBoxInterface::Widget widgetAt(int row) const;
143
144 int indexOfWidget(const QString &name);
145
146 QDesignerWidgetBoxInterface::Category category() const;
147 bool removeCustomWidgets();
148
149 private:
150 typedef QList<WidgetBoxCategoryEntry> WidgetBoxCategoryEntrys;
151
152 QRegExp m_classNameRegExp;
153 QDesignerFormEditorInterface *m_core;
154 WidgetBoxCategoryEntrys m_items;
155 QListView::ViewMode m_viewMode;
156 };
157
WidgetBoxCategoryModel(QDesignerFormEditorInterface * core,QObject * parent)158 WidgetBoxCategoryModel::WidgetBoxCategoryModel(QDesignerFormEditorInterface *core, QObject *parent) :
159 QAbstractListModel(parent),
160 m_classNameRegExp(QLatin1String("<widget +class *= *\"([^\"]+)\"")),
161 m_core(core),
162 m_viewMode(QListView::ListMode)
163 {
164 Q_ASSERT(m_classNameRegExp.isValid());
165 }
166
viewMode() const167 QListView::ViewMode WidgetBoxCategoryModel::viewMode() const
168 {
169 return m_viewMode;
170 }
171
setViewMode(QListView::ViewMode vm)172 void WidgetBoxCategoryModel::setViewMode(QListView::ViewMode vm)
173 {
174 if (m_viewMode == vm)
175 return;
176 m_viewMode = vm;
177 if (!m_items.empty())
178 reset();
179 }
180
indexOfWidget(const QString & name)181 int WidgetBoxCategoryModel::indexOfWidget(const QString &name)
182 {
183 const int count = m_items.size();
184 for (int i = 0; i < count; i++)
185 if (m_items.at(i).widget.name() == name)
186 return i;
187 return -1;
188 }
189
category() const190 QDesignerWidgetBoxInterface::Category WidgetBoxCategoryModel::category() const
191 {
192 QDesignerWidgetBoxInterface::Category rc;
193 const WidgetBoxCategoryEntrys::const_iterator cend = m_items.constEnd();
194 for (WidgetBoxCategoryEntrys::const_iterator it = m_items.constBegin(); it != cend; ++it)
195 rc.addWidget(it->widget);
196 return rc;
197 }
198
removeCustomWidgets()199 bool WidgetBoxCategoryModel::removeCustomWidgets()
200 {
201 // Typically, we are a whole category of custom widgets, so, remove all
202 // and do reset.
203 bool changed = false;
204 for (WidgetBoxCategoryEntrys::iterator it = m_items.begin(); it != m_items.end(); )
205 if (it->widget.type() == QDesignerWidgetBoxInterface::Widget::Custom) {
206 it = m_items.erase(it);
207 changed = true;
208 } else {
209 ++it;
210 }
211 if (changed)
212 reset();
213 return changed;
214 }
215
addWidget(const QDesignerWidgetBoxInterface::Widget & widget,const QIcon & icon,bool editable)216 void WidgetBoxCategoryModel::addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon,bool editable)
217 {
218 // build item. Filter on name + class name if it is different and not a layout.
219 QString filter = widget.name();
220 if (!filter.contains(QLatin1String("Layout")) && m_classNameRegExp.indexIn(widget.domXml()) != -1) {
221 const QString className = m_classNameRegExp.cap(1);
222 if (!filter.contains(className))
223 filter += className;
224 }
225 WidgetBoxCategoryEntry item(widget, filter, icon, editable);
226 const QDesignerWidgetDataBaseInterface *db = m_core->widgetDataBase();
227 const int dbIndex = db->indexOfClassName(widget.name());
228 if (dbIndex != -1) {
229 const QDesignerWidgetDataBaseItemInterface *dbItem = db->item(dbIndex);
230 const QString toolTip = dbItem->toolTip();
231 if (!toolTip.isEmpty())
232 item.toolTip = toolTip;
233 const QString whatsThis = dbItem->whatsThis();
234 if (!whatsThis.isEmpty())
235 item.whatsThis = whatsThis;
236 }
237 // insert
238 const int row = m_items.size();
239 beginInsertRows(QModelIndex(), row, row);
240 m_items.push_back(item);
241 endInsertRows();
242 }
243
data(const QModelIndex & index,int role) const244 QVariant WidgetBoxCategoryModel::data(const QModelIndex &index, int role) const
245 {
246 const int row = index.row();
247 if (row < 0 || row >= m_items.size())
248 return QVariant();
249
250 const WidgetBoxCategoryEntry &item = m_items.at(row);
251 switch (role) {
252 case Qt::DisplayRole:
253 // No text in icon mode
254 return QVariant(m_viewMode == QListView::ListMode ? item.widget.name() : QString());
255 case Qt::DecorationRole:
256 return QVariant(item.icon);
257 case Qt::EditRole:
258 return QVariant(item.widget.name());
259 case Qt::ToolTipRole: {
260 if (m_viewMode == QListView::ListMode)
261 return QVariant(item.toolTip);
262 // Icon mode tooltip should contain the class name
263 QString tt = item.widget.name();
264 if (!item.toolTip.isEmpty()) {
265 tt += QLatin1Char('\n');
266 tt += item.toolTip;
267 }
268 return QVariant(tt);
269
270 }
271 case Qt::WhatsThisRole:
272 return QVariant(item.whatsThis);
273 case FilterRole:
274 return item.filter;
275 }
276 return QVariant();
277 }
278
setData(const QModelIndex & index,const QVariant & value,int role)279 bool WidgetBoxCategoryModel::setData(const QModelIndex &index, const QVariant &value, int role)
280 {
281 const int row = index.row();
282 if (role != Qt::EditRole || row < 0 || row >= m_items.size() || value.type() != QVariant::String)
283 return false;
284 // Set name and adapt Xml
285 WidgetBoxCategoryEntry &item = m_items[row];
286 const QString newName = value.toString();
287 item.widget.setName(newName);
288
289 const QDomDocument doc = stringToDom(WidgetBoxCategoryListView::widgetDomXml(item.widget));
290 QDomElement widget_elt = doc.firstChildElement(QLatin1String(widgetElementC));
291 if (!widget_elt.isNull()) {
292 widget_elt.setAttribute(QLatin1String(nameAttributeC), newName);
293 item.widget.setDomXml(domToString(widget_elt));
294 }
295 emit dataChanged(index, index);
296 return true;
297 }
298
flags(const QModelIndex & index) const299 Qt::ItemFlags WidgetBoxCategoryModel::flags(const QModelIndex &index) const
300 {
301 Qt::ItemFlags rc = Qt::ItemIsEnabled;
302 const int row = index.row();
303 if (row >= 0 && row < m_items.size())
304 if (m_items.at(row).editable) {
305 rc |= Qt::ItemIsSelectable;
306 // Can change name in list mode only
307 if (m_viewMode == QListView::ListMode)
308 rc |= Qt::ItemIsEditable;
309 }
310 return rc;
311 }
312
rowCount(const QModelIndex &) const313 int WidgetBoxCategoryModel::rowCount(const QModelIndex & /*parent*/) const
314 {
315 return m_items.size();
316 }
317
removeRows(int row,int count,const QModelIndex & parent)318 bool WidgetBoxCategoryModel::removeRows(int row, int count, const QModelIndex & parent)
319 {
320 if (row < 0 || count < 1)
321 return false;
322 const int size = m_items.size();
323 const int last = row + count - 1;
324 if (row >= size || last >= size)
325 return false;
326 beginRemoveRows(parent, row, last);
327 for (int r = last; r >= row; r--)
328 m_items.removeAt(r);
329 endRemoveRows();
330 return true;
331 }
332
widgetAt(const QModelIndex & index) const333 QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryModel::widgetAt(const QModelIndex & index) const
334 {
335 return widgetAt(index.row());
336 }
337
widgetAt(int row) const338 QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryModel::widgetAt(int row) const
339 {
340 if (row < 0 || row >= m_items.size())
341 return QDesignerWidgetBoxInterface::Widget();
342 return m_items.at(row).widget;
343 }
344
345 /* WidgetSubBoxItemDelegate, ensures a valid name using a regexp validator */
346
347 class WidgetBoxCategoryEntryDelegate : public QItemDelegate
348 {
349 public:
WidgetBoxCategoryEntryDelegate(QWidget * parent=0)350 explicit WidgetBoxCategoryEntryDelegate(QWidget *parent = 0) : QItemDelegate(parent) {}
351 QWidget *createEditor(QWidget *parent,
352 const QStyleOptionViewItem &option,
353 const QModelIndex &index) const;
354 };
355
createEditor(QWidget * parent,const QStyleOptionViewItem & option,const QModelIndex & index) const356 QWidget *WidgetBoxCategoryEntryDelegate::createEditor(QWidget *parent,
357 const QStyleOptionViewItem &option,
358 const QModelIndex &index) const
359 {
360 QWidget *result = QItemDelegate::createEditor(parent, option, index);
361 if (QLineEdit *line_edit = qobject_cast<QLineEdit*>(result)) {
362 const QRegExp re = QRegExp(QLatin1String("[_a-zA-Z][_a-zA-Z0-9]*"));
363 Q_ASSERT(re.isValid());
364 line_edit->setValidator(new QRegExpValidator(re, line_edit));
365 }
366 return result;
367 }
368
369 // ---------------------- WidgetBoxCategoryListView
370
WidgetBoxCategoryListView(QDesignerFormEditorInterface * core,QWidget * parent)371 WidgetBoxCategoryListView::WidgetBoxCategoryListView(QDesignerFormEditorInterface *core, QWidget *parent) :
372 QListView(parent),
373 m_proxyModel(new QSortFilterProxyModel(this)),
374 m_model(new WidgetBoxCategoryModel(core, this))
375 {
376 setFocusPolicy(Qt::NoFocus);
377 setFrameShape(QFrame::NoFrame);
378 setIconSize(QSize(22, 22));
379 setSpacing(1);
380 setTextElideMode(Qt::ElideMiddle);
381 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
382 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
383 setResizeMode(QListView::Adjust);
384 setUniformItemSizes(true);
385
386 setItemDelegate(new WidgetBoxCategoryEntryDelegate(this));
387
388 connect(this, SIGNAL(pressed(QModelIndex)), this, SLOT(slotPressed(QModelIndex)));
389 setEditTriggers(QAbstractItemView::AnyKeyPressed);
390
391 m_proxyModel->setSourceModel(m_model);
392 m_proxyModel->setFilterRole(FilterRole);
393 setModel(m_proxyModel);
394 connect(m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SIGNAL(scratchPadChanged()));
395 }
396
setViewMode(ViewMode vm)397 void WidgetBoxCategoryListView::setViewMode(ViewMode vm)
398 {
399 QListView::setViewMode(vm);
400 m_model->setViewMode(vm);
401 }
402
setCurrentItem(AccessMode am,int row)403 void WidgetBoxCategoryListView::setCurrentItem(AccessMode am, int row)
404 {
405 const QModelIndex index = am == FilteredAccess ?
406 m_proxyModel->index(row, 0) :
407 m_proxyModel->mapFromSource(m_model->index(row, 0));
408
409 if (index.isValid())
410 setCurrentIndex(index);
411 }
412
slotPressed(const QModelIndex & index)413 void WidgetBoxCategoryListView::slotPressed(const QModelIndex &index)
414 {
415 const QDesignerWidgetBoxInterface::Widget wgt = m_model->widgetAt(m_proxyModel->mapToSource(index));
416 if (wgt.isNull())
417 return;
418 emit pressed(wgt.name(), widgetDomXml(wgt), QCursor::pos());
419 }
420
removeCurrentItem()421 void WidgetBoxCategoryListView::removeCurrentItem()
422 {
423 const QModelIndex index = currentIndex();
424 if (!index.isValid() || !m_proxyModel->removeRow(index.row()))
425 return;
426
427 // We check the unfiltered item count here, we don't want to get removed if the
428 // filtered view is empty
429 if (m_model->rowCount()) {
430 emit itemRemoved();
431 } else {
432 emit lastItemRemoved();
433 }
434 }
435
editCurrentItem()436 void WidgetBoxCategoryListView::editCurrentItem()
437 {
438 const QModelIndex index = currentIndex();
439 if (index.isValid())
440 edit(index);
441 }
442
count(AccessMode am) const443 int WidgetBoxCategoryListView::count(AccessMode am) const
444 {
445 return am == FilteredAccess ? m_proxyModel->rowCount() : m_model->rowCount();
446 }
447
mapRowToSource(int filterRow) const448 int WidgetBoxCategoryListView::mapRowToSource(int filterRow) const
449 {
450 const QModelIndex filterIndex = m_proxyModel->index(filterRow, 0);
451 return m_proxyModel->mapToSource(filterIndex).row();
452 }
453
widgetAt(AccessMode am,const QModelIndex & index) const454 QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryListView::widgetAt(AccessMode am, const QModelIndex & index) const
455 {
456 const QModelIndex unfilteredIndex = am == FilteredAccess ? m_proxyModel->mapToSource(index) : index;
457 return m_model->widgetAt(unfilteredIndex);
458 }
459
widgetAt(AccessMode am,int row) const460 QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryListView::widgetAt(AccessMode am, int row) const
461 {
462 return m_model->widgetAt(am == UnfilteredAccess ? row : mapRowToSource(row));
463 }
464
removeRow(AccessMode am,int row)465 void WidgetBoxCategoryListView::removeRow(AccessMode am, int row)
466 {
467 m_model->removeRow(am == UnfilteredAccess ? row : mapRowToSource(row));
468 }
469
containsWidget(const QString & name)470 bool WidgetBoxCategoryListView::containsWidget(const QString &name)
471 {
472 return m_model->indexOfWidget(name) != -1;
473 }
474
addWidget(const QDesignerWidgetBoxInterface::Widget & widget,const QIcon & icon,bool editable)475 void WidgetBoxCategoryListView::addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon, bool editable)
476 {
477 m_model->addWidget(widget, icon, editable);
478 }
479
widgetDomXml(const QDesignerWidgetBoxInterface::Widget & widget)480 QString WidgetBoxCategoryListView::widgetDomXml(const QDesignerWidgetBoxInterface::Widget &widget)
481 {
482 QString domXml = widget.domXml();
483
484 if (domXml.isEmpty()) {
485 domXml = QLatin1String(uiOpeningTagC);
486 domXml += QLatin1String("<widget class=\"");
487 domXml += widget.name();
488 domXml += QLatin1String("\"/>");
489 domXml += QLatin1String(uiClosingTagC);
490 }
491 return domXml;
492 }
493
filter(const QRegExp & re)494 void WidgetBoxCategoryListView::filter(const QRegExp &re)
495 {
496 m_proxyModel->setFilterRegExp(re);
497 }
498
category() const499 QDesignerWidgetBoxInterface::Category WidgetBoxCategoryListView::category() const
500 {
501 return m_model->category();
502 }
503
removeCustomWidgets()504 bool WidgetBoxCategoryListView::removeCustomWidgets()
505 {
506 return m_model->removeCustomWidgets();
507 }
508 } // namespace qdesigner_internal
509
510 QT_END_NAMESPACE
511