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 "qetelementeditor.h"
19 #include "qet.h"
20 #include "qetapp.h"
21 #include "elementscene.h"
22 #include "elementview.h"
23 #include "customelementpart.h"
24 #include "newelementwizard.h"
25 #include "elementitemeditor.h"
26 #include "elementdialog.h"
27 #include "recentfiles.h"
28 #include "qeticons.h"
29 #include "qetmessagebox.h"
30 #include "editorcommands.h"
31 
32 // editeurs de primitives
33 #include "arceditor.h"
34 #include "ellipseeditor.h"
35 #include "lineeditor.h"
36 #include "polygoneditor.h"
37 #include "rectangleeditor.h"
38 #include "terminaleditor.h"
39 #include "texteditor.h"
40 #include "partterminal.h"
41 #include "styleeditor.h"
42 #include "dynamictextfieldeditor.h"
43 
44 #include "eseventaddline.h"
45 #include "eseventaddrect.h"
46 #include "eseventaddellipse.h"
47 #include "eseventaddpolygon.h"
48 #include "eseventaddarc.h"
49 #include "eseventaddtext.h"
50 #include "eseventaddterminal.h"
51 #include "eseventadddynamictextfield.h"
52 
53 #include <QMessageBox>
54 #include <QTextStream>
55 #include <QFileDialog>
56 #include <QFile>
57 #include <QModelIndex>
58 #include <utility>
59 
60 /*
61 	Nombre maximum de primitives affichees par la "liste des parties"
62 	Au-dela, un petit message est affiche, indiquant que ce nombre a ete depasse
63 	et que la liste ne sera donc pas mise a jour.
64 */
65 #define QET_MAX_PARTS_IN_ELEMENT_EDITOR_LIST 200
66 
67 /**
68 	Constructeur
69 	@param parent QWidget parent
70 */
QETElementEditor(QWidget * parent)71 QETElementEditor::QETElementEditor(QWidget *parent) :
72 	QETMainWindow(parent),
73 	read_only(false),
74 	min_title(tr("QElectroTech - Éditeur d'élément", "window title")),
75 	opened_from_file(false)
76 {
77 	setWindowTitle(min_title);
78 	setWindowIcon(QET::Icons::QETLogo);
79 
80 	setupInterface();
81 	setupActions();
82 	setupMenus();
83 
84 	// la fenetre est maximisee par defaut
85 	setMinimumSize(QSize(500, 350));
86 	setWindowState(Qt::WindowMaximized);
87 
88 	// lecture des parametres
89 	readSettings();
90 	slot_updateMenus();
91 
92 	// affichage
93 	show();
94 }
95 
96 /// Destructeur
~QETElementEditor()97 QETElementEditor::~QETElementEditor() {
98 	/*
99 		retire le widget d'edition de primitives affiche par le dock
100 		cela evite qu'il ne soit supprime par son widget parent
101 	*/
102 	clearToolsDock();
103 
104 	// supprime les editeurs de primitives
105 	qDeleteAll(m_editors.begin(), m_editors.end());
106 	m_editors.clear();
107 }
108 
109 /**
110  * @brief QETElementEditor::setLocation
111  * The new location to edit
112  * @param el
113  */
setLocation(const ElementsLocation & el)114 void QETElementEditor::setLocation(const ElementsLocation &el)
115 {
116 	location_ = el;
117 	opened_from_file = false;
118 	setReadOnly(!location_.isWritable());
119 	slot_updateTitle();
120 }
121 
122 /**
123 	@param fn Le nouveau nom de fichier de l'element edite
124 */
setFileName(const QString & fn)125 void QETElementEditor::setFileName(const QString &fn) {
126 	filename_ = fn;
127 	opened_from_file = true;
128 	// modifie le mode lecture seule si besoin
129 	bool must_be_read_only = !QFileInfo(filename_).isWritable();
130 	if (isReadOnly() != must_be_read_only) {
131 		setReadOnly(must_be_read_only);
132 	}
133 	slot_updateTitle();
134 }
135 
136 /**
137  * @brief QETElementEditor::setupActions
138  * Create action used in Element editor
139  */
setupActions()140 void QETElementEditor::setupActions() {
141 	new_element       = new QAction(QET::Icons::DocumentNew,          tr("&Nouveau"),                                  this);
142 	open              = new QAction(QET::Icons::FolderOpen,         tr("&Ouvrir"),                                   this);
143 	open_file         = new QAction(QET::Icons::FolderOpen,         tr("&Ouvrir depuis un fichier"),                 this);
144 	open_dxf          = new QAction(QET::Icons::RunDxf,         tr("&Lancer le plugin convertisseur DXF"),       this);
145 	save              = new QAction(QET::Icons::DocumentSave,         tr("&Enregistrer"),                              this);
146 	save_as           = new QAction(QET::Icons::DocumentSaveAs,       tr("Enregistrer sous"),                          this);
147 	save_as_file      = new QAction(QET::Icons::DocumentSaveAs,       tr("Enregistrer dans un fichier"),               this);
148 	reload            = new QAction(QET::Icons::ViewRefresh,          tr("Recharger"),                                 this);
149 	quit              = new QAction(QET::Icons::ApplicationExit,      tr("&Quitter"),                                  this);
150 	selectall         = new QAction(QET::Icons::EditSelectAll,        tr("Tout sélectionner"),                         this);
151 	deselectall       = new QAction(QET::Icons::EditSelectNone,       tr("Désélectionner tout"),                       this);
152 	cut               = new QAction(QET::Icons::EditCut,              tr("Co&uper"),                                   this);
153 	copy              = new QAction(QET::Icons::EditCopy,             tr("Cop&ier"),                                   this);
154 	paste             = new QAction(QET::Icons::EditPaste,            tr("C&oller"),                                   this);
155 	paste_in_area     = new QAction(QET::Icons::EditPaste,            tr("C&oller dans la zone..."),                   this);
156 	paste_from_file   = new QAction(QET::Icons::XmlTextFile,          tr("un fichier"),                                this);
157 	paste_from_elmt   = new QAction(QET::Icons::Element,              tr("un élément"),                                this);
158 	inv_select        = new QAction(QET::Icons::EditSelectInvert,     tr("Inverser la sélection"),                     this);
159 	edit_delete       = new QAction(QET::Icons::EditDelete,           tr("&Supprimer"),                                this);
160 	edit_names        = new QAction(QET::Icons::Names,                tr("Éditer le nom et les traductions de l'élément"), this);
161 	edit_author       = new QAction(QET::Icons::UserInformations,     tr("Éditer les informations sur l'auteur"),      this);
162 	m_edit_properties = new QAction(QET::Icons::ElementEdit,          tr("Éditer les propriétés de l'élément"),        this);
163 
164 #if defined(Q_OS_WIN32) || defined(Q_OS_WIN64)
165 	open_dxf -> setStatusTip(tr("To install the plugin DXFtoQET\nVisit https://download.tuxfamily.org/qet/builds/dxf_to_elmt/\n"
166 					 "\n"
167 					 ">> Install on Windows\n"
168 					 "Put DXFtoQET.exe binary on C:\\Users\\user_name\\AppData\\Roaming\\qet\\ directory \n"
169 					   ));
170 #elif defined(Q_OS_MAC)
171 	open_dxf -> setStatusTip(tr("To install the plugin DXFtoQET\nVisit https://download.tuxfamily.org/qet/builds/dxf_to_elmt/\n"
172 					 "\n"
173 					 ">> Install on macOSX\n"
174 					 "Put DXFtoQET.app binary on /Users/user_name/.qet/ directory \n"
175 					  ));
176 #else
177 	open_dxf -> setStatusTip(tr("To install the plugin DXFtoQET\nVisit https://download.tuxfamily.org/qet/builds/dxf_to_elmt/\n"
178 					 "\n"
179 					 ">> Install on Linux\n"
180 					 "Put DXFtoQET binary on your /home/user_name/.qet/ directory\n"
181 					 "make it executable : chmod +x ./DXFtoQET\n"
182 					  ));
183 #endif
184 
185 	open_dxf -> setWhatsThis (tr("To install the plugin DXFtoQET\nVisit https://download.tuxfamily.org/qet/builds/dxf_to_elmt/\n"
186 					 "\n"
187 					 ">> Install on Linux\n"
188 					 "Put DXFtoQET binary on your /home/user_name/.qet/ directory\n"
189 					 "make it executable : chmod +x ./DXFtoQET\n"
190 					 ">> Install on Windows\n"
191 					 "Put DXFtoQET.exe binary on C:\\Users\\user_name\\AppData\\Roaming\\qet\\ directory \n"
192 					 "\n"
193 					 ">> Install on macOSX\n"
194 					 "Put DXFtoQET.app binary on /Users/user_name/.qet/ directory \n"
195 					  ));
196 
197 	undo = m_elmt_scene -> undoStack().createUndoAction(this, tr("Annuler"));
198 	redo = m_elmt_scene -> undoStack().createRedoAction(this, tr("Refaire"));
199 	undo -> setIcon(QET::Icons::EditUndo);
200 	redo -> setIcon(QET::Icons::EditRedo);
201 	undo -> setShortcuts(QKeySequence::Undo);
202 	redo -> setShortcuts(QKeySequence::Redo);
203 
204 	new_element       -> setShortcut(QKeySequence::New);
205 	open              -> setShortcut(QKeySequence::Open);
206 	open_file         -> setShortcut(tr("Ctrl+Shift+O"));
207 	save              -> setShortcut(QKeySequence::Save);
208 	save_as_file      -> setShortcut(tr("Ctrl+Shift+S"));
209 	quit              -> setShortcut(QKeySequence(tr("Ctrl+Q")));
210 	selectall         -> setShortcut(QKeySequence::SelectAll);
211 	deselectall       -> setShortcut(QKeySequence(tr("Ctrl+Shift+A")));
212 	inv_select        -> setShortcut(QKeySequence(tr("Ctrl+I")));
213 	cut               -> setShortcut(QKeySequence::Cut);
214 	copy              -> setShortcut(QKeySequence::Copy);
215 	paste             -> setShortcut(QKeySequence::Paste);
216 	paste_in_area     -> setShortcut(tr("Ctrl+Shift+V"));
217 #ifndef Q_OS_MAC
218 	edit_delete       -> setShortcut(QKeySequence(Qt::Key_Delete));
219 #else
220 	edit_delete       -> setShortcut(QKeySequence(tr("Backspace")));
221 #endif
222 
223 	edit_names        -> setShortcut(QKeySequence(tr("Ctrl+E")));
224 	edit_author       -> setShortcut(tr("Ctrl+Y"));
225 
226 	connect(new_element,     SIGNAL(triggered()), this,     SLOT(slot_new()));
227 	connect(open,            SIGNAL(triggered()), this,     SLOT(slot_open()));
228 	connect(open_dxf,       SIGNAL(triggered()), this,     SLOT(slot_openDxf()));
229 	connect(open_file,       SIGNAL(triggered()), this,     SLOT(slot_openFile()));
230 	connect(save,            SIGNAL(triggered()), this,     SLOT(slot_save()));
231 	connect(save_as,         SIGNAL(triggered()), this,     SLOT(slot_saveAs()));
232 	connect(save_as_file,    SIGNAL(triggered()), this,     SLOT(slot_saveAsFile()));
233 	connect(reload,          SIGNAL(triggered()), this,     SLOT(slot_reload()));
234 	connect(quit,            SIGNAL(triggered()), this,     SLOT(close()));
235 	connect(selectall,       SIGNAL(triggered()), m_elmt_scene, SLOT(slot_selectAll()));
236 	connect(deselectall,     SIGNAL(triggered()), m_elmt_scene, SLOT(slot_deselectAll()));
237 	connect(inv_select,      SIGNAL(triggered()), m_elmt_scene, SLOT(slot_invertSelection()));
238 	connect(cut,             SIGNAL(triggered()), m_view,  SLOT(cut()));
239 	connect(copy,            SIGNAL(triggered()), m_view,  SLOT(copy()));
240 	connect(paste,           SIGNAL(triggered()), m_view,  SLOT(paste()));
241 	connect(paste_in_area,   SIGNAL(triggered()), m_view,  SLOT(pasteInArea()));
242 	connect(paste_from_file, SIGNAL(triggered()), this,     SLOT(pasteFromFile()));
243 	connect(paste_from_elmt, SIGNAL(triggered()), this,     SLOT(pasteFromElement()));
244 	connect(edit_delete,     SIGNAL(triggered()), m_elmt_scene, SLOT(slot_delete()));
245 	connect(edit_names,      SIGNAL(triggered()), m_elmt_scene, SLOT(slot_editNames()));
246 	connect(edit_author,     SIGNAL(triggered()), m_elmt_scene, SLOT(slot_editAuthorInformations()));
247 	connect(m_edit_properties, SIGNAL(triggered()), m_elmt_scene, SLOT(slot_editProperties()));
248 
249 		//Action related to change depth of primitive
250 	m_depth_action_group = QET::depthActionGroup(this);
251 
252 	connect(m_depth_action_group, &QActionGroup::triggered, [this](QAction *action) {
253 		this->elementScene()->undoStack().push(new ChangeZValueCommand(this->elementScene(), action->data().value<QET::DepthOption>()));
254 		emit(this->elementScene()->partsZValueChanged());
255 	});
256 
257 	depth_toolbar = addToolBar(tr("Profondeur", "toolbar title"));
258 	depth_toolbar -> setObjectName("depth_toolbar");
259 	depth_toolbar -> addActions(m_depth_action_group -> actions());
260 	addToolBar(Qt::TopToolBarArea, depth_toolbar);
261 
262 
263 	/*
264 	 * Action related to zoom
265 	 */
266 	m_zoom_ag = new QActionGroup(this);
267 
268 	QAction *zoom_in    = new QAction(QET::Icons::ZoomIn,       tr("Zoom avant"),      m_zoom_ag);
269 	QAction *zoom_out   = new QAction(QET::Icons::ZoomOut,      tr("Zoom arrière"), m_zoom_ag);
270 	QAction *zoom_fit   = new QAction(QET::Icons::ZoomFitBest,  tr("Zoom adapté"),  m_zoom_ag);
271 	QAction *zoom_reset = new QAction(QET::Icons::ZoomOriginal, tr("Pas de zoom"),     m_zoom_ag);
272 
273 	zoom_in    -> setShortcut(QKeySequence::ZoomIn);
274 	zoom_out   -> setShortcut(QKeySequence::ZoomOut);
275 	zoom_fit   -> setShortcut(QKeySequence(tr("Ctrl+9")));
276 	zoom_reset -> setShortcut(QKeySequence(tr("Ctrl+0")));
277 
278 	connect(zoom_in,    SIGNAL(triggered()), m_view,  SLOT(zoomIn()    ));
279 	connect(zoom_out,   SIGNAL(triggered()), m_view,  SLOT(zoomOut()   ));
280 	connect(zoom_fit,   SIGNAL(triggered()), m_view,  SLOT(zoomFit()   ));
281 	connect(zoom_reset, SIGNAL(triggered()), m_view,  SLOT(zoomReset() ));
282 
283 
284 	/*
285 	 * Action related to primitive creation
286 	 */
287 	connect (m_elmt_scene, SIGNAL(partsAdded()), this, SLOT(UncheckAddPrimitive()));
288 	parts = new QActionGroup(this);
289 
290 	QAction *add_line      = new QAction(QET::Icons::PartLine,      tr("Ajouter une ligne"),         parts);
291 	QAction *add_rectangle = new QAction(QET::Icons::PartRectangle, tr("Ajouter un rectangle"),      parts);
292 	QAction *add_ellipse   = new QAction(QET::Icons::PartEllipse,   tr("Ajouter une ellipse"),       parts);
293 	QAction *add_polygon   = new QAction(QET::Icons::PartPolygon,   tr("Ajouter un polygone"),       parts);
294 	QAction *add_text      = new QAction(QET::Icons::PartText,      tr("Ajouter du texte"),          parts);
295 	QAction *add_arc       = new QAction(QET::Icons::PartArc,       tr("Ajouter un arc de cercle"),  parts);
296 	QAction *add_terminal  = new QAction(QET::Icons::Terminal,      tr("Ajouter une borne"),         parts);
297 	QAction *add_dynamic_text_field = new QAction(QET::Icons::PartTextField, tr("Ajouter un champ texte dynamique"), parts);
298 
299 	foreach (QAction *action, parts -> actions()) action -> setCheckable(true);
300 
301 	connect(add_line,      SIGNAL(triggered()), this, SLOT(addLine()      ));
302 	connect(add_rectangle, SIGNAL(triggered()), this, SLOT(addRect()      ));
303 	connect(add_ellipse,   SIGNAL(triggered()), this, SLOT(addEllipse()   ));
304 	connect(add_polygon,   SIGNAL(triggered()), this, SLOT(addPolygon()   ));
305 	connect(add_text,      SIGNAL(triggered()), this, SLOT(addText()      ));
306 	connect(add_arc,       SIGNAL(triggered()), this, SLOT(addArc()       ));
307 	connect(add_terminal,  SIGNAL(triggered()), this, SLOT(addTerminal()  ));
308 	connect(add_dynamic_text_field, &QAction::triggered, this, &QETElementEditor::addDynamicTextField);
309 
310 	add_polygon ->setStatusTip(tr("Double-click pour terminer la forme, Click droit pour annuler le dernier point"));
311 	add_text    ->setStatusTip(tr("Ajouter un texte d'élément non éditable dans les schémas"));
312 	add_dynamic_text_field ->setStatusTip(tr("Ajouter un texte d'élément pouvant être édité dans les schémas"));
313 
314 	parts_toolbar =  addToolBar(tr("Parties", "toolbar title"));
315 	parts_toolbar -> setAllowedAreas(Qt::AllToolBarAreas);
316 	parts_toolbar -> setObjectName("parts");
317 	parts_toolbar -> addActions(parts -> actions());
318 	addToolBar(Qt::LeftToolBarArea, parts_toolbar);
319 
320 
321 	main_toolbar = new QToolBar(tr("Outils", "toolbar title"), this);
322 	main_toolbar -> setObjectName("main_toolbar");
323 	view_toolbar = new QToolBar(tr("Affichage", "toolbar title"), this);
324 	view_toolbar -> setObjectName("display");
325 	element_toolbar = new QToolBar(tr("Élément", "toolbar title"), this);
326 	element_toolbar -> setObjectName("element_toolbar");
327 
328 	main_toolbar -> addAction(new_element);
329 	main_toolbar -> addAction(open);
330 	main_toolbar -> addAction(save);
331 	main_toolbar -> addAction(save_as);
332 	main_toolbar -> addAction(reload);
333 	main_toolbar -> addSeparator();
334 	main_toolbar -> addAction(undo);
335 	main_toolbar -> addAction(redo);
336 	main_toolbar -> addSeparator();
337 	main_toolbar -> addAction(edit_delete);
338 
339 	view_toolbar -> addAction(zoom_fit);
340 	view_toolbar -> addAction(zoom_reset);
341 
342 	element_toolbar -> addAction(edit_names);
343 	element_toolbar -> addAction(m_edit_properties);
344 
345 	addToolBar(Qt::TopToolBarArea, main_toolbar);
346 	addToolBar(Qt::TopToolBarArea, view_toolbar);
347 	addToolBar(Qt::TopToolBarArea, element_toolbar);
348 
349 	connect(m_elmt_scene, SIGNAL(selectionChanged()), this, SLOT(slot_updateInformations()), Qt::QueuedConnection);
350 	connect(m_elmt_scene, SIGNAL(selectionChanged()), this, SLOT(slot_updateMenus()));
351 	connect(QApplication::clipboard(),  SIGNAL(dataChanged()),      this, SLOT(slot_updateMenus()));
352 	connect(&(m_elmt_scene -> undoStack()), SIGNAL(cleanChanged(bool)), this, SLOT(slot_updateMenus()));
353 	connect(&(m_elmt_scene -> undoStack()), SIGNAL(cleanChanged(bool)), this, SLOT(slot_updateTitle()));
354 
355 	// Annuler ou refaire une action met a jour la liste des primitives ; cela sert notamment pour les
356 	// ajouts et suppressions de primitives ainsi que pour les actions entrainant un change
357 	connect(&(m_elmt_scene -> undoStack()), SIGNAL(indexChanged(int)),  this, SLOT(slot_updatePartsList()));
358 
359 	// Annuler ou refaire une action met a jour les informations affichees sur les primitives selectionnees,
360 	// celles-ci etant potentiellement impactees
361 	connect(&(m_elmt_scene -> undoStack()), SIGNAL(indexChanged(int)),  this, SLOT(slot_updateInformations()));
362 }
363 
364 /**
365  * @brief QETElementEditor::setupMenus
366  */
setupMenus()367 void QETElementEditor::setupMenus() {
368 	file_menu    = new QMenu(tr("&Fichier"),       this);
369 	edit_menu    = new QMenu(tr("&Édition"),    this);
370 	display_menu = new QMenu(tr("Afficha&ge"),     this);
371 	tools_menu   = new QMenu(tr("O&utils"),        this);
372 
373 	file_menu    -> setTearOffEnabled(true);
374 	edit_menu    -> setTearOffEnabled(true);
375 	display_menu -> setTearOffEnabled(true);
376 	tools_menu   -> setTearOffEnabled(true);
377 
378 	file_menu    -> addAction(new_element);
379 	file_menu    -> addAction(open);
380 	file_menu    -> addAction(open_file);
381 	file_menu    -> addAction(open_dxf);
382 	QMenu *recentfile = file_menu -> addMenu(QET::Icons::DocumentOpenRecent, tr("&Récemment ouverts"));
383 	recentfile->addActions(QETApp::elementsRecentFiles()->menu()->actions());
384 	connect(QETApp::elementsRecentFiles(), SIGNAL(fileOpeningRequested(const QString &)), this, SLOT(openRecentFile(const QString &)));
385 	file_menu    -> addAction(save);
386 	file_menu    -> addAction(save_as);
387 	file_menu    -> addAction(save_as_file);
388 	file_menu    -> addSeparator();
389 	file_menu    -> addAction(reload);
390 	file_menu    -> addSeparator();
391 	file_menu    -> addAction(quit);
392 
393 	paste_from_menu = new QMenu(tr("Coller depuis..."));
394 	paste_from_menu -> setIcon(QET::Icons::EditPaste);
395 	paste_from_menu -> addAction(paste_from_file);
396 	paste_from_menu -> addAction(paste_from_elmt);
397 
398 	edit_menu -> addAction(undo);
399 	edit_menu -> addAction(redo);
400 	edit_menu -> addSeparator();
401 	edit_menu -> addAction(selectall);
402 	edit_menu -> addAction(deselectall);
403 	edit_menu -> addAction(inv_select);
404 	edit_menu -> addSeparator();
405 	edit_menu -> addAction(cut);
406 	edit_menu -> addAction(copy);
407 	edit_menu -> addAction(paste);
408 	edit_menu -> addAction(paste_in_area);
409 	edit_menu -> addMenu(paste_from_menu);
410 	edit_menu -> addSeparator();
411 	edit_menu -> addAction(edit_delete);
412 	edit_menu -> addSeparator();
413 	edit_menu -> addAction(edit_names);
414 	edit_menu -> addAction(edit_author);
415 	edit_menu -> addAction(m_edit_properties);
416 	edit_menu -> addSeparator();
417 	edit_menu -> addActions(m_depth_action_group -> actions());
418 
419 	display_menu -> addActions(m_zoom_ag -> actions());
420 
421 	insertMenu(settings_menu_, file_menu);
422 	insertMenu(settings_menu_, edit_menu);
423 	insertMenu(settings_menu_, display_menu);
424 }
425 
426 /**
427  * @brief QETElementEditor::contextMenu
428  * Display a context menu, with all available action.
429  * @param p, the pos of the menu, in screen coordinate
430  * @param actions, a list of actions who can be prepended to the context menu.
431  */
contextMenu(QPoint p,QList<QAction * > actions)432 void QETElementEditor::contextMenu(QPoint p, QList<QAction *> actions)
433 {
434 		QMenu menu(this);
435 		menu.addActions(std::move(actions));
436 		menu.addSeparator();
437 		menu.addAction(undo);
438 		menu.addAction(redo);
439 		menu.addAction(selectall);
440 		menu.addAction(deselectall);
441 		menu.addAction(inv_select);
442 		menu.addSeparator();
443 		menu.addAction(edit_delete);
444 		menu.addAction(cut);
445 		menu.addAction(copy);
446 		menu.addSeparator();
447 		menu.addAction(paste);
448 		menu.addAction(paste_in_area);
449 		menu.addMenu(paste_from_menu);
450 		menu.addSeparator();
451 		menu.addActions(m_depth_action_group -> actions());
452 
453 			//Remove from the context menu the actions which are disabled.
454 		const QList<QAction *>menu_actions = menu.actions();
455 		for(QAction *action : menu_actions)
456 		{
457 			if(!action->isEnabled())
458 				menu.removeAction(action);
459 		}
460 		menu.exec(p);
461 }
462 
463 
464 /**
465 	Met a jour les menus
466 */
slot_updateMenus()467 void QETElementEditor::slot_updateMenus() {
468 	bool selected_items = !read_only && !m_elmt_scene -> selectedItems().isEmpty();
469 	bool clipboard_elmt = !read_only && ElementScene::clipboardMayContainElement();
470 
471 	// actions dependant seulement de l'etat "lecture seule" de l'editeur
472 	foreach (QAction *action, parts -> actions()) {
473 		action -> setEnabled(!read_only);
474 	}
475 	selectall       -> setEnabled(!read_only);
476 	inv_select      -> setEnabled(!read_only);
477 	paste_from_file -> setEnabled(!read_only);
478 	paste_from_elmt -> setEnabled(!read_only);
479 	m_parts_list      -> setEnabled(!read_only);
480 
481 	// Action enabled if primitive selected
482 	deselectall     -> setEnabled(selected_items);
483 	cut             -> setEnabled(selected_items);
484 	copy            -> setEnabled(selected_items);
485 	edit_delete     -> setEnabled(selected_items);
486 	foreach (QAction *action, m_depth_action_group -> actions())
487 		action->setEnabled(selected_items);
488 
489 	// actions dependant du contenu du presse-papiers
490 	paste           -> setEnabled(clipboard_elmt);
491 	paste_in_area   -> setEnabled(clipboard_elmt);
492 
493 	// actions dependant de l'etat de la pile d'annulation
494 	save            -> setEnabled(!read_only && !m_elmt_scene -> undoStack().isClean());
495 	undo            -> setEnabled(!read_only && m_elmt_scene -> undoStack().canUndo());
496 	redo            -> setEnabled(!read_only && m_elmt_scene -> undoStack().canRedo());
497 }
498 
499 /**
500 	Met a jour le titre de la fenetre
501 */
slot_updateTitle()502 void QETElementEditor::slot_updateTitle() {
503 	QString title = min_title;
504 	title += " - " + m_elmt_scene -> names().name() + " ";
505 	if (!filename_.isEmpty() || !location_.isNull()) {
506 		if (!m_elmt_scene -> undoStack().isClean()) title += tr("[Modifié]", "window title tag");
507 	}
508 	if (isReadOnly()) title += tr(" [lecture seule]", "window title tag");
509 	setWindowTitle(title);
510 }
511 
512 /**
513  * @brief QETElementEditor::setupInterface
514  */
setupInterface()515 void QETElementEditor::setupInterface()
516 {
517 		// editeur
518 	m_elmt_scene = new ElementScene(this, this);
519 	m_view = new ElementView(m_elmt_scene, this);
520 	slot_setRubberBandToView();
521 	setCentralWidget(m_view);
522 
523 		// widget par defaut dans le QDockWidget
524 	m_default_informations = new QLabel();
525 
526 		// ScrollArea pour accueillir un widget d'edition (change a la volee)
527 //	m_tools_dock_scroll_area = new QScrollArea();
528 //	m_tools_dock_scroll_area -> setFrameStyle(QFrame::NoFrame);
529 //	m_tools_dock_scroll_area -> setAlignment(Qt::AlignHCenter|Qt::AlignTop);
530 
531 		// Pile de widgets pour accueillir les deux widgets precedents
532 	m_tools_dock_stack = new QStackedWidget();
533 	m_tools_dock_stack -> insertWidget(0, m_default_informations);
534 //	m_tools_dock_stack -> insertWidget(1, m_tools_dock_scroll_area);
535 
536 		// widgets d'editions pour les parties
537 	m_editors["arc"]       = new ArcEditor(this);
538 	m_editors["ellipse"]   = new EllipseEditor(this);
539 	m_editors["line"]      = new LineEditor(this);
540 	m_editors["polygon"]   = new PolygonEditor(this);
541 	m_editors["rect"]      = new RectangleEditor(this);
542 	m_editors["terminal"]  = new TerminalEditor(this);
543 	m_editors["text"]      = new TextEditor(this);
544 	m_editors["style"]     = new StyleEditor(this);
545 	m_editors["dynamic_text"] = new DynamicTextFieldEditor(this);
546 
547 	// panel sur le cote pour editer les parties
548 	m_tools_dock = new QDockWidget(tr("Informations", "dock title"), this);
549 	m_tools_dock -> setObjectName("informations");
550 	m_tools_dock -> setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
551 	m_tools_dock -> setFeatures(QDockWidget::AllDockWidgetFeatures);
552 	//m_tools_dock -> setMinimumWidth(380);
553 	addDockWidget(Qt::RightDockWidgetArea, m_tools_dock);
554 	m_tools_dock -> setWidget(m_tools_dock_stack);
555 
556 	// panel sur le cote pour les annulations
557 	m_undo_dock = new QDockWidget(tr("Annulations", "dock title"), this);
558 	m_undo_dock -> setObjectName("undo");
559 	m_undo_dock -> setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
560 	m_undo_dock -> setFeatures(QDockWidget::AllDockWidgetFeatures);
561 	m_undo_dock -> setMinimumWidth(290);
562 	addDockWidget(Qt::RightDockWidgetArea, m_undo_dock);
563 	QUndoView* undo_view = new QUndoView(&(m_elmt_scene -> undoStack()), this);
564 	undo_view -> setEmptyLabel(tr("Aucune modification"));
565 	m_undo_dock -> setWidget(undo_view);
566 
567 	// panel sur le cote pour la liste des parties
568 	m_parts_list = new QListWidget(this);
569 	m_parts_list -> setSelectionMode(QAbstractItemView::ExtendedSelection);
570 	connect(m_elmt_scene,   SIGNAL(partsAdded()),           this, SLOT(slot_createPartsList()));
571 	connect(m_elmt_scene,   SIGNAL(partsRemoved()),         this, SLOT(slot_createPartsList()));
572 	connect(m_elmt_scene,   SIGNAL(partsZValueChanged()),   this, SLOT(slot_createPartsList()));
573 	connect(m_elmt_scene,   SIGNAL(selectionChanged()),     this, SLOT(slot_updatePartsList()));
574 	connect(m_parts_list, SIGNAL(itemSelectionChanged()), this, SLOT(slot_updateSelectionFromPartsList()));
575 	m_parts_dock = new QDockWidget(tr("Parties", "dock title"), this);
576 	m_parts_dock -> setObjectName("parts_list");
577 	m_parts_dock -> setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
578 	m_parts_dock -> setFeatures(QDockWidget::AllDockWidgetFeatures);
579 	m_parts_dock -> setMinimumWidth(290);
580 	tabifyDockWidget(m_undo_dock, m_parts_dock);
581 	m_parts_dock -> setWidget(m_parts_list);
582 
583 	slot_updateInformations();
584 	slot_createPartsList();
585 
586 	// barre d'etat
587 	statusBar() -> showMessage(tr("Éditeur d'éléments", "status bar message"));
588 }
589 
590 /**
591 	Passe l'editeur d'element en mode selection : le pointeur deplace les
592 	elements selectionnes et il est possible d'utiliser un rectangle de selection.
593 */
slot_setRubberBandToView()594 void QETElementEditor::slot_setRubberBandToView() {
595 	m_view -> setDragMode(QGraphicsView::RubberBandDrag);
596 }
597 
598 /**
599 	Passe l'editeur d'element en mode immobile (utilise pour la lecture seule)
600 */
slot_setNoDragToView()601 void QETElementEditor::slot_setNoDragToView() {
602 	m_view -> setDragMode(QGraphicsView::NoDrag);
603 }
604 
605 /**
606 	Met a jour la zone d'information et d'edition des primitives.
607 	Si plusieurs primitives sont selectionnees, seule leur quantite est
608 	affichee. Sinon, un widget d'edition approprie est mis en place.
609 */
slot_updateInformations()610 void QETElementEditor::slot_updateInformations()
611 {
612 	QList<QGraphicsItem *> selected_qgis = m_elmt_scene -> selectedItems();
613 	QList<CustomElementPart *> cep_list;
614 	bool style_editable = false;
615 
616 		//Test if part are editable by style editor
617 	if (selected_qgis.size() >= 2)
618 	{
619 		style_editable = true;
620 		foreach (QGraphicsItem *qgi, selected_qgis)
621 		{
622 			if (CustomElementPart *cep = dynamic_cast<CustomElementPart *>(qgi))
623 				cep_list << cep;
624 			else
625 				style_editable = false;
626 		}
627 		if (style_editable)
628 			style_editable = StyleEditor::isStyleEditable(cep_list);
629 
630 	}
631 
632 	if(selected_qgis.size() == 1)
633 	{
634 		QGraphicsItem *qgi = selected_qgis.first();
635 		if (CustomElementPart *selection = dynamic_cast<CustomElementPart *>(qgi))
636 		{
637 			if (QWidget *widget = m_tools_dock_stack->widget(1))
638 			{
639 				if (ElementItemEditor *editor = dynamic_cast<ElementItemEditor *>(widget))
640 				{
641 					if(editor->currentPart() == selection)
642 						return;
643 				}
644 			}
645 		}
646 	}
647 
648 		//There's one selected item
649 	if (selected_qgis.size() == 1)
650 	{
651 		QGraphicsItem *qgi = selected_qgis.first();
652 		if (CustomElementPart *selection = dynamic_cast<CustomElementPart *>(qgi))
653 		{
654 				//The current editor already edit the selected part
655 			if (QWidget *widget = m_tools_dock_stack->widget(1))
656 				if (ElementItemEditor *editor = dynamic_cast<ElementItemEditor *>(widget))
657 					if(editor->currentPart() == selection)
658 						return;
659 
660 			clearToolsDock();
661 				//We add the editor widget
662 			QString selection_xml_name = selection -> xmlName();
663 			ElementItemEditor *selection_editor = m_editors[selection_xml_name];
664 			if (selection_editor)
665 			{
666 				if (selection_editor->setPart(selection))
667 				{
668 					m_tools_dock_stack->insertWidget(1, selection_editor);
669 					m_tools_dock_stack -> setCurrentIndex(1);
670 				}
671 				else
672 				{
673 					qDebug() << "Editor refused part.";
674 				}
675 			}
676 		}
677 	}
678 		//There's several parts selecteds and all can be edited by style editor.
679 	else if (style_editable)
680 	{
681 		clearToolsDock();
682 		ElementItemEditor *selection_editor = m_editors["style"];
683 		if (selection_editor)
684 		{
685 			if (selection_editor -> setParts(cep_list))
686 			{
687 				m_tools_dock_stack->insertWidget(1, selection_editor);
688 				m_tools_dock_stack -> setCurrentIndex(1);
689 			}
690 			else
691 			{
692 				qDebug() << "Editor refused part.";
693 			}
694 		}
695 	}
696 		//Else we only display the number of selected items
697 	else
698 	{
699 		clearToolsDock();
700 		m_default_informations -> setText(tr("%n partie(s) sélectionnée(s).",
701 										   "",
702 										   selected_qgis.size()));
703 		m_default_informations -> setAlignment(Qt::AlignHCenter | Qt::AlignTop);
704 		m_tools_dock_stack -> setCurrentIndex(0);
705 	}
706 }
707 
708 /**
709  * @brief QETElementEditor::checkElement
710  * Do several check about element.
711  * If error is occured return false
712  */
checkElement()713 bool QETElementEditor::checkElement()
714 {
715 		//List of warning and error
716 	typedef QPair<QString, QString> QETWarning;
717 	QList<QETWarning> warnings;
718 	QList<QETWarning> errors;
719 
720 		/// Warning #1: Element haven't got terminal
721 		/// (except for report, because report must have one terminal and this checking is do below)
722 	if (!m_elmt_scene -> containsTerminals() && !m_elmt_scene -> elementType().contains("report"))
723 	{
724 		warnings << qMakePair(
725 			tr("Absence de borne", "warning title"),
726 			tr(
727 				"<br>En l'absence de borne, l'élément ne pourra être"
728 				" relié à d'autres éléments par l'intermédiaire de conducteurs.",
729 				"warning description"
730 			)
731 		);
732 	}
733 
734 		/// Check folio report element
735 	if (m_elmt_scene -> elementType().contains("report"))
736 	{
737 		int terminal =0;
738 
739 		foreach(QGraphicsItem *qgi, m_elmt_scene->items())
740 			if (qgraphicsitem_cast<PartTerminal *>(qgi))
741 				terminal ++;
742 
743 			///Error folio report must have only one terminal
744 		if (terminal != 1)
745 		{
746 			errors << qMakePair (tr("Absence de borne"),
747 								 tr("<br><b>Erreur</b> :"
748 									"<br>Les reports de folio doivent posséder une seul borne."
749 									"<br><b>Solution</b> :"
750 									"<br>Verifier que l'élément ne possède qu'une seul borne"));
751 		}
752 	}
753 
754 	if (!errors.count() && !warnings.count()) return(true);
755 
756 		// Display warnings
757 	QString dialog_message = tr("La vérification de cet élément a généré", "message box content");
758 
759 	if (errors.size())
760 		dialog_message += QString(tr(" %n erreur(s)", "errors", errors.size()));
761 
762 	if (warnings.size())
763 	{
764 		if (errors.size())
765 			dialog_message += QString (tr(" et"));
766 
767 		dialog_message += QString (tr(" %n avertissement(s)", "warnings", warnings.size()));
768 	}
769 	dialog_message += " :";
770 
771 	dialog_message += "<ol>";
772 	QList<QETWarning> total = warnings << errors;
773 	foreach(QETWarning warning, total) {
774 		dialog_message += "<li>";
775 		dialog_message += QString(
776 			tr("<b>%1</b> : %2", "warning title: warning description")
777 		).arg(warning.first).arg(warning.second);
778 		dialog_message += "</li>";
779 	}
780 	dialog_message += "</ol>";
781 
782 	if (errors.size())
783 		QMessageBox::critical(this, tr("Erreurs"), dialog_message);
784 	else
785 		QMessageBox::warning(this, tr("Avertissements"), dialog_message);
786 
787 		//if error == 0 that means they are only warning, we return true.
788 	if (errors.count() == 0) return(true);
789 	return false;
790 }
791 
792 /**
793 	Charge un fichier
794 	@param filepath Chemin du fichier a charger
795 */
fromFile(const QString & filepath)796 void QETElementEditor::fromFile(const QString &filepath) {
797 	bool state = true;
798 	QString error_message;
799 
800 	// le fichier doit exister
801 	QFileInfo infos_file(filepath);
802 	if (!infos_file.exists() || !infos_file.isFile()) {
803 		state = false;
804 		error_message = QString(tr("Le fichier %1 n'existe pas.", "message box content")).arg(filepath);
805 	}
806 
807 	// le fichier doit etre lisible
808 	QFile file(filepath);
809 	if (state) {
810 		if (!file.open(QIODevice::ReadOnly)) {
811 			state = false;
812 			error_message = QString(tr("Impossible d'ouvrir le fichier %1.", "message box content")).arg(filepath);
813 		}
814 	}
815 
816 	// le fichier doit etre un document XML
817 	QDomDocument document_xml;
818 	if (state) {
819 		if (!document_xml.setContent(&file)) {
820 			state = false;
821 			error_message = tr("Ce fichier n'est pas un document XML valide", "message box content");
822 		}
823 		file.close();
824 	}
825 
826 	if (!state) {
827 		QET::QetMessageBox::critical(this, tr("Erreur", "toolbar title"), error_message);
828 		return;
829 	}
830 
831 	// chargement de l'element
832 	m_elmt_scene -> fromXml(document_xml);
833 	slot_createPartsList();
834 
835 	// gestion de la lecture seule
836 	if (!infos_file.isWritable()) {
837 		QET::QetMessageBox::warning(
838 			this,
839 			tr("Édition en lecture seule", "message box title"),
840 			tr("Vous n'avez pas les privilèges nécessaires pour modifier cet élement. Il sera donc ouvert en lecture seule.", "message box content")
841 		);
842 		setReadOnly(true);
843 	} else {
844 		setReadOnly(false);
845 	}
846 
847 	// memorise le fichier
848 	setFileName(filepath);
849 	QETApp::elementsRecentFiles() -> fileWasOpened(filepath);
850 	slot_updateMenus();
851 }
852 
853 /**
854  * @brief QETElementEditor::toFile
855  * Save to file the drawed element.
856  * @param fn : path of the file
857  * @return : true if succesfully save.
858  */
toFile(const QString & fn)859 bool QETElementEditor::toFile(const QString &fn)
860 {
861 	m_elmt_scene->clearEventInterface();
862 	m_elmt_scene->clearSelection();
863 	UncheckAddPrimitive();
864 
865 	QDomDocument element_xml = m_elmt_scene->toXml();
866 	bool writing = QET::writeXmlFile(element_xml, fn);
867 	if (!writing) {
868 		QET::QetMessageBox::warning(
869 			this,
870 			tr("Erreur", "message box title"),
871 			tr("Impossible d'écrire dans ce fichier", "message box content")
872 		);
873 	}
874 	return(writing);
875 }
876 
877 
878 /**
879  * @brief QETElementEditor::toLocation
880  * Save the element to Location
881  * @param location : location where we must save the current element
882  * @return true if succesfully saved
883  */
toLocation(const ElementsLocation & location)884 bool QETElementEditor::toLocation(const ElementsLocation &location)
885 {
886 	m_elmt_scene->clearEventInterface();
887 	m_elmt_scene->clearSelection();
888 	UncheckAddPrimitive();
889 
890 	if (!location.setXml(m_elmt_scene->toXml()))
891 	{
892 		QET::QetMessageBox::critical(this,
893 									 tr("Erreur", "message box title"),
894 									 tr("Impossible d'enregistrer l'élément", "message box content"));
895 		return(false);
896 	}
897 	return(true);
898 }
899 
900 /**
901 	@param provided_location Emplacement d'un element
902 	@return true si cet editeur est en train d'editer l'element dont
903 	l'emplacement est location, false sinon
904 */
isEditing(const ElementsLocation & provided_location)905 bool QETElementEditor::isEditing(const ElementsLocation &provided_location) {
906 	if (opened_from_file) {
907 		return(
908 			QET::compareCanonicalFilePaths(
909 				filename_,
910 				QETApp::realPath(provided_location.toString())
911 			)
912 		);
913 	} else {
914 		return(provided_location == location_);
915 	}
916 }
917 
918 /**
919 	@param provided_filepath Chemin d'un element sur un filesystem
920 	@return true si cet editeur est en train d'editer l'element dont
921 	le chemin est filepath, false sinon
922 */
isEditing(const QString & provided_filepath)923 bool QETElementEditor::isEditing(const QString &provided_filepath) {
924 	// determine le chemin canonique de l'element actuelle edite, si applicable
925 	QString current_filepath;
926 	if (opened_from_file) {
927 		current_filepath = filename_;
928 	} else {
929 		current_filepath = QETApp::realPath(location_.toString());
930 	}
931 
932 	return(
933 		QET::compareCanonicalFilePaths(
934 			current_filepath,
935 			provided_filepath
936 		)
937 	);
938 }
939 
940 /**
941 	specifie si l'editeur d'element doit etre en mode lecture seule
942 	@param ro true pour activer le mode lecture seule, false pour le desactiver
943 */
setReadOnly(bool ro)944 void QETElementEditor::setReadOnly(bool ro) {
945 	read_only = ro;
946 
947 	// active / desactive les interactions avec la scene
948 	m_view -> setInteractive(!ro);
949 
950 	slot_updateMenus();
951 }
952 
953 /**
954 	@return true si l'editeur d'element est en mode lecture seule
955 */
isReadOnly() const956 bool QETElementEditor::isReadOnly() const {
957 	return(read_only);
958 }
959 
960 /**
961  * @brief QETElementEditor::addLine
962  * Set line creation interface to scene
963  */
addLine()964 void QETElementEditor::addLine() {
965 	m_elmt_scene -> setEventInterface(new ESEventAddLine(m_elmt_scene));
966 }
967 
968 /**
969  * @brief QETElementEditor::addRect
970  * Set rectangle creation interface to scene
971  */
addRect()972 void QETElementEditor::addRect() {
973 	m_elmt_scene -> setEventInterface(new ESEventAddRect(m_elmt_scene));
974 }
975 
976 /**
977  * @brief QETElementEditor::addEllipse
978  * Set ellipse creation interface to scene
979  */
addEllipse()980 void QETElementEditor::addEllipse() {
981 	m_elmt_scene -> setEventInterface(new ESEventAddEllipse(m_elmt_scene));
982 }
983 
984 /**
985  * @brief QETElementEditor::addPolygon
986  * Set polygon creation interface to scene
987  */
addPolygon()988 void QETElementEditor::addPolygon() {
989 	m_elmt_scene -> setEventInterface(new ESEventAddPolygon(m_elmt_scene));
990 }
991 
992 /**
993  * @brief QETElementEditor::addArc
994  * Set arc creation interface to scene
995  */
addArc()996 void QETElementEditor::addArc() {
997 	m_elmt_scene -> setEventInterface(new ESEventAddArc(m_elmt_scene));
998 }
999 
1000 /**
1001  * @brief QETElementEditor::addText
1002  * Set text creation interface to scene
1003  */
addText()1004 void QETElementEditor::addText() {
1005 	m_elmt_scene -> setEventInterface(new ESEventAddText(m_elmt_scene));
1006 }
1007 
1008 /**
1009  * @brief QETElementEditor::addTerminal
1010  * Set terminal creation interface to scene
1011  */
addTerminal()1012 void QETElementEditor::addTerminal() {
1013 	m_elmt_scene -> setEventInterface(new ESEventAddTerminal(m_elmt_scene));
1014 }
1015 
1016 /**
1017  * @brief QETElementEditor::addDynamicTextField
1018  * Set dynamic text field creation interface to scene
1019  */
addDynamicTextField()1020 void QETElementEditor::addDynamicTextField() {
1021 	m_elmt_scene->setEventInterface(new ESEventAddDynamicTextField(m_elmt_scene));
1022 }
1023 
1024 /**
1025  * @brief QETElementEditor::UncheckAddPrimitive
1026  * Uncheck all action related to primitive
1027  */
UncheckAddPrimitive()1028 void QETElementEditor::UncheckAddPrimitive() {
1029 	foreach(QAction *action, parts->actions()) action -> setChecked(false);
1030 }
1031 
1032 /**
1033 	Lance l'assistant de creation d'un nouvel element.
1034 */
slot_new()1035 void QETElementEditor::slot_new() {
1036 	NewElementWizard new_element_wizard(this);
1037 	new_element_wizard.exec();
1038 }
1039 
1040 /**
1041 	Ouvre un element
1042 */
slot_open()1043 void QETElementEditor::slot_open() {
1044 	// demande le chemin virtuel de l'element a ouvrir a l'utilisateur
1045 	ElementsLocation location = ElementDialog::getOpenElementLocation(this);
1046 	if (location.isNull()) return;
1047 	QETApp::instance() -> openElementLocations(QList<ElementsLocation>() << location);
1048 }
1049 
1050 /**
1051 	Ouvre un fichier
1052 	Demande un fichier a l'utilisateur et ouvre ce fichier
1053 */
slot_openFile()1054 void QETElementEditor::slot_openFile() {
1055 	// repertoire a afficher initialement dans le dialogue
1056 	QString open_dir = filename_.isEmpty() ? QETApp::customElementsDir() : QDir(filename_).absolutePath();
1057 
1058 	// demande un nom de fichier a ouvrir a l'utilisateur
1059 	QString user_filename = QETElementEditor::getOpenElementFileName(this, open_dir);
1060 
1061 	// ouvre l'element
1062 	openElement(user_filename);
1063 }
1064 
1065 /**
1066 	Slot utilise pour ouvrir un fichier recent.
1067 	Transfere filepath au slot openElement seulement si cet editeur est actif
1068 	@param filepath Fichier a ouvrir
1069 	@see openElement
1070 */
openRecentFile(const QString & filepath)1071 void QETElementEditor::openRecentFile(const QString &filepath) {
1072 	// small hack to prevent all element editors from trying to topen the required
1073 	// recent file at the same time
1074 	if (qApp -> activeWindow() != this) return;
1075 	openElement(filepath);
1076 }
1077 
1078 /**
1079  * @brief QETElementEditor::slot_openDxf
1080  */
slot_openDxf()1081 void QETElementEditor::slot_openDxf (){
1082 
1083 #if defined(Q_OS_WIN32) || defined(Q_OS_WIN64)
1084 QString program = (QDir::homePath() + "/Application Data/qet/DXFtoQET.exe");
1085 #elif defined(Q_OS_MAC)
1086 QString program = (QDir::homePath() + "/.qet/DXFtoQET.app");
1087 #else
1088 QString program = (QDir::homePath() + "/.qet/DXFtoQET");
1089 #endif
1090 QStringList arguments;
1091 QProcess *DXF = new QProcess(qApp);
1092 DXF->start(program,arguments);
1093 
1094 }
1095 
1096 /**
1097 	Ouvre un fichier element dans un nouvel editeur
1098 	Cette methode ne controle pas si le fichier est deja ouvert
1099 	@param filepath Fichier a ouvrir
1100 	@see fromFile
1101 	@see QETApp::openElementFiles
1102 */
openElement(const QString & filepath)1103 void QETElementEditor::openElement(const QString &filepath) {
1104 	if (filepath.isEmpty()) return;
1105 	// we have to test the file existence here because QETApp::openElementFiles()
1106 	// will discard non-existent files through QFileInfo::canonicalFilePath()
1107 	if (!QFile::exists(filepath)) {
1108 		QET::QetMessageBox::critical(
1109 			this,
1110 			tr("Impossible d'ouvrir le fichier", "message box title"),
1111 			QString(
1112 				tr("Il semblerait que le fichier %1 que vous essayez d'ouvrir"
1113 				" n'existe pas ou plus.")
1114 			).arg(filepath)
1115 		);
1116 	}
1117 	QETApp::instance() -> openElementFiles(QStringList() << filepath);
1118 }
1119 
1120 /**
1121  * @brief QETElementEditor::slot_reload
1122  * Reload the element from the file or location
1123  */
slot_reload()1124 void QETElementEditor::slot_reload()
1125 {
1126 		//If user already edit the element, ask confirmation to reload
1127 	if (!m_elmt_scene -> undoStack().isClean())
1128 	{
1129 		QMessageBox::StandardButton answer = QET::QetMessageBox::question(this,
1130 																		  tr("Recharger l'élément", "dialog title"),
1131 																		  tr("Vous avez efffectué des modifications sur cet élément. Si vous le rechargez, ces modifications seront perdues. Voulez-vous vraiment recharger l'élément ?", "dialog content"),
1132 																		  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
1133 																		  QMessageBox::Cancel);
1134 		if (answer != QMessageBox::Yes) return;
1135 	}
1136 
1137 		//Reload the element
1138 	m_elmt_scene -> reset();
1139 	if (opened_from_file)
1140 		fromFile(filename_);
1141 	else
1142 		fromLocation(location_);
1143 }
1144 
1145 /**
1146  * @brief QETElementEditor::slot_save
1147  * Save the current editing element.
1148  * If the filepath or location is unknow, use save_as instead
1149  * @return true if save with success
1150  */
slot_save()1151 bool QETElementEditor::slot_save()
1152 {
1153 		// Check element befor writing
1154 	if (checkElement())
1155 	{
1156 			//If we don't know the name of the current file, use save as instead
1157 		if (opened_from_file)
1158 		{
1159 			if (filename_.isEmpty())
1160 				return(slot_saveAsFile());
1161 
1162 				//Else wa save to the file at filename_ path
1163 			bool result_save = toFile(filename_);
1164 			if (result_save) m_elmt_scene -> undoStack().setClean();
1165 			return(result_save);
1166 		}
1167 		else
1168 		{
1169 			if (location_.isNull())
1170 				return(slot_saveAs());
1171 
1172 				//Else save to the known location
1173 			bool result_save = toLocation(location_);
1174 			if (result_save) {
1175 				m_elmt_scene -> undoStack().setClean();
1176 				emit saveToLocation(location_);
1177 			}
1178 			return(result_save);
1179 		}
1180 	}
1181 
1182 	QMessageBox::critical(this, tr("Echec de l'enregistrement"), tr("L'enregistrement à échoué,\nles conditions requises ne sont pas valides"));
1183 	return false;
1184 }
1185 
1186 /**
1187  * @brief QETElementEditor::slot_saveAs
1188  * Ask a location to user and save the current edited element
1189  * to this location
1190  * @return true if save with success
1191  */
slot_saveAs()1192 bool QETElementEditor::slot_saveAs()
1193 {
1194 		// Check element befor writing
1195 	if (checkElement())
1196 	{
1197 			//Ask a location to user
1198 		ElementsLocation location = ElementDialog::getSaveElementLocation(this);
1199 		if (location.isNull())
1200 			return(false);
1201 
1202 		bool result_save = toLocation(location);
1203 		if (result_save)
1204 		{
1205 			setLocation(location);
1206 			m_elmt_scene->undoStack().setClean();
1207 			emit saveToLocation(location);
1208 		}
1209 
1210 		return(result_save);
1211 	}
1212 	QMessageBox::critical(this, tr("Echec de l'enregistrement"), tr("L'enregistrement à échoué,\nles conditions requises ne sont pas valides"));
1213 	return (false);
1214 }
1215 
1216 /**
1217  * @brief QETElementEditor::slot_saveAsFile
1218  * Ask a file to user and save the current edited element to this file
1219  * @return true if save with success
1220  */
slot_saveAsFile()1221 bool QETElementEditor::slot_saveAsFile()
1222 {
1223 		// Check element befor writing
1224 	if (checkElement())
1225 	{
1226 			//Ask a filename to user, for save the element
1227 		QString fn = QFileDialog::getSaveFileName(
1228 			this,
1229 			tr("Enregistrer sous", "dialog title"),
1230 			filename_.isEmpty() ? QETApp::customElementsDir() : QDir(filename_).absolutePath(),
1231 			tr(
1232 				"Éléments QElectroTech (*.elmt)",
1233 				"filetypes allowed when saving an element file"
1234 			)
1235 		);
1236 
1237 		if (fn.isEmpty())
1238 			return(false);
1239 
1240 			//If the name doesn't end by .elmt, we add it
1241 		if (!fn.endsWith(".elmt", Qt::CaseInsensitive))
1242 			fn += ".elmt";
1243 
1244 		bool result_save = toFile(fn);
1245 			//If the save success, the filename is keep
1246 		if (result_save)
1247 		{
1248 			setFileName(fn);
1249 			QETApp::elementsRecentFiles() -> fileWasOpened(fn);
1250 			m_elmt_scene -> undoStack().setClean();
1251 		}
1252 
1253 		return(result_save);
1254 	}
1255 	QMessageBox::critical(this, tr("Echec de l'enregistrement"), tr("L'enregistrement à échoué,\nles conditions requises ne sont pas valides"));
1256 	return false;
1257 }
1258 
1259 /**
1260 	@return true si l'element peut etre ferme.
1261 	Un element peut etre ferme s'il ne comporte aucune modification.
1262 	Si l'element comporte des modifications, la question est posee a
1263 	l'utilisateur.
1264 */
canClose()1265 bool QETElementEditor::canClose() {
1266 	if (m_elmt_scene -> undoStack().isClean()) return(true);
1267 	// demande d'abord a l'utilisateur s'il veut enregistrer l'element en cours
1268 	QMessageBox::StandardButton answer = QET::QetMessageBox::question(
1269 		this,
1270 		tr("Enregistrer l'élément en cours ?", "dialog title"),
1271 		QString(
1272 			tr(
1273 				"Voulez-vous enregistrer l'élément %1 ?",
1274 				"dialog content - %1 is an element name"
1275 			)
1276 		).arg(m_elmt_scene -> names().name()),
1277 		QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
1278 		QMessageBox::Cancel
1279 	);
1280 	bool result;
1281 	switch(answer) {
1282 		case QMessageBox::Cancel: result = false;         break; // l'utilisateur annule : echec de la fermeture
1283 		case QMessageBox::Yes:    result = slot_save();   break; // l'utilisateur dit oui : la reussite depend de l'enregistrement
1284 		default:                  result = true;                 // l'utilisateur dit non ou ferme le dialogue: c'est reussi
1285 	}
1286 	return(result);
1287 }
1288 
1289 /**
1290 	Enleve et cache le widget affiche par le dock permettant d'editer les
1291 	parties.
1292 	@return le widget enleve, ou 0 s'il n'y avait pas de widget a enlever
1293 */
clearToolsDock()1294 QWidget *QETElementEditor::clearToolsDock() {
1295 	if (QWidget *previous_widget = m_tools_dock_stack->widget(1))
1296 	{
1297 		m_tools_dock_stack->removeWidget(previous_widget);
1298 		previous_widget -> setParent(nullptr);
1299 		previous_widget -> hide();
1300 		return(previous_widget);
1301 	}
1302 	return(nullptr);
1303 }
1304 
1305 /**
1306 	Exporte le document XML xml_document vers le presse-papier puis declenche
1307 	son collage dans l'editeur courant, avec selection de la zone de collage
1308 	@param xml_document Document XML a copier/coller
1309 	@see ElementView::pasteInArea
1310 */
copyAndPasteXml(const QDomDocument & xml_document)1311 void QETElementEditor::copyAndPasteXml(const QDomDocument &xml_document) {
1312 	// accede au presse-papier
1313 	QClipboard *clipboard = QApplication::clipboard();
1314 
1315 	// genere la description XML de la selection
1316 	QString clipboard_content = xml_document.toString(4);
1317 
1318 	// met la description XML dans le presse-papier
1319 	if (clipboard -> supportsSelection()) {
1320 		clipboard -> setText(clipboard_content, QClipboard::Selection);
1321 	}
1322 	clipboard -> setText(clipboard_content);
1323 
1324 	m_view -> pasteInArea();
1325 }
1326 
1327 /**
1328 	Permet de quitter l'editeur lors de la fermeture de la fenetre principale
1329 	@param qce Le QCloseEvent correspondant a l'evenement de fermeture
1330 */
closeEvent(QCloseEvent * qce)1331 void QETElementEditor::closeEvent(QCloseEvent *qce) {
1332 	if (canClose()) {
1333 		writeSettings();
1334 		setAttribute(Qt::WA_DeleteOnClose);
1335 		m_elmt_scene -> reset();
1336 		qce -> accept();
1337 	} else qce -> ignore();
1338 }
1339 
1340 /**
1341 	Executed the first time the window editor is displayed.
1342 */
firstActivation(QEvent * event)1343 void QETElementEditor::firstActivation(QEvent *event) {
1344 	Q_UNUSED(event)
1345 	QTimer::singleShot(250, m_view, SLOT(zoomFit()));
1346 }
1347 
1348 /**
1349 	Remplit la liste des parties
1350 */
slot_createPartsList()1351 void QETElementEditor::slot_createPartsList() {
1352 	m_parts_list -> blockSignals(true);
1353 	m_parts_list -> clear();
1354 	QList<QGraphicsItem *> qgis = m_elmt_scene -> zItems();
1355 
1356 	// on ne construit plus la liste a partir de 200 primitives
1357 	// c'est ingerable : la maj de la liste prend trop de temps et le resultat
1358 	// est inexploitable
1359 	if (qgis.count() <= QET_MAX_PARTS_IN_ELEMENT_EDITOR_LIST) {
1360 		for (int j = qgis.count() - 1 ; j >= 0 ; -- j) {
1361 			QGraphicsItem *qgi = qgis[j];
1362 			if (CustomElementPart *cep = dynamic_cast<CustomElementPart *>(qgi)) {
1363 				QString part_desc = cep -> name();
1364 				QListWidgetItem *qlwi = new QListWidgetItem(part_desc);
1365 				QVariant v;
1366 				v.setValue<QGraphicsItem *>(qgi);
1367 				qlwi -> setData(42, v);
1368 				m_parts_list -> addItem(qlwi);
1369 				qlwi -> setSelected(qgi -> isSelected());
1370 			}
1371 		}
1372 	} else {
1373 		m_parts_list -> addItem(new QListWidgetItem(tr("Trop de primitives, liste non générée.")));
1374 	}
1375 	m_parts_list -> blockSignals(false);
1376 }
1377 
1378 /**
1379 	Met a jour la selection dans la liste des parties
1380 */
slot_updatePartsList()1381 void QETElementEditor::slot_updatePartsList() {
1382 	int items_count = m_elmt_scene -> items().count();
1383 	if (m_parts_list -> count() != items_count) {
1384 		slot_createPartsList();
1385 	} else if (items_count <= QET_MAX_PARTS_IN_ELEMENT_EDITOR_LIST) {
1386 		m_parts_list -> blockSignals(true);
1387 		int i = 0;
1388 		QList<QGraphicsItem *> items = m_elmt_scene -> zItems();
1389 		for (int j = items.count() - 1 ; j >= 0 ; -- j) {
1390 			QGraphicsItem *qgi = items[j];
1391 			QListWidgetItem *qlwi = m_parts_list -> item(i);
1392 			if (qlwi) qlwi -> setSelected(qgi -> isSelected());
1393 			++ i;
1394 		}
1395 		m_parts_list -> blockSignals(false);
1396 	}
1397 }
1398 
1399 /**
1400 	Met a jour la selection des parties de l'element a partir de la liste des
1401 	parties
1402 */
slot_updateSelectionFromPartsList()1403 void QETElementEditor::slot_updateSelectionFromPartsList() {
1404 	m_elmt_scene  -> blockSignals(true);
1405 	m_parts_list -> blockSignals(true);
1406 	for (int i = 0 ; i < m_parts_list -> count() ; ++ i) {
1407 		QListWidgetItem *qlwi = m_parts_list -> item(i);
1408 		QGraphicsItem *qgi = qlwi -> data(42).value<QGraphicsItem *>();
1409 		if (qgi) {
1410 			qgi -> setSelected(qlwi -> isSelected());
1411 		}
1412 	}
1413 	m_parts_list -> blockSignals(false);
1414 	m_elmt_scene -> blockSignals(false);
1415 	slot_updateInformations();
1416 	slot_updateMenus();
1417 }
1418 
1419 /**
1420  * @brief QETElementEditor::readSettings
1421  * Read settings
1422  */
readSettings()1423 void QETElementEditor::readSettings()
1424 {
1425 	QSettings settings;
1426 
1427 	// dimensions et position de la fenetre
1428 	QVariant geometry = settings.value("elementeditor/geometry");
1429 	if (geometry.isValid()) restoreGeometry(geometry.toByteArray());
1430 
1431 	// etat de la fenetre (barres d'outils, docks...)
1432 	QVariant state = settings.value("elementeditor/state");
1433 	if (state.isValid()) restoreState(state.toByteArray());
1434 
1435 	// informations complementaires de l'element : valeur par defaut
1436 	m_elmt_scene -> setInformations(settings.value("elementeditor/default-informations", "").toString());
1437 }
1438 
1439 /**
1440  * @brief QETElementEditor::writeSettings
1441  * Write the settings
1442  */
writeSettings()1443 void QETElementEditor::writeSettings()
1444 {
1445 	QSettings settings;
1446 	settings.setValue("elementeditor/geometry", saveGeometry());
1447 	settings.setValue("elementeditor/state", saveState());
1448 }
1449 
1450 /**
1451 	@return les decalages horizontaux et verticaux (sous la forme d'un point) a
1452 	utiliser lors d'un copier/coller avec decalage.
1453 */
pasteOffset()1454 QPointF QETElementEditor::pasteOffset() {
1455 	QPointF paste_offset(5.0, 0.0);
1456 	return(paste_offset);
1457 }
1458 
1459 /**
1460 	Demande a l'utilisateur d'ouvrir un fichier sense etre un element.
1461 	@param parent QWidget parent du dialogue d'ouverture de fichier
1462 	@param initial_dir Repertoire a afficher initialement - si une chaine vide
1463 	est fournie, QETApp::customElementsDir() sera utilise.
1464 	@return Le chemin du fichier choisi ou une chaine vide si l'utilisateur a
1465 	clique sur le bouton "Annuler".
1466 	@see QETApp::customElementsDir()
1467 */
getOpenElementFileName(QWidget * parent,const QString & initial_dir)1468 QString QETElementEditor::getOpenElementFileName(QWidget *parent, const QString &initial_dir) {
1469 	// demande un nom de fichier a ouvrir a l'utilisateur
1470 	QString user_filename = QFileDialog::getOpenFileName(
1471 		parent,
1472 		tr("Ouvrir un fichier", "dialog title"),
1473 		initial_dir.isEmpty() ? QETApp::customElementsDir() : initial_dir,
1474 		tr(
1475 			"Éléments QElectroTech (*.elmt);;"
1476 			"Fichiers XML (*.xml);;"
1477 			"Tous les fichiers (*)",
1478 			"filetypes allowed when opening an element file"
1479 		)
1480 	);
1481 	return(user_filename);
1482 }
1483 
1484 /**
1485  * @brief QETElementEditor::fromLocation
1486  * Location of the element to edit
1487  * @param location
1488  */
fromLocation(const ElementsLocation & location)1489 void QETElementEditor::fromLocation(const ElementsLocation &location)
1490 {
1491 	if (!location.isElement())
1492 	{
1493 		QET::QetMessageBox::critical(this,
1494 									 tr("Élément inexistant.", "message box title"),
1495 									 tr("Le chemin virtuel choisi ne correspond pas à un élément.", "message box content"));
1496 		return;
1497 	}
1498 	if (!location.exist())
1499 	{
1500 		QET::QetMessageBox::critical(this,
1501 									 tr("Élément inexistant.", "message box title"),
1502 									 tr("L'élément n'existe pas.", "message box content"));
1503 		return;
1504 	}
1505 
1506 		//The file must be an xml document
1507 	QDomDocument document_xml;
1508 	QDomNode node = document_xml.importNode(location.xml(), true);
1509 	document_xml.appendChild(node);
1510 
1511 		//Load the element
1512 	m_elmt_scene -> fromXml(document_xml);
1513 	slot_createPartsList();
1514 
1515 		//location is read only
1516 	if (!location.isWritable())
1517 	{
1518 		QET::QetMessageBox::warning(this,
1519 									tr("Édition en lecture seule", "message box title"),
1520 									tr("Vous n'avez pas les privilèges nécessaires pour modifier cet élement. Il sera donc ouvert en lecture seule.", "message box content"));
1521 		setReadOnly(true);
1522 	}
1523 	else {
1524 		setReadOnly(false);
1525 	}
1526 
1527 	setLocation(location);
1528 	slot_updateMenus();
1529 }
1530 
1531 /**
1532 	Demande un fichier a l'utilisateur, l'ouvre en tant que fichier element,
1533 	met son contenu dans le presse-papiers, et appelle ElementView::PasteInArea
1534 */
pasteFromFile()1535 void QETElementEditor::pasteFromFile() {
1536 	// demande le chemin du fichier a ouvrir a l'utilisateur
1537 	QString element_file_path = getOpenElementFileName(this);
1538 	if (element_file_path.isEmpty()) return;
1539 
1540 	QString error_message;
1541 	QDomDocument xml_document;
1542 	QFile element_file(element_file_path);
1543 	// le fichier doit etre lisible
1544 	if (!element_file.open(QIODevice::ReadOnly)) {
1545 		error_message = QString(tr("Impossible d'ouvrir le fichier %1.", "message box content")).arg(element_file_path);
1546 	} else {
1547 		// le fichier doit etre un document XML
1548 		if (!xml_document.setContent(&element_file)) {
1549 			error_message = tr("Ce fichier n'est pas un document XML valide", "message box content");
1550 		}
1551 		element_file.close();
1552 	}
1553 
1554 	if (!error_message.isEmpty()) {
1555 		QET::QetMessageBox::critical(this, tr("Erreur", "toolbar title"), error_message);
1556 	}
1557 	copyAndPasteXml(xml_document);
1558 }
1559 
1560 /**
1561  * @brief QETElementEditor::pasteFromElement
1562  * Ask an element to user, copy the xml definition of the element
1563  * to the clipboard and call ElementView::PasteInArea
1564  */
pasteFromElement()1565 void QETElementEditor::pasteFromElement()
1566 {
1567 		//Ask for a location
1568 	ElementsLocation location = ElementDialog::getOpenElementLocation(this);
1569 	if (location.isNull())
1570 		return;
1571 
1572 	if (!location.isElement())
1573 	{
1574 		QET::QetMessageBox::critical(this,
1575 									 tr("Élément inexistant.", "message box title"),
1576 									 tr("Le chemin virtuel choisi ne correspond pas à un élément.", "message box content"));
1577 		return;
1578 	}
1579 	if (!location.exist())
1580 	{
1581 		QET::QetMessageBox::critical(this,
1582 									 tr("Élément inexistant.", "message box title"),
1583 									 tr("L'élément n'existe pas.", "message box content"));
1584 		return;
1585 	}
1586 
1587 		//Create an xml document from the location xml
1588 	QDomDocument document_xml;
1589 	QDomNode node = document_xml.importNode(location.xml(), true);
1590 	document_xml.appendChild(node);
1591 
1592 	copyAndPasteXml(document_xml);
1593 }
1594 
1595 /**
1596 	Met a jour l'editeur de primitive actuellement visible.
1597 	Si aucun editeur de primitive n'est visible, ce slot ne fait rien.
1598 */
updateCurrentPartEditor()1599 void QETElementEditor::updateCurrentPartEditor() {
1600 	// si aucun widget d'edition n'est affiche, on ne fait rien
1601 	if (!m_tools_dock_stack -> currentIndex()) return;
1602 
1603 	// s'il y a un widget d'edition affiche, on le met a jour
1604 	if (ElementItemEditor *current_editor = dynamic_cast<ElementItemEditor *>(m_tools_dock_stack->widget(1))) {
1605 		current_editor -> updateForm();
1606 	}
1607 }
1608