Apple Enterprise - NeXTanswers Support Archive

Search NeXTanswers for:

NeXTSTEP Text Programming Q&A

Creation Date: August 17, 1998


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.


Q: When should I use NXLocalString() and when should I use NXLocalizedString() in my code? The localization chapter in /NextLibrary/Documentation/NextDev/Concepts just discusses NXLocalizedString().

A: NXLocalString() is an old name, and you should use NXLocalizedString() instead. The name NXLocalString() is only there for compatibility.

Q: The program genstrings complains if NXRunLocalizedAlertPanel() contains a constant (like from a #define) instead of a string. This means that we may end up having "OK" defined multiple times in the string table, since most alert panels include an "OK" button. Should I avoid using NXRunLocalizedAlertPanel()?

A: NXRunLocalizedAlertPanel() will be obsolete in the future, and we strongly recommend that you use NXRunAlertPanel() instead. It's true that genstrings doesn't eliminate duplicate entries from NXRunLocalizedAlertPanel().

Q: The localization chapter in /NextLibrary/Documentation/NextDev/Concepts suggests using #define to retrieve a string just once. It seems like if we want to avoid extra lookups, we should define a variable, initialize it once to the result of an NXLocalizedString(), and then use it where necessary.

A: You are right. If you want to avoid extra lookups, you should define a variable to have the value. For example, you could do the following:

const char *fooString = NULL;
#define FOO_STRING (fooString = fooString ? fooString :
NXLocalizedString("Foo", NULL, "Nonsense."))

That way, the string will only be looked up if it is needed and will only be looked up at most once. The macro is a convenient way to make sure that the string has been initialized each time you want to use it, especially if you want to use it as an argument.

Q: How big a performance hit do I take for a lookup from a string table? At what size is it worth breaking up a string table into multiple files?

A: Lookups are cheap. It's only worth going to multiple files if you have hundreds of entries, or if you have a large number of entries which are almost never accessed or which are only accessed together such as error conditions or strings for a certain panel like an Inspector Panel.

Q: How can I determine which TextField is being edited, while performing a delegate method such as textWillEnd: or textDidChange:?

A: All of TextField's delegate methods send the Text object as an argument. You can message this Text object to get its delegate, which has been set to the id of the TextField being edited. If you have several TextFields in a Matrix (or a Form) then the Matrix (or the Form) is the delegate of the Text object. In which case you should use the following method calls to get the actual TextField:

[[sender delegate] selectedCell];

This code also works for individual TextFields, because single Controls return self when asked for selectedCell. We recommend that you use the above code snippet in all cases.

Q: In my application I have overridden the Text object methods:


However, no matter what I try, these methods are never called. I have also tried implementing the equivalent delegate methods:


but I'm not getting anywhere here either. What am I doing wrong?

A: While these methods were implemented under 1.0, they have been obsoleted by replaceSelWithCell: and its associated methods in subsequent releases. Although these methods appear in the header file and the specification sheet for the Text object, they are never called within your application.

Q: How do the Text object margins work?

A: The margins do work, but it's difficult to correctly set them. You need to offset the values by the frame rectangle of the text. The following code snippet illustrates the necessary steps:

/* This routine assumes that you have defined currentDocText somewhere else */

#define DESIREDleftMARGIN some int value
#define DESIREDrightMARGIN some int value
#define DESIREDtopMARGIN some int value
#define DESIREDbottomMARGIN some int value

- setMargins:sender
NXRect aRect;

/* Get the current frame values */
[currentDocText getFrame:&aRect];
[currentDocText setMarginLeft: NX_X(&aRect) + DESIREDleftMARGIN
right: - NX_X(&aRect) + DESIREDrightMARGIN
top: NX_Y(&aRect) + DESIREDtopMARGIN
bottom: - NX_Y(&aRect) + DESIREDbottomMARGIN]; ];
return self;

If you call getMarginLeft:right:top:bottom:, the returned values are the same as the ones you provide to setMarginLeft:right:top:bottom:. So, to determine the "true" margins, you need to subtract the frame's dimensions.

Q: How do I get at the list of runs in a Text object, without subclassing the Text class? Once I have them, how do I determine how many of them there are?


#import <appkit/Text.h>

@interface Text(Private)
- (int)nRuns;

@implementation Text(Private)
return theRuns->chunk.used/sizeof(NXRun);

main ()
id text = [[Text alloc]
initFrame:NULL text:"Hello there" \
printf("there are %d runs.\n", [text nRuns]);

Q: I'm trying to use the pasteFont: method of the Text object to extract font information from the Font Pasteboard. The trouble is, when I try to do this, I always get the default font information--Helvetica 12pt. What am I doing wrong?

A: The solution is easy but perhaps not obvious. In order for the paste operation to have any effect, the Text object must contain a selection. It is not even strictly necessary that it contain any text. The following code illustrates the ``low-pain'' method of extracting font values from the Pasteboard. (The alternative ``high-pain'' method would involve parsing the rich text format from the Pasteboard):

#import <appkit/Text.h>
#import <appkit/Font.h>

id font, myText;

/* Allocate a temporary Text object */
myText = [[Text alloc] init];

/* It must allow multiple fonts -- i.e. rich text */
[myText setMonoFont: NO];

/* Stick in some text -- optional */
[myText setText:"hello"];

/* And select it -- any selection will do so long */
/* as something is selected */
[myText setSel:0 :1];

/* pasteFont: will copy the font info from the Pasteboard
/* to the font in the Text object */
[myText pasteFont:sender];

/* Do what you need to with the font */
font = [myText font];

/* Then free up the Text object */
[myText free];

Q: How can you add text to the end of a text object?

A: The trick is to make a zero length selection at the end of the text, and add the new text there. The following code snippet illustrates the necessary steps:

/* This routine assumes that you have defined currentDocWindow somewhere else */

- addText:(const char *)newString
int length;
id currentDocText;

/* Get the current text document */
currentDocText = [[currentDocWindow contentView]docView];
length = [currentDocText textLength];
[currentDocText setSel:length :length];
[currentDocText replaceSel:newString];

return self;

Q: How do I append text to a text object, such that the new text makes use of a different font?

A: First of all, make sure that the text object can contain multiple fonts. Send the following message to your text object:

id myText;
[myText setMonoFont:NO];

Then, use the following code snippet to append the text, select the text, and change its font:

id myText;
int length;

length = [myText textLength];
[myText setSel:length:length]; // put an empty selection at the end of the text
[myText replaceSel:"some new text"]; // add some text
[myText setSel:length :[myText textLength]]; // select the newly added text
[myText setSelFont:[Font newFont:"Symbol" size:24.0]]; // change its font
The above code works in all releases and leaves the text selected.

The following code snippet will not work (in Release 1.0 or 2.0) due to the way in which setSelFont:, and replaceSel: interact. (For 3.0 this code snippet works similar to the code above except that the text is not highlighted when completed.)

id myText;
int length;

length = [myText textLength];
[myText setSel:length:length]; // put an empty selection at the end of the text
[myText setSelFont:[Font newFont:"Symbol" size:24.0]]; // change the font
[myText replaceSel:"some new text"]; // add some text

For 2.0:
To avoid some of the flashing that may occur to the text object while selecting and modifying the font programmatically, you should perform a disableDisplay on the window containing the text object before selecting and modifying the text. After the modifications you should then reenableDisplay on the window and display the text object.

Q: Why won't my textField become firstResponder? The following code is in my appDidInit: method:

[[NXApp mainWindow] makeKeyAndOrderFront:self];
[[NXApp mainWindow] makeFirstResponder:myTextField];

The I-beam cursor does not appear in myTextField. In the debugger, makeFirstResponder: returns the id of the window as it should. And when asked, the window claims that the textField is the firstResponder. What's going on?

A: The problem is that makeFirstResponder: just sets the firstResponder. It does not modify the selection. You have to explicitly set the selection if you want the cursor to appear in the TextField. The method selectText: does the trick--and it calls makeFirstResponder: for you. Try using the following code instead:

[[NXApp mainWindow] makeKeyAndOrderFront:self];
[myTextField selectText:nil];

The same is true of Form objects as well. For Forms you must do the following:

[[NXApp mainWindow] makeKeyAndOrderFront:self];
[myForm selectTextAt:0];

Q: I am trying to set the background color for my TextField. It seems that the color is being ignored. What is going on? Here is my code, which uses a color well:

- setBackgroundColorWithWell:sender
[myTextField setBackgroundColor:[sender color]];
[myTextField display];
return self;

A: You must set shades in both the color model and the gray model separately. The AppKit figures out what type of machine is being used and sets the window depth for the window accordingly.

The model of being able to specify colors and gray separately can be useful; typically you don't want to give a TextField some random color when it is displayed in a monochrome window. For instance, yellow 12 point text on a red background is usually reduced a few pixels when displayed in a 2-bit window. Thus, in most cases you might want a TextField to remain black on white on the monochrome display. Or you might want to just alter the foreground while always keeping the background white. If you do want the gray and color values to track, then you have to set them both, as shown below.

- setBackgroundColorWithWell:sender
[myTextField setBackgroundColor:[sender color]];
[myTextField setBackgroundGray: NXGrayComponent([sender color])];
[myTextField display];
return self;

Q: When I use the Action Cell method setAlignment: in conjunction with the Cell method setScrollable:, I experience some inconsistent behavior. If I have called [myTextCell setAlignment:NX_CENTERED] and [myTextCell setScrollable:YES], the display does not update in the cell. However, when the TextFieldCell is sent [myTextCell setScrollable:NO], then the display works fine.

A: The Text Object does not support scrolling for centered or right-justified text. To get proper display, you have to disable scrolling with [myTextCell setScrollable:NO] when using the method setAlignment: with the values NX_CENTERED or NX_RIGHTALIGNED. If your text is left-justified, scrolling will work fine, i.e. you can use[myTextCell setAlignment:NX_LEFTALIGNED] with [myTextCell setScrollable:YES].

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