/*
* 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 NatObj from '../types/NatObj'
import CElementBase from './CElementBase'
import Color from '../types/Color'
import Fonte from '../types/Fonte'
import { cens } from '../kernel/kernel'
import Dimf from 'src/types/Dimf'
import addQueue from 'src/kernel/addQueue'
export default CElementGraphique
/**
* @constructor
* @extends CElementBase
* Objet ancêtre de tous les objets graphiques.
* @param {CListeObjets} listeProprietaire La liste propriétaire de l'objet
* @param {CImplementationProto} impProto null ou pointe sur la construction propriétaire de l'objet
* @param {boolean} estElementFinal true si l'objet est un objet inal de construction
* @param {Color} couleur La couleur de l'objet
* @param {boolean} nomMasque true si le nom de l'objet est masqué
* @param {number} decX Le décalage horizontal du nom
* @param {number} decY Le décalage vertical du nom
* @param {boolean} masque true si l'objet est masque
* @param {string} nom le nom éventuel de l'objet
* @param {number} tailleNom l'indice donnant la taille du nom
* @returns {void}
*/
function CElementGraphique (listeProprietaire, impProto, estElementFinal, couleur, nomMasque,
decX, decY, masque, nom, tailleNom) {
if (arguments.length === 0) return // Ajout version 4.9.9.4 pour constructeur
if (arguments.length === 1) {
CElementBase.call(this, listeProprietaire)
this.nom = '' // Sert pour le protocole de la figure
} else {
CElementBase.call(this, listeProprietaire, impProto, estElementFinal)
this.couleur = couleur
this.nomMasque = nomMasque
this.decX = decX
this.decY = decY
this.masque = masque
this.nom = nom
this.tailleNom = tailleNom
// Rajout version 4.9.9.4
if (couleur !== null) this.color = couleur.rgb() // Certains objets inclus dans d'autres
// peuvent ne pas avoir de couleur quand ils sont créés.
}
this.id = '' // Spécial JavaScript
this.g = null // Idem
this.gname = null
// Ajout version 5.0.4 :Sert pour les objets dupliqués de CLatex
this.hasDuplicate = false
// Ajout version 6.5.2 : Dans le player on peut donner à l'utilisateur la possibilité d'associer des gestionnaires d'événements
// sur des éléments svg de la figure
this.pointerevents = 'none'
// Version 6.6.0 : On rajoute un tag à chaque élément (chaîne vide par défaut).
this.tag = ''
}
CElementGraphique.prototype = new CElementBase()
CElementGraphique.prototype.constructor = CElementGraphique
CElementGraphique.prototype.superClass = 'CElementBase'
CElementGraphique.prototype.className = 'CElementGraphique'
/**
* Fonction renvoyant un clone de l'objet
* @returns {CElementGraphique}
*/
CElementGraphique.prototype.creeClone = function () {
return this.getClone(this.listeProprietaire, this.listeProprietaire)
}
/**
* Fonction faisant de this un objet de mêmes caractéristiques que ptel
* ptel doit être un élément du même type que this
* @param {CElementBase} ptel
* @returns {void}
*/
CElementGraphique.prototype.setClone = function (ptel) {
CElementBase.prototype.setClone.call(this, ptel)
this.xNom = ptel.xNom
this.yNom = ptel.yNom
this.decX = ptel.decX
this.decY = ptel.decY
this.masque = ptel.masque
}
/**
* Ajout version 5.2 (numéro de version 16). Renverra true si l'objet possède un nom qui doit être enregistré dans le flux
* @returns {boolean}
*/
CElementGraphique.prototype.hasName = function () {
return false
}
/**
* Ajout version 5.2 (numéro de version 16). Renverra true si l'objet possède deux éléments decX et decY doit être enregistré dans le flux
* Sera redéfini à true dans CAffLiePt
* @returns {boolean}
*/
CElementGraphique.prototype.hasDec = function () {
return this.hasName()
}
/**
* Fonction rendant l'objet masqué
* @returns {void}
*/
CElementGraphique.prototype.cache = function () {
this.masque = true
}
/**
* Fonction rendant l'objet visible
* @returns {void}
*/
CElementGraphique.prototype.montre = function () {
this.masque = false
}
/**
* Fonction donnant à l'objet le nom nomInit
* @param {string} nomInit
* @returns {void}
*/
CElementGraphique.prototype.donneNom = function (nomInit) {
this.nom = nomInit
}
/**
* Fonction plaçant le point ux coordonnées (x,y)
* @param {number} xn
* @param {number} yn
* @returns {void}
*/
CElementGraphique.prototype.placeNom = function (xn, yn) {
this.xNom = xn
this.yNom = yn
}
/**
* Decale le nom de l'objet de (decXn, decYn)
* @param {number} decXn
* @param {number} decYn
* @returns {void}
*/
CElementGraphique.prototype.decaleNom = function (decXn, decYn) {
this.decX = decXn
this.decY = decYn
}
// A revoir svg
/**
* Fonction créant un élément svg représentant le nom de l'objet
* @returns {SVGTextElement}
*/
CElementGraphique.prototype.createName = function () {
let indPremierChiffre
const nom = this.nom
const taille = this.tailleNom // Modifié version 5.0
const y = this.yNom + this.decY + taille
const text = cens('text', {
x: this.xNom + this.decX,
y,
style: 'text-anchor : left;' + 'fill:' + this.couleur.rgb() + ';' + 'font-size:' + taille + 'px;',
id: this.id + 'name',
'pointer-events': 'none' // Ne marche pas avec chrome
})
// disableSelection(text);
// On recherche un éventuel indice
for (indPremierChiffre = 0; indPremierChiffre < nom.length; indPremierChiffre++) {
if (Fonte.chiffre(nom.charAt(indPremierChiffre))) break
}
if (indPremierChiffre === nom.length) {
// Pas de chifres à écrire en exposant, on écrit tout d'un bloc
text.innerHTML = nom
} else {
const parFinale = nom.endsWith(')')
const deb = nom.substring(0, indPremierChiffre)
const indfin = parFinale ? nom.length - 1 : nom.length
const span1 = cens('tspan')
const cont1 = document.createTextNode(deb)
span1.appendChild(cont1)
text.appendChild(span1)
const indicesPresents = indPremierChiffre < nom.length
const decalageIndices = Math.floor(taille / 3)
if (indicesPresents) {
const fin = nom.substring(indPremierChiffre, indfin)
const span2 = cens('tspan', {
dy: decalageIndices
})
span2.style.fontSize = Math.round(2 * taille / 3) + 'px'
const cont2 = document.createTextNode(fin)
span2.appendChild(cont2)
text.appendChild(span2)
}
if (parFinale) {
const spanfin = cens('tspan', {
dy: indicesPresents ? -decalageIndices : 0
})
const contfin = document.createTextNode(')')
spanfin.appendChild(contfin)
text.appendChild(spanfin)
}
}
// Ajout version mtgApp car la version mtgApp a besoin de connaître les dimensions d'affichage du nom
if (this.createNameAdd) this.createNameAdd(text)
//
return text
}
/**
* Fonction ajoutant au svg svg le texte element représentant le nom de l'objet
* @param {SVGElement} svg
* @returns {void}
*/
CElementGraphique.prototype.afficheNom = function (svg) {
this.gname = this.createName()
svg.appendChild(this.gname)
}
CElementGraphique.prototype.updateNamePosition = function () {
this.gname.setAttribute('x', this.xNom + this.decX)
this.gname.setAttribute('y', this.yNom + this.decY + this.tailleNom)
// Les lignes suivantes ne concernent que la version mtgApp
if (this.rectName) {
const bb = this.gname.getBBox()
this.rectName.x = bb.x
this.rectName.y = bb.y
this.rectName.width = bb.width
this.rectName.height = bb.height
}
}
/**
* Fonction mettant à jour le sgv text element représentant le nom de l'objet
* @param {SVGElement} svg
* @param {boolean} masquage
* @returns {void}
*/
CElementGraphique.prototype.updateName = function (svg, masquage) {
if (this.nom === '') {
if (this.gname !== null) {
svg.removeChild(this.gname)
this.gname = null
}
} else {
const gn = this.createName()
if (this.gname !== null) {
try {
svg.replaceChild(gn, this.gname)
} catch (e) { // Rajouté suite à rapport BusNag
this.g.parentNode.insertBefore(gn, this.g.nextSibling)
}
} else {
// Ne pas utiliser ici kernelAdd qui utilise jquery alors que le player ne l'utilise pas
// kernelAdd.insertAfter(gn, this.g)
this.g.parentNode.insertBefore(gn, this.g.nextSibling)
}
this.gname = gn
if (this.masque || (masquage && this.nomMasque)) this.gname.setAttribute('visibility', 'hidden')
}
/*
var oldgname = this.gname;
var newgname = this.createName();
svg.replaceChild(newgname, oldgname);
this.gname = newgname;
*/
/**
* Ne marche pas pour une raison que j'ignore. Après le premier affichage, le suivant se décale
var taille = Fonte.taille(this.tailleNom);
g.removeAttribute("x");
g.removeAttribute("y");
g.setAttribute("x", this.x);
g.setAttribute("y", this.y + this.decY + taille);
*/
}
/**
* Fonction donnant la couleur coul à l'objet
* @param {Color} coul
* @returns {void}
*/
CElementGraphique.prototype.donneCouleur = function (coul) {
this.couleur = coul
this.color = coul.rgb() // Ajout version 4.9.9.5
}
/**
* Fonction renvoyant la distance entre this et le point de coordonnnées (xp,yp).
* Si masquage est à true, renvoie la distance même si le point est masqué.
* A redéfinir pour les descendants
* @param {number} xp
* @param {number} yp
* @param {boolean} masquage
* @returns {number}
*/
CElementGraphique.prototype.distancePoint = function (xp, yp, masquage) {
return -1
}
/**
* Fonction renvoyant la distance entre this et le point de coordonnnées (xp,yp)
* pour les objest de type surface.
* Si masquage est à true, renvoie la distance même si le point est masqué.
* A redéfinir pour les descendants
* @param {number} xp
* @param {number} yp
* @param {boolean} masquage
* @returns {number}
*/
CElementGraphique.prototype.distancePointPourSurface = function (xp, yp, masquage) {
return -1
}
/**
* Fonction renvoyant, pour un point lié à un objet, l'abscisse maximale relative à cet objet
* @returns {number}
*/
CElementGraphique.prototype.abscisseMaximale = function () {
return 1
}
/**
* Fonction renvoyant, pour un point lié à un objet, l'abscisse minimale relative à cet objet
* @returns {number}
*/
CElementGraphique.prototype.abscisseMinimale = function () {
return 0
}
/**
* Fonction ajoutant à svg le svg element représentant l'objet (via trace())
* et donnant à cet élément l'attribut visible ou masqué suivant que
* l'objet est masqué ou non
* @param {SVGElement} svg Le svg dans lequel est tracée la figure
* @param {boolean} masquage true si on cache les objets cachés
* @param {Color} couleurFond
* @returns {void}
*/
CElementGraphique.prototype.affiche = function (svg, masquage, couleurFond) {
// Version 7.9.1 : La ligne suivante a été rajoutée par précaution car normalement à l'appel
// le svg ne devrait pas contenir la repésentation SVG de l'objet mais cela peut arriver
// par exemple quand on lance une macro exécutant une suite de macros de constructions récursives
if (this.g !== null && svg.contains(this.g)) svg.removeChild(this.g)
this.trace(svg, couleurFond)
if ((this.g !== null) && masquage && this.masque) this.g.setAttribute('visibility', 'hidden')
}
CElementGraphique.prototype.replaceg = function (svg, couleurFond, opacity) {
const g = this.createg(svg, couleurFond, opacity)
svg.replaceChild(g, this.g)
g.setAttribute('id', this.id)
this.g = g
}
/**
* Spécial JavaScript : Renverra true si l'objet est bien représenté graphiquement dans le svg
* Si le deuxième paramètre est à true, renvoie true même si l'objet est masqué
* @param {boolean} masquage true si les obejts masqués ne sont pas affichés
* @param {boolean} memeMasque CF ci-dessus
* @returns {boolean}
*/
CElementGraphique.prototype.hasg = function (masquage, memeMasque = false) {
return this.existe && (memeMasque || !(this.masque && masquage))
}
/**
* Fonction qui renverra true si l'objet exsite mas est hors fenêtre.
* Rajouté par rapport à la version Java car un objet dupliqué a besoin de savoir
* si l'élément qu'il duplique est ou non représenté dans le DOM par un sgv element.
* Utilisé dans cObjetDuplique
* @returns {boolean}
*/
CElementGraphique.prototype.horsCadre = function () {
return false
}
/**
* Fonction recréant le svg element représentant l'objet et remplaçant dans le svg
* svg l'ancien élément par le nouveau.
* Spécial JavaScript : A redéfinir pour les éléments dont il faut recalculer
* le svg élément quand ils sont dépacés ou modifiés.
* @param {SVGElement} svg
* @returns {void}
*/
CElementGraphique.prototype.update = function (svg) {
}
/**
* Fonction à redéfinir pour CPt (CPointAncetre en java)
* Sert pour le tracé des points marqués pour la trace.
* @param {SVGElement} svg
* @returns {void}
*/
CElementGraphique.prototype.updateTrace = function (svg) {
}
/**
* Fonction renvoyant true si l'objet est à l'intérieur de la fenêtre définie par dimfen
* @param {Dimf} dimfen Donne les dimensions de la fenêtre
* @returns {boolean}
*/
CElementGraphique.prototype.dansFen = function (dimfen) {
return true
}
/**
* Fonction renvoyant le nombre d'objets pour un lieu d'objets
* Redéfinit pour ls lieux de lieux
* @returns {number}
*/
CElementGraphique.prototype.nombreObjetsPourLieuObjet = function () {
return 1
}
/**
* Spécial version mtgApp : Crée l'élement associé à l'objet dans le DOM
* @param {svg} svg
* @param {boolean} masquage true pour que les éléments masqués ne soient pas affichés
* @param {Color} couleurFond
*/
CElementGraphique.prototype.creeAffichage = function (svg, masquage, couleurFond) {
if (this.hasg(masquage)) {
this.affiche(svg, masquage, couleurFond)
this.hasgElement = true
if (this.estDeNature(NatObj.NObjNommable) && (this.nom !== '') &&
!this.nomMasque) this.afficheNom(svg)
this.updateTrace(svg, couleurFond) // Pour les points marqués pour la trace
} else {
const newg = cens('g', {
id: this.id
}) // Un g vide
svg.appendChild(newg)
this.g = newg
this.hasgElement = false
if (this.estDeNature(NatObj.NObjNommable) && (this.nom !== '') &&
!this.nomMasque) {
const gname = cens('g') // Un g vide
this.gname = gname
svg.appendChild(gname)
}
}
// Ajout version 7.6 pour pouvoir repérer un objet graphique dans le DOM
if (this.tag !== '') this.g.setAttribute('data-mtg-tag', this.tag)
}
/**
* Fonction qui met à jour le g elemnt représentant l'objet dans le svg
* @param svg
* @param {Color} couleurFond couleur de fond uilisée
* @param {boolean} masquage true si les objets masqués ne doivent pas être affichés
*/
CElementGraphique.prototype.updategElt = function (svg, couleurFond, masquage) {
const hasg = this.hasg(masquage)
const hasComponent = this.hasComponent()
if (this.hasgElement) { // Si l'objet avait une implémentation graphique dans le svg
if (hasg) {
if (this.g.parentNode === svg) { // Test rajouté version 6.3.5 suite à rapports BusNag
if (hasComponent) this.showComponent(true)
this.update(svg, couleurFond)
this.updateTrace(svg, couleurFond) // Pour les points marqués pour la trace
if (this.estDeNature(NatObj.NObjNommable) && (this.nom !== '') && !this.nomMasque) {
this.updateNamePosition()
}
// this.hasgElement = true; // Supprimé version mtgApp. Inutile
}
} else {
if (hasComponent) this.showComponent(false)
const newg = cens('g') // Un g vide
try { // Suite à des erreurs BusNag
svg.replaceChild(newg, this.g)
} catch (e) {
svg.appendChild(newg)
}
newg.setAttribute('id', this.id)
this.g = newg
this.hasgElement = false
if (this.estDeNature(NatObj.NObjNommable) && (this.nom !== '') && !this.nomMasque) {
const gname = cens('g')
try {
svg.replaceChild(gname, this.gname)
} catch (e) { // Suite à des erreurs BusNag
svg.appendChild(gname)
}
this.gname = gname
}
}
} else {
// this.hasgElement est false
if (hasg) {
// Il semble arriver que la fonction de callBack soit appelée après que le svg ait été vidé
// J'utilise donc un test du parent
if (this.g?.parentNode === svg) { // Modifié version 6.8.0
if (hasComponent) this.showComponent(true)
const g = this.createg(svg, couleurFond)
try {
svg.replaceChild(g, this.g)
} catch (e) { // Suite à des erreurs BusNag
svg.appendChild(g)
}
g.setAttribute('id', this.id)
this.g = g
if (this.cbmap) {
this.resetEventListeners()
} // Ajouté version 8.0.O
this.hasgElement = true
if (this.estDeNature(NatObj.NObjNommable) && (this.nom !== '') && !this.nomMasque) {
this.updateName(svg, masquage)
}
this.updateTrace(svg, couleurFond) // Pour les points marqués pour la trace
}
}
}
}
/**
* Détruit l'élément et le retire de son composant graphique s'il existe
* @param svg Le svg de la figure
*/
CElementGraphique.prototype.removegElement = function (svg) {
// Corrigé version 6.8.1 suite à des rapports Busnag
// if (this.g !== null) svg.removeChild(this.g)
if (this.g !== null) {
if (svg.contains(this.g)) svg.removeChild(this.g)
this.g = null
}
if (this.gname !== null) svg.removeChild(this.gname)
}
/**
* Change la couleur de l'objet dans le svg
* @param {Color} color la couleur à donner à l'objet
* @param {SVGElement} svg le svg contenant l'objet
* @param {boolean} bImmediat si true on lance le changement de couleur
*/
CElementGraphique.prototype.setColor = function (color, svg, bImmediat) {
this.donneCouleur(color)
if (bImmediat && !this.masque) {
const dimf = new Dimf(svg)
this.positionne(false, dimf)
addQueue(() => {
this.setReady4MathJax()
addQueue(() => {
if (this.g) this.setgColor()
})
})
}
}
/**
* Donnant directement au svg element représentant l'élément graphique la couleur de l'élément
* @since version 6.5.2
*/
CElementGraphique.prototype.setgColor = function () {
if (this.g) {
this.g.style.color = this.color
// Ligne suivante ajoutée version 6.9.1
this.g.style.opacity = this.couleur.opacity
return true
} else return false
}
/**
* Réaffecte au svg element représentant l'élement graphique les fontions de callBack
* qui lui avaient été affectées précédemment, au cas où ce svg element a été mis à jour
* @since version 6.5.2
*/
CElementGraphique.prototype.resetEventListeners = function () {
if (this.cbmap && this.g) {
if (this.cbmap.size > 0) {
this.pointerevents = 'all'
this.g.setAttribute('pointer-events', 'all')
for (let i = 0; i < this.g.childNodes.length; i++) {
this.g.childNodes[i].setAttribute('pointer-events', 'all')
}
}
this.cbmap.forEach((value, key) =>
this.g.addEventListener(key, value))
}
}
CElementGraphique.prototype.read = function (inps, list) {
const numeroVersion = list.numeroVersion
CElementBase.prototype.read.call(this, inps, list)
this.masque = inps.readBoolean()
const r = inps.readByte()
const g = inps.readByte()
const b = inps.readByte()
let opacity
if (numeroVersion >= 20) {
opacity = inps.readFloat()
} else opacity = 1
this.couleur = new Color(r, g, b, opacity)
// Rajout version 4.9.9.4
this.color = this.couleur.rgb()
//
if (this.hasName() || (numeroVersion < 16)) {
this.nomMasque = inps.readBoolean()
this.tailleNom = inps.readByte()
// Modification version 5.0 : taillePolice est maintenant la taille réelle de la police
if (numeroVersion < 15) this.tailleNom = Fonte.taille(this.tailleNom)
// Correction de figures qui ont été mal renregistrées
this.nom = inps.readUTF()
}
if (this.hasDec() || (numeroVersion < 16)) {
if (numeroVersion < 11) {
this.decX = inps.readDouble()
this.decY = inps.readDouble()
} else {
const by = inps.readByte()
if (by === 1) {
this.decX = 0
this.decY = 0
} else {
this.decX = inps.readDouble()
this.decY = inps.readDouble()
}
}
}
// Ajout version 6.6.0
if (numeroVersion < 19) this.tag = ''
else this.tag = inps.readUTF()
}
CElementGraphique.prototype.write = function (oups, list) {
CElementBase.prototype.write.call(this, oups, list)
oups.writeBoolean(this.masque)
oups.writeByte(this.couleur.getRed())
oups.writeByte(this.couleur.getGreen())
oups.writeByte(this.couleur.getBlue())
oups.writeFloat(this.couleur.opacity)
if (this.hasName()) {
oups.writeBoolean(this.nomMasque)
oups.writeByte(this.tailleNom)
oups.writeUTF(this.nom)
}
if (this.hasDec()) {
if ((this.decX === 0) && (this.decY === 0)) oups.writeByte(1)
else {
oups.writeByte(0)
oups.writeDouble(this.decX)
oups.writeDouble(this.decY)
}
}
// Ajout version 6.6.0
oups.writeUTF(this.tag)
}