Ancora un breve capitolo per esportare in un file di testo l'elenco dei file presenti all'interno del catalogo.
Sorgenti: nessuna
Prima stesura: 10 novembre 2004
Prima di esportare dati, cambio i nomi delle preferenze. Fino ad ora mi ero trovato ad utilizzare delle #define per individuare delle stringhe con cui individuare alcune grandezze la cui specifica era a cura dell'utente. Ad esempio:
#define keyColModDate @"ColModDate"
...
#define keyCDLCW_ModDate @"CDLCW_ModDate"
specificano la prima la presenza o meno di una colonna nella finestra del catalogo, mentre la seconda specifica la dimensione in pixel nella visualizzazione di quella stessa colonna. Ogni volta che aggiunto preferenze riguardanti i singoli attributi di un file, devo aggiungere una diecina di #define, una per ogni attributo.
Trovo tutto ciò piuttosto scomodo, ed ho quindi deciso di lavorare in altro modo. Il punto di partenza è il nome di quell'attributo, che è anche l'identificatore della colonna. Sono un gruppo di #define in CatFileInfo.h:
#define COLID_FILENAME @"fileName"
#define COLID_MODDATE @"modDate"
...
Per ottenere un nome di preferenza, faccio precedere la stringa da un prefisso opportuno, costruendo così una nuova stringa, che uso appunto come identificatore della grandezza.
#define keyDsplPrefPrefix @"CDLDF_"
#define keyExprPrefPrefix @"CDLPF_"
#define keyColWPrefPrefix @"CDLCW_"
Ho definito quindi questi tre prefissi, per indicare il primo la visualizzazione o meno di una colonna, mentre il terzo specifica la larghezza di una colonna; il secondo prefisso sarà utilizzato poco sotto per determinare l'esportazione o meno di quell'attributo in un file di testo.
Ho poi definito una sintetica funzione in djZeroUtils.m per costruire i nomi:
NSString *
bldStrPref( NSString * pref, NSString * colid )
{
return ( [ pref stringByAppendingString: colid] );
}
Infine, con infinita pazienza, sono andato a sostituire nell'intero codice tutte le occorrenze delle precedenti #define con nuove espressioni.
A titolo di esempio, mostro solo come cambia il metodo che salva le dimensioni delle colonne all'interno delle preferenze:
- (BOOL)
shouldCloseDocument
{
NSTableColumn * tc ;
NSNumber * cw ;
#define SET_COLWIDTH_PREF( quale ) \
tc = [ fullList tableColumnWithIdentifier: quale ]; \
if ( tc ) \
{ \
cw = [ NSNumber numberWithInt: [ tc width ] ] ; \
[ UserPrefs setSinglePrefValue: cw forKey: \
bldStrPref(keyColWPrefPrefix, quale) ]; \
}
// ne approfitto per mettere a posto le larghezze di default
tc = [ fullList tableColumnWithIdentifier: COLID_FILENAME];
cw = [ NSNumber numberWithInt: [ tc width ] ] ;
[ UserPrefs setSinglePrefValue: cw forKey: bldStrPref(keyColWPrefPrefix, COLID_FILENAME) ];
SET_COLWIDTH_PREF( COLID_MODDATE ) ;
...
SET_COLWIDTH_PREF( COLID_MODDATE ) ;
return ( YES );
}
Attenzione che c'è anche un altro importante file da modificare: si tratta di defCodeValues.dict, dove sono contenuti i valori di default. Occorre cambiare tutti i nomi:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CDLDF_creatDate</key>
<true/>
<key>CDLDF_creatorCode</key>
<false/>
...
<key>ExpandBundle</key>
<false/>
<key>ShowDotFiles</key>
<true/>
</dict>
</plist>
Alla fine, sono rimaste due sole preferenze con un nome proprio, le grandezze booleane che indicano l'espansione o meno dei bundle e la visualizzazione o meno dei così detti dot file.
Accade talvolta di dover trasferire i dati di un programma verso altri programmi; spesso il formato di interscambio non è altro che un semplice file di testo. Mi prefiggo quindi di produrre un file di testo con al suo interno l'elenco dei file presenti nel catalogo prescelto. Per complicarmi la vita, decido di utilizzare ancora una volta il campo is2Print per determinare se un dato file (e sui discendenti se si tratta di una cartella) è esportato oppure no; inoltre, permetto all'utente di specificare quali informazioni relative al file sono presenti. L'utente può quindi determinare, all'interno delle preferenze, quali campi esportare, così come decide quali campi sono visualizzati nella finestra.
Allo scopo, predispongo una finestra delle preferenze modificata: ho aggiunto una NSTabView con due viste. Nella prima vista sono comprese le preferenze di quali colonne visualizzare; nella seconda vista, assolutamente uguale, si specificano quali colonne/attributi esportare. Ho aggiunto un outlet verso la NSMatrix con i nuovi pulsanti, e ho modificato alcuni metodi di PrefWinCtl per tenere conto dei nuovi valori.
A titolo di esempio, riporto il metodo che aggiorna le preferenze secondo i desideri dell'utente:
- (void)
userSelectUpdate:(id)sender
{
#define MATRIX_GET_STATE( key, row, col ) \
[defDispValues setObject: \
([[colDspMatrix cellAtRow: row column:col] state ] ? lsPrefsYes : lsPrefsNo) \
forKey:key]
// devo recuperare i valori dalla finestra
[defDispValues setObject:
([expandBundleButton state] ? lsPrefsYes : lsPrefsNo)
forKey:keyExpandBundle];
[defDispValues setObject:
([showDotFilesButton state] ? lsPrefsYes : lsPrefsNo)
forKey:keyShowDotFiles];
// recupero i valori secondo i controlli ed assegno le prefs
MATRIX_GET_STATE( bldStrPref(keyDsplPrefPrefix, COLID_MODDATE), 0, 0 );
MATRIX_GET_STATE( bldStrPref(keyDsplPrefPrefix, COLID_CREATDATE), 0, 1 );
MATRIX_GET_STATE( bldStrPref(keyDsplPrefPrefix, COLID_FILESIZE), 1, 0 );
MATRIX_GET_STATE( bldStrPref(keyDsplPrefPrefix, COLID_OSTYPE), 1, 1 );
MATRIX_GET_STATE( bldStrPref(keyDsplPrefPrefix, COLID_GROUPNAME), 2, 0 );
MATRIX_GET_STATE( bldStrPref(keyDsplPrefPrefix, COLID_OSCREATOR), 2, 1 );
MATRIX_GET_STATE( bldStrPref(keyDsplPrefPrefix, COLID_OWNERNAME), 3, 0 );
MATRIX_GET_STATE( bldStrPref(keyDsplPrefPrefix, COLID_POSIXPERM), 3, 1 );
#define MXEXPR_GET_STATE( key, row, col ) \
[defDispValues setObject: \
([[colPrtMatrix cellAtRow: row column:col] state ] ? lsPrefsYes : lsPrefsNo) \
forKey:key]
MXEXPR_GET_STATE( bldStrPref(keyExprPrefPrefix, COLID_MODDATE), 0, 0 );
MXEXPR_GET_STATE( bldStrPref(keyExprPrefPrefix, COLID_CREATDATE), 0, 1 );
MXEXPR_GET_STATE( bldStrPref(keyExprPrefPrefix, COLID_FILESIZE), 1, 0 );
MXEXPR_GET_STATE( bldStrPref(keyExprPrefPrefix, COLID_OSTYPE), 1, 1 );
MXEXPR_GET_STATE( bldStrPref(keyExprPrefPrefix, COLID_GROUPNAME), 2, 0 );
MXEXPR_GET_STATE( bldStrPref(keyExprPrefPrefix, COLID_OSCREATOR), 2, 1 );
MXEXPR_GET_STATE( bldStrPref(keyExprPrefPrefix, COLID_OWNERNAME), 3, 0 );
MXEXPR_GET_STATE( bldStrPref(keyExprPrefPrefix, COLID_POSIXPERM), 3, 1 );
// recuperati i valori; li assegno ai correnti
// salvo le preference su file
[ UserPrefs savePrefsToDefault: defDispValues ];
// e per finire nascondo la finestra
[[self window] setIsVisible: FALSE ];
}
Non ho fatto altro che applicare le modifica sul nome delle preferenze, ed aggiungere nuove preferenze, molto simili alle precedenti.
Sono a questo punto pronto per esportare i dati. Aggiungo al MainMenu.nib una voce di menu che scateni l'intero processo chiamando il metodo textExport della classe ListWinCtl:
- (void)
textExport: (id)sender
{
NSSavePanel *sPanel = [ NSSavePanel savePanel ] ;
// personalizzo il pannello di salvataggio
[ sPanel setTitle:@"Export text file" ];
// i file salvati avranno l'estensione lscat
[ sPanel setRequiredFileType: @"txt" ];
if ( [ sPanel runModal ] == NSOKButton )
{
// recupero il nome del file
NSString * aFile = [ sPanel filename ] ;
NSString * exportStr ;
exportStr = [ [[ self document] dataSource] exportData ];
if ( ! [ exportStr writeToFile: aFile atomically: YES ] )
{
// che ne so
}
}
}
Questo metodo chiede la specifica di un file di testo tramite il consueto pannello; se l'utente non cambia idea e fornisce un adeguato percorso, il metodo exportData produce una NSString con tutti i dati da esportare, e la semplice istruzione writeToFile: produce il file testo voluto. La potenza di Cocoa e soprattutto della classe NSString permette di sorvolare su molti dettagli.
Adesso non rimane che scrivere il metodo exportData, proprio della classe CatDataSrc. Ancora una volta, si tratta di eseguire una discesa ricorsiva della base di dati e di produrre, per ogni elemento attraversato, una stringa apposita. Questa volta utilizzo un metodo piuttosto pedestre:
- (NSString *)
exportData
{
NSString * expStr = [ NSString string ] ;
int jj ;
for ( jj = 0 ; jj < [ startPoint count ] ; jj ++ )
{
FileStruct * item ;
item = [ startPoint objectAtIndex: jj ] ;
expStr = [ item exportData: expStr withLevel: 0 ] ;
}
return ( expStr ) ;
}
Qui si esaminano tutti i volumi presenti, e per ognuno di essi si comanda l'esportazione dei dati. Qui occorre notare come la stringa sia passata come parametro e poi restituita dal metodo; si suppone che alla stringa siano state aggiunte le informazioni proprie di quell'elemento (e di tutti i discendenti dell'elemento). Inoltre, come già visto altrove, faccio in modo che gli elementi siano indentati in accordo alla profondità della struttura delle cartelle.
Al termine di questa cascata di metodi di esportazione c'è finalmente questo metodo, proprio della classe FileStruct:
- (NSString *)
exportData: (NSString *) expStr withLevel: (int) lev
{
if ( [ self is2Print ] )
{
int i ;
int numFile = [ fileList count ] ;
expStr = [ self writeExportToString: expStr withLevel: lev ] ;
for ( i = 0; i < numFile; i++)
{
FileStruct * tmpFile ;
// costruisco l'albero
tmpFile = [ fileList objectAtIndex: i ] ;
expStr = [ tmpFile exportData: expStr withLevel: (lev+1) ] ;
}
}
return ( expStr );
}
La prima cosa che il metodo si chiede è il valore di is2Print. Se Falso, restituisce intonsa la stringa passatagli, dal momento che non si devono aggiungere dati a quelli da esportare. Se invece is2Print è vero, aggiunge i propri dati alla stringa e poi passa il compito ordinatamente ai propri discendenti (se presenti). Alla fine, la stringa, ripetutamente aggiornata, è restituita al chiamante.
Il lavoro finale, di produzione della stringa di testo da aggiungere alla stringa già disponibile, è svolta da un ulteriore metodo; questo non è nel suo impianto particolarmente difficile, ma ci sono alcuni piccoli accorgimenti da utilizzare se si intende ottenere un file testo leggibile in maniera decente.
- (NSString *)
writeExportToString: (NSString *) expStr withLevel: (int) lev
{
NSString * tmpStr ;
// la stringa di prefisso con un po' di tabulazioni, ed il nome del file
tmpStr = [ expStr stringByAppendingFormat: @"%@%@\t", prefixLevString( lev ), [ self fileName ] ];
// esamino poi i vari attributi: data di modifica
if ( [[ UserPrefs getPrefValue: bldStrPref(keyExprPrefPrefix, COLID_MODDATE)] boolValue] )
{
// costruisco un formattatore per le date
NSDate * tmp = [ self modDate ] ;
NSDateFormatter *dateFormat = [[NSDateFormatter alloc]
initWithDateFormat:@"%d %b %Y %H:%M" allowNaturalLanguage:NO];
NSString * localString = [ dateFormat stringForObjectValue: tmp ] ;
tmpStr = [ tmpStr stringByAppendingFormat: @"%@\t", localString ];
}
// data di creazione
if ( [[ UserPrefs getPrefValue: bldStrPref(keyExprPrefPrefix, COLID_CREATDATE)] boolValue] )
{
// costruisco un formattatore per le date
NSDate * tmp = [ self creatDate ] ;
NSDateFormatter *dateFormat = [[NSDateFormatter alloc]
initWithDateFormat:@"%d %b %Y %H:%M" allowNaturalLanguage:NO];
NSString * localString = [ dateFormat stringForObjectValue: tmp ] ;
tmpStr = [ tmpStr stringByAppendingFormat: @"%@\t", localString ];
}
// dimensione del file
if ( [[ UserPrefs getPrefValue: bldStrPref(keyExprPrefPrefix, COLID_FILESIZE)] boolValue] )
{
// costruisco un formattatore per la dimensioen del file
FileSizeForm * numFmt = [ [ FileSizeForm alloc ] init ] ;
long long tmp = [ self fileSize ] ;
NSString * localString = [ numFmt stringForObjectValue: [ NSNumber numberWithLongLong: tmp ] ] ;
tmpStr = [ tmpStr stringByAppendingFormat: @"%@\t", localString ];
}
// nome del gruppo e del proprietario
if ( [[ UserPrefs getPrefValue: bldStrPref(keyExprPrefPrefix, COLID_GROUPNAME)] boolValue] )
{
// e' una stringa, aggiungo brutalmente
tmpStr = [ tmpStr stringByAppendingFormat: @"%@\t", [ self ownGroupName ] ];
}
if ( [[ UserPrefs getPrefValue: bldStrPref(keyExprPrefPrefix, COLID_OWNERNAME)] boolValue] )
{
// e' una stringa, aggiungo brutalmente
tmpStr = [ tmpStr stringByAppendingFormat: @"%@\t", [ self ownerName ] ];
}
// posix permissions
if ( [[ UserPrefs getPrefValue: bldStrPref(keyExprPrefPrefix, COLID_POSIXPERM)] boolValue] )
{
// formattatore per i permessi di lettura/scrittura/esecuzione
FilePosixPerm * numFmt = [ [ FileSizeForm alloc ] init ] ;
long tmp = [ self filePosixPerm ] ;
NSString * localString = [ numFmt stringForObjectValue: [ NSNumber numberWithLong: tmp ] ] ;
tmpStr = [ tmpStr stringByAppendingFormat: @"%@\t", localString ];
}
// tipo e creatore
if ( [[ UserPrefs getPrefValue: bldStrPref(keyExprPrefPrefix, COLID_OSCREATOR)] boolValue] )
{
// formattatore per tipo/creatore in OS9
TOS9TCForm * numFmt = [ [ FileSizeForm alloc ] init ] ;
long tmp = [ self creatorCode ] ;
NSString * localString = [ numFmt stringForObjectValue: [ NSNumber numberWithLong: tmp ] ] ;
tmpStr = [ tmpStr stringByAppendingFormat: @"%@\t", localString ];
}
if ( [[ UserPrefs getPrefValue: bldStrPref(keyExprPrefPrefix, COLID_OSTYPE)] boolValue] )
{
// formattatore per tipo/creatore in OS9
TOS9TCForm * numFmt = [ [ FileSizeForm alloc ] init ] ;
long tmp = [ self typeCode ] ;
NSString * localString = [ numFmt stringForObjectValue: [ NSNumber numberWithLong: tmp ] ] ;
tmpStr = [ tmpStr stringByAppendingFormat: @"%@\t", localString ];
}
return ( [ tmpStr stringByAppendingString: @"\n" ] ) ;
}
In effetti, finché l'attributo è ben rappresentato da una stringa, uso l'attributo così come si trova? Negli altri casi, utilizzo invece (come al solito) un formattatore per poterne dare una rappresentazione più leggibile.
Eccetto dove diversamente specificato, i contenuti di questo sito sono rilasciati sotto Licenza Creative Commons.
Pagina a cura di Livio Sandel (macocoa2012@gmail.com).