JMEScript JME Homepage Manual de Usuario de JME IDE JMEScriptGUI

introduccion
sentencias
  ■ set
  ■ multiset
  ■ set_mut
  ■ swap
  ■ devolver
  ■ si
  ■ si_no_si
  ■ si_no
  ■ seleccionar
  ■ mientras
  ■ repetir_hasta
  ■ para
  ■ para_cada
  ■ fin
  ■ romper
  ■ continuar
  ■ bin_op
  ■ unary_op
  ■ limpiar
  ■ def_func
  ■ def_op
  ■ rutina
  ■ llamar_a
  ■ try
  ■ imprimir
  ■ leer
  ■ eval
  ■ pausar
  ■ asegurar
  ■ pasar
  ■ varmap
  ■ ctx2d
   clsl_iniciar
   clsl_finalizar
   clsl_color
   clsl_gradiente
   clsl_trazo
   clsl_punto
   clsl_segmento
   clsl_rectangulo
   clsl_circunferencia
   clsl_elipse
   clsl_poligono
   clsl_ruta
   clsl_texto
   clsl_repintar
   clsl_limpiar
   clsl_matriz
   clsl_entorno
   clsl_click
   clsl_pulsacion
   clsl_msj
   clsl_leer
   clsl_archivo
  ■ sql
   clsl_conectar
   clsl_ejecutar
   clsl_resultado
   clsl_cerrar
  ■ accion
vars
  ■ ext_vars
comment
developers
  ■ thread
  ■ scriptthread
  ■ abstractctx2d
  ■ abstractsql
  ■ accioncode
examples


Introducción a JMEScript


JMEScript es un lenguaje estructurado, dinámicamente tipado, para la creación de scripts que hacen uso del lenguaje funcional JME para representación de tipos y expresiones. Es un lenguaje que nació orientado al Cálculo Numérico pero no limitado a éste, ya que puede ejecutar acciones definidas sobre la propia aplicación que utilice el lenguaje para interactuar con la propia aplicación y crear scripts y macros avanzados sobre ella.

JMEScript consta básicamente de;
∎ estructuras de selección; si / si no, si / si no / seleccionar
∎ cuatro tipos de bucles;  mientras / para / para cada / repetir hasta que
∎ procedimientos locales y globales con parámetros de entrada y de entrada/salida
∎ variables dinámicas que contienen tipos JME
∎posibilidad de llamar a acciones de la propia aplicación implementante para crear scripts sobre ella
∎ creación de funciones y operadores JME definidos por el usuario

Puede utilizarse este descriptor de lenguaje definido por el usuario (UDL) para Notepad++ que permite usar la sintaxis resaltada utilizada en los ejemplos de esta página.


Sentencias del lenguaje


Las sentencias del lenguaje JMEScript se sitúan cada una en su propia línea del código fuente del script, aunque se pueden partir en varias líneas con el separador >. Los espacios y tabulaciones iniciales y finales son ignorados, pudiéndose usar el sangrado que se desee.

JMEScript, es case-insensitive, por lo que las palabras reservadas del lenguaje (keywords), variables, rutinas, etc. pueden escribirse indistintamente en mayúsculas o minúsculas (el convenio usado en la documentación es minúsculas), siendo por tanto la variable nombre igual a NoMbrE.

Las sentencias que terminan en 'inicio:' son sentencias de bloque, que ejecutan o pueden ejecutar las líneas debajo de ellas hasta llegar a su sentencia fin correspondiente.

Las expresiones JME en las sentencias se pueden rodear en caso de ambigüedad con dobles llaves; {{3x^2+1}}.



Establecer/Borrar variable


El operador := permite establecer el valor de una o varias variables a un único valor mediante una expresión JME o eliminar la/s variable/s si no se especifica ninguna expresión:



Diagrama sintáctico:




Salida en consola:

100 falso [11,10,2,3,12] <<<ScriptException>>> en la sentencia '[#8: imprimir x]': <<<EvaluarException>>>; identificador "x" no definido. --> ...



Establecer/Borrar múltiples variables


El operador :== permite establecer el valor de varias variables mediante un vector o un diccionario o eliminar las variables.


Diagrama sintáctico:



[<varname> [,<varname>]*] :== [<vector|diccionario>]


Ejemplo con vector:


Salida en consola:

7 I 2 3 4 <<<ScriptException>>> en la sentencia '[#6: imprimir x, y, z]': <<<EvaluarException>>>; identificador "x" no definido. --> ...


Ejemplo con diccionario:


Salida en consola:

{ 'x': 100 'y': 200 } { 'y': 200 'b': 'hola' 'vm': {'x'=100, 'y'=200} }



Establecer valor de forma mutable en vector o diccionario


El operador de acceso {<idx>} permite modificar/añadir/eliminar un valor de un vector o diccionario "IN-PLACE", es decir, modificando directamente el objeto vector/diccionario.

Esto puede mejorar la eficiencia en operaciones que requieran modificar grandes matrices o diccionarios sin tener que recurrir a una copia del objeto, pero lógicamente se debe programar con cuidado como cada vez que se usa programación no funcional (inmutable). JME siempre trabaja de forma funcional para el usuario, al igual que JMEScript excepto mediante este operador de acceso.


  1. caso vector:
    • modificar posición; v{3}:=5 modifica la posición 3 del vector a 5
    • eliminar posición; v{3}:= elimina la posición 3 del vector (reduciendo su tamaño)
    • modificar rango; v{[2,4]}:=0 establece a 0 las posiciones 2,3,4 del vector
    • eliminar rango; v{[2,4]}:= elimina las posiciones 2,3,4 del vector (reduciendo su tamaño)
    • insertar en posición; v{3b}:=5 inserta en la posición 3 del vector un 5, desplazando los elementos siguientes una posición y aumentando el tamaño. Se debe especificar la posición con un EnteroGrande
  2. caso diccionario:
    • modificar o crear clave; d{'key'}:=5 crea clave y valor {'key',5} o la modifica
    • eliminar clave; d{'key'}:= elimina la clave 'key' y su valor si existe, en caso contrario no tiene efecto


Diagrama sintáctico:



<varname> { <idx|key> } := [<valor>]


Operador sobre vector:


Salida en consola:

0: ['a','b','c','d'] 1: ['a','z','c','d'] 2: ['a','c','d'] 3: ['inicio','a','c','d','fin'] 4: ['inicio','fin'] 5: [0,0]


Operador sobre diccionario:


Salida en consola:

0: {'p'=[1,2], 'q'=[0,1], 'r'=5, 'x'=1, 'y'=-3, 'z'=0} 1: {'p'='__null__', 'q'=[0,1], 'r'=5, 'x'=1, 'y'=-3, 'z'=0} 2: {'q'=[0,1], 'r'=5, 'x'=1, 'y'=-3, 'z'=0} 3: {'q'=[0,1], 'r'=5, 'x'=1, 'y'=-3, 'z'=0, 'p'=[-2,-3]}


Operador sobre matriz:


Salida en consola:

| 8 0 8 | | 0 5 45 | | 0 0 1 | | 8 0 8 | | 0 5 100 | | 0 0 1 |



Intercambiar variables


La sentencia intercambiar o swap permite intercambiar dos variables sin usar una tercera auxiliar.

La sentencia también intercambia si una variable está indefinida, haciendo que sea la otra la indefinida.



Diagrama sintáctico:



intercambio|swap <var1> [,] <var2>



Salida en consola:

'a' y 'b' intercambiados: a: 2 b: 1 borrar 'b' e intercambiar con 'a': b: 2 a indefinido: verdadero



Devolver resultado


La sentencia devolver termina el script y devuelve opcionalmente un resultado. Si no se especifica el resultado, el valor devuelto es null.

Si se usa esta sentencia dentro de una rutina, termina todo el script (el significado no es el mismo de return en otros lenguajes). Las rutinas se rompen con romper rutina.


Diagrama sintáctico:



devolver [<valor_devuelto>]



Salida en REPL:

72 ==> Texto: 'valor no válido' (parse: 475µs(3%) / eval: 18,1ms(97%) / total: 18,6ms)


37 ==> RealDoble: 1370 (parse: 741µs(6%) / eval: 12,1ms(94%) / total: 12,9ms)



Selección 'Si'


La sentencia si selecciona el código de su bloque según una condición.


Diagrama sintáctico:




Valor devuelto:

'no es primo'



Selección 'Si no, si'


La sentencia si no, si selecciona el código de su bloque según una condición si las condiciones 'si' y 'si no, si' previas han fallado. Esta sentencia siempre debe seguir a un bloque 'si', u otro 'si no, si'.


Diagrama sintáctico:




Valor devuelto:

'notable'



Selección 'Si no'


La sentencia si no selecciona el código de su bloque si las condiciones 'si' y 'si no, si' previas han fallado. Esta sentencia siempre debe seguir a un bloque 'si' o 'si no, si'.


Diagrama sintáctico:



Ver ejemplo previo de la sentencia si no, si.



Selección de casos 'Seleccionar'


La sentencia seleccionar ejecuta el caso/casos que cumplan con la condición especificada o con el valor dado.

Puede definirse un caso por defecto que se ejecutará en caso de que los demás no se cumplan (en multicaso ejecuta siempre). Debe definirse al final mediante caso*.

Si se añade * a seleccionar, se utilizará el multicaso, se ejecutarán todos los casos que cumplan la condición en lugar de sólo el primero que la cumpla.


Diagrama sintáctico:

seleccionar[*] <valor> inicio: [caso <valor|condición> inicio:]* [caso* inicio:]


Salida:

'>90' 'default' 'default' 'default' '>90' 'default' 'default' 'default' '1' 'default'



Salida:

item: 'b' 'b|d' 'a|b'



Bucle 'Mientras'


La sentencia mientras es un bucle pre-condición que repite un bloque de código mientras la condición es verdadera.


Diagrama sintáctico:



Este script añade tiradas de un dado hasta obtener un 6:


Valor devuelto:

[2,5,1,3,5,2,5,3,3,6]



Bucle 'Repetir hasta que'


La sentencia repetir hasta que es un bucle post-condición que repite un bloque de código hasta que la condición es verdadera ejecutando una o más veces.


Diagrama sintáctico:



Este script añade tiradas de un dado hasta obtener un 6 (obsérvese que 'n' no necesita estar definida al principio del bucle):


Valor devuelto:

[3,1,3,6]



Bucle 'Para'


La sentencia para es un bucle contador que repite un bloque de código en el rango especificado.
Pueden usarse números enteros o reales para especificar el rango, aunque los reales pueden acarrear problemas de precisión.


Diagrama sintáctico:




Salida consola:

2 3 5 7 11 13 17 19 23



Salida consola:

0 0.1 0.2 0.30000000000000004 0.4 0.5 0.6 0.7 0.7999999999999999 0.8999999999999999 0.9999999999999999 1.0999999999999999 1.2 1.3 1.4000000000000001 0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1 1.1 1.2 1.3 1.4 1.5



Bucle 'Para cada'


La sentencia para cada es un bucle que itera sobre elementos JME iterables (elementos de vector, entradas de diccionario y caracteres de texto).

El bucle permite especificar, además de la variable de iteración que contiene cada elemento particular, una variable contadora. Si esta variable no está inicializada o está inicializada a un valor no entero, se inicializa a 1 por defecto o al valor especificado.


Diagrama sintáctico:



para cada [<varidx> [:= <idx>] ,] <varname> en <iterable> inicio:



Salida en consola:

'leido un 4' 'leido un 2' 'leido un 7' 'leido un 1' 'leido un 9' 'leido un 0'



Salida en consola:

'caracter 1: 'H'' 'caracter 2: 'o'' 'caracter 3: 'l'' 'caracter 4: 'a'' 'caracter 5: ' '' 'caracter 6: 'M'' 'caracter 7: 'u'' 'caracter 8: 'n'' 'caracter 9: 'd'' 'caracter 10: 'o'' 'caracter 11: '!'' ==> VectorEvaluado: | 4 'a' | | 5 ' ' | | 6 'M' | | 7 'u' | | 8 'n' | | 9 'd' | | 10 'o' | | 11 '!' | (parse: 651µs(2%) / eval: 27,7ms(98%) / total: 28,4ms)



Salida en consola:

'usuario: 'viviana', contraseña: 'ridan'' 'usuario: 'user29', contraseña: 'masca3'' 'usuario: 'pepe', contraseña: 1234'



Salida en consola:

'odnum aloh' verdadero



Finalizador de bloque


La sentencia fin indica el final de un bloque, y debe ir en concordancia con el inicio de bloque que se desea cerrar. Una sentencia de bloque sin 'fin' o un 'fin' sin sentencia de bloque provocará un error.


Diagrama sintáctico:




Romper bucle o rutina


La sentencia romper finaliza incondicionalmente un bucle o una rutina.


Diagrama sintáctico:




Valor devuelto:

[2,2,5,1,2,2,2,4,2,4,2,5,4,4,6]



Valor devuelto:

[93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000,verdadero]



Continuar


La sentencia continuar salta a la siguiente iteración del bucle.


Diagrama sintáctico:




Valor devuelto:

'El cielo est entrbicudriquindo'



Asignación binaria implícita


Esta sentencia permite autoasignar implícitamente el resultado de una operación binaria en la que el valor de la variable es el primer operando.

Es una forma corta del operador de asignación := cuando la expresión es una operación binaria, por tanto a<operador>=<expresión> es equivalente a a:=a<operador><expresión>


Diagrama sintáctico:




Valor devuelto:

314.1



Asignación unaria implícita


Esta sentencia permite autoasignar implícitamente el resultado de una operación unaria en la que el valor de la variable es el operando.

Es una forma corta del operador de asignación := cuando la expresión es una operación unaria, por tanto a<operador> es equivalente a a:=a<operador>


Diagrama sintáctico:




Valor devuelto:

720



Limpiar variables


Esta sentencia actúa como el operador :== cuando elimina las variables del mapa de variables con la diferencia de que además sugiere al depurador de memoria de JAVA que libere la memoria reservada por la variable. Esto puede ser especialmente útil con variables que almacenen un valor extremadamente grande y que ya no sean necesarias en el resto de la ejecución del script. Además puede limpiar todas las variables del ámbito con el modificador *.


Diagrama sintáctico:




Salida en consola:

<<<ScriptException>>> en la sentencia '[#16: imprimir a, b, x, y]': <<<EvaluarException>>>; identificador "a" no definido. --> ...



Definir función JME de usuario


Esta sentencia establece una función JME definida por el usuario.

Las funciones definidas en el script permanecen en el mapa de funciones de JME al terminal el script, por lo que pueden usarse scripts como bibliotecas de funciones.


Diagrama sintáctico:




Salida en consola:

-11 [[[[[[[[[['Hola Mundo']]]]]]]]]] [[falso,falso,'valor:verdadero'],[falso,verdadero,'valor:verdadero'],[verdadero,falso,'valor:falso'],[verdadero,verdadero,'valor:falso']] [7,9] [0,1,2,3,4,5,6,7,8,9,30,31,32,33,34]



Definir operador JME de usuario


Esta sentencia establece un operador unario o binario JME definido por el usuario.

Los operadores definidos en el script permanecen en el mapa de operadores de JME al terminal el script, por lo que pueden usarse scripts como bibliotecas de operadores.


Diagrama sintáctico:




Salida en consola:

'edad: 38' [7,8,9,10] 999999999999999999990000000000000000000199999999999999999991



Crear rutina


Esta sentencia crea una rutina o procedimiento. Las rutinas JMEScript no devuelven un valor como las funciones, pero permiten la entrada y salida de datos mediante parámetros formales de entrada y entrada/salida (precedidos por var). Las rutinas pueden ejecutarse mediante la sentencia llamar a.

Las rutinas pueden definirse en cualquier punto del programa y ser llamadas en cualquier punto, no siendo necesario definirlas antes de llamarlas. En los ejemplos de esta página se sigue el convenio de definirlas al principio. También se pueden anidar (definir una dentro de otra) para indicar simbólicamente que una es auxiliar de otra, pero para el compilador es indiferente el lugar de declaración, son igualmente visibles en todo el script.

Los parámetros formales son variables solo definidas en el ámbito de la rutina. En una rutina no global, toda variable definida dentro de la rutina es local a la rutina. Las variables del programa principal no son accesibles en la rutina.

Una rutina termina al finalizar su bloque de código, o al usar la sentencia romper rutina, o al usar la sentencia devolver, pero esta última termina todo el script, no solo la rutina.

Nota: antes de definir una rutina JMEScript es preferible comprobar si existe alguna expresión JME que realice fácilmente la misma tarea. Por lo general será más simple y mucho más eficiente.


Diagrama sintáctico:

procedimiento|rutina [global] <nombre_funcion> ( [[var] <param> [,[var] <param>]*] ) inicio:



Salida en consola:

7 4



Valor devuelto:

[93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000,verdadero]



+ Rutinas globales


Las rutinas o procedimientos globales (usan la cláusula global) difieren de las anteriores en que pueden acceder y modificar las variables del ámbito en el que son llamadas, además de poder definir nuevas variables en su cuerpo. Si son llamadas desde el TOP-LEVEL pueden acceder a la variables de éste, si se llaman desde otra rutina, pueden acceder a las de ésta.

Si se define una variable nueva en una rutina global, será visible también en el ámbito previo.

El comportamiento de los parámetros formales es idéntico al de las rutinas "locales".



Salida en consola:

var_outer visible en inner: 100 var_inner visible en outer: [] var_inner no visible en TOP-LEVEL var_inner visible en TOP-LEVEL: []


Ejemplo que usa rutinas globales:

   grafos_jme.jmes



Llamar a rutina


La sentencia llamar a ejecuta una rutina JMEScript.

La cláusula llamar a es opcional, basta con el nombre de la rutina, pero clarifica para no confundir con funciones JME. Pueden verse ejemplos en la sentencia anterior de creación de rutina.


Diagrama sintáctico:



[llamar a] <nombre_rutina> ( [<param> [, <param>]*] )


Intentar/Capturar


Las sentencias intentar y capturar permiten controlar excepciones ocurridas en el bloque de código dentro de intentar, equivalentemente a los bloques try/catch de la mayoría de lenguajes.

El bloque capturar puede opcionalmente capturar en una variable un diccionario con la información de la excepción, con las claves tipo, exmsg, sentencia, linea, causa


Diagramas sintácticos:



intentar inicio:
capturar [en <varname>] inicio:



Cuatro salidas en consola:

Aseveración errónea: <<<AssertException>>> en condicion "'Hola'='HOLA'"


ScriptException: { 'tipo': 'jme.script.ScriptException' 'causa': '__null__' 'exmsg': '<<<ScriptException>>> en la sentencia [#11: a, b :== [1]]: Imposible esparcir, el nº de variables es '2' y los valores '1'' 'sentencia': 'a,b:==[1]' 'linea': 11 }


error no esperado: { 'tipo': 'jme.script.ScriptException' 'causa': {'tipo'='jme.excepciones.EvaluarException', 'causa'='__null__', 'exmsg'='<<<EvaluarException>>>; identificador "x" no definido.'} 'exmsg': '<<<ScriptException>>> en la sentencia [#8: imprimir x]: <<<EvaluarException>>>; identificador "x" no definido.' 'sentencia': 'imprimir x' 'linea': 8 }


Operación no válida: <<<OperacionException>>> en operador [ + ] : operacion no definida --> (RealDoble:3)+(Texto:'')



Imprimir en salida por defecto


Esta sentencia vuelca valores en el 'stream' especificado (por defecto System.out).

Si se usa sin ningún argumento imprime un salto de línea. Una coma al final evita el salto de línea al terminar.

La cláusula vertical imprime las entradas separadas por saltos de línea en lugar de doble espacio. Para matrices y diccionarios, la salida es en formato vertical (más legible).

La cláusula msj imprime el tipo texto sin comillas, para el resto de datos no tiene efecto.

Nota: la salida por defecto es System.out, puede modificarse con el método Script#setSalida.


Diagrama sintáctico:



imprimir [msj] [vertical] [<expresion> [, <expresion>]*] [,]



Salida en consola:

Ejemplo de 'imprimir' en horizontal: 2 3 4 en vertical: -1 -2 -3 matriz de escalada en vertical: | 2 0 0 | | 0 3 0 | | 0 0 1 | diccionario en vertical: { 'a': 3 'b': 100 'c': 21 } 'FIN'



Lectura desde entrada por defecto


Esta sentencia lee una cadena en el 'stream' especificado (por defecto System.in).

La cadena leída se evaluará y asignará a la variable especificada. Si se añade el modificador cadena, la cadena se convertirá a texto sin evaluar, y no serán necesarias comillas. Si se añade el modificador exp, la cadena se convertirá en una expresión que se evaluará cada vez que se utilice la variable. Si hay un error en la expresión, la variable quedará indefinida (incluso si antes estaba definida). Se puede especificar un mensaje en la salida al pedir el dato.

Notas: para que esta sentencia funcione, la entrada por defecto del script debe permitir la introducción de datos por el usuario (mediante consola, diálogo, ...). El script estará detenido hasta la introducción del dato.


Diagrama sintáctico:




Salida en REPL:

x? 100 y? -30 x? 15^2-1 y? 10+20 x? 0 y? -10000 x? pi y? _e x? ==> VectorEvaluado: | 100 -30 | | 224 30 | | 0 -10000 | | 3.141592653589793 2.718281828459045 | (parse: 237ms(0%) / eval: 48,423s(100%) / total: 48,66s)



Evaluar expresión


Evalúa una expresión sin asignar resultado (v0.1.1).


Diagrama sintáctico:






Pausar script


Pausa el hilo de ejecución del script. Sólo puede ser reanudado desde otro hilo mediante el método JAVA Object#notify.


Diagrama sintáctico:




Asegurar


Esta sentencia lanza una excepción o un mensaje dado en caso de que no se cumpla una condición (assert sentence).


Diagrama sintáctico:



asegurar <condición> [msj <mensaje>]



Salida en consola:

No es primo jme.script.ScriptException: <<<ScriptException>>> en la sentencia [#5: asegurar primo(10)]: Error al asegurar [primo(10)] at jme.script.Asegurar.ejecutar(Asegurar.java:116) at jme.script.Script.ejecutar(Script.java:345) at jme.script.ScriptThread.run(ScriptThread.java:99)



Sentencia vacía


Sentencia vacía, es equivalente a una línea en blanco.


Diagrama sintáctico:




VarMap


Esta sentencia permite acceder/modificar el mapa de variables.


Diagrama sintáctico:



varmap [[<vartxt>] en <varname>] | [borrar <vartxt>] | [ <vartxt> := <valor> ]



Salida en consola:

{ 'var6': 4 'var5': 3 'var4': 5 'var3': 1 'var2': 3 'var1': 2 } var1 vale 2 { 'var6': 4 'var5': 3 'var4': 5 'var3': 1 'var2': 3 }



Sentencia de contexto gráfico


Ejecuta las primitivas gráficas 2D de JMEScript; dibuja líneas, polígonos, puntos,… , muestra mensajes de usuario, entrada de datos de usuario, lectura de pulsaciones, toques/clicks, ….

Para ejecutar esta sentencia debe estar definido un contexto gráfico en el sistema que ejecute JME/JMEScript. En JMEScriptGUI para escritorio pueden ejecutarse todas las primitivas. En el apartado para desarrolladores se explica como implementar un contexto gráfico para JMEScript a partir de la clase AbstractPrimitivas2D.


Diagrama sintáctico:


ctx2d [#<indice-lienzo>#] <claúsula-ctx2d>


El índice del lienzo por defecto es 1, e indica el lienzo a utilizar en caso de múltiples lienzos en el script.


+ Cláusula iniciar


La cláusula iniciar inicializa el contexto gráfico (típicamente crear un lienzo para el dibujo y mostrarlo). Permite definir el tamaño, posición y título del lienzo que lo contiene.


Diagrama sintáctico:

iniciar <ancho> <alto> [posicion <posicion>] [titulo <titulo>]

Snippet de código de ejemplo de juego de bola móvil (ver ejemplos)

Salida en JMEScriptGUI (escritorio)

   bola_loca.jmes


+ Cláusula finalizar


La cláusula finalizar finaliza el contexto gráfico (típicamente cierra el lienzo o lo inhabilita) especificado. También permite cerrar todos los lienzos si hay más de uno con *.


Diagrama sintáctico:

finalizar [*]

Snippet de código de ejemplo de juego de bola móvil (ver ejemplos)


+ Cláusula color


Establece el color de línea, de relleno y fondo del lienzo.

El color puede establecerse mediante

  • un vector [rojo,verde,azul] o [rojo,verde,azul,trasparencia], con valores [0-255]
  • una cadena de color HTML, ejemplo: '#3F2AB0', '#FFFFFF77'
  • un entero RGBA de 32 bits con el patrón  [bit 31]■■■■■■■■ ■■■■■■■■ ■■■■■■■■ ■■■■■■■■[bit 0]

Si no se establece un determinado color, se mantiene el valor previo.


Diagrama sintáctico:

color [linea <color>] [relleno <color>] [fondo <color>]

Ejemplo que dibuja dos rectángulos de diferentes colores en diferentes formatos de color

Salida en JMEScriptGUI (escritorio)


+ Cláusula gradiente


Aplica un gradiente de color lineal o radial a relleno y trazo.

El inicio y final indican el comienzo del primer color y último en gradiente lineal o centro y foco en radial.
Las fracciones y colores son vectores de la misma dimensión conteniendo las longitudes ponderadas de cada patrón de color.
El ciclo repite, refleja, o ninguna de las dos, el patrón.
La matriz aplica una matriz de transformación al patrón (por defecto la identidad).


Diagrama sintáctico:

gradiente inicio <posición> final <posición> [radio <radio>] fracciones <fracciones> colores <colores> [ciclo <ciclo>] [matriz <matriz>]

Ejemplo de gradiente

Salida en JMEScriptGUI (escritorio)


+ Cláusula trazo


Establece el grosor y tipo de trazo de las líneas en las formas geométricas.

Nota: el grosor se ve afectado por la escala de la matriz de transformación.

Si no se establece un determinado parámetro, se mantiene el valor previo, excepto si sólo se especifica grosor, que establece a 'redondo' los extremos y uniones y elimina el rayado (el rayado también se elimina con [0]).

Los valores otro1, otro2, otro3 permiten ampliar la implementación estándar (no tienen efecto en JMEScriptGUI).


Diagrama sintáctico:


trazo grosor <grosor> [extremo <extremo>] [union <union>] [inglete <inglete>] [raya <raya] [fase <fase>]

Ejemplo de distintos trazos

Salida en JMEScriptGUI (escritorio)


+ Cláusula punto


Dibuja un punto en el lienzo.

Cuatro tipos de puntos pueden dibujarse en la implementación estándar; pixel, rombo, cruz, mira. El valor por defecto es pixel.

Los valores otro1, otro2, otro3 permiten ampliar la implementación estándard (no tienen efecto en JMEScriptGUI).


Diagrama sintáctico:

punto <posicion> [tipo <tipo>]

Ejemplo de 4 tipos de puntos

Salida en JMEScriptGUI (escritorio)


+ Cláusula segmento


Dibuja un segmento en el lienzo.

Los parámetros inicio y final determinan los extremos del segmento.


Diagrama sintáctico:


segmento inicio <extremo> final <extremo>

Ejemplo de cláusula segmento

Salida en JMEScriptGUI (escritorio)


+ Cláusula rectangulo


Dibuja un rectángulo en el lienzo.

Los parámetros inicio y final determinan las esquinas superior izquierda e inferior derecha respectivamente. El parámetro esquina acepta un vector [semiejeX,semiejeY] que permite definir bordes redondeados. relleno y borde son flags que indican si se dibuja el interior y la frontera del rectángulo respectivamente (por defecto, borde es verdadero si no se especifica relleno). 3d indica el tipo de de relieve (resaltado, grabado o ninguno (defecto)). Los valores otro1, otro2, otro3 permiten ampliar la implementación estándar (no tienen efecto en JMEScriptGUI).


Diagrama sintáctico:


rectangulo inicio <esquina_sup_izq> final <esquina_inf_der> [esquina <radio_esquinas>] [relleno <relleno>] [borde <borde>] [3d <resaltado>]

Ejemplo de cláusula rectangulo

Salida en JMEScriptGUI (escritorio)


+ Cláusula circunferencia


Dibuja una circunferencia, círculo o arco en el lienzo.


Diagrama sintáctico:


circunferencia centro <centro> radio <radio> [angulo <angulo_ini>] [arco <long_arco>] [cierre <tipo_cierre>] [relleno <relleno>] [borde <borde>]

Ejemplo de cláusula circunferencia

Salida en JMEScriptGUI (escritorio)


+ Cláusula elipse


Dibuja una elipse en el lienzo.


Diagrama sintáctico:


elipse inicio <esquina_sup_izq> final <esquina_inf_der> [relleno <relleno>] [borde <borde>]

Ejemplo de cláusula elipse

Salida en JMEScriptGUI (escritorio)


+ Cláusula poligono


Dibuja un polígono en el lienzo.

Las coordenadas se introducen mediante un vector de coordenadas X y un vector Y (de la misma dimensión).

La cláusula cerrar a falso permite dejar abierto el polígono en los extremos (defecto verdadero).


Diagrama sintáctico:

poligono coordx <coordx> coordy <coordy> [cerrar <cerrar>] [relleno <relleno>] [borde <borde>]

Ejemplo de cláusula poligono

Salida en JMEScriptGUI (escritorio)


+ Cláusula ruta


Dibuja una ruta genérica en el lienzo a partir de diferentes acciones.

La ruta se establece mediante una matriz nx2 en que cada fila especifica la acción y un vector de puntos 2D:
 | <acción1> <puntos1> |
 | <acción2> <puntos2> |
 | <acción3> <puntos3> |
    …
 | <acciónN> <puntosN> |

Las acciones y puntos se especifican mediante:

  • 'mover_a': y el punto al que se desplaza el pincel sin dibujar en forma de matriz [[x1,y1]]
  • 'linea_a': y el punto al que se desplaza el pincel dibujando una línea en forma de matriz [[x1,y1]]
  • 'cuad_a': y los dos puntos que completan la curva de tres puntos en forma de matriz [[x1,y1],[x2,y2]]
  • 'curva_a': y los tres puntos que completan la curva de cuatro puntos en forma de matriz [[x1,y1],[x2,y2],[x3,y3]]
  • 'cerrar': cierra la curva si es cerrada especificando un vector vacío

regla especifica la regla de determinación del interior (winding-rules), sus valores son 'no_cero' o 'par_impar' (por defecto)


Diagrama sintáctico:


ruta <matriz-acciones> [regla <regla>] [relleno <relleno>] [borde <borde>]

Ejemplo de cláusula ruta

Salida en JMEScriptGUI (escritorio)


+ Cláusula texto


Dibuja y mide texto en el lienzo.

El parámetro dimension en devuelve un vector [ancho,alto] con el tamaño del texto en la variable especificada.

Si no se especifica un parámetro relativo a la fuente, se mantiene el actual.

La cláusula estilo toma los valores plano, negrita, cursiva, neg_cur. Los valores otro1, otro2, otro3 permiten ampliar la implementación estándar (no tienen efecto en JMEScriptGUI).


Diagrama sintáctico:


texto [posicion <posicion>]|[dimension en <varname>] [tama <tamaño>] [familia <familia>] [estilo <estilo>]

Ejemplo de cláusula 'texto'

Salida en JMEScriptGUI (escritorio)

Ejemplo de sombreado

Salida en JMEScriptGUI (escritorio)


+ Cláusula repintar


Redibuja el lienzo con los últimos cambios


Diagrama sintáctico:


repintar

Ejemplo de cláusula 'repintar'

Salida en JMEScriptGUI (escritorio)


+ Cláusula limpiar


Borra el lienzo o una parte de él con el color de fondo o el color especificado.

Si no se especifica el color se tomará el color de fondo.

Si no se especifica la posición inicial y final, se limpia todo el lienzo.


Diagrama sintáctico:


limpiar [inicio <posicion> final <posicion>] [color <color>]

Ejemplo de cláusula 'limpiar'

Salida en JMEScriptGUI (escritorio)


+ Cláusula matriz


Establece la matriz de transformación del lienzo u obtiene la actual matriz del lienzo en formato de vector de 6 elementos.

La matriz puede establecerse de cuatro formas:

  • matriz normalizada de 3x3:
    | sx shx tx | | shy sy ty | | 0 0 1 |
  • matriz 2x3:
    | sx shx tx | | shy sy ty |
  • vector de 4 elementos (sin traslación):
    [ sx shy shx sy ]
  • vector de 6 elementos:
    [ sx shy shx sy tx ty ]

Si se añade el modificador *, la matriz especificada se premultiplicará a la actual


Diagrama sintáctico:


{matriz|matriz* <matriz>|<vector>}|{matriz en <varname>}

Ejemplo de cláusula 'matriz'

Salida en JMEScriptGUI (escritorio)


+ Cláusula entorno


Devuelve un diccionario con información del sistema, del lienzo, del lenguaje JMEScript y el parser JME (no es necesario que el lienzo esté inicializado)


Diagrama sintáctico:

entorno en <varname>

Ejemplo de cláusula 'entorno'

Salida en consola:

{ 'jme.lib_name': 'JME' 'jme.version': '0.6.2.0' 'jmescript.lang_name': 'JMEScript' 'jmescript.version': '0.2.0' 'ui.name': 'JMEScriptGUI' 'ui.version_name': '' 'ui.version': '0.1.2.0' 'prim2d.lib_name.name': 'JMEScriptGUI Primitivas2D' 'prim2d.lib_name.version_name': 'Viviana para JMEScriptGUI' 'prim2d.lib_name.version': '0.1.0' 'lienzo.inicializado': falso 'lienzo.ancho': -1 'lienzo.alto': -1 'lienzo.color.linea': [0,0,0,255] 'lienzo.color.relleno': [0,0,0,255] 'lienzo.color.fondo': [0,0,0,255] 'pantalla.ancho': 1920 'pantalla.alto': 1080 'user.dir': 'C:\(...)' 'user.home': 'C:\(...)' 'user.name': 'M(...)' 'java.vendor': 'Oracle Corporation' 'sun.java.launcher': 'SUN_STANDARD' 'NUMBER_OF_PROCESSORS': '8' 'sun.management.compiler': 'HotSpot 64-Bit Tiered Compilers' 'FPS_BROWSER_USER_PROFILE_STRING': 'Default' 'os.name': '(...)' 'RegionCode': 'EMEA' 'sun.boot.class.path': 'C:\(...)' 'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC' 'sun.desktop': '(...)' 'java.vm.specification.vendor': 'Oracle Corporation' 'TEMP': 'C:\(...)' 'java.runtime.version': '(...)' 'FPS_BROWSER_APP_PROFILE_STRING': '(...)' 'CommonProgramW6432': 'C:\(...)' 'ProgramFiles': 'C:\(...)' 'OnlineServices': 'Online Services' 'user.language': 'es' 'OneDrive': 'C:\(...)' 'sun.boot.library.path': 'C:\(...)' 'LOCALAPPDATA': 'C:\(...)' 'java.version': '1(...)' 'PROCESSOR_LEVEL': '6' 'user.timezone': 'Europe/Paris' 'USERNAME': 'M(...)' 'sun.arch.data.model': '64' 'java.endorsed.dirs': 'C:\(...)' 'sun.cpu.isalist': '(...)' 'sun.jnu.encoding': 'Cp1252' 'file.encoding.pkg': 'sun.io' 'HOMEDRIVE': 'C:' 'file.separator': '\' 'java.specification.name': 'Java Platform API Specification' '=::': '::\' 'java.class.version': '52.0' 'user.country': 'ES' 'ALLUSERSPROFILE': 'C:\(...)' 'java.home': 'C:\(...)' 'java.vm.info': 'mixed mode' 'os.version': '(...)' 'platformcode': 'KV' 'TMP': 'C:\(...)' 'path.separator': ';' 'java.vm.version': '2(...)' 'user.variant': '' 'CommonProgramFiles(x86)': 'C:\(...)' 'sun.awt.enableExtraMouseButtons': 'true' 'USERDOMAIN_ROAMINGPROFILE': 'H(...)' 'java.awt.printerjob': 'sun.awt.windows.WPrinterJob' 'CommonProgramFiles': 'C:\(...)' 'sun.io.unicode.encoding': 'UnicodeLittle' 'ComSpec': 'C:\(...)' 'HOMEPATH': '\(...)' 'awt.toolkit': 'sun.awt.windows.WToolkit' 'COMPUTERNAME': 'H(...)' 'user.script': '' 'VBOX_MSI_INSTALL_PATH': 'C:\(...)' 'PUBLIC': 'C:\(...)' 'ProgramData': 'C:\(...)' 'java.specification.vendor': 'Oracle Corporation' 'USERPROFILE': 'C:\(...)' 'java.library.path': 'C:\(...)' 'java.vendor.url': 'http://java.oracle.com/' 'APPDATA': 'C:\(...)' 'java.vm.vendor': 'Oracle Corporation' 'java.runtime.name': 'Java(TM) SE Runtime Environment' 'sun.java.command': 'jmescriptgui.Launcher' 'java.class.path': 'C:\(...)' 'Path': 'C:/(...)' 'java.vm.specification.name': 'Java Virtual Machine Specification' 'PROCESSOR_ARCHITECTURE': '(...)' 'PROCESSOR_IDENTIFIER': '(...)' 'SESSIONNAME': 'Console' 'java.vm.specification.version': '1.8' 'sun.cpu.endian': 'little' 'sun.os.patch.level': '' 'OS': '(...)' 'java.io.tmpdir': 'C:\(...)' 'ProgramFiles(x86)': 'C:\(...)' 'java.vendor.url.bug': 'http://bugreport.sun.com/bugreport/' 'SystemRoot': 'C:\(...)' 'os.arch': '(...)' 'java.awt.graphicsenv': 'sun.awt.Win32GraphicsEnvironment' 'java.ext.dirs': 'C:\(...)' 'LOGONSERVER': '\(...)' 'USERDOMAIN': 'H(...)' 'line.separator': ' ' 'java.vm.name': 'Java HotSpot(TM) 64-Bit Server VM' 'PROCESSOR_REVISION': '8e0a' 'ProgramW6432': 'C:\(...)' 'windir': 'C:\(...)' 'file.encoding': 'Cp1252' 'SystemDrive': 'C:' 'java.specification.version': '1.8' 'PSModulePath': 'C:\(...)' 'DriverData': 'C:\(...)' }


+ Cláusula click


Captura eventos de ratón, touchpad, toque en pantalla,….
También permite ignorar todos los eventos encolados en ese momento o parte de ellos.

El valor almacenado en la variable es un diccionario con la información del evento de ratón:

  • id: tipo de evento; [PRESSED, RELEASED, CLICKED, MOVED, DRAGGED, ENTERED, EXITED, WHEEL, OTRO1, OTRO2, OTRO3]
  • boton: nº de botón; [BOTON1, BOTON2, BOTON3, NO_BOTON, OTRO1, OTRO2, OTRO3]
  • clicks: nº de clicks
  • posicion: vector bidimensional con la posición en el lienzo (no tiene en cuenta transformaciones)
  • posicion_en_pantalla: vector bidimensional con la posición respecto al dispositivo
  • timestamp: marca de tiempo del evento
  • rotaciones: rotaciones de la rueda (positivas o negativas), solo para evento WHEEL

Si no hay ningún evento, la variable queda indefinida.

La cláusula consumir elimina todos los eventos actuales o si se especifica un número de no descartados, elimina todos los eventos excepto los especificados.
consumir 1 mantiene el último evento, consumir 0 es igual a consumir.


Diagrama sintáctico:

click [en <varname>]|[consumir [<eventos>]]

Snippet de cláusula 'click' del script garabatos.jmes

Salida en JMEScriptGUI (escritorio)

   garabatos.jmes

   aguanta.jmes



+ Cláusula pulsacion


Captura eventos de teclado.
También permite ignorar todos los eventos encolados en ese momento o parte de ellos.

El valor almacenado en la variable es un diccionario con la información del evento de teclado:

  • keycode: código asociado al evento
  • modifiers: modificadores del evento;
    ValorConstante JavaConstante JME
    64AbstractKeyStroke.SHIFT_shift_
    128AbstractKeyStroke.CONTROL_control_
    256AbstractKeyStroke.META_meta_
    512AbstractKeyStroke.ALT_alt_
    1024AbstractKeyStroke.BUTTON1_button1_
    2048AbstractKeyStroke.BUTTON2_button2_
    4096AbstractKeyStroke.BUTTON3_button3_
    8192AbstractKeyStroke.ALT_GRAPH_alt_graph_
    Ejemplo, comprobar mayúsculas y alt;tecla;#;'modifiers' = (_shift_|_alt_)
  • modifierstext: descripción textual de los modificadores. Nota: esta representación es dependiente de la plataforma y el idioma, y no debería usarse para controlar el flujo del script
  • keychar: Carácter Unicode asociado al evento (puede estar indefinido)
  • iskeyreleased: verdadero si el evento ocurre al soltar
  • timestamp: marca de tiempo del evento
  • esaccion: verdadero si es evento de acción (SHIFT, CONTROL, NUMPAD,…)
  • localizacion: localización para teclas dobles; [ESTANDAR,DERECHA,IZQUIERDA,NUMPAD,DESCONOCIDA]

Si no hay ningún evento, la variable queda indefinida.

La cláusula consumir elimina todos los eventos actuales o si se especifica un número de no descartados, elimina todos los eventos excepto los especificados.
consumir 1 mantiene el último evento, consumir 0 es igual a consumir.


Diagrama sintáctico:


pulsacion [en <varname>]|[consumir [<eventos>]]

Snippet de cláusula 'pulsacion' del script 2048.jmes

Salida en JMEScriptGUI (escritorio)

   2048.jmes

   lector_pulsaciones.jmes



+ Cláusula msj


Muestra mensajes al usuario por el intefaz gráfico.

Los mensajes toast se mostrarán el tiempo especificado en milisegundos.

En JMEScriptGUI, los tipos debug y confirm son idénticos a info y ok sólo está disponible para toast, en caso contrario también es idéntico a info. El tipo por defecto es info. La posición sólo está disponible para toast, si no se especifica, aparece centrado-abajo. Los tipos otro1, otro2 y otro3 no forman parte del estándar y permiten ampliar la funcionalidad.


Diagrama sintáctico:


msj <msj> [tipo <tipo>] [posicion <posición>] [toast <tiempo>]

Ejemplo de cláusula 'msj'

Salida en JMEScriptGUI (escritorio)


+ Cláusula leer


Permite al usuario introducir información a través del interfaz gráfico.

  • tipo booleano; hace una pregunta SI/NO y devuelve Booleano,
  • tipo texto; lee un texto y devuelve Texto,
  • tipo expresion; lee una expresión JME, la evalúa con las variables actuales del script y devuelve el resultado,
  • tipo opciones; ofrece un grupo de opciones y devuelve la escogida como Texto

Los tipos otro1, otro2 y otro3 no forman parte del estándar y permiten ampliar la funcionalidad.

Si la entrada de datos se cancela, se obtiene la constante de error __error1__. Si se produce un error, se obtiene la constante __error2__ (véase ejemplo).


Diagrama sintáctico:


leer en <varname> [tipo <tipo>] [opciones <opciones>] [msj <mensaje>]

Ejemplo de cláusula 'leer'

Salida en JMEScriptGUI (escritorio)


+ Cláusula archivo


Muestra un diálogo del sistema de archivos para la selección de uno o varios archivos/directorios.

Si no se especifica la ruta se toma la ruta por defecto del sistema. Si se usa la cadena vacía, la ruta de la última ejecución (si la hay).

El valor devuelto es un vector con las rutas de los archivos/directorios seleccionados. Si se cancela o no se selecciona ninguno, devuelve un vector vacío.


Diagrama sintáctico:


archivo en <varname> [ruta <ruta>] [titulo <titulo>]

Ejemplo de cláusula 'archivo'

Salida en JMEScriptGUI (escritorio)



Sentencia de acceso a SGBDR


Permite conectar a una base de datos, cerrarla, acceder a la información y modificar datos.

Para ejecutar esta sentencia debe estar definido un SGBDR en el sistema que permita ejecutar todas sus primitivas mediante JMEScript. En JMEScriptGUI para escritorio pueden ejecutarse todas las primitivas usando MySQL y el Conector-J. En el apartado para desarrolladores se explica como implementar un interfaz de conexión entre la base de datos y JMEScript a partir de la clase AbstractSql.


Diagrama sintáctico:


sql [# <indice> #] <claúsula_sql>


El índice del SGBDR por defecto es 1, e indica el SGBDR a utilizar en caso de múltiples gestores.


sql_visor.jmes

Ejemplo completo: visor SQL para la lectura secuencial de consultas

+ Cláusula conectar


La cláusula conectar permite especificar el host, usuario (si existe), y clave (si existe) de la conexión a la BD, además de asignarle un identificador (puede ser cualquier valor JME).


El valor almacenado en la variable de la subcláusula exito en es verdadero si la conexión es correcta, si no lo es, se almacena un texto con el mensaje de error


Diagrama sintáctico:


conectar exito en <varname> id <id_conexión> host <host> [usuario <user>] [clave <password>]


Ejemplo de cláusula 'conectar'

Salida en consola:

- host falso: 'Communications link failure The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.' - necesario usuario: 'Access denied for user '[...]'@'[...]' (using password: NO)' - conexión válida: verdadero


+ Cláusula ejecutar


La cláusula ejecutar efectúa una consulta contra la base de datos.


La cláusula toma el identificador de la conexión en id y asigna un identificador de consulta en sentencia (cualquier valor JME).

El valor almacenado en la variable de la subcláusula exito en es un booleano si la consulta es correcta, si no lo es, se almacena un texto con el mensaje de error. El valor booleano es verdadero si la consulta es de selección, y falso si es de acción


Diagrama sintáctico:


ejecutar exito en <varname> id <id_conexión> sentencia <id_sent> consulta <consulta_sql>


Ejemplo de cláusula 'ejecutar'

Salida en consola:

- consulta 1: falso 'consulta de acción' - consulta 2: verdadero 'consulta de selección' - consulta 3: 'Table 'ej6_catastro_municipal.inexistente' doesn't exist' - consulta 4: 'You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'desde vivienda' at line 1'


+ Cláusula resultado


La cláusula resultado lee secuencialmente los registros o número de actualizaciones de una consulta a partir del identificador de consulta.


La cláusula toma el identificador de la conexión en id y el identificador de consulta en sentencia de la sentencia ejecutar.

La subcláusula columnas permite especificar la columnas devueltas en una selección. Columnas individuales se especifican con el nombre de la columna (ej. 'nombre'), múltiples con un vector de columnas (ej. ['nombre','apellidos']). Por defecto es '*', que muestra todas las columnas especificadas en la consulta.

El valor almacenado en la variable de la subcláusula registro en es

  • un diccionario si la lectura de una consulta de selección es correcta,
  • un entero si es una consulta de acción correcta,
  • valor nulo si se ha terminado la lectura del conjunto de resultados,
  • un texto distinto de nulo con el mensaje de error si ha ocurrido un error en la lectura

Diagrama sintáctico:


resultado id <id_conexión> sentencia <id_sent> [columnas <columnas>] registro en <varname>


Ejemplo de cláusula 'resultado'

Salida en consola:

{ 'calle': 'Damasco' 'numero': 20 'tipo_vivienda': 'B' 'codigo_postal': 14500 'metros': 120 'od_vivienda': 0 'nombre_zona': 'SECTOR SUR' } { 'calle': 'Guerra' 'numero': 2 'tipo_vivienda': 'C' 'codigo_postal': 14500 'metros': 150 'od_vivienda': 0 'nombre_zona': 'SUBURBIO TERMINAL' } { 'calle': 'Urbanización las Lomas' 'numero': 53 'tipo_vivienda': 'C' 'codigo_postal': 14009 'metros': 250 'od_vivienda': 0 'nombre_zona': 'TRASIERRA' } -- terminado --


+ Cláusula cerrar


La cláusula cerrar cierra la conexión con la base de datos especificada por el identificador.

La subcláusula exito en permite almacenar en una variable el resultado de la operación; verdadero si la conexión se cerró correctamente o un mensaje de error en caso contrario.


Diagrama sintáctico:


cerrar [exito en <varname>] id <id_conexión>


Ejemplo de cláusula 'cerrar'

Salida en consola:

'Cerrada conexión con1'



Acciones en el entorno de ejecución


La sentencia accion permite ejecutar acciones preprogramadas en la propia aplicación JAVA donde ejecuta el script de JMEScript.

Si la aplicación implementa el interfaz AbstractAppAcciones con un mapa de acciones definidas por su identificador (String), pueden ejecutarse esas acciones mediante esta sentencia, recibir parámetros y devolver un valor convertido a tipo JME. Esto aporta a la aplicación mucho más que simplemente la creación de macroinstrucciones (MACROS), ya que incluye toda la funcionalidad del lenguaje JMEScript, y no sólo acciones secuenciales predefinidas (similarmente a VBA en el paquete Office).

En la sección de desarrolladores, puede verse un ejemplo de implementación del interfaz AbstractAppAcciones usado por el IDE JMEScriptGUI.

Nota: existe otra forma de ejecución de operaciones de la aplicación y de la API de JAVA, en este caso funcional, mediante la función java.


Diagrama sintáctico:


accion <accion_id> [params <parámetros>] [retorno en <varname>]


Ejemplo de sentencia 'accion' en JMEScriptGUI

Salida en JMEScriptGUI

Variables en JMEScript


Las variables en JMEScript pueden almacenar cualquier tipo de terminal JME o estar indefinidas. Su valor se puede cambiar por el de cualquier otro tipo al ser un lenguaje débilmente tipado, o eliminarse y posteriormente asignar un nuevo valor. La función JME indefinido permite comprobar si la variable está definida o asignar un valor por defecto.

El nombre de las variables, como cualquier identificador JME, debe estar formado por un carácter inicial que debe ser una letra o _ , y seguido por cualquier número de carácteres alfanuméricos o _ .

Las variables son accesibles en todo el script a partir del punto en que se definen (si son externas desde el principio) hasta el final del script, excepto dentro de las rutinas 'locales', a menos que se eliminen mediante los operadores := y :== o la sentencia limpiar.

Existen por lo tanto dos ámbitos en las variables de JMEScript, el del programa principal y el de las rutinas 'locales' (no globales), que tienen su propio diccionario de variables. Las variables definidas en bloques o sentencias siguen siendo visibles posteriormente.



Variables externas


Los scripts pueden recibir datos a través de variables definidas externamente, y que son visibles en el script. Esto convierte a un script JMEScript en una especie de función, capaz de recibir parámetros externos y devolver un resultado.

Las variables externas pueden definirse a través de JAVA si se está desarrolando una aplicación o a través de la función script, que permite ejecutar un script dentro de JME.


Ejemplo de llamada sin variables externas al script 'palindromo.jmes' y ejemplo pasándole variable externa 'text':


Salida en REPL:

>>> script(archivotxt('palindromo.jmes'),dic([])) ==> Texto: 'MEROL MUSPI ROLOD TIS TEMA RUTETCESNOC GNICSIPIDA TILE DES DOMSUIE ROPMET TNUDICNI TU EROBAL TE EROLOD ANGAM AUQILA TU MINE DA MINIM MAINEV SIUQ DURTSON NOITATICREXE OCMALLU SIROBAL ISIN TU DIUQILA XE AE IDOMMOC TAUQESNOC SIUQ ETUA ERUI TIREDNEHERPER NI ETATPULOV TILEV ESSE MULLIC EROLOD UE TAIGUF ALLUN RUTAIRAP RUETPECXE TNIS TACEACBO TATIDIPUC NON TNEDIORP TNUS NI APLUC IUQ AICIFFO TNURESED TILLOM MINA DI TSE MUROBAL' (parse: 543µs(4%) / eval: 14,3ms(96%) / total: 14,9ms)



Salida en REPL:

>>> script(archivotxt('palindromo.jmes'),dic('text','Hola Mundo')) ==> Texto: 'ALOH ODNUM' (parse: 2,8ms(16%) / eval: 14,3ms(84%) / total: 17,1ms)



Comentarios, líneas en blanco y multilínea


Los comentarios en JMEScript son comentarios de una línea que comienzan con el carácter #.
Pueden ir precedidos por espacios o ir al final de una sentencia, y al igual que las líneas en blanco, son ignorados por el proceso de compilación.

Una sentencia JMEScript muy larga puede fragmentarse simplemente añadiendo > al principio de la siguiente línea por cualquier parte de la misma.



JMEScript para Desarrolladores


La clase jme.script.Script es la clase principal de los scripts JMEScript. Consta de dos constructores, uno para una cadena con el código fuente (con saltos de línea en cada instrucción), y otro con las líneas del código fuente como array de cadenas:

	/**
	 * Compila script a partir de cadena de código fuente JMEScript
	 * 
	 * @param script código fuente
	 * @throws ScriptException error al compilar script
	 */
	public Script( String script ) throws ScriptException 
	/**
	 * Compila script a partir de líneas del código fuente JMEScript
	 * 
	 * @param lineas array con las líneas del script
	 * @throws ScriptException error al compilar script
	 */
	public Script( String[] lineas ) throws ScriptException 

Los constructores de la clase Script compilan e inicializan el script (identifican las sentencias y parsean las expresiones JME).

El script puede ser ejecutado con los métodos Script#ejecutar, que ejecuta el script sin ninguna variable externa, y Script#ejecutar( HashMap<String,Token> varMapIni ) que permite inicializar con un mapa de variables externas.

El script se puede ejecutar múltiples veces. Evitar compilar el mismo script más de una vez (por ejemplo en un bucle), ya que compilar puede ser un proceso lento, y solo es necesario una vez.

Si el script ha finalizado correctamente, puede obtenerse el resultado del script (si se ha especificado una instrucción devolver con parámetro), mediante los métodos:

El resultado es de tipo Terminal o también puede ser null si el script no devuelve nada, o usa devolver sin parámetro, o no ha terminado correctamente.

El script puede detenerse cuando está ejecutando en un hilo separado (que sería la forma correcta de hacerlo, véase la sección para ScriptThread), llamando al método Thread#interrupt() del hilo. Esto devolverá un ScriptException.

Puede comprobarse que un script ha terminado si se ejecuta en otro hilo comprobando que Script#isTerminado es verdadero o Script#isInterrumpido es verdadero.

Los métodos Script#getScript y Script#getLineas permiten leer el código fuente original del script (como cadena única o array de cadenas).

El método estático Script#cargarScriptDesdeArchivo( String filename ) permite cargar las líneas del script desde un archivo de texto, a partir de la ruta y nombre del archivo, que pueden pasarse al constructor.

Los métodos gettesr&setters Script#getEntrada, Script#setEntrada, Script#getSalida y Script#setSalida, permiten establecer los stream de entrada y salida por consola, por defecto System.in y System.out (puede asignarse null).


Ejemplo de ejecución del siguiente script en Java:

// crear y compilar script
Script script = new Script( Script.cargarScriptDesdeArchivo( "./primos_gemelos.jmes" ) );

// crear mapa de variables externas
HashMap<String,Token> vars = new HashMap<>();
vars.put( "m", new RealDoble(1) );
vars.put( "n", new RealDoble(100) );

// ejecutar y resultado
System.out.println( script.ejecutar(vars).getUltimoResultadoDiccionario().toStringVertical( " ", ": " ) );

// modificar variables externas
vars.put( "m", new RealDoble(1001) );
vars.put( "n", new RealDoble(1100) );

// ejecutar y resultado
System.out.println( script.ejecutar(vars).getUltimoResultadoDiccionario().toStringVertical( " ", ": " ) );

{ 'lista parejas': [[3,5],[5,7],[11,13],[17,19],[29,31],[41,43],[59,61],[71,73]] 'parejas': 8 'lista individual': [3,5,7,11,13,17,19,29,31,41,43,59,61,71,73] 'primos gemelos': 15 'aproximacion Brun': 1.330990365719087 } { 'lista parejas': [[1019,1021],[1031,1033],[1049,1051],[1061,1063],[1091,1093]] 'parejas': 5 'lista individual': [1019,1021,1031,1033,1049,1051,1061,1063,1091,1093] 'primos gemelos': 10 'aproximacion Brun': 0.009518280355144647 }



Ejecución en hilo


Al ejecutar determinados scripts siempre existe la posibilidad de que generen un bucle extremadamente largo o infinito (así como ciertas operaciones en JME). Por eso, la mejor forma de ejecutar cualquier script es usar un hilo. Cargando el script en un hilo, éste puede detenerse mediante Thread#interrupt, lo que hará que el script intente detenerse lo antes posible al detectar el hilo como interrumpido, lo que lanzará una excepción. Como se comentó en la sección anterior, puede comprobarse que el script está interrumpido con Script#isInterrumpido (también se interrumpe por una excepción del propio script, no solo por una señal de interrupción).

Puede realizarse espera activa comprobando si el hilo ha terminado o a sido interrumpido mediante instrucciones como

while ( !(script.isTerminado() || script.isInterrumpido()) ) /* hacer cosas */;

pero si no se están realizando acciones mientras se espera al resultado, es preferible usar Thread#join, y evitar la espera activa.



Clase ScriptThread


Puede utilizarse cualquier tipo de hilo para lanzar un script JMEScript (en Java 8+, por ejemplo, un FutureTask sería una buena opción), pero la clase jme.script.ScriptThread permite lanzar fácilmente un script, incluyendo la posibilidad de un temporizador.

ScriptThread incluye dos constructores, iniciando a partir de un script compilado y un mapa opcional de variables iniciales. El segundo permite añadir el tiempo máximo aproximado de ejecución:

/**
 * Crea un hilo a partir del script y un mapa inicial de variables
 * sin limite de tiempo
 * 
 * @param script Script compilado
 * @param varMap mapa de variables iniciales o {@code null}
 */
public ScriptThread( Script script, 
		     HashMap<String,Token> varMap )


/**
 * Crea un hilo a partir del script y un mapa inicial de variables
 * con el límite de tiempo especificado en milisegundos
 * 
 * @param script Script compilado
 * @param varMap mapa de variables iniciales o {@code null}
 * @param maxTiempo tiempo antes de parar el script en milisegundos
 */
public ScriptThread( Script script, 
		     HashMap<String,Token> varMap,
		     long maxTiempo )

El método ScriptThread#getScript permite obtener la instancia del script, y ScriptThread#getException la excepción producida en caso de error o interrupción.

El método ScriptThread#alTerminar es una rutina vacía en la clase base que ejecuta al terminar el script haya excepción o no. Puede reescribirse para ejecutar cualquier código que se desee al terminar (avisar al hilo principal, imprimir un mensaje,...).

El método ScripThread#startAndJoin, lanza el hilo del script y realiza un 'join' del hilo padre a la espera del resultado y puede capturar una excepción de tipo Throwable (generalmente una ScriptException) en caso de error o interrupción.


Ejemplo de script con temporizador en 2 segundos:

Script script = new Script(
		"mientras rand(-1)<0.999 inicio:",
		"jme: dormir(1e7)",
		"imprimir randint(1,6),",
		"fin" );

ScriptThread scriptThread = new ScriptThread( script, null, 2000 ) {
	@Override
	public void alTerminar() 
	{
		if ( getScript().isTerminado() )
			System.out.println( "--- Script terminado ---" );
		else if ( getScript().isInterrumpido() )
			System.out.println( "--- Script interrumpido ---" );
	}
};
try
{
    scriptThread.startAndJoin();
}
catch ( Throwable e )
{
    System.err.println( e.getMessage() );
}
Ejecución 1:

6 4 5 6 4 2 6 3 6 5 4 6 5 2 6 3 1 1 4 4 5 3 3 3 1 3 4 3 2 2 4 2 2 1 4 1 3 2 4 5 2 2 5 5 4 5 6 4 6 2 5 4 2 1 1 4 1 3 6 1 6 6 4 5 4 6 6 1 1 5 4 6 1 5 4 6 1 4 2 2 5 2 1 4 2 3 2 4 3 1 5 6 2 4 --- Script terminado ---


Ejecución 2:

5 2 6 6 3 6 4 2 2 6 1 6 4 3 6 1 1 4 6 1 3 6 3 2 1 6 4 1 2 5 4 3 1 1 3 1 1 3 2 1 4 3 5 4 2 5 3 6 3 3 3 2 3 1 6 5 2 5 4 4 6 1 6 2 1 4 3 6 4 4 2 2 1 5 4 6 3 6 2 5 3 4 4 4 4 5 4 3 2 6 6 2 4 1 3 1 4 1 3 5 4 1 3 2 3 3 1 3 3 4 3 5 2 2 3 5 --- Script interrumpido --- <<<ScriptException>>> en la sentencia '[#3: imprimir randint(1,6),]': <<<FuncionException>>> en funcion "randint": Tiempo expirado --> randint(Vector:[1,6])



Implementación de soporte gráfico


Para implementar lienzos 2D y E/S mediante un interfaz gráfico, es necesario implementar el interfaz jme.script.ctx2d.AbstractPrimitivas2D, y asignar la clase implementante al script, antes de ejecutarlo, mediante Script#getListaPrimitivas#add o Script#setListaPrimitivas para asignar una lista de contextos gráficos. Puede utilizarse la clase adaptadora me.script.ctx2d.Primitivas2DAdapter.

Interfaz jme.script.ctx2d.AbstractPrimitivas2D:

package jme.script.ctx2d;

import java.io.File;
import java.io.Serializable;
import java.util.List;
import java.util.Map;

import jme.Expresion;
import jme.abstractas.Numero;
import jme.abstractas.Terminal;
import jme.script.Script;
import jme.terminales.RealDoble;
import jme.terminales.Texto;
import jme.terminales.VectorEvaluado;

/**
 * Primitivas gráficas a implementar en cada sistema gráfico
 * @author Miguel Alejandro Moreno Barrientos, (C)2020-2021
 * @since 0.6.2.0, JMEScript 0.2.0
 * @version 0.6.2.1, JMEScript 0.2.0
 */
public interface AbstractPrimitivas2D 
{	
	//////////////////////////////////////////////////////
	// ENUMERACIONES VÁLIDAS PARA PARÁMETROS ENUMERADOS
	//////////////////////////////////////////////////////
	
	/** Estilo de texto */
	static enum ESTILO { /** Texto plano */
						 PLANO,
						 /** Texto en negrita */
						 NEGRITA,
						 /** Texto en cursiva */
						 CURSIVA,
						 /** Texto en negrita y cursiva */
						 NEG_CUR, 
						 OTRO1, OTRO2, OTRO3 };
	/** Estilo de resaltado 3D */
	static enum RECT3D { RESALTADO, GRABADO, NINGUNO, OTRO1, OTRO2, OTRO3 };
	/** Estilo de redondeo de línea */
	static enum CAP { REDONDO, CUADRADO, TOPE, OTRO1, OTRO2, OTRO3 };
	/** Estilo de unión de líneas */
	static enum JOIN { REDONDO, BISEL, INGLETE, OTRO1, OTRO2, OTRO3 };
	/** Estilo de gradiente */
	static enum CICLO { CICLO, REFLEJO, NO_CICLO }
	/** Tipos de puntos */
	static enum PUNTO { /** Punto simple o círculo dependiendo del grosor */
						PIXEL,
						/** Punto con forma de rombo */
						ROMBO,
						/** Punto con forma de cruz */
						CRUZ,
						/** Punto con forma de mira */
						MIRA, 
						OTRO1, OTRO2, OTRO3 };
	/** Tipos de cierre de arco */
	static enum CIERRE { /** Deja el arco abierto */ 
						 ABIERTO,
						 /** Cierra el arco con un segmento */
						 CUERDA,
						 /** Cierra el arco uniendo con el centro */
						 TARTA };
	/** Tipos de segmentos en una ruta */
	static enum RUTA { /** Desplazar a posición */
					   MOVER_A,
					   /** Segmento desde la última posición */
					   LINEA_A,
					   /** Segmento de curva mediante tres nuevos puntos */
					   CURVA_A,
					   /** Segmento de curva mediante dos nuevos puntos */
					   CUAD_A,
					   /** Cerrar ruta */					   
					   CERRAR,
					   /** Aplica regla No Cero */
					   NO_CERO,
					   /** Aplica regla Par-Impar */
					   PAR_IMPAR, 
					   OTRO1, OTRO2, OTRO3 };
	/** Tipos de mensajes */
	static enum MSJ { /** Mensaje de información */
					  INFO,
					  /** Mensaje de advertencia */
					  WARNING,
					  /** Mensaje de error */
					  ERROR,
					  /** Mensaje de consulta al usuario */
					  QUESTION,
					  /** Mensaje plano */
					  PLAIN,
					  /** Mensaje de depuración */
					  DEBUG,
					  /** Mensaje de confirmación */ 
					  CONFIRM,
					  /** Mensaje de éxito */
					  OK, 
					  OTRO1, OTRO2,  OTRO3 }
	/** tipos de entrada de datos por parte del usuario */
	static enum ENTRADA { /** Introducir booleano o valor binario */
						  BOOLEANO,
						  /** Introducir texto directamente sin delimitador 
						   * {@link Texto#DELIMITADOR} */
						  TEXTO,
						  /** Introducir expresión JME */
						  EXPRESION,
						  /** Introducir valor desde conjunto de opciones */
						  OPCIONES, 
						  OTRO1, OTRO2, OTRO3 }

	
	////////////////////////////////////////////////
	// INFORMACIÓN DEL SISTEMA GRÁFICO Y LIBRERÍA
	////////////////////////////////////////////////

	/**
	 * Nombre de entorno gráfico en el que ejecutará JMEScript (ej: Swing, AWT, JavaFx, Android,...)
	 * @return nombre del UI
	 * @since 0.6.2.0, JMEScript 0.2.0
	 */
	String getUIName();
	
	/**
	 * Nombre de versión del entorno gráfico (si existe)
	 * @return nombre de la versión del UI
	 * @since 0.6.2.0, JMEScript 0.2.0
	 */
	String getUIVersionName();
	
	/**
	 * Versión del entorno gráfico (ej: 0.2.0, v3.7)
	 * @return versión numérica del UI
	 * @since 0.6.2.0, JMEScript 0.2.0
	 */
	String getUIVersion();
	
	/**
	 * Nombre de esta implementación 
	 * @return nombre
	 */
	String getPrim2DName();
	
	/**
	 * Nombre de versión de esta implementación
	 * @return nombre de versión
	 */
	String getPrim2DVersionName();
	
	/**
	 * Versión de esta implementación
	 * @return versión numérica de la versión
	 */
	String getPrim2DVersion();

	
	////////////////////////////////////
	// PRIMITIVAS DE CONTEXTO GRÁFICO
	////////////////////////////////////	
	
	/**
	 * Obtiene el componente padre del lienzo
	 * @return componente padre o null
	 */
	Object getPadre();
	
	/**
	 * Inicia un lienzo (canvas) para dibujar y le asigna un índice único
	 * @param indice índice del contexto {@code idx>0}
	 * @param ancho ancho del lienzo
	 * @param alto alto del lienzo
	 * @param posX posición inicial x
	 * @param posY posición inicial y
	 * @param titulo título del lienzo (o null)
	 */
	void iniciarContexto( int indice, int ancho, int alto, int posX, int posY, String titulo );
	
	/**
	 * Cerrar lienzo
	 */
	void finalizarContexto();
	
	/**
	 * Comprobar si se ha iniciado el lienzo
	 * @return {@code true} si está iniciado
	 */
	boolean esContextoIniciado();
	
	/**
	 * Repintar el lienzo especificado (generalmente éste)
	 * @param indice índice del lienzo
	 */
	void repintar( int indice );
	
	/**
	 * Ancho del lienzo 
	 * @return ancho en pixels del lienzo
	 */
	int getAncho();
	
	/**
	 * Alto del lienzo 
	 * @return alto en pixels del lienzo
	 */
	int getAlto();	
	
	/**
	 * Ancho del dispositivo 
	 * @return ancho del dispositivo en pixels
	 */
	int getAnchoPantalla();
	
	/**
	 * Alto del dispositivo  
	 * @return alto del dispositivo en pixels
	 */
	int getAltoPantalla();	

	/**
	 * Obtiene un mapa de propiedades del sistema definidas por el desarrollador 
	 * @return mapa de propiedades del sistema 
	 */
	Map getPropiedadesEntorno();
	
	
	//////////////////////////////
	// PRIMITIVAS DE E/S  (I/O)
	//////////////////////////////
	
	/**
	 * Muestra un mensaje al usuario mediante diálogo o tostada
	 * @param msj mensaje
	 * @param tipo {@link MSJ}
	 * @param posX coordenada X del mensaje. Puede ser null
	 * @param posY coordenada Y del mensaje. Puede ser null
	 * @param tiempo tiempo a mostrar el mensaje. Puede ser null
	 */
	void mostrarMensaje( String msj, MSJ tipo, Integer posX, Integer posY, Integer tiempo );
	
	/**
	 * Lectura de entradas de usuario.
* El valor almacenado deberá ser * {@link Expresion.ERROR#__error1__} * si se cancela la entrada.
* Si se evalúa un expresión y se obtiene un error, el valor almacenado deberá ser * {@link Expresion.ERROR#__error2__} * @param script script actual * @param entrada tipo de entrada de usuario; {@link ENTRADA} * @param varname variable para almacenar el valor * @param msj mensaje de petición de datos al usuario * @param opciones opciones para el tipo {@link ENTRADA#OPCIONES}. Puede ser null */ void entradaUsuario( Script script, ENTRADA entrada, String varname, String msj, String[] opciones ); /** * Lista de pulsaciones de teclas a consumir * @return lista de pulsaciones */ List getStrokeList(); /** * Lista de toques en pantalla o clicks de ratón a consumir * @return lista de eventos de ratón/toque */ List getRatonEventList(); /** * Permite seleccionar uno o varios archivos del sistema * @param path ruta actual (ruta vacía mantiene el directorio actual, * ruta errónea establece la ruta por defecto) * @param titulo título de un posible diálogo de selección o null para valor por defecto * @return array de archivos seleccionados con la ruta completa. * Si no se selecciona ninguno devuelve array vacío y null si hay un error */ File[] seleccionarArchivos( String path, String titulo ); /////////////////////////////////// // PRIMITIVAS GRÁFICAS /////////////////////////////////// // COLOR /** * Obtiene una factoría (definida a partir de un AbstractColor) para obtener nuevos colores * @return factoría de color */ AbstractColor getColorFactory(); /** * Establecer color de línea del lienzo * @param color color de línea */ void setColor( AbstractColor color ); /** * Establecer color de relleno del lienzo * @param color color de relleno */ void setRelleno( AbstractColor color ); /** * Establecer color de fondo del lienzo * @param color color de fondo */ void setFondo( AbstractColor color ); /** * Color de línea del lienzo * @return color de línea */ AbstractColor getColor(); /** * Color de relleno del lienzo * @return color de relleno */ AbstractColor getRelleno(); /** * Color de fondo del lienzo * @return color de fondo */ AbstractColor getFondo(); // TRAZO void setTrazo( float grosor ); void setTrazo( float grosor, CAP extremo, JOIN union, Float inglete, float[] raya, Float faseRaya ); // GRADIENTE /** * Gradiente aplicado a relleno y línea * @param x1 inicio x del primer color o centro x * @param y1 inicio y del primer color o centro y * @param x2 final x del último color o foco X * @param y2 final y del último color o foco y * @param radio radio del gradiente radial * @param fracciones división ponderada del patrón de color * @param colores color asociado a cada fracción * @param ciclo {@link CICLO#CICLO}, {@link CICLO#REFLEJO}, {@link CICLO#NO_CICLO} * @param transform matriz de transformación (puede ser AffineTransform en escritorio o Matrix * en Android) */ void setGradiente( double x1, double y1, double x2, double y2, Double radio, float[] fracciones, AbstractColor[] colores, CICLO ciclo, Object transform ); // TRANSFORMACIONES /** * Matriz de transformación del lienzo {@code [[sx,shx,tx],[shy,sy,ty],[0,0,1]]} * @param storeMatrizTransformacion matriz 3x3 inicializada donde se almacenará la matriz * @return la propia matriz {@code storeMatrizTransformacion} actualizada */ double[] getMatriz( double[] storeMatrizTransformacion ); /** * Establece la matriz del lienzo {@code [[sx,shx,tx],[shy,sy,ty],[0,0,1]]} * @param matrizTransformacion matriz 3x3 */ void setMatriz( double[] matrizTransformacion ); // LIMPIAR ZONA /** * Borra una parte del lienzo, con el color de fondo especificado * @param x extremo superior izquierda x * @param y extremo superior izquierda y * @param ancho ancho del rectángulo de borrado * @param alto alto del rectángulo de borrado * @param cfondo color de fondo */ void limpiar( int x, int y, int ancho, int alto, AbstractColor cfondo ); // FIGURAS Y TEXTO /** * Dibuja un punto del tipo especificado, con el grosor establecido con 'trazo' y color * de línea establecido * @param x coordenada X del punto * @param y coordenada Y del punto * @param tipo {@link PUNTO} */ void punto( double x, double y, PUNTO tipo ); /** * Traza segmento entre dos puntos con el color de línea establecido * @param x1 coordenada X del extremo inicial * @param y1 coordenada Y del extremo inicial * @param x2 coordenada X del extremo final * @param y2 coordenada Y del extremo final */ void segmento( double x1, double y1, double x2, double y2 ); /** * Dibuja circunferencias, círculos y arcos * @param cx coordenada X del centro * @param cy coordenada Y del centro * @param radio radio * @param ang ángulo inicial (grados) * @param arco longitud del arco (grados) * @param tipo {@link CIERRE#ABIERTO}, {@link CIERRE#CUERDA}, {@link CIERRE#TARTA} * @param relleno {@code verdadero} para dibujar el interior (círculo) con color de relleno * @param borde {@code verdadero} para dibujar la circunferencia o arco. Si {@code relleno} * es {@code falso}, debe dibujarse aunque {@code borde} sea falso */ void circunferencia( double cx, double cy, double radio, double ang, double arco, CIERRE tipo, boolean relleno, boolean borde ); /** * Dibuja elipses * @param x1 extremo superior izquierda x * @param y1 extremo superior izquierda y * @param x2 extremo inferior derecha x * @param y2 extremo inferior derecha y * @param relleno true para dibujar el interior con color de relleno * @param borde {@code verdadero} para dibujar el borde. Si {@code relleno} * es {@code falso}, debe dibujarse aunque {@code borde} sea falso */ void elipse( double x1, double y1, double x2, double y2, boolean relleno, boolean borde ); /** * Dibuja un rectángulo recto, redondeado o 3D. Con relleno y/o borde. * @param x1 x extremo superior izquierda * @param y1 y extremo superior izquierda * @param x2 x extremo inferior derecha * @param y2 y extremo inferior derecha * @param anchoArco ancho esquinas redondeadas * @param altoArco alto esquinas redondeadas * @param relleno true para dibujar el interior con color de relleno * @param borde {@code verdadero} para dibujar el borde. Si {@code relleno} * es {@code falso}, debe dibujarse aunque {@code borde} sea falso * @param rect3d {@link RECT3D#GRABADO}, {@link RECT3D#RESALTADO}, {@link RECT3D#NINGUNO} */ void rectangulo( double x1, double y1, double x2, double y2, double anchoArco, double altoArco, boolean relleno, boolean borde, RECT3D rect3d ); /** * Dibuja un polígono o polilínea * @param x array de coordenadas x * @param y array de coordenadas y (mismo tamaño y punto correspondiente) * @param cerrar {@code verdadero} para cerrar primer y último punto * @param relleno true para dibujar el interior con color de relleno * @param borde {@code verdadero} para dibujar el borde. Si {@code relleno} * es {@code falso}, debe dibujarse aunque {@code borde} sea falso */ void poligono( double[] x, double[] y, boolean cerrar, boolean relleno, boolean borde ); /** * Dibuja un texto. *
Puede incluir sombreado si {@code sombraX} o {@code sombraY} no son * iguales a 0. El color del sombreado será el color de relleno * @param texto texto a graficar * @param posX coordenada X del texto * @param posY coordenada Y del texto * @param tama tamaño del texto * @param sombraX desplazamiento de la sombra en X * @param sombraY desplazamiento de la sombra en Y * @param familia familia del texto * @param estilo estilo del texto; {@link ESTILO} */ void texto( String texto, int posX, int posY, int tama, int sombraX, int sombraY, String familia, ESTILO estilo ); /** * Devuelve la dimensión del texto a graficar * @param texto texto a graficar * @param tama tamaño del texto * @param sombraX desplazamiento de la sombra en X * @param sombraY desplazamiento de la sombra en Y * @param familia familia del texto * @param estilo estilo del texto; {@link ESTILO} * @return array {@code [ancho,alto]} */ int[] medirTexto( String texto, int tama, int sombraX, int sombraY, String familia, ESTILO estilo ); /** * Dibuja una figura en base a una ruta especificada por diferentes acciones * @param accionPuntos * @param regla regla de relleno del interior: {@link RUTA#PAR_IMPAR}, {@link RUTA#NO_CERO} * @param relleno true para dibujar el interior con color de relleno * @param borde {@code verdadero} para dibujar el borde. Si {@code relleno} * es {@code falso}, debe dibujarse aunque {@code borde} sea falso */ void ruta( AccionPuntos[] accionPuntos, RUTA regla, boolean relleno, boolean borde ); //////////////////////////////////// // INTERFACES Y CLASES ABSTRACTAS //////////////////////////////////// /** * Holder para la primitiva * {@link AbstractPrimitivas2D#ruta(AccionPuntos[], RUTA, boolean, boolean)} */ static class AccionPuntos { public RUTA accion; public double[][] puntos; } /** * Implementa de forma abstracta un color ARGB, dado por sus canales */ static abstract class AbstractColor implements Serializable { private static final long serialVersionUID = 1L; // MÉTODOS FACTORÍA /** * Obtiene un color a partir de los canales RGB * @param r red * @param g green * @param b blue * @return nuevo color */ public abstract AbstractColor nuevaInstancia( int r, int g, int b ); /** * Obtiene un color a partir de los canales ARGB * @param r reg * @param g green * @param b blue * @param a alpha * @return nuevo color */ public abstract AbstractColor nuevaInstancia( int r, int g, int b, int a ); /** * Obtiene un color a partir de una cadena hexadecimal (ej.: #AABBCC) * @param stringcolor cadena hexadecimal * @return nuevo color */ public AbstractColor nuevaInstancia( String stringcolor ) { throw new UnsupportedOperationException(); } /** * Obtiene un color a partir de un entero AARRGGBB desde el bit 0 a 31 * @param intcolor entero de 32 bits * @return nuevo color */ public AbstractColor nuevaInstancia( int intcolor ) { throw new UnsupportedOperationException(); } /** * Convierte un terminal a un formato de color aceptado * @param color color como terminal compatible * @param factory factoría de color * @return nuevo color */ public static AbstractColor colorDesdeTerminal( Terminal color, AbstractColor factory ) { // RGB & RGBA if ( color.esVector() ) { final VectorEvaluado vcolor = (VectorEvaluado) color; try { int r = ((Numero) vcolor.getComponente(0)).ent(), g = ((Numero) vcolor.getComponente(1)).ent(), b = ((Numero) vcolor.getComponente(2)).ent(); if ( vcolor.dimension() == 4 ) // RGBA return factory.nuevaInstancia( r, g, b, ((Numero) vcolor.getComponente(3)).ent() ); else // RGB return factory.nuevaInstancia( r, g, b ); } catch ( Exception e ) { throw new IllegalArgumentException( color + " no es un formato de color aceptado", e ); } } // color mediante entero else if ( color.esEntero() ) return factory.nuevaInstancia( ((Numero) (color)).ent() ); // color por formato de texto else if ( color.esTexto() ) return factory.nuevaInstancia( ((Texto) color).textoPlano() ); else throw new IllegalArgumentException( color + " no es un formato de color aceptado" ); } /** * Devuelve el canal rojo del color * @return red */ public abstract int red(); /** * Devuelve el canal green del color * @return green */ public abstract int green(); /** * Devuelve el canal azul del color * @return blue */ public abstract int blue(); /** * Devuelve el canal alpha del color * @return alpha */ public abstract int alpha(); /** * Devuelve el color como entero * @return AARRGGBB (0-31 bits) */ public int intcolor() { throw new UnsupportedOperationException(); } /** * Devuelve el color como cadena * @return cadena hexadecimal */ public int stringcolor() { throw new UnsupportedOperationException(); } /** * Devuelve el color como vector JME * @return vector [R,G,B,A] */ public VectorEvaluado vectorColor() { return new VectorEvaluado( new RealDoble( red() ), new RealDoble( green() ), new RealDoble( blue() ), new RealDoble( alpha() ) ); } } // class AbstractColor /** * Intefaz para la representación de pulsaciones de teclas */ static interface AbstractKeyStroke extends Serializable { /** Zona del teclado */ static enum LOCALIZACION { ESTANDAR, IZQUIERDA, DERECHA, NUMPAD, DESCONOCIDA } /** Valores para los modificadores (máscaras) del evento, idénticos a los especificados para * {@link java.awt.event.InputEvent}*/ static int SHIFT = 64, CONTROL = 128, META = 256, ALT = 512, BUTTON1 = 1024, BUTTON2 = 2048, BUTTON3 = 4096, ALT_GRAPH = 8192; /** key code de la pulsación */ int getKeycode(); /** marca de tiempo del evento (ms) */ long getTimeStamp(); /** modificadores de la pulsación * @return {@link #SHIFT}, {@link #CONTROL}, {@link #META}, {@link #ALT}, {@link #BUTTON1}, * {@link #BUTTON2}, {@link #BUTTON3}, {@link #ALT_GRAPH} * */ int getModifiers(); /** modificadores como texto (dependiente de la plataforma) */ String getModifiersText(); /** carácter pulsado si corresponde a un simbolo no de control */ char getKeychar(); /** verdadero si la tecla es soltada, falso para presionada */ boolean isKeyReleased(); /** Localización en que ocurre el evento (dos SHIFTS, dos CONTROL, teclado numérico,...)*/ LOCALIZACION getLocalizacion(); /** verdadero si es una tecla de acción (teclas de dirección, CAPS, PAGE DOWN,...)*/ boolean isAccion(); } // interface AbstractKeyStroke /** * Interfaz para la representación de eventos de ratón o toques táctiles */ static interface AbstractRatonEvent extends Serializable { /** botón pulsado (B2 central) */ static enum BOTON { BOTON1, BOTON2, BOTON3, NO_BOTON, OTRO1, OTRO2, OTRO3 }; /** tipo de evento de ratón o toque */ static enum RATON_ID { PRESSED, RELEASED, CLICKED, MOVED, DRAGGED, ENTERED, EXITED, WHEEL, OTRO1, OTRO2, OTRO3 } /** id de evento */ RATON_ID getId(); /** marca de tiempo del evento (ms) */ long getTimeStamp(); /** array [x,y] respecto a componente padre */ int[] getPosicion(); /** array [x,y] respecto a pantalla */ int[] getPosicionEnPantalla(); /** id de botón */ BOTON getButton(); /** nº de clicks */ int getClicks(); /** rotación de rueda */ double getRotacion(); } // interface AbstractRatonEvent } // interface AbstractPrimitivas2D

El siguiente es un ejemplo de implementación incompleta del adaptador para Swing. JMEscriptGUI completa esta implementación.

package swing_primitivas;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.MultipleGradientPaint.ColorSpaceType;
import java.awt.MultipleGradientPaint.CycleMethod;
import java.awt.RadialGradientPaint;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;

import jme.Expresion;
import jme.ExpresionThread;
import jme.abstractas.Token;
import jme.script.Script;
import jme.script.ScriptException;
import jme.script.ctx2d.AbstractPrimitivas2D;
import jme.script.ctx2d.Primitivas2DAdapter;
import jme.terminales.Booleano;
import jme.terminales.Texto;

/**
 * Implementación básica de {@link AbstractPrimitivas2D} para Swing. 
 * @author Miguel Alejandro Moreno Barrientos, (C)2021
 * @since 0.1.0, JME v0.6.2.0, JMEScript 0.1.3.0
 */
public class JMEPrimitivas2DParaSwing extends Primitivas2DAdapter 
{
	private Graphics2D g2;
	
	/** máximo tiempo de ejecución de las expresiones en la entrada (ms) */
	public long maxTiempoDeEvaluacion = 4000L;
	
	public static JFileChooser fileChooser;
	
	private List strokeList = 
							Collections.synchronizedList( new LinkedList() );
	private List ratonEventList = 
							Collections.synchronizedList( new LinkedList() );
	
	protected Color colorRelleno = Color.BLACK;  // color de relleno en figuras
	
	public JMEPrimitivas2DParaSwing() {}
	
	public JMEPrimitivas2DParaSwing( Graphics2D g2 ) 
	{
		this();
		this.g2 = g2;
	}
	
	public Graphics2D getGraphics() { return g2; }	
	public void setGraphics( Graphics2D g2 ) { this.g2 = g2; }
	
	@Override
	public String getUIName() { return "Swing"; }
	
	@Override
	public String getUIVersion() 
	{ 
		try
		{
			return System.getProperty("java.version");
		}
		catch ( SecurityException e )
		{
			return "?";
		}
	}
	
	@Override
	public String getPrim2DName() { return "Implementacion Basica de Primitivas2D para SWING"; }
	
	@Override
	public String getPrim2DVersionName() { return "Cuarentena"; }
	
	@Override
	public String getPrim2DVersion() { return "0.1.0"; }
	
	@Override
	public int getAnchoPantalla() { return Toolkit.getDefaultToolkit().getScreenSize().width; }
	
	@Override
	public int getAltoPantalla() { return Toolkit.getDefaultToolkit().getScreenSize().height; }

	@Override
	public void mostrarMensaje( String msj, MSJ tipo, Integer posX, Integer posY, Integer tiempo ) 
	{
		int msjTipo;
		switch( tipo )
		{
			case WARNING: msjTipo = JOptionPane.WARNING_MESSAGE; break;
			case ERROR: msjTipo = JOptionPane.ERROR_MESSAGE; break;
			case QUESTION: msjTipo = JOptionPane.QUESTION_MESSAGE; break;
			case PLAIN: msjTipo = JOptionPane.PLAIN_MESSAGE; break;
			default: msjTipo = JOptionPane.INFORMATION_MESSAGE;
		}
		JOptionPane.showConfirmDialog( getPadre() instanceof Component 
									   ? (Component) getPadre() 
									   : null, 
									   msj, 
									   "Script mensaje:", 
									   JOptionPane.OK_CANCEL_OPTION, 
									   msjTipo ); 
	}
	
	@Override
	public void entradaUsuario( Script script, ENTRADA entrada, String varname, String msj, 
								String[] opciones ) 
	{
		final Map scriptVars = script.getVarMap();
		final Component padre = getPadre() instanceof Component ? (Component) getPadre() : null;
		
		switch ( entrada )
		{
			default:
			// solicitud de texto
			case TEXTO:
			{
				String txt = JOptionPane.showInputDialog( padre, msj, 
												String.format( "Solicitud de <%s> JME para '%s'", 
														Texto.class.getSimpleName(), varname ),
												JOptionPane.QUESTION_MESSAGE );
				if ( txt == null )  // entrada cancelada
					txt = Expresion.ERROR.__error1__.name();
				// establecer variable en script
				scriptVars.put( varname, new Texto( txt ) ); 
				break;
			}
			// solicitud de expresión
			case EXPRESION:    
			{
				final String exptxt = JOptionPane.showInputDialog( padre, msj, 
												String.format( "Solicitud de <%s> JME para '%s'", 
														Expresion.class.getSimpleName(), varname ),
												JOptionPane.QUESTION_MESSAGE ); 
				if ( exptxt == null )  // entrada cancelada
				{
					scriptVars.put( varname, new Texto( Expresion.ERROR.__error1__.name() ) );
					break;
				}

				final ExpresionThread expThread;
				try
				{
					// crear expresión
					final Expresion exp = new Expresion( exptxt )
									.setVariables( new HashMap( script.getVarMap() ) );
					// crear hilo y ejecutar
					expThread = new ExpresionThread( exp, maxTiempoDeEvaluacion );
					expThread.startAndJoin();
				}
				catch ( Throwable e )
				{
					scriptVars.put( varname, new Texto( Expresion.ERROR.__error2__.name() ) );
					break;
				}
				// establecer variable en script
				scriptVars.put( varname, expThread.getResultado() ); 
				break;
			}
			// solicitud de confirmación (booleano)
			case BOOLEANO:
			{
				// seleccionar SI/NO
				final int opcion = JOptionPane.showConfirmDialog( padre, msj, 
										String.format( "Solicitud de <%s> para '%s'", 
													   Booleano.class.getSimpleName(), varname ),
										JOptionPane.YES_NO_OPTION ); 
				if ( opcion == JOptionPane.CLOSED_OPTION )  // entrada cancelada
					scriptVars.put( varname, new Texto( Expresion.ERROR.__error1__.name() ) );
				else  // establecer variable en script
					scriptVars.put( varname, Booleano.booleano( opcion == JOptionPane.YES_OPTION ) ); 
				break;
			}
			// selección de opciones
			case OPCIONES:
			{
				// obtener índice de la opción
				final int opcion = JOptionPane.showOptionDialog( padre, msj, 
						"Opciones para '" + varname + "'", 
						JOptionPane.NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, opciones, null );
				if ( opcion == JOptionPane.CLOSED_OPTION )  // entrada cancelada
					scriptVars.put( varname, new Texto( Expresion.ERROR.__error1__.name() ) );
				else  // establecer variable en script
					scriptVars.put( varname, new Texto( opciones[opcion] ) ); 
				break;
			}
		}		
	}
	
	/**
	 * {@inheritDoc}
	 * 

* Nota: aunque sea sincronizada, es necesario acceder en bloque, o se obtendrá en * ocasiones un error de concurrencia */ @Override public List getStrokeList() { return strokeList; } /** * {@inheritDoc} *

* Nota: aunque sea sincronizada, es necesario acceder en bloque, o se obtendrá en * ocasiones un error de concurrencia */ @Override public List getRatonEventList() { return ratonEventList; } @Override public File[] seleccionarArchivos( String path, String titulo ) { fileChooser = fileChooser != null ? fileChooser : new JFileChooser(); fileChooser.setMultiSelectionEnabled(true); fileChooser.setFileSelectionMode( JFileChooser.FILES_AND_DIRECTORIES ); fileChooser.setCurrentDirectory( path != null ? new File(path) : null ); fileChooser.setDialogTitle( titulo ); fileChooser.updateUI(); switch( fileChooser.showOpenDialog( getPadre() instanceof Component ? (Component) getPadre() : null ) ) { case JFileChooser.APPROVE_OPTION: return fileChooser.getSelectedFiles(); case JFileChooser.CANCEL_OPTION: return new File[0]; case JFileChooser.ERROR_OPTION: default: return null; } } @Override public void limpiar( int x, int y, int ancho, int alto, AbstractColor cfondo ) { // guardar color y matriz originales final AffineTransform oriMatriz = g2.getTransform(); final Color oriColor = g2.getColor(); // establecer matriz identidad g2.setTransform( new AffineTransform() ); // establecer color de fondo if ( cfondo != null ) g2.setColor( new Color( cfondo.red(), cfondo.green(), cfondo.blue(), cfondo.alpha() ) ); else if ( g2.getPaint() instanceof Color ) g2.setColor( g2.getBackground() ); // dibujar rectángulo de fondo g2.fill( new Rectangle2D.Double( x, y, ancho, alto ) ); // recuperar color y matriz originales g2.setColor( oriColor ); g2.setTransform( oriMatriz ); } @Override public AbstractColor getColorFactory() { return new JSColor(); } public static class JSColor extends AbstractColor { private static final long serialVersionUID = 1L; protected Color color = Color.BLACK; public JSColor() {} public JSColor( Color color ) { this.color = color; } public JSColor( int r, int g, int b, int a ) { color = new Color( r, g, b, a ); } public JSColor( int r, int g, int b ) { this( r, g, b, 255 ); } public JSColor( int intcolor ) { color = new Color( intcolor, true ); } public JSColor( String stringcolor ) { color = Color.decode( stringcolor ); } @Override public JSColor nuevaInstancia( int r, int g, int b, int a ) { return new JSColor( r, g, b, a ); } @Override public JSColor nuevaInstancia( int r, int g, int b ) { return new JSColor( r, g, b ); } @Override public JSColor nuevaInstancia( String stringcolor ) { return new JSColor( stringcolor ); } @Override public JSColor nuevaInstancia( int intcolor ) { return new JSColor( intcolor ); } @Override public int red() { return color.getRed(); } @Override public int green() { return color.getGreen(); } @Override public int blue() { return color.getBlue(); } @Override public int alpha() { return color.getAlpha(); } } // class JSColor ///////////////////////// // PRIMITIVAS GRÁFICAS ///////////////////////// @Override public void setColor( AbstractColor color ) { g2.setColor( new Color( color.red(), color.green(), color.blue(), color.alpha() ) ); } @Override public AbstractColor getColor() { return esContextoIniciado() ? new JSColor( g2.getColor() ) : new JSColor(); } @Override public void setRelleno( AbstractColor color ) { // eliminar gradiente si se modifica el relleno if ( !(g2.getPaint() instanceof Color) ) g2.setPaint( colorRelleno ); colorRelleno = new Color( color.red(), color.green(), color.blue(), color.alpha() ); } @Override public AbstractColor getRelleno() { return esContextoIniciado() ? new JSColor( colorRelleno ) : new JSColor(); } @Override public void setFondo( AbstractColor color ) { final Color c = new Color( color.red(), color.green(), color.blue(), color.alpha() ); // eliminar gradiente si se modifica el fondo if ( !(g2.getPaint() instanceof Color) ) g2.setPaint( c ); g2.setBackground( c ); } @Override public AbstractColor getFondo() { return esContextoIniciado() ? new JSColor( g2.getBackground() ) : new JSColor(); } @Override public double[] getMatriz( double[] storeMatrizTransformacion ) { if ( !esContextoIniciado() ) return null; getGraphics().getTransform().getMatrix(storeMatrizTransformacion); return storeMatrizTransformacion; } @Override public void setMatriz( double[] matrizTransformacion ) { getGraphics().setTransform( new AffineTransform( matrizTransformacion ) ); } @Override public void setTrazo( float grosor ) { getGraphics().setStroke( new BasicStroke( grosor, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND ) ); } @Override public void setTrazo( float grosor, CAP extremo, JOIN union, Float inglete, float[] raya, Float faseRaya ) { final BasicStroke oldStroke = g2.getStroke() instanceof BasicStroke ? (BasicStroke) g2.getStroke() : new BasicStroke(); int awtExtremo = ((BasicStroke) g2.getStroke()).getEndCap(); if ( extremo != null ) { switch ( extremo ) { case REDONDO: awtExtremo = BasicStroke.CAP_ROUND; break; case TOPE: awtExtremo = BasicStroke.CAP_BUTT; break; case CUADRADO: awtExtremo = BasicStroke.CAP_SQUARE; break; default: } } int awtUnion = ((BasicStroke) g2.getStroke()).getLineJoin(); if ( union != null ) { switch ( union ) { case BISEL: awtUnion = BasicStroke.JOIN_BEVEL; break; case INGLETE: awtUnion = BasicStroke.JOIN_MITER; break; case REDONDO: awtUnion = BasicStroke.JOIN_ROUND; break; default: } } g2.setStroke( new BasicStroke( grosor, awtExtremo, awtUnion, inglete != null ? inglete : oldStroke.getMiterLimit(), raya != null ? (raya.length == 1 && raya[0] == 0f ? null : raya) : oldStroke.getDashArray(), faseRaya != null ? faseRaya : oldStroke.getDashPhase() ) ); } @Override public void setGradiente( double x1, double y1, double x2, double y2, Double radio, float[] fracciones, AbstractColor[] colores, CICLO ciclo, Object transform ) { // convertir ciclo desde especificación a AWT final CycleMethod metCiclo; switch ( ciclo ) { default: case CICLO: metCiclo = CycleMethod.REPEAT; break; case REFLEJO: metCiclo = CycleMethod.REFLECT; break; case NO_CICLO: metCiclo = CycleMethod.NO_CYCLE; break; } // convertir colores final Color[] awtColores = new Color[colores.length]; for ( int i = 0; i < colores.length; i++ ) awtColores[i] = new Color( colores[i].red(), colores[i].green(), colores[i].blue(), colores[i].alpha() ); // gradiente lineal if ( radio == null ) { final LinearGradientPaint lgrad = new LinearGradientPaint( new Point2D.Double( x1, y1 ), new Point2D.Double( x2, y2 ), fracciones, awtColores, metCiclo, ColorSpaceType.LINEAR_RGB, transform == null ? new AffineTransform() : (AffineTransform) transform ); g2.setPaint( lgrad ); } // gradiente radial else { final RadialGradientPaint rgrad = new RadialGradientPaint( new Point2D.Double( x1, y1 ), radio.floatValue(), new Point2D.Double( x2, y2 ), fracciones, awtColores, metCiclo, ColorSpaceType.LINEAR_RGB, transform == null ? new AffineTransform() : (AffineTransform) transform ); g2.setPaint( rgrad ); } } @Override public void punto( double x, double y, PUNTO tipo ) { switch ( tipo ) { default: case PIXEL: g2.draw( new Line2D.Double( x, y, x, y ) ); break; case ROMBO: { final float grosor = ((BasicStroke) g2.getStroke()).getLineWidth(); final double[] valoresX = { x, x + grosor, x, x - grosor }, valoresY = { y + grosor, y, y - grosor, y }; final Path2D poly = new Path2D.Double(); poly.moveTo( valoresX[0], valoresY[0] ); for( int i = 1; i < valoresX.length; i++ ) poly.lineTo(valoresX[i], valoresY[i]); poly.closePath(); g2.fill( poly ); break; } case CRUZ: { final float grosor = ((BasicStroke) g2.getStroke()).getLineWidth(); g2.draw( new Line2D.Double( x - grosor, y, x + grosor, y ) ); g2.draw( new Line2D.Double( x, y - grosor, x, y + grosor ) ); break; } case MIRA: { final float grosor = ((BasicStroke) g2.getStroke()).getLineWidth(), grosor2 = grosor*3/2f; g2.draw( new Line2D.Double( x, y + grosor2, x, y + grosor*2 ) ); g2.draw( new Line2D.Double( x, y - grosor2, x, y - grosor*2 ) ); g2.draw( new Line2D.Double( x + grosor2, y, x + grosor*2, y ) ); g2.draw( new Line2D.Double( x - grosor2, y, x - grosor*2, y ) ); break; } } } @Override public void segmento( double x1, double y1, double x2, double y2 ) { g2.draw( new Line2D.Double( x1, y1, x2, y2 ) ); } /** * {@inheritDoc}

* Nota: los rectángulos 3D no aceptan valores flotantes, por lo que serán redondeados * a enteros. Las coordenadas normalizadas a rangos pequeños como [0,1] no funcionarán * correctamente.
* Nota2: No dibuja rectángulos redondeados 3D. */ @Override public void rectangulo( double x1, double y1, double x2, double y2, double anchoArco, double altoArco, boolean relleno, boolean borde, RECT3D rect3d ) { final double ancho = x2 - x1, alto = y2 - y1; final Shape rect; // rectángulo recto o 3D if ( anchoArco <= 0 && altoArco <= 0 ) rect = rect3d == RECT3D.NINGUNO ? new Rectangle2D.Double( x1, y1, ancho, alto ) : null; // rectángulo redondeado else rect = new RoundRectangle2D.Double( x1, y1, ancho, alto, anchoArco, altoArco ); // relleno if ( relleno ) { // color if ( g2.getPaint() instanceof Color || rect == null ) { final Color oriColor = g2.getColor(); g2.setColor( colorRelleno ); if ( rect != null ) g2.fill( rect ); else g2.fill3DRect( (int) Math.round(x1), (int) Math.round(y1), (int) Math.round(ancho), (int) Math.round(alto), rect3d == RECT3D.RESALTADO ); g2.setColor( oriColor ); } // gradiente else g2.fill( rect ); } // borde if ( /*!relleno || */borde ) { if ( rect != null ) g2.draw( rect ); else g2.draw3DRect( (int) Math.round(x1), (int) Math.round(y1), (int) Math.round(ancho), (int) Math.round(alto), rect3d == RECT3D.RESALTADO ); } } @Override public void circunferencia( double cx, double cy, double radio, double ang, double arco, CIERRE tipo, boolean relleno, boolean borde ) { final Shape circ; // circunferencia if ( arco >= 360. ) circ = new Ellipse2D.Double( cx-radio, cy-radio, 2*radio, 2*radio ); // arco else { final int cierre; switch ( tipo ) { default: case ABIERTO: cierre = Arc2D.OPEN; break; case TARTA: cierre = Arc2D.PIE; break; case CUERDA: cierre = Arc2D.CHORD; } circ = new Arc2D.Double( cx-radio, cy-radio, 2*radio, 2*radio, ang, arco, cierre ); } drawOrFill( circ, relleno, borde ); } @Override public void elipse( double x1, double y1, double x2, double y2, boolean relleno, boolean borde ) { drawOrFill( new Ellipse2D.Double( x1, y1, x2 - x1, y2 - y1 ), relleno, borde ); } @Override public void poligono( double[] x, double[] y, boolean cerrar, boolean relleno, boolean borde ) { if ( x.length != y.length ) throw new ScriptException( "Las coordenadas del poligono/polilinea son de distinto tamano" ); final Path2D poly = new Path2D.Double(); poly.moveTo( x[0], y[0] ); for( int i = 1; i < x.length; i++ ) poly.lineTo( x[i], y[i] ); if ( cerrar ) poly.closePath(); drawOrFill( poly, relleno, borde ); } @Override public void ruta( AccionPuntos[] accionPuntos, RUTA regla, boolean relleno, boolean borde ) { final Path2D.Double path = new Path2D.Double( regla == RUTA.NO_CERO ? Path2D.WIND_NON_ZERO : Path2D.WIND_EVEN_ODD ); for ( AccionPuntos ap : accionPuntos ) { switch ( ap.accion ) { case MOVER_A: path.moveTo( ap.puntos[0][0], ap.puntos[0][1] ); break; case LINEA_A: path.lineTo( ap.puntos[0][0], ap.puntos[0][1] ); break; case CURVA_A: path.curveTo( ap.puntos[0][0], ap.puntos[0][1], ap.puntos[1][0], ap.puntos[1][1], ap.puntos[2][0], ap.puntos[2][1] ); break; case CUAD_A: path.quadTo( ap.puntos[0][0], ap.puntos[0][1], ap.puntos[1][0], ap.puntos[1][1] ); break; case CERRAR: path.closePath(); break; default: throw new IllegalArgumentException( ap.accion.toString() ); } } drawOrFill( path, relleno, borde ); } private void drawOrFill( Shape figura, boolean relleno, boolean borde ) { if ( relleno ) { // color if ( g2.getPaint() instanceof Color ) { final Color oriColor = g2.getColor(); g2.setColor( colorRelleno ); g2.fill( figura ); g2.setColor( oriColor ); } // gradiente else g2.fill( figura ); } if ( /*!relleno ||*/ borde ) g2.draw( figura ); } @Override public void texto( String texto, int posX, int posY, int tama, int sombraX, int sombraY, String familia, ESTILO estilo ) { final int awtEstilo; switch (estilo) { case PLANO: awtEstilo = Font.PLAIN; break; case NEGRITA: awtEstilo = Font.BOLD; break; case CURSIVA: awtEstilo = Font.ITALIC; break; case NEG_CUR: awtEstilo = Font.ITALIC|Font.BOLD; break; default: awtEstilo = Font.PLAIN; } final Font oriFont = g2.getFont(), nuevaFont = new Font( familia, awtEstilo, tama ); g2.setFont( nuevaFont ); final FontMetrics fm = g2.getFontMetrics( nuevaFont ); if ( sombraX != 0 || sombraY != 0 ) { final Color oriColor = g2.getColor(); g2.setColor( colorRelleno ); g2.drawString( texto, posX + sombraX, posY + sombraY + fm.getHeight() - fm.getDescent() /*- fm.getAscent()*/ ); g2.setColor( oriColor ); } g2.drawString( texto, posX, posY + fm.getHeight() - fm.getDescent() /*- fm.getAscent()*/ ); g2.setFont( oriFont ); } @Override public int[] medirTexto( String texto, int tama, int sombraX, int sombraY, String familia, ESTILO estilo ) { final int awtEstilo; switch (estilo) { case PLANO: awtEstilo = Font.PLAIN; break; case NEGRITA: awtEstilo = Font.BOLD; break; case CURSIVA: awtEstilo = Font.ITALIC; break; case NEG_CUR: awtEstilo = Font.ITALIC|Font.BOLD; break; default: awtEstilo = Font.PLAIN; } final FontMetrics fm = g2.getFontMetrics( new Font( familia, awtEstilo, tama ) ); return new int[] { fm.stringWidth( texto ) + sombraX, fm.getHeight() + sombraY }; } public static class KeyStrokeEvent implements AbstractKeyStroke { private static final long serialVersionUID = 1L; final protected KeyEvent kEvent; public KeyStrokeEvent( KeyEvent kEvent ) { this.kEvent = kEvent; } @Override public boolean isKeyReleased() { return kEvent.getID() == KeyEvent.KEY_RELEASED; } @Override public int getModifiers() { return kEvent.getModifiersEx(); } @Override public String getModifiersText() { return KeyEvent.getModifiersExText( kEvent.getModifiersEx() ); } @Override public int getKeycode() { return kEvent.getKeyCode(); } @Override public char getKeychar() { return kEvent.getKeyChar(); } @Override public long getTimeStamp() { return kEvent.getWhen(); } @Override public boolean isAccion() { return kEvent.isActionKey(); } @Override public LOCALIZACION getLocalizacion() { switch ( kEvent.getKeyLocation() ) { case KeyEvent.KEY_LOCATION_STANDARD: return LOCALIZACION.ESTANDAR; case KeyEvent.KEY_LOCATION_LEFT: return LOCALIZACION.IZQUIERDA; case KeyEvent.KEY_LOCATION_RIGHT: return LOCALIZACION.DERECHA; case KeyEvent.KEY_LOCATION_NUMPAD: return LOCALIZACION.NUMPAD; case KeyEvent.KEY_LOCATION_UNKNOWN: return LOCALIZACION.DESCONOCIDA; default: return null; } } } /** * Implementación de evento de click/toque para AWT/Swing */ public static class RatonEvent implements AbstractRatonEvent { private static final long serialVersionUID = 1L; final protected MouseEvent mEvent; public RatonEvent( MouseEvent mEvent ) { this.mEvent = mEvent; } public MouseEvent getMouseEvent() { return mEvent; } @Override public RATON_ID getId() { switch ( mEvent.getID() ) { case MouseEvent.MOUSE_PRESSED: return RATON_ID.PRESSED; case MouseEvent.MOUSE_RELEASED: return RATON_ID.RELEASED; case MouseEvent.MOUSE_CLICKED: return RATON_ID.CLICKED; case MouseEvent.MOUSE_MOVED: return RATON_ID.MOVED; case MouseEvent.MOUSE_DRAGGED: return RATON_ID.DRAGGED; case MouseEvent.MOUSE_ENTERED: return RATON_ID.ENTERED; case MouseEvent.MOUSE_EXITED: return RATON_ID.EXITED; case MouseEvent.MOUSE_WHEEL: return RATON_ID.WHEEL; default: return null; } } @Override public BOTON getButton() { switch( mEvent.getButton() ) { case MouseEvent.BUTTON1: return BOTON.BOTON1; case MouseEvent.BUTTON2: return BOTON.BOTON2; case MouseEvent.BUTTON3: return BOTON.BOTON3; case MouseEvent.NOBUTTON: default: return BOTON.NO_BOTON; } } @Override public int getClicks() { return mEvent.getClickCount(); } @Override public int[] getPosicion() { return new int[] { mEvent.getX(), mEvent.getY() }; } @Override public int[] getPosicionEnPantalla() { return new int[] { mEvent.getXOnScreen(), mEvent.getYOnScreen() }; } @Override public long getTimeStamp() { return mEvent.getWhen(); } @Override public double getRotacion() { return mEvent instanceof MouseWheelEvent ? ((MouseWheelEvent) mEvent).getPreciseWheelRotation() : 0.; } } // class RatonEvent } // class JMEPrimitivas2DParaSwing

Implementación de JMEPrimitivas2DParaSwing en JMEScriptGUI:

package jmescriptgui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.filechooser.FileNameExtensionFilter;

import jme.script.ctx2d.AbstractPrimitivas2D;
import swing_primitivas.JMEPrimitivas2DParaSwing;

/**
 * Implementación de las primitivas de interfaz gráfico de JMEScript para JMEScriptGUI 
 * @author Miguel Alejandro Moreno Barrientos
 * @since 0.1.2, JME 0.6.2.0, JMEScript 0.2.0
 */
public class JmeScriptGuiPrimitivas2D extends JMEPrimitivas2DParaSwing
{
	final private GUI gui;  // componente padre
	private Dialogo ctxDialog;  // diálogo que contiene el lienzo de dibujo
	
	public JmeScriptGuiPrimitivas2D( GUI gui ) 
	{
		super();
		this.gui = gui;
	}


	///////////////////////////////////
	// PRIMITIVAS GRÁFICAS
	///////////////////////////////////	
	
	/**
	 * Esta implementación incluye tostadas y usa la superclase para los mensajes
	 */
	@Override
	public void mostrarMensaje( String msj, MSJ tipo, Integer posX, Integer posY, Integer tiempo )
	{
		// diálogo de mensaje por defecto
		if ( tiempo == null || tiempo < 0 )
			super.mostrarMensaje( msj, tipo, posX, posY, tiempo );
		// tostada
		else
		{
			final Point point = posX == null || posY == null ? null : new Point( posX, posY );
			switch( tipo )
			{
				case OK: ToastFactory.createOkToast( msj, point, tiempo ).showToast(); break;
				case WARNING: ToastFactory.createWarningToast(msj,point,tiempo).showToast(); break;
				case ERROR: ToastFactory.createErrorToast( msj, point, tiempo ).showToast(); break;
				default: ToastFactory.createToast( msj, point, tiempo ).showToast();
			}
		}
	}	
	
	@Override
	public String getUIName() { return GUI.APPNAME; }
	
	@Override
	public String getUIVersion() { return GUI.VERSION; }
	
	@Override
	public String getPrim2DName() { return GUI.APPNAME + " Primitivas2D"; }
	
	@Override
	public String getPrim2DVersionName() 
	{ 
		return super.getPrim2DVersionName() + " para " + GUI.APPNAME; 
	}
	
	@Override
	public GUI getPadre() { return gui; }
	
	@Override
	public void iniciarContexto(int indice, int ancho, int alto, int posX, int posY, String titulo) 
	{
		// crear diálogo gráfico y canvas
		ctxDialog = new Dialogo( indice, ancho, alto, posX, posY, titulo );
		ctxDialog.setVisible(true);
		ctxDialog.pack();
		if ( posX >= 0 && posY >= 0 )
			ctxDialog.setLocation( posX, posY );
		else
			ctxDialog.setLocationRelativeTo( gui );
		ctxDialog.canvas.requestFocusInWindow();
	}

	@Override
	public void finalizarContexto() 
	{
		if ( esContextoIniciado() && ctxDialog.isVisible() )
		{
			ctxDialog.setVisible( false );
			ctxDialog.dispose();
			ctxDialog = null;
		}
	}
	
	@Override
	public boolean esContextoIniciado() { return ctxDialog != null; }
	
	@Override
	public void repintar( int indice ) 
	{
		if ( !esContextoIniciado() )
			throw new IllegalStateException( 
								"El contexto " + indice + " no esta inicializado o esta cerrado" );
		ctxDialog.canvas.repaint();
		
		synchronized ( ctxDialog )
		{
			ctxDialog.imagenActual = new BufferedImage( 
									ctxDialog.backImage.getWidth(), ctxDialog.backImage.getHeight(), 
									BufferedImage.TYPE_INT_RGB );
			ctxDialog.imagenActual.getGraphics().drawImage( 
								ctxDialog.backImage, 0, 0, ctxDialog.canvas.getBackground(), null );			
		}
	}
	
	@Override
	public int getAncho() 
	{ 
		return esContextoIniciado() ? ctxDialog.backImage.getWidth() : -1; 
	}
	
	@Override
	public int getAlto()
	{ 
		return esContextoIniciado() ? ctxDialog.backImage.getHeight() : -1; 
	}
	
	@Override
	public Map getPropiedadesEntorno() 
	{
		final Properties prop = System.getProperties();
		prop.putAll( System.getenv()
					 .entrySet()
					 .parallelStream()
					 .collect( Collectors.toMap( entry -> (Object) entry.getKey(), 
							 					 entry -> (Object) entry.getValue() ) ) );
		
		return Collections.unmodifiableMap(prop);
	}
	
	
	///////////////////
	// CLASE DIALOGO
	///////////////////
	
	private static final JColorChooser COLOR_CHOOSER = new JColorChooser();  // selector de color del lienzo
	private static final JFileChooser fileChooser = new JFileChooser();  // selector de archivo para guardar lienzo
	
	static
	{
		fileChooser.addChoosableFileFilter( 
									  new FileNameExtensionFilter( "imagen JPEG", "jpg", "jpeg" ) );
		fileChooser.addChoosableFileFilter( new FileNameExtensionFilter( "imagen PNG", "png" ) );
		fileChooser.addChoosableFileFilter( new FileNameExtensionFilter( "imagen GIF", "gif" ) );
		fileChooser.setAcceptAllFileFilterUsed(false);
	}
	
	/**
	 * Diálogo creado en {@link JmeScriptGuiPrimitivas2D#iniciarContexto} que contiene el lienzo
	 * y opciones de manipulación de éste
	 */
	private class Dialogo extends JDialog
	{
		private JPanel canvas;
		private BufferedImage backImage,		
							  imagenActual;

		@SuppressWarnings("serial")
		public Dialogo( int indice, int ancho, int alto, int posX, int posY, String titulo ) 
		{
			super( gui, titulo != null ? titulo : "Contexto  " + indice );
			
			// TOOLBAR
			final JToolBar toolbar = new JToolBar( "Herramientas lienzo" );
			getContentPane().add( toolbar, BorderLayout.NORTH );
			
			// BUTTON selector de fondo del lienzo
			toolbar.add( new JButton( new ImageIcon( getClass().getResource( 
	   												GUI.resourcePath + "color_select.png" ) ) ) {
				{
					setHideActionText(true);
					setToolTipText( "Color de fondo del lienzo" );
					addActionListener( e -> {
						COLOR_CHOOSER.setColor( canvas.getBackground() );
						JColorChooser.createDialog( ctxDialog, "Color del fondo del lienzo", true, 
													COLOR_CHOOSER, null, null ).setVisible(true);
						final Color nuevoColor = COLOR_CHOOSER.getColor();
						if ( nuevoColor != null )
						{
							setFondo( new JSColor( nuevoColor ) );
							/*limpiar( 0, 0, getAncho(), getAlto(), null );
							repintar( indice );*/
						}
					});
				}
			});
			// BUTTON repintar
			toolbar.add( new JButton( new ImageIcon( getClass().getResource( 
										   			GUI.resourcePath + "repintar_icon.png" ) ) ) {
				{
					setHideActionText(true);
					setToolTipText( "Repintar lienzo" );
					addActionListener( e -> canvas.repaint() );
				}
			});
			// BUTTON guardar lienzo
			toolbar.add( new JButton( new ImageIcon( getClass().getResource( 
														GUI.resourcePath + "save_icon.png" ) ) ) {
				{
					setHideActionText(true);
					setToolTipText( "Guardar lienzo como imagen" );
					addActionListener( e -> {
						fileChooser.setDialogTitle( 
											"Guardar lienzo de <" + ctxDialog.getTitle() + ">" );
						if ( fileChooser.showSaveDialog(ctxDialog) == JFileChooser.APPROVE_OPTION )
						{
							String filename = fileChooser.getSelectedFile().getAbsolutePath(), ext;
							final int extIdx = filename.lastIndexOf( '.' );
							if ( extIdx >= 0 )
								ext = filename.substring( extIdx+1 ).toLowerCase();
							else
							{
								ext = ((FileNameExtensionFilter) fileChooser.getFileFilter())
									  .getExtensions()[0];
								filename += "." + ext;
							}
							final BufferedImage imagenFinal;
							synchronized ( ctxDialog )  // asegurar que la imagen actual está actualizada
							{
								if ( ext.equals("jpg") || ext.equals("jpeg") )  // quitar alfa en JPEG
								{
									imagenFinal = new BufferedImage( 
												imagenActual.getWidth(), imagenActual.getHeight(), 
												BufferedImage.TYPE_INT_RGB );
									imagenFinal.getGraphics().setColor( canvas.getBackground() );
									imagenFinal.getGraphics().drawImage( 
												imagenActual, 0, 0, canvas.getBackground(), null );
								}
								else
									imagenFinal = imagenActual;
							}
							try 
							{
							    ImageIO.write( imagenFinal, ext, new File(filename) );
							} 
							catch (IOException ex) 
							{
								JOptionPane.showMessageDialog( gui, 
														   	   ex.getMessage(), 
														   	   "Error al salvar " + filename, 
														   	   JOptionPane.ERROR_MESSAGE );
							}
						}
					});
				}
			});
			// BUTTON tamaño original
			toolbar.add( new JButton( new ImageIcon( getClass().getResource( 
														GUI.resourcePath + "pack_icon.png" ) ) ) {
				{
					setHideActionText(true);
					setToolTipText( "Tamaño por defecto" );
					addActionListener( e -> ctxDialog.pack() );
				}
			});
			// BUTTON cerrar todo
			toolbar.add( new JButton( new ImageIcon( getClass().getResource( 
												GUI.resourcePath + "cerrar_todo_icon.png" ) ) ) {
				{
					setHideActionText(true);
					setToolTipText( "Cerrar todo" );
					addActionListener( e -> {
						final List primitivas = 
															gui.runningScript.getListaPrimitivas(); 
						primitivas.forEach( prim2d -> prim2d.finalizarContexto() );
					});
				}
			});
			
			// crear imagen de respaldo del lienzo
			backImage = new BufferedImage(ancho, alto, BufferedImage.TYPE_INT_ARGB);
			
			// crear canvas
			canvas = new JPanel() {
				@Override
				protected void paintComponent(Graphics g) {
					super.paintComponent(g);
					g.drawImage( backImage, 
								 0, 0, getWidth(), getHeight(), 
								 0, 0, backImage.getWidth(), backImage.getHeight(), 
								 null );
				}
			};
			canvas.setBackground( Color.BLACK );
			canvas.setPreferredSize( new Dimension( ancho, alto ) );
			canvas.setFocusable(true);
			// keylistener para detectar key strokes en el lienzo 
			canvas.addKeyListener( new KeyAdapter() {
				@Override
				public void keyPressed(KeyEvent e) 
				{
					synchronized( getStrokeList() )
					{
						getStrokeList().add( new KeyStrokeEvent( e ) );
					}
				}
				@Override
				public void keyReleased(KeyEvent e)
				{
					synchronized( getStrokeList() )
					{
						getStrokeList().add( new KeyStrokeEvent( e ) );
					}
				};
			});
			// mouseadapter para detectar eventos de ratón
			final MouseAdapter mouseAdap = new MouseAdapter() {
				@Override
				public void mousePressed( MouseEvent e ) 
				{
					synchronized( getRatonEventList() )
					{
						getRatonEventList().add( new RatonEvent( e ) );
					}
				}
				@Override
				public void mouseReleased(MouseEvent e)
				{
					synchronized( getRatonEventList() )
					{
						getRatonEventList().add( new RatonEvent( e ) );
					}
				}
				@Override
				public void mouseEntered(MouseEvent e) 
				{
					synchronized( getRatonEventList() )
					{
						getRatonEventList().add( new RatonEvent( e ) );
					}
				}
				@Override
				public void mouseExited( MouseEvent e ) 
				{
					synchronized( getRatonEventList() )
					{
						getRatonEventList().add( new RatonEvent( e ) );
					}
				};
				@Override
				public void mouseDragged(MouseEvent e) 
				{
					synchronized( getRatonEventList() )
					{
						getRatonEventList().add( new RatonEvent( e ) );
					}
				}
				@Override
				public void mouseMoved(MouseEvent e) 
				{
					synchronized( getRatonEventList() )
					{
						getRatonEventList().add( new RatonEvent( e ) );
					}
				}
				@Override
				public void mouseWheelMoved( MouseWheelEvent e ) 
				{
					synchronized( getRatonEventList() )
					{
						getRatonEventList().add( new RatonEvent( e ) );
					}
				}
			};
			canvas.addMouseListener( mouseAdap );
			canvas.addMouseMotionListener( mouseAdap );
			canvas.addMouseWheelListener( mouseAdap );
			
			add( canvas );
			
			// cerrar ventana
			addWindowListener( new WindowAdapter() {
				public void windowClosing(WindowEvent e) 
				{
					finalizarContexto();
				};
			});
			
			// añadir acción pausa/reanudar
			getRootPane().getActionMap().put( "pause_resume", gui.actionMap.get( "pause_resume" ) );
			getRootPane().getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT )
						 .put( KeyStroke.getKeyStroke( "PAUSE" ), "pause_resume" );
			
			// definir Graphics2D desde imagen de respaldo
			final Graphics2D g2 = backImage.createGraphics();
			//g2.setColor( Color.BLACK );  // defecto blanco
			setGraphics( g2 );
			// establecer alta calidad
			g2.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING,
		  				 		 RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
			g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, 
			  			 		 RenderingHints.VALUE_ANTIALIAS_ON );
			g2.setRenderingHint( RenderingHints.KEY_RENDERING, 
				  			 	 RenderingHints.VALUE_RENDER_QUALITY );
			g2.setRenderingHint( RenderingHints.KEY_ALPHA_INTERPOLATION, 
				  			 	 RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY );
			g2.setRenderingHint( RenderingHints.KEY_COLOR_RENDERING, 
				  			 	 RenderingHints.VALUE_COLOR_RENDER_QUALITY );
			g2.setRenderingHint( RenderingHints.KEY_DITHERING, 
				  			 	 RenderingHints.VALUE_DITHER_ENABLE );
			g2.setRenderingHint( RenderingHints.KEY_INTERPOLATION,
				    			 RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		}
		
		private static final long serialVersionUID = 1L;

	}  // class Dialogo
	
}  // class JMEScriptGuiPrimitivas2D


Implementación de soporte SQL


Para implementar soporte SQL a una aplicación que use JMEScript, sólo es necesario implentar el interfaz jme.script.sql.AbstractSQL, y asignar la clase implementante al script, antes de ejecutarlo, mediante Script#getListaSgbdrSql#add o Script#setListaSgbdrSql para asignar una lista de SGBDR.

package jme.script.sql;

/**
 * Interfaz para la representación abstracta de una conexión a SGBDR mediante SQL
 * @author Miguel Alejandro Moreno Barrientos, (C)2020-2021
 * @since 0.6.2.0, JMEScript 0.2.0
 */
public interface AbstractSql

Este interfaz contiene cinco métodos para implentar la apertura, cierre, ejecución de consultas y lectura de datos de la consulta. Al final de esta sección se muesta un ejemplo de implementación para MySQL con el Conector-J que utiliza JMEScriptGUI.

  • El método conectar establece la conexión con la base de datos y en caso de fallo lanza una excepción apropiada
    /**
     * Conectar a la BBDD
     * @param idConexion identificador de conexión, para diferenciarla de múltiples conexiones
     * @param host host de la BBDD
     * @param usuario nombre de usuario de la BBDD
     * @param password contraseña de la BBDD
     * @throws Throwable error en la conexión
     */
    void conectar( Object idConexion, String host, String usuario, String password ) throws Throwable;
    
  • El método cerrar termina la conexión con la base de datos a partir del identificador de conexión del método conectar. En caso de fallo lanza una excepción apropiada
    /**
     * Cerrar la conexión 
     * @param idConexion identificador de conexión
     * @throws Throwable error al cerrar la conexión
     */
    void cerrar( Object idConexion ) throws Throwable;
    
  • El método ejecutar efectúa una consulta contra la base de datos y le asigna un identificador, y en caso de fallo lanza la excepción apropiada
    /**
     * Ejecuta una sentencia SQL
     * @param idConexion identificador de conexión
     * @param idSentencia identificador de la sentencia, para poder acceder a sus resultados 
     *        particulares
     * @param sql sentencia SQL del SGBDR utilizado
     * @return {@code true} si el valor devuelto es un conjunto de resultados o {@code false} si
     * no hay resultados o es conteo de actualizaciones
     * @throws Throwable error en la sentencia
     */
    boolean ejecutar( Object idConexion, Object idSentencia, String sql ) throws Throwable;
    
  • El método getData (sin columnas) devuelve secuencialmente los registros de una consulta o conteo de actualizaciones para una sola columna o para todas si se asigna a nombreCol la cadena '*', y en caso de fallo lanza la excepción apropiada
    /**
     * Devuelve el siguiente registro (fila) del resultado de la sentencia, 
     * o el conteo de actualizaciones para una sola columna de la tabla o para todas '*'
     * @param idConexion identificador de conexión
     * @param idSentencia identificador de sentencia
     * @param nombreCol nombre de la columna de la que se quiere obtener información
     * @return registro o conteo de actualizaciones (int) o {@code null} si no hay más registros
     * @throws Throwable no se pueden obtener más resultados o la sentencia está terminada
     * @see #getData(Object, Object, String[])
     */
    Object getData( Object idConexion, Object idSentencia, String nombreCol ) throws Throwable;
    
  • El método getData (con columnas) hace lo mismo que el método anterior para las columnas especificadas
    /**
     * Devuelve el siguiente registro (fila) del resultado de la sentencia, 
     * o el conteo de actualizaciones para las columnas seleccionadas
     * @param idConexion identificador de conexión
     * @param idSentencia identificador de sentencia
     * @param arrayCol nombres de las columnas de las que se quiere obtener información
     * @return registro o conteo de actualizaciones
     * @throws Throwable no se pueden obtener más resultados o la sentencia está terminada
     * @see #getData(Object, Object, String)
     */
    Object getData( Object idConexion, Object idSentencia, String[] arrayCol ) throws Throwable;
    



La clase jmescriptgui.MySql es una implementación de AbstractSql para JMEScriptGUI usando el Conector-J para MySQL:

package jmescriptgui;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import jme.script.sql.AbstractSql;

/**
 * Implementación MySQL del interfaz de conexión ente SGBDR y JMEScript para JMEScriptGUI.<br>
 * Usa el Conector-J 
 * @author Miguel Alejandro Moreno Barrientos, (C)2020-2021
 */
public class MySql implements AbstractSql 
{
	/** Las conexiones activas se guardan aquí */
	private final Map<Object,Connection> mapaConexiones = new HashMap<>();
	/** Las sentencias en activo se guardan aquí */
	private final Map<Object,Map<Object,Statement>> mapaSentencias = new HashMap<>();
	
	@Override
	public void conectar( Object idConexion, String host, String usuario, String password ) throws SQLException
	{
		// crear conexión
		final Connection con = DriverManager.getConnection( host, usuario, password );
		// si no ha habido excepción, mapear la conexión
		mapaConexiones.put( idConexion, con );
		mapaSentencias.put( idConexion, new HashMap<Object,Statement>() );
	}
	
	@Override
	public void cerrar( Object idConexion ) throws SQLException
	{
		// accede y comprueba la conexión especificada
		final Connection con = mapaConexiones.get( idConexion );
		if ( con == null )
			throw new SQLException( "Conexion " + idConexion + " inexistente" );
		// intenta cerrarla (SQLException if a database access error occurs)
		con.close();
		// si no ha habido excepción, elimina la conexión y sentencias del mapa 
		mapaConexiones.remove( idConexion );
		mapaSentencias.remove( idConexion );
	}

	@Override
	public boolean ejecutar( Object idConexion, Object idSentencia, String sql ) throws SQLException
	{
		// accede y comprueba la conexión especificada
		final Connection con = mapaConexiones.get( idConexion );
		if ( con == null )
			throw new SQLException( "Conexion " + idConexion + " inexistente" );
		// crea y ejecuta la sentencia
		final Statement stmt = con.createStatement();		
		final boolean res = stmt.execute( sql );
		// guarda la sentencia en su conexión
		mapaSentencias.get( idConexion ).put( idSentencia, stmt );
		
		return res;
	}
	
	@Override
	public Object getData( Object idConexion, Object idSentencia, String nombreCol ) throws Throwable 
	{
		if ( mapaConexiones.get( idConexion ) == null )
			throw new SQLException( "Conexion " + idConexion + " inexistente" );

		final Statement stmt = mapaSentencias.get( idConexion ).get( idSentencia );
		if ( stmt == null )
			throw new SQLException( "Sentencia " + idSentencia + " inexistente" );
		
		if ( stmt.getUpdateCount() >= 0 )
			return stmt.getUpdateCount();

		// leer nombre de columnas
		final int numColumnas = stmt.getResultSet().getMetaData().getColumnCount();
		final String[] arrayCol = new String[ numColumnas ];
		for ( int i = 1; i <= numColumnas; i++ )
			arrayCol[i-1] = stmt.getResultSet().getMetaData().getColumnName(i);

		// leer siguiente registro
		if ( stmt.getResultSet().next() )
		{
			return nombreCol.trim().equals("*")
				   ? getData( idConexion, idSentencia, arrayCol )
				   : stmt.getResultSet().getObject( nombreCol );
		}
		// limpiar sentencia del mapa de sentencias
		else
		{
			mapaSentencias.get( idConexion ).remove( idSentencia );
			
			return null;
		}
	}
	
	@Override
	public Object getData( Object idConexion, Object idSentencia, String[] arrayCol ) throws Throwable 
	{
		// obtener sentencia
		if ( mapaConexiones.get( idConexion ) == null )
			throw new SQLException( "Conexion " + idConexion + " inexistente" );

		final Statement stmt = mapaSentencias.get( idConexion ).get( idSentencia );
		if ( stmt == null )
			throw new SQLException( "Sentencia " + idSentencia + " inexistente" );

		// leer siguiente registro 
		if ( stmt.getResultSet().next() )
		{
			// diccionario columna/dato
			final Map<String,Object> res = new LinkedHashMap<>();

			for ( String col : arrayCol )
				res.put( col, stmt.getResultSet().getObject( col ) );
			
			return res;
		}
		// limpiar sentencia del mapa de sentencias
		else
		{
			mapaSentencias.get( idConexion ).remove( idSentencia );
			
			return null;
		}
	}
}
Asignación de un gestor MySQl en JMEScriptGUI antes del lanzamiento del script,
…
// iniciar gestor de bases de datos relacional (1 gestor mysql)
gui.runningScript.getListaSgbdrSql().add( new MySql() );
…


Implementación de acciones


Una aplicación que quiera utilizar acciones personalizadas sobre la propia aplicación debe implementar el interfaz AbstractAppAcciones y cargar la clase implementante en el script, antes de lanzarlo, mediante el método Script#setAppAcciones

package jme.script.appacciones;

/**
 * Interfaz para la implementación de acciones reconocibles por JMEScript
 * @author Miguel Alejandro Moreno Barrientos
 * @since 0.6.2.0, JMEScript 0.2.0
 */
public interface AbstractAppAcciones 
{
	/**
	 * Realiza la acción seleccionada y pasa/recibe opcionalmente parámetros 
	 * y resultado de la acción
	 * @param accionID identificador único de la acción
	 * @param parametros parámetro o parámetros como array que requiere la acción (o null)
	 * @return valor devuelto por la acción (o null)
	 */
	Object efectuarAccion( String accionID, Object parametros );
}

El método #efectuarAccion puede ejecutar la acción especificada mediante un mapa de acciones, o un simple switch, o cualquier método que se desee implementar para efectuar la acción.

En JMEScriptGUI, la clase JMEScriptGuiAppAcciones implementa éste interfaz mediante un simple switch:

package jmescriptgui;

import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.WindowEvent;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import com.sun.istack.internal.Nullable;

import jme.Expresion;
import jme.excepciones.ConversionException;
import jme.script.appacciones.AbstractAppAcciones;

/**
 * Implementación para JMEScriptGUI de {@code AbstractAppAcciones} para la ejecución de acciones
 * sobre el IDE mediante script
 * @author Miguel Alejandro Moreno Barrientos, (C)2020-2021
 * @since JMEScriptGUI 0.1.2.0
 */
public class JMEScriptGuiAppAcciones implements AbstractAppAcciones
{
	private final GUI gui;
	
	JMEScriptGuiAppAcciones( GUI gui ) 
	{
		this.gui = gui;
	}
	
	@Override
	public @Nullable Object efectuarAccion( String accionID, @Nullable Object parametros ) 
	{
		switch( accionID.toLowerCase() )
		{
			case "accion.cerrar": gui.dispatchEvent( new WindowEvent( gui, WindowEvent.WINDOW_CLOSING ) ); break;

			case "accion.flush": gui.actionMap.get( "flush" ).actionPerformed( null ); break;			
			
			case "set.mul_implicita": Expresion.setMultiplicacionImplicita( (boolean) parametros ); 
									  break;  // !Nota: no tiene efecto en la primera ejecución
			case "get.mul_implicita": return Expresion.isMultiplicacionImplicita();
			
			case "accion.maximizar": gui.setExtendedState( JFrame.MAXIMIZED_BOTH ); break;
			case "accion.minimizar": gui.setExtendedState( JFrame.ICONIFIED ); break;
			
			case "get.console": return System.out != gui.defOut;
			case "accion.console": gui.actionMap.get( "console" ).actionPerformed( 
						    new ActionEvent( gui.togConsole, ActionEvent.ACTION_PERFORMED, null ) );
			
			case "get.flip": return gui.outerSplitPanel.getOrientation();			
			case "accion.flip": gui.actionMap.get( "flip" ).actionPerformed( 
							   new ActionEvent( gui.btnFlip, ActionEvent.ACTION_PERFORMED, null ) );

			case "get.wrap": return gui.taConsola.getLineWrap();			
			case "accion.wrap": gui.actionMap.get( "wrap_console" ).actionPerformed( 
				  			   new ActionEvent( gui.togWrap, ActionEvent.ACTION_PERFORMED, null ) );

			case "set.font_consola":
			{
				final Map fontParam = (Map) parametros;
				gui.taConsola.setFont( new Font( fontParam.get( "name" ).toString(),
											 	 ((Number) fontParam.get( "style" )).intValue(),
											 	 ((Number) fontParam.get( "size" )).intValue() ) );
				break;
			}
			
			case "get.spin_salida": return EnhancedMapUtils.putIntoMap( 
										   new LinkedHashMap(),
										   "value", gui.spinSalidaMenu.getValue(),
										   "next_value", gui.spinSalidaMenu.getNextValue(),
										   "previousValue", gui.spinSalidaMenu.getPreviousValue() );
			case "set.spin_salida":
			{
				try
				{
					final int value = ((Number) parametros).intValue();
					SwingUtilities.invokeLater( () -> gui.spinSalidaMenu.setValue( value ) );
				}
				catch ( ClassCastException e )
				{
					throw new ConversionException( "Al establecer longitud de salida en consola", 
												   Integer.class, parametros.getClass(), e );
				}
				break;
			}		
			
			case "debug.threads":
			{
				final Thread[] tarray = new Thread[ Thread.activeCount()+1 ];
				Thread.enumerate( tarray );

				return Arrays.stream( tarray )
							 .filter( Objects::nonNull )
							 .map( t -> t.toString() + ": " + t.getState() )
							 .toArray( String[]::new );
			}
		}
		
		return null;
	}
	
}  // JMEScriptGuiAppAcciones

Cargar en el script, antes de ejecutar, la clase anterior:

…
// añadir control de acciones de la aplicación
gui.runningScript.setAppAcciones( new JMEScriptGuiAppAcciones( gui ) );
…


Ejemplos de script para descargar