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};
)