NSCell subclass editing

  • Hi !

    I'm trying to build a custom cell for inclusion in a NSTableView that
    should represent a file on disk. This cell shows a file icon, a file
    name, and a small 'change' button when hovered (I haven't got to the
    hovering part, since that's eye-candy IMHO). I actually have code
    that make it draw like I want, but I can't find a 'nice' way to
    handle editing.

    I would like to display an NSOpenPanel when the 'change' button is
    clicked, and get back the new path (as an NSString) from the
    NSTableView delegate method - tableView:setObjectValue:..., but I
    haven't been able to do so.

    I tried overriding NSCell -
    editWithFrame:inView:editor:delegate:event:, but it's never called...

    One of the alternate ways I've found is to use NSApp's -
    sendAction:to:from: with as action a selector I add to my main window
    controller and sender my cell, which kinda works, but it forces me to
    lookup the data corresponding to the cell, which is cumbersome...

    Etienne Samson
  • Sorry, forgot to sent to the list ;-)

    Le 2 oct. 07 à 18:54, Boris Remizov a écrit :

    > Hi and you!
    > As my own way, I solve these problems by creating compound NSCells
    > subclasses like
    >
    > @interface CompoundCell: NSActionCell
    > {
    > NSImageCell* cell1;
    > NSImageCell* cell2;
    > NSTextCell* cell3;
    > NSButtonCell* cell4;
    > InfoButtonCell* cell5;
    > ....
    > }
    >
    > - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)
    > controlView
    >
    > @end
    >
    > In drawInteriorWithFrame i call the drawInteriorWithFrame methods
    > of sub-cells with appropriate coordinates.
    > As about tableView:setObjectValue delegate, you may return any
    > object from objectValueForTableColumn method with any properties,
    > and read ones in Cell's setObjectValue method.

    I'm sorry I don't understand what you mean... ;-)
    What I'm basically trying to achieve is that I want my delegate
    method tableView:setObjectValue called when the NSOpenPanel closes,
    so I'm not forced to use ugly hacks (like resorting to the Responder
    chain...).

    Here is code I have, that display an NSOpenPanel as a sheet, the only
    thing missing is how I call the delegate method... There is some
    twiddling included with the NSText field, since it seems to be
    responsible of the delegate's calls...

    @implementation ARPathCell

    - (NSImage*) fileImage
    {
        if (fileImage == nil)
        {
            fileImage = [[[NSWorkspace sharedWorkspace] iconForFile:
    [self objectValue]] retain];
            [fileImage setFlipped:YES];
        }

        return fileImage;
    }

    - (NSImage*) editImage
    {
        if (editImage == nil)
        {
            editImage = [[[NSWorkspace sharedWorkspace] iconForFile:
    [self objectValue]] retain];
            [editImage setFlipped:YES];
        }

        return editImage;
    }

    - (NSRect) imageRectForBounds:(NSRect)frame
    {
        NSRect result = frame;
        float iconSize = MIN( frame.size.width, frame.size.height );

        result.size = NSMakeSize( iconSize, iconSize );
        return result;
    }

    - (NSRect) textRectForBounds:(NSRect)frame
    {
        NSRect result = frame;

        float iconSize = MIN( frame.size.width, frame.size.height );

        result.origin.x += iconSize + 5;
        result.origin.y += (frame.size.height / 3);

        result.size.width = frame.size.width - ( iconSize + 5 );

        return result;
    }

    - (NSRect) buttonRectForBounds:(NSRect)frame
    {
        NSRect result = frame;

        result.origin.x += frame.size.width - 16 ;
        result.origin.y += (frame.size.height / 3);

        result.size = NSMakeSize( 16, 16 );

        return result;
    }

    - (void) drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
    {
        [[self fileImage] drawInRect:[self imageRectForBounds:cellFrame]
                            fromRect:NSZeroRect
                            operation:NSCompositeSourceOver
                            fraction:1.0];

        NSString * displayName = [[NSFileManager defaultManager]
    displayNameAtPath:[self objectValue]];
        [displayName drawInRect:[self textRectForBounds:cellFrame]
                  withAttributes:nil];

        [[self editImage] drawInRect:[self buttonRectForBounds:cellFrame]
                            fromRect:NSZeroRect
                            operation:NSCompositeSourceOver
                            fraction:1.0];
    }

    - (void)editWithFrame:(NSRect)aRect inView:(NSView *)controlView
    editor:(NSText *)textObj delegate:(id)anObject event:(NSEvent *)theEvent
    {
        NSLog(@"%s %@ %@ %@ %@ %@", _cmd, NSStringFromRect(aRect),
    controlView, textObj, anObject, theEvent);

        NSOpenPanel * oPanel = [NSOpenPanel openPanel];
        [textObj setDelegate:anObject];

        [oPanel setAllowsMultipleSelection:NO];
        [oPanel beginSheetForDirectory:nil
                                  file:nil
                        modalForWindow:[controlView window]
                          modalDelegate:self
                        didEndSelector:@selector
    (editPathCellSheetDidEnd:returnCode:context:)
                            contextInfo:textObj];
    }

    - (void) editPathCellSheetDidEnd:(NSOpenPanel *)panel returnCode:(int)
    returnCode context:(void*)contextInfo
    {
        NSText * text = contextInfo;
        if (returnCode == NSOKButton)
        {
            NSString * file = [[panel filenames] objectAtIndex:0];
            [self setObjectValue:file];
            [text setString:file];
        }

        [self endEditing:text];
    }

    @end
  • Hi and you!
    As my own way, I solve these problems by creating compound NSCells
    subclasses like

    @interface CompoundCell: NSActionCell
    {
    NSImageCell* cell1;
    NSImageCell* cell2;
    NSTextCell* cell3;
    NSButtonCell* cell4;
    InfoButtonCell* cell5;
    ....
    }

    - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)
    controlView

    @end

    In drawInteriorWithFrame i call the drawInteriorWithFrame methods of
    sub-cells with appropriate coordinates.
    As about tableView:setObjectValue delegate, you may return any object
    from objectValueForTableColumn method with any properties, and read
    ones in Cell's setObjectValue method.

    On Oct 2, 2007, at 17:16 , tiennou wrote:

    > Hi !
    >
    > I'm trying to build a custom cell for inclusion in a NSTableView
    > that should represent a file on disk. This cell shows a file icon,
    > a file name, and a small 'change' button when hovered (I haven't
    > got to the hovering part, since that's eye-candy IMHO). I actually
    > have code that make it draw like I want, but I can't find a 'nice'
    > way to handle editing.
    >
    > I would like to display an NSOpenPanel when the 'change' button is
    > clicked, and get back the new path (as an NSString) from the
    > NSTableView delegate method - tableView:setObjectValue:..., but I
    > haven't been able to do so.
    >
    > I tried overriding NSCell -
    > editWithFrame:inView:editor:delegate:event:, but it's never called...
    >
    > One of the alternate ways I've found is to use NSApp's -
    > sendAction:to:from: with as action a selector I add to my main
    > window controller and sender my cell, which kinda works, but it
    > forces me to lookup the data corresponding to the cell, which is
    > cumbersome...
    >
    >
    > Etienne Samson
  • Hi there,

    AFAIK, what you're trying to achieve is not 'in cell editing' but
    simple button pressing, so your cell acts more like a NSButtonCell,
    right? So why not use the target/action mechanism to inform the
    controller object (which probably is also the dataSource for your
    tableView)? The controller would then handle the open panel (as its
    delegate) and write the returned value into your model object. You
    could get the appropriate position of the model object (in your
    dataSource's array) by determining the 'calling' cell's row (i.e. use
    something like [myTableView selectedRow]). After that, simply update
    the table view by sending [tableView reloadData]. If everything is
    properly implemented, the tableView should display the new value in
    the specific row. IMHO, this should conform better to the MVC
    paradigm, does it?

    Just my 2 cents.

    Best regards,
    Oliver
  • Le 2 oct. 07 à 20:26, Oliver Quas a écrit :

    > Hi there,
    >
    > AFAIK, what you're trying to achieve is not 'in cell editing' but
    > simple button pressing, so your cell acts more like a NSButtonCell,
    > right? So why not use the target/action mechanism to inform the
    > controller object (which probably is also the dataSource for your
    > tableView)? The controller would then handle the open panel (as its
    > delegate) and write the returned value into your model object. You
    > could get the appropriate position of the model object (in your
    > dataSource's array) by determining the 'calling' cell's row (i.e.
    > use something like [myTableView selectedRow]). After that, simply
    > update the table view by sending [tableView reloadData]. If
    > everything is properly implemented, the tableView should display
    > the new value in the specific row. IMHO, this should conform better
    > to the MVC paradigm, does it?
    >
    > Just my 2 cents.

    Hmm, yes I think I'll go with this approach instead. It just felt
    that my custom cell was a better place to manage it's data ;-).
    Actually it seems like non-text NSCell can't easily be customized to
    support another editor (I mean I won't get my updated data by the
    delegate method)... Guess I'll file a bug requesting an API allowing
    to use something else than NSText for editing ;-)

    Thanks for you suggestions all !

    Etienne Samson
  • >>> In drawInteriorWithFrame i call the drawInteriorWithFrame methods
    >>> of sub-cells with appropriate coordinates.
    >>> As about tableView:setObjectValue delegate, you may return any
    >>> object from objectValueForTableColumn method with any properties,
    >>> and read ones in Cell's setObjectValue method.
    >>
    >> I'm sorry I don't understand what you mean... ;-)
    >> What I'm basically trying to achieve is that I want my delegate
    >> method tableView:setObjectValue called when the NSOpenPanel
    >> closes, so I'm not forced to use ugly hacks (like resorting to the
    >> Responder chain...).
    > Sorry I don't hear anything about setObjectValue delegate method
    > of NSTableView. setObjectValue is a method of NSCell class. IMHO
    > you should simply use your dataSource reference to "save" the
    > data you selected in NSOpenPanel into its internal structures
    > (with its custom methods) and then update NSTableView....

    Hmm, I'll try to explain what I mean :
    Right now, when you're using default (eg NSTextFieldCell) cells, your
    tableview datasource recieve -
    tableView:setObjectValue:tableColumn:row when editing finishes,
    allowing the datasource to update itself (storing the new value in to
    whatever stuff you're using as datasource ;-)).
    Trying to override this behavior in -[NSCell
    editWithFrame:inView:editor:delegate:event:] right now is a real
    pain, meaning that if I want my NSCell subclass to use something
    other than NSText for editing, (eg display an NSOpenPanel, or
    NSColorPanel) :
    - I need to call my super implementation (which has the drawback that
    the text editor display for a short while before the panel covers
    it), setting the -[NSText string] to the NSOpenPanel result back in
    the NSOpenPanel selector called after the sheet is closed (whici is
    the value passed to the datasource in the end). This also means
    passing a color is impossible with this approach...
    - Use private stuff inside NSTableView to make it call it's delegate
    with the correct object (which break the separation between a cell
    and its control, because now my cell will only work inside
    NSTableViews).

    NSCell should be made editor-agnostic, by changing -
    [editWithFrame:...] into -[editWithFrame:InView:] (putting the
    current implementation in NSTextFieldCell, where it belongs), and -
    [endEditing:(NSText*)] into -[endEditing:(id)
    theObjectThatWillBePassedToTheDelegate], but that's my 2 cents...

    Etienne Samson
  • >
    > Hmm, I'll try to explain what I mean :
    > Right now, when you're using default (eg NSTextFieldCell) cells,
    > your tableview datasource recieve -
    > tableView:setObjectValue:tableColumn:row when editing finishes,
    > allowing the datasource to update itself (storing the new value in
    > to whatever stuff you're using as datasource ;-)).
    > Trying to override this behavior in -[NSCell
    > editWithFrame:inView:editor:delegate:event:] right now is a real
    > pain, meaning that if I want my NSCell subclass to use something
    > other than NSText for editing, (eg display an NSOpenPanel, or
    > NSColorPanel) :
    > - I need to call my super implementation (which has the drawback
    > that the text editor display for a short while before the panel
    > covers it), setting the -[NSText string] to the NSOpenPanel result
    > back in the NSOpenPanel selector called after the sheet is closed
    > (whici is the value passed to the datasource in the end). This also
    > means passing a color is impossible with this approach...
    > - Use private stuff inside NSTableView to make it call it's delegate
    > with the correct object (which break the separation between a cell
    > and its control, because now my cell will only work inside
    > NSTableViews).
    >
    > NSCell should be made editor-agnostic, by changing -
    > [editWithFrame:...] into -[editWithFrame:InView:] (putting the
    > current implementation in NSTextFieldCell, where it belongs), and -
    > [endEditing:(NSText*)] into -[endEditing:
    > (id)theObjectThatWillBePassedToTheDelegate], but that's my 2 cents...

    You *could* do this in a subclass of NSTableView. Simply make some
    informal protocol and expect your cells to call it.

    Have you logged a bug/feature request for this? Please do, if you want
    to see things like this end up in AppKit.

    corbin
previous month october 2007 next month
MTWTFSS
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
Go to today