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 "qet.h"
19 #include "qeticons.h"
20 
21 #include <limits>
22 #include <QGraphicsSceneContextMenuEvent>
23 #include <QAction>
24 #include <QFileInfo>
25 #include <QSaveFile>
26 
27 /**
28 	Permet de convertir une chaine de caracteres ("n", "s", "e" ou "w")
29 	en orientation. Si la chaine fait plusieurs caracteres, seul le
30 	premier est pris en compte. En cas d'incoherence, Qet::North est
31 	retourne.
32 	@param s Chaine de caractere cense representer une orientation
33 	@return l'orientation designee par la chaine de caractere
34 */
orientationFromString(const QString & s)35 Qet::Orientation Qet::orientationFromString(const QString &s) {
36 	QChar c = s[0];
37 	if (c == 'e') return(Qet::East);
38 	else if (c == 's') return(Qet::South);
39 	else if (c == 'w') return (Qet::West);
40 	else return(Qet::North);
41 }
42 
43 /**
44 	@param o une orientation
45 	@return une chaine de caractere representant l'orientation
46 */
orientationToString(Qet::Orientation o)47 QString Qet::orientationToString(Qet::Orientation o) {
48 	QString ret;
49 	switch(o) {
50 		case Qet::North: ret = "n"; break;
51 		case Qet::East : ret = "e"; break;
52 		case Qet::South: ret = "s"; break;
53 		case Qet::West : ret = "w"; break;
54 	}
55 	return(ret);
56 }
57 
58 /**
59 	Indique si deux orientations de Borne sont sur le meme axe (Vertical / Horizontal).
60 	@param a La premiere orientation de Borne
61 	@param b La seconde orientation de Borne
62 	@return Un booleen a true si les deux orientations de bornes sont sur le meme axe
63 */
surLeMemeAxe(Qet::Orientation a,Qet::Orientation b)64 bool Qet::surLeMemeAxe(Qet::Orientation a, Qet::Orientation b) {
65 	if ((a == Qet::North || a == Qet::South) && (b == Qet::North || b == Qet::South)) return(true);
66 	else if ((a == Qet::East || a == Qet::West) && (b == Qet::East || b == Qet::West)) return(true);
67 	else return(false);
68 }
69 
70 /**
71  * @brief Qet::isOpposed
72  * @param a
73  * @param b
74  * @return true if a and b is opposed, else false;
75  */
isOpposed(Qet::Orientation a,Qet::Orientation b)76 bool Qet::isOpposed(Qet::Orientation a, Qet::Orientation b)
77 {
78 	bool result = false;
79 
80 	switch (a)
81 	{
82 		case Qet::North:
83 			if (b == Qet::South) result = true;
84 			break;
85 		case Qet::East:
86 			if (b == Qet::West) result = true;
87 			break;
88 		case Qet::South:
89 			if (b == Qet::North) result = true;
90 			break;
91 		case Qet::West:
92 			if (b == Qet::East) result = true;
93 			break;
94 		default:
95 			break;
96 	}
97 
98 	return result;
99 }
100 
101 /**
102  * @brief Qet::isHorizontal
103  * @param a
104  * @return true if @a is horizontal, else false.
105  */
isHorizontal(Qet::Orientation a)106 bool Qet::isHorizontal(Qet::Orientation a) {
107 	return(a == Qet::East || a == Qet::West);
108 }
109 
110 /**
111  * @brief Qet::isVertical
112  * @param a
113  * @return true if @a is vertical, else false.
114  */
isVertical(Qet::Orientation a)115 bool Qet::isVertical(Qet::Orientation a) {
116 	return(a == Qet::North || a == Qet::South);
117 }
118 
119 /**
120 	Permet de connaitre l'orientation suivante apres celle donnee en parametre.
121 	Les orientations sont generalement presentees dans l'ordre suivant : Nord,
122 	Est, Sud, Ouest.
123 	@param o une orientation
124 	@return l'orientation suivante
125 */
nextOrientation(Qet::Orientation o)126 Qet::Orientation Qet::nextOrientation(Qet::Orientation o) {
127 	if (o < 0 || o > 2) return(Qet::North);
128 	return((Qet::Orientation)(o + 1));
129 }
130 
131 /**
132 	Permet de connaitre l'orientation precedant celle donnee en parametre.
133 	Les orientations sont generalement presentees dans l'ordre suivant : Nord,
134 	Est, Sud, Ouest.
135 	@param o une orientation
136 	@return l'orientation precedente
137 */
previousOrientation(Qet::Orientation o)138 Qet::Orientation Qet::previousOrientation(Qet::Orientation o) {
139 	if (o < 0 || o > 3) return(Qet::North);
140 	if (o == Qet::North) return(Qet::West);
141 	return((Qet::Orientation)(o - 1));
142 }
143 
144 /**
145 	@param line Un segment de droite
146 	@param point Un point
147 	@return true si le point appartient au segment de droite, false sinon
148 */
lineContainsPoint(const QLineF & line,const QPointF & point)149 bool QET::lineContainsPoint(const QLineF &line, const QPointF &point) {
150 	if (point == line.p1()) return(true);
151 	QLineF point_line(line.p1(), point);
152 	if (point_line.unitVector() != line.unitVector()) return(false);
153 	return(point_line.length() <= line.length());
154 }
155 
156 /**
157 	@param point Un point donne
158 	@param line Un segment de droite donnee
159 	@param intersection si ce pointeur est different de 0, le QPointF ainsi
160 	designe contiendra les coordonnees du projete orthogonal, meme si celui-ci
161 	n'appartient pas au segment de droite
162 	@return true si le projete orthogonal du point sur la droite appartient au
163 	segment de droite.
164 */
orthogonalProjection(const QPointF & point,const QLineF & line,QPointF * intersection)165 bool QET::orthogonalProjection(const QPointF &point, const QLineF &line, QPointF *intersection) {
166 	// recupere le vecteur normal de `line'
167 	QLineF line_normal_vector(line.normalVector());
168 	QPointF normal_vector(line_normal_vector.dx(), line_normal_vector.dy());
169 
170 	// cree une droite perpendiculaire a `line' passant par `point'
171 	QLineF perpendicular_line(point, point + normal_vector);
172 
173 	// determine le point d'intersection des deux droites = le projete orthogonal
174 	QPointF intersection_point;
175 	QLineF::IntersectType it = line.intersect(perpendicular_line, &intersection_point);
176 
177 	// ne devrait pas arriver (mais bon...)
178 	if (it == QLineF::NoIntersection) return(false);
179 
180 	// fournit le point d'intersection a l'appelant si necessaire
181 	if (intersection) {
182 		*intersection = intersection_point;
183 	}
184 
185 	// determine si le point d'intersection appartient au segment de droite
186 	if (QET::lineContainsPoint(line, intersection_point)) {
187 		return(true);
188 	}
189 	return(false);
190 }
191 
192 /**
193 	Permet de savoir si l'attribut nom_attribut d'un element XML e est bien un
194 	entier. Si oui, sa valeur est copiee dans entier.
195 	@param e Element XML
196 	@param nom_attribut Nom de l'attribut a analyser
197 	@param entier Pointeur facultatif vers un entier
198 	@return true si l'attribut est bien un entier, false sinon
199 */
attributeIsAnInteger(const QDomElement & e,const QString & nom_attribut,int * entier)200 bool QET::attributeIsAnInteger(const QDomElement &e, const QString& nom_attribut, int *entier) {
201 	// verifie la presence de l'attribut
202 	if (!e.hasAttribute(nom_attribut)) return(false);
203 	// verifie la validite de l'attribut
204 	bool ok;
205 	int tmp = e.attribute(nom_attribut).toInt(&ok);
206 	if (!ok) return(false);
207 	if (entier != nullptr) *entier = tmp;
208 	return(true);
209 }
210 
211 /**
212 	Permet de savoir si l'attribut nom_attribut d'un element XML e est bien un
213 	reel. Si oui, sa valeur est copiee dans reel.
214 	@param e Element XML
215 	@param nom_attribut Nom de l'attribut a analyser
216 	@param reel Pointeur facultatif vers un double
217 	@return true si l'attribut est bien un reel, false sinon
218 */
attributeIsAReal(const QDomElement & e,const QString & nom_attribut,qreal * reel)219 bool QET::attributeIsAReal(const QDomElement &e, const QString& nom_attribut, qreal *reel) {
220 	// verifie la presence de l'attribut
221 	if (!e.hasAttribute(nom_attribut)) return(false);
222 	// verifie la validite de l'attribut
223 	bool ok;
224 	qreal tmp = e.attribute(nom_attribut).toDouble(&ok);
225 	if (!ok) return(false);
226 	if (reel != nullptr) *reel = tmp;
227 	return(true);
228 }
229 
230 /**
231 	Permet de composer rapidement la proposition "x elements et y conducteurs"
232 	ou encore "x elements, y conducteurs et z champs de texte".
233 	@param elements_count nombre d'elements
234 	@param conductors_count nombre de conducteurs
235 	@param texts_count nombre de champs de texte
236 	@param images_count nombre d'images
237 	@return la proposition decrivant le nombre d'elements, de conducteurs et de
238 	textes
239 */
ElementsAndConductorsSentence(int elements_count,int conductors_count,int texts_count,int images_count,int shapes_count,int element_text_count)240 QString QET::ElementsAndConductorsSentence(int elements_count, int conductors_count, int texts_count, int images_count, int shapes_count, int element_text_count) {
241 	QString text;
242 	if (elements_count) {
243 		text += QObject::tr(
244 			"%n élément(s)",
245 			"part of a sentence listing the content of a diagram",
246 			elements_count
247 		);
248 	}
249 
250 	if (conductors_count) {
251 		if (!text.isEmpty()) text += ", ";
252 		text += QObject::tr(
253 			"%n conducteur(s)",
254 			"part of a sentence listing the content of a diagram",
255 			conductors_count
256 		);
257 	}
258 
259 	if (texts_count) {
260 		if (!text.isEmpty()) text += ", ";
261 		text += QObject::tr(
262 			"%n champ(s) de texte",
263 			"part of a sentence listing the content of a diagram",
264 			texts_count
265 		);
266 	}
267 
268 	if (images_count) {
269 		if (!text.isEmpty()) text += ", ";
270 		text += QObject::tr(
271 			"%n image(s)",
272 			"part of a sentence listing the content of a diagram",
273 			images_count
274 		);
275 	}
276 
277 	if (shapes_count) {
278 		if (!text.isEmpty()) text += ", ";
279 		text += QObject::tr(
280 			"%n forme(s)",
281 			"part of a sentence listing the content of a diagram",
282 			shapes_count
283 		);
284 	}
285 
286 	if (element_text_count) {
287 		if (!text.isEmpty()) text += ", ";
288 		text += QObject::tr(
289 					"%n texte(s) d'élément",
290 					"part of a sentence listing the content of a diagram",
291 					element_text_count);
292 	}
293 
294 	return(text);
295 }
296 
297 /**
298 	@return the list of \a tag_name elements directly under the \a e XML element.
299 */
findInDomElement(const QDomElement & e,const QString & tag_name)300 QList<QDomElement> QET::findInDomElement(const QDomElement &e, const QString &tag_name) {
301 	QList<QDomElement> return_list;
302 	for (QDomNode node = e.firstChild() ; !node.isNull() ; node = node.nextSibling()) {
303 		if (!node.isElement()) continue;
304 		QDomElement element = node.toElement();
305 		if (element.isNull() || element.tagName() != tag_name) continue;
306 		return_list << element;
307 	}
308 	return(return_list);
309 }
310 
311 /**
312 	Etant donne un element XML e, renvoie la liste de tous les elements
313 	children imbriques dans les elements parent, eux-memes enfants de l'elememt e
314 	@param e Element XML a explorer
315 	@param parent tag XML intermediaire
316 	@param children tag XML a rechercher
317 	@return La liste des elements XML children
318 */
findInDomElement(const QDomElement & e,const QString & parent,const QString & children)319 QList<QDomElement> QET::findInDomElement(const QDomElement &e, const QString &parent, const QString &children) {
320 	QList<QDomElement> return_list;
321 
322 	// parcours des elements parents
323 	for (QDomNode enfant = e.firstChild() ; !enfant.isNull() ; enfant = enfant.nextSibling()) {
324 		// on s'interesse a l'element XML "parent"
325 		QDomElement parents = enfant.toElement();
326 		if (parents.isNull() || parents.tagName() != parent) continue;
327 		// parcours des enfants de l'element XML "parent"
328 		for (QDomNode node_children = parents.firstChild() ; !node_children.isNull() ; node_children = node_children.nextSibling()) {
329 			// on s'interesse a l'element XML "children"
330 			QDomElement n_children = node_children.toElement();
331 			if (!n_children.isNull() && n_children.tagName() == children) return_list.append(n_children);
332 		}
333 	}
334 	return(return_list);
335 }
336 
337 /// @return le texte de la licence de QElectroTech (GNU/GPL)
license()338 QString QET::license() {
339 	// Recuperation du texte de la GNU/GPL dans un fichier integre a l'application
340 	QFile *file_license = new QFile(":/LICENSE");
341 	QString txt_license;
342 	// verifie que le fichier existe
343 	if (!file_license -> exists()) {
344 		txt_license = QString(QObject::tr("Le fichier texte contenant la licence GNU/GPL est introuvable - bon bah de toute façon, vous la connaissez par coeur non ?"));
345 	} else {
346 		// ouvre le fichier en mode texte et en lecture seule
347 		if (!file_license -> open(QIODevice::ReadOnly | QIODevice::Text)) {
348 			txt_license = QString(QObject::tr("Le fichier texte contenant la licence GNU/GPL existe mais n'a pas pu être ouvert - bon bah de toute façon, vous la connaissez par coeur non ?"));
349 		} else {
350 			// charge le contenu du fichier dans une QString
351 			QTextStream in(file_license);
352 			txt_license = QString("");
353 			while (!in.atEnd()) txt_license += in.readLine()+"\n";
354 			// ferme le fichier
355 			file_license -> close();
356 		}
357 	}
358 	return(txt_license);
359 };
360 
361 
362 /**
363 	@return la liste des caracteres interdits dans les noms de fichiers sous
364 	Windows
365 */
forbiddenCharacters()366 QList<QChar> QET::forbiddenCharacters() {
367 	return(QList<QChar>() << '\\' << '/' << ':' << '*' << '?' << '"' << '<' << '>' << '|');
368 }
369 
370 /**
371 	Cette fonction transforme une chaine de caracteres (typiquement : un nom de
372 	schema, de projet, d'element) en un nom de fichier potable.
373 	Par nom de fichier potable, on entend un nom :
374 	  * ne comprenant pas de caracteres interdits sous Windows
375 	  * ne comprenant pas d'espace
376 	@param name Chaine de caractere a transformer en nom de fichier potable
377 	@todo virer les caracteres accentues ?
378 */
stringToFileName(const QString & name)379 QString QET::stringToFileName(const QString &name) {
380 	QString file_name(name.toLower());
381 
382 	// remplace les caracteres interdits par des tirets
383 	foreach(QChar c, QET::forbiddenCharacters()) {
384 		file_name.replace(c, '-');
385 	}
386 
387 	// remplace les espaces par des underscores
388 	file_name.replace(' ', '_');
389 
390 	return(file_name);
391 }
392 
393 /**
394 	@param string une chaine de caracteres
395 	@return la meme chaine de caracteres, mais avec les espaces et backslashes
396 	echappes
397 */
escapeSpaces(const QString & string)398 QString QET::escapeSpaces(const QString &string) {
399 	return(QString(string).replace('\\', "\\\\").replace(' ', "\\ "));
400 }
401 
402 /**
403 	@param string une chaine de caracteres
404 	@return la meme chaine de caracteres, mais avec les espaces et backslashes
405 	non echappes
406 */
unescapeSpaces(const QString & string)407 QString QET::unescapeSpaces(const QString &string) {
408 	return(QString(string).replace("\\\\", "\\").replace("\\ ", " "));
409 }
410 
411 /**
412 	Assemble une liste de chaines en une seule. Un espace separe chaque chaine.
413 	Les espaces et backslashes des chaines sont echappes.
414 	@param string_list une liste de chaine
415 	@return l'assemblage des chaines
416 */
joinWithSpaces(const QStringList & string_list)417 QString QET::joinWithSpaces(const QStringList &string_list) {
418 	QString returned_string;
419 
420 	for (int i = 0 ; i < string_list.count() ; ++ i) {
421 		returned_string += QET::escapeSpaces(string_list.at(i));
422 		if (i != string_list.count() - 1) returned_string += " ";
423 	}
424 
425 	return(returned_string);
426 }
427 
428 /**
429 	@param string Une chaine de caracteres contenant des sous-chaines a
430 	extraire separees par des espaces non echappes. Les espaces des sous-chaines
431 	sont echappes.
432 	@return La liste des sous-chaines, sans echappement.
433 */
splitWithSpaces(const QString & string)434 QStringList QET::splitWithSpaces(const QString &string) {
435 	// les chaines sont separees par des espaces non echappes = avec un nombre nul ou pair de backslashes devant
436 	QStringList escaped_strings = string.split(QRegExp("[^\\]?(?:\\\\)* "), QString::SkipEmptyParts);
437 
438 	QStringList returned_list;
439 	foreach(QString escaped_string, escaped_strings) {
440 		returned_list << QET::unescapeSpaces(escaped_string);
441 	}
442 	return(returned_list);
443 }
444 
445 /**
446 	@param end_type un type d'extremite
447 	@return une chaine representant le type d'extremite
448 */
endTypeToString(const Qet::EndType & end_type)449 QString Qet::endTypeToString(const Qet::EndType &end_type) {
450 	switch(end_type) {
451 		case Qet::Simple:   return("simple");
452 		case Qet::Triangle: return("triangle");
453 		case Qet::Circle:   return("circle");
454 		case Qet::Diamond:  return("diamond");
455 		case Qet::None:
456 		default:
457 			return("none");
458 	}
459 }
460 
461 /**
462 	@param string une chaine representant un type d'extremite
463 	@return le type d'extremite correspondant ; si la chaine est invalide,
464 	QET::None est retourne.
465 */
endTypeFromString(const QString & string)466 Qet::EndType Qet::endTypeFromString(const QString &string) {
467 	if (string == "simple")        return(Qet::Simple);
468 	else if (string == "triangle") return(Qet::Triangle);
469 	else if (string == "circle")   return(Qet::Circle);
470 	else if (string == "diamond")  return(Qet::Diamond);
471 	else return(Qet::None);
472 }
473 
474 /**
475 	@param diagram_area un type de zone de schema
476 	@return une chaine representant le type de zone de schema
477 */
diagramAreaToString(const QET::DiagramArea & diagram_area)478 QString QET::diagramAreaToString(const QET::DiagramArea &diagram_area) {
479 	if (diagram_area == ElementsArea) return("elements");
480 	else return("border");
481 }
482 
483 /**
484 	@param string une chaine representant un type de zone de schema
485 	@return le type de zone de schema correspondant ; si la chaine est invalide,
486 	QET::ElementsArea est retourne.
487 */
diagramAreaFromString(const QString & string)488 QET::DiagramArea QET::diagramAreaFromString(const QString &string) {
489 	if (!string.compare("border", Qt::CaseInsensitive)) return(QET::BorderArea);
490 	else return(QET::ElementsArea);
491 }
492 
493 /**
494 	Round \a x to the nearest multiple of the invert of \a epsilon.
495 	For instance, epsilon = 10 will round to 1/10 = 0.1
496 */
round(qreal x,qreal epsilon)497 qreal QET::round(qreal x, qreal epsilon) {
498 	return(int(x * epsilon) / epsilon);
499 }
500 
501 /**
502 	@param angle Un angle quelconque
503 	@return l'angle passe en parametre, mais ramene entre -360.0 + 360.0 degres
504 */
correctAngle(const qreal & angle)505 qreal QET::correctAngle(const qreal &angle) {
506 	// ramene l'angle demande entre -360.0 et +360.0 degres
507 	qreal corrected_angle = angle;
508 	while (corrected_angle <= -360.0) corrected_angle += 360.0;
509 	while (corrected_angle >=  360.0) corrected_angle -= 360.0;
510 	return(corrected_angle);
511 }
512 
513 /**
514 	@param first  Un premier chemin vers un fichier
515 	@param second Un second chemin vers un fichier
516 	@return true si les deux chemins existent existent et sont identiques
517 	lorsqu'ils sont exprimes sous forme canonique
518 */
compareCanonicalFilePaths(const QString & first,const QString & second)519 bool QET::compareCanonicalFilePaths(const QString &first, const QString &second) {
520 	QString first_canonical_path = QFileInfo(first).canonicalFilePath();
521 	if (first_canonical_path.isEmpty()) return(false);
522 
523 	QString second_canonical_path = QFileInfo(second).canonicalFilePath();
524 	if (second_canonical_path.isEmpty()) return(false);
525 
526 #ifdef Q_OS_WIN
527 	// sous Windows, on ramene les chemins en minuscules
528 	first_canonical_path  = first_canonical_path.toLower();
529 	second_canonical_path = second_canonical_path.toLower();
530 #endif
531 
532 	return(first_canonical_path == second_canonical_path);
533 }
534 
535 /**
536 	Export an XML document to an UTF-8 text file indented with 4 spaces, with LF
537 	end of lines and no BOM.
538 	@param xml_doc An XML document to be exported
539 	@param filepath Path to the file to be written
540 	@param error_message If non-zero, will contain an error message explaining
541 	what happened when this function returns false.
542 	@return false if an error occurred, true otherwise
543 */
writeXmlFile(QDomDocument & xml_doc,const QString & filepath,QString * error_message)544 bool QET::writeXmlFile(QDomDocument &xml_doc, const QString &filepath, QString *error_message)
545 {
546     QSaveFile file(filepath);
547 
548         // Note: we do not set QIODevice::Text to avoid generating CRLF end of lines
549     bool file_opening = file.open(QIODevice::WriteOnly);
550     if (!file_opening)
551     {
552         if (error_message)
553         {
554             *error_message = QString(QObject::tr("Impossible d'ouvrir le fichier %1 en écriture, erreur %2 rencontrée.",
555                                                  "error message when attempting to write an XML file")).arg(filepath).arg(file.error());
556         }
557         return(false);
558     }
559 
560     QTextStream out(&file);
561     out.setCodec("UTF-8");
562     out.setGenerateByteOrderMark(false);
563     out << xml_doc.toString(4);
564     if  (!file.commit())
565     {
566         if (error_message) {
567             *error_message = QString(QObject::tr("Une erreur est survenue lors de l'écriture du fichier %1, erreur %2 rencontrée.",
568                                                  "error message when attempting to write an XML file")).arg(filepath).arg(file.error());
569         }
570 
571         return false;
572     }
573 
574     return(true);
575 }
576 
577 /**
578  * @brief QET::eachStrIsEqual
579  * @param qsl list of string to compare
580  * @return true if every string is identical, else false;
581  * The list must not be empty
582  * If the list can be empty, call isEmpty() before calling this function
583  */
eachStrIsEqual(const QStringList & qsl)584 bool QET::eachStrIsEqual(const QStringList &qsl) {
585 	if (qsl.size() == 1) return true;
586 	foreach (const QString t, qsl) {
587 		if (qsl.at(0) != t) return false;
588 	}
589 	return true;
590 }
591 
592 /**
593  * @brief QET::qetCollectionToString
594  * @param c QetCollection value to convert
595  * @return The QetCollection enum value converted to a QString
596  */
qetCollectionToString(const QET::QetCollection & c)597 QString QET::qetCollectionToString(const QET::QetCollection &c)
598 {
599 	switch (c)
600 	{
601 		case Common :
602 			return "common";
603 		case Custom :
604 			return "custom";
605 		case Embedded :
606 			return "embedded";
607 		default:
608 			return "common";
609 	}
610 }
611 
612 /**
613  * @brief QET::qetCollectionFromString
614  * @param str string to convert
615  * @return The corresponding QetCollection value from a string.
616  * If the string don't match anything, we return the failsafe value QetCollection::Common
617  */
qetCollectionFromString(const QString & str)618 QET::QetCollection QET::qetCollectionFromString(const QString &str)
619 {
620 	if (str == "common")
621 		return QetCollection::Common;
622 	else if (str == "custom")
623 		return QetCollection::Custom;
624 	else if (str == "embedded")
625 		return QetCollection::Embedded;
626 	else
627 		return QetCollection::Common;
628 }
629 
630 /**
631  * @brief QET::depthActionGroup
632  * @param parent
633  * @return an action group which contain 4 actions (forward, raise, lower, backward)
634  * already made with icon, shortcut and data (see QET::DepthOption)
635  */
depthActionGroup(QObject * parent)636 QActionGroup *QET::depthActionGroup(QObject *parent)
637 {
638 	QActionGroup *action_group = new QActionGroup(parent);
639 
640 	QAction *edit_forward  = new QAction(QET::Icons::BringForward, QObject::tr("Amener au premier plan"), action_group);
641 	QAction *edit_raise    = new QAction(QET::Icons::Raise,        QObject::tr("Rapprocher"),             action_group);
642 	QAction *edit_lower    = new QAction(QET::Icons::Lower,        QObject::tr("Éloigner"),               action_group);
643 	QAction *edit_backward = new QAction(QET::Icons::SendBackward, QObject::tr("Envoyer au fond"),        action_group);
644 
645 	edit_forward ->setStatusTip(QObject::tr("Ramène la ou les sélections au premier plan"));
646 	edit_raise   ->setStatusTip(QObject::tr("Rapproche la ou les sélections"));
647 	edit_lower   ->setStatusTip(QObject::tr("Éloigne la ou les sélections"));
648 	edit_backward->setStatusTip(QObject::tr("Envoie en arrière plan la ou les sélections"));
649 
650 	edit_raise   ->setShortcut(QKeySequence(QObject::tr("Ctrl+Shift+Up")));
651 	edit_lower   ->setShortcut(QKeySequence(QObject::tr("Ctrl+Shift+Down")));
652 	edit_backward->setShortcut(QKeySequence(QObject::tr("Ctrl+Shift+End")));
653 	edit_forward ->setShortcut(QKeySequence(QObject::tr("Ctrl+Shift+Home")));
654 
655 	edit_forward ->setData(QET::BringForward);
656 	edit_raise   ->setData(QET::Raise);
657 	edit_lower   ->setData(QET::Lower);
658 	edit_backward->setData(QET::SendBackward);
659 
660 	return action_group;
661 }
662 
writeToFile(QDomDocument & xml_doc,QFile * file,QString * error_message)663 bool QET::writeToFile(QDomDocument &xml_doc, QFile *file, QString *error_message)
664 {
665 	bool opened_here = file->isOpen() ? false : true;
666 
667 	if (!file->isOpen())
668 	{
669 		bool open_ = file->open(QIODevice::WriteOnly);
670 		if (!open_)
671 		{
672 			if (error_message)
673 			{
674 				QFileInfo info_(*file);
675 				*error_message = QString(
676 							QObject::tr("Impossible d'ouvrir le fichier %1 en écriture, erreur %2 rencontrée.",
677 										"error message when attempting to write an XML file")
678 				).arg(info_.absoluteFilePath()).arg(file->error());
679 			}
680 			return false;
681 		}
682 	}
683 
684 	QTextStream out(file);
685 	out.seek(0);
686 	out.setCodec("UTF-8");
687 	out.setGenerateByteOrderMark(false);
688 	out << xml_doc.toString(4);
689 	if (opened_here) {
690 		file->close();
691 	}
692 
693 	return(true);
694 }
695