This is BoxViewSwitcher.m in view mode; [Download] [Up]
/*--------------------------------------------------------------------------- BoxViewSwitcher.m -- switch between box views using a popup list (button) Copyright (c) 1990 Doug Brenner This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA, or send electronic mail to the the author. Implementation for the BoxViewSwitcher class. BoxViewSwitcher allows you to switch between a group of views using a popup button. (Something like the popup button on the Inspector panel of Interface Builder.) *** Using BoxViewSwitcher This is the easy part because you do everything in Interface Builder! Here is a step-by-step guide meant for people familiar with Interface Builder. 1. Setup: Launch IB, create a new application, create a project, and save. 2. Main window setup: You should already have one window from when you created the new application. This will be the main window with which people will interacte. On this window create one button and one box view. Within the box view put any controls that you want and give the box view a name. This box view will be the "default" box view that people will see when your application is launched. 3. Alternate window setup: Create another window. (I'll refer to this window as the "alternate" window and the window from step 2 as the "main" window.) Select and copy the box view from the main window and paste it onto this alternate window. Give this newly pasted box view a different name and update it's controls, etc., to your needs. If you want more than two alternate views, repeat the copy/paste/udpate process until you have all you need. BoxViewSwitcher will locate all the box views at the "top level" of the alternate window. (In other words, it doesn't descend the view hierarchy looking for box views within views.) 4. Bring in BoxViewSwitcher: Open the Classes panel and create a subclass of Object. (Make sure it's a subclass of object.) Rename this subclass as BoxViewSwitcher. Drag copies of BoxViewSwitcher.h and BoxViewSwitcher.m into the same folder as this Interface Builder project. Choose Parse from the Classes popdown button. (Click "Replace" since you know the outlets, etc., are different.) Choose Instantiate from the Classes popdown button. You should see a new instance (probably named BoxViewSwitcherInstance) in the objects window. Add BoxViewSwitcher.[hm] to the Files section of the Project window. 5. Attach the BoxViewSwitcher instance outlets: There are only three outlets that you need connect: controlButton, boxView, and boxViewWindow. (Ignore boxViewList and popup.) Connect the BoxViewSwitcher instance outlets as follows: Outlet Name Connect to: ------------- ------------------------------------------------- controlButton button on the main window boxView box view on the main window boxViewWindow alternate window of box views (the entire window) (If you want the instantiated BoxViewSwitcher to send delegate methods [see setDelegate:], connect some other custom object to the delegate outlet.) 6. Make everything: Save and then make the application. *** Hints You can connect the controls in any of the box views to your own custom objects or other controls. These connections will be maintained even after the box views "move" from one window to the other. In general you want all the box views to be the same size and to have unique names. The alternate box view window should have "Visible at Launch Time" (in the Inspector panel) unchecked. You can also create menu items to select between the various box views. Just name the menu item the same as the desired box view title and connect the menu item to BoxViewSwitcher's selectBoxView: method. All three outlets (controlButton, boxView, boxViewWindow) must be connected, and connected to the proper classes or nothing will happen. BoxViewSwitcher assumes the alternate window is for its use only. After it collects all the box views, the alternate window is freed. (I.e., if you need a place for other things, don't put them on the alternate window.) *** Other controls and the selectBoxView: method BoxViewSwitcher only knows about one control, controlButton. This means only controlButton is always updated to reflect which box view is currently displayed. Why is this important? Let's say you attach a radio button matrix to the selectBoxView: method; things will look fine at first. The matrix will correctly select between the various box views based on which button is selected. The problem shows up when you use the controlButton/popup combination to select a box view. Because BoxViewSwitcher doesn't know about your radio button matrix, it isn't updated after a new selection is made. That is the problem. To deal with this situation, the delegate method boxViewDidSwitch:to: has been supplied. Use it to update other controls you might use to select between box views. To solve the radio button problem given above, you might try something like this in your delegate: #import <appkit/Button.h> #import <appkit/Matrix.h> #import <appkit/Box.h> #import <objc/List.h> - boxViewDidSwitch:sender { id cells, cell; int i; const char *newTitle; cells = [radioButtonMatrix cellList]; for (i = [cells count]-1; i >= 0; i--) { cell = [cells objectAt:i]; newTitle = [[sender boxView] title]; if (strcmp ([cell title], newTitle) == 0) { [radioButtonMatrix selectCell:cell]; break; } } return self; } return self; And then again, you may find the above code silly. Use it as you see fit. Doug Brenner <dbrenner@umaxc.weeg.uiowa.edu> $Header: /rpruess/apps/Remotes3.0/RCS/BoxViewSwitcher.m,v 3.0 92/09/23 22:16:54 rpruess Exp $ ----------------------------------------------------------------------------- $Log: BoxViewSwitcher.m,v $ Revision 3.0 92/09/23 22:16:54 rpruess Checked in to RCS to get the revision number updated to 3.0. Revision 2.1 92/09/23 21:29:31 rpruess Updated code for NeXT System Release 3.0. Revision 2.0 91/01/22 15:24:01 rpruess Incorporated into Remotes at Release 2.0. Revision 2.0 91/01/22 13:13:36 rpruess Initial production release of Remotes-2.0. Revision 2.0 90/06/22 18:28:39 dbrenner Comment changes; added delegate notification in selectBoxViewTitle: (this required a new instance variable, delegate, and method (setDelegate:); added methods to return most instance variables (boxViewList, boxView, popup, controlButton); moved code to force the controlButton title from replaceBoxViewWith: to selectBoxViewTitle: (this seems more reasonable); added archive methods (read and write); added class method initialize to set the class version number. Revision 1.3 90/06/05 17:06:08 dbrenner Minor comment changes for automatic documentation generation. Revision 1.2 90/05/26 14:52:16 dbrenner Many more comments; added code to update the controlButton title after a switch has been made (in case things were done via another control). Revision 1.1 90/05/18 21:12:44 dbrenner Initial revision -----------------------------------------------------------------------------*/ #import <appkit/Button.h> #import <appkit/Window.h> #import <appkit/Box.h> #import <appkit/PopUpList.h> #import <appkit/View.h> #import <objc/List.h> #include <string.h> #import "BoxViewSwitcher.h" @implementation BoxViewSwitcher /*--------------------------------------------------------------------------- The methods setControlButton:, setBoxViewWindow:, and setBoxView: set the outlets controlButton, boxViewWindow, and boxView, respectively. (Connect these outlets via Interface Builder.) In all cases, if the id received is of the proper type, its value is stored; otherwise, the method just returns. After storing the id, the setup method is called. Since you cannot (and should not) depend on the order in which ids are supplied, setup is called every time but doesn't do anything until all three outlets are initialized. -----------------------------------------------------------------------------*/ - setControlButton: anObject { if ([anObject isKindOf:[Button class]]) { controlButton = anObject; [self setup]; } return self; } - setBoxViewWindow: anObject { if ([anObject isKindOf:[Window class]]) { boxViewWindow = anObject; [self setup]; } return self; } - setBoxView: anObject { if ([anObject isKindOf:[Box class]]) { boxView = anObject; [self setup]; } return self; } /*--------------------------------------------------------------------------- This method sets the BoxViewSwitcher's delegate to anObject. After a switch is made, the message boxViewDidSwitchTo: is sent to the delegate with the title of the new box view. This message is informative only; the return value is ignored. ---------------------------------------------------------------------------*/ - setDelegate: anObject { delegate = anObject; return self;} /*--------------------------------------------------------------------------- These methods return instance variable information. ---------------------------------------------------------------------------*/ - boxViewList { return boxViewList; } - popup { return popup; } - boxView { return boxView; } - controlButton { return controlButton; } /*--------------------------------------------------------------------------- This method sets everything up. In particular it does the following: Creates a popup list of possible box view titles. (The titles are from boxView's title and from the title of every box view in the contentView of boxViewWindow.) Populates boxViewList with the ids of the possible box views. (This allows easy searching and a provides a storage locate for the views after boxViewWindow is freed. [This list includes the original boxView.]) Sets the popup list target/action and connects the popup to controlButton. (The popup list sends selectBoxView: to BoxViewSwitcher when something is selected, hence we set it's target and action appropriately. [These could possibly be over-ridden if controlButton already has a target and action. See the discussion under NXAttachPopUpList in the PopUpList specification.]) Frees boxViewWindow after removing the box views from its view hierarchy. (This reduces memory requirements somewhat. It is assumed that boxViewWindow is nothing more than a convient place for you to design and store the alternate box views. Once collected into boxViewList, the window is useless to BoxViewSwitcher.) -----------------------------------------------------------------------------*/ - setup { int i; id views; /* list of subviews in boxViewWindow */ id oneView; /* one view from the above list */ /* ---------- we need all three outlets before we can setup */ if (! (controlButton && boxViewWindow && boxView)) return self; /* ---------- setup popup list and populate boxViewList */ popup = [[PopUpList alloc] init]; /* popup list for controlButton */ boxViewList = [[List alloc] init]; /* we'll save the box views here */ [popup addItem:[boxView title]]; [boxViewList addObject:boxView]; views = [[boxViewWindow contentView] subviews]; for (i = [views count]-1; i >= 0; i--) { oneView = [views objectAt:i]; if ([oneView isKindOf:[Box class]]) { [popup addItem:[oneView title]]; [boxViewList addObject:oneView]; } } [[popup setTarget:self] setAction:@selector(selectBoxView:)]; /* ---------- setup controlButton */ [controlButton setTitle:[boxView title]]; NXAttachPopUpList (controlButton, popup); /* ---------- dettach box views from boxViewWindow and free the window */ views = [[boxViewWindow contentView] subviews]; for (i = [views count]-1; i >= 0; i--) [[views objectAt:i] removeFromSuperview]; [boxViewWindow free]; boxViewWindow = nil; /* to prevent problems */ return self; } /*--------------------------------------------------------------------------- This is the primary interface for controls that have titles. (Anything that uses ButtonCell is a good bet, e.g., Menu, PopUpList.) This method simply queries the sender for its title (via [[sender selectedCell] title]) and uses selectBoxViewTitle: to do the real work. If BoxViewSwitch did the setup, the method is called by the popup list that BoxViewSwitch created during the setup method. -----------------------------------------------------------------------------*/ - selectBoxView: sender { [self selectBoxViewTitle:[[sender selectedCell] title]]; return self; } /*--------------------------------------------------------------------------- This method searches boxViewList for a box view with the same title as the one passed in. If a match is found, replaceBoxViewWith: is used to replace boxView with the found view. The controlButton title is normally updated by the popup list, but this method also updates it to handle the case where some other control has made the selection. (See the opening comments for more information about using other controls with BoxViewSwitcher.) After the switch, the delegate is informed about the change. -----------------------------------------------------------------------------*/ - selectBoxViewTitle:(const char *) title { int i; id thisView; if (! boxViewList || ! title) return self; for (i = [boxViewList count]-1; i >= 0; i--) { thisView = [boxViewList objectAt:i]; if (strcmp ([thisView title], title) == 0) { [self replaceBoxViewWith:thisView]; [controlButton setTitle:[boxView title]]; /* notify the delegate about the switch, if it wants to know */ if ([delegate respondsTo:@selector(boxViewDidSwitch:)] == YES) [delegate boxViewDidSwitch:self]; break; } } return self; } /*--------------------------------------------------------------------------- This method replaces boxView with aView, forces aView into the same frame as boxView, and asks the superview to redisplay. The frame of aView must be changed because the various view frames are most likely different. Keep in mind that the alternate views start out on another window and have unknown frames. It is also possible that boxView's window may have resized. (It is this latter resizing issue that really matters since the alternate box view frames could have been forced during setup.) It is assumed that the current boxView always has the proper frame, hence, its frame is forced upon aView. And a final special note for those of you using View's replaceSubview:with: method. Don't replace a view with itself. This is a degenerate case that caused me a bit of confusion. The following code, or its equivalent, [aSuperView replaceSubview:aView with:aView] results in aView being removed from aSuperView's hierarchy. (At least it happened at the time of this writing.) Not necessarily what you might want. -----------------------------------------------------------------------------*/ - replaceBoxViewWith: aView { NXRect r; if (! boxView || ! aView) return self; if (boxView == aView || [aView isKindOf:[View class]] == NO) return self; [boxView getFrame:&r]; [aView setFrame:&r]; [[boxView superview] replaceSubview:boxView with:aView]; [[aView superview] display]; boxView = aView; return self; } /*--------------------------------------------------------------------------- Frees the popup list, all the views in boxViewList, and boxViewList. The fact that all the views in boxViewList are freed seems to imply that BoxViewSwitcher "owns" them. (This could be considered wrong.) -----------------------------------------------------------------------------*/ - free { [popup free]; [boxViewList freeObjects]; [boxViewList free]; [super free]; return self; } /*--------------------------------------------------------------------------- This method write the BoxViewSwitcher instance to the typed stream. ---------------------------------------------------------------------------*/ - write:(NXTypedStream *) stream { [super write:stream]; /* ---------- version 1 variables */ NXWriteTypes (stream, "@@@", controlButton, boxViewWindow, boxView); NXWriteTypes (stream, "@@", popup, boxViewList); /* ---------- version 2 variables */ NXWriteObjectReference (stream, delegate); return self; } /*--------------------------------------------------------------------------- This method reads the BoxViewSwitcher instance from the typed stream. ---------------------------------------------------------------------------*/ - read: (NXTypedStream *) stream { int streamVer; /* stream class version */ [super read:stream]; streamVer = NXTypedStreamClassVersion (stream, "BoxViewSwitcher"); /* ---------- version 1 variables */ NXReadTypes (stream, "@@@", controlButton, boxViewWindow, boxView); NXReadTypes (stream, "@@", popup, boxViewList); /* ---------- version 2 variables */ if (streamVer >= 2) NXReadTypes (stream, "@", delegate); return self; } /*--------------------------------------------------------------------------- This class method sets the version number of the User class. The current version number is 2; version number 1 did not implement this method. This method should not be called directly. ---------------------------------------------------------------------------*/ + initialize { [super initialize]; [BoxViewSwitcher setVersion:2]; return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.