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