How to insert a NSButton into a NSTextView? (inline) - objective-c

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.

Related

NSTextField accessibility - How to provide alternate text for voiceover

It will probably be obvious, but I have never done any work with NSAccessibility before so I'm assuming what I'm asking is something simple I've overlooking.
I have an NSTextField displaying a duration like this, 15:39. This text field is a subview of an NSTableCellView in a view-based NSTableView.
When VoiceOver is enabled, it currently reads off, "one five, three nine", which seems completely useless. Instead, I want it to say "Duration is 15 minutes, 39 seconds."
I can produce the desired string, but I cannot figure out which accessibility attributes I have to set to make this happen.
I've tried setting the accessibility description in IB, which has no effect (whether setting it on the NSTextField or NSTextFieldCell).
I've also tried overriding accessibilityValue: and accessibilityAttributeValue:forParameter: in order to provide custom attribute values for:
NSAccessibilityNumberOfCharactersAttribute
NSAccessibilityStringForRangeParameterizedAttribute
NSAccessibilityAttributedStringForRangeParameterizedAttribute
NSAccessibilityStringForRangeParameterizedAttribute.
This seemed to be the right track since that does allow me to replace what is read aloud by voice over, however, providing any NSRange for NSAccessibilityVisibleCharacterRangeAttribute that doesn't match the length of the "15:39" string causes voice over to completely skip this field when reading off the NSTableCellView's contents. So, the best I've been able to do is get Voice over to say "Durat" instead of reading off "15:39" :(
Everything I've tried, I've tried on NSTextField and NSTextFieldCell.
Ideally, I'd prefer to do what I'd do in iOS and just set the accessibilityLabel of the NSTableCellView, but I see no reasonable way of doing this in AppKit. Hopefully I'm just missing something.
I was able to achieve this simply by setting accessibilityValueDescription on the NSTextField. This method is part of the new Accessibility API on OS X 10.10 and higher. With the older API, you may be able to use kAXValueDescriptionAttribute to achieve the same thing.
The solution for overriding the text read by Voice Over was much simpler than I thought. All I had to do was override the value returned for NSAccessibilityAttributedStringForRangeParameterizedAttribute:
// The displayed text for this text field is "45m".
// The voice over for this field incorrectly reads "forty five meters" instead of "forty five minutes".
// The following forces it to read "Duration forty five minutes"
-(id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter {
return ([attribute isEqualToString:NSAccessibilityAttributedStringForRangeParameterizedAttribute])
? [[NSAttributedString alloc] initWithString:#"Duration: 45min"];
: [super accessibilityAttributeValue:attribute forParameter:parameter];
}
I believe I've answered my own question, at least enough to control what's read off when clicking a table cell view.
By overriding accessibilityIsIgnored to return "NO" in my NSTableCellView sub-class I was able to specify exactly what I wanted to be read off for the table cell by overriding the NSAccessibilityTitleAttribute for the cell. I had not tried this before b/c I had misunderstood the purpose of the accessibilityIsIgnored selector.
From the documentation for accessibilityIsIgnored:
When asking for an object’s children, ignored children should not be included; instead, the ignored children should be replaced by their own unignored children.
I'd like to be able to control exactly what is read off for the individual NSTextFields in the future, but controlling what is read off for an entire NSTableCellView is actually ideal for my particular situation.

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.

Popup window similar to Xcode's code completion?

I would like to build something similar to the code-completion feature in Xcode 4. (The visual style and behavior, not the data structure type work required for code completion).
As the user is typing, a pop-up window presents other word choices that can be selected.
The Feature in action:
I'm not exactly sure where to start. I am mainly concerned with the visual appearance of the window and how I should populate the list with a given set of words. Later I will get into making the window follow the cursor around the screen and etc.
I am mainly looking for an overview of how to display such data in a "window", and how to cusomize the appearance of the thing so it looks like a nice little informational popup rather than a full-on OS X window.
Just add a subview to your current view that happens to be a tableview. Programmatically cause it to be visible on an event (such as mouseDown), and adjust it's position based on where you want it. You will need to instantiate the proper delegate/datasource methods, but it should be pretty straight forward. You will also need a source for the words you want to use in your autocomplete, and put them in an array or something for your tableview datasource to go through.
Like I said, it's not terribly hard, provided you are comfortable using tableviews and adding views to your existing view. If this doesn't explain enough, leave a comment and I can flesh this comment out more.
Add a (completions) subview to your view and set its visible property to NO. Create a separate AutoComplete object that includes the subview as a property and fills it with potential completions. Your controller can respond to key pressed (key typed) events and give the last word (substring the text from the end to the 1st preceding blank) to the AutoComplete on each event. The basic logic in AutoComplete could be something like:
Given an AutoComplete with a list of known words "dog, spaghetti, minute, horse, spare, speed"
When asked to complete a fragment "sp"
Then the following words should be offered as potential completions, "spaghetti, spare, speed"
Which would imply that you need to instantiate it with a list of words (this could be done in the init of your controller), and create a method "-(NSArray*) completeFragment:(NSString*)fragment;". You could drop this into an OCUnit test case and iterate over your implementation of autocomplete until you get it right. You would then write a method that takes the completions from AutoComplete and lists them in the subview. Even better you might create currentWord and potentialCompletions properties in AutoComplete that gets updated as you send it "newFragment:(NSString*)fragment;" messages. Throw that into OCUnit and work it out then use that potentialCompletions property to drive updated of a the subview (which is probably best modeled as a custom tableView).

How to get highlighted text from my NSTextField?

It must be certainly easy but I haven't found anything in the doc.
The behavior I need is exactly as when you right-click any portion of text and then you can do some action with it.
For the moment I have a my own custom NSTextField class which re-implment 'mouseDown' action. This part works ! I thought I could get the selected portion of text of my nstextfield thanks to 'theEvent' but apparently it is not possible.
I don't actually think you can do this with NSTextField. I think you have to use an NSTextView instead and then make use of the -selectedRanges method.
EDIT: I should have said, you can't do this directly (i.e., there's no NSTextField method for doing this). I think rather, you have to use the field editor (which is itself an NSTextView) associated with the window in order to do this. Here's the apple guide for using the field editor.
NSString *selectedText = ((NSTextFieldCell*)textField.selectedCell).stringValue;

Best way to capture key events in NSTextView?

I'm slowly learning Objective-C and Cocoa, and the only way I see so far to capture key events in Text Views is to use delegation, but I'm having trouble finding useful documentation and examples on how to implement such a solution. Can anyone point me in the right direction or supply some first-hand help?
Generally, the way you implement it is simply to add the required function to your view's controller, and set its delegate. For example, if you want code to run when the view loads, you just delegate your view to the controller, and implement the awakeFromNib function.
So, to detect a key press in a text view, make sure your controller is the text view's delegate, and then implement this:
- (void)keyUp:(NSEvent *)theEvent
Note that this is an inherited NSResponder method, not a NSTextView method.
Just a tip for syntax highlighting:
Don't highlight the whole text view at once - it's very slow. Also don't highlight the last edited text using -editedRange - it's very slow too if the user pastes a large body of text into the text view.
Instead you need to highlight the visible text which is done like this:
NSRect visibleRect = [[[textView enclosingScrollView] contentView] documentVisibleRect];
NSRange visibleRange = [[textView layoutManager] glyphRangeForBoundingRect:visibleRect inTextContainer:[textView textContainer]];
Then you feed visibleRange to your highlighting code.
It's important to tell us what you're really trying to accomplish — the higher-level goal that you think capturing key events in an NSTextView will address.
For example, when someone asks me how to capture key events in an NSTextField what they really want to know is how to validate input in the field. That's done by setting the field's formatter to an instance of NSFormatter (whether one of the formatters included in Cocoa or a custom one), not by processing keystrokes directly.
So given that example, what are you really trying to accomplish?
I've done some hard digging, and I did find an answer to my own question. I'll get at it below, but thanks to the two fellas who replied. I think that Stack Overflow is a fantastic site already--I hope more Mac developers find their way in once the beta is over--this could be a great resource for other developers looking to transition to the platform.
So, I did, as suggested by Danny, find my answer in delegation. What I didn't understand from Danny's post was that there are a set of delegate-enabled methods in the delegating object, and that the delegate must implement said events. And so for a TextView, I was able to find the method textDidChange, which accomplished what I wanted in an even better way than simply capturing key presses would have done. So if I implement this in my controller:
- (void)textDidChange:(NSNotification *)aNotification;
I can respond to the text being edited. There are, of course, other methods available, and I'm excited to play with them, because I know I'll learn a whole lot as I do. Thanks again, guys.