如何以编程方式向NSTextView添加项目符号列表

8
这个问题听起来有些奇怪,但我已经为此苦苦思索了几天。
我有一个NSTextView,它可以显示一些文本,并具有一些格式选项。其中之一是能够为选择或当前行打开/关闭项目符号列表(最简单的方法)。
我知道在NSTextView上有一个orderFrontListPanel:方法,它会打开窗口并提供可供选择和编辑的列表参数(就像在TextView中按下菜单->格式->列表...时一样)。 我已经想出并实现了手动添加项目符号,并且NSTextView似乎对它们表现得几乎正确。所谓的“几乎正确”是指它保留了制表位位置,在“回车”时继续列表等等。但是,有一些小故障不适合我,与标准实现不同。
我试图找到通过“列表...”菜单以编程方式设置列表的默认方式,但没有成功。
求助,每一点信息都将不胜感激:)。
附言:我查看了TextView源代码,发现了很多有趣的内容,但没有发现如何在程序中启用列表的迹象或线索。
更新
仍在调查中。我发现当您向NSTextView发送orderFrontListPanel:,然后选择项目符号并按Enter键时,不会向NSTextView发送任何特殊消息。这意味着项目符号列表可能是在此弹出面板内构建并直接设置到TextView的文本容器中...
1个回答

13

程序化地向NSTextView添加带项目符号的列表的两种方法:

方法1:

以下链接引导我找到了第一种方法,但除非您想使用某些特殊的非Unicode符号作为项目符号,否则这是不必要的绕路:

这需要:(1)一个替换任意字符的子类布局管理器来代替项目符号;和(2)一个具有firstLineHeadIndent、略微大于该缩进的制表位和用于合并两者的换行的headIndent的段落样式。

布局管理器如下:

#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's string is set 4 times, so this implementation is not too expensive.
    printf("\nCalling TickerLayoutManager's 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

在您的窗口/视图控制器的 awakeFromNib 方法中,将布局管理器分配给文本视图,如下所示:
- (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's "Text System Overview" is not clear on this point.)

}

然后添加一个类似于这样的方法:

- (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];

}

测试一下:

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];

你得到这个:

enter image description here

方法2:

但是,子弹符号是一个普通的Unicode字符,十六进制为2022。因此,您可以直接将其放入字符串中,并获得精确的测量结果,如下所示:

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

因此,不需要自定义布局管理器。只需将paragStyle缩进设置为上述内容,并将文本字符串附加到包含回车符+项目符号字符+空格(或+制表符,在这种情况下,您仍然需要该制表符停止)的字符串中即可。
使用空格,这将产生更紧凑的结果:

enter image description here

想要使用除了圆点以外的符号吗?这里有一个不错的Unicode表格:http://www.danshort.com/unicode/


谢谢您的出色回答,可能有点晚了,但这个问题仍然没有解决,该模块被包装在DEBUG语句中 :). 我将尝试应用第二种方法,我认为这是实现结果最简单的方法。问题是(据我所记得的)手动添加字形和通过菜单添加字形会产生不同的结果,但我会再次确认一下。 - GregoryM
不客气。我很高兴这个实验仍然相关——希望它对你有用。 - Wienke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接