blob: 73a66debefc464f8c40f2c559d61df7c5656cb1e [file] [log] [blame]
/****************************************************************************
**
** 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