MVC e Python + GTK, seconda parte

Ri-eccomi sull’argomento, prima di cominciare voglio ringraziare l’autore del framework in questione, Roberto Cavada, per aver commentato l’articolo precedente.

Nella prima puntata di questo tutorial avevo promesso di mostrarvi una piccola applicazione di esempio, Todoman, utile per comprendere in modo più approfondito il funzionamento di Pygtkmvc.

L’applicazione gestisce delle liste ToDo e consente all’utente di inserire, modificare e eliminare i task a proprio piacimento.

Ma basta chiacchiere e partiamo subito alla scoperta delle numerose funzionalità offerte dal framework. Innanzitutto analizziamo il modello di dominio e partiamo con una delle classi principali (come al solito, i commenti sono embedded):

import gtkmvc, gtkmvc.model

class ToDoListModel(gtkmvc.model.TreeStoreModel):
    """
    Rappresenta la lista dei task.
    
    La classe estende un modello di vista ad albero,
    in modo da velocizzare la sua rappresentazione.
    """
    # Questo dizionario contiene gli attributi
    # "observable", ogni modifica apportata
    # alle proprietà verrà notificata in modo
    # automatico. Nel corpo dei metodi della
    # classe si potrà accedere a tali proprietà
    # come a qualsiasi altra variabile di istanza.
    __properties__ = { 'name' : '', 'author' : '' }
    def __init__(self):
        """
        Costruttore della classe.
        
        Richiama il costruttore della classe base, passandogli
        come parametri il tipo di ognuna delle quattro colonne
        del widget TreeStore.
        """
        gtkmvc.model.TreeStoreModel.__init__(self, int, str, str, 'gboolean')
        self.__setup_properties() # inizializza i widget
    def __setup_properties(self):
        """
        Inizializza i widget grafici.
        """
        self.name = _("Unnamed todo list")
        self.author = _("Unknown author")
    def get_todo_by_path(self, path):
        """
        T.get_todo_by_path(path) -> todo -- inizializza un nuovo task con le informazioni presenti nella vista ad albero
        """
        ta = ToDoItemModel()
        ta.name = self[path][columns['NAME']]
        ta.priority = self[path][columns['PRIORITY']]
        ta.description = self[path][columns['DESCRIPTION']]
        ta.done = self[path][columns['DONE']]
        return ta
    def has_item(self, item_name):
        """
        T.has_item(item_name) -> bool -- restituisce True se nella vista ad albero è presente una riga con la stesso valore nella colonna 'NAME', False altrimenti
        """
        found = False
        for i in self:
            if i[columns['NAME']] == item_name:
                found = True
            break
        return found
    def append_todo(self, todo):
        """
        T.append(todo) -- inserisce un oggetto nella lista
        """
        self.append(None, [
            todo.priority,
            todo.name,
            todo.description,
            todo.done])
    def clear(self):
        """
        T.clear() -- cancella il contenuto della lista
        """
        gtkmvc.TreeStoreModel.clear(self)
        self.__setup_properties()

Quelli che seguono sono gli indici delle colonne che desideriamo mostrare nel widget GtkTreeView:

# Contiene gli indici delle colonne della vista ad albero.
columns = { 'PRIORITY' : 0,
            'NAME' : 1,
            'DESCRIPTION' : 2,
            'DONE' : 3 }

Una volta definita la lista, possiamo occuparci degli elementi che dovrà contenere:

class ToDoItemModel(gtkmvc.model.Model):
    """
    Rappresenta i task.
    

    La classe contiene delle proprietà testuali, numeriche e booleane,
    perciò estende un semplice modello generico.
    """
    __properties__ = {'name' : '', 'priority' : 0, 'description' : '', 'done' : False}
    def __init__(self):
        """
        Costruttore della classe.
        """
        gtkmvc.model.Model.__init__(self)
        self.__setup_properties()
        self.treestore = None               # relazione 1-a-1 con la lista che lo contiene
    def __setup_properties(self):
        """
        Inizializza i widget grafici.
        """
        self.name = "Unnamed todo item"     # nome di default
        self.description = ""
        self.done = False
        self.priority = 5
    def set_treestore(self, treestore):
        """
        T.set_treestore(treestore) -- imposta la vista ad albero nella quale è elencato.
        """
        self.treestore = treestore

Infine, poichè desideriamo salvare su file le nostre liste todo, definiamo una versione serializzabile del tipo lista:

class ToDoListModelSerializable(ToDoListModel):
    """
    Questa è la versione serializzabile della classe
    ToDoListModel, fornisce i metodi necessari per la
    scrittura e la lettura della configurazione su file.
    """
    SECTION_TODOLIST_DETAILS = 'Todolist details'
    OPTION_TODOLIST_NAME = 'Name'
    OPTION_TODOLIST_AUTHOR = 'Author'
    OPTION_TODOACTION_DESCRIPTION = 'Description'
    OPTION_TODOACTION_PRIORITY = 'Priority'
    OPTION_TODOACTION_DONE = 'Done'
    def __init__(self):
        """
        Costruttore della classe.
        """
        ToDoListModel.__init__(self)
        self.filename = ''
        self.changed = False
        # oggetto ConfigParser necessario per le operazioni su file
        self.config_parser = ConfigParser()
    def clear(self):
        """
        T.clear() -- cancella il contenuto della lista
        
        Richiama il metodo della classe base per cancellare
        il contenuto della lista, quindi reimposta il nome del file
        e il flag sui cambiamenti.
        """
        ToDoListModel.clear(self)
        self.filename = ''
        self.changed = False
    def save_to_file(self):
        """
        T.save_to_file() -> bool -- salva la lista su disco
        
        Memorizza il contenuto su file.
        """
        try:
            config_file = open(self.filename, 'w') # apre in scrittura
        except IOError, e:
            raise errors.FileUnknownException
        if self.config_parser.has_section(ToDoListModelSerializable.SECTION_TODOLIST_DETAILS) == False:
            self.config_parser.add_section(ToDoListModelSerializable.SECTION_TODOLIST_DETAILS)
        self.config_parser.set(ToDoListModelSerializable.SECTION_TODOLIST_DETAILS, ToDoListModelSerializable.OPTION_TODOLIST_NAME, self.name)
        self.config_parser.set(ToDoListModelSerializable.SECTION_TODOLIST_DETAILS, ToDoListModelSerializable.OPTION_TODOLIST_AUTHOR, self.author)

        for row in self: # memorizza tutti i task
            todo_name = row[columns['NAME']]
            if self.config_parser.has_section(todo_name) == False:
                self.config_parser.add_section(todo_name)
            self.config_parser.set(todo_name, ToDoListModelSerializable.OPTION_TODOACTION_DESCRIPTION, row[columns['DESCRIPTION']])
            self.config_parser.set(todo_name, ToDoListModelSerializable.OPTION_TODOACTION_PRIORITY, str(row[columns['PRIORITY']]))
            self.config_parser.set(todo_name, ToDoListModelSerializable.OPTION_TODOACTION_DONE, str(row[columns['DONE']]))
        self.config_parser.write(config_file) # save to file
        config_file.close()
        return True
    def read_from_file(self):
        """
        T.read_from_file() -> bool -- legge la lista da file
        
        Recupera le informazioni sulla lista da file.
        """
        config_file = self.filename
        self.config_parser.read([config_file])
        sections = self.config_parser.sections()
        if sections == []:
            raise errors.FileNotFoundException
        self.clear() # clears the treestore
        self.name = self.config_parser.get(ToDoListModelSerializable.SECTION_TODOLIST_DETAILS, ToDoListModelSerializable.OPTION_TODOLIST_NAME)
        self.author = self.config_parser.get(ToDoListModelSerializable.SECTION_TODOLIST_DETAILS, ToDoListModelSerializable.OPTION_TODOLIST_AUTHOR)
        self.changed = False
        sections.remove(ToDoListModelSerializable.SECTION_TODOLIST_DETAILS)
        for s in sections: # crea gli oggetti dei task
            # Si, lo so, si tratta di eager loading, ma questa è solo un'applicazione di esempio!
            todo = ToDoItemModel()
            todo.name = s
            todo.description = self.config_parser.get(s, ToDoListModelSerializable.OPTION_TODOACTION_DESCRIPTION)
            todo.priority = self.config_parser.getint(s, ToDoListModelSerializable.OPTION_TODOACTION_PRIORITY)
            todo.done = self.config_parser.getboolean(s, ToDoListModelSerializable.OPTION_TODOACTION_DONE)
            self.append_todo(todo)
        self.filename = config_file
        return True

Non credo siano necessari ulteriori spiegazioni, il codice illustrato è molto semplice e ben commentato.

Una volta sistemato il modello possiamo creare la vista, ma prima di scrivere altro codice apriamo Glade e progettiamo una semplice interfaccia grafica. Il mio consiglio è di creare un file di progetto per ogni finestra.

Ecco degli screenshot, per prima vi mostro la finestra principale del programma:

Questo è il dialogo delle proprietà della lista todo:

All’interno della seguente finestra di dialogo sono presenti le proprietà dei singoli task:

Carino, no?

Per quanto riguarda il codice, la questione è molto semplice; apriamo un nuovo file view.py e definiamo al suo interno le classi destinate a contenere le proprietà delle tre finestre mostrate sopra:

import gtkmvc, gtkmvc.view

class ToDoListView(gtkmvc.view.View):
    def __init__(self, ctrl):
        gtkmvc.view.View.__init__(self, ctrl, GLADE_MAIN_WINDOW)

class ToDoListPropertiesView(gtkmvc.view.View):
    def __init__(self, ctrl):
        gtkmvc.view.View.__init__(self, ctrl, GLADE_DIALOG_PROPERTIES)

class ToDoItemView(gtkmvc.view.View):
    def __init__(self, ctrl):
        gtkmvc.view.View.__init__(self, ctrl, GLADE_DIALOG_EDIT)

I commenti qui sono inutili, basta solo assegnare alle tre costanti globali (quelle in MAIUSCOLO) i percorsi dei tre file Glade.

Ora il modello è pronto, la vista anche, manca solo il controller, che implementeremo nella prossima puntata.

Facebook Twitter Linkedin Plusone Pinterest Email

Un pensiero su “MVC e Python + GTK, seconda parte

  1. Pingback: Alessio Treglia » Wordpress e il planet di Ubuntu Italia

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *