mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-03 01:04:13 +02:00
924b87447f
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 master51b122993(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>
1080 lines
29 KiB
C++
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;
|
|
}
|