Primero de todo, hemos de entender cómo funciona internamente Pineboo. Además, habremos de familiarizarnos con Python3, especialmente hay algunas técnicas más avanzadas que se usan para "hacer creer" al código QS que lo que hay detrás es un motor QSA Qt3.
Pineboo está programado como una librería, por lo que todo el código está dentro de la carpeta pineboolib. Los ficheros exteriores únicamente sirven como lanzadores. El programa principal es main.py. En él se desarrolla prácticamente todo lo que hace Pineboo, hasta el punto que deberíamos intentar separarlo en partes más pequeñas según qué funcionalidad cubren. Ahora mismo main.py controla casi todo lo que hace pineboo.
A grosso modo, el proceso de carga es el que sigue:
De forma más reciente, se soporta también la llamada a la acción EditRecord que hace algo parecido a lo anterior: (este proceso está bastante inacabado)
La descarga de ficheros de base de datos a la caché, tiene una "heurística" para detectar la codificación correcta. No es infalible, pero parece que funciona bien.
Los ficheros de la caché se guardan en carpetas con el nombre del fichero, y el fichero realmente tiene de nombre el hash. Esto está así a propósito. De este modo mezclo las cachés de distintos programas. Si dos programas comparten un fichero, este no se convierte dos veces. Si un fichero se modifica, cambia de nombre. Esto me simplifica bastante la vida.
La lectura de los XML y los UI se hace a través de python-lxml, que tendréis que aprender si queréis modificar o mejorar el código, aunque no creo que haga falta mucha colaboración en este punto concreto. Se guardan en una serie de clases, que la verdad, podrían estar mejor organizadas, ya que todas están en main.py y por otra parte hay cosas que están desperdigadas en otras clases cuando podríamos unificarlas por simplicidad. Hay clases diferentes para acciones según si venía del xml o del ui.
Estoy usando XMLStruct para mapear lo que se ve en los ficheros XML a propiedades de la clase. De este modo es mucho más fácil leer los ficheros. El problema es que en el código es un poco más complicado de seguir.
Respecto al formulario principal de la aplicación, José Antonio Fernández ya lo mejoró bastante, y viendo cómo está ahora mismo yo, en mi opinión, focalizaría los esfuerzos en otro área.
La conversión de QS a Python tiene bastante miga, en principio no espero colaboración en este área por su complejidad, pero también es la que más acabada tenemos. Sí agradecería colaboración detectando errores traduciendo de QS a Python; por ejemplo si un código de QS parece que vaya a hacer otra cosa en Python, o no se convierte del todo, u otro error. Obviamente para que convierta, tiene que parsear bien, y es mucho más sensible que Eneboo a ciertas formas de trabajo.
La importación dinámica de python, bueno, en principio se supone que sólo se pueden importar ficheros de código si se conoce previamente su nombre, pero usando (hackeando) el código del importador, es posible decirle que importe el que quieras. No es muy complicado, funciona, y creo que está terminado. La ventaja de importar dentro es que Python compila a bytecode el código fuente, por lo que acelera su ejecución y además se cachea en disco para que la próxima vez la importación sea casi instantánea.
Sobre la conversión de Qt3 a Qt5, lo que se hace a groso modo es leer el XML del fichero UI (formato Qt3) y empezamos a seguir sus instrucciones para crear un formulario con la misma "receta". El problema es que los "ingredientes" no son los mismos en Qt4, por lo que hay que ir traduciendo nombres de propiedad o controles al vuelo. Lo más importante aquí es que un control lo busca primero en "flcontrols.py" y después lo busca en Qt4. Eso quiere decir que si defines en flcontrols un control que sí existe en Qt4, fuerzas al UI a cargar este control en lugar del que viene por defecto, eso nos permite cambiarle las propiedades o ampliarlas.
Sobre la ejecución del código de la acción, actualmente solo lanzamos el init. Hay que tener en cuenta que al principio del py traducido hay unos cuantos imports que facilitan emular Qt3+QSA.
La parte donde más trabajo tenemos que poner todos es en la API de los ficheros QS y en los controles FL*.
Por mucho que ya traducimos a Python, ahora ese código empieza a buscar funciones en FLUtil que no existen, los controles FLTableDB le falta casi toda la funcionalidad, las FLSqlQuery no están ni implementadas, etc, etc.
Muchas de esas cosas están en Qt5 mejor resueltas que en Qt3, por lo que a veces con sencillos "wrappers" que emulen el comportamiento antiguo haría que todo empezase a funcionar. Pero hay mucho curro de ir función por función y enlazándola donde le toca. Además hay que probar mucho código, para ir viendo cómo funciona todo en la realidad.
Todo esto de la API se divide en unos pocos ficheros:
Para colaborar, lo normal es intentar ejecutar un programa completo, ir probando y viendo en la consola todos los mensajes que se reportan; localizar qué parte del código qs no se está lanzando y decidir qué nuevas API implementar.
Una vez implementemos lo nuevo, debemos comprobar que se ejecuta correctamente, al menos lo que nosotros hemos programado.
Muchas de las clases y funciones tienen triquiñuelas para evitar que de error cuando el QS haga algo para lo que no está programado. El primero que hice es el decorador "NotImplementedWarn", y más adelante hice el "DefFun". El primero hay que ponerlo función a función. El segundo solo una vez por clase. La funcionalidad de ambos es que cuando se llame a algo inexistente, lo informe por la consola, pero que emule el método devolviendo un valor por defecto, permitiendo que el código se ejecute más allá del error.
La conversión de ficheros de QS a Python se hace en dos pasos, primero de QS a XML y luego de XML a Python. Lo tenéis todo en la carpeta flparser.
El primer paso convierte el fichero QS en un XML. Consiste internamente en:
El segundo paso lee el XML y lo convierte en un fichero Python. Este es mucho más sencillo que el anterior. Lo que hacemos es convertir cada nodo del xml en un patrón de programación de Python. Cuando algo no se reconoce, hay que agregar un nuevo patrón de programación. Algunos son más complicados o confictivos que otros. Todo esto está controlado en el fichero pytnyzer.py.
En este último paso, me encargo de modificar lo que se escribe a Python. Por ejemplo se introducen una serie de encabezados al inicio del fichero. También detecto ciertas llamadas/patrones y reemplazo por otro contenido.
Un ejemplo de esto es la clase "qsa", que cuando el parser detecta llamadas a propiedades conflictivas (por ejemplo antes era posible hacer child("x").text = "1" y ahora hay que hacer un setText("1")) consigue detectar más o menos el tipo de llamada que pretende y lanzar en su lugar la correcta. De ese modo el código qsa sigue funcionando. Este problema lo hemos tenido ya en Eneboo al mejorar su parseador (cuando lo hizo infoSial en AbanQ), y también ocurre de forma mucho más grave cuando intentas lanzar en Qt4 el QtScript, ya que ocurre exactamente lo mismo, lo que antes eran propiedades ahora son funciones.
Por eso, cuando veáis el código Python, algunas partes las veréis cambiadas. Al principio tendremos que depurar cuidadosamente para estar seguros de que estamos ejecutando lo mismo que antes.
Todo empieza con la función loadUi de qt3ui.py. En ella leemos a mano el fichero, y identificamos las conexiones a realizar, las imágenes y "el widget".
El widget, que sería alo como el widget raíz, es donde empieza lo complicado. Dentro del widget se define todo el formulario en forma de layouts y más widgets.
La función "loadWidget" se encarga de este dilema recursivo. Tienen toda la lógica necesaria y se llama a sí misma. La clave reside en el "for c in xml", el cual procesa todas las ordenes del ui para este widget. Según el tipo de etiqueta realizamos una u otra acción. Cuando encontramos otro widget, lo creamos usando la función "createWidget" que nos busca y nos crea la clase adecuada. A partir de aquí se llama a sí mismo para completar la carga del widget de forma recursiva.
Lo más curioso de esta función es que traduce unas propiedades por otras (translate_properties) y para asignarlas, busca una función setXyz para una propiedad xyz.
De momento lo que más faena da son los FLTableDB dentro de pestañas (QTabWidget). Dado que este control se inicializa en su construcción, por el modo en que se construye en este caso, queda mal inicializado.