This chapter shows how to do a few interesting things with NSViews. In the first part, we’ll show how to put a zoom pop-up menu in an NSScrollView. In the second part, we’ll show how to generate Encapsulated PostScript (EPS), PDF, or TIFF files from an NSView; how to save a graph into an EPS, PDF, or TIFF file; and how to add controls to a Save panel (dialog).
In the previous chapter, we arranged for the GraphView object to rescale its coordinate system when its containing window was resized. Although the scaled coordinate system is appropriate for our graphing application and is a good way to show how to catch resizing events, stretching an application’s window isn’t the right way for a user to get a magnified view of the window’s contents. Consider the Aqua interface standard: a window is supposed to be just that — a window into a page of a virtual document. The document itself shouldn’t get bigger or smaller when the window is resized; rather, a bigger or smaller window should let the user see more or less of a document.
Mac OS X applications should enable the user to see more detail using a zoom button — not the green zoom button in a window’s title bar, but a little pop-up menu button, typically located at the bottom of an NSScrollView, that allows you to change the magnification of the NSScrollView. Microsoft Word and Stone Design’s Create applications both have such a button (see Figure 19-1).
The window on the left in Figure 19-1 is set to a zoom factor of 100%. Pressing the zoom button reveals a pop-up menu of different magnification settings, which in turn lets you change the size of the text that is displayed. In the right window, we changed the setting to 200%.
It’s easy to add a zoom button to any NSScrollView, but you have to know a little bit about how the NSScrollView works first.
Each NSScrollView object has another object “inside” it, called its docView , which is the actual NSView being displayed. In addition to the docView, each NSScrollView has the following three NSView objects to help it perform its scrolling tasks:
An NSScroller to control horizontal scrolling
An NSScroller to control vertical scrolling
An NSClipView that displays the part of the docView that is visible
We can change the magnification of the NSView displayed in the NSScrollView simply by sending a scale: message to the NSClipView. The NSClipView will scale the NSView that it contains when it is drawn. Zooming happens without the knowledge or cooperation of the docView.
Whenever an NSScrollView changes size (for example, when it is resized), and whenever the NSView that it contains changes size, the NSScrollView sends the tile message to itself to alert its subviews to change their sizes. By subclassing the NSScrollView class and overriding the tile method, we can place additional objects (such as rulers and zoom buttons) over or next to the scrollers that the NSScrollView displays.
In this section, we will subclass the NSScrollView class to make a new class called ZoomScrollView. This class will have outlets for its docView — the TrackingGraphView that we created in the previous chapter — as well as for the zoom pop-up menu. We’ll control the ZoomScrollView instance from the Controller object that we created in Chapter 17.
Open your GraphPaper project in Project Builder
and the MainMenu.nib
file in Interface Builder.
Resize the TrackingGraphView in the GraphPaper window so that it’s smaller, and put it somewhere in the window that is out of the way.
Don’t worry about the size or position of the TrackingGraphView — it will automatically be resized and placed in the proper position when GraphPaper runs.
Subclass the NSScrollView class and rename the new subclass “ZoomScrollView”.
Add the subView
and zoomButton
outlets and the changeZoom:
action method to the new ZoomScrollView
class.
Drag a CustomView icon from IB’s Cocoa-Containers palette and drop it in the GraphPaper window. Change its class to ZoomScrollView.
Resize the on-screen ZoomScrollView instance to be as large as the previous TrackingGraphView instance. Note that the TrackingGraphView instance shows through the ZoomScrollView a bit.
Choose IB’s Layout → Send To Back menu command to put the ZoomScrollView instance “behind” the TrackingGraphView instance, as shown in Figure 19-2.
The only reason to send the ZoomScrollView to the back is so we can easily see both NSViews at the same time; it won’t make a difference in the way GraphPaper runs.
Drag a pop-up menu button from IB’s Cocoa-Other palette and place it below the ZoomScrollView, to the left of the x: field (see Figure 19-3). Do not place it on the ZoomScrollView.
Double-click the pop-up menu button.
When you double-click the pop-up menu button in IB, you’ll see the three menu cells that the associated pop-up menu initially contains. The on-screen pop-up menu is controlled by an instance of the NSPopUpButton class. The NSPopUpButton class, a subclass of the NSButton class, creates an NSMenu object to handle its menu-like functionality.
You can add a new item to an open pop-up menu in IB by dragging the Item menu cell from the Cocoa-Menus palette and dropping it in the pop-up menu. You can’t add submenus to a pop-up menu, however, because that would violate the Aqua interface guidelines.
You can give an individual target and action to each item within the pop-up menu. Alternatively, you can simply give an action to the button on top, which is called the cover (a type of NSPopUpButton). After a selection is made, the NSPopUpButton automatically changes the title of the button by sending it the setTitle: message.
One by one, drag three more menu items from the Cocoa-Menus palette and drop them in the pop-up menu. Make the width of the pop-up menu smaller.
Name the menu cells 100%, 125%, 150%, 175%, 200%, and 300%, and give them the tags 100, 125, 150, 175, 200, and 300, respectively (use the NSMenuCell Attributes Info dialog). When you’re done, the pop-up menu should look like the one shown here.
Connect the NSPopUpButton’s cover to the ZoomScrollView so that it sends the changeZoom: action message (see Figure 19-3).
Click the 100% item and close the pop-up menu by clicking somewhere else in the window. This ensures that the initial condition will be with the pop-up menu at 100%.
Connect the ZoomScrollView’s
zoomButton
outlet to the pop-up menu button. Note
that this connection is in the opposite direction of the previous
one.
Connect the ZoomScrollView’s
subView
outlet to the TrackingGraphView, as shown
in Figure 19-4.
Create the class files for ZoomScrollView and insert them into the GraphPaper project.
Insert the lines shown here in bold into
ZoomScrollView.h
:
#import <Cocoa/Cocoa.h> @interface ZoomScrollView : NSScrollView { IBOutlet id subView; IBOutlet id zoomButton; double scaleFactor; } - (IBAction)changeZoom:(id)sender;- (id)initWithFrame:(NSRect)theFrame; - (void)awakeFromNib; - (void)setScaleFactor:(float)aFloat; - (void)tile; @end
We’ll use the
scaleFactor
instance variable to store
the current scale factor of the ZoomScrollView. For example, a
user’s zoom choice of 100% will yield a
scaleFactor
of 1.0, a choice of 150% will yield a
scaleFactor
of 1.5, and so on. When the user
changes the zoom percentage using the pop-up menu, the NSPopUpButton
object will send the changeZoom:
message to the ZoomScrollView object, which in turn will send the
setScaleFactor:
message to itself.
ZoomScrollView’s setScaleFactor: method will then compare the
new zoom factor with the current one and calculate the proper
arguments to the scale: message. If
the new magnification is the same as the old, the ZoomScrollView
won’t do anything.
The initWithFrame: method is the designated initializer for the NSView class. ZoomScrollView’s initWithFrame: method sends the initWithFrame: message to its superclass and then sets the background of the NSScrollView to be white. Finally, it sets the current scale factor to be 1.0, which corresponds to the 100% menu item in the pop-up menu.
TrackingGraphView’s awakeFromNib method installs the TrackingGraphView instance as the docView inside the NSScrollView.
Insert the following awakeFromNib
method into
ZoomScrollView.m:
-(void) awakeFromNib { [self setHasHorizontalScroller:YES]; [self setHasVerticalScroller:YES]; [self setBorderType:NSLineBorder]; // Set up the zoom button [[zoomButton cell] setBordered:NO]; [[zoomButton cell] setBezeled:YES]; [[zoomButton cell] setFont:[NSFont labelFontOfSize:10.0]]; [self addSubview:zoomButton]; // The next two lines install the subView (TrackingGraphView) // and set its size to be the same as the NSScrollView [self setDocumentView:subView]; [subView setFrame:[[self contentView] frame]]; }
This method tells the ZoomScrollView that both scrollers are required
and that its border should be of type NSLineBorder (as opposed to
NSNoBorder, NSBezelBorder, or NSGrooveBorder, all of which are
defined in NSView.h
). Finally, it sizes the
TrackingGraphView (the subView) to be the size of the
NSScrollView’s contentView, which is the NSClipView.
The next method is the one that actually changes the magnification of the NSClipView object. NSClipViews are used by the NSScrollView class to do the actual displaying of the NSView.
Insert the following setScaleFactor:
method into
ZoomScrollView.m
:
- (void)setScaleFactor:(float)aFactor { NSAssert(aFactor!=0,@"Illegal scale factor. Set the tag!"); if (scaleFactor != aFactor) { float delta = aFactor/scaleFactor; scaleFactor = aFactor; [[self contentView] scaleUnitSquareToSize:NSMakeSize(delta,delta)]; } }
The scaleUnitSquareToSize:
method rescales the NSClipView (the
contentView), with the arguments being the delta
(change) necessary to make the NSClipView have the magnification that
the user wants. This method uses the NSAssert( )
macro, which is similar to the ANSI C
assert( )
macro, except that it allows you to
specify a printf
-style string that is printed if
the assertion is false. If aFactor==0
, the
NSAssert( )
macro will raise the exception
NSInternalInconsistencyException.
The next method is the action that is invoked when the user clicks the pop-up menu button.
Finally, there is our tile method, an override of NSScrollView’s tile method. It is invoked automatically when the ZoomScrollView’s size changes.
Insert the following tile
method into
ZoomScrollView.m
:
- (void)tile { NSRect scrollerRect, buttonRect; [super tile]; // Place the pop-up button next to the scroller scrollerRect = [[self horizontalScroller] frame]; NSDivideRect(scrollerRect, &buttonRect, &scrollerRect, 50.0, NSMaxXEdge); [[self horizontalScroller] setFrame: scrollerRect]; [zoomButton setFrame: NSInsetRect(buttonRect, 1.0, 1.0)]; }
This tile method gets the frame of
the horizontal scroller, snips off 50 pixels, and gives that space to
the zoom button. The 50-pixel limit was determined by trial and
error. Note how handy the Application Kit functions (e.g.,
NSDivideRect( )
and NSInsetRect( )
) are for manipulating rectangles!
Build and run GraphPaper, saving all files first.
You should get a magnification button in the lower-right corner of the ZoomScrollView, as shown in the window at the top of Figure 19-5. Enter a function and click the Graph button.
Press the pop-up menu button, drag to 175%, and release the mouse button. The zoom button’s title should change, as shown in the window at the bottom of Figure 19-5.
Resize the GraphPaper window — oops, we haven’t taken care of Autosizing yet! We’ll show you how to do that in the next section.
Resize the GraphPaper window anyway and drag the scroll knobs. The ZoomScrollView should stretch, making more of the graph visible, rather than changing its scale, as it did previously.
The zoom pop-up menu will make it more likely that a user will want to resize (probably enlarge) the GraphPaper window, but we didn’t take care of that possibility yet. We’ll do that now.
Back in IB, select ZoomScrollView and type Command-3. Make ZoomScrollView’s Autosizing box look like the one on the left in Figure 19-6.
Then select the NSForm (xmin
,
xmax
, etc.) and make its Autosizing box look like
the one on the right in Figure 19-6.
Build and run GraphPaper, saving the nib file. Note how quickly the
building process took this time — PB only had to stuff a new nib
file in the .app
directory, not much work.
Graph a function and resize GraphPaper. It works better but is still not perfect. See Section 19.6 at the end of this chapter for more.
Quit GraphPaper.
18.191.202.240