Archivio

Archivio per la categoria ‘wxPython’

Mqtt e python: esempio pratico

11 Gennaio 2019 Commenti chiusi

Dopo i primi passi su Mqtt, vediamo un esempio con un’app wx.
Creiamo un frame con all’interno un semplice widget.
Usiamo per comodità una static text, in modo da poterne cambiare il background colour ed il testo contenuto.

# monitor.py
#
# A simple wx app with mqtt client example
# Send command from another client to change the widget colour.
# ON and OFF commands are accepted.

import time
import paho.mqtt.client as paho
import wx


BROKER = "test.mosquitto.org"
PORT = 1883
TOPIC = "bancaldo/test/monitor"


class MonitorPanel(wx.Panel):
    def __init__(self, parent):
        super(MonitorPanel, self).__init__(parent=parent)
        self.text = wx.StaticText(self, size=(200, 200))


class MonitorFrame(wx.Frame):
    def __init__(self, parent, title):
        super(MonitorFrame, self).__init__(parent=parent, title=title)
        self.client = None
        self.parent = parent
        self.panel = MonitorPanel(parent=self)

        self.Bind(wx.EVT_CLOSE, self.on_quit)

        self.panel.SetBackgroundColour(wx.WHITE)
        self.SetSize((200, 200))
        self.Center()

    # Mqtt client on_message callback
    def on_message(self, client, userdata, message):
        time.sleep(1)
        com = str(message.payload.decode("utf-8"))
        print("[DEBUG] received message =", 
        	  str(message.payload.decode("utf-8")))
        if com.upper() == "ON":
            self.panel.text.SetLabel('LIGHT ON!')
            self.panel.text.SetBackgroundColour(wx.GREEN)

        elif com.upper() == "OFF":
            self.panel.text.SetLabel('LIGHT OFF!')
            self.panel.text.SetBackgroundColour(wx.WHITE)
        else:
            self.panel.text.SetLabel("Unknown command:\n"
                                     "'%s'\n Use 'ON' or 'OFF' commands only!"
                                     % com)
            self.panel.text.SetBackgroundColour(wx.RED)

    def init_client(self):
        self.client = paho.Client("Mqtt Monitor")
        self.client.on_message = self.on_message
        print("connecting %s to broker " % self.client._client_id)
        self.client.connect(BROKER, PORT)
        print("subscribing %s to topic '%s'..." % (self.client._client_id,
        	                                       TOPIC))
        self.client.subscribe(TOPIC)
        self.client.loop_start()

    def on_quit(self, event):
        self.client.loop_stop()
        self.client.disconnect()
        self.Destroy()


class MQTTApp(wx.App):
    def OnInit(self):
        monitor = MonitorFrame(parent=None, title='MQTT Monitor')
        monitor.Show()
        monitor.init_client()
        return True


if __name__ == '__main__':
    app = MQTTApp(False)
    app.MainLoop()

Ha molta importanza la callback on_message, che personalizziamo qui:

class MonitorFrame(wx.Frame):
    ...
    # Mqtt client on_message callback
    def on_message(self, client, userdata, message):
        time.sleep(1)
        com = str(message.payload.decode("utf-8"))
        print("[DEBUG] received message =", 
        	  str(message.payload.decode("utf-8")))
        if com.upper() == "ON":
            self.panel.text.SetLabel('LIGHT ON!')
            self.panel.text.SetBackgroundColour(wx.GREEN)

        elif com.upper() == "OFF":
            self.panel.text.SetLabel('LIGHT OFF!')
            self.panel.text.SetBackgroundColour(wx.WHITE)
        else:
            self.panel.text.SetLabel("Unknown command:\n"
                                     "'%s'\n Use 'ON' or 'OFF' commands only!"
                                     % com)
            self.panel.text.SetBackgroundColour(wx.RED)

In pratica preleviamo il messaggio da payload e a seconda del
suo valore, coloro il widget in modo diverso.

L’assegnazione della callback, avviene durante l’inizializzazione del client mqtt,
che avviene nel metodo init_client:

class MonitorFrame(wx.Frame):
    ...
    def init_client(self):
        self.client = paho.Client("Mqtt Monitor")
        self.client.on_message = self.on_message
        print("connecting %s to broker " % self.client._client_id)
        self.client.connect(BROKER, PORT)
        print("subscribing %s to topic '%s'..." % (self.client._client_id,
        	                                       TOPIC))
        self.client.subscribe(TOPIC)
        self.client.loop_start()
    ...

class MQTTApp(wx.App):
    def OnInit(self):
        monitor = MonitorFrame(parent=None, title='MQTT Monitor')
        monitor.Show()
        monitor.init_client() # inizializzo il client mqtt
        return True


if __name__ == '__main__':
    app = MQTTApp(False)
    app.MainLoop()

Per testarne invece il funzionamento, utilizziamo l’applicazione MQTTfx,


ma andrebbe benissimo anche una shell di python, dove poter creare un client
e dare i comandi sul topic corretto:

>>> from paho.mqtt.client import Client
>>> client = Client("Mqtt Client 1")
>>> topic = "bancaldo/test/monitor"
>>> broker = "test.mosquitto.org"
>>> client.connect(broker, port=1883)
0
>>> client.publish(topic, "ON")
<paho.mqtt.client.MQTTMessageInfo object at 0x0221C750>

>>> client.publish(topic, "OFF")
<paho.mqtt.client.MQTTMessageInfo object at 0x0222A330>

>>> client.publish(topic, "FOO")
<paho.mqtt.client.MQTTMessageInfo object at 0x0221C750>


Se controlliamo la shell dove abbiamo lanciato l’app monitor.py, troveremo i messaggi di log ricevuti
grazie alla callback on_message:

(venv) C:\tmp>python monitor.py
connecting b'Mqtt Monitor' to broker
subscribing b'Mqtt Monitor' to topic 'bancaldo/test/monitor'...
[DEBUG] received message = ON
[DEBUG] received message = OFF
[DEBUG] received message = FOO
Categorie:iot, mqtt, python, wxPython Tag: , , ,

wxpython: switch between different panels

9 Novembre 2018 Commenti chiusi
import wx


ID_PANEL_1 = 10001
ID_PANEL_2 = 10002


class PanelOne(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent=parent)
        self.btn = wx.Button(self, label="Button 1")
        self.size_x = 300
        self.size_y = 300
        self.SetSize((self.size_x, self.size_y))


class PanelTwo(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent=parent)
        self.btn = wx.Button(self, label="Button 2")
        self.size_x = 200
        self.size_y = 200
        self.SetSize((self.size_x, self.size_y))


class CommonFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Test")
        self.panel_one = PanelOne(self)
        self.panel_two = PanelTwo(self)
        self.panel_two.Hide()

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.panel_one, 1, wx.EXPAND)
        self.sizer.Add(self.panel_two, 1, wx.EXPAND)
        self.SetSizer(self.sizer)

        self.panels = {ID_PANEL_1: self.panel_one,
                       ID_PANEL_2: self.panel_two}

        menu_bar = wx.MenuBar()
        menu = wx.Menu()
        switch_to_panel_1 = menu.Append(ID_PANEL_1, "to panel 1", "Some text")
        switch_to_panel_2 = menu.Append(ID_PANEL_2, "to panel 2", "Some text")
        menu_bar.Append(menu, "switch panels")
        self.SetMenuBar(menu_bar)

        self.Bind(wx.EVT_MENU, self.on_switch, switch_to_panel_1)
        self.Bind(wx.EVT_MENU, self.on_switch, switch_to_panel_2)
        self.Bind(wx.EVT_BUTTON, self.on_btn_1, self.panel_one.btn)
        self.Bind(wx.EVT_BUTTON, self.on_btn_2, self.panel_two.btn)

    def on_btn_1(self, event):
        print("BUTTON 1 CLICKED")

    def on_btn_2(self, event):
        print("BUTTON 2 CLICKED")

    def on_switch(self, event):
        for ids in [k for k in self.panels.keys() if k != event.GetId()]:
            self.panels[ids].Hide()
        panel = self.panels[event.GetId()]
        panel.Show()
        self.SetSize((panel.size_x, panel.size_y))
        self.sizer.Layout()


if __name__ == "__main__":
    app = wx.App(False)
    frame = CommonFrame()
    frame.Show()
    app.MainLoop()
Categorie:python, wxPython Tag: ,

Ubuntu 16.04: pip install wxpython

17 Ottobre 2018 Commenti chiusi

Installare wx su ubuntu 16.04 all’interno di un virtual environment, può risultare ostico
Se tentassimo infatti di installare le librerie wx con il comando:

pip install wxpython

otterremmo un errore e l’installazione non andrebbe a buon fine.

Come indicato sul sito ufficiale delle librerie wx,

è possibile installare le suddette librerie, con pip, tramite il comando:

pip install -U --pre -f https://wxpython.org/Phoenix/snapshot-builds/linux/gtk3/ubuntu-16.04 wxPython

dove le opzioni stanno ad indicare:

-U, –upgrade: aggiorna i pacchetti alla versione più recente;
–pre: Include le versioni pre-rilascio o in sviluppo, poichè di default, pip troverebbe solo le versioni stable;
-f, –find-links: cerca i link ad eventuali archivi, all’interno dell’url specificato.

Se l’errore durante l’installazione persiste, affidarsi ai Wheel.
Sul sito https://extras.wxpython.org/wxPython4/extras/linux/gtk3/
selezionare la propria distribuzione e successivamente la relativa architettura e versione desiderata di wx.
Scaricato il wheel, posizionarlo all’interno del venv ed installarlo con il comando:

pip install wxPython-4.0.3-cp35-cp35m-linux_x86_64.whl

Ovviamente il nome del file.whl varierà da caso a caso

Categorie:python, Ubuntu, wxPython Tag: , , ,

wxpython: statusbar + gauge

22 Novembre 2017 Commenti chiusi
import wx
import time


class View(wx.Frame):
    def __init__(self, parent):
        self.parent = parent
        super(View, self).__init__(parent=self.parent)
        self.panel = wx.Panel(parent=self)
        self.SetSize((350, 100))
        self.status_bar = self.CreateStatusBar(2)
        self.status_bar.SetStatusText('0/0')
        self.status_bar.SetStatusWidths([100, -1])
        gauge_pos, gauge_size = self.get_gauge_dimensions()
        self.gauge = wx.Gauge (self.status_bar, -1, 100, gauge_pos, gauge_size)
        # bindings
        self.Bind(wx.EVT_SIZE, self.on_size)
        self.Show()

    def get_gauge_dimensions(self):
        """Get gauge position and dimensions"""
        pos_x, pos_y, dim_x, dim_y = self.status_bar.GetFieldRect(1)
        return (pos_x, pos_y), (dim_x, dim_y)
       
    def on_quit(self, event):
        self.Destroy()

    def on_size(self, event):
        """Resize gauge when the main frame is resized"""
        size = self.GetSize()
        self.SetSize(size)
        gauge_pos, gauge_size = self.get_gauge_dimensions()
        self.gauge.SetSize(gauge_size)
        event.Skip ()       
        self.Update()

    def set_range(self, value):
        """Set the range of gauge"""
        self.gauge.SetRange(value)

    def get_range(self):
        """Get the range of gauge"""
        return self.gauge.GetRange()

    def set_progress(self, value):
        """Set the indicator gauges progress"""
        self.gauge.SetValue(value)

    def set_status_text(self, value):
        """Set the indicator text gauges progress"""
        self.status_bar.SetStatusText(value)


if __name__ == "__main__":
    app = wx.App(False)
    tot = 300  # maximum value for gauge
    v = View(parent=None)
    v.set_range(tot)
    for n in range(1, tot + 1):
        v.set_progress(n)
        time.sleep(.001)
        v.set_status_text('%s/%s' % (n, tot))
    v.set_progress(0)
    app.MainLoop()

Categorie:python, wxPython Tag: ,

FantaStat 2.0

22 Novembre 2017 Commenti chiusi

Introduzione

Fstat 2.0 è una semplice applicazione per visualizzare le statistiche
dei giocatori di fantacalcio.
La precedente versione serviva a visualizzare i file.txt veccio formato, quelli che indicavano anche i goal segnati, gli assist ecc.
I file più recenti indicano solo il fanta voto, voto senza bonus malus e la quotazione:

code|NAME|TEAM|fanta voto|voto|quotazione

Link utili

Il codice dell’applicazione è reperibile nel repo corrispondente di github.
I binari win con database players.db qui.
I file txt delle giornate di fantacampionato sono reperibili sul blog.

Moduli utilizzati
Python 2.7.8
grafica: wx 3.0.2.0
ORM: Django 1.10.1

L’utilizzo è molto semplice e di seguito viene descritto:

Creazione database

Qualora ci si dimenticasse di creare il database per primo, al lancio dell’applicazione si verrà avvisati da un messaggio.

Ogni baco segnalato sarà ben gradito.

Creare quindi il database con il seguente comando:

python manage.py makemigrations players

Le migrations iniziali dovrebbero già essere presenti nel repo di github, quindi potrebbero non risultare modifiche, procedere quindi con il secondo comando:

python manage.py migrate

Avviare Fstat2.0

Per avviare l’applicazione eseguire lo script main.py con il comando:

python main.py

Non avendo ancora importato nessun giocatore, verremo avvisati da un messaggio che è necessario importare il primo file:

Le prime giornata disputate sono già fornite con il repo in posizione players/days, oppure sono sempre reperibili qui.
Dal menu Import-> Import players importare il file desiderato, solitamente la giornata 1 (MCC1.txt)

Il file dei giocatori va importato solo la prima volta, dopodichè il menu non sarà più attivo.
Da qui in avanti si importeranno solo i file dei voti.
Il primo sarà nuovamente MCC1.txt. Dal menu Import -> Import Evaluations, procedere con l’importazione.

Finita un’importazione eseguire un refresh dall’apposito pulsante.
L’interfaccia si presenta come in figura:

I radio-buttons permettono di filtrare i giocatori per ruolo, mentre cliccando sulle colonne della lista,
l’ordinamento avverrà in base alla colonna cliccata. Ad esempio cliccando su real_team, i giocatori
saranno ordinati per ruolo e per squadra di appartenenza.

Cliccando invece sulla “riga” di un determinato giocatore, sarà possibile modificarlo.

Dal menu Player -> Summary si accede alla lista riassuntiva del giocatori, poichè la visualizzazione della schermata del frame principale, mostra le medie dei giocatori e non i valori iniziali.
Da qui cliccando sui giocatori si possono modificare come in precedenza.

Qualora nei file non risultasse un giocatore, sarà possibile crearlo dal menu Players -> New Player

Stesso discorso vale per le valutazioni; esiste un sommario accessibile dal menu Evaluations -> Summary

dove cliccando sulla riga di una valutazione, sarà possibile modificarla

Come per i giocatori, sarà possibile aggiungere una valutazione ad hoc, dal menu
Evaluations -> New Evaluation

Come ultima operazione, è possibile cancellare tutti i dati, dal menu Reset.

Fantamanager 2.2

20 Settembre 2017 Commenti chiusi

Diversamente dalla versione precedente FantaLega 2.1 e dalla versione Light, qui è possibile gestire un campionato di n.squadre, a scontri diretti (con più di 8/10 squadre).
Il giocatore non potrà più appartenere a più squadre ma è prevista un’asta.

Opzioni:
Asta
Costruzione Squadre
Generazione calendario
Modifica Squadre
Consegna formazioni
Modifica Formazioni
Importazione Giocatori e Voti Gazzetta
Modifica Giocatori
Mercato tra squadre
Calcolo punteggio formazioni
Classifica Squadre

Grafica: WX
Django per l’ORM

Utilizzare virtualenv ed installare i requirements:

pip install -r requirements.txt

Creare il database:

python manage.py makemigrations fantalega
python manage.py migrate

lanciare l’applicazione con il comando:

python main.py

Il link di github:
repo su github

Fantalega: gestore Asta

1 Settembre 2017 Commenti chiusi

Ieri è terminata ufficialmente la sessione di calciomercato.
Molti fanta-allenatori hanno rischiosamente già effettuato le loro aste,
molti altri (noi), NO.

Ecco un tool parecchio spartano che velocizzerà le annotazioni
squadra-giocatore durante l’asta.

Auction1.0 è un gestore di aste di fantacalcio scritto in python.
Per la grafica sono state utilizzate le librerie wx, mentre per la
gestione del database e l’ORM, è stato utilizzato django.

Requisiti: installare django e wxpython

Su github è presente una prima bozza di repo.

Per prima cosa creare i database con i due comandi:

python manage.py makemigrations auction
python manage.py migrate

dopo queste operazioni che creeranno il database con le relative tabelle, è
possibile avviare la app:

python main.py

per prima cosa importiamo i giocatori utilizzando i soliti file txt in
formato Gazzetta, reperibili qui:

E’ possibile importare i giocatori più e più volte in modo da aggiornare
la lista dei calciatori.
Importati i giocatori, è necessario creare le squadre.

Dopo aver creato le squadre, si comincia l’asta.

Si seleziona il giocatore per squadra reale di appartenenza,
si utilizzano i filtri di ruolo e si assegna un costo d’asta e la squadra
che si aggiudica il giocatore. Durante l’asta è possibile avere un resoconto
delle squadre, con budget rimanente e giocatori ancora da comprare

Dai menu Teams e Players è possibile editare le squadre e i giocatori
per apportare delle modifiche. E’ anche possibile aggiungere giocatori che,
in fase d’asta, non sono ancora stati ufficializzati da Gazzetta e quindi
non presenti nei file.

Terminata l’asta è possibile effettuare scambi tra fanta-allenatori con il menu
“Trades”.

Nota: la casella max trades del pannello edit-team indica le massime operazioni
di mercato concesse durante la fantalega, mutuate dall’applicazione completa.
Queste operazioni (Trades) in fase di asta, sono invece illimitate.

Come ultima operazione, è possibile esportare un file in formato csv (menu Auction).

python: generatore squadre Calcio 7

28 Gennaio 2015 Commenti chiusi

I tuoi amici si lamentano sempre perchè fai delle squadre squilibrate?
Una volta quando eri giovane eri fortissimo e adesso sei un vecchio che vive di ricordi come me?
Ecco un’applicazione utile, scritta in python, per comporre le squadre di una partita di calcio a 7.
Questa app, tiene conto della valutazione complessiva di un calciatore e del suo stato di forma, definisce un massimo gap ammissibile tra le due squadre e compone le stesse casualmente, tenendo conto anche dei ruoli.
Tutto quello che devi fare è inserire i giocatori e valutarli da 0 a 100, sia come valore complessivo, sia come stato di forma.

core

Inserire i nomi dei propri amici (lo so ho amici importanti…)
dal menu Giocatore (Nuovo Giocatore):
aggnuovo

Poi stabilire se il gap delle due squadre è ottimale.
gap

Infine comporre le squadre selezionando gli amici che verranno al campo e premendo sul pulsante dedicato.
fatte

Ovviamente restringendo il gap, le combinazioni saranno sempre meno. In caso non si riuscisse dopo 10000 tentativi, un messaggio avviserà di allargare la forbice.

In caso di disparità dei giocatori, crearne uno JOLLY con valutazione 0 0, in modo da equilibrare le squadre nonostante la disparità.

Grossi bug non dovrebbero essercene, in caso una segnalazione sarebbe gradita.

Un po’ di codice:

# controller.py
__author__ = 'Banchellia'

import wx
from model import Model
from view import View
from random import shuffle

# pyinstaller
#import flask_sqlalchemy._compat as _

#import flask.ext.sqlalchemy as s

class Controller(object):
    def __init__(self, model):
        self.model = model
        self.giocatori = self.tutti_giocatori()
        self.view = View(parent=None, title='"calcio a 7" v.2.1 by Bancaldo',
                         controller=self)

    def salva_nuovo_giocatore(self, nome, cognome, valore, forma, ruolo):
        msg = self.model.nuovo_giocatore(nome, cognome, valore, forma, ruolo)
        return msg

    def modifica_giocatore(self, nome, cognome, valore, forma, ruolo):
        msg = self.model.modifica_valori(nome, cognome, valore, forma, ruolo)
        return msg

    def tutti_giocatori(self):
        return  ['%s %s %s %s <%s>' %(g.cognome, g.nome.capitalize(), g.valore,
                                      g.forma, g.ruolo[0])
                 for g in self.model.tutti_giocatori()]

    def trova_giocatore(self, nome, cognome):
        giocatore =  self.model.trova_giocatore(nome.upper(), cognome.upper())
        self.model.giocatore = giocatore
        self.view.edit_view.update_ui_edit_giocatore(giocatore)

    def cancella_giocatore(self, nome, cognome):
        msg = self.model.cancella_giocatore(nome, cognome)
        return msg

    def get_gap(self):
        return self.model.diff

    def set_gap(self, value):
        try:
            self.model.diff = int(value)
            return '+++ nuovo GAP settato!'
        except ValueError:
            return "ERROR: il valore del GAP deve essere un numero!"

    # Algoritmo di composizione
    # 1) controllo che siano stati selezionati i giocatori
    # 2) controllo che i giocatori siano pari e se sono dispari
    #        seleziono il giocatore jolly "ASSENTE Assente"
    # 3) creata la lista, la seziono in base ai ruoli e la mescolo
    # 4) la ricompongo mescolata ma in ordine di ruoli e divido
    #        in due con metodo zigzag [::2][1::2]

    def componi(self, iterable):
        list_selezionati = list(iterable)
        ordinati = self.dividi_per_ruolo(list_selezionati)
        verde, gialla = ordinati[::2], ordinati[1::2]
        diff = self.check_gap(verde, gialla)
        tentativi = 1
        while diff >= self.model.diff:
            print 'GAP: <%s> troppo alto' % diff
            print '[CORE] Formazioni non equilibrate: ricalcolo...'
            print '[CORE] Tentativo n. %s...' % tentativi
            ordinati = self.dividi_per_ruolo(list_selezionati)
            verde, gialla = ordinati[::2], ordinati[1::2]
            tentativi += 1
            if tentativi > 10000:
                verde, gialla = [], []
                break
            else:
                diff = self.check_gap(verde, gialla)
        if not verde and not gialla:
            self.view.alert(
                """Devi aumentare il GAP o non riesco [conferma con set GAP]""")
        else:
            print '[CORE] GAP squadre <%s>' % diff
            print '[CORE] Composizione squadre riuscita!'
            self.view.update_squadre(verde, gialla)

    def dividi_per_ruolo(self, iterable):
        pp = [g for g in iterable if g.split(' ')[4] == '<p>']
        dd = [g for g in iterable if g.split(' ')[4] == '<d>']
        cc = [g for g in iterable if g.split(' ')[4] == '<c>']
        aa = [g for g in iterable if g.split(' ')[4] == '<a>']
        for lr in (pp, dd, cc, aa):
            shuffle(lr)
        return pp + dd + cc + aa

    def check_gap(self, verde, gialla):
        val_verde = sum([int(g.split(' ')[2]) for g in verde])
        sdf_verde = sum([int(g.split(' ')[3]) for g in verde])/2
        val_gialla = sum([int(g.split(' ')[2]) for g in gialla])
        sdf_gialla = sum([int(g.split(' ')[3]) for g in gialla])/2
        diff = abs((val_verde + sdf_verde) - (val_gialla + sdf_gialla))
        return diff


if __name__ == '__main__':
    app = wx.App()
    model = Model()
    controller = Controller(model)
    app.MainLoop()


# model.py
__author__ = 'Banchellia'

import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

# pyinstaller
import flask_sqlalchemy._compat as _
import flask.ext.sqlalchemy as s

t = s
print t.__str__

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///giocatori.db'
db = SQLAlchemy(app)


class Giocatore(db.Model):
    """Class Giocatore per creazione tabella in sqlite"""
    id = db.Column(db.Integer, primary_key=True)
    nome = db.Column(db.String(30))
    cognome = db.Column(db.String(30))
    valore = db.Column(db.Integer())
    forma = db.Column(db.Integer())
    ruolo = db.Column(db.String(20))

    def __init__(self, nome, cognome, valore=60, forma=60, ruolo='difensore'):
        self.nome = nome
        self.cognome = cognome
        self.valore = valore
        self.forma = forma
        self.ruolo = ruolo

    def __repr__(self):
        return '<%s - %s>' % (self.cognome, self.nome.capitalize())


class Model(object):
    """classe Model per pattern MVC."""
    def __init__(self):
        self._diff = 25
        self._giocatore = None
        if not self._database_exist():
            self._create_database()
            print "[CORE] Database creato con successo!"
        else:
            print "[CORE] rilevato database."

    @property
    def diff(self):
        return self._diff

    @diff.setter
    def diff(self, value):
        self._diff = value

    @property  # attributo a cui associo l'oggetto calciatore trovato
    def giocatore(self):
        return self._giocatore

    @giocatore.setter  # attributo a cui associo l'oggetto calciatore trovato
    def giocatore(self, obj):
        self._giocatore = obj

    def _database_exist(self):
        """Ritorna True se il database e' gia' esistente"""
        return 'giocatori.db' in os.listdir(os.getcwd())

    def _delete_database(self):
        """Cancella il database e tutti i dati contenuti in esso"""
        ans = raw_input("[CORE] Sto per eliminare il database, sicuro? (Y/N): ")
        if ans.upper() == 'Y':
            db.session.close()
            os.remove('giocatori.db')
        else:
            print "[CORE] Operazione annullata!"

    def _create_database(self):
        """Crea il database se non esiste"""
        db.create_all()

    def nuovo_giocatore(self, nome, cognome, valore, forma, ruolo):
        """Inserisce in database un nuovo giocatore:
           nuovo_giocatore(nome, cognome).
           Ritorna in entrambi i casi un messaggio da visualizzare in UI"""
        if not self.giocatore_esistente(nome, cognome):
            giocatore = Giocatore(nome=nome.strip().upper(),
				  cognome=cognome.strip().upper(),
                                  valore=int(valore.strip()),
                                  forma=int(forma.strip()),
                                  ruolo=ruolo.lower())
            db.session.add(giocatore)
            db.session.commit()
            return '+++ Giocatore <%s> inserito correttamente' % cognome.upper()
        else:
            return 'Giocatore <%s> esistente' % cognome

    def giocatore_esistente(self, nome, cognome):
        """Ritorna True se il giocatore di nome e cognome esiste"""
        return Giocatore.query.filter_by(
            nome=nome.strip().upper(), 
            cognome=cognome.strip().upper()).first() is not None

    def trova_giocatore(self, nome, cognome):
        """Ritorna un oggetto Giocatore di nome=nome.
           Se l'oggetto non e' a database, il metodo non ritorna nulla:
           trova_giocatore(nome, cognome)"""
        return Giocatore.query.filter_by(
            nome=nome.strip().upper(),
            cognome=cognome.strip().upper()).first()

    def tutti_giocatori(self):
        """Ritorna una tupla con tutti i Giocatori presenti a database"""
        return db.session.query(Giocatore).all()

    def modifica_valori(self, *args):
        """Aggiorna gli attributi dell'oggetto giocatore.
           Ritorna un messaggio da visualizzare sulla UI"""
        nome, cognome, valore, forma, ruolo = args
        self.giocatore.nome = nome.strip().upper()
        self.giocatore.cognome = cognome.strip().upper()
        self.giocatore.valore = int(valore.strip())
        self.giocatore.forma = int(forma.strip())
        self.giocatore.ruolo = ruolo.lower()
        db.session.commit()
        return '+++ Giocatore <%s> modificato!' % self.giocatore.cognome

    def cancella_giocatore(self, nome, cognome):
        """Cerca il giocatore di nome e cognome e lo cancella dal database.
           Ritorna un messaggio da visualizzare sulla UI"""
        giocatore = self.trova_giocatore(nome, cognome)
        db.session.delete(giocatore)
        db.session.commit()
        return '+++ Giocatore <%s> eliminato!' % cognome


if __name__ == '__main__':
    m = Model()
# view.py
__author__ = 'Banchellia'

import wx
from wx import html as wxhtml


class View(wx.Frame):
    def __init__(self, parent, title, controller=None):
        super(View, self).__init__(parent=parent, title=title)
        self.controller = controller
        self.giocatori = controller.tutti_giocatori()
        # Menu
        menubar = wx.MenuBar()
        self.SetMenuBar(menubar)

        # Menu Giocatore
        menu_giocatore = wx.Menu()
        menubar.Append(menu_giocatore, "Giocatore")
        self.menu_nuovo = menu_giocatore.Append(-1, "Nuovo Giocatore",
                        "Crea nuovo giocatore")
        self.menu_modifica = menu_giocatore.Append(-1, "Modifica Giocatore",
                        "Modifica dati giocatore")
        self.menu_elimina = menu_giocatore.Append(-1, "Elimina Giocatore",
                        "Elimina giocatore")

        # Menu Info
        menu_help = wx.Menu()
        menubar.Append(menu_help, "?")
        self.menu_about = menu_help.Append(-1, "about...",
                        "info software")
        self.menu_helpsw = menu_help.Append(-1, "help",
                        "software help")

        # Layout
        self.panel = Panel(parent=self, giocatori=self.giocatori)
        self.update_gap()
        self.SetSize((700, 700))
        self.Centre()
        self.Show()

        # Bind
        self.Bind(wx.EVT_MENU, self.nuovo_giocatore, self.menu_nuovo)
        self.Bind(wx.EVT_MENU, self.modifica_giocatore, self.menu_modifica)
        self.Bind(wx.EVT_MENU, self.elimina_giocatore, self.menu_elimina)
        self.Bind(wx.EVT_MENU, self.about, self.menu_about)
        self.Bind(wx.EVT_MENU, self.helpsw, self.menu_helpsw)
        self.Bind(wx.EVT_BUTTON, self.componi, self.panel.btn_componi)
        self.Bind(wx.EVT_BUTTON, self.esci, self.panel.btn_quit)
        self.Bind(wx.EVT_BUTTON, self.set_gap, self.panel.btn_set)

    # Callbacks
    def nuovo_giocatore(self, event):
        """Apre la subUI nuovo giocatore disattivando il frame principale"""
        self.Disable()
        self.new_view = ViewNuovoGiocatore(parent=self, title='Nuovo Giocatore')

    def modifica_giocatore(self, event):
        """Apre la subUI edit giocatore disattivando il frame principale"""
        self.Disable()
        self.edit_view = ViewEditGiocatore(parent=self, title='Edit Giocatore')

    def elimina_giocatore(self, event):
        """Apre la subUI elimina giocatore disattivando il frame principale"""
        self.Disable()
        ViewEliminaGiocatore(parent=self, title='Elimina Giocatore')

    def set_gap(self, event):
        """Modifica il gap tra le due squadre"""
        gap = self.panel.gap.GetValue()
        msg = self.controller.set_gap(gap)
        InfoMessage(self, msg).get_choice()
        self.update_gap()

    def update_gap(self):
        """Aggiorna il textctrl del gap"""
        gap = self.controller.get_gap()
        self.panel.gap.SetValue(str(gap))

    def componi(self, event):
        """Richiama l'algoritmo di composizione del controller"""
        self.panel.verde.Clear()
        self.panel.gialla.Clear()
        selezionati = self.panel.clb_giocatori.GetCheckedStrings()
        if len(selezionati) % 2 == 1:
            InfoMessage(self, 'Giocatori dispari').get_choice()
        elif len(selezionati) == 0:
            InfoMessage(self, 'Seleziona i giocatori!').get_choice()
        else:
            self.controller.componi(selezionati)

    def update_squadre(self, verde, gialla):
        """Compone con le due squadre, le listbox relative"""
        InfoMessage(self, """Composizione riuscita!\n""").get_choice()
        self.panel.verde.Clear()
        self.panel.gialla.Clear()
        self.panel.verde.AppendItems(verde)
        self.panel.gialla.AppendItems(gialla)

    def esci(self, event):
        self.Destroy()

    def helpsw(self, event):
        self.Disable()
        HelpFrame(parent=self, title='Help "Calcio a 7"')

    def about(self, event):
        self.Disable()
        AboutFrame(parent=self, title='About "Calcio a 7"')

    def alert(self, msg):
        InfoMessage(self, msg).get_choice()

class Panel(wx.Panel):
    def __init__(self, parent, giocatori):
        super(Panel, self).__init__(parent=parent)
        self.gap = wx.TextCtrl(self)
        self.clb_giocatori = wx.CheckListBox(self, choices=sorted(giocatori),
                                             size=(250, 500))
        self.verde = wx.ListBox(parent=self, size=(180, 200), choices=[],
                                style=wx.LB_SINGLE)
        self.gialla = wx.ListBox(parent=self, size=(180, 200), choices=[],
                                style=wx.LB_SINGLE)
        self.btn_set = wx.Button(self, label='set GAP')
        self.btn_componi = wx.Button(self, label='Componi')
        self.btn_quit = wx.Button(self, label='Quit')

        sizer = wx.FlexGridSizer(rows=4, cols=3, hgap=5, vgap=5)

        sizer.Add(wx.StaticText(self, label="giocatori disponibili"),
                  1, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 5)
        sizer.Add(wx.StaticText(self, label="squadra VERDE"),
                  1, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 5)
        sizer.Add(wx.StaticText(self, label="squadra GIALLA"),
                  1, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 5)

        sizer.Add(self.clb_giocatori, 1, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 5)
        sizer.Add(self.verde, 1, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 5)
        sizer.Add(self.gialla, 1, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 5)

        sizer.Add(wx.StaticText(self, label="max gap squadre"),
                  1, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 5)
        sizer.Add(self.gap, 1, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 5)
        sizer.Add(self.btn_set, 1, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 5)
        sizer.Add(self.btn_componi,
                          1, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 5)
        sizer.Add(self.btn_quit,
                          1, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 5)
        self.SetSizer(sizer)


class ViewNuovoGiocatore(wx.Frame):
    '''GUI per creazione nuovo giocatore'''
    def __init__(self, parent, title):
        super(ViewNuovoGiocatore, self).__init__(parent=parent, title=title)
        self.parent = parent
        self.controller = self.parent.controller
        # Layout
        self.panel = PanelNuovoGiocatore(parent=self)
        self.SetSize((400, 300))
        self.Centre()
        self.Show()
        # Bind
        self.Bind(wx.EVT_BUTTON, self.salva_giocatore, self.panel.btn_save)
        self.Bind(wx.EVT_BUTTON, self.on_quit, self.panel.btn_cancel)

    def salva_giocatore(self, event):
        nome = self.panel.nome.GetValue()
        cognome = self.panel.cognome.GetValue()
        valore = self.panel.valore.GetValue()
        forma = self.panel.forma.GetValue()
        ruolo = self.panel.ruolo.GetStringSelection()
        if nome and cognome and valore and forma and ruolo:
            msg = self.controller.salva_nuovo_giocatore(
                nome, cognome, valore, forma, ruolo)
        else:
            msg = 'Inserisci tutti i valori'
        self.update_ui_nuovo_giocatore(msg)

    def on_quit(self, event):
        # Aggiorno ComboBox Top Frame
        giocatori = self.controller.tutti_giocatori()
        self.parent.panel.clb_giocatori.Clear()
        self.parent.panel.clb_giocatori.AppendItems(sorted(giocatori))
        self.parent.Enable()
        self.Destroy()

    def update_ui_nuovo_giocatore(self, msg):
        InfoMessage(parent=self, message=msg).get_choice()
        self.panel.nome.SetValue('')
        self.panel.cognome.SetValue('')
        self.panel.valore.SetValue('60')
        self.panel.forma.SetValue('60')
        self.panel.ruolo.SetLabel('scegli ruolo...')


class PanelNuovoGiocatore(wx.Panel):
    def __init__(self, parent):
        super(PanelNuovoGiocatore, self).__init__(parent=parent)
        self.nome = wx.TextCtrl(self)
        self.cognome = wx.TextCtrl(self)
        self.valore = wx.TextCtrl(self, value='60')
        self.forma = wx.TextCtrl(self, value="60")
        ruoli = ('portiere', 'difensore', 'ccampista', 'attaccante')
        self.ruolo = wx.ComboBox(self, -1, "scegli ruolo...", choices=ruoli,
                                 style=wx.CB_DROPDOWN)

        panel_sizer = wx.FlexGridSizer(rows=6, cols=2, hgap=5, vgap=5)
        panel_sizer.Add(wx.StaticText(self, label="* nome:"),
                  0, wx.ALIGN_CENTER_VERTICAL, 10)
        panel_sizer.Add(self.nome, 0, wx.EXPAND | wx.ALL)
        panel_sizer.Add(wx.StaticText(self, label="** cognome:"),
                  0, wx.ALIGN_CENTER_VERTICAL, 10)
        panel_sizer.Add(self.cognome, 0, wx.EXPAND | wx.ALL)
        panel_sizer.Add(wx.StaticText(self, label="valore (0/100):"),
                  0, wx.ALIGN_CENTER_VERTICAL, 10)
        panel_sizer.Add(self.valore, 0, wx.EXPAND | wx.ALL)
        panel_sizer.Add(wx.StaticText(self, label="Stato di forma (0/100):"),
                  0, wx.ALIGN_CENTER_VERTICAL, 10)
        panel_sizer.Add(self.forma, 0, wx.EXPAND | wx.ALL)
        panel_sizer.Add(wx.StaticText(self, label="ruolo:"),
                  0, wx.ALIGN_CENTER_VERTICAL, 10)
        panel_sizer.Add(self.ruolo, 0, wx.EXPAND | wx.ALL)

        panel_sizer.AddGrowableCol(1)
        # button sizer
        button_sizer = wx.StdDialogButtonSizer()
        self.btn_save = wx.Button(self, wx.ID_SAVE)
        self.btn_save.SetDefault()
        self.btn_cancel = wx.Button(self, wx.ID_CANCEL)
        button_sizer.AddButton(self.btn_save)
        button_sizer.AddButton(self.btn_cancel)
        button_sizer.Realize()

        panel_sizer.Add(button_sizer, 0, wx.ALIGN_CENTER|wx.ALL, 2)
        self.SetSizer(panel_sizer)


class ViewEditGiocatore(wx.Frame):
    '''GUI per modifica giocatore Esistente'''
    def __init__(self, parent, title):
        self.parent = parent
        super(ViewEditGiocatore, self).__init__(parent=self.parent, title=title)
        self.controller = self.parent.controller
        giocatori = self.controller.tutti_giocatori()
        # Layout
        self.panel = PanelEditGiocatore(parent=self, giocatori=giocatori)
        self.SetSize((400, 300))
        self.Centre()
        self.Show()
        # Bind
        self.Bind(wx.EVT_BUTTON, self.modifica_giocatore, self.panel.btn_save)
        self.Bind(wx.EVT_BUTTON, self.on_quit, self.panel.btn_cancel)
        self.Bind(wx.EVT_COMBOBOX, self.on_giocatore, self.panel.giocatori)

    def modifica_giocatore(self, event):
        nome = self.panel.nome.GetValue()
        cognome = self.panel.cognome.GetValue()
        valore = self.panel.valore.GetValue()
        forma = self.panel.forma.GetValue()
        ruolo = self.panel.ruolo.GetValue()
        if nome and cognome and valore and forma and ruolo:
            msg = self.controller.modifica_giocatore(nome, cognome, valore,
                                                     forma, ruolo)
            self.update_ui_edit_giocatore()

        else:
            msg = 'Inserisci tutti i valori o seleziona giocatore!'
        InfoMessage(self, msg).get_choice()

    def on_quit(self, event):
        giocatori = self.controller.tutti_giocatori()
        self.parent.panel.clb_giocatori.Clear()
        self.parent.panel.clb_giocatori.AppendItems(sorted(giocatori))
        self.parent.Enable()
        self.Destroy()

    def on_giocatore(self, event):
        cognome, nome = self.panel.giocatori.GetStringSelection().split(' ')[:2]
        self.controller.trova_giocatore(nome, cognome)

    def update_ui_edit_giocatore(self, giocatore=None):
        if giocatore:
            self.panel.nome.SetValue(giocatore.nome)
            self.panel.cognome.SetValue(giocatore.cognome)
            self.panel.valore.SetValue(str(giocatore.valore))
            self.panel.forma.SetValue(str(giocatore.forma))
            self.panel.ruolo.SetValue(giocatore.ruolo)
        else:
            self.panel.nome.SetValue('')
            self.panel.cognome.SetValue('')
            self.panel.valore.SetValue('')
            self.panel.forma.SetValue('')
            self.panel.ruolo.SetLabel('scegli ruolo...')

        self.panel.giocatori.Clear()
        self.panel.giocatori.AppendItems(sorted(
            self.controller.tutti_giocatori()))
        self.panel.giocatori.SetLabel('scegli...')


class PanelEditGiocatore(wx.Panel):
    def __init__(self, parent, giocatori):
        super(PanelEditGiocatore, self).__init__(parent=parent)
        self.giocatori = wx.ComboBox(self, -1, "scegli...",
                                     choices=sorted(giocatori),
                                     style=wx.CB_DROPDOWN)
        self.nome = wx.TextCtrl(self)
        self.cognome = wx.TextCtrl(self)
        self.valore = wx.TextCtrl(self)
        self.forma = wx.TextCtrl(self)
        ruoli = ('portiere', 'difensore', 'ccampista', 'attaccante')
        self.ruolo = wx.ComboBox(self, -1, "scegli ruolo...", choices=ruoli,
                                 style=wx.CB_DROPDOWN)

        panel_sizer = wx.FlexGridSizer(rows=8, cols=2, hgap=5, vgap=5)
        panel_sizer.Add(wx.StaticText(self, label="giocatori esistenti:"),
                        0, wx.ALIGN_CENTER_VERTICAL, 10)
        panel_sizer.Add(self.giocatori, 0, wx.EXPAND | wx.ALL)
        panel_sizer.Add(wx.StaticText(self, label="nome:"),
                        0, wx.ALIGN_CENTER_VERTICAL, 10)
        panel_sizer.Add(self.nome, 0, wx.EXPAND | wx.ALL)
        panel_sizer.Add(wx.StaticText(self, label="cognome:"),
                        0, wx.ALIGN_CENTER_VERTICAL, 10)
        panel_sizer.Add(self.cognome, 0, wx.EXPAND | wx.ALL)
        panel_sizer.Add(wx.StaticText(self, label="valore (0/100):"),
                        0, wx.ALIGN_CENTER_VERTICAL, 10)
        panel_sizer.Add(self.valore, 0, wx.EXPAND | wx.ALL)
        panel_sizer.Add(wx.StaticText(self, label="Stato di forma (0/100):"),
                        0, wx.ALIGN_CENTER_VERTICAL, 10)
        panel_sizer.Add(self.forma, 0, wx.EXPAND | wx.ALL)
        panel_sizer.Add(wx.StaticText(self, label="ruolo:"),
                        0, wx.ALIGN_CENTER_VERTICAL, 10)
        panel_sizer.Add(self.ruolo, 0, wx.EXPAND | wx.ALL)
        panel_sizer.AddGrowableCol(1)
        # button sizer
        button_sizer = wx.StdDialogButtonSizer()
        self.btn_save = wx.Button(self, wx.ID_SAVE)
        self.btn_save.SetDefault()
        self.btn_cancel = wx.Button(self, wx.ID_CANCEL)
        button_sizer.AddButton(self.btn_save)
        button_sizer.AddButton(self.btn_cancel)
        button_sizer.Realize()

        panel_sizer.Add(button_sizer, 0, wx.ALIGN_CENTER|wx.ALL, 2)
        self.SetSizer(panel_sizer)

class ViewEliminaGiocatore(wx.Frame):
    """GUI per cancellazione giocatore Esistente"""
    def __init__(self, parent, title):
        self.parent = parent
        super(ViewEliminaGiocatore, self).__init__(
            parent=self.parent, title=title)
        self.controller = self.parent.controller
        giocatori = self.controller.tutti_giocatori()
        # Layout
        self.panel = PanelEliminaGiocatore(parent=self, giocatori=giocatori)
        self.SetSize((400, 150))
        self.Centre()
        self.Show()
        # Bind
        self.Bind(wx.EVT_BUTTON, self.elimina_giocatore, self.panel.btn_del)
        self.Bind(wx.EVT_BUTTON, self.on_quit, self.panel.btn_cancel)
        self.Bind(wx.EVT_COMBOBOX, self.on_giocatore, self.panel.giocatori)

    def elimina_giocatore(self, event):
        cognome, nome = self.panel.giocatori.GetStringSelection().split(' ')[:2]
        msg = 'Sicuro di voler eliminare %s?' % cognome
        if ChoiceMessage(self, msg).is_yes():
            msg = self.controller.cancella_giocatore(nome, cognome)
            InfoMessage(self, msg).get_choice()
            self.update_giocatori()

    def on_quit(self, event):
        giocatori = self.controller.tutti_giocatori()
        self.parent.panel.clb_giocatori.Clear()
        self.parent.panel.clb_giocatori.AppendItems(sorted(giocatori))
        self.parent.Enable()
        self.Destroy()

    def on_giocatore(self, event):
        self.panel.btn_del.Enable()

    def update_giocatori(self):
        self.panel.giocatori.Clear()
        self.panel.giocatori.AppendItems(
            sorted(self.controller.tutti_giocatori()))


class PanelEliminaGiocatore(wx.Panel):
    def __init__(self, parent, giocatori):
        super(PanelEliminaGiocatore, self).__init__(parent=parent)
        self.giocatori = wx.ComboBox(self, -1, "scegli...",
                            choices=sorted(giocatori), style=wx.CB_DROPDOWN)

        panel_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=5, vgap=5)
        panel_sizer.Add(wx.StaticText(self, label="giocatori esistenti:"),
                  0, wx.ALIGN_CENTER_VERTICAL, 10)
        panel_sizer.Add(self.giocatori, 0, wx.EXPAND | wx.ALL)
        panel_sizer.AddGrowableCol(1)
        # button sizer
        self.btn_del = wx.Button(self, wx.ID_DELETE)
        self.btn_del.Disable()
        self.btn_cancel = wx.Button(self, wx.ID_CANCEL)
        panel_sizer.Add(self.btn_del, 0, wx.EXPAND|wx.ALL, 2)
        panel_sizer.Add(self.btn_cancel, 0, wx.EXPAND|wx.ALL, 2)
        self.SetSizer(panel_sizer)


class InfoMessage(wx.MessageDialog):
    '''Simple message Dialog'''
    def __init__(self, parent, message):
        wx.MessageDialog.__init__(self, parent, message, 'core info', wx.OK |
                                  wx.ICON_EXCLAMATION)

    def get_choice(self):
        '''get the state of the user choice'''
        if self.ShowModal() == wx.ID_OK:
            self.Destroy()

class ChoiceMessage(wx.MessageDialog):
    '''Simple choice message Dialog'''
    def __init__(self, parent, message):
        wx.MessageDialog.__init__(self, parent, message, 'Core question',
                                  wx.YES_NO | wx.ICON_QUESTION)

    def is_yes(self):
        '''get True if YES is clicked'''
        if self.ShowModal() == wx.ID_YES:
            return True
        else:
            self.Destroy()

class AboutFrame(wx.Frame):
    """Frame for Abuot text"""
    def __init__(self, parent, title):
        super(AboutFrame, self).__init__(parent=parent, title=title)
        self.parent = parent
        self.text = """
        <html>
        <center><h1>Calcio a 7</h1> versione 2.1<br>
        by Bancaldo</td></center><br><br>
        <b>Calcio a 7</b> e' un semplice software per la creazione<br>
        di squadre il piu' possibile equilibrate<br>
        <br>
        <b>pacchetti utilizzati:</b><br>
        - wxPython</b> for Graphics<br>
        - Flask-SQLAlchemy</b> for database and Object Ralation Mapping<br>
        <br>
        <b>link utili:</b><br>
        web-site: www.bancaldo.wordpress.com<br>
        web-site: www.bancaldo.altervista.org<br>
        <br>
        <b>last revision:</b> Gen 22, 2015</p><br>
        <b>author:</b> bancaldo
        </html>
        """
        self.SetSize((400, 600))
        html = wxhtml.HtmlWindow(self)
        html.SetPage(self.text)
        self.btn_quit = wx.Button(self, -1, 'quit', (25, 150), (150, -1))
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(html, 1, wx.EXPAND|wx.ALL, 5)
        sizer.Add(self.btn_quit, 0, wx.ALIGN_CENTER|wx.ALL, 5)
        self.SetSizer(sizer)
        self.Centre()
        self.Show()

        # Bind
        self.Bind(wx.EVT_BUTTON, self.on_quit, self.btn_quit)

    # callback
    def on_quit(self, event):
        self.parent.Enable()
        self.Destroy()


class HelpFrame(wx.Frame):
    """Frame for Abuot text"""
    def __init__(self, parent, title):
        super(HelpFrame, self).__init__(parent=parent, title=title)
        self.parent = parent
        self.text = """
        <html>
        <center><b>Calcio a 7</b> help.</center><br><br>

        <b>1)</b> Al primo avvio viene creato il database 'giocatori.db'
        nella stessa directory.<br><br>

        <center><b>Gestione giocatori</b></center><br><br>

        <b>1)</b> inserire i giocatori nel database da <em>Nuovo Giocatore</em>
            del menu <em>Giocatore</em><br>
        ---- <b>1.a)</b> impostare i valori: nome, cognome, valore,
                         forma e ruolo.<br>
        ---------- <u>valore</u> e' l'indice di bravura del giocatore e va
                   da 0 a 100<br>
        ---------- <u>forma</u> e' lo stato di forma del giocatore, va
                   da 0 a 100.<br>
        ---- <b>1.b)</b> salvare il giocatore con l'apposito pulsante.<br><br>

        <b>2)</b> Modificare i valori di un giocatore da
              <em>Modifica Giocatore</em> del menu <em>Giocatore</em><br><br>

        <b>3)</b> Eliminare un giocatore da <em>Elimina Giocatore</em> del
             menu <em>Giocatore</em><br><br>

        <center><b>Composizione Squadre</b></center><br><br>

        <b>1)</b> selezionare i giocatori cliccando sui box a sx dei nomi<br>
        ---- <b>1.a)</b> Se sono dispari creare un giocatore JOLLY e
                         selezionarlo per avere la parita'.<br><br>

        <b>2)</b> Settare il massimo GAP ammesso tra le due squadre <br><br>

        <b>3)</b> Premere su componi per ottenere le squadre.<br>
        ---- <b>3.a)</b> Se non ci sono combinazioni, aumentare il GAP e
                         premere <b>Set new GAP</b>.<br>
        ---- <b>3.b)</b> Per ottenere invece squadre piu' equilibrate
                         diminuire il GAP</p>.<br><br>
        </html>
        """
        self.SetSize((700, 600))
        html = wxhtml.HtmlWindow(self)
        html.SetPage(self.text)
        self.btn_quit = wx.Button(self, -1, 'quit', (25, 150), (150, -1))
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(html, 1, wx.EXPAND|wx.ALL, 5)
        sizer.Add(self.btn_quit, 0, wx.ALIGN_CENTER|wx.ALL, 5)
        self.SetSizer(sizer)
        self.Centre()
        self.Show()

        # Bind
        self.Bind(wx.EVT_BUTTON, self.on_quit, self.btn_quit)

    # callback
    def on_quit(self, event):
        self.parent.Enable()
        self.Destroy()
Categorie:python, wxPython Tag:

Python: MVC con Observer per principianti

23 Ottobre 2014 Commenti chiusi

Dopo l’assaggio in questo articolo, passiamo a questa versione di pattern MVC un po’ più elaborata.
Comprende anche il concetto di observer.
Per rendere il tutto il più chiaro possibile, utilizzeremo un programma molto semplice, ovvero un incrementatore di numero.

Come già visto, il pattern MVC si fonda su tre elementi principali:

– Il model che, nelle “retrovie” si occupa dei dati dell’applicazione e delle operazioni che si effettuano su di essi.

– La View, che si occupa di editare e visualizzare i dati.

– Il Controller che si occupa di gestire il flusso di dati, dal Model alla View e viceversa. Fa da collante tra i primi due oggetti.

Come dicevamo, all’Interno del Model, sarà implementato un Observer Pattern, che verrà spiegato di seguito.

Cominciamo con il definire il Model

class ModelInterface(object):
    """Definisce una interfaccia per il Model che
       gestisce l'incremento di un numero"""
    def __init__(self):
        super(ModelInterface, self).__init__()
        self.num = 0
        self.observers = [] # Inizializzo una lista di observers

    def incrementa(self):
        """Metodo interfaccia da implementare nelle sottoclassi"""
        raise NotImplementedError

    def set_value(self, num):
        self.num = num
        self.notifica_observer() # Ogni volta che viene modificato il numero

    def get_value(self):
        return self.num

    def notifica_observer(self):
        """Notifica chiamata ad ogni modifica di numero"""
        for observer in self.observers:
            observer()
            print "Click pulsante: numero incrementato a %s" \
                % self.get_value()

    def registra_observer(self, callback):
        """registro la callback dell'observer"""
        print "Registro la callback <%s>" % callback.__name__
        self.observers.append(callback)


class ModelIncrementa(ModelInterface):
    def incrementa(self):
        num = self.get_value() + 1
        self.set_value(num)

La prima classe, è l’interfaccia che verrà estesa con la successiva sottoclasse.
In questo specifico caso, l’interfaccia è il metodo incrementa, esteso da ModelIncrementa.
La View, avrà a che fare con tale metodo…
Nel Model, come si nota, sono previsti anche i due metodi relativi all’observer.
Il primo è collegato all’evento click sul pulsante, come vedremo dopo.
Il secondo è responsabile della registrazione della callback-observer.

Come si nota, Model non ha nessun riferimento alla View o al Controller, è all’oscuro di tutto.

Ora vediamo la View:

import wx


class View(wx.Frame):
    def __init__(self, parent, controller, model, title):
        super(View, self).__init__(parent=parent, title=title)

        self.panel = Panel(self, controller, model)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.panel, 1, wx.EXPAND)
        self.SetSizer(sizer)
        self.SetInitialSize((150, 150))

    def button_activated(self, state=True):
        self.panel.button.Enable(state)
        self.panel.button.SetBackgroundColour('Yellow')


class Panel(wx.Panel):
    def __init__(self, parent, controller, model):
        super(Panel, self).__init__(parent)
        #Attributi
        self.model = model
        self.controller = controller
        start_number = self.model.get_value()
        self.text = wx.TextCtrl(self, value='%s' % start_number)
        self.button = wx.Button(self, label='Incrementa')
        self.button.SetBackgroundColour('White')
        # Registro l'Observer
        self.model.registra_observer(self.on_model_update)
        # Handler evento click
        self.Bind(wx.EVT_BUTTON, self.on_click)
        # Creo il Layout
        self.__crea_layout()

    def __crea_layout(self):
        v_sizer = wx.BoxSizer(wx.VERTICAL)
        v_sizer.Add(self.text, 0, wx.ALIGN_CENTER|wx.ALL, 5)
        v_sizer.Add(self.button, 0, wx.ALIGN_CENTER)
        self.SetSizer(v_sizer)

    def on_model_update(self):
        """Metodo Observer"""
        numero = self.model.get_value()
        self.text.SetValue(str(numero))
        self.button.Enable(True)
        self.button.SetBackgroundColour('White')

    def on_click(self, event):
        self.controller.incrementa()

Qui come si nota, in fase di inizializzazione si crea un Panel, nel quale si instanziano il model esteso ed il controller esteso (tramite i metodi interfaccia).

        self.model = model
        self.controller = controller

Poi si inizializza il numero di partenza per visualizzarlo nel widget apposito:

start_number = self.model.get_value()

quello che sarà “start_number”, lo decido nel Model. La View non ha nessun potere su questo.

In seguito registriamo l’observer:

        self.model.registra_observer(self.on_model_update)
	...
    def on_model_update(self):
        """Metodo Observer"""
        numero = self.model.get_value()
        self.text.SetValue(str(numero))
        self.button.Enable(True)
        self.button.SetBackgroundColour('White')

In pratica: in fase di inizializzazione della View, registro l’observer, che nel model viene inserito nella lista degli observers:

# model.py
    def registra_observer(self, callback):
        """registro la callback dell'observer"""
        print "Registro la callback <%s>" % callback.__name__
        self.observers.append(callback)

Vediamo il Controller per concludere il ragionamento:

import wx
from view import View
from model import ModelIncrementa
import time


class ControllerInterface(object):
    """Definisce una interfaccia per il Controller che
       gestisce l'incremento di un numero"""
    def __init__(self, model):
        super(ControllerInterface, self).__init__()
        self.model = model
        self.view = View(parent=None, controller=self, model=self.model,
                         title='Incrementa')

        self.view.Show()

    def incrementa(self):
        raise NotImplementedError


class ControllerIncrementa(ControllerInterface):
    def incrementa(self):
        self.view.button_activated(False)
        self.model.incrementa()
        time.sleep(1)


class Core(wx.App):
    def OnInit(self):
        self.model = ModelIncrementa()
        self.controller = ControllerIncrementa(self.model)
        return True



if __name__ == '__main__':
    app = Core(False)
    app.MainLoop()

Torniamo alla View…

Creiamo la callback legata all’evento click del bottone:

        self.Bind(wx.EVT_BUTTON, self.on_click)

    def on_click(self, event):
        self.controller.incrementa()

Qui la View chiede al Controller cosa fare, anche qui c’è una netta separazione dei compiti.
Quando il bottone viene premuto, viene chiamato il metodo incrementa dell’interfaccia-controller per sapere cosa fare.

#controller.py
    def incrementa(self):
        self.view.button_activated(False)
        self.model.incrementa()
        time.sleep(1)

questo metodo agisce sulla View disattivando il bottone, poi
chiama il metodo incrementa dell’interfaccia model estesa…

class ModelIncrementa(ModelInterface):
    def incrementa(self):
        num = self.get_value() + 1
        self.set_value(num)

che preleva il numero dal ModelInterface e lo incrementa di 1…

        num = self.get_value() + 1

…e lo sostituisce al precedente…

        self.set_value(num)

…chiamando il metodo set_value di ModelInterface, che succede?

#model.py ModelInterface
    def set_value(self, num):
        self.num = num
        self.notifica_observer()

il numero viene sostituito dal valore incrementato e l’observer se ne accorge…

        self.notifica_observer()

ed esegue la callback registrata precedentemente;

    def notifica_observer(self):
        """Notifica chiamata ad ogni modifica di numero"""
        for observer in self.observers:
            observer() # on_model_update() di Panel

prelevando il numero…

numero = self.model.get_value()

visualizzandolo sulla textcontrol…

        self.text.SetValue(str(numero))

riattivando e ricolorando il bottono come all’inizio.

        self.button.Enable(True)
        self.button.SetBackgroundColour('White')

Ribadiamo i punti fondamentali del pattern MVC:

Il Model NON sa nulla del Controller e della View, ne è completamente estraneo.

La View, dal canto suo, non sa nulla di come il Model ed il Controller interagiscano tra loro e non sa come essi siano strutturati, poichè interagisce con loro, solo tramite le interfacce che li estendono, nel nostro caso i metodo incrementa() di ModelInterface esteso da ModelIncrementa e ControllerInterface esteso da ControllerIncrementa.
Quando premiamo il bottone, la View chiede al Controller cosa fare e per sapere quando un dato del Model viene modificato, registra una callback-observer, sul model stesso, per sapere quando la callback legata al pulsante, viene chiamata.

Il Controller come già detto, funge da collante tra i due oggetti precedenti.
Implementa il comportamento della View, in funzione dei cambiamenti di stato del Model.

Questa netta separazione dei compiti torna utile nel caso di riutilizzo del codice.
Potremmo ad esempio, invece di incrementare, decrementare il numero, senza dover mettere mano alla View, che non implementa nessuna operazione sui dati che visualizza.
Inoltre, potremmo effettuare semplici Unittest sul Model senza impazzire scorporando parti di codice innestati nella View o nel Controller.

Categorie:python, wxPython Tag:

python: calendario fantalega

1 Settembre 2014 Commenti chiusi

Ecco una applicazione per creare un calendario con n squadre Pari.
Non tiene conto della giornata di riposo in caso di squadre dispari!
Testandola, dovrebbe tenere conto anche del “casa”, “fuoricasa”.

codice view:

# -*- coding: utf-8 -*-#
'''VIEW Module for FantaCalendario:
Graphic library: wx
Bindings are defined under CONTROLLER Module.
''' 
import wx, os

class FrameCal(wx.Frame):
    '''Main frame for FantaPython application'''
    def __init__(self, *args, **kwargs):
        self.nsq = kwargs.pop('nsq')
        wx.Frame.__init__(self, *args, **kwargs)
        self.panel = wx.Panel(self)
        self.panel.SetBackgroundColour('Pink')
        sq_cont = 1
        xpos, ypos = 100, 20
        while sq_cont &lt;= self.nsq:
            wx.StaticText(self.panel, wx.ID_NEW, 'squadra%s' % sq_cont,
                          pos = ((xpos - 85), ypos), size = (70, 25))
            wx.TextCtrl(self.panel, wx.ID_NEW,
                        pos = (xpos, ypos), size = (140, 25))
            ypos += 25
            sq_cont += 1
        
        self.btn_create = wx.Button(self.panel, wx.ID_NEW, 'Crea Calendario',
                                    size = (240, 25), pos = (0, (ypos)))
        self.SetSizeWH(250, (ypos + 65))
        self.Centre()
        self.Show()

class InfoMessage(wx.MessageDialog):
    '''Simple message Dialog'''
    def __init__(self, parent, message):
        wx.MessageDialog.__init__(self, parent, message, 'core info', wx.OK |
                                  wx.ICON_EXCLAMATION)
    def get_choice(self):
        '''get the state of the user choice'''
        if self.ShowModal() == wx.ID_OK:
            self.Destroy()

class ChoiceMessage(wx.MessageDialog):
    '''Simple choice message Dialog'''
    def __init__(self, parent, message):
        wx.MessageDialog.__init__(self, parent, message, 'Core question',
                                  wx.YES_NO | wx.ICON_QUESTION)
    def get_yes(self):
        '''get True if YES is clicked'''
        if self.ShowModal() == wx.ID_YES:
            return True
        else:
            self.Destroy()

class EntryDialog(wx.TextEntryDialog):            
    '''Simple Text Entry Dialog'''
    def __init__(self, parent, msg, value):
        wx.TextEntryDialog.__init__(self, parent, msg, 'Core request',
                                    defaultValue = value, style = wx.OK)
    def get_choice(self):
        '''get the state of the user choice'''
        if self.ShowModal() == wx.ID_OK:
            response = self.GetValue()
            self.Destroy()
            return response

class FileSaver(wx.FileDialog):
    '''Class for file Saver'''
    def __init__(self):
        self.f_save = None
        wildcard = &quot;File Calendario (*.txt)|*.txt|&quot; \
            &quot;Tutti i files (*.*)|*.*&quot;
        wx.FileDialog.__init__(self, None, &quot;salva il file&quot;, os.getcwd(),
                               &quot;&quot;, wildcard, wx.SAVE)
        if self.ShowModal() == wx.ID_OK:  
            print &quot;...salvo il file in: %s&quot; % (self.GetPath())
            self.output = self.GetPath()
            self.f_save = open(self.output, 'w')
        else:
            print &quot;operazione salvataggio annullata&quot;
            self.f_save = None
            self.Destroy()
        self.Destroy()

def main():
    '''app starter'''
    app = wx.PySimpleApp()
    n_sq = 8 # number of teams
    FrameCal(None, wx.ID_NEW, &quot;Calendario 1.1&quot;, nsq = n_sq)
    app.MainLoop()

if __name__ == '__main__':
    main()

codice controller:

'''FantaCalendario v 2.0
   Controller Module for FantaCalendario Application:
   All the binding method to frame widget are defined here'''

import wx

from View import (FrameCal, EntryDialog, InfoMessage, FileSaver)
from copy import copy


class Team(object):
    '''A team class'''
    def __init__(self, name, status=True):
        self.name = name
        self.casa = status
        
    def set_casa(self):
        &quot;&quot;&quot;set the status 'casa' to True&quot;&quot;&quot;
        self.casa = True

    def set_fuori(self):
        &quot;&quot;&quot;set the status 'fuori' to True&quot;&quot;&quot;
        self.casa = False


class Controller(object):
    '''Controller class for MVC-like pattern'''
    def __init__(self):
        n_sq = int(EntryDialog(None, &quot;Quante Squadre partecipano?&quot;,
                                     '8').get_choice())
        self.gui = FrameCal(None, wx.ID_NEW, &quot;Calendario 2.0&quot;, nsq=n_sq)
        self.gui.Bind(wx.EVT_BUTTON, self.on_create)

    def on_create(self, evt):
        '''create every &quot;team_x vs team_y&quot; combinations possible'''
        panel = evt.GetEventObject().GetParent()
        teams = []
        err = 0
        for widget in panel.GetChildren():
            if isinstance(widget, wx.TextCtrl):
                if widget.GetValue() != '':
                    teams.append(Team(widget.GetValue()))
                else:
                    err += 1
        if err &gt; 0:
            msg = &quot;mancano uno o piu' nomi squadra&quot;
            InfoMessage(panel.GetParent(), msg).get_choice()
        else:
            print teams
            crea_calendario(teams)
            self.gui.Close()


def crea_calendario(lista):
    '''crea gli accoppiamenti da una lista'''
    matrix = []
    cont = 0
    while cont &lt; len(lista):
        matrix.append([None] * len(lista))
        cont += 1
    matrix[0] = lista  # intestazione

    # Riga Intestazione invertita meno l'ultima squadra
    row2 = copy(lista)
    row2.pop()
    row2.reverse()
    matrix[1][0:(len(lista) - 1)] = row2[0:(len(lista) - 1)]

    # Composizione tabella prima FASE
    i = 1
    while i &lt; len(lista):
        k = 1
        for item in matrix[i]:
            try:
                matrix[i + 1][k] = item
                matrix[i + 1][0] = matrix[i + 1][(len(lista) - 1)]
                matrix[i + 1][(len(lista) - 1)] = None
                k += 1
            except IndexError:
                break
        i += 1

    # Composizione tabella seconda FASE
    row_m = 1
    while row_m &lt; len(lista):
        for item_a in matrix[0]:
            for item_b in matrix[row_m]:
                if matrix[0].index(item_a) == matrix[row_m].index(item_b):
                    if item_a == item_b:
                        matrix[row_m][matrix[row_m].index(item_b)] = lista[-1]
                        matrix[row_m][(len(lista) - 1)] = item_b
        row_m += 1

    # Stampa giornata
    cont = 1
    fbs = FileSaver()
    outputfile = fbs.f_save
    while cont &lt; len(lista):
        outputfile.write(&quot;\nGiornata n. &quot; + str(cont) + &quot;\n&quot;)
        andata = []
        for sq_a in matrix[0]:
            for sq_b in matrix[cont]:
                if matrix[0].index(sq_a) == matrix[cont].index(sq_b):
                    if sq_b not in andata or sq_a not in andata:
                        if sq_a.casa == True:
                            andata.append(sq_a)
                            andata.append(sq_b)
                            outputfile.write(sq_a.name + &quot;-&quot; + sq_b.name + &quot;\n&quot;)
                            sq_a.set_fuori()
                            sq_b.set_casa()
                        else:
                            andata.append(sq_b)
                            andata.append(sq_a)
                            outputfile.write(sq_b.name + &quot;-&quot; + sq_a.name + &quot;\n&quot;)
                            sq_a.set_casa()
                            sq_b.set_fuori()
        cont += 1
    outputfile.close()


def main():
    '''starter'''
    app = wx.PySimpleApp()
    Controller()
    app.MainLoop()

if __name__ == '__main__':
    main()

il funzionamento è semplicissimo:
si avvia, si sceglie il numero di squadre:
cal_1
si mettono i nomi delle squadre:
cal_2
si preme il pulsante crea e si sceglie un nome per la destinazione in txt, che poi potrà essere importata ad esempio in excel.

Giornata n. 1
A-C
B-D

Giornata n. 2
D-A
C-B

Giornata n. 3
A-B
D-C

link binario (exe)

Categorie:FantaLega, python, wxPython Tag: