/*
* Created by yvesb on 16/09/2016.
*/
/*
* 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 CDroiteImage from '../objets/CDroiteImage'
import CCercleImage from '../objets/CCercleImage'
import CArcDeCercleImage from '../objets/CArcDeCercleImage'
import CPointImage from '../objets/CPointImage'
import CDemiDroiteImage from '../objets/CDemiDroiteImage'
import CSegment from '../objets/CSegment'
import CLigneBrisee from '../objets/CLigneBrisee'
import CPolygone from '../objets/CPolygone'
import CNoeudPointeurSurPoint from '../objets/CNoeudPointeurSurPoint'
import StyleTrait from '../types/StyleTrait'
import CCercleOR from '../objets/CCercleOR'
import Color from '../types/Color'
import CValeur from '../objets/CValeur'
import CDemiDroiteOA from '../objets/CDemiDroiteOA'
import CIntDroiteCercle from '../objets/CIntDroiteCercle'
import CPointLieBipoint from '../objets/CPointLieBipoint'
import MotifPoint from '../types/MotifPoint'
import CRotation from '../objets/CRotation'
import CValeurAngle from '../objets/CValeurAngle'
import CHomothetie from '../objets/CHomothetie'
import CCommentaire from '../objets/CCommentaire'
import CAffLiePt from '../objets/CAffLiePt'
import StyleEncadrement from '../types/StyleEncadrement'
import addQueue from 'src/kernel/addQueue'
export default Outil
/**
* Classe ancêtre de tous les outils pouvant agir sur la figure.
* @param {MtgApp} app Application propriétaire
* @param {string} toolName Le nom de l'outil
* @param {number} toolIndex L'index de l'outil tel que dans la version Java
* @param {boolean} [avecClign=false] true si l'outil utilise un clignotement d'objets lors de la création
* @param {boolean} [isSelectable=true] true si l'outil est sélectionnable, false s'il est à action immédiate quand
* on clique sur le bouton de recalcul de la figure. true par defaut
* @param {boolean} [always=false] true si l'outil est toujours présent dans les exercices de construction
* @param {boolean} [hasIco=true] true par défaut. A metre à false pour les outils sans icône associée
* @constructor
*/
function Outil (app, toolName, toolIndex, avecClign = false,
isSelectable = true, always = false, hasIcon = true) {
this.app = app
this.toolName = toolName
this.avecClign = avecClign
this.toolIndex = toolIndex
this.timer = null
this.isSelectable = isSelectable
this.always = always
this.hasIcon = hasIcon
this.cursor = 'crosshair'
}
/**
* Fonction sélectionnant l'outil
*/
Outil.prototype.select = function () {
// On englobe dans un try catch pour capturer les erreurs arrivant dans LaboMep quand on ferme la fenêtre
// de l'exercice
try {
// Le nombre de points créés au clic de la souris si l'option de création des points au clic est activée
const app = this.app
// Ajout version 6.4 : Si une macro est en cours d'exécution on l'arrête.
app.termineMacroEnCours()
this.nbPtsCrees = 0
const btn = this.app['button' + this.toolName]
// Certains outils ne sont pas associés à un bouton (obtenus pas bouton +)
if (btn) btn.activate(true)
app.cacheDesignation()
app.outilActifPrec = app.outilActif // Ajout version 6.3.3
app.outilActif = this
// Version 6.9.0 : Ligne suivante our que si un outil n'a pas d'indication fugitive l'outil
// affichant la dernière indication de l'outil en cours n'affiche pas l'indication d'un autre outil
app.indication('')
// if (app.nameEditor.isVisible) app.nameEditor.montre(false);
// Si l'outil qu'on active est un outil de création de surface, on met le curseur d'opacité à 0.2, sinon à 1
if (this.isSurfTool()) app.setDefaultOpacityForSurf()
else {
if (this !== app.outilPalette) app.setDefaultOpacity()
}
} catch (error) {
console.error('Erreur dans outil.select', error)
}
}
/**
* Déselectionne l'outil
*/
Outil.prototype.deselect = function () {
// On englobe dans un try catch pour capturer les erreurs arrivant dans LaboMep quand on ferme la fenêtre
// de l'exercice
try {
const app = this.app
const btn = this.app['button' + this.toolName]
// Certains outils ne sont pas associés à un bouton (obtenus pas bouton +)
if (btn) btn.activate(false)
if (this.avecClign) this.annuleClignotement()
app.supprimeObjetsVisuels()
app.listeExclusion.retireTout()
// La ligne suivante remet à jour la barre d'icônes car une fois qu'un outil est déselectionné pour
// en sélecionner un autre certains outils peuvent ne plus être activables alors que d'autres peuvent l'être
// app.updateToolsToolBar();// Supprimé et déplacé dans Gestionnaire.enregistreFigureEnCours
// Certains outils d'affichage ont céé un affichage clignotatnt pour désigner l'endroit d'affichage
// (commentaire libre, latex libre, affichage de valeur libre, macros)
if (this.comClig) {
this.comClig.removegElement(app.svgFigure)
this.comClig = null
}
// Lorsque la création de point par défaut est activée on peut avoir créé des points qu'il fait détruire si l'objet
// n'a pas été créé
if (this.nbPtsCrees > 0) {
const list = app.listePourConst
for (let i = 0; i < this.nbPtsCrees; i++) {
const pt = list.get(list.longueur() - i - 1)
if (app.nameEditor.isVisible && app.nameEditor.eltAssocie === pt) app.nameEditor.montre(false)
pt.removegElement(app.svgFigure)
}
app.detruitDerniersElements(this.nbPtsCrees)
this.nbPtsCrees = 0
}
// if (app.nameEditor.isVisible) app.nameEditor.montre(false);
} catch (e) {
console.error('Erreur dans outil.deselect', e)
}
}
Outil.prototype.reselect = function () {
this.deselect()
this.select()
}
/**
* Fonction qui renverra true seulement pour les outils servant à créer une surface.
* Cela permet de savoir s'il faut ou non mettre le curseur d'opacité à 1 ou à 0.3
* lorsqu'on active un outil de création d'objet
* @returns {boolean}
*/
Outil.prototype.isSurfTool = function () {
return false
}
/**
* Fonction renvoyant éventuellement une indication supplémentaire à rajouter devant indication()
* et suivi de :
* @returns {string}
*/
Outil.prototype.preIndication = function () {
return ''
}
/**
* Fonction renvoyant true si l'outil est activable
* A redéfinir pour les descendants
* @returns {boolean}
*/
Outil.prototype.activationValide = function () {
return true
}
/*
Fonction relançant le clignotement de la liste listeClignotante de l'application this.app
*/
Outil.prototype.resetClignotement = function () {
if (this.timer !== null) this.annuleClignotement()
this.app.clignotementPair = true
const app = this.app
this.timer = setInterval(function () {
app.actionClignotement()
}, 500)
}
/**
* Annule le clignotement de this.app.listeClignotante
* @returns {void}
*/
Outil.prototype.annuleClignotement = function () {
// Le try catch ci-dessous est pour supprimer des erreurs quand on est dans LaboMep
// Cette fonction peut être appelée lors d'un try catch de actionClignotement
// et si la fenêtre de l'exercice a été fermée il ne faut plus touher au DOM
try {
clearInterval(this.timer)
this.timer = null
const app = this.app
if (!this.app.clignotementPair) {
app.listeClignotante.montreTout(true)
const doc = app.doc
// Version 6.7.2 : Il faut passer un dernier paramètre byBlock à false car sinon il est true pas défaut
app.listeClignotante.update(app.svgFigure, doc.couleurFond, true, false)
}
this.app.listeClignotante.retireTout()
} catch (e) {
console.error('erreur dans annuleClignotement', e)
}
}
/**
* Fonction ajoutant el à la liste listeClignotatnte de this.app l'élément el
* @param el CElementGraphique : l'objet à rajouter à la liste clignotante
*/
Outil.prototype.ajouteClignotementDe = function (el) {
this.app.listeClignotante.add(el)
}
/**
* Enlève le clignotement actif de l'objet obj
* @param {COb} obj
*/
Outil.prototype.enleveDeClign = function (obj) {
clearInterval(this.timer)
this.timer = null
const app = this.app
const list = app.listeClignotante
if (!this.app.clignotementPair) {
list.montreTout(true)
const doc = app.doc
list.update(app.svgFigure, doc.couleurFond, true)
}
this.app.clignotementPair = true
// Correction version 6.9.0 : Il faut attendre pour relancer le clignotement que le update précédent ait été
// effectué
const me = this
addQueue(function () {
for (let i = 0; i < list.longueur(); i++) {
const el = list.get(i)
if (el === obj) {
list.col.splice(i, 1)
}
}
me.timer = setInterval(function () {
app.actionClignotement(app)
}, 500)
})
}
/**
* Fonction ajoutanat à la liste listeObjetsVisuels de l'application this.app
* les élémengts graphiques servant à visualiser l'action de l'outil.
* A redéfinir pour les descendants
*/
Outil.prototype.ajouteObjetsVisuels = function () {
}
/**
* Fonction rajoutant à al liste listeExclusion de l'application this.app l'élément el
* el ne pourra pas être désigné par poinatge de souris.
* @param el CElementGraphique : L'objet à rajouter
*/
Outil.prototype.excluDeDesignation = function (el) {
this.app.listeExclusion.add(el)
}
Outil.prototype.addImage = function (el, bEditionNom = true) {
this.app.ajouteElement(el, bEditionNom)
}
Outil.prototype.ajouteImageParTransformation = function (objet, trans, nombreObjetsCrees) {
let ptim1, ptim2, col, lig, ptim
const app = this.app
const list = app.listePr
const coul = app.getCouleur()
const st = app.getStyleTrait()
const taille = app.getTaillePoliceNom()
nombreObjetsCrees.setValue(nombreObjetsCrees.getValue() + 1)
app.nameEditor.montre(false) // Pour annuler l'édition d'un éventuel objet image précédent
trans.positionne(false) // Nécessaire car ajouteElement ne positionne que l'image et pas la transformation
if (objet.estDeNature(NatObj.NTtPoint)) {
this.addImage(new CPointImage(list, null, false, coul, false, 0, 3, false, '',
taille, app.getStylePoint(), false, objet, trans))
} else {
const nat = objet.getNature()
switch (nat) {
case NatObj.NDroite :
this.addImage(new CDroiteImage(list, null, false, coul, false, 0, 0, false, '', taille, st, 0.9, objet, trans))
break
case NatObj.NDemiDroite :
this.addImage(new CDemiDroiteImage(list, null, false, coul, false, st, objet, trans))
break
case NatObj.NCercle :
this.addImage(new CCercleImage(list, null, false, coul, false, st, objet, trans))
break
case NatObj.NArc :
this.addImage(new CArcDeCercleImage(list, null, false, coul, false, st, objet, trans))
break
case NatObj.NSegment :
ptim1 = list.pointImageDePar(objet.point1, trans)
if (ptim1 === null) {
ptim1 = new CPointImage(list, null, false, coul, false, 0, 3, false, '',
taille, app.getStylePoint(), false, objet.point1, trans)
this.addImage(ptim1, false)
nombreObjetsCrees.setValue(nombreObjetsCrees.getValue() + 1)
}
ptim2 = list.pointImageDePar(objet.point2, trans)
if (ptim2 === null) {
ptim2 = new CPointImage(list, null, false, coul, false, 0, 3, false, '',
taille, app.getStylePoint(), false, objet.point2, trans)
this.addImage(ptim2, false)
nombreObjetsCrees.setValue(nombreObjetsCrees.getValue() + 1)
}
this.addImage(new CSegment(list, null, false, coul, false, st, ptim1, ptim2))
break
case NatObj.NLigneBrisee :
case NatObj.NPolygone :
col = []
if (nat === NatObj.NLigneBrisee) { lig = new CLigneBrisee(list, null, false, coul, false, app.getStyleTrait(), col) } else lig = new CPolygone(list, null, false, coul, false, app.getStyleTrait(), col)
for (let i = 0; i < objet.colPoints.length; i++) {
const pt = objet.colPoints[i].pointeurSurPoint
ptim = list.pointImageDePar(pt, trans)
if (ptim === null) {
ptim = new CPointImage(list, null, false, coul, false, 0, 3, false, '',
taille, app.getStylePoint(), false, pt, trans)
this.addImage(ptim)
nombreObjetsCrees.setValue(nombreObjetsCrees.getValue() + 1)
}
col.push(new CNoeudPointeurSurPoint(list, ptim))
}
lig.prepareTableauxCoordonnees()
this.addImage(lig)
}
}
}
/**
* Fonction qui devra être redéfinie pour les descendants
* Renverra true si lorsque, sur un périphérique mobile, on relâche le doigt et qu'on traite un objet proche de ce doigt
* Par exemple, pour un outil de création par deux points, il faudra que this.point1 ne soit pas null,
* c'est-à-dire que le premier point ait déjà été désigné
* @returns {boolean}
*/
Outil.prototype.isReadyForTouchEnd = function () {
return true
}
/**
* Fonction redéfinir pour les descendants et servant sur les périphériques mobiles
* Si cette fonction renvoie true, les événements touch ne sont pas propagés pour éviter
* par exemple qu'on glisse dans la figure quand on faut glisser le doigt après avoir cliqué sur un premier point
* @returns {boolean}
*/
Outil.prototype.isWorking = function () {
return false
}
Outil.prototype.actionApresDlg = function () {
}
/**
* Fonction qui devra être redéfinie par les outils qui utilisent un clic sur l'icône stop
* en bas et à droite de la barre d'outils de droite pour finir une action.
*/
Outil.prototype.actionFin = function () {
}
/**
* Méthode ajoutant un rapporteur à la liste d'objets visuels. Utilisée pour
* l'outil rapporteur et peut-être plus tard les outils de création d'arc de
* cercle. Renvoie un pointeur sur un point d'intersection entre le cercle
* extérieur du rapporteur et une demi-droite joignant le centre de ce cercle
* avec le point suivant le pointeur souris
* Modifié version 5.0
*/
Outil.prototype.ajouteRapporteur = function (origine) {
let coul, styleTrait
const app = this.app
const doc = app.doc
// On crée d'abord un cercle de centre origine et de rayon fixe
// qui servira de support extérieur au rapporteur
const liste = app.listeObjetsVisuels
const stfc = StyleTrait.stfc(liste)
const stfp = StyleTrait.stfp(liste)
const bl = Color.black
const ptrond = MotifPoint.petitRond
const taille = app.getTaillePoliceNom()
// Pour la version js on se sert d'un CCercleOR avec rayon en pixels
const ptCercleRapExt = new CCercleOR(liste, null, false, bl, false, stfc, origine,
new CValeur(liste, 80), true)
app.ajouteObjetVisuel(ptCercleRapExt)
// Puis un cercle intérieur
const ptCercleRapInt = new CCercleOR(liste, null, false, bl, false, stfc, origine,
new CValeur(liste, 40), true)
app.ajouteObjetVisuel(ptCercleRapInt)
// On crée un demi-droite de Origine vers PointSouris
// var ptdemid = new CDemiDroiteOA(liste, null, false, Couleur
// .TraduitCouleur(1), false, StyleTrait.traitPointille, origine, frame
// .cadreActif().paneFigure.pointSouris);
const ptdemid = new CDemiDroiteOA(liste, null, false, bl, true, stfp, origine, app.mousePoint)
app.ajouteObjetVisuel(ptdemid)
// On crée l'intersection de cette demi-droite avec le cercle extérieur
// du rapporteur
// CIntDroiteCercle ptint = new CIntDroiteCercle(liste, null, false, ptdemid,
// ptCercleRapExt);
const ptint = new CIntDroiteCercle(liste, null, false, ptdemid, ptCercleRapExt)
app.ajouteObjetVisuel(ptint)
const ptplb = new CPointLieBipoint(liste, null, false, bl, true, 0, 0, false, '', taille, ptrond, false, ptint, 1)
app.ajouteObjetVisuel(ptplb)
// On rajoute des graduations au rapporteur
// On crée d'abord une rotation d'angle 10°
// CRotation ptrot10 = new CRotation(liste, null, false, origine, 10);
const ptrot10 = new CRotation(liste, null, false, origine, new CValeurAngle(liste, 10))
app.ajouteObjetVisuel(ptrot10)
// On crée une homothétie servant à créer les graduations de 10°
// CHomothetie hom1 = new CHomothetie(liste, null, false, origine, 0.85);
const hom1 = new CHomothetie(liste, null, false, origine, new CValeur(liste, 0.85))
app.ajouteObjetVisuel(hom1)
// Et une autre pour les graduations de 90°
const hom2 = new CHomothetie(liste, null, false, origine, new CValeur(liste, 0.5))
app.ajouteObjetVisuel(hom2)
// On crée les images par des rotations d'angle 10°
let ptp1 = ptplb
for (let i = 1; i <= 36; i++) {
const ptim = new CPointImage(liste, null, false, Color.green, true, 0, 0, true, '', app.getTaillePoliceNom(),
ptrond, false, ptp1, ptrot10)
app.ajouteObjetVisuel(ptim)
// On crée l'image du point créé par l'homothétie pour créer les
// graduations
// Pour les multiples de 90° elles sont rouges, sinon vertes
// CPointImage ptima = new CPointImage(liste, null, false, Couleur
// .TraduitCouleur(3), 13, MotifPoint.Pixel, ptim, hom1);
const ptima = new CPointImage(liste, null, false, Color.green, true, 0, 0, true, '', taille, ptrond, false, ptim, hom1)
app.ajouteObjetVisuel(ptima)
// On crée un segment graduation
if ((i % 9) === 0) {
coul = 'red'
styleTrait = new StyleTrait(liste, StyleTrait.styleTraitContinu, 2)
} else {
coul = 'blue'
styleTrait = stfc
}
// On crée des graduations joignant le centre aux 90°
if ((i % 9) === 0) {
// CPointImage ptim2 = new CPointImage(liste, null, false, Couleur
// .TraduitCouleur(1), MotifPoint.Croix, 13, ptim, hom2);
const ptim2 = new CPointImage(liste, null, false, bl, true, 0, 0, true, '', app.getTaillePoliceNom(), ptrond,
false, ptim, hom2)
app.ajouteObjetVisuel(ptim2)
// CSegment ptseg90 = new CSegment(liste, null, false, origine, ptim2); // Segment
// en
// pointillés
const ptseg90 = new CSegment(liste, null, false, bl, false, stfp, origine, ptim2)
app.ajouteObjetVisuel(ptseg90)
// CPointImage ptim3 = new CPointImage(liste, null, false, Couleur
// .TraduitCouleur(1), MotifPoint.Croix, 2, ptima, hom1);
const ptim3 = new CPointImage(liste, null, false, bl, true, 0, 0, true, '', app.getTaillePoliceNom(), ptrond,
false, ptima, hom1)
app.ajouteObjetVisuel(ptim3)
let chai
switch (i) {
case 9:
case 27:
chai = '90°'
break
case 18:
chai = '180°'
break
case 36:
chai = '0°'
break
}
// CCommentaire ptcom = new CCommentaire(liste, null, false, Couleur
// .TraduitCouleur(coul), 0d, 0d,(int)(-8d*k), (int)(-6d*k), false, ptim3, (int)(k*10),
// StyleEncadrement.Sans, false, doc.couleurFond, chai,
// CAffLiePt.alignHorLeft, CAffLiePt.alignVerTop);
const ptcom = new CCommentaire(liste, null, false, Color[coul], 0, 0, -8, -6, false, ptim3, 10, StyleEncadrement.Sans,
false, doc.couleurFond, CAffLiePt.alignHorLeft, CAffLiePt.alignVerTop, chai)
app.ajouteObjetVisuel(ptcom)
}
// CSegment ptgrad = new CSegment(liste, null, false, Couleur
// .TraduitCouleur(coul), false, styleTrait, ptima, ptim);
const ptgrad = new CSegment(liste, null, false, Color[coul], false, styleTrait, ptima, ptim)
app.ajouteObjetVisuel(ptgrad)
// Pour 10° et -10° on ajoute un commentaire de graduation
if ((i === 1) || (i === 35)) {
// CPointImage ptim4 = new CPointImage(liste, null, false, Couleur
// .TraduitCouleur(1), MotifPoint.Croix, 13, ptima, hom1);
const ptim4 = new CPointImage(liste, null, false, bl, true, 0, 0, true, '', app.getTaillePoliceNom(), ptrond,
false, ptima, hom1)
app.ajouteObjetVisuel(ptim4)
// CCommentaire ptcom = new CCommentaire(liste, null, false, Couleur
// .TraduitCouleur(coul), 0d, 0d, (int)(-10d*k), (int)(-6d*k), false, ptim4, (int)(k*10),
// StyleEncadrement.Sans, false, doc.couleurFond, "10°",
// CAffLiePt.alignHorLeft, CAffLiePt.alignVerTop);
const ptcom = new CCommentaire(liste, null, false, Color[coul], 0, 0, -10, -6, false, ptim4, 10, StyleEncadrement.Sans,
false, doc.couleurFond, CAffLiePt.alignHorLeft, CAffLiePt.alignVerTop, '10°')
app.ajouteObjetVisuel(ptcom)
}
ptp1 = ptim
}
return ptplb
}
/**
* @returns {void}
*/
Outil.prototype.saveFig = function () {
this.app.gestionnaire.enregistreFigureEnCours(this.toolName)
}
/**
* Fonction renvoyant true si l'outil accepte qu'on crée un point par défaut lors d'un clic sur un endroit vide
*/
Outil.prototype.creationPointPossible = function () {
return false
}
/**
* Fonction interdisant la désignation comme objets sources graphiques d'objets dépendant des objets sources
* graphiques ou non graphiques déjà désignés
*/
Outil.prototype.excluDesignationObjDepObjSrc = function () {
const app = this.app
const list = app.listePr
for (let i = 0; i < list.longueur(); i++) {
const elb = list.get(i)
if (elb.dependDeAuMoinsUn(app.listeSrcNG) || elb.dependDeAuMoinsUn(app.listeSrcG) ||
app.listeSrcNG.depDe(elb) || app.listeSrcG.depDe(elb)) {
app.listeExclusion.add(elb)
}
}
}
/**
* Fonction interdisant la designation d'objets dépendant de l'objet graphique elg
* @param elg
*/
Outil.prototype.excluDesignationObjDepDe = function (elg) {
const app = this.app
const list = app.listePr
for (const elb of list.col) {
if (elb.depDe(elg)) app.listeExclusion.add(elb)
}
}