| /**************************************************************************** |
| ** |
| ** 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 "widgetboxtreewidget.h" |
| #include "widgetboxcategorylistview.h" |
| |
| // shared |
| #include <iconloader_p.h> |
| #include <sheet_delegate_p.h> |
| #include <QtDesigner/private/abstractsettings_p.h> |
| #include <ui4_p.h> |
| #include <qdesigner_utils_p.h> |
| #include <pluginmanager_p.h> |
| |
| // sdk |
| #include <QtDesigner/QDesignerFormEditorInterface> |
| #include <QtDesigner/QDesignerDnDItemInterface> |
| #include <QtDesigner/QDesignerCustomWidgetInterface> |
| #include <QtDesigner/private/abstractsettings_p.h> |
| |
| #include <QtGui/QHeaderView> |
| #include <QtGui/QApplication> |
| #include <QtGui/QTreeWidgetItem> |
| #include <QtGui/QContextMenuEvent> |
| #include <QtGui/QAction> |
| #include <QtGui/QActionGroup> |
| #include <QtGui/QMenu> |
| |
| #include <QtCore/QFile> |
| #include <QtCore/QTimer> |
| #include <QtCore/QDebug> |
| |
| static const char *widgetBoxRootElementC = "widgetbox"; |
| static const char *widgetElementC = "widget"; |
| static const char *uiElementC = "ui"; |
| static const char *categoryElementC = "category"; |
| static const char *categoryEntryElementC = "categoryentry"; |
| static const char *nameAttributeC = "name"; |
| static const char *typeAttributeC = "type"; |
| static const char *iconAttributeC = "icon"; |
| static const char *defaultTypeValueC = "default"; |
| static const char *customValueC = "custom"; |
| static const char *iconPrefixC = "__qt_icon__"; |
| static const char *scratchPadValueC = "scratchpad"; |
| static const char *qtLogoC = "qtlogo.png"; |
| static const char *invisibleNameC = "[invisible]"; |
| |
| enum TopLevelRole { NORMAL_ITEM, SCRATCHPAD_ITEM, CUSTOM_ITEM }; |
| |
| QT_BEGIN_NAMESPACE |
| |
| static void setTopLevelRole(TopLevelRole tlr, QTreeWidgetItem *item) |
| { |
| item->setData(0, Qt::UserRole, QVariant(tlr)); |
| } |
| |
| static TopLevelRole topLevelRole(const QTreeWidgetItem *item) |
| { |
| return static_cast<TopLevelRole>(item->data(0, Qt::UserRole).toInt()); |
| } |
| |
| namespace qdesigner_internal { |
| |
| WidgetBoxTreeWidget::WidgetBoxTreeWidget(QDesignerFormEditorInterface *core, QWidget *parent) : |
| QTreeWidget(parent), |
| m_core(core), |
| m_iconMode(false), |
| m_scratchPadDeleteTimer(0) |
| { |
| setFocusPolicy(Qt::NoFocus); |
| setIndentation(0); |
| setRootIsDecorated(false); |
| setColumnCount(1); |
| header()->hide(); |
| header()->setResizeMode(QHeaderView::Stretch); |
| setTextElideMode(Qt::ElideMiddle); |
| setVerticalScrollMode(ScrollPerPixel); |
| |
| setItemDelegate(new SheetDelegate(this, this)); |
| |
| connect(this, SIGNAL(itemPressed(QTreeWidgetItem*,int)), |
| this, SLOT(handleMousePress(QTreeWidgetItem*))); |
| } |
| |
| QIcon WidgetBoxTreeWidget::iconForWidget(QString iconName) const |
| { |
| if (iconName.isEmpty()) |
| iconName = QLatin1String(qtLogoC); |
| |
| if (iconName.startsWith(QLatin1String(iconPrefixC))) { |
| const IconCache::const_iterator it = m_pluginIcons.constFind(iconName); |
| if (it != m_pluginIcons.constEnd()) |
| return it.value(); |
| } |
| return createIconSet(iconName); |
| } |
| |
| WidgetBoxCategoryListView *WidgetBoxTreeWidget::categoryViewAt(int idx) const |
| { |
| WidgetBoxCategoryListView *rc = 0; |
| if (QTreeWidgetItem *cat_item = topLevelItem(idx)) |
| if (QTreeWidgetItem *embedItem = cat_item->child(0)) |
| rc = qobject_cast<WidgetBoxCategoryListView*>(itemWidget(embedItem, 0)); |
| Q_ASSERT(rc); |
| return rc; |
| } |
| |
| void WidgetBoxTreeWidget::saveExpandedState() const |
| { |
| QStringList closedCategories; |
| if (const int numCategories = categoryCount()) { |
| for (int i = 0; i < numCategories; ++i) { |
| const QTreeWidgetItem *cat_item = topLevelItem(i); |
| if (!isItemExpanded(cat_item)) |
| closedCategories.append(cat_item->text(0)); |
| } |
| } |
| QDesignerSettingsInterface *settings = m_core->settingsManager(); |
| settings->beginGroup(QLatin1String(widgetBoxRootElementC)); |
| settings->setValue(QLatin1String("Closed categories"), closedCategories); |
| settings->setValue(QLatin1String("View mode"), m_iconMode); |
| settings->endGroup(); |
| } |
| |
| void WidgetBoxTreeWidget::restoreExpandedState() |
| { |
| typedef QSet<QString> StringSet; |
| QDesignerSettingsInterface *settings = m_core->settingsManager(); |
| m_iconMode = settings->value(QLatin1String("WidgetBox/View mode")).toBool(); |
| updateViewMode(); |
| const StringSet closedCategories = settings->value(QLatin1String("WidgetBox/Closed categories"), QStringList()).toStringList().toSet(); |
| expandAll(); |
| if (closedCategories.empty()) |
| return; |
| |
| if (const int numCategories = categoryCount()) { |
| for (int i = 0; i < numCategories; ++i) { |
| QTreeWidgetItem *item = topLevelItem(i); |
| if (closedCategories.contains(item->text(0))) |
| item->setExpanded(false); |
| } |
| } |
| } |
| |
| WidgetBoxTreeWidget::~WidgetBoxTreeWidget() |
| { |
| saveExpandedState(); |
| } |
| |
| void WidgetBoxTreeWidget::setFileName(const QString &file_name) |
| { |
| m_file_name = file_name; |
| } |
| |
| QString WidgetBoxTreeWidget::fileName() const |
| { |
| return m_file_name; |
| } |
| |
| bool WidgetBoxTreeWidget::save() |
| { |
| if (fileName().isEmpty()) |
| return false; |
| |
| QFile file(fileName()); |
| if (!file.open(QIODevice::WriteOnly)) |
| return false; |
| |
| CategoryList cat_list; |
| const int count = categoryCount(); |
| for (int i = 0; i < count; ++i) |
| cat_list.append(category(i)); |
| |
| QXmlStreamWriter writer(&file); |
| writer.setAutoFormatting(true); |
| writer.setAutoFormattingIndent(1); |
| writer.writeStartDocument(); |
| writeCategories(writer, cat_list); |
| writer.writeEndDocument(); |
| |
| return true; |
| } |
| |
| void WidgetBoxTreeWidget::slotSave() |
| { |
| save(); |
| } |
| |
| void WidgetBoxTreeWidget::handleMousePress(QTreeWidgetItem *item) |
| { |
| if (item == 0) |
| return; |
| |
| if (QApplication::mouseButtons() != Qt::LeftButton) |
| return; |
| |
| if (item->parent() == 0) { |
| setItemExpanded(item, !isItemExpanded(item)); |
| return; |
| } |
| } |
| |
| int WidgetBoxTreeWidget::ensureScratchpad() |
| { |
| const int existingIndex = indexOfScratchpad(); |
| if (existingIndex != -1) |
| return existingIndex; |
| |
| QTreeWidgetItem *scratch_item = new QTreeWidgetItem(this); |
| scratch_item->setText(0, tr("Scratchpad")); |
| setTopLevelRole(SCRATCHPAD_ITEM, scratch_item); |
| addCategoryView(scratch_item, false); // Scratchpad in list mode. |
| return categoryCount() - 1; |
| } |
| |
| WidgetBoxCategoryListView *WidgetBoxTreeWidget::addCategoryView(QTreeWidgetItem *parent, bool iconMode) |
| { |
| QTreeWidgetItem *embed_item = new QTreeWidgetItem(parent); |
| embed_item->setFlags(Qt::ItemIsEnabled); |
| WidgetBoxCategoryListView *categoryView = new WidgetBoxCategoryListView(m_core, this); |
| categoryView->setViewMode(iconMode ? QListView::IconMode : QListView::ListMode); |
| connect(categoryView, SIGNAL(scratchPadChanged()), this, SLOT(slotSave())); |
| connect(categoryView, SIGNAL(pressed(QString,QString,QPoint)), this, SIGNAL(pressed(QString,QString,QPoint))); |
| connect(categoryView, SIGNAL(itemRemoved()), this, SLOT(slotScratchPadItemDeleted())); |
| connect(categoryView, SIGNAL(lastItemRemoved()), this, SLOT(slotLastScratchPadItemDeleted())); |
| setItemWidget(embed_item, 0, categoryView); |
| return categoryView; |
| } |
| |
| int WidgetBoxTreeWidget::indexOfScratchpad() const |
| { |
| if (const int numTopLevels = topLevelItemCount()) { |
| for (int i = numTopLevels - 1; i >= 0; --i) { |
| if (topLevelRole(topLevelItem(i)) == SCRATCHPAD_ITEM) |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| int WidgetBoxTreeWidget::indexOfCategory(const QString &name) const |
| { |
| const int topLevelCount = topLevelItemCount(); |
| for (int i = 0; i < topLevelCount; ++i) { |
| if (topLevelItem(i)->text(0) == name) |
| return i; |
| } |
| return -1; |
| } |
| |
| bool WidgetBoxTreeWidget::load(QDesignerWidgetBox::LoadMode loadMode) |
| { |
| switch (loadMode) { |
| case QDesignerWidgetBox::LoadReplace: |
| clear(); |
| break; |
| case QDesignerWidgetBox::LoadCustomWidgetsOnly: |
| addCustomCategories(true); |
| updateGeometries(); |
| return true; |
| default: |
| break; |
| } |
| |
| const QString name = fileName(); |
| |
| QFile f(name); |
| if (!f.open(QIODevice::ReadOnly)) // Might not exist at first startup |
| return false; |
| |
| const QString contents = QString::fromUtf8(f.readAll()); |
| return loadContents(contents); |
| } |
| |
| bool WidgetBoxTreeWidget::loadContents(const QString &contents) |
| { |
| QString errorMessage; |
| CategoryList cat_list; |
| if (!readCategories(m_file_name, contents, &cat_list, &errorMessage)) { |
| qdesigner_internal::designerWarning(errorMessage); |
| return false; |
| } |
| |
| foreach(const Category &cat, cat_list) |
| addCategory(cat); |
| |
| addCustomCategories(false); |
| // Restore which items are expanded |
| restoreExpandedState(); |
| return true; |
| } |
| |
| void WidgetBoxTreeWidget::addCustomCategories(bool replace) |
| { |
| if (replace) { |
| // clear out all existing custom widgets |
| if (const int numTopLevels = topLevelItemCount()) { |
| for (int t = 0; t < numTopLevels ; ++t) |
| categoryViewAt(t)->removeCustomWidgets(); |
| } |
| } |
| // re-add |
| const CategoryList customList = loadCustomCategoryList(); |
| const CategoryList::const_iterator cend = customList.constEnd(); |
| for (CategoryList::const_iterator it = customList.constBegin(); it != cend; ++it) |
| addCategory(*it); |
| } |
| |
| static inline QString msgXmlError(const QString &fileName, const QXmlStreamReader &r) |
| { |
| return QDesignerWidgetBox::tr("An error has been encountered at line %1 of %2: %3") |
| .arg(r.lineNumber()).arg(fileName, r.errorString()); |
| } |
| |
| bool WidgetBoxTreeWidget::readCategories(const QString &fileName, const QString &contents, |
| CategoryList *cats, QString *errorMessage) |
| { |
| // Read widget box XML: |
| // |
| //<widgetbox version="4.5"> |
| // <category name="Layouts"> |
| // <categoryentry name="Vertical Layout" icon="win/editvlayout.png" type="default"> |
| // <widget class="QListWidget" ...> |
| // ... |
| |
| QXmlStreamReader reader(contents); |
| |
| |
| // Entries of category with name="invisible" should be ignored |
| bool ignoreEntries = false; |
| |
| while (!reader.atEnd()) { |
| switch (reader.readNext()) { |
| case QXmlStreamReader::StartElement: { |
| const QStringRef tag = reader.name(); |
| if (tag == QLatin1String(widgetBoxRootElementC)) { |
| //<widgetbox version="4.5"> |
| continue; |
| } |
| if (tag == QLatin1String(categoryElementC)) { |
| // <category name="Layouts"> |
| const QXmlStreamAttributes attributes = reader.attributes(); |
| const QString categoryName = attributes.value(QLatin1String(nameAttributeC)).toString(); |
| if (categoryName == QLatin1String(invisibleNameC)) { |
| ignoreEntries = true; |
| } else { |
| Category category(categoryName); |
| if (attributes.value(QLatin1String(typeAttributeC)) == QLatin1String(scratchPadValueC)) |
| category.setType(Category::Scratchpad); |
| cats->push_back(category); |
| } |
| continue; |
| } |
| if (tag == QLatin1String(categoryEntryElementC)) { |
| // <categoryentry name="Vertical Layout" icon="win/editvlayout.png" type="default"> |
| if (!ignoreEntries) { |
| QXmlStreamAttributes attr = reader.attributes(); |
| const QString widgetName = attr.value(QLatin1String(nameAttributeC)).toString(); |
| const QString widgetIcon = attr.value(QLatin1String(iconAttributeC)).toString(); |
| const WidgetBoxTreeWidget::Widget::Type widgetType = |
| attr.value(QLatin1String(typeAttributeC)).toString() |
| == QLatin1String(customValueC) ? |
| WidgetBoxTreeWidget::Widget::Custom : |
| WidgetBoxTreeWidget::Widget::Default; |
| |
| Widget w; |
| w.setName(widgetName); |
| w.setIconName(widgetIcon); |
| w.setType(widgetType); |
| if (!readWidget(&w, contents, reader)) |
| continue; |
| |
| cats->back().addWidget(w); |
| } // ignoreEntries |
| continue; |
| } |
| break; |
| } |
| case QXmlStreamReader::EndElement: { |
| const QStringRef tag = reader.name(); |
| if (tag == QLatin1String(widgetBoxRootElementC)) { |
| continue; |
| } |
| if (tag == QLatin1String(categoryElementC)) { |
| ignoreEntries = false; |
| continue; |
| } |
| if (tag == QLatin1String(categoryEntryElementC)) { |
| continue; |
| } |
| break; |
| } |
| default: break; |
| } |
| } |
| |
| if (reader.hasError()) { |
| *errorMessage = msgXmlError(fileName, reader); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /*! |
| * Read out a widget within a category. This can either be |
| * enclosed in a <ui> element or a (legacy) <widget> element which may |
| * contain nested <widget> elements. |
| * |
| * Examples: |
| * |
| * <ui language="c++"> |
| * <widget class="MultiPageWidget" name="multipagewidget"> ... </widget> |
| * <customwidgets>...</customwidgets> |
| * <ui> |
| * |
| * or |
| * |
| * <widget> |
| * <widget> ... </widget> |
| * ... |
| * <widget> |
| * |
| * Returns true on success, false if end was reached or an error has been encountered |
| * in which case the reader has its error flag set. If successful, the current item |
| * of the reader will be the closing element (</ui> or </widget>) |
| */ |
| bool WidgetBoxTreeWidget::readWidget(Widget *w, const QString &xml, QXmlStreamReader &r) |
| { |
| qint64 startTagPosition =0, endTagPosition = 0; |
| |
| int nesting = 0; |
| bool endEncountered = false; |
| bool parsedWidgetTag = false; |
| QString outmostElement; |
| while (!endEncountered) { |
| const qint64 currentPosition = r.characterOffset(); |
| switch(r.readNext()) { |
| case QXmlStreamReader::StartElement: |
| if (nesting++ == 0) { |
| // First element must be <ui> or (legacy) <widget> |
| const QStringRef name = r.name(); |
| if (name == QLatin1String(uiElementC)) { |
| startTagPosition = currentPosition; |
| } else { |
| if (name == QLatin1String(widgetElementC)) { |
| startTagPosition = currentPosition; |
| parsedWidgetTag = true; |
| } else { |
| r.raiseError(QDesignerWidgetBox::tr("Unexpected element <%1> encountered when parsing for <widget> or <ui>").arg(name.toString())); |
| return false; |
| } |
| } |
| } else { |
| // We are within <ui> looking for the first <widget> tag |
| if (!parsedWidgetTag && r.name() == QLatin1String(widgetElementC)) { |
| parsedWidgetTag = true; |
| } |
| } |
| break; |
| case QXmlStreamReader::EndElement: |
| // Reached end of widget? |
| if (--nesting == 0) { |
| endTagPosition = r.characterOffset(); |
| endEncountered = true; |
| } |
| break; |
| case QXmlStreamReader::EndDocument: |
| r.raiseError(QDesignerWidgetBox::tr("Unexpected end of file encountered when parsing widgets.")); |
| return false; |
| case QXmlStreamReader::Invalid: |
| return false; |
| default: |
| break; |
| } |
| } |
| if (!parsedWidgetTag) { |
| r.raiseError(QDesignerWidgetBox::tr("A widget element could not be found.")); |
| return false; |
| } |
| // Oddity: Startposition is 1 off |
| QString widgetXml = xml.mid(startTagPosition, endTagPosition - startTagPosition); |
| const QChar lessThan = QLatin1Char('<'); |
| if (!widgetXml.startsWith(lessThan)) |
| widgetXml.prepend(lessThan); |
| w->setDomXml(widgetXml); |
| return true; |
| } |
| |
| void WidgetBoxTreeWidget::writeCategories(QXmlStreamWriter &writer, const CategoryList &cat_list) const |
| { |
| const QString widgetbox = QLatin1String(widgetBoxRootElementC); |
| const QString name = QLatin1String(nameAttributeC); |
| const QString type = QLatin1String(typeAttributeC); |
| const QString icon = QLatin1String(iconAttributeC); |
| const QString defaultType = QLatin1String(defaultTypeValueC); |
| const QString category = QLatin1String(categoryElementC); |
| const QString categoryEntry = QLatin1String(categoryEntryElementC); |
| const QString iconPrefix = QLatin1String(iconPrefixC); |
| const QString widgetTag = QLatin1String(widgetElementC); |
| |
| // |
| // <widgetbox> |
| // <category name="Layouts"> |
| // <categoryEntry name="Vertical Layout" type="default" icon="win/editvlayout.png"> |
| // <ui> |
| // ... |
| // </ui> |
| // </categoryEntry> |
| // ... |
| // </category> |
| // ... |
| // </widgetbox> |
| // |
| |
| writer.writeStartElement(widgetbox); |
| |
| foreach (const Category &cat, cat_list) { |
| writer.writeStartElement(category); |
| writer.writeAttribute(name, cat.name()); |
| if (cat.type() == Category::Scratchpad) |
| writer.writeAttribute(type, QLatin1String(scratchPadValueC)); |
| |
| const int widgetCount = cat.widgetCount(); |
| for (int i = 0; i < widgetCount; ++i) { |
| const Widget wgt = cat.widget(i); |
| if (wgt.type() == Widget::Custom) |
| continue; |
| |
| writer.writeStartElement(categoryEntry); |
| writer.writeAttribute(name, wgt.name()); |
| if (!wgt.iconName().startsWith(iconPrefix)) |
| writer.writeAttribute(icon, wgt.iconName()); |
| writer.writeAttribute(type, defaultType); |
| |
| const DomUI *domUI = QDesignerWidgetBox::xmlToUi(wgt.name(), WidgetBoxCategoryListView::widgetDomXml(wgt), false); |
| if (domUI) { |
| domUI->write(writer); |
| delete domUI; |
| } |
| |
| writer.writeEndElement(); // categoryEntry |
| } |
| writer.writeEndElement(); // categoryEntry |
| } |
| |
| writer.writeEndElement(); // widgetBox |
| } |
| |
| static int findCategory(const QString &name, const WidgetBoxTreeWidget::CategoryList &list) |
| { |
| int idx = 0; |
| foreach (const WidgetBoxTreeWidget::Category &cat, list) { |
| if (cat.name() == name) |
| return idx; |
| ++idx; |
| } |
| return -1; |
| } |
| |
| static inline bool isValidIcon(const QIcon &icon) |
| { |
| if (!icon.isNull()) { |
| const QList<QSize> availableSizes = icon.availableSizes(); |
| if (!availableSizes.empty()) |
| return !availableSizes.front().isEmpty(); |
| } |
| return false; |
| } |
| |
| WidgetBoxTreeWidget::CategoryList WidgetBoxTreeWidget::loadCustomCategoryList() const |
| { |
| CategoryList result; |
| |
| const QDesignerPluginManager *pm = m_core->pluginManager(); |
| const QDesignerPluginManager::CustomWidgetList customWidgets = pm->registeredCustomWidgets(); |
| if (customWidgets.empty()) |
| return result; |
| |
| static const QString customCatName = tr("Custom Widgets"); |
| |
| const QString invisible = QLatin1String(invisibleNameC); |
| const QString iconPrefix = QLatin1String(iconPrefixC); |
| |
| foreach(QDesignerCustomWidgetInterface *c, customWidgets) { |
| const QString dom_xml = c->domXml(); |
| if (dom_xml.isEmpty()) |
| continue; |
| |
| const QString pluginName = c->name(); |
| const QDesignerCustomWidgetData data = pm->customWidgetData(c); |
| QString displayName = data.xmlDisplayName(); |
| if (displayName.isEmpty()) |
| displayName = pluginName; |
| |
| QString cat_name = c->group(); |
| if (cat_name.isEmpty()) |
| cat_name = customCatName; |
| else if (cat_name == invisible) |
| continue; |
| |
| int idx = findCategory(cat_name, result); |
| if (idx == -1) { |
| result.append(Category(cat_name)); |
| idx = result.size() - 1; |
| } |
| Category &cat = result[idx]; |
| |
| const QIcon icon = c->icon(); |
| |
| QString icon_name; |
| if (isValidIcon(icon)) { |
| icon_name = iconPrefix; |
| icon_name += pluginName; |
| m_pluginIcons.insert(icon_name, icon); |
| } else { |
| icon_name = QLatin1String(qtLogoC); |
| } |
| |
| cat.addWidget(Widget(displayName, dom_xml, icon_name, Widget::Custom)); |
| } |
| |
| return result; |
| } |
| |
| void WidgetBoxTreeWidget::adjustSubListSize(QTreeWidgetItem *cat_item) |
| { |
| QTreeWidgetItem *embedItem = cat_item->child(0); |
| if (embedItem == 0) |
| return; |
| |
| WidgetBoxCategoryListView *list_widget = static_cast<WidgetBoxCategoryListView*>(itemWidget(embedItem, 0)); |
| list_widget->setFixedWidth(header()->width()); |
| list_widget->doItemsLayout(); |
| const int height = qMax(list_widget->contentsSize().height() ,1); |
| list_widget->setFixedHeight(height); |
| embedItem->setSizeHint(0, QSize(-1, height - 1)); |
| } |
| |
| int WidgetBoxTreeWidget::categoryCount() const |
| { |
| return topLevelItemCount(); |
| } |
| |
| WidgetBoxTreeWidget::Category WidgetBoxTreeWidget::category(int cat_idx) const |
| { |
| if (cat_idx >= topLevelItemCount()) |
| return Category(); |
| |
| QTreeWidgetItem *cat_item = topLevelItem(cat_idx); |
| |
| QTreeWidgetItem *embedItem = cat_item->child(0); |
| WidgetBoxCategoryListView *categoryView = static_cast<WidgetBoxCategoryListView*>(itemWidget(embedItem, 0)); |
| |
| Category result = categoryView->category(); |
| result.setName(cat_item->text(0)); |
| |
| switch (topLevelRole(cat_item)) { |
| case SCRATCHPAD_ITEM: |
| result.setType(Category::Scratchpad); |
| break; |
| default: |
| result.setType(Category::Default); |
| break; |
| } |
| return result; |
| } |
| |
| void WidgetBoxTreeWidget::addCategory(const Category &cat) |
| { |
| if (cat.widgetCount() == 0) |
| return; |
| |
| const bool isScratchPad = cat.type() == Category::Scratchpad; |
| WidgetBoxCategoryListView *categoryView; |
| QTreeWidgetItem *cat_item; |
| |
| if (isScratchPad) { |
| const int idx = ensureScratchpad(); |
| categoryView = categoryViewAt(idx); |
| cat_item = topLevelItem(idx); |
| } else { |
| const int existingIndex = indexOfCategory(cat.name()); |
| if (existingIndex == -1) { |
| cat_item = new QTreeWidgetItem(); |
| cat_item->setText(0, cat.name()); |
| setTopLevelRole(NORMAL_ITEM, cat_item); |
| // insert before scratchpad |
| const int scratchPadIndex = indexOfScratchpad(); |
| if (scratchPadIndex == -1) { |
| addTopLevelItem(cat_item); |
| } else { |
| insertTopLevelItem(scratchPadIndex, cat_item); |
| } |
| setItemExpanded(cat_item, true); |
| categoryView = addCategoryView(cat_item, m_iconMode); |
| } else { |
| categoryView = categoryViewAt(existingIndex); |
| cat_item = topLevelItem(existingIndex); |
| } |
| } |
| // The same categories are read from the file $HOME, avoid duplicates |
| const int widgetCount = cat.widgetCount(); |
| for (int i = 0; i < widgetCount; ++i) { |
| const Widget w = cat.widget(i); |
| if (!categoryView->containsWidget(w.name())) |
| categoryView->addWidget(w, iconForWidget(w.iconName()), isScratchPad); |
| } |
| adjustSubListSize(cat_item); |
| } |
| |
| void WidgetBoxTreeWidget::removeCategory(int cat_idx) |
| { |
| if (cat_idx >= topLevelItemCount()) |
| return; |
| delete takeTopLevelItem(cat_idx); |
| } |
| |
| int WidgetBoxTreeWidget::widgetCount(int cat_idx) const |
| { |
| if (cat_idx >= topLevelItemCount()) |
| return 0; |
| // SDK functions want unfiltered access |
| return categoryViewAt(cat_idx)->count(WidgetBoxCategoryListView::UnfilteredAccess); |
| } |
| |
| WidgetBoxTreeWidget::Widget WidgetBoxTreeWidget::widget(int cat_idx, int wgt_idx) const |
| { |
| if (cat_idx >= topLevelItemCount()) |
| return Widget(); |
| // SDK functions want unfiltered access |
| WidgetBoxCategoryListView *categoryView = categoryViewAt(cat_idx); |
| return categoryView->widgetAt(WidgetBoxCategoryListView::UnfilteredAccess, wgt_idx); |
| } |
| |
| void WidgetBoxTreeWidget::addWidget(int cat_idx, const Widget &wgt) |
| { |
| if (cat_idx >= topLevelItemCount()) |
| return; |
| |
| QTreeWidgetItem *cat_item = topLevelItem(cat_idx); |
| WidgetBoxCategoryListView *categoryView = categoryViewAt(cat_idx); |
| |
| const bool scratch = topLevelRole(cat_item) == SCRATCHPAD_ITEM; |
| categoryView->addWidget(wgt, iconForWidget(wgt.iconName()), scratch); |
| adjustSubListSize(cat_item); |
| } |
| |
| void WidgetBoxTreeWidget::removeWidget(int cat_idx, int wgt_idx) |
| { |
| if (cat_idx >= topLevelItemCount()) |
| return; |
| |
| WidgetBoxCategoryListView *categoryView = categoryViewAt(cat_idx); |
| |
| // SDK functions want unfiltered access |
| const WidgetBoxCategoryListView::AccessMode am = WidgetBoxCategoryListView::UnfilteredAccess; |
| if (wgt_idx >= categoryView->count(am)) |
| return; |
| |
| categoryView->removeRow(am, wgt_idx); |
| } |
| |
| void WidgetBoxTreeWidget::slotScratchPadItemDeleted() |
| { |
| const int scratch_idx = indexOfScratchpad(); |
| QTreeWidgetItem *scratch_item = topLevelItem(scratch_idx); |
| adjustSubListSize(scratch_item); |
| save(); |
| } |
| |
| void WidgetBoxTreeWidget::slotLastScratchPadItemDeleted() |
| { |
| // Remove the scratchpad in the next idle loop |
| if (!m_scratchPadDeleteTimer) { |
| m_scratchPadDeleteTimer = new QTimer(this); |
| m_scratchPadDeleteTimer->setSingleShot(true); |
| m_scratchPadDeleteTimer->setInterval(0); |
| connect(m_scratchPadDeleteTimer, SIGNAL(timeout()), this, SLOT(deleteScratchpad())); |
| } |
| if (!m_scratchPadDeleteTimer->isActive()) |
| m_scratchPadDeleteTimer->start(); |
| } |
| |
| void WidgetBoxTreeWidget::deleteScratchpad() |
| { |
| const int idx = indexOfScratchpad(); |
| if (idx == -1) |
| return; |
| delete takeTopLevelItem(idx); |
| save(); |
| } |
| |
| |
| void WidgetBoxTreeWidget::slotListMode() |
| { |
| m_iconMode = false; |
| updateViewMode(); |
| } |
| |
| void WidgetBoxTreeWidget::slotIconMode() |
| { |
| m_iconMode = true; |
| updateViewMode(); |
| } |
| |
| void WidgetBoxTreeWidget::updateViewMode() |
| { |
| if (const int numTopLevels = topLevelItemCount()) { |
| for (int i = numTopLevels - 1; i >= 0; --i) { |
| QTreeWidgetItem *topLevel = topLevelItem(i); |
| // Scratch pad stays in list mode. |
| const QListView::ViewMode viewMode = m_iconMode && (topLevelRole(topLevel) != SCRATCHPAD_ITEM) ? QListView::IconMode : QListView::ListMode; |
| WidgetBoxCategoryListView *categoryView = categoryViewAt(i); |
| if (viewMode != categoryView->viewMode()) { |
| categoryView->setViewMode(viewMode); |
| adjustSubListSize(topLevelItem(i)); |
| } |
| } |
| } |
| |
| updateGeometries(); |
| } |
| |
| void WidgetBoxTreeWidget::resizeEvent(QResizeEvent *e) |
| { |
| QTreeWidget::resizeEvent(e); |
| if (const int numTopLevels = topLevelItemCount()) { |
| for (int i = numTopLevels - 1; i >= 0; --i) |
| adjustSubListSize(topLevelItem(i)); |
| } |
| } |
| |
| void WidgetBoxTreeWidget::contextMenuEvent(QContextMenuEvent *e) |
| { |
| QTreeWidgetItem *item = itemAt(e->pos()); |
| |
| const bool scratchpad_menu = item != 0 |
| && item->parent() != 0 |
| && topLevelRole(item->parent()) == SCRATCHPAD_ITEM; |
| |
| QMenu menu; |
| menu.addAction(tr("Expand all"), this, SLOT(expandAll())); |
| menu.addAction(tr("Collapse all"), this, SLOT(collapseAll())); |
| menu.addSeparator(); |
| |
| QAction *listModeAction = menu.addAction(tr("List View")); |
| QAction *iconModeAction = menu.addAction(tr("Icon View")); |
| listModeAction->setCheckable(true); |
| iconModeAction->setCheckable(true); |
| QActionGroup *viewModeGroup = new QActionGroup(&menu); |
| viewModeGroup->addAction(listModeAction); |
| viewModeGroup->addAction(iconModeAction); |
| if (m_iconMode) |
| iconModeAction->setChecked(true); |
| else |
| listModeAction->setChecked(true); |
| connect(listModeAction, SIGNAL(triggered()), SLOT(slotListMode())); |
| connect(iconModeAction, SIGNAL(triggered()), SLOT(slotIconMode())); |
| |
| if (scratchpad_menu) { |
| menu.addSeparator(); |
| menu.addAction(tr("Remove"), itemWidget(item, 0), SLOT(removeCurrentItem())); |
| if (!m_iconMode) |
| menu.addAction(tr("Edit name"), itemWidget(item, 0), SLOT(editCurrentItem())); |
| } |
| e->accept(); |
| menu.exec(mapToGlobal(e->pos())); |
| } |
| |
| void WidgetBoxTreeWidget::dropWidgets(const QList<QDesignerDnDItemInterface*> &item_list) |
| { |
| QTreeWidgetItem *scratch_item = 0; |
| WidgetBoxCategoryListView *categoryView = 0; |
| bool added = false; |
| |
| foreach (QDesignerDnDItemInterface *item, item_list) { |
| QWidget *w = item->widget(); |
| if (w == 0) |
| continue; |
| |
| DomUI *dom_ui = item->domUi(); |
| if (dom_ui == 0) |
| continue; |
| |
| const int scratch_idx = ensureScratchpad(); |
| scratch_item = topLevelItem(scratch_idx); |
| categoryView = categoryViewAt(scratch_idx); |
| |
| // Temporarily remove the fake toplevel in-between |
| DomWidget *fakeTopLevel = dom_ui->takeElementWidget(); |
| DomWidget *firstWidget = 0; |
| if (fakeTopLevel && !fakeTopLevel->elementWidget().isEmpty()) { |
| firstWidget = fakeTopLevel->elementWidget().first(); |
| dom_ui->setElementWidget(firstWidget); |
| } else { |
| dom_ui->setElementWidget(fakeTopLevel); |
| continue; |
| } |
| |
| // Serialize to XML |
| QString xml; |
| { |
| QXmlStreamWriter writer(&xml); |
| writer.setAutoFormatting(true); |
| writer.setAutoFormattingIndent(1); |
| writer.writeStartDocument(); |
| dom_ui->write(writer); |
| writer.writeEndDocument(); |
| } |
| |
| // Insert fake toplevel again |
| dom_ui->takeElementWidget(); |
| dom_ui->setElementWidget(fakeTopLevel); |
| |
| const Widget wgt = Widget(w->objectName(), xml); |
| categoryView->addWidget(wgt, iconForWidget(wgt.iconName()), true); |
| setItemExpanded(scratch_item, true); |
| added = true; |
| } |
| |
| if (added) { |
| save(); |
| QApplication::setActiveWindow(this); |
| // Is the new item visible in filtered mode? |
| const WidgetBoxCategoryListView::AccessMode am = WidgetBoxCategoryListView::FilteredAccess; |
| if (const int count = categoryView->count(am)) |
| categoryView->setCurrentItem(am, count - 1); |
| categoryView->adjustSize(); // XXX |
| adjustSubListSize(scratch_item); |
| } |
| } |
| |
| void WidgetBoxTreeWidget::filter(const QString &f) |
| { |
| const bool empty = f.isEmpty(); |
| const QRegExp re = empty ? QRegExp() : QRegExp(f, Qt::CaseInsensitive, QRegExp::FixedString); |
| const int numTopLevels = topLevelItemCount(); |
| bool changed = false; |
| for (int i = 0; i < numTopLevels; i++) { |
| QTreeWidgetItem *tl = topLevelItem(i); |
| WidgetBoxCategoryListView *categoryView = categoryViewAt(i); |
| // Anything changed? -> Enable the category |
| const int oldCount = categoryView->count(WidgetBoxCategoryListView::FilteredAccess); |
| categoryView->filter(re); |
| const int newCount = categoryView->count(WidgetBoxCategoryListView::FilteredAccess); |
| if (oldCount != newCount) { |
| changed = true; |
| const bool categoryEnabled = newCount > 0 || empty; |
| if (categoryEnabled) { |
| categoryView->adjustSize(); |
| adjustSubListSize(tl); |
| } |
| setRowHidden (i, QModelIndex(), !categoryEnabled); |
| } |
| } |
| if (changed) |
| updateGeometries(); |
| } |
| |
| } // namespace qdesigner_internal |
| |
| QT_END_NAMESPACE |