Inheriting from Slider class (again)


#1

Hello all,

I read the earlier discussion, but must beg for the important data members of Slider to be made protected rather than private, i.e., to remove line 675 of src/juce_appframework/gui/components/controls/juce_Slider.h that says

private:

For an app we built in a class, we needed a subclass (shown in action below) that handles the min and max values differently - to set the base value and random distribution range of probability distributions. For this, we wanted the look the remain, but the feel of having the min and max constrained had to change. I got it to work with just a bit of code (attached), but I had to make the data members of Slider visible to the subclass, since there are no setter/getter methods for them.

stp

//
// A RangeSlider interprets its two markers as base value and range, rather than min/max
//

class RangeSlider : public Slider {
public:
    RangeSlider (const String& componentName) : Slider(componentName) { };
    ~RangeSlider() { };
	
	double getBaseValue();
    double getRangeValue();
	void setBaseValue (double newValue, const bool sendUpdateMessage = true, const bool sendMessageSynchronously = true);
	void setRangeValue (double newValue, const bool sendUpdateMessage = true, const bool sendMessageSynchronously = true);
	
protected:
	void mouseDown (const MouseEvent& e);
	void mouseDrag (const MouseEvent& e);
	double baseValue, rangeValue;
	
};

///////////////////////////////////////////////////////////////////////
// RangeSlider methods
//

// Answer the base value

double RangeSlider::getBaseValue() {
    return baseValue;
}

// answer the range (normalized to 0 - 1 -- not certain if there's a single way to do this, or if we need another state flag

double RangeSlider::getRangeValue() {
	return (rangeValue - minimum) / (maximum - minimum);
}

void RangeSlider::mouseDown (const MouseEvent& e) {
	float mousePos;
	float maxPosDistance;
	float minPosDistance;
    mouseWasHidden = false;
    incDecDragged = false;
	
    if (isEnabled()) {
		menuShown = false;
		if (valueBox != 0)
			valueBox->hideEditor (true);
		sliderBeingDragged = 0;
		mousePos = (float) (isVertical() ? e.y : e.x);
		maxPosDistance = fabsf (getLinearSliderPos (valueMax) - 0.1f - mousePos);
		minPosDistance = fabsf (getLinearSliderPos (valueMin) + 0.1f - mousePos);
		if (maxPosDistance <= minPosDistance)
			sliderBeingDragged = 2;
		else
			sliderBeingDragged = 1;
	}
	minMaxDiff = valueMax - valueMin;
	mouseXWhenLastDragged = e.x;
	mouseYWhenLastDragged = e.y;
	if (sliderBeingDragged == 2)
		valueWhenLastDragged = currentValue;
	else if (sliderBeingDragged == 1)
		valueWhenLastDragged = baseValue;
	valueOnMouseDown = valueWhenLastDragged;            
	
	sendDragStart();
	mouseDrag (e);
}

void RangeSlider::mouseDrag (const MouseEvent& e) {
    if (isEnabled() && (! menuShown)) {
		const int mousePos = (isHorizontal() || style == RotaryHorizontalDrag) ? e.x : e.y;
		double scaledMousePos = (mousePos - sliderRegionStart) / (double) sliderRegionSize;
		if (style == LinearVertical)
			scaledMousePos = 1.0 - scaledMousePos;
		valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, scaledMousePos));
		mouseXWhenLastDragged = e.x;
        mouseYWhenLastDragged = e.y;
        valueWhenLastDragged = jlimit (minimum, maximum, valueWhenLastDragged);
        if (sliderBeingDragged == 1)
            setBaseValue(valueWhenLastDragged, ! sendChangeOnlyOnRelease, false);
        else if (sliderBeingDragged == 2)
            setRangeValue(valueWhenLastDragged, ! sendChangeOnlyOnRelease, false);
        mouseXWhenLastDragged = e.x;
        mouseYWhenLastDragged = e.y;
    }
}

void RangeSlider::setBaseValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously) {
    if (baseValue != newValue) {
        baseValue = newValue;
        valueMin = newValue;
        currentValue = newValue;
        repaint();
        if (sendUpdateMessage)
            triggerChangeMessage (sendMessageSynchronously);
    }
}

void RangeSlider::setRangeValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously) {
    if (rangeValue != newValue) {
        rangeValue = newValue;
        valueMax = newValue;
        repaint();
        if (sendUpdateMessage)
            triggerChangeMessage (sendMessageSynchronously);
    }
}

#2

When it comes to making member variables protected, I generally allow it if they’re straightforward and if there’s not much danger that changes to the parent class will mess up subclasses that use them. But the slider is so horribly complex that I think its internal data is best kept hidden…

Can you think of some sort of more generic virtual methods I could add that would let you do the same thing?


#3

Gee, (since you asked), if you look at the code I attached, I simply overrode mouseDown() and mouseDrag(), but needed access to the private values (currentValue, valueMin, valueMax, minimum, maximum, valueWhenLastDragged, valueOnMouseDown). If these had been protected, or had getter/setter methods, I could have done this without changing Slider class.

Alternatively, It’s been my observation that a lot of the JUCE code is complex because a single class does too many things. I teach my students to be wary of classes that have type flags (e.g., SliderStyle) and long methods that should be broken into smaller methods polymorphically implemented in several classes. Of course, the extreme application of this leads to class explosion (scores of slider classes), but for widgets, I’d say it would make sense, be easier to read, and be easier to extend and customize. (I have > 30 years experience doing OOP, so yes, I do think I know everything.)

Why not have a family (class hierarchy) of sliders, with the only flag being orientation (and maybe incr/decr buttons), and subclasses for 2- and 3-value sliders and rotary sliders.

Don’t get me wrong, I love JUCE, and use it to teach digital audio programming at the graduate level (http://www.create.ucsb.edu/240), but we also learn and evaluate many other OO frameworks, and the JUCE learning curve would be aided by simplification.

Thanks for making JUCE, Jules!

stp


#4

Oh no, a proper programming expert! Someone’s finally rumbled me!

Of course you’re quite right about the slider class, I’ll award myself a D- for that one. It’s one of the ones that annoys me every time I go in there. The trouble is that whenever I’ve needed to add something to it, there was always a practical reason for me choose to extend it rather than break it down into separate classes. And using these components in the Jucer also pushes things in that direction too - e.g. it’s much neater for the Jucer to just have a “slider type” box rather than lots of different slider classes to choose from.

Good point about anything with type-flags being a sign of bad design… That’s definitely a nice rule to bear in mind.

I’ll see what I can do if I get chance to look at the Slider class, and keep hassling me if you see other things that could use a makeover!


#5

Thanks so much, Jules, for not blowing up at my suggestions; JUCE is quite a wonderful piece of work!

Having built a GUI editor or two myself (in LISP and Smalltalk), I think it should be possible to allow the Jucer to select between slider classes based on the settings of a couple of “type” combo boxes (e.g., # of markers, linear/rotary), and to use other combo boxes to set flags in the objects (e.g., orientation, increments).

The type-flag “rule” is also, of course, flexible. You trade simpler methods and classes for more of them. In systems with good browsing tools (Smalltalk), this is a no-brainer; in C++ with file-based tools and no dynamic cross-referencing, one should think it over. I’ve seen over-designed GUI frameworks with (literally > 1000 classes).

At any rate, the current Slider is certainly worth at least a B+…

As to “other things that could use a makeover” – my current audio code just uses AudioIODeviceCallbacks with our local CSL library (http://fastlabinc.com/CSL), but I have a couple of students slogging their way through the Synthesizer classes and coming up with a raft of questions…

Cheers!

stp


#6

Thanks!

Yes, ideas about the synthesiser class would be welcome, as I don’t write very many synths myself.

Re the audio classes, bear in mind that I’m currently working on a whole bunch of new code to replace the AudioSources with much more better AudioProcessor classes. The idea will be that you’ll put some of those in a graph and let it play without having to worry about i/o callbacks and things.


#7

Wow – this is really interesting.

I currently teach a class on sound synthesis techniques, and we’re using CSL (http://fastlabinc.com/CSL) and Synz (http://www.uweb.ucsb.edu/~ljputnam/synz.html). As I mentioned above, we currently use JUCE as a front-end or wrapper around CSL code.

Our plan (starting 1/1/08) is to have a study group of the advanced students design a new framework that’s a merge of ideas from our previous work in CSL, Synz and CLAM (http://clam.iua.upf.edu) within JUCE. I could certainly see this as a general-purpose synthesis/processing framework in JUCE that allows the creation of stand-alone apps or plug-ins…

So, would you like to collaborate on this?
We could at least contribute the current CSL glue code…

stp


#8

Sure, I’d be interesting in discussing it, though I’ve already got most of the code written. I’m basically taking the new AudioProcessor class, (which is already in there, being used for plugin hosting/writing) and adding a graph object that can join them together, like in the plugin-host example project. This will be able to do anything that the old audiosources can do, but also host plugins, recording, etc.


#9

whau that’s cool ! one of the things i was waiting for, AudioSources converted to be AudioProcessors ! all in the same graph, all in the same style. Keep in mind to find a common way for them to respond to transport changes from different sources (or even notify them, so we could write an audio processor which takes a midi input and convert messages to control the global transport, putting it in the graph).

Also, one of the things i presented a lot of time ago with ejuce was the ability to add processors of spectrum data, along with audio. i think that have the AudioProcessor be capable of handling midi data and audio data in time and frequency domain all in the same graph would be all what we need ! i dream of endless possibilities (ie. spectrum plugins that generates midi for next plugins in the graph, taking into account spectral differences)…

(finger crossed)

:wink:


#10