MaCocoa 050

Capitolo 050 - Il costruttore diventa ispettore

L'inserimento di nuovi elementi all'interno di una copertina avviene attraverso il mouse. Non è quindi più utile la finestra ObjMaker; in questo capitolo la sostituisco con una finestra che riporta le caratteristiche dell'elemento selezionato; attraverso la quale, è possibile modificare alcune caratteristiche dell'elemento.

Sorgenti: documentazione Apple

Prima stesura: 9 agosto 2004

Morte di un costruttore

La finestra ObjMaker non è più utile. Distruggo quindi il file nib e i file .h e .m relativi e sostituisco il tutto con una nuova finestra, invero piuttosto simile, di nome ElemInfo (confesso, ho solo cambiato il nome ai file). Scopo di questa finestra è di presentare in bella copia gli attributi dell'elemento selezionato (colore di riempimento, dimensioni, eccetera) e di modificarli; la modifica degli attributi di questo elemento dovrebbe riflettersi in tempo reale sull'elemento stesso, in modo da poter ritoccare elementi che al momento della creazione col mouse non erano riusciti molto bene.

figura 01

figura 01

Con Interface Builder modifico la finestra in modo opportuno. Scompaiono il menu di scelta del tipo di elemento ed il pulsante di creazione, sostituiti da una semplice etichetta (riprenderà una breve descrizione dell'elemento selezionato). Una connessione degna di nota è quella che mette in relazione ogni singolo controllo della finestra (pulsanti, dispositivi di scelta del colore, ed anche i campi di testo) con il File's Owner attraverso l'azione changedElem:. In questo modo sarà possibile rispondere immediatamente ad ogni cambiamento del valore del controllo.

Una storia un po' più lunga è legata allo slider ed al campo di testo collegati, entrambi dedicati alla specifica dello spessore della linea. Ora, per poter collegare strettamente il campo testo e lo slider (in modo che una modifica sull'uno si rifletta nell'aggiornamento del valore dell'altro), ognuno è destinatario (target) di una action dell'altro. Per fare in modo che la modifica sia riconosciuta anche all'esterno (il meccanismo target/action può essere applicato una sola volta), ho scollegato slider e campo testo, e li ho invece collegati allo stesso metodo slideChange: del File's Owner. In altre parole, realizzo tramite mio codice il collegamento tra slider e campo testo, in modo da inserire a piacere altre istruzioni utili.

- (IBAction)
slideChange:(id)sender
{
    // recupero il valore corrente
    float newVal = [ sender floatValue ];
    // limite massimo
    if ( newVal > 10 ) newVal = 10 ;
    // imposto il valore per entrambi
    [ lineWidthTextField setFloatValue: newVal ] ;
    [ lineWidthSlider setFloatValue: newVal ] ;
    // dico che e' cambiato il valore di un attributo
    [ self changedElem: sender ];
}

Nel caso, l'istruzione aggiunta è proprio l'invocazione del metodo changedElem:, come se il tutto provenisse dal controllo interessato.

figura 02

figura 02

Una seconda operazione degna di nota è la presenza di nuovi outlet per collegare il File's Owner alle quattro etichette del box Dimensions. Infatti, quando l'elemento selezionato è una linea, non ha senso individuare le grandezze rappresentate (il punto di partenza e quello di fine del segmento) con le stesse etichette di un rettangolo (posizione e dimensioni).

A questo punto torno in XCode e scrivo (aggiorno) i metodi per la costruzione della finestra.

- (void)
windowDidLoad
{
    NSWindowController *controller ;

    [super windowDidLoad];
    // predisposizione dei vari elementi della finestra
    [fillCheckbox setState:NSOffState];
    ...
    [heightTextField setEnabled:YES];
    // aggiungo due osservatori quando cambia la finestra principale
    // in modo che possa aggiustare theSelectedElem
    [[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(mainWindowChanged:)
        name:NSWindowDidBecomeMainNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(mainWindowResigned:)
        name:NSWindowDidResignMainNotification object:nil];
    // osservatore sul cambiamento della selezioen corrente
    [[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(changedSelection:)
        name:@"CoverViewChangedSelection" object:nil];
    // pero' devo mettere a posto la prima volta
    // recupero la finestra di fronte a tutti
    controller = [ [NSApp mainWindow] windowController];
    // se e' del tipo giusto, aggiorno di conseguenza
    if (controller && [controller isKindOfClass:[CoverWinCtl class]])
            [self setMainWindow: [(CoverWinCtl *)controller cvrView]];
    else    [self setMainWindow:nil];
}

Aggiornamento delle informazioni

A parte l'aggiustamento dei vari elementi dell'interfaccia, ci sono i tre abbonamenti a varie notifiche. Le prime due sono ordinaria amministrazione (ad esempio, nel capitolo 22): la finestra è interessata agli eventi di evidenziazione della finestra principale per aggiornare di conseguenza le informazioni mostrate al proprio interno. La terza notifica è invece meno standard: dico che la finestra è interessa a notifiche di nome CoverViewChangedSelection, in corrispondenza delle quali conviene eseguire il metodo changedSelection:. Non si tratta di una notifica standard di Cocoa, ma di una notifica che genererò direttamente all'interno del mio codice quando sono più o meno convinto che sia cambiata la selezione corrente all'interno di una copertina.

Subito dopo, le istruzioni seguenti si preoccupano di aggiustare l'aspetto della finestra la prima volta che appare, andando ad aggiustare direttamente le informazioni richieste.

Infatti, i tre metodi conseguenti alle notifiche si basano tutti sullo stesso metodo setMainwindow::

- (void)
mainWindowChanged: (NSNotification *)notification
{
    NSWindowController *controller = [[notification object] windowController];
    if (controller && [controller isKindOfClass:[CoverWinCtl class]])
    {
        [self setMainWindow: [(CoverWinCtl *)controller cvrView]];
    }
    else    [self setMainWindow:nil];
}

- (void)
mainWindowResigned: (NSNotification *)notification
{
    [self setMainWindow:nil];
}

- (void)
changedSelection: (NSNotification *)notification
{
    id notifObj = [notification object] ;
    if ( [notifObj isKindOfClass:[CoverView class]])
    {
        [self setMainWindow: (CoverView *) notifObj ];
    }
}

In tutti e tre i casi, se la finestra in primo piano dipende da una CoverWinCtl, allora siamo a cavallo: chiamo setMainwindow: passandogli come argomento l'oggetto della classe CoverView dove si trovano gli elementi d'interesse. Negli altri casi, chiamo il metodo con argomento nil.

Infatti il metodo setMainWindow: lavora come segue:

- (void)
setMainWindow: (CoverView *) coverView
{
    NSMutableArray * selArr ;
    int numElem ;
    // tutti gli elementi sono disabilitati
    [fillCheckbox setEnabled:NO];
    ...
    [heightTextField setEnabled:NO];
    theSelectedElem = nil;
    // e se la fienstra davanti non e' giusta, ho finito
    if ( coverView == nil )
        return ;
    // se arrivo qui, la finestra davanti ' di una copertina
    // guardo se ci sono elementi selezionati
    selArr = [ [ coverView theElements] getSelectedGraphics ];
    numElem = [ selArr count ];
    // se non ce ne sono...    
    if ( numElem == 0 )
    {
        // dico che se ne fa nulla
        [ elemId setStringValue: @"No graphic element selected"];        
    }
    // ... o ce ne sono troppi
    else if ( numElem > 1 )
    {
        // dico che se ne fa nulla
        [ elemId setStringValue: @"Too many graphic elements selected"];                
    }
    else
    {
        // c'e' un solo elemento, imposto il puntatore
        theSelectedElem = [ selArr objectAtIndex: 0 ] ;
        // e aggiorno la finestra di conseguenza
        [ self updateWindowInfo ];
    }
}

figura 04

figura 04

figura 05

figura 05

Per prima cosa, pone la finestra in uno stato di default (tutto disabilitato); poi controlla se c'è un elemento selezionato. Sicuramente l'elemento non c'è se la finestra di fronte non è corretta (parametro di valore nil); se invece la CoverView è effettiva, verifica quanti sono gli elementi selezionati. Se non ce ne sono, o ce n'è più d'uno, si dichiara incapace di trattare la cosa, e riporta un messaggio appropriato. Se invece, e finalmente, c'è un solo elemento selezionato, passa ad aggiornare il contenuto della finestra.

- (void)
updateWindowInfo
{
    NSRect locR = [theSelectedElem elemLimit] ;        
    // attivo tutti gli elementi della finestra
    [fillCheckbox setEnabled:YES];
    ...
    [heightTextField setEnabled:YES];
    // predispongo gli elementi che corrispondono agli attributi comuni
    [fillCheckbox setState: [ theSelectedElem cceIsFilled] ];
    [fillColorWell setColor: [ theSelectedElem cceFillColor] ];
    [lineCheckbox setState: [ theSelectedElem cceIsStroked] ];
    [lineColorWell setColor:[ theSelectedElem cceLineColor] ];
    [lineWidthSlider setFloatValue: [ theSelectedElem cceLineWidth] ];
    [lineWidthTextField setFloatValue: [ theSelectedElem cceLineWidth] ];
    // la linea va trattata in maniera particolare
    if ( [theSelectedElem isKindOfClass:[CCE_Line class]])
    {
        // recupero i due punti di inizio e fine
        NSPoint locP = [(CCE_Line*)theSelectedElem startPoint] ;        
        [xTextField setStringValue: [ NSString stringWithFormat: @"%f", locP.x ] ];
        [yTextField setStringValue: [ NSString stringWithFormat: @"%f", locP.y ] ];
        locP = [(CCE_Line*)theSelectedElem endPoint] ;    
        [widthTextField setStringValue: [ NSString stringWithFormat: @"%f", locP.x ] ];
        [heightTextField setStringValue: [ NSString stringWithFormat: @"%f", locP.y ] ];
        [ elemId setStringValue: [ theSelectedElem describeMe ] ];    
        // aggiusto le etichette coi nomi corretti
        [labelx1 setStringValue: @"X1:" ];
        [labelx2 setStringValue: @"X2:" ];
        [labely1 setStringValue: @"Y1:" ];
        [labely2 setStringValue: @"Y2:" ];
        // una linea non puo' essere riempita di colore
        [fillCheckbox setEnabled: NO];
        [fillColorWell setEnabled: NO];
        return ;    
    }
    // se arrivo qui, sono sicuro che non e' una linea
    [xTextField setStringValue: [ NSString stringWithFormat: @"%f", locR.origin.x ] ];
    [yTextField setStringValue: [ NSString stringWithFormat: @"%f", locR.origin.y ] ];
    [widthTextField setStringValue: [ NSString stringWithFormat: @"%f", locR.size.width ] ];
    [heightTextField setStringValue: [ NSString stringWithFormat: @"%f", locR.size.height ] ];
    // predispongo le etichette standard
    [labelx1 setStringValue: @"X:" ];
    [labely1 setStringValue: @"Y:" ];
    [labelx2 setStringValue: @"Width:" ];
    [labely2 setStringValue: @"Height:" ];
    // preparo una scritta a seconda dell'elemento
    [ elemId setStringValue: [ theSelectedElem describeMe ] ];        
}

figura 03

figura 03

Per prima cosa, attivo tutti gli elementi dell'interfaccia, e poi aggiorno il valore di ciascuno con i valori che ricavo dalle variabili d'istanza dell'elemento stesso. Devo come al solito distinguere il comportamento degli elementi CCE_Line dagli altri; la differenza si trova nel significato dei quattro campi relativi alle dimensioni, che nel caso di una linea sono le coordinate dei due punti estremi del segmento; negli altri casi si tratta invece del punto origine e delle dimensioni (larghezza ed altezza) del rettangolo che racchiude l'elemento.

Per produrre poi una stringa in grado di descrivere l'elemento, ricorro come sempre al polimorfismo, realizzando per ciascuna classe di elementi il metodo describeMe:. Ad esempio, per un cerchio il metodo è:

- (NSString*)
describeMe
{
    return ( [ NSString stringWithFormat:@"Circle Elem ID %d", [ self objID] ] );        
}

Ho così trovato anche un uso interessante per la variabile d'istanza objId che avevo introdotto ai soli scopi di debugging.

C'è un'ultima notazione prima che tutto quanto funzioni; occorre aggiungere l'invio della notifica CoverViewChangedSelection nel punto più opportuno. Non ho trovato di meglio che farlo all'interno del metodo mouseDown: della classe CoverView, al termine di tutte le operazioni, attraverso l'istruzione

    [ [NSNotificationCenter defaultCenter]
        postNotificationName:@"CoverViewChangedSelection"
        object:self];

Infatti, alla conclusione di questo metodo, o è stato creato (e quindi selezionato) un nuovo elemento, o comunque è avvenuto un clic (o trascinamento), il quale, a meno di casi particolari, ha cambiato la selezione corrente.

Cambio dei parametri

Oltre che alla visualizzazione del valore dei parametri, la finestra può essere utilizzata per modificarne il valore. Operando su uno qualsiasi dei controlli (due pulsanti di spunta, due NSColorWheel, lo slider ed il campo testo associato, e i quattro campi testo con le coordinate), al termine della modifica è automaticamente chiamato il metodo changedElem:. Quando, all'interno di Interface Builder, ho modificato la finestra, mi sono preoccupato di attribuire ad ogni controllo un tag univoco. In questo modo il metodo può discriminare facilmente chi ha causato la chiamata.

- (IBAction)
changedElem:(id)sender
{
    float p1, p2, p3, p4 ;
    // a seconda dell'attributo modificato
    switch ( [ sender tag ] ) {
    case 10 :     // stato di riempimento
        [ theSelectedElem setCceIsFilled: [ fillCheckbox state ]];
        break ;
    case 11 :     // colore di riempimento
        [ theSelectedElem setCceFillColor: [ fillColorWell color ]];
        break ;
    case 12 :     // stato di disegno linea/contorno
        [ theSelectedElem setCceIsStroked: [ lineCheckbox state ]];
        break ;
    case 13 :     // colore della linea
        [ theSelectedElem setCceLineColor: [ lineColorWell color ]];
        break ;
    case 14 :     // dimensione della linea
    case 15 :
        [ theSelectedElem setCceLineWidth: [ lineWidthSlider floatValue ]];
        break ;
    case 16 :     // coordinate limite dell'elemento
    case 17 :
    case 18 :
    case 19 :
        // recupero i quattro valori
        p1 = [xTextField floatValue ];
        p2 = [yTextField floatValue ];
        p3 = [widthTextField floatValue ];
        p4 = [heightTextField floatValue ];
        // la linea ha il solito trattamento particolare        
        if ( [theSelectedElem isKindOfClass:[CCE_Line class]])
            [theSelectedElem resizeMeWith: NSMakePoint(p1,p2) andPt: NSMakePoint(p3,p4) ];
        else
            [theSelectedElem resizeMeWith: NSMakePoint(p1,p2) andPt: NSMakePoint(p1+p3,p2+p4) ];
        break ;
    default:
        NSLog( @"changed %@", sender );
    }
    // forzo un ridisegno
    [ [theSelectedElem ownerView] setNeedsDisplay: YES ];
}

figura 06

figura 06

In corrispondenza di ogni chiamata sono eseguite le istruzioni corrispondenti alla modifica richiesta. Se ad esempio si è verificata un'azione sullo slider (caso 14 o 15), è modificata la variabile d'istanza cceLineWidth dell'elemento selezionato; così, similmente, negli altri casi. Un po' più complicata (non di tanto) la modifica di una delle coordinate, in cui si deve distinguere tra CCE_Line e gli altri elementi. è chiamato il metodo resizeMeWith:andPt: perché questo metodo ha incorporato il meccanismo di undo.

Le altre modifiche

Nel corso delle mie operazioni, ho effettuato altre modifiche.

Ad esempio, ho dovuto aggiungere i metodi accessor per le variabili della classe CCE_Line, in quando per la visualizzazione delle coordinate era necessario accedervi.

Ho provato poi a giocare un po' con la modifica dei font dei campi di testo CCE_Text (attualmente limitati alla sola visualizzazione della lista degli elementi). Quando l'utente utilizza il pannello font o seleziona le voci di menu come Grassetto, Corsivo, eccetera, Cocoa invia vari messaggi in giro per la gerarchia. Il più gettonato pare essere il messaggio changeFont: inviato al First Responder. Per essere sicuro i intercettare tale messaggio, piazzo il metodo corrispondente all'interno della classe CoverWinCtl, alla quale sono sicuro arrivi.

- (void)
changeFont:(id)sender
{
    [ [ cvrView theElements ] changeFont: sender ];
}

Mi limito a passare la palla al gestore di tutti gli elementi all'interno della finestra. A sua volta, l'oggetto CCE_ElemGroup non farà altro che spazzolare tutti gli elementi della finestra, e inviare un apposito metodo ai soli elementi CCE_Text selezionati:

- (void)
changeFont:(id)sender
{
    int        i , numElem ;
    CCE_BasicForm * elem ;

    // ciclo su tutti gli elementi del gruppo
    numElem = [ elemArray count ];
    for ( i = 0 ; i < numElem ; i ++ )
    {
        elem = [ elemArray objectAtIndex: i ];
        if ( [ elem cceIsSelected ] && [elem isKindOfClass:[CCE_Text class]])
            [ elem changeFont: sender ];
    }
}

Finalmente, l'oggetto CCE_Text potrà trattare il cambio di font. Lo fa nel seguente modo (seguo pedissequamente le documentazione Apple):

- (void)
changeFont:(id)sender
{
    // recupero il font corrente
    NSFont * curFont = [ textStorage font ];
    // lo converto nel nuovo font come stabilito da NSFontManager
    NSFont * newFont = [sender convertFont:curFont];
    // imposto il nuovo font
    [textStorage setFont:newFont];
    // ridisegno tutto
    [ ownerView setNeedsDisplay: YES ];
}

Qui bisogna tenere presente che il sender è sempre l'istanza (condivisa da tutta l'applicazione) della classe NSFontManager, che si occupa appunto di gestire il pannello dei font, e che è utilizzato per trasformare il font corrente nel nuovo font selezionato (perché trasformare? Perché changeFont: è invocato anche quando, ad esempio, si seleziona la voce Grassetto da menu).

Nel realizzare la precedente funzionalità, mi sono accorto di alcuni problemi nella costruzione della lista di file. Le procedure relative, infatti, imponevano sempre lo stesso tipo di carattere, rendendo quindi insensbile CCE_Text alla modifica del font. Ho allora cambiato il metodo come segue:

- (void)
tableViewSelectionDidChange:(NSNotification *)aNotification
{
    NSTextStorage * newFileList ;
    CCE_Text        * wxw ;
    int                elNum, jj, nn ;
    NSTableView        * ov = (NSTableView*)[ aNotification object ] ;

    // predispongo il volume in selezioen corrente
    currentVol = [ ov selectedRow ] ;
    // recupero se disponibile un CCE_Text per mostrare l'elenco dei file
    wxw = [ [[refDoc coverWin] cvrView] fileList];    
    // se non c'e', ho nulla da fare
    if ( wxw == nil )
        return ;
    // costruisco un nuovo contenitore per il testo
    newFileList = [[NSTextStorage alloc] initWithString:@"dummy"];
    // ci metto del testo 'dummy' altrimenti l'assegnazione di font non funziona
    [ newFileList setFont:[ [ wxw textStorage] font ] ];
    // recupero il primo livello del volume
    elNum = [ self outlineView: nil numberOfChildrenOfItem: nil ];
    // esploro il primo livello
    for ( jj = 0 ; jj < elNum; jj ++ )
    {
        FileStruct * elem ;
        // esamino tutti gli elementi uno ad uno
        elem = [ self outlineView:     nil child: jj ofItem: nil ];
        // controllo se ha figli
        nn = [ self outlineView: nil numberOfChildrenOfItem: elem ];
        // se non e' da stampare, salto
        if ( ! [ elem is2Print ] ) continue ;
        // se e' una directory, espando di un livello
        if (nn > 0 )
            addStringExpandItem (newFileList, self, elem, nn, 0 );
        else    // lo aggiungo al testo gia' predisposto
            terminalWithLevel( newFileList, [ elem fileName], 0, NO );
    }
    // elimino i primi cinque caratteri, che non mi servono piu'
    [ newFileList deleteCharactersInRange: NSMakeRange( 0, 5 )];
    // aggiorno la stringa
    [[ wxw textStorage] setAttributedString: newFileList ] ;
    // forzo un rinfresco della vocerView
    [ [[refDoc coverWin] cvrView] setNeedsDisplay: YES ];
}

Qui recupero dall'elemento CCE_Text il font corrente, e lo utilizzo per scrivere la nuova lista di file. A quanto pare, cominciare con una stringa nulla impedisce l'assegnazione del font (mi sembra ragionevole, anche se seccante). Ho allora utilizzato una stringa fittizia, che poi elimino alla fine del procedimento.

figura 07

figura 07

Per concludere con la classe CCE_Text, ho aggiunto la possibilità di disegnare (ed eventualmente riempire di colore) il rettangolo che contiene il testo; la cosa giusta da fare sarebbe di impostare il colore del font e dello sfondo tramite il pannello font, ma non ci riesco (ma prima o poi ci arrivo...); utilizzando invece la finestra ElemInfoWinCtl è una sciocchezza. Per il disegno vero e proprio, lascio fare alla superclasse:

- (void) drawElement: (NSRect) inRect
{
    // recupero il primo layout
    NSLayoutManager *layoutManager = [ [ textStorage layoutManagers ] objectAtIndex: 0] ;
    // recupero il range del testo
    NSRange glyphRange = [layoutManager glyphRangeForTextContainer: theTextCont ];
    // disegno del testo
    [ super drawElement: inRect ];
    [ layoutManager drawGlyphsForGlyphRange:glyphRange atPoint: elemLimit.origin ];
}

Infatti, ho modificato il metodo di default della classe CCE_BasicForm perché disegni un rettangolo e lo riempia (in precedenza tale metodo era vuoto, e doveva essere riscritto da ogni sottoclasse):

- (void)
drawElement: (NSRect) inRect
{
    // se il rettangolo e' pieno
    if ( cceIsStroked )
    {
        NSBezierPath *path = [NSBezierPath bezierPathWithRect: elemLimit ];
        // imposto il colore del contorno
        [ cceLineColor set ];
        // imposto lo spesosre del contorno
        [ path setLineWidth: [ self cceLineWidth ]];
        // disegno il contorno
        [ path stroke ];
    }
    if ( cceIsFilled )
    {
        NSBezierPath *path = [NSBezierPath bezierPathWithRect: elemLimit ];
        // imposto il colore del pieno
        [ cceFillColor set ];
        // riempi tutto
        [ path fill ];
    }
}

Infine, l'ultima modifica degna di nota è il meccanismo di undo anche per gli attributi degli elementi (colori, riempimenti, eccetera). Ho quindi modificato tutti i metodi accessor della classe CCE_BasicForm in modo che tangano traccia delle operazioni effettuate sugli attributi. Ad esempio per lo spessore della linea:

- (void)setCceLineWidth:(float)newLineWidth
{
    // gestione dello undo
    [[self undoManager] setActionName: @"BasicForm -> cceLineWidth"];
    [[[self undoManager] prepareWithInvocationTarget: [ownerView theElements]]
        changeElem: self attrib: kUndoLineWidth value: [NSNumber numberWithFloat: cceLineWidth] ];
    // assegnazione effettiva
    cceLineWidth = newLineWidth;
}

In corrispondenza di una operazione di undo (o anche di redo), è invocato il metodo changeElem:attrib:value: che conserva la precedente operazione sotto forma di: oggetto; identificatore dell'attributo modificato (secondo un mio codice personale); valore dell'attributo (sotto forma di oggetto). Ancora una volta, per mantenere continuità nelle operazioni di undo, responsabile della faccenda è sempre l'oggetto theElements; di conseguenza, all'interno della classe CCE_ElemGroup si trova il metodo seguente:

- (void)
changeElem: (CCE_BasicForm*) elem attrib: (int) quale value: (id) valore
{
    switch ( quale ) {
    case kUndoLineWidth:    [ elem setCceLineWidth: [(NSNumber*)valore floatValue ] ]; break ;
    case kUndoFillColor:    [ elem setCceFillColor: (NSColor*)valore ]; break ;
    case kUndoLineColor:    [ elem setCceLineColor: (NSColor*)valore ]; break ;
    case kUndoIsFilled:        [ elem setCceIsFilled:    [(NSNumber*)valore boolValue ] ]; break ;
    case kUndoIsStroked:    [ elem setCceIsStroked:    [(NSNumber*)valore boolValue ] ]; break ;
    case kUndoIsSelected:    [ elem setCceIsSelected:[(NSNumber*)valore boolValue ] ]; break ;
    case kUndoIsLocked:        [ elem setCceIsLocked:    [(NSNumber*)valore boolValue ] ]; break ;
    }
    [ [ self ownerView ] setNeedsDisplay: YES ];
}

Per avere uniformità di trattamento, l'idea è di trasformare ogni valore degli attributi in un oggetto non meglio caratterizzato. La discriminazione sul tipo avviene tramite il codice dell'attributo.

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).