NSRuleEditor – you are so cool but so frustrating

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.

NSPredicate trick

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).

3 thoughts on “NSRuleEditor – you are so cool but so frustrating

    • That was someone else’s code… I didn’t write it. But you are correct, it should be. I updated the posting. 🙂

      Thanx!

  1. Pingback: NSPredicateEditor & NSPredicate | seanhuang 技术点滴

Comments are closed.