Is your NSTableView or NSOutlineView showing 2 tooltips (in Mac OS X 10.5 or later)?
If so, you may be being bit by some well-meaning automagical code. But there’s a simple solution. Read on!
Is your NSTableView or NSOutlineView showing 2 tooltips (in Mac OS X 10.5 or later)?
If so, you may be being bit by some well-meaning automagical code. But there’s a simple solution. Read on!
If you’re not a Mac programmer, you can stop reading now. 🙂
Apple has this cool new widget called NSRuleEditor, introduced in Mac OS X 10.5. A lot of the software I’ve written over the past some years involves searching for things and often the user needs to provide you with rules by which to search. I’ve always had to invent my own solutions, and it’s a non-trivial thing to do. So when I finally heard about NSRuleEditor some years ago I was most excited to finally have an OS-provided solution.
The way Apple implement the widget is pretty cool. They realize people will use it to display whatever arbitrary data model they have, so they structured it that way. The NSRuleEditor iself is a single control, the contents of which are opaque to you… mostly. There’s your data model objects, which NSRuleEditor calls your criteria (that bugs me, as “criteria” is plural and the API uses it in a singular sense, but oh well… I can understand why they did that). The criteria can be whatever you want them to be, it doesn’t matter. But each criteria object then has a “display value”, which is how the criteria is displayed and/or worked with in the GUI, such as a string or a menu item or a text field or date picker. Thus I can have a LabelObject, an OperatorObject, and a TextObject which represent my data model. The LabelObject would have a display value of “name” and a predicate value of “left hand expression” and kMDItemDisplayName, the OperatorObject would have a value of “contains” and a predicate operator value of “IN”, and the TextObject would have a display value of an NSTextField and a predicate value of the “right hand expression”. Thus I could get the GUI to display “name contains <foo>” and create a query string like “kMDItemDisplayName IN <foo>”.
It’s a little obtuse to wrap around at first, but once you figure it out, it’s actually pretty well done. Apple has to make a generic control that anyone can use, and rule display/editing is non-trivial. Really, they did a pretty good job with this.
One thing to realize is the displayValues are not necessarily what is displayed in the GUI. Yes they generally are, but when you do this there is no localization. However, NSRuleEditor has a way you can provide a dictionary of formatting strings that allow you to change the displayed format. You see, the criteria objects are truly the data model. The displayValues aren’t truly the view… they can be, but they’re like “one step back” from being the view. Really, you should be using the formatting strings to get the real view… and then it’s like the displayValues are a “view model” for the view. Confusing? I know, but this is how it is. The formatting strings are your way to localize the display, but of course you don’t need to use it for localizing… you could just use them to change what is truly displayed to something other than the strict “view model”. There’s flexibility in the mapping the formatting strings provide.
So for instance I want to do a name criteria. The formatting strings are a key-value pair. The key is a pattern that matches the labels of your criteria objects. For instance:
%[Name]@ %[matches, contains, is, begins with, ends with]@ %@
Provides the formatting key. The %@ groups demarcate special identifiers, so that in your value, you’d have something like:
%1$[Name]@ %2$[matches, contains, is, begins with, ends with]@ %$3@
As the formatting value is parsed, it picks up the “x$” identifiers and knows this affects the ordering of the elements in the string, so you could shuffle things around. As well then, the items within the brackets are also ordered. So for instance if I had a value string like:
%1$[Name]@ %2$[ends with, begins with, is, contains, matches]@ %$3@
then when my data model said “matches” the GUI would present “ends with” to the user. Of course, this would be highly confusing to the user thinking they were doing an “ends with” search but the actual search ended up doing a “matches” search. But it shows what you can do.
Of course, the real benefit of this is localization. Let’s say we were converting “name contains foo” to Japanese. The key is the same as above, but the value then becomes:
%1$[お名前は]@ %$3@ %2$[が入っている]@
Which is “name contains”.
All of this is good and great. But where it falls flat? You have no way to know what NSRuleEditor is displaying. What if you wanted to get that that information? You can’t. You can get the -criteriaForRow: to get the row’s criteria objects. You can get the -displayValuesForRow:, which gets the raw display values. But you can’t get the formatted display values, like a -formattedDisplayValuesForRow:
*sigh*
This put a big crimp in things for me and the code I’m presently working on. Granted, I can get all the elements myself, of the criteria, the display values, and the formatting strings, but to apply the formatting strings to the display values is a non-trivial task. IMHO, a bug/shortcoming in the NSRuleEditor API. Bug filed: rdar://problem/7741182.
That all said, there is a cool NSPredicate trick.
This comes from blacktree-alchemy’s code for QSMDPredicate. It’s basically a way you can shove a MDQuery syntax string into an NSPredicate and have it all work out. Handy because NSRuleEditor is based upon NSPredicate, but since NSPredicate and the MDQuery syntax don’t have a perfect one-to-one correspondence (or maybe you’re using MDQuery instead of NSMetadataQuery), it’s handy.
In case the QSMDPredicate code ever goes away, here it is. Again, this is NOT mine, just someone thankful for it’s existence and release under the Apache License 2.0.
QSMDPredicate.h
//
// QSMDPredicate.h
// QSSpotlightPlugIn
//
// Created by Alcor on 5/6/05.
//
#import
@interface QSMDQueryPredicate : NSPredicate {
NSString *query;
}
+ (id)predicateWithString:(NSString *)aQuery;
- (NSString *)query;
- (void)setQuery:(NSString *)aQuery;
@end
QSMDPredicate.m
//
// QSMDPredicate.m
// QSSpotlightPlugIn
//
// Created by Alcor on 5/6/05.
//
#import "QSMDPredicate.h"
@implementation QSMDQueryPredicate
- (id)generateMetadataDescription{
return query;
}
+ (id)predicateWithString:(NSString *)aQuery{
QSMDQueryPredicate *predicate=[[[self alloc]init]autorelease];
[predicate setQuery:aQuery];
return predicate;
}
- (NSString *)predicateFormat{
return query;
}
- (NSString *)query { return [[query retain] autorelease]; }
- (void)setQuery:(NSString *)aQuery
{
if (query != aQuery) {
[query release];
query = [aQuery copy];
}
}
- (id)copyWithZone:(NSZone *)zone
{
id copy = [[QSMDQueryPredicate allocWithZone:zone]init];
[copy setQuery:[self query]];
return copy;
}
@end
(Wow… I just realized my current WordPress.com theme is crappy at handling blocks. I need to learn more CSS so I can tweak that).
Ye old blog has been quiet because I’ve been occupied the past few days with a fun little project.
You see, every so often Apple releases a new OS version. In this new OS version there’s always all kinds of cool technologies and goodies that makes easier the lives of us software developers. I remember when Apple released Core Data in Mac OS X 10.4 (gory technical documentation here) and what a boon that was for object graph management and being able to work with more complex data structures and files right out of the box instead of having to invent your own management system. Quite a boon. However, we (me and my team) were unable to take advantage of it when it first came out. I’m a commercial software developer and we’re driven by the market… especially what OS version our potential customers are likely to use. Thus if the new technology is only available in some particular OS version we have to wait until that’s our minimum supported OS version. That could take us a couple releases or years before that happens. Thus, we’re always way behind on adopting cutting edge technology.
I totally understand this situation from a market and sales perspective. But as a geek it always pains me because I don’t get to play with the new goodies. 🙂 By the time I get to play with them, I’m way behind the curve. In a sense that’s good because kinks get worked out, maybe Apple has updated the toolbox and filled in the holes. But it’s just not exciting to the geek in me.
Right now I’m working on implementing a new interface for the application I work on. We’re going to have to support Mac OS X 10.4 (Tiger), and that was really paining me because we’re wanting to modernize the look and feel of the app, but so long as we’re tied to 10.4 that really hampers things. Eventually we were able to come up with a strategy to give the 10.4 users one look and feel, but for users of Mac OS X 10.5 (Leopard) and beyond? They’re going to get something really new and snazzy. The cool part is I get to work with some modern technologies, like Core Animation and Core Image. I know that doesn’t mean much to you non-Mac-programmer types. Just know that a lot of the slick stuff about the Mac OS X graphical user interface comes from these. No, I don’t want to get all eye candy just for the sake of eye candy: form must follow function. But it will allow for a much more modern interface and I don’t have to wait another year or two before I can do it.
I’ve been reading documentation, experimenting in a test bed, and working to come up with a good prototype. It’s just been exciting and I haven’t had this much fun programming in a long time. I’m a kid in a candy store! So I’ve just been head-down in it. I’d love to show you what I’ve been doing but I can’t. Sorry. If you want a vague idea, if you’re a Mac user just invoke Front Row. It won’t be exactly like that, but all that behavior and effect? Core Animation, Core Image, Quartz Compositioning, all make that go.
I just bit myself, hard.
Day job has me as a software developer. I was testing out some changes to a window layout. This window allows you to configure automated tasks… so say, at midnight your computer will automatically execute whatever you told it to do. Well, I was just testing out the GUI changes but needed a task in the window so I created a dummy task. The task ended up being set to move basically every file in my home directory into my Documents folder. I didn’t set it that way, those are just all the initial settings when you create a new task. I didn’t care.. .it was just to be a dummy task for the GUI testing, right?
Only I forgot to remove the task before I stopped work for the day.
I get to my work computer this morning and about shit myself because I saw everything was gone.
Only no, it’s not all gone, it just moved some 97,000 files to a single folder.
Shit. Shit. Shit!
And the sad thing is, my Time Machine backups were turned off because last month it started to constantly choke on these particular files, and I couldn’t resolve the issue. So the last backup I have is a month old. But hey, if I had a backup from a few hours prior I could just blast it back and be done with it.
Damnit.
Well, I know what I’m spending today doing.
*sigh*
I figured with the growing amounts of electronics in cars that there must be a lot of code behind it, but I didn’t realize just how much code until I read this article.
Speaking as a software developer, that is massive amounts of code. Huge. Trying to maintain that would be a bear. Furthermore, trying to ensure it’s error/bug-free? A monumental task, edging on impossible.
I don’t know how the automotive industry works in terms of producing this software, but if it’s anything like most commercial software companies then lord… it makes me want to drive a Model T, that’s all mechanical and easy to fix. All I can do is hope they have a good understanding of proper software development process and are not willing to sacrifice solid reliable code because they need some new feature and needed it yesterday.
I also hope they understand how simple is better. All those millions of lines of code? Gosh… if they can take the time to simplify and trim that down, so much better in the long run. More maintainable, less chance of errors and problems and risking catastrophic failure.
Gosh I hope they do things right.
Of course the geek in me wants to know nitty gritty details now like the language they’re using and the toolset. 🙂
This is documented but in a subtle way, so I thought it’d be worthwhile to mention in a more obvious way.
If you are using NSTableView’s -setDoubleAction: method, the action is only invoked if the cell or column double-clicked upon is uneditable.
Thus, it may not be enough to go:
[theTableView setDoubleAction:@selector(myAction:)];
You may also have to do something like:
[theTableView setDoubleAction:@selector(myAction:)];
[theTableColumn setEditable:NO];
I ran into this just now because I had a single-column table (just a simple table to display a list of stuff, thus the user wasn’t allowed to edit anything) and I wanted to allow a double-clicking on the item (row/cell) to advance the user to the next stage of things (they could also single-click to select the item then click a “Go” button… double-click would just be a shortcut). It actually was working fine under Mac OS X 10.6 Snow Leopard, but the double-click was failing under Mac OS X 10.4 Tiger. Once I made the table column explictly uneditable, it started working and the table received the clicks, the clicks didn’t fall through to the table column.
Apparently there’s an obscure gcc compiler directive to help resolve Objective C namespace conflicts.
It’s called @compatibility_alias.
Never knew that, and so far I’ve been fortunate and haven’t encountered many conflicts in my career (had a couple, easily remedied). Neat.
Work ran long today. I was determined to finish the compliance with the new open source software usage policy, so I could get back to more creative endeavors. I’m all done save hearing back from the CTO on a couple things.
Consequently I missed my martial arts class tonight. Bummer too, as it’s weapons night.
Tonight is also the sparring class. If all goes well, I’ll have something from the UPS man in a couple days that will make that class finally happen. Details will be posted after Mr. UPS stops by.
In lieu of proper class, off to the garage I go. Going to work out on the DIY mook jong. I think I’ll stick my blue gun in the holster too for a little “cross-training”. And on all that knife blarg I’ve been writing about lately? That is one benefit to the Delica: there’s a trainer version. Hrm.
“Freedom was given to humanity by God. But, governments, if they can help it, never give freedom. They just hand out slavery with slogans.” — Taylor Caldwell
I’ve never written an installer with Apple’s PackageMaker. Never had a need to.
However, the day job has recently required me to write a few custom plugins to customize how some installers work.
I was amazed at how easy it was. The InstallerPlugins.framework is very simple and well-constructed. Sure you can’t do everything you need with it, but you can do most and then write scripts and other things to customize it to your heart’s content.
The thing that gets me? There’s almost no documentation on it. Apple provides the InstallerPluginSample code, and really in a lot of ways between that and reading the InstallerPlugins.framework headers, that’s about all you need. It’s that simple and well-written of an SDK.
Still, some things came up while I was working that I couldn’t find an answer to. For instance, I was instructed to create a “display license” panel that replicated the stock panel the installer provided but had a few customizations. The trouble with this? Anything outside of the content panel I couldn’t replicate, such as the “Print” and “Save” buttons. I was pointed to this resource and specifically this FAQ:
[Q] Can I add Print… and Save… buttons similar to the ones used in the default License pane?
- You can do it through an undocumented and private method:
1 Open the nib of your plugin project in Interface Builder. 2 Add a custom view to the nib file. 3 Add Print… and Save… buttons to this custom view. 4 Create outlets in your controller class for this custom view and buttons and make the appropriate connections in Interface Builder. 5 Add 2 methods in your controller class to be called from the 2 buttons and connect the buttons to these methods in Interface Builder. 6 Add the - (id) bottomContentView;method to your controller class and make it return the reference to your custom view.
IMHO, using an undocumented and private method is bad and just asking for breakage and trouble down the line. But, some people are willing to live with that so tread accordingly.
Stéphane Sudre also provides this HOWTO on using PackageMaker. By the screenshots and some text I can see it’s old, so I don’t know how relevant it is to day’s PackageMaker. But there you go.