TTools (2.2)
by Thomas Burkholder, NeXT Developer Support Team

See the section entitled "Changes Since 2.1" if you are running EOF PR2!!!
Valid for 3.1 and later.
Not valid for 3.0.

Goals
1.  To provide an educational example of advanced palette-building techniques.
2.  To address issues associated with managing a large palette project.
3.  To push the envelope of IB tools.



Overview

This MiniExample is an advanced IB Palette primer.  The prerequisite knowledge for this is a good understanding of Interface Builder and Objective C including knowledge of the Protocol and Category language elements.  The palette illustrates:

How to palettize a simple view, window, and object.
How to provide your palettized view or object with an inspector in IB.
How to write a custom connection inspector and custom connector objects for your palettized object.
How to access your superclass's inspector.
How to effectively palettize Matrix without restricting its prototype cell class.
How to palettize an object that uses the target/action connection paradigm in IB.
How to browse the class hierarchy.
How to implement a complete object editor.
How to generate a library from your palette project so that applications can link in the palette code.
How to automatically generate a library .h file, and precompile it.
How to bind to instance variables that are not of type id (hyper advanced).
How to add menu-driven functionality to IB
How to share code among different palettes by using common bundles
How to provide a new menu item with a custom, accessible manager target (similar to the Font menu/Font Manager paradigm).
How to provide a correct control/cell combination that can be expanded into a matrix.
How to provide multiple targets of the same object

Topics Of Interest

How To Get Started::

First, drag the entire TTools directory into some known location within your home directory. Open the PB.project, change the target to "install", and click build.  Note that this will create a ~/Library/Palettes, if you don't already have one; in that it will put the palette, a Documentation/TTools directory, the libTTools.a library, and a Headers/TTools directory. Once you've done this, you can change the target to "clean", and do a build to regain the space used by the object files during the build.  When you're ready to use the palette, just double-click on it in your ~/Library/Palettes directory.  For a quick look at what the palette can do, take a look at the files in the Examples directory.  Any questions?  Take a look at the HOWTO document in the Examples directory, which outlines how each example was built.  Try reading the relevant parts of this document, the QA.rtf document, and any relevant parts of the class documentation you'll find either in the project directory under Documentation/TTools or in your ~/Library/Palettes/Documentation/TTools directories.  If you're still stuck, drop me a line at thomas@next.com and I'll see if I can make it a bit clearer.  A lot of this stuff is really advanced; if you're a relative neophyte, your best route to figuring it all out will be to try to learn about the objects in the order that they're presented in this document.  And unless you think you really understand IB well, don't try to dissect TBinderList at all.

Timer:

Timer is intended to be a non-trivial palettized object example.  An instance of the Timer class calls an action in it's target at specified intervals.  In addition, the Timer instance stores a value, so that it can keep track of some state.  Hooking up the -takeIntValuFrom: method of a TextField object to the action of a Timer instance will allow the TextField to display the timer's current value, for example.  In addition, because the timer's value can increment by an amount independent of it's period, and has the ability to "wrap" at a certain value, it can be used as a "clock" in many situations.  Timer's action methods are fairly simple - one can tell a timer to start:, stop:, pause:, and resume:.  It is possible, for instance, to connect three Timer instances to three TextFields, and have a fairly accurate real-time clock.

SwitchView:

This is probably the most challenging and interesting of all the palettized objects provided. The SwitchView class is intended to allow simple view-swapping to occur, such as one often wants to do when implementing an inspector paradigm.  The logical process to follow in IB in such a case is to connect all the views that you want to show to outlets of the same managing view, which will display a different view according to some stimulus.  For example, InterfaceBuilder's inspector panel operates in such a fashion - the PopUpList at the top of the inspector is the control that causes several different views to appear in the box beneath it.

The major problem with implementing this is that your managing view only has a certain number of outlets to connect to - really what you want is for your managing view to keep a list of views that can be variable in number.  However, IB contains no connection paradigm to allow this.  Enter the custom Connection inspector, something the writers of IB thoughtfully provided to allow the palette builder to customize the way in which custom objects are connected to each other.  In this case, we implement a connection inspector that allows the user to connect any number of views to our SwitchView instance.  This is fairly simple programmatically, but is not entirely obvious from the documentation.

The method by which the connection inspector is supplied is very similar to the way that standard attributes inspectors are supplied - via a "getConnectInspectorClassName" method and associated module.  The connection inspector makes heavy use of various calls to the IBDocument-protocol-conforming object acquired through the [NXApp activeDocument] call, and also manages a number of IBConnector-protocol-conforming objects (SVConnector instances).

Another area of interest in the SwitchView class is it's callback ability.  The control which swaps views in and out of it's content area needs to be connected to the "takeViewNumberFrom:" method in SwitchView.  When this method is called in a SwitchView instance, a callback to the sender is done.  If the SwitchView instance has been left in "automatic" mode, it will figure out what selector it should use to call in it's sender in order to determine what view to swap in.  If the sender is a matrix with more rows than columns, for instance, the SwitchView instance will call "-selectedRow".  If the sender is a slider, it will call "-intValue".  As a last resort, if the sender does not fit into any other category, the SwitchView instance will use the "-tag" method to call back for the value.  If the SwitchView instance is in "manual" mode, then the method name to be called will have been set in IB, via the SwitchView's attribute inspector.  This makes SwitchView very general in nature, such that any object that has a no-arguments method that returns an integer can be used as the control for the SwitchView.

Because SwitchView is a subclass of Box, it has all of the Box functionality.  However, it has it's own inspector, which over-rides the Box inspector.  Rather than re-implement the Box inspector, however, you can, through a neat, four-line trick in the -getInspectorClassName method, have your cake and eat it to:

- (const char *)getInspectorClassName
{
NXEvent *ev = [NXApp currentEvent];

if (ev->flags & NX_ALTERNATEMASK)
return [super getInspectorClassName];
else
return "SwitchViewInspector";
}

The above is the method in it's entirety.  It allows you to use the superclass inspector by alt-clicking the object you want to inspect.  This is a big time-saver!

WhizzyInfo:

This is a short example showing how to use two of the object provided, the Timer object and the SwitchView object, for simple animation purposes, such as is often found in application info panels.  To observe it's functionality, simply open the nib file, and use the test-interface mode.

In this example a Timer object is hooked up to a TAList, which is connected to two SwitchView objects.  One SwitchView is connected to several buttons, each of which shows a person's picture.  The other shows the corresponding person's name under the picture.  The Timer instance is configured to start it's value at 0, increment by 1, and wrap at the value 4.  The timer's target is connected up to the TAList instance, which sends the -takeIntValueFrom method to both SwitchViews.  A slider connected to the Timer allows one to increase and decrease the animation speed.  Lastly, two buttons are provided to start and stop the Timer.



Ranker:

Ranker is a matrix subclass whose added functionality is similar to that of "NiftyMatrix" (from the ScrollDoodScroll MiniExample.)  The main teaching purpose of the palettized Ranker class, however, is to solve the problem of subclassing Matrix in IB.  The problem is that there is no way to tell IB that you want your subclass of Matrix to be used when an alt-drag is done on a control.  IB will always use the vanilla Matrix class.  Often, palette builders will provide a palette containing many instances of their matrix each populated with a different cell class - a matrix of SliderCells, a matrix of TextFieldCells, a matrix of ButtonCells, and so forth.  This not only takes up a lot of room on a palette, but it doesn't allow new cells (provided in other palettes) to be used with the new Matrix subclass.

To solve this, Ranker's IB code asks the user for a Cell class when the Ranker is first dropped onto a window.  It does this by displaying a class hierarchy (rooted at the ActionCell class) in a panel, and allowing the user to select the desired prototype cell. After the selection has been made, Ranker will assign it's prototype to be an instance of the selected class, and will instantiate itself upon the window that it was dragged onto. Because the Ranker asks for it's prototype at drag-time, there need only be one instance of Ranker on the palette, and because the class-hierarchy-browser that the user selects cell classes from is dynamic, it is able to use cell classes that are dynamically loaded from other palettes.

List:

Basic information about the List class can be found in the on-line documentation.  The List object available in the TToolsPalette is in fact the exact same object.  All that's been added is the ability to set up a list in IB to contain objects of your choice.  To use the List object, drag the instance from the palette into your objects window; double-click on the newly instantiated object to get it's inspector.  Select the class of object you'd like to add to the list in the top browser, and then click the add button to add a new instance of that object. Notice that the inspector reflects what you have selected in your editor - you can add a new Timer instance, for example, and be able to inspect it as well.  You'll also be able to cut, copy, and paste objects within the editor and between the editor and the rest of Interface Builder as well.  Notice that you can create a new list as an element of your list, double click on it, and get a sub-editor for it, providing a potentially infinite level of detail for lists of stored objects.  IBEditor functionality is extremely powerful, but requires a fairly thorough knowledge of IB's inner workings; hopefully the ListEditor example will be useful.

SortedList:

This is a subclass of List that inserts elements in sorted order.  To do this, the addObject: method is over-ridden, so that a binary insertion may be done.  Since the method of comparing an element in the list and the candidate element is entirely dependant on what objects are being stored in the list, the sorted list asks the object connected to the agent outlet, using the compare:with:sender: method in the SortedListAgent protocol.  These "agent" objects are meant to have object-specific information about how to compare and display particular objects, among other things.  Several sample agents are provided, including a ClassAgent (used for class browsers) and a String Agent (used for a custom string object, also included).  In addition to providing sorted insertion, SortedList is meant to act as a browser's delegate; in this way, using the List editor, one can set up a SortedList in IB, add the desired objects, change each object with it's inspector, attach the list to the appropriate agent (StringAgent, for instance, is asked "displayStringFor:anObject sender:sender", for which it does a : "return [anObject stringValue]") and using Test Interface mode, immediately view the resulting browser/list combination.  This tends to eliminate a lot of code.

In addition to providing this functionality on the palette itself, all of the various inspectors in TTools that use browsers to display information use the SortedList or SortedStorage classes - examples of how to use the Browser/SortedList/Agent combinations are the palette inspectors and editors themselves!  To understand how to correctly use the SortedList, StringAgent, and ClassAgent objects, see the ListBrowsing example.

TBinderList:

Or "Toward graphical programming".  Users of TTools should be warned up front; TBinderList is extremely experimental and potentially unsafe to use.  Moreover, it contains a high level of abstraction, and is currently under-documented.  It is an attempt at binding an object's instance variables of any type to user-interface objects.  What does this mean to the IB user?  The net result is that the user will be able to make, for instance, a TextField, display the contents of an instance variable in a data-bearing object.  The converse is also true; often, we'll want the user to be able to enter a string in a TextField, hit enter, and have an instance variable (probably a char *) in some object to have changed as a result.  All of this is accomplished through the use of the TBinderList, TBinder, and lots of IB-inspection code that allows it all to be done in IB and saved into a nib.  Users of TTools should note that it is meant as an advanced educational tool, and not as a product.  There are probably some bugs (no IB-crashers, I think...), and some rough edges.  I'll try to include an example of how to do this, but I expect documentation to be minimal for this release.  A thorough understanding of the rest of the palette code is necessary before attempting to understand what's happening in this part of the palette.

Other Classes and Categories:

A category of object, called ClassAdditions, is used to provide information about classes and method selectors not readily available.  For those interested in burrowing into the maze of the run-time, this category may be very useful.  For dedicated run-time fiends, the TBinder and SelPairAgent objects should provide some new tricks.

Dependent Palettes

Often you'll want to write several palettes that use the same class.  Link that class in to more than one palette, and you will be unable to load the second one into IB (because it contains a class that already exists).  The solution is to put your common classes into a bundle, and load the bundle into IB before loading the dependent palettes.  This is done in TTools by extending IB's functionality to add the capability to load bundles.  When TTools is loaded, a menu item, Tools/Bundles is added to IB's main menu.  The target of this menu item is set to be a custom object called BundleLoader, which handles the management of a list of bundles.  Currently, the only functionality available to the BundleLoader is the ability to load new bundles - unloading bundles can only be done on a stack basis, and since each palette is a bundle, it is unfeasible to add bundle - unloading capability at this time.

The BundleLoader class saves the bundles that you specify in a file ~/.NeXT/bundledefaults.ttools, so that palettes that depend on bundles should load properly, so long as the TToolsPalette is loaded first.  In addition, an alert panel will appear if a bundle fails to load.

Classes in these loaded bundles will not immediately show up in the class browser; however, any dependant palette can include required class names in the ExportClasses property of the palette.table file, which will cause the class to be identified within the class hierarchy in IB.

CircularSlider

Many UI objects come in the form of a Control subclass that manages a cell; this subproject shows you how to implement such a control (a CircularSlider), in such a way that it can be alt-dragged into a matrix of CircularCells, which can be inspected with the ordinary control's attributes inspector.  Appreciation and kudos to Toby Paterson for providing this useful subproject.

TAList

Short for "Target/Action List".  This is yet another custom-connection project; make an instance of this the target of your UI object, choose sendAction: as the action, and connect the list to as many targets and actions as you like.  This overcomes the problem that some people have of needing the target/action paradigm to work with more than one target.  For run-time hounds, this subproject contains some minor parsing of method descriptions that is perhaps cleaner and in a less cluttered context than the TBinderList example.

Menu Items and Accompanying Objects

Occasionally, you'll want to implement something similar to the Font menu/Font manager paradigm, where a menu item may be provided by a palette that causes an object to appear in the Objects window when the menu item is first added to the application's main menu. TTools provides a menu item entitled "Show Menus", which when added to the main menu causes a "ShowMenus" object to be added to the Objects window.  In addition, an automatic connection is made between the menucell added and the -showMenus: method in the ShowMenus object, by means of a custom IBConnector object.  There are a number of tricks involved; first, it is necessary to subclass MenuCell in order to get an entry point after the new cell is dropped; the "Show Menus" needn't be anything more complicated than an ordinary MenuCell.  Accordingly, we use the -finishUnarchiving method in the ShowMenuCell class to return an instance of MenuCell, create, add and display a ShowMenus object, and to create and instantiate a connector object between them.

Makefile stuff:

The TTools product automatically generates it's own libTTools.a library, TTools.h header file, and TTools.p precompiled header file, through the use of some Makefile hacking.  The principle location of the added rules is in the main Makefile.preamble and Makefile.postamble files.



Changes since TTools 2.1:

Fixed bug in CircularSlider that caused any Slider's allocated after the palette was loaded to use a CircularSliderCell instead of a SliderCell.  Added TAList subproject.  Fixed WhizzyInfo example, updated it to use TAList.  Fixed nasty connection inspector bugs. Had to change SortedList to support subdirectories that are SortedStorage objects; as a result, we generate all sorts of warnings because the compiler doesn't like it when you multiply define (and then include) identical methods with different type arguments.  C'est la vie; this is a compiler problem, not an implementation bug.  Also, ClassAdditions has been fiddled with to make TTools2.2 work properly with EOF's InterfaceBuilder.  Note that you must run the fix script in the "BetaEOFFix" directory before building if your are running the PR2 release of EOF distributed at NEXTSTEP EXPO.  If you have run this script, but wish to use an older or newer version of IB, run the undofix script in the UndoBetaEOFFix directory, and re-build the palette.  See the Q&A document about why these fixes are necessary.

Changes since TTools 2.0:

Fixed some inspector bugs, added bundle-loading subproject, fixed SwitchView initial-view display problem in Test Interface mode, added ShowMenus subproject, minor documentation modifications, add CircularSlider subproject.

Changes since TTools 1.0:

Basically everything.  The Ranker and Timer objects are the same, the AlertPanel object I threw away, and everything else is new.  Timer changed a bit; versioning was used, so old instances of Timer should be fine.  Also, this version works only in 3.1, not in 3.0.  It requires minimal Makefile hackery to make it work under 3.0, but I decided the project was large and confusing enough as it is.

Kudos:

Thanks to:
Mike Gobbi and others for coming up with the alt-click inspector trick.
Alan Ethanson who helped me with the TTools.h generation sh-code.
Jason Jobe for coming up with a sensible workaround for the SwitchView awakeFromNib.
Toby Paterson for CircularSlider, and for helping debug the ShowMenus functionality.
Brian Griswold for helping beta-test 2.2.
Robert Nicholson for suggesting the TAList.

Disclaimer:
You may freely copy, distribute, and reuse the code in this example.  NeXT disclaims any warranty of any kind, expressed or implied, as to its fitness for any particular use.