HelpBrowser: path box becomes a more conventional search box
[supercollider.git] / SCClassLibrary / JITLib / GUI / JITGui.sc
blob0ee73a0c81c5eca480a48d0c18a012ef85d8c93b
1 /***
2         Abstract superclass for
3         TdefGui,PdefGui,
4         TdefAllGui, PdefAllGui
5         EnvirGui,
7         MonitorGui,
8         NPGui, NdefGui,
9         ProxyMixer (ProxySpaceGui),
10         ParamGui,
11         and possibly others to follow.
13         common traits:
14         an observed object (a proxy, monitor, space, envir, ...)
15         a skipjack for watching it;
16         a getState method for getting all the observed state
17         a prevState instvar for keeping the old state around;
18         a checkUpdate method for comparing old and new state,
19                 and updating the gui elements that have changed;
21         parent, minimum bounds for its numItems, actual bounds;
23         if the object has settings, make an EnvirGui or ParamGui
24         ParamGui can do sliders or knobs ...
26         Simpler ones: EnvirGui, TaskProxyGui;
28 ***/
30 JITGui {
31         var <object, <numItems, <parent, <bounds, <zone, <minSize, <defPos, <skin, <font;
32         var <nameView, <csView;
33         var <prevState, <skipjack, <scroller;
34         var <config, <hasWindow = false;
36         *initClass {
37                 Class.initClassTree(GUI);
39                 GUI.skins.put(\jit, (
40                                 fontSpecs:      ["Helvetica", 12],
41                                 fontColor:      Color.black,
42                                 background:     Color(0.8, 0.85, 0.7, 0.5),
43                                 foreground:     Color.grey(0.95),
44                                 onColor:                Color(0.5, 1, 0.5),
45                                 offColor:               Color.clear,
46                                 hiliteColor:    Color.green(1.0, 0.5),
47                                 gap:                    0 @ 0,
48                                 margin:                 2@2,
49                                 buttonHeight:   18,
50                                 headHeight:     24
52                         )
53                 );
54         }
56         *new { |object, numItems = (0), parent, bounds, makeSkip = true, options = #[]|
57                 ^super.newCopyArgs(nil, numItems, parent, bounds)
58                         .init(makeSkip, options)
59                         .object_(object);
60         }
62         init { |makeSkip, options|
64                 skin = GUI.skins.jit;
65                 font = Font(*skin.fontSpecs);
66                 prevState = ();
68                 // calc bounds - at least minbounds
69                 this.setDefaults(options);
70                 this.calcBounds(options);
72                 if (parent.isNil) { this.makeWindow };
73                 this.makeZone;
75                         // then put all the other gui items on it
76                 this.makeViews(options);
77                         // and start watching
78                 if (makeSkip) { this.makeSkip };
79         }
81         accepts { |obj| ^true   }
83         object_ { |obj|
84                 if (this.accepts(obj)) {
85                         object = obj;
86                         this.checkUpdate;
87                 } {
88                         "% : object % not accepted!".format(this.class, obj).warn;
89                 }
90         }
92         name_ { |name|
93                 if (hasWindow) { parent.name_(this.winName(name)) };
94                 if (nameView.notNil) {
95                         try { nameView.object_(object) };
96                         nameView.string_(name);
97                 };
98         }
100         getName {       ^try { object.key } ? "_anon_" }
101         winName { |name| ^this.class.name ++ $_ ++ (name ?? { this.getName }) }
103         calcBounds {
104                 var defBounds;
105                 if(bounds.isKindOf(Rect)) {
106                         bounds.setExtent(max(bounds.width, minSize.x), max(bounds.height, minSize.y));
107                         ^this
108                 };
110                 defBounds = Rect.fromPoints(defPos, defPos + minSize + (skin.margin + skin.margin));
111                 if (bounds.isNil) {
112                         bounds = defBounds;
113                         ^this
114                 };
116                 if (bounds.isKindOf(Point)) {
117                         bounds = defBounds.setExtent(max(bounds.x, minSize.x), max(bounds.y, minSize.y));
118                 }
119         }
121         makeWindow {
122                 parent = Window(this.winName, bounds.copy.resizeBy(10, 10)).front;
123                 parent.addFlowLayout;
124                 hasWindow = true;
125         }
127         makeZone {
128                 zone = CompositeView(parent, bounds).background_(Color.white);
129                 zone.addFlowLayout(skin.margin, skin.gap);
130                 zone.resize_(2);
131                 zone.background_(skin.foreground);
132         }
134         moveTo { |h, v|
135                 if (hasWindow) { parent.bounds = parent.bounds.moveTo(h, v); }
136         }
138         close {
139                 if (hasWindow) { parent.close }
140         }
142         makeSkip {
143                 skipjack = SkipJack({
144                 //      try {
145                                 this.checkUpdate
146                 //      } {
147                 //              (this.getName  + "checkUpdate failed.").postln;
148                 //              }
149                         },
150                         0.5,
151                         { parent.isNil or: { parent.isClosed } },
152                         this.getName
153                 );
154         }
157                 // these methods should be overridden in subclasses:
158         setDefaults { |options|
159                 if (parent.isNil) {
160                         defPos = 10@260
161                 } {
162                         defPos = skin.margin;
163                 };
164                 minSize = 250 @ (numItems * skin.buttonHeight + skin.headHeight);
165         //      "minSize: %\n".postf(minSize);
166         }
168         makeViews {
169                 var lineheight = max(
170                         skin.buttonHeight * numItems + skin.headHeight,
171                         zone.bounds.height)  - (skin.margin.y * 2);
173                 nameView = DragBoth(zone, Rect(0,0, 60, lineheight))
174                         .font_(font)
175                         .align_(\center)
176                         .receiveDragHandler_({ arg obj; this.object = View.currentDrag });
178                 csView = EZText(zone,
179                         Rect(0,0, bounds.width - 65, lineheight),
180                         nil, { |ez| object = ez.value; })
181                         .font_(font);
182                 csView.bounds.postln;
183         }
185         getState {
186                 // get all the state I need to know of the object I am watching
187                 ^(object: object)
188         }
190         checkUpdate {
191                 var newState = this.getState;
193                 // compare newState and prevState, update gui items as needed
194                 if (newState == prevState) { ^this };
196                 if (newState[\object] != prevState[\object]) {
197                         this.name_(this.getName);
198                         if (csView.textField.hasFocus.not) { csView.value_(object) };
199                 };
200                 prevState = newState;
201         }
203         makeScroller {
204                 // if numItems is exceeded, shift items along by some number with an EZScroller
205         }