Multiple text views

Yes I did. You can actually set newRange to the selected word with the following code:

set wrValue to current application's NSValue's valueWithRange_(wordRange)
	set newRange to current application's NSArray's arrayWithObject_(wrValue)

Then it will actually select the word, but if you try to type while the word is selected you get crazy results which eventually cause the app to crash (I don’t know if you would ever try to type while the word was selected, but a user might try that and get into trouble). So, I think it’s best to return newRange unaltered.

The reason you have to use valueWithRange, is that despite what the docs say, the return value of textView_willChangeSelectionFromCharacterRanges_toCharacterRanges_ is actually an array of NSConcreteValues not NSRanges – I’m not sure why that is, but if you log newRange’s objectAtIndex_(0)'s |class|() you get NSConcreteValue. That’s why if you try to use “newRange’s |location|” you get an error. By using rangeValue(), it turns the NSConcreteValue into a range (which is a C-struct).

Ric

Ric,

To come back to the subject of this post, I confess that the idea of multiple text views is a trick which, effectively, gives more problems than it resolves.

My problem was more : “what happens when the versions are switched” than making the switching the most IB-driven possible.

When you have text view’s contents connected to a property (say myRecord.longText) and you switch to myRecord.shortText, I thought in terms of ResourceManager’s handle (sorry, that was my reference for years) pointing to the textedit charsHandle: two references to the same block of memory.

I’m not sure I can think in these terms with Cocoa and its binding-by-name mechanism. Is “saving a copy” to the switched version really necessary? Or is my myRecord.longText permanently updated when I type something in the editor?

If yes, I have the choice between switching by filling the text editor’s textContents with myRecord.shortText (let’s name this “setting approach”) or changing the Key Path Value of the text view (let’s name this “keying approach” – it would be my preferred one).

But is it the case? When I tried the first time, I remember I had troubles. But maybe I’ve been (as usual) unprecise in my code.

Regards

No, I don’t think it’s permanently updated after you type something --that changes the content of the textStorage but your dictionary is still the same. You have to update your dictionary to reflect the changes you made, and that’s what I did in the code I posted near the top of this thread.

I’m not sure I understand the difference here. The way I did it changes the key path, and that’s what makes sense to my way of thinking. But what that does is to fill the text view with the contents of another attributed string, so that seems like what you are calling the setting approach – so, I don’t really see these as two different approaches. Maybe I just don’t understand what you’re thinking

Ric

After Edit: After thinking about this some more, I don’t really like the idea of changing the bindings every time you click the switch button – this kind of defeats the purpose of bindings. Bindings are supposed to make things happen automatically behind the scenes, so setting them repeatedly just seems wrong. If you get away from bindings, then I think it’s easier to use an array rather than a dictionary, because you can swap the 2 items easily, so this is the method I prefer for its simplicity and directness:

on switch_(sender) --IBAction for an NSButton of the "switch" type
		theArray's replaceObjectAtIndex_withObject_(0, tv's textStorage()'s |copy|()) --copies the new data to the array
		theArray's exchangeObjectAtIndex_withObjectAtIndex_(0, 1) -- swaps the 2 array items
		tv's textStorage()'s setAttributedString_(theArray's objectAtIndex_(0)) --sets the contents of the text view to the new first array element
	end switch_

Hello Ric,
Not at my computer now, I read and write on my iPhone, so excuse any error and approximate method names…

On a small test app I made when exploring this twin text view idea, it seemed to work for each tv independantly – for instance I didn’t have to “save” my dictionnary entry bound to the text view. I loaded a file, typed my text, saved to the file and the tv’s content was perfectly and immediately dumped on the persistant document (I have quit and re-launched the app to be sure).

So couldn’t it be simply:

property switchVersions {"longText","shortText"}
...
on switch_(sender)
set n to sender's value()
my tv's <replaceWholeTextWith>_(myRecord's valueForKey_(item n-1 of switchVersion))
end

Hm. Not so easy with such a “keyboard”…

Regards

I can’t tell from that small code snippet whether it would work or not, I would have to see how the data is put into the text view (and what’s myRecord??).

Ric

After edit: I got to thinking about this problem, and why the bound strings weren’t being updated when I switched the bindings – it seems like it should have worked. Bindings go both ways, so when you change the text in the text view, it should change the string that it’s bound to, but that wasn’t happening. After some experimenting with text fields instead of views, I think the reason is that the text view was never resigning first responder status, and thus never saying " I’m done editing, go ahead and update my bound property". So, one way I found that worked was to use the window method makeFirstResponder: to change the first responder to the window itself, and then the new values were updated in the dictionary.

But, even easier was just to check the “Continuously Updates Value” check box in the bindings pane, and that causes the bound property to be updated continuously. So, this little program worked to keep the dictionary in sync with the text view with a minimum of code (the text views attributed string is originally bound in IB to theDict.key0):

script TextPersistenceAppDelegate
	property parent : class "NSObject"
	property theDict : missing value
	property tv : missing value --IBOutlet for the text view
	property switcher : 0
	
	on applicationWillFinishLaunching_(aNotification)
		set longText to ""
		set shortText to ""
		setTheDict_(current application's NSMutableDictionary's dictionaryWithObjectsAndKeys_(longText, "key0", shortText, "key1", missing value))
	end applicationWillFinishLaunching_
	
	on switchTexts_(sender) --IBAction for a button
		set switcher to 1 - switcher
		set theKey to "theDict.key" & switcher
		tv's bind_toObject_withKeyPath_options_("attributedString", me, theKey, missing value)
		log theDict
	end switchTexts_
end script

Philosophically, I’m still not sure whether I like repeatedly setting the binding like this, but it does work, so either this way or the way I posted above are possible ways to accomplish this.

Ric

Let’s choose the right philosophy then. How about “What’s does not kill my program makes it stronger”? :slight_smile:

I’m back to computer, I’ll try your solution(s)

Regards

EDIT :

    on switchVersion_(sender)
        if switcher as integer is 0 then
            my theTextView's bind_toObject_withKeyPath_options_("data", me, "gCurrentCard.cShortText", missing value)
        else
           my theTextView's bind_toObject_withKeyPath_options_("data", me, "gCurrentCard.cLongText", missing value)
        end if
    end
  1. As all my files have been saved when “data” was bound to the NSDictionary’s key, I just adjusted the name of the binding (otherwise I get very bad messages.)

  2. And. coding for coding, I made the change of binding totally explicite, rewriting the whole line twice. Sometimes writing more is less.

OK, back to a unique textView, I’m feeling better – for me changing the binding is philosophically acceptable, because if Cocoa has provided such a (documented) mechanism, it here to be used. :wink:

Well. Every seems to be OK except:

I’m facing again this problem of “default dictionary”. There is no problem to open, modify and dump an existing file, but when you create a new file, there is no defined dictionary, so no keys, even empty, are written to the file.

That’s why (in another post) I was wondering if it shouldn’t be defined, in properties, a “blank record”, to write the keys (with empty values) into the file before reading them again. In my app, there should be, in order:

  1. User creates a new file, giving it a name;
  2. An empty file is created in disk;
    3) A blank record is written into it, with all possible keys;
    4) This record is read and displayed on the editor.

I tried all other manners, they have failed. A NSDictionary must, one moment or another, be keyed, even empty, for the bindings to work.

Why even have a dictionary? Why not just bind directly to cShortText or cLongText? This works for me:

on switch_(sender) --IBAction for an NSButton of the "switch" type (title:"Switch to Short Text", Alt. title:"Switch to Long Text") 
		if sender's |state|() is 1 then
			tv's bind_toObject_withKeyPath_options_("attributedString", me, "cShortText", missing value)
		else
			tv's bind_toObject_withKeyPath_options_("attributedString", me, "cLongText", missing value)
		end if
	end switch_

Ric

I’m lost.

In that case, what will be written/read from file? In the current state, when there is no dictionary (filled with a file) the contents of the text view are NOT retained if I switch between versions. If there is a keyed dictionary, everything if fine.

Now, imagine that, for commodity reasons (as linkage control), I abandon this idea of reading/writing one file at a time, and wanted to load not only (as now) an array containing references to files, but an array of files contents as NSDictionaries (as if they were “pages” of a unique document)?

Having a single item like a dictionary, rather then 2 strings would make it easier to save. So why not do it like my last post from page 1 of this thread? To save the dictionary you would have to archive it, and then unarchive it when you read it back in – that should work.

Ric

And is it possible to do it this way:

property cRecord : {cName:missing value,cLongText:missing value,cShortText:missing value,cIcon:missing value,cOtherStuff:missing value}
.
setTheDict_(current application's NSMutableDictionary's dictionaryWithdictionary_(cRecord))

? (that’s just to have the structure clearly defined at the beginning of my code, a bit like (pardon me):

type
myHandle = ^myPtr;
myPtr = ^myRecord;
myRecord = record
cName: str255;
cLongText: charsHandle;
cShortText: charsHandle;
cIcon: picHandle;
cOtherStuff: someHandle;
end;

Eeeek. this is REALLY offtopic. but I think you get the idea. :wink:

Regards

Ok, this works ” now for the tricky part.

By now, the editor have a “current card” (*card" is the name of one of my dictionaries), which is defined each time a new card is accessed. BTW, all “cards” are now read and placed on an NSMutableArray.

I can already set much things on editor directly bound to the array controller, such as ID, icon, sound. Now for the “switch” part.

Everything works fine so far because the switcher says :

my theTextView's bind_toObject_withKeyPath_options_("data", me, "gCurrentCard.cLongText", missing value)

but now I want to switch via the Controller, as there is always a dictionary selected (the current card). But how do I bind

The cardReference is the NSDict. I can extract what I want from it without problem, but there is a parameter missing: If I want to bind the text via the array controller, I have to give “arranged objects” or “selection”. How can I precise that I want the selection?

I forgot. a big thank you for all your patience and kindness!

Regards,

It’s “selection.cRefCard.cShortText”!

Ric, you inspired me :wink:

Edit : my code is simply melting. Incredible, this has nothing more to do with procedural programming.

And. back again, with the inverse problem: how can I retrieve the textView’s string, so superbly coded?

The textView is binded to the array controller’s selected item : a card reference {cID:theID,cRefCard:cRecord} , and the cRecord contains {., cLongText:, cShortText:,.}

I want my text back! And to put it into a raw string:

        set theString to theCard's cRefCard's cLongText's data as string -- nope
        set theString to theCard's cRefCard's cLongText's data's textStorage() as string -- nope
        set theString to theCard's cRefCard's cLongText's data's textStorage()'s |string|() -- nope

sob< :frowning:


How was the text view’s contents encoded in the first place, and what is theCard? It’s really hard to tell what your data structure is now – it seems like you keep multiplying the layers. You need to provide more complete information if you want help.

Ric

Hello Ric, sorry to be so confusing, sometimes I can even confuse myself…

The text is bound by data to the arrayController, by selection.cRefCard.cShortText. The list controlled by the arrCtrler contains records : {cID (an integer ID, the identifier of the file) and cRefCard (the NSDictionary extracted from the file, containing the texts, icon, etc.)}. Then it is saved to file, using NSKeyedArchiever, under the form of a record.

It could effectively be more simple to have an array containing pure NSDictionaries, I confess. To have direct access to IDs, and to have a sorted list, I have added (certainly useless) complexity. My tangents, as Shane says. And yet my program is much simplier that it was on beginning – even if simplicity is not my first choice…

But so far the app is running well: I open files create some records, delete others, edit, save etc, very fastly and smoothly. But maybe too easily. So how are my file encoded?

When a new “card” is created, I put in the list (ruled by the ArrCtrler) a new reference record, as described below. The text (a keyed missing value) is bound to the data parameter of the tv (should have been AttrStrung but I read your post too late). The key is given above and it works fine.

When I want to extract the atring of the texts, I tried the accesses listed above but I get error (the NSConcreteData does not understant the “string” message, something like that, I can’t say more because I’m on the iPhone again).

I hope it will help – curiousely, the simpler I think my app to be, the more complexe it seems to you (and Shane, too). It’s certainly the sign I’m nor going to the right direction…

Regards

Sorry, but I’m still lost in all your records, lists and dictionaries. I still don’t know what a card is. You need to post some code, not explanations. I would need to see the code that creates a card, the code that saves whatever it is you’re saving to file, and how you read it back in. The overall structure of your data is still a mystery to me.

Ric

– DATA DIVISION.

property cRecord : {cName:“”,cLongText:missing value,cShortText:missing value,cIcon:missing value,cSound:missing value,cMessage:missing value,cScript:missing value}
property gCardList : {} – The navigation array, storing the card IDs (declared as a AS list, because when the controller awakes from nib it cannot refer to a missing value}
property gCardController : missing value – The card list controller


– PROCEDURE DIVISION.

on loadFolderContents()
try
tell application “Finder” to set aList to name of files in gCurrentFolder whose name extension is “atxt”
repeat with theFile in aList
set globalData to current application’s NSKeyedUnarchiver’s unarchiveObjectWithFile_(theFile)
set aInt to (first word of theFile as string)as integer – this works because digits/alpha are 2 words
set cardReference to {cID: aInt,cRefCard:globalData}
gCardController’s addObject_(cardReference)
end repeat
on error – a folder must contain at least one card, create a new card with ID 1
createNewCard(1)
end try
end

on createNewCard(withID)
    set theFile to withID as string & ".atxt"
    tell application "Finder" to make new file at gCurrentFolder with properties {name:theFile, creator type:"ATXX"}
    set cName of cRecord to gProposedCardName
    current application's NSKeyedArchiver's archiveRootObject_toFile_(cRecord, theFile)
    my gCardController's addObject_({cID:withID,cRefCard: cRecord})
end

on dumpFolderContents()
    repeat with theRef in gCardList
        current application's NSKeyedArchiver's archiveRootObject_toFile_(cRefCard of theRef, (cID of theRef) as string & ".atxt")
    end repeat
end

– STOP RUN.

That’s all. What is selection, modification, deletion, etc., is completely done in the interface. As records and NSDictionaries are equivalent, I don’t coerce them (same thing for lists and NSArrays).

Hope this helps.

Regards,

In your dumpFolderContents() method, you save records out of gCardList, but you define gCardList as an empty record, and I don’t see any place where you put anything into it.

Also, do have the array controller’s content array set to anything in the bindings pane (or are you just filling that content array in code with gCardController’s addObject_(cardReference)) ?

Ric

Hello Ric,

The gCardList is the array managed by the gCardController. So instead of writing :

my gCardController’s addObject_({cID:withID,cRefCard: cRecord})

I could have written :

set my gCardList to gCardList & {cID:withID,cRefCard: cRecord}

It’s just I prefer to tell the controller than its list.

And of course, you can’t see anything in code because all the filling is made via bindings. The current card, i.e. the cRecord of the current cardReference (the one selected in the list) is filled by the user thanks to the different interface elements, for example:

  • the two texts are filled via the text view, bound by value (selection.cRefCard.LongText/cShortText),
  • the icon is set by copy&paste or drag&drop into an image view, bound by value (selection.cRefCard.cIcon),
  • etc.

When the user has finished, he saves the folder contents and everything is stored on disk. Once again, it works.

Maybe there is a layer of useless complication by defining, as a new card is created, an empty record which is stored into the newly created file – it’s just to have keys inside it, because if I don’t, and dump an empty card, when I try to read it, the NSKeyedUnarchiver says that “the data is empty, are you sure you have called finishEncoding() after writing to the file?”

So, instead of having only defined keys in the file:

I have:

Is it really that bad?

What I could do to simplify a bit, would be to add a cID field into the cRecord, so I could simply make:

set globalData to current application's NSKeyedUnarchiver's unarchiveObjectWithFile_(theFile)
                gCardController's addObject_(globalData)

If I put this cID field into the first column of the tableView, it should be ascending arranged as well.

If you have further questions, ask them, because explaining things make me understanding them better, too.

Regards,