gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

uiObject.cpp (18072B)


      1 #include "uiObject.h"
      2 
      3 #include <utility>
      4 
      5 #include "editor.h"
      6 
      7 #include "buttonStyle.h"
      8 #include "comboboxStyle.h"
      9 #include "hyperlinkbuttonStyle.h"
     10 #include "labelStyle.h"
     11 #include "listBoxStyle.h"
     12 #include "rotaryStyle.h"
     13 #include "scrollbarStyle.h"
     14 #include "textbuttonStyle.h"
     15 #include "textEditorStyle.h"
     16 #include "treeViewStyle.h"
     17 
     18 #include <cassert>
     19 
     20 #include "button.h"
     21 #include "slider.h"
     22 
     23 namespace genericUI
     24 {
     25 	UiObject::UiObject(UiObject* _parent, const juce::var& _json, const bool _isTemplate/* = false*/) : m_isTemplate(_isTemplate), m_parent(_parent)
     26 	{
     27 		auto* obj = _json.getDynamicObject();
     28 
     29 		parse(obj);
     30 	}
     31 
     32 	UiObject::~UiObject()
     33 	{
     34 		m_juceObjects.clear();
     35 		m_style.reset();
     36 	}
     37 
     38 	void UiObject::createJuceTree(Editor& _editor)
     39 	{
     40 		apply(_editor, _editor);
     41 
     42 		createChildObjects(_editor, _editor);
     43 	}
     44 
     45 	void UiObject::createChildObjects(Editor& _editor, juce::Component& _parent) const
     46 	{
     47 		for (auto& ch : m_children)
     48 		{
     49 			auto* obj = ch->createJuceObject(_editor);
     50 
     51 			if(!obj)
     52 				continue;
     53 
     54 			_parent.addAndMakeVisible(obj);
     55 
     56 			if(ch->m_condition)
     57 				ch->m_condition->refresh();
     58 
     59 			ch->createChildObjects(_editor, *obj);
     60 		}
     61 	}
     62 
     63 	void UiObject::createTabGroups(Editor& _editor)
     64 	{
     65 		if(m_tabGroup.isValid())
     66 		{
     67 			m_tabGroup.create(_editor);
     68 			_editor.registerTabGroup(&m_tabGroup);
     69 		}
     70 
     71 		for (const auto& ch : m_children)
     72 		{
     73 			ch->createTabGroups(_editor);
     74 		}
     75 	}
     76 
     77 	void UiObject::createControllerLinks(Editor& _editor) const
     78 	{
     79 		for (const auto& link : m_controllerLinks)
     80 			link->create(_editor);
     81 
     82 		for (const auto& ch : m_children)
     83 		{
     84 			ch->createControllerLinks(_editor);
     85 		}
     86 	}
     87 
     88 	void UiObject::registerTemplates(Editor& _editor) const
     89 	{
     90 		for(auto& s : m_templates)
     91 			_editor.registerTemplate(s);
     92 
     93 		for (auto& ch : m_children)
     94 			ch->registerTemplates(_editor);
     95 	}
     96 
     97 	void UiObject::apply(const Editor& _editor, juce::Component& _target)
     98 	{
     99 		const auto x = getPropertyInt("x");
    100 		const auto y = getPropertyInt("y");
    101 		const auto w = getPropertyInt("width");
    102 		const auto h = getPropertyInt("height");
    103 
    104 		if(w > 0 && h > 0)
    105 		{
    106 			_target.setTopLeftPosition(x, y);
    107 			_target.setSize(w, h);
    108 		}
    109 		else if (!m_isTemplate)
    110 		{
    111 			std::stringstream ss;
    112 			ss << "Size " << w << "x" << h << " for object named " << m_name << " is invalid, each side must be > 0";
    113 			throw std::runtime_error(ss.str());
    114 		}
    115 
    116 		createCondition(_editor, _target);
    117 	}
    118 
    119 	void UiObject::apply(Editor& _editor, juce::Slider& _target)
    120 	{
    121 	    _target.setTextBoxStyle(juce::Slider::NoTextBox, false, 0, 0);
    122 
    123 		apply(_editor, static_cast<juce::Component&>(_target));
    124 
    125 		auto* const s = new RotaryStyle(_editor);
    126 
    127 		createStyle(_editor, _target, s);
    128 
    129 		const auto sliderStyle = s->getStyle();
    130 
    131 	    switch (sliderStyle)
    132 	    {
    133 	    case RotaryStyle::Style::Rotary: 
    134 			_target.setSliderStyle(juce::Slider::RotaryHorizontalVerticalDrag);
    135 			break;
    136 	    case RotaryStyle::Style::LinearVertical:
    137 			_target.setSliderStyle(juce::Slider::LinearVertical);
    138 			break;
    139 	    case RotaryStyle::Style::LinearHorizontal:
    140 			_target.setSliderStyle(juce::Slider::LinearHorizontal);
    141 			break;
    142 	    }
    143 
    144 		bindParameter(_editor, _target);
    145 	}
    146 
    147 	void UiObject::apply(Editor& _editor, juce::ComboBox& _target)
    148 	{
    149 		apply(_editor, static_cast<juce::Component&>(_target));
    150 		auto* s = new ComboboxStyle(_editor);
    151 		createStyle(_editor, _target, s);
    152 		bindParameter(_editor, _target);
    153 		s->apply(_target);
    154 	}
    155 
    156 	void UiObject::apply(Editor& _editor, juce::DrawableButton& _target)
    157 	{
    158 		apply(_editor, static_cast<juce::Component&>(_target));
    159 		auto* s = new ButtonStyle(_editor);
    160 		createStyle(_editor, _target, s);
    161 		s->apply(_target);
    162 		bindParameter(_editor, _target);
    163 	}
    164 
    165 	void UiObject::apply(Editor& _editor, juce::Label& _target)
    166 	{
    167 		applyT<juce::Label, LabelStyle>(_editor, _target);
    168 		bindParameter(_editor, _target);
    169 	}
    170 
    171 	void UiObject::apply(Editor& _editor, juce::ScrollBar& _target)
    172 	{
    173 		applyT<juce::ScrollBar, ScrollBarStyle>(_editor, _target);
    174 	}
    175 
    176 	void UiObject::apply(Editor& _editor, juce::TextButton& _target)
    177 	{
    178 		applyT<juce::TextButton, TextButtonStyle>(_editor, _target);
    179 	}
    180 
    181 	void UiObject::apply(Editor& _editor, juce::HyperlinkButton& _target)
    182 	{
    183 		applyT<juce::HyperlinkButton, HyperlinkButtonStyle>(_editor, _target);
    184 	}
    185 
    186 	void UiObject::apply(Editor& _editor, juce::TreeView& _target)
    187 	{
    188 		applyT<juce::TreeView, TreeViewStyle>(_editor, _target);
    189 	}
    190 
    191 	void UiObject::apply(Editor& _editor, juce::ListBox& _target)
    192 	{
    193 		applyT<juce::ListBox, ListBoxStyle>(_editor, _target);
    194 	}
    195 
    196 	void UiObject::apply(Editor& _editor, juce::TextEditor& _target)
    197 	{
    198 		applyT<juce::TextEditor, TextEditorStyle>(_editor, _target);
    199 	}
    200 
    201 	template <typename TComponent, typename TStyle> void UiObject::applyT(Editor& _editor, TComponent& _target)
    202 	{
    203 		apply(_editor, static_cast<juce::Component&>(_target));
    204 
    205 		TStyle* s = nullptr;
    206 
    207 		if (!m_style)
    208 			s = new TStyle(_editor);
    209 
    210 		createStyle(_editor, _target, s);
    211 
    212 		s = dynamic_cast<TStyle*>(m_style.get());
    213 		assert(s);
    214 		s->apply(_target);
    215 	}
    216 
    217 	void UiObject::collectVariants(std::set<std::string>& _dst, const std::string& _property) const
    218 	{
    219 		const std::string res = getProperty(_property);
    220 
    221 		if (!res.empty())
    222 			_dst.insert(res);
    223 
    224 		for (const auto& child : m_children)
    225 			child->collectVariants(_dst, _property);
    226 
    227 		for (const auto& child : m_templates)
    228 			child->collectVariants(_dst, _property);
    229 	}
    230 
    231 	juce::Component* UiObject::createJuceObject(Editor& _editor)
    232 	{
    233 		m_juceObjects.clear();
    234 
    235 		if(hasComponent("rotary"))
    236 		{
    237 			createJuceObject<Slider>(_editor);
    238 		}
    239 		else if(hasComponent("image"))
    240 		{
    241 			// @Juce: Juce does not respect translations on drawables by using setPositionAndSize, even though a Drawable is a component?! Doesn't make sense to me
    242 			auto* c = createJuceObject<juce::Component>(_editor);
    243 			auto img = _editor.createImageDrawable(getProperty("texture"));
    244 			c->addAndMakeVisible(img.get());
    245 			m_juceObjects.emplace_back(std::move(img));
    246 		}
    247 		else if(hasComponent("combobox"))
    248 		{
    249 			createJuceObject<juce::ComboBox>(_editor);
    250 		}
    251 		else if(hasComponent("button"))
    252 		{
    253 			createJuceObject<Button<juce::DrawableButton>>(_editor, m_name, juce::DrawableButton::ImageRaw);
    254 		}
    255 		else if(hasComponent("hyperlinkbutton"))
    256 		{
    257 			createJuceObject<Button<juce::HyperlinkButton>>(_editor);
    258 		}
    259 		else if(hasComponent("textbutton"))
    260 		{
    261 			createJuceObject<Button<juce::TextButton>>(_editor);
    262 		}
    263 		else if(hasComponent("label"))
    264 		{
    265 			createJuceObject<juce::Label>(_editor);
    266 		}
    267 		else if(hasComponent("root") || hasComponent("component"))
    268 		{
    269 			createJuceObject<juce::Component>(_editor);
    270 		}
    271 		else
    272 		{
    273 			throw std::runtime_error("Failed to determine object type for object named " + m_name);
    274 		}
    275 
    276 		return m_juceObjects.empty() ? nullptr : m_juceObjects.front().get();
    277 	}
    278 
    279 	int UiObject::getPropertyInt(const std::string& _key, int _default) const
    280 	{
    281 		const auto res = getProperty(_key);
    282 		if (res.empty())
    283 			return _default;
    284 		return strtol(res.c_str(), nullptr, 10);
    285 	}
    286 
    287 	float UiObject::getPropertyFloat(const std::string& _key, float _default) const
    288 	{
    289 		const auto res = getProperty(_key);
    290 		if (res.empty())
    291 			return _default;
    292 		return static_cast<float>(atof(res.c_str()));
    293 	}
    294 
    295 	std::string UiObject::getProperty(const std::string& _key, const std::string& _default) const
    296 	{
    297 		for (const auto& properties : m_components)
    298 		{
    299 			for(const auto& prop : properties.second)
    300 			{
    301 				if (prop.first == _key)
    302 					return prop.second;
    303 			}
    304 		}
    305 		return _default;
    306 	}
    307 
    308 	size_t UiObject::getConditionCountRecursive() const
    309 	{
    310 		size_t count = m_condition ? 1 : 0;
    311 
    312 		for (const auto & c : m_children)
    313 			count += c->getConditionCountRecursive();
    314 
    315 		return count;
    316 	}
    317 
    318 	size_t UiObject::getControllerLinkCountRecursive() const
    319 	{
    320 		size_t count = m_controllerLinks.size();
    321 
    322 		for (const auto & c : m_children)
    323 			count += c->getControllerLinkCountRecursive();
    324 
    325 		return count;
    326 	}
    327 
    328 	void UiObject::setCurrentPart(Editor& _editor, const uint8_t _part)
    329 	{
    330 		if(m_condition)
    331 			m_condition->setCurrentPart(_editor, _part);
    332 
    333 		for (const auto& child : m_children)
    334 			child->setCurrentPart(_editor, _part);
    335 	}
    336 
    337 	void UiObject::updateKeyValueConditions(const std::string& _key, const std::string& _value) const
    338 	{
    339 		auto* cond = dynamic_cast<ConditionByKeyValue*>(m_condition.get());
    340 		if(cond && cond->getKey() == _key)
    341 			cond->setValue(_value);
    342 
    343 		for (const auto& child : m_children)
    344 			child->updateKeyValueConditions(_key, _value);
    345 	}
    346 
    347 	void UiObject::createCondition(const Editor& _editor, juce::Component& _target)
    348 	{
    349 		if(!hasComponent("condition"))
    350 			return;
    351 
    352 		const auto conditionValues = getProperty("enableOnValues");
    353 
    354 		size_t start = 0;
    355 
    356 		std::set<std::string> values;
    357 
    358 		for(size_t i=0; i<=conditionValues.size(); ++i)
    359 		{
    360 			const auto isEnd = i == conditionValues.size() || conditionValues[i] == ',' || conditionValues[i] == ';';
    361 
    362 			if(!isEnd)
    363 				continue;
    364 
    365 			const auto valueString = conditionValues.substr(start, i - start);
    366 
    367 			values.insert(valueString);
    368 
    369 			start = i + 1;
    370 		}
    371 		
    372 		if(values.empty())
    373 			throw std::runtime_error("Condition does not specify any values");
    374 
    375 		const auto paramName = getProperty("enableOnParameter");
    376 
    377 		if(!paramName.empty())
    378 		{
    379 			const auto paramIndex = _editor.getInterface().getParameterIndexByName(paramName);
    380 
    381 			if(paramIndex < 0)
    382 				throw std::runtime_error("Parameter named " + paramName + " not found");
    383 
    384 			const auto v = _editor.getInterface().getParameterValue(paramIndex, 0);
    385 
    386 			if(!v)
    387 				throw std::runtime_error("Parameter named " + paramName + " not found");
    388 
    389 			std::set<uint8_t> valuesInt;
    390 
    391 			for (const auto& valueString : values)
    392 			{
    393 				const int val = strtol(valueString.c_str(), nullptr, 10);
    394 				valuesInt.insert(static_cast<uint8_t>(val));
    395 			}
    396 
    397 			m_condition.reset(new ConditionByParameterValues(_target, v, static_cast<uint32_t>(paramIndex), valuesInt));
    398 		}
    399 		else
    400 		{
    401 			auto key = getProperty("enableOnKey");
    402 
    403 			if(key.empty())
    404 				throw std::runtime_error("Unknown condition type, neither 'enableOnParameter' nor 'enableOnKey' specified");
    405 
    406 			m_condition.reset(new ConditionByKeyValue(_target, std::move(key), std::move(values)));
    407 		}
    408 	}
    409 
    410 	juce::DynamicObject& UiObject::applyStyle(juce::DynamicObject& _obj, const std::string& _styleName)
    411 	{
    412 		if (m_parent)
    413 			m_parent->applyStyle(_obj, _styleName);
    414 
    415 		const auto it = m_styles.find(_styleName);
    416 		if (it == m_styles.end())
    417 			return _obj;
    418 
    419 		const auto& s = it->second;
    420 		const auto sourceObj = s.getDynamicObject();
    421 
    422 		if (sourceObj)
    423 			copyPropertiesRecursive(_obj, *sourceObj);
    424 
    425 		return _obj;
    426 	}
    427 
    428 	bool UiObject::copyPropertiesRecursive(juce::DynamicObject& _target, const juce::DynamicObject& _source)
    429 	{
    430 		bool result = false;
    431 
    432 		for (const auto& sourceProp : _source.getProperties())
    433 		{
    434 			const auto& key = sourceProp.name;
    435 			const auto& val = sourceProp.value;
    436 
    437 			if (!_target.hasProperty(key))
    438 			{
    439 				_target.setProperty(key, val);
    440 				result = true;
    441 			}
    442 			else
    443 			{
    444 				auto& targetProperty = _target.getProperty(key);
    445 
    446 				if (targetProperty.isObject())
    447 				{
    448 					auto targetObj = targetProperty.getDynamicObject();
    449 					if (targetObj)
    450 					{
    451 						auto sourceObj = val.getDynamicObject();
    452 						if (sourceObj)
    453 							copyPropertiesRecursive(*targetObj, *sourceObj);
    454 					}
    455 				}
    456 			}
    457 		}
    458 		return true;
    459 	}
    460 
    461 	bool UiObject::parse(juce::DynamicObject* _obj)
    462 	{
    463 		if (!_obj)
    464 			return false;
    465 
    466 		auto props = _obj->getProperties();
    467 
    468 		auto styleName = props["style"].toString().toStdString();
    469 
    470 		std::unique_ptr<juce::DynamicObject> newObj;
    471 
    472 		if (!styleName.empty())
    473 		{
    474 			newObj = _obj->clone();
    475 			props = applyStyle(*newObj, styleName).getProperties();
    476 		}
    477 
    478 		for (int i = 0; i < props.size(); ++i)
    479 		{
    480 			const auto key = std::string(props.getName(i).toString().toUTF8());
    481 			const auto& value = props.getValueAt(i);
    482 
    483 			if (key == "name")
    484 			{
    485 				m_name = value.toString().toStdString();
    486 			}
    487 			else if(key == "children")
    488 			{
    489 				const auto children = value.getArray();
    490 
    491 				if (children)
    492 				{
    493 					for (auto&& c : *children)
    494 					{
    495 						std::unique_ptr<UiObject> child;
    496 						child.reset(new UiObject(this, c));
    497 						m_children.emplace_back(std::move(child));
    498 					}
    499 				}
    500 			}
    501 			else if (key == "templates")
    502 			{
    503 				if (const auto children = value.getArray())
    504 				{
    505 					for (const auto& c : *children)
    506 						m_templates.emplace_back(std::make_shared<UiObject>(this, c, true));
    507 				}
    508 			}
    509 			else if (key == "styles")
    510 			{
    511 				auto obj = value.getDynamicObject();
    512 				if (!obj)
    513 					throw std::runtime_error("styles must be an object");
    514 
    515 				auto p = obj->getProperties();
    516 
    517 				for (const auto& s : p)
    518 				{
    519 					const auto name = s.name.toString().toStdString();
    520 
    521 					if (name.empty())
    522 						throw std::runtime_error("style needs to have a name");
    523 					if (m_styles.find(name) != m_styles.end())
    524 						throw std::runtime_error("style with name " + name + " already exists");
    525 
    526 					m_styles.insert({name, s.value});
    527 				}
    528 
    529 				for (const auto& [name,s] : m_styles)
    530 				{
    531 					if (auto* styleObj = s.getDynamicObject())
    532 					{
    533 						auto parentStyleName = styleObj->getProperty("style").toString().toStdString();
    534 
    535 						if (!parentStyleName.empty())
    536 						{
    537 							applyStyle(*styleObj, parentStyleName);
    538 						}
    539 					}
    540 				}
    541 			}
    542 			else if(key == "tabgroup")
    543 			{
    544 				auto buttons = value["buttons"].getArray();
    545 				auto pages = value["pages"].getArray();
    546 				auto name = value["name"].toString().toStdString();
    547 
    548 				if(name.empty())
    549 					throw std::runtime_error("tab group needs to have a name");
    550 				if(buttons == nullptr)
    551 					throw std::runtime_error("tab group needs to define at least one button but 'buttons' array not found");
    552 				if(pages == nullptr)
    553 					throw std::runtime_error("tab group needs to define at least one page but 'pages' array not found");
    554 
    555 				std::vector<std::string> buttonVec;
    556 				std::vector<std::string> pagesVec;
    557 
    558 				for (const auto& button : *buttons)
    559 				{
    560 					const auto b = button.toString().toStdString();
    561 					if(b.empty())
    562 						throw std::runtime_error("tab group button name must not be empty");
    563 					buttonVec.push_back(b);
    564 				}
    565 
    566 				for (const auto& page : *pages)
    567 				{
    568 					const auto p = page.toString().toStdString();
    569 					if(p.empty())
    570 						throw std::runtime_error("tab group page name must not be empty");
    571 					pagesVec.push_back(p);
    572 				}
    573 
    574 				if(buttonVec.size() != pagesVec.size())
    575 					throw std::runtime_error("tab group page count must match tap group button count");
    576 
    577 				m_tabGroup = TabGroup(name, pagesVec, buttonVec);
    578 			}
    579 			else if(key == "controllerlinks")
    580 			{
    581 				auto* entries = value.getArray();
    582 
    583 				if(entries && !entries->isEmpty())
    584 				{
    585 					for(auto j=0; j<entries->size(); ++j)
    586 					{
    587 						const auto& e = (*entries)[j];
    588 						const auto source = e["source"].toString().toStdString();
    589 						const auto dest = e["dest"].toString().toStdString();
    590 						const auto condition = e["condition"].toString().toStdString();
    591 
    592 						if(source.empty())
    593 							throw std::runtime_error("source for controller link needs to have a name");
    594 						if(dest.empty())
    595 							throw std::runtime_error("destination for controller link needs to have a name");
    596 						if(condition.empty())
    597 							throw std::runtime_error("condition for controller link needs to have a name");
    598 
    599 						m_controllerLinks.emplace_back(new ControllerLink(source, dest, condition));
    600 					}
    601 				}
    602 			}
    603 			else
    604 			{
    605 				auto* componentDesc = value.getDynamicObject();
    606 
    607 				if(componentDesc)
    608 				{
    609 					const auto& componentProps = componentDesc->getProperties();
    610 
    611 					std::map<std::string, std::string> properties;
    612 
    613 					for(int p=0; p<componentProps.size(); ++p)
    614 					{
    615 						properties.insert(std::make_pair(componentProps.getName(p).toString().toStdString(), componentProps.getValueAt(p).toString().toStdString()));
    616 					}
    617 
    618 					m_components.insert(std::make_pair(key, properties));
    619 				}
    620 			}
    621 		}
    622 
    623 		return true;
    624 	}
    625 
    626 	void UiObject::readProperties(juce::Component& _target)
    627 	{
    628 		const auto it = m_components.find("componentProperties");
    629 		if(it == m_components.end())
    630 			return;
    631 
    632 		const auto props = it->second;
    633 
    634 		for (const auto& prop : props)
    635 			_target.getProperties().set(juce::Identifier(prop.first.c_str()), juce::var(prop.second.c_str()));
    636 	}
    637 
    638 	template <typename T, class... Args> T* UiObject::createJuceObject(Editor& _editor, Args... _args)
    639 	{
    640 		T* comp = nullptr;
    641 
    642 		comp = _editor.createJuceComponent(comp, *this, _args...);
    643 
    644 		if(!comp)
    645 			comp = new T(_args...);
    646 
    647 		return createJuceObject(_editor, comp);
    648 	}
    649 
    650 	template <typename T> T* UiObject::createJuceObject(Editor& _editor, T* _object)
    651 	{
    652 		std::unique_ptr<T> c(_object);
    653 		apply(_editor, *c);
    654 		auto* comp = c.get();
    655 		m_juceObjects.emplace_back(std::move(c));
    656 
    657 		if(!m_name.empty())
    658 			_editor.registerComponent(m_name, comp);
    659 
    660 		auto* tooltipClient = dynamic_cast<juce::SettableTooltipClient*>(_object);
    661 
    662 		if(tooltipClient)
    663 		{
    664 			const auto tooltip = getProperty("tooltip");
    665 
    666 			if(!tooltip.empty())
    667 				tooltipClient->setTooltip(tooltip);
    668 		}
    669 
    670 		readProperties(*_object);
    671 
    672 		return comp;
    673 	}
    674 
    675 	template<typename T>
    676 	void UiObject::bindParameter(const Editor& _editor, T& _target) const
    677 	{
    678 		const std::string param = getProperty("parameter");
    679 
    680 		if(param.empty())
    681 			return;
    682 
    683 		const auto index = _editor.getInterface().getParameterIndexByName(param);
    684 
    685 		if(index < 0)
    686 			throw std::runtime_error("Parameter named " + param + " not found");
    687 
    688 		_target.getProperties().set("parameter", index);
    689 		_target.getProperties().set("parametername", juce::String(param));
    690 		
    691 		if constexpr(std::is_base_of_v<juce::Button, T>)
    692 		{
    693 			auto value = getPropertyInt("value", -1);
    694 			if(value != -1)
    695 				_target.getProperties().set("parametervalue", value);
    696 			auto valueOff = getPropertyInt("valueOff", -1);
    697 			if(valueOff != -1)
    698 				_target.getProperties().set("parametervalueoff", valueOff);
    699 		}
    700 
    701 		_editor.getInterface().bindParameter(_target, index);
    702 	}
    703 
    704 	template <typename Target, typename Style> void UiObject::createStyle(Editor& _editor, Target& _target, Style* _style)
    705 	{
    706 		if(_style)
    707 			m_style.reset(_style);
    708 		m_style->apply(_editor, *this);
    709 		_target.setLookAndFeel(m_style.get());
    710 	}
    711 }