1 /*
2 	Copyright 2006-2019 The QElectroTech Team
3 	This file is part of QElectroTech.
4 
5 	QElectroTech is free software: you can redistribute it and/or modify
6 	it under the terms of the GNU General Public License as published by
7 	the Free Software Foundation, either version 2 of the License, or
8 	(at your option) any later version.
9 
10 	QElectroTech is distributed in the hope that it will be useful,
11 	but WITHOUT ANY WARRANTY; without even the implied warranty of
12 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 	GNU General Public License for more details.
14 
15 	You should have received a copy of the GNU General Public License
16 	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "projectview.h"
19 #include "qetproject.h"
20 #include "diagramview.h"
21 #include "diagram.h"
22 #include "diagramprintdialog.h"
23 #include "exportdialog.h"
24 #include "qetapp.h"
25 #include "qetelementeditor.h"
26 #include "borderpropertieswidget.h"
27 #include "titleblockpropertieswidget.h"
28 #include "conductorpropertieswidget.h"
29 #include "qeticons.h"
30 #include "qetmessagebox.h"
31 #include "qettemplateeditor.h"
32 #include "diagramfoliolist.h"
33 #include "projectpropertiesdialog.h"
34 #include "xmlelementcollection.h"
35 #include "autoNum/assignvariables.h"
36 #include "dialogwaiting.h"
37 
38 /**
39 	Constructeur
40 	@param project projet a visualiser
41 	@param parent Widget parent
42 */
ProjectView(QETProject * project,QWidget * parent)43 ProjectView::ProjectView(QETProject *project, QWidget *parent) :
44 	QWidget(parent),
45 	m_project(nullptr)
46 {
47 	initActions();
48 	initWidgets();
49 	initLayout();
50 
51 	setProject(project);
52 }
53 
54 /**
55 	Destructeur
56 	Supprime les DiagramView embarquees
57 */
~ProjectView()58 ProjectView::~ProjectView() {
59 	// qDebug() << "Suppression du ProjectView" << ((void *)this);
60 	foreach(int id, m_diagram_ids.keys()) {
61 		DiagramView *diagram_view = m_diagram_ids.take(id);
62 		delete diagram_view;
63 	}
64 }
65 
66 /**
67 	@return le projet actuellement visualise par le ProjectView
68 */
project()69 QETProject *ProjectView::project() {
70 	return(m_project);
71 }
72 
73 /**
74 	Definit le projet visualise par le ProjectView. Ne fait rien si le projet a
75 	deja ete defini.
76 	@param project projet a visualiser
77 */
setProject(QETProject * project)78 void ProjectView::setProject(QETProject *project) {
79 	if (!m_project) {
80 		m_project = project;
81 		connect(m_project, SIGNAL(projectTitleChanged(QETProject *, const QString &)),	this, SLOT(updateWindowTitle()));
82 		connect(m_project, SIGNAL(projectModified	(QETProject *, bool)),				this, SLOT(updateWindowTitle()));
83 		connect(m_project, SIGNAL(readOnlyChanged	(QETProject *, bool)),				this, SLOT(adjustReadOnlyState()));
84 		connect(m_project, SIGNAL(addAutoNumDiagram()),									this, SLOT(addNewDiagram()));
85 		adjustReadOnlyState();
86 		loadDiagrams();
87 	}
88 }
89 
90 /**
91 	@return la liste des schemas ouverts dans le projet
92 */
diagram_views() const93 QList<DiagramView *> ProjectView::diagram_views() const {
94 	return(m_diagram_view_list);
95 }
96 
97 /**
98  * @brief ProjectView::currentDiagram
99  * @return The current active diagram view or nullptr if there isn't diagramView in this project view.
100  */
currentDiagram() const101 DiagramView *ProjectView::currentDiagram() const {
102 	int current_tab_index = m_tab -> currentIndex();
103 	if (current_tab_index == -1)
104 		return nullptr;
105 	return(m_diagram_ids[current_tab_index]);
106 }
107 
108 /**
109 	Gere la fermeture du schema.
110 	@param qce Le QCloseEvent decrivant l'evenement
111 */
closeEvent(QCloseEvent * qce)112 void ProjectView::closeEvent(QCloseEvent *qce) {
113 	bool can_close_project = tryClosing();
114 	if (can_close_project) {
115 		qce -> accept();
116 		emit(projectClosed(this));
117 	} else {
118 		qce -> ignore();
119 	}
120 }
121 
122 /**
123 	@brief change current diagramview to next folio
124 */
changeTabDown()125 void ProjectView::changeTabDown(){
126 	DiagramView *nextDiagramView = this->nextDiagram();
127 	if (nextDiagramView!=nullptr){
128 		rebuildDiagramsMap();
129 		m_tab -> setCurrentWidget(nextDiagramView);
130 	}
131 }
132 
133 /**
134 	@return next folio of current diagramview
135 */
nextDiagram()136 DiagramView *ProjectView::nextDiagram() {
137 	int current_tab_index = m_tab -> currentIndex();
138 	int next_tab_index = current_tab_index + 1;	//get next tab index
139 	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.
140 		return(m_diagram_ids[next_tab_index]);
141 	else
142 		return nullptr;
143 }
144 
145 /**
146 	@brief change current diagramview to previous tab
147 */
changeTabUp()148 void ProjectView::changeTabUp(){
149 	DiagramView *previousDiagramView = this->previousDiagram();
150 	if (previousDiagramView!=nullptr){
151 		rebuildDiagramsMap();
152 		m_tab -> setCurrentWidget(previousDiagramView);
153 	}
154 }
155 
156 /**
157 	@return previous folio of current diagramview
158 */
previousDiagram()159 DiagramView *ProjectView::previousDiagram() {
160 	int current_tab_index = m_tab -> currentIndex();
161 	int previous_tab_index = current_tab_index - 1;	//get previous tab index
162 	if (previous_tab_index>=0) //if previous tab index = 0 then the first tab is activated so no need to change tab.
163 		return(m_diagram_ids[previous_tab_index]);
164 	else
165 		return nullptr;
166 }
167 
168 /**
169 	@brief change current diagramview to last tab
170 */
changeLastTab()171 void ProjectView::changeLastTab(){
172 	DiagramView *lastDiagramView = this->lastDiagram();
173 	m_tab->setCurrentWidget(lastDiagramView);
174 }
175 
176 /**
177 	@return last folio of current project
178 */
lastDiagram()179 DiagramView *ProjectView::lastDiagram(){
180 	return(m_diagram_ids.last());
181 }
182 
183 /**
184 	@brief change current diagramview to first tab
185 */
changeFirstTab()186 void ProjectView::changeFirstTab(){
187 	DiagramView *firstDiagramView = this->firstDiagram();
188 	m_tab->setCurrentWidget(firstDiagramView);
189 }
190 
191 /**
192 	@return first folio of current project
193 */
firstDiagram()194 DiagramView *ProjectView::firstDiagram(){
195 	return(m_diagram_ids.first());
196 }
197 
198 
199 /**
200 	Cette methode essaye de fermer successivement les editeurs d'element puis
201 	les schemas du projet. L'utilisateur peut refuser de fermer un schema ou un
202 	editeur.
203 	@return true si tout a pu etre ferme, false sinon
204 	@see tryClosingElementEditors()
205 	@see tryClosingDiagrams()
206 */
tryClosing()207 bool ProjectView::tryClosing() {
208 	if (!m_project) return(true);
209 
210 	// First step: require external editors closing -- users may either cancel
211 	// the whole closing process or save (and therefore add) content into this
212 	// project. Of course, they may also discard them.
213 	if (!tryClosingElementEditors()) {
214 		return(false);
215 	}
216 
217 	// Check how different the current situation is from a brand new, untouched project
218 	if (m_project -> filePath().isEmpty() && !m_project -> projectWasModified()) {
219 		return(true);
220 	}
221 
222 	// Second step: users are presented with a dialog that enables them to
223 	// choose whether they want to:
224 	//   - cancel the closing process,
225 	//   - discard all modifications,
226 	//   - or specify what is to be saved, i.e. they choose whether they wants to
227 	// save/give up/remove diagrams considered as modified.
228 	int user_input = tryClosingDiagrams();
229 	if (user_input == QMessageBox::Cancel) {
230 		return(false); // the closing process was cancelled
231 	} else if (user_input == QMessageBox::Discard) {
232 		return(true); // all modifications were discarded
233 	}
234 
235 	// Check how different the current situation is from a brand new, untouched project (yes , again)
236 	if (m_project -> filePath().isEmpty() && !m_project -> projectWasModified()) {
237 		return(true);
238 	}
239 
240 	if (m_project -> filePath().isEmpty()) {
241 		QString filepath = askUserForFilePath();
242 		if (filepath.isEmpty()) return(false); // users may cancel the closing
243 	}
244 	QETResult result = m_project -> write();
245 	updateWindowTitle();
246 	if (!result.isOk()) emit(errorEncountered(result.errorMessage()));
247 	return(result.isOk());
248 }
249 
250 /**
251 	Un projet comporte des elements integres. Cette methode ferme les editeurs
252 	d'elements associes a ce projet. L'utilisateur peut refuser la fermeture
253 	d'un editeur d'element.
254 	@return true si tous les editeurs d'element ont pu etre fermes, false sinon
255 */
tryClosingElementEditors()256 bool ProjectView::tryClosingElementEditors() {
257 	if (!m_project) return(true);
258 	/*
259 		La QETApp permet d'acceder rapidement aux editeurs d'element
260 		editant un element du projet.
261 	*/
262 	QList<QETElementEditor *> editors = QETApp::elementEditors(m_project);
263 	foreach(QETElementEditor *editor, editors) {
264 		if (!editor -> close()) return(false);
265 	}
266 
267 	QList<QETTitleBlockTemplateEditor *> template_editors = QETApp::titleBlockTemplateEditors(m_project);
268 	foreach(QETTitleBlockTemplateEditor *template_editor, template_editors) {
269 		if (!template_editor -> close()) return(false);
270 	}
271 	return(true);
272 }
273 
274 /**
275  * @brief ProjectView::tryClosingDiagrams
276  * try to close this project, if diagram or project option are changed
277  * a dialog ask if user want to save the modification.
278  * @return the answer of dialog or discard if no change.
279  */
tryClosingDiagrams()280 int ProjectView::tryClosingDiagrams() {
281 	if (!m_project) return(QMessageBox::Discard);
282 
283 	if (!project()->projectOptionsWereModified() &&
284 		project()->undoStack()->isClean() &&
285 		!project()->filePath().isEmpty()) {
286 		// nothing was modified, and we have a filepath, i.e. everything was already
287 		// saved, i.e we can close the project right now
288 		return(QMessageBox::Discard);
289 	}
290 
291 	QString title = project()->title();
292 	if (title.isEmpty()) title = "QElectroTech ";
293 
294 	int close_dialog = QMessageBox::question(this, title,
295 								   tr("Le projet à été modifié.\n"
296 									  "Voulez-vous enregistrer les modifications ?"),
297 								   QMessageBox::Save | QMessageBox::Discard
298 								   | QMessageBox::Cancel,
299 								   QMessageBox::Save);
300 
301 	return(close_dialog);
302 }
303 
304 /**
305 	Ask the user to provide a file path in which the currently edited project will
306 	be saved.
307 	@param assign When true, assign the provided filepath to the project through
308 	setFilePath(). Defaults to true.
309 	@return the file path, or an empty string if none were provided
310 */
askUserForFilePath(bool assign)311 QString ProjectView::askUserForFilePath(bool assign) {
312 	// ask the user for a filepath in order to save the project
313 	QString filepath = QFileDialog::getSaveFileName(
314 		this,
315 		tr("Enregistrer sous", "dialog title"),
316 		m_project -> currentDir(),
317 		tr("Projet QElectroTech (*.qet)", "filetypes allowed when saving a project file")
318 	);
319 
320 	// if no filepath is provided, return an empty string
321 	if (filepath.isEmpty()) return(filepath);
322 
323 	// if the name does not end with the .qet extension, append it
324 	if (!filepath.endsWith(".qet", Qt::CaseInsensitive)) filepath += ".qet";
325 
326 	if (assign) {
327 		// assign the provided filepath to the currently edited project
328 		m_project -> setFilePath(filepath);
329 	}
330 
331 	return(filepath);
332 }
333 
334 /**
335 	@return the QETResult object to be returned when it appears this project
336 	view is not associated to any project.
337 */
noProjectResult() const338 QETResult ProjectView::noProjectResult() const {
339 	QETResult no_project(tr("aucun projet affiché", "error message"), false);
340 	return(no_project);
341 }
342 
343 /**
344  * @brief ProjectView::addNewDiagram
345  * Add new diagram to project view
346  */
addNewDiagram()347 void ProjectView::addNewDiagram() {
348 	if (m_project -> isReadOnly()) return;
349 
350 	Diagram *new_diagram = m_project -> addNewDiagram();
351 	DiagramView *new_diagram_view = new DiagramView(new_diagram);
352 	addDiagram(new_diagram_view);
353 
354 	if (m_project -> diagrams().size() % 58 == 1 && m_project -> getFolioSheetsQuantity() != 0)
355 		addNewDiagramFolioList();
356 	showDiagram(new_diagram_view);
357 }
358 
359 /**
360  * @brief ProjectView::addNewDiagramFolioList
361  * Add new diagram folio list to project
362  */
addNewDiagramFolioList()363 void ProjectView::addNewDiagramFolioList() {
364 	if (m_project -> isReadOnly()) return;
365 	int i = 1; //< Each new diagram is added  to the end of the project.
366 			   //< We use @i to move the folio list at second position in the project
367 	foreach (Diagram *d, m_project -> addNewDiagramFolioList()) {
368 		DiagramView *new_diagram_view = new DiagramView(d);
369 		addDiagram(new_diagram_view);
370 		showDiagram(new_diagram_view);
371 		m_tab->tabBar()->moveTab(diagram_views().size()-1, i);
372 		i++;
373 	}
374 }
375 
376 /**
377  * @brief ProjectView::addDiagram
378  * Add diagram view to this project view
379  * @param diagram_view
380  */
addDiagram(DiagramView * diagram_view)381 void ProjectView::addDiagram(DiagramView *diagram_view)
382 {
383 	if (!diagram_view)
384 		return;
385 
386 		//Check if diagram isn't present in the project
387 	if (m_diagram_ids.values().contains(diagram_view))
388 		return;
389 
390 		// Add new tab for the diagram
391     m_tab->addTab(diagram_view, QET::Icons::Diagram, diagram_view -> title());
392 	diagram_view->setFrameStyle(QFrame::Plain | QFrame::NoFrame);
393 
394 	m_diagram_view_list << diagram_view;
395 
396 	rebuildDiagramsMap();
397 	updateAllTabsTitle();
398 
399 	connect(diagram_view, SIGNAL(showDiagram(Diagram*)), this, SLOT(showDiagram(Diagram*)));
400 	connect(diagram_view, SIGNAL(titleChanged(DiagramView *, const QString &)), this, SLOT(updateTabTitle(DiagramView *)));
401 	connect(diagram_view, SIGNAL(findElementRequired(const ElementsLocation &)), this, SIGNAL(findElementRequired(const ElementsLocation &)));
402 	connect(diagram_view, SIGNAL(editElementRequired(const ElementsLocation &)), this, SIGNAL(editElementRequired(const ElementsLocation &)));
403 	connect(&diagram_view->diagram()->border_and_titleblock , &BorderTitleBlock::titleBlockFolioChanged, [this, diagram_view]() {this->updateTabTitle(diagram_view);});
404 
405 		// signal diagram view was added
406 	emit(diagramAdded(diagram_view));
407 	m_project -> setModified(true);
408 }
409 
410 /**
411  * @brief ProjectView::removeDiagram
412  * Remove a diagram (folio) of the project
413  * @param diagram_view : diagram view to remove
414  */
removeDiagram(DiagramView * diagram_view)415 void ProjectView::removeDiagram(DiagramView *diagram_view)
416 {
417 	if (!diagram_view)
418         return;
419 	if (m_project -> isReadOnly())
420         return;
421 	if (!m_diagram_ids.values().contains(diagram_view))
422         return;
423 
424 
425         //Ask confirmation to user.
426 	int answer = QET::QetMessageBox::question(
427 		this,
428 		tr("Supprimer le folio ?", "message box title"),
429 		tr("Êtes-vous sûr  de vouloir supprimer ce folio du projet ? Ce changement est irréversible.", "message box content"),
430 		QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
431 		QMessageBox::No
432 	);
433 	if (answer != QMessageBox::Yes) {
434 		return;
435 	}
436 
437         //Remove the diagram view of the tabs widget
438 	int index_to_remove = m_diagram_ids.key(diagram_view);
439 	m_tab->removeTab(index_to_remove);
440 	m_diagram_view_list.removeAll(diagram_view);
441 	rebuildDiagramsMap();
442 
443 	m_project -> removeDiagram(diagram_view -> diagram());
444 	delete diagram_view;
445 
446 	emit(diagramRemoved(diagram_view));
447 
448         //Make definitve the withdrawal
449 	m_project -> write();
450     updateAllTabsTitle();
451     m_project -> setModified(true);
452 
453 }
454 
455 /**
456 	Enleve un schema du ProjectView
457 	@param diagram Schema a enlever
458 */
removeDiagram(Diagram * diagram)459 void ProjectView::removeDiagram(Diagram *diagram) {
460 	if (!diagram) return;
461 
462 	if (DiagramView *diagram_view = findDiagram(diagram)) {
463 		removeDiagram(diagram_view);
464 	}
465 }
466 
467 /**
468 	Active l'onglet adequat pour afficher le schema passe en parametre
469 	@param diagram Schema a afficher
470 */
showDiagram(DiagramView * diagram)471 void ProjectView::showDiagram(DiagramView *diagram) {
472 	if (!diagram) return;
473 	m_tab -> setCurrentWidget(diagram);
474 }
475 
476 /**
477 	Active l'onglet adequat pour afficher le schema passe en parametre
478 	@param diagram Schema a afficher
479 */
showDiagram(Diagram * diagram)480 void ProjectView::showDiagram(Diagram *diagram) {
481 	if (!diagram) return;
482 	if (DiagramView *diagram_view = findDiagram(diagram)) {
483 		m_tab -> setCurrentWidget(diagram_view);
484 	}
485 }
486 
487 /**
488 	Enable the user to edit properties of the current project through a
489 	configuration dialog.
490 */
editProjectProperties()491 void ProjectView::editProjectProperties() {
492 	if (!m_project) return;
493 	ProjectPropertiesDialog dialog(m_project, parentWidget());
494 	dialog.exec();
495 }
496 
497 /**
498 	Edite les proprietes du schema courant
499 */
editCurrentDiagramProperties()500 void ProjectView::editCurrentDiagramProperties() {
501 	editDiagramProperties(currentDiagram());
502 }
503 
504 /**
505 	Edite les proprietes du schema diagram_view
506 */
editDiagramProperties(DiagramView * diagram_view)507 void ProjectView::editDiagramProperties(DiagramView *diagram_view) {
508 	if (!diagram_view) return;
509 	showDiagram(diagram_view);
510 	diagram_view -> editDiagramProperties();
511 }
512 
513 /**
514 	Edite les proprietes du schema diagram
515 */
editDiagramProperties(Diagram * diagram)516 void ProjectView::editDiagramProperties(Diagram *diagram) {
517 	editDiagramProperties(findDiagram(diagram));
518 }
519 
520 /**
521 	Deplace le schema diagram_view vers le haut / la gauche
522 */
moveDiagramUp(DiagramView * diagram_view)523 void ProjectView::moveDiagramUp(DiagramView *diagram_view) {
524 	if (!diagram_view) return;
525 
526 	int diagram_view_position = m_diagram_ids.key(diagram_view);
527 	if (!diagram_view_position) {
528 		// le schema est le premier du projet
529 		return;
530 	}
531 	m_tab -> tabBar() -> moveTab(diagram_view_position, diagram_view_position - 1);
532 }
533 
534 /**
535 	Deplace le schema diagram vers le haut / la gauche
536 */
moveDiagramUp(Diagram * diagram)537 void ProjectView::moveDiagramUp(Diagram *diagram) {
538 	moveDiagramUp(findDiagram(diagram));
539 }
540 
541 /**
542 	Deplace le schema diagram_view vers le bas / la droite
543 */
moveDiagramDown(DiagramView * diagram_view)544 void ProjectView::moveDiagramDown(DiagramView *diagram_view) {
545 	if (!diagram_view) return;
546 
547 	int diagram_view_position = m_diagram_ids.key(diagram_view);
548 	if (diagram_view_position + 1 == m_diagram_ids.count()) {
549 		// le schema est le dernier du projet
550 		return;
551 	}
552 	m_tab -> tabBar() -> moveTab(diagram_view_position, diagram_view_position + 1);
553 }
554 
555 /**
556 	Deplace le schema diagram vers le bas / la droite
557 */
moveDiagramDown(Diagram * diagram)558 void ProjectView::moveDiagramDown(Diagram *diagram) {
559 	moveDiagramDown(findDiagram(diagram));
560 }
561 
562 /*
563  * Deplace le schema diagram_view vers le haut / la gauche en position 0
564  */
moveDiagramUpTop(DiagramView * diagram_view)565 void ProjectView::moveDiagramUpTop(DiagramView *diagram_view)
566 {
567 	if (!diagram_view) return;
568 
569 	int diagram_view_position = m_diagram_ids.key(diagram_view);
570 	if (!diagram_view_position) {
571 		// le schema est le premier du projet
572 		return;
573 	}
574 	m_tab -> tabBar() -> moveTab(diagram_view_position, (diagram_views().size(), 0));
575 }
576 
577 /*
578  * Deplace le schema diagram vers le haut / la gauche en position 0
579  */
moveDiagramUpTop(Diagram * diagram)580 void ProjectView::moveDiagramUpTop(Diagram *diagram)
581 {
582 	moveDiagramUpTop(findDiagram(diagram));
583 }
584 
585 /**
586 	Deplace le schema diagram_view vers le haut / la gauche x10
587 */
moveDiagramUpx10(DiagramView * diagram_view)588 void ProjectView::moveDiagramUpx10(DiagramView *diagram_view) {
589 	if (!diagram_view) return;
590 
591 	int diagram_view_position = m_diagram_ids.key(diagram_view);
592 	if (!diagram_view_position) {
593 		// le schema est le premier du projet
594 		return;
595 	}
596 	m_tab -> tabBar() -> moveTab(diagram_view_position, diagram_view_position - 10);
597 }
598 
599 /**
600 	Deplace le schema diagram vers le haut / la gauche x10
601 */
moveDiagramUpx10(Diagram * diagram)602 void ProjectView::moveDiagramUpx10(Diagram *diagram) {
603 	moveDiagramUpx10(findDiagram(diagram));
604 }
605 
606 /**
607 	Deplace le schema diagram_view vers le bas / la droite x10
608 */
moveDiagramDownx10(DiagramView * diagram_view)609 void ProjectView::moveDiagramDownx10(DiagramView *diagram_view) {
610 	if (!diagram_view) return;
611 
612 	int diagram_view_position = m_diagram_ids.key(diagram_view);
613 	if (diagram_view_position + 1 == m_diagram_ids.count()) {
614 		// le schema est le dernier du projet
615 		return;
616 	}
617 	m_tab -> tabBar() -> moveTab(diagram_view_position, diagram_view_position + 10);
618 }
619 
620 /**
621 	Deplace le schema diagram vers le bas / la droite x10
622 */
moveDiagramDownx10(Diagram * diagram)623 void ProjectView::moveDiagramDownx10(Diagram *diagram) {
624 	moveDiagramDownx10(findDiagram(diagram));
625 }
626 
627 /**
628 	Ce slot demarre un dialogue permettant a l'utilisateur de parametrer et de
629 	lancer l'impression de toute ou partie du projet.
630 */
printProject()631 void ProjectView::printProject() {
632 	if (!m_project) return;
633 
634 	// transforme le titre du projet en nom utilisable pour le document
635 	QString doc_name;
636 	if (!(m_project -> title().isEmpty())) {
637 		doc_name = m_project -> title();
638 	} else if (!m_project -> filePath().isEmpty()) {
639 		doc_name = QFileInfo(m_project -> filePath()).baseName();
640 	}
641 	doc_name = QET::stringToFileName(doc_name);
642 	if (doc_name.isEmpty()) {
643 		doc_name = tr("projet", "string used to generate a filename");
644 	}
645 
646 	// recupere le dossier contenant le fichier courant
647 	QString dir_path = m_project -> currentDir();
648 
649 	// determine un chemin pour le pdf / ps
650 	QString file_name = QDir::toNativeSeparators(QDir::cleanPath(dir_path + "/" + doc_name));
651 
652 	DiagramPrintDialog print_dialog(m_project, this);
653 	print_dialog.setDocName(doc_name);
654 	print_dialog.setFileName(file_name);
655 	print_dialog.exec();
656 }
657 
658 /**
659 	Exporte le schema.
660 */
exportProject()661 void ProjectView::exportProject() {
662 	if (!m_project) return;
663 
664 	ExportDialog ed(m_project, parentWidget());
665 #ifdef Q_OS_MAC
666 	ed.setWindowFlags(Qt::Sheet);
667 #endif
668 	ed.exec();
669 }
670 
671 /**
672 	Save project properties along with all modified diagrams.
673 	@see filePath()
674 	@see setFilePath()
675 	@return a QETResult object reflecting the situation
676 */
save()677 QETResult ProjectView::save() {
678 	return(doSave());
679 }
680 
681 /**
682 	Ask users for a filepath in order to save the project.
683 	@param options May be used to specify what should be saved; defaults to
684 	all modified diagrams.
685 	@return a QETResult object reflecting the situation; note that a valid
686 	QETResult object is returned if the operation was cancelled.
687 */
saveAs()688 QETResult ProjectView::saveAs()
689 {
690 	if (!m_project) return(noProjectResult());
691 
692 	QString filepath = askUserForFilePath();
693 	if (filepath.isEmpty()) return(QETResult());
694 	return(doSave());
695 }
696 
697 /**
698 	Save project content, then write the project file. May
699 	call saveAs if no filepath was provided before.
700 	@return a QETResult object reflecting the situation; note that a valid
701 	QETResult object is returned if the operation was cancelled.
702 */
doSave()703 QETResult ProjectView::doSave()
704 {
705 	if (!m_project) return(noProjectResult());
706 
707 	if (m_project -> filePath().isEmpty()) {
708 		// The project has not been saved to a file yet,
709 		// so save() actually means saveAs().
710 		return(saveAs());
711 	}
712 
713 	// write to file
714 	QETResult result = m_project -> write();
715 	updateWindowTitle();
716 	project()->undoStack()->clear();
717 	return(result);
718 }
719 
720 /**
721 	Allow the user to clean the project, which includes:
722 	  * deleting unused title block templates
723 	  * deleting unused elements
724 	  * deleting empty categories
725 	@return an integer value above zero if elements and/or categories were
726 	cleaned.
727 */
cleanProject()728 int ProjectView::cleanProject() {
729 	if (!m_project) return(0);
730 
731 	// s'assure que le schema n'est pas en lecture seule
732 	if (m_project -> isReadOnly()) {
733 		QET::QetMessageBox::critical(
734 			this,
735 			tr("Projet en lecture seule", "message box title"),
736 			tr("Ce projet est en lecture seule. Il n'est donc pas possible de le nettoyer.", "message box content")
737 		);
738 		return(0);
739 	}
740 
741 	// construit un petit dialogue pour parametrer le nettoyage
742 	QCheckBox *clean_tbt		= new QCheckBox(tr("Supprimer les modèles de cartouche inutilisés dans le projet"));
743 	QCheckBox *clean_elements   = new QCheckBox(tr("Supprimer les éléments inutilisés dans le projet"));
744 	QCheckBox *clean_categories = new QCheckBox(tr("Supprimer les catégories vides"));
745 	QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
746 
747 	clean_tbt		-> setChecked(true);
748 	clean_elements   -> setChecked(true);
749 	clean_categories -> setChecked(true);
750 
751 	QDialog clean_dialog(parentWidget());
752 #ifdef Q_OS_MAC
753 	clean_dialog.setWindowFlags(Qt::Sheet);
754 #endif
755 
756 	clean_dialog.setWindowTitle(tr("Nettoyer le projet", "window title"));
757 	QVBoxLayout *clean_dialog_layout = new QVBoxLayout();
758 	clean_dialog_layout -> addWidget(clean_tbt);
759 	clean_dialog_layout -> addWidget(clean_elements);
760 	clean_dialog_layout -> addWidget(clean_categories);
761 	clean_dialog_layout -> addWidget(buttons);
762 	clean_dialog.setLayout(clean_dialog_layout);
763 
764 	connect(buttons, SIGNAL(accepted()), &clean_dialog, SLOT(accept()));
765 	connect(buttons, SIGNAL(rejected()), &clean_dialog, SLOT(reject()));
766 
767 	int clean_count = 0;
768 	if (clean_dialog.exec() == QDialog::Accepted)
769 	{
770 		if (clean_tbt -> isChecked()) {
771 			m_project->embeddedTitleBlockTemplatesCollection()->deleteUnusedTitleBlocKTemplates();
772 		}
773 		if (clean_elements->isChecked()) {
774 			m_project->embeddedElementCollection()->cleanUnusedElement();
775 		}
776 		if (clean_categories->isChecked()) {
777 			m_project->embeddedElementCollection()->cleanUnusedDirectory();
778 		}
779 	}
780 
781 	m_project -> setModified(true);
782 	return(clean_count);
783 }
784 
785 /**
786 	Initialize actions for this widget.
787 */
initActions()788 void ProjectView::initActions() {
789 	add_new_diagram_ = new QAction(QET::Icons::AddFolio, tr("Ajouter un folio"), this);
790 	connect(add_new_diagram_, SIGNAL(triggered()), this, SLOT(addNewDiagram()));
791 }
792 
793 /**
794 	Initialize child widgets for this widget.
795 */
initWidgets()796 void ProjectView::initWidgets() {
797 	setObjectName("ProjectView");
798 	setWindowIcon(QET::Icons::ProjectFileGP);
799 
800 	// initialize the "fallback" widget
801 	fallback_widget_ = new QWidget();
802 	fallback_label_ = new QLabel(
803 		tr(
804 			"Ce projet ne contient aucun folio",
805 			"label displayed when a project contains no diagram"
806 		)
807 	);
808 	fallback_label_ -> setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
809 
810 	// initialize tabs
811 	m_tab = new QTabWidget(this);
812 	m_tab -> setMovable(true);
813 
814 	QToolButton *add_new_diagram_button = new QToolButton;
815 	add_new_diagram_button -> setDefaultAction(add_new_diagram_);
816 	add_new_diagram_button -> setAutoRaise(true);
817 	m_tab -> setCornerWidget(add_new_diagram_button, Qt::TopRightCorner);
818 
819 	connect(m_tab, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
820 	connect(m_tab, SIGNAL(tabBarDoubleClicked(int)), this, SLOT(tabDoubleClicked(int)));
821 	connect(m_tab->tabBar(), SIGNAL(tabMoved(int, int)), this, SLOT(tabMoved(int, int)));
822 
823 	fallback_widget_ -> setVisible(false);
824 	m_tab -> setVisible(false);
825 }
826 
827 /**
828 	Initialize layout for this widget.
829 */
initLayout()830 void ProjectView::initLayout() {
831 	QVBoxLayout *fallback_widget_layout_ = new QVBoxLayout(fallback_widget_);
832 	fallback_widget_layout_ -> addWidget(fallback_label_);
833 
834 	layout_ = new QVBoxLayout(this);
835 #ifdef Q_OS_MAC
836 	layout_ -> setContentsMargins(0, 8, 0, 0);
837 #else
838 	layout_ -> setContentsMargins(0, 0, 0, 0);
839 #endif
840 	layout_ -> setSpacing(0);
841 	layout_ -> addWidget(fallback_widget_);
842 	layout_ -> addWidget(m_tab);
843 }
844 
845 
846 /**
847  * @brief ProjectView::loadDiagrams
848  * Load diagrams of project.
849  * We create a diagram view for each diagram,
850  * and add it to the project view.
851  */
loadDiagrams()852 void ProjectView::loadDiagrams()
853 {
854 	if (!m_project) return;
855 
856 	setDisplayFallbackWidget(m_project -> diagrams().isEmpty());
857 
858 	DialogWaiting *dialog = nullptr;
859 	if(DialogWaiting::hasInstance())
860 	{
861 		dialog = DialogWaiting::instance();
862 		dialog->setTitle( tr("<p align=\"center\">"
863 												"<b>Ouverture du projet en cours...</b><br/>"
864 												"Création des onglets de folio :"
865 												"</p>"));
866 	}
867 	for(Diagram *diagram : m_project->diagrams())
868 	{
869 		if(dialog)
870 		{
871 			dialog->setDetail(diagram->title());
872 			dialog->setProgressBar(dialog->progressBarValue()+1);
873 		}
874 
875 		DiagramView *sv = new DiagramView(diagram);
876 		addDiagram(sv);
877 	}
878 
879 	this->currentDiagram()->diagram()->loadElmtFolioSeq();
880 	this->currentDiagram()->diagram()->loadCndFolioSeq();
881 
882 	// If project have the folios list, move it at the beginning of the project
883 	if (m_project -> getFolioSheetsQuantity()) {
884 		for (int i = 0; i < m_project->getFolioSheetsQuantity(); i++)
885 		m_tab -> tabBar() -> moveTab(diagram_views().size()-1, + 1);
886 		m_project->setModified(false);
887 	}
888 }
889 
890 /**
891  * @brief ProjectView::updateWindowTitle
892  * Update the project view title
893  */
updateWindowTitle()894 void ProjectView::updateWindowTitle() {
895 	QString title;
896 	if (m_project) {
897 		title = m_project -> pathNameTitle();
898 	} else {
899 		title = tr("Projet", "window title for a project-less ProjectView");
900 	}
901 	setWindowTitle(title);
902 }
903 
904 /**
905 	Effectue les actions necessaires lorsque le projet visualise entre ou sort
906 	du mode lecture seule.
907 */
adjustReadOnlyState()908 void ProjectView::adjustReadOnlyState() {
909 	bool editable = !(m_project -> isReadOnly());
910 
911 	// prevent users from moving existing diagrams
912 	m_tab -> setMovable(editable);
913 	// prevent users from adding new diagrams
914 	add_new_diagram_ -> setEnabled(editable);
915 
916 	// on met a jour le titre du widget, qui reflete l'etat de lecture seule
917 	updateWindowTitle();
918 }
919 
920 /**
921  * @brief ProjectView::updateTabTitle
922  * Update the title of the tab which display the diagram view @diagram_view.
923  * @param diagram : The diagram view.
924  */
updateTabTitle(DiagramView * diagram_view)925 void ProjectView::updateTabTitle(DiagramView *diagram_view)
926 {
927     int diagram_tab_id = m_diagram_ids.key(diagram_view, -1);
928 
929     if (diagram_tab_id != -1)
930     {
931         QSettings settings;
932         QString title;
933         Diagram *diagram = diagram_view->diagram();
934 
935         if (settings.value("genericpanel/folio", false).toBool())
936         {
937             QString formula = diagram->border_and_titleblock.folio();
938             autonum::sequentialNumbers seq;
939             title = autonum::AssignVariables::formulaToLabel(formula, seq, diagram);
940         }
941         else
942             title = QString::number(diagram->folioIndex() + 1);
943 
944         title += " - ";
945         title += diagram->title();
946         m_tab->setTabText(diagram_tab_id ,title);
947     }
948 }
949 
950 /**
951  * @brief ProjectView::updateAllTabsTitle
952  * Update all tabs title
953  */
updateAllTabsTitle()954 void ProjectView::updateAllTabsTitle()
955 {
956 	for (DiagramView *dv : m_diagram_ids.values())
957 		updateTabTitle(dv);
958 }
959 
960 /**
961 	@param from Index de l'onglet avant le deplacement
962 	@param to   Index de l'onglet apres le deplacement
963 */
tabMoved(int from,int to)964 void ProjectView::tabMoved(int from, int to)
965 {
966 	if (!m_project)
967 		return;
968 
969 	m_project->diagramOrderChanged(from, to);
970 	rebuildDiagramsMap();
971 
972 		//Rebuild the title of each diagram in range from - to
973 	for (int i= qMin(from,to) ; i< qMax(from,to)+1 ; ++i)
974 	{
975 		DiagramView *dv = m_diagram_ids.value(i);
976 		updateTabTitle(dv);
977 	}
978 }
979 
980 /**
981 	@param diagram Schema a trouver
982 	@return le DiagramView correspondant au schema passe en parametre, ou 0 si
983 	le schema n'est pas trouve
984 */
findDiagram(Diagram * diagram)985 DiagramView *ProjectView::findDiagram(Diagram *diagram) {
986 	foreach(DiagramView *diagram_view, diagram_views()) {
987 		if (diagram_view -> diagram() == diagram) {
988 			return(diagram_view);
989 		}
990 	}
991 	return(nullptr);
992 }
993 
994 /**
995 	Reconstruit la map associant les index des onglets avec les DiagramView
996 */
rebuildDiagramsMap()997 void ProjectView::rebuildDiagramsMap() {
998 	// vide la map
999 	m_diagram_ids.clear();
1000 
1001 	foreach(DiagramView *diagram_view, m_diagram_view_list) {
1002 		int dv_idx = m_tab -> indexOf(diagram_view);
1003 		if (dv_idx == -1) continue;
1004 		m_diagram_ids.insert(dv_idx, diagram_view);
1005 	}
1006 }
1007 
1008 /**
1009  * @brief ProjectView::tabChanged
1010  * Manage the tab change.
1011  * If tab_id == -1 (there is no diagram opened),
1012  * we display the fallback widget.
1013  * @param tab_id
1014  */
tabChanged(int tab_id)1015 void ProjectView::tabChanged(int tab_id)
1016 {
1017 	if (tab_id == -1)
1018 		setDisplayFallbackWidget(true);
1019 	else if(m_tab->count() == 1)
1020 		setDisplayFallbackWidget(false);
1021 
1022 	emit(diagramActivated(m_diagram_ids[tab_id]));
1023 
1024 	if (m_diagram_ids[tab_id] != nullptr)
1025 		m_diagram_ids[tab_id]->diagram()->diagramActivated();
1026 
1027 		//Clear the event interface of the previous diagram
1028 	if (DiagramView *dv = m_diagram_ids[m_previous_tab_index])
1029 		dv->diagram()->clearEventInterface();
1030 	m_previous_tab_index = tab_id;
1031 }
1032 
1033 /**
1034 	Gere le double-clic sur un onglet : edite les proprietes du schema
1035 	@param tab_id Index de l'onglet concerne
1036 */
tabDoubleClicked(int tab_id)1037 void ProjectView::tabDoubleClicked(int tab_id) {
1038 	// repere le schema concerne
1039 	DiagramView *diagram_view = m_diagram_ids[tab_id];
1040 	if (!diagram_view) return;
1041 
1042 	diagram_view -> editDiagramProperties();
1043 }
1044 
1045 /**
1046 	@param fallback true pour afficher le widget de fallback, false pour
1047 	afficher les onglets.
1048 	Le widget de Fallback est le widget affiche lorsque le projet ne comporte
1049 	aucun schema.
1050 */
setDisplayFallbackWidget(bool fallback)1051 void ProjectView::setDisplayFallbackWidget(bool fallback) {
1052 	fallback_widget_ -> setVisible(fallback);
1053 	m_tab -> setVisible(!fallback);
1054 }
1055