Although things like multiple entry fields, checkboxes and popup menus are useful, what would be more useful would be some way of being able to mix-and-match -- a dialog toolkit in a script library. So this is an attempt to build such a thing, based on the NSAlert stuff I have already covered.
The handler that shows the alert will be similar, and I'll come back to it. The idea is this: a series of handlers will each produce a type of control. You call them in the order you want them to appear, and pass a list of the resulting controls to the handler that produces the alert. By doing it this way, all the ASObjC code can be stashed away in a script library, and you can call it all from there under Mavericks or Yosemite.
Before I start, I'll point out that this code is only minimally tested. Be prepared for corrections, and also don't expect it to behave particularly well if you pass it nonsense values.
The controls covered are text labels and fields, popup menus, checkboxes, radio buttons, and path controls, as well as horizontal rules for breaking stuff up. In several cases there will also be convenience handlers that build both the controls and related labels.
The potentially messy bit of the whole business is the positioning of controls, and I'll try to keep that as simple as possible with a few rules. The resulting dialogs will not match what you can do in Xcode, but they will be much more convenient to use.
The first thing needed is a handler to build a label. The earlier scripts that built labeled text fields show how it is done, but for this exercise more flexibility is needed -- specifically the ability to have labels that run over multiple lines, and to set alignment alternatives.
The handler therefore requires the string to use, the x coordinate (leftInset), the bottom y-coordinate (bottom -- remember, y values go from bottom to top), the maximum width (maxWidth), the alignment (pass "left", "right" or "center"), and a boolean for whether to make the text wrap if needed. The NSTextField is created and it's properties set as in the earlier examples, but this time the code gets it's real size (fittingSize), and adjusts its frame based on that plus the choice of alignment.
The handler returns the label, plus the y-coordinate of its top, and it's actual width. The top value is particularly important, and all the handlers will return a similar value. The idea is this: rather than trying to calculate all the y-coordinates, which can be fairly complex (actually, in most cases impossible until the controls are all constructed), you build your controls starting at the bottom one, and work up. That way, you set the bottom of your first control to 0. And when you create the one above it, you set its bottom to the top value returned from the previous one (plus some space), and so on.
So here is the code that creates a label:
-- alignment is "left", "right" or "center"; multiLine is a boolean, true for multi-line labels on makeLabel:labelString leftInset:theLeft bottom:theBottom maxWidth:maxWidth alignment:alignment multiLine:wrapsBool set theLabel to (current application's NSTextField's alloc()'s initWithFrame:(current application's NSMakeRect(theLeft, theBottom, maxWidth, 17))) tell theLabel (its setStringValue:labelString) (its setEditable:false) (its setBordered:false) (its setDrawsBackground:false) its (cell()'s setWraps:wrapsBool) set {width:newWidth, height:theHeight} to its fittingSize() -- actual width and height if alignment begins with "r" then its setAlignment:(current application's NSRightTextAlignment) its setFrame:{origin:{x:maxWidth - newWidth, y:theBottom}, |size|:{width:newWidth, height:theHeight}} else if alignment begins with "c" then its setAlignment:(current application's NSCenterTextAlignment) its setFrame:{origin:{x:(maxWidth - newWidth) / 2, y:theBottom}, |size|:{width:newWidth, height:theHeight}} else its setAlignment:(current application's NSLeftTextAlignment) its setFrameSize:{width:newWidth, height:theHeight} end if end tell -- return theLabel, the top of the label, and its width return {theLabel, theBottom + theHeight, newWidth} end makeLabel:leftInset:bottom:maxWidth:alignment:multiLine:
The next handler is for a text field. The code in this is also similar to code used earlier, but again it's a bit more flexible. The handler requires the initial string, the placeholder to appear when there's no text, the left, bottom and width, and an extraHeight parameter. If you pass a value greater than 0 for extraHeight, the field will be deepened by that amount, and text wrapping will be turned on. So this is the handler:
-- extraHeight of 0 means it takes a single line, otherwise add as many points as you want on makeTextField:enteredText placeholderText:placeholder leftInset:theLeft bottom:theBottom theWidth:theWidth extraHeight:extraHeight set theTop to theBottom + 22 + extraHeight set theField to (current application's NSTextField's alloc()'s initWithFrame:(current application's NSMakeRect(theLeft, theBottom, theWidth, theTop - theBottom))) tell theField (its setEditable:true) (its setBordered:true) (its setPlaceholderString:placeholder) if extraHeight > 0 then its (cell()'s setWraps:true) its setStringValue:enteredText end tell -- return theField, the top of the field return {theField, theTop} end makeTextField:placeholderText:leftInset:bottom:theWidth:extraHeight:
Again, you will notice it returns its top, so that the control above it can easily be positioned.
|