know the position of the finger in the trackpad under Mac OS X - objective-c

I am developing an Mac application and I would like to know the position of the finger in the trackpad when there is a touch.
Is it something possible and if yes, how?

Your view needs to be set to accept touches ([self setAcceptsTouchEvents:YES]). When you get a touch event like -touchesBeganWithEvent:, you can figure out where the finger lies by looking at its normalizedPosition (range is [0.0, 1.0] x [0.0, 1.0]) in light of its deviceSize in big points (there are 72 bp per inch). The lower-left corner of the trackpad is treated as the zero origin.
So, for example:
- (id)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
if (!self) return nil;
/* You need to set this to receive any touch event messages. */
[self setAcceptsTouchEvents:YES];
/* You only need to set this if you actually want resting touches.
* If you don't, a touch will "end" when it starts resting and
* "begin" again if it starts moving again. */
[self setWantsRestingTouches:YES]
return self;
}
/* One of many touch event handling methods. */
- (void)touchesBeganWithEvent:(NSEvent *)ev {
NSSet *touches = [ev touchesMatchingPhase:NSTouchPhaseBegan inView:self];
for (NSTouch *touch in touches) {
/* Once you have a touch, getting the position is dead simple. */
NSPoint fraction = touch.normalizedPosition;
NSSize whole = touch.deviceSize;
NSPoint wholeInches = {whole.width / 72.0, whole.height / 72.0};
NSPoint pos = wholeInches;
pos.x *= fraction.x;
pos.y *= fraction.y;
NSLog(#"%s: Finger is touching %g inches right and %g inches up "
#"from lower left corner of trackpad.", __func__, pos.x, pos.y);
}
}
(Treat this code as an illustration, not as tried and true, battle-worn sample code; I just wrote it directly into the comment box.)

Swift 3:
I've written an extension to NSTouch that returns the trackpad-touch pos, relative to an NSView:
extension NSTouch {
/**
* Returns the relative position of the touch to the view
* NOTE: the normalizedTouch is the relative location on the trackpad. values range from 0-1. And are y-flipped
* TODO: debug if the touch area is working with a rect with a green stroke
*/
func pos(_ view:NSView) -> CGPoint{
let w = view.frame.size.width
let h = view.frame.size.height
let touchPos:CGPoint = CGPoint(self.normalizedPosition.x,1 + (self.normalizedPosition.y * -1))/*flip the touch coordinates*/
let deviceSize:CGSize = self.deviceSize
let deviceRatio:CGFloat = deviceSize.width/deviceSize.height/*find the ratio of the device*/
let viewRatio:CGFloat = w/h
var touchArea:CGSize = CGSize(w,h)
/*Uniform-shrink the device to the view frame*/
if(deviceRatio > viewRatio){/*device is wider than view*/
touchArea.height = h/viewRatio
touchArea.width = w
}else if(deviceRatio < viewRatio){/*view is wider than device*/
touchArea.height = h
touchArea.width = w/deviceRatio
}/*else ratios are the same*/
let touchAreaPos:CGPoint = CGPoint((w - touchArea.width)/2,(h - touchArea.height)/2)/*we center the touchArea to the View*/
return CGPoint(touchPos.x * touchArea.width,touchPos.y * touchArea.height) + touchAreaPos
}
}
Here is an article I wrote about my GestureHUD class in macOS. With link to a ready-made extension as well: http://eon.codes/blog/2017/03/15/Gesture-HUD/
Example:

I don't know if there's an ObjC interface, but you might find the C HID Class Device Interface interesting.

At a Cocoa (Obj-C level) try the following - although remember that many users are still using mouse control.
http://developer.apple.com/mac/library/documentation/cocoa/conceptual/EventOverview/HandlingTouchEvents/HandlingTouchEvents.html

Related

Velocity Verlet Algorithm: Cannot seem to determine correct velocity for stable Orbit?

Like many that post about this topic, I too am busy trying to write myself an accurate simulator for the movement of objects in a 2D gravitation field.
I decided early on that I would settle on Velocity Verlet Integration, as I want my objects to maintain stable orbits and conserve energy even if the timestep is rather large. So, what might the problem be?
Well, so far, everything seems to behave correctly, except for one component. When I try to calculate the correct velocity for a stable orbit at a certain distance, the resulting velocity sends them into odd elliptical orbits that quickly increase in magnitude each time.
So, to begin, here are the following methods that determine an objects next position, velocity, and acceleration in scene: (Objective C)
Acceleration:
-(CGVector)determineAccelerationFor:(SKObject *)object
{ // Ok, let's find Acceleration!
CGVector forceVector = (CGVector){0,0}; // Blank vector that we will add forces to
for (SKObject *i in self.sceneObjects)
{
if (![i isEqual:object]) // Just make sure we're not counting ourselves here
{
CGPoint distance = [self getDistanceBetween:i.position And:object.position];
float hypotenuse = sqrtf(powf(distance.x, 2)+ powf(distance.y, 2));
float force = ((self.gravity * object.mass * i.mass)/powf(hypotenuse, 3));
float xMagnitude = (force * distance.x);
float yMagnitude = (force * distance.y);
forceVector.dx += xMagnitude;
forceVector.dy += yMagnitude;
}
}
CGVector acceleration = (CGVector){forceVector.dx/object.mass, forceVector.dy/object.mass};
return acceleration;
}
Cool, so basically, I just take an object, add all the other forces that each other object imposes on it together then divide the X & Y factor by the mass of the current object to get the acceleration!
Next up is Velocity. Here I use the following equation:
The method for it is pretty straightforward too:
-(CGVector)determineVelocityWithCurrentVelocity:(CGVector)v OldAcceleration:(CGVector)ao NewAcceleration:(CGVector)a
{
float xVelocity = (v.dx + ((ao.dx + a.dx)/2) * self.timeStep);
float yVelocity = (v.dy + ((ao.dy + a.dy)/2) * self.timeStep);
CGVector velocity = (CGVector){xVelocity,yVelocity};
return velocity;
}
And finally, position! The equation for this is:
And it is determined with the following method!
-(CGPoint)determinePositionWithCurrentPosition:(CGPoint)x CurrentVelocity:(CGVector)v OldAcceleration:(CGVector)ao
{
float xPosition = (x.x + v.dx * self.timeStep + ((ao.dx * powf(self.timeStep, 2))/2));
float yPosition = (x.y + v.dy * self.timeStep + ((ao.dy * powf(self.timeStep, 2))/2));
CGPoint position = (CGPoint){xPosition,yPosition};
return position;
}
This is all called from the below method!!
-(void)refreshPhysics:(SKObject *)object
{
CGPoint position = [self determinePositionWithCurrentPosition:object.position CurrentVelocity:object.velocity OldAcceleration:object.acceleration]; // Determine new Position
SKAction *moveTo = [SKAction moveTo:position duration:0.0];
[object runAction:moveTo]; // Move to new position
CGVector acceleration = [self determineAccelerationFor:object]; // Determine acceleration off new position
CGVector velocity = [self determineVelocityWithCurrentVelocity:object.velocity OldAcceleration:object.acceleration NewAcceleration:acceleration];
NSLog(#"%# Old Velocity: %f, %f",object.name,object.velocity.dx,object.velocity.dy);
NSLog(#"%# New Velocity: %f, %f\n\n",object.name,velocity.dx,velocity.dy);
[object setAcceleration:acceleration];
[object setVelocity:velocity];
}
Okay, so those methods above dictate how objects are moved in scene. Now onto the initial issue, the ever present problem of achieving a stable orbit!
In order to determine what velocity an object should have to maintain an orbit, I use the following equation:
And I implement that as follows:
-(void)setObject:(SKObject *)object ToOrbit:(SKObject *)parent
{
float defaultSeparation = 200;
// Move Object to Position at right of parent
CGPoint defaultOrbitPosition = (CGPoint){parent.position.x + (parent.size.width/2)+ defaultSeparation,parent.position.y};
[object setPosition:defaultOrbitPosition];
// Determine Orbital Velocity
float velocity = sqrtf((self.gravity * parent.mass)/(parent.size.width/2+defaultSeparation));
CGVector vector = (CGVector){0,velocity};
[object setVelocity:vector];
}
And for some reason, despite this, I get abysmal results. Here is some of the output:
Information:
Gravity(constant) = 1000 (For test purposes)
Mass(Parent) = 5000 units
Mass(Satellite) = 1 units
Separation = 224 pixels
It determines that in order for the Satellite to Orbit the Parent, a velocity of:
149.403580 pixels/timeStep
is required. And that checks out on my calculator.
So this has left me a little confused as to what could be going wrong. I log all the output concerning new velocities and positions, and it does use the velocity I set it to, but that just doesn't seem to make a difference. If anyone could possible help spot what's going wrong here I would be immensely grateful.
If anyone believes I have left something out, tell me and I will edit this right away. Thanks!

How To Create a Rotating Wheel Control with UIKit

Hi I'm trying to create a Rotation Wheel in iOS and I found this fantastic tutorial
How to Create a Rotation Wheel Control
and it is very nice and complete, but in this case the selected object is in the left and need the object in the right.
So I'm wondering if somebody knows what I need to change in order to select the rigth side
Well in the example we can see in the endtrackingWithTouch Event the following code
// 1 - Get current container rotation in radians
CGFloat radians = atan2f(container.transform.b,container.transform.a);
NSLog(#"Radians %f", radians);
// 2 - Initialize new value
CGFloat newVal = 0.0;
// 3 - Iterate through all the sectors
for (SMSector *s in sectors) {
// 4 - Check for anomaly (occurs with even number of sectors)
if (s.minValue > 0 && s.maxValue < 0) {
if (s.maxValue > radians || s.minValue < radians) {
// 5 - Find the quadrant (positive or negative)
if (radians > 0) {
newVal = radians - M_PI;
} else {
newVal = M_PI + radians;
}
currentSector = s.sector;
}
}
// 6 - All non-anomalous cases
else if (radians > s.minValue && radians < s.maxValue) {
newVal = radians - s.midValue;
currentSector = s.sector;
}
}
Doing the Math for radians and making some comparing the min and max in the sectors we get the selected sector also if I change (CGFloat radians = atan2f(container.transform.b,container.transform.a);) for CGFloat radians = atan2f(container.transform.d,container.transform.c); I'm able to get the sector from the bottom
I think you can simply put your wheel in another view and rotate this view one PI. Something like this:
UIView *testView = [[UIView alloc]initWithFrame:CGRectMake(10, 80,300, 300)];
SMRotaryWheel *wheel = [[SMRotaryWheel alloc] initWithFrame:CGRectMake(0, 0,300, 300)
andDelegate:self
withSections:5];
[testView addSubview:wheel];
testView.transform = CGAffineTransformMakeRotation(M_PI);
[self.view addSubview:testView];

Find point on the perimeter of a rectangle in Objective-C (Sprite Kit)

I want to move an object from a random point just outside of the view in a Sprite Kit game.
The logical way of doing this would be to create a rectangle 100px (example) bigger than the view, and pick a random point on it's perimeter. Unfortunately, I don't know an easy way to do this.
How can I easily create a random point on the perimeter of a rectangle (which is slightly bigger than my view)?
Update
This should do what you want:
- (CGPoint)randomPointOutsideRect:(CGRect)rect withOffset:(CGFloat)offset {
NSUInteger random = arc4random_uniform(4);
UIRectEdge edge = 1 << random; // UIRectEdge enum values are defined with bit shifting
CGPoint randomPoint = CGPointZero;
if (edge == UIRectEdgeTop || edge == UIRectEdgeBottom) {
randomPoint.x = arc4random_uniform(CGRectGetWidth(rect)) + CGRectGetMinX(rect);
if (edge == UIRectEdgeTop) {
randomPoint.y = CGRectGetMinY(rect) - offset;
}
else {
randomPoint.y = CGRectGetMaxY(rect) + offset;
}
}
else if (edge == UIRectEdgeLeft || edge == UIRectEdgeRight) {
randomPoint.y = arc4random_uniform(CGRectGetHeight(rect)) + CGRectGetMinY(rect);
if (edge == UIRectEdgeLeft) {
randomPoint.x = CGRectGetMinX(rect) - offset;
}
else {
randomPoint.x = CGRectGetMaxX(rect) + offset;
}
}
return randomPoint;
}
This should be fairly straightforward, let me know if there's something unclear.
Basically, we pick one edge at random, then "fix" one axis and pick a random value on the other (within the width/height boundaries).
arc4random_uniform gives us only integers, but that's fine because floating point values in frames are bad when displaying stuff on screen.
There is probably a shorter way to write this; feel free to edit to improve, everyone.
Original answer
How can I easily create a point 100 pixels away from the edge of my view?
With CGRectOffset().
Assuming you want a CGPoint 100pt "higher" (ie. with a lower y) than your view, do:
CGRect viewFrame = // lets say for this example that your frame is at {{20, 40}, {300, 600}}
CGRect offsetFrame = CGRectOffset(viewFrame, 0, -100);
CGPoint offsetPoint = offsetFrame.origin
// offsetPoint = {20, -60}

cocos2d Generating multiple of the same sprites with velocity

I'm pretty new to iOS and cocos2d and I'm having a problem trying to create what I want the code to do. Let me give you the rundown first then i'll show what I've got.
What I got so far is a giant sprite in the middle and when that is touched, I want to have say 2000 of a different sprite generate from the center position and like a particle system, shoot off in all directions.
First off, I tried coding implementing the velocity code (written in Objective-c) over to Cocos2d and that didn't work. -code-
-(void)ccTouchBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if(CGRectContainsPoint([[self getChildByTag:1] boundingBox], location))
{
for( int i = 0; i < 100; i++)
{
CCSprite *ballGuySprite = [CCSprite spriteWithFile:#"ball.png"];
[self addChild:ballGuySprite z:7];
ballGuySprite.position = ccp(((s.width + i *10) /2), (s.height + i *10) /2);
}
}
}
What that does is when I touch the first sprite, 100 of the other sprites are on top of each other leading to the top right corner.
The velocity code that I used when as followed and when I try to apply it to the sprite nothing happens. - Velocity code -
-(void) checkCollisionWithScreenEdges
{
if(ballGuysRect.origin.x <= 0)
{
ballVelocity.x = abs(ballVelocity.x);
}
if(ballGuysRect.origin.x >= VIEW_WIDTH - GUY_SIZE)
{
ballVelocity.x = -1 * abs(ballVelocity.x);
}
if(ballGuysRect.origin.y <= 0)
{
ballVelocity.y = abs(ballVelocity.y);
}
if(ballGuysRect.origin.y >= VIEW_HEIGHT - GUY_SIZE)
{
ballVelocity.y = -1 * abs(ballVelocity.y);
}
}
-(void) updateModelWithTime:(CFTimeInterval)timestamp
{
if(lastTime == 0.0)
{
lastTime = timestamp;
}
else
{
timeDelta = timestamp - lastTime;
lastTime = timestamp;
ballGuysRect.origin.x += ballVelocity.x * timeDelta;
ballGuysRect.origin.y += ballVelocity.y * timeDelta;
[self checkCollisionWithScreenEdges];
}
}
When I attach that code to the sprite, nothing happen.
I also tried adding a CCParticleExplosion which did do what I wanted but I still want to add a touch function to each individual sprite that's generated and they tend to just fade away.
So again, I'm still fairly new to this and if anyone could give any advice that would be great.
Thanks for your patients and time to read this.
Your code looks good to me, but you never seem to update the position of your sprites. Somewhere in updateModelWithTime I would expect you to set ballGuySprite.position = ballGuysRect.origin plus half of its height or width, respectively.
Also, I don't see how updateModelWithTime can control 100 different sprites. I see only one instance of ballGuysRect here. You will need a ballGuysRect for each sprite, e.g. an array.
Finally, I'd say that you don't really need ballGuysRect, ballVelocity, and the sprite. Ball could be a subclass of CCSprite, including a velocity vector. Then all you need to do is keep an array of Balls and manage those.
I am not sure what version of cocos2d you are using but a few things look a bit odd.
Your first problem appears to be that you are just using the same sprite over and over again.
Since you want so many different sprites shooting away, I would recommend that you use a CCSpriteBatchNode, as this should simplify things and speed things up.
The following code should help you get that set up and move them offscreen with CCMoveTo:
//in your header file:
CCSpriteBatchNode *batch;
//in your init method
batch = [CCSpriteBatchNode batchNodeWithFile:#"ball.png"];
//Then in your ccTouches method
for( int i = 0; i < 100; i++)
{
CCSprite *ballGuySprite = [CCSprite spriteWithFile:#"ball.png"];
[batch addChild:ballGuySprite z:7 tag:0];
ballGuySprite.position = ccp(where-ever the center image is located);
id actionMove = [CCMoveTo actionWithDuration:actualDuration
position:ccp(random off screen location)];
[ballGuySprite runAction:actionMove];
}
Also usually your update method looks something like the following:
-(void)update:(ccTime)delta{
//check for sprites that have moved off screen and disable them.
}
Hope this helps.

How to get the on-screen location of an NSStatusItem

I have a question about the NSStatusItem for cocoa in mac osx. If you look at the mac app called snippets (see the movie at http://snippetsapp.com/). you will see that once you clicked your statusbar icon that a perfectly aligned view / panel or maybe even windows appears just below the icon.
My question is ... How to calculate the position to where to place your NSWindow just like this app does?
I have tried the following:
Subclass NSMenu
Set the view popery for the first item of the menu (Worked but enough)
Using addSubview instead of icon to NSStatusItem this worked but could not get higher then 20px
Give the NSStatusItem a view, then get the frame of that view's window. This technically counts as UndocumentedGoodness, so don't be surprised if it breaks someday (e.g., if they start keeping the window offscreen instead).
I don't know what you mean by “could not get heigher then 20px”.
To do this without the hassle of a custom view, I tried the following (that works). In the method that is set as the action for the status item i.e. the method that is called when the user clicks the status item, the frame of the status item can be retrieved by:
[[[NSApp currentEvent] window] frame]
Works a treat for me
Given an NSMenuItem and an NSWindow, you can get the point that centers your window right below the menu item like this:
fileprivate var centerBelowMenuItem: CGPoint {
guard let window = window, let barButton = statusItem.button else { return .zero }
let rectInWindow = barButton.convert(barButton.bounds, to: nil)
let screenRect = barButton.window?.convertToScreen(rectInWindow) ?? .zero
// We now have the menu item rect on the screen.
// Let's do some basic math to center our window to this point.
let centerX = screenRect.origin.x-(window.frame.size.width-barButton.bounds.width)/2
return CGPoint(x: centerX, y: screenRect.origin.y)
}
No need for undocumented API's.
Maybe another solution which works for me (swift 4.1) :
let yourStatusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let frameOrigin = yourStatusItem.button?.window?.frame.origin
let yourPoint = CGPoint(x: (frameOrigin?.x)!, y: (frameOrigin?.y)! - 22)
yourWindow?.setFrameOrigin(yourPoint)
It seems that this app uses Matt's MAAttachedWindow. There's an sample application with the same layout & position.
NOTE: PLEASE DO NOT USE THIS, at least not for the purpose of locating an NSStatusItem.
Back when I posted this, this crazy image matching technique was the only way to solve this problem without undocumented API. Now, you should use Oskar's solution.
If you're willing to use image analysis to find the status item on a menu bar, here's a category for NSScreen which does exactly that.
It might seem crazy to do it this way, but it's fast, relatively small, and it's the only way of finding a status item without undocumented API.
If you pass in the current image for the status item, this method should find it.
#implementation NSScreen (LTStatusItemLocator)
// Find the location of IMG on the screen's status bar.
// If the image is not found, returns NSZeroPoint
- (NSPoint)originOfStatusItemWithImage:(NSImage *)IMG
{
CGColorSpaceRef csK = CGColorSpaceCreateDeviceGray();
NSPoint ret = NSZeroPoint;
CGDirectDisplayID screenID = 0;
CGImageRef displayImg = NULL;
CGImageRef compareImg = NULL;
CGRect screenRect = CGRectZero;
CGRect barRect = CGRectZero;
uint8_t *bm_bar = NULL;
uint8_t *bm_bar_ptr;
uint8_t *bm_compare = NULL;
uint8_t *bm_compare_ptr;
size_t bm_compare_w, bm_compare_h;
BOOL inverted = NO;
int numberOfScanLines = 0;
CGFloat *meanValues = NULL;
int presumptiveMatchIdx = -1;
CGFloat presumptiveMatchMeanVal = 999;
// If the computer is set to Dark Mode, set the "inverted" flag
NSDictionary *globalPrefs = [[NSUserDefaults standardUserDefaults] persistentDomainForName:NSGlobalDomain];
id style = globalPrefs[#"AppleInterfaceStyle"];
if ([style isKindOfClass:[NSString class]]) {
inverted = (NSOrderedSame == [style caseInsensitiveCompare:#"dark"]);
}
screenID = (CGDirectDisplayID)[self.deviceDescription[#"NSScreenNumber"] integerValue];
screenRect = CGDisplayBounds(screenID);
// Get the menubar rect
barRect = CGRectMake(0, 0, screenRect.size.width, 22);
displayImg = CGDisplayCreateImageForRect(screenID, barRect);
if (!displayImg) {
NSLog(#"Unable to create image from display");
CGColorSpaceRelease(csK);
return ret; // I would normally use goto(bail) here, but this is public code so let's not ruffle any feathers
}
size_t bar_w = CGImageGetWidth(displayImg);
size_t bar_h = CGImageGetHeight(displayImg);
// Determine scale factor based on the CGImageRef we got back from the display
CGFloat scaleFactor = (CGFloat)bar_h / (CGFloat)22;
// Greyscale bitmap for menu bar
bm_bar = malloc(1 * bar_w * bar_h);
{
CGContextRef bmCxt = NULL;
bmCxt = CGBitmapContextCreate(bm_bar, bar_w, bar_h, 8, 1 * bar_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);
// Draw the menu bar in grey
CGContextDrawImage(bmCxt, CGRectMake(0, 0, bar_w, bar_h), displayImg);
uint8_t minVal = 0xff;
uint8_t maxVal = 0x00;
// Walk the bitmap
uint64_t running = 0;
for (int yi = bar_h / 2; yi == bar_h / 2; yi++)
{
bm_bar_ptr = bm_bar + (bar_w * yi);
for (int xi = 0; xi < bar_w; xi++)
{
uint8_t v = *bm_bar_ptr++;
if (v < minVal) minVal = v;
if (v > maxVal) maxVal = v;
running += v;
}
}
running /= bar_w;
uint8_t threshold = minVal + ((maxVal - minVal) / 2);
//threshold = running;
// Walk the bitmap
bm_bar_ptr = bm_bar;
for (int yi = 0; yi < bar_h; yi++)
{
for (int xi = 0; xi < bar_w; xi++)
{
// Threshold all the pixels. Values > 50% go white, values <= 50% go black
// (opposite if Dark Mode)
// Could unroll this loop as an optimization, but probably not worthwhile
*bm_bar_ptr = (*bm_bar_ptr > threshold) ? (inverted?0x00:0xff) : (inverted?0xff:0x00);
bm_bar_ptr++;
}
}
CGImageRelease(displayImg);
displayImg = CGBitmapContextCreateImage(bmCxt);
CGContextRelease(bmCxt);
}
{
CGContextRef bmCxt = NULL;
CGImageRef img_cg = NULL;
bm_compare_w = scaleFactor * IMG.size.width;
bm_compare_h = scaleFactor * 22;
// Create out comparison bitmap - the image that was passed in
bmCxt = CGBitmapContextCreate(NULL, bm_compare_w, bm_compare_h, 8, 1 * bm_compare_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);
CGContextSetBlendMode(bmCxt, kCGBlendModeNormal);
NSRect imgRect_og = NSMakeRect(0,0,IMG.size.width,IMG.size.height);
NSRect imgRect = imgRect_og;
img_cg = [IMG CGImageForProposedRect:&imgRect context:nil hints:nil];
CGContextClearRect(bmCxt, imgRect);
CGContextSetFillColorWithColor(bmCxt, [NSColor whiteColor].CGColor);
CGContextFillRect(bmCxt, CGRectMake(0,0,9999,9999));
CGContextScaleCTM(bmCxt, scaleFactor, scaleFactor);
CGContextTranslateCTM(bmCxt, 0, (22. - IMG.size.height) / 2.);
// Draw the image in grey
CGContextSetFillColorWithColor(bmCxt, [NSColor blackColor].CGColor);
CGContextDrawImage(bmCxt, imgRect, img_cg);
compareImg = CGBitmapContextCreateImage(bmCxt);
CGContextRelease(bmCxt);
}
{
// We start at the right of the menu bar, and scan left until we find a good match
int numberOfScanLines = barRect.size.width - IMG.size.width;
bm_compare = malloc(1 * bm_compare_w * bm_compare_h);
// We use the meanValues buffer to keep track of how well the image matched for each point in the scan
meanValues = calloc(sizeof(CGFloat), numberOfScanLines);
// Walk the menubar image from right to left, pixel by pixel
for (int scanx = 0; scanx < numberOfScanLines; scanx++)
{
// Optimization, if we recently found a really good match, bail on the loop and return it
if ((presumptiveMatchIdx >= 0) && (scanx > (presumptiveMatchIdx + 5))) {
break;
}
CGFloat xOffset = numberOfScanLines - scanx;
CGRect displayRect = CGRectMake(xOffset * scaleFactor, 0, IMG.size.width * scaleFactor, 22. * scaleFactor);
CGImageRef displayCrop = CGImageCreateWithImageInRect(displayImg, displayRect);
CGContextRef compareCxt = CGBitmapContextCreate(bm_compare, bm_compare_w, bm_compare_h, 8, 1 * bm_compare_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);
CGContextSetBlendMode(compareCxt, kCGBlendModeCopy);
// Draw the image from our menubar
CGContextDrawImage(compareCxt, CGRectMake(0,0,IMG.size.width * scaleFactor, 22. * scaleFactor), displayCrop);
// Blend mode difference is like an XOR
CGContextSetBlendMode(compareCxt, kCGBlendModeDifference);
// Draw the test image. Because of blend mode, if we end up with a black image we matched perfectly
CGContextDrawImage(compareCxt, CGRectMake(0,0,IMG.size.width * scaleFactor, 22. * scaleFactor), compareImg);
CGContextFlush(compareCxt);
// Walk through the result image, to determine overall blackness
bm_compare_ptr = bm_compare;
for (int i = 0; i < bm_compare_w * bm_compare_h; i++)
{
meanValues[scanx] += (CGFloat)(*bm_compare_ptr);
bm_compare_ptr++;
}
meanValues[scanx] /= (255. * (CGFloat)(bm_compare_w * bm_compare_h));
// If the image is very dark, it matched well. If the average pixel value is < 0.07, we consider this
// a presumptive match. Mark it as such, but continue looking to see if there's an even better match.
if (meanValues[scanx] < 0.07) {
if (meanValues[scanx] < presumptiveMatchMeanVal) {
presumptiveMatchMeanVal = meanValues[scanx];
presumptiveMatchIdx = scanx;
}
}
CGImageRelease(displayCrop);
CGContextRelease(compareCxt);
}
}
// After we're done scanning the whole menubar (or we bailed because we found a good match),
// return the origin point.
// If we didn't match well enough, return NSZeroPoint
if (presumptiveMatchIdx >= 0) {
ret = CGPointMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame));
ret.x -= (IMG.size.width + presumptiveMatchIdx);
ret.y -= 22;
}
CGImageRelease(displayImg);
CGImageRelease(compareImg);
CGColorSpaceRelease(csK);
if (bm_bar) free(bm_bar);
if (bm_compare) free(bm_compare);
if (meanValues) free(meanValues);
return ret;
}
#end
From the Apple NSStatusItem Class Reference:
Setting a custom view overrides all the other appearance and behavior settings defined by NSStatusItem. The custom view is responsible for drawing itself and providing its own behaviors, such as processing mouse clicks and sending action messages.