« Tk: comando wmAppunti di Tcl/TkTk: alcuni widget: comando menu »

Tk

la selezione

Un metodo che consente a diverse applicazioni grafiche di comunicare, attivato manualmente dall'utente, è quello ben noto della selezione e del copia/taglia e incolla.

Esistono due tipi di selezione nel sistema grafico X: la selezione primaria (PRIMARY selection) e la CLIPBOARD. Solitamente ogni volta che selezionate qualcosa trascinando il mouse e tenendo premuto il tasto numero 1 (usualmente il tasto sinistro), le informazioni vengono copiate nella selezione primaria, senza che sia necessario fare altro. A questo punto il tasto destro serve per ridimensionare la selezione (se il widget lo consente). Solitamente il tasto centrale nel sistema grafico X permette di incollare il contenuto della selezione primaria, nella stessa applicazione oppure in un'altra. Vedere a proposito il comando selection(n), sottocomando get.

Cosa succede dietro le quinte? La selezione primaria è un buffer condiviso. Quando un widget vuole permettere di selezionare i dati che contiene, deve registrare tramite Tk un gestore della selezione (selection handler), ossia una funzione C o Tcl che restituisce il valore della selezione quando questa è nel widget stesso. Questo si effettua tramite il comando selection(n), sottocomando handle. Inoltre il widget deve fornire un meccanismo all'utente per reclamare la selezionare, intercettando e gestendo opportunamente alcuni eventi della X.

Ad esempio nel caso di un widget di tipo text(n) se clicco sul tasto 1 del mouse, viene spostato il cursore di inserzione prima del carattere sotto il cursore del mouse, viene dato il focus al widget di tipo text(n) e viene annullata una eventuale selezione precedente in questo widget. Se continuo a trascinare col tasto 1 del mouse premuto, la selezione viene estesa tra la posizione del cursore e il carattere che si trova sotto il mouse. Questo è il metodo che le caselle di testo offrono per permettere all'utente di selezionare parte del loro contenuto.

Un widget per reclamare la selezione primaria deve chiamare un'opportuna procedura di Tk (Tk_OwnSelection(3)). Quest'ultima procedura dapprima notifica il widget che precedentemente aveva la selezione (che potrebbe essere nella stessa applicazione o in un'altra) del fatto che l'ha persa e poi fa sì che tutte le richieste di lettura della selezione vengano adesso indirizzate al nuovo possessore della selezione, tramite una chiamata al suo selection handler.

Utilizzando un opportuno comando, di solito attivato dall'utente manualmente nell'applicazione (tipicamente da menu alla voce Edit/Copy), il contenuto della selezione primaria può essere trasferito nella clipboard, un altro buffer condiviso. In Tcl vedere a proposito il comando clipboard(n). Lo scopo della clipboard è di conservare una particolare selezione primaria più a lungo, evitando il problema che ogni nuova selezione primaria fa perdere la precedente. Infatti mentre la selezione primaria cambia, il contenuto della clipboard rimane costante fino alla prossima operazione di copia nella clipboard stessa.

Così in un widget di tipo text(n) non potete fare una operazione di copia e incolla tramite la sola selezione primaria, perché nel momento in cui premete il tasto 1 per spostare il punto di inserzione nel punto di incollaggio, il widget effettua un clear della selezione primaria che diventa vuota ed è quindi persa. Dovete passare necessariamente per la clipboard.

Nell'editor di testi che scriveremo, avremo un menu Edit che contiene le solite voci per copiare, tagliare, cancellare, incollare. Le opzioni Copy, Cut, Delete dovranno essere abilitate solo se la selezione primaria appartiene al widget utilizzato per l'editing e la selezione stessa non è vuota (non avrebbe molto senso copiare o tagliare una stringa vuota nella clipboard, né tantomeno cancellare dal testo la selezione corrente se questa è vuota). L'opzione Paste deve essere attiva solo se la selezione della clipboard (non quella primaria) non è vuota, vale a dire se c'è qualcosa nella clipboard.

Ogni volta che il menu Edit viene srotolato, verrà chiamata la seguente procedura, che setta lo stato delle voci del menu, disabilitando quelle che non avrebbero alcun effetto come abbiamo detto. Questo non è strettamente necessario, ma rende la nostra applicazione maggiormente user-friendly:

...
menu .menubar.edit -postcommand SetState
...
proc SetState {} {
  if { [selection own] != ".text" || [catch {selection get}] } {
    .menubar.edit entryconfigure Copy -state disabled
    .menubar.edit entryconfigure Cut -state disabled
    .menubar.edit entryconfigure Delete -state disabled
  } else {
    .menubar.edit entryconfigure Copy -state normal
    .menubar.edit entryconfigure Cut -state normal
    .menubar.edit entryconfigure Delete -state normal
  }

  if { [catch {selection get -selection CLIPBOARD}] } {
    .menubar.edit entryconfigure Paste -state disabled
  } else {
    .menubar.edit entryconfigure Paste -state normal
  }
}

Notate che il comando selection(n) per default opera sulla selezione primaria, come se fosse data l'opzione -selection PRIMARY.

Sfortunatamente questo approccio ha un inconveniente, di cui mi sono accorto solo in seguito: poiché una copia di un menu, e quindi anche del menu Edit, può essere staccata (un cosiddetto tear-off, come vedremo nella prossima sezione), il settaggio delle voci non avviene su un tear-off qualora venga fatto ad ogni operazione di posting, in quanto il tear-off viene aperto una sola volta, nel momento in cui viene staccato.

È meglio quindi aggiornare lo stato delle voci ogni volta che la selezione cambia nel widget text. Esiste un evento virtuale, <<Selection>>, che viene generato proprio ogni volta che l'intervallo di definizione (range) del tag sel che rappresenta sempre la selezione corrente nel widget cambia; basta quindi associare del codice di risposta a questo evento tramite bind(n):

bind .text <<Selection>> SetState

Questa correzione tuttavia non è sufficiente, occorre anche modificare la procedura SetState, in quanto nel momento in cui viene chiamata, il selection owner e la selezione primaria della X non sono state ancora aggiornate, quindi non possiamo utilizzare all'interno di SetState i comandi selection own e selection get. Tuttavia possiamo sapere se la selezione include almeno un carattere (o addirittura l'intervallo dei caratteri selezionati, ma questo non ci è necessario qui):

...
menu .menubar.edit
...
bind .text <<Selection>> SetState
event generate .text <<Selection>>
proc SetState {} {
  if { [catch {$p.text index sel.first}] } {
    .menubar.edit entryconfigure Copy -state disabled
    .menubar.edit entryconfigure Cut -state disabled
    .menubar.edit entryconfigure Delete -state disabled
  } else {
    .menubar.edit entryconfigure Copy -state normal
    .menubar.edit entryconfigure Cut -state normal
    .menubar.edit entryconfigure Delete -state normal
  }
}

Occorre inoltre fare attenzione ad alcuni bachi che nel momento in cui scrivo esistono ancora, essendo l'evento virtuale <<Selection>> stato aggiunto da poco. In particolare nel momento in cui si cancella una selezione tramite il tasto Backspace oppure Delete, l'evento <<Selection>> non viene innescato, quindi occorre innescarlo manualmente.

Per quanto riguarda l'abilitazione/disabilitazione dell'opzione Paste occorre invece continuare ad utilizzare il metodo precedente, in quanto non si può fare altrimenti. La migliore soluzione di compromesso è dunque lasciare l'opzione Paste sempre abilitata nel caso si vogliano staccare i menù.

L'utility xclipboard(1) del sistema grafico X Window, permette di salvare i vari contenuti della clipboard (una specie di storia della clipboard), in modo che un contenuto precedente possa essere di nuovo richiamato (inserito nella clipboard). Ha inoltre alcune utili funzioni per manipolare la selection history (aggiunta, cancellazione, salvataggio su disco).

« Tk: comando wmAppunti di Tcl/TkTk: alcuni widget: comando menu »