sabato 11 giugno 2011

La progettazione di una base di dati (seconda parte)

La progettazione logica realizza a partire da uno schema concettuale uno schema logico, aggiungendo altri dettagli allo schema finale. Prima di effettuare la traduzione dallo schema concettuale a quello logico è necessario compiere un ulteriore passaggio, solitamente detto di ristrutturazione. Ciò è indispensabile poiché non tutti i costrutti usati dal modello ER hanno un equivalente per lo schema logico. Basti pensare al costrutto usato dal modello ER per le generalizzazioni, quest'ultimo, infatti, va ridisegnato mettendo in relazione le entità figlie con l'entità padre. Per i motivi detti sopra suddivideremo la progettazione logica in due fasi: ristrutturazione e traduzione dello schema concettuale.
Prima di analizzare in dettaglio le due fasi dette sopra occorre affrontare un importante discorso ovvero quello sulle prestazioni offerte dallo schema ER. In questa fase, infatti, vanno analizzati alcuni parametri prestazionali come l'occupazione in memoria dei dati e il costo di una operazione (inteso come numero medio di entità e associazioni che il sistema informativo necessita per realizzare una operazione). Per stimare i parametri detti sopra abbiamo bisogno di altre informazioni:
  • volume dei dati: inteso sia come numero di entità e relazioni che compongono lo schema che come dimensione (massima) di ogni attributo;
  • caratteristiche delle operazioni: richiede la suddivisione delle operazioni per tipo (operazione interattiva, operazione batch) e una stima della frequenza per le operazioni del sistema informativo (anche in relazione ai dati che essa coinvolge);
Le informazione viste sopra sono solitamente riassunte all'interno di tabelle: tavola delle operazioni e tavola dei volumi. Nella tavola delle operazioni si riporta per ogni operazione il tipo e la frequenza prevista o stimata. Nella tavola dei volumi si riporta, invece, il numero medio di partecipazioni delle occorrenze di un'entità con le occorrenze di un'associazione (questo parametro è evidentemente condizionato dalla cardinalità delle associazioni).
Per la fase di ristrutturazione occorre applicare sullo schema concettuale i seguenti passi:
  • analisi delle ridondanza: per ridondanza si intende la presenza di un dato all'interno dello schema che può essere ricavato da altri dati dello schema. Occorre analizzare gli attributi di ogni entità e stabilire se questi siano fra loro dipendenti, eliminando quindi quelli derivabili. La scelta dell'attributo da eliminare va fatto considerando le operazioni sui dati. Per scongiurare la ridondanza degli attributi vanno analizzati anche gli attributi delle entità in relazione. All'interno di uno schema possono essere presenti anche ridondanze di associazioni, l'analisi va sempre condotta considerando le tavole dette prime;
  • eliminazione delle generalizzazioni: occorre tradurre le generalizzazione effettuando a seconda dei casi l'accorpamento delle entità che vi partecipano. Le entità figlie possono essere accorpate nell'entità padre, in tal caso l'entità padre prenderà gli attributi delle entità figlie (utile quando la distinzione fra le entità figlie e padre e degli attributi non incide sul costo delle operazioni). Altra possibilità è quella di accorpare l'entità padre nelle entità figlie, in tal caso le entità figlie prenderanno gli attributi dell'entità padre e parteciperanno alle relazioni dell'entità padre (questa soluzione è possibile solo se la generalizzazione è totale, altrimenti le occorrenze dell'entità padre non sarebbero rappresentate nelle occorrenze delle entità figlie). L'ultima possibilità per eliminare le generalizzazioni prevede la sostituzione del costrutto con associazioni fra entità padre e entità figlie (da usare quando la generalizzazione non è totale e conviene tenere nello schema, a causa di alcune operazioni, tutte le entità);
  • partizionamento e/o accorpamento di entità e/o associazioni: il partizionamento di una entità è utile quando la stessa contiene troppi attributi e le operazioni accedono, solitamente, a un sottoinsieme di tali attributi (decomposizione verticale). Anche l'eliminazione di un attributo multi-valore avviene attraverso il partizionamento dell'entità. L'attributo multi-valore viene messo in una nuova entità che va quindi messa in relazione con l'entità di partenza. Se le operazioni del sistema informativo insistono su insieme di attributi appartenenti a due o più entità si può pensare a un'accorpamento degli stessi all'interno di un'unica entità;
  • scelta degli identificatori primari: ha lo scopo di stabilire le chiavi primarie per le entità dello schema. Meglio scegliere un identificatore costituito da pochi attributi, possibilmente interno all'entità;

(generalizzazione)

(soluzione 1)

(soluzione 2)

(soluzione 3)

Lo schema concettuale ristrutturato ci permette di avviare, finalmente, la traduzione verso uno schema logico equivalente. Le regole per la traduzione prevedono che per ogni entità dello schema ci sia una relazione (in questo contesto bisogno si intende per relazione la rappresentazione tabellare dei dati, organizzati dunque su righe e colonne) avente lo stesso nome, gli stessi attributi dell'entità e come chiave primaria il suo identificatore. Anche le associazioni vengono tradotte in relazioni, gli attributi e il nome della relazione saranno gli attributi e il nome dell'associazione, la chiave primaria sarà composta dagli identificatori delle entità che partecipano all'associazione.
In base alla cardinalità delle entità partecipanti a una relazione è possibile avere uno dei seguenti casi di traduzione: molti a molti, uno a molti, uno a uno. Nella traduzione molti a molti ogni entità e ogni associazione viene tradotta in una relazione. L'informazione rappresentata dal seguente schema concettuale:


può essere rappresentate dal seguente modello:

studente(matricola, nome, cognome)
esame(nome,crediti)
sessione(studente, esame, voto, data)

con i vincoli di integrità referenziale fra gli attributi studente ed esame della relazione sessione e gli attributi matricola e nome. Nella traduzione uno a molti occorre prestare attenzione alla cardinalità delle occorrenze. Se la cardinalità minima di una occorrenza è 0 possono esserci più traduzioni (la scelta ricadrà su quella che garantisce le prestazioni migliori), in tal caso la traduzione è come il caso già analizzato sopra (ci sarà, pertanto, una relazione per ogni entità o associazione). Se la partecipazione di un'entità all'associazione è obbligatoria la traduzione tenderà ad accorpare in essa anche gli attributi dell'associazione. L'informazione rappresentata dal seguente schema concettuale:


può essere rappresentata dal seguente modello:

impiegato(cf, nome, cognome, settore, data)
settore(nome, città)

con il vincolo di integrità referenziale fra l'attributo settore della relazione impiegato e l'attributo nome della relazione settore. Nella traduzione uno a uno possiamo avere due casi, anche in questo caso occorre analizzare la cardinalità minima delle singole partecipazione. Possiamo avere una partecipazione obbligatoria oppure opzionale. Per la partecipazione obbligatoria sono possibili più traduzioni del modello poiché gli attributi dell'associazione possono essere accorpati in una qualunque delle relazioni. L'informazione rappresentata dal seguente schema concettuale:


può essere rappresentata dal seguente modello:

utente(login, nome, cognome, upgrade, stats)
upgrade(codice, nome, descrizione)

oppure dal modello:

utente(login, nome. cognome)
upgrade(codice, nome, descrizione, utente, data)

Per la partecipazione opzionale va invece escluso uno dei possibili modelli (essendo la cardinalità minima di una partecipazione pari a 0). L'informazione rappresentata dal seguente schema concettuale:


può essere rappresentata dal seguente modello:

utente(login, nome, cognome)
upgrade(codice, nome, descrizione, utente, data)

In seguito analizzeremo un esempio completo di progettazione di una base di dati, dal modello concettuale al quello fisico.

mercoledì 8 giugno 2011

Arduino: leggere i valori dal microfono

Il microfono è stato uno dei primi trasduttori inventati dall'uomo, la pressione delle onde sonore che impatta sulla superficie del microfono viene trasformata in un segnale elettrico. Se avete una vecchia radio oppure un telefono cellulare da rottamare potete procurarvi un piccolo microfono a capsula! Possiamo provarne il funzionamento misurandone la resistenza interna con un multimetro, soffiando oppure parlando nel microfono la sua resistenza interna aumenterà. Il segnale generato dal microfono risulta debole e va quindi amplificato prima di poterne apprezzare i valori con Arduino. Nel datasheet del circuito integrato LM386 ho trovato lo schema di un semplice amplificatore:


Se vogliamo utilizzare questo circuito per leggere il segnale generato da un microfono dobbiamo usare l'uscita Vout del condensatore C3 come pin di ingresso per la nostra scheda Arduino. In fase di realizzazione, per favorire il setup del circuito, ho sostituito i resistori R1 ed R2 con due resistori variabili. Il condensatore C2 permette di ottenere un guadagno pari a 200 (se omesso il guadagno scende a 20).


Il led sul pin numero 13 (variabile LED) si accenderà non appena il segnale generato dal microfono supera un valore limite (variabile NOISE_MARGIN). Il segnale di input generato dal microfono (la sua versione amplificata) è mappato sul pin (analogico) numero 0 (variabile MIC) ed è aggiornato ogni 20ms circa (variabile SAMPLING). La frequenza di campionamento e la soglia minima di rumore possono essere modificate solo all'interno del codice sorgente che trovate qui. Alcune regolazioni in fase di setup possono essere fatte attraverso i resistori R1 ed R2 che regolano il volume in ingresso.
int MIC=0;
int LED=13;
int NOISE_MARGIN=489;
int SAMPLING=10;

int value=0;

void setup() {
pinMode(MIC,INPUT);
pinMode(LED,OUTPUT);
Serial.begin(9600);
}

void loop() {
value=analogRead(MIC);
if(value>NOISE_MARGIN) digitalWrite(LED,HIGH);
else digitalWrite(LED,LOW);
Serial.println(value);
delay(SAMPLING);
}
Spero di aver disegnato bene lo sketch per Arduino, se avete problemi scrivete. Ecco un video del circuito in funzione:

venerdì 3 giugno 2011

Xubuntu: risolvere i problemi con il tema Ambiance

Ambiance è il nuovo tema usato di defautl in Ubuntu 11.04 (Natty Narwhal), nulla ci vieta di usarlo anche in Xubuntu. Alcune icone, tuttavia, possono apparire in modo non uniforme al tema in questione:


Se ho capito bene il problema sembra essere causato da un uso errato dei nomi dei vari widget all'interno del file gtkrc (ogni tema ha un file gtkrc). Per risolvere questo problema date il comando sudo mousepad /usr/share/themes/Ambiance/gtk-2.0/gtkrc per editare il file detto prima, aggiungete in coda al file queste righe:
#Aggiunti da me
widget "*Panel*" style "menubar"
class "*Panel*" style "infobar"
widget_class "*Panel*" style "panel_task_button"
widget "*panel*" style "panel_task_button"
class "*panel*" style "infobar"
widget_class "*panel*" style "darkmenu"
Non dimenticate di salvare le modifiche. Riavviate il sistema operativo oppure solo il pannello, con i comandi sudo killall xfce4-panel && xfce4-panel &. Il risultato dovrebbe essere il seguente:

martedì 31 maggio 2011

La progettazione di una base di dati (prima parte)

Le applicazioni molto spesso interagiscono con i dati, manipolando l'informazione. L'informazione è l'elemento centrale di un programma e la sua progettazione deve precedere quella dell'intero sistema informativo che vi accede. Per questo motivo la progettazione di una base di dati, coerente con la realtà di riferimento descritta nelle specifiche del software, è indispensabile per la progettazione degli altri componenti. L'esistenza di modelli di riferimento per la progettazione di una base di dati ne dimostra l'importanza. Per la progettazione dei dati possiamo fare riferimento ad una consolidata metodologia che prevede le seguenti fasi:
  • progettazione concettuale;
  • progettazione logica;
  • progettazione fisica;
Ognuna di queste fasi, rispecchiando i requisiti della base di dati, realizza il modello finale per i dati del sistema informativo.
La progettazione concettuale rappresenta il livello di astrazione più alto ed ha come scopo la rappresentazione dei requisiti della base di dati, il prodotto finale di questa fase è chiamato schema concettuale. In questa fase il progettista non deve mai considerare gli aspetti implementativi della base di dati e l'efficienza dello schema concettuale ottenuto (tutte le osservazioni e le modifiche del modello avvengono nei successivi livelli di astrazione). Lo schema concettuale è un'alternativa alla scrittura dei requisiti funzionali della base di dati. La sua semplicità, infatti, ne permette l'uso anche all'interno della documentazione del sistema informativo.
La progettazione logica prevede la traduzione dello schema concettuale (ottenuto nella fase precedente) in schema logico. In questo livello di astrazione, per ottimizzare lo schema logico vengono considerati i vincoli e le operazioni sui dati che il sistema informativo dovrà compiere sugli stessi. Il modello logico più usato in questa fase della progettazione è il modello relazionale, che si basa sull'algebra relazionale e sul concetto di relazione. Tale modello, come vedremo in seguito, prima di essere tradotto in schema logico necessita di un ulteriore processo di ristrutturazione!
La progettazione fisica genera a partire dallo schema logico uno schema fisico, ottenuto considerando adesso le specifiche sui dati, il database scelto e le ottimizzazioni sullo stesso per migliorare le operazioni che il sistema informativo eseguirà in futuro (questo potrebbe significare anche la modifica dello schema logico).
Il modello Entità-Relazione (entity-relationship) è un modello concettuale per l'astrazione dei dati di un sistema informativo. Viene di solito abbreviato con la sigla E-R, la cui vera traduzione è Entità-Associazione. Questo modello offre al progettista una serie di utili costrutti grafici che, miscelati tra loro, permettono di generare un modello concettuale dei dati. Questi i principali costrutti:

Entità
L'entità modella un insieme omogeneo di dati, rappresenta una classe di oggetti che ha motivo di esistere all'interno del modello poiché è indispensabile per modellare una porzione della realtà di riferimento. Va comunque detto che un entità è anche indipendente ed autonoma ai fini dell'applicazioni, può cioè fornire informazioni anche senza interagire con altri componenti del modello. Il costrutto grafico usato per l'entità prevede un rettangolo che racchiude il nome (univoco all'interno dello schema) dell'entità. Alcuni esempi di entità, validi all'interno di realtà di riferimento assegnate, potrebbero essere le entità: impiegato, studente, città, per modellare l'occorrenza di tali oggetti nel sistema informativo.


Relazione
Una relazione rappresenta il legame logico esistente fra due o più entità della base di dati. Una relazione fra due entità è detta binaria, altrimenti è n-aria (fra n entità). Esistono anche relazioni ricorsive! Il costrutto grafico usato per la relazione prevede un rombo che racchiude il nome (univoco all'interno dello schema) della relazione alla quale afferiscono le entità che vi prendono parte. E' preferibile scegliere come nome di una relazione un sostantivo piuttosto che un verbo, in maniera tale da non far intendere il verso della relazione.
E' opportuno osservare che l'insieme delle occorrenze di una relazione corrisponde al prodotto cartesiano delle occorrenze delle entità che vi prendono parte. Pertanto, fra le occorrenze di una relazione del modello E-R non possono esserci, come vedremo meglio in seguito, duplicati! Questa osservazione ha un impatto molto importante sul modello poiché non permette di rappresentare alcuni eventi. Ad esempio, la relazione binaria esame collega logicamente le entità studente e corso:


Poiché le occorrenze di una relazione sono univoche, in questo modello non è possibile rappresentare i vari tentativi che uno studente compie per superare l'esame di un corso. Se vogliamo rappresentare questi eventi all'interno della base di dati dobbiamo allora modificare il modello in questo modo (usando una relazione n-aria):


Attributi
Gli attributi descrivono le proprietà delle entità o delle relazioni che sono utili ai fini dell'applicazione. Ad esempio, per l'entità studente potrebbero interessare gli attributi: matricola, nome, cognome e data di nascita. Per la relazione esame, invece, potrebbero interessare gli attributi relativi al voto conseguito e alla data dell'esame. L'attributo, quindi, specifica per ogni occorrenza di una entità o relazione i valori che tale occorrenza può assumere (il dominio). Tutti gli oggetti della stessa entità o relazione hanno gli stessi attributi.
Il costrutto grafico usato per la rappresentazione degli attributi prevede un piccolo cerchio vuoto, collegato all'entità o alla relazione mediante una linea, caratterizzato nelle immediate vicinanze da un nome (univoco all'interno dello schema) che identifica la proprietà dell'occorrenza. In alcuni casi gli attributi possono essere raggruppati per formare attributi di tipo composto, come avviene in questo esempio:


Cardinalità delle relazioni e degli attributi
Specificano, per le relazioni, il numero minimo e massimo di occorrenze per le entità che partecipano all'associazione. Per la cardinalità minima i possibili valori possono essere 0 o 1. Se la cardinalità minima per un'occorrenza è 0 la partecipazione alla relazione è detta opzionale. Se 1, invece, è detta obbligatoria. Per la cardinalità massima i possibili valori possono essere 1 o N (molti, più di uno). Se la cardinalità massima e quella minima è 1 si dice che la relazione realizza una funzione, associa cioè a un'occorrenza di un'entità una sola occorrenza delle altre entità della relazione. Le relazioni binarie (fra due entità) vengono talvolta ricordate considerando le cardinalità massime delle entità partecipanti, questi i possibili casi:
  • relazione uno a uno;
  • relazione uno a molti;
  • relazione molti a molti;
Seguono alcuni esempi:


Nella relazione uno a uno: un impiegato può avere al massimo un solo telefono aziendale (la cardinalità minima, in questo caso pari a 0, indica che un impiegato può anche non avere un telefono aziendale). Dall'altro lato della relazione: un telefono aziendale può essere assegnato a un solo impiegato.
Nella relazione uno a molti: un impiegato risiede in una sola città (occorrenza obbligatoria per il modello di dati analizzato). Dall'altro lato della relazione: una città può avere N impiegati ivi residenti (data la cardinalità minima della relazione, una città potrebbe anche non aver nessun impiegato!).
Nella relazione molti a molti: un impiegato può ricevere N incarichi di progetto (data la cardinalità minima della relazione possiamo anche dire che ne riceve almeno uno). Dall'altro lato della relazione: un progetto può essere assegnato ad N impiegati (oppure, vista la cardinalità minima, non è ancora stato assegnato).
Anche un attributo può avere una cardinalità minima e massima. L'attributo ha una cardinalità minima che può valere 0 (attributo opzionale) oppure 1 (attributo obbligatorio). Se la cardinalità massima di un attributo è pari ad N l'attributo è allora detto multi-valore. Se l'attributo ha cardinalità (1,1) la notazione viene omessa dallo schermo (per non appesantirne il disegno).


Identificatori delle entità
Ogni occorrenza di una entità deve essere sempre distinguibile dalle altre, per questo motivo il modello E-R offre un ulteriore costrutto per l'identificazione. L'identificatore di un occorrenza è in molti casi una proprietà dell'entità. Per segnare tale proprietà come identificatore dell'occorrenza occorre annerire il cerchio della proprietà che afferisce all'entità analizzata, come in questo esempio:


In alcune realtà di riferimento il modello dei dati potrebbe suggerire come identificatore un insieme di più attributi, il formalismo grafico da usare in questo caso è il seguente:


In altri contesti, infine, gli attributi interni non sono sufficienti a identificare univocamente un'occorrenza. In tal caso si userà come identificatore un insieme di attributi interni e un insieme di attributi di entità esterne (attributo esterno). In questo esempio:


l'identificatore è composto dall'attributo matricola della entità studente e dall'attributo nome dell'entità università. Ciò potrebbe essere sufficientemente reale se si pensa ad esempio che due studenti iscritti a due università differenti possano poi avere lo stesso numero di matricole! In questo modello l'uso dell'attributo esterno è dunque indispensabile!

Generalizzazioni
Attraverso questo costrutto possiamo distinguere due o più entità che differiscono fra loro per la presenza o l'assenza di uno o più attributi. In altre parole con questo costrutto possiamo generalizzare un entità padre in una o più entità figlie. Le entità figlie ereditano gli attributi dell'entità padre, oltre ad avere i propri attributi. Ad esempio, l'entità uomo o donna potrebbe essere una generalizzazione dell'entità persona. Ogni occorrenza dell'entità figlia è anche una occorrenza dell'entità figlia. Le generalizzazioni vengono rappresentate graficamente da una freccia che che unisce le entità figlie all'entità padre, la freccia punta all'entità padre. Se la freccia è piena la generalizzazione si dice totale: ogni occorrenza della classe padre è una occorrenza di almeno una delle entità figlie, in caso contrario la generalizzazione è detta parziale. Se la freccia non è piena la generalizzazione si dice esclusiva: ogni occorrenza della classe padre è al più una occorrenza di una delle entità figlie, in caso contrario è detta sovrapposta.


Adesso siamo in grado di comprendere il seguente modello concettuale:

giovedì 26 maggio 2011

Android: esecuzione di file audio e video

L'esecuzione di file audio e video all'interno di un'applicazione Android avviene attraverso la classe MediaPlayer. Le risorse da riprodurre vanno messe nella cartella res/raw del progetto. Il file può essere letto eventualmente dal filesystem (ad esempio sulla scheda di memoria, referenziato dal suo path) oppure da un flusso di rete (in tal caso sarà referenziato dall'url per la risorsa). Android permette la riproduzione dei principali formati audio e video, alcuni di questi sono: mp3, ogg e flac per i file audio, 3gp ed mp4 per i file video. I protocolli supportati per i flussi di rete sono: http e rtsp. L'audio viene riprodotto nell'altoparlante del dispositivo, dalla documentazione leggo che non è possibile eseguire file audio all'interno di una conversazione telefonica (se ricordo bene esiste un'applicazione che riesce a farlo comunque).
Dopo aver aggiunto al progetto i file audio e/o video da riprodurre (nella cartella res/raw) è possibile avviarne la riproduzione attraverso i seguenti passi:
  • creare un'istanza della classe MediaPlayer, l'oggetto ottenuto verrà usato in seguito per avviare o fermare la riproduzione. Il costruttore della classe istanzia un oggetto vuoto, se abbiamo un riferimento alla risorse da riprodurre è preferibile usare il metodo create(context,resource) della classe MediaPlayer che accetta due parametri: l'oggetto Context da usare e il riferimento alla risorsa. Tale riferimento può essere fornito attraverso l'id (univoco) associato alla risorsa (nella classe R), che corrisponde al nome del file nella cartella res/raw, oppure attraverso l'uri (uniform resource identifier). Vedremo in seguito come riprodurre file audio e/o video da flussi di rete o da filesystem;
  • l'oggetto creato è in grado di riprodurre la risorsa all'interno dell'applicazione Android. I metodi start() e stop(), rispettivamente, avviano e fermano la riproduzione della risorsa. Attenzione, se la riproduzione della risorsa viene fermata con il metodo stop() non possiamo, poi, riavviarla! In tal caso andranno invocati prima i metodi reset() e prepare();
MediaPlayer sound=MediaPlayer.create(getBaseContext(),R.raw.sound);
sound.start();
Particolarmente utile è a mio avviso il metodo isPlaying(). Questo metodo, se invocato su un oggetto MediaPlayer, ritorna un valore booleano: true se l'oggetto MediaPlayer è in esecuzione, false altrimenti. Usatelo, ad esempio, se non volete sovrapporre l'esecuzione di due file audio!
MediaPlayer sound1=MediaPlayer.create(getBaseContext(),R.raw.sound1);
MediaPlayer sound2=MediaPlayer.create(getBaseContext(),R.raw.sound2);

public void playSound1() {
if (!sound2.isPlaying()) sound1.start();
}

public void playSound2() {
if (!sound1.isPlaying()) sound2.start();
}
Immaginate di avviare la riproduzione di un file audio o video all'interno di un'applicazione, dopo averla utilizzata per un po decidete di uscire dall'applicazione, premendo il tasto back. La riproduzione del file continuerà anche mettendo in background l'applicazione! Lo scenario che ho appena descritto può andar bene per alcune applicazioni, per altre è preferibile terminare la riproduzione non appena l'utente mette l'applicazione in background. In tal caso non dimenticate di mettere una chiamata al metodo stop() sull'oggetto MediaPlayer che sta eseguendo il file audio nel metodo di callback pause().
Altri metodi particolarmente utili sono setLooping(boolean loop), che permette di abilitare la riproduzione continua della risorsa referenziata nell'oggetto MediaPlayer, e seekTo(int ms), che sposta la riproduzione a ms millisecondi dall'inizio.
La riproduzione da un file del filesystem o da un flusso di rete può avvenire sia attraverso il metodo create() visto sopra che attraverso il metodo setDataSource(). In quest'ultimo caso, dopo aver istanziato un oggetto MediaPlayer vuoto (senza riferimento a una risorsa) occorrerà invocare il metodo setDataSource(string resource) per caratterizzare con una stringa il path o l'uri della risorsa da riprodurre. Esistono altri metodi setDataSource(), quello appena indicato è sicuramente più immediato. Con questo metodo è possibile cambiare la risorsa referenziata dall'oggetto MediaPlayer, anche se assegnata in prima battuta con il metodo create(). In questo modo possiamo riciclare un oggetto MediaPlayer per eseguire prima un suono e successivamente un altro. Ogni volta che usiamo il metodo setDataSource() dobbiamo necessariamente invocare il metodo prepare().
MediaPlayer sounds=new MediaPlayer()
String path="SOME_PATH_HERE";
String file="FILE_NAME_HERE";

public void playSound1() {
if (!sounds.isPlaying()) {
try {
sounds.setDataSource(path+"/"+file);
sounds.prepare();
}
catch (IllegalArgumentException e) {
sounds=MediaPlayer.create(getBaseContext(),R.raw.default1);
}
catch (IOException e) {
sounds=MediaPlayer.create(getBaseContext(),R.raw.default1);
}
sounds.start();
}
}

public void playSound2() {
if (!sounds.isPlaying()) {
try {
sounds.setDataSource(path+"/"+file);
sounds.prepare();
}
catch (IllegalArgumentException e) {
sounds=MediaPlayer.create(getBaseContext(),R.raw.default2);
}
catch (IOException e) {
sounds=MediaPlayer.create(getBaseContext(),R.raw.default2);
}
sounds.start();
}
}
Il metodo setDataSource() può lanciare un IOExcpetion (se il file non viene trovato al path/uri indicato) e un IllegalArgumentException (se al metodo vengono passati degli argomenti non corretti).

Xubuntu: avviare le applicazioni lontane dai bordi


Ho notato che le finestre delle applicazioni in Xfce vengono disposte sempre lungo i bordi dello schermo, a partire dal vertice in alto a sinistra del monitor. Se eseguiamo più volte la stessa applicazione, ad esempio Thunar o Terminal, il gestore delle finestre (xfwm) cercherà di non sovrapporle, disponendole sempre lungo i bordi dello schermo. Molte applicazioni accettano in fase di avvio il parametro --geometry e consentono di specificare la dimensione della finestra e le coordinate da assegnare al primo vertice della finestra stessa (quello in alto a sinistra). Per Terminal, ad esempio, la sintassi da usare è: --geometry N_COLxN_RIG+X+Y, dove: COL e RIG sono, rispettivamente, il numero di colonne e di righe della finestra, mentre X ed Y sono le coordinate del primo vertice della finestra.
Se volete avviare la finestra di un'applicazione tenendola lontana dai bordi dello schermo non dimenticate di dare uno sguardo alla documentazione in linea dell'applicazione (man eseguibile_applicazione). Cercate, se presente, una descrizione della sintassi per l'opzione geometry. Nell'immagine sopra, ad esempio, ho avviato Terminal in questo modo: xfce4-terminal --geometry 60x15+150+50. In questo modo la finestra del terminale avrà 60 colonne e 15 righe, il primo vertice della finestra ha coordinate 150,50 (attenzione, l'asse delle ordinate è invertito).

mercoledì 25 maggio 2011

Android: si gonfia la rete!


I marcatori 2010/2011 del Napoli gridati da Raffaele Auriemma, direttamente sul vostro dispositivo Android. All'interno dell'applicazione trovate anche una traccia speciale, qui il codice sorgente. Potete installare l'applicazione sul vostro dispositivo Android attraverso il seguente codice QR:

qrcode


martedì 24 maggio 2011

Script per shell - funzioni...

Se nella scrittura di uno script vi accorgete di usare in più punti lo stesso set di istruzioni già scritto in precedente è arrivato il momento di scrivere una funzione e richiamare la stessa laddove occorre! Il codice sotteso a una funzione viene richiamato nello script facendo riferimento al nome dato alla funzione. La sintassi da seguire è la seguente:
function nome_funzione() {
...
}
La definizione di una funzione va fatta all'inizio dello script o comunque prima del suo impiego nello script. Per una funzione le variabili $1, $2, etc... non identificano gli argomenti passati allo script ma quelli passati alla funzione stessa! Una funzione ritorna al chiamante il suo codice di stato attraverso l'istruzione return (se presente). Una funzione, infine, può chiamare al suo interno altre funzioni, come nell'esempio:
#!/bin/bash
#Uso di funzioni...

range_time=4

function ping() {
echo "ping..."
wait_time=`expr $RANDOM % $range_time`
sleep $wait_time
pong
}

function pong() {
echo "pong..."
wait_time=`expr $RANDOM % $range_time`
sleep $wait_time
ping
}

#Pronti per il loop...
ping
Con $RANDOM viene generato un numero intero casuale, variabile da 0 a 32767. Nello script, usando il valore generato da $RANDOM come argomento di sleep, viene limitato l'intervallo attraverso l'operatore % (che ne fissa i valori nell'intervallo 0,$range_time-1).

lunedì 23 maggio 2011

Script per shell - until...

Verifica la condizione dopo la prima iterazione, il set di comandi verrà pertanto eseguito almeno una volta. Questa la sintassi:
until [ condizione ]
do
...
done
Esempio:
#!/bin/bash
#Ciclo until...
answer=""
until [ ! $answer -eq "2" ]
do
echo -e "Chiudere il programma?\\n1. si;\\n2. no;\\n"
read answer
done

Script per shell - while...

Verifica la condizione prima di ogni iterazione, segue questa sintassi:
while [ condizione ]
do
...
done
Esempio:
#!/bin/bash
#Ciclo while...
counter=10
while [ $counter -ne 0 ]
do
if [ `expr $counter % 2` -eq 0 ]
then
echo "$counter è un numero pari!"
else
echo "$counter è un numero dispari!"
fi
counter=`expr $counter - 1`
done

domenica 22 maggio 2011

Script per shell - for...

A volte occorre iterare uno o più comandi finché una condizione non si esaurisce, è questo il caso del ciclo for la cui sintassi è riportati qui di seguito:
for valore in [lista_di_valori]
do
...
done
Riprendiamo l'esempio visto in precedenza, quello che si occupa della rimozione di un file. Aggiungeremo un controllo sui parametri passati allo script in maniera tale da cancellarli tutti (lo script visto considerava solo il primo parametro!):
#!/bin/bash
#Ciclo for...
if [ $# -gt 0 ]
then
for file in $*
do
if [ -e $file ]
then
echo -e "Vuoi cancellare il file $file?\\n1. si;\\n2. no;"
read answer
case $answer in
1)
rm $file
echo "Ho cancellato il file $file!"
;;
2)
echo "Il file $file non è stato cancellato!"
;;
*)
echo "Il file $file non è stato cancellato!"
;;
esac
else
echo "Il file $file non esiste!"
fi
continue
done
else
echo "Indicare i nomi dei file da cancellare!"
fi

Script per shell - case...

La struttura di controllo case effettua un solo test sul valore di una variabile per determinare quale caso applicare (se previsto) fra quelli presenti nello script. Ogni caso è individuato da un etichetta che coincide con il valore eventualmente testato. All'interno di un caso è possibile, quindi, inserire le istruzioni relative al valore intercettato. I comandi di un caso previsto terminano con i caratteri ;;, è possibile descrivere un caso di default (seguito dalla struttura di controllo se nessun valore intercettato rientra fra quelli previsti) attraverso l'etichetta *). La sintassi è la seguente:
case $valore in
caso1)
...
;;
caso2)
...
;;
...
*)
...
;;
esac
Esempio:
#!/bin/bash
#Uso di case
if [ $# -eq 1 ]
then
if [ -e $1 ]
then
echo -e "Cancellare il file $1?\\n1. si;\\n2. no;"
read answer
case $answer in
1)
rm $1
echo "Ho cancellato il file $1!"
;;
2)
echo "Il file $1 non è stato cancellato!"
;;
*)
echo "Il file $1 non è stato cancellato!"
;;
esac
else
echo "Il file $1 non esiste!"
fi
else
echo "Indicare il nome del file!"
fi

sabato 21 maggio 2011

Script per shell - if... then...

Si tratta della classica struttura di controllo. L'esito del test sulla condizione data, che segue la parola chiave if, farà eseguire un preciso insieme di istruzioni. La condizione da valutare va racchiusa all'interno di parentesi quadre, le istruzioni subito dopo la parola chiave then verranno eseguite solo se la condizione nell'if è vera. In caso contrario, se presente, verrà eseguito l'altro set di istruzioni, quello che segue la parola chiave else. La sintassi è la seguente:
if [ condizione ]
then
...
fi
Esempio:
#!/bin/bash
#If... then...
echo -n "Inserisci il primo numero: "
read a
echo -n "Inserisci il secondo numero: "
read b
if [ ${a} -gt ${b} ]
then
echo "$a è maggiore di $b"
else
echo "$a è minore di $b"
fi
Notare la raccolta dell'input con il comando read, che scrive nella variabile il contenuto nel buffer dello standard input (la tastiera). Lo script non analizza tuttavia tutti i casi! Provate, ad esempio, a dare come input due numeri uguali. L'output coinciderà con l'ultima istruzione echo! E' possibile annidare più condizioni:
#!/bin/bash
#If... then...
echo -n "Inserisci il primo numero: "
read a
echo -n "Inserisci il secondo numero: "
read b
if [ ${a} -eq ${b} ]
then
echo "$a è uguale a $b"
else
if [ ${a} -gt ${b} ]
then
echo "$a è maggiore di $b"
else
echo "$a è minore di $b"
fi
fi
Qui, invece, un esempio che effettua un test su file:
#!/bin/bash
#Test su file
if [ $# -eq 1 ]
then
if [ -e $1 ]
then
echo "Il file $1 esiste!"
else
echo "Il file $1 non esiste!"
fi
else
echo "Indicare un nome di file!"
fi

Script per shell - operatori

Prima di dedicare un po di tempo ai costrutti messi a disposizioni dal linguaggio di scripting per il controllo del flusso delle istruzioni è opportuno analizzare gli operatori che è possibile applicare alle variabili. Iniziamo con gli operatori aritmetici. In tal caso è possibile applicare una delle sintassi utilizzate nell'esempio:
#!/bin/bash
A=10
B=5
ris1=$((A+B))
ris2=`expr $A + $B`
let "ris3=A+B"
echo "$A + $B = "$ris1
echo "$A + $B = "$ris2
echo "$A + $B = "$ris3
Questi gli operatori aritmetici che è possibile usare in uno script: + (per la somma), - (per la differenza), \* (per la moltiplicazione), / (per la divisione), ** (per l'elevamento a potenza) e % (per il resto della divisione). Esistono, poi, gli operatori di confronto, riassunti qui di seguito: -eq (uguale a), -ne (non uguale a), -lt (minore di), -gt (maggiore di), -le (minore o uguale di), -ge (maggiore o uguale di). Applicheremo in seguito questi operatori come condizione delle strutture di controllo (ad esempio if... then).
Un'altra famiglia di operatori particolarmente utile è quella per la verifica di file. All'interno di uno script, infatti, può capitare di dover stabilire la presenza di un file (o directory) prima di eseguire un set di istruzioni. Questi i principali operatori per i test su file: -e nome_file, per stabilire se il file esiste; -s nome_file, per stabilire se il file è vuoto; -d nome_file, per stabilire se il file è una directory; -r nome_file, per stabilire se il file ha i permessi in lettura; -w nome_file, per stabilire se il file ha i permessi in scrittura; -x nome_file, per stabilire se il file ha i permessi per l'esecuzione.
Il confronto fra stringhe avviene grazie agli operatori di stringhe: stringa1=stringa2, per stabilire se due stringhe sono uguali (può essere usato anche ==); stringa1!=stringa2, per stabilire se due stringhe sono diverse; -z stringa, per stabilire se la stringa è nulla (non contiene caratteri, è vuota). Quando descriviamo una condizione, possiamo combinare l'esito di più confronti legandoli logicamente con gli operatori logici: espressione1 -a espressione2, per l'and logico (ritorna 0 se è vera sia espressione1 che espressione2, è possibile usare &&); espressione2 -o espressione2, per l'or logico (ritorna 0 se è vera espressione1, oppure espressione2, oppure entrambe, è possibile usare ||).
Nei prossimi articoli vedremo da vicino le strutture di controllo e utilizzeremo gli operatori visti sopra.

venerdì 20 maggio 2011

Script per shell - variabili

Uno script shell permette la definizione di variabili, richiamabili all'interno dello script attraverso il carattere $. L'assegnazione di un valore a una variabile deve rispettare la seguente sintassi: nome_variabile=valore (notare l'assenza di caratteri di spazio).
#!/bin/bash
# Uso di variabili
testo="Data di oggi:"
data=$(date)
echo $testo $data
echo "Output: $testo $data"
Una variabile può contenere un valore (stringhe di caratteri o numeri) oppure l'esito di un comando. L'assegnazione di una stringa a una variabile va quotata con le virgolette, come nell'esempio. L'assegnazione dell'output di un comando a una variabile avviene, invece, attraverso la sintassi $(comando). Per fare riferimento al valore contenuto nella variabile, infine, usare la sintassi $nome_variabile. Ricordate, le variabili in uno script non sono tipizzate. L'ultima riga dello script ci permette di osservare la sostituzione operata dal comando echo con i rispettivi valori delle variabili $testo e $data: se nel testo racchiuso dalle doppie virgolette compare uno o più riferimenti a variabili questi verranno sostituiti dai rispettivi valori.
All'interno di uno script possiamo fare riferimento a due tipi di variabili: variabili locali o variabili d'ambiente (o di sistema). Le variabili locali sono visibili solo all'interno dello script e vengono dichiarate con la sintassi vista sopra. Le variabili d'ambiente sono, invece, disponibili a tutti gli script poiché passate dalla shell al processo che esegue lo script.
Alcune di queste sono ad esempio: $HOME, che contiene la stringa con il path della directory di home; $PWD, che contiene la stringa con il path della directory corrente; $USERNAME, che contiene la stringa del nome utente, etc...
#!/bin/bash
#Uso di variabili d'ambiente
echo -e "Directory corrente: "$PWD
echo -e "Directory home: "$HOME
echo -e "Username: "$USERNAME
L'utente può passare allo script altre variabili (argomenti) facendo seguire al nome dello script i valori da passare, ogni valore è separato dal successivo da un carattere di spazio. Con $# accediamo al numero di argomenti passati allo script, con $n (con n numero intero compreso nell'intervallo 0,9) accediamo al singolo argomento passato. Poiché il conteggio degli argomenti passati allo script inizia da 0 e poiché il primo argomento contiene sempre il nome del file ($0) che esegue lo script, il primo argomento utile per lo script (se esiste) si troverà in $1, il secondo in $2, etc...
#!/bin/bash
#Argomenti passati allo script
echo "Nome del file che esegue lo script: "$0
echo "Argomenti passati allo script: "$#
echo "Lista degli argomenti passati: "$*
echo "PID: "$$
exit 0
Altra variabile d'ambiente particolarmente utilizzata è $?, che rappresenta il valore tornato (exit status) dall'ultimo comando. Uno script può tornare un valore di stato attraverso l'istruzione exit codice_di_stato. La variabile d'ambiente $? accede al codice di stato tornato dall'ultimo comando. Provate a dare il comando $? dopo un comando, oppure dopo l'esecuzione dello script sopra. Ogni comando, infatti, torna in caso di successo il valore 0, in caso di errore un valore diverso da zero (che in molti casi identifica la tipologia dell'errore). La variabile d'ambiente $@, a differenza di $*, torna tutti gli argomenti passati allo script quotati.

Script per shell - introduzione

L'uso dei comandi Linux può essere di notevole aiuto nella gestione del sistema operativo. Talvolta, poi, in una sessione al terminale, occorre combinare l'esito di uno o più comandi e applicare sull'output prodotto una logica, iterare nuovamente comandi etc... Attraverso la scrittura di uno script possiamo automatizzare l'esecuzione di un set di comandi! Per la scrittura di script possiamo utilizzare qualunque editor di testo, al file scritto occorre dare i permessi per l'esecuzione. Supponiamo ad esempio di aver scritto il nostro primo script e di averlo chiamato myscript, i permessi per l'esecuzione verranno assegnati al file dal comando chmod +x myscript. E' possibile assegnare a un file i permessi per l'esecuzione anche attraverso il comando chmod 755 myscript. Trovate qui altre informazioni sul comando chmod (vi ricordo che a uno script occorre, ancora prima del permesso di esecuzione, il permesso di lettura!).
La prima riga di uno script indica al sistema operativo l'interprete dei comandi, inizia con i caratteri #! ed è seguita dal path all'interprete dei comandi. Ad esempio: #!/bin/sh identifica l'interprete dei comandi per sistemi operativi Unix (detta Bourne shell, da Stephen Bourne), #!/bin/bash è la shell del progetto GNU/Linux, disponibile anche per altri sistemi operativi. Per evitare errori verificate sempre il path verso l'interprete dei comandi. Se siete curiosi di sapere quante shell sono note al vostro sistema operativo vi invito a digitare il comando cat /etc/shells.
Le righe dello script che iniziano con il carattere # non verranno interpretate e costituiscono dunque delle righe di commento per le istruzioni dello script (oppure disabilitano l'interpretazione di un comando). Un commento può essere aggiunto anche dopo un comando, separando il comando e il commento con un carattere di spazio.
Nel primo script che segue viene mostrato l'uso di un particolare comando, il comando echo. Useremo molte volte questo comando per la presentazione dell'output nella finestra del terminale.
#!/bin/bash
#Primo script, utilizzo del comando echo
echo Questo è il mio primo script.
Per l'output di alcuni caratteri speciali e importante adoperare le sequenze di escape, abilitate nel comando echo attraverso l'opzione -e.
#!/bin/bash
#Primo script, utilizzo del comando echo
echo -e Questo è il mio primo script.\\nCiao!

martedì 17 maggio 2011

Xubuntu 11.04: consigli per aspire one 110

Il 28 Aprile 2011 è stata rilasciata (in concomitanza con Ubuntu) l'ultima versione di Xubuntu, 11.04 Natty Narwhal. Xfce, l'ambiente desktop usato di default in Xubuntu, sempre veloce e leggero, è stato aggiornato all'ultima versione (4.8).
Sto provando da un paio di giorni questa versione sul mio Aspire One (110L), vi riassumo i problemi incontrati prima e dopo l'installazione, segnando per alcuni di questi le soluzioni che finora ho trovato.
Iniziamo dall'installazione, l'immagine trasferita con UNetbootin, dal file iso scaricato qui alla pendrive-usb, presenta il solito errore: No init found. Try passing init=bootarg. Ho aggirato questo problema creando l'immagine su pendrive-usb con il tool che trovate in Applicazioni>Sistema>Creatore dischi di avvio. L'installazione richiede circa 25 minuti. La dotazione software è come sempre orientata verso programmi leggeri ma comunque efficienti. Per l'ufficio disponiamo delle ultime versioni di: AbiWord (editor di testi), Gnumeric (fogli di calcolo) ed Evince (lettore di file pdf). Nella sezione dedicata a Internet troviamo installati: Firefox (il noto browser), Thunderbird (client per la posta elettronica), Pidgin (client per la chat istantanea, supporta numerosi protocolli), Xchat (client per irc) e il client per torrent. Nella sezione dedicata ai file multimediale Gmucibrowser è il nuovo lettore audio (ora installato di default al posto di Exaile), completano la sezione Xfburn (per la copia di dati su cd/dvd) e Parole Media Player (player di file audio e video). Nella sezione dedicata alla grafica troviamo come sempre: Gimp (per l'editing), l'ottimo Ristretto (visualizzatore di immagini) e Simple Scan (per l'acquisizione di immagini da scanner).
I tempi di avvio e spegnimento del sistema operativo sono rimasti invariati, il sistema operativo appare reattivo ai comandi e piacevole nell'utilizzo. Con la nuova versione di Xfce possiamo modificare il menu delle applicazioni attraverso uno dei tanti editor di menu disponibili nei repository di Ubuntu, come ad esempio Alacarte (sudo apt-get install alacarte). L'hardware dell'Aspire One è supportato bene anche in questa versione: il wifi viene riconosciuto e configurato bene in fase di installazione, nessun problema per la scheda video e l'audio, dei due lettori di schede SD funziona solo quello a sinistra. Quest'ultimo problema è ormai noto, vedremo a breve come risolverlo.
Con Xfce 4.8 possiamo dare ai pannelli un livello di trasparenza. Vanno tuttavia risolti alcuni bug relativi ai plugin usati nei pannelli, per alcuni di questi non sono riuscito a regolarne la trasparenza e il colore di background (mi riferisco a Generic Monitor, il plugin che permette di aggiungere nel pannello script personalizzati, e Indicator Plugin).
La gestione della ventola è affidata anche in questa versione al modulo acerhdf. Dopo qualche minuto la ventola rimane sempre accesa! Colpa dei valori usati di default dal modulo in questione che ne comanda l'accensione non appena vengono superati i 30 °C! Per risolvere questo problema (la batteria del nostro Aspire One ci ringrazierà) occorre creare il seguente file di configurazione con: sudo mousepad /etc/modprobe.d/acerhdf.conf e scrivere questa riga per le opzioni: options acerhdf kernelmode=1 interval=10 fanon=55000 fanoff=50000 verbose=0 (non dimenticate di salvare il file). Aprite il seguente file con sudo mousepad /etc/modules e aggiungete su una nuova riga (subito sotto lp): acerhdf (non dimenticate di salvare il file).
Se avete installato Xubuntu 11.04 su filesystem ext4 (più efficiente di ext2) vi consiglio di disabilitare la funzione di journaling (come già descritto qui). Avviate la live di Xubuntu, aprite una finestra di terminale e date il seguente comando: sudo tune2fs -O ^has_journal /dev/sda1 (sostituite, eventualmente, l'etichetta sda1 con quella del vostro dispositivo!). Altra ottimizzazione da fare è quella che riguarda le fasi di scrittura e lettura, per un disco SSD, è inutile ordinarle dal momento che queste richiedono lo stesso tempo di accesso! Potete applicare il consiglio già scritto qui.
Veniamo adesso ai lettori di schede SD, anche in questo caso la storia si ripete. Il lettore a destra funziona solo se è presente una scheda SD in fase di avvio. Ho risolto questo problema in questo modo: aprite una finestra del terminale e date il comando sudo mousepad /etc/modules, aggiungete su una nuova riga la stringa acpiphp (il nome del modulo da caricare), non dimenticate di salvare il file. Aprite il file usato da grub all'avvio con il comando sudo mousepad /etc/default/grub e aggiungete alla riga GRUB_CMDLINE_LINUX_DEFAULT la stringa pciehp.pciehp_force=1. Ad esempio, sul mio Aspire One, nel file in questione, ho questa riga: GRUB_CMDLINE_LINUX_DEFAULT="quiet splash elevator=noop rootfstype=ext4 pciehp.pciehp_force=1" (notare anche la presenza delle altre opzioni dette prima), non dimenticate come sempre di salvare il file. Ogni modifica fatta al file grub deve essere seguita dal comando sudo update-grub (fatelo!).


Sul mio netbook ho rivisto e organizzato lo spazio del desktop in modo differente da quello proposto di default (pannello a scomparsa e desktop libero per le finestre delle applicazioni).

venerdì 22 aprile 2011

Android, menu per le applicazioni (GList v.2)

Tutti i dispositivi per Android dispongono di un tasto per richiamare il menu dell'applicazione in esecuzione. I menu permettono all'utente di interagire con l'applicazione e di modificarne il comportamento. Non vi è alcun dubbio, quindi, che costituiscono una parte dell'applicazione che in molti casi è indispensabile! Non possiamo, infatti, appesantire l'interfaccia grafica di un'activity con bottoni utili al setup dell'applicazione. Meglio includere gli stessi all'interno di un menu. Tale menu, ovviamente, è disponibile all'utente solo se previsto dal programmatore, vediamo come crearne uno.
Android definisce per le applicazioni tre tipi di menu:
  • menu delle opzioni: menu accessibile premendo l'apposito tasto sul dispositivo;
  • menu per la scelta rapida: menu accessibile attraverso il tocco su una view (come ad esempio per l'elemento di una lista);
  • sottomenu: menu che raccoglie sotto un unico gruppo una lista di opzioni, condensate all'interno di un'unica voce;
Android separa il codice per l'istanza del menu da quello per la gestione degli eventi.
Per l'istanza di un menu Android richiede al programmatore la descrizione dello stesso all'interno di una risorsa xml e la sovrascrittura di alcuni metodi dell'activity. La descrizione del menu avviene attraverso i seguenti tag xml:
  • menu: è l'elemento radice che descrive il menu, il contenitore;
  • item: permette, in fase di inflating, l'istanza di oggetti MenuItem (interfaccia del package android.view). Questo elemento può a sua volta contenere altri elementi menu per descrivere, quindi, sottomenu. Le proprietà android:id, android:icon e android:title permettono di specificare, rispettivamente: l'id della voce del menu, il riferimento a una risorsa drawable e il testo per la voce del menu;
  • group: è un contenitore (invisibile) di elementi item (gli oggetti del menu), permette di raggruppare vari item sotto un'unica categoria (eventualmente da abilitare o disabilitare);
Il tag menu richiede come attributo il nome dello schema xml (attributo xmlns:android), che per Android è http://schemas.android.com/apk/res/android. L'elemento item può caratterizzare l'oggetto istanziato attraverso molti attributi, alcuni di questi sono: android:titleCondensed, per assegnare all'icona un testo più breve (Android userà questo testo qualora quello assegnato di default, con l'attributo visto prima, risulti lungo e quindi illeggibile); android:onClick, per collegare l'evento generato dalla selezione di un bottone a un metodo presente nell'activity (il metodo deve ricevere come argomento un oggetto MenuItem); android:visibile, specifica con un valore logico la visibilità dell'item; android:enabled, specifica con un valore logico se l'item è abilitato. Gli attributi visti per item sono validi anche per gli elementi group del menu. Ovviamente essendo group un elemento che fa da contenitore non prevede l'attributo android:onClick. Per questo elemento è invece valido l'attributo android:checkableBehavior, che permette di specificare il comportamento di un gruppo di item attraverso le costanti: none (nessuna voce del gruppo può essere selezionato), all (tutti le voci del gruppo possono essere selezionati) e single (una sola voce del gruppo può essere selezionato).
Il menu per le opzioni dell'applicazione viene istanziato una sola volta, non appena l'utente preme il bottone sul dispositivo. Il metodo dell'activity che viene invocato in corrispondenza di questo evento è public boolean onCreateOptionsMenu(Menu menu). Pertanto, se vogliamo dotare la nostra activity di un comodo menu dobbiamo intervenire in questo punto!
L'activity della nostra applicazione eredita tale metodo che va dunque riscritto, occorre passare all'oggetto che istanzia il menu un riferimento alla risorsa xml che lo descrive. La classe MenuInflater, del package android.MenuInflater, è usata da Android per istanziare un oggetto Menu a partire da una risorsa xml. E' possibile ottenere un riferimento a questo oggetto attraverso il metodo getMenuInflater() (della classe Activity). Infine, per caratterizzare il menu con la risorsa xml che lo descrive passiamo al metodo inflate() i riferimenti alla stessa e all'oggetto menu del metodo. Ecco un esempio:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater=getMenuInflater();
inflater.inflate(R.menu.options,menu);
return true;
}
Il metodo ritorna true se il menu deve essere visibile all'utente, false in caso contrario. Per la getsione degli eventi collegati a un menu Android usa il metodo public boolean onOptionsItmSelected(MenuItem item). Al metodo viene passato l'id dell'item selezionato dall'utente, il codice dovrà dunque riconoscere tale id e chiamare, a seconda dei casi, un metodo dell'activity per la gestione della selezione. Trattandosi di un metodo ereditato dalla classe Activity va quindi riscritto. Ecco un esempio:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.option1:
method1();
return true;
case R.id.option2:
method2();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
Ogni caso previsto dalla struttura di controllo ritorna true per indicare al chiamante (l'activity dell'applicazione in esecuzione) la gestione dell'opzione selezionata. Il caso usato di default passa l'item selezionato alla superclasse che, non sapendo gestire alcuna opzione, ritorna sempre false! L'attributo android:onClick permette di specificare durante la scrittura del file xml il metodo da invocare alla pressione dell'item selezionato. Se questo attributo è presente nella risorsa xml, Android delega la gestione dell'evento al metodo indicato dalla stessa. In altre parole, l'attributo android:onClick ha la precedenza sul metodo onOptionsItemSelected() nella gestione degli eventi legati a una voce di un menu. Tale attributo, tuttavia, è valido solo per API di livello 11 e successive versioni. Qui trovate un esempio di menu per le opzioni. Segue l'immagine del menu aggiunto all'applicazione:


Android permette di disporre nel menu delle opzioni le prime sei voci selezionabili, se il menu ne prevede più di sei l'ultima icona, allora, permetterà l'accesso alle altre opzioni non visibili. Ecco come Android dispone gli item di un menu con più di sei opzioni:



Il menu per la scelta rapida è il menu che Android mostra quando l'utente preme a lungo (non più di 2 secondi) su un item o una view. Per assegnare un menu per la scelta rapida a un oggetto View è necessario registrare l'oggetto al menu, con il metodo registerForContextMenu(). Al metodo va ovviamente passato il riferimento dell'oggetto View da registrare. L'istanza del menu avviene sovrascrivendo il metodo public void onCreateContextMenu(), per la gestione degli eventi il metodo da riscrivere è invece public boolean onContextItemSelected(). Per l'istanza del menu possiamo fare riferimento a questo pezzo di codice:
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater=getMenuInflater();
inflater.inflate(R.menu.context_menu, menu);
}
Anche in questo caso l'obiettivo è ottenere un riferimento all'oggetto che istanzia il menu (metodo getMenuInflater()) e passare a quest'ultimo un riferimento alla risorsa xml che descrive il menu (con il metodo inflate()). Il metodo prevede come argomenti un oggetto ContextMenu (il menu per la scelta rapida che verrà istanziato), il riferimento all'oggetto View che dovrà ricevere un menu per la scelta rapida e un oggetto ContextMenuInfo (con informazioni aggiuntive sull'oggetto View).
Quando l'utente esegue una scelta all'interno di un menu per la scelta rapida viene invocato il metodo public boolean onContextItemSelected(MenuItem item), il cui codice è molto simile al metodo onOptionItemSelected() già visto in precedenza.
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo info=(AdapterContextMenuInfo)item.getMenuInfo();
switch(item.getItemId()){
case R.id.option1:
method1();
return true;
case R.id.option2:
method2();
return true;
default:
return super.onContextItemSelected(itm);
}
}
Qui trovate un esempio di menu per la scelta rapida. L'immagine che segue mostra la comparsa del menu dopo la pressione di un elemento della lista:


Quando il menu per la scelta rapida contiene troppe opzioni è allora utile organizzare le stesse opzioni all'interno di un sottomenu, possibilmente raggruppandole (con il tag xml group). La realizzazione di un sottomenu è davvero molto semplice, è sufficiente aggiungere all'interno della risorsa xml che descrive il menu per la scelta rapida un nuovo elemento menu con altre opzioni. Le voci aggiunte all'interno del sottomenu saranno poi accessibili tramite l'opzione del menu per la scelta rapida che le contiene.


Come potete notare dalle immagini mostrate sopra non è possibile aggiungere un'icona all'interno di un menu per la scelta rapida (Android ignora il riferimento alla risorsa drawable eventualmente passata con l'attributo android:icon).
Vi invito a vedere il codice sorgente per il menu di scelta rapida, all'interno dei sottomenu ho avuto alcuni problemi nella gestione degli elementi della lista. Il metodo getMenuInfo() ritorna, infatti, un valore nullo per un item che appartiene a un sottomenu. Non ne sono sicuro ma dovrebbe trattarsi di un bug, possiamo comunque rimediare salvando il riferimento tornato dal metodo getMenuInfo() in una variabile di appoggio e usare la stessa all'interno della porzione di codice per il sottomenu.


Se volete assegnare al menu per la scelta rapida un titolo usate all'interno del codice Java il metodo setHeaderTitle(String title) (non sono riuscito a trovare nella documentazione un attributo per il tag xml), su un oggetto Menu. In realtà Android offre numerosi metodi per generare menu e sottomenu attraverso il codice Java. Questi metodi possono essere utili anche a run-time, per modificare ad esempio il menu dell'applicazione dopo qualche modifica ai dati, li trovate all'interno della documentazione. Potete installare l'applicazione GList (v.2) sul vostro dispositivo attraverso il seguente codice QR:

qrcode

Cosa ho messo nella lista questa volta? Facilissimo, l'occorrente per fare gli gnocchi alla sorrentina!

sabato 16 aprile 2011

Un cestino per rm

Il comando rm (come già detto qui) non usa un cestino per i file cancellati e va usato sempre con cautela. Vi propongo un piccolissimo script che aggiunge un nuovo comando alla vostra shell, l'ho chiamato srm e sta per secure rm. Il comando crea nella home dell'utente un cestino per i file cancellati con il comando srm, potete crearlo aprendo l'editor con il comando sudo mousepad /usr/bin/srm e scrivendo:
#!/bin/bash
TRASH=$HOME/Trash
mkdir $TRASH &> /dev/null
while [ ! -z "$1" ]
do
mv "$1" $TRASH
shift
done
Per poter eseguire i comandi dovete assegnare i permessi con sudo chmod +x /usr/bin/srm. Lo script provvede a spostare i file nel cestino (la cartella Trash/) finché esiste ancora un argomento passato al comando (il path o il nome di un file), quindi incrementa l'indice al prossimo argomento e ripete la condizione prevista dal ciclo. Qualcuno potrebbe pensare: perché non creare un alias per rm, ad esempio con il comando alias rm='srm'?
La risposta è semplice, se create l'alias detto sopra poi non potete più cancellare i file nel cestino con il comando rm (che altrimenti invocherebbe se stesso e quindi lo script visto sopra, generando un errore). Pertanto, se decidete di usare questa soluzione ricordatevi di cancellare i file con il nuovo comando srm.
Dimenticavo, se avete creato l'alias per il comando rm potete annullarne gli effetti con il comando unalias rm.

venerdì 15 aprile 2011

Conky, widget orizzontale


Lo spazio sul desktop di un netbook è sempre poco e in questi giorni sto sperimentando nuove soluzioni. L'ultima versione di Xfce è davvero funzionale ed elegante, ho disposto tutti i collegamenti su un unico pannello a scomparsa. Le informazioni sulle risorse di sistema, invece, sono descritte da uno script per Conky che riporto qui di seguito (file /etc/conky/conky.conf):
background yes
use_xft yes
xftfont DejaVu Sans Mono:size=8
xftalpha 0.8
out_to_console no
update_interval 5.0
total_run_times 0
draw_shades no
own_window yes
own_window_type override
own_window_transparent yes
double_buffer yes
default_color 555755
color1 grey
alignment top_middle
gap_x 0
gap_y 2
#no_buffers yes
use_spacer yes

TEXT
${image /opt/conky/cpu.png -p 0,0}${image /opt/conky/ram.png -p 150,0}${image /opt/conky/hdd.png -p 305,0}${image /opt/conky/swap.png -p 535,0}${image /opt/conky/temperature.png -p 768,0}
${goto 40}${color}cpu: ${color1}${cpu cpu}% ${cpubar 5,50 cpu}${goto 192}${color}ram: ${color1}$memperc% ${membar 5,50}${color}${goto 348}ssd: ${color1}${fs_used /}/${fs_size /} ${fs_bar 5,50 /}${color}${goto 578}swap: ${color1}${swap}/${swapmax} ${swapbar 5,50}${color}${goto 810}temperature: ${color1}${exec tempcpu}${goto 150}
Aprite il file conky.conf e copiate le istruzioni riportate sopra (sudo mousepad /etc/conky/conky.conf). Prestate attenzione, il widget descritto nello script si sviluppa su due sole righe (una per le icone e una per il testo). Le icone usate nello script vanno in /opt/conky/, le aggiungo qui di seguito:


Questi, dall'alto verso il basso, i nomi dati alle icone: cpu.png, hdd.png, ram.png, swap.png e temperature.png (i nomi dopo il download dovrebbero essere già quelli detti sopra).
Per leggere la temperatura del processore ho invece scritto questo piccolo script per shell (create il file per lo script con sudo mousepad /usr/bin/tempcpu):
#!/bin/sh
temp=$(cat /sys/class/thermal/thermal_zone0/temp)
unit=1000
temperature=$(($temp/$unit))
echo ${temperature}C°
Assegnate allo script i permessi per l'esecuzione, con sudo chmod +x /usr/bin/tempcpu. All'interno di Conky lo script viene eseguito dall'istruzione ${exec tempcpu}. Il valore della temperatura viene letto al path indicato all'interno dello script dal comando cat, che sul mio Aspire One è scritto dal modulo acerhfd. Infine, per dare modo ad Xfce di caricare il desktop e il pannello inferiore, ritardo l'avvio di Conky attraverso questo semplice script (create il file per lo script con sudo mousepad /usr/bin/execconky.sh):
#!/bin/sh
sleep 15
exec conky
Per l'esecuzione automatica di Conky richiamo lo script detto sopra (non dimenticate di assegnare, anche in questo caso, i permessi per l'esecuzione dello script, con sudo chmod +x /usr/bin/execconky.sh) all'interno della scheda Avvio automatico della finestra Sessione e avvio del menu Impostazioni di Xfce (Menu di Xfce > Impostazioni > Gestore delle impostazioni > Sessione e avvio > scheda Avvio automatico, bottone Add). Alla riga Comando della finestra Aggiungi applicazione va dunque scritto execconky.sh.