objets/CCommentaire.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 { traiteAccents } from 'src/kernel/kernel'
import NatObj from '../types/NatObj'
import NatCal from '../types/NatCal'
import Pointeur from '../types/Pointeur'
import Nat from '../types/Nat'
import CAffLiePt from './CAffLiePt'
import CListeObjets from './CListeObjets'

export default CCommentaire

/**
 * Classe représentant un affichage de texte sur la figure.
 * Si le texte est encadré de $ ce sera un affichage LaTeX.
 * @constructor
 * @extends CAffLiePt
 * @param {CListeObjets} listeProprietaire  La liste propriétaire.
 * @param {CImplementationProto} impProto  null ou la construction propriétaire.
 * @param {boolean} estElementFinal  true si l'objet est un élément final de construction.
 * @param {Color} couleur  La couleur d'éciture de l'éditeur (et du cadre éventuel).
 * @param {number} xNom  L'abscisse d'affichage de l'éditeur
 * @param {number} yNom  L'ordonnée d'affichage de l'éditeur
 * @param {number} decX  Décalage horizontal du nom
 * @param {number} decY  Décalage vertical du nom
 * @param {boolean} masque  true si l'éditeur est masqué
 * @param {CPt} pointLie  null ou pointe sur un point auquel l'affichage est lié.
 * @param {number} taillePolice  Taille de police à utiliser
 * @param {number} encadrement  entier pour le style d'encadrement (0, 1 ou 2, voir CAffLiePt).
 * @param {boolean} effacementFond  Passer true pour rendre le fond transparent
 * @param {Color} couleurFond  La couleur de fond
 * @param {number} alignementHorizontal  0 pour alignement gauche, 1 pour centré, 2 pour droite.
 * @param {number} alignementVertical  0 pour alignement vers le haut, 1 pour centré, 2 pour bas.
 * @param {string} chaineCommentaire  Le texte à afficher. S'il est entouré de $ ce sera un affichage LaTeX.
 * @param {CValeurAngle} angText  L'angle du texte par rapport à l'horizontale
 * @param {boolean} fixed true si l'affichage est punaisé et ne peut pas être capturé à la souris
 * @returns {CCommentaire}
 */
function CCommentaire (listeProprietaire, impProto, estElementFinal, couleur,
  xNom, yNom, decX, decY, masque, pointLie, taillePolice, encadrement, effacementFond,
  couleurFond, alignementHorizontal, alignementVertical, chaineCommentaire,
  angText, fixed) {
  if (arguments.length !== 0) {
    if (arguments.length === 1) CAffLiePt.call(this, listeProprietaire)
    else {
      CAffLiePt.call(this, listeProprietaire, impProto, estElementFinal, couleur, xNom, yNom,
        decX, decY, masque, pointLie, taillePolice, encadrement, effacementFond, couleurFond, alignementHorizontal,
        alignementVertical, angText, fixed)
      this.chaineCommentaire = chaineCommentaire
    }
    this.listeValeursDynamiquesUtilisees = new CListeObjets(listeProprietaire.uniteAngle, listeProprietaire.pointeurLongueurUnite)
  }
}
CCommentaire.prototype = new CAffLiePt()
CCommentaire.prototype.constructor = CCommentaire
CCommentaire.prototype.superClass = 'CAffLiePt'
CCommentaire.prototype.className = 'CCommentaire'

CCommentaire.prototype.numeroVersion = function () {
  return 2
}

CCommentaire.prototype.getClone = function (listeSource, listeCible) {
  const ind1 = listeSource.indexOf(this.pointLie)
  const ind2 = listeSource.indexOf(this.impProto)
  const angTextClone = this.angText.getClone(listeSource, listeCible)
  const ptelb = new CCommentaire(listeCible, listeCible.get(ind2, 'CImplementationProto'),
    this.estElementFinal, this.couleur, this.xNom, this.yNom, this.decX, this.decY,
    this.masque, listeCible.get(ind1, 'CPt'), this.taillePolice, this.encadrement,
    this.effacementFond, this.couleurFond, this.alignementHorizontal, this.alignementVertical, this.chaineCommentaire,
    angTextClone, this.fixed)
  ptelb.listeValeursDynamiquesUtilisees =
    new CListeObjets(listeCible.uniteAngle, listeCible.pointeurLongueurUnite)
  if (listeCible.className !== 'CPrototype') ptelb.determineDependances()
  // Ligne suivante nécessaire car utilsié pour les exportations tikz
  ptelb.id = this.id
  return ptelb
}

CCommentaire.prototype.getNature = function () {
  return NatObj.NCommentaire
}

CCommentaire.prototype.metAJour = function () {
  this.determineDependances()
}

CCommentaire.prototype.donneChaine = function (chaine) {
  this.chaineCommentaire = chaine
}
/**
 * Fonction plaçant l'affichage de texte aux coordonnées (x,y)
 * @param {number} x
 * @param {number} y
 * @returns {void}
 */
CCommentaire.prototype.placeEn = function (x, y) {
  this.xNom = x
  this.yNom = y
}

CCommentaire.prototype.rendChaineAffichage = function () {
  return this.chaineAffichage
}

CCommentaire.prototype.ajouteAntecedents = function (liste) {
  if (this.pointLie !== null) liste.add(this.pointLie)
}
/**
 * Fonction renvoyant -1 si this.chaineCommentaire ne contient pas de soous-chaîne
 * #Val ou \For et sinon renvoie l'indice du premier des deux rencontrés, formule.getValue()
 * renvoyant true si c'est un #For qui a été trouvé et false sinon.
 * @param{number} indDeb L'indice du début de la recherche
 * @param{Pointeur} formule
 **/
CCommentaire.prototype.indicePremierValOuForm = function (indDeb, formule) {
  const indVal = this.chaineCommentaire.indexOf('#Val(', indDeb)
  const indFor = this.chaineCommentaire.indexOf('#For(', indDeb)
  if ((indVal === -1) && (indFor === -1)) return -1
  if (((indVal > indFor) || (indVal === -1)) && (indFor !== -1)) {
    formule.setValue(true)
    return indFor
  } else {
    formule.setValue(false)
    return indVal
  }
}
/**
 * Fonction faisant en sorte que this.listeValeursDynamiquesUtilisees contienne
 * la liste de tous les objets dont dépend le coomentaire par des appels
 * à #Val() ou #For()
 * @param {number} indiceReel  Si présent c'est qu'on est en train d'utiliser un commentaire
 * dans une boîte de dialogue d'aperçu et c'est alors l'indice du vrai commentaire qu'on est en train d'éditer
 * @returns {void}
 */
CCommentaire.prototype.determineDependances = function (indiceReel = -1) {
  let jdeb, j, indparf, indsaut, ch, nomvaleur, indicecommentaire, pValeur
  const bformule = new Pointeur(true)
  this.listeValeursDynamiquesUtilisees.retireTout()
  // On examine la chaine à la recherche de #Val(
  const len = this.chaineCommentaire.length
  j = 0
  if ((this.chaineCommentaire.indexOf('#Val(', j) === -1) && (this.chaineCommentaire.indexOf('#For(', j) === -1)) return
  while (((jdeb = this.indicePremierValOuForm(j, bformule)) !== -1) && j < len) {
    // On recherche la parenthèse fermante correspondante
    indparf = this.chaineCommentaire.indexOf(')', jdeb + 5)
    if (indparf === -1) j = len + 1
    else {
      indsaut = this.chaineCommentaire.indexOf('\n', jdeb + 5)
      if ((indsaut !== -1) && (indsaut < indparf))j = indsaut + 1
      else {
        // On crée une chaine formé de ce qu'il y a entre #Val( et la parenthèse fermante
        ch = this.chaineCommentaire.substring(jdeb + 5, indparf)
        // On retire les éventuels espaces de cete chaine
        ch = ch.trim()
        // On sépare cette chaine avec les virgules
        const st = ch.split(/\s*,\s*/)
        if (st.length === 0) j = indparf + 1
        else {
          nomvaleur = st[0]
          // On recherche si c'est bien le nom d'une valeur valide
          // Il ne faut pas que la valeur ait été définie après l'affichage de valeur
          if (indiceReel !== -1) indicecommentaire = indiceReel
          else indicecommentaire = this.listeProprietaire.indexOf(this)
          // Très important : quand determineDependances est appelé
          // par getClone() de CCommentaire, le commentaire n'a pas encoré été rajouté
          // à la liste clone et ainsi indiceCommentaire renvoie -1.
          // dans ce cas, indiceCommentaire doit valoir le nombre d'éléments actuels
          // de la liste clone qui le possède
          if (indicecommentaire === -1) indicecommentaire = this.listeProprietaire.longueur() - 1
          pValeur = this.pointeur(NatCal.NTteValPourComDyn, nomvaleur, indicecommentaire)
          if (pValeur === null) j = indparf + 1
          else {
            /** Corrigé version mtgApp
            natc = pValeur.getNatureCalcul();
            if ((bformule.getValue() && (Nat.and(natc, NatCal.NCalcRouC |
                NatCal.NTteFoncRouC))) || !bformule.getValue())
             */
            if ((bformule.getValue() && pValeur.estDeNatureCalcul(Nat.or(NatCal.NCalcRouC,
              NatCal.NTteFoncRouC))) || !bformule.getValue()) { this.listeValeursDynamiquesUtilisees.add(pValeur) }
            // Si la valeur n'existe pas, on ne l'inclut pas dans la chaîne à afficher
            j = indparf + 1
          }
        }
      }
    }
  }
}

CCommentaire.prototype.positionne = function (infoRandom, dimf) {
  let jdeb, j, indparf, indsaut, ch, st, nomvaleur, indfin, pValeur, natc, chnombre, chnbdec, nbdecimales, chplus
  const bformule = new Pointeur(true)
  // On examine la chaine à la recherche de #Val(
  const len = this.chaineCommentaire.length
  let bufferAffichage = ''
  j = 0
  if ((this.chaineCommentaire.indexOf('#Val(', j) === -1) && (this.chaineCommentaire.indexOf('#For(', j) === -1)) {
    this.chaineAffichage = this.chaineCommentaire
    CAffLiePt.prototype.positionne.call(this, infoRandom, dimf)
    return
  }
  while (((jdeb = this.indicePremierValOuForm(j, bformule)) !== -1) && j < len) {
    bufferAffichage += this.chaineCommentaire.substring(j, jdeb)
    // On recherche la parenthèse fermante correspondante
    indparf = this.chaineCommentaire.indexOf(')', jdeb + 5)
    if (indparf === -1) {
      bufferAffichage += this.chaineCommentaire.substring(jdeb, len)
      j = len + 1
    } else {
      // On recherche aussi un éventuel retour à la ligne
      indsaut = this.chaineCommentaire.indexOf('\n', jdeb + 5)
      if ((indsaut !== -1) && (indsaut < indparf)) {
        bufferAffichage += this.chaineCommentaire.substring(jdeb, indsaut + 2)
        j = indsaut + 1
      } else {
        // On crée une chaine formé de ce qu'il y a entre #Val( et la parenthèse fermante
        ch = this.chaineCommentaire.substring(jdeb + 5, indparf)
        // On retire les éventuels espaces de cete chaine
        ch = ch.trim()
        // On sépare cette chaine avec les virgules
        st = ch.split(/\s*,\s*/)
        if (st.length === 0) { // Pas de virgule
          bufferAffichage += this.chaineCommentaire.substring(jdeb, indparf + 1)
          j = indparf + 1
        } else {
          nomvaleur = st[0]
          // On recherche si c'est bien le nom d'une valeur valide
          // Il ne faut pas que la valeur ait été définie après l'affichage de valeur
          // Si c'est le nom d'une valeur utilisée, elle est comprise dans la
          // liste listeValeursDynamiquesUtilisees
          indfin = this.listeValeursDynamiquesUtilisees.longueur() - 1
          pValeur = this.listeValeursDynamiquesUtilisees.pointeurParNatureCalcul(NatCal.NTteValPourComDyn,
            nomvaleur, indfin)
          if (pValeur === null) {
            bufferAffichage += this.chaineCommentaire.substring(jdeb, indparf + 1)
            j = indparf + 1
          } else {
            // Si la valeur n'existe pas, on ne l'inclut pas dans la chaîne à afficher
            if (pValeur.existe) {
              if (bformule.getValue()) {
              // L'affichage de formule n'a de sens que pour un calcul ou une fonction
                natc = pValeur.getNatureCalcul()
                if (Nat.and(natc, NatCal.NCalcRouC | NatCal.NTteFoncRouC)) { bufferAffichage += pValeur.chaineCalcul }
              } else {
                chnombre = ''
                if (st.length >= 2) {
                  chnbdec = st[1]
                  if (chnbdec === '+') {
                    chnombre = pValeur.rendChaineValeurPourCommentaire(2)
                    if (chnombre.indexOf('-') !== '0') chnombre = '+ ' + chnombre
                  } else {
                    // nbdec = parseInt(chnbdec);
                    nbdecimales = parseInt(chnbdec)
                    chnombre = pValeur.rendChaineValeurPourCommentaire(nbdecimales)
                    if (st.length >= 3) {
                      chplus = st[2]
                      if (chplus === '+') if (chnombre.indexOf('-') !== 0) chnombre = '+ ' + chnombre
                    }
                  }
                } else {
                  // Si le nombre de décimales n'est pas précisé, on en met deux par défaut
                  chnombre = pValeur.rendChaineValeurPourCommentaire(2)
                }
                bufferAffichage += chnombre
              }
            }
            j = indparf + 1
          }
        }
      }
    }
  }
  if (j < len) bufferAffichage += this.chaineCommentaire.substring(j, len)
  this.chaineAffichage = bufferAffichage
  CAffLiePt.prototype.positionne.call(this, infoRandom, dimf)
}

CCommentaire.prototype.depDe = function (p) {
  if (this.elementTestePourDependDe === p) return this.dependDeElementTeste
  return this.memDep(CAffLiePt.prototype.depDe.call(this, p) ||
    this.listeValeursDynamiquesUtilisees.depDe(p))
}

CCommentaire.prototype.dependDePourBoucle = function (p) {
  return CAffLiePt.prototype.dependDePourBoucle.call(this, p) ||
    this.listeValeursDynamiquesUtilisees.dependDePourBoucle(p)
}
/**
 * Fonction renvoyant un pointeur sur l'élément de type calcul de nature nat
 * et ayant pour nom nom si le commentaire utilise ce calcul pour un affichage
 * dynamique de valeur. Renvoie null s'il ne l'utilise pas.
 * @param {Nat} nat
 * @param {string} nom
 * @param {number} indiceMaxi
 * @returns {CValDyn}
 */
CCommentaire.prototype.pointeur = function (nat, nom, indiceMaxi) {
  let i, elb, j
  // Si le CLatex est un objet final de construction on recherhe d'abord la valeur
  // dans les objets intermédiaires ou finaux de cette construction
  // Important : on ne peut pas utiliser impProto car lors de getClone appelé par CImplementationProto.implemente
  // le membre impProto n'a pas encore été établi
  // Modifié version 4.5 : Aussi à faire pour les objets intermédiaires car un CLatex ou un CCommentaire peut générer un lieu d'objets.
  if (this.estElementFinal || this.estElementIntermediaire()) {
    i = indiceMaxi
    while (i >= 0) {
      elb = this.listeProprietaire.get(i)
      if (elb.estElementFinal || elb.estElementIntermediaire()) {
        if (elb.estDeNatureCalcul(nat)) {
          if (elb.getNom() === nom) return elb
          else i--
        } else i--
      } else break
    }
  }
  // Sinon on recherche dans tous les objets qui ne sont pas de objets intermédiaires de construction
  i = 0
  j = 0
  while (i <= indiceMaxi) {
    elb = this.listeProprietaire.get(j++)
    // j++;
    if (!elb.estElementIntermediaire() && elb.estDeNatureCalcul(nat)) {
      if (elb.getNom() === nom) return elb
      else i++
    } else i++
  }
  return null
}

CCommentaire.prototype.chaineDesignation = function () {
  return 'desCommentaire'
}

CCommentaire.prototype.read = function (inps, list) {
  CAffLiePt.prototype.read.call(this, inps, list)
  this.chaineCommentaire = traiteAccents(inps.readUTF())
  // Ajout version 5.0 pour traiter les $$ et ## représentés par un seul caractère
  this.chaineCommentaire = this.chaineCommentaire.replace(/\$\$/g, '$')
  // this.chaineCommentaire = this.chaineCommentaire.replace(/##/g,"#");
  //
  this.listeValeursDynamiquesUtilisees = new CListeObjets(list.uniteAngle, list.pointeurLongueurUnite)
  if (this.listeProprietaire.className !== 'CPrototype') this.determineDependances()
  // Ajout version 6.7.2 pour que le player mtg32 sache si la liste chargée nécessite MathJax ou non
  const ch = this.chaineCommentaire
  if (this.className === 'CCommentaire' && ch.charAt(0) === '$' && ch.charAt(ch.length - 1) === '$') {
    this.listeProprietaire.useLatex = true
  }
}

CCommentaire.prototype.write = function (oups, list) {
  CAffLiePt.prototype.write.call(this, oups, list)
  oups.writeUTF(this.chaineCommentaire)
}
/**
 * Fonction remmplaçant les appels dynamiques de la valeur nommée ancienNom par nouveauNom
 * @param {string} ancienNom
 * @param {string} nouveauNom
 * @returns {boolean}
 */
CCommentaire.prototype.remplaceNomValeurDynamique = function (ancienNom, nouveauNom) {
  // On supprime d'abord tous les caractères espace suivant une ( ou une virgule ou précédant une parenthèse
  let buffer = this.chaineCommentaire
  /* Inutile avec des expressions régulières
  while((ind = buffer.indexOf("#Val( ")) !== - 1) {
    buffer.deleteCharAt(ind+5);
  }
  while((ind = buffer.indexOf("#For( ")) !== - 1) {
    buffer.deleteCharAt(ind+8);
  }
  */
  let ch1 = '#Val\\([ ]*' + ancienNom + '[ ]*\\)'
  let ch2 = '#Val(' + nouveauNom + ')'
  buffer = buffer.replace(new RegExp(ch1, 'g'), ch2)
  ch1 = '#Val\\([ ]*' + ancienNom + '[ ]*\\,'
  ch2 = '#Val(' + nouveauNom + ','
  buffer = buffer.replace(new RegExp(ch1, 'g'), ch2)
  ch1 = '#For\\([ ]*' + ancienNom + '[ ]*\\)'
  ch2 = '#For(' + nouveauNom + ')'
  buffer = buffer.replace(new RegExp(ch1, 'g'), ch2)
  ch1 = '#For\\([ ]*' + ancienNom + '[ ]*\\,'
  ch2 = '#For(' + nouveauNom + ','
  buffer = buffer.replace(new RegExp(ch1, 'g'), ch2)
  const modif = (buffer !== this.chaineCommentaire)
  this.chaineCommentaire = buffer
  return modif
}