mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-17 19:34:13 +02:00
e8a5d6aec0
Saving replaced the project file whatever had happened to it since it was opened. If another user, another program or a second QET had saved the same file in between (a shared drive, a copy synced from another PC), their work was lost without a word. QETProject now remembers the project file's size and modification time when it reads it and after each save of its own. Before writing, write() compares them with the file on disk and, if they differ, writes nothing and returns "someone else changed the file on disk at <time> since it was opened". - Interactive save (Ctrl+S, and the save on closing a project) asks: "Someone else changed <file> on disk at <time> since you opened it" with Save as a copy (opens Save As), Overwrite, or Cancel. - Scripts (--run), the command line, MCP and the autosave timer have no one to ask, so they keep the refusal (autosave logs it). Setting QET_OVERWRITE_CHANGED=1 makes a script overwrite on purpose. - A file that does not exist yet, and a Save As to another name, are not a conflict; after Save As the remembered state is the new file's. No change to what is written, so .qet and .qetz stay as they were. Evidence: new tst_changedondisk (unchanged file saves twice; a file written behind the open project is refused; the override overwrites; a Save As target is no conflict) fails on the changed-file case without the source change and passes with it. Full ctest 107/107. Physical check with qet-gui-dialog.sh: file modified behind the open project, Ctrl+S shows the three-button dialog. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgPFANhWgD66V3v9KVKV58
1352 lines
41 KiB
C++
1352 lines
41 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 "projectview.h"
|
|
|
|
#include "ElementsCollection/xmlelementcollection.h"
|
|
#include "autoNum/assignvariables.h"
|
|
#include "diagram.h"
|
|
#include "diagramview.h"
|
|
#include "editor/ui/qetelementeditor.h"
|
|
#include "exportdialog.h"
|
|
#include "qetapp.h"
|
|
#include "qeticons.h"
|
|
#include "qetmessagebox.h"
|
|
#include "qetproject.h"
|
|
#include "titleblock/qettemplateeditor.h"
|
|
#include "ui/borderpropertieswidget.h"
|
|
#include "ui/conductorpropertieswidget.h"
|
|
#include "ui/dialogwaiting.h"
|
|
#include "ui/projectpropertiesdialog.h"
|
|
#include "ui/titleblockpropertieswidget.h"
|
|
#include "undocommand/movediagramcommand.h"
|
|
#include "undocommand/removediagramcommand.h"
|
|
|
|
/**
|
|
Constructeur
|
|
@param project projet a visualiser
|
|
@param parent Widget parent
|
|
*/
|
|
ProjectView::ProjectView(QETProject *project, QWidget *parent) :
|
|
QWidget(parent),
|
|
m_project(nullptr)
|
|
{
|
|
initActions();
|
|
initWidgets();
|
|
initLayout();
|
|
|
|
setProject(project);
|
|
}
|
|
|
|
/**
|
|
Destructeur
|
|
Supprime les DiagramView embarquees
|
|
*/
|
|
ProjectView::~ProjectView()
|
|
{
|
|
for (auto dv_ : m_diagram_ids.values())
|
|
dv_->deleteLater();
|
|
}
|
|
|
|
/**
|
|
@return le projet actuellement visualise par le ProjectView
|
|
*/
|
|
QETProject *ProjectView::project()
|
|
{
|
|
return(m_project);
|
|
}
|
|
|
|
/**
|
|
@brief ProjectView::setProject
|
|
Set the project display by the project view
|
|
@param project
|
|
*/
|
|
void ProjectView::setProject(QETProject *project)
|
|
{
|
|
if (!m_project)
|
|
{
|
|
m_project = project;
|
|
connect(m_project, &QETProject::projectTitleChanged, this, &ProjectView::updateWindowTitle);
|
|
connect(m_project, &QETProject::projectModified, this, &ProjectView::updateWindowTitle);
|
|
connect(m_project, &QETProject::readOnlyChanged, this, &ProjectView::adjustReadOnlyState);
|
|
connect(m_project, &QETProject::addAutoNumDiagram, [this](){this->project()->addNewDiagram();});
|
|
|
|
connect(m_project, &QETProject::diagramAdded, [this](QETProject *project, Diagram *diagram) {
|
|
Q_UNUSED(project)
|
|
this->diagramAdded(diagram);
|
|
});
|
|
connect(m_project, &QETProject::diagramRemoved, [this](QETProject *project, Diagram *diagram) {
|
|
Q_UNUSED(project)
|
|
this->diagramRemoved(diagram);
|
|
});
|
|
|
|
adjustReadOnlyState();
|
|
loadDiagrams();
|
|
}
|
|
}
|
|
|
|
/**
|
|
@return la liste des schemas ouverts dans le projet
|
|
*/
|
|
QList<DiagramView *> ProjectView::diagram_views() const
|
|
{
|
|
return(m_diagram_view_list);
|
|
}
|
|
|
|
/**
|
|
@brief ProjectView::currentDiagram
|
|
@return The current active diagram view or nullptr if there isn't diagramView in this project view.
|
|
*/
|
|
DiagramView *ProjectView::currentDiagram() const
|
|
{
|
|
int current_tab_index = m_tab -> currentIndex();
|
|
if (current_tab_index == -1)
|
|
return nullptr;
|
|
return(m_diagram_ids[current_tab_index]);
|
|
}
|
|
|
|
/**
|
|
Gere la fermeture du schema.
|
|
@param qce Le QCloseEvent decrivant l'evenement
|
|
*/
|
|
void ProjectView::closeEvent(QCloseEvent *qce) {
|
|
bool can_close_project = tryClosing();
|
|
if (can_close_project) {
|
|
qce -> accept();
|
|
emit(projectClosed(this));
|
|
} else {
|
|
qce -> ignore();
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief change current diagramview to next folio
|
|
*/
|
|
void ProjectView::changeTabDown()
|
|
{
|
|
DiagramView *nextDiagramView = this->nextDiagram();
|
|
if (nextDiagramView!=nullptr){
|
|
rebuildDiagramsMap();
|
|
m_tab -> setCurrentWidget(nextDiagramView);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@return next folio of current diagramview
|
|
*/
|
|
DiagramView *ProjectView::nextDiagram()
|
|
{
|
|
int current_tab_index = m_tab -> currentIndex();
|
|
int next_tab_index = current_tab_index + 1; //get next tab index
|
|
if (next_tab_index<m_diagram_ids.count()) //if next tab index >= greatest tab the last tab is activated so no need to change tab.
|
|
return(m_diagram_ids[next_tab_index]);
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
@brief change current diagramview to previous tab
|
|
*/
|
|
void ProjectView::changeTabUp()
|
|
{
|
|
DiagramView *previousDiagramView = this->previousDiagram();
|
|
if (previousDiagramView!=nullptr){
|
|
rebuildDiagramsMap();
|
|
m_tab -> setCurrentWidget(previousDiagramView);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@return previous folio of current diagramview
|
|
*/
|
|
DiagramView *ProjectView::previousDiagram()
|
|
{
|
|
int current_tab_index = m_tab -> currentIndex();
|
|
int previous_tab_index = current_tab_index - 1; //get previous tab index
|
|
if (previous_tab_index>=0) //if previous tab index = 0 then the first tab is activated so no need to change tab.
|
|
return(m_diagram_ids[previous_tab_index]);
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
@brief change current diagramview to last tab
|
|
*/
|
|
void ProjectView::changeLastTab()
|
|
{
|
|
DiagramView *lastDiagramView = this->lastDiagram();
|
|
m_tab->setCurrentWidget(lastDiagramView);
|
|
}
|
|
|
|
/**
|
|
@return last folio of current project
|
|
*/
|
|
DiagramView *ProjectView::lastDiagram()
|
|
{
|
|
return(m_diagram_ids.last());
|
|
}
|
|
|
|
/**
|
|
@brief change current diagramview to first tab
|
|
*/
|
|
void ProjectView::changeFirstTab()
|
|
{
|
|
DiagramView *firstDiagramView = this->firstDiagram();
|
|
m_tab->setCurrentWidget(firstDiagramView);
|
|
}
|
|
|
|
/**
|
|
@return first folio of current project, or nullptr if the project
|
|
has no diagram.
|
|
*/
|
|
DiagramView *ProjectView::firstDiagram()
|
|
{
|
|
if (m_diagram_ids.isEmpty()) {
|
|
return(nullptr);
|
|
}
|
|
return(m_diagram_ids.first());
|
|
}
|
|
|
|
|
|
/**
|
|
Cette methode essaye de fermer successivement les editeurs d'element puis
|
|
les schemas du projet. L'utilisateur peut refuser de fermer un schema ou un
|
|
editeur.
|
|
@return true si tout a pu etre ferme, false sinon
|
|
@see tryClosingElementEditors()
|
|
@see tryClosingDiagrams()
|
|
*/
|
|
bool ProjectView::tryClosing()
|
|
{
|
|
if (!m_project) return(true);
|
|
|
|
// First step: require external editors closing -- users may either cancel
|
|
// the whole closing process or save (and therefore add) content into this
|
|
// project. Of course, they may also discard them.
|
|
if (!tryClosingElementEditors()) {
|
|
return(false);
|
|
}
|
|
|
|
// Check how different the current situation is from a brand new, untouched project
|
|
if (m_project -> filePath().isEmpty() && !m_project -> projectWasModified()) {
|
|
return(true);
|
|
}
|
|
|
|
// Second step: users are presented with a dialog that enables them to
|
|
// choose whether they want to:
|
|
// - cancel the closing process,
|
|
// - discard all modifications,
|
|
// - or specify what is to be saved, i.e. they choose whether they wants to
|
|
// save/give up/remove diagrams considered as modified.
|
|
int user_input = tryClosingDiagrams();
|
|
if (user_input == QMessageBox::Cancel) {
|
|
return(false); // the closing process was cancelled
|
|
} else if (user_input == QMessageBox::Discard) {
|
|
return(true); // all modifications were discarded
|
|
}
|
|
|
|
// Check how different the current situation is from a brand new, untouched project (yes , again)
|
|
if (m_project -> filePath().isEmpty() && !m_project -> projectWasModified()) {
|
|
return(true);
|
|
}
|
|
|
|
if (m_project -> filePath().isEmpty()) {
|
|
QString filepath = askUserForFilePath();
|
|
if (filepath.isEmpty()) return(false); // users may cancel the closing
|
|
}
|
|
bool cancelled = false;
|
|
QETResult result = writeProject(&cancelled);
|
|
if (cancelled) return(false);
|
|
updateWindowTitle();
|
|
if (!result.isOk()) emit(errorEncountered(result.errorMessage()));
|
|
return(result.isOk());
|
|
}
|
|
|
|
/**
|
|
Un projet comporte des elements integres. Cette methode ferme les editeurs
|
|
d'elements associes a ce projet. L'utilisateur peut refuser la fermeture
|
|
d'un editeur d'element.
|
|
@return true si tous les editeurs d'element ont pu etre fermes, false sinon
|
|
*/
|
|
bool ProjectView::tryClosingElementEditors()
|
|
{
|
|
if (!m_project) return(true);
|
|
/*
|
|
La QETApp permet d'acceder rapidement aux editeurs d'element
|
|
editant un element du projet.
|
|
*/
|
|
QList<QETElementEditor *> editors = QETApp::elementEditors(m_project);
|
|
foreach(QETElementEditor *editor, editors) {
|
|
if (!editor -> close()) return(false);
|
|
}
|
|
|
|
QList<QETTitleBlockTemplateEditor *> template_editors = QETApp::titleBlockTemplateEditors(m_project);
|
|
foreach(QETTitleBlockTemplateEditor *template_editor, template_editors) {
|
|
if (!template_editor -> close()) return(false);
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
/**
|
|
@brief ProjectView::tryClosingDiagrams
|
|
try to close this project, if diagram or project option are changed
|
|
a dialog ask if user want to save the modification.
|
|
@return the answer of dialog or discard if no change.
|
|
*/
|
|
int ProjectView::tryClosingDiagrams()
|
|
{
|
|
if (!m_project) return(QMessageBox::Discard);
|
|
|
|
if (!project()->projectOptionsWereModified() &&
|
|
project()->undoStack()->isClean() &&
|
|
!project()->filePath().isEmpty()) {
|
|
// nothing was modified, and we have a filepath, i.e. everything was already
|
|
// saved, i.e we can close the project right now
|
|
return(QMessageBox::Discard);
|
|
}
|
|
|
|
QString title = project()->title();
|
|
if (title.isEmpty()) title = "QElectroTech ";
|
|
|
|
int close_dialog = QMessageBox::question(this, title,
|
|
tr("The project was modified.\n"
|
|
"Do you want to save changes?"),
|
|
QMessageBox::Save | QMessageBox::Discard
|
|
| QMessageBox::Cancel,
|
|
QMessageBox::Save);
|
|
|
|
return(close_dialog);
|
|
}
|
|
|
|
/**
|
|
Ask the user to provide a file path in which the currently edited project will
|
|
be saved.
|
|
@param assign When true, assign the provided filepath to the project through
|
|
setFilePath(). Defaults to true.
|
|
@return the file path, or an empty string if none were provided
|
|
*/
|
|
QString ProjectView::askUserForFilePath(bool assign) {
|
|
// ask the user for a filepath in order to save the project
|
|
const QString qet_filter = tr("Project QElectroTech (*.qet)", "filetypes allowed when saving a project file");
|
|
const QString qetz_filter = tr("Zipped project QElectroTech (*.qetz)", "filetypes allowed when saving a project file");
|
|
//A project is saved as a .qetz unless a .qet is chosen
|
|
//(stage D1)
|
|
QString selected_filter = qetz_filter;
|
|
QString filepath = QFileDialog::getSaveFileName(
|
|
this,
|
|
tr("Save as", "dialog title"),
|
|
m_project -> currentDir() + "/" + tr("untitled")
|
|
+ (selected_filter == qetz_filter ? ".qetz" : ".qet"),
|
|
qetz_filter + ";;" + qet_filter,
|
|
&selected_filter
|
|
);
|
|
|
|
// if no filepath is provided, return an empty string
|
|
if (filepath.isEmpty()) return(filepath);
|
|
|
|
// Ensure the path ends with exactly one .qet extension, regardless of
|
|
// whether the active file dialog already appended one. Whether it does
|
|
// depends on which backend actually shows the dialog (Qt's own vs. the
|
|
// xdg-desktop-portal used by sandboxed Snap/Flatpak builds), and that
|
|
// isn't reliably predictable from environment variables alone -- an
|
|
// earlier attempt at that (only appending when *not* Snap/Flatpak,
|
|
// assuming the portal always appends it) left Snap saves with no
|
|
// extension at all whenever the portal didn't (bugtracker #270).
|
|
// Stripping any existing suffix first and re-appending it once is
|
|
// correct either way.
|
|
// Zipped (.qetz) when that file type is chosen, or when the name typed
|
|
// says .qetz anywhere in its trailing extensions: typing "plan.qetz"
|
|
// over the selected "untitled" of "untitled.qet" gives "plan.qetz.qet"
|
|
// with the GTK dialog, and still means a .qetz.
|
|
bool zipped = selected_filter == qetz_filter;
|
|
for (;;) {
|
|
if (filepath.endsWith(".qetz", Qt::CaseInsensitive)) {
|
|
filepath.chop(5);
|
|
zipped = true;
|
|
} else if (filepath.endsWith(".qet", Qt::CaseInsensitive)) {
|
|
filepath.chop(4);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
filepath += zipped ? ".qetz" : ".qet";
|
|
|
|
if (assign) {
|
|
// assign the provided filepath to the currently edited project
|
|
m_project -> setFilePath(filepath);
|
|
}
|
|
|
|
return(filepath);
|
|
}
|
|
|
|
/**
|
|
@return the QETResult object to be returned when it appears this project
|
|
view is not associated to any project.
|
|
*/
|
|
QETResult ProjectView::noProjectResult() const
|
|
{
|
|
QETResult no_project(tr("no active project", "error message"), false);
|
|
return(no_project);
|
|
}
|
|
|
|
/**
|
|
* @brief ProjectView::removeDiagram
|
|
* Remove a diagram (folio) of the project
|
|
* @param diagram_view : diagram view to remove
|
|
* @param silent : if true, bypasses the confirmation message box
|
|
*/
|
|
void ProjectView::removeDiagram(DiagramView *diagram_view, bool silent)
|
|
{
|
|
if (!diagram_view)
|
|
return;
|
|
if (m_project -> isReadOnly())
|
|
return;
|
|
if (!m_diagram_ids.values().contains(diagram_view))
|
|
return;
|
|
|
|
if (!silent) {
|
|
//Ask confirmation to user.
|
|
int answer = QET::QetMessageBox::question(
|
|
this,
|
|
tr("Delete this sheet ?", "message box title"),
|
|
tr("Are you sure you want to delete this sheet from the project?", "message box content"),
|
|
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
|
|
QMessageBox::No
|
|
);
|
|
if (answer != QMessageBox::Yes) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_project->undoStack()->push(new RemoveDiagramCommand(m_project, diagram_view->diagram()));
|
|
}
|
|
|
|
/**
|
|
@brief ProjectView::diagramRemoved
|
|
Slot called when the project emits diagramRemoved (either from a direct,
|
|
non-undoable QETProject::removeDiagram() call, or -- the normal UI path
|
|
-- from a RemoveDiagramCommand's redo()/undo()). Tears down the tab and
|
|
DiagramView for the given diagram; the diagram itself is left untouched,
|
|
since by this point it has already been detached (and possibly parked
|
|
for undo) by the caller.
|
|
@param diagram
|
|
*/
|
|
void ProjectView::diagramRemoved(Diagram *diagram)
|
|
{
|
|
DiagramView *diagram_view = findDiagram(diagram);
|
|
if (!diagram_view)
|
|
return;
|
|
|
|
int index_to_remove = m_diagram_ids.key(diagram_view);
|
|
m_tab->removeTab(index_to_remove);
|
|
m_diagram_view_list.removeAll(diagram_view);
|
|
rebuildDiagramsMap();
|
|
|
|
emit(diagramRemoved(diagram_view));
|
|
delete diagram_view;
|
|
|
|
updateAllTabsTitle();
|
|
m_project -> setModified(true);
|
|
}
|
|
|
|
/**
|
|
* Enleve un schema du ProjectView
|
|
* @param diagram Schema a enlever
|
|
* @param silent Si vrai, supprime sans demander confirmation
|
|
*/
|
|
void ProjectView::removeDiagram(Diagram *diagram, bool silent) {
|
|
if (!diagram) return;
|
|
|
|
if (DiagramView *diagram_view = findDiagram(diagram)) {
|
|
removeDiagram(diagram_view, silent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Active l'onglet adequat pour afficher le schema passe en parametre
|
|
@param diagram Schema a afficher
|
|
*/
|
|
void ProjectView::showDiagram(DiagramView *diagram) {
|
|
if (!diagram) return;
|
|
m_tab -> setCurrentWidget(diagram);
|
|
}
|
|
|
|
/**
|
|
Active l'onglet adequat pour afficher le schema passe en parametre
|
|
@param diagram Schema a afficher
|
|
*/
|
|
void ProjectView::showDiagram(Diagram *diagram) {
|
|
if (!diagram) return;
|
|
if (DiagramView *diagram_view = findDiagram(diagram)) {
|
|
m_tab -> setCurrentWidget(diagram_view);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Enable the user to edit properties of the current project through a
|
|
configuration dialog.
|
|
*/
|
|
void ProjectView::editProjectProperties()
|
|
{
|
|
if (!m_project) return;
|
|
ProjectPropertiesDialog dialog(m_project, parentWidget());
|
|
dialog.exec();
|
|
}
|
|
|
|
/**
|
|
Edite les proprietes du schema courant
|
|
*/
|
|
void ProjectView::editCurrentDiagramProperties()
|
|
{
|
|
editDiagramProperties(currentDiagram());
|
|
}
|
|
|
|
/**
|
|
Edite les proprietes du schema diagram_view
|
|
*/
|
|
void ProjectView::editDiagramProperties(DiagramView *diagram_view) {
|
|
if (!diagram_view) return;
|
|
showDiagram(diagram_view);
|
|
diagram_view -> editDiagramProperties();
|
|
}
|
|
|
|
/**
|
|
Edite les proprietes du schema diagram
|
|
*/
|
|
void ProjectView::editDiagramProperties(Diagram *diagram) {
|
|
editDiagramProperties(findDiagram(diagram));
|
|
}
|
|
|
|
/**
|
|
Deplace le schema diagram_view vers le haut / la gauche
|
|
*/
|
|
void ProjectView::moveDiagramUp(DiagramView *diagram_view) {
|
|
if (!diagram_view) return;
|
|
|
|
int diagram_view_position = m_diagram_ids.key(diagram_view);
|
|
if (!diagram_view_position) {
|
|
// le schema est le premier du projet
|
|
return;
|
|
}
|
|
m_project->undoStack()->push(new MoveDiagramCommand(this, diagram_view->diagram(), diagram_view_position - 1));
|
|
}
|
|
|
|
/**
|
|
Deplace le schema diagram vers le haut / la gauche
|
|
*/
|
|
void ProjectView::moveDiagramUp(Diagram *diagram) {
|
|
moveDiagramUp(findDiagram(diagram));
|
|
}
|
|
|
|
/**
|
|
Deplace le schema diagram_view vers le bas / la droite
|
|
*/
|
|
void ProjectView::moveDiagramDown(DiagramView *diagram_view) {
|
|
if (!diagram_view) return;
|
|
|
|
int diagram_view_position = m_diagram_ids.key(diagram_view);
|
|
if (diagram_view_position + 1 == m_diagram_ids.count()) {
|
|
// le schema est le dernier du projet
|
|
return;
|
|
}
|
|
m_project->undoStack()->push(new MoveDiagramCommand(this, diagram_view->diagram(), diagram_view_position + 1));
|
|
}
|
|
|
|
/**
|
|
Deplace le schema diagram vers le bas / la droite
|
|
*/
|
|
void ProjectView::moveDiagramDown(Diagram *diagram) {
|
|
moveDiagramDown(findDiagram(diagram));
|
|
}
|
|
|
|
/*
|
|
Deplace le schema diagram_view vers le haut / la gauche en position 0
|
|
*/
|
|
void ProjectView::moveDiagramUpTop(DiagramView *diagram_view)
|
|
{
|
|
if (!diagram_view) return;
|
|
|
|
int diagram_view_position = m_diagram_ids.key(diagram_view);
|
|
if (!diagram_view_position) {
|
|
// le schema est le premier du projet
|
|
return;
|
|
}
|
|
m_project->undoStack()->push(new MoveDiagramCommand(this, diagram_view->diagram(), 0));
|
|
}
|
|
|
|
/*
|
|
Deplace le schema diagram vers le haut / la gauche en position 0
|
|
*/
|
|
void ProjectView::moveDiagramUpTop(Diagram *diagram)
|
|
{
|
|
moveDiagramUpTop(findDiagram(diagram));
|
|
}
|
|
|
|
/**
|
|
Deplace le schema diagram_view vers le haut / la gauche x10
|
|
*/
|
|
void ProjectView::moveDiagramUpx10(DiagramView *diagram_view) {
|
|
if (!diagram_view) return;
|
|
|
|
int diagram_view_position = m_diagram_ids.key(diagram_view);
|
|
if (!diagram_view_position) {
|
|
// le schema est le premier du projet
|
|
return;
|
|
}
|
|
m_project->undoStack()->push(new MoveDiagramCommand(this, diagram_view->diagram(), diagram_view_position - 10));
|
|
}
|
|
|
|
/**
|
|
Deplace le schema diagram vers le haut / la gauche x10
|
|
*/
|
|
void ProjectView::moveDiagramUpx10(Diagram *diagram) {
|
|
moveDiagramUpx10(findDiagram(diagram));
|
|
}
|
|
|
|
/**
|
|
* @brief ProjectView::moveDiagramUpx100
|
|
* Moves the diagram_view up / left x100
|
|
* @param diagram_view View to move
|
|
*/
|
|
void ProjectView::moveDiagramUpx100(DiagramView *diagram_view) {
|
|
if (!diagram_view) return;
|
|
|
|
int diagram_view_position = m_diagram_ids.key(diagram_view);
|
|
if (!diagram_view_position) {
|
|
// The diagram is the first of the project
|
|
return;
|
|
}
|
|
m_project->undoStack()->push(new MoveDiagramCommand(this, diagram_view->diagram(), diagram_view_position - 100));
|
|
}
|
|
|
|
/**
|
|
* @brief ProjectView::moveDiagramUpx100
|
|
* Moves the diagram up / left x100
|
|
* @param diagram Diagram to move
|
|
*/
|
|
void ProjectView::moveDiagramUpx100(Diagram *diagram) {
|
|
moveDiagramUpx100(findDiagram(diagram));
|
|
}
|
|
|
|
/**
|
|
* @brief ProjectView::moveDiagramDownx100
|
|
* Moves the diagram_view down / right x100
|
|
* @param diagram_view View to move
|
|
*/
|
|
void ProjectView::moveDiagramDownx100(DiagramView *diagram_view) {
|
|
if (!diagram_view) return;
|
|
|
|
int diagram_view_position = m_diagram_ids.key(diagram_view);
|
|
if (diagram_view_position + 1 == m_diagram_ids.count()) {
|
|
// The diagram is the last of the project
|
|
return;
|
|
}
|
|
m_project->undoStack()->push(new MoveDiagramCommand(this, diagram_view->diagram(), diagram_view_position + 100));
|
|
}
|
|
|
|
/**
|
|
* @brief ProjectView::moveDiagramDownx100
|
|
* Moves the diagram down / right x100
|
|
* @param diagram Diagram to move
|
|
*/
|
|
void ProjectView::moveDiagramDownx100(Diagram *diagram) {
|
|
moveDiagramDownx100(findDiagram(diagram));
|
|
}
|
|
|
|
/**
|
|
Deplace le schema diagram_view vers le bas / la droite x10
|
|
*/
|
|
void ProjectView::moveDiagramDownx10(DiagramView *diagram_view) {
|
|
if (!diagram_view) return;
|
|
|
|
int diagram_view_position = m_diagram_ids.key(diagram_view);
|
|
if (diagram_view_position + 1 == m_diagram_ids.count()) {
|
|
// le schema est le dernier du projet
|
|
return;
|
|
}
|
|
m_project->undoStack()->push(new MoveDiagramCommand(this, diagram_view->diagram(), diagram_view_position + 10));
|
|
}
|
|
|
|
/**
|
|
Deplace le schema diagram vers le bas / la droite x10
|
|
*/
|
|
void ProjectView::moveDiagramDownx10(Diagram *diagram) {
|
|
moveDiagramDownx10(findDiagram(diagram));
|
|
}
|
|
|
|
/**
|
|
Exporte le schema.
|
|
*/
|
|
void ProjectView::exportProject()
|
|
{
|
|
if (!m_project) return;
|
|
|
|
ExportDialog ed(m_project, parentWidget());
|
|
#ifdef Q_OS_MACOS
|
|
ed.setWindowFlags(Qt::Sheet);
|
|
#endif
|
|
ed.exec();
|
|
}
|
|
|
|
/**
|
|
Save project properties along with all modified diagrams.
|
|
@see filePath()
|
|
@see setFilePath()
|
|
@return a QETResult object reflecting the situation
|
|
*/
|
|
QETResult ProjectView::save()
|
|
{
|
|
return(doSave());
|
|
}
|
|
|
|
/**
|
|
Ask users for a filepath in order to save the project.
|
|
@param options May be used to specify what should be saved; defaults to
|
|
all modified diagrams.
|
|
@return a QETResult object reflecting the situation; note that a valid
|
|
QETResult object is returned if the operation was cancelled.
|
|
*/
|
|
QETResult ProjectView::saveAs()
|
|
{
|
|
if (!m_project) return(noProjectResult());
|
|
|
|
QString filepath = askUserForFilePath();
|
|
if (filepath.isEmpty()) return(QETResult());
|
|
return(doSave());
|
|
}
|
|
|
|
/**
|
|
@brief ProjectView::saveCopyForOlderVersions
|
|
Ask for a .qet path and write the project there, for a version of
|
|
QElectroTech that cannot open a .qetz. The project keeps its file.
|
|
@return a QETResult object reflecting the situation; a valid one if
|
|
the operation was cancelled
|
|
*/
|
|
QETResult ProjectView::saveCopyForOlderVersions()
|
|
{
|
|
if (!m_project) return(noProjectResult());
|
|
|
|
QString base = m_project->filePath().isEmpty()
|
|
? m_project->currentDir() + "/" + tr("untitled")
|
|
: m_project->filePath();
|
|
for (const QString &suffix : {QStringLiteral(".qetz"), QStringLiteral(".qet")})
|
|
if (base.endsWith(suffix, Qt::CaseInsensitive)) base.chop(suffix.size());
|
|
QString path = QFileDialog::getSaveFileName(
|
|
this,
|
|
tr("Save a copy for an older QElectroTech", "dialog title"),
|
|
base + ".qet",
|
|
tr("Project QElectroTech (*.qet)", "filetypes allowed when saving a project file"));
|
|
if (path.isEmpty()) return(QETResult());
|
|
if (!path.endsWith(".qet", Qt::CaseInsensitive)) path += ".qet";
|
|
return(m_project->writeCopyForOlderVersions(path));
|
|
}
|
|
|
|
/**
|
|
@brief ProjectView::writeProject
|
|
Write the project file. If the file was changed on disk since this QET
|
|
read or wrote it, ask what to do first: a plain save would replace
|
|
the other change.
|
|
@param cancelled set to true if the user gave up, or chose to save a
|
|
copy and did not complete it: nothing was saved to the project's file
|
|
@return the result of the write
|
|
*/
|
|
QETResult ProjectView::writeProject(bool *cancelled)
|
|
{
|
|
*cancelled = false;
|
|
QDateTime when;
|
|
if (!m_project -> changedOnDisk(&when)) {
|
|
return(m_project -> write());
|
|
}
|
|
|
|
const QString name = QFileInfo(m_project -> writeTarget()).fileName();
|
|
QMessageBox box(QMessageBox::Warning,
|
|
tr("File changed on disk", "message box title"),
|
|
tr("Someone else changed %1 on disk at %2 since you opened it.\n\n"
|
|
"Saving over it would lose their changes.")
|
|
.arg(name, QLocale::system().toString(when, QLocale::ShortFormat)),
|
|
QMessageBox::NoButton, this);
|
|
QPushButton *copy_button = box.addButton(tr("Save as a copy"), QMessageBox::AcceptRole);
|
|
QPushButton *overwrite_button = box.addButton(tr("Overwrite"), QMessageBox::DestructiveRole);
|
|
box.addButton(QMessageBox::Cancel);
|
|
box.setDefaultButton(copy_button);
|
|
box.setEscapeButton(QMessageBox::Cancel);
|
|
box.exec();
|
|
|
|
if (box.clickedButton() == overwrite_button) {
|
|
return(m_project -> write(true));
|
|
}
|
|
if (box.clickedButton() == copy_button) {
|
|
QETResult result = saveAs();
|
|
//The Save As dialog was cancelled: the project is still modified
|
|
if (m_project -> projectWasModified()) *cancelled = true;
|
|
return(result);
|
|
}
|
|
*cancelled = true;
|
|
return(QETResult());
|
|
}
|
|
|
|
/**
|
|
Save project content, then write the project file. May
|
|
call saveAs if no filepath was provided before.
|
|
@return a QETResult object reflecting the situation; note that a valid
|
|
QETResult object is returned if the operation was cancelled.
|
|
*/
|
|
QETResult ProjectView::doSave()
|
|
{
|
|
if (!m_project) return(noProjectResult());
|
|
|
|
if (m_project -> filePath().isEmpty()) {
|
|
// The project has not been saved to a file yet,
|
|
// so save() actually means saveAs().
|
|
return(saveAs());
|
|
}
|
|
|
|
//A project read from a .qet is saved as a .qetz beside it: say so,
|
|
//and ask before replacing a .qetz already there
|
|
if (m_project->savesBesideOpenedFile()) {
|
|
const QString beside = m_project->besideContainerPath();
|
|
const QString message = tr("This project was opened from %1. It is saved as %2, "
|
|
"beside it, in the zipped project format; %1 stays as "
|
|
"it is.\n\nTo write a file for a version of "
|
|
"QElectroTech that cannot open %2, use File > Save a "
|
|
"copy for an older QElectroTech.")
|
|
.arg(QFileInfo(m_project->filePath()).fileName(), QFileInfo(beside).fileName());
|
|
if (QFileInfo::exists(beside)) {
|
|
if (QET::QetMessageBox::question(
|
|
this, tr("Save as a zipped project", "message box title"),
|
|
message + QStringLiteral("\n\n")
|
|
+ tr("%1 already exists. Replace it?").arg(QFileInfo(beside).fileName()),
|
|
QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel)
|
|
!= QMessageBox::Yes) {
|
|
return(QETResult());
|
|
}
|
|
} else {
|
|
QET::QetMessageBox::information(
|
|
this, tr("Save as a zipped project", "message box title"), message);
|
|
}
|
|
}
|
|
|
|
// write to file
|
|
bool cancelled = false;
|
|
QETResult result = writeProject(&cancelled);
|
|
if (cancelled) return(QETResult());
|
|
updateWindowTitle();
|
|
// This marks the stack's current index as the new "saved" point (so isClean()/cleanChanged() correctly
|
|
// resume tracking unsaved changes from here) without discarding the undo history. Edits made before this save
|
|
// stay undoable for the rest of the session.
|
|
project()->undoStack()->setClean();
|
|
return(result);
|
|
}
|
|
|
|
/**
|
|
Allow the user to clean the project, which includes:
|
|
* deleting unused title block templates
|
|
* deleting unused elements
|
|
* deleting empty categories
|
|
@return an integer value above zero if elements and/or categories were
|
|
cleaned.
|
|
*/
|
|
int ProjectView::cleanProject()
|
|
{
|
|
if (!m_project) return(0);
|
|
|
|
// s'assure que le schema n'est pas en lecture seule
|
|
if (m_project -> isReadOnly()) {
|
|
QET::QetMessageBox::critical(
|
|
this,
|
|
tr("Read-only project", "message box title"),
|
|
tr("This project is read-only. Thus it can not be cleaned.", "message box content")
|
|
);
|
|
return(0);
|
|
}
|
|
|
|
// construit un petit dialogue pour parametrer le nettoyage
|
|
QCheckBox *clean_tbt = new QCheckBox(tr("delete unused templates in the project"));
|
|
QCheckBox *clean_elements = new QCheckBox(tr("Delete unused elements in the project"));
|
|
QCheckBox *clean_categories = new QCheckBox(tr("Delete empty categories"));
|
|
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
|
|
|
clean_tbt -> setChecked(true);
|
|
clean_elements -> setChecked(true);
|
|
clean_categories -> setChecked(true);
|
|
|
|
QDialog clean_dialog(parentWidget());
|
|
#ifdef Q_OS_MACOS
|
|
clean_dialog.setWindowFlags(Qt::Sheet);
|
|
#endif
|
|
|
|
clean_dialog.setWindowTitle(tr("Clean project", "window title"));
|
|
QVBoxLayout *clean_dialog_layout = new QVBoxLayout();
|
|
clean_dialog_layout -> addWidget(clean_tbt);
|
|
clean_dialog_layout -> addWidget(clean_elements);
|
|
clean_dialog_layout -> addWidget(clean_categories);
|
|
clean_dialog_layout -> addWidget(buttons);
|
|
clean_dialog.setLayout(clean_dialog_layout);
|
|
|
|
connect(buttons, &QDialogButtonBox::accepted, &clean_dialog, &QDialog::accept);
|
|
connect(buttons, &QDialogButtonBox::rejected, &clean_dialog, &QDialog::reject);
|
|
|
|
int clean_count = 0;
|
|
if (clean_dialog.exec() == QDialog::Accepted)
|
|
{
|
|
if (clean_tbt -> isChecked()) {
|
|
m_project->embeddedTitleBlockTemplatesCollection()->deleteUnusedTitleBlocKTemplates();
|
|
}
|
|
if (clean_elements->isChecked()) {
|
|
m_project->embeddedElementCollection()->cleanUnusedElement();
|
|
}
|
|
if (clean_categories->isChecked()) {
|
|
m_project->embeddedElementCollection()->cleanUnusedDirectory();
|
|
}
|
|
}
|
|
|
|
m_project -> setModified(true);
|
|
return(clean_count);
|
|
}
|
|
|
|
/**
|
|
Initialize actions for this widget.
|
|
*/
|
|
void ProjectView::initActions()
|
|
{
|
|
m_add_new_diagram = new QAction(QET::Icons::AddFolio, tr("Add a sheet"), this);
|
|
connect(m_add_new_diagram, &QAction::triggered, [this](){this->m_project->addNewDiagram();});
|
|
|
|
m_first_view = new QAction(QET::Icons::ArrowLeftDouble, tr("Go back to the start of the project"),this);
|
|
connect(m_first_view, &QAction::triggered, [this](){this->m_tab->setCurrentWidget(firstDiagram());});
|
|
|
|
m_end_view = new QAction(QET::Icons::ArrowRightDouble, tr("Go to the end of the project"),this);
|
|
connect(m_end_view, &QAction::triggered, [this](){this->m_tab->setCurrentWidget(lastDiagram());});
|
|
|
|
// button to scroll one page left
|
|
m_next_view_left = new QAction(QET::Icons::ArrowLeft, tr("go one page left"),this);
|
|
connect(m_next_view_left, &QAction::triggered, [this](){this->m_tab->setCurrentWidget(previousDiagram());});
|
|
|
|
// button to scroll one page right
|
|
m_next_view_right = new QAction(QET::Icons::ArrowRight, tr("go one page right"),this);
|
|
connect(m_next_view_right, &QAction::triggered, [this](){this->m_tab->setCurrentWidget(nextDiagram());});
|
|
}
|
|
|
|
/**
|
|
Initialize child widgets for this widget.
|
|
*/
|
|
void ProjectView::initWidgets()
|
|
{
|
|
setObjectName("ProjectView");
|
|
setWindowIcon(QET::Icons::ProjectFileGP);
|
|
|
|
// initialize the "fallback" widget
|
|
fallback_widget_ = new QWidget();
|
|
fallback_label_ = new QLabel(
|
|
tr(
|
|
"This project does not contain any sheet",
|
|
"label displayed when a project contains no diagram"
|
|
)
|
|
);
|
|
fallback_label_ -> setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
|
|
|
|
// initialize tabs
|
|
#ifdef Q_OS_MACOS
|
|
m_tab = new WheelEnabledTabBar(this);
|
|
#else
|
|
m_tab = new QTabWidget(this);
|
|
#endif
|
|
m_tab -> setMovable(true);
|
|
// setting UsesScrollButton ensures that when the tab bar is full, the tabs are scrolled.
|
|
m_tab -> setUsesScrollButtons(true);
|
|
// disable the internal scroll buttons of the TabWidget, we will use our own buttons.
|
|
m_tab->setStyleSheet("QTabBar QToolButton {border-image: ;border-width: 0px}");
|
|
m_tab->setStyleSheet("QTabBar::scroller {width: 0px;}");
|
|
|
|
// add layouts
|
|
QHBoxLayout *TopRightCorner_Layout = new QHBoxLayout();
|
|
TopRightCorner_Layout->setContentsMargins(0,0,0,0);
|
|
// some place left to the 'next_right_view_button' button
|
|
TopRightCorner_Layout->addSpacing(10);
|
|
|
|
QHBoxLayout *TopLeftCorner_Layout = new QHBoxLayout();
|
|
TopLeftCorner_Layout->setContentsMargins(0,0,0,0);
|
|
|
|
// add buttons
|
|
QToolButton *m_next_right_view_button =new QToolButton;
|
|
m_next_right_view_button->setDefaultAction(m_next_view_right);
|
|
m_next_right_view_button->setAutoRaise(true);
|
|
TopRightCorner_Layout->addWidget(m_next_right_view_button);
|
|
|
|
QToolButton *m_end_view_button =new QToolButton;
|
|
m_end_view_button->setDefaultAction(m_end_view);
|
|
m_end_view_button->setAutoRaise(true);
|
|
TopRightCorner_Layout->addWidget(m_end_view_button);
|
|
|
|
QToolButton *add_new_diagram_button = new QToolButton;
|
|
add_new_diagram_button -> setDefaultAction(m_add_new_diagram);
|
|
add_new_diagram_button -> setAutoRaise(true);
|
|
TopRightCorner_Layout->addWidget(add_new_diagram_button);
|
|
// some place right to the 'add_new_diagram_button' button
|
|
TopRightCorner_Layout->addSpacing(5);
|
|
|
|
QToolButton *m_first_view_button =new QToolButton;
|
|
m_first_view_button->setDefaultAction(m_first_view);
|
|
m_first_view_button->setAutoRaise(true);
|
|
TopLeftCorner_Layout->addWidget(m_first_view_button);
|
|
|
|
QToolButton *m_next_left_view_button =new QToolButton;
|
|
m_next_left_view_button->setDefaultAction(m_next_view_left);
|
|
m_next_left_view_button->setAutoRaise(true);
|
|
TopLeftCorner_Layout->addWidget(m_next_left_view_button);
|
|
|
|
// some place right to the 'first_view_button' button
|
|
TopLeftCorner_Layout->addSpacing(10);
|
|
|
|
// add widgets to tabbar
|
|
QWidget *tabwidgetRight=new QWidget(this);
|
|
tabwidgetRight->setLayout(TopRightCorner_Layout);
|
|
m_tab -> setCornerWidget(tabwidgetRight, Qt::TopRightCorner);
|
|
|
|
QWidget *tabwidgetLeft=new QWidget(this);
|
|
tabwidgetLeft->setLayout(TopLeftCorner_Layout);
|
|
m_tab -> setCornerWidget(tabwidgetLeft, Qt::TopLeftCorner);
|
|
|
|
// manage signals
|
|
connect(m_tab, &QTabWidget::currentChanged, this, &ProjectView::tabChanged);
|
|
connect(m_tab, &QTabWidget::tabBarDoubleClicked, this, &ProjectView::tabDoubleClicked);
|
|
connect(m_tab->tabBar(), &QTabBar::tabMoved, this, &ProjectView::tabMoved, Qt::QueuedConnection);
|
|
|
|
fallback_widget_ -> setVisible(false);
|
|
m_tab -> setVisible(false);
|
|
}
|
|
|
|
/**
|
|
Initialize layout for this widget.
|
|
*/
|
|
void ProjectView::initLayout()
|
|
{
|
|
QVBoxLayout *fallback_widget_layout_ = new QVBoxLayout(fallback_widget_);
|
|
fallback_widget_layout_ -> addWidget(fallback_label_);
|
|
|
|
layout_ = new QVBoxLayout(this);
|
|
#ifdef Q_OS_MACOS
|
|
layout_ -> setContentsMargins(0, 8, 0, 0);
|
|
#else
|
|
layout_ -> setContentsMargins(0, 0, 0, 0);
|
|
#endif
|
|
layout_ -> setSpacing(0);
|
|
layout_ -> addWidget(fallback_widget_);
|
|
layout_ -> addWidget(m_tab);
|
|
}
|
|
|
|
|
|
/**
|
|
@brief ProjectView::loadDiagrams
|
|
Load diagrams of project.
|
|
We create a diagram view for each diagram,
|
|
and add it to the project view.
|
|
*/
|
|
void ProjectView::loadDiagrams()
|
|
{
|
|
if (!m_project) return;
|
|
|
|
//The folios' items are built when their tab is first shown
|
|
//(tabChanged()), not here: a tab needs only the folio's own data
|
|
setDisplayFallbackWidget(m_project -> folios().isEmpty());
|
|
|
|
auto dialog = DialogWaiting::instance();
|
|
if(dialog)
|
|
{
|
|
dialog->setTitle( tr("<p align=\"center\"><b>Opening the project "
|
|
"...</b><br/>Creating sheet tabs :</p>"));
|
|
}
|
|
|
|
for(auto diagram : m_project->folios())
|
|
{
|
|
if(dialog)
|
|
{
|
|
dialog->setDetail(diagram->title());
|
|
dialog->setProgressBar(dialog->progressBarValue()+1);
|
|
}
|
|
|
|
auto dv = new DiagramView(diagram);
|
|
dv->setFrameStyle(QFrame::Plain | QFrame::NoFrame);
|
|
|
|
auto index = m_project->folioIndex(diagram);
|
|
m_tab->insertTab(index, dv, QET::Icons::Diagram, dv->title());
|
|
m_diagram_view_list.insert(index, dv);
|
|
|
|
connect(dv, &DiagramView::showDiagram, this, QOverload<Diagram*>::of(&ProjectView::showDiagram));
|
|
connect(dv, &DiagramView::titleChanged, this, &ProjectView::updateTabTitle);
|
|
connect(dv, &DiagramView::findElementRequired, this, &ProjectView::findElementRequired);
|
|
connect(&dv->diagram()->border_and_titleblock , &BorderTitleBlock::titleBlockFolioChanged, [this, dv]() {this->updateTabTitle(dv);});
|
|
}
|
|
|
|
rebuildDiagramsMap();
|
|
updateAllTabsTitle();
|
|
|
|
m_tab->setCurrentWidget(firstDiagram());
|
|
}
|
|
|
|
/**
|
|
@brief ProjectView::updateWindowTitle
|
|
Update the project view title
|
|
*/
|
|
void ProjectView::updateWindowTitle()
|
|
{
|
|
QString title;
|
|
if (m_project) {
|
|
title = m_project -> pathNameTitle();
|
|
} else {
|
|
title = tr("Project", "window title for a project-less ProjectView");
|
|
}
|
|
setWindowTitle(title);
|
|
}
|
|
|
|
/**
|
|
Effectue les actions necessaires lorsque le projet visualise entre ou sort
|
|
du mode lecture seule.
|
|
*/
|
|
void ProjectView::adjustReadOnlyState()
|
|
{
|
|
bool editable = !(m_project -> isReadOnly());
|
|
|
|
// prevent users from moving existing diagrams
|
|
m_tab -> setMovable(editable);
|
|
// prevent users from adding new diagrams
|
|
m_add_new_diagram -> setEnabled(editable);
|
|
|
|
// on met a jour le titre du widget, qui reflete l'etat de lecture seule
|
|
updateWindowTitle();
|
|
}
|
|
|
|
/**
|
|
@brief ProjectView::diagramAdded
|
|
Slot called when qetproject emit diagramAdded
|
|
@param diagram
|
|
*/
|
|
void ProjectView::diagramAdded(Diagram *diagram)
|
|
{
|
|
auto dv = new DiagramView(diagram);
|
|
auto index = m_project->folioIndex(diagram);
|
|
m_tab->insertTab(index, dv, QET::Icons::Diagram, dv->title());
|
|
dv->setFrameStyle(QFrame::Plain | QFrame::NoFrame);
|
|
|
|
m_diagram_view_list.insert(index, dv);
|
|
|
|
rebuildDiagramsMap();
|
|
updateAllTabsTitle();
|
|
|
|
connect(dv, &DiagramView::showDiagram, this, QOverload<Diagram*>::of(&ProjectView::showDiagram));
|
|
connect(dv, &DiagramView::titleChanged, this, &ProjectView::updateTabTitle);
|
|
connect(dv, &DiagramView::findElementRequired, this, &ProjectView::findElementRequired);
|
|
connect(&dv->diagram()->border_and_titleblock , &BorderTitleBlock::titleBlockFolioChanged, [this, dv]() {this->updateTabTitle(dv);});
|
|
|
|
// signal diagram view was added
|
|
emit(diagramAdded(dv));
|
|
m_project->setModified(true);
|
|
showDiagram(dv);
|
|
}
|
|
|
|
/**
|
|
@brief ProjectView::updateTabTitle
|
|
Update the title of the tab which display the diagram view.
|
|
@param diagram_view : The diagram view.
|
|
*/
|
|
void ProjectView::updateTabTitle(DiagramView *diagram_view)
|
|
{
|
|
int diagram_tab_id = m_diagram_ids.key(diagram_view, -1);
|
|
|
|
if (diagram_tab_id != -1)
|
|
{
|
|
QSettings settings;
|
|
QString title;
|
|
Diagram *diagram = diagram_view->diagram();
|
|
|
|
if (settings.value("genericpanel/folio", false).toBool())
|
|
{
|
|
QString formula = diagram->border_and_titleblock.folio();
|
|
autonum::sequentialNumbers seq;
|
|
title = autonum::AssignVariables::formulaToLabel(
|
|
formula,
|
|
seq,
|
|
diagram);
|
|
}
|
|
else
|
|
title = QString::number(diagram->folioIndex() + 1);
|
|
|
|
title += " - ";
|
|
title += diagram->title();
|
|
m_tab->setTabText(diagram_tab_id ,title);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief ProjectView::updateAllTabsTitle
|
|
Update all tabs title
|
|
*/
|
|
void ProjectView::updateAllTabsTitle()
|
|
{
|
|
for (DiagramView *dv : m_diagram_ids.values())
|
|
updateTabTitle(dv);
|
|
}
|
|
|
|
/**
|
|
@param from Index de l'onglet avant le deplacement
|
|
@param to Index de l'onglet apres le deplacement
|
|
*/
|
|
void ProjectView::tabMoved(int from, int to)
|
|
{
|
|
if (!m_project)
|
|
return;
|
|
|
|
m_project->diagramOrderChanged(from, to);
|
|
rebuildDiagramsMap();
|
|
|
|
//Rebuild the title of each diagram in range from - to
|
|
for (int i= qMin(from,to) ; i< qMax(from,to)+1 ; ++i)
|
|
{
|
|
DiagramView *dv = m_diagram_ids.value(i);
|
|
updateTabTitle(dv);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief ProjectView::setDiagramPosition
|
|
Move \p diagram's tab to \p new_position (out-of-range values are
|
|
clamped by the underlying tab bar, same as a direct QTabBar::moveTab()
|
|
call). Used by MoveDiagramCommand for both redo and undo.
|
|
|
|
tabMoved() -- the slot that normally keeps the project's diagram list in
|
|
sync with the tab bar -- is connected with Qt::QueuedConnection (needed
|
|
so interactive drag-and-drop reordering settles before the model
|
|
updates). That queued call would run a second, redundant
|
|
diagramOrderChanged() after this method already performed it
|
|
synchronously, so that one connection is temporarily dropped around the
|
|
move (blocking all of the tab bar's signals instead would also suppress
|
|
QTabWidget's own internal tabMoved connection, which is what keeps its
|
|
page stack in the same order as the tab bar -- breaking the move
|
|
visually).
|
|
@param diagram
|
|
@param new_position
|
|
*/
|
|
void ProjectView::setDiagramPosition(Diagram *diagram, int new_position)
|
|
{
|
|
if (!m_project)
|
|
return;
|
|
|
|
DiagramView *diagram_view = findDiagram(diagram);
|
|
if (!diagram_view)
|
|
return;
|
|
|
|
int current_position = m_diagram_ids.key(diagram_view, -1);
|
|
if (current_position < 0)
|
|
return;
|
|
|
|
disconnect(m_tab->tabBar(), &QTabBar::tabMoved, this, &ProjectView::tabMoved);
|
|
m_tab->tabBar()->moveTab(current_position, new_position);
|
|
connect(m_tab->tabBar(), &QTabBar::tabMoved, this, &ProjectView::tabMoved, Qt::QueuedConnection);
|
|
|
|
rebuildDiagramsMap();
|
|
|
|
int actual_new_position = m_tab->indexOf(diagram_view);
|
|
if (actual_new_position != current_position)
|
|
m_project->diagramOrderChanged(current_position, actual_new_position);
|
|
|
|
for (int i = qMin(current_position, actual_new_position); i <= qMax(current_position, actual_new_position); ++i)
|
|
updateTabTitle(m_diagram_ids.value(i));
|
|
}
|
|
|
|
/**
|
|
@param diagram Schema a trouver
|
|
@return le DiagramView correspondant au schema passe en parametre, ou 0 si
|
|
le schema n'est pas trouve
|
|
*/
|
|
DiagramView *ProjectView::findDiagram(Diagram *diagram) {
|
|
foreach(DiagramView *diagram_view, diagram_views()) {
|
|
if (diagram_view -> diagram() == diagram) {
|
|
return(diagram_view);
|
|
}
|
|
}
|
|
return(nullptr);
|
|
}
|
|
|
|
/**
|
|
Reconstruit la map associant les index des onglets avec les DiagramView
|
|
*/
|
|
void ProjectView::rebuildDiagramsMap()
|
|
{
|
|
// vide la map
|
|
m_diagram_ids.clear();
|
|
|
|
foreach(DiagramView *diagram_view, m_diagram_view_list) {
|
|
int dv_idx = m_tab -> indexOf(diagram_view);
|
|
if (dv_idx == -1) continue;
|
|
m_diagram_ids.insert(dv_idx, diagram_view);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief ProjectView::tabChanged
|
|
Manage the tab change.
|
|
If tab_id == -1 (there is no diagram opened),
|
|
we display the fallback widget.
|
|
@param tab_id
|
|
*/
|
|
void ProjectView::tabChanged(int tab_id)
|
|
{
|
|
//A folio opened without its items (lazy folios) is built when
|
|
//its tab is first shown. Read the tab itself: the first tab is
|
|
//shown while loadDiagrams() is still filling m_diagram_ids
|
|
if (auto dv = qobject_cast<DiagramView *>(m_tab->widget(tab_id)))
|
|
dv->buildFolio();
|
|
|
|
if (tab_id == -1)
|
|
setDisplayFallbackWidget(true);
|
|
else if(m_tab->count() == 1)
|
|
setDisplayFallbackWidget(false);
|
|
|
|
emit(diagramActivated(m_diagram_ids[tab_id]));
|
|
|
|
if (m_diagram_ids[tab_id] != nullptr)
|
|
m_diagram_ids[tab_id]->diagram()->diagramActivated();
|
|
|
|
//Clear the event interface of the previous diagram
|
|
if (DiagramView *dv = m_diagram_ids[m_previous_tab_index])
|
|
dv->diagram()->clearEventInterface();
|
|
m_previous_tab_index = tab_id;
|
|
}
|
|
|
|
/**
|
|
Gere le double-clic sur un onglet : edite les proprietes du schema
|
|
@param tab_id Index de l'onglet concerne
|
|
*/
|
|
void ProjectView::tabDoubleClicked(int tab_id) {
|
|
// repere le schema concerne
|
|
DiagramView *diagram_view = m_diagram_ids[tab_id];
|
|
if (!diagram_view) return;
|
|
|
|
diagram_view -> editDiagramProperties();
|
|
}
|
|
|
|
/**
|
|
@param fallback true pour afficher le widget de fallback, false pour
|
|
afficher les onglets.
|
|
Le widget de Fallback est le widget affiche lorsque le projet ne comporte
|
|
aucun schema.
|
|
*/
|
|
void ProjectView::setDisplayFallbackWidget(bool fallback) {
|
|
fallback_widget_ -> setVisible(fallback);
|
|
m_tab -> setVisible(!fallback);
|
|
}
|