PopUpTreeMenu hierarchical PopUp menu


Inherits from: SCViewHolder


known problems / todo:

* the tree must must follow the structure described below.  later it might be possible to use e.g. Help.tree

* screen width and height are not respected.  menus with many entries may be cut off at the borders.

* click once and menu should stay open so one can browse without keeping the mouse pressed.

difficult due to mouseOverAction bug - it does not fire when already clicked somewhere else.

* swingosc: scroll handles appear.  difficult to solve - something with font size calculation.


note: this quark now requires sc version 3.3.1 or newer.


Instance Variables


<>tree


dictionary of dictionaries.  branches terminate if they have an empty dictionary as a value (= leaf).

everything have to be symbols.


( 'symbol1': (), 'symbol2': () ) //same level

( 'symbol': ( 'symbol': () ) ) //nested

( '1' : ('11': () ), '2.2': ('22': () ) ) //numbers also have to be symbols


<value


array of symbols pointing to the last selected node.  nil if nothing selected.


<currentLeaf


array of symbols pointing to current selection (leaves only).  empty if nothing selected.

see currentPath for reporting both nodes and leaves.


<>action


function that gets evaluated when some leaf is selected.  args passed: this, value


<>openAction


function that gets evaluated when the popup menu is clicked.  args passed: this, mouseX, mouseY.

can be used to automatically populate the tree to keep it in sync with files in a folder.  see below.


<>closeAction


function that gets evaluated when the popup menu closes.  args passed: this, mouseX, mouseY.


<>sortFunc


sorts the dictionary in other ways than in the default alphabetic order.


Instance Methods


currentPath


array of symbols pointing to current selection.  array is empty if nothing selected.


value_(path)


set the value.  path should be an array of symbols.


valueAction_(path)


set the value and evaluate the action.  path should be an array of symbols.




//--simple example

(

var w= Window("popuptreemenu - simple", Rect(200, 400, 300, 100)).front;

a= PopUpTreeMenu.new(w, Rect(50, 30, 100, 20))

.tree_(

(

'a drum': (),

'bass': (

'funky': (),

'unhip': (

'umpahTuba': (),

'umpahUpright': ()

)

),

'melo': ()

)

);

a.action_{|view, val| ("selected:"+val).postln};

)

a.value_(['bass', 'funky'])

a.value_(['melo'])

a.valueAction_(['bass', 'unhip', 'umpahTuba'])



//--polling state of the menu in use - example also show custom color/font settings

(

var w= Window("popuptreemenu - state", Rect(400, 400, 300, 100)).front;

a= PopUpTreeMenu.new(w, Rect(10, 20, 200, 34))

    .tree_(('aaa': (), 'bbb': (), 'ccc': ('123': (), '456': (), '789': ('hier': ()))))

    .font_(Font("Arial", 24))

    .background_(Color.red(1))

    .hiliteColor_(Color.red(0.75))

    .stringColor_(Color.red(0.5));

Routine({while({w.isClosed.not}, {

    ("path:"++a.currentPath).post;

    (" leaf:"++a.currentLeaf).post;

    (" value:"++a.value).postln;

0.4.wait;

})}).play(AppClock);

)

//--changing position and font while active

a.value_(['aaa'])

a.bounds_(Rect(30, 30, 100, 20));

a.font_(Font("Geneva", 10));

a.refresh

a.value;

a.sortFunc= {|a, b| a>b}

a.sortFunc= nil //the default

//cmd period to stop



//--scanning sound file folder each time menu is pressed.

//--slightly inefficient but keeps menu/folder in sync

(

var w= Window("popuptreemenu - scan", Rect(200, 400, 300, 100)).front;

var buildTreeFunc;

a= PopUpTreeMenu.new(w, Rect(10, 20, 200, 20)).items_(["sounds"]);

buildTreeFunc= {|path|

var deepPathMatch, syms, tree;

deepPathMatch= {|pn|

var arr= [];

pn.pathMatch.do{|x|

if(x.last.isPathSeparator, {

arr= arr++deepPathMatch.value(PathName(x++"*"));

}, {

arr= arr++PathName(x);

})

};

arr

};

syms= deepPathMatch.value(PathName(path)).collect{|x|

x.fullPath.split($/).collect{|y| y.asSymbol}

};

tree= ();

syms.do{|x|

var parent= tree;

x.do{|y, i|

var node= parent[y];

if(node.isNil, {

parent.put(y, ());

parent= parent[y];

}, {

parent= node;

});

};

};

tree;

};

a.openAction_{|view, x, y|

"rebuilding tree of /sounds directory".postln;

view.tree= buildTreeFunc.value("sounds"); //build and replace tree

};

a.closeAction_{|view, x, y| "closing".postln};

a.action_{|view, val| ("selected:"+val).postln};

)