summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordakkar <dakkar@fcb26f47-9200-0410-b104-b98ab5b095f3>2007-06-05 20:32:03 +0000
committerdakkar <dakkar@fcb26f47-9200-0410-b104-b98ab5b095f3>2007-06-05 20:32:03 +0000
commit9f7b4df38c32ae5af7279235be1b2cd3a1ebc851 (patch)
treebbf764103e573dbfe875973db4515a4cc9b5a5dc
parentdirectory per l'articolo (diff)
downloadURLQueue-9f7b4df38c32ae5af7279235be1b2cd3a1ebc851.tar.gz
URLQueue-9f7b4df38c32ae5af7279235be1b2cd3a1ebc851.tar.bz2
URLQueue-9f7b4df38c32ae5af7279235be1b2cd3a1ebc851.zip
prima stesura dell'articolo
git-svn-id: svn://luxion/repos/URLQueue/article@247 fcb26f47-9200-0410-b104-b98ab5b095f3
-rw-r--r--document.rest.txt322
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!
+