MtgApp.js

/*
 * MathGraph32 Javascript : Software for animating online dynamic mathematics figures
 * https://www.mathgraph32.org/
 * @Author Yves Biton (yves.biton@sesamath.net)
 * @License: GNU AGPLv3 https://www.gnu.org/licenses/agpl-3.0.html
 */
import { empty, ge, setAttrs, setStyle } from './kernel/dom'
/**
 * Classe principale de l'application mathGraph32 permettant de créer ou modifier une figure
 * @constructor
 * @param {SVGElement} svg Le svg dans lequel l'application travaille (il doit avoir un id)
 * @param {MtgOptions} mtgOptions Les informations sur l'initialisation de l'application
 */
import MtgApp from './MtgAppBase'
import CListeObjets from './objets/CListeObjets'
import NatObj from './types/NatObjAdd'
import NatCal from './types/NatCal'
import Nat from './types/Nat'
import {
  addZoomListener,
  base64Decode,
  base64Encode,
  ce,
  cens,
  getStr,
  mtgFileExtension,
  preventDefault,
  uniteAngleRadian
} from './kernel/kernel'
import DataInputStream from './entreesSorties/DataInputStream'
import DataOutputStream from './entreesSorties/DataOutputStream'
import CMathGraphDoc from './objets/CMathGraphDoc'
import CValeur from './objets/CValeur'
import CCalcul from './objets/CCalcul'
import CConstante from './objets/CConstante'
import CImplementationProto from './objets/CImplementationProto'
import CImage from './objets/CImage'
import CSegment from './objets/CSegment'
import CPolygone from './objets/CPolygone'
import CCommentaire from './objets/CCommentaire'
import CNoeudPointeurSurPoint from './objets/CNoeudPointeurSurPoint'
import CAffLiePt from './objets/CAffLiePt'
import StyleEncadrement from './types/StyleEncadrement'
import Dimf from './types/Dimf'
import constantes from './kernel/constantes'
import { chaineNatGraphPourProto, mousePosition, natObjGraphPourProto } from './kernel/kernelAdd'
import ButtonTool from './interface/ButtonTool'
import Color from './types/Color'
import $ from 'jquery'
import AvertDlg from './dialogs/AvertDlg'
import ConfirmDlg from './dialogs/ConfirmDlg'
import CValeurAngle from './objets/CValeurAngle'
import addQueue from 'src/kernel/addQueue'

import '../css/mtgApp.css'

// on exporte MtgAppBase en ajoutant plein de méthodes à son prototype
export default MtgApp

/**
 * Fonction initialisant la variable tipDisplayed de tous les boutons à false;
 */
MtgApp.prototype.annuleTipsButtons = function annuleTipsButtons () {
  for (let i = 0; i < this.buttons.length; i++) this.buttons[i].tipDisplayed = false
}

MtgApp.prototype.updateActiveTools = function updateActiveTools () {
  for (let i = 0; i < this.expandableBars.length; i++) {
    this.expandableBars[i].updateActiveTools()
  }
}

MtgApp.prototype.updateActiveIcons = function updateActiveIcons () {
  let row = 0
  for (let i = 0; i < this.expandableBars.length; i++) {
    if (this.expandableBars[i].tools.length !== 0) this.expandableBars[i].updateActiveIcon(row++)
    else this.expandableBars[i].desactive()
  }
}

MtgApp.prototype.updateToolsToolBar = function updateToolsToolBar () {
  // Il faut d'abord cacher toutes les icônes de tous les outils
  for (let i = 0; i < this.expandableBars.length; i++) this.expandableBars[i].hideAllIcons()
  // Puis on remet à jour les outils actifs et leurs icônes
  this.updateActiveTools()
  this.updateActiveIcons()
}

MtgApp.prototype.unFoldExpandableBars = function unFoldExpandableBars () {
  let row = 0
  for (let i = 0; i < this.expandableBars.length; i++) {
    const b = this.expandableBars[i]
    if (b.tools.length !== 0) {
      if (b.expanded) b.updateActiveIcon(row)
      row++
    }
  }
}

MtgApp.prototype.hasBarExpanded = function hasBarExpanded () {
  let res = false
  for (let i = 0; i < this.expandableBars.length; i++) {
    res = res || this.expandableBars[i].expanded
    if (res) break
  }
  return res
}
/**
 *
 * @returns {ExpandableBar|null}
 */
MtgApp.prototype.getExpandedBar = function getExpandedBar () {
  for (let i = 0; i < this.expandableBars.length; i++) {
    if (this.expandableBars[i].expanded) return this.expandableBars[i]
  }
  return null
}

/**
 * Fonction rajoutant à la figure actuelle des graduations en utilisant la construction nommée nomProto
 * @param {string} nomProto
 */
MtgApp.prototype.creeGrad = function creeGrad (nomProto) {
  const list = this.doc.listePr
  const repere = list.premierParNatCal(NatCal.NRepere)
  const nbgradx = new CCalcul(list, null, false, 'nbgradx', '20', new CConstante(list, 20))
  list.add(nbgradx)
  const nbgrady = new CCalcul(list, null, false, 'nbgrady', '20', new CConstante(list, 20))
  list.add(nbgrady)
  const proto = this.docCons.getPrototype(nomProto)
  const listeSources = new CListeObjets()
  listeSources.add(nbgradx)
  listeSources.add(nbgrady)
  proto.get(0).elementAssocie = repere // Pointe sur le repère
  proto.get(1).elementAssocie = nbgradx
  proto.get(2).elementAssocie = nbgrady
  const impProto = new CImplementationProto(list, proto)
  impProto.implemente(this.dimf, proto)
}

/**
 * Fonction préparant la liste principale pour que la figure possède un segment longueur unité
 * @param {KernelUniteAngle} uniteAngle L'unité d'angle de la figure
 */
MtgApp.prototype.initAvecLongueurUnite = function initAvecLongueurUnite (uniteAngle) {
  const list = this.doc.listePr
  list.uniteAngle = uniteAngle
  list.ajouteConstantePi()
  list.ajouteLongueurUnite(this, this.dimf)
}
/**
 * Fonction préparant la liste principale pour que la figure ne possède pas un segment longueur unité
 * @param {KernelUniteAngle} uniteAngle L'unité d'angle de la figure
 */
MtgApp.prototype.initSansLongueurUnite = function initSansLongueurUnite (uniteAngle) {
  const list = this.doc.listePr
  list.uniteAngle = uniteAngle
  list.ajouteConstantePi()
  list.pointeurLongueurUnite = null
}

/**
 * Fonction préparant la liste principale pour que la figure possède un repère orthonormal avec graduations
 * @param {KernelUniteAngle} uniteAngle L'unité d'angle de la figure
 * @param {boolean} quadhor true si on veut que le repère soit quadrillé horizontalement
 * @param {boolean} quadver true si on veut que le repère soit quadrillé verticalement
 * @param {boolean} grid true si on veut que le repère est des pointilés aux points de coordonnées entières
 * @param {boolean} withvect si true, on rajoute des vecteurs sur les axes
 * @param {string} typegrad String valant "no" pour pas de graduations, "trig" pour une graduation spéciale trigo, "simple"
 */
MtgApp.prototype.initAvecRepereOrthonormal = function initAvecRepereOrthonormal (uniteAngle, quadhor, quadver, grid, withvect, typegrad) {
  const list = this.doc.listePr
  list.uniteAngle = uniteAngle
  list.ajouteConstantePi()
  list.ajouteRepereOrthonormal(this.dimf, quadhor, quadver, grid, 'O', 'I', 'J', withvect ? 'i' : '',
    withvect ? 'j' : '', new CValeur(list, 0), new CValeur(list, 0), new CValeur(list, 1), new CValeur(list, 1),
    typegrad !== 'no')
  if (typegrad !== 'no') { this.creeGrad(typegrad === 'trig' ? 'GraduationReperePourTrigo' : 'GraduationAxesRepere') }
  if (typegrad === 'trig') list.uniteAngle = uniteAngleRadian
}

/**
 * Fonction préparant la liste principale pour que la figure possède un repère orthogonal avec graduations
 * @param {KernelUniteAngle} uniteAngle L'unité d'angle de la figure
 * @param {boolean} quadhor true si on veut que le repère soit quadrillé horizontalement
 * @param {boolean} quadver true si on veut que le repère soit quadrillé verticalement
 * @param {boolean} grid true si on veut que le repère est des pointilés aux points de coordonnées entières
 * @param {boolean} withvect si true, on rajoute des vecteurs sur les axes
 * @param {string} typegrad String valant "no" pour pas de graduations, "trig" pour une graduation spéciale trigo, "simple"
 */
MtgApp.prototype.initAvecRepereOrthogonal = function initAvecRepereOrthogonal (uniteAngle, quadhor, quadver, grid, withvect, typegrad) {
  const list = this.doc.listePr
  list.uniteAngle = uniteAngle
  list.ajouteConstantePi()
  list.ajouteRepereOrthonormal(this.dimf, quadhor, quadver, grid, 'O', 'I', 'J', withvect ? 'i' : '',
    withvect ? 'j' : '', new CValeur(list, 0), new CValeur(list, 0), new CValeur(list, 1), new CValeur(list, 1),
    typegrad !== 'no')
  if (typegrad !== 'no') { this.creeGrad(typegrad === 'trig' ? 'GraduationReperePourTrigo' : 'GraduationAxesRepere') }
  if (typegrad === 'trig') list.uniteAngle = uniteAngleRadian
}
/// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
//          Ce qui suit concerne les exercices de construction en ligne
/// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
 * Fonction renvoyant true si la figure chargée est une figure destinée à faire un exercice de construction.
 * Pour cela la figure doit contenir une macro d'apparition d'objets d'intitulé #Solution#.
 * Les objets que cette macro fait apparaître son alors considérés comme les objets que l'élève doit construire.
 * @returns {boolean}
 */
MtgApp.prototype.isExercise = function isExercise () {
  return this.macroPourConst !== null
}

/**
 * Fonction cherchant s'il existe une macro d'apparition d'objets d'intitulé #Solution# ou #SolutionIso# et renvoyant
 * un pointeur sur celle-ci si elle existe et sinon null.
 * Dans le cas #SolutionIso#, la macro d'apparition doit avoir un seul objet qui soit un polygone
 * @returns {CMacroApparition|null}
 */
MtgApp.prototype.getMacroPourConst = function getMacroPourConst () {
  const list = this.listePr
  for (const el of list.col) {
    if (el.className === 'CMacroApparition') {
      if (['#Solution#', '#SolutionIso#'].indexOf(el.intitule) !== -1) {
        // Dans les deux cas d'un exercice de construction isométrique, si un des objets à construire est un polygone, tous ses sommets doivent être nommés
        if (el.intitule === '#Solution#') return el
        else {
          if (el.intitule === '#SolutionIso#') {
            // Pour être valide, la macro de doit gérer que des objets de type Polygone, cercle ou segment
            let valide = true
            const listeAssociee = el.listeAssociee
            for (let j = 0; (j < listeAssociee.longueur()) && valide; j++) {
              const el2 = listeAssociee.get(j)
              // Si demande de construire un polygone, tous ses sommets doivent être nommés dans la figure initiale
              if (el2.estDeNature(NatObj.NPolygone)) {
                const nbp = el2.nombrePoints
                for (let k = 0; (k < nbp) && valide; k++) {
                  valide = valide && el2.colPoints[k].pointeurSurPoint.nom !== ''
                }
              } else {
                // Si on demande de créer un segment, les extrémités doivent être nommées
                if (el2.estDeNature(NatObj.NSegment)) {
                  valide = valide && (el2.point1.nom !== '') && (el2.point2.nom !== '')
                } else {
                  // Si demande de créer un cercle, il faut qu'il soit de type CCercleOA ou CCercleOR ou CCercleOAB
                  if (el2.estCercleParCentre()) {
                    valide = valide && (el2.o.nom !== '')
                  } else valide = false // Pas d'autres types d'objets à construire que polygône, segment ou cercle
                }
              }
              valide = valide &&
                listeAssociee.get(j).estDeNature(Nat.or(NatObj.NPolygone, NatObj.NCercle, NatObj.NSegment))
            }
            if (valide) return el
          }
        }
      }
    }
  }
  return null
}

/**
 * Fonction appelée pour le cas d'un exercice de construction et renvoyant une liste formée de tous les
 * objets (numériques ou non) que l'élève a le droit d'utiliser pour résoudre l'exercice.
 * Pour un exercice de construction, il contient une macro d'apparition d'objets.
 * Pour tous les calculs nommés (saut les constantes comme pi) si le commentaire de cette macro contient
 * une chaine de caractères du type {nomducalcul} alors le calcul ou la fonction nommé nomducalcul peut
 * être utilisée par l'élève
 * @returns {CListeObjets}
 */
MtgApp.prototype.listePourConstruction = function listePourConstruction () {
  if (!this.estExercice) return this.listePr
  const list = this.listePr
  const nbObj = this.nbObjInit // Le nombre initial d'objets de la figure de l'exercice
  // var li = new CListeObjets(list.uniteAngle, list.pointeurLongueurUnite)
  const li = new CListeObjets(list.uniteAngle, '', list.decimalDot) // Corrigé version 6.5.2
  li.associeA(this.doc)
  for (let i = 0; i < list.longueur(); i++) {
    const el = list.get(i)
    if (!el.estElementIntermediaire()) {
      if (i < nbObj) {
        if (el.estDeNature(NatObj.NTtObj) && !el.masque) {
          li.add(el)
        } else {
          if (el.estDeNatureCalcul(NatCal.NCalculReelConstant)) {
            li.add(el) // Pour la constante pi
          } else if (el.estDeNatureCalcul(NatCal.NTtCalcNommeSaufConst)) {
            if (this.calculOKForConst(el)) li.add(el)
          }
        }
      } else {
        li.add(el)
      }
    }
  }
  return li
}

/**
 * Fonction appelée dans le cas d'un exercice de construction/
 * Renvoie true si calc fait partie des calcul que l'élève a le droit d'utiliser
 * @param {CCalculAncetre} calc
 * @returns {boolean}
 */
MtgApp.prototype.calculOKForConst = function calculOKForConst (calc) {
  const mac = this.macroPourConst
  return mac.commentaireMacro.indexOf('{' + calc.nomCalcul + '}') !== -1
}
/**
 * Retourne la liste des index
 * @returns {number[]}
 */
MtgApp.prototype.arrayObjAConstruire = function arrayObjAConstruire () {
  const mac = this.macroPourConst
  const ar = []
  const list = mac.listeAssociee
  // si el.index est l'index de el dans list.col, alors c'est plus rapide de faire
  // return Array.from(list.col.keys()) // retourne un vrai Array
  // ou, si un itérateur suffit
  // return list.col.keys() // retourne un Iterator contenant des nombres
  for (const el of list.col) {
    ar.push(el.index) // el.index contient l'indice de el dans la liste qui le contient
  }
  return ar
}

/**
 * Fonction déplaçant un point libre ou un point lié un tout petit peude façon aléatoire
 * @param {CElementBase} pt Le point libre ou lié à déplacer
 */
MtgApp.prototype.epsilon = function epsilon (pt) {
  let a, b, s1, s2, x1, y1, absmin, absmax, nabs
  if (pt.estDeNature(NatObj.NPointBase)) {
    a = (Math.floor(Math.random() * 5) + 1) * 0.001
    b = (Math.floor(Math.random() * 5) + 1) * 0.001
    s1 = Math.floor(Math.random() * 2)
    s2 = Math.floor(Math.random() * 2)
    x1 = s1 === 0 ? a : -a
    y1 = s2 === 0 ? b : -b
    pt.placeEn(pt.x + x1, pt.y + y1)
  } else { // Cas d'un point lié
    absmin = pt.abscisseMinimale()
    absmax = pt.abscisseMaximale()
    // on décale l'abscisse au maximum de 5% vers la haut ou vers la bas au
    // hasard
    a = Math.floor(Math.random() * 5) + 1
    s1 = Math.floor(Math.random() * 2)
    if (s1 === 0) a = -a
    nabs = pt.abscisse + a * (absmax - absmin) / 10000
    if (nabs > absmax) nabs = absmax
    else if (nabs < absmin) nabs = absmin
    pt.donneAbscisse(nabs)
  }
}
/**
 *
 * @param {CListeObjets} list
 * @returns {boolean}
 */
MtgApp.prototype.validateAnswerOnePass = function validateAnswerOnePass (list) {
  for (let i = 0; i < this.arrayObjAConst.length; i++) {
    const el = list.get(this.arrayObjAConst[i])
    const name = el.estDeNature(NatObj.NObjNommable) ? el.nom : ''
    let conf = false
    for (let j = this.nbObjInit; (j < list.longueur()) && !conf; j++) {
      const e = list.get(j)
      conf = conf || (e.existe && e.coincideAvec(el) && ((name.length !== 0) ? (e.nom === el.nom) : true))
    }
    if (!conf) return false
  }
  return true
}
/**
 *
 * @returns {boolean}
 */
MtgApp.prototype.validateAnswerIso = function validateAnswerIso () {
  const list = this.listePr
  for (let i = 0; i < this.arrayObjAConst.length; i++) {
    const el = list.get(this.arrayObjAConst[i])
    let conf = false
    for (let j = this.nbObjInit; (j < list.longueur()) && !conf; j++) {
      conf = conf || (el.isIsomTo(list.get(j)))
    }
    // Pour le cas particulier d'un polygone on regarde si l'équivalent a été créé avec des segments
    if (!conf && el.estDeNature(NatObj.NPolygone) && this.existePolyEq(el)) {
      // On va créer un polygone provisoire dont les sommets sont les points construits ayant les mêmes noms que les
      // sommets de el
      const col = new Array(el.nombrePoints)
      for (let j = 0; j < el.nombrePoints; j++) {
        const pt1 = el.colPoints[j].pointeurSurPoint
        const pt2 = this.pointParNom(pt1.nom)
        col[j] = new CNoeudPointeurSurPoint(list, pt2)
      }
      const poly = new CPolygone(list, null, false, el.couleur, false, el.style, col)
      poly.positionne()
      conf = conf || el.isIsomTo(poly)
    }
    if (!conf) return false
  }
  return true
}

/**
 * Ne sert que pour les exercices de construction
 * Fonction renvoyant true s'il a été construit un segment, une droite ou une demi droite auquel appatiennent
 * les points construits nommés nom1 et nom2
 * @param {string} nom1
 * @param {string} nom2
 * @returns {boolean}
 */
MtgApp.prototype.existeObjDte = function existeObjDte (nom1, nom2) {
  const list = this.listePr
  const pt1 = this.pointParNom(nom1)
  const pt2 = this.pointParNom(nom2)
  if (pt1 === null || pt2 === null) return false
  // for (var i = this.nbObjInit; i < list.longueur(); i++) { // On accepte aussi les objets lignes présents dans la figure initiale.
  for (let i = 0; i < list.longueur(); i++) {
    const el = list.get(i)
    if (el.estDeNature(NatObj.NTteDroite) && ((i <= this.nbObjInit) ? !el.masque : true)) {
      if (pt1.appartientParDefinition(el) && pt2.appartientParDefinition(el)) return true
    }
  }
  return false
}

/**
 * Fonction utilisée seulement pour les exercices de construction.
 * Renvoie le point ayant pour nom nom sauf si ce point fait partie de la figure initiale et est masque.
 * Renvoie null s'il n'y a pas de tel point.
 * Normalement ne doit pas renvoyer null car n'est appelé que si tous les points ont été nommés comme demandé.
 * @param {string} nom
 * @returns {CElementBase|null}
 */
MtgApp.prototype.pointParNom = function pointParNom (nom) {
  const list = this.listePr
  // for (var i = this.nbObjInit; i < list.longueur(); i++) {
  for (let i = 0; i < list.longueur(); i++) {
    const el = list.get(i)
    if (el.estDeNature(NatObj.NTtPoint) && (el.nom === nom) && ((i <= this.nbObj) ? !el.masque : true)) return el
  }
  return null
}

/**
 * Ne sert que pour les exercices de construction
 * Fonction renvoyant true s'il a été constuit de segments joignant tous les sommets du polygone poly
 * @param {CPolygone} poly
 * @returns {boolean}
 */
MtgApp.prototype.existePolyEq = function existePolyEq (poly) {
  let existe = true
  for (let i = 0; i < poly.nombrePoints - 1; i++) {
    const nom1 = poly.colPoints[i].pointeurSurPoint.nom
    const nom2 = poly.colPoints[i + 1].pointeurSurPoint.nom
    existe = existe && this.existeObjDte(nom1, nom2)
  }
  return existe
}

/*
MtgApp.prototype.getMissingNames = function() {
  var ret = [];
  var list = this.listePr;
  for (var i = 0; i < this.arrayObjAConst.length; i++) {
    var el = list.get(this.arrayObjAConst[i]);
    if (el.estDeNature(NatObj.NObjNommable)) {
      if (el.nom.length !== 0) {
        var valid = false;
        for (var j = this.nbObjInit; (j < list.longueur()) && !valid; j++) {
          var e = list.get(j);
          if (e.estDeNature(NatObj.NObjNommable)) valid = valid || (e.nom === el.nom);
        }
        if (!valid) ret.push(el.nom);
      }
    }
  }
  return ret;
}
*/
/**
 *
 * @returns {string[]}
 */
MtgApp.prototype.getMissingNames = function getMissingNames () {
  let i, j, k, el, ch, valid, noeud, pt, pt1, pt2
  const ret = []
  const list = this.listePr
  // On crée d'abord un tableau formé de tous les noms des points et droites qui doivent être nommés
  const tab = []
  for (i = 0; i < this.arrayObjAConst.length; i++) {
    el = list.get(this.arrayObjAConst[i])
    if (el.estDeNature(NatObj.NObjNommable) && (el.nom !== '')) tab.push(el.nom)
    else {
      if (el.estDeNature(NatObj.NPolygone)) {
        for (k = 0; k < el.nombrePoints - 1; k++) {
          noeud = el.colPoints[k]
          pt = noeud.pointeurSurPoint
          ch = pt.nom
          if (pt.masque && (ch !== '') && (tab.indexOf(ch) === -1)) tab.push(ch)
        }
      } else {
        if (el.className === 'CSegment') {
          pt1 = el.point1
          ch = pt1.nom
          if (pt1.masque && (ch !== '') && (tab.indexOf(ch) === -1)) tab.push(ch)
          pt2 = el.point2
          ch = pt2.nom
          if (pt2.masque && (ch !== '') && (tab.indexOf(ch) === -1)) tab.push(ch)
        } else {
          if (el.estCercleParCentre()) {
            pt = el.o
            ch = pt.nom
            if (pt.masque && (ch !== '') && (tab.indexOf(ch) === -1)) tab.push(ch)
          }
        }
      }
    }
  }
  for (i = 0; i < tab.length; i++) {
    valid = false
    ch = tab[i]
    for (j = this.nbObjInit; (j < list.longueur()) && !valid; j++) {
      const e = list.get(j)
      if (e.estDeNature(NatObj.NObjNommable) && e.existe) valid = valid || (e.nom === ch)
    }
    if (!valid) ret.push(ch)
  }

  return ret
}

/**
 * Fonction utilisée dans les exercices de construction et renvoyant un tableau formé de chaînes de caractères
 * correspondant aux types graphiques d'éléments qui auraient dû être créée et ne l'ont pas été.
 * Renvoie une tableau vide s'il ne manque d'éléments.
 * @returns {string[]}
 */
MtgApp.prototype.getMissingTypes = function getMissingTypes () {
  const ret = []
  const list = this.listePr
  for (let i = 0; i < this.arrayObjAConst.length; i++) {
    const el = list.get(this.arrayObjAConst[i])
    // Pour le exercices de construction isométrique quand on demande un polygone on accepte l'équivalent tracé avec des segments
    if ((this.macroPourConst.intitule === '#Solution#') || (this.macroPourConst.intitule === '#SolutionIso#' && !el.estDeNature(NatObj.NPolygone))) {
      let valid = false
      for (let j = this.nbObjInit; (j < list.longueur()) && !valid; j++) {
        const e = list.get(j)
        valid = e.estDeNature(natObjGraphPourProto(el.getNature())) && e.existe
      }
      const ch = chaineNatGraphPourProto(el.getNature())
      if (!valid && ret.indexOf(ch)) ret.push(ch)
    }
  }
  return ret
}
/**
 *
 * @returns {boolean}
 */
MtgApp.prototype.validateAnswer = function validateAnswer () {
  let i, j
  // On regarde d'abord s'il s'agit de construire un seul objet isométrique à un objet donné
  if (this.macroPourConst.intitule === '#SolutionIso#') return this.validateAnswerIso()
  else {
    // S'il n'existe pas des objets créés par l'utilisateur coincidant avec les objets à construire
    // la construction est fausse
    if (!this.validateAnswerOnePass(this.listePr)) return false
    // Sinon on crée une nouvelle liste formée des clones des objets de la liste principale
    // On déplace de epsilon trois fois de suites tous les points libres ou points liés et on regarde
    // si la réponse est encore valide.
    const list = this.listePr
    const listAux = new CListeObjets(list.uniteAngle, '', list.decimalDot)
    list.setCopy(listAux)
    const listAConstruire = new CListeObjets()
    for (i = 0; i < this.arrayObjAConst.length; i++) listAConstruire.add(list.get(this.arrayObjAConst[i]))
    const nat = Nat.or(NatObj.NPointBase, NatObj.NPointLie)
    for (j = 0; (j < 3); j++) {
      for (i = 0; i < this.nbObjInit; i++) {
        const el = listAux.get(i)
        if (el.estDeNature(nat) && listAConstruire.depDe(el)) this.epsilon(el)
      }
      listAux.positionne(false, this.dimf)
      if (!this.validateAnswerOnePass(listAux)) return false
    }
    return true
  }
}

/**
 * Fonctionrenvoyant, dans le cas d'un exercice, true si l'utilisateur a rajouté des objets
 * à la figure initiale.
 * @returns {boolean}
 */
MtgApp.prototype.objectConstructed = function objectConstructed () {
  return this.listePr.longueur() > this.nbObjInit
}

/**
 * Fonction remplaçant la figure actuelle par la figure dont le code Base64 est code
 * @param {string} code Le code Base64 de la nouvelle figure
 * @param {boolean} [bdisplay=true] Passer false pour ne pas afficher la figure (et être sync, sinon on appelle la callback ou retourne une promesse)
 * @param {DisplayCallback} [callBack] éventuelle fonction de callBack à appeler une fois la figure affichée
 * @returns {Promise<void>|void} Une promesse si bdisplay sans callback
 */
MtgApp.prototype.setFigByCode = function setFigByCode (code, bdisplay = true, callBack = null) {
  let doc
  try {
    // doc = new CMathGraphDoc(this.id + "figurePanel", true, true); // Modifié version 6.3.0
    doc = new CMathGraphDoc(this.id, true, true, this.decimalDot)
    const ba = base64Decode(code)
    const inps = new DataInputStream(ba, code)
    doc.read(inps)
  } catch (e) {
    new AvertDlg(this, e.message)
    return
  }
  this.doc = doc
  // Ligne suivante nécessaire pour que les macros d'activation et désactivation du mode trace activent ou non
  // l'icône correspondante
  this.doc.app = this
  const list = this.listePr
  list.retireTout()
  this.retireTout()
  this.prepareTracesEtImageFond()
  doc.dimf = this.dimf
  this.listePr = doc.listePr
  /** @type {CListeObjets} */
  this.listePourConst = this.listePourConstruction()
  this.updateToolsToolBar()
  this.creeCommentaireDesignation()
  this.listePr.creePaneVariables()
  this.calculate(false)
  if (this.cadre !== null) this.createCadre(this.widthCadre, this.heightCadre)
  // Ligne ci-dessous modifiée. On appelle this.gestionnaire.initialise(); car cette fonction peut-être appelée depuis j3p
  // et il faut dans ce cas qu'on ne puisse pas annuler ce qui a été fait avant.
  // if (this.electron) this.gestionnaire.initialise(); else this.gestionnaire.enregistreFigureEnCours("NewFig");
  this.gestionnaire.initialise()
  this.reInitConst() // Pour réinitialiser une éventuelle construction en cours
  if (this.zoomOnWheel) addZoomListener(this.doc, this.svgFigure, this.svgRect) // Ajoute au doc le listener sur le wheel
  // if (doc.modeTraceActive) this.buttonModeTrace.activate(true);
  if (bdisplay) {
    if (!callBack) return this.display()
    this.display(callBack)
  }
}

/**
 * Rend la figure éactive ou inative aux événements souris et clavier
 * suivant la valeur du boolean ba.
 * @param {boolean} ba
 * @returns {void}
 */
MtgApp.prototype.setActive = function setActive (ba) {
  if (this.nameEditor.isVisible) this.nameEditor.montre(false)
  this.activeOutilCapt()
  const doc = this.doc
  if (doc !== null) {
    doc.isActive = ba
    // Il faut aussi activer ou désactiver les champs d'édition de formule
    const liste = doc.listePr
    for (const el of liste.col) {
      if (el.estDeNature(NatObj.NEditeurFormule)) el.editeur.readOnly = !ba
      else {
        if (el.getNatureCalcul() === NatCal.NVariable) {
          if (el.dialogueAssocie) {
            el.buttonplus.setAttribute('disabled', !ba)
            el.buttonmoins.setAttribute('disabled', !ba)
            el.buttonegal.setAttribute('disabled', !ba)
          }
        }
      }
    }
  }
}
/**
 *
 * @returns {Nat}
 */
MtgApp.prototype.natPourImages = function natPourImages () {
  const mac = this.macroPourConst
  let nat = NatObj.NTtPoint
  if (mac === null) {
    return this.onlyPoints
      ? nat
      : NatObj.NTtObjPourTransf
  }
  const com = mac.commentaireMacro
  const tab1 = ['droite', 'segment', 'demidroite', 'cercle', 'arc', 'polygone', 'lignebrisee']
  const tab2 = [NatObj.NDroite, NatObj.NSegment, NatObj.NDemiDroite, NatObj.NCercle, NatObj.NArc, NatObj.NPolygone, NatObj.NLigneBrisee]
  for (let i = 0; i < tab1.length; i++) {
    if (com.indexOf('#' + tab1[i]) !== -1) nat = Nat.or(nat, tab2[i])
  }
  return nat
}

/**
 * Fonction changeant la formule du calcul ou de
 * la fonction (réelle ou complexe) de nom nomCalcul.
 * La nouvelle formule est contenue dans la chaîne de caractères formule.
 * Renvoie true si la formule était valide et false sinon.
 * Utilisé par j3p pour certains exercices de construction
 * @param {string} nomCalcul
 * @param {string} formule
 * @returns {void}
 */
MtgApp.prototype.giveFormula2 = function giveFormula2 (nomCalcul, formule) {
  this.listePr.giveFormula2(nomCalcul, formule)
}

/**
 * Renvoie la valeur actuelle du calcul réel nommé nomCalcul dans la figure.
 * Renvoie -1 si le calcul n'existe pas.
 * @param {string} nomCalcul
 * @param {boolean} bNoCase true si la recherche se fait sans tenir compte de la casse majuscule ou minuscule
 * @returns {number}
 */
MtgApp.prototype.valueOf = function valueOf (nomCalcul, bNoCase = false) {
  return this.listePr.valueOf(nomCalcul, bNoCase)
}

/**
 * Retourne le code LaTeX de l'affichage LaTex d'indice ind dans la liste
 * des objets créés (les indices commençant à zéro)
 * Utilisé par j3p pour certains exercices de construction
 * @param {number|string} ind Si number c'est l'indice de l'affichage LaTeX parmi tous les affichages LaTeX, si string ça doit être un "#" suivi du tag de l'objet du type CLatex (depuis version 6.6).
 * @returns {string}
 */
MtgApp.prototype.getLatexCode = function getLatexCode (ind) {
  return this.listePr.getLatexCode(ind)
}

/**
 * Reconstruit une figure à partir d'un flux binaire
 * @param {number[]} ba Tableau de bytes contenant le flux binaire de la figure
 * @param {function} [callBackOnOK=null] Fonction éventelle de callBack à appeler après chargement de la figrue
 * @param {string} [filePath='']  Le chemin éventuel d'accès à la figure si on ouvre depuis le disque
 */
MtgApp.prototype.resetFromByteArray = function resetFromByteArray (ba, callBackOnOK = null, filePath = '') {
  const self = this
  let doc
  try {
    const inps = new DataInputStream(ba)
    // var doc = new CMathGraphDoc(self.id + "figurePanel", true, true); // Modifié version 6.3.0
    doc = new CMathGraphDoc(self.id, true, true, this.decimalDot)
    doc.read(inps)
  } catch (e) {
    new AvertDlg(self, e.message)
    return
  }
  self.nameEditor.montre(false) // Des fois qu'un nom soit en cours d'édition

  const list = self.listePr
  list.retireTout()
  self.retireTout()
  doc.dimf = self.dimf
  doc.app = self
  self.doc = doc
  // Ligne suivante nécessaire pour que les macros d'activation et désactivation du mode trace activent ou non
  // l'icône correspondante
  self.doc.app = self
  self.prepareTracesEtImageFond()
  self.listePr = doc.listePr
  self.listePourConst = self.listePr
  self.updateToolsToolBar()
  self.creeCommentaireDesignation()
  this.listePr.creePaneVariables()
  self.calculateAndDisplay(false)
  // if (doc.modeTraceActive) self.buttonModeTrace.activate(true);
  if (self.cadre !== null) self.createCadre(self.widthCadre, self.heightCadre)
  if (self.electron) self.gestionnaire.initialise(); else self.gestionnaire.enregistreFigureEnCours('NewFig')
  self.reInitConst() // Pour réinitialiser une éventuelle construction en cours
  if (this.zoomOnWheel) addZoomListener(this.doc, this.svgFigure, this.svgRect) // Ajoute au doc le listener sur le wheel
  if (this.electron) {
    doc.setDirty(true, false)
    // On appelle setNewPath qui est une fonction de index.html sert à autoriser main.js à changer le chemin d'accès de la figure
    setNewPath(filePath) // eslint-disable-line no-undef
  }
  this.updateToolbar()
  self.activeOutilDem()
  if (callBackOnOK !== null) callBackOnOK()
}

/**
 * Fonction chargeant une figure depuis une chaîne de caractères (en utf-8)
 * Cette fonction est utilisée par la version electron
 * @param {string} ch La chaîne de caractères contenant le code de la figure (chaque caractère a le code Ascii
 * permettant de créer un ByteArray représentant le flux binaire de la figure.
 * @param {function} [callBackOnOK] callback rappelée si ok
 */
MtgApp.prototype.resetFromString = function resetFromString (ch, callBackOnOK) {
  const ba = []
  for (let i = 0; i < ch.length; i++) ba.push(ch.charCodeAt(i))
  this.resetFromByteArray(ba, callBackOnOK)
}

/**
 * Fonction chargeant une figure depuis un objet File
 * @param {Blob} file
 * @param {function} [callBackOnOK] Fonction de callBack à appeler si le chargement a réussi
 */
MtgApp.prototype.resetFromFile = function resetFromFile (file, callBackOnOK) {
  const reader = new FileReader()
  reader.readAsArrayBuffer(file)
  const self = this
  reader.onload = function () {
    try {
      const ba = new Uint8Array(reader.result)
      self.resetFromByteArray(ba, callBackOnOK, file.path)
    } catch (e) {
      new AvertDlg(self, e.message)
    }
  }
}

/**
 * Fonction utilisée par la version electron quand on double-clique sur un fichier pour lancer le logiciel
 * @param {string} ch Contient une chaîne de caractères contenant un flux binaire représentant une figure
 * @returns {CMathGraphDoc|null} Renvoie null si le code n'est pas valide et sinon le CMathGraphDoc correspondant
 */
MtgApp.prototype.getDocFromString = function getDocFromString (ch) {
  try {
    const ba = []
    for (let i = 0; i < ch.length; i++) ba.push(ch.charCodeAt(i))
    const inps = new DataInputStream(ba)
    // var doc = new CMathGraphDoc(this.id + "figurePanel", true, true); // Modifié version 6.3.0
    const doc = new CMathGraphDoc(this.id, true, true, this.decimalDot)
    doc.read(inps)
    return doc
  } catch (e) {
    return null
  }
}

/**
 * Fonction ajoutant un prototype à la figure depuis un tavbeau d'entier ba
 * @param {number[]} ba
 */
MtgApp.prototype.addProtoFromByteArray = function addProtoFromByteArray (ba) {
  const self = this
  try {
    const inps = new DataInputStream(ba)
    this.doc.addPrototype(inps, this.doc.numeroVersion)
  } catch (e) {
    new AvertDlg(self, 'FichierErr')
  }
}
/**
 *
 * @param {string} ch
 */
MtgApp.prototype.addProtoFromString = function addProtoFromString (ch) {
  const ba = []
  for (let i = 0; i < ch.length; i++) ba.push(ch.charCodeAt(i))
  this.addProtoFromByteArray(ba)
}
/**
 *
 * @param {Blob} file
 */
MtgApp.prototype.addProtoFromFile = function addProtoFromFile (file) {
  const reader = new FileReader()
  reader.readAsArrayBuffer(file)
  const self = this
  reader.onload = function () {
    const ba = new Uint8Array(reader.result)
    self.addProtoFromByteArray(ba)
  }
}
/**
 *
 * @param {BlobEvent} ev
 */
MtgApp.prototype.onDropFile = function onDropFile (ev) {
  if (ev.dataTransfer && ev.dataTransfer.files && ev.dataTransfer.files.length) {
    this.activeOutilCapt()
    ev.stopPropagation()
    preventDefault(ev)
    const file = ev.dataTransfer.files[0]
    let imgType = file.name.split('.')
    imgType = imgType[imgType.length - 1].toLowerCase() // On utilise toLowerCase() pour éviter les extensions en majuscules
    const allowedTypes = [mtgFileExtension, 'png', 'jpg', 'jpeg', 'gif']
    const ind = allowedTypes.indexOf(imgType)
    if (ind !== -1) {
      if (ind === 0) { // Cas d'un fichier mtg32
        if (this.doc.isDirty) {
          const self = this
          new ConfirmDlg(this, 'AvertDirty', function () {
            self.resetFromFile(file, function () {
            })
            // Si on est dans la version electron on remet à zéro le document
            // resetDocument est une fonction de la page index.html de la vesion electron
            if (self.electron) resetDocument() // eslint-disable-line no-undef
          })
        } else {
          this.resetFromFile(file, function () {
          })
        }
      } else { // cas d'un fichier image
        const self = this
        const natImage = DataOutputStream.getNatImage(imgType)
        const reader = new FileReader()
        reader.readAsDataURL(file)
        reader.addEventListener('load', function () {
          const database64 = reader.result
          const tab = database64.split(',')
          const { x, y } = mousePosition(self.svgFigure, ev, self.zoomFactor) // 3 ième paramètre rajouté version 6.5.2
          // reader.result contient la chaîne Base64 représentant l'image
          const listePr = self.listePr
          const im = new CImage(listePr, null, false, self.getCouleur(), x, y, 0, 0, false, null, 13,
            StyleEncadrement.Sans, false, self.doc.couleurFond, CAffLiePt.alignHorCent,
            CAffLiePt.alignVerCent, natImage, base64Decode(tab[tab.length - 1], true),
            new CValeurAngle(listePr, 0), new CValeur(listePr, 0), false)
          const image = ce('img', {
            src: database64
          })
          image.onload = function () {
            im.width = image.width
            im.height = image.height
            self.ajouteElement(im)
            im.creeAffichage(self.svgFigure, false, self.doc.couleurFond)
            self.outilActif.saveFig()
            self.activeOutilCapt()
          }
        }, false)
      }
    }
  }
}
/**
 *
 * @param {number} width
 * @param {number} height
 */
MtgApp.prototype.createCadre = function createCadre (width, height) {
  this.widthCadre = width
  this.heightCadre = height
  this.cadre = cens('rect', {
    x: 2,
    y: 2,
    width,
    height,
    rx: 4,
    ry: 4,
    style: 'fill:none;stroke:lightgrey;stroke-width:4;'
  })
  this.svgFigure.insertBefore(this.cadre, this.doc.gTraces)
}

MtgApp.prototype.deleteCadre = function deleteCadre () {
  this.svgFigure.removeChild(this.cadre)
  this.cadre = null
}

/**
 * Fonction créant la représentation de la figure dans un URI et une fois que c'est prêt appelant
 * la fonction de callBack() callBack avec comme paramètre le blob représentant l'image
 * @param {string} imageType  "png" ou "jpeg"
 * @param svg Le svg dans lequel se fait l'affichage
 * @param callBack La fonction de callBack
 * @param fileName Le nom du fichier pour l'enregistrement
 * @param {number} coefMult Le coefficient multiplicateur utilisé pour gagner (ou perdre ) en définition
 * @param {number}coef Coefficient d'agrandissement-réduction utilisé pour ,'exportation en pNG avec unité
 */
/* Abandonné
MtgApp.prototype.getImageBlobData = function(imageType, svg, fileName, callBack, coefMult, coef) {
  var dimf = this.dimf;
  var avecCadre = this.cadre !== null;
  // var coefMult = this.pref_coefMult; // Le coefficient multiplicateur pour la taille des images
  var w = (avecCadre ? this.widthCadre : dimf.x)*coefMult;
  var h = (avecCadre ? this.heightCadre : dimf.y)*coefMult;

  var canvas = ce("canvas", {
    width : w * coef, // On utilise round pour arrondir à la valeur entière la plus proche
    height : h * coef
    // style : "width:" + w + ";height:" + h
  });

  var ctx = canvas.getContext('2d');
  var data = (new XMLSerializer()).serializeToString(svg);
  var DOMURL = window.URL || window.webkitURL || window;
  var img = new Image();
  var svgBlob = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
  var url = DOMURL.createObjectURL(svgBlob);

  img.onload = function () {
    ctx.drawImage(img, 0, 0);
    DOMURL.revokeObjectURL(url);
    // callBack(canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream'));
    // callBack(fileName, canvas.toDataURL('image/png'));
    canvas.toBlob(
      function(blob) {
        callBack(fileName, blob);
      },
      "image/" + imageType
    )
  };

  img.src = url;
}
*/
/**
 * Fonction appelée par la version electron pour obtenir le codeBase64 correspondant à la figure en PNG
 * @param {string} imageType  "png" ou "jpeg"
 * @param {function} callBack appelée avec le code64 de la figure
 * @param {number} [coefMult] Le coefficient multiplicateur utilisé pour gagner (ou perdre) en définition
 * @param {number} [coef] le coefficient d'agrandissement ou réduction utilisé pour l'exportation en PNG avec unité
 * @returns {void}
 */
MtgApp.prototype.getBase64ImageData = function getBase64ImageData (imageType, callBack, coefMult, coef) {
  const doc = this.doc
  const listePr = this.listePr
  // Si un cadre a été choisi dans la figure et si la case à cocher est cochée, on enregistre la figure
  // avec les dimensions du cadre
  const avecCadre = this.cadre !== null
  // var coefMult = this.pref_coefMult; // Le coefficient multiplicateur pour la taille des images
  const w = (avecCadre ? this.widthCadre : doc.dimf.x) * coefMult
  const h = (avecCadre ? this.heightCadre : doc.dimf.y) * coefMult
  const dimf = new Dimf(w, h)
  const svg = cens('svg', {
    width: w * coef,
    height: h * coef,
    viewBox: '0 0 ' + w + ' ' + h
  })
  /// /////////////////////////////////////////////////////////////////////////////////////////////////
  // Très important : Il faut cloner les defs de MathJax contenues dans le document pour que les LaTeX
  // puissent être tracés correctement
  /* Plus nécessaire version 6.4 avec MathJax 3
  var defs = document.getElementById("MathJax_SVG_glyphs");
  svg.appendChild(defs.cloneNode(true));
  */
  // Et il faut aussi cloner les pattern pour les remplissages de surface
  const defs = document.getElementById('mtg32_patterns')
  const defsclone = svg.appendChild(defs.cloneNode(true))
  // On remplace les patterns de remplissage par des deux fois plus gros pour exportation
  const coefarr = (coefMult < 1) ? 1 : Math.round(coefMult) // On arrondit à l'entier le plus proche si >= 1
  const dimRectPattern = String(coefarr * 6)
  for (const el of listePr.col) {
    if (el.estDeNature(NatObj.NSurface) && (el.pattern !== null)) {
      // Modification version 6.3.2 pour rendre le player compatible avec les hachures
      // var pattern = $(defsclone).children("#" + el.index + "mtg32pattern")[0];
      const pattern = $(defsclone).children('#' + listePr.id + el.index + 'mtg32pattern')[0]
      $(pattern).attr('width', dimRectPattern).attr('height', dimRectPattern)
      pattern.childNodes[0].setAttribute('d', el.getPattern(true, coefarr))
    }
  }
  /// /////////////////////////////////////////////////////////////////////////////////////////////////
  // Il faut cloner la liste principale car sinon on a des problèmes de rafraichissement des g elements
  // des objets graphiques
  // FIXME le paramètre listePr ci-dessous semble ne pas servir
  const list = new CListeObjets(listePr)
  list.isForExport = true // Sert à supprimer l'éditeur de l'export.
  this.listePr.setCopy(list)
  list.coefMult = coefMult // Utilisé pour la trace des points et des marques d'angles
  list.adaptRes(coefMult)
  list.positionne(false, dimf)
  // Pour les images dont la largeur ne dépend pas de la longueur unité (largeur nulle pour dimensions initiales
  // ou strictement négative pour des dimensions imposées en pixels, il faut adapter leurs dimensions au coefficient
  // multiplicateur utilisé
  list.adaptDimImagesRes(coefMult)
  list.setReady4MathJax(false, false)
  addQueue(function () {
    svg.appendChild(cens('rect', {
      width: '100%',
      height: '100%',
      fill: doc.couleurFond.rgb()
    }))
    if (doc.imageFond !== null) {
      const g = cens('image', {
        x: '0',
        y: '0',
        width: doc.widthImageFond * coefMult,
        height: doc.heightImageFond * coefMult
      })
      // g.setAttributeNS("http://www.w3.org/1999/xlink", "href", "data:image/png;base64," + base64Encode(doc.imageFond, true));
      g.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'data:image/' + imageType + ';base64,' + base64Encode(doc.imageFond, true))
      svg.appendChild(g)
    }
    const traces = doc.gTraces.cloneNode(true)
    $(traces).attr('transform', 'scale(' + coefMult + ')')
    svg.appendChild(traces)
    list.afficheToutForCopy(svg, doc.couleurFond) // On appelle afficheToutForCopy pour ne pas mettre dans le svg
    // les foreign objects qui ne sont pas acceptés
    // Version 4.6 : Pour que les patterns de remplissages restent les bons il faut les supprimer et les recréer
    empty(defs)
    for (const el of listePr.col) {
      if (el.estDeNature(NatObj.NSurface) && (el.pattern !== null)) el.createPattern()
    }

    const canvas = ce('canvas', {
      width: w * coef, // On utilise round pour arrondir à la valeur entière la plus proche
      height: h * coef
      // style : "width:" + w + ";height:" + h
    })

    const ctx = canvas.getContext('2d')
    // On supprime les pointer-events de la réprésentation de la figure
    const data = (new XMLSerializer()).serializeToString(svg).replace(/pointer-events[ ]*=[ ]*(['"])none\1/g, '')
    // Si on veut l'exportation en svg tout est déjà dans data
    if (imageType === 'svg') callBack(data)
    else {
      const DOMURL = window.URL || window.webkitURL || window
      const img = new Image()
      const svgBlob = new Blob([data], { type: 'image/svg+xml;charset=utf-8' })
      const url = DOMURL.createObjectURL(svgBlob)
      img.onload = function () {
        ctx.drawImage(img, 0, 0)
        DOMURL.revokeObjectURL(url)
        callBack(canvas.toDataURL('image/' + imageType).replace(/^data:image\/\w+;base64,/, ''))
      }
      img.src = url
    }
  })
}

// Les fonctions suivantes sont des fonctions uniquement utiles pour la version electron

/**
 * Fonction utilisée par electron et renvoyant un tableau d'entiers contenant le code binaire de la figure
 * @returns {number[]}
 */
MtgApp.prototype.getByteArrayCode = function getByteArrayCode () {
  const oups = new DataOutputStream()
  // Si un cadre a été choisi dans la figure et si la case à cocher est cochée, on enregistre la figure
  // avec les dimensions du cadre
  const doc = this.doc
  const olddimf = this.dimf
  const avecCadre = (this.cadre !== null)
  if (avecCadre) doc.dimf = new Dimf(this.widthCadre, this.heightCadre)
  doc.write(oups)
  doc.dimf = olddimf
  return oups.ba
}

/**
 * Fonction utilisée pour la version electron et renvoyant un tableau d'entiers contenant le code binaire du prototype n° ind de la figure
 * @param {number} ind
 * @returns {number[]}
 */
MtgApp.prototype.getProtoByteArrayCode = function getProtoByteArrayCode (ind) {
  const oups = new DataOutputStream()
  this.doc.tablePrototypes[ind].write(oups)
  return oups.ba
}

/**
 * Appellera cb quand tous les rendus seront terminés
 * @param {function} [cb] Si non fourni, ça retourne une promesse qui sera résolue quand l'appli est prête (tous les rendus lancés terminés)
 * @returns {Promise|undefined}
 */
MtgApp.prototype.ready = function ready (cb) {
  if (!cb) return new Promise(resolve => addQueue(resolve))
  addQueue(cb)
}

/**
 * Lance le resize de l'éditeur (utiliser app.ready() pour savoir quand ce sera terminé)
 * @param {number} w
 * @param {number} h
 * @returns {void}
 */
MtgApp.prototype.resize = function resize (w, h) {
  if (!Number.isFinite(w) || !Number.isFinite(h) || w < 100 || h < 100) return console.error(Error(`valeurs de resize incorrectes (${w}×${h})`))
  // on utilise addQueue pour ne lancer un resize que entre deux rendus LaTeX, jamais pendant
  addQueue(() => {
    const doc = this.doc
    const svg = this.svg
    const svgFig = this.svgFigure
    const zf = this.zoomFactor
    const sizeStyle = { width: `${w}px`, height: `${h}px` }
    const sizeAttrs = { width: String(w), height: String(h) }
    const div = svg.parentNode // Le div parent
    setStyle(div, sizeStyle)
    // pour le svg il faut fixer les attributs ET le style, sinon ça reste tronqué à droite
    setAttrs(svg, sizeAttrs)
    setStyle(svg, sizeStyle)

    // changer la largeur de la toolbar change rien, appeler updateToolbar ensuite non plus
    // => si la fenêtre rétrécit en largeur on ne voit plus les boutons
    /* FIXME trouver comment régler ça
    const tw = Number(this.toolBar.getAttribute('width'))
    if (tw > w || tw < w * 0.6) {
      setAttrs(this.toolBar, { width: w })
      this.updateToolbar()
    } */
    setAttrs(this.rightPanel, {
      x: String(w - constantes.rightPanelWidth * zf),
      y: String(constantes.toolbarHeight * zf + 26)
    })
    setAttrs(this.svgPanel, sizeAttrs)
    this.dimf = new Dimf(w - constantes.svgPanelWidth * zf - constantes.rightPanelWidth * zf, h - constantes.topIconSize * zf)
    doc.dimf = this.dimf
    const dimAttrs = { width: String(this.dimf.x), height: String(this.dimf.y) }
    setAttrs(this.svgFigureRect, dimAttrs)
    setStyle(this.divDlg, sizeStyle)
    setAttrs(svgFig, dimAttrs)
    this.nameEditor.montre(false) // Des fois qu'un nom soit en cours d'édition
    const list = this.listePr
    list.positionne(false, this.dimf)
    list.update(svgFig, doc.couleurFond, true)
    // On met à jour les div éventuels associés à des variables.
    let ind = 0
    for (let i = list.longueur() - 1; i >= 0; i--) {
      const el = list.get(i)
      if (el.getNatureCalcul() === NatCal.NVariable) {
        if (el.dialogueAssocie) {
          const left = `${w - el.div.offsetWidth}px`
          const top = `${h - el.div.offsetHeight * (ind + 1)}px`
          setStyle(el.div, { left, top })
          ind++
        }
      }
    }
  })
}

MtgApp.prototype.addToolsSup = function addToolsSup () {
  // A revoir une fois les constructions au point
  const toolsNameArray = ['CreationConst', 'GestionConst', 'TaillePlus', 'TailleMoins', 'ReclassDebObjGra', 'ReclassFinObjGra']
  // var toolsNameArray = ["CopierStyle", "TaillePlus", "TailleMoins", "ReclassDebObjGra", "ReclassFinObjGra"];
  const svg = this.svg
  const zoomFactor = this.zoomFactor
  const nbIcones = toolsNameArray.length
  const width = parseFloat(svg.getAttribute('width'))
  const iconSize = constantes.topIconSize
  const w = nbIcones * (iconSize + 2) * zoomFactor
  const h = constantes.topIconSize * zoomFactor
  this.svgToolsAdd = cens('svg', {
    id: 'svgToolsAdd',
    width: w,
    height: h,
    x: width - w,
    y: iconSize * zoomFactor + 26
  })
  svg.appendChild(this.svgToolsAdd)
  this.svgToolsAdd.style.cursor = 'default'
  for (let row = 0; row < nbIcones; row++) {
    const toolName = toolsNameArray[row]
    const button = new ButtonTool(this, toolName, 'float', false, row)
    this['button' + toolName] = button
    this.svgToolsAdd.appendChild(button.container)
  }
}

/**
 * Fonction ajoutant à listePr un segment d'extrémités pt1 et pt2 dans les styles actifs
 * à condition qu'un tel objet n'ait pas déjà été défini
 * @param {CPt} pt1
 * @param {CPt} pt2
 */
MtgApp.prototype.addSegment = function addSegment (pt1, pt2) {
  const li = this.listePr
  const seg = new CSegment(li, null, false, this.getCouleur(), false, this.getStyleTrait(), pt1, pt2)
  seg.positionne(false, this.dimf)
  if (!this.existeDeja(seg)) li.add(seg)
}

MtgApp.prototype.addPolygon = function addPolygon () {
  const li = this.listePr
  const colPt = []
  for (let i = 0; i < arguments.length; i++) {
    const pt = arguments[i]
    colPt.push(new CNoeudPointeurSurPoint(li, pt))
  }
  const poly = new CPolygone(li, null, false, this.getCouleur(), false, this.getStyleTrait(), colPt)
  poly.positionne(false, this.dimf)
  if (!this.existeDeja(poly)) li.add(poly)
}

MtgApp.prototype.creeDefs = function creeDefs () {
  // Création du linear  Gradiant pour les boutons
  let defs = cens('defs', {
    id: 'mtg32_defs'
  })
  const lg = cens('linearGradient', {
    id: 'arrowGrad',
    x1: '0%',
    y1: '50%',
    x2: '100%',
    y2: '50%'
  })
  let stop = cens('stop', {
    offset: '0%',
    style: 'stop-color:#0000FF;stop-opacity:0.6'
  })
  lg.appendChild(stop)
  stop = cens('stop', {
    offset: '100%',
    style: 'stop-color:#CEF6F5;stop-opacity:0.4'
  })
  lg.appendChild(stop)
  defs.appendChild(lg)

  let rg = cens('radialGradient', {
    id: 'buttonGrad',
    r: '100%'
  })
  stop = cens('stop', {
    offset: '0%',
    style: 'stop-color:#0000FF;stop-opacity:0'
  })
  rg.appendChild(stop)
  stop = cens('stop', {
    offset: '100%',
    style: 'stop-color:#0000FF;stop-opacity:0.2'
  })
  rg.appendChild(stop)
  defs.appendChild(rg)
  rg = cens('radialGradient', {
    id: 'buttonGradAct',
    r: '100%'
  })
  stop = cens('stop', {
    offset: '2%',
    style: 'stop-color:#0000FF;stop-opacity:0'
  })
  rg.appendChild(stop)
  stop = cens('stop', {
    offset: '100%',
    style: 'stop-color:#0000FF;stop-opacity:0.5'
  })
  rg.appendChild(stop)
  defs.appendChild(rg)
  // Création d'un radial gradiant pour les boutons des sliders
  rg = cens('radialGradient', {
    id: 'sliderGrad',
    r: '100%'
  })
  stop = cens('stop', {
    offset: '0%',
    style: 'stop-color:#9B9EFC;'
  })
  rg.appendChild(stop)
  stop = cens('stop', {
    offset: '100%',
    style: 'stop-color:#0000FF;'
  })
  rg.appendChild(stop)
  defs.appendChild(rg)
  this.svg.appendChild(defs)
  // un defs pour contenir les patterns de remplissages pour les surfaces avec quadrillage
  defs = cens('defs', {
    id: 'mtg32_patterns'
  })
  this.svg.appendChild(defs)
}

MtgApp.prototype.releaseSliders = function releaseSliders () {
  this.thicknessSlider.captured = false
  this.opacitySlider.captured = false
}

/**
 * Fonction vérifiant si le dernier élément de la liste est confondu avec un objet déjà créé
 * sachant que si c'est le cas il faut retirer les nbOjetsAjoutes derniers objets de la liste.
 * Si on ne trouve pas d'élément confondu, les nbObjetsAjoutes derniers objets sont positioonés
 * @param {number} nbObjetsAjoutes
 * @returns {boolean}
 */
MtgApp.prototype.verifieDernierElement = function verifieDernierElement (nbObjetsAjoutes) {
  const list = this.listePr
  const len = list.longueur()
  const der = list.get(len - 1)
  // Avant la vérification il faut positionner les derniers éléments ajoutés à la liste
  for (let i = 0; i < nbObjetsAjoutes; i++) {
    const ob = list.get(len - nbObjetsAjoutes + i)
    ob.positionne(false, this.dimf)
  }
  if (this.existeDeja(der)) {
    list.retireNDerniersElements(nbObjetsAjoutes)
    if (this.estExercice) this.listePourConst.retireNDerniersElements(nbObjetsAjoutes)
    this.nameEditor.montre(false)
    return false
  }
  return true
}
/**
 *
 * @param {CPt} pt
 * @param {boolean} [bRemovegElements=true]
 */
MtgApp.prototype.detruitDependants = function detruitDependants (pt, bRemovegElements = true) {
  this.listePr.detruitDependants(pt, this.svgFigure, bRemovegElements)
  if (this.estExercice) this.listePourConst = this.listePourConstruction()
}

/**
 * Fonction détruisant les constructions itératives ou récursives de la figure dépendant du prototype prot
 * @param {CPrototype} proto
 */
MtgApp.prototype.detruitDepProto = function detruitDepProto (proto) {
  for (const el of this.listePr.col) {
    if (el.depDeProto(proto)) this.detruitDependants(el, true)
  }
  this.reCreateDisplay()
}

/**
 * Fonction retirant toutes les définitions de quadrillages associés à des surfaces
 */
MtgApp.prototype.removeSurfacePatterns = function removeSurfacePatterns () {
  const defs = ge('mtg32_patterns')
  empty(defs)
  for (const el of this.listePr.col) {
    if (el.estDeNature(NatObj.NSurface)) el.pattern = null
  }
}

/**
 * Retourne l'id de la boite de dialogue courante (celle du dessus)
 * (utile pour avertDialog, pour éviter de réafficher deux fois le même message d'erreur sur un double clic par ex)
 * @returns {string} vide si y'a pas de boite de dialogue ouverte
 */
MtgApp.prototype.lastDlgId = function lastDlgId () {
  if (this.dlg.length > 0) return this.dlg[this.dlg.length - 1]
  return ''
}
/**
 *
 * @param {boolean} bVisible
 */
MtgApp.prototype.showStopButton = function showStopButton (bVisible) {
  $(this.stopButton.container).attr('visibility', bVisible ? 'visible' : 'hidden')
}

/**
 * Lance l'affichage en haut et à droite d'un message d'indication correspondant à getStr(ch), pendant 6s
 * Si le paramètre prech est présent, getStr(prech) est affiché devant getStr(ch) avec : entre les deux
 * Utiliser app.ready() pour savoir quand l'indication est effectivement affichée
 * @param {string} textCode
 * @param {string} [preTextCode='']
 * @param {boolean} [bstraight=false] Si true on n'utilise pas getStr(textCode) et on utilise directement textCode
 * @returns {void}
 */
MtgApp.prototype.indication = function indication (textCode, preTextCode = '', bstraight = false) {
  let st = bstraight ? textCode : getStr(textCode)
  if (st === '') {
    this.lastindch = '' // Pour les outils qui n'ont pas d'indication fugitive
    this.effaceIndication()
    return
  }
  //
  if (preTextCode) st = getStr(preTextCode) + ' : ' + st
  // Sur la version electron, quand on appuie sur F10 cela réaffiche la dernière indication
  // Modifié version 6.9.0 pas besoin de mémoriser prech
  // Quand on appelera lastIndication() ce sera avec bstraight = true et la même chose sera réaffichée.
  this.lastindch = st
  this.effaceIndication()
  const CAlp = CAffLiePt
  const comm = new CCommentaire(this.listePr, null, false, Color.blue, this.dimf.x - 10, 5, 0, 0, false, null,
    this.dys ? 20 : 15, StyleEncadrement.Simple, true, new Color(204, 204, 255), CAlp.alignHorRight, CAlp.alignVerTop,
    st, new CValeurAngle(this.listePr, 0))
  comm.positionne(false, this.dimf)
  // Version 6.8.1 : On délègue l'affichage de l'indication à la pile d'appels
  // Version 6.9 : Un double appel de addQueue pour que l'affichage se fasse après celui de la figure au démarrage
  addQueue(() => {
    addQueue(() => {
      if (this.comm !== null) this.effaceIndication()
      this.comm = comm
      comm.affiche(this.svgFigure, true, this.doc.couleurFond)
      $(comm.g.childNodes[0]).attr('rx', 10)
      $(comm.g.childNodes[0]).attr('ry', 5)
      // Version 6.8.1 : plus de try catch car removegElement a été sécurisé
      setTimeout(() => comm.removegElement(this.svgFigure), 6000) // on vire l'indication après 6s
    })
  })
}

MtgApp.prototype.lastIndication = function lastIndication () {
  this.indication(this.lastindch, '', true)
}

MtgApp.prototype.effaceIndication = function effaceIndication () {
  if ((this.comm !== null) && this.comm.g) {
    if (this.comm.g.parentNode === this.svgFigure) this.comm.removegElement(this.svgFigure)
  }
  this.comm = null
}
/**
 *
 * @param {string} toolName
 */
MtgApp.prototype.selectTool = function selectTool (toolName) {
  const name = 'outil' + toolName
  // Modification version 6.4.8 pour qu'on en puisse pas activer un outil si la figure a été bloquée
  // par un setAtive(fals)
  const activationPossible = (this.doc.isActive) || (toolName === 'Capt' || toolName === 'Protocole')
  if (activationPossible && this[name].activationValide()) {
    if (this[name].isSelectable) this.outilActif.deselect() // Par exemple les outils de zoom ne déscativent pas l'outil actif
    this[name].select()
    this.nameEditor.montre(false) // Des fois qu'un nom soit en cours d'édition
  }
}
/**
 * Active l'outil capture (désactive l'outil actif avant)
 */
MtgApp.prototype.activeOutilCapt = function activeOutilCapt () {
  // Modification version 6.3.3 pour pouvoir activer directement l'outil précédent.
  this.outilActif.deselect()
  this.outilCapt.select()
}
/**
 * Active l'outil précédemment sélectionné
 * @since version 6.3.3
 */
MtgApp.prototype.activeOutilPrec = function activeOutilPrec () {
  // Ne pas appeler si une boîte de dialogue est ouverte !
  if ((this.dlg.length === 0) && this.outilActifPrec.activationValide()) {
    this.outilActif.deselect()
    this.outilActifPrec.select()
  }
}

/**
 * Appelée dans la version electron par les raccourcis clavier
 * Active l'outil ainsi que son icône dans sa barre d'outils
 * @param {string} toolName
 */
MtgApp.prototype.activateTool = function activateTool (toolName) {
  const name = 'outil' + toolName
  if (this[name].activationValide()) {
    this.nameEditor.montre(false) // Des fois qu'un nom soit en cours d'édition
    this.outilActif.deselect() // Par exemple les outils de zoom ne déscativent pas l'outil actif
    this[name].select()
    let row = 0
    for (let i = 0; i < this.expandableBars.length; i++) {
      const b = this.expandableBars[i]
      if (b.tools.length !== 0) {
        if (b.tools.indexOf(toolName) !== -1) {
          b.activeToolName = toolName
          b.updateActiveIcon(row)
          break
        }
        row++
      }
    }

    // this.unFoldExpandableBars();
    // this.updateActiveIcons();
  }
}

/**
 * Fonction renvoyant true si l'objet el a déjà été créé
 * S'il s'agit d'un exercice de construction on autorise la création d'un objet déjà présent
 * dans la figure au début de l'exercice.
 * @param {CElementBase} el
 * @returns {boolean}
 */
MtgApp.prototype.existeDeja = function existeDeja (el) {
  const list = this.listePr
  let res = false
  for (let i = this.estExercice ? this.nbObjInit : 0; (i < list.longueur()) && !res; i++) {
    const elb = list.get(i)
    // Modifié par rapport à la version Java car on peut vérifier un éléméent déjà ajouté à la liste
    // if (!elb.estElementIntermediaire()) res = res || elb.confonduAvec(el);
    if ((elb.className === el.className) && !elb.estElementIntermediaire() && (elb !== el)) { res = res || elb.confonduAvec(el) }
  }
  return res
}

MtgApp.prototype.copyFig = function copyFig () {
  this.getBase64ImageData('png', function (data) {
    copy(data) // eslint-disable-line no-undef
    // copy est défini dans index.html
  }, this.pref_coefMult, 1)
  new AvertDlg(this, getStr('CopyOK'))
}

MtgApp.prototype.reInitConst = function reInitConst () {
  if (!this.estExercice) {
    this.listeSrcG.retireTout()
    this.listeSrcNG.retireTout()
    this.listeFinG.retireTout()
    this.listeFinNG.retireTout()
  }
}

/**
 * Fonction recréant un nouveau document
 */
MtgApp.prototype.resetDoc = function resetDoc () {
  // var doc = new CMathGraphDoc(this.id + "figurePanel", true, true); // Modifié version 6.3.0
  const doc = new CMathGraphDoc(this.id, true, true, this.decimalDot)
  this.doc = doc
  // Ligne suivante nécessaire pour que les macros d'activation et désactivation du mode trace activent ou non
  // l'icône correspondante
  doc.app = this
  this.listePr = doc.listePr
  doc.dimf = this.dimf
  this.listePourConst = this.listePourConstruction()
}

MtgApp.prototype.getResult = function getResult () {
  const ch = this.getBase64Code()
  let score
  if (this.estExercice) {
    if (this.validateAnswer()) score = 1
    else score = 0
  }
  return {
    fig: ch,
    score,
    level: this.levelIndex,
    dys: this.dys,
    nbObjInit: this.editionConstruction ? this.listePr.longueur() : this.nbObj
  }
}

/**
 * Fonction enlevant toutes les icones de la barre d'outil supérieure et les remplaçant par des icônes adaptées
 * au niveau d'utilisation en cours
 */
MtgApp.prototype.updateToolbar = function updateToolbar () {
  const doc = this.doc
  // Si le document a des choix d'outils c'est selon lui qu'on chosit les outils
  // sinon c'est avec le niveau choisi
  /* Rectification version 8.2 on n'a pas à tenir vompte di niveau pour le icônes de la barre horizontale et, de plus,
  si on avait coché la case aux outil permis et sélectionné aucun outil tous les outils de la barre horizontale
  étaient présents puisque, dans ce cas this.doc.listeIdMenus.length > 0 est false et dans le cas o*
  dans this.level, on a choisit que les outils de la liste sont les outils interdits
  if (this.doc.listeIdMenus.length > 0) {
    doc = this.doc
  } else {
    doc = this.level
  }
   */
  const toolBar = this.toolBar
  const estExercice = this.estExercice
  // On détruit les icônes précédentes
  while (toolBar.childNodes.length !== 0) toolBar.removeChild(toolBar.childNodes[0])
  //
  let indOutil = 0
  this.buttonLastInd = new ButtonTool(this, 'LastInd', 'top', false, indOutil++)
  this.buttonNew = new ButtonTool(this, 'New', 'top', false, this.newFig ? indOutil++ : -1)
  const b = !estExercice && window.File && window.FileReader && window.FileList && window.Blob
  this.buttonOpen = new ButtonTool(this, 'Open', 'top', false, this.open && b ? indOutil++ : -1)
  // this.buttonSave = new ButtonTool(this, "Save", "top", false, this.save && b ? indOutil++ : -1);
  this.buttonSave = new ButtonTool(this, 'Save', 'top', false, this.save ? indOutil++ : -1)
  this.buttonAnnuler = new ButtonTool(this, 'Annuler', 'top', false, indOutil++)
  this.buttonRefaire = new ButtonTool(this, 'Refaire', 'top', false, indOutil++)
  this.buttonSup = new ButtonTool(this, 'Sup', 'top', false, indOutil++)
  this.buttonModifObjGraph = new ButtonTool(this, 'ModifObjGraph', 'top', false, doc.toolDispo(this.outilModifObjGraph.toolIndex) ? indOutil++ : -1)
  this.buttonZoomPlus = new ButtonTool(this, 'ZoomPlus', 'top', false, doc.toolDispo(this.outilZoomPlus.toolIndex) ? indOutil++ : -1)
  this.buttonZoomMoins = new ButtonTool(this, 'ZoomMoins', 'top', false, doc.toolDispo(this.outilZoomMoins.toolIndex) ? indOutil++ : -1)
  this.buttonTranslationFigure = new ButtonTool(this, 'TranslationFigure', 'top', false, !this.translatable && doc.toolDispo(this.outilTranslationFigure.toolIndex) ? indOutil++ : -1)
  this.buttonModifObjNum = new ButtonTool(this, 'ModifObjNum', 'top', false, doc.toolDispo(this.outilModifObjNum.toolIndex) ? indOutil++ : -1)
  this.buttonPalette = new ButtonTool(this, 'Palette', 'top', false, doc.toolDispo(this.outilPalette.toolIndex) ? indOutil++ : -1)
  this.buttonCopierStyle = new ButtonTool(this, 'CopierStyle', 'top', false, doc.toolDispo(this.outilCopierStyle.toolIndex) ? indOutil++ : -1)
  this.buttonNommer = new ButtonTool(this, 'Nommer', 'top', false, indOutil++)
  this.buttonCaptNom = new ButtonTool(this, 'CaptNom', 'top', false, indOutil++)
  // En mode exercice on n'affiche pas les 3 boutons suivants mais on les crée pour qu'ils soient dispo dans le choix des options
  this.buttonModeTrace = new ButtonTool(this, 'ModeTrace', 'top', false, !estExercice && doc.toolDispo(this.outilModeTrace.toolIndex) ? indOutil++ : -1)
  this.buttonModePointsAuto = new ButtonTool(this, 'ModePointsAuto', 'top', false, !estExercice && doc.toolDispo(this.outilModePointsAuto.toolIndex) ? indOutil++ : -1)
  // this.buttonModePointsAuto.activate(this.pref_PointsAuto);
  this.buttonRecalculer = new ButtonTool(this, 'Recalculer', 'top', false, !estExercice && doc.toolDispo(this.outilRecalculer.toolIndex) ? indOutil++ : -1)
  // Fin
  this.buttonGomme = new ButtonTool(this, 'Gomme', 'top', false, doc.toolDispo(this.outilGomme.toolIndex) ? indOutil++ : -1)
  this.buttonRideau = new ButtonTool(this, 'Rideau', 'top', false, doc.toolDispo(this.outilRideau.toolIndex) ? indOutil++ : -1)
  this.buttonExecutionMacro = new ButtonTool(this, 'ExecutionMacro', 'top', false, doc.toolDispo(this.outilExecutionMacro.toolIndex) ? indOutil++ : -1)
  // En mode exercice on n'active pas le bouton d'exportation
  this.buttonExport = new ButtonTool(this, 'Export', 'top', false, !estExercice ? indOutil++ : -1)
  // this.buttonProtocole = new ButtonTool(this, "Protocole", "top", false, doc.toolDispo(this.outilProtocole.toolIndex) ? indOutil++ : -1);
  this.buttonProtocole = new ButtonTool(this, 'Protocole', 'top', false, this.modeBilan || doc.toolDispo(this.outilProtocole.toolIndex) ? indOutil++ : -1)
  // En mode exercice on n'active pas le bouton d'aide
  this.buttonHelp = new ButtonTool(this, 'Help', 'top', false, !estExercice ? indOutil++ : -1)
  this.buttonOptionsFig = new ButtonTool(this, 'OptionsFig', 'top', false, this.options ? indOutil++ : -1)
  // En mode exercice on n'affiche pas le bouton Outils supplémentaires
  this.buttonToggleToolsAdd = new ButtonTool(this, 'ToggleToolsAdd', 'top', false, !estExercice ? (doc.toolDispo(this.outilToggleToolsAdd.toolIndex) ? indOutil : -1) : -1)
}

/**
 * Fonction utilisée pour les corrections d'exercices de construction sous j3P et n'ajoutant que l'outil de protocole
 * dans la barre d'outils horizontale.
 */
MtgApp.prototype.updateToolbarForCor = function updateToolbarForCor () {
  const toolBar = this.toolBar
  // On détruit les icônes précédentes
  while (toolBar.childNodes.length !== 0) toolBar.removeChild(toolBar.childNodes[0])
  this.buttonProtocole = new ButtonTool(this, 'Protocole', 'top', false, 0)
}

/**
 * Fonction qui, si on a un exercice de construction, c'est-à dire une macro d'apparition d'initulé #Solution
 * et si le commentaire de cette macro commence par #Enonce, ce qui signifie que la figure contient un énoncé
 * qui doit être le dernier affichage de texte ou LaTeX de la figure initiale, renvoie un pointeur sur cet
 * affichage LaTeX.
 * Renvoie null s'il n'y en a pas.
 * On s'arrangera pour que le g element de cet affichage soit toujours le dernier à chaque action sur la figure.
 * @returns {CElementBase|null}
 */
MtgApp.prototype.getEnonce = function getEnonce () {
  if (this.estExercice) {
    const mac = this.macroPourConst
    if (mac.commentaireMacro.indexOf('#Enonce') !== -1) {
      const list = this.listePr
      for (let i = list.longueur() - 1; i >= 0; i--) {
        const el = list.get(i)
        if (el.estDeNature(Nat.or(NatObj.NCommentaire, NatObj.NLatex))) {
          return el
        }
      }
    }
  }
  return null
}

/**
 * Fonction utilisée pour la correction des exerices de construction pour que le prof puisse capturer un point mobile
 * et voir comment la figure a été faite par l'élève
 */
MtgApp.prototype.activateForCor = function activateForCor () {
  this.activeOutilCapt()
  this.doc.setIdMenus(this, false, [33023, 32892], 3)
  this.updateToolsToolBar()
  this.updateToolbarForCor()
}

/**
 * Fonction qui, pour un exercice de construction, renvoie le nombre d'objets qu'a créés l'élève.
 * @returns {number}
 */
MtgApp.prototype.getNbObjConst = function getNbObjConst () {
  if (this.estExercice) {
    const list = this.listePr
    let compt = 0
    for (let i = this.nbObjInit; i < list.longueur(); i++) {
      const el = list.get(i)
      if (el.estDeNature(NatObj.NTtObj) && !el.estElementIntermediaire() && !el.masque) compt++
    }
    return compt
  }
  return 0
}

/**
 * Désactive une éventuelle macro en cours d'exécution
 * @since version 6.4
 */
MtgApp.prototype.termineMacroEnCours = function termineMacroEnCours () {
  const list = this.listePr
  const mac = list.macroEnCours
  if (mac !== null) {
    const macEnCours = mac.macroEnCours()
    const doc = this.doc
    if (macEnCours !== null) {
      list.terminerMacros = true // Utilisé dans CMacro.passageMacroSuivante
      macEnCours.termineAction(this.svgFigure, this.dimf, doc.couleurFond)
      macEnCours.executionEnCours = false
      if (mac.macroLanceuse !== null) mac.macroLanceuse.termineAction(this.svgFigure, this.dimf, doc.couleurFond)
    }
    list.macroEnCours = null
  }
}

/**
 * Fonction retirant les éventuels petits div associés à des variables en bas et à droite de la figure
 */
MtgApp.prototype.removePaneVariables = function removePaneVariables () {
  this.listePr.removePaneVariables(this.svg.parentNode)
}

/**
 * Renvoie la liste CListeObjets contenant les objets du document.
 * @returns {CListeObjets}
 */
MtgApp.prototype.getList = function getList () {
  return this.doc.listePr
}
/**
 * Fonction destinée à mettre une fonction sur la pile des appels.
 * A utiliser de façon externe pour être sûr qu'une action soit faite après les affichages en cours
 * @param {function} f
 */
MtgApp.prototype.addFunctionToQueue = function addFunctionToQueue (f) {
  if (typeof f !== 'function') console.warn('Appel de addQueue avec un paramètre qui n’est pas une fonction')
  else {
    addQueue(f)
  }
}

MtgApp.prototype.setNewfigWithUnity = function setNewFigWithUnity (uniteAngle) {
  const list = this.listePr
  list.retireTout()
  this.retireTout()
  this.resetDoc()
  this.doc.listePr.uniteAngle = uniteAngle
  this.prepareTracesEtImageFond()
  this.initAvecLongueurUnite(uniteAngle)
  this.updateToolsToolBar()
  this.creeCommentaireDesignation()
  this.calculateAndDisplay(false)
  if (this.cadre !== null) this.createCadre(this.widthCadre, this.heightCadre)
  this.reInitConst() // Pour réinitialiser une éventuelle construction en cours
  if (this.electron) {
    this.gestionnaire.initialise()
    resetDocument() // eslint-disable-line no-undef
    // resetDocument est défini dans index.html
  } else this.gestionnaire.enregistreFigureEnCours('NewFig')
  // On met à jour les icônes de la barre horizontale
  this.updateToolbar()
}

MtgApp.prototype.ajouteCourbeSurR = function (repere, fonction, nomPointLieAxe, nomCalculAbscisse,
  nomCalculOrdonnee, nombrePoints, pointLieCache, pointCourbeCache, gestionAutoDiscontinuite) {
  const list = this.listePr
  // Le dernier paramètre n'étant pas null list.ajouteCourbeSurR tiendra compte qu'on est dans une MTgApp
  list.ajouteCourbeSurR(repere, fonction, nomPointLieAxe, nomCalculAbscisse,
    nomCalculOrdonnee, nombrePoints, pointLieCache, pointCourbeCache, gestionAutoDiscontinuite,
    this.getCouleur(), this.getStyleTrait(), this.getTaillePoliceNom(), this)
}

MtgApp.prototype.ajouteCourbeSurab = function (repere, fonction, nomPointLieAxe, nomCalculAbscisse,
  nomCalculOrdonnee, nombrePoints, pointLieCache, pointCourbeCache, gestionAutoDiscontinuite, a, b) {
  const list = this.listePr
  // Le dernier paramètre n'étant pas null list.ajouteCourbeSurR tiendra compte qu'on est dans une MTgApp
  list.ajouteCourbeSurab(repere, fonction, nomPointLieAxe, nomCalculAbscisse,
    nomCalculOrdonnee, nombrePoints, pointLieCache, pointCourbeCache, gestionAutoDiscontinuite, a, b,
    this.getCouleur(), this.getStyleTrait(), this.getTaillePoliceNom(), this)
}

MtgApp.prototype.ajouteCourbeSuraInf = function (repere, fonction, nomPointLieAxe, nomCalculAbscisse,
  nomCalculOrdonnee, nombrePoints, pointLieCache, pointCourbeCache, gestionAutoDiscontinuite, a) {
  const list = this.listePr
  // Le dernier paramètre n'étant pas null list.ajouteCourbeSurR tiendra compte qu'on est dans une MTgApp
  list.ajouteCourbeSuraInf(repere, fonction, nomPointLieAxe, nomCalculAbscisse,
    nomCalculOrdonnee, nombrePoints, pointLieCache, pointCourbeCache, gestionAutoDiscontinuite, a,
    this.getCouleur(), this.getStyleTrait(), this.getTaillePoliceNom(), this)
}

MtgApp.prototype.ajouteCourbeSurInfa = function (repere, fonction, nomPointLieAxe, nomCalculAbscisse,
  nomCalculOrdonnee, nombrePoints, pointLieCache, pointCourbeCache, gestionAutoDiscontinuite, a) {
  const list = this.listePr
  // Le dernier paramètre n'étant pas null list.ajouteCourbeSurR tiendra compte qu'on est dans une MTgApp
  list.ajouteCourbeSurInfa(repere, fonction, nomPointLieAxe, nomCalculAbscisse,
    nomCalculOrdonnee, nombrePoints, pointLieCache, pointCourbeCache, gestionAutoDiscontinuite, a,
    this.getCouleur(), this.getStyleTrait(), this.getTaillePoliceNom(), this)
}