|
![]() |
|
AmigaDev Parte decima: Una simpatica GUI Questo mese torno, su richiesta, a parlare di GUI RxMUI. Come al solito, piuttosto che dilungarmi in spiegazioni teoriche, preferisco illustrare le varie tecniche trattando un esempio pratico. Lo spunto me lo offrono i bellissimi Warp datatype, che hanno l'unico difetto di avere un'antipatica GUI Reaction. E' proprio nel desiderio di riscrivere la GUI di configurazione del WarpJPEG datatype che nasce questo articolo. Entriamo subito nel vivo. Il primo passo da compiere è analizzare il nostro obiettivo: dobbiamo creare una piccola applicazione che gestisca la configurazione del WarpJPEG datatype. Questo salva la sua configurazione come un file di testo in
parm=valueove parm è un parametro di configurazione e value il suo valore. I parametri usati sono indicati nella seguente tabella:
La configurazione che dobbiamo gestire è costituita quindi da un insieme di 11 valori che conserveremo nelle variabili globali:
Si leggerà la configurazione presente in ENV: in global.TestPrefs, un momento prima di salvare global.Prefs in ENV: nella funzione di "Test". A questo punto appare ovvio che avremmo bisogno di un certo numero di funzioni che gestiscono questi insiemi:
do while conf~=""
parse upper var conf key "=" value "A"x conf
select
…
end
end
basato sulla lettura dell'intero file di configurazione e quindi del suo
parsing linea per linea.
E la funzione CopyConfig():
CopyConfig: procedure expose global.
parse arg from,to
interpret to".DITHER_OVERRIDE = "from".DITHER_OVERRIDE"
interpret to".DITHER_QUALITY = "from".DITHER_QUALITY"
interpret to".DITHER_DEPTH = "from".DITHER_DEPTH"
interpret to".PENS_OVERRIDE = "from".PENS_OVERRIDE"
interpret to".PENS_QUALITY = "from".PENS_QUALITY"
interpret to".OUTPUT_MODE = "from".OUTPUT_MODE"
interpret to".DCT_METHOD = "from".DCT_METHOD"
interpret to".FANCY_UPSAMPLING = "from".FANCY_UPSAMPLING"
interpret to".DITHER = "from".DITHER"
interpret to".QUANTIZATION = "from".QUANTIZATION"
interpret to".NUM_COLOURS = "from".NUM_COLOURS"
return
basata sull'uso di interpret, in un tipico trucchetto ARexx, che permette
di rendere semplice un'operazione altrimenti molto complicata.
Un'altra parte importante del programma è ovviamente la funzione che crea la GUI stessa. Io ho scelto di dividere la GUI in:
Host: |String host |
Proxy: |String proxy |
Resume: [Checkmark resume]
Definirlo è semplicissimo:
g.columns=2
g.0=Label("_Host")
g.1=String("Host","h")
g.2=Label("_Proxy")
g.3=String("Proxy","p")
g.4=Label("_Resume")
g.5=Checkmark("Resume",,"r")
Purtroppo il codice sopra non produce il risultato desiderato; infatti
per le regole che controllano le dimensioni di un gruppo, g avrà larghezza
fissa, perché la sua larghezza massima sarà "la più piccola delle larghezze
massime dei suoi figli" e quindi la larghezza del Checkmark. Per ottenere
il risultato voluto dovremo fare:
g.columns=2
g.0=Label("_Host")
g.1=String("Host","h")
g.2=Label("_Proxy")
g.3=String("Proxy","p")
g.4=Label("_Resume")
g.5="rg"
rg.class="group"
rg.horiz=1
rg.0=Checkmark("Resume",,"r")
rg.1=hspace()
Il questo caso il sesto figlio di g è il gruppo orizzontale rg che ha
come larghezza massima, la somma delle larghezze massime dei suoi figli.
Uno dei suoi due figli è uno spazio orizzontale di larghezza 0 (ovvero
con larghezza minima 0 e massima infinito) e quindi la sua larghezza massima
sarà infinita. Per riassumere: il processo di layout, cioè la disposizione
degli oggetti, è controllato dalla classe Group e può essere tenuto sotto
controllo con un attento uso degli oggetto spazio.
Definite le funzioni di base per trattare la configurazione del WarpJEPG datatype e creata la GUI siamo ben oltre la metà dell'opera. Il codice di gestione della GUI è il solito banale:
HandleApp: procedure expose global.
ctrl_c=2**12
do forever
call NewHandle("app","h",ctrl_c)
if and(h.signals,ctrl_c)~=0 then call SafeExit()
if h.EventFlag then
select
when h.event="QUIT" then call SafeExit()
otherwise interpret h.event
end
end
In HandleApp() controlliamo soltanto gli eventi:
call Notify(notifier,TriggerAttr,TriggerValue,target,method,parm1,...)e istruisce RxMUI ad invocare method su target quando l'attributo TriggerAttr di notifier assume il valore TriggerValue. Si noti che:
call Notify("Save","pressed",0,...)
Una notifica basata sulla selezione del menù mabout si definisce come:
call notify("mabout","MenuTrigger","everytime",...)
Una notifica basata sulla pressione del gadget di chiusura finestra della
finestra win si definisce come:
call notify("win","CloseRequest",1,...)
Una notifica basata sul cambiamento delle voce corrente del listview hlister
si definisce come:
call notify("hlister","active","everytime",...)
Una notifica basata sul cambiamento delle voce corrente dell'oggetto Cycle
DITHER_OVERRIDE si definisce come:
call notify("DITHER_OVERRIDE","active","everytime",...)
La pressione del gadget di chiusura finestra significa "Esci", ovvero
ritorna all'oggetto Application lo speciale ID Quit:
call notify("win","CloseRequest",1,"app","ReturnID","Quit")
La selezione del menù "About..." significa "apri la finestra About", ricordando
che Application class possiede un metodo per mostrare una finestra di
About automaticamente creata, si farà semplicemente:
call notify("mabout","MenuTrigger","EveryTime","app","about","win")
Cioè: "Quando l'attributo MenuTrigger dell'oggetto mabout cambia valore
(il che vuol dire che l'utente lo ha selezionato), invoca su app il metodo
about con argomento win (ovvero centra la finestra di About sulla finestra
win).
La selezione del menù "AboutRxMUI..." significa "apri la finestra AboutRxMUI":
call notify("maboutrxmui","MenuTrigger","EveryTime","app","AboutRxMUI","win")
La selezione del menù "AboutMUI..." significa "apri la finestra AboutMUI":
call notify("maboutmui","menutrigger","everytime","app","AboutMUI","win")
La selezione del menù "Hide" significa "iconificati", per iconificare
un'applicazione, basta settare a 1 l'attributo Iconified dell'oggetto
app, ovvero:
call notify("mhide","menutrigger","everytime","app","set","iconified",1)
La selezione del menù "Quit" significa "FINE":
call notify("mquit","menutrigger","everytime","app","ReturnID","quit")
La selezione del menù "Reset to defaults..." significa "Chiama la funzione
DefaultConfig()":
call notify("mdefault","menutrigger","everytime","app","return","call DefaultConfig()")
Scriviamo la funzione DefaultConfig(): il suo compito è quello di riportare
la configurazione attuale a quella di default, ovvero:
DefaultConfig: procedure expose global.
call ConfigToDefaults()
call CopyConfig("GLOBAL.PREFS","GLOBAL.BACKPREFS")
call ConfigToGadgets()
return
La selezione del menù "Last saveds..." significa "Chiama la funzione RestoreConfig(1)":
call notify("mlast","menutrigger","everytime","app","return","call RestoreConfig(1)")
Scriviamo la funzione RestoreConfig(): il suo compito è quello di riportare
la configurazione attuale a:
RestoreConfig: procedure expose global.
parse arg saved
if saved then call ReadConfig(1)
else call CopyConfig("GLOBAL.BACKPREFS","GLOBAL.PREFS")
call ConfigToGadgets()
return
La selezione del menù "Restore..." significa "Chiama la funzione RestoreConfig(0)":
call notify("mrestore","menutrigger","everytime","app","return","call RestoreConfig(0)")
La selezione del menù "MUI..." significa "Apri la finestra di configurazione
di MUI":
call notify("mmui","menutrigger","everytime","app","OpenConfigWindow")
La selezione di una voce del Listview significa "spostati nella pagina
corrispondente":
call notify("hlister","active","everytime","hpager","set","activepage","triggervalue")
Se l'utente preme il bottone Save, chiama la funzione SaveConfigExit:
call notify("save","pressed",0,"app","return","call SaveConfigExit(1)")
SaveConfigExit() semplicemente:
SaveConfigExit: procedure expose global.
parse arg save
call set("win","open",0)
call GetSaveConfig(save)
exit
Ove GetSaveConfig() è:
GetSaveConfig: procedure expose global. parse arg save call GadgetsToConfig() call SaveConfig(save) returnSe l'utente preme il bottone Use, chiama la funzione SaveConfigExit:
call notify("use","pressed",0,"app","return","call SaveConfigExit(0)")
Se l'utente preme il bottone Test, chiama la funzione TestConfig:
call notify("test","pressed",0,"app","return","call TestConfig()")
TestConfig() è più complessa: se l'utente vuole "testare" la configurazione,
dovremo copiare quella corrente in ENV: ovvero:
TestConfig: procedure expose global.
call CopyConfig("GLOBAL.BACKPREFS","GLOBAL.TESTPREFS")
call GetSaveConfig(0)
global.test=1
return
Il flag global.test è usato per segnalare lo speciale stato. Viene
SafeExit: procedure expose global.
if global.test then do
call CopyConfig("GLOBAL.TESTPREFS","GLOBAL.PREFS")
call SaveConfig(0)
end
exit
Ricapitoliamo…
Ricapitoliamo. Dobbiamo gestire una configurazione contenuta in un file di testo, fornendo all'utente la possibilità di
Il compitino a casa è: redere il tutto localizzato, leggendo le stringhe dal catalogo WarpJPEG.catalog usando la funzione rmh.library/LocalizeStrings(). Buon divertimento. Alla prossima. Tabella 1 - L'albero degli oggetti della GUI di WarpJPEG
|
Copyright (C) 1999-2002, la redazione di AmigaLife.
Il logo e le copertine della rivista sono tratti dal sito Pluricom e sono Copyright (C) 1992-2001 Pluricom S.r.l.