In questo ultima parte illustrerò come sono stati utilizzati i vari widgets per realizzare l’editor e l’emulatore. Come vedrete, una volta capito il funzionamento della libreria, sarà molto semplice implemtentare soluzioni per le vostre necessità. Spero che gli esempi siano abbastanza chiari, così da potervi permettere di sperimentare autonomamente la creazione di programmi multipiattaforma con tkinter.
Per prima cosa vediamo il codice di Editor:
def __init__(self,master, calcolatore): """ Inizializza i frame della finestra dell'Editor """ self.master = master self.CD = calcolatore ## Codice Assembly self.codice = LabelFrame(self.master, text = 'Codice Assembly', relief = RIDGE, borderwidth = 5, labelanchor = 'n', pady = 5) self.codice.rowconfigure(0, weight=1) self.codice.columnconfigure(0, weight=1) self.codice.grid(row = 1, column = 0,rowspan = 3, columnspan = 5, sticky = W+E+N+S) self.menubar = Menu(self.master) self.create_widgets(self.menubar) def create_widgets(self,menubar): """ Crea il layout del programma, finestra dell'Editor """ ### Menu self.filemenu = Menu(menubar,tearoff = 0) self.filemenu.add_command(label = 'Apri', command = self.aprifile) self.filemenu.add_command(label = 'Salva', command = self.salvafile) self.filemenu.add_command(label = 'Cancella', command = self.cancella) self.filemenu.add_separator() self.filemenu.add_command(label = 'Esci', command = self.exit) menubar.add_cascade(label = 'Opzioni', menu = self.filemenu) self.master.config(menu = self.menubar) self.helpmenu = Menu(menubar,tearoff = 0) self.helpmenu.add_command(label = 'Informazioni', command = self.infor) self.helpmenu.add_command(label = 'Legenda', command = self.leg) self.helpmenu.add_command(label = 'Guida', command = self.guida) menubar.add_cascade(label = 'Aiuto', menu = self.helpmenu) ## Codice Assembly self.Inserisci = Text(self.codice, width = 50,height = 30,wrap = WORD) self.Inserisciscrollbar = Scrollbar(self.codice) self.Inserisciscrollbar.config(command = self.Inserisci.yview) self.Inserisci.config(yscrollcommand = self.Inserisciscrollbar.set) self.Inserisciscrollbar.grid(row = 0, column = 1, sticky = N+S) self.Inserisci.grid(row = 0, column = 0, sticky = W)
Come potete vedere dal costruttore, oltre alla finestra principale viene passato anche calcolatore (oggetto creato con la classe pdp8 dei precendeit articoli). Questa finestra non è molto complessa, infatti è composta solo dal LabelFrame codice e dal menu in alto “menubar”. Non dimenticate che ogni frame ha la sua griglia (grid).
In create_widgets() possiamo vedere più dettagliatamente come menubar venga arricchito delle varie voci previste dal programma e di come si inserisce un oggetto Text al frame codice.
C’è da notare che all’oggetto Text abbiamo subito associato la barra di scorrimento laterale e che ogni opzione del menù è stata associata ad un comando specifico, ovvero una funzione interna alla classe che ha determinati compiti (non posto il codice delle varie funzioni per non rendere troppo dispersivo l’articolo, si possono comunque cosultare i sorgenti online, visto che ho aggiornato il repository con mercurial).
La finestra dell’emulatore è un pò più complessa, come potete notare dal costruttore:
def __init__(self,master,codice,calcolatore,emulatore): """ Inizializza i frame per l'interfaccia dell'emulatore """ self.CD = calcolatore self.codice = codice self.delay = 100 self.master = Frame(master) self.root = emulatore ## Memoria Ram self.ram = LabelFrame(self.master, text = 'Memoria RAM', relief = RIDGE, borderwidth = 5, labelanchor = 'n', pady = 5) self.ram.rowconfigure(0, weight=1) self.ram.columnconfigure(0, weight=1) self.ram.grid(row = 0, column = 0,rowspan = 3, columnspan = 5, sticky = W+E+N+S) ## Controlli self.controlli = Frame(self.master, padx = 10, pady = 10) self.controlli.grid(row = 0, column = 5,rowspan = 1) ## Status CD self.registri = LabelFrame(self.master, text = 'REGISTRI', relief = RIDGE, borderwidth = 5, labelanchor = 'n',padx = 25,pady = 10) self.registri.grid(row = 0, column = 6,rowspan = 1, sticky = W+E+N+S) self.unita = LabelFrame(self.master, text = 'UC', relief = RIDGE, borderwidth = 5, labelanchor = 'n', padx = 10, pady = 10) self.unita.grid(row = 2, column = 6, rowspan = 1, sticky = N) ## Var self.variabili = Frame(self.master) self.variabili.grid(row = 2, column = 5) self.nstep = LabelFrame(self.variabili, text = 'Num. Step', relief = RIDGE, borderwidth = 5, labelanchor = 'n') self.nstep.grid(row = 0, column = 5,sticky = W+E) self.delays = LabelFrame(self.variabili, text = 'Delay', relief = RIDGE, borderwidth = 5, labelanchor = 'n') self.delays.grid(row = 1, column = 5,sticky = W+E) self.tempo = LabelFrame(self.variabili, text = 'Tempo', relief = RIDGE, borderwidth = 5, labelanchor = 'n') self.tempo.grid(row = 1, column = 6,sticky = W+E) ### Unita' di controllo self.unitas = LabelFrame(self.unita, text = 'S', labelanchor = 's', padx = 10) self.unitas.grid(row = 0, column = 0, sticky = N) self.unitaf = LabelFrame(self.unita, text = 'F', labelanchor = 's', padx = 10) self.unitaf.grid(row = 0, column = 1, sticky = N) self.unitar = LabelFrame(self.unita, text = 'R', labelanchor = 's', padx = 10) self.unitar.grid(row = 0, column = 2, sticky = N) self.unitaint = LabelFrame(self.unita, text = 'Int.', labelanchor = 's', padx = 10) self.unitaint.grid(row = 0, column = 3, sticky = N) ### Registri self.programc = LabelFrame(self.registri, text = 'PC',relief = FLAT, labelanchor = 'e', padx = 5) self.programc.grid(row = 0, column = 0, sticky = W+E) self.mar = LabelFrame(self.registri, text = 'MAR',relief = FLAT, labelanchor = 'e', padx = 5) self.mar.grid(row = 1, column = 0, sticky = W+E) self.mbr = LabelFrame(self.registri, text = 'MBR',relief = FLAT, labelanchor = 'e', padx = 5) self.mbr.grid(row = 2, column = 0, sticky = W+E) self.lac = LabelFrame(self.registri, text = 'AC',relief = FLAT, labelanchor = 'e', padx = 5) self.lac.grid(row = 3, column = 0, sticky = W+E) self.vare = LabelFrame(self.registri, text = 'E',relief = FLAT, labelanchor = 'e', padx = 5) self.vare.grid(row = 4, column = 0, sticky = W+E) self.lopr = LabelFrame(self.registri, text = 'OPR',relief = FLAT, labelanchor = 'e', padx = 5) self.lopr.grid(row = 5, column = 0, sticky = W+E) self.vari = LabelFrame(self.registri, text = 'I',relief = FLAT, labelanchor = 'e', padx = 5) self.vari.grid(row = 6, column = 0, sticky = W+E) ## Microistruzioni self.micro = LabelFrame(self.master, text = 'Microistruzioni eseguite', relief = RIDGE, borderwidth = 5, labelanchor = 'n', pady = 5) self.micro.rowconfigure(0, weight=1) self.micro.columnconfigure(0, weight=1) self.micro.grid(row = 3, column = 4,rowspan = 5, columnspan = 5, sticky = W+E+N+S) ## Inout self.inout = LabelFrame(self.master, text = 'Input & Output', relief = RIDGE, borderwidth = 5, labelanchor = 'n', pady = 5) self.inout.rowconfigure(0, weight=1) self.inout.columnconfigure(0, weight=1) self.inout.grid(row = 3, column = 0, columnspan = 4, sticky = W+E+N+S) self.create_widgets() def create_widgets(self): """ Crea il layout del programma, finestra dell'emulatore """ ## Memoria RAM self.Visualizza = Text(self.ram, width = 80) self.Visualizzascrollbar = Scrollbar(self.ram) self.Visualizzascrollbar.config(command = self.Visualizza.yview) self.Visualizza.config(yscrollcommand = self.Visualizzascrollbar.set) self.Visualizzascrollbar.grid(row = 0, column = 1, sticky = N+S) self.Visualizza.grid(row = 0, column = 0, sticky = W) ## INOUT self.Visualizzainout = Text(self.inout, width = 62, height = 7, fg = 'green', bg = 'black') self.Visualizzascrollbar_inout = Scrollbar(self.inout) self.Visualizzascrollbar_inout.config(command = self.Visualizzainout.yview) self.Visualizzainout.config(yscrollcommand = self.Visualizzascrollbar_inout.set) self.Visualizzascrollbar_inout.grid(row = 0, column = 1, sticky = N+S) self.Visualizzainout.grid(row = 0, column = 0, sticky = W) ## Mircroistruzioni self.Visualizzamicro = Text(self.micro, width = 55, height = 7) self.Visualizzascrollbar_m = Scrollbar(self.micro) self.Visualizzascrollbar_m.config(command = self.Visualizzamicro.yview) self.Visualizzamicro.config(yscrollcommand = self.Visualizzascrollbar_m.set) self.Visualizzascrollbar_m.grid(row = 0, column = 1, sticky = N+S) self.Visualizzamicro.grid(row = 0, column = 0, sticky = W) ### Pulsanti self.butload = Button(self.controlli, text = 'LOAD', anchor = CENTER, width = 15, command = self.loading, bg = 'SkyBlue') self.butload.grid(row = 0, column = 0) self.butstep = Button(self.controlli, text = 'Step', anchor = CENTER, width = 15, command = self.step, bg = 'linen') self.butstep.grid(row = 1, column = 0) self.butminstep = Button(self.controlli, text = 'miniStep', anchor = CENTER, width = 15, command = self.mini_step, bg = 'linen') self.butminstep.grid(row = 2, column = 0) self.butstep = Button(self.controlli, text = 'microStep', anchor = CENTER, width = 15, command = self.micro_step, bg = 'linen') self.butstep.grid(row = 3, column = 0) self.butsetstep = Button(self.controlli, text = 'Set n Step', anchor = CENTER, width = 15, command = self.setnstep, bg = 'linen') self.butsetstep.grid(row = 4, column = 0) self.butsetdelay = Button(self.controlli, text = 'Set Delay', anchor = CENTER, width = 15, command = self.setdelay, bg = 'linen') self.butsetdelay.grid(row = 5, column = 0) self.butstart = Button(self.controlli, text = 'START', anchor = CENTER, width = 15, command = self.start, bg = 'DarkOliveGreen3') self.butstart.grid(row = 6, column = 0) self.butreset = Button(self.controlli, text = 'RESET', anchor = CENTER, width = 15, command = self.resetCD, bg = 'Orange3') self.butreset.grid(row = 7, column = 0) self.butstop = Button(self.controlli, text = 'STOP', anchor = CENTER, width = 15, command = self.stop, bg = 'IndianRed') self.butstop.grid(row = 8, column = 0) self.butbreak = Button(self.controlli, text = 'BREAK', anchor = CENTER, width = 15, command = self.breakpoint, bg = 'Magenta2') self.butbreak.grid(row = 9, column = 0) self.butcontinue = Button(self.controlli, text = 'CONTINUA', anchor = CENTER, width = 15, command = self.continua, bg = 'Magenta2') self.butcontinue.grid(row = 10, column = 0) self.butesegui = Button(self.controlli, text = 'ESEGUI', anchor = CENTER, width = 15, command = self.esegui, bg = 'Yellow') self.butesegui.grid(row = 11, column = 0) ### Labels self.labelprogramc = Label(self.programc, text = '00000000000', relief = SUNKEN, bg = 'red') self.labelprogramc.grid() self.labelmar = Label(self.mar, text = '00000000000', relief = SUNKEN, bg = 'yellow') self.labelmar.grid() self.labelmbr = Label(self.mbr, text = '000000000000000', relief = SUNKEN) self.labelmbr.grid() self.labelac = Label(self.lac, text = '000000000000000', relief = SUNKEN) self.labelac.grid() self.labelvari = Label(self.vari, text = '0', relief = SUNKEN) self.labelvari.grid() self.labelvare = Label(self.vare, text = '0', relief = SUNKEN) self.labelvare.grid() self.labelopr = Label(self.lopr, text = '000', relief = SUNKEN) self.labelopr.grid() self.labelucs = Label(self.unitas, text = '0') self.labelucs.grid() self.labelucf = Label(self.unitaf, text = '0') self.labelucf.grid() self.labelucr = Label(self.unitar, text = '0') self.labelucr.grid() self.labelucint = Label(self.unitaint, text = '0') self.labelucint.grid() self.labelnstep = Label(self.nstep, text = '1') self.labelnstep.grid() self.labeldelay = Label(self.delays, text = str(self.delay)) self.labeldelay.grid() self.labeltempo = Label(self.tempo, text = str(self.CD.tempo)) self.labeltempo.grid()
Apparentemente sembra difficile da interpretare, ma questo schemino riassuntivo dei frame principali dovrebbe rendere meglio l’idea della sua implementazione.
Una volta localizzati i frame principali è quasi banale capire l’inserimento dei vari widgets. Molti sotto-frame sono stati inseriti solo per raggiungere un buon compromesso visivo, ma sostanzialmente se ne poteva anche fare a meno.
Da notare che ci siamo portati dietro l’oggetto codice della precedente finestra, così da poter estrapolare il testo al suo interno e darlo in pasto all’Assembler del pdp8. L’oggetto pdp8 è lo stesso creato nel main, quindi ne esiste uno per tutta la sessione del programma ed è raggiungibile sia da Editor (la finestra principale) che da Emulatore (finestra toplevel, secondaria ma di uguale importanza).
Come prima, non metterò il codice di tutte le funzioni presenti perché sono consultabili online; piuttosto vorrei parlarvi della risoluzione di un problema affrontato con la scrittura dell’interfaccia.
Se avete già provato il programma avrete notato che facendo partire l’emulazione è possibile premere altri pulsati ed interagire con l’interfaccia (come in qualsiasi altro programma) mentre è in esecuzione il codice. Questo risultato è stato raggiunto grazie ad un metodo basilare dei widgets per “alarm handlers and other non-event callbacks”, ovverro per gestire eventi che non sono semplicemente associati ad un oggetto.
In questo modo ho potuto ricostruire l’esecuzione temporale del processore secondo un certo delay (che è possobile settare sempre tramite interfaccia grafica) che non tocca minimamente l’implementazione interna del pdp8.
Nei sorgenti troverete per esempio “self.butesegui.after(self.delay, self.esegui)” in Emulatore nel metodo esegui: premuto il tasto ESEGUI (il quale è collegato al medesimo metodo), dopo un certo delay il widget richiamerà la funzione esegui della classe Emulatore, così da permettere la continua esecuzione del codice.
Per il resto, gli unici widgets utilizzati sono Text e Button, mentre i LabelFrame vengono utilizzati per organizzare visivamente l’emulatore, visto che è possibile scrivere un testo di contorno al frame creato. Per evidenziare il testo si è utilizzato un particolare vincolo di Text, ovvero i “tag” che possono essere personalizzati dall’utente.
Conclusioni
Spero che i codici sorgeti disponibili e queste mini-guide esplicative del programma siano utili per la realizzazione di piccoli progetti. Tkinter è molto semplice da utilizzare e per questo non ho speso molto tempo nell’analisi riga per riga del codice visto poiché, a parte l’organizzazione dei frame, l’utilizzo dei widgets è banale e basta consultare bene i manuali online per capire il funzionamento dei vari metodi. C’è da tenere poi conto che 3/4 del tempo è stato speso per il testing, potete quindi immaginare quanto semplice sia il codice.
Con questo concludo la serie di articoli sull’emulatore del pdp8 in python, curioso di ascoltare le vostre critiche, opinioni, suggerimenti e dubbi. In futuro mi piacerebbe convertire l’applicazione in wxPython, visto che quest’ultima ha suscitato di recente il mio interesse.