/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2013 - Raw Material Software 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. ============================================================================== */ Label::Label (const String& name, const String& labelText) : Component (name), textValue (labelText), lastTextValue (labelText), font (15.0f), justification (Justification::centredLeft), border (1, 5, 1, 5), minimumHorizontalScale (0.7f), editSingleClick (false), editDoubleClick (false), lossOfFocusDiscardsChanges (false) { setColour (TextEditor::textColourId, Colours::black); setColour (TextEditor::backgroundColourId, Colours::transparentBlack); setColour (TextEditor::outlineColourId, Colours::transparentBlack); textValue.addListener (this); } Label::~Label() { textValue.removeListener (this); if (ownerComponent != nullptr) ownerComponent->removeComponentListener (this); editor = nullptr; } //============================================================================== void Label::setText (const String& newText, const NotificationType notification) { hideEditor (true); if (lastTextValue != newText) { lastTextValue = newText; textValue = newText; repaint(); textWasChanged(); if (ownerComponent != nullptr) componentMovedOrResized (*ownerComponent, true, true); if (notification != dontSendNotification) callChangeListeners(); } } String Label::getText (const bool returnActiveEditorContents) const { return (returnActiveEditorContents && isBeingEdited()) ? editor->getText() : textValue.toString(); } void Label::valueChanged (Value&) { if (lastTextValue != textValue.toString()) setText (textValue.toString(), sendNotification); } //============================================================================== void Label::setFont (const Font& newFont) { if (font != newFont) { font = newFont; repaint(); } } Font Label::getFont() const noexcept { return font; } void Label::setEditable (const bool editOnSingleClick, const bool editOnDoubleClick, const bool lossOfFocusDiscardsChanges_) { editSingleClick = editOnSingleClick; editDoubleClick = editOnDoubleClick; lossOfFocusDiscardsChanges = lossOfFocusDiscardsChanges_; setWantsKeyboardFocus (editOnSingleClick || editOnDoubleClick); setFocusContainer (editOnSingleClick || editOnDoubleClick); } void Label::setJustificationType (Justification newJustification) { if (justification != newJustification) { justification = newJustification; repaint(); } } void Label::setBorderSize (BorderSize newBorder) { if (border != newBorder) { border = newBorder; repaint(); } } //============================================================================== Component* Label::getAttachedComponent() const { return static_cast (ownerComponent); } void Label::attachToComponent (Component* owner, const bool onLeft) { if (ownerComponent != nullptr) ownerComponent->removeComponentListener (this); ownerComponent = owner; leftOfOwnerComp = onLeft; if (ownerComponent != nullptr) { setVisible (owner->isVisible()); ownerComponent->addComponentListener (this); componentParentHierarchyChanged (*ownerComponent); componentMovedOrResized (*ownerComponent, true, true); } } void Label::componentMovedOrResized (Component& component, bool /*wasMoved*/, bool /*wasResized*/) { const Font f (getLookAndFeel().getLabelFont (*this)); if (leftOfOwnerComp) { setSize (jmin (f.getStringWidth (textValue.toString()) + 8, component.getX()), component.getHeight()); setTopRightPosition (component.getX(), component.getY()); } else { setSize (component.getWidth(), 8 + roundToInt (f.getHeight())); setTopLeftPosition (component.getX(), component.getY() - getHeight()); } } void Label::componentParentHierarchyChanged (Component& component) { if (Component* parent = component.getParentComponent()) parent->addChildComponent (this); } void Label::componentVisibilityChanged (Component& component) { setVisible (component.isVisible()); } //============================================================================== void Label::textWasEdited() {} void Label::textWasChanged() {} void Label::editorShown (TextEditor* textEditor) { Component::BailOutChecker checker (this); listeners.callChecked (checker, &LabelListener::editorShown, this, *textEditor); } void Label::editorAboutToBeHidden (TextEditor*) { if (ComponentPeer* const peer = getPeer()) peer->dismissPendingTextInput(); } void Label::showEditor() { if (editor == nullptr) { addAndMakeVisible (editor = createEditorComponent()); editor->setText (getText(), false); editor->addListener (this); editor->grabKeyboardFocus(); if (editor == nullptr) // may be deleted by a callback return; editor->setHighlightedRegion (Range (0, textValue.toString().length())); resized(); repaint(); editorShown (editor); enterModalState (false); editor->grabKeyboardFocus(); } } bool Label::updateFromTextEditorContents (TextEditor& ed) { const String newText (ed.getText()); if (textValue.toString() != newText) { lastTextValue = newText; textValue = newText; repaint(); textWasChanged(); if (ownerComponent != nullptr) componentMovedOrResized (*ownerComponent, true, true); return true; } return false; } void Label::hideEditor (const bool discardCurrentEditorContents) { if (editor != nullptr) { WeakReference deletionChecker (this); ScopedPointer outgoingEditor (editor); editorAboutToBeHidden (outgoingEditor); const bool changed = (! discardCurrentEditorContents) && updateFromTextEditorContents (*outgoingEditor); outgoingEditor = nullptr; repaint(); if (changed) textWasEdited(); if (deletionChecker != nullptr) exitModalState (0); if (changed && deletionChecker != nullptr) callChangeListeners(); } } void Label::inputAttemptWhenModal() { if (editor != nullptr) { if (lossOfFocusDiscardsChanges) textEditorEscapeKeyPressed (*editor); else textEditorReturnKeyPressed (*editor); } } bool Label::isBeingEdited() const noexcept { return editor != nullptr; } TextEditor* Label::createEditorComponent() { TextEditor* const ed = new TextEditor (getName()); ed->applyFontToAllText (getLookAndFeel().getLabelFont (*this)); copyAllExplicitColoursTo (*ed); return ed; } TextEditor* Label::getCurrentTextEditor() const noexcept { return editor; } //============================================================================== void Label::paint (Graphics& g) { getLookAndFeel().drawLabel (g, *this); } void Label::mouseUp (const MouseEvent& e) { if (editSingleClick && e.mouseWasClicked() && contains (e.getPosition()) && ! e.mods.isPopupMenu()) { showEditor(); } } void Label::mouseDoubleClick (const MouseEvent& e) { if (editDoubleClick && ! e.mods.isPopupMenu()) showEditor(); } void Label::resized() { if (editor != nullptr) editor->setBounds (getLocalBounds()); } void Label::focusGained (FocusChangeType cause) { if (editSingleClick && cause == focusChangedByTabKey) showEditor(); } void Label::enablementChanged() { repaint(); } void Label::colourChanged() { repaint(); } void Label::setMinimumHorizontalScale (const float newScale) { if (minimumHorizontalScale != newScale) { minimumHorizontalScale = newScale; repaint(); } } //============================================================================== // We'll use a custom focus traverser here to make sure focus goes from the // text editor to another component rather than back to the label itself. class LabelKeyboardFocusTraverser : public KeyboardFocusTraverser { public: LabelKeyboardFocusTraverser() {} Component* getNextComponent (Component* c) { return KeyboardFocusTraverser::getNextComponent (getComp (c)); } Component* getPreviousComponent (Component* c) { return KeyboardFocusTraverser::getPreviousComponent (getComp (c)); } static Component* getComp (Component* current) { return dynamic_cast (current) != nullptr ? current->getParentComponent() : current; } }; KeyboardFocusTraverser* Label::createFocusTraverser() { return new LabelKeyboardFocusTraverser(); } //============================================================================== void Label::addListener (LabelListener* const listener) { listeners.add (listener); } void Label::removeListener (LabelListener* const listener) { listeners.remove (listener); } void Label::callChangeListeners() { Component::BailOutChecker checker (this); listeners.callChecked (checker, &LabelListener::labelTextChanged, this); // (can't use Label::Listener due to idiotic VC2005 bug) } //============================================================================== void Label::textEditorTextChanged (TextEditor& ed) { if (editor != nullptr) { jassert (&ed == editor); if (! (hasKeyboardFocus (true) || isCurrentlyBlockedByAnotherModalComponent())) { if (lossOfFocusDiscardsChanges) textEditorEscapeKeyPressed (ed); else textEditorReturnKeyPressed (ed); } } } void Label::textEditorReturnKeyPressed (TextEditor& ed) { if (editor != nullptr) { jassert (&ed == editor); const bool changed = updateFromTextEditorContents (ed); hideEditor (true); if (changed) { WeakReference deletionChecker (this); textWasEdited(); if (deletionChecker != nullptr) callChangeListeners(); } } } void Label::textEditorEscapeKeyPressed (TextEditor& ed) { if (editor != nullptr) { jassert (&ed == editor); (void) ed; editor->setText (textValue.toString(), false); hideEditor (true); } } void Label::textEditorFocusLost (TextEditor& ed) { textEditorTextChanged (ed); } void Label::Listener::editorShown (Label*, TextEditor&) {}