SwingOSC – Java-based cross-platform replacements for Cocoa GUI classes

This class is meant as an emulation of SCEnvelopeView. last mod: 04-apr-09 sciss

Also refer to [JSCView] for different behaviour affecting all widgets

no-op / not working

different behaviour

size_, editable, readOnly_, elasticMode_, allConnections, items were removed

because they have no defined behaviour or are broken in cocoa GUI

superclass superclass is JSCAbstractMultiSliderView, not JSCMultiSliderView

mouse control slightly different. special cocoa "shift+click mode" not implemented, instead multiple selections

index should not be confused with current selection. call 'selection' to see which nodes are selected

extended functionality

font_ sets the text-label font

clipThumbs_ alternative coordinate mode

strings returns the text labels (or nil if none had been set)

connections returns all the custom connections (or nil if none had been made)

selection returns an array of booleans specifying which nodes are selected

deselectIndex complements selectIndex

curve_, setCurve to change the shape of the node connections

lockBounds_ to lock the time value of the bounding nodes

known issues / todo

focus border not adjusted when resizing the component programmatically (why?)

drag+drop NOT YET IMPLEMENTED

background todo: custom colours should not be darkened?

metaAction does not get evaluated on ctrl+mouseclick/drag

cursor keys movements should apply to all selected nodes

JSCEnvelopeView

Note: please use the abstraction layer GUI.envelopeView if possible! (see [GUI])

An envelope view is a collections of two dimensional (x, y) points, possibly connected through lines. A common application is the presentation of a breakpoint-function or envelope (hence the name), where the x-axis corresponds to time and the y-axis to some parameter that is controlled by the envelope (e.g. amplitude, frequency, etc.). It is similar to [JSCMultiSliderView] but instead of vertical "sliders" associated with integer indices, we instead have knobs that can be also moved horizontally.

value_([times,values])

value_([times,values,curves])

sets all the nodes, where times and values are all 0..1.

curves is optional (defaults to \linear) and can be either a single element

or an array of elements. see [Env] for the possible values. Note: \exponential and floats

are not yet supported!

value

[times,values]

reads all the nodes, where times and values are all 0..1

action_

registers a function that is called when the user selects or moves the nodes. the function is passed the view

index

the current or last moved node. initially -1

drawRect_(boolean)

sets whether to show the nodes' knob-boxes or not

drawLines_(boolean)

sets whether to draw lines between the nodes

setThumbSize(index, size)

sets the size of a point for the specified index, if the index is -1 set the size for all points

thumbSize_(size)

sets the size of all nodes' knob-boxes (width and height)

setThumbWidth(index, width)

sets the width of a knob-box for the node specified by the index, if the index is -1 set the width for all nodes

thumbWidth_( width)

sets the width of all nodes' knob-boxes

setThumbHeight(index, height)

sets the height of a point for the specified index, if the index is -1 set the height for all points

thumbHeight_(height)

sets the height of all nodes' knob-boxes

setEditable(index, boolean)

makes a specified node unmoveable (if boolean is false) or moveable (if boolean is true)

editable_(boolean)

makes all nodes unmoveable (if boolean is false) or moveable (if boolean is true)

selectionColor_(color)

sets the color of knob-boxes which are selected

setFillColor(index, color)

sets the color of a nodes' knob-box

fillColor_(color)

sets the color for all nodes

setString(index, string)

assigns a text label to a node

curve_(curve)

sets the curve shape for all node's connections

setCurve(index, curve)

sets the curve shape for a particular node's connection(s)

connect_(index, arrayofpoints)

connects a node to others. the first time you call this, the standard connection scheme is switched off

// to select + move several points, press the mouse

// somewhere on the panel and drag a rubberband.

// to extend the rubberband-selection, keep shift pressed.

//

// alternatively, shift+click on successive nodes.

//

// to deselect all, click somewhere on the panel.

// to deselect a single node, shift+click on it.

(

a = JSCWindow( "envelope", Rect( 200, 450, 250, 100 ));

b = JSCEnvelopeView( a, Rect( 4, 4, 230, 80 ))

.selectionColor_( Color.red )

.resize_( 5 )

.action_({ arg b; [ b.index, b.value ].postln }) // print last clicked index along with all node values

.value_([[ 0.0, 0.1, 0.5, 1.0 ], [ 0.1, 1.0, 0.8, 0.0 ]]); // set the initial node values (x and y)

a.front;

)

(

// make the first node unmoveable

// (you can still move it using x_ though!)

b.setEditable( 0, false );

)

(

// alternatively (SwingOSC only) you can

// lock the x coordinate of the bounding nodes:

b.setEditable( 0, true );

b.lockBounds = true;

)

// to enforce causal order, you can set the

// horizontal editing mode to one of the

// values \clamp (nodes cannot be moved beyond their neighbours)

// or \relay (when trying to move a node beyond a neighbour,

// the neighbour gets selected and moved instead). to return

// to normal mode, use \free:

// lock the x coordinate of the bounding nodes:

// (SwingOSC only)

b.horizontalEditMode = \clamp;

b.horizontalEditMode = \relay;

b.horizontalEditMode = \free;

// snap movements to a grid (0 = no snapping)

b.step = 0.125;

b.step = 0.0;

// check out the current selection (SwingOSC only)

b.selection; // an array of booleans, one for each node, true if that node is selected

// add a node to the selection

b.selectIndex( 0 );

b.selectIndex( 2 );

// remove a node from the selection (SwingOSC only)

b.deselectIndex( 2 );

// programmatically move a node

b.select( 1 ); // note: no visible effect

b.x = (b.x + 0.1.bilinrand).clip( 0, 1 );

b.y = 1.0.rand;

(

a = JSCWindow("test", Rect(200 , 450, 370, 120));

a.view.decorator = FlowLayout(a.view.bounds);

b = JSCEnvelopeView( a, Rect( 10, 10, 350, 100 ))

.resize_( 5 )

.fillColor_( Color.green )

.selectionColor_( Color.red )

.drawRects_( true )

.value_([(0.0, 0.1 .. 1.0), (0.0, 0.1 .. 1.0)])

.horizontalEditMode_( \relay )

.lockBounds_( true );

a.front;

)

(

r = Routine({

var j = 0;

20.do({ arg i;

b.select((b.size -1).rand.abs);

0.1.wait;

b.x_(1.0.rand.abs);

b.y_(1.0.rand.abs);

});

b.select(-1);

});

AppClock.play(r);

)

c = b.value[0]; // the x-values

c = b.value[1]; // the y-values

b.selection; // see which nodes are selected! (SwingOSC only)

// show boxes with a label in it

(

a = JSCWindow( "text-boxes", Rect( 200, 450, 450, 450 ));

b = JSCEnvelopeView( a, Rect( 4, 4, 440, 440 ))

.resize_( 5 )

.thumbWidth_( 60.0 )

.thumbHeight_( 15.0 )

.selectionColor_( Color.red )

.value_([[ 0.1, 0.4, 0.5, 0.3 ], [ 0.1, 0.2, 0.9, 0.7 ]]);

4.do({ arg i;

b.setString( i, [ "this", "is", "so much", "fun" ].at( i ));

b.setFillColor( i, [ Color.yellow, Color.white, Color.green ].choose ); // random colour

});

a.front;

)

// switch the label font (SwingOSC only!)

b.font = JFont( "SansSerif", 16 );

b.thumbHeight = 22; // no automatic thumb size adjustment, so we do it manually

b.thumbWidth = 80;

// let's see what the labels are

b.strings;

// control the way the view is displayed

b.drawLines = false;

b.drawRects = false;

b.drawLines = true;

b.drawRects = true;

b.strokeColor = Color.blue; // colour for outlines, labels, and connections

// the nodes can be connected.

// once you call connect, the standard neighbouring connections disappear!

// note that each connect overwrites all previous connections of the particular node.

(

b.connect( 3, [ 0, 1, 2 ]); // connect node 3 to nodes 0, 1, and 2

b.connect( 0, [ 1, 2, 3 ]); // connect node 0 to nodes 1, 2, and 3

b.drawLines_( true ); // (in case it was switched off)

)

// check out what the connections are

b.connections; // nil if no connections had been made, otherwise an array of arrays (one for each source node)

At the moment, the graph is considered "undirected", therefore as you can see, setting a connection from for example node 0 to node 1, implies that a connection is added from node 1 to 0. in a future version, there might be an option to use a directed graph instead. note, that the returned connection arrays are not garantueed to be sorted in any way.

Using Curve-Shapes

In SwingOSC, it is possible to use other shapes for the connections than straight lines. The possible shapes are described in the [Env] helpfile. Note: \exponential and Float values are not yet implemented. You can specify the curves of the envelope by calling curve_( <curve> ), or to change just a single node's shape, call setCurve( <index>, <curve> ). You can also supply a third array to the value_ method, so that it becomes value_([ <times>, <levels>, <curves> ]). Here is an example that applies new curves from a popup menu to the currently selected nodes:

(

a = JSCWindow( "envelope", Rect( 200, 450, 250, 114 ));

b = JSCEnvelopeView( a, Rect( 4, 4, 242, 80 ))

.selectionColor_( Color.red )

.resize_( 5 )

.action_({ arg b; [ b.index, b.value ].postln }) // print last clicked index along with all node values

.lockBounds_( true )

.horizontalEditMode_( \relay )

.value_([(0.0, 0.1 .. 1.0), (0.0, 0.1 .. 1.0)]);

c = JSCPopUpMenu( a, Rect( 4, 88, 90, 24 ))

.resize_( 7 )

.allowsReselection_( true )

.items_([ \step, \linear, /* exponential, */ \sine, \welch, /* 2.0, */ \squared, \cubed ])

.value_( 1 )

.action_({ arg pop; var curve = pop.items[ pop.value ];

b.selection.do({ arg selected, idx; if( selected, { b.setCurve( idx, curve )})});

b.doAction;

});

a.front;

)

Note that for backwards compatibility, value by default returns only a two element array. As soon as you modify the curves, value will return a third element that is an array of curve parameters.

Envelope Support

There are methods to directly feed in Env objects and to retrieve Env objects from the current values. To set the envelope view according to an Env object, use the setEnv method. To retrieve the values of the view as an Env object use asEnv( <minLevel>, <maxLevel> ), or call editEnv( <env>, <minLevel>, <maxLevel> ) to update an existing Env object. Example:

(

v = Env.adsr;

a = JSCWindow( "envelope", Rect( 200, 450, 250, 114 )).front;

b = JSCEnvelopeView( a, Rect( 4, 4, 242, 80 ))

.resize_( 5 )

.action_({ arg b; b.editEnv( v, 0.0, 1.0 )})

.lockBounds_( true )

.horizontalEditMode_( \relay )

.setEnv( v, 0.0, 1.0 );

JSCButton( a, Rect( 4, 88, 90, 24 ))

.resize_( 7 )

.states_([[ "Plot" ]])

.action_({

GUI.useID( \swing, { v.plot });

});

a.front;

)

Alternative Coordinates Mode

normally, as in the cocoa GUI, the coordinates of each node are "tweaked" to make its knob-box fit entirely into the view. That is, if you have say, the first node located at x = 0, y = 1, and thumbWidth is 80 pixels, thumbHeight is 40 pixels ...

b.select( 0 ); b.x = 0; b.y = 1; b.thumbWidth = 40; b.thumbHeight = 40; b.setFillColor( 0, Color.clear ); b.setString( 0, nil );

... you will notice that the connection to the other nodes does not begin in the top left corner of the view. that is because the knob-box is always centered around the connection location, and the knob-box was moved a little to fit completely onto the view. This can be confusing when you wish to see the precise coordinates of your envelope. Therefore, in SwingOSC you can switch to an alternative coordinates mode, where all the value coordinates appear as expect and hence the knob-boxes are allowed to be clipped at the view's boundaries:

b.clipThumbs = true;

b.clipThumbs = false;

b.clipThumbs = true;

// default keyboard mappings

csr left/right decrease/increase x

csr down/up decrease/increase y

alt + csr left/left select previous/next node