Custom NSTextFieldCell and background drawing - cocoa

I created a custom NSTextFieldCell and overwrote - (void)drawInteriorWithFrame: (NSRect)cellFrame inView: (NSView *)controlView to do my own drawing here. However, I have trouble with background drawing. Without calling super the background is not cleared and subsequent drawings create something like a smear effect. This does not happen when drawsBackground is set, as in this case I can just fill the cellFrame with the background color.
- (void)drawInteriorWithFrame: (NSRect)cellFrame inView: (NSView *)controlView {
if (self.drawsBackground) {
[self.backgroundColor set];
} else {
[NSColor.clearColor set];
}
NSRectFill(cellFrame);
[self.attributedStringValue drawInRect: cellFrame];
}
But what do I have to do to clear the background in case background drawing is disabled? I want to let the other content under the text view to shine through of course (so, just erasing with the superview's background color is no solution).

If you try to fill the cell with a [NSColor clearColor] it will draw it Black.
Try to avoid the Fill when it is not needed. And you will be able to remove your super call.
Example:
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
if (self.drawsBackground) {
if (self.backgroundColor && self.backgroundColor.alphaComponent>0) {
[self.backgroundColor set];
NSRectFill(cellFrame);
}
}
NSRect titleRect = [self titleRectForBounds:cellFrame];
NSAttributedString *aTitle = [self attributedStringValue];
if ([aTitle length] > 0) {
[aTitle drawInRect:titleRect];
}
}

Related

Custom NSMenu like view not displaying selectedMenuItemColor correctly

I wrote my own NSMenu like class to show dynamic search results below a NSSearchField in a borderless NSWindow. It's working well but doesn't draw the magic selectedMenuItemColor correctly if I add some padding to the top of the subview. I put a 5 pixel padding at the top of container view to mimic NSMenu and when I do it blue selected gradient looks off. A picture & code should make this clear:
Here is my drawRect code inside my item view (remember this is just a regular NSView):
-(void) drawRect:(NSRect)dirtyRect {
CGRect b = self.bounds;
if (selected) {
[NSGraphicsContext saveGraphicsState];
[[NSColor selectedMenuItemColor] set];
NSRectFill( b );
[NSGraphicsContext restoreGraphicsState];
if (textField) {
textField.textColor = [NSColor selectedMenuItemTextColor];
}
} else {
[[NSColor clearColor] set];
NSRectFillUsingOperation(b, NSCompositeSourceOver);
if (textField) {
textField.textColor = [NSColor blackColor];
}
}
}
You have to get the pattern phase origin to match your view frame.
That is, selectedMenuItemColor is actually a pattern, not a color, and that pattern is designed to display "properly" in "standard menu item height" increments. Because you have added the padding, now it is not being drawn at the "standard" location.
Try this:
-(void) drawRect:(NSRect)dirtyRect {
CGRect b = self.bounds;
if (selected) {
NSPoint origin = [self frame].origin;
curContext = [NSGraphicsContext currentContext];
[curContext saveGraphicsState];
[curContext setPatternPhase: origin];
[[NSColor selectedMenuItemColor] set];
NSRectFill( b );
[curContext restoreGraphicsState];
if (textField) {
textField.textColor = [NSColor selectedMenuItemTextColor];
}
} else {
[[NSColor clearColor] set];
NSRectFillUsingOperation(b, NSCompositeSourceOver);
if (textField) {
textField.textColor = [NSColor blackColor];
}
}
}

setNeedsDisplay for DrawRect after button press

I have this in a custom class on my NSView and when I press a button using my drawMyRec IBAction I want to change the color of my NSRect however this is not working, can anyone help?
#import "myView.h"
#implementation myView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
RectColor = [[NSColor blackColor] init];
}
return self;
}
- (IBAction)drawMyRec:(id)sender;
{
NSLog(#"pressed");
RectColor = [[NSColor blueColor] init];
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)dirtyRect
{
NSRectFill(dirtyRect);
[RectColor set];
}
#end
Your drawRect function is incorrect. change to this:
- (void)drawRect:(NSRect)dirtyRect
{
[RectColor set];
NSRectFill(dirtyRect);
}
First, why are you calling -init on an NSColor? Second, you're setting the color after you've drawn the rect, so it won't take affect until the next redraw. 3rd, what's dirtyRect when -drawRect: is called? Why not just fill the entire rect regardless of what's dirty?

NSScrollView background image in Lion

I'm working on a Mac application with an NSScrollView, and I want the NSScrollView to have a custom background image. I used this code in the custom documentView NSView subclass:
- (void)drawRect:(NSRect)rect {
[[NSColor colorWithPatternImage:[NSImage imageNamed:#"wood.jpg"]] set];
NSRectFill(rect);
}
That displays a pattern image as a background for the documentView.
But now in Mac OS X Lion, the NSScrollView bounces when scrolling further than possible, showing ugly white space. How can I make the white space also being covered by the background image?
Instead of overriding drawRect:, use the scroll view's setBackgroundColor: method, passing the NSColor you created with the pattern image.
You should subclass use NSScrollView setBackgroundColor, but then you should subclass NSClipView like this to pin the texture origin to the top:
#implementation MYClipView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
if (self.drawsBackground)
{
NSGraphicsContext* theContext = [NSGraphicsContext currentContext];
[theContext saveGraphicsState];
float xOffset = NSMinX([self convertRect:[self frame] toView:nil]);
float yOffset = NSMaxY([self convertRect:[self frame] toView:nil]);
[theContext setPatternPhase:NSMakePoint(xOffset, yOffset)];
NSColor* color = self.backgroundColor;
[color set];
NSRectFill([self bounds]);
[theContext restoreGraphicsState];
}
// Note: We don't call [super drawRect:dirtyRect] because we don't need it to draw over our background.
}
+ (void)replaceClipViewInScrollView:(NSScrollView*)scrollView
{
NSView* docView = [scrollView documentView]; //[[scrollView documentView] retain];
MYClipView* newClipView = nil;
newClipView = [[[self class] alloc] initWithFrame:[[scrollView contentView] frame]];
[newClipView setBackgroundColor:[[scrollView contentView] backgroundColor]];
[scrollView setContentView:(NSClipView*)newClipView]; [scrollView setDocumentView:docView];
// [newClipView release];
// [docView release];
}
#end
And call + (void)replaceClipViewInScrollView:(NSScrollView*)scrollView with the NSScrollView instance.
Put your drawRect code in an NSScrollView subclass. In IB, change the NSScrollView to use your custom subclass instead of NSScrollView. Also make sure to uncheck Draw Background in the scroll view's attributes inspector.

How to determine when NSTextFieldCell isHighlighted has no focus?

I've subclassed a NSTextFieldCell (inside a NSTableView) to draw a custom foreground color when a cell (ie row) is selected (eg isHighlighted is true) and everything works fine.
The problem is when the table view loses the focus I want to draw the selected rows with a different color, how can I determine if the table view containing the cell isn't the first responder inside drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView?
My current code is
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
NSColor* textColor = [self isHighlighted]
? [NSColor alternateSelectedControlTextColor]
: [NSColor darkGrayColor];
}
The best way I've found that doesn't make you deal with responders (since sometimes the controlView's superview is the responder or some nonsense) is to use the editor:
BOOL isEditing = [(NSTextField *)[self controlView] currentEditor] != nil;
Easy as that!
I've found a solution that uses the firstResponder, it is simple and seems efficient
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
NSColor* textColor;
if ([self isHighlighted]) {
textColor = [[controlView window] firstResponder] == controlView
? [NSColor alternateSelectedControlTextColor]
: [NSColor yellowColor];
} else {
textColor = [NSColor darkGrayColor];
}
// use textColor
...
...
[super drawWithFrame:cellFrame inView:controlView];
}
one more thing, the above code is perfect, however if you have multiple windows
you will need to check if your window is key
if (controlView && ([[controlView window] firstResponder] == controlView) && [[controlView window] isKeyWindow]) {
[attributes setObject:[NSColor whiteColor] forKey:NSForegroundColorAttributeName];
}

Cocoa: Accepting and responding to keystrokes

Hey everyone, I'm a newbie and I have what I anticipate will be a pretty easy question to answer. In order to learn a bit about event handling and drawing, I'm attempting to write a program that draws a black rectangle that increases in length every time the user hits the 'c' key. So far it just draws a black rectangle on a blue background without responding to keystrokes. Here is what I have so far:
Input.h
#import <Cocoa/Cocoa.h>
#interface Input : NSView {
int length;
}
- (void)keyDown:(NSEvent *)theEvent;
#end
Input.m
#import "Input.h"
#implementation Input
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
length = 10;
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
//set variables
NSRect r1;
NSBezierPath *bp;
// set background color
[[NSColor blueColor] set];
NSRectFill(dirtyRect);
//set color to black & draw r1
[[NSColor blackColor] set];
r1 = NSMakeRect(1, 1, length, 10);
bp = [NSBezierPath bezierPathWithRect:r1];
[bp fill];
}
- (void)keyDown:(NSEvent *)theEvent
{
NSString *key = [theEvent characters];
if ( [key isEqualToString:#"c"] ) {
length += 10;
}
}
#end
I copied the keyDown method from Cocoa in a Nutshell, by the way. Needless to say, I don't really understand it. Do I have to make connections in IB in order to get the program to recognize keystrokes? Basically, I would love it if somebody could help me to get this program to work, because as of yet I have not gotten anything to respond to keystrokes.
And here's Cocoa in a Nutshell
IIRC, to receive keystrokes your view needs to become first responder. It should work if you add something like these methods:
- (BOOL) acceptsFirstResponder
{
return YES;
}
- (BOOL) resignFirstResponder
{
return YES;
}
- (BOOL) becomeFirstResponder
{
return YES;
}
(You can do other stuff in them too, of course, if appropriate.)
Update: You also need to mark your view as needing to be redrawn. Add:
[self setNeedsDisplay:YES];
To your event handler. And it's probably a good idea to add a log message at the beginning as well, so that you can see whether the method is getting called:
NSLog(#"keyDown [%#]", [theEvent characters]);

Resources