Capitolo 9 Lavorare con i files
Transcript
Capitolo 9 Lavorare con i files
Capitolo 9 Lavorare con i files In questo capitolo viene introdotta la gestione dei file in Gambas e le operazioni di input/output. Gambas possiede un assortimento di funzioni e subroutine per supportare ogni tipo di operazione in i/o con i files. • Access • Dir • Eof • Exist • IsDir / Dir? • Lof • Stat • Temp / Temp$ • OPEN e CLOSE • INPUT, LINE INPUT e PRINT • READ, SEEK, WRITE e FLUSH • COPY, KILL e RENAME • MKDIR e RMDIR • LINK 0.0.1 Access Access è una funzione utilizzata per determinare se un file è accessibile oppure no. La sintassi Gambas è la seguente: Accessible = Access ( Path [ , Mode ] ) La chiamata della funzione Access restituisce TRUE se il file specificato con Path è accessibile. Se il valore di Mode è gb.Read, allora la funzione restituisce TRUE se il file può essere letto. Questo è il valore di default per tutti i file. Se Mode è gb.Write, allora Access restituisce TRUE, se il file può essere scritto. Quando Mode è gb.Exec la funzione restituisce TRUE e il file può essere eseguito. I flags di cui sopra possono essere combinati con l’operatore OR. Per una directory, il flag di esecuzione rappresenta la possibilità di accedere alla directory. Per esempio: 1 This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. 2 A Beginner’s Guide to Gambas Tabella 1: Caratteri per le directory Caratteri generici * ? [abc] [x-y] [ˆx-y] Corrispondenze Un numero di un qualunque tipo di carattere. Un singolo carattere di qualsiasi tipo. Un carattere specificato tra parentesi quadre. Qualsiasi carattere che si trova entro l’intervallo specificato. Qualsiasi carattere che non si trova entro l’intervallo. PRINT Access("/home/rabbit", gb.Write OR gb.Exec) restituisce True mentre l’istruzione PRINT Access("/root", gb.Write) restituisce False 0.0.2 Dir La funzione Dir restituisce un array stringa contenente i nomi dei file presenti nella Directory corrispondente al percorso del file specificato. Se non è specificato nessun percorso, viene restituito qualsiasi nome di file esistente nella directory. Il percorso può contenere i medesimi caratteri generici previsti per l’operatore LIKE. In altre parole, i caratteri presenti nella tabella 1. Il carattere speciale \ impedisce che il carattere che lo segue sia interpretato come generico. La sintassi standard di Gambas è: File name array = Dir ( Directory [ , File pattern ] ) Di seguito un esempio esplicativo: ’ Print a directory SUB PrintDirectory(Directory AS String) DIM sFile AS String FOR EACH sFile IN Dir(Directory, "*.*") PRINT sFile NEXT END 0.0.3 Eof La funzione Eof restituisce un valore booleano TRUE, quando ci troviamo alla fine dello stream. La sintassi standard in Gambas è: Bolean = Eof ( File ) Di seguito un esempio di come viene usato Eof : ... OPEN FileName FOR READ AS #hFile WHILE NOT Eof(hFile) LINE INPUT #hFile, OneLine PRINT OneLine WEND CLOSE #hFile ... This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. A Beginner’s Guide to Gambas 0.0.4 3 Exist La funzione Exist restituisce TRUE, quando esiste un file oppure una directory. L’uso di ˜ come identificatore di una directory non funziona. Se il percorso specificato non esiste, viene restituito FALSE. La sintassi in Gambas è: Bolean = Exist ( Path ) Ecco un esempio dell’uso di Exist per determinare se un file esiste prima della chiamata OPEN per leggere quel file: ... DIM sCurrentFileName AS String sCurrentFileName = "The Raven.txt" IF Exist(sCurrentFileName) THEN ... ’open the file ELSE Message.Info("File " & sCurrentFileName & " does not exist.") ENDIF ... 0.0.5 IsDIR / DIR? La funzione IsDir restituisce TRUE, se un percorso punta a una directory. Se il percorso non esiste ovvero non è una directory, questa funzione restituisce FALSE. La sintassi di Gambas per IsDir è: Bolean = IsDir ( Path ) Qualche esempio di come usare IsDir lo prendiamo dalla documentazione Wiki di Gambas: PRINT IsDir ("/etc/password") False PRINT IsDir (Application.Home &/".gamba") True PRINT IsDir ("/windows") False 0.0.6 STAT La funzione Stat restituisce informazioni su un file o una directory. Il file oggetto dell’informazione riportata include il tipo, la dimensione, la data/ora dell’ultima modifica, i permessi, ecc. . . Usando questa funzione possono essere ottenute le seguenti proprietà per un file: .Group .Hidden .LastAccess .LastChange .LastUpdate .Mode .Perm .SetGID .SetUID .Size .Sticky .Time .Type .User La sintassi standard di Gambas per Stat è: File info = Stat ( Path ) Ecco un esempio di come usare Stat e ciò che viene riportato dalla console: This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. 4 A Beginner’s Guide to Gambas WITH Stat ("/home") PRINT .Type = gb.directory PRINT Round(.Size / 1024); "K" END WITH La console risponderà con: True 4k 0.0.7 TEMP / TEMP$ La funzione Temp$ restituisce un nome unico per un file, utilizzabile nella creazione di file temporanei. Il nome del file creato verrà memorizzato nella directory /tmp. La sintassi di Gambas è: File name = Temp$ () Esempio: PRINT Temp$ () /tmp/gambas.0/12555.1.tmp 0.0.8 OPEN e CLOSE OPEN e CLOSE operano insieme. OPEN apre un file per la lettura, la scrittura, la creazione e l’aggiunta di dati. Se non è specificata la parola chiave CREATE, il file deve esistere già. Se la parola chiave CREATE è specificata, allora il file viene creato, oppure azzerato (nei contenuti) se già esiste. Se il file è aperto in scrittura, il percorso per accedere ad esso deve essere assoluto, poiché percorsi relativi riguardano, di default, file presenti nel progetto corrente di Gambas. La sintassi di Gambas per OPEN è: OPEN File name FOR [ READ ] [ WRITE ] [ CREATE | APPEND ] [ DIRECT ] [ WATCH ] [ BIG | LITTLE ] AS # Variable Se è specificata la parola chiave APPEND, allora il puntatore del file è spostato alla fine del file non appena il file è aperto. Se è specificata la parola chiave DIRECT, l’input-output non vengono bufferizzati. Se è specificata la parola chiave WATCH, il file è analizzato dall’interprete usando le seguenti linee guida condizionali: • Se può essere letto almeno un byte dal file, viene invocato l’evento gestore File Read( ). • Se può essere scritto almeno un byte nel file, viene invocato l’evento gestore File Write( ). Se è specificata la parola chiave BIG o LITTLE, tutte le operazioni successive a READ e WRITE su questo file, utilizzeranno la rappresentazione dei dati big-Endian o little-Endian. Variable riceve l’oggetto che rappresenta lo stream aperto. CLOSE è la funzione reciproca e chiude semplicemente un file aperto. La sintassi per usare CLOSE è: CLOSE # File Ecco un esempio che apre e chiude un file: ’ Gambas Class File sCurrentFileName AS String sCurrentFileLength AS Integer PUBLIC SUB Form_Open() This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. A Beginner’s Guide to Gambas 5 DIM sInputLine AS String DIM hFileIn AS File sCurrentFileName = "The Raven.txt" IF Exist(sCurrentFileName) THEN OPEN sCurrentFileName FOR READ AS hFileIn sCurrentFileLength = Lof(hFileIn) WHILE NOT Eof(hFileIn) LINE INPUT #hFileIn, sInputLine ’LINE INPUT is explained below TextArea1.Text= TextArea1.Text & Chr(13) & Chr(10) & sInputLine WEND CLOSE hFileIn ELSE Message.Info("File " & sCurrentFileName & " does not exist.") ENDIF END 0.0.9 LINE INPUT LINE INPUT legge un’intera linea di testo dall’input standard. Può essere utilizzata su console o con i file. La sintassi in Gambas è: LINE INPUT Variabile o, per file dove i dati sono letti dallo stream: LINE INPUT #File, Variabile Ecco un esempio per provare a vedere come opera LINE INPUT: STATIC PUBLIC SUB Main() DIM hFile AS File DIM sInputLine AS String DIM lineCtr AS Integer OPEN "The Raven.txt" FOR READ AS hFile WHILE NOT Eof(hFile) LINE INPUT #hFile, sInputLine PRINT Str (lineCtr) & Chr(9) & sInputLine INC lineCtr WEND CLOSE #hFile END NOTA: non si dovrebbe usare la chiamata LINE INPUT per leggere dati da un file binario, perché perderebbe i caratteri di salto di linea. Per leggere dati da file binari, si dovrà invece usare il comando READ: READ #sFile, OneLine, -256 dove -256 è il numero massimo di bytes che si intende leggere (preceduto con un simbolo del trattino). Il -256 può essere un numero qualsiasi, ma dovrebbe essere limitato alla dimensione massima di ogni variabile che il programma utilizzerà per memorizzare i dati letti. This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. 6 A Beginner’s Guide to Gambas 0.0.10 READ, SEEK, WRITE e FLUSH La funzione READ legge i dati dall’output standard. Esso tratta lo stream input come dati binari, il cui tipo di dati è specificato dal tipo di variabile, nella quale i dati sono memorizzati durante l’operazione di lettura. La rappresentazione binaria dei dati dovrebbe essere la stessa rappresentazione creata dall’uso dell’istruzione WRITE. Se Variable è una stringa, è possibile specificare una lunghezza che indica il numero di bytes da leggere. Se il valore del parametro Length è negativo, allora i bytes -Length vengono letti dalla fine dello stream. Se il parametro Length per una stringa non viene specificato , l’intera stringa viene letta dallo stream. Il valore della stringa deve essere stato scritto mediante l’istruzione WRITE. La sintassi standard in Gambas è: READ Variabile [ , Length ] Se READ è utilizzato con operazioni su file, la sintassi è: READ # File , Variabile [ , Length ] La funzione WRITE scrive espressioni per l’output standard usando le loro rappresentazioni binarie. Se Expression è una stringa, è possibile specificare una lunghezza che indica il numero di bytes da scrivere. Se non viene specificata nessuna lunghezza per un valore della stringa, l’intero valore della stringa viene scritto direttamente nello stream. Ecco la sintassi di Gambas per uno standard input stream: WRITE Espressione [ , Length ] e, per scrivere in un file, la regola è la seguente: WRITE # File Espressione [ , Length ] La funzione SEEK definisce (o sposta) la posizione del puntatore dello stream, soprattutto nella preparazione della successiva operazione di lettura/scrittura. Se la posizione del parametro Length è negativa, il puntatore dello stream viene spostato indietro a –Length bytes dalla fine del file. Per spostare il puntatore dello stream oltre la fine del file, è necessario utilizzare la funzione Lof(). La sintassi di Gambas è: SEEK #File, Position Ecco alcuni esempi dell’uso di SEEK: ’ Move to the beginning of the file SEEK #hFile, 0 ’ Move after the end of the file SEEK #hFile, Lof(#hFile) ’ Move 100 bytes before the end of the file SEEK #hFile, -100 La funzione FLUSH viene invocata per svuotare (o scaricare) il contenuto dello stream del buffer. Se non viene specificato alcuno stream, viene scaricato ogni stream aperto. Solitamente, FLUSH viene invocato dopo molteplici operazioni di scrittura e prima di chiudere un programma o una routine al fine di garantire che tutti i dati memorizzati in un buffer, siano trascritti nel file prima dell’uscita. Ecco la sintassi in Gambas per FLUSH: FLUSH [ #File ] This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. 7 A Beginner’s Guide to Gambas Tabella 2: Menu File Nome variabile FileNewItem FileOpenItem FileSaveItem FileSaveAsItem FilePrintItem FileExitItem Menu 0.0.11 Etichetta “New” “Open...” “Save...” “Save as...” “Print” “Exit” Proprietà icona (nome file) Picture[“filenew.png”] Picture[“fileopen.png”] Picture[“filesave.png”] Picture[“filesaveas.png”] Picture[“fileprint.png”] Picture[“exit.png”] COPY, KILL e RENAME La funzione COPY copia un file sorgente in un file con destinazione specifica. Il percorso di destinazione necessita che non abbia il medesimo nome del sorgente. Con questa funzione non è possibile copiare directories ricorsivamente. La sintassi di Gambas per questa funzione è: COPY Source_Path TO Destination_Path Ecco un esempio di come usare COPY: ’ Save the gambas configuration file COPY System.Home & / ".gambas/gambas.conf" TO"/mnt/save/gambas.conf.save" La funzione KILL rimuove un file. La sintassi in Gambas è: KILL Path e qualunque cosa esista in Path viene eliminata. RENAME cambia il nome di un file o di una directory da VecchioNome a NuovoNome. Ecco un esempio: RENAME VecchioNome AS NuovoNome 0.0.12 MKDIR, RMDIR La funzione MKDIR viene impiegata per creare una directory nel proprio file system mentre RMDIR viene impiegta per eliminare una directory. Entrambe le funzioni contengono un solo parametro, un percorso del File ed un nome. La sintassi in Gambas è: MKDIR "file:/rittingj/My Documents/Gambas/test" RMDIR "file:/rittingj/My Documents/Gambas/test" Al fine di mostrare la maggior parte delle funzioni su file e su I/O descritte in questo capitolo, creiamo un’applicazione che produrrà un semplice editor di testo. L’intento non è di realizzare un editor di testo completo, bensı̀ di mostrare come utilizzare queste funzioni nel contesto di un’applicazione. L’applicazione che si costruirà in questo capitolo è un po’ più complessa di quelle che abbiamo visto in precedenza, in quanto saranno creati due form, un file di input, un file help, e un certo numero d’icone di sistema, quest’ultime saranno copiate dalla cartella delle icone di sistema della distribuzione Linux. L’applicazione al suo avvio sarà simile alla figura 1. In questo programma, si creeranno un menu principale, un’area di testo per mostrare e modificare il testo ed elementi dello status al di sotto della schermata. Inoltre s’introdurrà l’uso del controllo Timer (che appare come un orologio nella figura 1). Questo form, chiamato Form1.form, apparirà nella modalità design come la figura 2. Per cominciare, avviare Gambas e creare un nuovo progetto con interfaccia grafica. Lo si chiami FileOps e nell’IDE Gambas creare un nuovo form, Form1 e renderlo classe d’avvio. Quando il form appare sullo schermo andiamo a realizzare il menu. Basta premere CTRL-E per aprire l’editor del menu. Aggiungere i seguenti oggetti del menu mostrati nelle tabelle 2, 3, 4 e 5. Riguardo alle icone, bisognerà copiarle dalla medesima directory specificata nell’ultimo capitolo. Molte distribuzioni Linux contengono i files delle icone memorizzati solitamente nelle cartelle: This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. 8 A Beginner’s Guide to Gambas Figura 1: Il programma FileOps all’avvio. Figura 2: Il programma FileOps in modalità design. Tabella 3: Menu Edit Nome variabile EditSelectAllItem EditUndoItem EditRedoItem EditCutItem EditCopyItem EditPasteItem EditPrefsItem Etichetta “Select All” “Undo” “Redo” “Cut” “Copy” “Paste” “Preferences” Proprietà icona (nome file) Picture[“selectall.png”] Picture[“undo.png”] Picture[“redo.png”] Picture[“editcut.png”] Picture[“editcopy.png”] Picture[“editpaste.png”] Picture[“configure.png”] This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. 9 A Beginner’s Guide to Gambas Tabella 4: Menu Format Nome variabile FomatColorItem FormatFontItem Etichetta “Colors” “Font” Proprietà icona (nome file) Picture[“colorize.png”] Picture[“charset.png”] Tabella 5: Menu Help Nome variabile HelpContentsItem HelpAboutItem Etichetta “Contents” “About” Proprietà icona (nome file) Picture[“contents.png”] Picture[“buildingblocks.png”] • namedfile:/usr/share/icons/default-kde/32x32/action • orfile:/usr/share/icons/default-kde/32x32/applications Il vostro particolare sistema potrebbe essere un po’ diverso. A prescindere da dove siano presenti le icone, si prenderanno le icone appropriate per il programma e le si copieranno nella cartella FileOps. La figura 3 mostra le icone scelte da utilizzare nel progetto. Se non si riesce a trovare le icone, sceglierne altre che siano il più possibile simili a quelle proposte nell’esempio e salvarle con il nome del file adatto per quella opzione corrispondente, come mostrato nelle tabelle di cui sopra. Non è importante che ogni icona sia esattamente quella dell’esempio. Infatti, l’icona SelectAll è stata creata usando l’editor per icone di Gambas. Ecco il codice da usare per Form1. Sarà esaminato linea per linea al fine di spiegarlo al meglio. Si comincia il programma dichiarando due classi di variabili globali, sCurrentFileName e sCurrentFileLength. ’ Gambas class file sCurrentFileName As String sCurrentFileLength As Integer Quando si avvia il programma, viene richiamata questa subroutine. Quest’ultima chiama un constructor, new() per effettuare una inizializzazione. Il constructor è spiegato nella sezione successiva. All’avvio del programma, sarà caricato automaticamente il file di testo di default, chiamato “Raven.txt”. Questo file è stato copiato da Intenet ma è possibile usare qualunque altro file di testo in questo progetto FileOps. Si ricordi soltanto che il nome del file deve essere cambiato nel codice sottostante, se non s’intende utilizzare il filename scelto nel progetto. PUBLIC SUB Form_Open () Figura 3: Icone del programma FileOps. This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. 10 A Beginner’s Guide to Gambas DIM sInputLine AS String DIM hFileIn AS File Si assegnerà il nome del file alla variabile stringa sCurrentFileName e si controllerà se il file esiste nella cartella del progetto. Se esso esiste, allora il programma aprirà il file e otterrà la lunghezza del file. Il nome e la dimensione del file compariranno nella proprietà Form1.Caption. Sicuramente non è consuetudine far apparire la dimensione di un file come questo, ma in questa sede è stato fatto per mostrare l’utilizzo della funzione Lof. sCurrentFileName = "The Raven.txt" IF Exist(sCurrentFileName) THEN OPEN sCurrentFileName FOR READ AS hFileIn sCurrentFileLength = Lof(hFileIn) ’to show how to get file size Form1.Caption = " " & sCurrentFileName & ", size: " & Str (sCurrentFileLength) & " bytes." Ora, s’inserisce una struttura ciclica per leggere tutte le linee del file di testo nella proprietà TextArea1.Text. Si proseguirà con il ciclo fino a che non si giunge alla fine del file. Man mano che ogni linea viene letta ed assegnata alla variabile sInputLine, bisogna ricordare di aggiungere il CRLF (Ritorno a capo (Chr(13)) ed un salto di linea (Chr(10)) alla fine della linea, affinché appaia correttamente nella struttura. WHILE NOT Eof(hFileIn) LINE INPUT #hFileIn, sInputLine TextArea1.Text= TextArea1.Text & Chr(13) & Chr(10) & sInputLine WEND Non appena usciti dal ciclo, il che significa aver raggiunto la fine del file, verrà chiuso il file. CLOSE hFileIn Se il controllo sull’esistenza del file non va a buon fine, s’inserisce questo blocco ELSE di codice e si visualizza un messaggio che il file non esiste. Successivamente, si richiama la subroutine FileOpenItem Click() affinché l’utente possa scegliere un file da aprire. ELSE Message.Info("File " & sCurrentFileName & " does not exist.") FileOpenItem_Click() ’ force a file to be picked if no default. ENDIF END Questo è tutto ciò che c’è nella subroutine Form Open(). Ecco il constructor per il form: PUBLIC SUB _new() Timer1.Delay = 1000 Timer1.Enabled = TRUE END Il constructor imposta semplicemente il ritardo del timer a 1 secondo (1000 millisecondi) ed imposta la proprietà Enabled su True. L’effetto finale è l’aggiornamento del timer ogni secondo. Il controllo del timer sarà implementato più avanti. Si presti ora attenzione all’opzione del menu File — New. Affinché l’utente possa creare un nuovo file, si deve per prima cosa eliminare il testo nella proprietà TextArea1.Text. Si chiederà un nome per un nuovo file vuoto da salvare sul disco. Qualunque modifica effettuerà l’utente, sarà salvata all’uscita o tutte le volte che l’utente scelga di salvare dal menu. L’ultima cosa da fare in questa subroutine è impostare la proprietà Form1.Caption nel filename nuovamente salvato. This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. A Beginner’s Guide to Gambas 11 PUBLIC SUB FileNewItem_Click() TextArea1.Text = "" Dialog.Title = "Enter new file name..." Dialog.Filter = [ "Text files (*.txt)", "All files (*.*)" ] Dialog.SaveFile Form1.Caption = Dialog.Path END La successiva routine viene attivata quando l’utente clicca sull’oggetto del menu File — Open. PUBLIC SUB FileOpenItem_Click() Questa routine richiede due variabili locali. hFileIn è l’handle per l’oggetto FILE che si aprirà e sInputLine è una variabile stringa che si utilizzerà per leggere il file di testo nella proprietà TextArea1.Text. DIM hFileIn AS File DIM sInputLine As String Appena prima della chiamata della finestra di dialogo standard, è possibile impostare la caption della finestra (o il titolo) usando la proprietà Dialog.Title. Inoltre si può impostare un filtro nella finestra di dialogo per vedere solamente file con estensione .txt. Come ultima risorsa, si lascerà all’utente la possibilità di scegliere se vedere tutti i tipi di file selezionando il filtro *.* all’interno della finestra di dialogo. Dialog.Title = "Choose a text file" Dialog.Filter = ["Text files (*.txt)", "All files (*.*)"] Quando viene effettuata la chiamata di Dialog.OpenFile, si deve ricordare che sarà restituito un valore TRUE se l’utente clicca sul tasto Cancel, altrimenti, viene restituito FALSE. Si avrà un valore TRUE seguito dal Return se è il valore che si verifica. Altrimenti, significa che l’utente ha selezionato un file che sarà processato. IF Dialog.OpenFile() THEN RETURN ELSE Il nome del file che è stato selezionato dall’utente viene memorizzato nella proprietà Dialog.Path. Si assegnerà il valore alla variabile globale sCurrentFileName affinché altre parti del programma abbiano accesso ad esso. La proprietà Form1.Caption sarà impostata con il nuovo nome del file aperto e ripulita dell’attuale area TextArea1 richiamando il metodo CLEAR. sCurrentFileName = Dialog.Path Form1.Caption = " " & sCurrentFileName & " " TextArea1.Clear Successivamente, si aprirà il file e si leggerà ogni linea mediante la funzione LINE INPUT e la variabile stringa sInputLine. Una volta che i dati sono stati memorizzati in sInputLine, verranno aggiunti alla fine di qualunque cosa sia attualmente memorizzata nella proprietà TextArea1.Text e aggiungendo i caratteri CRLF. Non appena giunti alla fine del file, si esce dal ciclo e si chiude il file. OPEN sCurrentFileName FOR READ AS hFileIn WHILE NOT Eof(hFileIn) LINE INPUT #hFileIn, sInputLine TextArea1.Text= TextArea1.Text & Chr(13) & Chr(10) & sInputLine WEND CLOSE hFileIn ENDIF END This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. 12 A Beginner’s Guide to Gambas L’oggetto del menu File — Save, quando viene cliccato, richiama la subroutine corrispondente. Essa opera quasi come la precedente, tranne per il fatto che già si conosce il nome del file presente nella variabile globale sCurrentFileName. Quando si ritorna dalla chiamata, la variabile globale viene aggiornata con qualunque dato di ritorno – senza preoccuparsi se il file ha il medesimo nome oppure no. Qualunque esso sia o sia diventato sarà ciò che è stato salvato. PUBLIC SUB FileSaveItem_Click() Dialog.Title = "Save text file" Dialog.Filter = [ "Text files (*.txt)", "All files (*.*)" ] IF Dialog.SaveFile() THEN RETURN ELSE sCurrentFileName = Dialog.Path File.Save(sCurrentFileName, TextArea1.Text) ENDIF END La prossima subroutine che si codificherà è l’oggetto del menu FileSaveAs. Se l’utente intende salvare il file esistente con un altro nome, questa è la routine per effettuare quella opzione. Riguardo a questa subroutine, essa funzionerà quasi come la subroutine FileSave, ma si avrà bisogno di verificare se il nome del file riportato dalla finestra di dialogo del file sia realmente diverso dal nome corrente del file. Se è diverso, allora il file sarà salvato con il nuovo nome. Se il nome è uguale, si farà apparire una casella di messaggio e s’informerà l’utente che nessuna azione di salvataggio è stata intrapresa, poiché il nome era uguale a quello originale. PUBLIC SUB FileSaveAsItem_Click() DIM sTmpName AS String Viene usata la variabile stringa locale sTmpName per effettuare la verifica. Innanzitutto, si ha bisogno del percorso completo dell’applicazione e concatenare il nome del file corrente a quello per ottenere il nome del percorso completo per il file. A tal fine si utilizzerà la classe Application, verificando la proprietà Path per ottenere il nome ed il percorso dell’applicazione corrente. Notare l’impiego dell’operatore &/ che viene impiegato per concatenare i nomi dei file. sTmpName = Application.Path &/ sCurrentFileName Proprio come fatto in precedenza, si impostano le proprietà del titolo e del filtro, poi si richiama la finestra di dialogo del file. Dialog.Title = "Save text file as..." Dialog.Filter = ["Text files (*.txt)", "All files (*.*)"] Se la chiamata della finestra di dialogo restituisce TRUE, l’utente è uscito dalla routine e si tornerà alla procedura di chiamata (cioè il ciclo dell’evento standard). IF Dialog.SaveFile() THEN RETURN Se i nomi dei file sono gli stessi, si farà apparire una MessageBox cosı̀ fatta: ELSE IF sTmpName = Dialog.Path THEN Message.Info("File not saved: name same as current file.", "OK") RETURN Altrimenti, si imposta la variabile globale sul nome del file restituito dalla finestra di dialogo del file che richiamerà il metodo Save, passando il nome del file ed il testo come parametri. Di seguito viene aggiornata l’etichetta della finestra come ultima cosa da fare prima di passare al processo chiamante. This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. A Beginner’s Guide to Gambas 13 ELSE sCurrentFileName = Dialog.Path File.Save(sCurrentFileName, TextArea1.Text) Form1.Caption = sCurrentFileName ENDIF END Stampare un tipo di oggetto in Gambas è molto facile. L’oggetto del menu File — Print richiama questa subroutine quando si clicca su di esso. Per prima cosa si dichiareranno le variabili locali: PUBLIC SUB FilePrintItem_Click() ’integer variables to hold our margin values DIM iLeftMargin AS Integer DIM iTopMargin AS Integer DIM iRightMargin AS Integer DIM iBottomMargin AS Integer ’string variables we will use DIM arsTxtToPrint AS String[] DIM iTxtPosY AS Integer DIM sTxtLine AS String DIM strMsg AS String ’integer variables our program uses to calculate page dimensions DIM iPrinterAreaWidth AS Integer DIM iPrinterAreaHeight AS Integer Adesso si deve chiamare la subroutine Printer.Setup() prima di provare a stampare qualcosa. IF Printer.Setup() THEN RETURN La chiamata successiva assicura che la misura predefinita della pagina sia impostata a 8.5 per 11 (formato lettera). Se non si vive negli Stati Uniti, si dovrebbe probabilmente configurare la misura sul formato “A4”, essendo questo il formato classico impiegato al di fuori degli Stati Uniti. L’oggetto Printer.Setup, ogni volta che viene invocato, imposta in modo predefinito la pagina nel formato “A4”, cosı̀ se si sta usando una stampante che adopera soltanto carta per le lettere, tralasciare questa proprietà è il modo più facile per gestire la situazione. ’override the printer.setup dialog so you don’t have ’to set this every single time you print something Printer.Size = "Letter" ’or A4, or whatever you use ’older printers do not seem to be set to DPIs lower than 600 in GB ’so we will set it manually for an older HP Laserjet III printer ’if your printer is newer, you can comment this line out Printer.Resolution = 300 ’my HPLJIII settings Per una stampante a 300 dpi, un valore pari a 300 equivale al margine di un pollice. Ora, si impostino i valori dei margini mediante le proprietà Printer (o quelle di cui non si è tenuto conto sopra) restituite dalla chiamata Printer.Setup. ’set left margin to one inch, equal to the DPI of the printer iLeftMargin = Printer.Resolution ’set top margin to one inch, equal to the DPI of the printer iTopMargin = Printer.Resolution ’set right margin to one inch, equal to the DPI of the printer iRightMargin = Printer.Resolution This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. 14 A Beginner’s Guide to Gambas ’set top margin to one inch, equal to the DPI of the printer iBottomMargin= Printer.Resolution ’Define the Printable area width iPrinterAreaWidth = Printer.Width ’Define the Printable area height iPrinterAreaHeight = Printer.Height iLeftMargin iRightMargin iTopMargin { iBottomMargin Il calcolo per ottenere iPrinterAreaWidth e iPrinterAreaHeight sottrae i margini sinistro/destro e superiore/inferiore dalla larghezza/lunghezza totale della pagina. Per fogli di misura Letter (8.5 x 11) la larghezza sarebbe 8.5 pollici moltiplicato 300 dpi ovvero 2550 e la lunghezza sarebbe 11 pollici moltiplicato 300 dpi ovvero 3300. Il calcolo sottrae 600 (300 per il margine sinistro + 300 per il margine destro) da entrambi i valori della larghezza e della lunghezza restituiti dalla chiamata Printer.Setup. Quindi bisogna realizzare la stringa dell’output per la chiamata Message.Info e mostrare i dati all’utente: ’build a string for MessageBox with data from printer setup call strMsg = "Res: " & Str(Printer.Resolution) & ", Width: " & Str (Printer.Width) & ", Height: " & Str(Printer.Height) ’show a MessageBox with data obtained from the printer setup call message.Info(strMsg) Successivamente, si deve analizzare in una stringa array tutto il contenuto del testo nel controllo TextArea1. È molto facile da realizzare in Gambas ed occupa soltanto una linea di codice. Per fare questo, si utilizzerà la funzione Split e si configurerà il parametro di delimitazione con carattere “\n” il quale indica la fine della linea di testo: ’use an array of strings to store each line of text in TextArea1.Text arsTxtToPrint = Split(TextArea1.Text, "\n") Adesso si è pronti per stampare. Per fare questo, è necessario innanzitutto chiamare il metodo Begin e specificare che si vuole che l’oggetto Printer sia il dispositivo output usato dal metodo Draw : Draw.Begin(Printer) ’ Initializes the draw routine for Printer Nel codice seguente, è stata invocata la funzione Draw.Rect per definire un bordo di un pollice intorno alla pagina. ’make a border around the page Draw.Rect(iLeftMargin,iTopMargin,iRightMargin,iBottomMargin) ’now we will loop through each line in the array and print it FOR EACH sTxtLine IN arsTxtToPrint ’increment the vertical (Y) line position for each line printed iTxtPosY = iTxtPosY + Draw.TextHeight(sTxtLine) ’test the line position ... if is > the bottom of the printer ’area then we need to reset it to zero and perform a new page IF iTxtPosY >= (iPrinterAreaHeight) THEN iTxtPosY = 0 ’reinit the Current Text Position Y Printer.NewPage This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. 15 A Beginner’s Guide to Gambas ’make a border around the new page Draw.Rect(iLeftMargin,iTopMargin,iRightMargin,iBottomMargin) ENDIF ’our test to see if line falls past bottom margin Adesso, si chiamerà Draw.Text per stampare il testo. Il primo parametro Draw.Text assume l’oggetto stringa sTxtLine che rappresenta la linea di testo da stampare dall’array della stringa arsTextToPrint. I successivi due parametri iLeftMargin e (iTopMargin+iTxtPosY ) sono utilizzati per definire il margine sinistro e la posizione verticale di testo Y corrente. Ciascuna linea è incrementata dal valore contenuto nella proprietà Draw.TextHeight. Come opzione, è possibile specificare il margine destro e quello inferiore, ed una proprietà di allineamento. Nel caso sottostante, si è specificato il margine destro, definito da iPrinterAreaWidth (come calcolato sopra) e si è tralasciato il margine inferiore, dato che esso è contenuto dal codice del FOR LOOP sottostante. Infine, si è specificato che il testo è allineato a destra. ’Now we print the text line at the new line position Draw.Text(sTxtLine,iLeftMargin,iTopMargin+iTxtPosY,iPrinterAreaWidth,,Align.Left) NEXT ’iteration of the FOR LOOP Per terminare il processo di stampa bisogna ricordare di chiamare Draw.End : Draw.End ’Then print final contents and eject last page END Giacché il programma funziona soltanto con file di testo, c’è un’alternativa, che a volte rappresenta il metodo più facile per stampare. È possibile impiegare il comando incorporato di sistema pr (print). Viene eseguito mediante il metodo della SHELL di Gambas. Questa chiamata consente di stampare qualunque file di testo dall’interno del programma direttamente sulla stampante. Per vedere come funziona questo metodo, bisogna aggiungere un oggetto del menu al programma. Si vada su Form1.form e si prema CTRL-E per caricare l’editor del menu. Al di sotto dell’oggetto del menu File — Print, aggiungere un nuovo oggetto, FileSystemPrintItem e si scriva la caption Print (metodo di sistema). Si crei un evento clic e si aggiunga il codice seguente: PUBLIC SUB FileSysPrintItem_Click() DIM aString AS String aString = "pr h \" " & sCurrentFileName & "\"" & " sCurrentFileName & "\" | lpr " o 10 < \"" & message.Info(aString,"Ok") SHELL aString END Sembra molto più difficile di quanto sia nella realtà. Esaminiamo il codice linea per linea. Innanzitutto, viene dichiarata una variabile stringa, aString. Successivamente si deve creare la stringa che sarà trasferita al sistema come comando da console ed assegnarla alla variabile aString. Poiché bisogna garantire che la stringa sia prodotta esattamente come dovrebbe essere scritta dalla console, ci si deve assicurare che essa contenga anche le virgolette. La prima parte della stringa: "pr h \" " richiama il comando di sistema pr. Il parametro -h specifica che si vuole avere un’intestazione stampata in ogni pagina, in tal caso, sarà il nome del file corrente, contenuto nella variabile sCurrentFileName, che nel programma è globale. Il nome del file deve essere incluso fra virgolette, cosı̀ si deve usare il carattere \ prima dell’apertura delle virgolette e terminare questa parte della stringa con altre virgolette. La successiva parte della stringa: & sCurrentFileName & "\"" This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. 16 A Beginner’s Guide to Gambas concatena il nome del file e le virgolette finali per il nome del file passato come una variabile stringa, ancora una volta preceduto da un carattere \. Le successive virgolette terminano questa porzione della stringa. La seguente parte del comando da collegare è: & " -o 10 < \"" la quale aggiunge il parametro -o (che specifica un offset) ed un valore di 10 spazi. Il carattere < viene utilizzato sulla linea di comando per indicare che i dati in entrata provengono dal nome del file che segue il carattere <. In tal caso, bisogna delimitare la stringa ancora con le virgolette, in modo che il carattere \ venga impiegato per consentire alle virgolette di essere parte della stringa di output. Le successive virgolette danno inizio alla stringa del nome del file virgolettato. L’ultima parte del comando è: sCurrentFileName & "\" | lpr" esso specifica il file da leggere (e da stampare) e conclude il nome del file con delle virgolette come spiegato sopra. La parte finale della stringa è il carattere | (pipe) che convoglia (re-indirizza) l’uscita alla stampante di linea (lpr). In pratica, per stampare un file mediante questa tecnica ci vogliono una dichiarazione di variabile e tre linee di codice. Funzioneranno entrambe le soluzioni, pertanto si scelga quella che vi soddisfa di più. Un altro metodo che può essere utilizzato, se si ha KDE installato, è stampare un file postscript ed inviare il file .ps a kprinter. Questo metterà a disposizione le finestre di dialogo di stampa di KDE e tutto quanto riguarda le stampanti di CUPS e le funzioni installate nel sistema. Se l’utente clicca sull’oggetto del menu File — Exit viene chiuso il programma. Ad ogni modo, bisogna dare all’utente la possibilità di salvare prima di uscire, pertanto si predispone una finestra di dialogo interrogativa per domandare se si intende salvare il programma. Se sı̀, viene salvato. Se no, si esce dal programma. PUBLIC SUB FileExitItem_Click() DIM iResult AS Integer iResult = Message.Question("Save your work?","Yes","No","Cancel") SELECT CASE iResult CASE 0 File.Save(sCurrentFileName, TextArea1.Text) CASE 1 CASE 2 CASE ELSE END SELECT ME.close END Questo è quanto per il menu File. Ora, la reale potenza di Gambas risulterà evidente non appena viene realizzato il codice per le funzioni del Menu Edit. In questo menu, si vuole che l’utente sia in grado di selezionare tutto il testo, di annullare l’azione precedente (undo), di ripetere l’azione precedentemente annullata (redo), di tagliare, di incollare, di copiare o di richiamare una routine di configurazione. A tal proposito, la routine di configurazione sarà piuttosto semplice. Se l’utente clicca sull’oggetto del menu Edit — Select All, selezionerà tutto il testo della TextArea. Si fa ciò ottenendo la lunghezza del testo mediante la proprietà TextArea1.Length e passando tale valore al metodo TextArea1.Selection con un punto d’inizio pari a zero ed un punto finale di caratteri Length a partire da quel punto iniziale, come mostrato nel codice seguente: PUBLIC SUB EditSelectAllItem_Click() DIM ilength AS Integer ilength = TextArea1.Length TextArea1.Selection(0,ilength) END This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. A Beginner’s Guide to Gambas 17 Si sarebbe potuto scrivere il codice precedente in maniera più semplificata senza usare la variabile integer iLength. Si sarebbe potuto ridurlo ad una singola linea, come descritto di seguito: PUBLIC SUB EditSelectAllItem_Click() TextArea1.Selection(0, TextArea1.Length) END Funzionano entrambi i metodi, ma la preferenza dell’autore è quella di adoperare il secondo metodo, poiché riflette la semplicità della programmazione di Gambas. Si riscontra tale semplicità nell’uso dei metodi interni Undo, Redo, Cut, Paste e Copy messi a disposizione da Gambas anche per l’oggetto TextArea1.Text. Gambas fornisce questi metodi per offrire la facoltà agli utenti del programma di aggiungere una sola linea di codice per ogni evento clic degli oggetti del menu, come mostrato di seguito: PUBLIC SUB EditUndoItem_Click() TextArea1.Undo END PUBLIC SUB EditRedoItem_Click() TextArea1.Redo END PUBLIC SUB EditCutItem_Click() TextArea1.Cut END PUBLIC SUB EditPasteItem_Click() TextArea1.Paste END PUBLIC SUB EditCopyItem_Click() TextArea1.Copy END L’opzione del menu Edit — Preferences sarà usata per mostrare come mettere un secondo form nel programma. Si salveranno i dati raccolti dal Form2.form in un file di configurazione chiamato FileOps.conf e che darà la facoltà di scambiare successivamente questi dati, salvati prima, tra il form principale, Form1.form e il secondo form, Form2.form. Questo è il modo più semplice per scambiare dati fra le classi, ma è possibile trasferire dati come parametri tra forms. Si discuterà di questo più avanti. La subroutine EditPrefsItem Click() è abbastanza semplice. Viene mostrato per adesso il secondo form. PUBLIC SUB EditPrefsItem_Click() Form2.Show END La figura 4 mostra come apparirà il Form2.form. Come si può notare, è piuttosto semplice. Una TextLabel viene usata per far comparire il percorso predefinito. Vi sono due tasti, uno per cambiare il percorso predefinito mediante la chiamata della routine Dialog.SelectDirectory e l’altro per tornare alla procedura di chiamata, in questo caso Form1.form. Quando si apre il form, verrà mostrato la caption “Preferences” nella parte superiore della finestra. La routine constructor new() viene invocata automaticamente. Ecco il codice per il Form2 : ’ Gambas class file PUBLIC SUB Form_Open() Form2.Caption = " Preferences " END This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. 18 A Beginner’s Guide to Gambas Figura 4: Il form Form2.form. Il constructor richiamerà la subroutine ReadPrefs, trasferendo il nome del file di configurazione come parametro. FileOps.conf è il nome che si utilizzerà per memorizzare i dati di configurazione dell’applicazione. Successivamente, si riempirà la proprietà textLabel1.Text con l’attuale directory. PUBLIC SUB _new() ReadPrefs("FileOps.conf") TextLabel1.Text = Application.Path & " is the default directory." END Quando l’utente clicca sul pulsante Change Default Path, viene chiamata la seguente routine: PUBLIC SUB Button1_Click() Dialog.SelectDirectory TextLabel1.Text = Dialog.Path END La subroutine di cui sopra richiama la funzione standard di dialogo di Gambas Dialog.SelectDirectory ed aggiorna la proprietà TextLabel1.Text con ciò che l’utente ha scelto come valore prestabilito. Quando l’utente esce da questa subroutine, l’unica altra opzione sul form è cliccare sul pulsante Return, il quale esegue il seguente codice: PUBLIC SUB Button2_Click() DIM MyPath AS String MyPath = application.Path &/ "FileOps.conf" WritePrefs(MyPath) Form2.Close END Come è possibile vedere sopra, viene dichiarata una variabile stringa locale MyPath e riempita concatenando il percorso corrente dell’applicativo con il nome del file di configurazione FileOps.conf. Poi, si chiama la subroutine WritePrefs e si passa il valore di MyPath come parametro. Quel percorso sarà immagazzinato nel file e potrà essere letto da ogni altra classe che abbia bisogno di avere questa informazione. Ecco la subroutine ReadPrefs() per completare questo lavoro: PUBLIC SUB ReadPrefs( filename AS String) ’declare our local variables DIM sInputLine AS String DIM hFileIn AS File ’see if the file already exists, if so open it. IF Exist(filename) THEN OPEN filename FOR READ AS #hFileIn ’read our single line of data ’if there were more lines, we could set up a WHILE NOT Eof LOOP This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. A Beginner’s Guide to Gambas 19 LINE INPUT #hFileIn, sInputLine ’update the display data TextLabel1.Text = sInputLine ’close the file CLOSE # hFileIn ELSE ’ there was no .conf file to open Message.Info("No preferences .conf file present.","OK") ENDIF END La routine reciproca, WritePrefs() viene mostrata di seguito: PUBLIC SUB WritePrefs( filename AS String) ’declare our local variables DIM MyPath AS String DIM hFileOut AS File ’note we are using the Dialog.Path value, not Application.Path MyPath = Dialog.Path &/ filename ’ if there is already a config file, open it for write ’ if not, open for create and write IF Exist(MyPath) THEN OPEN MyPath FOR WRITE AS # hFileOut ELSE OPEN MyPath FOR WRITE CREATE AS # hFileOut ENDIF ’ print our data to the file PRINT # hFileOut, MyPath ’ close the file CLOSE # hFileOut END Come si può vedere, è piuttosto semplice, ma può essere facilmente ampliata per adattare altre impostazioni di dati di configurazione. In Gambas si ha solo il limite della propria immaginazione. Adesso, si dia un’occhiata al codice del menu Format. Nel menu Format, le uniche opzioni di cui si dispone, sono il cambio dei colori o dei font dei caratteri. Quindi si utilizzano le finestre predefinite di dialogo di Gambas per completare entrambi i lavori. Ecco come si presentano: PUBLIC SUB FormatColorItem_Click() ’first, pop up a message to explain how we are going to work Message.Info("Select Background color first, text color second.","OK") ’set dialog caption to remind the user we are selecting BACKGROUND Dialog.Title = " Select Background Color " ’call the standard dialog routine to select color Dialog.SelectColor This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author. 20 A Beginner’s Guide to Gambas ’update our TextArea1.Backcolor with the value selected. TextArea1.BackColor = Dialog.Color ’set dialog caption to remind the user we are selecting FOREGROUND Dialog.Title = " Select Text Color " ’call the standard dialog routine to select color Dialog.SelectColor ’update our TextArea1.ForeColor with the value selected. TextArea1.ForeColor = Dialog.Color END Selezionare il font ed i suoi attributi è altrettanto semplice come lo è stato per la scelta dei colori. S’imposterà la caption della finestra di dialogo, s’invocherà la routine Dialog.SelectFont, e si provvederà ad aggiornare la proprietà TextArea1.Font, come di seguito: PUBLIC SUB FormatFontItem_Click() Dialog.Title = " Select font " Dialog.SelectFont TextArea1.Font = Dialog.Font END All’inizio del programma, nella routine del constructor, è stato abilitato il timer. Per azionare effettivamente il timer, bisogna programmarlo. Cliccando due volte sul controllo del timer si aprirà la subroutine Timer1 Timer(). Se la si richiama, il programma mostrerà il tempo corrente su un’etichetta e la posizione attuale del cursore nell’area di testo di un’altra etichetta. Ecco come operare: PUBLIC SUB Timer1_Timer() ’get the time for right now and convert to a string ’to put in the TimeLabel.Text property TimeLabel.Text = Str$(Now) ’we will get line and column data from the .Line and ’.Column properties of the Label control and update the ’PosLabel.Text property with their values: PosLabel.Text = "Line: " & Str(TextArea1.Line) & " Col: " & Str (TextArea1.Column) END L’ultimo menu è quello relativo all’Help. Ha due opzioni, Contents e About. PUBLIC SUB HelpContentsItem_Click() SHELL "konqueror \"file:/root/My Documents/Gambas for Beginners/FileOps/Help.html\"" END La chiamata della shell, di cui sopra, punta al file chiamato Help.html. Con il proposito di descrivere questa caratteristica, copiare semplicemente un qualsiasi file .html sul proprio sistema nella directory del programma e rinominarlo Help.html. In alternativa, è possibile crearsi un file .html, se si sa come farlo. Questo è quello che farebbero i veri programmatori. Ed ecco l’ultima cosa da fare: PUBLIC SUB HelpAboutItem_Click() Message.Info("<b>Simple Text Editor</b> by <br>J. Rittinghouse", "OK") END Avviare il programma e quando si è soddisfatti, si può cominciare ad occuparsi di matematica. This product is [C] 2005 by John W. Rittinghouse, all rights reserved. It is released to the Gambas User Community under an Open Content License [OCL] and may not be distributed under any other terms or conditions without the express written consent of the author.