How to properly set left indent

Hi, ALL,

My first post here, so please be gentle... ;-)


I'm trying to apply the left indentation to the NSTextView. So, I wrote following code:


(I know it is inefficient, but I need to make it work first)



NSMutableParagraphStyle *paragraphStyle;
double indent = 100 * mm2pt / 10.0;
if ( start == -1 && end == -1 )      // no selection, control is either empty or contain some text
{
paragraphStyle = [[NSMutableParagraphStyle alloc] init];        
[paragraphStyle setHeadIndent: indent];        
[paragraphStyle setFirstLineHeadIndent: indent];        
[m_textView setDefaultParagraphStyle:paragraphStyle];        
[paragraphStyle release];
}
else // Set the attributes just for the selection
{
NSRange range = NSMakeRange(start, end-start);
NSTextStorage* storage = [m_textView textStorage];
paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setFirstLineHeadIndent: indent];
[paragraphStyle setHeadIndent: indent];
[storage addAttribute: NSParagraphStyleAttributeName value: paragraphStyle range: range];
[paragraphStyle release];
}

The problem is that if I don't have a selection, I don't see any indentation applied. Going thru the code with debugger,
the code is executed successfully. No errors or exceptions. But if the selection is present, everything works and the text
is left indented.

My question is: is setDefaultParagraphStyle() a correct function to use? And if yes - do I need some other call inside the if
clause? I want this code to work even on the empty text view.

I'm testing on OSX 10.8 with Xcode 5. And the minimum OSX version is 10.7.

Thank you.
Answered by QuinceyMorris in 137933022

The numbers in NSMakeRange are the start and length, not the start and end. So if storage.string.length is 200, you're trying to change characters beyond the end of the text (200, 201, 202, … 399), which obviously can't work.


You should use something like NSMakeRange (insPoint, storage.string.length - insPoint), but you should always check that insPoint isn't larger than storage.string.length — a negative length is also going to crash.

I'm not an expert in this area, but I believe that if you want to set attributes that will get applied automatically to text that is subsequently typed, you need to do it via NSTextView's "typingAttributes" property. IIRC it's a bit of a pain because you have to do this whenever typingAttributes gets reset.


While it seems logical that it should use the paragraph style attribute that you set into "defaultParagaphStyle", it may perhaps be more of a passive annotation, rather than an automatic default for new text.

Hi,

Well that actually yes and no.

Right now I want the left indentation to be applied to either empty view and when I set some text in it it will appear indented or if I have a text there already after I set the indentation the whole text should become indented.

Then I can solve the typing issue.


Also, it is not necessary for the new text - it should apply for an existing text as well.


One step at a time.


Unless you know that the typing attribute will solve both.

But in this case I have no idea how to do that.


So, since you said you are not an expert in this area, I will wait for one.


Thank you anyway.

Your original question was not entirely clear. There are 3 possible cases you need to consider:


1. There is selected text. You already wrote the code for that.


2. There is text but no selection, and you want to indent the existing (unselected) text. You'd do it almost the same way as case #1. Set up a range from 0 to storage.string.length (that is, the number of text characters), and use your existing technique (setAttribute:value:range:).


3. You want to set an indent for future text (typed by the user or inserted via 'insertText:'). I think you need to need to use typingAttributes.


Possibly "defaultParagraphStyle" will work for #3, but it certainly won't work for #2.

Hi,

Yes, there are multiple scenarios:


1. Yup, the code is already there and it works.

2. OK, so I need to check if there is a text in the view. Do you know how to check it and get the size of the text?

3. I tried to use defaultParagraphStyle on the empty text view, but it didn't work. So how do I use typingAttributes? Do you have any code to share?

>> so I need to check if there is a text in the view.


No, the difference between #2 and #3 is NOT whether the text is empty, but whether you want to indent PAST text or FUTURE text as it's typed.


>> Do you know how to … get the size of the text?


I already gave you that answer. The size of the text (in characters) is 'storage.string.length'. So make a range:


NSRange range = NSMakeRange (0, storage.string.length);


and set the paragraph style like before:


[storage addAttribute: NSParagraphStyleAttributeName value: paragraphStyle range: range];


You do NOT need to check if the size is 0, because setting an attribute on an empty range is harmless.


>> So how do I use typingAttributes? Do you have any code to share?


You need to construct a dictionary of all the attributes you want. If the only thing that you want to control is the indent, you can create the dictionary like this:


NSDictionary* attributes = @{ NSParagraphStyleAttributeName : paragraphStyle };


where 'paragraphStyle' is what your current code creates. Then set the typing attributes like this:


[m_textView setTypingAttributes: attributes];


There are other ways of creating a dictionary. I've used the shorthand @{ … } syntax.

Hi, QuinceyMorris,

I'm trying to implement and test the solution you proposed.

The question I have is: do I have to release the memory I made for the NSDictionary or it will be owned by the NSTextView and will be deleted when the control will be released?


Thank you.


P.S.: This is my latest code:


   
NSTextStorage* storage = [m_textView textStorage];
NSMutableParagraphStyle *paragraphStyle;
NSRange range;
paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setFirstLineHeadIndent: indent];
[paragraphStyle setHeadIndent: indent];
[storage addAttribute: NSParagraphStyleAttributeName value: paragraphStyle range: range];
[paragraphStyle release];
if( start == -1 && end == -1 )
{
    NSDictionary *attrib = @{ NSParagraphStyleAttributeName : paragraphStyle };   
    [m_textView setTypingAttributes: attrib];
}


Just to clarify: do I need to delete attrib or the memory will be released when the NSTextView will be deleted?

Your current memory management is correct.


'@{ … }' returns an "unowned" object (that is, you do not need to release it), and the text view will retain whatever it needs to keep around.

Hi, QuinceyMorris,

Could you please look at my latest code:


NSRange range;
if ( start == -1 && end == -1 )
{
    NSInteger insPoint = [[[m_textView selectedRanges] objectAtIndex:0] rangeValue].location ;
    range = NSMakeRange(/insPoint*/0, storage.string.length);
}
if( style.HasLeftIndent() )
{
    paragraphStyle = [[NSMutableParagraphStyle alloc] init];
    [paragraphStyle setFirstLineHeadIndent: indent];
    [paragraphStyle setHeadIndent: indent];
    [storage addAttribute: NSParagraphStyleAttributeName value: paragraphStyle range: range];
    if( start == -1 && end == -1 )
    {
        [attrs setValue: paragraphStyle forKey: NSParagraphStyleAttributeName];
        [m_textView setTypingAttributes:attrs];
    }
    [paragraphStyle release];
}


The idea is to apply the indentation from the current cursor position and not from "0".

If I run this code everything works. But if I use "insPoint" for the NSMakeRange() call, it crashes on the call to addAttribute() call.

Event if I do something like:


range = NSMakeRange[200, storage.string.length];


which creates a range of (200, 200) the program crashes.


Any idea?


Thank you.

Accepted Answer

The numbers in NSMakeRange are the start and length, not the start and end. So if storage.string.length is 200, you're trying to change characters beyond the end of the text (200, 201, 202, … 399), which obviously can't work.


You should use something like NSMakeRange (insPoint, storage.string.length - insPoint), but you should always check that insPoint isn't larger than storage.string.length — a negative length is also going to crash.

Hi, QuinceyMorris,

[quote]

You should use something like NSMakeRange (insPoint, storage.string.length - insPoint), but you should always check that insPoint isn't larger than storage.string.length — a negative length is also going to crash.

[/quote]


Well I don't see how that possible.

The current insertion point will always be less or equal the length of the text inside the view. Or can it?


Thank you.

With the code you've shown, it should always be less than the length, but it isn't a bad idea to check it anyway. When you come back to this later, it's easy to change one line of code without noticing that the other line might be invalidated.

QuinceyMorris,

Thank you for the help.

I hope you also learn something new. ;-)

How to properly set left indent
 
 
Q