Archivos de teclas de Digi3D 2011

Hay una pregunta que me hacen siempre que hago un curso de Digi3D cuando explico cómo asignar órdenes a teclas:

¿Cómo se qué en qué tecla tengo asignada una determinada orden?

Y la respuesta muy a mi pesar es siempre la misma: No hay manera de saberlo.

Este escenario cambia completamente en Digi3D 2011, ya que los archivos de teclas son XML y están pensados para que se puedan imprimir para tener un listado de las teclas asignadas. Además ahora podemos tener asignadas varias órdenes a una pulsación de teclas, haciendo innecesario el uso de macroinstrucciones.

Al instalar Digi3D 2011, se crea en la carpeta %ProgramData%\Digi3D 2011\Esquemas XML una carpeta con los esquemas de los archivos XML soportados por Digi3D 2011, y entre otros se copia el archivo Keyboard.xsd con el siguiente contenido:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://schemas.digi21.net/Digi3D/keyboard/v1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="Keyboard">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="Key">
          <xs:complexType>
            <xs:simpleContent>
              <xs:extension base="xs:string">
                <xs:attribute name="Name" type="xs:string" use="required" />
                <xs:attribute name="Shift" type="xs:boolean" use="optional" />
                <xs:attribute name="Control" type="xs:boolean" use="optional" />
                <xs:attribute name="Menu" type="xs:boolean" use="optional" />
              </xs:extension>
            </xs:simpleContent>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

En el que podemos ver que el nuevo formato está compuesto por un nodo raiz de tipo Keyboard que está compuesto por nodos Key que tienen que tener obligatoriamente un atributo Name y que opcionalmente pueden o no tener atributos de tipo Control, Shift o Menu

Veamos el contenido del archivo Default.keyboard.xml que instala el instalador de Digi3D 2011:

<?xml version="1.0" encoding="utf-8"?>
<Keyboard xmlns="http://schemas.digi21.net/Digi3D/keyboard/v1.0">
  <Key Name="AV PAG">rest_zoomin</Key>
  <Key Name="RE PAG">rest_zoomout</Key>
  <Key Name="F3">rest_zoome</Key>
  <Key Name="F4">rest_zoom1x1</Key>
  <Key Name="TECLA DE ADICION">velocidad_mas_xy</Key>
  <Key Name="TECLA DE SUSTRACCION">velocidad_menos_xy</Key>
  <Key Name="TECLA DE MULTIPLICACION">velocidad_mas_z</Key>
  <Key Name="TECLA DE DIVISION">velocidad_menos_z</Key>
  <Key Name="F2">tipo_indice</Key>
  <Key Name="F5">positivo_negativo</Key>
  <Key Name="B">correlar</Key>
  <Key Name="TABULACION">cambia_teclas_mnu</Key>
  <Key Name="F12">mostrar_estereo</Key>
  <Key Name="FIN" Control="true">salir_digi3d</Key>
  <Key Name="P" Control="true">ir_a_punto_apoyo</Key>
  <Key Name="F1">raton_digi3d</Key>
</Keyboard>

Se puede comprobar perfectamente que pulsando la combinación de teclas Control + Fin se ejecuta la orden SALIR_DIGI3D.

Ahora disponemos de un formato mucho mejor, pero tenemos un problema: Hay que hacer un programa que genere este tipo de archivos a partir de versiones anteriores.
El problema es que no es trivial, ya que no hay una forma directa de averiguar la tecla o la combinación de teclas que corresponde con el número de tecla almacenado en los archivos Teclas.mnu, ya que los números de tecla que aparecen en los archivos Teclas.mnu son números de teclas generados por MS-DOS y no por Windows.

Cuando pasamos de Digi (la versión de MS-DOS) a Digi21 (la versión de Windows) hicimos el esfuerzo de hacer que los archivos TECLAS.MNU de MS-DOS fueran compatibles con los archivos TECLAS.MNU del programa de Windows para así evitar que los usuarios de MS-DOS tuvieran que volver a generar sus archivos de teclas.

Para conseguirlo, dedicamos tiempo en intentar simular el número de tecla que genera MS-DOS a partir del número de tecla que nos envía Windows cuando el usuario realiza una pulsación.

Estos números no tienen nada que ver, pero al final conseguimos un algoritmo que acertaba en el 90% de los casos.

El algoritmo es el siguiente:

    UINT teclaDigi21 = 0;
    UINT nFlags = (pMsg->lParam & 0xffff0000) >> 16;

    if( mayusculasPulsada )
        nFlags += 25;
    else if( controlPulsada )
        nFlags += 35;
    else if( altPulsada )
        nFlags += 45;

    if( ::TranslateMessage(pMsg) ) {
        MSG msg;
        teclaDigi21 = msg.wParam;
    }

    int scanCode = (nFlags & 0xFF) << 8;
    teclaDigi21 |= scanCode;

Si te fijas, hay que hacer muchas operaciones para transformar el número de tecla que envía Windows para conseguir el valor teclaDigi21.

Una vez conocido el algoritmo, podríamos hacer un programa que crée una tabla con todas las posibles combinaciones. Este programa podría esperar a que el usuario pulse todo tipo de combinaciones y al finalizar almacenar la tabla que luego utilizará otro programa que es el que realmente transforma los archivos Teclas.mnu a Teclas.keyboard.xml.

Captura de pantalla del programa CreaTablaTeclasMnuAXml

Este programa espera a que el usuario pulse teclas. Por cada tecla pulsada añade una entrada en el ControlList donde nos muestra el código de tecla Digi21 calculado y la combinación de teclas que se ha pulsado.

Al aceptar el cuadro de diálogo, el programa pregunta la ruta del archivo de texto a crear, y lo que crea es código fuente en C# para luego añadirlo a un proyecto en C#.

A continuación tienes un ejemplo del resultado generado por este programa tras realizar una serie de pulsaciones de teclas:

    struct DatosTecla
    {
        public string Nombre;
        public bool Control;
        public bool Mayusculas;
        public bool Alt;

        public DatosTecla(string nombre, bool control, bool may, bool alt)
        {
            this.Nombre = nombre;
            this.Control = control;
            this.Mayusculas = may;
            this.Alt = alt;
        }
    };
private static Dictionary<int, DatosTecla> diccionario = new Dictionary<int, DatosTecla>
{
    { 8051, new DatosTecla("S", false, false, false) },
    { 8292, new DatosTecla("D", false, false, false) },
    { 7777, new DatosTecla("A", false, false, false) },
    { 8550, new DatosTecla("F", false, false, false) },
    { 9579, new DatosTecla("K", false, false, false) },
    { 9322, new DatosTecla("J", false, false, false) },
    { 11875, new DatosTecla("C", false, false, false) },
    { 12654, new DatosTecla("N", false, false, false) },
    { 12909, new DatosTecla("M", false, false, false) },
};

Si te fijas, este programa crea una estructura en C# con cuatro campos: Nombre, Control, Mayúsculas y Alt, y a continuación crea un diccionario cuya clave principal es el número de tecla de Digi21 y cuyo valor es esta estructura, de modo que el número de tecla 8051 es la tecla S sin Control, ni Mayúsculas ni Alt.

Ahora tan solo nos queda crear un proyecto de C# (yo he creado un proyecto de consola), copiar tanto la estructura como la instancia del diccionario dentro del proyecto y leer el archivo original para crear un archivo XML.

Y el algoritmo de este programa es muy sencillo:

  1. Abrimos el archivo Teclas.mnu antiguo.
  2. Leemos una línea
  3. Separamos la primera palabra de la línea y el resto.
  4. Transformamos la primera palabra a un número entero.
  5. Buscamos ese número en el diccionario.
  6. Almacenamos en el XML un nodo con la información devuelta por el diccionario.
  7. Volvemos al punto 2 mientras queden líneas

A continuación un recorte del código del programa que realiza las acciones 3, 4, 5 y 6:

                        int teclaDigi21 = int.Parse(partes[0]);

                        if (!diccionario.ContainsKey(teclaDigi21))
                        {
                            ++erroresLocalizados;
                            Console.WriteLine(string.Format("No ha sido posible traducir la tecla con número: {0}",
                                teclaDigi21));
                            continue;
                        }

                        DatosTecla datosTecla = diccionario[teclaDigi21];

                        escritor.WriteStartElement("Key");
                        escritor.WriteAttributeString("Name", datosTecla.Nombre);
                        if (datosTecla.Control)
                            escritor.WriteAttributeString("Control", "true");
                        if (datosTecla.Mayusculas)
                            escritor.WriteAttributeString("Shift", "true");
                        if (datosTecla.Alt)
                            escritor.WriteAttributeString("Menu", "true");

                        escritor.WriteString(partes[1]);
                        escritor.WriteEndElement();

y por último, el enlace al código fuente del programa en el repositorio de código fuente de Digi21 en GitHub: https://github.com/digi21/TeclasMnuATeclasMnuXml