Apple Enterprise - NeXTanswers Support Archive
Enterprise
[Index]
[Download]


Search NeXTanswers for:

NeXTSTEP AppKit Miscellaneous Q&A



Creation Date: August 19, 1998
Keywords:
NeXTSTEP

Disclaimer

This document pertains to the NeXTSTEP operating system, which is no longer a supported product of Apple Computer. This information is provided only as a convenience to our customers who have not yet upgraded their systems, and may not apply to OPENSTEP, WebObjects, or any other product of Apple Enterprise Software. Some questions in this Q&A document may not apply to version 3.3 or to any other specific version of NeXTSTEP.

Questions

Q: Where should I install a new service (regular, filter or printer)?

A: Release 3 adds new flexibility in providing services. The format of the service definition remains the same, but new places are searched for services.

1) Your app wrapper can contain a service description file named services. (e.g. ~/Apps/MyApp.app/services) To include it in your ProjectBuilder project, just drop it into the Other Resources briefcase in the files viewer of ProjectBuilder.

This is the preferred way to provide a service. Even lightweight services providers should have a small NEXTSTEP interface which at least allows the user to get information, help, and possibly set preferences for the service.

2) The service registration process also recognizes files and directories with a .service extension which are installed in one of the newly established ~/Library/Services or /LocalLibrary/Services directories. A file with a .service extension must be a valid service description file. A .service directory must contain a valid service description file named services. (e.g. the service description file would be named something like ~/Library/Services/myOwn.service or /LocalLibrary/Services/ALocalOne.service/services.) This is the least preferred way because it doesn't allow the for a NEXTSTEP interface to the service. Also, the Workspace Manager only registers these services when the user logs in, it doesn't automatically update the service cache during a working session.

The proper format for a service description file is described in the documentation. Here is an example for a fictitious application called MyApp. It owns the .mine extension and has a mine-ascii filter which is installed in the MyApp.app directory. Here is the filter service description file which is also in the MyApp.app directory:

Filter:
Port: NXUNIXSTDIO
Send Type: NXTypedFilenamePboardType:mine
Return Type: NXAsciiPboardType
Executable: mine-ascii

Note that you don't need to specify the full pathname of the executable if it is in the same directory as the service description file.

For backward compatibility, the services registration process continues to look for the __services section of the __ICON segment in Mach-O files that are installed in your application path.

Note that if you convert a 2.0 IB.proj file file to a 3.0 PB.project file you will have probably want to make two changes so that your services file will conform to the new preferred protocol for installing services:

* Delete the line in your Makefile.preamble which builds the services file into your Mach-O. It looks something like "LDFLAGS=-sectcreate __ICON __services services".
* Make sure that the service specification file is called services and move it from the Supporting Files briefcase to the Other Resources briefcase. This will ensure that it gets copied into the app wrapper when you install.


Q: How do I register my service dynamically while I am debugging it?

A: The Workspace Manager dynamically registers or reregisters any services described in an application wrapper that is dragged into one of the directories in your application path (e.g. ~/Apps, /LocalApps). Note that the Workspace does not notice if you just drop a new services file into your app wrapper, you must install the whole application.

If you add the service using some other method, you can rebuild the services cache in one of two ways.

1) You can call the function NXUpdateDynamicServices(void) to register services which you add programmatically. Note that this function spawns a task in parallel that may take a short time to complete, so do not expect the services to be immediately available.

2) You can rebuild the services cache manually. This is useful if you are editing the services file in place and don't want to install a new copy of the application each time you make a change, or if you install a .service in one of the Services directories, or if you install the service using ProjectBuilder or the command line interface to UNIX. To rebuild the services cache you can run the make_services program from a Terminal window manually or log in and out so the Workspace will rebuild the services cache.


Q: How can I tell which services have been installed?

A: For regular services that are installed properly in an application wrapper, you can simply bring the Services menu of any application on screen. It rebuilds itself if needed before it is redisplayed. You no longer have to quit an application to take advantage of a new service. But remember to be patient, it takes a short time to reregister all of the services. So you might toggle the Services menu of an application that can take advantage of the particular data type until your new service appears.

If you are installing a filter service, it's difficult to know if it's really installed properly because there are no user interface elements which display it. But you can use the make_services program to explicitly update all of the services. It has a debugging mode which prints out the service information as it is processed. Run it from a Terminal window:

make_services debug

Alternately, this bit of codes prints the list of services that are available for a given filename:

- printTypesForFile:(const char *)filename
{
const NXAtom *listOfTypes;
static Pasteboard *pb = nil;

if (pb) pb = [pb free];
pb = [Pasteboard newByFilteringFile:filename];
if (!(listOfTypes = [pb types]) || !listOfTypes[0])
return (const NXAtom *) 0;

[self clearText]; // I've got a little method that just clears the
// text object used to display the debugging info
for (; listOfTypes && *listOfTypes; listOfTypes++) {
[self appendText: *listOfTypes]; // and a method to append to it
[self appendText:"\n"];
}
return self;
}


Q: What if there is more than one filter service provider for a given set of send and receive types?

A: All services are installed even if duplicated, but there is no guaranteed order of registration. The first one encountered with the proper send and return types is the one that happens to be used.


Q: Any known bugs?

A: #24667--registering for more services in the middle of handling a service is dicey.

#29821-- (fixed in 3.1) your filter service cannot expect to be launched with a full pathname if it uses the special NXUNIXSTDIO port. For example, if a mine-ascii filter is registered for the .mine extension, it will be spawned like so:

execl("/LocalApps/myApp.app/mine-ascii", "mine-ascii", "somefile.mine")

Since the second argument doesn't include the full path, mine-ascii can't check its name from within the program to see where it was run from. But you shouldn't need to do this anyway.

#29905--(fixed in 3.1) the documentation describes new features for the Workspace which don't currently work, this document documents the mechanisms that work. Here are the bugs for your info:

* The Workspace doesn't register .service files or directories that are installed in your application path. It only understands about services files within app wrappers in your application path, and .services in the Services directories.
* The Workspace doesn't automatically update your services if you drop a new .service into one of the Services directories. You have to log out/in to get it to register if you install it there.
* The Workspace doesn't automatically update when you drop a new services file into your app wrapper, you have to drop the whole application into the Apps directory.


Q: Where else can I get information about Services, especially about the format for a service description file?

A: You can read the release notes, and the services chapter of the concepts manual.

/NextLibrary/Documentation/NextDev/Concepts/Services.rtf
/NextLibrary/Documentation/NextDev/ReleaseNotes/Appkit.rtf


Q: My application provides a Service, but I want it to behave a little differently than usual. Specifically, I don't want the application requesting the Service to deactivate, and I don't want my application to become the active application when it receives the data. This way, users can send data to this service without being thrown out of whatever application they're in. Is this possible?

A: Yes! Service providers can dictate whether or not they're activated when providing a Service, as well as whether or not the requesting application deactivates, through the following two options in the Services message description:

Activate Provider: <YES or NO>
Deactivate Requestor: <YES or NO>

Specifying ``NO'' in the Activate Provider field instructs the Services machinery to launch the Service provider (if not already launched) but not make it the active application. Specifying ``NO'' in the Deactivate Requestor field instructs the Services machinery not to deactivate the application requesting the Service.

Omitting one of these two fields is equivalent to specifying ``YES.''

N.B. Be careful how you use this feature. Technically, you can have any combination of provider and requester activation but it would violate user interface guidelines. It really only makes sense to set these both to either YES or NO, but not a combination of the two.


Q: I've written an application which instantiates a Listener subclass and checks in its listenPort under a well-known name so that other applications can find it using NXPortNameLookup(). In Release 2.x, applications on a remote hosts were able to look up this port without problems, but when I run my application under Release 3.0, it can't be found. What's changed?

Q: I find that in Release 3.0 my applications which make use of Speaker/Listener messages between two hosts can only do so if Public Window Server is enabled for the host on the Listener side. Why is this so?

A: Prior to Release 3.0, Speaker/Listener messages were not secure between hosts (i.e., it was possible to obtain and send messages to the port of a Listener on another machine). This was possible because the Listener's port was registered with the local Network Name Server under the name passed to Listener's checkInAs: method exactly as specified.

In Release 3.0, the same is true only if Public Window Server is enabled (this is documented in the AppKit Release Notes). If not, the name passed to the checkInAs: method is modified so as to make it unique to the local host before the port is registered. Similarly, NXPortFromName() and NXPortNameLookup() attempt to find registered Listener ports under the requested name and the local modified form of that name. An application which attempts to look up a port on a remote host which has Public Window Server disabled will not be able to find it because that port will be registered under a name unique to that remote host.

It's wise to have this restriction on the application's default Listener and to have Public Window Server disabled for security purposes. If this is so, however, and you instantiate another Listener (or Listener subclass) in your application which will be registered under a name which is different from that of the application itself and must be accessible to remote applications, it can't be registered using Listener's checkInAs: method. The following category of Listener implements an additional method (publicCheckInAs:) which forces the registered name of the Listener's port to be the name requested whether Public Window Server is enabled or not.


#import <appkit/appkit.h>
#import "Controller.h"

#import <servers/netname.h>

@interface Listener(PublicPort)

- (int)publicCheckInAs:(const char *)name;

@end

@implementation Listener(PublicPort)

/*
* This method should be used INSTEAD OF -checkInAs:, not in addition to it.
* Using both may lead to problems later if checked in again under the same
* name.
*/
- (int)publicCheckInAs:(const char *)name
{
kern_return_t result;

if (![self listenPort]) {
/*
* Causes Listener to alloc a port.
*/
[self usePrivatePort];
}

/*
* Now register the port with the Network Name Server.
*/
result = netname_check_in(name_server_port,
(char *)name,[self signaturePort],[self listenPort]);
if (result != NETNAME_SUCCESS)
return result;

/*
* Set my portName to be the registed name.
*/
free(portName);
portName = NXCopyStringBufferFromZone(name, [self zone]);
return 0;
}

@end


Q: How does a server that registers like:

[NXConnection registerRoot:self withName: "myname"];

get notification of a client that has disconnected? The delegate method connection:didConnect: indicates when the client connects, but not when it disconnects. Specifically, I want the server to know when a client application has been killed or crashed.

A: In order to receive invalidation notification you must select the object that is to be notified and register it for invalidation notification. This object then must conform to the NXSenderIsInvalid protocol. For example, one of your server objects (probably the one that called registerRoot:withName:) should conform to the NXSenderIsInvalid protocol. The method you need to implement to conform to this protocol is: -senderIsInvalid:. When the client connection dies, senderIsInvalid: will be called passing that connection.

You will also need to call worryAboutPortInvalidation (NXPort class method)--this spawns a thread that listens for a Port death and will message senderIsInvalid:. The code would look something like:

NXConnection *primary = [NXConnection registerRoot: rootObj withName: "foo"];
[primary setDelegate:self];
[NXPort worryAboutPortInvalidation];
[primary run];

Finally, in your connection:didConnect: delegate method you should call registerForInvalidationNotification:. You should register on the second connection passed to this method.

Note: The above information assumes a non-AppKit program. If you are using the AppKit, this notification mechanism is already in place. Another important distinction is that in the AppKit case, there is not a separate thread, so there is less worrying that needs to be done about multi-threaded access.


Q: My application creates its own documents. How do I make sure that Digital Librarian can index these documents?

A: In Release 3, the indexing code uses the new filter services to convert documents to ASCII before parsing them. Therefore, if you install a filter service which converts your format to ASCII or RTF you will reap the benefits of being able to index your documents, as well as let others filter them for other uses.

See the document on services in 3.0 (../AppKit/installing_new_services.rtf), as well as the Release Notes for the AppKit and the Services chapter of the Concepts manual for more information on registering filter services.

Q: OK, but I also want to be able to include a mini-icon of my document type which the Librarian can display when it lists titles.

A: Unfortunately, you can no longer do this. The decision was made to break compatibility in 3.0 for third party supplied icons in Librarian. There is no way to supply icons for new file types.

Q: What about description filters? In Release 2 I could register a program that created an appropriate description for each file that was indexed. Now Librarian doesn't even display the description of the file anymore.

A: Registering a filter with a return type of IXFileDescriptionPboardType provides you this same functionality. In 3.0, Librarian only displays a description of a file if the index is built with the descriptions. There is no user interface for building an index with a description, but running ixbuild -g from the command line will build an index that stores the descriptions.

Let's say that you have a service for files with a .mine extension. The service might look like this:

myhost> ls ~/Library/Services/Mine.service
mine-ascii* mine-desc* services

where the text of services would be:

Filter:
Port: NXUNIXSTDIO
Send Type: NXTypedFilenamePboardType:mine
Return Type: IXFileDescriptionPboardType
Executable: mine-desc

Filter:
Port: NXUNIXSTDIO
Send Type: NXTypedFilenamePboardType:mine
Return Type: NXAsciiPboardType
Executable: mine-ascii

and the two executables would take one argument, a filename.


Q: My WriteNow, Frame or WordPerfect documents don't index anymore. What's wrong?

A: The services aren't automatically registered by NEXTSTEP, but you can easily register the services yourself.

For WriteNow and Frame documents, place the following text in a file called ThirdPartyFilters.service in ~/Library/Services, or /LocalLibrary/Services.

Filter:
Port: NXUNIXSTDIO
Send Type: NXTypedFilenamePboardType:wn
Return Type: NXRTFPboardType
Executable: /LocalApps/WriteNow.app/wn-rtf

Filter:
Port: NXUNIXSTDIO
Send Type: NXTypedFilenamePboardType:frame
Return Type: NXAsciiPboardType
Executable: /LocalApps/FrameMaker.app/frame-ascii

Make sure that you correct the pathname of the filter if the application is not in /LocalApps.

You can add other filters for document types that have the same problem by adding additional similar paragraphs to the same file for each type.


Q: My application is filling up the console with this message:

Jan 29 14:11:13 foo MyApp[22]: Exception handlers were not properly removed.

What can I do about this?

A: The message is often a report of a harmless unexpected situation, but can indicate an error requiring your attention. This message is printed when something disrupts the expected stack-like use of exception handling domains. If you install Handler B while in the domain of Handler A, you should remove Handler B before removing Handler A. If you remove HandlerA without having removed HandlerB, it reports this condition to the console. For example, using return instead of NX_VALRETURN() from the NX_DURING context of Handler B would not properly remove Handler B, and would cause an error when you try to remove Handler A. This is an example of an error you should take care to avoid.

You'll want to verify that your exception handling domains are nested properly. If you're not directly using exception handling, your application can still generate the error message by mismatched calls to AppKit functionality. For example, lock/unlock Focus, begin/end timer, and begin/end modal session all set and remove exception handlers. If there is a combination of calls that doesn't nest correctly, you'll see this message. Here's an example of the ``incorrect'' situation:

lockFocus sets handler #1
NXBeginTimer() sets handler #2
unlockFocus removes hander #1
NXEndTimer() removes handler #2

By changing this to call NXEndTimer() before unlockFocus , the handlers are removed in the expected stack-based manner, and thus avert the error message. (The message is actually harmless in this case, but still annoying)

This message can also result from a known bug in the SoundView under 2.0 that is fixed in 3.0. It occurs when you click or double-click in the SoundView to make a selection. There's no workaround for this; however, it shouldn't have any effects other than cluttering your console window.


Q: How do you make an application hide when it's auto-launched from the dock?

Q: I'm having trouble getting my application to hide when auto-launched. What's the correct way to make it happen?

A: When your application launches, it should check the NXAutoLaunch default. A ``NO'' value means the application wasn't auto-launched from the Dock, and a ``YES'' value means it was. The application should check this value before creating or displaying any user interface. It should complete its initialization when the user activates it for the first time (the code for detecting and handling this condition should reside within the appDidUnhide: delegate method).

Another approach might be to completely initialize the application and then check the default. If the auto-launch condition were true, it could send itself the hide: message and disappear. The first problem with this approach is that it leads to flashing windows, meaning that if (for example) your application creates a default document or displays a palette when it starts up, the window or palette will appear onscreen, and then quickly disappear when the application hides itself. The second problem is that some timing snarls in the AppKit currently prevent this (sending the hide: message from within appDidInit:) from working.


Q: I want to find my application's icon and put it on a disabled button on my panel, just like the SavePanel and other NeXTstep panels do. How do I go about this?

A: Creating a disabled button with your application's icon is a simple task. You can do everything without leaving InterfaceBuilder! Create a button the size and placement you want. Now open the Inspector for this button. Check the "Disabled" box and enter "app" for the name of icon for this button. Set the Icon Position to be icon only, no title. While in InterfaceBuilder, the button displays the application icon for InterfaceBuilder (the screwdriver), but once your program is compiled and run, the button has your application's icon. If you haven't created a custom icon for your application, the button displays the generic application icon.


Q: I have allocated an instance of NXBitmapImageRep and am reusing the instance in my application with repeated calls to

-initData:pixelsWide:pixelHigh:bitsPerSample:samplesPerPixel:
hasAlpha:isPlanar:colorSpace:bytesPerRow:bitsPerPixel:

each time passing a new chunk of raw image data. However my application is crashing. Is my use of -initData legal?

Q: Once I have allocated an object in the AppKit can I then reinitialize it repeatedly?

A: The init methods should never be called twice on the same alloc'd instance. Some classes allow you to reuse an instance by resetting key values using a setFoo: method. However this is not possible with the NXBitmapImageRep class. In this case, the overhead of the alloc is very small; the required implementation is to free the previous instance and allocate a new one on the fly each time you need to perform an initData.

Q: What are timer events useful for? How are they different from timed entries?

A: These two different event-handling features are often confused because of the similarities of their names. However, they are intended for different uses. A timed entry is used for scheduling regular periodic activities in your application. See ../AppKit/timed_entries.rtf for more information about timed entries. A timer event is used in conjunction with a modal loop when an application must continue to do something even when no user events are being received. Modal loops are used to temporarily circumvent the primary application loop. The loop is triggered by an event such as a mouse down event, and is terminated when a specific event is encountered, such as a mouse up event.

The scroll buttons in the standard NeXT Scroller use a timer event in a modal loop to scroll continuously while the mouse button is held down. When the Scroller receives a mouse down event on a scroll button, it begins to scroll the contents of the view. While the user is simply holding down the mouse button, no events are generated. The application must continue to scroll the view, even though the events have stopped. This is when timer events come into play. Once you start a timer, it will insert timer events into the queue at regular time intervals. These events are ``dummy'' events --the user has not actually done something (moved the mouse, hit a key, let the mouse up, etc) but the event indicates that a certain time interval has passed since the last event. Turn on the timer as the modal loop begins, and turn it off when the modal loop terminates. (If you forget to turn it off, you may suffer performance problems because of the extra event processing!) During the execution of the modal loop, you call getNextEvent: with the appropriate event mask to receive timer events as they are generated and do the desired processing for each one. Here is an example of a modal loop which implements the scrolling behavior described above:

- mouseDown:(NXEvent *) thisEvent
{
int shouldLoop = YES;
int oldMask;
NXTrackingTimer myTimer;
NXEvent *nextEvent, lastEvent;

oldMask = [window addToEventMask:NX_LMOUSEDRAGGEDMASK];
lastEvent = thisEvent;
NXBeginTimer(&myTimer, 0.05, 0.05);

while (shouldLoop) {
nextEvent = [NXApp getNextEvent:(NX_LMOUSEUPMASK
| NX_LMOUSEDRAGGEDMASK
| NX_TIMERMASK)];
switch (nextEvent->type) {
case NX_LMOUSEUP:
shouldLoop = NO;
break;
case NX_LMOUSEDRAGGED:
lastEvent = *nextEvent;
break;
case NX_TIMER:
[self autoscroll:&lastEvent];
break;
default:
break;
}
}

NXEndTimer(&myTimer);
[window setEventMask:oldMask];
return self;
}

The code segment above was taken directly from the Concepts manual of the NeXT System Reference Manual. For more complete information about modal loops and timer events, please read Concepts Chapter 7.

In /NextDeveloper/Examples/AppKit there are two example programs (ScrollDoodScroll and Draw) which show how to use timer events with modal loops and give you an impression as to why they are useful.


Q: I am saving the start-up defaults for my application in the defaults database using the NXReadDefault() and NXWriteDefault() routines. Is there a limit on the number of bytes that I can put here with a single write?

A: There was a bug in Release 1.0 which caused a segmentation error when reading back any default which exceeded 69 bytes. This bug was fixed in Release 2.0. However the limit is fixed at 1024 bytes. In Release 3.1 the limit is fixed at 8192 bytes.


Q: Is there a way to make a .wn-style document file package, so it appears to the user as a file rather than a directory?

A: A file package is a special kind of directory containing a set of related files that usually aren't accessed individually. You can use a file package to hold a collection of files that make up one complete document, such as the text of a document and its imbedded graphic files. A file package provides a convenient way to shield the files related to the application from the user; the package acts as though it were one document. The Workspace Manager represents the package in the File Viewer by the document icon, and the Open command launches the application and opens the file rather than opening the directory.

It is easy to make your own document file package, because a file package is simply a directory which ends with the extension your application claims for its documents. In the InterfaceBuilder Project Attributes Inspector, you can associate a custom document icon with a file name extension. (For Release 3, use the Attributes display in Project Builder.) When you create a directory with that extension, the Workspace Manager displays it using your custom icon instead of the folder icon. When a user double-clicks that icon, it launches your program and requests that it open that document/directory.

When opening a document, your application should stat the file pathname to determine whether it is a file or a directory. If it is a directory, you should probably open particular files within the directory and put together the complete document. As an example, when WriteNow is opening a document package, it looks inside the directory for the file ``WNDocument.wn''. This file contains the text of the document and also has pointers to the embedded graphic files which are also stored in the file package.


Q: In the process of porting my code to the Intel platform I am encountering a problem that I was not previously encountering on Motorola hardware. Evidently when I send a message to a nil object where the sender returns a floating point value, my program experiences problems. Is this a bug?

A: The definition of the Objective-C language states that sending a message to a nil receiver returns a zero value. This may be something that your code relies upon. On Motorola hardware, no matter what the return type, this is dependable behavior. However, the architecture of the Intel platform has forced a change you need to be aware of. The 486 maintains a separate floating point stack that is used to return values not derived from integer/pointer. When confronted with a nil receiver, the runtime system cannot establish the appropriate Class to determine whether it should return a zero on the floating point stack or the integer stack. It returns an integer zero, and so methods returning id, integer, char or short are not affected. In cases where the return type is float, double, or struct, the behavior is unpredictable. The best approach is to write your code such that it is portable between all NEXTSTEP architectures, current and future. To do this, you should treat messaging a nil receiver as an error when the selector's return type is non-integer.

In Release 3.1, a new AppKit default has been added that can help to debug this problem. NXTrapIllegalFloatingPointOps will catch invalid comparisons to NaN as well as other illegal IEEE floating point operations. When one occurs, a floating point exception will be raised and the application will crash helping you to debug the problem. The syntax for this default is:

appname.app/appname -NXTrapIllegalFloatingPointOps YES

See the Release 3.1 AppKit Release Notes for more information on NXTrapIllegalFloatingPointOps.


Q: Help! NXMapFile() is returning a NULL stream.

A: The documentation is not explicit on this point, but the file must already exist for NXMapFile() to return a valid stream. As the name of the function indicates, it maps the file into a memory stream. If the file doesn't exist, you can't very well map it. If you want to open a memory stream to write to and later save to that path, you can use NXOpenMemory() to get a memory stream and then use NXSaveToFile() to write the stream out to that path.


Q: I am using NXWriteTypes() to archive a character array as follows:

NXWriteTypes (typedStream, "[32c]", &myCharArray);

I am following the syntax specified in the documentation, but I consistently get a memory exception error. Why?

A: The reason is that you are using NXWriteTypes() (plural), which expects multiple types of data, but you only have one single data type. The fix is to use NXWriteType():

NXWriteType(typedStream, "[32c]", &myCharArray);

There is another way to archive an array with the specialized NXFunction NXWriteArray(). The above example would be changed to:

NXWriteArray(stream, "c", 32, myCharArray);

Note the different syntax, and see the technical documentation for details on other types of array elements.


Q: The Open and/or Save panels in my application don't resize horizontally in increments the size of a Browser column, as they should. What's going on?

A: Have you changed the panel's delegate? If so, you need to implement the windowWillResize:toSize: method in the delegate and have it send the windowWillResize:toSize: message to the panel.

QA506

Valid for 1.0, 2.0, 3.0

Q: Why can't I resize a SavePanel or OpenPanel with sizeWindow::?

A: The reason that sizeWindow:: does not work with OpenPanel and SavePanel is that the size for these panels is set by the user. When the user resizes and Open or SavePanel, the size is written to the defaults database. When the window is brought up the next time, the size is read from the defaults database, and set. Since you have to call sizeWindow:: before any of the runModal methods, and runModal sets the size of the panel, doing a sizeWindow:: before is too soon.

You probably shouldn't be modifying the size of the SavePanel or OpenPanel in your application anyway. In general, users set that the way they like it, and then it's the same for all applications.

If you add an accessoryview to the panel, then it's automatically resized vertically so that the accessoryview will fit, but the width is not modified.



OpenStep | Alliances | Training | Tech Support | Where to Buy