Source list with counter next to item title

  • What's the best way to have a sourcelist with a counter next to the title as
    you see in many apps (Mail and other indie apps)? I mean the grey
    circle-ellipse with the number of items written inside it.
    Maybe I need to subclass NSCell and override the drawInteriorWithFrame:
    method, but how could I set the counter from, let's say, an arraycontroller?
    Would it be possible using bindings?

    Who could give me a detailed explanation of the besth method to achieve
    that? I read other posts and articles about subclassing NSCell but none of
    that seems clear and easy to do.

    Thanks a lot.
  • On Nov 19, 2007, at 4:14 AM, GbT wrote:

    > What's the best way to have a sourcelist with a counter next to the
    > title as
    > you see in many apps (Mail and other indie apps)? I mean the grey
    > circle-ellipse with the number of items written inside it.
    > Maybe I need to subclass NSCell

    Yes, You'd need to write your own subclass of NSCell to do this.

    Have a look at the ClockControl and SimpleBrowser examples in /
    Developer/Examples/AppKit

    > and override the drawInteriorWithFrame:
    > method, but how could I set the counter from, let's say, an
    > arraycontroller?
    >
    > Would it be possible using bindings?

    You'd need to add bindings support to your subclass of NSCell.

    I'm not sure that the on-disk version of ClockControl has this
    implemented.. you may want to check the web version at
    developer.apple.com to see if it is more current.
  • just to follow up to my own answer.

    if you're looking at leopard only compatibility, you may want to
    consider NSCollectionView instead.

    it would allow you to create the individual items as multiple views,
    which may already support bindings

    On Nov 19, 2007, at 4:23 AM, Scott Anguish wrote:

    >
    > On Nov 19, 2007, at 4:14 AM, GbT wrote:
    >
    >> What's the best way to have a sourcelist with a counter next to the
    >> title as
    >> you see in many apps (Mail and other indie apps)? I mean the grey
    >> circle-ellipse with the number of items written inside it.
    >> Maybe I need to subclass NSCell
    >
    > Yes, You'd need to write your own subclass of NSCell to do this.
    >
    > Have a look at the ClockControl and SimpleBrowser examples in /
    > Developer/Examples/AppKit
    >
    >> and override the drawInteriorWithFrame:
    >> method, but how could I set the counter from, let's say, an
    >> arraycontroller?
    >>
    >> Would it be possible using bindings?
    >
    >
    > You'd need to add bindings support to your subclass of NSCell.
    >
    > I'm not sure that the on-disk version of ClockControl has this
    > implemented.. you may want to check the web version at
    > developer.apple.com to see if it is more current.
  • 2007/11/19, Scott Anguish <scott...>:
    >
    > just to follow up to my own answer.
    >
    > if you're looking at leopard only compatibility, you may want to
    > consider NSCollectionView instead.
    >
    > it would allow you to create the individual items as multiple views,
    > which may already support bindings

    Thanks, I'll try the first solution before switching to leopard only
    compatibility.
    I understand I need to create a subclass of nscell, I'm thinking of setting
    in interface builder the new class name on the table column property, then
    implement the drawInteriorWithFrame: method on the nscell subclass, but for
    the value to display I don't know how to proceed, even without bindings.
    Other than Developer Examples, is there a simple and clear solution fitting
    my basic need? Maybe I need to understand better how this is working from
    the start.

    Thanks again.


    >
    >
  • I could return the value to display for the count cell in the datasource
    method of the source list, that is:
    tableView: objectValueForTableColumn :row, or the corresponding method of
    the outlineview if I use that instead of a tableview.
  • You can use bindings or a data source; either way return an NSNumber with
    the count and then draw it in your custom cell. DVDpedia, Bookpedia, CDpedia
    and Gamepedia use bindings directly to a core data managed object that
    returns the count of a relationship:

    return [NSNumber numberWithInt:[[self entries] count]];

    For the custom cell a subclass of an NSImageCell:

    @interface MyCountImageCell : NSImageCell {
        @private
        NSColor *blueOvalColor;
        NSDictionary *selectedFontAttribute, *fontAttribute;
        int previousCount;
    }
    @end

    // http://www.cocoadev.com/index.pl?NSBezierPathCategory
    @interface NSBezierPath (CocoaDevCategory)
    + (NSBezierPath*)bezierPathWithRoundRectInRect:(NSRect)aRect
    radius:(float)radius;
    @end

    @implementation MyCountImageCell

    - (id)init {
        self = [super init];
        if (self != nil) {
            blueOvalColor = [[NSColor colorWithDeviceRed:154.0/255.0
    green:171.0/255.0 blue:201.0/255.0 alpha:1.0] retain];
            selectedFontAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
    [NSFont fontWithName:@"Helvetica-Bold" size:13], NSFontAttributeName,
    [NSColor colorWithDeviceRed:150.0/255.0 green:161.0/255.0 blue:183.0/255.0
    alpha:1.0], NSForegroundColorAttributeName, nil] retain];
            fontAttribute = [[NSDictionary dictionaryWithObjectsAndKeys: [NSFont
    fontWithName:@"Helvetica-Bold" size:13], NSFontAttributeName,  [NSColor
    whiteColor], NSForegroundColorAttributeName, nil] retain];
        }
        return self;
    }

    - (void)setObjectValue:(id)aValue {

        if ([aValue isKindOfClass:[NSImage class]]) {
            [super setObjectValue:aValue];
            return;
        }

        unsigned int count = [aValue intValue];
        if (previousCount != count) {

            if (count == -1) { // a Group colelction don't draw
                [super setObjectValue:nil];
                return;
            }

            int additonalSpace;
            if (count < 10)
                additonalSpace = 0;
            else if (count < 100)
                additonalSpace = 8;
            else if (count < 1000)
                additonalSpace = 16;
            else
                additonalSpace = 24 ;

            NSString *numberString = [NSString stringWithFormat:@"%d", count];
            NSSize imageSize = NSMakeSize(22 + additonalSpace, 18);
            NSImage *dragImage = [[NSImage alloc] initWithSize:imageSize];

            [dragImage lockFocus];

            [blueOvalColor set];
            NSBezierPath *p = [NSBezierPath
    bezierPathWithRoundRectInRect:NSMakeRect(0, 2, imageSize.width - 2, 15)
    radius:9.0];
            [p fill];

            [numberString drawInRect:NSMakeRect(6, 1, imageSize.width, 18)
    withAttributes:fontAttribute];

            [dragImage unlockFocus];

            [super setObjectValue:[dragImage autorelease]];
            previousCount = count;
        }
    }

    @end

    @implementation NSBezierPath (CocoaDevCategory)

    + (NSBezierPath*)bezierPathWithRoundRectInRect:(NSRect)aRect
    radius:(float)radius
    {
      NSBezierPath* path = [self bezierPath];
      radius = MIN(radius, 0.5f * MIN(NSWidth(aRect), NSHeight(aRect)));
      NSRect rect = NSInsetRect(aRect, radius, radius);
      [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect),
    NSMinY(rect))
                                              radius:radius startAngle:180.0
    endAngle:270.0];
      [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect),
    NSMinY(rect))
                                              radius:radius startAngle:270.0
    endAngle:360.0];
      [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect),
    NSMaxY(rect))
                                              radius:radius startAngle:  0.0
    endAngle: 90.0];
      [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect),
    NSMaxY(rect))
                                              radius:radius startAngle: 90.0
    endAngle:180.0];
      [path closePath];
      return path;
    }

    @end

    Regards,
    Conor
    http://www.bruji.com/
  • Thanks very much!
    Right before this approach I subclassed nstextfieldcell and draw a filled
    oval to contain the textual count, it works well, but not as much as this
    solution I suppose. What's the difference with mine solution, the final
    effect or what else? In your case you use NSImageCell as a base, I choosed
    NSTextFieldCell, maybe the rendering of the oval is much different and
    elegant.

    2007/11/20, Conor <mailinglists...>:
    >
    > You can use bindings or a data source; either way return an NSNumber with
    > the count and then draw it in your custom cell. DVDpedia, Bookpedia,
    > CDpedia
    > and Gamepedia use bindings directly to a core data managed object that
    > returns the count of a relationship:
    >
    > return [NSNumber numberWithInt:[[self entries] count]];
    >
    > For the custom cell a subclass of an NSImageCell:
    >
    > @interface MyCountImageCell : NSImageCell {
    > @private
    > NSColor *blueOvalColor;
    > NSDictionary *selectedFontAttribute, *fontAttribute;
    > int previousCount;
    > }
    > @end
    >
    > // http://www.cocoadev.com/index.pl?NSBezierPathCategory
    > @interface NSBezierPath (CocoaDevCategory)
    > + (NSBezierPath*)bezierPathWithRoundRectInRect:(NSRect)aRect
    > radius:(float)radius;
    > @end
    >
    >
    > @implementation MyCountImageCell
    >
    > - (id)init {
    > self = [super init];
    > if (self != nil) {
    > blueOvalColor = [[NSColor colorWithDeviceRed:154.0/255.0
    > green:171.0/255.0 blue:201.0/255.0 alpha:1.0] retain];
    > selectedFontAttribute = [[NSDictionary
    > dictionaryWithObjectsAndKeys:
    > [NSFont fontWithName:@"Helvetica-Bold" size:13], NSFontAttributeName,
    > [NSColor colorWithDeviceRed:150.0/255.0 green:161.0/255.0 blue:183.0/255.0
    > alpha:1.0], NSForegroundColorAttributeName, nil] retain];
    > fontAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
    > [NSFont
    > fontWithName:@"Helvetica-Bold" size:13], NSFontAttributeName,  [NSColor
    > whiteColor], NSForegroundColorAttributeName, nil] retain];
    > }
    > return self;
    > }
    >
    >
    > - (void)setObjectValue:(id)aValue {
    >
    > if ([aValue isKindOfClass:[NSImage class]]) {
    > [super setObjectValue:aValue];
    > return;
    > }
    >
    > unsigned int count = [aValue intValue];
    > if (previousCount != count) {
    >
    > if (count == -1) { // a Group colelction don't draw
    > [super setObjectValue:nil];
    > return;
    > }
    >
    > int additonalSpace;
    > if (count < 10)
    > additonalSpace = 0;
    > else if (count < 100)
    > additonalSpace = 8;
    > else if (count < 1000)
    > additonalSpace = 16;
    > else
    > additonalSpace = 24 ;
    >
    > NSString *numberString = [NSString stringWithFormat:@"%d", count];
    > NSSize imageSize = NSMakeSize(22 + additonalSpace, 18);
    > NSImage *dragImage = [[NSImage alloc] initWithSize:imageSize];
    >
    > [dragImage lockFocus];
    >
    > [blueOvalColor set];
    > NSBezierPath *p = [NSBezierPath
    > bezierPathWithRoundRectInRect:NSMakeRect(0, 2, imageSize.width - 2, 15)
    > radius:9.0];
    > [p fill];
    >
    > [numberString drawInRect:NSMakeRect(6, 1, imageSize.width, 18)
    > withAttributes:fontAttribute];
    >
    > [dragImage unlockFocus];
    >
    > [super setObjectValue:[dragImage autorelease]];
    > previousCount = count;
    > }
    > }
    >
    > @end
    >
    >
    > @implementation NSBezierPath (CocoaDevCategory)
    >
    > + (NSBezierPath*)bezierPathWithRoundRectInRect:(NSRect)aRect
    > radius:(float)radius
    > {
    > NSBezierPath* path = [self bezierPath];
    > radius = MIN(radius, 0.5f * MIN(NSWidth(aRect), NSHeight(aRect)));
    > NSRect rect = NSInsetRect(aRect, radius, radius);
    > [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect),
    > NSMinY(rect))
    > radius:radius startAngle:180.0
    > endAngle:270.0];
    > [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect),
    > NSMinY(rect))
    > radius:radius startAngle:270.0
    > endAngle:360.0];
    > [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect),
    > NSMaxY(rect))
    > radius:radius startAngle:  0.0
    > endAngle: 90.0];
    > [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect),
    > NSMaxY(rect))
    > radius:radius startAngle: 90.0
    > endAngle:180.0];
    > [path closePath];
    > return path;
    > }
    >
    > @end
    >
    > Regards,
    > Conor
    > http://www.bruji.com/
    >
  • > Right before this approach I subclassed nstextfieldcell and draw a filled
    > oval to contain the textual count, it works well, but not as much as this
    > solution I suppose. What's the difference with mine solution, the final
    > effect or what else?

    There is really no exact approach. As you mentioned it's just a personal
    choice. NSTextFieldCell works just as well, in fact if you wanted editing
    it would be the way to go; it's lower in the chain (after NSActionCell) and
    has that functionality, including access to a field editor. It is even more
    flexible than my solution, you could subclass drawInteriorWithFrame:inView:
    and draw your oval to fit the cell size and let the cell take care of the
    text. I just choose NSImageCell because it's something I am more familiar
    with. I am glad you found the code useful.

    - Conor
    http://www.bruji.com/
previous month november 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    
Go to today