/* ============================================================================== 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. ============================================================================== */ #pragma once #include "jucer_ColouredElement.h" #include "../properties/jucer_FontPropertyComponent.h" #include "../properties/jucer_JustificationProperty.h" //============================================================================== class PaintElementText : public ColouredElement { public: PaintElementText (PaintRoutine* pr) : ColouredElement (pr, "Text", false, false), text ("Your text goes here"), font (15.0f), typefaceName (FontPropertyComponent::getDefaultFont()), justification (Justification::centred) { fillType.colour = Colours::black; position.rect.setWidth (200); position.rect.setHeight (30); } //============================================================================== void draw (Graphics& g, const ComponentLayout* layout, const Rectangle& parentArea) { fillType.setFillType (g, getDocument(), parentArea); font = FontPropertyComponent::applyNameToFont (typefaceName, font); g.setFont (font); g.drawText (replaceStringTranslations (text, owner->getDocument()), position.getRectangle (parentArea, layout), justification, true); } static String replaceStringTranslations (String s, JucerDocument* document) { s = s.replace ("%%getName()%%", document->getComponentName()); s = s.replace ("%%getButtonText()%%", document->getComponentName()); return s; } void getEditableProperties (Array& props) { ColouredElement::getEditableProperties (props); props.add (new TextProperty (this)); props.add (new FontNameProperty (this)); props.add (new FontStyleProperty (this)); props.add (new FontSizeProperty (this)); props.add (new FontKerningProperty (this)); props.add (new TextJustificationProperty (this)); props.add (new TextToPathProperty (this)); } void fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode) { if (! fillType.isInvisible()) { String r; fillType.fillInGeneratedCode (code, paintMethodCode); String x, y, w, h; positionToCode (position, code.document->getComponentLayout(), x, y, w, h); r << "g.setFont (" << FontPropertyComponent::getCompleteFontCode (font, typefaceName) << ");\ng.drawText (" << quotedString (text, code.shouldUseTransMacro()) << ",\n " << x << ", " << y << ", " << w << ", " << h << ",\n " << CodeHelpers::justificationToCode (justification) << ", true);\n\n"; paintMethodCode += r; } } static const char* getTagName() noexcept { return "TEXT"; } XmlElement* createXml() const { XmlElement* e = new XmlElement (getTagName()); position.applyToXml (*e); addColourAttributes (e); e->setAttribute ("text", text); e->setAttribute ("fontname", typefaceName); e->setAttribute ("fontsize", roundToInt (font.getHeight() * 100.0) / 100.0); e->setAttribute ("kerning", roundToInt (font.getExtraKerningFactor() * 1000.0) / 1000.0); e->setAttribute ("bold", font.isBold()); e->setAttribute ("italic", font.isItalic()); e->setAttribute ("justification", justification.getFlags()); if (font.getTypefaceStyle() != "Regular") { e->setAttribute ("typefaceStyle", font.getTypefaceStyle()); } return e; } bool loadFromXml (const XmlElement& xml) { if (xml.hasTagName (getTagName())) { position.restoreFromXml (xml, position); loadColourAttributes (xml); text = xml.getStringAttribute ("text", "Hello World"); typefaceName = xml.getStringAttribute ("fontname", FontPropertyComponent::getDefaultFont()); font.setHeight ((float) xml.getDoubleAttribute ("fontsize", 15.0)); font.setBold (xml.getBoolAttribute ("bold", false)); font.setItalic (xml.getBoolAttribute ("italic", false)); font.setExtraKerningFactor ((float) xml.getDoubleAttribute ("kerning", 0.0)); justification = Justification (xml.getIntAttribute ("justification", Justification::centred)); auto fontStyle = xml.getStringAttribute ("typefaceStyle"); if (! fontStyle.isEmpty()) font.setTypefaceStyle (fontStyle); return true; } jassertfalse; return false; } //============================================================================== const String& getText() const noexcept { return text; } class SetTextAction : public PaintElementUndoableAction { public: SetTextAction (PaintElementText* const element, const String& newText_) : PaintElementUndoableAction (element), newText (newText_), oldText (element->getText()) { } bool perform() { showCorrectTab(); getElement()->setText (newText, false); return true; } bool undo() { showCorrectTab(); getElement()->setText (oldText, false); return true; } private: String newText, oldText; }; void setText (const String& t, const bool undoable) { if (t != text) { if (undoable) { perform (new SetTextAction (this, t), "Change text element text"); } else { text = t; changed(); } } } //============================================================================== const Font& getFont() const { return font; } class SetFontAction : public PaintElementUndoableAction { public: SetFontAction (PaintElementText* const element, const Font& newFont_) : PaintElementUndoableAction (element), newFont (newFont_), oldFont (element->getFont()) { } bool perform() { showCorrectTab(); getElement()->setFont (newFont, false); return true; } bool undo() { showCorrectTab(); getElement()->setFont (oldFont, false); return true; } private: Font newFont, oldFont; }; void setFont (const Font& newFont, const bool undoable) { if (font != newFont) { if (undoable) { perform (new SetFontAction (this, newFont), "Change text element font"); } else { font = newFont; changed(); } } } //============================================================================== class SetTypefaceAction : public PaintElementUndoableAction { public: SetTypefaceAction (PaintElementText* const element, const String& newValue_) : PaintElementUndoableAction (element), newValue (newValue_), oldValue (element->getTypefaceName()) { } bool perform() { showCorrectTab(); getElement()->setTypefaceName (newValue, false); return true; } bool undo() { showCorrectTab(); getElement()->setTypefaceName (oldValue, false); return true; } private: String newValue, oldValue; }; void setTypefaceName (const String& newFontName, const bool undoable) { if (undoable) { perform (new SetTypefaceAction (this, newFontName), "Change text element typeface"); } else { typefaceName = newFontName; changed(); } } String getTypefaceName() const noexcept { return typefaceName; } //============================================================================== Justification getJustification() const noexcept { return justification; } class SetJustifyAction : public PaintElementUndoableAction { public: SetJustifyAction (PaintElementText* const element, Justification newValue_) : PaintElementUndoableAction (element), newValue (newValue_), oldValue (element->getJustification()) { } bool perform() { showCorrectTab(); getElement()->setJustification (newValue, false); return true; } bool undo() { showCorrectTab(); getElement()->setJustification (oldValue, false); return true; } private: Justification newValue, oldValue; }; void setJustification (Justification j, const bool undoable) { if (justification.getFlags() != j.getFlags()) { if (undoable) { perform (new SetJustifyAction (this, j), "Change text element justification"); } else { justification = j; changed(); } } } void convertToPath() { if (PaintRoutineEditor* parent = dynamic_cast (getParentComponent())) { font = FontPropertyComponent::applyNameToFont (typefaceName, font); const Rectangle r = getCurrentBounds (parent->getComponentArea().withZeroOrigin()); GlyphArrangement arr; arr.addCurtailedLineOfText (font, text, 0.0f, 0.0f, (float) r.getWidth(), true); arr.justifyGlyphs (0, arr.getNumGlyphs(), (float) r.getX(), (float) r.getY(), (float) r.getWidth(), (float) r.getHeight(), justification); Path path; arr.createPath (path); convertToNewPathElement (path); } else { jassertfalse; } } private: String text; Font font; String typefaceName; Justification justification; Array justificationTypes; //============================================================================== class TextProperty : public TextPropertyComponent, public ChangeListener { public: TextProperty (PaintElementText* const e) : TextPropertyComponent ("text", 2048, false), element (e) { element->getDocument()->addChangeListener (this); } ~TextProperty() { element->getDocument()->removeChangeListener (this); } void setText (const String& newText) override { element->setText (newText, true); } String getText() const override { return element->getText(); } void changeListenerCallback (ChangeBroadcaster*) override { refresh(); } private: PaintElementText* const element; }; //============================================================================== class FontNameProperty : public FontPropertyComponent, public ChangeListener { public: FontNameProperty (PaintElementText* const e) : FontPropertyComponent ("font"), element (e) { element->getDocument()->addChangeListener (this); } ~FontNameProperty() { element->getDocument()->removeChangeListener (this); } void setTypefaceName (const String& newFontName) { element->setTypefaceName (newFontName, true); } String getTypefaceName() const { return element->getTypefaceName(); } void changeListenerCallback (ChangeBroadcaster*) { refresh(); } private: PaintElementText* const element; }; //============================================================================== class FontStyleProperty : public ChoicePropertyComponent, public ChangeListener { public: FontStyleProperty (PaintElementText* const e) : ChoicePropertyComponent ("style"), element (e) { element->getDocument()->addChangeListener (this); updateStylesList (element->getTypefaceName()); } ~FontStyleProperty() { element->getDocument()->removeChangeListener (this); } void updateStylesList (const String& name) { if (getNumChildComponents() > 0) { if (auto cb = dynamic_cast (getChildComponent (0))) cb->clear(); getChildComponent (0)->setVisible (false); removeAllChildren(); } choices.clear(); choices.add ("Regular"); choices.add ("Bold"); choices.add ("Italic"); choices.add ("Bold Italic"); choices.mergeArray (Font::findAllTypefaceStyles (name)); refresh(); } void setIndex (int newIndex) { Font f (element->getFont()); if (f.getAvailableStyles().contains (choices[newIndex])) { f.setBold (false); f.setItalic (false); f.setTypefaceStyle (choices[newIndex]); } else { f.setTypefaceStyle ("Regular"); f.setBold (newIndex == 1 || newIndex == 3); f.setItalic (newIndex == 2 || newIndex == 3); } element->setFont (f, true); } int getIndex() const { auto f = element->getFont(); const auto typefaceIndex = choices.indexOf (f.getTypefaceStyle()); if (typefaceIndex == -1) { if (f.isBold() && f.isItalic()) return 3; else if (f.isBold()) return 1; else if (f.isItalic()) return 2; return 0; } return typefaceIndex; } void changeListenerCallback (ChangeBroadcaster*) { updateStylesList (element->getTypefaceName()); } private: PaintElementText* const element; }; //============================================================================== class FontSizeProperty : public SliderPropertyComponent, public ChangeListener { public: FontSizeProperty (PaintElementText* const e) : SliderPropertyComponent ("size", 1.0, 250.0, 0.1, 0.3), element (e) { element->getDocument()->addChangeListener (this); } ~FontSizeProperty() { element->getDocument()->removeChangeListener (this); } void setValue (double newValue) { element->getDocument()->getUndoManager().undoCurrentTransactionOnly(); Font f (element->getFont()); f.setHeight ((float) newValue); element->setFont (f, true); } double getValue() const { return element->getFont().getHeight(); } void changeListenerCallback (ChangeBroadcaster*) { refresh(); } private: PaintElementText* const element; }; //============================================================================== class FontKerningProperty : public SliderPropertyComponent, public ChangeListener { public: FontKerningProperty (PaintElementText* const e) : SliderPropertyComponent ("kerning", -0.5, 0.5, 0.001), element (e) { element->getDocument()->addChangeListener (this); } ~FontKerningProperty() { element->getDocument()->removeChangeListener (this); } void setValue (double newValue) { element->getDocument()->getUndoManager().undoCurrentTransactionOnly(); Font f (element->getFont()); f.setExtraKerningFactor ((float) newValue); element->setFont (f, true); } double getValue() const { return element->getFont().getExtraKerningFactor(); } void changeListenerCallback (ChangeBroadcaster*) { refresh(); } private: PaintElementText* const element; }; //============================================================================== class TextJustificationProperty : public JustificationProperty, public ChangeListener { public: TextJustificationProperty (PaintElementText* const e) : JustificationProperty ("layout", false), element (e) { element->getDocument()->addChangeListener (this); } ~TextJustificationProperty() { element->getDocument()->removeChangeListener (this); } void setJustification (Justification newJustification) { element->setJustification (newJustification, true); } Justification getJustification() const { return element->getJustification(); } void changeListenerCallback (ChangeBroadcaster*) { refresh(); } private: PaintElementText* const element; }; //============================================================================== class TextToPathProperty : public ButtonPropertyComponent { public: TextToPathProperty (PaintElementText* const e) : ButtonPropertyComponent ("path", false), element (e) { } void buttonClicked() { element->convertToPath(); } String getButtonText() const { return "convert text to a path"; } private: PaintElementText* const element; }; };