Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Scrivere un'applicazione completa con PHP-GTK

Scrittura passo passo di un'applicazione complessa visuale con PHP-GTK
Scrittura passo passo di un'applicazione complessa visuale con PHP-GTK
Link copiato negli appunti

Continua il nostro tour introduttivo alla scoperta
di PHP-GTK. In questo e nei prossimi articoli ci
addentreremo più in profondità rispetto ad i precedenti, imparando
a gestire i menu, dialog, tabelle e molto altro ancora. Per fare tutto ciò
svilupperemo, passo passo, un'applicazione capace di estrarre il contenuto
di file compressi. La gestione di file compressi
tramite PHP è un topic molto interessante; purtroppo, per motivi di
tempo e di spazio, non ci addentreremo in profondità nell'argomento.
Sfrutteremo un ottima libreria free presente sulla rete, che potete recuperare
subito da www.phpconcept.net.
La libreria in questione, chiamata PclZip, è un'ottima classe capace
di gestire completamente i file compressi di tipo ZIP, permettendone la creazione,
l'estrazione dei file e molto altro ancora.

Visto che gli argomenti che tratteremo oggi sono molti e non di facile apprendimento,
posso solamente invitarvi, in caso vogliate approfondire la vostra conoscenza
della classe PclZip, a visitare il sito internet su cui è ospitata
ed a leggere la documentazione ufficiale (presente, tra l'altro, anche in
francase, in caso non mastichiate bene l'inglese).

Decomprimete il file scaricato in una cartella in cui sucessivamente posizioneremo
il nostro progetto.

L'applicazione che ci accingiamo a scrivere sarà molto semplice ed
intuitiva: sarà composta dal tipico menu a tendina da cui potremmo
decidere quale file aprire, come decomprimerlo ed uscire dal programma. Avrà
una tabella in cui verranno visualizzati i file contenuti nell'archivio e
comunicherà con l'utente tramite messaggi visibili all'interno di dialog.

Un esempio visuale di questa applicazione lo potete vedere qui sotto:

Immagine dell'applicazione: una finestra stile GTK con dei menu

GTK ed i Menu

Iniziamo il nostro progetto. Per prima cosa, ampliamo la nostra libreria,
sviluppando una classe che ci faciliterà di molto il lavoro durante
la creazione di menu. La classe in questione permetterà di specificare
un'array multidimensionale di valori rappresentante la struttura del nostro
menu, permettendoci oltretutto di specificare delle callback da richiamare
in caso una delle voci venga selezionata.

In PHP-GTK la costruzione dei menu avviene tramite i seguenti passi:

  • Inizialmente deve essere creata un'istanza della classe GtkMenuBar,
    un widget capace di contenere le istanze che rappresenteranno i nostri menu.
    La classe GtkMenuBar è molto semplice, e presenta solo un metodo
    proprio, GtkMenuBar::set_shadow_type(),
    che permette di specificare il tipo di ombreggiatura che vuole essere data
    alla barra. Un altro metodo che utilizzeremo sarà io metodo append()
    ereditato dalla classe GtkMenuShell
    che ci permetterà di aggungere fisicamente i menu alla barra. Pe
    pura informazione vi informo che GtkMenuShell
    è una classe astratta, utilizzata come base per GtkMenuBar
    e GtkMenu, che
    contiene tutti i metodi ed i segnali comuni ai due widget.
  • Successivamente vanno aggiunti alla barra varie istanze di GtkMenuItem,
    che rappresenteranno le voci principali del nostro menu. GtkMenuItem è
    una classe che rappresenta un singolo pulsante del nostro menu. Ogni label
    cliccabile presente sulla barra e sui suoi sottomenu sarà quindi
    un'istanza di questa classe.
  • Ad ogni voce principale potremmo associare istanze della classe GtkMenu
    (contenenti GtkMenuItem) che rappresenteranno le tendine che appariranno
    dopo la pressione del pulsante sinistro del mouse sulle voci principali.

Riassumo in modo più intuitivo quello che ho elencato sopra, con l'intendo
di chiarire a tutti le idee. Ogni tendina è un'istanza della classe
GtkMenu, con aggiunte N istanze di GtkMenuItem in base al numero di opzioni
che il menu deve contenere. Ogni tendina può essere associata solamente
ad un'istanza di GtkMenuItem tramite GtkMenuItem::set_submenu.
La barra principale, che serve solo da container, può contenere direttamente
solo istanze di GtkMenuItem.

La nostra classe si preoccupa di generare automaticamente il menu partendo
da una struttura data in input sotto forma di array. Segue il codice della
classe MenuBar, che salveremo nel file gtk_menubar.lib:

<?php
class MenuBar{

var $menuitems = array();
var $_menubar = null;

function MenuBar(){
$this->box = &new GtkVBox();
$this->_menubar = &new GtkMenuBar();
$this->box->pack_start($this->_menubar, false, false, 0);
}

function Create(&$array){
foreach($array as $item=>$s){
$this->menuitems[$item] = &new GtkMenuItem($this->get_name($item));
if(is_array($s)){
$callback = $s["__callback"];
if($callback){
unset($s["__callback"]);
$this->menuitems[$item]->connect_object('activate', $callback);
}
$this->_createsubmenu($this->menuitems[$item], $s);
}
$this->_menubar->append($this->menuitems[$item]);
}
}

function _createsubmenu(&$ref, &$structure){
if(count($structure) == 0) return;
$submenu = &new GtkMenu();
foreach($structure as $item=>$s){
if($s == "-"){
$separator = &new GtkMenuItem();
$separator->set_sensitive(false);
$menuitem = &$separator;
}else{
$this->menuitems[$item] = &new GtkMenuItem($this->get_name($item));
$menuitem = $this->menuitems[$item];
}
if(is_array($s)){
$callback = $s["__callback"];
if($callback){
unset($s["__callback"]);
$menuitem->connect_object('activate', $callback);
}
$this->_createsubmenu($menuitem, $s);
}
$submenu->append($menuitem);
}
$ref->set_submenu($submenu);
}

function separator(){
static $count = 0;
return "sep".(++$count);
}

function get_name($itemname){
return eregi_replace("(*[0-9]+)", "", $itemname);
}
}

$mb = &new MenuBar;
return($mb);
?>

Analizziamo la classe:

  • Il costruttore non accetta argomenti, e si assume la responsabilità
    di generare un container in cui viene posizionata la nostra menubar.
  • La funzione separator è molto semplice, e si assume il compito
    di generare un nome univoco per ogni separatore che utilizzeremo nel nostro
    menu. Vedrete un'esempio pratico in seguito.
  • la funzione get_name si occupa di sostituire nel nome del menu ogni asterisco
    (*) seguito da un numero. Questo viene fatto perché; la nostra classe
    salva all'interno dell'array monodimensionale $menuitems, tutte le istanze
    generate da GtkMenuItem
    in modo da permetterci di accedere direttamente agli oggetti del menu tramite
    istanza->menuitems[$nome]. Dato che l'array è monodimensionale
    l'utilizzo nei nomi dell'asterisco ci permette di riferirci univocamente
    alle istanze di GtkMenuItem anche in caso esistano due voci con valore uguale.
    Questa funzione ci permetterà, per esempio di creare due voci chiamate
    "apri" e di riferirci a loro tramite istanza->menuitems["apri
    *1"] e istanza->menuitems["apri *2"], senza incorrere
    in problemi dovuti alla sostituzione dei valori associati alle chiavi omonime.
  • La funzione Create accetta come argomento un array, che viene analizzato
    per strutturare il menu: ogni voce principale viene suddivisa in chiave=>valore,
    dove la chiave rappresenta il nome della voce principale, e valore un'array
    contenente l'eventuale sottomenu. Dell'array valore è ricercata l'eventuale
    chiave __callback che contiene la funzione da richiamare quando la voce
    viene attivata, e viene preso l'eventuale array risultante e passato alla
    funzione _createsubmenu,che si occuperà di generare la tendina associata.
    Come potete notare, la callback da richiamare in caso di selezione di una
    determinata voce del menu viene connessa direttamente all'istanza di GtkMenuItem
    tramite connect_object. L'utilizzo del segnale 'activate' ci permetterà,
    in futuro, di rendere possibile l'attivazione della callback anche nel caso
    in cui all'oggetto del menu si voglia accedere tramite i pulsanti della
    tastiera, sfruttando quelle che in PHP-GTK sono chiamate accelerator key.
    Non tratteremo questo argomento nell'articolo odierno, ma chiunque è
    invitato a controllare sulla documentazione
    ufficiale
    il loro funzionamento.
  • La funzione _createsubmenu (che è un metodo ricorsivo), si occupa
    di creare in modo completo le tendine da associare alle istanze di GtkMenuItem.
    Come possiamo notare, la funzione è molto simile a Create: esegue
    un ciclo sull'array passatole come input, utilizzando la chiave di ogni
    valore come nome dell'opzione e il valore come base per associare callback
    ed eventualmente un sottomenu. L'unica differenza sta nel fatto che questa
    funzione si occupa anche di generare, in caso venga trovato un valore pari
    a '-', una linea di separazione. I cosidetti separator vengono generati
    in modo molto semplice in PHP-GTK creando un'istanza di GtkMenuItem senza
    argomenti al costruttore e rendendola non sensibile utilizzando set_sensitive
    , metodo ereditato dalla classe padre di tutti i widget: GtkWidget.

Come potrete notare non sono state prese in considerazione alcune possibilità
aggiuntive, come i menu a scelta multipla, offerte da PHP-GTK per la creazione
dei menu. Verranno trattate nei successivi articoli.

Un esempio pratico

Dopo aver appreso le tecniche per creare menu tramite PHP-GTK, non ci resta
altro che testare la nostra nuova libreria. Create un nuovo file (che io ho
chiamato semplicemente menu.dgtk) nel quale inserirete il seguente codice:

<?php
$myApp = include_once('gtk_application.lib');
$myMenuBar = include_once('gtk_menubar.lib');

function s(){
global $myMenuBar;
return $myMenuBar->separator();
}

class MainWindow extends GtkWindow{

function MainWindow($name, $title, $size, $border, $position){
$this->GtkWindow();
$this->set_name($name);
$this->set_title($title);
$this->set_usize($size[0], $size[1]);
$this->set_border_width($border);
$this->set_position($position);
}
}

$data = array(
'File' => array(
'Nuovo' => array(
'Documento*1' => array(
'__callback' => 'OnDocumento'
),
'Progetto' => array(
'Php*1' => array(
'__callback' => 'OnPhp'
),
'Php-Gtk*1' => array(
'__callback' => 'OnGtk'
)
)
),
'Apri' => array(
'Documento' => array(
'Php*2' => array(
'__callback' => 'OnOPhp'
),
'Php-Gtk*2' => array(
'__callback' => 'OnOGtk'
)
)
),
s() => '-',
'Esci' => array(
'__callback' => $myApp->destroy()
)
),
'Opzioni' => array(
'Cambia Visualizzazione' => array(
'Struttura' => array(
'__callback' => 'OnStrut'
),
'Documento*2' => array(
'__callback' => 'OnDoc'
)
)
)
);
function OnDocumento(){
echo 'File->Nuovo->Documento'."n";
}

function OnPhp(){
echo 'File->Nuovo->Progetto->Php'."n";
}

function OnGtk(){
echo 'File->Nuovo->Progetto->Php-gtk'."n";
}

function OnOPhp(){
echo 'File->Apri->Documento->Php'."n";
}

function OnOGtk(){
echo 'File->Apri->Documento->Php-gtk'."n";
}

function OnStrut(){
echo 'Opzioni->Cambia Visualizzazione->Struttura'."n";
}

function OnDoc(){
echo 'Opzioni->Cambia Visualizzazione->Documento'."n";
}

$window = &new MainWindow('main_win', 'GTK-Menu by HTML.it', array(400,
250), 5, GTK_WIN_POS_CENTER);
$box = &new GtkVBox(false, 5);

$myMenuBar->Create($data);
$box->pack_start($myMenuBar->box, false, false, 0);

$window->Add($box);

$box->show();
$window->show_all();
$myApp->set_main($window);
$myApp->main_loop();
?>

Il menu contiene opzioni casuali, e serve solo come esempio per
farvi comprendere l'utilizzo degli array come struttura di base per generare
la nostra barra. L'utilizzo, dopo un po di pratica, riulterà semplice
e veloce. Procediamo con una veloce analisi del codice sorgente:

  • Le prime due righe di codice includono le librerie di base utili alla
    creazione della nostra applicazione di esempio.
  • La funzione s() è stata definita come scorciatoia per creare i
    nomi per i separatori.
  • La classe MainWindow eredita dalla classe GtkWindow
    per permetterci di generare la nostra finestra utilizzando una sola istruzione.
    L'utilità di creare una classe di base stà anche nella possibilità
    di utilizzarla in futuro per altre applicazioni, estendendola e modificandola
    in base alle nostre esigenze.
  • L'array $data rappresenta la struttura del nostro menu. Come potete notare,
    una volta creato questo array e passatolo al metodo Create, il vostro menu
    verrà creato automaticamente dalla classe, risparmiandovi molto lavoro.
    La struttura dell'array non è complessa: la chiave rappresenta il
    nome del menu, e può contenere un asterisco seguito da un numero
    in caso di omonimia tra voci; il valore può essere o un array, oppure
    un meno in caso la chiave sia un valore restituito da MenuBar::separator():
    in caso sia un array, questo può essere vuoto, contenere una sola
    chiave chiamata __callback che fa riferimento alla funzione da richiamare
    in caso di attivazione, e contenere, opzionalmente, chiavi aggiuntive rappresentanti
    la struttura del sottomenu associato all'istanza di GtkMenuItem
    corrente.
  • Vengono poi create le funzioni da richiamare in caso di selezione di una
    data voce. Queste callback stampano a video una stringa.

Eseguite lo script e dovreste ottenere un risultato simile al seguente:

Immagine dell'applicazione: una finestra stile GTK con dei menu

Conclusione

Abbiamo iniziato oggi ad addentrarci in profondità in PHP-GTK. Nei
prossimi articoli tratteremo molti altri argomenti interessanti e cominceremo
a creare le nostre prime applicazioni utili e funzionanti. Vi invito a chiedere
sul forum di HTML.it in caso di eventuali problemi, e di testare ciò
che avete imparato oggi, magari aggiungendo nuove funzionalità alle
librerie create, come per esempio la possibilità di assegnare acellerator
key ai menu, oppure la possibilità di creare voci di menù selezionabili
e non solo attivabili.

Se siete interessati ad argomenti particolari a proposito di PHP-GTK, non
tardate a comunicarcelo. Saranno trattati il prima possibile.

Vi avevo lasciati qualche giorno fa con il proposito di insegnarvi a sviluppare un semplice applicazione funzionante da applicare al mondo reale. In questi giorni sono anche da annoverare piccoli stravolgimenti nel mondo GTK che abbiamo sintetizzato alla fine di quest'articolo.

Tornando ad argomenti più pertinenti all'articolo odierno, inizieremo con il comprendere cosa sono i Dialog e come posso essere utilizzati in PHP-GTK. Ricordiamo a chi avesse perso i primi tre articoli di questa serie che stiamo scrivendo un'applicazione pratica passo passo per mostrare le possibilità di applicazione di PHP-GTK. L'aplicazione presa per esempio sarà capace di estrarre il contenuto di file compressi attraverso delle maschere di dialogo. Il tutto con PHP!.

Nell'Scrivere un'applicazione completa con PHP-GTK - I (che seguiva i due di introduzione [Introduzione a PHP-GTK e Una prima applicazione in PHP-GTK]) abbiamo visto come sviluppare il menu dell'applicazione. In questo articolo ci addentreremo nelle profondità del codice mostrando come creare le finestre di dialogo e tutto ciò che serve a rendere funzionante l'applicazione. Faremo uso di molte schermate di codice in modo da darvi l'idea, ad un solo sguardo, del cuore di un'applicazione completa.

I dialog

Un dialog non è altro che una finestra che appare in popup contenente alcuni widget predefiniti, utile allo sviluppatore per gestire messaggi d'errore, semplici avvisi da mostrare all'utente e molto altro ancora. I dialog sono creati con lo scopo di visualizzare poche informazioni, e quindi non sono adatti, date le limitazioni alle quali vanno incontro, per generare finestre ricche di contenuti. Tutto ciò che può essere fatto con un dialog lo si può fare anche tramite istanze di GtkWindow, con un po' di lavoro aggiuntivo. La libreria standard comprende alcune tipologie di dialog predefinite, che facilitano ulteriormente lo sviluppatore.

Per comprendere le procedure da seguire per lo sviluppo di un dialog, inizieremo nello svilupparne 3 tipologie che useremo nella nostra applicazione. Il primo tipo di dialog verrà utilizzato per visualizzare errori, il secondo per leggere ed elaborare semplice input dell'utente, ed il terzo per visualizzare semplici informazioni. Creiamo un file chiamato zipreader.dgtk nel quale includeremo le seguenti righe di codice:

$myApp = include_once('gtk_application.lib');
$myMenuBar = include_once('gtk_menubar.lib');
include_once('pclzip.lib.php');

class ErrorDialog extends GtkDialog{
  
  function ErrorDialog($error){
    $this->GtkDialog();
    $this->set_title("ERRORE");
    $this->set_modal(true);
    $vBox = $this->vbox;
    $aArea = $this->action_area;
    $label = &new GtkLabel(wordwrap($error, 30,"n"));
    $label->set_alignment(1.0, 1.0);
    $vBox->pack_start($label);
    $this->set_policy(true, false, false);
    $this->set_position(GTK_WIN_POS_CENTER);
    $okbt = &new GtkButton("OK");
    $aArea->pack_start($okbt, false, false, 2);
  
    $okbt->connect_object('clicked', array($this, 'destroy'));
  }
  
  function deastroy(){
    parent::destroy();
  }
}

class EntryDialog extends GtkDialog{
  
  function EntryDialog($title, $label = false){
    $this->GtkDialog();
    $this->set_title($title);
    $this->set_modal(true);
    $this->set_policy(true, false, false);
    $this->set_position(GTK_WIN_POS_CENTER);
    
    $vBox = $this->vbox;
    $aArea = $this->action_area;
    
    $e = &new GtkEntry();
    $o = &new GtkButton('OK');
    $this->entry = $e;
    if($label){
      $label_ = &new GtkLabel(wordwrap($label, 30,"n"));
      $label_->set_alignment(1.0, 1.0);
    }
    $this->okbt = $o;
    $cancelbt = &new GtkButton('ANNULLA');
    
    if($label)
      $vBox->pack_start($label_);
    $vBox->pack_start($this->entry);
    $aArea->pack_start($this->okbt, false, false, 5);
    $aArea->pack_start($cancelbt, false, false, 5);
    
    $cancelbt->connect_object('clicked', array($this, 'destroy'));
  }
  
  function destroy(){
    parent::destroy();
  }
  
  function get_path(){
    $e = $this->entry;
    $text = $e->get_text();
    if($text == '')
      $text = '.';
    if($text{strlen($text)-1} != "")
      $text .= "";
    return $text;
  }
}

class InfoDialog extends GtkDialog{
  
  function InfoDialog($info, $wrap = false){
    $this->GtkDialog();
    $this->set_title("Informazione");
    $this->set_modal(true);
    $vBox = $this->vbox;
    $aArea = $this->action_area;
    $label = &new GtkLabel(($wrap) ? wordwrap($info, 30,"n") : $info);
    $label->set_alignment(1.0, 1.0);
    $vBox->pack_start($label);
    $this->set_policy(true, false, false);
    $this->set_position(GTK_WIN_POS_CENTER);
    $okbt = &new GtkButton("OK");
    $aArea->pack_start($okbt, false, false, 2);
  
    $okbt->connect_object('clicked', array($this, 'destroy'));
  }
  
  function deastroy(){
    parent::destroy();
  }
}

Il file deve essere posizionato all'interno di una cartella contenente:

  • La libreria per decomprimere i file .zip (trovate il link nel mio ultimo articolo)
  • La libreria che abbiamo creato nell'ultimo articolo per la generazione dei menu
  • La libreria che abbiamo generato nel primo articolo,utile per creare semplicemente un'applicazione GTK;

Passiamo all'analisi del codice prentato:

  • La prima classe, ErrorDialog, estende la classe GtkDialog con il pretesto di generare un nuovo tipo di dialog capace di visualizzare un semplice messaggio di errore. Vengono utilizzati i metodi GtkWindow::set_title() e GtkWindow::set_modal() rispettivamente al fine di specificare un titolo standard per la finestra e di impedire che le altre finestre generate accettino l'input dell'utente finché la finestra di dialog è visibile. Le istanze di GtkDialog contengono due container predefiniti (accessibili tramite $istanza->vbox ed $istanza->action_area) che conterranno rispettivamente la label usata per mostrare il messaggio ed i pulsanti visualizzati. L'assegnamento dei valori di queste due proprietà a delle variabili è necessario per evitare errori. In queste due aree sono posizionate, usando le solite regole che non spiegherò, la label contenente il testo ed un pulsante OK al quale è associato il metodo destroy(): questo metodo richiama GtkObject::destroy() al fine di eliminare la finestra e restituire il focus a quella che la ha generata.
  • La seconda classe, EntryDialog, si comporta esattamente con la prima, con la differenza che, in questo caso, aggiungeremo un'istanza di GtkEntry per accettare input dall'utente ed un secondo pulsante per annullare l'operazione. Siccome il nostro dialog verrà utilizzato per permettere all'utente di inserire il path in cui vuole decomprimere il file .zip caricato, ho aggiunto una funzione get_path, che si preoccupa di restituire il contenuto della entry modificandolo se necessario. È necessario memorizzare come attributo di istanza un riferimento al bottone OK, per permettere successivamente la connessione di una callback da richiamare dopo il click su di esso.
  • L'ultima classe, InfoDialog, è fondamentalmente identica a ErrorDialog, ed è stata creata solamente per permettere delle future migliorie per quanto riguarda l'interazione con l'utente.

Come avrete potuto notare, gestire un dialog equivale a gestire una finestra, con l'unica differenza che l'istanza di GtkDialog può contenere widget posizionati solamente all'interno delle due aree predefinite.

Le tabelle

Proseguiamo velocemente il nostro tour parlando della facilità con cui GTK permette di gestire le tabelle (GtkCList). Nella nostra applicazione verranno utilizzate per visualizzare i file contenuti nel file compresso che abbiamo deciso di aprire, specificando, in ogni riga, il nome del file, le dimensioni, le dimensioni compresso, la data di ultima modifica ed il path in cui si trova.

Aggiungiamo al file zipreader.dgtk il seguente codice:

class tList extends GtkCList{
    
  function tList(){
    $this->GtkCList(5, array("Filename", "Dimensione (Kb)", "Dimensione Compresso (Kb)", "Data Ultima Modifica", "Path"));
    $this->append(array("Nessun file selezionato","","","",""));
    $this->set_selectable(0, false);    
    $this->columns_autosize();
    $this->set_selection_mode(GTK_SELECTION_EXTENDED);
    $this->connect('click_column', array('tList', 'clist_click_column'));
  }
  
  function clist_click_column($clist, $column){
    if ($column == $clist->sort_column) {
      if ($clist->sort_type == GTK_SORT_ASCENDING)
        $clist->set_sort_type(GTK_SORT_DESCENDING);
      else
        $clist->set_sort_type(GTK_SORT_ASCENDING);
    } else
      $clist->set_sort_column($column);
    $clist->sort();
  }

  function clear(){
    parent::clear();
  }

  function AddFile(&$file){
    $row = $file->set_row($this->append($file->get_array()));
    $this->set_row_data($row, $file);
  }
}

Analizziamo il codice:

  • Come sempre la nostra classe estenderà quella base, GtkCList, al fine di aggiungere funzionalità che la personalizzino e ci permettano di sfruttarla con efficienza nella nostra applicazione. Il costruttore si occupa di generare la tabella correttamente: GtkCList::GtkCList() accetta 2 argomenti: il primo è il numero di colonne che la nostra tabella conterrà, il secondo un array contenete le label da associare ad ogni colonna.
  • Il metodo GtkCList::append() permette di aggiungere una nuova riga alla tabella, specificando un array contenente i valori da associare ad ogni cella.
  • Il metodo GtkCList::set_selectable() accetta come argomenti un indice numerico che specifica il numero della riga partendo da 0, ed un secondo parametro che indica se questa può essere selezionata o meno.
  • Il metodo GtkCList::column_autosize() genera automaticamente le dimensioni delle colonne in base al contesto nelle quali si trovano ed alla lunghezza delle label a loro assegnate;
  • Il metodo GtkCList::set_selection_mode() setta il tipo di selezione che è possibile effettuare sulle righe. Per una lista completa dei valori che accetta la funzione, vi rimando al manuale.
  • GtkCList permette di controllare svariati tipi di signal: uno di questi, click_column, viene sfruttato per ordinare la colonna sulla quale l'utente ha ciccato in modo ascendente o discendente. Per fare questo utilizziamo il metodo clist_click_column, da me creato per ordinare le colonne: tramite GtkCList::sort_type e GtkCList::set_sort_type() decidiamo il tipo di sorting che dobbiamo effettuare sulla colonna (ascendente o discendente), e poi ordiniamo la colonna usando GtkClist::sort(). Come potete notare, GtkCList permette l'accesso a varie proprietà che permettono di avere un buon controllo sul widget. Purtroppo la documentazione scarseggia in proposito, e molte di queste proprietà devono essere scoperte con il tempo. In questo caso abbiamo usato GtkCList::sort_column che contiene l'indice della colonna cliccata più di recente e GtkCList::sort_type che contiene il tipo di sorting associato alla colonna cliccata.
  • Il metodo clear() sfrutta GtkClist::clear() per svuotare completamente la tabella.
  • Il metodo AddFile sfrutta una classe di cui segue il listato per aggiungere una nuova riga contenente le informazioni su un file. Utilizza GtkCList::append() per aggiungere i dati e prelevare l'indice della riga. Associa l'indice al file ed infine associa alla riga l'istanza passata per argomento, che verrà sfruttata al momento dell'estrazione dei file. GtkCList:set_row_data() permette di associare qualsiasi tipo di dato ad una riga, permettendovi di recuperarlo successivamente tramite GtkCList::get_row_data()

Aggiungete al file la seguente classe:

class myFile{
  
  function myFile(&$stored){
    $this->name = basename($stored['filename']);
    $this->size = ceil($stored['size']/1024);
    $this->csize = ceil($stored['compressed_size']/1024);
    $this->mtime = date("d/m/Y H:i", $stored['mtime']);
    $this->index = $stored['index'];
    $this->dirname = dirname($stored['filename']);
  }
  
  function get_array(){
    return array(  $this->name,
          $this->size,
          $this->csize,
          $this->mtime,
          $this->dirname
        );
  }
  
  function set_row($row){
    $this->row = $row;
    return $row;
  }
}

Questa si occupa semplicemente di memorizzare in un oggetto le informazioni riguardanti ogni singolo file presente nel file compresso, permettendovi di recuperarle sotto forma di array tramite get_array(). L'argomento $stored, passato al costruttore, è un riferimento ad una array associativo restituito dalla funzione che si occupa di estrarre le informazioni sui file compressi all'interno del file .zip selezionato. Queste informazioni sono modificate per adattarsi correttamente al contesto con semplici funzioni PHP.

Le basi per la nostra applicazione

Ora che abbiamo sviluppato le classi utili per la gestione della nostra applicazione, possiamo cominciare con la creazione delle callback che sfrutteremo per gestirla. Aggiungiamo al file sul quale stiamo operando il seguente codice:

class MainWindow extends GtkWindow{
  
  function MainWindow($name, $title, $size, $border, $position){
    $this->GtkWindow();
    $this->set_name($name);
    $this->set_title($title);
    $this->set_usize($size[0], $size[1]);
    $this->set_border_width($border);
    $this->set_position($position);
  }
  
}

//CallBacks
function OnOpen(){
  $f = &new GtkFileSelection("Seleziona un file compresso");
  $f->hide_fileop_buttons();
  $okbt = $f->ok_button;
  $canbt = $f->cancel_button;
  $okbt->connect_object("clicked", "LoadFile",$f);
  $canbt->connect_object("clicked", array($f, "destroy"));
  $f->Show();
}

function DecompressAll($location = false){
  global $current_zip;
  if(!$location){
    $current_zip->extract();
    $d = &new InfoDialog('File Decompresso con successo in '.dirname(__FILE__), true);
  }else{
    $current_zip->extract(PCLZIP_OPT_PATH, $location);
    $d = &new InfoDialog('File Decompresso con successo in '.$location, true);
  }
  $d->show_all();
}

function OnExtract(){
  global $current_zip;
  if(!$current_zip){
    $d = &new ErrorDialog("Nessun file compresso caricato");
    $d->show_all();
  }else DecompressAll();
}

function OnExtractIn(){
  global $myList, $current_zip;
  if(!$current_zip){
    $d = &new ErrorDialog("Nessun file compresso caricato");
    $d->show_all();
  }else {
    $d = &new EntryDialog("Estrai tutti in ...", "Specifica una locazione in cui decomprimere il file");
    $ok = $d->okbt;
    $ok->connect_object('clicked', 'ExtractInLocation', $d);
    $d->show_all();
  }
}

function OnExtractSelectedIn(){
  global $myList;
  $sel = $myList->selection;
  if(count($sel) > 0){
    $d = &new EntryDialog("Estrai selezionati in ...", "Specifica una locazione in cui decomprimere il file");
    $ok = $d->okbt;
    $ok->connect_object('clicked', 'ExtractSelInLocation', $d);
    $d->show_all();
  }else{
    $d = &new ErrorDialog("Nessun File Selezionato");
    $d->show_all();
  }
}

function ExtractInLocation($dialog){
  $location = $dialog->get_path();
  $dialog->destroy();
  DecompressAll($location);
}

function ExtractSelInLocation($dialog){
  global $myList;
  global $current_zip;
  $location = $dialog->get_path();
  $dialog->destroy();
  $sel = $myList->selection;
  $msg = "Decompressi con successo i seguenti file:n";
  foreach($sel as $id=>$row){
    $file = $myList->get_row_data($row);
    $current_zip->extractByIndex($file->index, PCLZIP_OPT_PATH, $location);
    $msg .= " - ".$file->name."n";
  }
  $d = &new InfoDialog($msg."nella cartella ".$location);
  $d->show_all();
}

function LoadFile($f){
  global $myList, $current_zip;

  $current_zip = new PclZip($f->get_filename());
  $f->destroy();
  if (($list = $current_zip->listContent()) == 0) {
    $d = &new ErrorDialog($current_zip->errorInfo(true));
    $d->show_all();
  }else{
    $myList->clear();
    foreach($list as $stored){
      if(!$stored['folder']){
        $file = new myFile($stored);
        $myList->AddFile($file);
      }
    }
  }  
}

La classe MainWindow serve solamente per generare la finestra principale. Analizziamo le varie callback:

  • OnOpen: non accetta argomenti, e si occupa di visualizzare un dialog tramite cui specificare il file che si desidera decomprimenre. Per fare questo sfruttiamo uno dei dialog preimpostati, GtkFileSelection: il metodo GtkFileSelection ::hide_fileop_buttons() ci permette di nascondere alcuni pulsanti opzionali (che servono per cancellare e rinominare dei file) nel widget; possiamo accedere a due attributi della classe: GtkFileSelection::ok_button contiene un riferimento al pulsante OK all'interno del dialog, al quale assoceremo una callback da richiamare, GtkFileSelection::cancel_button contiene invece un riferimento al pulsante CANCEL al quale assoceremo la funzione standard che permette di distruggere il dialog.
  • DecompressAll: accetta come argomento opzionale il path in cui vogliamo decomprimere tutto il contenuto del file .zip, e sfrutta il metodo PclZip::extract() per estrarre tutto il contenuto del file. Vi rimando alla documentazione della classe per eventuali spiegazioni a proposito dell'utilizzo di questo metodo. In base alla presenza o meno dell'attributo opzionale, visualizziamo due messaggi di informazione differenti.
  • OnExtract: ci permette di estrarre il contenuto del file compresso, visualizzando un errore nel caso nessun file sia stato selezionato.
  • OnExtractIn: ci permette di estrarre i file in una specifica locazione, letta come input attraverso un'istanza di EntryDialog.
  • OnExtractSelectedIn: ci permette di estrarre solo i file selezionati in una specifica locazione, letta sempre come input dell'utente.
  • ExtractInLocation: estrae realmente i file, prelevando il path dall'EntryDialog e distruggendolo successivamente.
  • ExtractSelInLocation: estrae realmente solo i file selezionati, prelevando il path dall'EntryDialog e distruggendolo successivamente. Accediamo alla proprietà GtkCList::selection che contiene un array associativo che rappresenta le righe selezionate dall'utente. Per ogni valore della selezione, recuperiamo l'istanza di MyFile associata ed estraiamo usando la funzione PclZip::extractByIndex() il file, aggiungendo successivamente il nome di questo al messaggio che visualizzeremo alla fine delle operazioni usando un'istanza di InfoDialog.
  • LoadFile: ci permette di caricare un file compresso: oltre le operazioni di controllo sul fatto che tutto vada correttamente, sfruttiamo il solito foreach per aggiungere una nuova riga alla tabella per ogni file trovato all'interno del file .zip. Un controllo sul valore di $stored['folder'] è d'obbligo per non aggiungere anche le directory.

Le callback, come potete notare, sono molto semplici, e permettono solamente operazioni base sui file, omettendo a volte controlli che avrebbero reso il codice troppo lungo.

La nostra applicazione funzionante

Abbiamo quasi terminato il lavoro. La struttura è stata completata, ed ora non ci resta che generare fisicamente le pagine e testare ciò che abbiamo prodotto. Aggiungiamo questo ultimo parte di codice al file zipreader.dgtk:

//Inizio
$current_zip = null;
$data = array(
    'File' => array(
        'Apri' => array(
          '__callback' => 'OnOpen'
        ),
        'Esci' => array(
          '__callback' => $myApp->destroy()
        )
        ),
    'Strumenti' => array(
        'Estrai Tutto' => array(
          '__callback' => 'OnExtract'
        ),
        'Estrai Tutto In ...' => array(
          '__callback' => 'OnExtractIn'
        ),
        'Estrai Selezionati In ...' => array(
          '__callback' => 'OnExtractSelectedIn'
        )
        )
    );

$window = &new MainWindow('main_win', 'Simple PhpZipReader by HTML.it', array(400, 250), 5, GTK_WIN_POS_CENTER);
$box = &new GtkVBox(false, 5);
$myList = &new tList;

$myMenuBar->Create($data);
$box->pack_start($myMenuBar->box, false, false, 0);
$box->pack_start($myList, true, true, 3);

$window->Add($box);

$box->show();
$window->show_all();
$myApp->set_main($window);
$myApp->main_loop();

Le operazioni che effettuiamo sono molto semplici:

  • $current_zip è una variabile globale che conterrà l'istanza corrente di PclZip;
  • generiamo una array che rappresenta la struttura del menu: vi rimando all'articolo precedente per eventuali chiarimenti;
  • generiamo i widget da inserire nella finestra principale;
  • Avviamo l'applicazione.

Se tutto va per il verso giusto, dovreste ottenere un output simile al seguente (mi scuso per la qualità scadente dell'immagine ...):

Figura 1
Una finestra di sistema con alcuni menu e le caratteristiche del file compresso

Dal menu file potrete accedere alle operazioni che consentono di aprire un file compresso e dal menu strumenti potrete accedere alle operazioni che consentono di estrarlo.

Conclusione

Con questo articolo concludiamo la nostra introduzione a PHP-GTK. Spero l'argomento vi sia interessato, e vi esorto a continuare lo studio di questa libreria che, con l'uscita della versione per PHP5, si appresta a diventare una buona alternativa per lo sviluppo di applicazione desktop in ambito multi piattaforma.

In caso di eventuali problemi, non esitate a contattarmi o a fare riferimento al forum PHP di HTML.it.

La versione attuale di PHP-GTK sembra abbastanza stabile da permettere lo sviluppo di applicazioni anche in campo commerciale, quindi non esitate a trovare il modo di sfruttare la libreria in modo da aumentare la sua importanza nel panorama dello sviluppo delle applicazioni desktop. La potete usare, ad esempio, per sviluppare applicazioni client che permettano le modifiche ad un sito internet o ad un'applicazione web sfruttando XML-RPC come tramite per le informazioni, oppure per creare applicativi client da far utilizzare all'utente per controllare le ultime informazioni importanti su un sito, oppure per visualizzare ed acquistare i prodotti di un applicativo e-commerce. Le possibilità sono infinite ovviamente.

Ultima cosa: ad inizio articolo vi avevo promesso di sintetizzarvi le novità nel mondo di sviluppo di PHP-GTK. PHP-GTK è giunto alla versione 1.0, scaricabile dal sito internet. Le modifiche apportate rispetto alla versione precedente non sono molte, e sono stati aggiunti solamente alcuni widget avanzati non implementati precedentemente. Il gruppo di sviluppo di PHP-GTK ha annunciato che non continuerà nello sviluppo della libreria (quindi non verranno create nuove versioni successive alla 1.0 e, molto probabilmente, non verrà migliorata neanche la documentazione), ma si dedicherà completamente al porting delle nuove librerie grafiche GTK2 per l'ormai imminente PHP5. la mailing list continuerà a fornire supporto allo sviluppatore anche in futuro ed ovviamente potrete far riferimento al forum di HTML.it per risolvere eventuali problemi.

Ti consigliamo anche