MaCocoa 067

Capitolo 067 - Un pannello delle preferenze

L'applicazione yahFortune ha poche possibilità di personalizzazione. Le aggiungo introducendo un elemento delle Preferenze di Sistema, oltre che a perfezionare il tutto con molte altre funzionalità.

Sorgenti: documentazione Apple, e poi fortune nelle varie accezioni

Prima stesura: 26 dicembre 2004.

Aggiornamento fortune

La prima cosa che faccio è di aggiornare o allineare la versione di fortune con il nuovo rilascio italiano e con la versione BSD del tutto. In termini pratici, preleto da Sunsite o da uno dei suoi mirror i sorgenti più o meno ufficiali di fortune:

ftp://sunsite.unc.edu/pub/Linux/games/amusements/fortune/fortune-mod*

Io ho trovato il file fortune-mod-9708 che risale appunto all'agosto 97 (ma che è comunque l'ultima disponibile). Rispetto alla versione utilizzata in precedenza, c'è un file in meno di cui tenere conto. Sostituisco i file precedenti con questi nuovi e, già che ci sono, faccio alcune modifiche cosmetiche in modo da eliminare alcuni warning di compilazione (sono un po' fanatico al riguardo). Sostanzialmente, ho introdotto qui e lì i prototipi delle funzioni. Più importante invece il fatto che i path dove rintracciare i file fortune sono adesso incorporati nel programma (e non in un file header separato) tramite due #define:

#define    FORTDIR    "./fortunes"
#define    OFFDIR     "./fortunes/off"

La directory off contiene i file di fortune dal contenuto così detto offensivo, che trattano di religione, sesso, minoranze, eccetera, non sempre in maniera garbata (è una delle peculiarità del politicamente corretto proveniente dagli stati uniti...). Per evitare che qualche utente si senta turbato, sono inseriti in file a parte, ed occorre attivamente spiegare al programma che si intendono ricevere fortune anche da questi file.

Più interessante invece è l'incorporazione all'interno dell'applicazione di tutti i fortune in lingua italiana, prelevati dal sito ufficiale fortune-it. In questo modo l'applicazione conta su una nutrita serie di fortune già incorporati nell'applicazione stessa, e non ha bisogno di file esterni.

Le preferenze

Dicevo che l'applicazione è piuttosto spartana e che non permette nessuna personalizzazione. Avevo davanti a me due strade: rendere l'applicazione uguale a tutte le altre (ricordo che adesso è una applicazione background only, che non ha menu e non compare nel dock), dotandola di un menu di preferenze; oppure, imparare qualcosa di nuovo, estendendo l'applicazione Preferenze di Sistema con un nuovo panello per impostare alcuni valori di personalizzazione. Dato lo scopo di questo sito, è ovvia la scelta della seconda alternativa, anche se probabilmente non si tratta dell'opzione più economica, felice e facile.

figura 06

figura 06

Costruire un pannello delle preferenze da aggiungere all'applicazione Preferenze di Sistema è tutto sommato piuttosto facile. Occorre preparare un bundle che abbia l'estensione prefPane, e che la classe principale sia una sottoclasse di NSPreferencePane. Occorre includere esplicitamente il framework PreferencePanes.framework, dal momento che non è incorporato di default nelle applicazioni Cocoa.

figura 07

figura 07

In effetti io mi sono complicato la vita in quanto ho voluto mantenere l'applicazione YahFortune ed il pannello delle preferenze all'interno dello stesso progetto, in qualità di nuovo target. Se avessi optato per un diverso progetto, tra le varie opzioni fornite da XCode c'è proprio Preference Pane, che predispone un template adatto alla bisogna. A partire da questo template, le cose sono piuttosto semplici: c'è un nib dove disegnare l'interfaccia utente, e una classe corrispondente per controllare le varie opzioni. Nel mio caso, invece, ho dovuto costruire un nuovo target (ormai è il quinto all'interno di uno stesso progetto), al quale riportare i nuovi file di codice, nib, ed un nuovo e diverso file Info.plist (che non posso usare quello dell'applicazione, visto il diverso utilizzo). Infine, un nuovo eseguibile, l'applicazione Preferenze di Sistema, per poter lanciare e debuggare il pannello direttamente all'interno di XCode.

figura 08

figura 08

Tenendo presente il solito paradigma MVC, la sottoclasse di NSPreferencePane funziona da controller. Ovviamente i controlli presenti nel nib sono gli oggetti view; la parte relativa al modello dei dati è il file delle preferenze, all'interno del quale sono appunti riportati i valori correnti di alcune grandezze. Passando attraverso la classe controller, le grandezze sono visualizzate in forma acconcia all'utente, in modo che possa eventualmente modificarle. Perché la cosa funzioni correttamente, il file delle preferenze deve essere condiviso tra il pannello stesso e l'applicazione. In questo modo l'applicazione accede (tipicamente in sola lettura) al file per leggerne il contenuto, mentre il pannello nelle Preferenze di Sistema permette di modificarle.

Dicevo che ho preferito inserire il pannello all'interno dello stesso progetto utilizzato per la costruzione dell'applicazione. Così facendo, riesco a tenere meglio sotto controllo le interazioni tra le due entità, ed in particolare l'accesso al file delle preferenze. Anzi, ho fatto in modo che il codice di accesso al file fosse sostanzialmente lo stesso, estraendo il tutto in un file condiviso tra il pannello e l'applicazione. Descrivo il file e lo header relativo, in modo da illustrare anche le varie grandezze (che fanno riferimento a nuove o diverse caratteristiche dell'applicazione).

Ho costruito una funzione per accedere al file delle preferenze ed inserire il tutto all'interno di un dizionario:

NSMutableDictionary *
getPrefFromFile ( )
{
    NSUserDefaults            * theDefs = [NSUserDefaults standardUserDefaults];
    NSMutableDictionary        * thePrefs = [ NSMutableDictionary dictionaryWithCapacity: 1 ] ;
    NSMutableDictionary        * currPrefs = [ NSMutableDictionary dictionaryWithCapacity: 1 ] ;
    // recupero le preferenze e le inserisco in un dizionario
    [ thePrefs addEntriesFromDictionary: [ theDefs persistentDomainForName: PFC_yahf_prefFileName ] ];
    // se uno dei valori non c'e', non c'e' il file
    if ( [ [ thePrefs valueForKey: PFC_yahf_version ]intValue] <= 0 )
    {
        // ed allora lo costruisco con i valori di default
        [ currPrefs addEntriesFromDictionary: defaultPrefValues( ) ];
        // e lo scrivo per la prima volta
        [ theDefs setPersistentDomain: currPrefs forName: PFC_yahf_prefFileName ];
        [ theDefs synchronize ];
    }
    else
    {
        // preparo un dizionario con i valori estratti
        [ currPrefs addEntriesFromDictionary: thePrefs ];
    }
    // restituisco comunque il dizionario coi valori delle preferenze
    return ( currPrefs ) ;
}

Qui ci sono un paio di concetti notevoli. Il primo concetto è l'accesso al file delle preferenze avviene, per una applicazione in maniera piuttosto trasparente, utilizzando la classe NSUserDefaults ed i suoi metodi. Il nome del file da utilizzare è ottenuto dal file Info.plist (che però dovrà essere diverso nei due casi). Tutto funziona finché si parla di applicazioni, ma in un caso ho da trattare un pannello delle preferenze, che utilizza di default un altro file (il file delle preferenze di sistema, ovviamente). Indico quindi esplicitamente il nome del file (quasi...) atttraverso la define PFC_yahf_prefFileName, che sta per la stringa it.macocoa.djzero00.yahFortune.

Utilizzo la stessa tecnica del capitolo 64 per verificare l'esistenza effettiva del file delle preferenze, controllando la presenza della grandezza version. Se assente, il file non esiste e lo devo costruire per la prima volta. Allo scopo, c'è una nuova funzione che produce un dizionario con i valori di default:

NSMutableDictionary *
defaultPrefValues ( )
{
    NSMutableDictionary     * np = [ NSMutableDictionary dictionaryWithCapacity: 9 ];
    NSFont                 * df = [ NSFont fontWithName: @"Georgia" size: 12 ] ;
    // versione
    [ np setObject: [ NSNumber numberWithInt: 1] forKey: PFC_yahf_version ] ;
    // font
    [ np setObject: [ df fontName] forKey: PFC_yahf_fontName ] ;
    [ np setObject: [ NSNumber numberWithFloat: [ df pointSize]] forKey: PFC_yahf_fontSize ] ;
    // fading
    [ np setValue: [ NSNumber numberWithFloat: 0.1F ] forKey: PFC_yahf_fadeInSpeed ] ;
    [ np setValue: [ NSNumber numberWithFloat: 0.1F ] forKey: PFC_yahf_fadeOutSpeed ] ;
    // cycleTime
    [ np setValue: [ NSNumber numberWithFloat: 60.0F ] forKey: PFC_yahf_cycleTime ] ;
    [ np setValue: [ NSNumber numberWithBool: YES ] forKey: PFC_yahf_timeBehaviour ] ;
    // dimensioni della finestra, limiti dei tempi
    [ np setValue: [ NSNumber numberWithFloat: 400.0F ] forKey: PFC_yahf_winMinWidth ] ;
    [ np setValue: [ NSNumber numberWithFloat: 160.0F ] forKey: PFC_yahf_winMinHeight ] ;
    [ np setValue: [ NSNumber numberWithFloat: 2.0F ] forKey: PFC_yahf_maxFadeIn ] ;
    [ np setValue: [ NSNumber numberWithFloat: 2.0F ] forKey: PFC_yahf_maxFadeOut ] ;
    [ np setValue: [ NSNumber numberWithFloat: 20.0F ] forKey: PFC_yahf_minCycleTime ] ;
    [ np setValue: [ NSNumber numberWithFloat: 300.0F ] forKey: PFC_yahf_maxCycleTime ] ;
    // colori
    [ np setObject: [ NSArchiver archivedDataWithRootObject: [ NSColor blackColor ] ] forKey: PFC_yahf_fgColor ];
    [ np setObject: [ NSArchiver archivedDataWithRootObject: [ NSColor whiteColor ] ] forKey: PFC_yahf_bgColor ];
    // gestione dei file di fortune
    [ np setValue: [ NSNumber numberWithBool: NO ] forKey: PFC_yahf_equalProb ] ;
    [ np setValue: [ NSNumber numberWithInt: PFC_OU_NORMAL ] forKey: PFC_yahf_useOffensive ] ;
    [ np setValue: [ NSNumber numberWithInt: 120 ] forKey: PFC_yahf_lenghtLimit ] ;
    [ np setValue: [ NSNumber numberWithInt: PFC_LO_NOLENGHTLIMIT ] forKey: PFC_yahf_lenghtOption ] ;
    [ np setValue: [ NSNumber numberWithFloat: 20.0F ] forKey: PFC_yahf_minFortuneLen ] ;
    [ np setValue: [ NSNumber numberWithFloat: 1000.0F ] forKey: PFC_yahf_maxFortuneLen ] ;
    // all'inizio non ci sono file aggiuntivi
    return ( np );
}

Ho introdotto una gran quantità di preferenze, ma sono tutte piuttosto semplici da spiegare. Ci sono preferenze che determinano carattere e dimensione del testo con cui è visualizzato il fortune; preferenze per determinare il tempo di fade in apertura e chiusura; ho introdotto la possibilità che la finestra rimanga sempre aperta e cambi il fortune ad intervalli di tempo fissati, oppure che si chiuda dopo un certo tempo. Ho introdotto all'interno del file le dimensioni minime della finestre e valori estremali per i tempi; c'è la possibilità di determinare il colore del testo e dello sfondo della finestra, si può personalizzare il tipo di fortune impartendo alcune direttive specifiche al comando (uso o meno di fortune offensivi, messaggi di lunghezza specifica, maggiore o minore di un certo numero di caratteri). Ho previsto, ma non ancora realizzato, la possibilità di utilizzare file esterni all'applicazione come sorgenti di fortune (sarà per un prossimo capitolo).

La nuova YahFortune

L'introduzione delle preferenze ha ovviamente e pesantemente modificato l'applicazione YahFortune. Ecco le modifiche principali.

Il metodo init si è molto semplificato, dato l'utilizzo delle funzioni viste sopra:

- (id)
init
{
    self = [super init];
    if ( self )
    {
        NSFont        * tmpFont ;
        // ricavo le preferenze dal file e le inserisco in un dizionario
        [ self setCurrPrefs: getPrefFromFile( ) ] ;
        tmpFont = [ NSFont    fontWithName: [ currPrefs valueForKey: PFC_yahf_fontName ]
                            size: [ [ currPrefs valueForKey: PFC_yahf_fontSize ] intValue] ] ;
        // costruisco un dizionario per mettere dentro il font
        curStyle = [ [NSMutableDictionary alloc] initWithCapacity:1] ;
        [ curStyle setObject: tmpFont forKey: NSFontAttributeName];
        [ curStyle retain ] ;
        // inizializzo le altre variabili
        currentFade = 0 ;
        // predispongo un fortune
        [ self setLastFortune: [ self makeAFortune ] ];    
    }
    return self;
}

Ho eliminato la maggior parte delle variabili d'istanza, rimpiazzate con l'accesso diretto all'unica variabile currPrefs, un dizionario che le contiene tutte. In questo modo cambiano, ma solo per l'aspetto gli altri metodi.

- (void)
awakeFromNib
{
    NSColor    * tmpcolor ;
    // per fare in modo che la finestra sia comunque
    // davanti a tutte le altre, anche se l'applicazione
    // non ha menu e non e' presente in dock
    [ NSApp activateIgnoringOtherApps:YES];
    // la rendo invisibile
    [ theWindow setAlphaValue: currentFade ];
    // la porto davanti a tutti
    [ theWindow makeKeyAndOrderFront: self ];
    // imposto il font del campo
    [ theText setFont: [ curStyle objectForKey:NSFontAttributeName ] ];    
    // colore del font
    tmpcolor = [ NSUnarchiver unarchiveObjectWithData:
            [ currPrefs valueForKey: PFC_yahf_fgColor ] ] ;
    [ theText setTextColor: tmpcolor ];
    [ theStaticText setTextColor: tmpcolor ];
    // colore dello sfondo
    tmpcolor = [ NSUnarchiver unarchiveObjectWithData:
            [ currPrefs valueForKey: PFC_yahf_bgColor ] ] ;
    [ theWindow setBackgroundColor: tmpcolor ];
    // ridimensiono e centro la finestra
    [ self resizeWindowToNewFortune ];
    [ theWindow center ];
    // faccio partire il processo di fading
    [ self makeWindowFadeInOut: YES ];
}

Ho aggiunto qualche istruzione per la gestione dei colori del testo e dello sfondo. Sono cambiati anche i metodi per il fade della finestra, che devono tenere conto del tempo richiesto dalle preferenze, e soprattutto devono gestire la chiusura dell'applicazione o il cambio automatico del fortune.

- (void)
updateAlpha: (NSTimer *)timer
{
    // recupero la direzioen del fading
    BOOL    dir = [ [ timer userInfo] boolValue ];
    // se YES, fading in crescendo
    if ( dir )
    {
        // fading da Zero a Uno, apparizione
        currentFade += (BASIC_INTERVAL_TIME / [[ currPrefs valueForKey: PFC_yahf_fadeInSpeed ] floatValue ] ) ;
        // se raggiungo o supero 1, ho finito
        if ( currentFade >= 1 )
        {
            float    cycleTime = [[ currPrefs valueForKey: PFC_yahf_cycleTime ] floatValue ] ;
            // uccido il timer
            [ fadeTimer invalidate ];
            // imposto 1 per essere sicuro
            currentFade = 1 ;
            // imposto un nuovo timer, con il tempo di ciclo/chiusura
            fadeTimer = [NSTimer scheduledTimerWithTimeInterval: cycleTime target:self
                selector: @selector(endOfDisplay:) userInfo: nil repeats: NO ];
        }
    }
    else
    {
        // fading da Uno a Zero, sparizione
        currentFade -= (BASIC_INTERVAL_TIME / [[ currPrefs valueForKey: PFC_yahf_fadeOutSpeed ] floatValue ] ) ;
        // se sotto o uguale a zero, ho finito
        if ( currentFade <= 0 )
        {
            // uccido il timer
            [ fadeTimer invalidate ];
            // ma tanto vale: uccido anche l'applicazione
            [ NSApp terminate: self ] ;
        }
    }
    // aggiorno la finestra con il nuovo valore alpha
    [ theWindow setAlphaValue: currentFade ];
}

La chiusura/generazione di un nuovo fortune è gestita in maniera molto semplice dal metodo

- (void)
endOfDisplay: (NSTimer *)timer
{
    if ( [[ currPrefs valueForKey: PFC_yahf_timeBehaviour ] boolValue ] )
    {
        // chiusura della finestra,
        // faccio ripartire il fading in dissolvenza
        [ self makeWindowFadeInOut: NO ];
    }
    else
    {
        [ self anotherOne: self ];
    }
}

Anche il metodo makeAFortune è stato rivisto, per tenere conto delle possibilità insite nel comando di fortune, configurate tramite le preferenze e l'inserimento di parametri di lancio:

- (NSString *)
makeAFortune
{
    ...
    // la directory di lavoro e' quella di destinazione
    [ fortuneTask setCurrentDirectoryPath: [ fortunePath stringByDeletingLastPathComponent] ];
    // aggiungo eventuali parametri di lancio
    switch ( [[ currPrefs valueForKey: PFC_yahf_useOffensive ] intValue ] ) {
    case PFC_OU_NORMAL :
        break ;
    case PFC_OU_ADDOFFENSIVE :
        [ argList addObject: @"-a" ];        
        break ;
    case PFC_OU_ONLYOFFENSIVE :
        [ argList addObject: @"-o" ];        
        break ;
    }
    switch ( [[ currPrefs valueForKey: PFC_yahf_lenghtOption ] intValue ] ) {
    case PFC_LO_NOLENGHTLIMIT :
        break ;
    case PFC_LO_LENGHTLONGER :
        [ argList addObject: @"-l" ];        
        [ argList addObject: [ NSString stringWithFormat: @"-n %d",
            [[ currPrefs valueForKey: PFC_yahf_lenghtLimit ] intValue ] ] ];        
        break ;
    case PFC_LO_LENGHTSHORTER :
        [ argList addObject: @"-s" ];        
        [ argList addObject: [ NSString stringWithFormat: @"-n %d",
            [[ currPrefs valueForKey: PFC_yahf_lenghtLimit ] intValue ] ] ];        
        break ;
    }
    if ( [ currPrefs valueForKey: PFC_yahf_equalProb ] )
        [ argList addObject: @"-e" ];
    // file aggiuntivi
//    [ argList addObject: @"10%" ];        
//    [ argList addObject: @"/path/to/file" ];        
    [ fortuneTask setArguments: argList ];
    // dico che l'uscita del comando va su una pipe
    ...
}

Con tutte queste modifiche , l'applicazione YahFortune è diventata più presentabile.

figura 01

figura 01

Ma adesso manca tutta la parte che permette di effettuare la configurazione senza modificare direttamente i file delle preferenze con un editor di testo.

Il Panello

Le preferenze da impostare sono talmente tante che le ho divise in due gruppi.

figura 02

figura 02

figura 03

figura 03

Il primo gruppo comprende le grandezze in grado di configurare l'interfaccia; sono quindi presenti i controlli per determinare i colori del testo e dello sfondo, slider per assegnare i tempi di apertura, chiusura e di aggiornamento del fortune. C'è il solito (vedi capitolo 64) pulsante per impostare il carattere da utilizzare.

Nel secondo gruppo sono invece presenti le opzioni proprie di fortune; per ora mi limito a determinare l'uso o meno dei fortune offensivi (assenti, assieme agli altri, solo offensivi) e la scelta dei fortune in base al numero di caratteri di cui sono composti (qualsiasi fortune, solo quelli lunghi, solo quelli corti, dove la soglia della lunghezza è determinata attraverso uno slider). C'è poi la possibilità di considerare i file dei fortune equiprobabili, ma qui non mi avventuro (per il momento).

Dal punto di vista del programmatore, devo scrivere una sottoclasse di NSPreferencePane, che funziona da controller. Inserisco quindi in questa classe tutti gli outlet verso i controlli dell'interfaccia e le azioni scatenate dai vari controlli. Con Interface Builder effettuo tutti i collegamenti, e sono pronto per codificare.

Ora, la logica del pannello dovrebbe essere la seguente: all'apertura, devo predisporre tutti i controlli in modo che rispecchino i valori contenuti nel file delle preferenze; alla chiusura (ed alla selezione da parte dell'utente di un altro pannello) devo prelevare i valori correnti impostati dall'utente per riscriverli nel file delle preferenze. Ma questa non è una applicazione, e non ho a disposizione il metodo awakeFromNib per la prima parte o di chiusura del per la seconda.

Però NSPreferencePane mette a disposizione una serie di metodi, equivalenti o dalla funzione simile, per eseguire queste operazioni.

Il primo metodo si chiama mainViewDidLoad ed è chiamato quando l'applicazione Preferenze di Sistema ha giusto caricato dal file nib tutti i vari controlli; è questo il punto dove aggiustarne l'aspetto. è un metodo lungo ma assolutamente lineare nel suo svolgimento:

- (void)
mainViewDidLoad
{
    BOOL                tmpBool ;
    int                    tmpInt ;
    NSMutableArray        * fl ;
    // ricavo le preferenze dal file e le inserisco in un dizionario
    [ self setCurrPrefs: getPrefFromFile( ) ] ;
    // predispongo l'interfaccia con i valori caricati
    // imposto il font corrente
    [ self setUserFont:
        [ NSFont fontWithName: [ currPrefs valueForKey: PFC_yahf_fontName ]
                 size: [ [ currPrefs valueForKey: PFC_yahf_fontSize ] intValue] ] ];
    // i colori di sfondo e del testo (che sono archiviati come NSData)
    [ fgColor setColor: [ NSUnarchiver unarchiveObjectWithData:
            [ currPrefs valueForKey: PFC_yahf_fgColor ] ] ];
    [ bgColor setColor: [ NSUnarchiver unarchiveObjectWithData:
            [ currPrefs valueForKey: PFC_yahf_bgColor ] ] ];
    // il testo di esempio
    [ fontSample setStringValue: [ NSString stringWithFormat: @"%@ %4.1f",
                    [ userFont displayName ], [ userFont pointSize ] ] ];
    [ fontSample setFont: userFont ] ;
    [ fontSample setTextColor: [ fgColor color ] ];
    [ fontSample setBackgroundColor: [ bgColor color ] ];
    // gli slider per il fade
    [ fadeInSlider setMinValue: 0 ];
    [ fadeInSlider setMaxValue:
            [[ currPrefs valueForKey: PFC_yahf_maxFadeIn ] floatValue] ];
    [ fadeInSlider setFloatValue:
            [[ currPrefs valueForKey: PFC_yahf_fadeInSpeed ] floatValue] ];
    [ fadeInText setFloatValue: [ fadeInSlider floatValue ] ];
    [ fadeOutSlider setMinValue: 0 ];
    [ fadeOutSlider setMaxValue:
            [[ currPrefs valueForKey: PFC_yahf_maxFadeOut ] floatValue] ];
    [ fadeOutSlider setFloatValue:
            [[ currPrefs valueForKey: PFC_yahf_fadeOutSpeed ] floatValue] ];
    [ fadeOutText setFloatValue: [ fadeOutSlider floatValue ] ];
    // lo slider per il tempo di ciclo/chiusura
    [ cycleTimeSlider setMinValue:
            [[ currPrefs valueForKey: PFC_yahf_minCycleTime ] floatValue] ];
    [ cycleTimeSlider setMaxValue:
            [[ currPrefs valueForKey: PFC_yahf_maxCycleTime ] floatValue] ];
    [ cycleTimeSlider setFloatValue:
            [[ currPrefs valueForKey: PFC_yahf_cycleTime ] floatValue] ];
    [ cycleTimeText setFloatValue: [ cycleTimeSlider floatValue ] ];
    tmpBool = [[ currPrefs valueForKey: PFC_yahf_timeBehaviour ] boolValue ] ;
    // e la gestione dei radio button
    [ timeBehaviour selectCellWithTag: ( ( tmpBool ) ? 0 : 1 ) ] ;
    // probabilita' uguali o meno
    tmpBool = [[ currPrefs valueForKey: PFC_yahf_equalProb ] boolValue ] ;
    [ equalProbBtn setState: ( tmpBool ? NSOnState : NSOffState )];
    // uso dei fortune offensivi
    tmpInt = [[ currPrefs valueForKey: PFC_yahf_useOffensive ] intValue ] ;
    [ offensiveUse selectCellWithTag: tmpInt ];
    // selezione della lunghezza del fortune
    tmpInt = [[ currPrefs valueForKey: PFC_yahf_lenghtOption ] intValue ] ;
    [ qualeLung selectCellWithTag: tmpInt ];
    [ fortuneLenght setMinValue:
            [[ currPrefs valueForKey: PFC_yahf_minFortuneLen ] floatValue] ];
    [ fortuneLenght setMaxValue:
            [[ currPrefs valueForKey: PFC_yahf_maxFortuneLen ] floatValue] ];
    [ fortuneLenght setFloatValue:
            [[ currPrefs valueForKey: PFC_yahf_lenghtLimit ] floatValue] ];
    [ fortuneLenTxt setFloatValue: [ fortuneLenght floatValue ] ];
    // recupero, se esiste, la lista dei file aggiuntivi
    fl = [ currPrefs valueForKey: PFC_yahf_fortuneFiles ] ;
    if ( fl != nil )
    {
        [ self setFortuneFiles: fl ];
        fl = [ currPrefs valueForKey: PFC_yahf_fortunePercs ] ;
        [ self setFortunePercs: fl ];
    }
    else
    {
        [ self setFortuneFiles: [ NSMutableArray arrayWithCapacity: 0] ];
        [ self setFortunePercs: [ NSMutableArray arrayWithCapacity: 0] ];
    }
}

Come per l'applicazione, i valori del file delle preferenze sono contenuti in un dizionario, che è costruito come variabile d'istanza, usando la stessa funzione getPrefFromFile().

Per ogni controllo presente, esiste almeno un valore nel file delle preferenze che permette di predisporne un aspetto acconcio. Per gli slider, ad esempio, oltre al valore corrente, è disponibile anche il valore massimo (ed anche in un caso il valore minimo) da impostare.

Per la chiusura delle operazioni, esiste, tra gli altri, il metodo willUnselect, che è chiamato da Preferenze di Sistema quando l'utente seleziona un altro pannello o chiude l'applicazione stessa, prima di passare ad un altro pannello; uso questo metodo perché all'istante di chiamata sono ancora presenti e validi tutti i controlli, e quindi prelevo le informazioni direttamente. Anche qui, il metodo è lungo ma non difficile:

- (void)
willUnselect
{
    NSUserDefaults            * theDefs = [NSUserDefaults standardUserDefaults];
    // per qualche motivo non posso usare direttamente currPrefs
    NSMutableDictionary        * thePrefs = [ NSMutableDictionary dictionaryWithCapacity: 1 ] ;
    // un nuovo dizionario con tutti i valori di default
    [ thePrefs addEntriesFromDictionary: currPrefs ];
    // sostituisco tutti quelli che possono essere stati cambiati
    [ thePrefs    setObject: [ userFont fontName]
                forKey: PFC_yahf_fontName ] ;
    [ thePrefs    setObject: [ NSNumber numberWithFloat: [ userFont pointSize] ]
                forKey: PFC_yahf_fontSize ] ;
    [ thePrefs    setValue: [ NSNumber numberWithFloat: [ fadeInSlider floatValue ] ]
                forKey: PFC_yahf_fadeInSpeed ] ;
    [ thePrefs    setValue: [ NSNumber numberWithFloat: [ fadeOutSlider floatValue ] ]
                forKey: PFC_yahf_fadeOutSpeed ] ;
    [ thePrefs    setValue: [ NSNumber numberWithFloat: [ cycleTimeSlider floatValue ] ]
                forKey: PFC_yahf_cycleTime ] ;
    [ thePrefs    setValue: [ NSNumber numberWithBool: ([ timeBehaviour selectedTag ] == 0 ? YES : NO) ]
                forKey: PFC_yahf_timeBehaviour ] ;
    // i colori vanno salvati come NSData
    [ thePrefs    setObject: [ NSArchiver archivedDataWithRootObject: [ fgColor color ] ]
                forKey: PFC_yahf_fgColor ];
    [ thePrefs    setObject: [ NSArchiver archivedDataWithRootObject: [ bgColor color ] ]
                forKey: PFC_yahf_bgColor ];
    // gestione dei file
    [ thePrefs    setValue: [ NSNumber numberWithBool: ([ equalProbBtn state ] == NSOnState) ]
                forKey: PFC_yahf_equalProb ] ;
    [ thePrefs    setValue: [ NSNumber numberWithInt: [ offensiveUse selectedTag ] ]
                forKey: PFC_yahf_useOffensive ] ;
    [ thePrefs    setValue: [ NSNumber numberWithInt: [ qualeLung selectedTag ] ]
                forKey: PFC_yahf_lenghtOption ] ;
    [ thePrefs    setValue: [ NSNumber numberWithFloat: [ fortuneLenght floatValue ] ]
                forKey: PFC_yahf_lenghtLimit ] ;
    // salvo il file delle preferenze al suo posto
    [ theDefs setPersistentDomain: thePrefs forName: PFC_yahf_prefFileName ];
    [ theDefs synchronize ];
}

A questo punto, ci sono solo un paio di semplici metodi da scrivere per facilitare l'interazione dell'utente. Ad esempio, uso metodi espliciti di gestione degli slider per poter mantenere accoppiati lo slider ed il testo corrispondente, e verificare che il testo inserito rispetti i limiti. Per lo slider che attribuisce la soglia della lunghezza del fortune da utilizzare la cosa è semplice:

- (IBAction)
updateFortuneSlider: (id) sender
{
    float    tmpFlt, lim1, lim2 ;
    switch ( [ sender tag ] ) {
    case 101 :    // slider
        // solo numeri interi
        [ fortuneLenTxt setIntValue: [ fortuneLenght intValue ] ];
        break;
    case 102 :    // text
        tmpFlt = (float) [ fortuneLenTxt intValue ] ;
        lim1 = [[ currPrefs valueForKey: PFC_yahf_minFortuneLen ] floatValue] ;
        lim2 = [[ currPrefs valueForKey: PFC_yahf_maxFortuneLen ] floatValue] ;
        if ( tmpFlt < lim1 )    tmpFlt = lim1 ;
        if ( tmpFlt > lim2 )    tmpFlt = lim2 ;
        [ fortuneLenght setIntValue: (int) tmpFlt ];
        break;
    default:
        break ;
    }
}

Come sempre, ho attribuito opportuni codici di tag al testo ed allo slider, in modo che uso un unico metodo per entrambi. Nel caso del testo, verifico che il valore immesso sia un intero compreso tra il massimo ed il minimo ammissibile. Prelevo questi due estremi dal file delle preferenze, ma solo per mio sfizio di generalità (avrei potuto usare dei numeri fissati una volta per tutte all'interno del codice). Il vantaggio è che un utente smaliziato li può modificare a mano (non esiste infatti un controllo nel pannello) senza per questo modificare il codice; ritengo comunque una buona pratica inserire tante più informazioni possibili all'interno di file esterni all'applicazione, in modo da evitare modifiche sul codice per operazioni magari banali.

Come per il capitolo 64, anceh qui c'è un pulsante per visualizzare il pannello di scelta caratteri, ed un corrispondente metodo che modifica opportunamente un testo per verificare l'aspetto del risultato.

- (IBAction)
selectFont:(id) sender
{
    [ [ NSFontPanel sharedFontPanel] orderFront: nil ];
    [ [ NSFontPanel sharedFontPanel] setDelegate: self ];
}

- (void)
updatePreferences:(id)sender
{
    NSString    * thestr ;
    // in pratica, cambiamento di colore e font
    // aggiorno la stringa visualizzata
    thestr = [ NSString stringWithFormat: @"%@ %4.1f", [ userFont displayName ], [ userFont pointSize ] ] ;
    [ fontSample setFont: [ NSFont fontWithName: [ userFont fontName ] size: 12 ] ] ;
    [ fontSample setStringValue: thestr ];
    [ fontSample setTextColor: [ fgColor color ] ];
    [ fontSample setBackgroundColor: [ bgColor color ] ];
}

- (void)
changeFont:(id)sender
{
    // ottengo il nuovo font
    NSFont        * newFont = [sender convertFont:userFont] ;
    // lo imposto internamente
    [ self setUserFont: newFont ];
    [ self updatePreferences: sender ];
}

L'azione selectFont è associata al pulsante di selezione, il metodo changeFont è chiamato dal pannello di scelta font, mentre il metodo updatePreferences modifica l'aspetto di un campo di testo per dare conto dell'avvenuta modifica del carattere e del colore di sfondo.

Ultime avvertenze

figura 04

figura 04

figura 05

figura 05

La parte più difficile del tutto è stata configurare il target del pannello delle preferenze in modo che producesse appunto un pannello delle preferenze. In effetti non occorrono molte operazioni, ma queste sono fondamentali. Mi sono sicuramente dimenticato qualcosa: per certo il nuovo target deve essere un bundle, e questo bundle deve avere una wrapper extension pari a prefPane. Il nome del target, della classe controller e del file è sostanzialmente lo stesso, yahFortunePref, ed ho aggiunto un file yahFortunePref-info.plist come file delle info. Tutto ciò ha richiesto la modifica delle informazioni del Target, utilizzando la finestra che si apre con la voce di menu apposita. Sono da modificare voci nel pannello Build e nel pannello Properties.

All'interno dei file del progetto si trovano altri metodi, per la gestione della tabella dei file aggiuntivi, che non sono ancora attivi. Saranno commentati più avanti, quando avrò capito come fare. All'interno del file che contiene il progetto sono presenti anche i sorgenti del comando fortune (privati dei fortune in inglese per ragioni di spazio) e i file accessori del pacchetto fortune-IT; i file di fortune veri e propri sono già incorporati all'interno del progetto (che proprio per questo è piuttosto corposo).

Licenza Creative Commons
Eccetto dove diversamente specificato, i contenuti di questo sito sono rilasciati sotto Licenza Creative Commons.
Pagina a cura di Livio Sandel (macocoa2012@gmail.com).