Sunday, September 16, 2007

Probando, probando

Pues como íbamos diciendo en posts anteriores, ya es hora de ir escribiendo algo de código. He puesto en el repo de twinpanel un ejemplo mínimo de inspector/vista/controlador llamado mvc.py.

Tal como hablábamos Oscar y yo, no es posible desacoplar totalmente vista y modelo cuando se usa "gtk.TreeView", ya que obliga a que el formato del modelo (colores y demás atributos) también esté en el modelo. A pesar de que rompe la idea de MVC, parece conveniente apostar por TreeView porque nos da gran parte del trabajo ya hecho.

Otro problema de TreeView es que, al menos en el uso habitual, la configuración de las columnas "visibles" (TreeViewColumn, TVC) se realiza en base al modelo (store) disponible, con lo cual hay mucho acoplamiento entre ambos. Y esto va totalmente en contra de nuestra idea de hacer vistas "plugables" que puedan utilizarse con muchos inspectores diferentes.

Afortunadamente, TreeView dispone de un mecanismo que permite rellenar las columnas utilizando un callback en lugar de indicar una columna de un store: set_cell_data_func(). Utilizando esta alternativa y definiendo un metamodelo (que describe el modelo) es posible hacer vistas totalmente genéricas, que se autoconfiguran en base a la información facilitada por cada inspector concreto.

En cualquier caso, TreeView sigue necesitando un store. Hemos optado por hacerlo lo más simple posible: un array de objetos. La clase de esos objetos la define cada inspector, y su metamodelo indica cómo obtener los datos a partir de los objetos contenidos en el store. De modo que es muy flexible y con el acoplamiento más pequeño posible (de los que se nos han ocurrido).

Tal cómo está implementado en el programa que refería, este sistema tiene algunas limitaciones:
  • Cada elemento del metamodelo corresponde a una columna de la vista. Por cada TVC, se crea un único 'cellrenderer' (indicado en el metamodelo). Se podría enriquecer el metamodelo para soportar varios cellrenders pero personalmente aquí aplicaría KISS.
  • Cada elemento del metamodelo sólo puede afectar a un atributo de la columna correspondiente. Esta puede que sea una limitación inadmisible, habrá que discutirlo. Puede que en este caso esté justificado enriquecer el metamodelo para hacerlo posible.
  • El store es una lista (ListStore) lo que implica que, en principio, no sería posible hacer vistas arbóreas. Yo creo que esto no es una limitación perjudicial en lo referente al store.
  • Raramente será admisible cargar toda una jerarquía en el modelo. Yo creo que sería más adecuada una solución similar a la de nautilus: sólo se cargan los hijos cuando se expande el árbol. Para lograr eso podríamos crear un nuevo inspector para ese directorio y combinar los nuevos objetos en el store existente. Aquí hay varias alternativas de implementación que habría que probar.
  • Al ser el store un array de objetos, las modificaciones en esos objetos no actualizan la vista. Esto sólo pasa con cambios asíncronos, por ejemplo, si cambia la fecha de un fichero cuando ya ha sido representado en la vista, ese cambio no se muestra. Se puede solucionar de una forma sencilla aunque no sé si muy elegante, forzando una modificación del store cuando sea necesaria. A la vez también es una ventaja, puesto que podemos modificar el modelo real sin que eso implique necesariamente que la vista deba recargarse.
Secuencia de operaciones:

Lo que voy a describir a continuación es un posible método para instanciación de inspectores y vistas. Una parte de esto es lo que pretende ser el ejemplo mvc.py.
  1. Al arrancar TP se registran en una 'factoría abstracta' todos los inspectors y vistas disponibles.
  2. El 'manager' recibe una petición para gestionar un uri.
  3. El 'manager' pide a la factoría un inspector que pueda manejar esa uri (puede haber varios). Se instancia el inspector pasándole una referencia al manager.
  4. El inspector crea un modelo básico, No hace falta calcular todos los datos hasta que sean necesarios (los pida la vista). Es decir, puede ser un modelo creado bajo demanda.
  5. El 'manager' pide a la factoría una vista que pueda representar ese inspector (puede haber varios). Se instancia la vista pasándole una referencia al inspector.
  6. La 'vista' se configura utilizando el metamodelo del inspector que se le pasa.
  7. El 'manager' pide a la vista el 'gui' y lo incrusta en la UI dónde corresponda.
Como ya he dicho, el ejemplo del repo no tiene factoría ni manager (aún) pero creo que sirve para hacerse una idea.

Saludos, y espero comentarios.

Saturday, September 15, 2007

Vistas e inspectores

Después de discutirlo, hemos decidido que lo que antes llamábamos "frontends" pasarán a llamarse "vistas" (lo siento Oscar) y los "backends" pasarán a llamarse "inspectores", dado que los nombres anteriores eran demasiado genéricos.

Aparte de este pequeño apunte sobre nomenclatura, hay una cuestión que debemos decidir lo antes posible porque puede afectar mucho a la estructura, funcionalidad e interfaces de TP. Probablemente vamos a tener dos tipos de inspectores:
  • Los que manipulan un modelo compuesto, es decir, manejan una lista. Esto incluye los que pueden representar un tgz como la lista de ficheros que contiene, y similares.
  • Los que manipulan un objeto concreto (una hoja), es decir, un inspector que puede manipular una imagen, una película, etc. Normalmente TP utilizará aplicaciones externas para manejar hojas, pero es muy posible que se nos presente la necesidad de desarrollar algunas que no existen, dado el ámbito en el que pretendemos movernos con TP.
En el caso de los inspectores de archivos ('archives', o sea tar, zip, etc) parece claro que al explorarlos se crea un inspector cuya vista substituye a la actual (como hace Total Commander), pero, en el caso de los "inspectores de objetos discretos", ¿qué hacemos?
  1. Sustituyen en el panel a la vista que los lanzó.
  2. Crean una nueva solapa
  3. Aparecen en una ventana diferente.
Me podéis decir que esto es simplemente una cuestión de configuración como hace firefox, pero en el caso de TP, esta decisión tiene implicaciones que no hay en firefox. Dado que TP asume que "el panel activo es el origen y el otro es el destino", elegir la opción 3 invalidaría ese principio. De modo que estaríamos dejando en la configuración algo que afecta mucho a las posibilidades de uso del inspector. Por otra parte, tenerlo como solapa (substituya o no a la que lo lanza) supone un problema de ortogonalidad puesto que rara vez tendrán sentido operaciones "inspector de discreto" <-> "inspector de compuesto", o sí?

No lo sé, ¿qué pensáis? ¿qué casos se os ocurren? Espero comentarios

Interfaces

Mmmm, esto va despacito, pero al menos parece que no se nos va de la cabeza, y eso es señal de que queremos que tire para adelante.

Lo cierto es que la idea tras TP da para mucho, casi cada día se me ocurren posibles funcionalidades que TP podría tener con relativamente poco esfuerzo (y no soy el único). Visto con cierta perspectiva, TP va camino de convertirse en el programa con más features de la historia de la informática, de llegar a hacerse, claro. Un buen slogan para el proyecto sería: "The most featured thing manager in the world" :-)

Ahora en serio, las conclusiones de nuestras primeras reuniones deberían dar lugar a un documento de diseño importante: el diagrama de clases, como forma de concretar esas decisiones. También deberíamos escribir (aquí mismo) una Especificación de Requisitos del Sistema. Aquí el problema básico es que los requisitos de usuario son prácticamente imposibles de fijar. Dado que la potencia de TP está en su flexibilidad, cada restricción le resta interés.

Las "interfaces" (en el más amplio sentido de la palabra) van a determinar cuan flexible podrá ser TP, pero está claro que las necesitamos. Elegir mal las interfaces, hará que la complejidad sea pronto inmanejable. Resumiendo, dudo que seamos capaces de definir unas interfaces perfectas a la primera (ni a la segunda). Por eso, creo que lo mejor es pensar que estamos desarrollando un prototipo evolutivo/desechable que nos permitirá probar nuestras ideas y a su vez capturar nuevos requisitos. Pero tenemos que tener claro desde el principio que habrá que tirarlo casi todo a la basura unas cuantas veces antes de llegar a un compromiso entre flexibilidad y complejidad. A partir de ese momento, nos podremos poner a programar plugins como locos, pero no antes.