Inheriting from ComboBox -- a private problem


#1

I want to subclass ComboBox because I would like to modify it so that I can use abbreviations in the label text for what appear as full words or short phrases in the popup menu. This would be easy to do if it were not for the private (as opposed to protected) internals. How can one leverage ComboBox (or indeed other classes) and make desired mods in a clean way?


#2

I came up with this, using a combinaton of a LookAndFeel to intercept the Label then using a Value to intercept the Value (text) when it changes:

[code]class MainComponent : public Component,
public LookAndFeel,
public Value::Listener
{
private:
ComboBox *menu;

public:
MainComponent ()
{
addAndMakeVisible(menu = new ComboBox());
menu->setLookAndFeel(this);
menu->addItem(“Sine”, 1);
menu->addItem(“Saw”, 2);
menu->addItem(“Square”, 3);
}

~MainComponent ()
{
	deleteAllChildren();
}

void resized ()
{
	menu->centreWithSize(40, 20);
}

Label* createComboBoxTextBox (ComboBox& box)
{
	Label* label = getDefaultLookAndFeel().createComboBoxTextBox(box);
	label->getTextValue().addListener(this);
	return label;
}

void valueChanged (Value& value)
{
	String string = value.toString();
	value.setValue(string.substring(0, 2));
}

};
[/code]

Not ideal.


#3

Thanks, this is very clever and very useful.


#4

Actually, - got a bit carried away and came up this this later:

[code]class ComboBrev : public ComboBox,
public LookAndFeel
{
public:
ComboBrev()
: interceptor(*this)
{
setLookAndFeel(this);
}

Label* createComboBoxTextBox (ComboBox& box)
{
	Label* label = getDefaultLookAndFeel().createComboBoxTextBox(box);
	label->getTextValue().addListener(&interceptor);
	return label;
}

virtual String abbreviateLabel(String const& string) = 0;

private:
class Interceptor : public Value::Listener
{
public:
Interceptor(ComboBrev& o)
: owner(o)
{
}

	void valueChanged (Value& value)
	{
		String string = value.toString();
		value.setValue(owner.abbreviateLabel(string));
	}
	
private:
	ComboBrev& owner;
	Interceptor();
};

Interceptor interceptor;

};

class ComboBrevTwoChar : public ComboBrev
{
public:
String abbreviateLabel(String const& string)
{
return string.substring(0, 2);
}
};

class ComboBrevNoVowels : public ComboBrev
{
public:
String abbreviateLabel(String const& string)
{
return string.substring(0, 1)+string.substring(1).removeCharacters(“AEIOUaeiou”);
}
};

class MainComponent : public Component
{
private:
ComboBrevTwoChar *menu1;
ComboBrevNoVowels *menu2;

public:
MainComponent ()
{
addAndMakeVisible(menu1 = new ComboBrevTwoChar());
menu1->addItem(“Sine”, 1);
menu1->addItem(“Saw”, 2);
menu1->addItem(“Square”, 3);

	addAndMakeVisible(menu2 = new ComboBrevNoVowels());
	menu2->addItem("Additive", 1);
	menu2->addItem("Subtractive", 2);
	menu2->addItem("Granular", 3);
}

~MainComponent ()
{
	deleteAllChildren();
}

void resized ()
{
	menu1->setBounds(getWidth()/2-20, 50, 40, 20);
	menu2->setBounds(getWidth()/2-40, 80, 80, 20);
}

};
[/code]

…so you could have a custom abbreviation function depending on what it’s needed for.

The main problem is this line:

Since it locks you down to using the default LookAndFeel. There’s probably a way round that.


#5

There is one small problem with this Value intercept approach. When the popup menu is displayed it will not show the abbreviated selection as checked in the popup menu list because the label text no longer matches the text in the popup menu list and this causes getSelectedId() to return 0. This is unfortunate because the user cannot then check to see what the abbreviation actually represents. I have not been able to find a way to establish a real equivalence between abbreviated label text and popup menu list text.

Obviously this is a general problem. The declaration of private in the Juce classes is meant to create a wall between what is exposed to the public and is more or less guaranteed to remain unchanged, and what is private with no guarantee of continuity. However, if the price one pays for this separation is that one cannot reasonably leverage existing classes then perhaps a bit more exposure to the risk of change would be OK. I suppose this can always be done by just modifying the original source and then keeping track of the mods, but this is rather painful if one wants to keep up with the tip release.


#6

Hmm, yes. Another hack might be to draw your own label over the top of the exisiting one?..

[code]class ComboBrev : public ComboBox,
public LookAndFeel,
public ComboBoxListener
{
public:
ComboBrev()
{
addListener(this);
addAndMakeVisible(briefLabel = new Label());
}

void resized()
{
	ComboBox::resized();
	
	if (getHeight() > 0 && getWidth() > 0)
		getLookAndFeel().positionComboBoxText (*this, *briefLabel);
}

void comboBoxChanged(ComboBox *box)
{
	String string = box->getText();
	briefLabel->setText(string.substring(0, 2), false);
}

private:
Label *briefLabel;
};

class MainComponent : public Component
{
private:
ComboBrev *menu1;

public:
MainComponent ()
{
addAndMakeVisible(menu1 = new ComboBrev());
menu1->addItem(“Sine”, 1);
menu1->addItem(“Saw”, 2);
menu1->addItem(“Square”, 3);
}

~MainComponent ()
{
	deleteAllChildren();
}

void resized ()
{
	menu1->centreWithSize(40, 20);
}

};[/code]


#7

Well, I appreciate the idea but we’re starting to get pretty kludgy. I think, we really need to figure out how to subclass in a clean way. This seems to me to be a perfectly reasonable situation where one wants to inherit a bunch of functionality and then insert a new twist on it. This is what inheritance is for after all. But there are some issues to resolve in order to be able to do this.