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.

Thursday, August 30, 2007

Conclusiones (2007-08-28)

Motivación

El día 2007-08-28 celebramos una reunión David, Óscar y yo Miguel Ángel, con el objetivo de definir con mayor precisión el ámbito de la aplicación. Así mismo se pretendía comenzar la construcción de la aplicación.

Este texto recoge la mayor parte de las conclusiones de dicha reunión.

Ámbito
  • ¿Hasta qué punto es bueno casarse con GTK? ¿Existirá la posibilidad de utilizar otra librería gráfica en el futuro (QT, ncurses, ...), de manera que reutilice la mayor parte de la aplicación? Si dependemos de GTK, la aplicación será más rápida, pero si no lo hacemos, será más dinámica.
    La conclusión final es que no nos importa demasiado depender de GTK, ya que, al fin y al cabo, realmente no vamos a implementar ninguna otra interfaz.
  • Se utilizará un patrón modelo-vista-controlador. Las vistas serán componentes GTK que pueden cambiarse (árbol, lista, iconos, ...).
  • Por definición, la aplicación contendrá en todo momento dos paneles. Cada panel, un notebook y una línea que indique la URI actual, que sea editable y permita también el cambio de URI. Un cambio de URI puede provocar la destrucción del backend asociado a la página del notebook para cargar otro (ejemplo: estábamos en "/home" y pasamos a "smb://192.0.0.1"). Además, contendrá un log ocultable (que será un objeto que admite escribir partes de un texto - bien un treestore, bien un textarea - con formato).
  • Existirá una línea de órdenes global a ambos paneles, aunque siempre actuará sobre el panel activo.
  • Siempre existirá un panel activo y uno no activo. Sin embargo, esto no se puede tratar mediante el foco, ya que es probable que el foco pertenezca a la línea de órdenes anteriormente citada.
  • Un plugin puede contener una vista, un backend o bien una vista y un backend.
Operaciones del backends

En principio, se pueden dividir las operaciones realizables por el usuario en tres tipos:
  • Unarias: Siempre tendrán como origen y destino un único panel.
  • Binarias: Se requiere un panel de origen y uno de destino.
  • Externas: Requieren de un elemento externo a TwinPanel.
Operaciones unarias
  • Obtener lista de elementos que se pueden crear para un vector de URI determinadas. Si se solicita sin URI, se devolverá una lista de todos los objetos que el backend es capaz de crear. Si se proporciona más de una URI, el resultado serán los objetos creables en todos ellos.
    El resultado se ofrecerá de la forma: id, cadena (nombre a mostrar), tipo (indefinido, folder, leaf), id del icono.
  • Obtener lista de elementos para una URI.
  • Obtener identificación y versión del backend.
  • Eliminar lista de URIs.
  • Mover lista de tupla de URIs.
  • Obtener lista de acciones soportadas para una lista de URIs. Si no se proporciona la lista, se devolverán todas las acciones soportadas por cualquier elemento del backend.
    Existirán dos métodos: uno para las acciones más comunes (Abrir, borrar, copiar, pegar, ...) y otro para las propias del backend. La existencia de estos dos métodos queda justificada, ya que las acciones comunes serán una Hash, mientras que las acciones propias se devolverán en forma de lista.
  • Obtener un descriptor de fichero. Éste puede ser de lectura o de escritura.
Operaciones binarias
  • Copy. Se descompone en leer origen, escribir destino. Los permisos dependen de estas operaciones unarias.
  • Move. Se descompone en leer origen y borrar origen, escribir destino. Los permisos dependen de estas operaciones unarias.
  1. Se obtiene el descriptor de fichero de origen de solo lectura.
  2. Se obtiene el descriptor de fichero de destino de solo escritura.
  3. Se van copiando los datos del origen al destino.
Esta opción siempre estará disponible para todos los backends.

Como bien me recuerda Oscar, existe la posibilidad de que un backend sea capaz de crear otro y cargarlo internamente para realizar distintas opciones (como las arriba citadas del archivo zip).

Operaciones externas

En principio no nos interesan. Se trabajará con el clipboard.

Glosario
  • Panel: Cada uno de los lados de nuestra aplicación.
  • URI: Cadena de texto que permite volver a generar un acceso a un punto
    determinado de una estructura jerárquica de forma unívoca.
  • Backend: Objeto que permite tratar en última instancia un contenedor de
    la estructura jerárquica.

Saturday, August 11, 2007

What is Twin Panel?

Usually navigators like MS IExplorer, Nautilus or Konqueror have not all the capabilities we want. And they have only one panel to operate.

There are a lot of programs with two panels: FreeCommander, TotalCommander, ... But all of them have any problem: or they are very limited, or they are non-free.

Now we want to forget everything we have found in these navigators, and we want to wonder "what in the hell I really need". We want to use it as a Computer Engineering practice, by using so many patterns as possible in design.

The result... Well. It must be the very best of the navigators, the last project in the bin or something in the middle. We will see it in the future.

How can you help us? Easy:
  1. By using the betas and reporting the bugs.
  2. By speaking about it to your friends.
  3. By adding any plugin.
And that's all. Here you will find our Twin Panel, free like "freedom", and not like "free beer".

Thank you.

Obviedades

Pues como dice el título, este post va sobre asunciones de perogrullo, o no tanto... El objetivo es que todos tengamos claros cuales son los elementos y objetivo básico de Twin Panel (TP). Aprovecho también para definir un poco de nomenclatura para que podamos discutir detalles sin volvernos locos.

Paneles

El panel es el componente esencial de TP. En principio la aplicación está pensada para facilitar las clásicas operaciones con ficheros que involucran un origen y un destino, idea más que explotada en aplicaciones como Midnight Commander, Total commander y otros tantos. Por esa razón, TP presentará por defecto 2 paneles (de ahí su nombre) aunque nuestra idea es que sea lo suficientemente flexible para soportar 2 bloques de n paneles cada uno.

Arquitectura

TP consta de tres elementos principales, que encajan perfectamente en el clásico modelo de tres capas.
  • Backends (persistencia). Son clases especializadas en filesystems, entendiendo filesystem de una manera un tanto peculiar como se verá después. TP sólo incluirá de serie un backend para gestión de ficheros y directorios. Los demás se distribuirán como plugins.
  • Frontends (presentación). Son clases especializadas en representación. Cada panel está ocupado por únicamente por un frontend. El frontend básico es una lista con columnas. Aunque están desacoplados, lo habitual será que los plugins traigan frontends específicos; por ejemplo una vista de mapa para ver la topología de una red ofrecida por el backend correspondiente. A parte de los frontends que representan listas de elementos, también existen otros que representan preferencias y opciones de usuario, como por ejemplo los datos de conexión de un FTP.
  • Core (modelo). El core de la aplicación es el encargado de cargar frontends y backends y asegurar la consistencia. Por ejemplo puede cargar un frontend "lista" y otro "árbol" para renderizar el contenido de un mismo backend "ftp". El core también se encarga de realizar operaciones sobre los backends y entre ellos. Por ejemplo, copia de ficheros desde un backend "samba" a uno "ftp". También provee los mecanismos para determinar qué operaciones tienen sentido sobre cada backend.

Ámbito

O sea, ¿qué cosas se van a poder navegar con TP? En general cualquier cosa que esté estructurada como un árbol (un grafo es un árbol con "enlaces").

Siendo más específicos podemos distinguir dos tipos de elementos "listables":
  • Hojas: elementos terminales no manejables con TP a menos que exista un backend para ellos, por ejemplo un fichero .zip. Cuando un backend cargue un elemento "hoja" usando un backend ha de abrirse un nuevo panel. Es decir, no se pueden mezclar en el mismo panel elementos gestionados por diferentes backends.
  • Compuestos: son elementos que pueden contener elementos hoja y también elementos compuestos. El ejemplo evidente es un directorio.
Tanto las "hojas" como los "compuestos" pueden contener cualquier número de propiedades, pero, para un mismo backend, todos los elementos deben tener las mismas propiedades, aunque para algunos de ellos estén vacías (como una tabla en un base de datos). Cada propiedad tiene un nombre (de tipo cadena) y un valor que ha de ser un escalar (cadena, entero, flotante, fecha, permisos)

Y esto es todo por ahora. ¿comentarios?