Maintain Undo In Modified, Bound, NSAttributedString - objective-c

I've got an attributed string bound to a NSTextView. I'm using a method that is called (using KVO) every time the string is edited to add background color attributes to string based on a regEx match. I do this by creating a new mutable attributed string with -initWithAttributedString: then -beginEditing, -addAttribute:, -endEditing. Once I've added all the background color attributes I want, I call the string's setter [self setTextViewString:mutableAttributedString] The issue is, that if there actually are any attributes added to the string, it kills undo and moves the cursor to the end of the string.
How can I maintain undo? I've maintained cursor position by calling the textView's selectedRanges and setSelectedRanges: methods on either side of the setter, but this still seems a bit hackish.
I wasn't able to bind the textview directly to the mutableattributedstring, but it seems like there should be a more direct way to modify the bound string so it doesn't mess up editing.
PS, the addition of attributes happens after the KVO method finishes by calling -performSelectorOnMainThread: It was the only way I could get the added attributes to display.

[self setTextView:mutableAttributedString]
Pardon? You're setting your textView to an attributed string? Wouldn't it make more sense to keep your text view there?
Try getting the text view's textStorage and replacing its contents with the new attributed string by sending the text storage a setAttributedString: message.

Related

Appending *simple* ASCII strings to an NSScrollView

Is there a simple way to append a string to an NSScrollView's NSTextView? I don't want attributes. I simply have error messages coming in as NSString's, and I wish to append each to the window. Nothing fancy. No formatting beyond CR, LF and perhaps TAB if I have too many beers and decide to get over the top fancy.
Every path I follow through the Class docs seems to lead down into a self referential blackhole... Like NSMutableAttributedStrings... which aren't really NSStrings, and don't even have a cString method.
I have been considering just keeping my own NSString and complete rewriting the contents of the scroll view after appending the errorstring the easy way. But that seems... inefficient... when the numbers of reports could get quite large.
Every string needs some attributes to be drawn to the screen — a font/size/colour at the very least is mandatory.
It's not entirely obvious from the docs, but the "proper" way to manipulate an NSTextView is by manipulating the NSTextStorage directly. Also, NSTextStorage is a subclass of NSMutableAttributedString.
You can add characters to the string without dealing with attributes, it will simply copy the attributes from the string around where you add the text to:
[textView.textStorage replaceCharactersInRange:NSMakeRange(textView.textStorage.length, 0) withString:#"\nhello world"];
You will probably also want to scroll down:
[textView scrollRangeToVisible:NSMakeRange(textView.storage.length, 0)];
Performance will be good, even up to gigabytes of data. NSTextView is very efficient, especially when only small changes to the content are made.

How to insert a NSButton into a NSTextView? (inline)

I have a NSTextView and a custom NSButton. What I want to do is to insert that button (60x16 in size) to the end of the NSTextView.
How can I do something like that? I've been trying to search around on how to do this but I'm not getting anywhere.
Where should I begin? Thanks
I believe this question is pretty similar to yours:
Buttons inside an NSTextView / NSTextStorage
Quote from the question:
how do I get an NSButton to appear inside the text and react to
clicks?
Note that the issue is not fully solved there, but it seems the OP got a good head start. Hopefully you can take some clues from the discussion.
Here is one answer:
NSTextAttachment holds the contents of an attachment; it is the value
of the NSAttachmentAttributeName attribute for the
NSAttachmentCharacter in the attributed string. The contents are
usually given by an NSFileWrapper, but this is not required; you can
create an empty NSTextAttachment with a nil file wrapper.
NSTextAttachmentCell handles display and user interaction for the
attachment. By default an NSTextAttachment will create an
NSTextAttachmentCell to display itself, depending on the contents of
the attachment; in the generic case this will just be an image cell
displaying an icon.
If you wish, however, you can supply a custom NSTextAttachmentCell for
your attachment. It need not be an member of the class
NSTextAttachmentCell; it only needs to conform to the
NSTextAttachmentCell protocol. Actually, even that is not strictly
necessary; it only needs to implement a few of the basic methods for
sizing and drawing. Most cells already do this.
You will, however, need to deal with mouse events yourself. The
methods you'll probably want to implement would be
wantsToTrackMouseForEvent:inRect:ofView:atCharacterIndex: and
trackMouse:inRect:ofView:atCharacterIndex:. The character index here
should let you determine which portion of the text is relevant.

Does setNeedsDisplay:NO have any use at all?

In Cocoa, when we want to redraw a view, we would send the view a setNeedsDisplay: message telling the view to redraw itself with a parameter of YES. I was wondering if there are any circumstances where you would want to send a view setNeedsDisplay:NO, such as multithreading environments, and if sending a view a setNeedsDisplay:YES, then setting it again immediately after with setNeedsDisplay:NO would make the view redraw itself. If there are no reasons to call setNeedsDisplay:NO, then why create such a tedious method, where they could instead implement something like [view redrawView]
setNeedsDisplay:NO may be used in case you want to discard previously called setNeedsDisplay:YES. E.g. sometimes it is easier to mark all subviews as needing display and then run an algorithm to unmark some of them.
As you perhaps know, the display update is automatic (if necessary) at each pass through the normal event loop. You call setNeedsDisplay: in order to force a display update in between if it is necessary.
From the documentation of NSView:
Discussion
Whenever the data or state used for drawing a view object changes, the view should be sent a setNeedsDisplay: message. NSView objects marked as needing display are automatically redisplayed on each pass through the application’s event loop. (View objects that need to redisplay before the event loop comes around can of course immediately be sent the appropriate display... method.)
The boolean parameter to this function simply specifies if the entire bounds of the view in question is affected or not, not if some property "needsDisplay" is set to true or false. Thus, setNeedsDisplay: does indeed work pretty much like a "redrawView", only with the additional parameter.
Edit
The above was inspired from the same documentation:
flag
If YES, marks the receiver’s entire bounds as needing display; if NO, marks it as not needing display.

Autoresizing NSTextView and it's Font Size

I'm trying to make my NSWindow autoresizable. I've gotten most of my UI items to resize, but one of the few remaining objects that pose issues are NSTextViews: indeed, I can't find a way to calculate the new font size once the NSTextView has been resized.
For NSTextFields, I had found a method that would find the font size based on the length of the text. Apparently, there doesn't seem to be an equivalent method for multi-line text containers (unless I just haven't found it).
The only actual place I found that mentionned something of the sort is here: http://boutcher.tumblr.com/post/7842216960/nstextview-auto-resizing-text
However, I wasn't able to implement this code into my application, as there seems to be an error I can't fix with the way an NSLayoutManager is created.
Has anyone done this in the past? I'm considering just allowing the user to resize to just one size, so I can hardcode the font size... It's a real pain dealing with these NSTextViews !
See the sizeWithAttributes: method in “NSString Application Kit Additions Reference.”
It returns an NSSize, which you can compare to the textview’s current frame.size.
For the “Attributes” arg make an NSDictionary with an NSFont as the object and NSFontAttributeName as the key. (Don't be confused by that constant. It looks like it's a key for a string, but it is not; it is a key for the font itself.)
Get the string from the textview: [[yourTextView textStorage] string].
Get the familyName of the font you are using and its current point size, a CGFloat. Compose fonts to test using the constructor [NSFont fontWithName:familyName size:floatChanged].
Once you've arrived at the correctly sized font, use it to make a new NSAttributedString out of the old string. Just use the "attributes" dictionary you made above (the one that produced the correct size) and feed it to NSAttributedString's initWithString:attributes constructor.
Assign that attributed string to the textStorage (itself a subclass of NSMutableAttributedString): [[yourTextView textStorage] setAttributedString:thatYouJustMade].
Note: If the textview employs attributes like underlining or fore/background coloring, or italicized/bold fonts, you must get that information from the textStorage and incorporate it into the new attributed string. It's doable. Look at the class refs for NSAttributedString and NSMutableAttributedString.

NSTextField custom subclass and "No selection" placeholder

I have a custom NSTextField subclass which is bound to the property of a managed object. During its drawRect method it is required that I know whether the stringValue of the field is nil (either caused by the managed objects attribute being nil or no managed object being selected), an empty string or a "normal" string.
As far as I can tell the string value is never nil, even when the bound attribute of the managed object is nil or no managed object is selected. Am I correct or missing something?
Note: I have tried various settings in the bindings panel but this does not seem to work.
EDIT: Maybe to clarify, the effect I'm after is something as shown in Xcode when there's no selection:
The difference is, I want my "badge" to turn up when the string is an empty string (although with a slightly different message in it). I want to suppress this effect when the string is nil and use the default placeholders from the mapping panel.