/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) b) the Affero GPL v3 Details of these licenses can be found at: www.gnu.org/licenses JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.juce.com for more information. ============================================================================== */ #include "../../jucer_Headers.h" #include "jucer_ButtonDocument.h" #include "../jucer_UtilityFunctions.h" //============================================================================== static const int normalOff = 0; static const int overOff = 1; static const int downOff = 2; static const int normalOn = 3; static const int overOn = 4; static const int downOn = 5; static const int background = 6; //============================================================================== ButtonDocument::ButtonDocument (SourceCodeDocument* c) : JucerDocument (c) { paintStatesEnabled [normalOff] = true; paintStatesEnabled [overOff] = true; paintStatesEnabled [downOff] = true; paintStatesEnabled [normalOn] = false; paintStatesEnabled [overOn] = false; paintStatesEnabled [downOn] = false; paintStatesEnabled [background] = false; parentClasses = "public Button"; for (int i = 7; --i >= 0;) { paintRoutines[i] = new PaintRoutine(); paintRoutines[i]->setDocument (this); paintRoutines[i]->setBackgroundColour (Colours::transparentBlack); } } ButtonDocument::~ButtonDocument() { } static const char* const stateNames[] = { "normal", "over", "down", "normal on", "over on", "down on", "common background" }; int stateNameToIndex (const String& name) { for (int i = 7; --i >= 0;) if (name.equalsIgnoreCase (stateNames[i])) return i; jassertfalse; return normalOff; } int ButtonDocument::getNumPaintRoutines() const { int n = 0; for (int i = 7; --i >= 0;) if (paintStatesEnabled [i]) ++n; return n; } StringArray ButtonDocument::getPaintRoutineNames() const { StringArray s; for (int i = 0; i < 7; ++i) if (paintStatesEnabled [i]) s.add (stateNames [i]); return s; } PaintRoutine* ButtonDocument::getPaintRoutine (const int index) const { int n = 0; for (int i = 0; i < 7; ++i) { if (paintStatesEnabled [i]) { if (index == n) return paintRoutines [i]; else ++n; } } jassertfalse; return 0; } void ButtonDocument::setStatePaintRoutineEnabled (const int index, bool b) { jassert (index > 0 && index < 7); if (paintStatesEnabled [index] != b) { paintStatesEnabled [index] = b; changed(); } } bool ButtonDocument::isStatePaintRoutineEnabled (const int index) const { return paintStatesEnabled [index]; } int ButtonDocument::chooseBestEnabledPaintRoutine (int paintRoutineWanted) const { switch (paintRoutineWanted) { case normalOff: return normalOff; case overOff: return paintStatesEnabled [overOff] ? overOff : normalOff; case downOff: return paintStatesEnabled [downOff] ? downOff : chooseBestEnabledPaintRoutine (overOff); case normalOn: return paintStatesEnabled [normalOn] ? normalOn : normalOff; case overOn: return paintStatesEnabled [overOn] ? overOn : (paintStatesEnabled [normalOn] ? normalOn : chooseBestEnabledPaintRoutine (overOff)); case downOn: return paintStatesEnabled [downOn] ? downOn : ((paintStatesEnabled [overOn] || paintStatesEnabled [normalOn]) ? chooseBestEnabledPaintRoutine (overOn) : chooseBestEnabledPaintRoutine (downOff)); default: jassertfalse; break; } return normalOff; } //============================================================================== String ButtonDocument::getTypeName() const { return "Button"; } JucerDocument* ButtonDocument::createCopy() { ButtonDocument* newOne = new ButtonDocument (cpp); newOne->resources = resources; ScopedPointer xml (createXml()); newOne->loadFromXml (*xml); return newOne; } XmlElement* ButtonDocument::createXml() const { XmlElement* const doc = JucerDocument::createXml(); for (int i = 0; i < 7; ++i) { XmlElement* e = paintRoutines [i]->createXml(); e->setAttribute ("buttonState", stateNames [i]); e->setAttribute ("enabled", paintStatesEnabled [i]); doc->addChildElement (e); } return doc; } bool ButtonDocument::loadFromXml (const XmlElement& xml) { if (JucerDocument::loadFromXml (xml)) { for (int i = 7; --i >= 0;) paintStatesEnabled [i] = false; forEachXmlChildElementWithTagName (xml, e, PaintRoutine::xmlTagName) { const int stateIndex = stateNameToIndex (e->getStringAttribute ("buttonState")); paintRoutines [stateIndex]->loadFromXml (*e); paintStatesEnabled [stateIndex] = e->getBoolAttribute ("enabled", stateIndex < normalOn); } changed(); getUndoManager().clearUndoHistory(); return true; } return false; } void ButtonDocument::getOptionalMethods (StringArray& baseClasses, StringArray& returnValues, StringArray& methods, StringArray& initialContents) const { JucerDocument::getOptionalMethods (baseClasses, returnValues, methods, initialContents); addMethod ("Button", "void", "clicked()", "", baseClasses, returnValues, methods, initialContents); addMethod ("Button", "void", "buttonStateChanged()", "", baseClasses, returnValues, methods, initialContents); } //============================================================================== class ButtonStatePaintEnabledProperty : public BooleanPropertyComponent, private ChangeListener { public: ButtonStatePaintEnabledProperty (const String& name, ButtonDocument& doc, const int stateMethod_) : BooleanPropertyComponent (name, "enabled", "disabled"), document (doc), stateMethod (stateMethod_) { document.addChangeListener (this); } ~ButtonStatePaintEnabledProperty() { document.removeChangeListener (this); } void setState (bool newState) { document.setStatePaintRoutineEnabled (stateMethod, newState); } bool getState() const { return document.isStatePaintRoutineEnabled (stateMethod); } private: void changeListenerCallback (ChangeBroadcaster*) { refresh(); } ButtonDocument& document; const int stateMethod; }; void ButtonDocument::addExtraClassProperties (PropertyPanel& panel) { Array props; for (int i = 1; i < 7; ++i) props.add (new ButtonStatePaintEnabledProperty (stateNames[i], *this, i)); panel.addSection ("Button paint routines", props); } //============================================================================== class ButtonTestComponent : public Button { public: ButtonTestComponent (ButtonDocument* const doc, const bool fillBackground) : Button (String()), document (doc), alwaysFillBackground (fillBackground) { setClickingTogglesState (true); } void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) override { if (document->paintStatesEnabled [background]) { document->paintRoutines [background]->fillWithBackground (g, alwaysFillBackground); document->paintRoutines [background]->drawElements (g, getLocalBounds()); } const int stateIndex = getToggleState() ? (isButtonDown ? document->chooseBestEnabledPaintRoutine (downOn) : (isMouseOverButton ? document->chooseBestEnabledPaintRoutine (overOn) : document->chooseBestEnabledPaintRoutine (normalOn))) : (isButtonDown ? document->chooseBestEnabledPaintRoutine (downOff) : (isMouseOverButton ? document->chooseBestEnabledPaintRoutine (overOff) : normalOff)); document->paintRoutines [stateIndex]->fillWithBackground (g, ! document->paintStatesEnabled [background]); document->paintRoutines [stateIndex]->drawElements (g, getLocalBounds()); } private: ButtonDocument* const document; const bool alwaysFillBackground; }; Component* ButtonDocument::createTestComponent (const bool alwaysFillBackground) { return new ButtonTestComponent (this, alwaysFillBackground); } //============================================================================== void ButtonDocument::fillInGeneratedCode (GeneratedCode& code) const { JucerDocument::fillInGeneratedCode (code); code.parentClassInitialiser = "Button (" + quotedString (code.componentName, false) + ")"; code.removeCallback ("void", "paint (Graphics& g)"); } void ButtonDocument::fillInPaintCode (GeneratedCode& code) const { jassert (paintStatesEnabled [normalOff]); String paintCode [7]; for (int i = 0; i < 7; ++i) if (paintStatesEnabled [i]) paintRoutines[i]->fillInGeneratedCode (code, paintCode [i]); String& s = code.getCallbackCode ("public Button", "void", "paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown)", false); int numPaintRoutines = getNumPaintRoutines(); if (paintStatesEnabled [background]) { s << paintCode [background] << "\n"; --numPaintRoutines; } if (numPaintRoutines == 1) { s << paintCode [normalOff]; } else if (numPaintRoutines == downOff && (paintStatesEnabled [overOff] || paintStatesEnabled [downOff] || paintStatesEnabled [normalOn])) { if (paintStatesEnabled [normalOn]) { s << "if (getToggleState())\n{\n " << CodeHelpers::indent (paintCode [normalOn], 4, false).trimEnd(); } else if (paintStatesEnabled [overOff]) { s << "if (isButtonDown || isMouseOverButton)\n{\n " << CodeHelpers::indent (paintCode [overOff], 4, false).trimEnd(); } else { s << "if (isButtonDown)\n{\n " << CodeHelpers::indent (paintCode [downOff], 4, false).trimEnd(); } s << "\n}\nelse\n{\n " << CodeHelpers::indent (paintCode [normalOff], 4, false).trimEnd() << "\n}\n"; } else if (numPaintRoutines == normalOn && paintStatesEnabled [overOff] && paintStatesEnabled [downOff]) { s << "if (isButtonDown)\n{\n " << CodeHelpers::indent (paintCode [downOff], 4, false).trimEnd() << "\n}\nelse if (isMouseOverButton)\n{\n " << CodeHelpers::indent (paintCode [overOff], 4, false).trimEnd() << "\n}\nelse\n{\n " << CodeHelpers::indent (paintCode [normalOff], 4, false).trimEnd() << "\n}\n"; } else { if (paintStatesEnabled [normalOn] || paintStatesEnabled [overOn] || paintStatesEnabled [downOn]) { s << "switch (getToggleState() ? (isButtonDown ? " << chooseBestEnabledPaintRoutine (downOn) << " : (isMouseOverButton ? " << chooseBestEnabledPaintRoutine (overOn) << " : " << chooseBestEnabledPaintRoutine (normalOn) << "))\n : (isButtonDown ? " << chooseBestEnabledPaintRoutine (downOff) << " : (isMouseOverButton ? " << chooseBestEnabledPaintRoutine (overOff) << " : 0)))\n{\n"; } else { s << "switch (isButtonDown ? " << chooseBestEnabledPaintRoutine (downOff) << " : (isMouseOverButton ? " << chooseBestEnabledPaintRoutine (overOff) << " : 0))\n{\n"; } for (int i = 0; i < 6; ++i) { if (paintStatesEnabled [i]) { s << "case " << i << ":\n {\n " << CodeHelpers::indent (paintCode [i], 8, false).trimEnd() << "\n break;\n }\n\n"; } } s << "default:\n break;\n}\n"; } }