Controlling Outline Views--bewildering

I cant find any good references on using many of the components in Applescript studio. As an example consider Outline Views.

If you open up the applescript studio example called “outline” you find an astonishly short example of how to have a outline build itself. it consists of just 6 subroutines.

On launched
on number of items
on child of items
on item expandanble
on item value
on will open

the amazing thing it the the whole table builds itself when the “update” method is called.
Apparently, I’m guessing, update must call “number of items” and “item expandable” and “child of items” in some way that lets it build the table. It discovers the number of rows, fetches their column contents, and figures out if they are exandable, and repsponds to opening the hierarchy by quering the finder just in time for new data to display.

An that brings me to my question. How in the heck am I supposed to know what happens when updat gets called on the Outline? How would I know which of the many event handlers it calls in turn to build up the table and in what order?

Is there some magic book I’m supposed to have read? All I can find are these examples. Sure you can read the API but that just defines the event names but not how they get called during an update event. I’ve looked in the programming ruide and the lanfuage reference. NADA.

What book am I missing? How am I supposed to know this?

Hi cems2,

In the studioreference.pdf:

http://developer.apple.com/documentation/AppleScript/Reference/StudioReference/

look under the ‘data item’ class for a good simple example of making an outline.

gl,

I looked at the data item in the refernce manual
http://developer.apple.com/documentation/AppleScript/Reference/StudioReference/sr6_data_suite/chapter_6_section_8.html
But it doesn’t answer my question. This describes a laborious process of defining the contents of an outline. What I am asking about is the update event which allows an outline to define itself with calls to events that gather the data it needs.

Hi cems2,

I don’t know if my version of example “Outline” is the same as yours.

What I did to understand what is happening is log the return values. It seems that the handlers other than the launched handler are called one time before the outline view is created, i.e. before the script gets the disk names. Here’s how I logged the return values:

property diskNames : {}

(* ==== Event Handlers ==== *)

on launched theObject
tell application “Finder”
set diskNames to name of every disk
end tell

tell outline view "outline" of scroll view "scroll" of window "main" to update

end launched

on number of items theObject outline item theItem
set itemCount to 0

tell application "Finder"
	if (count of diskNames) > 0 then
		if theItem is 0 then
			set itemCount to count of diskNames
		else
			set itemCount to count of items of (get item theItem)
		end if
	end if
end tell
log "NUM ITEMS"
log itemCount
return itemCount

end number of items

on child of item theObject outline item theItem child theChild
set childItem to “”

tell application "Finder"
	if theItem is 0 then
		set childItem to disk (get item theChild of diskNames as string) as string
	else
		set childItem to item theChild of (get item theItem) as string
	end if
end tell
log childItem
return childItem

end child of item

on item expandable theObject outline item theItem
set isExpandable to false

if theItem is 0 then
	if (count of diskNames) is greater than 1 then
		set isExpandable to true
	end if
else
	tell application "Finder"
		if (count of items of (get item theItem)) is greater than 1 then
			set isExpandable to true
		end if
	end tell
end if
log isExpandable
return isExpandable

end item expandable

on item value theObject outline item theItem table column theColumn
set itemValue to “”

if the identifier of theColumn is "name" then
	tell application "Finder"
		set itemValue to displayed name of (get item theItem) as string
	end tell
else if the identifier of theColumn is "date" then
	tell application "Finder"
		set itemValue to modification date of (get item theItem) as string
	end tell
else if the identifier of theColumn is "kind" then
	tell application "Finder"
		set itemValue to kind of (get item theItem) as string
	end tell
end if
log itemValue
return itemValue

end item value

on will open theObject
tell application “Finder”
set diskNames to name of every disk
end tell

tell outline view "outline" of scroll view "scroll" of window "main" to update

end will open

Look for results in the run window. I’m not sure what the will open handler is used for. I don’t understand the whole process yet, but think I will.

BTW, I’m getting an error if only the STartup disk is present. Are you?

Edit: one important thing I found is that the number of items handler is essential. Note that its first returned value is 0.

Later,

Kel, thank you for the tips. I too see that it gives an error if only one disk is present.
I’ll try to understand it using what you suggested. But I’m still belwidered as to how one is supposed to know what events need to be fired to build the table. When you expand tabs other event fire. I think “update” triggers a cascade of event that then build the table. But how would one know this?

Yes, this is true.

Simple, yet unfortunate answer… one wouldn’t. :frowning: Unless you’ve got some experience in obj-c and understand both how to read into the cocoa docs and how to follow and debug data objects on the cocoa level, you’d really have no way to know about this or even figure it out from just the applescript docs. In fact, if apple didn’t provide the pathetic single page of information about each of the commands associated with the outline view, I doubt anyone would be using it at all in ASS projects. There are some aspects of programming in ASS that are nearly a black art, and this, in my opinion, is one of them. The outline view handlers you’ve listed are very abstract, and take a while to fully understand. There is no book or website that covers this, and there’s little that the apple example’s or docs could tell you explicitly about outline views that would EVER help you understand them. Remember, that apple feels that the “preferred and far more efficient approach is to use a data source object.”… i.e. set up a data source and let the data source methods handle the building for you. Unfortunately, in all the talk of ‘preferred’ and ‘efficient’ methods, there’s no mention of this being easy, and managing data sources quickly becomes just as frustrating for some people. What’s more, once you stray from using an outline view to display anything by file hierarchies, getting code to process your data (presumably a list of lists) is tricky and painful.

To get a better understanding of what the ‘update’ command is for, you need to look into the cocoa end of it. Applescript is merely a gateway for executing cocoa methods of objects. Every view has a cocoa method which redraws the content of it’s view, enabling the programmer to reflect changes in the data in that view at will. The ‘update’ command is merely a convenience method provided to make this available to applescript studio users. As cems2 said, the update method does trigger a cascade of events, which appear to be in the order: “Number of items”,“child of item”,“item expandable”,“item value”. The reason for each of these is rooted in the cocoa process in which the outline view is drawn. If you think about each, it should become apparent WHY each is required. In fact, it’s actually kind of amazing that by simply providing 4 ways to gather data about your list, it will automatically create an infinitely deep tree for you. Once you grasp what each part is doing, you’ll be able to write the code to tackle your challenges.

What is it that you’re trying to display? If you’re trying to do anything but a file list, perhaps an outline view is not the tool for you. Apple seems to have built into the outline view code that it’s sole purpose is to handle file lists, so a lot of what the handlers return (for obvious reasons) relates to file lists. For example, in apple’s child of item handler docs

The child of item handler is the most confusing and abstract for me to understand, and although I see the reason for this to work this way it complicates things when you are not working with files, as it forces you to write a whole bunch of extremely complicated code to figure out where you are in a potentially infinite chain of nested items.

I actually just started messing with this a couple days ago, in trying to start work again on one of my old, forgotten projects. I’d like to try to work with my list directly, rather than indirectly through a data source as I had before. I have yet to detect the lack of efficiency apple claims regarding using this method. I’ve modified the handlers from the apple’s outline example project to display the contents of a folder, rather than of the entire computer. I have kept with using the finder to evaulate the items, which seems to be OK so far.

property diskNames : {}
property thePath : "/Path/To/Your/Root/Folder/"

on child of item theObject child theChild outline item outlineItem
	set childItem to ""
	try
		tell application "Finder"
			if outlineItem is 0 then
				set childItem to (get item theChild of diskNames as string) as string
			else
				set childItem to item theChild of (get item outlineItem) as string
			end if
		end tell
	end try
	return childItem
end child of item

on item expandable theObject outline item outlineItem
	set isExpandable to false
	try
		if outlineItem is 0 then
			if (count of diskNames) is greater than 1 then
				set isExpandable to true
			end if
		else
			tell application "Finder"
				if (count of items of (get item outlineItem)) is greater than 0 then
					set isExpandable to true
				else if kind of (get item outlineItem) is "folder" then
					set isExpandable to true
				end if
			end tell
		end if
	end try
	return isExpandable
end item expandable

on item value theObject table column tableColumn outline item outlineItem
	set itemValue to ""
	try
		if the identifier of tableColumn is "Item" then
			tell application "Finder"
				set itemValue to displayed name of (get item outlineItem) as string
			end tell
		end if
	end try
	return itemValue
end item value

on number of items theObject outline item outlineItem
	set itemCount to 0
	try
		tell application "Finder"
			if (count of diskNames) > 0 then
				if outlineItem is 0 then
					set itemCount to count of diskNames
				else
					set itemCount to count of items of (get item ((outlineItem as string) as alias))
				end if
			end if
		end tell
	end try
	return itemCount
end number of items

on will open theObject
	try
		tell application "Finder"
			
			set diskNames to (items of ((POSIX file thePath) as alias))
		end tell
	end try
	tell outline view "Outline" of scroll view "Scroll" of window "Window" to update
end will open

I’m not sure I understand what the error your seeing is when “only one disk is present.” I only have one disk and the example project works fine for me. Perhaps if it continues to be a problem you guys could elaborate. I agree with kel that using the ‘log’ feature is quite valuable in troubleshooting and evaulating how things work. By outputting a variable’s current state at different intervals, you can learn a lot about what handlers and routines are doing and where you might have strayed.

It merely serves to make sure that whenever the window is closed, and then reopened again, that a new version of the list is placed in the outline view before the window is drawn. It’s really just a way for apple to convey to you what code is needed to programmatically update the contents of the view immediately, should you need to.

Hope this helps,
j

Jubo, thank you so much. It really helps to know that I was not crazy and this thing is not documented. I felt like such a dufus thinking I was missing something.

Anyhow to get to my real issue.
What I have is a hierarchical list I want to display. As a bonus I like the idea of the expand/collapse feature to hide details.

For example, image a nested (threaded) list of mail messages. The first level is the message thread subject. Expand that and you get a list of the individual mails. the columns for this second level (sender, date, etc…) don’t have any meaning at the first level.

So one has a list of theads one can expand to see the contents of.

Am I supposed to fake this by inventing and parsing some sort of ficticious colon delimited hierarchy string just to satisfy the “child of” call?

Also could you elaborate how data source don’t require the same sort of understanding of the update cascade. Does the data source somehow intercept the user driven events (like an expand or collapse) and then handle that it self? If so then how does it know what to show (either it has the data statically or it has to dynamically get it, like in the case of the Finder. Somewhere there has to be a call to get that data. then there has to be some choice to delete it when it’s no longer on display.)

I’m still on the bewildered side, but at least I know I’m asking some of the right questions now.

Also why is there no decent book on ASS. This has been out there a long long time. Even apple’s own reference documents seem to show pictures from older xcodes so I can understand its a moving target. But the underlying discussion of how to work with the GUI objects and what all the event triggers are seems vacant. Even simple things like for example, how one accesses the scrollview inspector of a table object are not explained. (as near as I can tell, you have to click somewhere unused in the window to deselect the table, then click on the scroll bar area. Don’t click twice or you get the table itself and there’s no way to get back to just the scroll view inthe inspector. It’s bonkers)

What books do you think explain ASS best. (and what about applescript itself. Iv’e learned from the O’reily book and I think it fails to really teach one the elements of the language. I keep ending up doing things in perl or python out of frustration. And others books seem to leave out so much.

Hi Jobu,

Thanks for the explainations about cocoa, object c, and Apple’s hint about how to handle windows.

cems2, about the error when the only disk is the startup disk. In the launced handler:

on launched theObject
tell application “Finder”
set diskNames to (name of every disk) as list
end tell
– log diskNames
tell outline view “outline” of scroll view “scroll” of window “main” to update
end launched

“name of every disk” returns a string (name of the startup disk). For compatability, add the ‘as list’ coercion. If there are more than one disk, then “name of every disk” returns a list and coercion to list does nothing. Getting items of a string will return each character of the string. Apple must have fixed this in Jobu’s os.

Off to experiment.

Edit: here’s another important excerpt from the ‘item value’ event of studioreference.pdf:

For outline views that do not use a data source, this event handler is called once for
each currently displayed row in each column. For example, if an outline view
displays a file system hierarchy in three columns, one each for the name, date
modified, and size for each item, the outline view will call the item value handler
three times for each displayed item (once for the name column, once for the
modification date, and once for the size).

I haven’t checked it out yet, but suspect that when an item of the outline is expanded, the ‘item value’ handler is called.

Thanks for the new puzzle! :-))

Hi cems2,

cems2 wrote:

Am I supposed to fake this by inventing and parsing some sort of ficticious colon delimited hierarchy string just to satisfy the “child of” call?

The colon delimited hierarchy is an AppleScript path to the child items. At the first level (parent), this is not necessary because you can just access a disk by name. No path is needed. You can do research on referenceing Finder items.

cems2 wrote:

Also could you elaborate how data source don’t require the same sort of understanding of the update cascade. Does the data source somehow intercept the user driven events (like an expand or collapse) and then handle that it self? If so then how does it know what to show (either it has the data statically or it has to dynamically get it, like in the case of the Finder. Somewhere there has to be a call to get that data. then there has to be some choice to delete it when it’s no longer on display.)

The data source is created beforehand by you instead of dynamically as in the outline example. The advantage is that it’s faster when you expand an outline item. I read that if there is a data source, then the data source is used, so PB must check if there is a data source first. No call is needed to get the data because the data source is attached to the outline view.

Here’s a rough script on what you’re trying to do. I didn’t check it much and need to eat now :-), but it probably could use some fixing up. Maybe change some stuff to subroutines and think of better strategies as I was rushing.

on launched theObject
– Create the data source
set dataSource to make new data source at end of data sources with properties {name:“mail”}
– Create the data columns
tell dataSource
make new data column at end of data columns with properties {name:“subject”}
make new data column at end of data columns with properties {name:“sender”}
make new data column at end of data columns with properties {name:“date”}
make new data column at end of data columns with properties {name:“content”}
end tell
– Get a list of subjects
tell application “Mail”
set msg_list to every message of in mailbox
set subject_list to {}
repeat with this_msg in msg_list
set this_subject to subject of this_msg
if this_subject is not in subject_list then
set end of subject_list to this_subject
end if
end repeat
end tell
– Create outline items by subject from subject_list
repeat with this_subject in subject_list
set parentItem to make new data item at end of data items of dataSource
set contents of data cell “subject” of parentItem to this_subject
– get_msgs and data for this subject
tell application “Mail”
set sub_msgs to (every message of in mailbox whose subject is this_subject)
end tell
– get the data for each message with this subject
repeat with this_msg in sub_msgs
tell application “Mail”
set {the_subject, the_sender, the_date, the_content} to {subject, sender, date sent, content} of this_msg
end tell
set the_date to the_date as string – coerce AppleScript date to string
set childItem to make new data item at end of data items of parentItem
set contents of data cell “subject” of childItem to the_subject
set contents of data cell “sender” of childItem to the_sender
set contents of data cell “date” of childItem to the_date
set contents of data cell “content” of childItem to the_content
end repeat
end repeat
– attach data source to the outline view
set data source of outline view “outline” of scroll view “scroll” of window “main” to dataSource
end launched

The outline view has 4 columns. The launched handler is connected to the Application object (the big A in the MainMenu.nib window in IB.

gl,

I worked out some code to display a list of lists of arbitrary data in an outline view. You can use this code, or a variation thereof, to manually enter any list into the outline view. The ‘child of item’ handler is the most important to understand, and really defines how the different levels interact. I’ve found that the outlineItem variable is quite flexible, and realizing that it’s value persists through the entire cascade of events was a crucial discovery, for me. Essentialy, the outlineItem variable is set and saved for every item in the outline view, and can be referenced when drawing the child items. It works kind of like the ‘name’ or ‘tag’ property of other controls, and can be easily referenced while contrusting the outline. By saving a trail of indices for the list items in this variable, you can then code relative references to items and extract an item’s position in the hierarchy based on the outlineItem value for that item. I know, it confuses me too. :rolleyes:

For example, I have a simple outline…

When “Item 1” is drawn, I set it’s outlineItem variable to a list containing one item, it’s index in the main list (the variable ‘masterList’). “Setting” the value is done by by returning a value in the child of item handler. This makes the outlineItem variable for that item “{1}”. When the user clicks on the disclosure triangle for that item, it reads that item’s outlineItem variable and then uses the value it finds to determine which list item it’s supposed to read. In this case, it finds a 1, so it builds the list of children from the sublist found at index 1 in the main list. In this case, that would be items “1.1” and “1.2”. Rather than returning a colon-delimited path or the object’s name, I take the outlineItem value from the parent, which is “{1}”, and append the index of each item to it to create the outlineItem variable for each individual item… which results in something like {1, 1}… which would reference “Item 1 > 1.1”. Similarly, when the user clicks on the next level and exposes the “1.1” item, it brings up a list of it’s items. Each item in that list get’s it’s outlineItem variable set to whatever it’s index is in it’s enclosing list… resulting in something like “{1, 1, 3}”, which would be a reference to “Item 1 > 1.1 > c”. Then in the code, if you need to backtrack, like when you evaluate in the ‘item value’, ‘item expanded’, and ‘number of items’ handlers, you can then reference the item by reading the item’s outlineItem variable. This enhanced version of the original outline view shows how the code classifies each item and what it’s outlineItem variable is set to…

This kind of approach is not suitable for infinite layers of data. How each level is interpreted must be hard coded because references must be made statically to lists of lists, where the list must be well-structured and the indices and data predictable in format. It should suffice for smaller lists with few levels. Here’s the code I’m using…

property masterList : {{"Item 1", {{"1.1", {"a", "b", "c"}}, {"1.2", {"d", "e", "f"}}}}}

on will open theObject
	tell outline view "Outline" of scroll view "Scroll" of window "Window" to update
end will open

on number of items theObject outline item outlineItem
	if outlineItem is 0 then
		return (count masterList)
	else if (count outlineItem) is 1 then
		return (count items of item 2 of item (item 1 of outlineItem) of masterList)
	else if (count outlineItem) is 2 then
		return (count items of item 2 of item (item 2 of outlineItem) of item 2 of item (item 1 of outlineItem) of masterList)
	else
		return 0
	end if
end number of items

on child of item theObject child theChild outline item outlineItem
	if outlineItem is 0 then
		return {theChild}
	else
		return outlineItem & theChild
	end if
end child of item

on item expandable theObject outline item outlineItem
	if (count outlineItem) is 1 then
		return ((count (item 2 of item (item 1 of outlineItem) of masterList)) > 0)
	else if (count outlineItem) is 2 then
		return ((count (item 2 of item (item 2 of outlineItem) of item 2 of item (item 1 of outlineItem) of masterList)) > 0)
	else
		return false
	end if
end item expandable

on item value theObject table column tableColumn outline item outlineItem
	if the identifier of tableColumn is "Item" then
		if (count outlineItem) is 1 then
			return ((item 1 of item (item 1 of outlineItem) of masterList) as string)
		else if (count outlineItem) is 2 then
			return ((item 1 of item (item 2 of outlineItem) of item 2 of item (item 1 of outlineItem) of masterList) as string)
		else if (count outlineItem) is 3 then
			return ((item (item 3 of outlineItem) of item 2 of item (item 2 of outlineItem) of item 2 of item (item 1 of outlineItem) of masterList) as string)
		else
			return (outlineItem as string)
		end if
	end if
end item value

Hope this gets you on the right path…
j

Wow! Juno and kel, this is great. I’ll have to read this over carefully. Might have some question later.

I especially like the way you figured out the theChild of item section. Still unclear to me what actually fires that but atleast now I have a template for the functionality I need.

I wonder if I need to set some events in the interface builder to match these or if these all get generated by the update event. Seems like some of these need to be called when say the user opens a level (clickc the rotating triangle)

and kel thanks for the data object example.

Right now I’m wrestling with how to live without a hash (as in perl) or a dictionary (as in java/python) of key value pairs. Yeah records are nice but you cant use a variable for the key (unless you want some sort of second level evaluation to compile new scripts dynamically). So I just wrote my own hash manager function in applescript so I now can use key value pairs with variables. Now I have to figure out someway to create hash value from variables. numbers are easy. Strings are harder. and everything else is even harder…
Applescript is somewhat limited in functionality. The other thing driving me nuts is how to know when a reference gets dereferenced implictly.

Life sure would be better if there were a perl (or pyhton)API that could inherit all of the GUI and application scripting applescript has!!! Then I could just write in perl or python. I don’t really want to go the whole hog and learn cocoa or use a compiled language like java. Whine whine whine

Yes, you must connect all of these handlers to the outline view in IB in order to get them to fire when your outline view is updated.

I used to agree with you about having the ability to write in another language and still have the functionality of the applescript abilities. If you were to begin writing big-time applications, you’d certainly have to go into obj-c, java, etc. Applescript studio is really a mechanism for creating interfaces for scripts, but has come to be used to create some quite complex applications… more-so than apple probably originally intended. Most obstacles you run into can be overcome with creative scripting, shell scripts, gui scripting, and miscellaneous hacks. If these don’t seem reasonable, consider the alternatives. :wink:

With respect to the data source route and your questions about how the data source does everything “automatically”, there is a cocoa class which contains all of the methods used by the outline view which handle the drawing. The class is probably a subclass of the primary outline view class methods, and ultimately probably uses the “manual” methods we’ve been talking about to execute the drawing of the outline view. The data source class merely provides a convenient method to enter your information into the outline view in a different way. For this reason, I’m not convinced that using a data source is any BETTER than doing it manually, it may just place the processing burden in a different time in the drawing of the view or provide a method of interfacing with the outline view for people who want to organize their data in a different way.

Organizing data, especially large quantities of data or complex systems of data, is something that applescript is admitedly insufficient at. Finding a good way to safely and reliably manage data is an art in ASS, and it usually turns into a “custom” solution for every different project you do. Check out the list structure I created for the example project above. It seems to be a simple structure, without the need for keys and cumbersome hashing. It requires careful attention when building the list to avoid corrupting the data, but once you figure out the code and add some error handling to make it solid, you should have a fairly good way of organizing data without using a data source.

Good luck, let us know if you come up with more questions…
j (aka “jobu”, not juno or jubo :P)

Hi,

I didn’t know that you can set an identifier to a ‘data item’ through the ‘associated object’ property of ‘data item’. For instance, after creating a parentItem add this to the example script:

set associated object of parentItem to this_subject – add this after creating parent

If you then want a reference to a parentItem (subject):

set p to first data item of data source “mail” whose associated object is “SomeMailSubject”

You could use this for creating a new childItem for a new message. You might place this in an error handler for the case where the parentItem doesn’t exist. If the parentItem doesn’t exist, then create a new parent and childItem. Otherwise, just create a new childItem for the found parent.

To test this out I added a button with an on clicked handler attached.

on launched theObject
– Create the data source
set dataSource to make new data source at end of data sources with properties {name:“mail”}
– Create the data columns
tell dataSource
make new data column at end of data columns with properties {name:“subject”}
make new data column at end of data columns with properties {name:“sender”}
make new data column at end of data columns with properties {name:“date”}
make new data column at end of data columns with properties {name:“content”}
end tell
– Get a list of subjects
tell application “Mail”
set msg_list to every message of in mailbox
set subject_list to {}
repeat with this_msg in msg_list
set this_subject to subject of this_msg
if this_subject is not in subject_list then
set end of subject_list to this_subject
end if
end repeat
end tell
– Create outline items by subject from subject_list
repeat with this_subject in subject_list
set parentItem to make new data item at end of data items of dataSource
set associated object of parentItem to this_subject – add this after creating parent
set contents of data cell “subject” of parentItem to this_subject
– get_msgs and data for this subject
tell application “Mail”
set sub_msgs to (every message of in mailbox whose subject is this_subject)
end tell
– get the data for each message with this subject
repeat with this_msg in sub_msgs
tell application “Mail”
set {the_subject, the_sender, the_date, the_content} to {subject, sender, date sent, content} of this_msg
end tell
set the_date to the_date as string – coerce AppleScript date to string
set childItem to make new data item at end of data items of parentItem
set contents of data cell “subject” of childItem to the_subject
set contents of data cell “sender” of childItem to the_sender
set contents of data cell “date” of childItem to the_date
set contents of data cell “content” of childItem to the_content
end repeat
end repeat
– attach data source to the outline view
set data source of outline view “outline” of scroll view “scroll” of window “main” to dataSource
end launched

on clicked theObject
try
set p to first data item of data source “mail” whose associated object is “SomeMailSubject”
log p
on error
beep 2
end try
end clicked

gl,

I just wanted to say thanks to the super-stars here, Kel & Jobu. I came here for the first time trying to use outline view to display nested mail folders in Entourage. I quickly found, as did the original poster, that the existing examples and documentation are completely metaphysical. I have yet to really study these examples or this thread, but without it I have to say I would be totally dumbfounded.

More later (maybe).