/tools/designer/src/components/widgetbox/widgetboxtreewidget.cpp

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