diff options
Diffstat (limited to 'document.rest.txt')
-rw-r--r-- | document.rest.txt | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/document.rest.txt b/document.rest.txt new file mode 100644 index 0000000..8a74723 --- /dev/null +++ b/document.rest.txt @@ -0,0 +1,322 @@ +======================== + Gtk2, Perl e Drag&Drop +======================== +:Author: dakkar <dakkar@thenautilus.net> + +Il bisogno +========== + +Avevo bisogno di un modo semplice per tenere una coda di "cose da +leggere". Mi capita spesso di trovare in rete dei riferimenti +interessanti, ma di non avere tempo / modo di leggerli sul +momento. Certo, ci sono i "bookmark", ma li sento un po' troppo +permanenti (sì, uso un'applicazione web per gestirmi i bookmark in +modo centralizzato, ma quello è materiale per un altro articolo). + +Per cui ho pensato di avere un piccolo "riquadro" su cui trascinare +delle URL, e ritrovarmele in una cartella del mio account di posta, da +cui posso riprenderle con comodo, e cancellare dopo lette. + +Gli strumenti +============= + +Ovviamente il programma si scrive in Perl. + +Dovendo produrre un'applicazione grafica, io penso a gtk+ 2. Ci sono +anche tanti altri toolkit, ma a me piace gtk+ 2. Per cui ci serve +l'apposito modulo (chiamato, curiosamente, ``Gtk2``). + +Per disegnare l'interfaccia, ``glade``, e per caricarla, +``Gtk2::GladeXML::Simple``. + +Per mandare la posta, ``Email::Send``. + +Disegnare l'interfaccia +======================= + +Molti esempi di uso di gtk+ che si vedono in giro richiedono una marea +di codice per creare i vari "widget" e attaccarci le funzioni di +gestione. Troppo scomodo. + +Il sistema furbo si chiama Glade: è un programma che permette di +disegnare l'interfaccia utente, e di salvarne la descrizione in un +file di testo (permette anche di generare codice, ma in generale è una +cattiva idea, e in particolare non genera codice Perl). Inoltre, è +possibile definire quali metodi verranno invocati dai vari eventi +dell'interfaccia. + +Per cui, con un minimo di lavoro, si ottiene il file ``main.glade``, +che descrive la piccola finestra della nostra applicazione. + +.. image:: main-window.png + +.. image:: events-callbacks.png + +Programma minimale +================== + +Iniziamo col caricare l'interfaccia appena definita. Io di solito uso +un approccio a "controller": ogni finestra ha il suo controller, e il +programma principale si limita a istanziare la prima finestra. La +nostra applicazione ha una sola finestra, per cui avremo un solo +controller. + +Il programma principale, ``URLQueue.pl``:: + + use Gtk2 '-init'; + use URLQueue::MainController; + + my $main_controller=URLQueue::MainController->new(); + + $main_controller->run; + +Ovvero: carichiamo ``Gtk2``, istanziamo il controller, e passiamogli +la palla. + +Il controller, al minimo, ``URLQueue/MainController.pm``:: + + package URLQueue::MainController; + use base 'Gtk2::GladeXML::Simple'; + use Path::Class; + + sub new { + my ($class,%params)=@_; + + my $glade_file=file(__FILE__)->parent->file('main.glade'); + my $self=$class->SUPER::new($glade_file); + + return $self; + } + + 1; + +Notare la classe base: il modulo ``Gtk2::GladeXML::Simple`` ci riduce +al minimo il lavoro, preoccupandosi di istanziare l'interfaccia dal +file di Glade, e gestendo l'avvio del ciclo degli eventi. + +Per chi non lo conoscesse ancora, ``Path::Class`` è *il* modulo per +gestire i percorsi di file. Se cominciate a usarlo, non potrete più +farne a meno. + +Ah, nota importante: per ridurre la lunghezza degli esempi, non ho +scritto ``use strict;use warnings;``. Ma ci sono! Non dimenticateli +mai. + +Qualche evento +============== + +Se scrivete davvero i due file, e li eseguite (ovviamente dovete anche +mettere il file ``main.glade`` nella stessa directory del controller), +vi accorgerete che l'interfaccia funziona: potete spostare la +finestra, ridimensionarla, ridurla a icona, chiuderla (e poco altro, +visto che non abbiamo ancora scritto codice!). Vi potreste pure +accorgere che, chiudendo la finestra, il programma non termina. Cosa +sta succedendo? + +Sta succedendo che il ciclo degli eventi di gtk+ non è legato a una +finestra: per quel che lo riguarda, potreste stare aspettando un +segnale, una connessione di rete, un timeout, o qualsiasi altra +cosa. Bisogna dirgli esplicitamente di terminare! + +Per questo, in ``main.glade`` ho associato all'evento ``destroy`` +della finestra la subroutine ``quit`` (nel controller):: + + sub quit { + Gtk2->main_quit; + } + +Aggiungendo questa, la chiusura della finestra causa la terminazione +del programma. + +Drag & drop +=========== + +A questo punto possiamo cominciare con la parte interessante: gestire +il trascinamento. + +Prima, un avvertimento: la documentazione di ``Gtk2`` (il modulo Perl) +è alquanto scarna, limitandosi a un elenco di segnature di metodi e +eventi. Per capire come usarlo, si deve far riferimento alla +documentazione della libreria C di gtk+ (e gdk, e glib). Non proprio +il modo più comodo di questo mondo, ma neppure impossibile. Peraltro, +la maggior parte degli aspetti "dolorosi" dell'uso di quelle librerie +in C viene eliminata usando Perl: il modulo ``Gtk2`` è fatto *molto* +bene, e nasconde tutte le complessità sotto una serie di moduli molto +"perlici". + +Tornando al trascinamento, la nostra applicazione vuole essere +soltanto *destinazione*, non sorgente, di d&d. Questo ci semplifica la +vita. Per essere destinazione, dobbiamo: + +1) dichiarare i tipi che ci interessano +2) quando qualcuno ci trascina sopra qualcosa, decidere se la vogliamo + o no +3) quando avviene il rilascio, rechiedere i dati alla sorgente +4) quando arrivano i dati, usarli + +Notare la separazione tra gil ultimi due punti: ricevere dati è +asincrono, in quanto può richiedere parecchie operazioni e interazioni +con la sorgente, di cui fortunatamente si occupa la libreria. + +Cominciamo col dichiarare i tipi che ci interessano, nel costruttore +del controller:: + + my $target_list=Gtk2::TargetList->new(); + $target_list->add_uri_targets(1); + $target_list->add_text_targets(2); + $self->{input}->drag_dest_set('all', + [qw(default copy move link private ask)], + []); + $self->{input}->drag_dest_set_target_list($target_list); + +Qui abbiamo da notare un po' di cose. Innanzi tutto, +``$self->{input}``: il modulo ``Gtk2::GladeXML::Simple`` espone come +membri dell'oggetto tutti gli "widget" dichiarati del file +Glade. + +Dopodiché, ``Gtk2::TargetList`` definisce un insieme di tipi +accettabili: siccome non voglio preoccuparmi dei loro "veri nomi", uso +i metodi di comodo per dichiarare che mi va bene testo e URI. I +parametri che passo (1 e 2) sono dei numeri che mi serviranno per +distinguere quale tipo ho ricevuto, quando mi arriveranno i dati +davvero. + +Le ultime due chiamate associano i tipi al widget, per cui la libreria +si preoccuperà (sperabilmente) di rifiutare altri tipi. + +A questo punto scriviamo la subroutine associata al "qualcuno sta +trascinando roba sopra di me":: + + sub drag_motion { + my ($self, $widget, $context, $x, $y, $time) = @_; + + $context->status($context->suggested_action,$time); + + return 1; + } + +La chiamata a ``$context->status`` indica che accettiamo quel che +l'utente vuole passarci. Il ``return 1`` indica che abbiamo gestito +noi l'evento, e non serve passarlo al gestore predefinito. + +I parametri passati alla subroutine sono: + +``$widget``: + l'oggetto sul quale sta avvenendo il trascinamento (nel nostro caso + sarà sempre ``$self->{input}``) +``$context``: + oggetto che contiene varie informazioni per gestire il + trascinamento +``$x`` e ``$y``: + coordinate del mouse +``$time``: + momento in cui l'evento è stato generato + +Quando finalmente l'utente decide di rilasciare i dati, il file Glade +chiede di invocare questo metodo:: + + sub drag_drop { + my ($self, $widget, $context, $x, $y, $time) = @_; + + if (my $atom=$context->targets) { + $widget->drag_get_data($context, $atom, $time); + return 1; + } + + return 0; + } + +Che si legge: se riusciamo a capire che dati ci vuole dare, li +prendiamo; altrimenti lasciamo fare al gestore predefinito. + +Infine, quando riceviamo i dati:: + + sub drag_data_received { + my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_; + + if ($info==1) { + $self->handle_uris($data->get_uris) + } + elsif ($info==2) { + $self->handle_text($data->get_text); + } + else { + warn "What is $info??"; + } + + return 1; + } + +Ricordate 1 e 2, visti prima? Ora ci tornano utili: se ``$info`` è 1, +vulo dire che abbiamo ricevuto delle URI, se è 2 abbiamo ricevuto +testo. Passiamo i dati alle funzioni apposite, e abbiamo finito. + +Clipboard +========= + +Beh, avremmo finito se non volessimo gestire anche il copia & +incolla. Ma noi vogliamo, per cui dobbiamo scrivere un po' più di +codice. Non molto: solo due funzioni. + +Prima funzione: siccome il nostro widget principale è una "text entry" +(per nessun motivo particolare), può ricevere l'evento "incolla". In +quel caso facciamo questo:: + + sub paste_clipboard { + my ($self,$widget)=@_; + + my $clipboard=Gtk2::Clipboard->get(); + $clipboard->request_text(sub{$self->handle_text($_[1])}); + + return 1; + } + +Ovvero: prendiamo la "clipboard" predefinita, chiediamo il testo, e +(quando ci arriverà, è sempre asincrono) lo passiamo alla funzione +apposita. + +Seconda funzione: quando l'utente preme "tasto centrale", facciamo +questo:: + + sub button_release { + my ($self,$widget,$event)=@_; + + if ($event->button==2) { + my $clipboard=Gtk2::Clipboard->get(Gtk2::Gdk->SELECTION_PRIMARY); + $clipboard->request_text(sub{$self->handle_text($_[1])}); + + return 1; + } + + return 0; + } + +Non esiste un modo per agganciarsi al solo "tasto centrale", per cui +discriminiamo con un ``if`` apposito. Il codice è sostanzialmente +identico a prima, ma usiamo la "primary selection". Che cosa è? +Stranezze di X11, per indovinare quale clipboard usare sono andato a +tentativi. + +Usiamo i dati +============= + +Non scendo nei dettagli delle funzioni ``handle_text`` e +``handle_uris``, visto che sono lunghette e non direttamente collegate +a gtk+. Darò solo qualche cenno. + +Quando ricevo delle URI, voglio sapere a cosa puntano, e inviare un +messaggio contenente i "titoli" assieme alle URI. Per questo uso +``URI::Title``. + +Quando ricevo del testo, voglio controllare se non sia fatto di sole +URI, e se sì ricadere sul caso precedente. Per farlo uso +``URI::Find``. + +Infine, per mandare il messaggio, uso ``Email::Send``. + +Una nota: ``handle_text`` ha un controllo per evitare di spedire due +volte di fila lo stesso testo. Questo serve sia per evitarmi messaggi +duplicati se premo due volte il tasto centrale, sia soprattutto per +aggirare una stranezza di Firefox: se trascino un collegamento da +Firefox, la mia applicazione lo riceve due volte. Boh! + |