8#include <iconloader_p.h>
9#include <sheet_delegate_p.h>
10#include <QtDesigner/private/ui4_p.h>
11#include <qdesigner_utils_p.h>
12#include <pluginmanager_p.h>
15#include <QtDesigner/abstractformeditor.h>
16#include <QtDesigner/abstractdnditem.h>
17#include <QtDesigner/abstractsettings.h>
19#include <QtUiPlugin/customwidget.h>
21#include <QtWidgets/qapplication.h>
22#include <QtWidgets/qheaderview.h>
23#include <QtWidgets/qmenu.h>
24#include <QtWidgets/qscrollbar.h>
25#include <QtWidgets/qtreewidget.h>
27#include <QtGui/qaction.h>
28#include <QtGui/qactiongroup.h>
29#include <QtGui/qevent.h>
31#include <QtCore/qfile.h>
32#include <QtCore/qtimer.h>
33#include <QtCore/qdebug.h>
37using namespace Qt::StringLiterals;
57 item->setData(0, Qt::UserRole, QVariant(tlr));
62 return static_cast<TopLevelRole>(item->data(0, Qt::UserRole).toInt());
71 m_scratchPadDeleteTimer(
nullptr)
73 setFocusPolicy(Qt::NoFocus);
75 setRootIsDecorated(
false);
78 header()->setSectionResizeMode(QHeaderView::Stretch);
79 setTextElideMode(Qt::ElideMiddle);
80 setVerticalScrollMode(ScrollPerPixel);
82 setItemDelegate(
new SheetDelegate(
this,
this));
84 connect(
this, &QTreeWidget::itemPressed,
85 this, &WidgetBoxTreeWidget::handleMousePress);
90 if (iconName.isEmpty())
91 return qdesigner_internal::qtLogoIcon();
93 if (iconName.startsWith(iconPrefixC)) {
94 const auto it = m_pluginIcons.constFind(iconName);
95 if (it != m_pluginIcons.constEnd())
98 return createIconSet(iconName);
104 if (QTreeWidgetItem *cat_item = topLevelItem(idx))
105 if (QTreeWidgetItem *embedItem = cat_item->child(0))
106 rc = qobject_cast<WidgetBoxCategoryListView*>(itemWidget(embedItem, 0));
117 QStringList closedCategories;
119 for (
int i = 0; i < numCategories; ++i) {
120 const QTreeWidgetItem *cat_item = topLevelItem(i);
121 if (!cat_item->isExpanded())
122 closedCategories.append(cat_item->text(0));
125 QDesignerSettingsInterface *settings = m_core->settingsManager();
126 settings->beginGroup(widgetBoxSettingsGroupC);
127 settings->setValue(widgetBoxExpandedKeyC, closedCategories);
128 settings->setValue(widgetBoxViewModeKeyC, m_iconMode);
129 settings->endGroup();
134 using StringSet = QSet<QString>;
135 QDesignerSettingsInterface *settings = m_core->settingsManager();
136 const QString groupKey = widgetBoxSettingsGroupC + u'/';
137 m_iconMode = settings->value(groupKey + widgetBoxViewModeKeyC).toBool();
139 const auto &closedCategoryList = settings->value(groupKey + widgetBoxExpandedKeyC, QStringList()).toStringList();
140 const StringSet closedCategories(closedCategoryList.cbegin(), closedCategoryList.cend());
142 if (closedCategories.isEmpty())
146 for (
int i = 0; i < numCategories; ++i) {
147 QTreeWidgetItem *item = topLevelItem(i);
148 if (closedCategories.contains(item->text(0)))
149 item->setExpanded(
false);
161 m_file_name = file_name;
171 if (fileName().isEmpty())
174 QFile file(fileName());
175 if (!file.open(QIODevice::WriteOnly))
178 CategoryList cat_list;
180 for (
int i = 0; i < count; ++i)
181 cat_list.append(category(i));
183 QXmlStreamWriter writer(&file);
184 writer.setAutoFormatting(
true);
185 writer.setAutoFormattingIndent(1);
186 writer.writeStartDocument();
187 writeCategories(writer, cat_list);
188 writer.writeEndDocument();
203 if (QApplication::mouseButtons() != Qt::LeftButton)
206 if (item->parent() ==
nullptr) {
207 item->setExpanded(!item->isExpanded());
214 const int existingIndex = indexOfScratchpad();
215 if (existingIndex != -1)
216 return existingIndex;
218 QTreeWidgetItem *scratch_item =
new QTreeWidgetItem(
this);
219 scratch_item->setText(0, tr(
"Scratchpad"));
221 addCategoryView(scratch_item,
false);
227 QTreeWidgetItem *embed_item =
new QTreeWidgetItem(parent);
228 embed_item->setFlags(Qt::ItemIsEnabled);
230 categoryView->setViewMode(iconMode ? QListView::IconMode : QListView::ListMode);
231 connect(categoryView, &WidgetBoxCategoryListView::scratchPadChanged,
232 this, &WidgetBoxTreeWidget::slotSave);
233 connect(categoryView, &WidgetBoxCategoryListView::widgetBoxPressed,
234 this, &WidgetBoxTreeWidget::widgetBoxPressed);
239 setItemWidget(embed_item, 0, categoryView);
245 if (
const int numTopLevels = topLevelItemCount()) {
246 for (
int i = numTopLevels - 1; i >= 0; --i) {
256 const int topLevelCount = topLevelItemCount();
257 for (
int i = 0; i < topLevelCount; ++i) {
258 if (topLevelItem(i)->text(0) == name)
267 case QDesignerWidgetBox::LoadReplace:
270 case QDesignerWidgetBox::LoadCustomWidgetsOnly:
271 addCustomCategories(
true);
278 const QString name = fileName();
281 if (!f.open(QIODevice::ReadOnly))
284 const QString contents = QString::fromUtf8(f.readAll());
285 if (!loadContents(contents))
287 if (topLevelItemCount() > 0) {
290 const auto itemHeight = visualItemRect(topLevelItem(0)).height();
291 verticalScrollBar()->setSingleStep(itemHeight);
298 QString errorMessage;
299 CategoryList cat_list;
300 if (!readCategories(m_file_name, contents, &cat_list, &errorMessage)) {
305 for (
const Category &cat : std::as_const(cat_list))
308 addCustomCategories(
false);
310 restoreExpandedState();
318 if (
const int numTopLevels = topLevelItemCount()) {
319 for (
int t = 0; t < numTopLevels ; ++t)
324 const CategoryList customList = loadCustomCategoryList();
325 for (
const auto &c : customList)
329static inline QString
msgXmlError(
const QString &fileName,
const QXmlStreamReader &r)
331 return QDesignerWidgetBox::tr(
"An error has been encountered at line %1 of %2: %3")
332 .arg(r.lineNumber()).arg(fileName, r.errorString());
336 CategoryList *cats, QString *errorMessage)
346 QXmlStreamReader reader(contents);
350 bool ignoreEntries =
false;
352 while (!reader.atEnd()) {
353 switch (reader.readNext()) {
354 case QXmlStreamReader::StartElement: {
355 const auto tag = reader.name();
356 if (tag == widgetBoxRootElementC) {
360 if (tag == categoryElementC) {
362 const QXmlStreamAttributes attributes = reader.attributes();
363 const QString categoryName = attributes.value(wbNameAttributeC).toString();
364 if (categoryName == invisibleNameC) {
365 ignoreEntries =
true;
367 Category category(categoryName);
368 if (attributes.value(typeAttributeC) == scratchPadValueC)
369 category.setType(Category::Scratchpad);
370 cats->push_back(category);
374 if (tag == categoryEntryElementC) {
376 if (!ignoreEntries) {
377 QXmlStreamAttributes attr = reader.attributes();
378 const QString widgetName = attr.value(wbNameAttributeC).toString();
379 const QString widgetIcon = attr.value(iconAttributeC).toString();
380 const WidgetBoxTreeWidget::Widget::Type widgetType =
381 attr.value(typeAttributeC).toString()
383 WidgetBoxTreeWidget::Widget::Custom :
384 WidgetBoxTreeWidget::Widget::Default;
387 w.setName(widgetName);
388 w.setIconName(widgetIcon);
389 w.setType(widgetType);
390 if (!readWidget(&w, contents, reader))
393 cats->back().addWidget(w);
399 case QXmlStreamReader::EndElement: {
400 const auto tag = reader.name();
401 if (tag == widgetBoxRootElementC) {
404 if (tag == categoryElementC) {
405 ignoreEntries =
false;
408 if (tag == categoryEntryElementC) {
417 if (reader.hasError()) {
418 *errorMessage = msgXmlError(fileName, reader);
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
450 qint64 startTagPosition =0, endTagPosition = 0;
453 bool endEncountered =
false;
454 bool parsedWidgetTag =
false;
455 while (!endEncountered) {
456 const qint64 currentPosition = r.characterOffset();
457 switch(r.readNext()) {
458 case QXmlStreamReader::StartElement:
459 if (nesting++ == 0) {
461 const auto name = r.name();
462 if (name == uiElementC) {
463 startTagPosition = currentPosition;
465 if (name == wbWidgetElementC) {
466 startTagPosition = currentPosition;
467 parsedWidgetTag =
true;
469 r.raiseError(QDesignerWidgetBox::tr(
"Unexpected element <%1> encountered when parsing for <widget> or <ui>").arg(name.toString()));
475 if (!parsedWidgetTag && r.name() == wbWidgetElementC) {
476 parsedWidgetTag =
true;
480 case QXmlStreamReader::EndElement:
482 if (--nesting == 0) {
483 endTagPosition = r.characterOffset();
484 endEncountered =
true;
487 case QXmlStreamReader::EndDocument:
488 r.raiseError(QDesignerWidgetBox::tr(
"Unexpected end of file encountered when parsing widgets."));
490 case QXmlStreamReader::Invalid:
496 if (!parsedWidgetTag) {
497 r.raiseError(QDesignerWidgetBox::tr(
"A widget element could not be found."));
501 QString widgetXml = xml.mid(startTagPosition, endTagPosition - startTagPosition);
502 if (!widgetXml.startsWith(u'<'))
503 widgetXml.prepend(u'<');
504 w->setDomXml(widgetXml);
508void WidgetBoxTreeWidget::writeCategories(QXmlStreamWriter &writer,
const CategoryList &cat_list)
const
510 const QString widgetbox = widgetBoxRootElementC;
511 const QString name = wbNameAttributeC;
512 const QString type = typeAttributeC;
513 const QString icon = iconAttributeC;
514 const QString defaultType = defaultTypeValueC;
515 const QString category = categoryElementC;
516 const QString categoryEntry = categoryEntryElementC;
517 const QString iconPrefix = iconPrefixC;
533 writer.writeStartElement(widgetbox);
535 for (
const Category &cat : cat_list) {
536 writer.writeStartElement(category);
537 writer.writeAttribute(name, cat.name());
538 if (cat.type() == Category::Scratchpad)
539 writer.writeAttribute(type, scratchPadValueC);
541 const int widgetCount = cat.widgetCount();
542 for (
int i = 0; i < widgetCount; ++i) {
543 const Widget wgt = cat.widget(i);
544 if (wgt.type() == Widget::Custom)
547 writer.writeStartElement(categoryEntry);
548 writer.writeAttribute(name, wgt.name());
549 if (!wgt.iconName().startsWith(iconPrefix))
550 writer.writeAttribute(icon, wgt.iconName());
551 writer.writeAttribute(type, defaultType);
553 const DomUI *domUI = QDesignerWidgetBox::xmlToUi(wgt.name(), WidgetBoxCategoryListView::widgetDomXml(wgt),
false);
555 domUI->write(writer);
559 writer.writeEndElement();
561 writer.writeEndElement();
564 writer.writeEndElement();
567static int findCategory(
const QString &name,
const WidgetBoxTreeWidget::CategoryList &list)
570 for (
const WidgetBoxTreeWidget::Category &cat : list) {
571 if (cat.name() == name)
580 if (!icon.isNull()) {
581 const auto availableSizes = icon.availableSizes();
582 return !availableSizes.isEmpty() && !availableSizes.constFirst().isEmpty();
591 const QDesignerPluginManager *pm = m_core->pluginManager();
592 const QDesignerPluginManager::CustomWidgetList customWidgets = pm->registeredCustomWidgets();
593 if (customWidgets.isEmpty())
596 static const QString customCatName = tr(
"Custom Widgets");
598 const QString invisible = invisibleNameC;
599 const QString iconPrefix = iconPrefixC;
601 for (QDesignerCustomWidgetInterface *c : customWidgets) {
602 const QString dom_xml = c->domXml();
603 if (dom_xml.isEmpty())
606 const QString pluginName = c->name();
607 const QDesignerCustomWidgetData data = pm->customWidgetData(c);
608 QString displayName = data.xmlDisplayName();
609 if (displayName.isEmpty())
610 displayName = pluginName;
612 QString cat_name = c->group();
613 if (cat_name.isEmpty())
614 cat_name = customCatName;
615 else if (cat_name == invisible)
618 int idx = findCategory(cat_name, result);
620 result.append(Category(cat_name));
621 idx = result.size() - 1;
623 Category &cat = result[idx];
625 const QIcon icon = c->icon();
628 if (isValidIcon(icon)) {
629 icon_name = iconPrefix;
630 icon_name += pluginName;
631 m_pluginIcons.insert(icon_name, icon);
634 cat.addWidget(Widget(displayName, dom_xml, icon_name, Widget::Custom));
642 QTreeWidgetItem *embedItem = cat_item->child(0);
643 if (embedItem ==
nullptr)
647 list_widget->setFixedWidth(header()->width());
648 list_widget->doItemsLayout();
649 const int height = qMax(list_widget->contentsSize().height() ,1);
650 list_widget->setFixedHeight(height);
651 embedItem->setSizeHint(0, QSize(-1, height - 1));
656 return topLevelItemCount();
661 if (cat_idx >= topLevelItemCount())
664 QTreeWidgetItem *cat_item = topLevelItem(cat_idx);
666 QTreeWidgetItem *embedItem = cat_item->child(0);
669 Category result = categoryView->category();
670 result.setName(cat_item->text(0));
672 switch (topLevelRole(cat_item)) {
673 case SCRATCHPAD_ITEM:
674 result.setType(Category::Scratchpad);
677 result.setType(Category::Default);
685 if (cat.widgetCount() == 0)
688 const bool isScratchPad = cat.type() == Category::Scratchpad;
690 QTreeWidgetItem *cat_item;
693 const int idx = ensureScratchpad();
694 categoryView = categoryViewAt(idx);
695 cat_item = topLevelItem(idx);
697 const int existingIndex = indexOfCategory(cat.name());
698 if (existingIndex == -1) {
699 cat_item =
new QTreeWidgetItem();
700 cat_item->setText(0, cat.name());
703 const int scratchPadIndex = indexOfScratchpad();
704 if (scratchPadIndex == -1) {
705 addTopLevelItem(cat_item);
707 insertTopLevelItem(scratchPadIndex, cat_item);
709 cat_item->setExpanded(
true);
710 categoryView = addCategoryView(cat_item, m_iconMode);
712 categoryView = categoryViewAt(existingIndex);
713 cat_item = topLevelItem(existingIndex);
717 const int widgetCount = cat.widgetCount();
718 for (
int i = 0; i < widgetCount; ++i) {
719 const Widget w = cat.widget(i);
720 if (!categoryView->containsWidget(w.name()))
721 categoryView->addWidget(w, iconForWidget(w.iconName()), isScratchPad);
723 adjustSubListSize(cat_item);
728 if (cat_idx >= topLevelItemCount())
730 delete takeTopLevelItem(cat_idx);
735 if (cat_idx >= topLevelItemCount())
738 return categoryViewAt(cat_idx)->count(WidgetBoxCategoryListView::UnfilteredAccess);
743 if (cat_idx >= topLevelItemCount())
747 return categoryView->widgetAt(WidgetBoxCategoryListView::UnfilteredAccess, wgt_idx);
752 if (cat_idx >= topLevelItemCount())
755 QTreeWidgetItem *cat_item = topLevelItem(cat_idx);
758 const bool scratch = topLevelRole(cat_item) == SCRATCHPAD_ITEM;
759 categoryView->addWidget(wgt, iconForWidget(wgt.iconName()), scratch);
760 adjustSubListSize(cat_item);
765 if (cat_idx >= topLevelItemCount())
771 const WidgetBoxCategoryListView::AccessMode am = WidgetBoxCategoryListView::UnfilteredAccess;
772 if (wgt_idx >= categoryView->count(am))
775 categoryView->removeRow(am, wgt_idx);
780 const int scratch_idx = indexOfScratchpad();
781 QTreeWidgetItem *scratch_item = topLevelItem(scratch_idx);
782 adjustSubListSize(scratch_item);
789 if (!m_scratchPadDeleteTimer) {
790 m_scratchPadDeleteTimer =
new QTimer(
this);
791 m_scratchPadDeleteTimer->setSingleShot(
true);
792 m_scratchPadDeleteTimer->setInterval(0);
793 connect(m_scratchPadDeleteTimer, &QTimer::timeout,
794 this, &WidgetBoxTreeWidget::deleteScratchpad);
796 if (!m_scratchPadDeleteTimer->isActive())
797 m_scratchPadDeleteTimer->start();
802 const int idx = indexOfScratchpad();
805 delete takeTopLevelItem(idx);
824 if (
const int numTopLevels = topLevelItemCount()) {
825 for (
int i = numTopLevels - 1; i >= 0; --i) {
826 QTreeWidgetItem *topLevel = topLevelItem(i);
828 const QListView::ViewMode viewMode = m_iconMode && (topLevelRole(topLevel) != SCRATCHPAD_ITEM) ? QListView::IconMode : QListView::ListMode;
830 if (viewMode != categoryView->viewMode()) {
831 categoryView->setViewMode(viewMode);
832 adjustSubListSize(topLevelItem(i));
842 QTreeWidget::resizeEvent(e);
843 if (
const int numTopLevels = topLevelItemCount()) {
844 for (
int i = numTopLevels - 1; i >= 0; --i)
845 adjustSubListSize(topLevelItem(i));
851 QTreeWidgetItem *item = itemAt(e->pos());
853 const bool scratchpad_menu = item !=
nullptr
854 && item->parent() !=
nullptr
855 && topLevelRole(item->parent()) == SCRATCHPAD_ITEM;
858 menu.addAction(tr(
"Expand all"),
this, &WidgetBoxTreeWidget::expandAll);
859 menu.addAction(tr(
"Collapse all"),
this, &WidgetBoxTreeWidget::collapseAll);
862 QAction *listModeAction = menu.addAction(tr(
"List View"));
863 QAction *iconModeAction = menu.addAction(tr(
"Icon View"));
864 listModeAction->setCheckable(
true);
865 iconModeAction->setCheckable(
true);
866 QActionGroup *viewModeGroup =
new QActionGroup(&menu);
867 viewModeGroup->addAction(listModeAction);
868 viewModeGroup->addAction(iconModeAction);
870 iconModeAction->setChecked(
true);
872 listModeAction->setChecked(
true);
873 connect(listModeAction, &QAction::triggered,
this, &WidgetBoxTreeWidget::slotListMode);
874 connect(iconModeAction, &QAction::triggered,
this, &WidgetBoxTreeWidget::slotIconMode);
876 if (scratchpad_menu) {
885 menu.exec(mapToGlobal(e->pos()));
890 QTreeWidgetItem *scratch_item =
nullptr;
894 for (QDesignerDnDItemInterface *item : item_list) {
895 QWidget *w = item->widget();
899 DomUI *dom_ui = item->domUi();
900 if (dom_ui ==
nullptr)
903 const int scratch_idx = ensureScratchpad();
904 scratch_item = topLevelItem(scratch_idx);
905 categoryView = categoryViewAt(scratch_idx);
908 DomWidget *fakeTopLevel = dom_ui->takeElementWidget();
909 DomWidget *firstWidget =
nullptr;
910 if (fakeTopLevel && !fakeTopLevel->elementWidget().isEmpty()) {
911 firstWidget = fakeTopLevel->elementWidget().constFirst();
912 dom_ui->setElementWidget(firstWidget);
914 dom_ui->setElementWidget(fakeTopLevel);
921 QXmlStreamWriter writer(&xml);
922 writer.setAutoFormatting(
true);
923 writer.setAutoFormattingIndent(1);
924 writer.writeStartDocument();
925 dom_ui->write(writer);
926 writer.writeEndDocument();
930 dom_ui->takeElementWidget();
931 dom_ui->setElementWidget(fakeTopLevel);
933 const Widget wgt = Widget(w->objectName(), xml);
934 categoryView->addWidget(wgt, iconForWidget(wgt.iconName()),
true);
935 scratch_item->setExpanded(
true);
943 const WidgetBoxCategoryListView::AccessMode am = WidgetBoxCategoryListView::FilteredAccess;
944 if (
const int count = categoryView->count(am))
945 categoryView->setCurrentItem(am, count - 1);
946 categoryView->adjustSize();
947 adjustSubListSize(scratch_item);
949 scrollToItem(scratch_item, PositionAtTop);
955 const bool empty = f.isEmpty();
956 const int numTopLevels = topLevelItemCount();
957 bool changed =
false;
958 for (
int i = 0; i < numTopLevels; i++) {
959 QTreeWidgetItem *tl = topLevelItem(i);
962 const int oldCount = categoryView->count(WidgetBoxCategoryListView::FilteredAccess);
963 categoryView->filter(f, Qt::CaseInsensitive);
964 const int newCount = categoryView->count(WidgetBoxCategoryListView::FilteredAccess);
965 if (oldCount != newCount) {
967 const bool categoryEnabled = newCount > 0 || empty;
968 if (categoryEnabled) {
969 categoryView->adjustSize();
970 adjustSubListSize(tl);
972 setRowHidden (i, QModelIndex(), !categoryEnabled);
Combined button and popup list for selecting options.
Auxiliary methods to store/retrieve settings.
static int findCategory(const QString &name, const WidgetBoxTreeWidget::CategoryList &list)
static bool isValidIcon(const QIcon &icon)
static QString msgXmlError(const QString &fileName, const QXmlStreamReader &r)
static constexpr auto widgetBoxSettingsGroupC
static constexpr auto widgetBoxViewModeKeyC
QDESIGNER_SHARED_EXPORT void designerWarning(const QString &message)
static constexpr auto widgetBoxExpandedKeyC