tools/designer/src/components/widgetbox/widgetboxtreewidget.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the Qt Designer of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include "widgetboxtreewidget.h"
       
    43 #include "widgetboxcategorylistview.h"
       
    44 
       
    45 // shared
       
    46 #include <iconloader_p.h>
       
    47 #include <sheet_delegate_p.h>
       
    48 #include <QtDesigner/private/abstractsettings_p.h>
       
    49 #include <ui4_p.h>
       
    50 #include <qdesigner_utils_p.h>
       
    51 #include <pluginmanager_p.h>
       
    52 
       
    53 // sdk
       
    54 #include <QtDesigner/QDesignerFormEditorInterface>
       
    55 #include <QtDesigner/QDesignerDnDItemInterface>
       
    56 #include <QtDesigner/QDesignerCustomWidgetInterface>
       
    57 #include <QtDesigner/private/abstractsettings_p.h>
       
    58 
       
    59 #include <QtGui/QHeaderView>
       
    60 #include <QtGui/QApplication>
       
    61 #include <QtGui/QTreeWidgetItem>
       
    62 #include <QtGui/QContextMenuEvent>
       
    63 #include <QtGui/QAction>
       
    64 #include <QtGui/QActionGroup>
       
    65 #include <QtGui/QMenu>
       
    66 
       
    67 #include <QtCore/QFile>
       
    68 #include <QtCore/QTimer>
       
    69 #include <QtCore/QDebug>
       
    70 
       
    71 static const char *widgetBoxRootElementC = "widgetbox";
       
    72 static const char *widgetElementC = "widget";
       
    73 static const char *uiElementC = "ui";
       
    74 static const char *categoryElementC = "category";
       
    75 static const char *categoryEntryElementC = "categoryentry";
       
    76 static const char *nameAttributeC = "name";
       
    77 static const char *typeAttributeC = "type";
       
    78 static const char *iconAttributeC = "icon";
       
    79 static const char *defaultTypeValueC = "default";
       
    80 static const char *customValueC = "custom";
       
    81 static const char *iconPrefixC = "__qt_icon__";
       
    82 static const char *scratchPadValueC = "scratchpad";
       
    83 static const char *qtLogoC = "qtlogo.png";
       
    84 static const char *invisibleNameC = "[invisible]";
       
    85 
       
    86 enum TopLevelRole  { NORMAL_ITEM, SCRATCHPAD_ITEM, CUSTOM_ITEM };
       
    87 
       
    88 QT_BEGIN_NAMESPACE
       
    89 
       
    90 static void setTopLevelRole(TopLevelRole tlr, QTreeWidgetItem *item)
       
    91 {
       
    92     item->setData(0, Qt::UserRole, QVariant(tlr));
       
    93 }
       
    94 
       
    95 static TopLevelRole topLevelRole(const  QTreeWidgetItem *item)
       
    96 {
       
    97     return static_cast<TopLevelRole>(item->data(0, Qt::UserRole).toInt());
       
    98 }
       
    99 
       
   100 namespace qdesigner_internal {
       
   101 
       
   102 WidgetBoxTreeWidget::WidgetBoxTreeWidget(QDesignerFormEditorInterface *core, QWidget *parent) :
       
   103     QTreeWidget(parent),
       
   104     m_core(core),
       
   105     m_iconMode(false),
       
   106     m_scratchPadDeleteTimer(0)
       
   107 {
       
   108     setFocusPolicy(Qt::NoFocus);
       
   109     setIndentation(0);
       
   110     setRootIsDecorated(false);
       
   111     setColumnCount(1);
       
   112     header()->hide();
       
   113     header()->setResizeMode(QHeaderView::Stretch);
       
   114     setTextElideMode(Qt::ElideMiddle);
       
   115     setVerticalScrollMode(ScrollPerPixel);
       
   116 
       
   117     setItemDelegate(new SheetDelegate(this, this));
       
   118 
       
   119     connect(this, SIGNAL(itemPressed(QTreeWidgetItem*,int)),
       
   120             this, SLOT(handleMousePress(QTreeWidgetItem*)));
       
   121 }
       
   122 
       
   123 QIcon WidgetBoxTreeWidget::iconForWidget(QString iconName) const
       
   124 {
       
   125     if (iconName.isEmpty())
       
   126         iconName = QLatin1String(qtLogoC);
       
   127 
       
   128     if (iconName.startsWith(QLatin1String(iconPrefixC))) {
       
   129         const IconCache::const_iterator it = m_pluginIcons.constFind(iconName);
       
   130         if (it != m_pluginIcons.constEnd())
       
   131             return it.value();
       
   132     }
       
   133     return createIconSet(iconName);
       
   134 }
       
   135 
       
   136 WidgetBoxCategoryListView *WidgetBoxTreeWidget::categoryViewAt(int idx) const
       
   137 {
       
   138     WidgetBoxCategoryListView *rc = 0;
       
   139     if (QTreeWidgetItem *cat_item = topLevelItem(idx))
       
   140         if (QTreeWidgetItem *embedItem = cat_item->child(0))
       
   141             rc = qobject_cast<WidgetBoxCategoryListView*>(itemWidget(embedItem, 0));
       
   142     Q_ASSERT(rc);
       
   143     return rc;
       
   144 }
       
   145 
       
   146 void WidgetBoxTreeWidget::saveExpandedState() const
       
   147 {
       
   148     QStringList closedCategories;
       
   149     if (const int numCategories = categoryCount()) {
       
   150         for (int i = 0; i < numCategories; ++i) {
       
   151             const QTreeWidgetItem *cat_item = topLevelItem(i);
       
   152             if (!isItemExpanded(cat_item))
       
   153                 closedCategories.append(cat_item->text(0));
       
   154         }
       
   155     }
       
   156     QDesignerSettingsInterface *settings = m_core->settingsManager();
       
   157     settings->beginGroup(QLatin1String(widgetBoxRootElementC));
       
   158     settings->setValue(QLatin1String("Closed categories"), closedCategories);
       
   159     settings->setValue(QLatin1String("View mode"), m_iconMode);
       
   160     settings->endGroup();
       
   161 }
       
   162 
       
   163 void  WidgetBoxTreeWidget::restoreExpandedState()
       
   164 {
       
   165     typedef QSet<QString> StringSet;
       
   166     QDesignerSettingsInterface *settings = m_core->settingsManager();
       
   167     m_iconMode = settings->value(QLatin1String("WidgetBox/View mode")).toBool();
       
   168     updateViewMode();
       
   169     const StringSet closedCategories = settings->value(QLatin1String("WidgetBox/Closed categories"), QStringList()).toStringList().toSet();
       
   170     expandAll();
       
   171     if (closedCategories.empty())
       
   172         return;
       
   173 
       
   174     if (const int numCategories = categoryCount()) {
       
   175         for (int i = 0; i < numCategories; ++i) {
       
   176             QTreeWidgetItem *item = topLevelItem(i);
       
   177             if (closedCategories.contains(item->text(0)))
       
   178                 item->setExpanded(false);
       
   179             }
       
   180     }
       
   181 }
       
   182 
       
   183 WidgetBoxTreeWidget::~WidgetBoxTreeWidget()
       
   184 {
       
   185     saveExpandedState();
       
   186 }
       
   187 
       
   188 void WidgetBoxTreeWidget::setFileName(const QString &file_name)
       
   189 {
       
   190     m_file_name = file_name;
       
   191 }
       
   192 
       
   193 QString WidgetBoxTreeWidget::fileName() const
       
   194 {
       
   195     return m_file_name;
       
   196 }
       
   197 
       
   198 bool WidgetBoxTreeWidget::save()
       
   199 {
       
   200     if (fileName().isEmpty())
       
   201         return false;
       
   202 
       
   203     QFile file(fileName());
       
   204     if (!file.open(QIODevice::WriteOnly))
       
   205         return false;
       
   206 
       
   207     CategoryList cat_list;
       
   208     const int count = categoryCount();
       
   209     for (int i = 0; i < count; ++i)
       
   210         cat_list.append(category(i));
       
   211 
       
   212     QXmlStreamWriter writer(&file);
       
   213     writer.setAutoFormatting(true);
       
   214     writer.setAutoFormattingIndent(1);
       
   215     writer.writeStartDocument();
       
   216     writeCategories(writer, cat_list);
       
   217     writer.writeEndDocument();
       
   218 
       
   219     return true;
       
   220 }
       
   221 
       
   222 void WidgetBoxTreeWidget::slotSave()
       
   223 {
       
   224     save();
       
   225 }
       
   226 
       
   227 void WidgetBoxTreeWidget::handleMousePress(QTreeWidgetItem *item)
       
   228 {
       
   229     if (item == 0)
       
   230         return;
       
   231 
       
   232     if (QApplication::mouseButtons() != Qt::LeftButton)
       
   233         return;
       
   234 
       
   235     if (item->parent() == 0) {
       
   236         setItemExpanded(item, !isItemExpanded(item));
       
   237         return;
       
   238     }
       
   239 }
       
   240 
       
   241 int WidgetBoxTreeWidget::ensureScratchpad()
       
   242 {
       
   243     const int existingIndex = indexOfScratchpad();
       
   244     if (existingIndex != -1)
       
   245          return existingIndex;
       
   246 
       
   247     QTreeWidgetItem *scratch_item = new QTreeWidgetItem(this);
       
   248     scratch_item->setText(0, tr("Scratchpad"));
       
   249     setTopLevelRole(SCRATCHPAD_ITEM, scratch_item);
       
   250     addCategoryView(scratch_item, false); // Scratchpad in list mode.
       
   251     return categoryCount() - 1;
       
   252 }
       
   253 
       
   254 WidgetBoxCategoryListView *WidgetBoxTreeWidget::addCategoryView(QTreeWidgetItem *parent, bool iconMode)
       
   255 {
       
   256     QTreeWidgetItem *embed_item = new QTreeWidgetItem(parent);
       
   257     embed_item->setFlags(Qt::ItemIsEnabled);
       
   258     WidgetBoxCategoryListView *categoryView = new WidgetBoxCategoryListView(m_core, this);
       
   259     categoryView->setViewMode(iconMode ? QListView::IconMode : QListView::ListMode);
       
   260     connect(categoryView, SIGNAL(scratchPadChanged()), this, SLOT(slotSave()));
       
   261     connect(categoryView, SIGNAL(pressed(QString,QString,QPoint)), this, SIGNAL(pressed(QString,QString,QPoint)));
       
   262     connect(categoryView, SIGNAL(itemRemoved()), this, SLOT(slotScratchPadItemDeleted()));
       
   263     connect(categoryView, SIGNAL(lastItemRemoved()), this, SLOT(slotLastScratchPadItemDeleted()));
       
   264     setItemWidget(embed_item, 0, categoryView);
       
   265     return categoryView;
       
   266 }
       
   267 
       
   268 int WidgetBoxTreeWidget::indexOfScratchpad() const
       
   269 {
       
   270     if (const int numTopLevels =  topLevelItemCount()) {
       
   271         for (int i = numTopLevels - 1; i >= 0; --i) {
       
   272             if (topLevelRole(topLevelItem(i)) == SCRATCHPAD_ITEM)
       
   273                 return i;
       
   274         }
       
   275     }
       
   276     return -1;
       
   277 }
       
   278 
       
   279 int WidgetBoxTreeWidget::indexOfCategory(const QString &name) const
       
   280 {
       
   281     const int topLevelCount = topLevelItemCount();
       
   282     for (int i = 0; i < topLevelCount; ++i) {
       
   283         if (topLevelItem(i)->text(0) == name)
       
   284             return i;
       
   285     }
       
   286     return -1;
       
   287 }
       
   288 
       
   289 bool WidgetBoxTreeWidget::load(QDesignerWidgetBox::LoadMode loadMode)
       
   290 {
       
   291     switch (loadMode) {
       
   292     case QDesignerWidgetBox::LoadReplace:
       
   293         clear();
       
   294         break;
       
   295     case QDesignerWidgetBox::LoadCustomWidgetsOnly:
       
   296         addCustomCategories(true);
       
   297         updateGeometries();
       
   298         return true;
       
   299     default:
       
   300         break;
       
   301     }
       
   302 
       
   303     const QString name = fileName();
       
   304 
       
   305     QFile f(name);
       
   306     if (!f.open(QIODevice::ReadOnly)) // Might not exist at first startup
       
   307         return false;
       
   308 
       
   309     const QString contents = QString::fromUtf8(f.readAll());
       
   310     return loadContents(contents);
       
   311 }
       
   312 
       
   313 bool WidgetBoxTreeWidget::loadContents(const QString &contents)
       
   314 {
       
   315     QString errorMessage;
       
   316     CategoryList cat_list;
       
   317     if (!readCategories(m_file_name, contents, &cat_list, &errorMessage)) {
       
   318         qdesigner_internal::designerWarning(errorMessage);
       
   319         return false;
       
   320     }
       
   321 
       
   322     foreach(const Category &cat, cat_list)
       
   323         addCategory(cat);
       
   324 
       
   325     addCustomCategories(false);
       
   326     // Restore which items are expanded
       
   327     restoreExpandedState();
       
   328     return true;
       
   329 }
       
   330 
       
   331 void WidgetBoxTreeWidget::addCustomCategories(bool replace)
       
   332 {
       
   333     if (replace) {
       
   334         // clear out all existing custom widgets
       
   335         if (const int numTopLevels =  topLevelItemCount()) {
       
   336             for (int t = 0; t < numTopLevels ; ++t)
       
   337                 categoryViewAt(t)->removeCustomWidgets();
       
   338         }
       
   339     }
       
   340     // re-add
       
   341     const CategoryList customList = loadCustomCategoryList();
       
   342     const CategoryList::const_iterator cend = customList.constEnd();
       
   343     for (CategoryList::const_iterator it = customList.constBegin(); it != cend; ++it)
       
   344         addCategory(*it);
       
   345 }
       
   346 
       
   347 static inline QString msgXmlError(const QString &fileName, const QXmlStreamReader &r)
       
   348 {
       
   349     return QDesignerWidgetBox::tr("An error has been encountered at line %1 of %2: %3")
       
   350             .arg(r.lineNumber()).arg(fileName, r.errorString());
       
   351 }
       
   352 
       
   353 bool WidgetBoxTreeWidget::readCategories(const QString &fileName, const QString &contents,
       
   354                                        CategoryList *cats, QString *errorMessage)
       
   355 {
       
   356     // Read widget box XML:
       
   357     //
       
   358     //<widgetbox version="4.5">
       
   359     // <category name="Layouts">
       
   360     //  <categoryentry name="Vertical Layout" icon="win/editvlayout.png" type="default">
       
   361     //   <widget class="QListWidget" ...>
       
   362     // ...
       
   363 
       
   364     QXmlStreamReader reader(contents);
       
   365 
       
   366 
       
   367     // Entries of category with name="invisible" should be ignored
       
   368     bool ignoreEntries = false;
       
   369 
       
   370     while (!reader.atEnd()) {
       
   371         switch (reader.readNext()) {
       
   372         case QXmlStreamReader::StartElement: {
       
   373             const QStringRef tag = reader.name();
       
   374             if (tag == QLatin1String(widgetBoxRootElementC)) {
       
   375                 //<widgetbox version="4.5">
       
   376                 continue;
       
   377             }
       
   378             if (tag == QLatin1String(categoryElementC)) {
       
   379                 // <category name="Layouts">
       
   380                 const QXmlStreamAttributes attributes = reader.attributes();
       
   381                 const QString categoryName = attributes.value(QLatin1String(nameAttributeC)).toString();
       
   382                 if (categoryName == QLatin1String(invisibleNameC)) {
       
   383                     ignoreEntries = true;
       
   384                 } else {
       
   385                     Category category(categoryName);
       
   386                     if (attributes.value(QLatin1String(typeAttributeC)) == QLatin1String(scratchPadValueC))
       
   387                         category.setType(Category::Scratchpad);
       
   388                     cats->push_back(category);
       
   389                 }
       
   390                 continue;
       
   391             }
       
   392             if (tag == QLatin1String(categoryEntryElementC)) {
       
   393                 //  <categoryentry name="Vertical Layout" icon="win/editvlayout.png" type="default">
       
   394                 if (!ignoreEntries) {
       
   395                     QXmlStreamAttributes attr = reader.attributes();
       
   396                     const QString widgetName = attr.value(QLatin1String(nameAttributeC)).toString();
       
   397                     const QString widgetIcon = attr.value(QLatin1String(iconAttributeC)).toString();
       
   398                     const WidgetBoxTreeWidget::Widget::Type widgetType =
       
   399                         attr.value(QLatin1String(typeAttributeC)).toString()
       
   400                             == QLatin1String(customValueC) ?
       
   401                         WidgetBoxTreeWidget::Widget::Custom :
       
   402                         WidgetBoxTreeWidget::Widget::Default;
       
   403 
       
   404                     Widget w;
       
   405                     w.setName(widgetName);
       
   406                     w.setIconName(widgetIcon);
       
   407                     w.setType(widgetType);
       
   408                     if (!readWidget(&w, contents, reader))
       
   409                         continue;
       
   410 
       
   411                     cats->back().addWidget(w);
       
   412                 } // ignoreEntries
       
   413                 continue;
       
   414             }
       
   415             break;
       
   416         }
       
   417         case QXmlStreamReader::EndElement: {
       
   418            const QStringRef tag = reader.name();
       
   419            if (tag == QLatin1String(widgetBoxRootElementC)) {
       
   420                continue;
       
   421            }
       
   422            if (tag == QLatin1String(categoryElementC)) {
       
   423                ignoreEntries = false;
       
   424                continue;
       
   425            }
       
   426            if (tag == QLatin1String(categoryEntryElementC)) {
       
   427                continue;
       
   428            }
       
   429            break;
       
   430         }
       
   431         default: break;
       
   432         }
       
   433     }
       
   434 
       
   435     if (reader.hasError()) {
       
   436         *errorMessage = msgXmlError(fileName, reader);
       
   437         return false;
       
   438     }
       
   439 
       
   440     return true;
       
   441 }
       
   442 
       
   443 /*!
       
   444  * Read out a widget within a category. This can either be
       
   445  * enclosed in a <ui> element or a (legacy) <widget> element which may
       
   446  * contain nested <widget> elements.
       
   447  *
       
   448  * Examples:
       
   449  *
       
   450  * <ui language="c++">
       
   451  *  <widget class="MultiPageWidget" name="multipagewidget"> ... </widget>
       
   452  *  <customwidgets>...</customwidgets>
       
   453  * <ui>
       
   454  *
       
   455  * or
       
   456  *
       
   457  * <widget>
       
   458  *   <widget> ... </widget>
       
   459  *   ...
       
   460  * <widget>
       
   461  *
       
   462  * Returns true on success, false if end was reached or an error has been encountered
       
   463  * in which case the reader has its error flag set. If successful, the current item
       
   464  * of the reader will be the closing element (</ui> or </widget>)
       
   465  */
       
   466 bool WidgetBoxTreeWidget::readWidget(Widget *w, const QString &xml, QXmlStreamReader &r)
       
   467 {
       
   468     qint64 startTagPosition =0, endTagPosition = 0;
       
   469 
       
   470     int nesting = 0;
       
   471     bool endEncountered = false;
       
   472     bool parsedWidgetTag = false;
       
   473     QString outmostElement;
       
   474     while (!endEncountered) {
       
   475         const qint64 currentPosition = r.characterOffset();
       
   476         switch(r.readNext()) {
       
   477         case QXmlStreamReader::StartElement:
       
   478             if (nesting++ == 0) {
       
   479                 // First element must be <ui> or (legacy) <widget>
       
   480                 const QStringRef name = r.name();
       
   481                 if (name == QLatin1String(uiElementC)) {
       
   482                     startTagPosition = currentPosition;
       
   483                 } else {
       
   484                     if (name == QLatin1String(widgetElementC)) {
       
   485                         startTagPosition = currentPosition;
       
   486                         parsedWidgetTag = true;
       
   487                     } else {
       
   488                         r.raiseError(QDesignerWidgetBox::tr("Unexpected element <%1> encountered when parsing for <widget> or <ui>").arg(name.toString()));
       
   489                         return false;
       
   490                     }
       
   491                 }
       
   492             } else {
       
   493                 // We are within <ui> looking for the first <widget> tag
       
   494                 if (!parsedWidgetTag && r.name() == QLatin1String(widgetElementC)) {
       
   495                     parsedWidgetTag = true;
       
   496                 }
       
   497             }
       
   498             break;
       
   499         case QXmlStreamReader::EndElement:
       
   500             // Reached end of widget?
       
   501             if (--nesting == 0)  {
       
   502                 endTagPosition = r.characterOffset();
       
   503                 endEncountered = true;
       
   504             }
       
   505             break;
       
   506         case QXmlStreamReader::EndDocument:
       
   507             r.raiseError(QDesignerWidgetBox::tr("Unexpected end of file encountered when parsing widgets."));
       
   508             return false;
       
   509         case QXmlStreamReader::Invalid:
       
   510             return false;
       
   511         default:
       
   512             break;
       
   513         }
       
   514     }
       
   515     if (!parsedWidgetTag) {
       
   516         r.raiseError(QDesignerWidgetBox::tr("A widget element could not be found."));
       
   517         return false;
       
   518     }
       
   519     // Oddity: Startposition is 1 off
       
   520     QString widgetXml = xml.mid(startTagPosition, endTagPosition - startTagPosition);
       
   521     const QChar lessThan = QLatin1Char('<');
       
   522     if (!widgetXml.startsWith(lessThan))
       
   523         widgetXml.prepend(lessThan);
       
   524     w->setDomXml(widgetXml);
       
   525     return true;
       
   526 }
       
   527 
       
   528 void WidgetBoxTreeWidget::writeCategories(QXmlStreamWriter &writer, const CategoryList &cat_list) const
       
   529 {
       
   530     const QString widgetbox = QLatin1String(widgetBoxRootElementC);
       
   531     const QString name = QLatin1String(nameAttributeC);
       
   532     const QString type = QLatin1String(typeAttributeC);
       
   533     const QString icon = QLatin1String(iconAttributeC);
       
   534     const QString defaultType = QLatin1String(defaultTypeValueC);
       
   535     const QString category = QLatin1String(categoryElementC);
       
   536     const QString categoryEntry = QLatin1String(categoryEntryElementC);
       
   537     const QString iconPrefix = QLatin1String(iconPrefixC);
       
   538     const QString widgetTag = QLatin1String(widgetElementC);
       
   539 
       
   540     //
       
   541     // <widgetbox>
       
   542     //   <category name="Layouts">
       
   543     //     <categoryEntry name="Vertical Layout" type="default" icon="win/editvlayout.png">
       
   544     //       <ui>
       
   545     //        ...
       
   546     //       </ui>
       
   547     //     </categoryEntry>
       
   548     //     ...
       
   549     //   </category>
       
   550     //   ...
       
   551     // </widgetbox>
       
   552     //
       
   553 
       
   554     writer.writeStartElement(widgetbox);
       
   555 
       
   556     foreach (const Category &cat, cat_list) {
       
   557         writer.writeStartElement(category);
       
   558         writer.writeAttribute(name, cat.name());
       
   559         if (cat.type() == Category::Scratchpad)
       
   560             writer.writeAttribute(type, QLatin1String(scratchPadValueC));
       
   561 
       
   562         const int widgetCount = cat.widgetCount();
       
   563         for (int i = 0; i < widgetCount; ++i) {
       
   564            const  Widget wgt = cat.widget(i);
       
   565             if (wgt.type() == Widget::Custom)
       
   566                 continue;
       
   567 
       
   568             writer.writeStartElement(categoryEntry);
       
   569             writer.writeAttribute(name, wgt.name());
       
   570             if (!wgt.iconName().startsWith(iconPrefix))
       
   571                 writer.writeAttribute(icon, wgt.iconName());
       
   572             writer.writeAttribute(type, defaultType);
       
   573 
       
   574             const DomUI *domUI = QDesignerWidgetBox::xmlToUi(wgt.name(), WidgetBoxCategoryListView::widgetDomXml(wgt), false);
       
   575             if (domUI) {
       
   576                 domUI->write(writer);
       
   577                 delete domUI;
       
   578             }
       
   579 
       
   580             writer.writeEndElement(); // categoryEntry
       
   581         }
       
   582         writer.writeEndElement(); // categoryEntry
       
   583     }
       
   584 
       
   585     writer.writeEndElement(); // widgetBox
       
   586 }
       
   587 
       
   588 static int findCategory(const QString &name, const WidgetBoxTreeWidget::CategoryList &list)
       
   589 {
       
   590     int idx = 0;
       
   591     foreach (const WidgetBoxTreeWidget::Category &cat, list) {
       
   592         if (cat.name() == name)
       
   593             return idx;
       
   594         ++idx;
       
   595     }
       
   596     return -1;
       
   597 }
       
   598 
       
   599 static inline bool isValidIcon(const QIcon &icon)
       
   600 {
       
   601     if (!icon.isNull()) {
       
   602         const QList<QSize> availableSizes = icon.availableSizes();
       
   603         if (!availableSizes.empty())
       
   604             return !availableSizes.front().isEmpty();
       
   605     }
       
   606     return false;
       
   607 }
       
   608 
       
   609 WidgetBoxTreeWidget::CategoryList WidgetBoxTreeWidget::loadCustomCategoryList() const
       
   610 {
       
   611     CategoryList result;
       
   612 
       
   613     const QDesignerPluginManager *pm = m_core->pluginManager();
       
   614     const QDesignerPluginManager::CustomWidgetList customWidgets = pm->registeredCustomWidgets();
       
   615     if (customWidgets.empty())
       
   616         return result;
       
   617 
       
   618     static const QString customCatName = tr("Custom Widgets");
       
   619 
       
   620     const QString invisible = QLatin1String(invisibleNameC);
       
   621     const QString iconPrefix = QLatin1String(iconPrefixC);
       
   622 
       
   623     foreach(QDesignerCustomWidgetInterface *c, customWidgets) {
       
   624         const QString dom_xml = c->domXml();
       
   625         if (dom_xml.isEmpty())
       
   626             continue;
       
   627 
       
   628         const QString pluginName = c->name();
       
   629         const QDesignerCustomWidgetData data = pm->customWidgetData(c);
       
   630         QString displayName = data.xmlDisplayName();
       
   631         if (displayName.isEmpty())
       
   632             displayName = pluginName;
       
   633 
       
   634         QString cat_name = c->group();
       
   635         if (cat_name.isEmpty())
       
   636             cat_name = customCatName;
       
   637         else if (cat_name == invisible)
       
   638             continue;
       
   639 
       
   640         int idx = findCategory(cat_name, result);
       
   641         if (idx == -1) {
       
   642             result.append(Category(cat_name));
       
   643             idx = result.size() - 1;
       
   644         }
       
   645         Category &cat = result[idx];
       
   646 
       
   647         const QIcon icon = c->icon();
       
   648 
       
   649         QString icon_name;
       
   650         if (isValidIcon(icon)) {
       
   651             icon_name = iconPrefix;
       
   652             icon_name += pluginName;
       
   653             m_pluginIcons.insert(icon_name, icon);
       
   654         } else {
       
   655             icon_name = QLatin1String(qtLogoC);
       
   656         }
       
   657 
       
   658         cat.addWidget(Widget(displayName, dom_xml, icon_name, Widget::Custom));
       
   659     }
       
   660 
       
   661     return result;
       
   662 }
       
   663 
       
   664 void WidgetBoxTreeWidget::adjustSubListSize(QTreeWidgetItem *cat_item)
       
   665 {
       
   666     QTreeWidgetItem *embedItem = cat_item->child(0);
       
   667     if (embedItem == 0)
       
   668         return;
       
   669 
       
   670     WidgetBoxCategoryListView *list_widget = static_cast<WidgetBoxCategoryListView*>(itemWidget(embedItem, 0));
       
   671     list_widget->setFixedWidth(header()->width());
       
   672     list_widget->doItemsLayout();
       
   673     const int height = qMax(list_widget->contentsSize().height() ,1);
       
   674     list_widget->setFixedHeight(height);
       
   675     embedItem->setSizeHint(0, QSize(-1, height - 1));
       
   676 }
       
   677 
       
   678 int WidgetBoxTreeWidget::categoryCount() const
       
   679 {
       
   680     return topLevelItemCount();
       
   681 }
       
   682 
       
   683 WidgetBoxTreeWidget::Category WidgetBoxTreeWidget::category(int cat_idx) const
       
   684 {
       
   685     if (cat_idx >= topLevelItemCount())
       
   686         return Category();
       
   687 
       
   688     QTreeWidgetItem *cat_item = topLevelItem(cat_idx);
       
   689 
       
   690     QTreeWidgetItem *embedItem = cat_item->child(0);
       
   691     WidgetBoxCategoryListView *categoryView = static_cast<WidgetBoxCategoryListView*>(itemWidget(embedItem, 0));
       
   692 
       
   693     Category result = categoryView->category();
       
   694     result.setName(cat_item->text(0));
       
   695 
       
   696     switch (topLevelRole(cat_item)) {
       
   697     case SCRATCHPAD_ITEM:
       
   698         result.setType(Category::Scratchpad);
       
   699         break;
       
   700     default:
       
   701         result.setType(Category::Default);
       
   702         break;
       
   703     }
       
   704     return result;
       
   705 }
       
   706 
       
   707 void WidgetBoxTreeWidget::addCategory(const Category &cat)
       
   708 {
       
   709     if (cat.widgetCount() == 0)
       
   710         return;
       
   711 
       
   712     const bool isScratchPad = cat.type() == Category::Scratchpad;
       
   713     WidgetBoxCategoryListView *categoryView;
       
   714     QTreeWidgetItem *cat_item;
       
   715 
       
   716     if (isScratchPad) {
       
   717         const int idx = ensureScratchpad();
       
   718         categoryView = categoryViewAt(idx);
       
   719         cat_item = topLevelItem(idx);
       
   720     } else {
       
   721         const int existingIndex = indexOfCategory(cat.name());
       
   722         if (existingIndex == -1) {
       
   723             cat_item = new QTreeWidgetItem();
       
   724             cat_item->setText(0, cat.name());
       
   725             setTopLevelRole(NORMAL_ITEM, cat_item);
       
   726             // insert before scratchpad
       
   727             const int scratchPadIndex = indexOfScratchpad();
       
   728             if (scratchPadIndex == -1) {
       
   729                 addTopLevelItem(cat_item);
       
   730             } else {
       
   731                 insertTopLevelItem(scratchPadIndex, cat_item);
       
   732             }
       
   733             setItemExpanded(cat_item, true);
       
   734             categoryView = addCategoryView(cat_item, m_iconMode);
       
   735         } else {
       
   736             categoryView = categoryViewAt(existingIndex);
       
   737             cat_item = topLevelItem(existingIndex);
       
   738         }
       
   739     }
       
   740     // The same categories are read from the file $HOME, avoid duplicates
       
   741     const int widgetCount = cat.widgetCount();
       
   742     for (int i = 0; i < widgetCount; ++i) {
       
   743         const Widget w = cat.widget(i);
       
   744         if (!categoryView->containsWidget(w.name()))
       
   745             categoryView->addWidget(w, iconForWidget(w.iconName()), isScratchPad);
       
   746     }
       
   747     adjustSubListSize(cat_item);
       
   748 }
       
   749 
       
   750 void WidgetBoxTreeWidget::removeCategory(int cat_idx)
       
   751 {
       
   752     if (cat_idx >= topLevelItemCount())
       
   753         return;
       
   754     delete takeTopLevelItem(cat_idx);
       
   755 }
       
   756 
       
   757 int WidgetBoxTreeWidget::widgetCount(int cat_idx) const
       
   758 {
       
   759     if (cat_idx >= topLevelItemCount())
       
   760         return 0;
       
   761     // SDK functions want unfiltered access
       
   762     return categoryViewAt(cat_idx)->count(WidgetBoxCategoryListView::UnfilteredAccess);
       
   763 }
       
   764 
       
   765 WidgetBoxTreeWidget::Widget WidgetBoxTreeWidget::widget(int cat_idx, int wgt_idx) const
       
   766 {
       
   767     if (cat_idx >= topLevelItemCount())
       
   768         return Widget();
       
   769     // SDK functions want unfiltered access
       
   770     WidgetBoxCategoryListView *categoryView = categoryViewAt(cat_idx);
       
   771     return categoryView->widgetAt(WidgetBoxCategoryListView::UnfilteredAccess, wgt_idx);
       
   772 }
       
   773 
       
   774 void WidgetBoxTreeWidget::addWidget(int cat_idx, const Widget &wgt)
       
   775 {
       
   776     if (cat_idx >= topLevelItemCount())
       
   777         return;
       
   778 
       
   779     QTreeWidgetItem *cat_item = topLevelItem(cat_idx);
       
   780     WidgetBoxCategoryListView *categoryView = categoryViewAt(cat_idx);
       
   781 
       
   782     const bool scratch = topLevelRole(cat_item) == SCRATCHPAD_ITEM;
       
   783     categoryView->addWidget(wgt, iconForWidget(wgt.iconName()), scratch);
       
   784     adjustSubListSize(cat_item);
       
   785 }
       
   786 
       
   787 void WidgetBoxTreeWidget::removeWidget(int cat_idx, int wgt_idx)
       
   788 {
       
   789     if (cat_idx >= topLevelItemCount())
       
   790         return;
       
   791 
       
   792     WidgetBoxCategoryListView *categoryView = categoryViewAt(cat_idx);
       
   793 
       
   794     // SDK functions want unfiltered access
       
   795     const WidgetBoxCategoryListView::AccessMode am = WidgetBoxCategoryListView::UnfilteredAccess;
       
   796     if (wgt_idx >= categoryView->count(am))
       
   797         return;
       
   798 
       
   799     categoryView->removeRow(am, wgt_idx);
       
   800 }
       
   801 
       
   802 void WidgetBoxTreeWidget::slotScratchPadItemDeleted()
       
   803 {
       
   804     const int scratch_idx = indexOfScratchpad();
       
   805     QTreeWidgetItem *scratch_item = topLevelItem(scratch_idx);
       
   806     adjustSubListSize(scratch_item);
       
   807     save();
       
   808 }
       
   809 
       
   810 void WidgetBoxTreeWidget::slotLastScratchPadItemDeleted()
       
   811 {
       
   812     // Remove the scratchpad in the next idle loop
       
   813     if (!m_scratchPadDeleteTimer) {
       
   814         m_scratchPadDeleteTimer = new QTimer(this);
       
   815         m_scratchPadDeleteTimer->setSingleShot(true);
       
   816         m_scratchPadDeleteTimer->setInterval(0);
       
   817         connect(m_scratchPadDeleteTimer, SIGNAL(timeout()), this, SLOT(deleteScratchpad()));
       
   818     }
       
   819     if (!m_scratchPadDeleteTimer->isActive())
       
   820         m_scratchPadDeleteTimer->start();
       
   821 }
       
   822 
       
   823 void WidgetBoxTreeWidget::deleteScratchpad()
       
   824 {
       
   825     const int idx = indexOfScratchpad();
       
   826     if (idx == -1)
       
   827         return;
       
   828     delete takeTopLevelItem(idx);
       
   829     save();
       
   830 }
       
   831 
       
   832 
       
   833 void WidgetBoxTreeWidget::slotListMode()
       
   834 {
       
   835     m_iconMode = false;
       
   836     updateViewMode();
       
   837 }
       
   838 
       
   839 void WidgetBoxTreeWidget::slotIconMode()
       
   840 {
       
   841     m_iconMode = true;
       
   842     updateViewMode();
       
   843 }
       
   844 
       
   845 void WidgetBoxTreeWidget::updateViewMode()
       
   846 {
       
   847     if (const int numTopLevels = topLevelItemCount()) {
       
   848         for (int i = numTopLevels - 1; i >= 0; --i) {
       
   849             QTreeWidgetItem *topLevel = topLevelItem(i);
       
   850             // Scratch pad stays in list mode.
       
   851             const QListView::ViewMode viewMode  = m_iconMode && (topLevelRole(topLevel) != SCRATCHPAD_ITEM) ? QListView::IconMode : QListView::ListMode;
       
   852             WidgetBoxCategoryListView *categoryView = categoryViewAt(i);
       
   853             if (viewMode != categoryView->viewMode()) {
       
   854                 categoryView->setViewMode(viewMode);
       
   855                 adjustSubListSize(topLevelItem(i));
       
   856             }
       
   857         }
       
   858     }
       
   859 
       
   860     updateGeometries();
       
   861 }
       
   862 
       
   863 void WidgetBoxTreeWidget::resizeEvent(QResizeEvent *e)
       
   864 {
       
   865     QTreeWidget::resizeEvent(e);
       
   866     if (const int numTopLevels = topLevelItemCount()) {
       
   867         for (int i = numTopLevels - 1; i >= 0; --i)
       
   868             adjustSubListSize(topLevelItem(i));
       
   869     }
       
   870 }
       
   871 
       
   872 void WidgetBoxTreeWidget::contextMenuEvent(QContextMenuEvent *e)
       
   873 {
       
   874     QTreeWidgetItem *item = itemAt(e->pos());
       
   875 
       
   876     const bool scratchpad_menu = item != 0
       
   877                             && item->parent() != 0
       
   878                             && topLevelRole(item->parent()) ==  SCRATCHPAD_ITEM;
       
   879 
       
   880     QMenu menu;
       
   881     menu.addAction(tr("Expand all"), this, SLOT(expandAll()));
       
   882     menu.addAction(tr("Collapse all"), this, SLOT(collapseAll()));
       
   883     menu.addSeparator();
       
   884 
       
   885     QAction *listModeAction = menu.addAction(tr("List View"));
       
   886     QAction *iconModeAction = menu.addAction(tr("Icon View"));
       
   887     listModeAction->setCheckable(true);
       
   888     iconModeAction->setCheckable(true);
       
   889     QActionGroup *viewModeGroup = new QActionGroup(&menu);
       
   890     viewModeGroup->addAction(listModeAction);
       
   891     viewModeGroup->addAction(iconModeAction);
       
   892     if (m_iconMode)
       
   893         iconModeAction->setChecked(true);
       
   894     else
       
   895         listModeAction->setChecked(true);
       
   896     connect(listModeAction, SIGNAL(triggered()), SLOT(slotListMode()));
       
   897     connect(iconModeAction, SIGNAL(triggered()), SLOT(slotIconMode()));
       
   898 
       
   899     if (scratchpad_menu) {
       
   900         menu.addSeparator();
       
   901         menu.addAction(tr("Remove"), itemWidget(item, 0), SLOT(removeCurrentItem()));
       
   902         if (!m_iconMode)
       
   903             menu.addAction(tr("Edit name"), itemWidget(item, 0), SLOT(editCurrentItem()));
       
   904     }
       
   905     e->accept();
       
   906     menu.exec(mapToGlobal(e->pos()));
       
   907 }
       
   908 
       
   909 void WidgetBoxTreeWidget::dropWidgets(const QList<QDesignerDnDItemInterface*> &item_list)
       
   910 {
       
   911     QTreeWidgetItem *scratch_item = 0;
       
   912     WidgetBoxCategoryListView *categoryView = 0;
       
   913     bool added = false;
       
   914 
       
   915     foreach (QDesignerDnDItemInterface *item, item_list) {
       
   916         QWidget *w = item->widget();
       
   917         if (w == 0)
       
   918             continue;
       
   919 
       
   920         DomUI *dom_ui = item->domUi();
       
   921         if (dom_ui == 0)
       
   922             continue;
       
   923 
       
   924         const int scratch_idx = ensureScratchpad();
       
   925         scratch_item = topLevelItem(scratch_idx);
       
   926         categoryView = categoryViewAt(scratch_idx);
       
   927 
       
   928         // Temporarily remove the fake toplevel in-between
       
   929         DomWidget *fakeTopLevel = dom_ui->takeElementWidget();
       
   930         DomWidget *firstWidget = 0;
       
   931         if (fakeTopLevel && !fakeTopLevel->elementWidget().isEmpty()) {
       
   932             firstWidget = fakeTopLevel->elementWidget().first();
       
   933             dom_ui->setElementWidget(firstWidget);
       
   934         } else {
       
   935             dom_ui->setElementWidget(fakeTopLevel);
       
   936             continue;
       
   937         }
       
   938 
       
   939         // Serialize to XML
       
   940         QString xml;
       
   941         {
       
   942             QXmlStreamWriter writer(&xml);
       
   943             writer.setAutoFormatting(true);
       
   944             writer.setAutoFormattingIndent(1);
       
   945             writer.writeStartDocument();
       
   946             dom_ui->write(writer);
       
   947             writer.writeEndDocument();
       
   948         }
       
   949 
       
   950         // Insert fake toplevel again
       
   951         dom_ui->takeElementWidget();
       
   952         dom_ui->setElementWidget(fakeTopLevel);
       
   953 
       
   954         const Widget wgt = Widget(w->objectName(), xml);
       
   955         categoryView->addWidget(wgt, iconForWidget(wgt.iconName()), true);
       
   956         setItemExpanded(scratch_item, true);
       
   957         added = true;
       
   958     }
       
   959 
       
   960     if (added) {
       
   961         save();
       
   962         QApplication::setActiveWindow(this);
       
   963         // Is the new item visible in filtered mode?
       
   964         const WidgetBoxCategoryListView::AccessMode am = WidgetBoxCategoryListView::FilteredAccess;
       
   965         if (const int count = categoryView->count(am))
       
   966             categoryView->setCurrentItem(am, count - 1);
       
   967         categoryView->adjustSize(); // XXX
       
   968         adjustSubListSize(scratch_item);
       
   969     }
       
   970 }
       
   971 
       
   972 void WidgetBoxTreeWidget::filter(const QString &f)
       
   973 {
       
   974     const bool empty = f.isEmpty();
       
   975     const QRegExp re = empty ? QRegExp() : QRegExp(f, Qt::CaseInsensitive, QRegExp::FixedString);
       
   976     const int numTopLevels = topLevelItemCount();
       
   977     bool changed = false;
       
   978     for (int i = 0; i < numTopLevels; i++) {
       
   979         QTreeWidgetItem *tl = topLevelItem(i);
       
   980         WidgetBoxCategoryListView *categoryView = categoryViewAt(i);
       
   981         // Anything changed? -> Enable the category
       
   982         const int oldCount = categoryView->count(WidgetBoxCategoryListView::FilteredAccess);
       
   983         categoryView->filter(re);
       
   984         const int newCount = categoryView->count(WidgetBoxCategoryListView::FilteredAccess);
       
   985         if (oldCount != newCount) {
       
   986             changed = true;
       
   987             const bool categoryEnabled = newCount > 0 || empty;
       
   988             if (categoryEnabled) {
       
   989                 categoryView->adjustSize();
       
   990                 adjustSubListSize(tl);
       
   991             }
       
   992             setRowHidden (i, QModelIndex(), !categoryEnabled);
       
   993         }
       
   994     }
       
   995     if (changed)
       
   996         updateGeometries();
       
   997 }
       
   998 
       
   999 }  // namespace qdesigner_internal
       
  1000 
       
  1001 QT_END_NAMESPACE