I am using NSButtonCell subclass to display checkbox , image and label in same cell. Now i want to edit label in outlineView on double click or on enter key.
NSButtonCell subclass:
- (void)dealloc {
[image release];
image = nil;
[super dealloc];
}
-(id)copyWithZone:(NSZone *)zone {
ImageAndTextAndCheckBox *cell = (ImageAndTextAndCheckBox *)[super copyWithZone:zone];
cell->image = [image retain];
return cell;
}
- (void)setImage:(NSImage *)anImage {
if (anImage != image) {
[image release];
image = [anImage retain];
[image setSize:NSMakeSize(kIconImageSize, kIconImageSize)];
}
}
- (NSImage *)image {
return image;
}
#define KNImageAndTextButtonCellPadding 5 // Distance between the end of the image and the start of the text.
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
// Draw the original button first, then the image.
//shift check box to right
NSRect newCellFrame = cellFrame;
newCellFrame.origin.x = newCellFrame.origin.x + 5;
cellFrame = newCellFrame;
[super drawWithFrame:cellFrame inView:controlView];
if (image != nil) {
NSRect imageFrame;
NSSize imageSize = [image size];
NSDivideRect(cellFrame, &imageFrame, &cellFrame, KNImageAndTextButtonCellPadding + imageSize.width, NSMinXEdge);
imageFrame.origin.x += imageFrame.size.width;
imageFrame.size = imageSize;
if ([controlView isFlipped]) {
imageFrame.origin.y += ceil((cellFrame.size.height + imageFrame.size.height) / 2);
} else {
imageFrame.origin.y += ceil((cellFrame.size.height - imageFrame.size.height) / 2);
}
BOOL oldFlipped = [image isFlipped];
[image setFlipped:![controlView isFlipped]];
[image compositeToPoint:imageFrame.origin operation:NSCompositeSourceOver];
[image setFlipped:oldFlipped];
}
}
-(BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp {
// Make sure that the mouse only gets tracked if it is clicked on the check box, otherwise
// the row will be checked/unchecked whenever the user clicks on any part of the cell (which is bad).
if ([theEvent clickCount] > 1)
{
NSLog(#"do double-click action" );
}
else
{
NSLog(#"do single-click action" );
}
NSPoint event_location = [theEvent locationInWindow];
NSPoint localPoint = [controlView convertPoint:event_location fromView:nil];
//shift check box mouse event to right
NSRect newCellFrame = cellFrame;
newCellFrame.origin.x = newCellFrame.origin.x + 5;
cellFrame = newCellFrame;
if (localPoint.x <= cellFrame.origin.x + 16) {
return [super trackMouse:theEvent inRect:cellFrame ofView:controlView untilMouseUp:untilMouseUp];
} else {
return NO;
}
}
-(NSRect)drawTitle:(NSAttributedString *)title withFrame:(NSRect)frame inView:(NSView *)controlView {
// Adjust the rect so we don't interfere with the image's location
if (image != nil) {
//NSLog(#"orig %f %f %f %f", frame.size.width,frame.size.height,frame.origin.x,frame.origin.y);
NSRect newFrame = NSOffsetRect(frame, [[self image] size].width + KNImageAndTextButtonCellPadding, 0);
newFrame.size.width -= ([[self image] size].width + KNImageAndTextButtonCellPadding);
//NSLog(#"drawTitle %f %f %f %f", newFrame.size.width,newFrame.size.height,newFrame.origin.x,newFrame.origin.y);
return [super drawTitle:title withFrame:newFrame inView:controlView];
} else {
return [super drawTitle:title withFrame:frame inView:controlView];
}
}
- (void)editWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject event:(NSEvent *)theEvent {
NSLog(#"%#", anObject);
// id node = [anObject itemAtRow: [anObject selectedRow]];
// int length = [[node relativePath] length];
NSRect newFrame = NSOffsetRect(aRect, KNImageAndTextButtonCellPadding+kIconImageSize+ 16, 0);
newFrame.size.width -= (KNImageAndTextButtonCellPadding+kIconImageSize+ 16);
[super editWithFrame:newFrame inView:controlView editor:textObj delegate:anObject event:theEvent];
}
I am getting always 1 (boolean value) in
- (void)outlineView:(NSOutlineView *)ov
setObjectValue:(id)obj
forTableColumn:(NSTableColumn *)tableColumn
byItem:(id)item
NSOutlineView delegate method.(because of NSButtonCell) but i want to edit label.
How can i edit label?
How to handle double click ?
Before pressing enter key:
After
How to change color text? its displaying white color.
it should be like (with checkbox)
Can anyone please help me out?
Related
I've tried all found suggested solutions but ended up with this as the closest:
The target is to have custom color for:
complete header background (e.g. green)
text (e.g. white)
sort control color (e.g. white)
Currently I can only set the interior bg and text color properly while leaving the header borders and sort controls in default white color.
I use the approach of custom NCTableHeaderCell.
// <C> changing the bgColor doesn't work this way
[self.tv.headerView setWantsLayer:YES];
self.tv.headerView.layer.backgroundColor = NSColor.redColor.CGColor;
for (NSTableColumn *tc in self.tv.tableColumns) {
// <C> this only helps to change the header text color
tc.headerCell = [[NCTableHeaderCell_customizable alloc]initTextCell:#"Hdr"];
// <C> this changes the bgColor of the area of the headerCell label text (the interior) but it leaves border and sort controls in white color;
tc.headerCell.drawsBackground = YES;
tc.headerCell.backgroundColor = NSColor.greenColor;
// <C> changing the textColor doesn't work this way
// <SOLUTION> use NCTableHeaderCell_customizable as done above;
tc.headerCell.textColor = NSColor.redColor;
}
My custom class look like this:
#implementation NCTableHeaderCell_customizable
// <C> this works as expected
- (NSColor *) textColor
{
return NSColor.whiteColor;
}
// <C> this only sets the interior bgColor leaving the borders in standard color
//
//- (NSColor *) backgroundColor
//{
// return NSColor.redColor;
//}
- (void) drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
// <C> this only sets the interior bgColor leaving the borders in standard color
//
//self.backgroundColor = NSColor.orangeColor;
[super drawWithFrame:cellFrame inView:controlView];
// <C> this draws the red bg as expected but doesn't show the interior;
//
// [NSColor.redColor set];
// NSRectFillUsingOperation(cellFrame, NSCompositingOperationSourceOver);
// <C> this draws the red bg as expected but
// 1) doesn't layout the interior well (I could fix this);
// 2) doesn't show the sort controls (it's over-drawn with the code bellow);
//
// [NSColor.redColor setFill];
// NSRectFill(cellFrame);
// CGRect titleRect = [self titleRectForBounds:cellFrame];
// [self.attributedStringValue drawInRect:titleRect];
}
- (void) drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
- (void) drawFocusRingMaskWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
[super drawFocusRingMaskWithFrame:cellFrame inView:controlView];
}
- (void) drawSortIndicatorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView ascending:(BOOL)ascending priority:(NSInteger)priority;
{
[super drawSortIndicatorWithFrame:cellFrame inView:controlView ascending:ascending priority:priority];
//NSTableHeaderView *v = (NSTableHeaderView *)controlView;
}
I'm quite close to the solution but I don't know how to draw correctly the custom header cell to archive the goal.
Create the NSTableHeaderCell like below and play with the header cell's frame sizes and origins:
#import <Cocoa/Cocoa.h>
#interface CustomTableHeaderCell : NSTableHeaderCell {
NSMutableDictionary *attrs;
}
#import "CustomTableHeaderCell.h"
#implementation CustomTableHeaderCell
- (id)initTextCell:(NSString *)text
{
if (self = [super initTextCell:text]) {
if (text == nil || [text isEqualToString:#""]) {
[self setTitle:#"One"];
}
/* old_
self.textColor = [NSColor blackColor];
*/
// new_
self.textColor = [NSColor whiteColor];
attrs = [[NSMutableDictionary dictionaryWithDictionary:
[[self attributedStringValue]
attributesAtIndex:0
effectiveRange:NULL]]
mutableCopy];
// self.font = [NSFont fontWithName:appleeH8GhKr0 size:12];
return self;
}
return nil;
}
- (void)drawWithFrame:(NSRect)cellFrame
highlighted:(BOOL)isHighlighted
inView:(NSView *)view
{
CGRect fillRect, borderRect;
CGRectDivide(NSRectToCGRect(cellFrame), &borderRect, &fillRect, 1.0, CGRectMaxYEdge);
// sets the origin and frame for header's title
// fillRect.size.height = 25;
if (fillRect.origin.x == 0)
{
// fillRect.origin.y += 5;
// for adding left margin to first column title try to add spaces in text of header title
// try to play with the numbers of fillRect rect
}
else if (fillRect.origin.x == 239)
{
// fillRect.size.width -= 80;
}
// setting the background color of tableview's header view
NSGradient *gradient = [[NSGradient alloc]
initWithStartingColor:[NSColor greenColor]
endingColor:[NSColor greenColor]];
[gradient drawInRect:NSRectFromCGRect(fillRect) angle:90.0];
[self drawInteriorWithFrame:NSRectFromCGRect(CGRectInset(fillRect, 0.0, 1.0)) inView:view];
}
- (NSRect)adjustedFrameToVerticallyCenterText:(NSRect)frame
{
// super would normally draw text at the top of the cell
NSInteger offsetY = floor((NSHeight(frame) -
([[self font] ascender] - [[self font] descender])) / 2);
return NSInsetRect(frame, 20, offsetY);
}
-(void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView{
[super drawInteriorWithFrame:[self adjustedFrameToVerticallyCenterText:cellFrame]
inView:controlView];
}
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)view
{
[self drawWithFrame:cellFrame highlighted:NO inView:view];
}
- (void)highlight:(BOOL)isHighlighted
withFrame:(NSRect)cellFrame
inView:(NSView *)view
{
[self drawWithFrame:cellFrame highlighted:isHighlighted inView:view];
}
Set the header cell for column like below:
NSArray *columns = nil;
if(self.tblVc)
columns = [self.tblVc tableColumns];
NSEnumerator *cols = [columns objectEnumerator];
NSTableColumn *col = nil;
CustomTableHeaderCell *headerCell;
while (col = [cols nextObject]) {
NSString *key = [[col headerCell] stringValue];
headerCell = [[CustomTableHeaderCell alloc]
initTextCell:key];
NSRect rectHeader =self.tblVc.headerView.frame;
rectHeader.size.height = 25;
self.tblVc.headerView.frame=rectHeader;
[col setHeaderCell:headerCell];
}
and use disclosure button on top of table view's header like below:
It will show the results like this:
I don't find other ways to do it, just have to draw everything. Hope it's helpful.
CGRect outCellFrame;
- (void) drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
// <C> this only sets the interior bgColor leaving the borders in standard color
outCellFrame = cellFrame;
[super drawWithFrame:cellFrame inView:controlView];
// <C> this draws the red bg as expected but doesn't show the interior;
//
// <C> this draws the red bg as expected but
// 1) doesn't layout the interior well (I could fix this);
// 2) doesn't show the sort controls (it's over-drawn with the code bellow);
//
// [NSColor.redColor setFill];
// NSRectFill(cellFrame);
// CGRect titleRect = [self titleRectForBounds:cellFrame];
// [self.attributedStringValue drawInRect:titleRect];
}
-(NSRect *) outer:(NSRect)rect fromInner: (NSRect)innerRect {
NSRect * list = (NSRect *) malloc(sizeof(rect) * 4);
NSRect rem;
NSDivideRect(rect, &list[0], &rem, innerRect.origin.x - rect.origin.x, NSRectEdgeMinX);
NSDivideRect(rect, &list[1], &rem, - innerRect.origin.x - innerRect.size.width + rect.origin.x + rect.size.width , NSRectEdgeMaxX);
NSDivideRect(rect, &list[2], &rem, innerRect.origin.y - rect.origin.y, NSRectEdgeMinY);
NSDivideRect(rect, &list[3], &rem, -innerRect.origin.y - innerRect.size.height + rect.origin.y + rect.size.height , NSRectEdgeMaxY);
return list;
}
-(void) updateBackground:(CGRect) cellFrame and:(CGRect) innerCellFrame{
[self.backgroundColor set];
NSRect * list = [self outer:cellFrame fromInner:innerCellFrame];
NSRectFillListUsingOperation(list, 4, NSCompositingOperationSourceOver);
free(list);}
- (void) drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
[self updateBackground:outCellFrame and:cellFrame];
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
- (void) drawSortIndicatorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView ascending:(BOOL)ascending priority:(NSInteger)priority;
{
[self.backgroundColor set];
NSImage * image = ascending ? [NSImage imageNamed:#"NSDescendingSortIndicator"]:[NSImage imageNamed:#"NSAscendingSortIndicator"] ;
// use your image here. If you need to change color, try to make a colored templated image here.
CGRect frame = [self sortIndicatorRectForBounds:cellFrame];
[self.backgroundColor set];
CGRect res = NSMakeRect(frame.origin.x, cellFrame.origin.y, cellFrame.size.width - frame.origin.x, cellFrame.size.height);
NSRectFillUsingOperation(res , NSCompositingOperationSourceOver);
[NSColor.blueColor setFill];
[NSColor.blueColor setStroke];
[image drawInRect: [self sortIndicatorRectForBounds:frame]];
}
Thanks guys for your help. I've ended up with this simple solution which also solves the sort indicators. I've gave up on custom NSHeaderCell and resolved everything in single custom NSTableHeaderRowView method:
- (void)drawRect:(NSRect)dirtyRect
{
[NSGraphicsContext saveGraphicsState];
// Row: Fill the background
//
[NSColor.whiteColor setFill];
const CGRect headerRect = CGRectMake(0.0, 1.0, self.bounds.size.width, self.bounds.size.height-1.0);
[[NSBezierPath bezierPathWithRect:headerRect] fill];
// Columns
//
for (NSUInteger i = 0; i < self.tableView.numberOfColumns; ++i) {
NSRect rect = [self headerRectOfColumn:i];
// separator on left
//
if (i != 0) {
[[NSColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.35] setFill];
[[NSBezierPath bezierPathWithRect:NSMakeRect(rect.origin.x, rect.origin.y+4.0, 1.0, rect.size.height-8.0)] fill];
}
NSTableColumn *tableColumn = self.tableView.tableColumns[i];
NSSortDescriptor *col_sd = tableColumn.sortDescriptorPrototype;
NSTableHeaderCell *tableHeaderCell = tableColumn.headerCell;
// text
//
NSString *columnText = tableHeaderCell.stringValue;
[columnText drawInRect:NSInsetRect(rect, 5.0, 4.0) withAttributes:nil];
// sort indicator
//
for (NSInteger priority = 0; priority < self.tableView.sortDescriptors.count; ++priority) {
NSSortDescriptor *sortDesciptor = self.tableView.sortDescriptors[priority];
// <C> there is no way to get column from sortDesciptor so I use this trick comparing sortDesciptor with current column.sortDescriptorPrototype;
// <C> not sure why sel_isEqual() dosn't work so I compare its string representation;
if ([NSStringFromSelector(sortDesciptor.selector) isEqualToString:NSStringFromSelector(col_sd.selector)] == YES && [sortDesciptor.key isEqualToString:col_sd.key] == YES) {
SDEBUG(LEVEL_DEBUG, #"sort-hdr", #"MATCH: sel=%#", NSStringFromSelector(sortDesciptor.selector));
// <C> default implementation draws indicator ONLY for priority 0; Otherwise the indicator would have to graphically show the prio;
// <C> it is a support for multi-column sorting;
// <REF> shall you need it: https://stackoverflow.com/questions/46611972/sorting-a-viewbased-tableview-with-multiple-sort-descriptors-doesnt-work
[tableHeaderCell drawSortIndicatorWithFrame:rect inView:self ascending:sortDesciptor.ascending priority:priority];
}
}
}
[NSGraphicsContext restoreGraphicsState];
}
I am trying to make a table view cell that shows ratings for songs in a playlist. I have successfully created the cell so that it shows the current number of stars, and also an indication of how a new setting will be when you hover your mouse cursor over a cell to give a new rating.
The problem is that while mouseEnter, mouseExit and mouseMove works like a charm, I get no messages for mouseDown, which is required to actually change the value of the cell.
I have searched all over the Internet, but I can't find any solution to how to solve this problem anywhere. I have spent so many hours trying to sort this. I hope anyone have any answer or hint what I can do. Thank you.
The full code for the current implementation is as follows:
#import "FavouriteCellView.h"
#implementation FavouriteCellView {
NSTrackingArea *_trackingArea;
int _starsRated; //The current rating value
BOOL _hovering; //YES if the mouse is currently hovering over this cell
int _starsHovering; //The number of stars hovering, if the mouse is hovering over this cell
}
- (void)awakeFromNib {
[super awakeFromNib];
_starsRated = 1;
_hovering = NO;
_starsHovering = 0;
[self createTrackingArea];
}
- (void)createTrackingArea
{
_trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:NSTrackingMouseEnteredAndExited |NSTrackingActiveInActiveApp | NSTrackingMouseMoved owner:self userInfo:nil];
[self addTrackingArea:_trackingArea];
}
- (void)updateTrackingAreas{
[self removeTrackingArea:_trackingArea];
_trackingArea = nil;
[self createTrackingArea];
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// CGFloat middleX = [self bounds].size.width / 2.0f;
CGFloat middleY = [self bounds].size.height / 2.0f;
CGFloat starDivs = [self bounds].size.width / 5.0f;;
NSColor *starSelectedColor = [NSColor colorWithDeviceRed:0.8f green:0.0f blue:0.4f alpha:1.0f];
NSColor *starUnselectedColor = [NSColor colorWithDeviceRed:0.5f green:0.5f blue:0.5f alpha:1.0f];
NSColor *starHoverColor = [NSColor colorWithDeviceRed:1.0f green:0.843f blue:0.0f alpha:1.0f];
NSColor *starHoverColorSelected = [NSColor colorWithDeviceRed:0.9f green:0.843f blue:0.6f alpha:1.0f];
for (int i = 0; i < 5; i++) {
NSColor *useColor = [NSColor redColor];
if (_hovering && (i <= _starsHovering)) {
if (i <= _starsRated) {
useColor = starHoverColorSelected;
} else {
useColor = starHoverColor;
}
} else if (i <= _starsRated) {
useColor = starSelectedColor;
} else {
useColor = starUnselectedColor;
}
[self star:NSMakePoint((starDivs / 2.0f) + starDivs * i, middleY) color:useColor];
}
}
-(void)star:(NSPoint)center color:(NSColor *)color {
[color set];
CGFloat t = (2.0f * M_PI) / 10.0f;
NSBezierPath *path = [[NSBezierPath alloc] init];
CGFloat radii1 = 12.0f;
CGFloat radii2 = 4.0f;
CGFloat rot = M_PI / 2.0f;
BOOL first = YES;
for (int i = 0; i < 10; i++) {
CGFloat pointX = cos(t * i + rot) * radii1 + center.x;
CGFloat pointY = sin(t * i + rot) * radii1 + center.y;
CGFloat tempRadii = radii1;
radii1 = radii2;
radii2 = tempRadii;
if (first) {
first = NO;
[path moveToPoint:NSMakePoint(pointX, pointY)];
}
else {
[path lineToPoint:NSMakePoint(pointX, pointY)];
}
}
[path closePath];
[path fill];
/*
[[NSColor blackColor] set];
[path setLineWidth:0.25f];
[path stroke];
*/
}
-(NSView *)hitTest:(NSPoint)aPoint {
//THIS GETS CALLED
return self;
}
-(BOOL)validateProposedFirstResponder:(NSResponder *)responder forEvent:(NSEvent *)event {
printf("$"); //DOES NOT GET CALLED
return YES;
}
-(BOOL)acceptsFirstResponder {
printf("!"); //DOES NOT GET CALLED
return YES;
}
-(BOOL)acceptsFirstMouse:(NSEvent *)theEvent {
printf("8"); //DOES NOT GET CALLED
return YES;
}
-(void)mouseDown:(NSEvent *)theEvent {
printf("o"); //DOES NOT GET CALLED
_starsRated = _starsHovering;
}
-(void)mouseUp:(NSEvent *)theEvent {
printf("O"); //DOES NOT GET CALLED
}
-(void)mouseEntered:(NSEvent *)theEvent {
//DOES GET CALLED
_hovering = YES;
[self setNeedsDisplay:YES];
}
-(void)mouseExited:(NSEvent *)theEvent {
//DOES GET CALLED
_hovering = NO;
[self setNeedsDisplay:YES];
}
-(void)mouseMoved:(NSEvent *)theEvent {
//DOES GET CALLED
NSPoint mouseLocation = [[self window] mouseLocationOutsideOfEventStream];
mouseLocation = [self convertPoint: mouseLocation
fromView: nil];
int newStarsHoveringValue = mouseLocation.x / ([self bounds].size.width / 5.0f);
if (newStarsHoveringValue != _starsHovering) {
_starsHovering = newStarsHoveringValue;
[self setNeedsDisplay:YES];
}
}
#end
It was a bit fiddly, but I managed to create a solution that works. I subclassed NSTableView, then overrode mouseDown with the following code:
-(void)mouseDown:(NSEvent *)theEvent {
NSPoint globalLocation = [theEvent locationInWindow];
NSPoint localLocation = [self convertPoint:globalLocation fromView:nil];
NSInteger clickedRow = [self rowAtPoint:localLocation];
if (clickedRow != -1) {
NSInteger clickedColumn = [self columnAtPoint:localLocation];
if (clickedColumn != -1) {
if (clickedColumn == 3) {
FavouriteCellView *fv = [self viewAtColumn:clickedColumn row:clickedRow makeIfNecessary:NO];
if (fv != nil) {
[fv mouseDown:theEvent];
}
return;
}
}
}
[super mouseDown:theEvent];
}
Now it works exactly like I wanted.
I have a class called MNTRectangle which is a subclass of UIImage. I have overridden the drawRect method of this class to draw a border on the image (using its frame). I have it so when the user starts a panning/dragging gesture on a class called DocumentView (subclass of UIView) it adds an instance of MNTRectangle as a subview to the instance of DocumentView. As the user continues to drag the MNTRectangle is resized. The problem is that the MNTRectangle is appearing as solid black in the end, I have tried clearing the graphics context as well as saving the context before drawing the border and restoring the context after drawing the border. No matter what I seem to try I cannot get the MNTRectangle to clear and just show the border on resize.
Here is the code for my MNTRectangle class:
#implementation MNTRectangle
- (id)init
{
self = [super init];
if (self)
{
[self setup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self setup];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self setup];
}
return self;
}
- (void)setup
{
[self setBackgroundColor:[UIColor clearColor]];
}
- (void)drawRect:(CGRect)rect
{
// Get graphics context
CGContextRef context = UIGraphicsGetCurrentContext();
// CGContextSaveGState(context);
// CGContextClearRect(context, rect);
// Draw border
CGContextSetLineWidth(context, 4.0);
[[UIColor blackColor] setStroke];
CGContextStrokeRect(context, rect);
// CGContextRestoreGState(context);
}
Here is the code in DocumentView for the panning/dragging handling on the UIView:
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
_panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
}
return self;
}
- (void)handlePan:(UIPanGestureRecognizer *)sender
{
if ([sender state] == UIGestureRecognizerStateBegan)
{
_startPanPoint = [sender locationInView:self];
MNTRectangle *rectangle = [[MNTRectangle alloc] initWithFrame:CGRectMake(_startPanPoint.x, _startPanPoint.y, 1, 1)];
[_objects addObject:rectangle];
_currentPanObject = rectangle;
[self addSubview:rectangle];
}
else if ([sender state] == UIGestureRecognizerStateChanged)
{
CGPoint endPanPoint = [sender locationInView:self];
float height = fabsf(endPanPoint.y - _startPanPoint.y);
float width = fabsf(endPanPoint.x - _startPanPoint.x);
float x = MIN(_startPanPoint.x, endPanPoint.x);
float y = MIN(_startPanPoint.y, endPanPoint.y);
MNTRectangle *rectangle = (MNTRectangle *)_currentPanObject;
[rectangle setFrame:CGRectMake(x, y, width, height)];
}
else if ([sender state] == UIGestureRecognizerStateEnded)
{
CGPoint endPanPoint = [sender locationInView:self];
float height = fabsf(endPanPoint.y - _startPanPoint.y);
float width = fabsf(endPanPoint.x - _startPanPoint.x);
float x = MIN(_startPanPoint.x, endPanPoint.x);
float y = MIN(_startPanPoint.y, endPanPoint.y);
MNTRectangle *rectangle = (MNTRectangle *)_currentPanObject;
[rectangle setFrame:CGRectMake(x, y, width, height)];
}
}
Any help with this would be greatly appreciated.
I finally figured it out. In my handlePan: method after I set the frame of the rectangle I was missing [rectangle setNeedsDisplay];.
Now my DocumentView code looks like this:
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
_panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
}
return self;
}
- (void)handlePan:(UIPanGestureRecognizer *)sender
{
if ([sender state] == UIGestureRecognizerStateBegan)
{
_startPanPoint = [sender locationInView:self];
MNTRectangle *rectangle = [[MNTRectangle alloc] initWithFrame:CGRectMake(_startPanPoint.x, _startPanPoint.y, 1, 1)];
[_objects addObject:rectangle];
_currentPanObject = rectangle;
[self addSubview:rectangle];
}
else if ([sender state] == UIGestureRecognizerStateChanged)
{
CGPoint endPanPoint = [sender locationInView:self];
float height = fabsf(endPanPoint.y - _startPanPoint.y);
float width = fabsf(endPanPoint.x - _startPanPoint.x);
float x = MIN(_startPanPoint.x, endPanPoint.x);
float y = MIN(_startPanPoint.y, endPanPoint.y);
MNTRectangle *rectangle = (MNTRectangle *)_currentPanObject;
[rectangle setFrame:CGRectMake(x, y, width, height)];
[rectangle setNeedsDisplay];
}
else if ([sender state] == UIGestureRecognizerStateEnded)
{
CGPoint endPanPoint = [sender locationInView:self];
float height = fabsf(endPanPoint.y - _startPanPoint.y);
float width = fabsf(endPanPoint.x - _startPanPoint.x);
float x = MIN(_startPanPoint.x, endPanPoint.x);
float y = MIN(_startPanPoint.y, endPanPoint.y);
MNTRectangle *rectangle = (MNTRectangle *)_currentPanObject;
[rectangle setFrame:CGRectMake(x, y, width, height)];
[rectangle setNeedsDisplay];
}
}
You're gonna want to call [super drawRect:rect]
To clarify for anyone reading, calling [rectangle setNeedsDisplay]; is needed because you're overriding drawRect: and thus this subclass of UIView, MNTRectangle, needs to be told to manually redraw itself after it's internal variables have been changed.
This function is called automatically on standard objects that inherit from UIView, but since you've created a custom object, you'll need to call it manually if you have some special behavior in MNTRectangle that requires a redraw.
Further documentation here.
I want to add an icon to the NSTabViewItem with some text.
Please help me with the code in drawLabel:inRect: method.
- (id)initWithCoder:(NSCoder *)decoder
{
[super initWithCoder:decoder];
tabCell = [[NSBrowserCell alloc] initImageCell:[NSImage
imageNamed:#"xyz"]];
[tabCell setLeaf:YES];
[tabCell setFont:[[self tabView] font]];
[tabCell setStringValue: [self label]];
return self;
}
- (void)drawLabel:(BOOL)shouldTruncateLabel inRect:(NSRect)tabRect
{
{ // modify the rect a tad so the cell draws properly..
tabRect.origin.y += 2;
tabRect.size.width += 16;
}
[tabCell drawWithFrame:tabRect inView:[self tabView]];
}
- (NSSize)sizeOfLabel:(BOOL)shouldTruncateLabel
{
NSSize superSize = [super sizeOfLabel:shouldTruncateLabel];
NSImage *icon = [tabCell image];
superSize.width += [icon size].width-4;
return superSize;
}
I am able to add an icon to the NSTabViewItem but the icon is coming out of the tab because of its big size. How can I maintain the size of icon to stay within the TabViewItem?
Not sure, if your problem got resolve, i had similar use case,and i am using drawLabel and appending image in that,
refer the code snippet,
- (void)drawLabel:(BOOL)shouldTruncateLabel inRect:(NSRect)tabRect{
NSImage *pImage = [self getImage];
[[NSGraphicsContext currentContext] saveGraphicsState];
NSAffineTransform* xform = [NSAffineTransform transform];
[xform translateXBy:0.0 yBy: tabRect.size.height];
[xform scaleXBy:1.0 yBy:-1.0];
[xform concat];
if(pImage){
[pImage drawInRect:NSMakeRect(tabRect.origin.x-8,-6,16, 16)fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:opacity];
}
[[NSGraphicsContext currentContext] restoreGraphicsState];
[super drawLabel:shouldTruncateLabel inRect:tabRect];
NSLog(#" Inside drawRect text (%#)",[self labeltitle]);
}
This code works for me (Swift 3):
import Foundation
import Cocoa
class MyTabViewItem : NSTabViewItem
{
let iconWidth:CGFloat = 16
override func sizeOfLabel(_ shouldTruncateLabel: Bool) -> NSSize
{
var superSize: NSSize = super.sizeOfLabel(shouldTruncateLabel)
superSize.width += iconWidth
return superSize
}
override func drawLabel(_ shouldTruncateLabel: Bool, in tabRect: NSRect)
{
let icon: NSImage? = NSImage(named:"Eye")
if icon != nil
{
let opacity:CGFloat = 0.5
icon?.draw(in: NSMakeRect(tabRect.origin.x + tabRect.width - iconWidth + 4, 8, iconWidth, iconWidth), from: NSZeroRect, operation: NSCompositeSourceOver, fraction: opacity)
}
super.drawLabel(shouldTruncateLabel, in: tabRect)
}
}
Not fully tested, you may have to change some of the constants if the iconWidth changes.
Based on Amitg2k12's example with some additions:
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (self) {
[self setToolTip:[self label]];
[self setLabel:#" "];
}
return self;
}
- (NSSize)sizeOfLabel:(BOOL)computeMin {
return NSMakeSize(16, 18);
}
- (void)drawLabel:(BOOL)shouldTruncateLabel inRect:(NSRect)tabRect {
NSImage *image = [self image];
NSRect destRect = NSMakeRect(tabRect.origin.x, tabRect.origin.y + 2, 16, 16);
[[NSGraphicsContext currentContext] saveGraphicsState];
NSAffineTransform *affineTransform = [NSAffineTransform transform];
[affineTransform translateXBy:NSMaxX(destRect) yBy:NSMinY(destRect)];
[affineTransform scaleXBy:1.0 yBy:-1.0];
[affineTransform concat];
if(image) {
[image drawInRect:NSMakeRect(-NSWidth(destRect), -NSHeight(destRect), 16, 16) fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:1.0f];
}
[[NSGraphicsContext currentContext] restoreGraphicsState];
[super drawLabel:shouldTruncateLabel inRect:tabRect];
}
#end
I am trying to create an NSMatrix of NSButtonCells where between zero and four buttons can be selected (toggled on). I have tried the following (test) code, but am not sure how I can provide the functionality I require. Perhaps it's not possible with NSMatrix and I need to look at an alternative control, or create my own?
#interface MatrixView : NSView
{
NSScrollView *_scrollView;
NSMatrix *_matrixView;
}
#end
#implementation MatrixView
- (id)initWithFrame:(NSRect)frameRect
{
NSLog(#"initWithFrame. frameRect=%#", NSStringFromRect(frameRect));
self = [super initWithFrame:frameRect];
if (self != nil)
{
_scrollView = [[NSScrollView alloc] initWithFrame:NSMakeRect(0, 0, frameRect.size.width, frameRect.size.height)];
[_scrollView setBorderType:NSNoBorder];
[_scrollView setHasVerticalScroller:YES];
[_scrollView setHasHorizontalScroller:NO];
[_scrollView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
NSSize contentSize = [_scrollView contentSize];
contentSize.height = 300;
// Make it 3 x however-many-buttons-will-fit-the-height
CGFloat gap = 8.0;
CGFloat width = (contentSize.width / 3.0) - (gap * 2.0);
NSUInteger rows = (contentSize.height / (width + gap));
NSLog(#"width=%f, rows=%lu", width, rows);
NSButtonCell *prototype = [[NSButtonCell alloc] init];
[prototype setTitle:#"Hello"];
[prototype setButtonType:NSToggleButton];
[prototype setShowsStateBy:NSChangeGrayCellMask];
_matrixView = [[NSMatrix alloc] initWithFrame:NSMakeRect(0, 0, contentSize.width, contentSize.height)
mode:NSListModeMatrix
prototype:prototype
numberOfRows:rows
numberOfColumns:3];
[_matrixView setCellSize:NSMakeSize(width, width)];
[_matrixView setIntercellSpacing:NSMakeSize(gap, gap)];
[_matrixView setAllowsEmptySelection:YES];
[_matrixView sizeToCells];
[_scrollView setDocumentView:_matrixView];
[self addSubview:_scrollView];
[self setAutoresizesSubviews:YES];
[prototype release];
}
return self;
}
...
I got this to work with the following subclass of NSMatrix. I added one property, onCount, to keep track of how many buttons were in the on state:
#implementation RDMatrix
#synthesize onCount;
-(id) initWithParentView:(NSView *) cv {
NSButtonCell *theCell = [[NSButtonCell alloc ]init];
theCell.bezelStyle = NSSmallSquareBezelStyle;
theCell.buttonType = NSPushOnPushOffButton;
theCell.title = #"";
if (self = [super initWithFrame:NSMakeRect(200,150,1,1) mode:2 prototype:theCell numberOfRows:4 numberOfColumns:4]){
[self setSelectionByRect:FALSE];
[self setCellSize:NSMakeSize(40,40)];
[self sizeToCells];
self.target = self;
self.action = #selector(buttonClick:);
self.drawsBackground = FALSE;
self.autoresizingMask = 8;
self.allowsEmptySelection = TRUE;
self.mode = NSHighlightModeMatrix;
self.onCount = 0;
[cv addSubview:self];
return self;
}
return nil;
}
-(IBAction)buttonClick:(NSMatrix *)sender {
NSUInteger onOrOff =[sender.selectedCells.lastObject state];
if (onOrOff) {
self.onCount += 1;
}else{
self.onCount -= 1;
}
NSLog(#"%ld",self.onCount);
if (self.onCount == 5) {
[sender.selectedCells.lastObject setState:0];
self.onCount -= 1;
}
}
When you try to select the 5th button it will flash on, but then go off. This could be a problem depending on how you are using the state of these buttons. I just logged them with this method:
-(IBAction)checkMatrix:(id)sender {
NSIndexSet *indxs = [self.mat.cells indexesOfObjectsPassingTest:^BOOL(NSButtonCell *cell, NSUInteger idx, BOOL *stop) {
return cell.state == NSOnState;
}];
NSLog(#"%#",indxs);
}
After Edit: I didn't like the way my first method flashed the button on briefly before turning it off again when you try to click the 5th button. I found what I think is a better solution that involves overriding mouseDown in the matrix subclass (if you want to try this, you should delete the setAction and setTarget statements and delete the buttonClick method):
-(void)mouseDown:(NSEvent *) event {
NSPoint matPoint = [self convertPoint:event.locationInWindow fromView:nil];
NSInteger row;
NSInteger column;
[self getRow:&row column:&column forPoint:matPoint];
NSButtonCell *cell = [self cellAtRow:row column:column];
if (self.onCount < 4 && cell.state == NSOffState) {
cell.state = NSOnState;
self.onCount += 1;
}else if (cell.state == NSOnState) {
cell.state = NSOffState;
self.onCount -= 1;
}
}