NSMenu title cut off
With a lot of help from google, I've managed to put together a simple program which shows the user their IP Address when they select the 'Refresh IP' box from the program's dropdown menu.
It works, but there's one small problem The name of the menu isn't showing correctly.

As you can see in the image, half the IP is cut off on the top of the bar. I have no idea what's causing this and would appreciate any help! I don't usually program in Cocoa, so go easy on me :)
Thanks!
Mike
edit: oh, and here's the main.m contents
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
return NSApplicationMain(argc, (const char **) argv);
}
Attachment: AppController.m (1.0KB)
Attachment: AppController.h (0.0KB)
Mike
Well, if you don't usually program in Cocoa, you're doing very well to get to this point. You're very close.
There's a method in NSStatusItem - setAttributedTitle: and that accepts an NSAttributedString (which is a string which has formatting information). And I think that's what you'll have to use to get it to be the correct size. You can create NSAttributedString objects directly with the API. There's also support for RTF, HTML and some other formats to make you life easier (or harder).
However I think from looking at Aaron Hillegass' book (p278), this will fix it: .... bla bla bla
[statusItem setHighlightMode:YES];
NSMutableAttributedString* as_ipaddress
= [[NSMutableAttributedString alloc]initWithString:ipaddess];
[as_ipaddress addAttribute:NSFontAttributeName
value:[NSFont userFontOfSize:10]
range:NSMakeRange(0,[ipaddress length])];
//[statusItem setTitle:[NSString stringWithString:ipaddress]]; // don't need this anymore
Hi Robin, thanks for the help! It's much appreciated :)
That code works, and is MUCH better than the current situation, but there's still a *small* problem, pun intended :)
Using the code you provided (only replacing the bottom instance of the title change) I get the font looking like this:

Obviously if I change every instance of [statusItem setTitle:etc.] to your code the top 'GET IP' pic looks the same as the IP Address pic above.
If I don't use NSTask and get the ip via [[[NSHost currentHost] addresses] objectAtIndex:1] , which is very machine-specific, then the font looks like this:
It seems to me something to do with the way NSTask calls/saves the data. Any tips?
Thanks so much
Mike
You are right. I think there's a newline character at the end of the NSTask output (which is a wrapper for the system() API). You can stop in the debugger and inspect it. However the fix would be:
NSData *data = [file availableData];
ipaddress = [[NSString alloc] initWithData: data encoding: NSMacOSRomanStringEncoding];
NSLog(@"ipaddress=%@",ipaddress) ;
NSMutableString* ipaddress2 = [NSMutableString stringWithString: ipaddress];
[ipaddress2 replaceOccurancesOfString:@"\n"
withString:@\n"
options:NSLiteralSearch
range: NSRangeMakeRange( 0,[ipaddress length])
] ;
[file closeFile];
[task waitUntilExit];
// [statusItem setTitle:[NSString stringWithString:ipaddress]];
[statusItem setTitle:[NSString stringWithString:ipaddress2]];
Hmm, it didn't like NSRangeMakeRange for some reason. Also, not sure if there's a typo here:
[ipaddress2 replaceOccurancesOfString:@"\n"
withString:@\n"
missing a " somewhere?
Anyway, I've uploaded the project. Thanks for the help, you're a life saver! My brain was imploding :)
Attachment: Project.zip (18.0KB)
Mike
I think there were a couple of typos it the code I wrote earlier. This is working:
NSTask *task = [[[NSTask alloc] init] autorelease];
NSPipe *pipe = [[NSPipe alloc] init];
NSFileHandle *file = [pipe fileHandleForReading];
[task setLaunchPath: @"/bin/bash"];
[task setArguments:[NSArray arrayWithObjects: @"-c", @"/sbin/ifconfig en0 | /usr/bin/grep \"inet \" | /usr/bin/cut -f2 -d\" \"", nil]];
[task setStandardOutput: pipe];
[task launch];
//NSLog works before this line, e.g. shows up in Console
NSData *data = [file readDataToEndOfFile];
//After this, no matter what I try, NSLog no longer shows up in Console
//I have no idea what I'm meant to release/close/terminate here. I believe its to free memory from processes I no longer need?
//In other peoples codes I looked up, there was a [task release]; line. However that causes my program to crash with BAD_EXC_ACCESS
[task waitUntilExit];
[file closeFile];
[task terminate];
[pipe release];
//Taking the 'ipaddress' variable and loading it with the IP address retrieved by the ifconfig command
ipaddress = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
//Set the title of the menu to the IP Address (so the user can easily see it), thanks to Robin @ cocoaforum.com
NSMutableString* m_ipaddress = [[NSMutableString alloc]initWithString:ipaddress];
[m_ipaddress
replaceOccurrencesOfString:@"\n"
withString:@""
options:NSLiteralSearch
range:NSMakeRange(0,[ipaddress length])
];
[statusItem setTitle:m_ipaddress];
I had to put in the full paths to grep and stuff on my Snow Leopard machine. I was a little surprised by that. Anyway, you've done well here. It's working.
I'm getting excited. My next post is going to be number 100 on this forum in about 3 monts (1 post a day). Cocoa's fun.
Awesome! Thanks a ton :D
There's still that weird problem with NSLog, but that's not relevant to the program at all.
I'll definitely be back here if I hit any more problems
edit: Hmm, I replaced the extended bash part with the original, and it works on my machine? Running 10.6.3 too...weird:
[task setLaunchPath: @"/bin/sh"];
[task setArguments:[NSArray arrayWithObjects: @"-c", @"ifconfig en0 | grep \"inet \" | cut -f2 -d\" \"", nil]];
[task setStandardOutput: pipe];
Ah well, either way it works perfectly!
Thanks
Mike
One more question.
How do you check if a variable is empty? Currently if no ip address is set on en0, the menu will display nothing. I'm looking for a statement like this:
IF m_ipaddress = @"" THEN m_ipaddress = @"NO IP SET"
Thanks
Mike
edit: grats on 100 posts :)
btw, small typo on your site, you said you're active on cocalforum :D still links here tho.
like the scottish theme
Mike
This is my post #100. You've made my day. And I've fixed my system (also run 10..3). I had something horrible in ~/.MacOSX/.MacOSX/environment.plist for PATH.
You'll be back. Everybody finds Obj/C and Cocoa hard at first. It takes a while to understand the simple beauty of the system. It's worth the effort.
To help folks understand Cocoa, I'm writing a series of tutorials which I hope will help:
http://clanmills.com/articles/cocoatutorials
Don't be surprised to see your efforts reappearing in a tutorial. I will of course acknowledge your contribution.
Good Luck.
Thanks for the comments about the web site - and observing the typos. If you read any of my tutorials you'll discover that I am dyslexic.
And to answer your question about comparing strings, there are of course a myriad of ways to compare strings. In the case of the empty strings, I'd say:
if ( [myString length] == 0 ) { ... }
you could say:
if ( [myString isEqualToString:@""] ) { ... }
or
if ( [myString compare:@""]==0 ) { ... }
However this is Obj/C (.mm == Obj/C++) and you can't say:
if ( myStrings == @"" ) { ... }
for the same reason that in C, you can't say:
char* myString[100] ....
if ( myString == "" ) ...
you'd be comparing pointers which (in this case) could never match.
Obj/C object does not overload C operators such as = , or == (as you can with std::String in the STL/C++ world). The operator = is assignment, == is equality - no more, no less.
Hey Robin, thanks for the tips! isEqualTo works great :)
Oh, someone on macrumors showed me a different way to trim the newline characters off the end of the line, it's in the project :) extra info for u for when the next beginner comes stumbling along
I've got a weird one for you now: how do you 'unclick' or 'unselect' a menu?
As you'll see when you run the program, I've set it to automatically start checking for an IP address when first run (set to check only twice while I'm debugging, later I might set it to check 10 times to give DHCP a chance to assign an IP).
It works great, EXCEPT that when you click 'Refresh IP' the menu stays open and selected while the IBAction(s) run.
How do I get that menu to close/unselect before running the IBAction it's linked to?
Thanks mate
Mike
Attachment: Project2.zip (30.0KB)
Mike
I haven't looked at your code yet (I'm just out of bed). Instead of running the code immediately, you could use performSelector:withObject:afterDelay: I think a delay of 0 will do the trick (if not 0.1). This queues a message, so instead of doing it "in-line" you'll execute later. So the menu item will close, then you'll be called again. If you're wondering about selectors, I discuss this method in http://clanmills.com/files/Clock.pdf
Mike
I'm not sure what issue you're solving here because your code's working well on my 3 year old iMac. Anyhow, I've changed this:
// [self IpQueue:nil];
[self performSelector:@selector(IpQueue:) withObject:nil afterDelay:0];
And that seems to "do the business".
May I comment on your style. I know everybody holds very strong opinions about code layout and that kind of thing. Please use Capital Letters For The Names of Classes and use lower case for the names of properties and methods. So, say ipQueue and not IpQueue, getIpAddress and not GetIpAddress. AppController is correct - it's the name of a class.
Mike
Am I correct in thinking you've had an issue with NSLog() ? I'm debugging something and remembered about a couple of 'quirks' of NSLog().
1) Always give at least two arguments. If you want to print a string, use: NSLog(@"%@",myString);
2) I think there's a limit (200 characters or something).
3) Remember, you always use good old printf, like this:
printf("%s",[myString UTF8String]) ;
When you're debugging, XCode will direct printf's to his console.
printf however doesn't go to the console. I believe console is /dev/console (although, I don't seem to be able to write to that from "C").
If you want to run something from the Terminal and see the printf's, there's a good old command-line version of your app in project.app/Contents/MacOS/project and you can usually run him from the Terminal.
There's some mysterious difference between project.app (which you can start with the open command from the terminal) and running project.app/Contents/MacOS/project which you can simply run from the terminal (no open, nothing's needed - it's a command, just like ls or rm). One day I might even know the difference! If you find out, perhaps you can illuminate the rest of us.
Robin,
Thanks for the help. The problem isn't when the program first runs, the problem is when I click 'Refresh IP' from the menu. That is linked straight to the ipQueue: method. So changing the [self ipQueue:nil]; line in awakeFromNib doesn't make a difference.
The first time it runs (assuming you're not connected to any networks) you'll notice it flashes between 'Checking IP' and 'No IP Set' twice, then stops at No IP Set. If you then select 'Refresh IP' out of the dropdown 'No IP Set' menu, it will do the same thing BUT while it does it the menu will stay highlighted (e.g. blue instead of grey)
The NSLog() problem I was having has been (sort of) solved. I needed this line included:
[task setStandardInput: file];
Nobody seems to know why it's needed, but without it after this line NSLog would no longer work again:
NSData *data = [file readDataToEndOfFile];
Thanks for the naming tips too :)
Mike
Mike
I think you're sleeping on the job! Be careful about using sleep() in any UI code because it causes your process to "freeze" and I think that's what's causing you visual issues here. I made some wee changes which seems to make things better (I've pasted the code below).
I'm really impressed by what you're doing here. I am moderator on the forum http://forum.whatismyip.com/ So I'm making an application based on this. You get the remote IP of your machine with: curl http://whatismyip.com/automation/n09230945NL.asp
I'm writing an article about this for my web site and I'll probably get it finished on Monday. The application will arrives with its own "System Preferences" control panel so you can add and remove any "one liner". So we could report free disk space, number of emails in our inbox and so on. You've thought of something really useful here.
http://forum.whatismyip.com/f9/mac-testers-wanted-t911/
I of course acknowledge your contribution. Thank you. Comments (and criticism) welcome.
-(IBAction) IpQueue : (id) sender
{
NSInteger count = 0;
while (count != 2) {
// sleep(1);
[statusItem setTitle:@"Checking IP..."];
// sleep(1);
[self GetIpAddress:nil];
if ([ipaddress isEqualTo:@"No IP Set"]) {
count = count++;
}
else {
count = 2;
}
}
}
-(IBAction)reallyGetIpAddress:(id)sender
{
printf("reallyGetIpAddress:\n");
fflush(stdout);
NSTask *task = [[NSTask alloc] init];
NSPipe *pipe = [[NSPipe alloc] init];
NSFileHandle *file = [pipe fileHandleForReading];
[task setLaunchPath: @"/bin/sh"];
[task setArguments:[NSArray arrayWithObjects: @"-c", @"ifconfig en0 | grep \"inet \" | cut -f2 -d\" \"", nil]];
[task setStandardOutput: pipe];
[task setStandardInput: file];
[task launch];
NSData *data = [file readDataToEndOfFile];
[task waitUntilExit];
[file closeFile],[task terminate],[pipe release],[task release];
ipaddress = [[[[[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding] autorelease]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] retain];
if ([ipaddress isEqualTo: @""]) {
ipaddress = @"No IP Set";
}
[statusItem setTitle:ipaddress];
}
-(IBAction)GetIpAddress:(id)sender
{
printf("GetIpAddress:\n");
[statusItem setTitle:@"Checking IP..."];
fflush(stdout);
[self performSelector:@selector(reallyGetIpAddress:) withObject:nil afterDelay:1];
}
cheers! Works perfectly now
The reason I started this is I'm about to switch everyone in the company to DHCP. It'll be much easier if they can just see their IP in the menu bar.
Feel free to use whatever you want from the program, you pretty much ended up writing it yourself :)
Well, Mike. If this is your first efforts in Cocoa, I think you've done really well. Good Job.
Sure I'm going to borrow/steal you ideas. I'm a moderator on the WhatIsMyIP.com forum. So we're going to make a variant of your code available to our users:

I've added a load of extra stuff including a System Prefs Pane which will enable you to modify the behaviour. In fact it can run any 'old liner'. Eg disk space available, number of emails waiting - that kind of thing. The code will be GPL - open source.
Be sure and come back to visit the forum again and let us know how you're getting on.
Looks good :) I like the local and remote ip idea.The only other thing I noticed that could be change is I couldn't drag the menu bar around.
e.g. you can reposition most of the other icons there by holding command when you click them, but not the menubar.