Files
qelectrotech-source-mirror/sources/genericpanel.cpp
T
ispyisail 924b87447f Fix a use-after-free when closing QElectroTech without saving
Closing QElectroTech with an edited project and choosing "Close without
saving" read freed memory. Destroying the project clears each folio's
undo stack, which signals the Projects panel to refresh that project.
The refresh reached GenericPanel::updateItem(), whose only statement was
QApplication::processEvents(). That ran the editor window's pending
deleteLater(), destroying the panel, and GenericPanel::addProject() then
carried on using it (genericpanel.cpp:142).

The call was added in 2013 (70b7cd7d1) to keep the window responsive
while the panel reloaded, when it also listed the whole element
collection. The collection has its own panel now; this one lists
projects, folios and title block templates. updateItem() is again what
its comment says it is: a hook that does nothing.

Found by the GUI fuzzer under AddressSanitizer. Reproduced 3/3 on master
51b122993 (open a project, move everything on a folio, Ctrl+Q, Close
without Saving: heap-use-after-free, exit 1); 0/3 with this change
(exit 0, no report), with the save prompt confirmed on screen.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 16:15:51 +13:00

1080 lines
29 KiB
C++

/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "genericpanel.h"
#include "diagram.h"
#include "qetapp.h"
#include "qeticons.h"
#include "qetproject.h"
#include "titleblock/templatelocation.h"
#include "titleblock/templatescollection.h"
#include <QTreeWidgetItem>
/**
Constructor
@param parent Parent QWidget
*/
GenericPanel::GenericPanel(QWidget *parent) :
QTreeWidget(parent),
first_activation_(true)
{
header() -> hide();
setIconSize(QSize(16, 16));
}
/**
Destructor
*/
GenericPanel::~GenericPanel()
{
}
/**
@return the type of the current item
*/
int GenericPanel::currentItemType()
{
QTreeWidgetItem *current_qtwi = currentItem();
if (!current_qtwi) return(0);
return(current_qtwi -> type());
}
/**
@brief GenericPanel::projectForItem
@param item
@return nullptr
*/
QETProject *GenericPanel::projectForItem(QTreeWidgetItem *item) const
{
if (item && item -> type() == QET::Project) {
return(valueForItem<QETProject *>(item));
}
return(nullptr);
}
/**
@brief GenericPanel::diagramForItem
@param item
@return nullptr
*/
Diagram *GenericPanel::diagramForItem(QTreeWidgetItem *item) const
{
if (item && item -> type() == QET::Diagram) {
return(valueForItem<Diagram *>(item));
}
return(nullptr);
}
/**
@brief GenericPanel::templateLocationForItem
@param item
@return TitleBlockTemplateLocation()
*/
TitleBlockTemplateLocation GenericPanel::templateLocationForItem(
QTreeWidgetItem *item) const
{
if (item && item -> type() & QET::TitleBlockTemplatesCollectionItem) {
return(valueForItem<TitleBlockTemplateLocation>(item));
}
return(TitleBlockTemplateLocation());
}
/**
@brief GenericPanel::selectedProject
@return projectForItem(currentItem())
*/
QETProject *GenericPanel::selectedProject() const
{
return(projectForItem(currentItem()));
}
/**
@brief GenericPanel::selectedDiagram
@return diagramForItem(currentItem())
*/
Diagram *GenericPanel::selectedDiagram() const
{
return(diagramForItem(currentItem()));
}
/**
@brief GenericPanel::selectedTemplateLocation
@return templateLocationForItem(currentItem())
*/
TitleBlockTemplateLocation GenericPanel::selectedTemplateLocation() const
{
return(templateLocationForItem(currentItem()));
}
/**
@brief GenericPanel::addProject
@param project
@param parent_item
@param options
@return project_qtwi
*/
QTreeWidgetItem *GenericPanel::addProject(QETProject *project,
QTreeWidgetItem *parent_item,
PanelOptions options) {
if (!project) return(nullptr);
bool creation_required;
QTreeWidgetItem *project_qtwi = getItemForProject(project,
&creation_required);
updateProjectItem(project_qtwi, project, options, creation_required);
reparent(project_qtwi, parent_item);
fillProjectItem(project_qtwi, project, options, creation_required);
return(project_qtwi);
}
/**
@param project A standard project.
@return the tree item representing the provided project or 0 if this
project does not appear within this panel.
*/
QTreeWidgetItem *GenericPanel::itemForProject(QETProject *project) {
if (!project) return(nullptr);
return(projects_.value(project, nullptr));
}
/**
@param project A standard project.
@param created if provided with a pointer to a boolean, this method will
update it to reflect whether the returned item has been freshly created or
not.
@return the tree item representing the provided project. If it does not
appear within this panel, it is created.
*/
QTreeWidgetItem *GenericPanel::getItemForProject(QETProject *project,
bool *created) {
if (!project) return(nullptr);
QTreeWidgetItem *project_qtwi = projects_.value(project, nullptr);
if (project_qtwi) {
if (created) *created = false;
return(project_qtwi);
}
project_qtwi = makeItem(QET::Project);
if (created) *created = true;
return(project_qtwi);
}
/**
@brief GenericPanel::updateProjectItem
@param project_qtwi
@param project
@param options (unused)
@param freshly_created
@return updateItem(project_qtwi, options, freshly_created)
*/
QTreeWidgetItem *GenericPanel::updateProjectItem(QTreeWidgetItem *project_qtwi,
QETProject *project,
PanelOptions options,
bool freshly_created) {
Q_UNUSED(options)
if (!project_qtwi || !project) return(nullptr);
if (freshly_created) {
project_qtwi -> setData(0, GenericPanel::Item,
QVariant::fromValue(project));
projects_.insert(project, project_qtwi);
connect(
project,&QETProject::projectInformationsChanged,
this,&GenericPanel::projectInformationsChanged);
connect(
project,&QETProject::readOnlyChanged,
this,&GenericPanel::projectInformationsChanged);
}
// text
project_qtwi -> setText(0, project -> pathNameTitle());
// tooltip
QString final_tooltip = QDir::toNativeSeparators(project -> filePath());
if (final_tooltip.isEmpty()) {
final_tooltip = tr(
"Pas de fichier",
"tooltip for a file-less project in the element panel"
);
}
project_qtwi -> setToolTip(0, final_tooltip);
QString project_whatsthis = tr("Ceci est un projet QElectroTech, "
"c'est-à-dire un fichier d'extension .qet regroupant "
"plusieurs folios. Il embarque également les éléments et "
"modèles de cartouches utilisés dans ces folios.",
"\"What's this\" tip");
project_qtwi -> setWhatsThis(0, project_whatsthis);
return(updateItem(project_qtwi, options, freshly_created));
}
/**
@brief GenericPanel::fillProjectItem
@param project_qtwi
@param project
@param options
@param freshly_created
@return fillItem(project_qtwi, options, freshly_created)
*/
QTreeWidgetItem *GenericPanel::fillProjectItem(QTreeWidgetItem *project_qtwi,
QETProject *project,
PanelOptions options,
bool freshly_created) {
if (!project_qtwi || !project) return(nullptr);
if (options & AddChildDiagrams) {
if (freshly_created) {
connect(
project,&QETProject::diagramAdded,
this,&GenericPanel::diagramAdded);
connect(
project,&QETProject::diagramRemoved,
this,&GenericPanel::diagramRemoved);
connect(
project,&QETProject::projectDiagramsOrderChanged,
this,&GenericPanel::projectDiagramsOrderChanged);
} else {
// remove diagrams unknown to the project (presumably removed)
removeObsoleteItems(project -> diagrams(),
project_qtwi, QET::Diagram, false);
}
int index = 0;
foreach (Diagram *diagram, project -> diagrams()) {
QTreeWidgetItem *diagram_qtwi = addDiagram(diagram,
nullptr,
options);
project_qtwi -> insertChild(index, diagram_qtwi);
++ index;
}
}
if (options & AddChildTemplatesCollection) {
if (freshly_created) {
connect(
project,&QETProject::diagramUsedTemplate,
this,&GenericPanel::diagramUsedTemplate);
}
addTemplatesCollection(
project -> embeddedTitleBlockTemplatesCollection(),
project_qtwi,
options);
}
return(fillItem(project_qtwi, options, freshly_created));
}
/**
@brief GenericPanel::addDiagram
@param diagram
@param parent_item
@param options (unused)
@return diagram_qtwi
*/
QTreeWidgetItem *GenericPanel::addDiagram(Diagram *diagram,
QTreeWidgetItem *parent_item,
PanelOptions options) {
Q_UNUSED(options)
if (!diagram) return(nullptr);
bool creation_required;
QTreeWidgetItem *diagram_qtwi = getItemForDiagram(diagram,
&creation_required);
updateDiagramItem(diagram_qtwi, diagram, options, creation_required);
reparent(diagram_qtwi, parent_item);
fillDiagramItem(diagram_qtwi, options, creation_required);
return(diagram_qtwi);
}
/**
@brief GenericPanel::getItemForDiagram
@param diagram
@param created
@return diagram_qtwi
*/
QTreeWidgetItem *GenericPanel::getItemForDiagram(Diagram *diagram,
bool *created) {
if (!diagram) return(nullptr);
QTreeWidgetItem *diagram_qtwi = diagrams_.value(diagram, nullptr);
if (diagram_qtwi) {
if (created) *created = false;
return(diagram_qtwi);
}
if (!created) return(nullptr);
diagram_qtwi = makeItem(QET::Diagram);
*created = true;
return(diagram_qtwi);
}
/**
@brief GenericPanel::updateDiagramItem
@param diagram_qtwi
@param diagram
@param options
@param freshly_created
@return updateItem(diagram_qtwi, options, freshly_created)
*/
QTreeWidgetItem *GenericPanel::updateDiagramItem(QTreeWidgetItem *diagram_qtwi,
Diagram *diagram,
PanelOptions options,
bool freshly_created) {
if (!diagram || !diagram_qtwi) return(nullptr);
QSettings settings;
QString displayed_title = diagram -> title();
if (displayed_title.isEmpty())
{
displayed_title = tr("Folio sans titre",
"Fallback label when a diagram has no title");
}
if (settings.value("genericpanel/folio", true).toBool())
{
QString displayed_label =
diagram ->border_and_titleblock.finalfolio();
int diagram_folio_idx = diagram -> folioIndex();
if (diagram_folio_idx != -1)
{
displayed_label = QString(
tr(
"%1 - %2",
"label displayed for a diagram in the panel ;"
" %1 is the folio index, %2 is the diagram title"
)
).arg(displayed_label).arg(displayed_title);
diagram_qtwi -> setText(0, displayed_label);
}
}
else
{
QString displayed_label;
int diagram_folio_idx = diagram -> folioIndex();
if (diagram_folio_idx != -1)
{
displayed_label = QString(
tr(
"%1 - %2",
"label displayed for a diagram in the panel ;"
" %1 is the folio index, %2 is the diagram title"
)
).arg(diagram_folio_idx + 1).arg(displayed_title);
}
diagram_qtwi -> setText(0, displayed_label);
}
if (freshly_created)
{
diagram_qtwi -> setData(0,
GenericPanel::Item,
QVariant::fromValue(diagram));
diagrams_.insert(diagram, diagram_qtwi);
connect(diagram, &Diagram::diagramTitleChanged,
this, &GenericPanel::diagramTitleChanged);
}
return(updateItem(diagram_qtwi, options, freshly_created));
}
/**
@brief GenericPanel::fillDiagramItem
@param diagram_qtwi
@param options
@param freshly_created
@return fillItem(diagram_qtwi, options, freshly_created)
*/
QTreeWidgetItem *GenericPanel::fillDiagramItem(
QTreeWidgetItem *diagram_qtwi,
PanelOptions options,
bool freshly_created)
{
return(fillItem(diagram_qtwi, options, freshly_created));
}
/**
@brief GenericPanel::addTemplatesCollection
@param tbt_collection
@param parent_item
@param options
@return tbt_collection_qtwi
*/
QTreeWidgetItem *GenericPanel::addTemplatesCollection(
TitleBlockTemplatesCollection *tbt_collection,
QTreeWidgetItem *parent_item,
PanelOptions options) {
if (!tbt_collection) return(nullptr);
bool creation_required;
QTreeWidgetItem *tbt_collection_qtwi =
getItemForTemplatesCollection(tbt_collection,
&creation_required);
updateTemplatesCollectionItem(tbt_collection_qtwi,
tbt_collection,
options,
creation_required);
reparent(tbt_collection_qtwi, parent_item);
fillTemplatesCollectionItem(tbt_collection_qtwi,
tbt_collection,
options,
creation_required);
return(tbt_collection_qtwi);
}
/**
@brief GenericPanel::itemForTemplatesCollection
@param tbt_collection
@return tb_templates_.value(tbt_collection -> location(), nullptr)
@return nullptr
*/
QTreeWidgetItem *GenericPanel::itemForTemplatesCollection(
TitleBlockTemplatesCollection *tbt_collection) {
if (!tbt_collection) return(nullptr);
return(tb_templates_.value(tbt_collection -> location(), nullptr));
}
/**
@brief GenericPanel::getItemForTemplatesCollection
@param tbt_collection
@param created
@return tbt_collection_item
*/
QTreeWidgetItem *GenericPanel::getItemForTemplatesCollection(
TitleBlockTemplatesCollection *tbt_collection,
bool *created) {
if (!tbt_collection) return(nullptr);
QTreeWidgetItem *tbt_collection_item =
tb_templates_.value(tbt_collection -> location(),
nullptr);
if (tbt_collection_item) {
if (created) *created = false;
return(tbt_collection_item);
}
tbt_collection_item = makeItem(QET::TitleBlockTemplatesCollection);
if (created) *created = true;
return(tbt_collection_item);
}
/**
@brief GenericPanel::updateTemplatesCollectionItem
@param tbt_collection_qtwi
@param tbt_collection
@param options
@param freshly_created
@return updateItem(tbt_collection_qtwi, options, freshly_created)
*/
QTreeWidgetItem *GenericPanel::updateTemplatesCollectionItem(
QTreeWidgetItem *tbt_collection_qtwi,
TitleBlockTemplatesCollection *tbt_collection,
PanelOptions options,
bool freshly_created) {
QString label = tbt_collection -> title();
if (label.isEmpty()) label = tr("Modèles de cartouche");
tbt_collection_qtwi -> setText(0, label);
tbt_collection_qtwi -> setToolTip(0,
tbt_collection->location().toString());
if (freshly_created) {
tbt_collection_qtwi -> setData(0,
GenericPanel::Item,
QVariant::fromValue(
tbt_collection->location()));
tb_templates_.insert(tbt_collection -> location(),
tbt_collection_qtwi);
}
return(updateItem(tbt_collection_qtwi, options, freshly_created));
}
/**
@brief GenericPanel::fillTemplatesCollectionItem
@param tbt_collection_qtwi
@param tbt_collection
@param options
@param freshly_created
@return fillItem(tbt_collection_qtwi, options, freshly_created)
*/
QTreeWidgetItem *GenericPanel::fillTemplatesCollectionItem(
QTreeWidgetItem *tbt_collection_qtwi,
TitleBlockTemplatesCollection *tbt_collection,
PanelOptions options,
bool freshly_created) {
if (!tbt_collection_qtwi || !tbt_collection)
return(tbt_collection_qtwi);
if (options & AddChildTemplates) {
if (freshly_created) {
connect(
tbt_collection,
&TitleBlockTemplatesCollection::changed,
this,
&GenericPanel::templatesCollectionChanged);
if (QETProject *project = tbt_collection -> parentProject()) {
connect(
project, &QETProject::diagramUsedTemplate,
this,
&GenericPanel::templatesCollectionChanged);
}
} else {
// remove templates unknown to the collection (presumably removed)
removeObsoleteItems(
tbt_collection -> templatesLocations(),
tbt_collection_qtwi,
QET::TitleBlockTemplate,
false);
}
int index = 0;
foreach (QString template_name, tbt_collection -> templates()) {
QTreeWidgetItem *template_item =
addTemplate(
tbt_collection -> location(
template_name),
nullptr,
options);
tbt_collection_qtwi -> insertChild(index ++,
template_item);
}
}
return(fillItem(tbt_collection_qtwi, options, freshly_created));
}
/**
@brief GenericPanel::addTemplate
@param tb_template
@param parent_item
@param options
@return tb_template_qtwi
*/
QTreeWidgetItem *GenericPanel::addTemplate(
const TitleBlockTemplateLocation &tb_template,
QTreeWidgetItem *parent_item,
PanelOptions options) {
if (!tb_template.isValid()) return(nullptr);
bool creation_required;
QTreeWidgetItem *tb_template_qtwi = getItemForTemplate(tb_template,
&creation_required);
updateTemplateItem(tb_template_qtwi,
tb_template,
options,
creation_required);
reparent(tb_template_qtwi, parent_item);
fillTemplateItem(tb_template_qtwi,
tb_template,
options,
creation_required);
return(tb_template_qtwi);
}
/**
@brief GenericPanel::getItemForTemplate
@param tb_template
@param created
@return tb_template_qtwi
*/
QTreeWidgetItem *GenericPanel::getItemForTemplate(
const TitleBlockTemplateLocation &tb_template,
bool *created) {
if (!tb_template.isValid()) return(nullptr);
QTreeWidgetItem *tb_template_qtwi = tb_templates_.value(tb_template,
nullptr);
if (tb_template_qtwi) {
if (created) *created = false;
return(tb_template_qtwi);
}
tb_template_qtwi = makeItem(QET::TitleBlockTemplate);
if (created) *created = true;
return(tb_template_qtwi);
}
/**
@brief GenericPanel::updateTemplateItem
@param tb_template_qtwi
@param tb_template
@param options
@param freshly_created
@return updateItem(tb_template_qtwi, options, freshly_created)
*/
QTreeWidgetItem *GenericPanel::updateTemplateItem(
QTreeWidgetItem *tb_template_qtwi,
const TitleBlockTemplateLocation &tb_template,
PanelOptions options,
bool freshly_created) {
tb_template_qtwi -> setText(0,
tr("Modèle \"%1\"",
"used to display a title block template").arg(
tb_template.name()));
QString tbt_whatsthis = tr(
"Ceci est un modèle de cartouche, qui peut être appliqué à un folio.",
"\"What's this\" tip"
);
tb_template_qtwi -> setWhatsThis(0, tbt_whatsthis);
// note the following lines are technically marking the template as used
tb_template_qtwi -> setToolTip(0, tb_template.toString());
tb_template_qtwi -> setBackground(0, QBrush());
// special action for templates that belong to a project
if (QETProject *tbt_project = tb_template.parentProject()) {
// display unused templates using a red background
if (!tbt_project -> usesTitleBlockTemplate(tb_template)) {
markItemAsUnused(tb_template_qtwi);
}
}
if (freshly_created) {
tb_template_qtwi -> setData(0,
GenericPanel::Item,
QVariant::fromValue(tb_template));
tb_templates_.insert(tb_template, tb_template_qtwi);
}
return(updateItem(tb_template_qtwi, options, freshly_created));
}
/**
@brief GenericPanel::fillTemplateItem
@param tb_template_qtwi
@param tb_template (unused)
@param options
@param freshly_created
@return fillItem(tb_template_qtwi, options, freshly_created)
*/
QTreeWidgetItem *GenericPanel::fillTemplateItem(
QTreeWidgetItem *tb_template_qtwi,
const TitleBlockTemplateLocation &tb_template,
PanelOptions options,
bool freshly_created) {
Q_UNUSED(tb_template)
return(fillItem(tb_template_qtwi, options, freshly_created));
}
/**
@brief GenericPanel::updateItem
This generic method is called at the end of each update*Item method.
Its only purpose is being reimplemented in a subclass.
The default implementation does nothing.
@param qtwi
@param options (unused)
@param freshly_created (unused)
@return qtwi
*/
QTreeWidgetItem *GenericPanel::updateItem(QTreeWidgetItem *qtwi,
PanelOptions options,
bool freshly_created) {
Q_UNUSED(options);
Q_UNUSED(freshly_created);
//No QApplication::processEvents() here. It dates from when this
//panel also listed the whole element collection, to keep the
//window alive while that reloaded; the collection has its own
//panel now. Running the event loop from inside the panel's own
//methods let a pending deleteLater() destroy the panel while
//addProject() was still using it: closing QElectroTech without
//saving an edited project read freed memory.
return(qtwi);
}
/**
@brief GenericPanel::fillItem
This generic method is called at the end of each fill*Item method.
Its only purpose is being reimplemented in a subclass.
The default implementation does nothing.
@param qtwi
@param options (unused)
@param freshly_created (unused)
@return qtwi
*/
QTreeWidgetItem *GenericPanel::fillItem(QTreeWidgetItem *qtwi,
PanelOptions options,
bool freshly_created) {
Q_UNUSED(options);
Q_UNUSED(freshly_created);
return(qtwi);
}
/**
@brief GenericPanel::projectInformationsChanged
@param project
*/
void GenericPanel::projectInformationsChanged(QETProject *project) {
addProject(project, nullptr, AddAllChild);
emit(panelContentChanged());
}
/**
@brief GenericPanel::diagramAdded
@param project
@param diagram the newly added diagram to select in the tree
*/
void GenericPanel::diagramAdded(QETProject *project, Diagram *diagram) {
addProject(project, nullptr, GenericPanel::AddChildDiagrams);
if (diagram) {
QTreeWidgetItem *projectItem = itemForProject(project);
if (projectItem) {
for (int i = 0; i < projectItem->childCount(); ++i) {
QTreeWidgetItem *child = projectItem->child(i);
if (child && child->type() == QET::Diagram
&& valueForItem<Diagram *>(child) == diagram) {
clearSelection();
setCurrentItem(child);
child->setSelected(true);
break;
}
}
}
}
emit(panelContentChanged());
}
/**
@brief GenericPanel::diagramRemoved
@param project
@param diagram (unused)
*/
void GenericPanel::diagramRemoved(QETProject *project, Diagram *diagram) {
Q_UNUSED(diagram)
addProject(project, nullptr, GenericPanel::AddChildDiagrams);
emit(panelContentChanged());
}
/**
@brief GenericPanel::projectDiagramsOrderChanged
@param project project where diagram moved
@param from Index of diagram before move
@param to Index of diagram after move
*/
void GenericPanel::projectDiagramsOrderChanged(QETProject *project,
int from,
int to)
{
// get the item representing the provided project
QTreeWidgetItem *qtwi_project = itemForProject(project);
if (!qtwi_project) return;
// get the item representing the moved diagram
QTreeWidgetItem *moved_qtwi_diagram = qtwi_project -> child(from);
if (!moved_qtwi_diagram) return;
// remove the QTWI then insert it back at the adequate location
qtwi_project -> removeChild (moved_qtwi_diagram);
qtwi_project -> insertChild (to, moved_qtwi_diagram);
// update the QTWI labels because they may display the folio index
for (int i = qMin(from, to); i < qMax(from, to) + 1; i++)
{
QTreeWidgetItem *qtwi_diagram = qtwi_project -> child(i);
if (!qtwi_diagram) continue;
Diagram *diagram = valueForItem<Diagram *>(qtwi_diagram);
if (diagram)
updateDiagramItem(qtwi_diagram, diagram);
}
// select the moved diagram
if(m_selected_item){
setCurrentItem(moved_qtwi_diagram);
}
else{
setCurrentItem(qtwi_project -> child(from));
}
m_selected_item = nullptr;
emit(panelContentChanged());
}
/**
@brief GenericPanel::diagramTitleChanged
Inform this panel the diagram \a diagram has changed its title to \a title.
@param diagram
*/
void GenericPanel::diagramTitleChanged(Diagram *diagram) {
GenericPanel::addDiagram(diagram);
emit(panelContentChanged());
}
/**
@brief GenericPanel::templatesCollectionChanged
@param collection :
Title block templates collection that changed and should be updated
@param template_name : Name of the changed template (unused)
*/
void GenericPanel::templatesCollectionChanged(
TitleBlockTemplatesCollection*collection,
const QString &template_name) {
Q_UNUSED(template_name)
addTemplatesCollection(collection);
emit(panelContentChanged());
}
/**
@brief GenericPanel::diagramUsedTemplate
@param collection
@param name : (unused)
*/
void GenericPanel::diagramUsedTemplate(
TitleBlockTemplatesCollection *collection,
const QString &name) {
Q_UNUSED(name)
addTemplatesCollection(collection);
emit(panelContentChanged());
}
/**
@brief GenericPanel::defaultText
@param type
@return the default text for \a type
*/
QString GenericPanel::defaultText(QET::ItemType type) {
switch(type) {
case QET::ElementsCollectionItem:
case QET::Element: return("element");
case QET::ElementsContainer:
case QET::ElementsCategory: return("elements category");
case QET::ElementsCollection: return("elements collection");
case QET::TitleBlockTemplatesCollectionItem:
case QET::TitleBlockTemplate: return("title block template");
case QET::TitleBlockTemplatesCollection:
return("title block templates collection");
case QET::Diagram: return("diagram");
case QET::Project: return("project");
default: return(QString());
}
return(QString());
}
/**
@brief GenericPanel::defaultIcon
@param type Item type we want the default icon for
@return the default icon for \a type
*/
QIcon GenericPanel::defaultIcon(QET::ItemType type) {
if (type & QET::ElementsContainer) {
return(QET::Icons::Folder);
} else if (type & QET::TitleBlockTemplatesCollectionItem) {
return(QIcon(QET::Icons::TitleBlock.pixmap(QSize(16, 16))));
} else if (type == QET::Diagram) {
return(QET::Icons::Diagram);
} else if (type == QET::Project) {
return(QIcon(QET::Icons::ProjectFileGP.pixmap(QSize(16, 16))));
}
return(QIcon());
}
/**
@brief GenericPanel::makeItem
Create a QTreeWidgetItem
@param type Item type (e.g QET::Diagram, QET::Project, ...)
@param parent Parent for the created item
@param label Label for the created item
@param icon Icon for the created item
@return the create QTreeWidgetItem
*/
QTreeWidgetItem *GenericPanel::makeItem(QET::ItemType type,
QTreeWidgetItem *parent,
const QString &label,
const QIcon &icon) {
QTreeWidgetItem *qtwi = new QTreeWidgetItem(parent, type);
qtwi -> setText(0, label.isEmpty() ? defaultText(type) : label);
qtwi -> setIcon(0, icon.isNull() ? defaultIcon(type) : icon);
return(qtwi);
}
/**
@brief GenericPanel::deleteItem
Delete and item and its children.
@param item item to delete
@param deleted_on_cascade true if the item is not being directly deleted
but is undergoing the deletion of its parent. (unused)
*/
void GenericPanel::deleteItem(QTreeWidgetItem *item, bool deleted_on_cascade) {
Q_UNUSED(deleted_on_cascade)
// recursively delete child items first
for (int i = item -> childCount() - 1 ; i >= 0 ; -- i) {
deleteItem(item -> child(i), true);
}
// delete the item itself
unregisterItem(item);
delete item;
}
/**
@brief GenericPanel::markItemAsUnused
Mark the provided QTreeWidgetItem as unused in its parent project.
@param qtwi A QTreeWidgetItem
*/
void GenericPanel::markItemAsUnused(QTreeWidgetItem *qtwi) {
QLinearGradient t(0, 0, 200, 0);
t.setColorAt(0, QColor("#ffc0c0"));
t.setColorAt(1, QColor("#ffffff"));
qtwi -> setBackground(0, QBrush(t));
qtwi -> setToolTip(0,
QString(tr("%1 [non utilisé dans le projet]")).arg(
qtwi -> toolTip(0)));
}
/**
@brief GenericPanel::reparent
@param item
@param parent
*/
void GenericPanel::reparent(QTreeWidgetItem *item, QTreeWidgetItem *parent) {
if (parent && item -> parent() != parent) {
parent -> addChild(item);
}
}
/**
@brief GenericPanel::childItems
@param item Parent item that will be searched.
@param type Type of items to look for.
@param recursive Whether to search recursively.
@return the child items of \a item of type \a type
*/
QList<QTreeWidgetItem *> GenericPanel::childItems(
QTreeWidgetItem *item,
QET::ItemType type,
bool recursive) const
{
QList<QTreeWidgetItem *> items;
if (!item) return(items);
for (int i = 0 ; i < item -> childCount() ; ++ i) {
QTreeWidgetItem *current_item = item -> child(i);
if (!current_item) continue;
if (current_item -> type() == type) {
items << current_item;
}
if (recursive) {
items << childItems(current_item, type, true);
}
}
return(items);
}
/**
This variant of childItems() removes any child considered obsolete, i.e.
not found in \a expected_items.
@param expected_items A list of non-obsolete values
@param item Parent item that will be searched.
@param type Type of items to look for.
@param recursive Whether to search recursively.
@see GenericPanel::childItems()
*/
template<typename T>
void GenericPanel::removeObsoleteItems(
const QList<T> &expected_items,
QTreeWidgetItem *item,
QET::ItemType type,
bool recursive) {
// remove items not found in expected_items
foreach (QTreeWidgetItem *child_item,
childItems(item, type, recursive)) {
T child_value = valueForItem<T>(child_item);
if (!expected_items.contains(child_value)) {
deleteItem(child_item);
}
}
}
/**
@return the value stored in \a item
*/
template<typename T>
T GenericPanel::valueForItem(QTreeWidgetItem *item) const
{
return item -> data(0, GenericPanel::Item).value<T>();
}
template TitleBlockTemplateLocation
GenericPanel::valueForItem(QTreeWidgetItem *item) const;
template ElementsLocation GenericPanel::valueForItem(QTreeWidgetItem*)
const;
template QETProject* GenericPanel::valueForItem(QTreeWidgetItem*)
const;
template Diagram* GenericPanel::valueForItem(QTreeWidgetItem*)
const;
/**
@brief GenericPanel::unregisterItem
@param item
*/
void GenericPanel::unregisterItem(QTreeWidgetItem *item) {
if (!item) return;
int type = item ->type();
if (type & QET::TitleBlockTemplatesCollectionItem) {
tb_templates_.remove(valueForItem<TitleBlockTemplateLocation>(item));
} else if (type == QET::Diagram) {
diagrams_.remove(valueForItem<Diagram *>(item));
} else if (type == QET::Project) {
projects_.remove(valueForItem<QETProject *>(item));
}
}
/**
@brief GenericPanel::event
Handle various events; reimplemented here to emit the signal
firstActivated().
@param event
@return
*/
bool GenericPanel::event(QEvent *event) {
if (first_activation_) {
if (event -> type() == QEvent::WindowActivate
|| event -> type() == QEvent::Show) {
QTimer::singleShot(250, this, &GenericPanel::emitFirstActivated);
first_activation_ = false;
}
}
return(QTreeWidget::event(event));
}
/**
@brief GenericPanel::emitFirstActivated
Emit the signal firstActivated().
*/
void GenericPanel::emitFirstActivated()
{
emit(firstActivated());
}
/**
@brief GenericPanel::setSelectedItem
@param selectedItem
*/
void GenericPanel::setSelectedItem(QTreeWidgetItem *selectedItem)
{
m_selected_item = selectedItem;
}