Files
qelectrotech-source-mirror/sources/titleblock/qettemplateeditor.cpp
T
Beat Hangartner 0e2738b246 Stop listening to its children before the template editor is destroyed
Closing a title block template editor crashed it. The editor deletes
itself on close; QWidget's destructor then deletes its children, and
some of them signal the editor as they go:

- the scene deselects its cells, so the view emits
  selectedCellsChanged() to the editor's slot of the same name (when a
  cell was selected);
- the undo stack is cleared, so it emits cleanChanged() to
  updateEditorTitle() (when the template was edited).

Both slots then run on an object whose destructor has already run. A
Debug build aborts ("Called object is not of the correct type (class
destructor may have already run)"); a release build calls a member
function of a destroyed object.

Disconnect every child of the editor from it in the editor's
destructor, before the base destructors delete the children.

No file-format change, no change while the editor is open.

Tests: none automated; the editor needs the whole application.
Checked by hand (see the PR).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Beat Hangartner <beat@hangartners.ch>
2026-10-09 08:49:24 +02:00

1101 lines
37 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
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 "qettemplateeditor.h"
#include "../qetapp.h"
#include "../qeticons.h"
#include "../shortcutmanager.h"
#include "../qetmessagebox.h"
#include "../qetproject.h"
#include "templatecellwidget.h"
#include "templatecommands.h"
#include "templatelocationsaver.h"
#include "templatelogomanager.h"
#include "templateview.h"
#include "../toolbarsettings.h"
/**
@param parent parent QWidget of this window
*/
QETTitleBlockTemplateEditor::QETTitleBlockTemplateEditor(QWidget *parent) :
QETMainWindow(parent),
opened_from_file_(false),
read_only_(false),
duplicate_(false),
tb_template_(nullptr),
logo_manager_(nullptr)
{
setWindowIcon(QET::Icons::QETLogo);
setAttribute(Qt::WA_DeleteOnClose);
initWidgets();
initActions();
initMenus();
initToolbars();
ToolbarSettings::applyTo(this);
readSettings();
}
/**
Destructor
*/
QETTitleBlockTemplateEditor::~QETTitleBlockTemplateEditor()
{
// QWidget's destructor deletes the children, and some of them
// signal this editor as they go: the scene deselects its cells (the
// view emits selectedCellsChanged()), the undo stack is cleared
// (cleanChanged()). By then this editor is already destroyed, so
// stop listening to all of its children first.
const auto children = findChildren<QObject *>();
for (QObject *child : children) {
disconnect(child, nullptr, this, nullptr);
}
}
/**
@return the location of the currently edited template
*/
TitleBlockTemplateLocation QETTitleBlockTemplateEditor::location() const
{
return(location_);
}
/**
@return true if the provided filepath matches the currently edited template.
@param filepath path of a title block template on the filesystem
*/
bool QETTitleBlockTemplateEditor::isEditing(const QString &filepath)
{
QString current_filepath;
if (opened_from_file_) {
current_filepath = filepath_;
} else {
current_filepath = QETApp::realPath(location_.toString());
}
return(
QET::compareCanonicalFilePaths(
current_filepath,
filepath
)
);
}
/**
@brief QETTitleBlockTemplateEditor::setOpenForDuplication
@param duplicate : true for this editor to prompt the user for a
new template name as soon as the window appears in order to duplicate
the edited one.
*/
void QETTitleBlockTemplateEditor::setOpenForDuplication(bool duplicate)
{
duplicate_ = duplicate;
}
/**
@return true if this editor will prompt the user for a new template
name as soon as the window appears in order to duplicate the edited one.
*/
bool QETTitleBlockTemplateEditor::openForDuplication() const
{
return(duplicate_);
}
/**
@return true if the currently edited template can be closed. A template can be
closed if it has not been modified. If the template has been modified, this
method asks the user what he wants to do.
*/
bool QETTitleBlockTemplateEditor::canClose()
{
if (undo_stack_ -> isClean()) return(true);
// ask the user whether he wants to save the current template
QMessageBox::StandardButton answer = QET::QetMessageBox::question(
this,
tr("Save current template?", "dialog title"),
QString(
tr(
"Do you wish to save the template %1?",
"dialog content - %1 is a title block template name"
)
).arg(location_.name()),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
QMessageBox::Cancel
);
bool result;
switch(answer) {
case QMessageBox::Cancel: result = false; break; // the user hits Cancel or closes the dialog: abort the closing
case QMessageBox::Yes: result = save(); break; // the user hits Yes: la reussite depend de l'enregistrement
default: result = true; // the user hits no: the editor can be closed
}
return(result);
}
/**
@param event Object describing the received event.
*/
void QETTitleBlockTemplateEditor::firstActivation(QEvent *event)
{
Q_UNUSED(event)
if (duplicate_ && !opened_from_file_ && location_.parentCollection()) {
// this editor is supposed to duplicate its current location
QTimer::singleShot(250, this, &QETTitleBlockTemplateEditor::duplicateCurrentLocation);
}
}
/**
Handle the closing of the main window
@param qce The QCloseEvent event
*/
void QETTitleBlockTemplateEditor::closeEvent(QCloseEvent *qce)
{
if (canClose()) {
writeSettings();
setAttribute(Qt::WA_DeleteOnClose);
qce -> accept();
} else qce -> ignore();
}
/**
Ask the user for a new template name in order to duplicate the currently
edited template.
*/
void QETTitleBlockTemplateEditor::duplicateCurrentLocation()
{
// this method does not work for templates edited from the filesystem
if (opened_from_file_) return;
QString proposed_name;
if (location_.name().isEmpty()) {
proposed_name = tr("new_template",
"template name suggestion when duplicating the default one");
} else {
proposed_name = QString("%1_copy").arg(location_.name());
}
bool accepted = false;
QString new_template_name = QInputDialog::getText(
this,
tr("Duplicate a title block template", "input dialog title"),
tr("In order to duplicate this template, please enter a name for its copy",
"input dialog text"),
QLineEdit::Normal,
proposed_name,
&accepted
);
if (accepted) {
TitleBlockTemplateLocation new_template_location(
new_template_name,
location_.parentCollection());
saveAs(new_template_location);
}
}
/**
@param location Location of the tile block template to be edited.
*/
bool QETTitleBlockTemplateEditor::edit(
const TitleBlockTemplateLocation &location)
{
// the template name may be empty to create a new one
const TitleBlockTemplate *tb_template_orig;
if (location.name().isEmpty()) {
// loads the default title block template provided by the application
// it will be used as a start point to design the title block
tb_template_orig = QETApp::defaultTitleBlockTemplate();
} else {
tb_template_orig = location.getTemplate();
}
if (!tb_template_orig) {
/// TODO The TBT does not exist, manage error
#if TODO_LIST
#pragma message("@TODO The TBT does not exist, manage error")
#endif
return(false);
}
opened_from_file_ = false;
location_ = location;
setReadOnly(location.isReadOnly());
editCopyOf(tb_template_orig);
return(true);
}
/**
Edit the given template.
@param project Parent project of the template to edit.
@param template_name Name of the template to edit within its parent project.
@return true if this editor was able to edit the given template, false otherwise
*/
bool QETTitleBlockTemplateEditor::edit(
QETProject *project, const QString &template_name)
{
// we require a project we will rattach templates to
if (!project) return(false);
// the template name may be empty to create a new one
const TitleBlockTemplate *tb_template_orig;
if (template_name.isEmpty())
{
// loads the default title block template provided by the application
// it will be used as a start point to design the title block
tb_template_orig = QETApp::defaultTitleBlockTemplate();
}
else
{
tb_template_orig = project->embeddedTitleBlockTemplatesCollection()->getTemplate(template_name);
}
if (!tb_template_orig) {
/// TODO The TBT does not exist, manage error
#if TODO_LIST
#pragma message("@TODO The TBT does not exist, manage error")
#endif
return(false);
}
opened_from_file_ = false;
location_.setParentCollection(project -> embeddedTitleBlockTemplatesCollection());
location_.setName(template_name);
setReadOnly(project -> isReadOnly());
return(editCopyOf(tb_template_orig));
}
/**
@param file_path Path of the template file to edit.
@return false if a problem occurred while opening the template, true otherwise.
*/
bool QETTitleBlockTemplateEditor::edit(const QString &file_path)
{
// get title block template object from the file, edit it
TitleBlockTemplate *tbt = new TitleBlockTemplate();
bool loading = tbt -> loadFromXmlFile(file_path);
if (!loading) {
#if TODO_LIST
#pragma message("@TODO the file opening failed, warn the user?")
#endif
/// TODO the file opening failed, warn the user?
return(false);
}
bool editing = edit(tbt);
if (!editing) {
#if TODO_LIST
#pragma message("@TODO the file editing failed, warn the user?")
#endif
/// TODO the file editing failed, warn the user?
return(false);
}
QFileInfo file_path_info(file_path);
filepath_ = file_path;
opened_from_file_ = true;
setReadOnly(!file_path_info.isWritable());
return(true);
}
/**
@param tbt Title block template to be edited
@return false if a problem occurred while opening the template, true otherwise.
*/
bool QETTitleBlockTemplateEditor::editCopyOf(const TitleBlockTemplate *tbt)
{
if (!tbt) return(false);
return(edit(tbt -> clone()));
}
/**
@param tbt Title block template to be directly edited
@return false if a problem occurred while opening the template, true otherwise.
*/
bool QETTitleBlockTemplateEditor::edit(TitleBlockTemplate *tbt)
{
if (!tbt) return(false);
tb_template_ = tbt;
template_edition_area_view_ -> setTitleBlockTemplate(tb_template_);
template_cell_editor_widget_ -> updateLogosComboBox(tb_template_);
updateEditorTitle();
return(true);
}
/**
Launches the logo manager widget, which allows the user to manage the
logos embedded within the edited template.
*/
void QETTitleBlockTemplateEditor::editLogos()
{
if (tb_template_) {
if (!logo_manager_) {
initLogoManager();
}
logo_manager_ -> layout() -> setContentsMargins(0, 0, 0, 0);
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Close);
QVBoxLayout *vlayout0 = new QVBoxLayout();
vlayout0 -> addWidget(logo_manager_);
vlayout0 -> addWidget(buttons);
QDialog d(this);
d.setWindowTitle(logo_manager_ -> windowTitle());
d.setLayout(vlayout0);
connect(buttons, &QDialogButtonBox::rejected, &d, &QDialog::reject);
d.exec();
// prevent the logo manager from being deleted along with the dialog
logo_manager_ -> setParent(this);
}
}
/**
Launch a new title block template editor.
*/
void QETTitleBlockTemplateEditor::newTemplate()
{
QETTitleBlockTemplateEditor *qet_template_editor = new QETTitleBlockTemplateEditor();
qet_template_editor -> edit(TitleBlockTemplateLocation());
qet_template_editor -> show();
qet_template_editor -> readSettingsState(); // must run after show() in Qt6
}
/**
Initialize the various actions.
*/
void QETTitleBlockTemplateEditor::initActions()
{
new_ = new QAction(QET::Icons::DocumentNew, tr("&New", "menu entry"), this);
open_ = new QAction(QET::Icons::DocumentOpen, tr("&Open", "menu entry"), this);
open_from_file_ = new QAction(QET::Icons::DocumentOpen, tr("Open from a file", "menu entry"), this);
save_ = new QAction(QET::Icons::DocumentSave, tr("&Save", "menu entry"), this);
save_as_ = new QAction(QET::Icons::DocumentSaveAs, tr("Save as", "menu entry"), this);
save_as_file_ = new QAction(QET::Icons::DocumentSaveAs, tr("Save to a file", "menu entry"), this);
quit_ = new QAction(QET::Icons::ApplicationExit, tr("&Quit", "menu entry"), this);
undo_ = undo_stack_ -> createUndoAction(this);
redo_ = undo_stack_ -> createRedoAction(this);
cut_ = new QAction(QET::Icons::EditCut, tr("Cu&t", "menu entry"), this);
copy_ = new QAction(QET::Icons::EditCopy, tr("&Copy", "menu entry"), this);
paste_ = new QAction(QET::Icons::EditPaste, tr("&Paste", "menu entry"), this);
edit_logos_ = new QAction(QET::Icons::InsertImage, tr("Manage logos", "menu entry"), this);
edit_info_ = new QAction(QET::Icons::UserInformations, tr("Edit extra information", "menu entry"), this);
zoom_in_ = new QAction(QET::Icons::ZoomIn, tr("Zoom In", "menu entry"), this);
zoom_out_ = new QAction(QET::Icons::ZoomOut, tr("Zoom out", "menu entry"), this);
zoom_fit_ = new QAction(QET::Icons::ZoomFitBest, tr("Fit in view", "menu entry"), this);
zoom_reset_ = new QAction(QET::Icons::ZoomOriginal, tr("Reset zoom", "menu entry"), this);
add_row_ = new QAction(QET::Icons::EditTableInsertRowAbove, tr("Add a &row", "menu entry"), this);
add_col_ = new QAction(QET::Icons::EditTableInsertColumnRight, tr("Add a &column", "menu entry"), this);
merge_cells_ = new QAction(QET::Icons::EditTableCellMerge, tr("&Merge cells", "menu entry"), this);
split_cell_ = new QAction(QET::Icons::EditTableCellSplit, tr("&Split cells", "menu entry"), this);
undo_ -> setIcon(QET::Icons::EditUndo);
redo_ -> setIcon(QET::Icons::EditRedo);
ShortcutManager::instance().registerAction(new_, "titleblockeditor.new", tr("Title block template editors"), QKeySequence::New);
ShortcutManager::instance().registerAction(open_, "titleblockeditor.open", tr("Title block template editors"), QKeySequence::Open);
ShortcutManager::instance().registerAction(open_from_file_, "titleblockeditor.open_from_file", tr("Title block template editors"), Qt::CTRL | Qt::SHIFT | Qt::Key_O);
ShortcutManager::instance().registerAction(save_, "titleblockeditor.save", tr("Title block template editors"), QKeySequence::Save);
ShortcutManager::instance().registerAction(save_as_file_, "titleblockeditor.save_as_file", tr("Title block template editors"), Qt::CTRL | Qt::SHIFT | Qt::Key_S);
ShortcutManager::instance().registerAction(quit_, "titleblockeditor.quit", tr("Title block template editors"), Qt::CTRL | Qt::Key_Q);
ShortcutManager::instance().registerAction(undo_, "titleblockeditor.undo", tr("Title block template editors"), QKeySequence::Undo);
ShortcutManager::instance().registerAction(redo_, "titleblockeditor.redo", tr("Title block template editors"), QKeySequence::Redo);
ShortcutManager::instance().registerAction(cut_, "titleblockeditor.cut", tr("Title block template editors"), QKeySequence::Cut);
ShortcutManager::instance().registerAction(copy_, "titleblockeditor.copy", tr("Title block template editors"), QKeySequence::Copy);
ShortcutManager::instance().registerAction(paste_, "titleblockeditor.paste", tr("Title block template editors"), QKeySequence::Paste);
ShortcutManager::instance().registerAction(edit_logos_, "titleblockeditor.edit_logos", tr("Title block template editors"), Qt::CTRL | Qt::Key_T);
ShortcutManager::instance().registerAction(edit_info_, "titleblockeditor.edit_info", tr("Title block template editors"), Qt::CTRL | Qt::Key_Y);
ShortcutManager::instance().registerAction(merge_cells_, "titleblockeditor.merge_cells", tr("Title block template editors"), Qt::CTRL | Qt::Key_J);
ShortcutManager::instance().registerAction(split_cell_, "titleblockeditor.split_cell", tr("Title block template editors"), Qt::CTRL | Qt::Key_K);
ShortcutManager::instance().registerAction(zoom_in_, "titleblockeditor.zoom_in", tr("Title block template editors"), QKeySequence::ZoomIn);
ShortcutManager::instance().registerAction(zoom_out_, "titleblockeditor.zoom_out", tr("Title block template editors"), QKeySequence::ZoomOut);
ShortcutManager::instance().registerAction(zoom_fit_, "titleblockeditor.zoom_fit", tr("Title block template editors"), Qt::CTRL | Qt::Key_9);
ShortcutManager::instance().registerAction(zoom_reset_, "titleblockeditor.zoom_reset", tr("Title block template editors"), Qt::CTRL | Qt::Key_0);
connect(new_, &QAction::triggered, this, &QETTitleBlockTemplateEditor::newTemplate);
connect(open_, &QAction::triggered, this, &QETTitleBlockTemplateEditor::open);
connect(open_from_file_, &QAction::triggered, this, &QETTitleBlockTemplateEditor::openFromFile);
connect(save_, &QAction::triggered, this, &QETTitleBlockTemplateEditor::save);
connect(save_as_, &QAction::triggered, this, qOverload<>(&QETTitleBlockTemplateEditor::saveAs));
connect(save_as_file_, &QAction::triggered, this, &QETTitleBlockTemplateEditor::saveAsFile);
connect(quit_, &QAction::triggered, this, &QETTitleBlockTemplateEditor::quit);
connect(cut_, &QAction::triggered, template_edition_area_view_, &TitleBlockTemplateView::cut);
connect(copy_, &QAction::triggered, template_edition_area_view_, &TitleBlockTemplateView::copy);
connect(paste_, &QAction::triggered, template_edition_area_view_, &TitleBlockTemplateView::paste);
connect(zoom_in_, &QAction::triggered, template_edition_area_view_, &TitleBlockTemplateView::zoomIn);
connect(zoom_out_, &QAction::triggered, template_edition_area_view_, &TitleBlockTemplateView::zoomOut);
connect(zoom_fit_, &QAction::triggered, template_edition_area_view_, &TitleBlockTemplateView::zoomFit);
connect(zoom_reset_, &QAction::triggered, template_edition_area_view_, &TitleBlockTemplateView::zoomReset);
connect(edit_logos_, &QAction::triggered, this, &QETTitleBlockTemplateEditor::editLogos);
connect(edit_info_, &QAction::triggered, this, &QETTitleBlockTemplateEditor::editTemplateInformation);
connect(add_row_, &QAction::triggered, template_edition_area_view_, &TitleBlockTemplateView::addRowAtEnd);
connect(add_col_, &QAction::triggered, template_edition_area_view_, &TitleBlockTemplateView::addColumnAtEnd);
connect(merge_cells_, &QAction::triggered, template_edition_area_view_, &TitleBlockTemplateView::mergeSelectedCells);
connect(split_cell_, &QAction::triggered, template_edition_area_view_, &TitleBlockTemplateView::splitSelectedCell);
}
/**
Initialize the various menus.
*/
void QETTitleBlockTemplateEditor::initMenus()
{
file_menu_ = new QMenu(tr("&File", "menu title"), this);
edit_menu_ = new QMenu(tr("&Edit", "menu title"), this);
display_menu_ = new QMenu(tr("Displ&ay", "menu title"), this);
file_menu_ -> addAction(new_);
file_menu_ -> addAction(open_);
file_menu_ -> addAction(open_from_file_);
file_menu_ -> addAction(save_);
file_menu_ -> addAction(save_as_);
file_menu_ -> addAction(save_as_file_);
file_menu_ -> addSeparator();
file_menu_ -> addAction(quit_);
edit_menu_ -> addAction(undo_);
edit_menu_ -> addAction(redo_);
edit_menu_ -> addSeparator();
edit_menu_ -> addAction(cut_);
edit_menu_ -> addAction(copy_);
edit_menu_ -> addAction(paste_);
edit_menu_ -> addSeparator();
edit_menu_ -> addAction(add_row_);
edit_menu_ -> addAction(add_col_);
edit_menu_ -> addAction(merge_cells_);
edit_menu_ -> addAction(split_cell_);
edit_menu_ -> addSeparator();
edit_menu_ -> addAction(edit_logos_);
edit_menu_ -> addAction(edit_info_);
display_menu_ -> addAction(zoom_in_);
display_menu_ -> addAction(zoom_out_);
display_menu_ -> addAction(zoom_fit_);
display_menu_ -> addAction(zoom_reset_);
insertMenu(settings_menu_, file_menu_);
insertMenu(settings_menu_, edit_menu_);
insertMenu(settings_menu_, display_menu_);
}
/**
Initialize toolbars.
*/
void QETTitleBlockTemplateEditor::initToolbars()
{
QToolBar *main_toolbar = new QToolBar(tr("Tools", "toolbar title"), this);
main_toolbar -> setObjectName("tbt_main_toolbar");
main_toolbar -> addAction(new_);
main_toolbar -> addAction(open_);
main_toolbar -> addAction(save_);
main_toolbar -> addAction(save_as_);
addToolBar(Qt::TopToolBarArea, main_toolbar);
QToolBar *edit_toolbar = new QToolBar(tr("Edit", "toolbar title"), this);
edit_toolbar -> setObjectName("tbt_edit_toolbar");
edit_toolbar -> addAction(undo_);
edit_toolbar -> addAction(redo_);
edit_toolbar -> addSeparator();
edit_toolbar -> addAction(merge_cells_);
edit_toolbar -> addAction(split_cell_);
addToolBar(Qt::TopToolBarArea, edit_toolbar);
QToolBar *display_toolbar = new QToolBar(tr("Display", "toolbar title"), this);
display_toolbar -> setObjectName("tbt_display_toolbar");
display_toolbar -> addAction(zoom_in_);
display_toolbar -> addAction(zoom_out_);
display_toolbar -> addAction(zoom_fit_);
display_toolbar -> addAction(zoom_reset_);
addToolBar(Qt::TopToolBarArea, display_toolbar);
}
/**
Initialize layouts and widgets
*/
void QETTitleBlockTemplateEditor::initWidgets()
{
QSettings settings;
// undo list on the right
undo_stack_ = new QUndoStack(this);
undo_view_ = new QUndoView(undo_stack_);
undo_view_ -> setEmptyLabel(tr("No modification", "label displayed in the undo list when empty"));
undo_dock_widget_ = new QDockWidget(tr("Undo", "dock title"));
undo_dock_widget_ -> setObjectName("tbt_undo_dock");
undo_dock_widget_ -> setFeatures(
QDockWidget::DockWidgetClosable
|QDockWidget::DockWidgetMovable
|QDockWidget::DockWidgetFloatable);
undo_dock_widget_ -> setWidget(undo_view_);
undo_dock_widget_ -> setMinimumWidth(290);
addDockWidget(Qt::RightDockWidgetArea, undo_dock_widget_);
// WYSIWYG editor as central widget
template_edition_area_scene_ = new QGraphicsScene(this);
template_edition_area_view_ = new TitleBlockTemplateView(template_edition_area_scene_);
bool conv_ok;
int conf_preview_width = settings.value("titleblocktemplateeditor/preview_width", -1).toInt(&conv_ok);
if (conv_ok && conf_preview_width != -1) {
template_edition_area_view_ -> setPreviewWidth(conf_preview_width);
}
setCentralWidget(template_edition_area_view_);
// cell edition widget at the bottom
template_cell_editor_widget_ = new TitleBlockTemplateCellWidget(tb_template_);
template_cell_editor_dock_widget_ = new QDockWidget(tr("Cell properties", "dock title"), this);
template_cell_editor_dock_widget_ -> setObjectName("tbt_celleditor_dock");
template_cell_editor_dock_widget_ -> setFeatures(
QDockWidget::DockWidgetClosable
|QDockWidget::DockWidgetMovable
|QDockWidget::DockWidgetFloatable);
template_cell_editor_dock_widget_ -> setWidget(template_cell_editor_widget_);
template_cell_editor_dock_widget_ -> setMinimumWidth(180);
template_cell_editor_dock_widget_ -> setMinimumHeight(250);
addDockWidget(Qt::BottomDockWidgetArea, template_cell_editor_dock_widget_);
template_cell_editor_widget_ -> setVisible(false);
connect(
template_edition_area_view_,
&TitleBlockTemplateView::selectedCellsChanged,
this,
&QETTitleBlockTemplateEditor::selectedCellsChanged
);
connect(
template_cell_editor_widget_,
&TitleBlockTemplateCellWidget::logoEditionRequested,
this,
&QETTitleBlockTemplateEditor::editLogos
);
connect(
template_cell_editor_widget_,
&TitleBlockTemplateCellWidget::cellModified,
this,
&QETTitleBlockTemplateEditor::pushCellUndoCommand
);
connect(
template_edition_area_view_,
&TitleBlockTemplateView::gridModificationRequested,
this,
&QETTitleBlockTemplateEditor::pushGridUndoCommand
);
connect(
template_edition_area_view_,
&TitleBlockTemplateView::previewWidthChanged,
this,
&QETTitleBlockTemplateEditor::savePreviewWidthToApplicationSettings
);
connect(undo_stack_, &QUndoStack::cleanChanged, this, &QETTitleBlockTemplateEditor::updateEditorTitle);
connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &QETTitleBlockTemplateEditor::updateActions);
}
/**
Initialize the logo manager
*/
void QETTitleBlockTemplateEditor::initLogoManager()
{
logo_manager_ = new TitleBlockTemplateLogoManager(tb_template_, this);
logo_manager_ -> setReadOnly(read_only_);
connect(
logo_manager_,
&TitleBlockTemplateLogoManager::logosChanged,
template_cell_editor_widget_,
&TitleBlockTemplateCellWidget::updateLogosComboBox
);
}
/**
@return a string describing what is being edited, along with [Changed] or
[Read only] tags. Useful to compose the window title.
*/
QString QETTitleBlockTemplateEditor::currentlyEditedTitle() const
{
QString titleblock_title;
if (opened_from_file_) {
titleblock_title = filepath_;
} else {
titleblock_title = location_.name();
}
// if a (file)name has been added, also add a "[Changed]" tag if needed
if (!titleblock_title.isEmpty()) {
QString tag;
if (!undo_stack_ -> isClean()) {
tag = tr("[Changed]", "window title tag");
}
if (read_only_) {
tag = tr("[Read only]", "window title tag");
}
titleblock_title = QString(
tr(
"%1 %2",
"part of the window title - %1 is the filepath or template name, %2 is the [Changed] or [Read only] tag"
)
).arg(titleblock_title).arg(tag);
}
return(titleblock_title);
}
/**
@brief QETTitleBlockTemplateEditor::readSettings
Read settings
*/
void QETTitleBlockTemplateEditor::readSettings()
{
QSettings settings;
// window size and position
QVariant geometry = settings.value("titleblocktemplateeditor/geometry");
if (geometry.isValid()) restoreGeometry(geometry.toByteArray());
}
/**
@brief QETTitleBlockTemplateEditor::readSettingsState
Restore the window state (docks, toolbars).
Must be called AFTER show() in Qt6 for restoreState() to work correctly
-- callers are responsible for calling this after show(), since this
window isn't shown by its own constructor.
*/
void QETTitleBlockTemplateEditor::readSettingsState()
{
QSettings settings;
QVariant state = settings.value("titleblocktemplateeditor/state");
if (state.isValid()) {
if (!restoreState(state.toByteArray())) {
settings.remove("titleblocktemplateeditor/state");
}
}
}
/**
@brief QETTitleBlockTemplateEditor::writeSettings
Write the settings
*/
void QETTitleBlockTemplateEditor::writeSettings()
{
QSettings settings;
settings.setValue("titleblocktemplateeditor/geometry", saveGeometry());
settings.setValue("titleblocktemplateeditor/state", saveState());
}
/**
Update various things when user changes the selected cells.
@param selected_cells List of selected cells.
*/
void QETTitleBlockTemplateEditor::selectedCellsChanged(
const QList<TitleBlockCell *>& selected_cells)
{
if (selected_cells.count() == 1) {
template_cell_editor_widget_ -> edit(selected_cells.at(0));
template_cell_editor_widget_ -> setVisible(true);
} else {
template_cell_editor_widget_ -> setVisible(false);
}
updateActions();
}
/**
Configure an undo Command before adding it to the undo stack.
@param command to be added to the undo stack
*/
void QETTitleBlockTemplateEditor::pushCellUndoCommand(
ModifyTitleBlockCellCommand *command)
{
command -> setView(template_edition_area_view_);
pushUndoCommand(command);
}
/**
Add an undo Command to the undo stack.
@param command QUndoCommand to be added to the undo stack
*/
void QETTitleBlockTemplateEditor::pushGridUndoCommand(
TitleBlockTemplateCommand *command)
{
pushUndoCommand(command);
}
/**
Add an undo Command to the undo stack.
@param command QUndoCommand to be added to the undo stack
*/
void QETTitleBlockTemplateEditor::pushUndoCommand(QUndoCommand *command) {
undo_stack_ -> push(command);
}
/**
Set the title of this editor.
*/
void QETTitleBlockTemplateEditor::updateEditorTitle()
{
// base title
QString min_title(
tr(
"QElectroTech - Title block template editor",
"titleblock template editor: base window title"
)
);
// get the currently edited template (file)name
QString titleblock_title = currentlyEditedTitle();
// generate the final window title
QString title;
if (titleblock_title.isEmpty()) {
title = min_title;
} else {
title = QString(
tr(
"%1 - %2",
"window title: %1 is the base window title, %2 is a template name"
)
).arg(min_title).arg(titleblock_title);
}
setWindowTitle(title);
}
/**
Ensure the user interface remains consistent by enabling or disabling
adequate actions.
*/
void QETTitleBlockTemplateEditor::updateActions()
{
save_ -> setEnabled(!read_only_);
bool can_merge = true;
bool can_split = true;
int count = 0;
if (!read_only_) {
template_edition_area_view_ -> analyzeSelectedCells(&can_merge, &can_split, &count);
}
cut_ -> setEnabled(!read_only_ && count);
copy_ -> setEnabled(count);
paste_ -> setEnabled(!read_only_ && count && template_edition_area_view_ -> mayPaste());
add_row_ -> setEnabled(!read_only_);
add_col_ -> setEnabled(!read_only_);
merge_cells_ -> setEnabled(!read_only_ && can_merge);
split_cell_ -> setEnabled(!read_only_ && can_split);
}
/**
Save the template under the provided location.
@see QETProject::setTemplateXmlDescription()
@param location Location where the title block template should be saved.
*/
bool QETTitleBlockTemplateEditor::saveAs(const TitleBlockTemplateLocation &location) {
TitleBlockTemplatesCollection *collection = location.parentCollection();
if (!collection) return(false);
QDomDocument doc;
QDomElement elmt = doc.createElement("root");
tb_template_ -> saveToXmlElement(elmt);
elmt.setAttribute("name", location.name());
doc.appendChild(elmt);
if (!collection -> setTemplateXmlDescription(location.name(), elmt)) {
// Report the failure instead of marking the template "saved"
// regardless (bugtracker #251) -- this return value used to
// be discarded here.
QET::QetMessageBox::critical(
this,
tr("Error", "message box title"),
tr(
"Unable to save the template ‘%1’.",
"message box content - %1 is a title block template name"
).arg(location.name())
);
return(false);
}
opened_from_file_ = false;
location_ = location;
undo_stack_ -> setClean();
setReadOnly(false);
return(true);
}
/**
Save the template in the provided filepath.
@see TitleBlockTemplate::saveToXmlFile()
@param filepath location Location where the title block template should be saved.
*/
bool QETTitleBlockTemplateEditor::saveAs(const QString &filepath) {
bool saving = tb_template_ -> saveToXmlFile(filepath);
if (!saving) return(false);
opened_from_file_ = true;
filepath_ = filepath;
undo_stack_ -> setClean();
setReadOnly(false);
return(true);
}
/**
Ask the user to choose a title block template from the known collections
then open it for edition.
*/
void QETTitleBlockTemplateEditor::open()
{
TitleBlockTemplateLocation location = getTitleBlockTemplateLocationFromUser(
tr("Open a title block template", "File > open dialog window title"),
true
);
if (location.isValid()) {
QETApp::instance() -> openTitleBlockTemplate(location);
}
}
/**
Ask the user to choose a file supposed to contain a title block template,
then open it for edition.
*/
void QETTitleBlockTemplateEditor::openFromFile()
{
// directory to show
QString initial_dir = filepath_.isEmpty()
? QETApp::customTitleBlockTemplatesDir()
: QDir(filepath_).absolutePath();
// ask the user to choose a filepath
QString user_filepath = QFileDialog::getOpenFileName(
this,
tr("Open a file", "dialog title"),
initial_dir,
tr(
"QElectroTech title block templates "
"(*%1);;XML files (*.xml);; All files (*)",
"filetypes allowed when opening a title block template file"
" - %1 is the .titleblock extension"
).arg(QString(TITLEBLOCKS_FILE_EXTENSION))
);
if (!user_filepath.isEmpty()) QETApp::instance() -> openTitleBlockTemplate(user_filepath);
}
/**
Save the currently edited title block template back to its parent project.
*/
bool QETTitleBlockTemplateEditor::save()
{
if (opened_from_file_) {
if (!filepath_.isEmpty()) {
QFileInfo file_path_info(filepath_);
if (file_path_info.isWritable()) {
return(saveAs(filepath_));
}
}
return(saveAsFile());
} else {
if (location_.isValid()) {
if (!location_.isReadOnly()) {
return(saveAs(location_));
}
}
return(saveAs());
}
}
/**
Ask the user where he wishes to save the currently edited template.
*/
bool QETTitleBlockTemplateEditor::saveAs()
{
TitleBlockTemplateLocation location = getTitleBlockTemplateLocationFromUser(
tr("Save template as", "dialog window title"),
false
);
if (location.isValid()) {
return(saveAs(location));
}
if (!location.name().isEmpty()) {
// The name was rejected (e.g. contains a path separator or
// other filesystem-reserved character) rather than the user
// cancelling the dialog -- say so instead of silently doing
// nothing (bugtracker #251).
QET::QetMessageBox::critical(
this,
tr("Error", "message box title"),
tr(
"The name ‘%1’ is invalid: it must not contain the "
"following characters: \\ / : * ? \" < > |",
"message box content - %1 is the rejected template name"
).arg(location.name())
);
}
return(false);
}
/**
Ask the user where on the filesystem he wishes to save the currently edited template.
*/
bool QETTitleBlockTemplateEditor::saveAsFile()
{
// directory to show
QString initial_dir = filepath_.isEmpty()
? QETApp::customTitleBlockTemplatesDir()
: QDir(filepath_).absolutePath();
// ask the user to choose a target file
QString filepath = QFileDialog::getSaveFileName(
this,
tr("Save as", "dialog title"),
initial_dir,
tr(
"QElectroTech title block templates (*%1)",
"filetypes allowed when saving a title block template file - %1 is the .titleblock extension"
).arg(QString(TITLEBLOCKS_FILE_EXTENSION))
);
// if no name was entered, return false
if (filepath.isEmpty()) return(false);
// if the name does not end with ".titleblock", add it
if (!filepath.endsWith(".titleblock", Qt::CaseInsensitive)) filepath += ".titleblock";
// attempts to save the file
bool saving = saveAs(filepath);
// retourne un booleen representatif de la reussite de l'enregistrement
return(saving);
}
/**
@param read_only True to restrict this editor to visualization of the
currently edited template, false to allow full edition.
*/
void QETTitleBlockTemplateEditor::setReadOnly(bool read_only) {
if (read_only != read_only_) {
read_only_ = read_only;
if (logo_manager_) {
logo_manager_ -> setReadOnly(read_only_);
}
template_cell_editor_widget_ -> setReadOnly(read_only_);
template_edition_area_view_ -> setReadOnly(read_only_);
}
updateActions();
updateEditorTitle();
}
/**
Ask the user for a title block template location
@param title Title displayed by the dialog window
@param existing_only True for the user to be forced to choose an existing
template, false if he may specify the template name
@return The location chosen by the user, or an empty
TitleBlockTemplateLocation if the user cancelled the dialog
*/
TitleBlockTemplateLocation QETTitleBlockTemplateEditor::getTitleBlockTemplateLocationFromUser(
const QString &title, bool existing_only)
{
TitleBlockTemplateLocationChooser *widget;
if (existing_only) {
widget = new TitleBlockTemplateLocationChooser(location());
} else {
widget = new TitleBlockTemplateLocationSaver(location());
}
QDialogButtonBox *buttons = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
QVBoxLayout *dialog_layout = new QVBoxLayout();
dialog_layout -> addWidget(widget);
dialog_layout -> addWidget(buttons);
QDialog dialog;
dialog.setWindowTitle(title);
dialog.setLayout(dialog_layout);
connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
if (dialog.exec() == QDialog::Accepted) {
return(widget -> location());
}
return TitleBlockTemplateLocation();
}
/**
Close the current editor.
*/
void QETTitleBlockTemplateEditor::quit()
{
close();
}
/**
@brief QETTitleBlockTemplateEditor::savePreviewWidthToApplicationSettings
Save the new preview width to application settings
@param former_preview_width : former_preview_width Unused, former preview width
@param new_preview_width : new_preview_width New preview width
*/
void QETTitleBlockTemplateEditor::savePreviewWidthToApplicationSettings(
int former_preview_width, int new_preview_width)
{
Q_UNUSED(former_preview_width)
QSettings settings;
settings.setValue("titleblocktemplateeditor/preview_width", new_preview_width);
}
/**
Edit extra information attached to the template.
*/
void QETTitleBlockTemplateEditor::editTemplateInformation()
{
if (!tb_template_) return;
QDialog dialog_author(this);
dialog_author.setModal(true);
#ifdef Q_OS_MACOS
dialog_author.setWindowFlags(Qt::Sheet);
#endif
dialog_author.setMinimumSize(400, 260);
dialog_author.setWindowTitle(tr("Edit extra information", "window title"));
QVBoxLayout *dialog_layout = new QVBoxLayout(&dialog_author);
// explanation label
QLabel *information_label = new QLabel(tr("You can use this field to specify the authors of this template, its license, or any other information you may find useful."));
information_label -> setAlignment(Qt::AlignJustify | Qt::AlignVCenter);
information_label -> setWordWrap(true);
dialog_layout -> addWidget(information_label);
// add a QTextEdit to the dialog
QTextEdit *text_field = new QTextEdit();
text_field -> setAcceptRichText(false);
text_field -> setPlainText(tb_template_ -> information());
text_field -> setReadOnly(read_only_);
dialog_layout -> addWidget(text_field);
// add two buttons to the dialog
QDialogButtonBox *dialog_buttons = new QDialogButtonBox(
read_only_
? QDialogButtonBox::Ok
: QDialogButtonBox::Ok
| QDialogButtonBox::Cancel);
dialog_layout -> addWidget(dialog_buttons);
connect(dialog_buttons, &QDialogButtonBox::accepted, &dialog_author, &QDialog::accept);
connect(dialog_buttons, &QDialogButtonBox::rejected, &dialog_author, &QDialog::reject);
// run the dialog
if (dialog_author.exec() == QDialog::Accepted && !read_only_) {
QString new_info = text_field -> toPlainText().remove(QChar(13)); // CR-less text
if (new_info != tb_template_ -> information()) {
pushUndoCommand(new ChangeTemplateInformationsCommand(
tb_template_,
tb_template_ -> information(),
new_info));
}
}
}