How to programmatically add a token list to an NSTextView

The question may seem strange, but I struggled with it for several days.

I have an NSTextView that can display some text with several formatting options. One of them is the ability to turn on / off the list of markers (the easiest) to select or the current line.

I know that there is an orderFrontListPanel: method in NSTextView that opens a window with available list options for selection and editing (for example, in TextView when you press Menu-> Format-> List ...). I already understood and implemented adding bullets manually, and NSTextView seems to behave with them almost correctly. Speaking almost, I mean that it keeps tabs, continues the list with 'enter', etc. But there are some minor glitches that do not suit me and differ from the standard implementation.

I tried to find the default method for setting up lists programmatically, as done through the List ... menu with no luck.

I ask for help, every bit of information will be appreciated :).

PS: I looked into the source code of TextView, I found a lot of interesting things, but without a sign or did not understand how to include lists programmatically.

Update

Continuing the investigation. I found that when you send orderFrontListPanel: to your NSTextView, and then select the markers and press the enter key, no special messages are sent to the NSTextView. This means that the bullet list can be built somewhere inside this popup and installed directly in the TextView text container ...

+6
source share
1 answer

Two methods for programmatically adding a bulleted list to an NSTextView:

Method 1:

The following links led me to this first method, but its overly circular motion if you don't want to use some special non-Unicode character for the bullet:

This requires: (1) a subclass layout manager that replaces the marker marker for some arbitrary character; and (2) the paragraph style with firstLineHeadIndent, the tab is slightly larger than this indent, and headIndent for wrapped lines that combine the two.

The layout manager is as follows:

#import <Foundation/Foundation.h> @interface TickerLayoutManager : NSLayoutManager { // Might as well let this class hold all the fonts used by the progress ticker. // That way they're all defined in one place, the init method. NSFont *fontNormal; NSFont *fontIndent; // smaller, for indented lines NSFont *fontBold; NSGlyph glyphBullet; CGFloat fWidthGlyphPlusSpace; } @property (nonatomic, retain) NSFont *fontNormal; @property (nonatomic, retain) NSFont *fontIndent; @property (nonatomic, retain) NSFont *fontBold; @property NSGlyph glyphBullet; @property CGFloat fWidthGlyphPlusSpace; @end #import "TickerLayoutManager.h" @implementation TickerLayoutManager @synthesize fontNormal; @synthesize fontIndent; @synthesize fontBold; @synthesize glyphBullet; @synthesize fWidthGlyphPlusSpace; - (id)init { self = [super init]; if (self) { self.fontNormal = [NSFont fontWithName:@"Baskerville" size:14.0f]; self.fontIndent = [NSFont fontWithName:@"Baskerville" size:12.0f]; self.fontBold = [NSFont fontWithName:@"Baskerville Bold" size:14.0f]; // Get the bullet glyph. self.glyphBullet = [self.fontIndent glyphWithName:@"bullet"]; // To determine its point size, put it in a Bezier path and take its bounds. NSBezierPath *bezierPath = [NSBezierPath bezierPath]; [bezierPath moveToPoint:NSMakePoint(0.0f, 0.0f)]; // prevents "No current point for line" exception [bezierPath appendBezierPathWithGlyph:self.glyphBullet inFont:self.fontIndent]; NSRect rectGlyphOutline = [bezierPath bounds]; // The bullet should be followed with a space, so get the combined size... NSSize sizeSpace = [@" " sizeWithAttributes:[NSDictionary dictionaryWithObject:self.fontIndent forKey:NSFontAttributeName]]; self.fWidthGlyphPlusSpace = rectGlyphOutline.size.width + sizeSpace.width; // ...which is for some reason inexact. If this number is too low, your bulleted text will be thrown to the line below, so add some boost. self.fWidthGlyphPlusSpace *= 1.5; // } return self; } - (void)drawGlyphsForGlyphRange:(NSRange)range atPoint:(NSPoint)origin { // The following prints only once, even though the textview string is set 4 times, so this implementation is not too expensive. printf("\nCalling TickerLayoutManager drawGlyphs method."); NSString *string = [[self textStorage] string]; for (int i = range.location; i < range.length; i++) { // Replace all occurrences of the ">" char with the bullet glyph. if ([string characterAtIndex:i] == '>') [self replaceGlyphAtIndex:i withGlyph:self.glyphBullet]; } [super drawGlyphsForGlyphRange:range atPoint:origin]; } @end 

Assign the layout manager to the text view in your awakeFromNib viewport / view controllers, for example:

 - (void) awakeFromNib { // regular setup... // Give the ticker display NSTextView its subclassed layout manager. TickerLayoutManager *newLayoutMgr = [[TickerLayoutManager alloc] init]; NSTextContainer *textContainer = [self.txvProgressTicker textContainer]; // Use "replaceLM" rather than "setLM," in order to keep shared relnshps intact. [textContainer replaceLayoutManager:newLayoutMgr]; [newLayoutMgr release]; // (Note: It is possible that all text-displaying controls in this class's window will share this text container, as they would a field editor (a textview), although the fact that the ticker display is itself a textview might isolate it. Apple "Text System Overview" is not clear on this point.) } 

And then add a method something like this:

 - (void) addProgressTickerLine:(NSString *)string inStyle:(uint8_t)uiStyle { // Null check. if (!string) return; // Prepare the font. // (As noted above, TickerLayoutManager holds all 3 ticker display fonts.) NSFont *font = nil; TickerLayoutManager *tickerLayoutMgr = (TickerLayoutManager *)[self.txvProgressTicker layoutManager]; switch (uiStyle) { case kTickerStyleNormal: font = tickerLayoutMgr.fontNormal; break; case kTickerStyleIndent: font = tickerLayoutMgr.fontIndent; break; case kTickerStyleBold: font = tickerLayoutMgr.fontBold; break; default: font = tickerLayoutMgr.fontNormal; break; } // Prepare the paragraph style, to govern indentation. // CAUTION: If you propertize it for re-use, make sure you don't mutate it once it has been assigned to an attributed string. (See warning in class ref.) // At the same time, add the initial line break and, if indented, the tab. NSMutableParagraphStyle *paragStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; // ALLOC [paragStyle setAlignment:NSLeftTextAlignment]; // default, but just in case if (uiStyle == kTickerStyleIndent) { // (The custom layout mgr will replace '>' char with a bullet, so it should be followed with an extra space.) string = [@"\n>\t" stringByAppendingString:string]; // Indent the first line up to where the bullet should appear. [paragStyle setFirstLineHeadIndent:15.0f]; // Define a tab stop to the right of the bullet glyph. NSTextTab *textTabFllwgBullet = [[NSTextTab alloc] initWithType:NSLeftTabStopType location:15.0f + tickerLayoutMgr.fWidthGlyphPlusSpace]; [paragStyle setTabStops:[NSArray arrayWithObject:textTabFllwgBullet]]; [textTabFllwgBullet release]; // Set the indentation for the wrapped lines to the same place as the tab stop. [paragStyle setHeadIndent:15.0f + tickerLayoutMgr.fWidthGlyphPlusSpace]; } else { string = [@"\n" stringByAppendingString:string]; } // PUT IT ALL TOGETHER. // Combine the above into a dictionary of attributes. NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: font, NSFontAttributeName, paragStyle, NSParagraphStyleAttributeName, nil]; // Use the attributes dictionary to make an attributed string out of the plain string. NSAttributedString *attrs = [[NSAttributedString alloc] initWithString:string attributes:dict]; // ALLOC // Append the attributed string to the ticker display. [[self.txvProgressTicker textStorage] appendAttributedString:attrs]; // RELEASE [attrs release]; [paragStyle release]; } 

Check this:

 NSString *sTicker = NSLocalizedString(@"First normal line of ticker should wrap to left margin", @"First normal line of ticker should wrap to left margin"); [self addProgressTickerLine:sTicker inStyle:kTickerStyleNormal]; sTicker = NSLocalizedString(@"Indented ticker line should have bullet point and should wrap farther to right.", @"Indented ticker line should have bullet point and should wrap farther to right."); [self addProgressTickerLine:sTicker inStyle:kTickerStyleIndent]; sTicker = NSLocalizedString(@"Try a second indented line, to make sure both line up.", @"Try a second indented line, to make sure both line up."); [self addProgressTickerLine:sTicker inStyle:kTickerStyleIndent]; sTicker = NSLocalizedString(@"Final bold line", @"Final bold line"); [self addProgressTickerLine:sTicker inStyle:kTickerStyleBold]; 

You get the following:

enter image description here

Method 2:

But the bullet is a regular Unicode char, in hexadecimal format 2022. Thus, you can put it in a string directly and get an exact measurement, for example:

  NSString *stringWithGlyph = [NSString stringWithUTF8String:"\u2022"]; NSString *stringWithGlyphPlusSpace = [stringWithGlyph stringByAppendingString:@" "]; NSSize sizeGlyphPlusSpace = [stringWithGlyphPlusSpace sizeWithAttributes:[NSDictionary dictionaryWithObject:self.fontIndent forKey:NSFontAttributeName]]; self.fWidthGlyphPlusSpace = sizeGlyphPlusSpace.width; 

Therefore, there is no need for a custom layout manager. Just indent paragStyle as above and add a text line to the line containing the line return + bullet char + space (or + tab, in which case you still want this tab to stop).

Using a space, this led to a denser result:

enter image description here

Want to use a character other than a bullet? Here is a good Unicode graph: http://www.danshort.com/unicode/

+12
source

Source: https://habr.com/ru/post/886835/


All Articles