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