| /**************************************************************************** |
| ** |
| ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). |
| ** All rights reserved. |
| ** Contact: Nokia Corporation (qt-info@nokia.com) |
| ** |
| ** This file is part of the Qt Designer of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** GNU Lesser General Public License Usage |
| ** This file may be used under the terms of the GNU Lesser General Public |
| ** License version 2.1 as published by the Free Software Foundation and |
| ** appearing in the file LICENSE.LGPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU Lesser |
| ** General Public License version 2.1 requirements will be met: |
| ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
| ** |
| ** In addition, as a special exception, Nokia gives you certain additional |
| ** rights. These rights are described in the Nokia Qt LGPL Exception |
| ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU General |
| ** Public License version 3.0 as published by the Free Software Foundation |
| ** and appearing in the file LICENSE.GPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU General |
| ** Public License version 3.0 requirements will be met: |
| ** http://www.gnu.org/copyleft/gpl.html. |
| ** |
| ** Other Usage |
| ** Alternatively, this file may be used in accordance with the terms and |
| ** conditions contained in a signed written agreement between you and Nokia. |
| ** |
| ** |
| ** |
| ** |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "widgetboxcategorylistview.h" |
| |
| #include <QtDesigner/QDesignerFormEditorInterface> |
| #include <QtDesigner/QDesignerWidgetDataBaseInterface> |
| |
| #include <QtXml/QDomDocument> |
| |
| #include <QtGui/QIcon> |
| #include <QtGui/QListView> |
| #include <QtGui/QLineEdit> |
| #include <QtGui/QItemDelegate> |
| #include <QtGui/QSortFilterProxyModel> |
| |
| #include <QtCore/QAbstractListModel> |
| #include <QtCore/QList> |
| #include <QtCore/QTextStream> |
| #include <QtCore/QRegExp> |
| |
| static const char *widgetElementC = "widget"; |
| static const char *nameAttributeC = "name"; |
| static const char *uiOpeningTagC = "<ui>"; |
| static const char *uiClosingTagC = "</ui>"; |
| |
| QT_BEGIN_NAMESPACE |
| |
| enum { FilterRole = Qt::UserRole + 11 }; |
| |
| static QString domToString(const QDomElement &elt) |
| { |
| QString result; |
| QTextStream stream(&result, QIODevice::WriteOnly); |
| elt.save(stream, 2); |
| stream.flush(); |
| return result; |
| } |
| |
| static QDomDocument stringToDom(const QString &xml) |
| { |
| QDomDocument result; |
| result.setContent(xml); |
| return result; |
| } |
| |
| namespace qdesigner_internal { |
| |
| // Entry of the model list |
| |
| struct WidgetBoxCategoryEntry { |
| WidgetBoxCategoryEntry(); |
| explicit WidgetBoxCategoryEntry(const QDesignerWidgetBoxInterface::Widget &widget, |
| const QString &filter, |
| const QIcon &icon, |
| bool editable); |
| |
| QDesignerWidgetBoxInterface::Widget widget; |
| QString toolTip; |
| QString whatsThis; |
| QString filter; |
| QIcon icon; |
| bool editable; |
| }; |
| |
| |
| WidgetBoxCategoryEntry::WidgetBoxCategoryEntry() : |
| editable(false) |
| { |
| } |
| |
| WidgetBoxCategoryEntry::WidgetBoxCategoryEntry(const QDesignerWidgetBoxInterface::Widget &w, |
| const QString &filterIn, |
| const QIcon &i, bool e) : |
| widget(w), |
| filter(filterIn), |
| icon(i), |
| editable(e) |
| { |
| } |
| |
| /* WidgetBoxCategoryModel, representing a list of category entries. Uses a |
| * QAbstractListModel since the behaviour depends on the view mode of the list |
| * view, it does not return text in the case of IconMode. */ |
| |
| class WidgetBoxCategoryModel : public QAbstractListModel { |
| public: |
| explicit WidgetBoxCategoryModel(QDesignerFormEditorInterface *core, QObject *parent = 0); |
| |
| // QAbstractListModel |
| virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; |
| virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; |
| virtual bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole); |
| virtual Qt::ItemFlags flags (const QModelIndex & index ) const; |
| virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); |
| |
| // The model returns no text in icon mode, so, it also needs to know it |
| QListView::ViewMode viewMode() const; |
| void setViewMode(QListView::ViewMode vm); |
| |
| void addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon, bool editable); |
| |
| QDesignerWidgetBoxInterface::Widget widgetAt(const QModelIndex & index) const; |
| QDesignerWidgetBoxInterface::Widget widgetAt(int row) const; |
| |
| int indexOfWidget(const QString &name); |
| |
| QDesignerWidgetBoxInterface::Category category() const; |
| bool removeCustomWidgets(); |
| |
| private: |
| typedef QList<WidgetBoxCategoryEntry> WidgetBoxCategoryEntrys; |
| |
| QRegExp m_classNameRegExp; |
| QDesignerFormEditorInterface *m_core; |
| WidgetBoxCategoryEntrys m_items; |
| QListView::ViewMode m_viewMode; |
| }; |
| |
| WidgetBoxCategoryModel::WidgetBoxCategoryModel(QDesignerFormEditorInterface *core, QObject *parent) : |
| QAbstractListModel(parent), |
| m_classNameRegExp(QLatin1String("<widget +class *= *\"([^\"]+)\"")), |
| m_core(core), |
| m_viewMode(QListView::ListMode) |
| { |
| Q_ASSERT(m_classNameRegExp.isValid()); |
| } |
| |
| QListView::ViewMode WidgetBoxCategoryModel::viewMode() const |
| { |
| return m_viewMode; |
| } |
| |
| void WidgetBoxCategoryModel::setViewMode(QListView::ViewMode vm) |
| { |
| if (m_viewMode == vm) |
| return; |
| m_viewMode = vm; |
| if (!m_items.empty()) |
| reset(); |
| } |
| |
| int WidgetBoxCategoryModel::indexOfWidget(const QString &name) |
| { |
| const int count = m_items.size(); |
| for (int i = 0; i < count; i++) |
| if (m_items.at(i).widget.name() == name) |
| return i; |
| return -1; |
| } |
| |
| QDesignerWidgetBoxInterface::Category WidgetBoxCategoryModel::category() const |
| { |
| QDesignerWidgetBoxInterface::Category rc; |
| const WidgetBoxCategoryEntrys::const_iterator cend = m_items.constEnd(); |
| for (WidgetBoxCategoryEntrys::const_iterator it = m_items.constBegin(); it != cend; ++it) |
| rc.addWidget(it->widget); |
| return rc; |
| } |
| |
| bool WidgetBoxCategoryModel::removeCustomWidgets() |
| { |
| // Typically, we are a whole category of custom widgets, so, remove all |
| // and do reset. |
| bool changed = false; |
| for (WidgetBoxCategoryEntrys::iterator it = m_items.begin(); it != m_items.end(); ) |
| if (it->widget.type() == QDesignerWidgetBoxInterface::Widget::Custom) { |
| it = m_items.erase(it); |
| changed = true; |
| } else { |
| ++it; |
| } |
| if (changed) |
| reset(); |
| return changed; |
| } |
| |
| void WidgetBoxCategoryModel::addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon,bool editable) |
| { |
| // build item. Filter on name + class name if it is different and not a layout. |
| QString filter = widget.name(); |
| if (!filter.contains(QLatin1String("Layout")) && m_classNameRegExp.indexIn(widget.domXml()) != -1) { |
| const QString className = m_classNameRegExp.cap(1); |
| if (!filter.contains(className)) |
| filter += className; |
| } |
| WidgetBoxCategoryEntry item(widget, filter, icon, editable); |
| const QDesignerWidgetDataBaseInterface *db = m_core->widgetDataBase(); |
| const int dbIndex = db->indexOfClassName(widget.name()); |
| if (dbIndex != -1) { |
| const QDesignerWidgetDataBaseItemInterface *dbItem = db->item(dbIndex); |
| const QString toolTip = dbItem->toolTip(); |
| if (!toolTip.isEmpty()) |
| item.toolTip = toolTip; |
| const QString whatsThis = dbItem->whatsThis(); |
| if (!whatsThis.isEmpty()) |
| item.whatsThis = whatsThis; |
| } |
| // insert |
| const int row = m_items.size(); |
| beginInsertRows(QModelIndex(), row, row); |
| m_items.push_back(item); |
| endInsertRows(); |
| } |
| |
| QVariant WidgetBoxCategoryModel::data(const QModelIndex &index, int role) const |
| { |
| const int row = index.row(); |
| if (row < 0 || row >= m_items.size()) |
| return QVariant(); |
| |
| const WidgetBoxCategoryEntry &item = m_items.at(row); |
| switch (role) { |
| case Qt::DisplayRole: |
| // No text in icon mode |
| return QVariant(m_viewMode == QListView::ListMode ? item.widget.name() : QString()); |
| case Qt::DecorationRole: |
| return QVariant(item.icon); |
| case Qt::EditRole: |
| return QVariant(item.widget.name()); |
| case Qt::ToolTipRole: { |
| if (m_viewMode == QListView::ListMode) |
| return QVariant(item.toolTip); |
| // Icon mode tooltip should contain the class name |
| QString tt = item.widget.name(); |
| if (!item.toolTip.isEmpty()) { |
| tt += QLatin1Char('\n'); |
| tt += item.toolTip; |
| } |
| return QVariant(tt); |
| |
| } |
| case Qt::WhatsThisRole: |
| return QVariant(item.whatsThis); |
| case FilterRole: |
| return item.filter; |
| } |
| return QVariant(); |
| } |
| |
| bool WidgetBoxCategoryModel::setData(const QModelIndex &index, const QVariant &value, int role) |
| { |
| const int row = index.row(); |
| if (role != Qt::EditRole || row < 0 || row >= m_items.size() || value.type() != QVariant::String) |
| return false; |
| // Set name and adapt Xml |
| WidgetBoxCategoryEntry &item = m_items[row]; |
| const QString newName = value.toString(); |
| item.widget.setName(newName); |
| |
| const QDomDocument doc = stringToDom(WidgetBoxCategoryListView::widgetDomXml(item.widget)); |
| QDomElement widget_elt = doc.firstChildElement(QLatin1String(widgetElementC)); |
| if (!widget_elt.isNull()) { |
| widget_elt.setAttribute(QLatin1String(nameAttributeC), newName); |
| item.widget.setDomXml(domToString(widget_elt)); |
| } |
| emit dataChanged(index, index); |
| return true; |
| } |
| |
| Qt::ItemFlags WidgetBoxCategoryModel::flags(const QModelIndex &index) const |
| { |
| Qt::ItemFlags rc = Qt::ItemIsEnabled; |
| const int row = index.row(); |
| if (row >= 0 && row < m_items.size()) |
| if (m_items.at(row).editable) { |
| rc |= Qt::ItemIsSelectable; |
| // Can change name in list mode only |
| if (m_viewMode == QListView::ListMode) |
| rc |= Qt::ItemIsEditable; |
| } |
| return rc; |
| } |
| |
| int WidgetBoxCategoryModel::rowCount(const QModelIndex & /*parent*/) const |
| { |
| return m_items.size(); |
| } |
| |
| bool WidgetBoxCategoryModel::removeRows(int row, int count, const QModelIndex & parent) |
| { |
| if (row < 0 || count < 1) |
| return false; |
| const int size = m_items.size(); |
| const int last = row + count - 1; |
| if (row >= size || last >= size) |
| return false; |
| beginRemoveRows(parent, row, last); |
| for (int r = last; r >= row; r--) |
| m_items.removeAt(r); |
| endRemoveRows(); |
| return true; |
| } |
| |
| QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryModel::widgetAt(const QModelIndex & index) const |
| { |
| return widgetAt(index.row()); |
| } |
| |
| QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryModel::widgetAt(int row) const |
| { |
| if (row < 0 || row >= m_items.size()) |
| return QDesignerWidgetBoxInterface::Widget(); |
| return m_items.at(row).widget; |
| } |
| |
| /* WidgetSubBoxItemDelegate, ensures a valid name using a regexp validator */ |
| |
| class WidgetBoxCategoryEntryDelegate : public QItemDelegate |
| { |
| public: |
| explicit WidgetBoxCategoryEntryDelegate(QWidget *parent = 0) : QItemDelegate(parent) {} |
| QWidget *createEditor(QWidget *parent, |
| const QStyleOptionViewItem &option, |
| const QModelIndex &index) const; |
| }; |
| |
| QWidget *WidgetBoxCategoryEntryDelegate::createEditor(QWidget *parent, |
| const QStyleOptionViewItem &option, |
| const QModelIndex &index) const |
| { |
| QWidget *result = QItemDelegate::createEditor(parent, option, index); |
| if (QLineEdit *line_edit = qobject_cast<QLineEdit*>(result)) { |
| const QRegExp re = QRegExp(QLatin1String("[_a-zA-Z][_a-zA-Z0-9]*")); |
| Q_ASSERT(re.isValid()); |
| line_edit->setValidator(new QRegExpValidator(re, line_edit)); |
| } |
| return result; |
| } |
| |
| // ---------------------- WidgetBoxCategoryListView |
| |
| WidgetBoxCategoryListView::WidgetBoxCategoryListView(QDesignerFormEditorInterface *core, QWidget *parent) : |
| QListView(parent), |
| m_proxyModel(new QSortFilterProxyModel(this)), |
| m_model(new WidgetBoxCategoryModel(core, this)) |
| { |
| setFocusPolicy(Qt::NoFocus); |
| setFrameShape(QFrame::NoFrame); |
| setIconSize(QSize(22, 22)); |
| setSpacing(1); |
| setTextElideMode(Qt::ElideMiddle); |
| setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
| setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
| setResizeMode(QListView::Adjust); |
| setUniformItemSizes(true); |
| |
| setItemDelegate(new WidgetBoxCategoryEntryDelegate(this)); |
| |
| connect(this, SIGNAL(pressed(QModelIndex)), this, SLOT(slotPressed(QModelIndex))); |
| setEditTriggers(QAbstractItemView::AnyKeyPressed); |
| |
| m_proxyModel->setSourceModel(m_model); |
| m_proxyModel->setFilterRole(FilterRole); |
| setModel(m_proxyModel); |
| connect(m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SIGNAL(scratchPadChanged())); |
| } |
| |
| void WidgetBoxCategoryListView::setViewMode(ViewMode vm) |
| { |
| QListView::setViewMode(vm); |
| m_model->setViewMode(vm); |
| } |
| |
| void WidgetBoxCategoryListView::setCurrentItem(AccessMode am, int row) |
| { |
| const QModelIndex index = am == FilteredAccess ? |
| m_proxyModel->index(row, 0) : |
| m_proxyModel->mapFromSource(m_model->index(row, 0)); |
| |
| if (index.isValid()) |
| setCurrentIndex(index); |
| } |
| |
| void WidgetBoxCategoryListView::slotPressed(const QModelIndex &index) |
| { |
| const QDesignerWidgetBoxInterface::Widget wgt = m_model->widgetAt(m_proxyModel->mapToSource(index)); |
| if (wgt.isNull()) |
| return; |
| emit pressed(wgt.name(), widgetDomXml(wgt), QCursor::pos()); |
| } |
| |
| void WidgetBoxCategoryListView::removeCurrentItem() |
| { |
| const QModelIndex index = currentIndex(); |
| if (!index.isValid() || !m_proxyModel->removeRow(index.row())) |
| return; |
| |
| // We check the unfiltered item count here, we don't want to get removed if the |
| // filtered view is empty |
| if (m_model->rowCount()) { |
| emit itemRemoved(); |
| } else { |
| emit lastItemRemoved(); |
| } |
| } |
| |
| void WidgetBoxCategoryListView::editCurrentItem() |
| { |
| const QModelIndex index = currentIndex(); |
| if (index.isValid()) |
| edit(index); |
| } |
| |
| int WidgetBoxCategoryListView::count(AccessMode am) const |
| { |
| return am == FilteredAccess ? m_proxyModel->rowCount() : m_model->rowCount(); |
| } |
| |
| int WidgetBoxCategoryListView::mapRowToSource(int filterRow) const |
| { |
| const QModelIndex filterIndex = m_proxyModel->index(filterRow, 0); |
| return m_proxyModel->mapToSource(filterIndex).row(); |
| } |
| |
| QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryListView::widgetAt(AccessMode am, const QModelIndex & index) const |
| { |
| const QModelIndex unfilteredIndex = am == FilteredAccess ? m_proxyModel->mapToSource(index) : index; |
| return m_model->widgetAt(unfilteredIndex); |
| } |
| |
| QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryListView::widgetAt(AccessMode am, int row) const |
| { |
| return m_model->widgetAt(am == UnfilteredAccess ? row : mapRowToSource(row)); |
| } |
| |
| void WidgetBoxCategoryListView::removeRow(AccessMode am, int row) |
| { |
| m_model->removeRow(am == UnfilteredAccess ? row : mapRowToSource(row)); |
| } |
| |
| bool WidgetBoxCategoryListView::containsWidget(const QString &name) |
| { |
| return m_model->indexOfWidget(name) != -1; |
| } |
| |
| void WidgetBoxCategoryListView::addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon, bool editable) |
| { |
| m_model->addWidget(widget, icon, editable); |
| } |
| |
| QString WidgetBoxCategoryListView::widgetDomXml(const QDesignerWidgetBoxInterface::Widget &widget) |
| { |
| QString domXml = widget.domXml(); |
| |
| if (domXml.isEmpty()) { |
| domXml = QLatin1String(uiOpeningTagC); |
| domXml += QLatin1String("<widget class=\""); |
| domXml += widget.name(); |
| domXml += QLatin1String("\"/>"); |
| domXml += QLatin1String(uiClosingTagC); |
| } |
| return domXml; |
| } |
| |
| void WidgetBoxCategoryListView::filter(const QRegExp &re) |
| { |
| m_proxyModel->setFilterRegExp(re); |
| } |
| |
| QDesignerWidgetBoxInterface::Category WidgetBoxCategoryListView::category() const |
| { |
| return m_model->category(); |
| } |
| |
| bool WidgetBoxCategoryListView::removeCustomWidgets() |
| { |
| return m_model->removeCustomWidgets(); |
| } |
| } // namespace qdesigner_internal |
| |
| QT_END_NAMESPACE |