Multiple text views

Hello!

I have more than one text view in a window, and I have to know what text to check for syntax. I didn’t find a way to determinate which text is “active” (i.e. visible and enabled).

I suppose it could be the responder, but what if I clicked on an other control (say a text field) meanwhile?

As only one textview is visible, I think I could also check the Hidden attribute (if false, then I get the good one).

I just wanted to know if it was stored somewhere, so I don’t have to test for visibility.

Regards

The first question I have is why do you have 2 text views? Do you really have need for 2, or do you only have 2 because of the binding to your switch button that you mentioned in the post about how far you can go with properties in IB? If that’s the only reason, I think you’re making things more complicated than they need to be to avoid a couple of lines of code.

Ric

I know, Ric, I’ve always been so, making what could be simple very complicated :slight_smile:

What with my two keys for a single text view? You think it’s better to:

  • cut off key bindings for the data of the text view;
  • put the desired text content according to the value of the switch button;
  • implement a handler to watch when the button is clicked;
  • recover the current text content from the text view;
  • install the new text view and scroll to the top?

so I can effectively have a single text view. But are things really simpler?

I’ll not avoid a bit of coding. IB is powerful, but in some cases.

What about a

if gLongTextView's hidden() then
    get theShortTextContents
else
    get theLongTextContents
end if

because, anyway, the syntax control does not occur at each typing.

Regards

I was able to do it like this. The text view’s attributed string is originally bound to theDict.longText, and then I change that in code in the switch method. By setting the dictionary’s value for a key to “tv’s textStorage()'s |copy|()”, I update the value of that key to whatever is in the text view at the time you click the button. I’m not quite sure why you need the “copy()” in there – maybe because there is only one textStorage object for the text view. I just know that it resets the value of the key to an empty string if you don’t use it.

script TextViewTestAppDelegate
	property parent : class "NSObject"
	property tv : missing value --IBOutlet for the text view
	property theDict : missing value
	
	on applicationWillFinishLaunching_(aNotification)
		set theShortText to current application's NSMutableAttributedString's alloc()'s initWithString_("The short text")
		set theLongText to current application's NSMutableAttributedString's alloc()'s initWithString_("This is my long text with some extra typing")
		setTheDict_(current application's NSMutableDictionary's dictionaryWithObjectsAndKeys_(theLongText, "longText", theShortText, "shortText", missing value))
	end applicationWillFinishLaunching_
	
	on switch_(sender) --IBAction for an NSButton of the "switch" type
		theDict's setObject_forKey_(tv's textStorage()'s |copy|(), theDict's allKeys()'s objectAtIndex_(1 - (sender's |state|())))
		set theKey to current application's NSString's stringWithString_("theDict." & theDict's allKeys()'s objectAtIndex_(sender's |state|()))
		tv's bind_toObject_withKeyPath_options_("attributedString", me, theKey, missing value)
	end switch_
	
end script

Ric

Wow wow wow… Ric, you must let me some time to understand what you are doing.

You seem to change the initial settings of IB by code. That’s new for me. I’ll look at this later (it 03:15 AM here).

Meanwhile, my app has some progress to do, like selecting a word in the text as if it was an URL. Your method works, but the trick should be that “fields” are invisible, to force the user to read the text instead of just clicking when the cursor changes to a pointing hand.

See you “tomorrow”.

Regards

Well, that’s very easy to fix – in fact it takes extra work to make the link look different and have a different pointer, so all you need to do is delete the default link attributes (blue underlined text), and then add the link attribute to your text.

Ric

Hello again Ric (I suppose it’s night time for you now)

Your solution may be the right one if the creator of the text knew in advance what the contents of the text will be. He could define:

From here you may go to the castle [33] or to the village [186].

and define “33” and “186” as «URLs», even if not visually marked.

But the way my application is designed, there are some problems:

  • What if he changes the structure of its links, deciding it’s better for the castle to have number “133”?
  • What if the user click on a non-URL word? I want the app to detect, for example, “castle” and say something like «I don’t understand the word “castle”.»
  • What about the “objects” I mentioned in a previous post? If an object, by its settings, is supposed to be in the place described in the text, it must insert a sentence mentioning his presence, as “there is a bottle on the ground.” The word “bottle” becomes a URL…

That’s why I thought it would be more simple to “simulate” a double-click in the text (or, at last, extend the selection to the entire word) when the user clicks in the text. I get this word and check it so:

-valid integer ? YES → inside field delimiters ? YES → valid ID ? → YES → go to this ID

                                                                                      --> NO --> error "This ID doesn't exists"

                                                                NO --> say «I don't understand this word»

                   NO --> object name ? YES --> do something with the object

                                                 NO --> say «I don't understand this word»

I hope this makes things more clear.

Note for the administrators : It’s difficult to stay in the scope of a particular topic, especially if you’re talking about the same complex application with different people. I’m sorry for breaking the rules.

Meanwhile.

I found this method: doubleClickAtIndex, but it’s a NSAttributedString’s method, not a NSTextView’s.

I could use it on the textStorage of the text view (initializing the NSAttributedString with it). The doubleClickAtIndex returns the range of a word in which a click occurs. Sounds good.

It’s a bit tricky because I’ll have to:

  1. get the NSRange.location of the newRange of textView_willChangeSelectionFromCharacterRanges_toCharacterRanges_(aTextView, oldRange, newRange)
  2. get the chars of the text, pass it to the NSAttributedString
  3. pass my NSRange.location to the doubleClickAtIndex method and get the range of the word
  4. pass it as a return argument of willChangeSelectionFromCharacterRanges.

At the moment I’m struggling with these NSRanges. and I just get error messages. What are they made of and how to change them?. It’s not a good day.:confused:

What kind of error messages, and under what conditions do you get them?

It wasn’t clear to me from your post why you want to simulate a double click in the text. Do you want things to happen when a user just clicks in the text, or would it be ok to require the user to double click in the text to initiate actions in the program? If it’s ok to require double clicks, then it is very easy to get the word the user clicked on. The whole word is automatically selected when you double click in a text view, and that word can be extracted with the following code:

on textView_willChangeSelectionFromCharacterRanges_toCharacterRanges_(aTextView, oldRange, newRange)
		if newRange's objectAtIndex_(0)'s rangeValue()'s |length| is not 0 then
			log tv's attributedSubstringFromRange_(newRange's objectAtIndex_(0)'s rangeValue())--'s |string|()
		end if
		return newRange
	end textView_willChangeSelectionFromCharacterRanges_toCharacterRanges_

The way it’s written above, you get both the string and any attributes attached to it, so if you wanted to use links, you could get them here (or you could attach any kind of object to the text if you want to). If you delete the “–” above, then it will return just the word that the user double clicked on.

Ric

After Edit: If you need to get the word when a user just single clicks in the text view, this seems to work:

on textView_willChangeSelectionFromCharacterRanges_toCharacterRanges_(aTextView, oldRange, newRange)
		if newRange's objectAtIndex_(0)'s rangeValue()'s |location| is less than tv's textStorage()'s |length|() then
			set wordRange to tv's textStorage()'s doubleClickAtIndex_(newRange's objectAtIndex_(0)'s rangeValue()'s |location|)
			log tv's attributedSubstringFromRange_(wordRange)'s |string|()
		end if
		return newRange
	end textView_willChangeSelectionFromCharacterRanges_toCharacterRanges_

The if statement is needed to keep from getting error messages if you try to select the end of the range. If you don’t have it, you can’t add any new text to the end, because it won’t let you select there.

Hello Ric!

newRange’s objectAtIndex_(0)'s rangeValue()'s |location|. wow indeed! I missed this “rangeValue”, that was the point.

Thank you very much, I don’t have been at my computer for hours, but you surely spare me these hours AT my computer !

Your code do a great part of what I wanted – the rest is to modify “newRange” to return the selection corresponding to the word. It could not be so hard.

I’m sorry to fill these posts with so much considerations, but sometimes the answers you (and others) give me are not pertinent because I didn’t supply enough information. To say it briefly, I balance between exposing the whole concept of my application (which could get me the wrath of the administrators) and expose only a small point (but isolate from the rest, so when it is resolved it can be tedious to re-insert this piece of code).

Nevertheless, once again you helped me so much (and furnished code for future applications), so I’m grateful!

Regards

I don’t know what you mean by that – when you double click on a word, newRange (actually objectAtIndex_(0) of newRange to be precise) is the range of that word.

Ric

That is, if I double-click! If I don’t, the selection stays a blinking insertion caret. Your code is perfect as it returns the whole “selected” word, I want the textView to reflect this selection extension (in selecting the word).

I’m sure that can be done, but I’m not sure that modifying newRange is the way to do it.

Also, though you could do this, I think it would violate the Apple Human Interface Guidelines – a user expects that a single click in a text area will put the cursor there not select the word (unless it’s a link that is marked somehow with color or underlining). There’s nothing that stops you from violating these guidelines, but if you want the app to have the look and feel of a traditional Mac app, then you should try to stick to them.

Ric

Hi Ric,

My app will follow the Apple Human Interface Guidelines for all the creation/edition of the game: it allows the user to use my app like a standard text editor.

When the file is in Use mode, it follows its own rules (a game rarely follows the Apple Human Interface Guidelines!)

In fact I was wrong, this NSRange is very resistant. If I try to touch to this array, I always fall in errors (invalid selectors, not key-value compliance and so on. No way to

set newRange's objectAtIndex_(0) to wordRange

it would be too fine.

And if I use setSelectedRange_ into the handler, of course I enter a recursive loop and bangs to top of the stack.

Rats, curses, darn. :confused:

The only one trick I found on the Internet was in ObjC:

Is it a better idea to fake an AppleEvent rather than fiddle the text view selection?

As I said above, I don’t think you want to do this by modifying newRange. I modified the code I posted above to add another method that deals with the chosen word – in this example it just logs it, but you could do whatever with it. In that method I added a line to highlight the background just like a double click would. Also at the top of the textView_willChangeSelectionFromCharacterRanges_toCharacterRanges_ method, I added a few lines to take away that background highlighting when you change the selection.

on textView_willChangeSelectionFromCharacterRanges_toCharacterRanges_(aTextView, oldRange, newRange)
		if wordRange is not missing value then
			tv's textStorage()'s removeAttribute_range_(current application's NSBackgroundColorAttributeName, wordRange)
		end if
		if newRange's objectAtIndex_(0)'s rangeValue()'s |location| is less than tv's textStorage()'s |length|() then
			set wordRange to tv's textStorage()'s doubleClickAtIndex_(newRange's objectAtIndex_(0)'s rangeValue()'s |location|)
			set theWord to tv's attributedSubstringFromRange_(wordRange)'s |string|()
			performSelector_withObject_("doSomething:", theWord)
		end if
		return newRange
	end textView_willChangeSelectionFromCharacterRanges_toCharacterRanges_
	
	on doSomething_(inWord)
		tv's textStorage()'s addAttribute_value_range_(current application's NSBackgroundColorAttributeName, current application's NSColor's selectedTextBackgroundColor(), wordRange)
		log inWord
	end doSomething_

You also need to add “property wordRange:missing value” to the code.

Ric

Yes it works! Seems a bit curious to see the insertion point blinking inside a selection, but it works…

Tell me, this performSelector_withObject_, is it executed before or after the return statement?


[i]Back to Pascal ToolBox times ('90), my app was an editor that used massively textEdit and the ResourceManager routines to make its way through indexed resources. But the discovery of Cocoa has made me reconsider the whole thing: in this new MVC approach, the NSMutableArray becomes the vertebral column of the application, the “editor” is just the visual part of an array controller.

By progressing (thanks to you and other fine connoisseurs of the underlying frameworks) I’ll surely make my code smaller and smoother. I’ve learned more during the past month than in several years of “Inside Macintosh”.
[/i]
Thank you so much!

Before, just like any time you put a call to a method inside another method.

Ric

But this avoids recursivity because you fake a selection, you don’t change it. Subtle. :cool: