1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
Label(const String & name,const String & labelText)29 Label::Label (const String& name, const String& labelText)
30     : Component (name),
31       textValue (labelText),
32       lastTextValue (labelText)
33 {
34     setColour (TextEditor::textColourId, Colours::black);
35     setColour (TextEditor::backgroundColourId, Colours::transparentBlack);
36     setColour (TextEditor::outlineColourId, Colours::transparentBlack);
37 
38     textValue.addListener (this);
39 }
40 
~Label()41 Label::~Label()
42 {
43     textValue.removeListener (this);
44 
45     if (ownerComponent != nullptr)
46         ownerComponent->removeComponentListener (this);
47 
48     editor.reset();
49 }
50 
51 //==============================================================================
setText(const String & newText,NotificationType notification)52 void Label::setText (const String& newText, NotificationType notification)
53 {
54     hideEditor (true);
55 
56     if (lastTextValue != newText)
57     {
58         lastTextValue = newText;
59         textValue = newText;
60         repaint();
61 
62         textWasChanged();
63 
64         if (ownerComponent != nullptr)
65             componentMovedOrResized (*ownerComponent, true, true);
66 
67         if (notification != dontSendNotification)
68             callChangeListeners();
69     }
70 }
71 
getText(bool returnActiveEditorContents) const72 String Label::getText (bool returnActiveEditorContents) const
73 {
74     return (returnActiveEditorContents && isBeingEdited())
75                 ? editor->getText()
76                 : textValue.toString();
77 }
78 
valueChanged(Value &)79 void Label::valueChanged (Value&)
80 {
81     if (lastTextValue != textValue.toString())
82         setText (textValue.toString(), sendNotification);
83 }
84 
85 //==============================================================================
setFont(const Font & newFont)86 void Label::setFont (const Font& newFont)
87 {
88     if (font != newFont)
89     {
90         font = newFont;
91         repaint();
92     }
93 }
94 
getFont() const95 Font Label::getFont() const noexcept
96 {
97     return font;
98 }
99 
setEditable(bool editOnSingleClick,bool editOnDoubleClick,bool lossOfFocusDiscards)100 void Label::setEditable (bool editOnSingleClick,
101                          bool editOnDoubleClick,
102                          bool lossOfFocusDiscards)
103 {
104     editSingleClick = editOnSingleClick;
105     editDoubleClick = editOnDoubleClick;
106     lossOfFocusDiscardsChanges = lossOfFocusDiscards;
107 
108     setWantsKeyboardFocus (editOnSingleClick || editOnDoubleClick);
109     setFocusContainer (editOnSingleClick || editOnDoubleClick);
110 }
111 
setJustificationType(Justification newJustification)112 void Label::setJustificationType (Justification newJustification)
113 {
114     if (justification != newJustification)
115     {
116         justification = newJustification;
117         repaint();
118     }
119 }
120 
setBorderSize(BorderSize<int> newBorder)121 void Label::setBorderSize (BorderSize<int> newBorder)
122 {
123     if (border != newBorder)
124     {
125         border = newBorder;
126         repaint();
127     }
128 }
129 
130 //==============================================================================
getAttachedComponent() const131 Component* Label::getAttachedComponent() const
132 {
133     return ownerComponent.get();
134 }
135 
attachToComponent(Component * owner,bool onLeft)136 void Label::attachToComponent (Component* owner, bool onLeft)
137 {
138     jassert (owner != this); // Not a great idea to try to attach it to itself!
139 
140     if (ownerComponent != nullptr)
141         ownerComponent->removeComponentListener (this);
142 
143     ownerComponent = owner;
144     leftOfOwnerComp = onLeft;
145 
146     if (ownerComponent != nullptr)
147     {
148         setVisible (owner->isVisible());
149         ownerComponent->addComponentListener (this);
150         componentParentHierarchyChanged (*ownerComponent);
151         componentMovedOrResized (*ownerComponent, true, true);
152     }
153 }
154 
componentMovedOrResized(Component & component,bool,bool)155 void Label::componentMovedOrResized (Component& component, bool /*wasMoved*/, bool /*wasResized*/)
156 {
157     auto& lf = getLookAndFeel();
158     auto f = lf.getLabelFont (*this);
159     auto borderSize = lf.getLabelBorderSize (*this);
160 
161     if (leftOfOwnerComp)
162     {
163         auto width = jmin (roundToInt (f.getStringWidthFloat (textValue.toString()) + 0.5f)
164                              + borderSize.getLeftAndRight(),
165                            component.getX());
166 
167         setBounds (component.getX() - width, component.getY(), width, component.getHeight());
168     }
169     else
170     {
171         auto height = borderSize.getTopAndBottom() + 6 + roundToInt (f.getHeight() + 0.5f);
172 
173         setBounds (component.getX(), component.getY() - height, component.getWidth(), height);
174     }
175 }
176 
componentParentHierarchyChanged(Component & component)177 void Label::componentParentHierarchyChanged (Component& component)
178 {
179     if (auto* parent = component.getParentComponent())
180         parent->addChildComponent (this);
181 }
182 
componentVisibilityChanged(Component & component)183 void Label::componentVisibilityChanged (Component& component)
184 {
185     setVisible (component.isVisible());
186 }
187 
188 //==============================================================================
textWasEdited()189 void Label::textWasEdited() {}
textWasChanged()190 void Label::textWasChanged() {}
191 
editorShown(TextEditor * textEditor)192 void Label::editorShown (TextEditor* textEditor)
193 {
194     Component::BailOutChecker checker (this);
195     listeners.callChecked (checker, [this, textEditor] (Label::Listener& l) { l.editorShown (this, *textEditor); });
196 
197     if (checker.shouldBailOut())
198         return;
199 
200     if (onEditorShow != nullptr)
201         onEditorShow();
202 }
203 
editorAboutToBeHidden(TextEditor * textEditor)204 void Label::editorAboutToBeHidden (TextEditor* textEditor)
205 {
206     if (auto* peer = getPeer())
207         peer->dismissPendingTextInput();
208 
209     Component::BailOutChecker checker (this);
210     listeners.callChecked (checker, [this, textEditor] (Label::Listener& l) { l.editorHidden (this, *textEditor); });
211 
212     if (checker.shouldBailOut())
213         return;
214 
215     if (onEditorHide != nullptr)
216         onEditorHide();
217 }
218 
showEditor()219 void Label::showEditor()
220 {
221     if (editor == nullptr)
222     {
223         editor.reset (createEditorComponent());
224         addAndMakeVisible (editor.get());
225         editor->setText (getText(), false);
226         editor->setKeyboardType (keyboardType);
227         editor->addListener (this);
228         editor->grabKeyboardFocus();
229 
230         if (editor == nullptr) // may be deleted by a callback
231             return;
232 
233         editor->setHighlightedRegion (Range<int> (0, textValue.toString().length()));
234 
235         resized();
236         repaint();
237 
238         editorShown (editor.get());
239 
240         enterModalState (false);
241         editor->grabKeyboardFocus();
242     }
243 }
244 
updateFromTextEditorContents(TextEditor & ed)245 bool Label::updateFromTextEditorContents (TextEditor& ed)
246 {
247     auto newText = ed.getText();
248 
249     if (textValue.toString() != newText)
250     {
251         lastTextValue = newText;
252         textValue = newText;
253         repaint();
254 
255         textWasChanged();
256 
257         if (ownerComponent != nullptr)
258             componentMovedOrResized (*ownerComponent, true, true);
259 
260         return true;
261     }
262 
263     return false;
264 }
265 
hideEditor(bool discardCurrentEditorContents)266 void Label::hideEditor (bool discardCurrentEditorContents)
267 {
268     if (editor != nullptr)
269     {
270         WeakReference<Component> deletionChecker (this);
271         std::unique_ptr<TextEditor> outgoingEditor;
272         std::swap (outgoingEditor, editor);
273 
274         editorAboutToBeHidden (outgoingEditor.get());
275 
276         const bool changed = (! discardCurrentEditorContents)
277                                && updateFromTextEditorContents (*outgoingEditor);
278         outgoingEditor.reset();
279         repaint();
280 
281         if (changed)
282             textWasEdited();
283 
284         if (deletionChecker != nullptr)
285             exitModalState (0);
286 
287         if (changed && deletionChecker != nullptr)
288             callChangeListeners();
289     }
290 }
291 
inputAttemptWhenModal()292 void Label::inputAttemptWhenModal()
293 {
294     if (editor != nullptr)
295     {
296         if (lossOfFocusDiscardsChanges)
297             textEditorEscapeKeyPressed (*editor);
298         else
299             textEditorReturnKeyPressed (*editor);
300     }
301 }
302 
isBeingEdited() const303 bool Label::isBeingEdited() const noexcept
304 {
305     return editor != nullptr;
306 }
307 
copyColourIfSpecified(Label & l,TextEditor & ed,int colourID,int targetColourID)308 static void copyColourIfSpecified (Label& l, TextEditor& ed, int colourID, int targetColourID)
309 {
310     if (l.isColourSpecified (colourID) || l.getLookAndFeel().isColourSpecified (colourID))
311         ed.setColour (targetColourID, l.findColour (colourID));
312 }
313 
createEditorComponent()314 TextEditor* Label::createEditorComponent()
315 {
316     auto* ed = new TextEditor (getName());
317     ed->applyFontToAllText (getLookAndFeel().getLabelFont (*this));
318     copyAllExplicitColoursTo (*ed);
319 
320     copyColourIfSpecified (*this, *ed, textWhenEditingColourId, TextEditor::textColourId);
321     copyColourIfSpecified (*this, *ed, backgroundWhenEditingColourId, TextEditor::backgroundColourId);
322     copyColourIfSpecified (*this, *ed, outlineWhenEditingColourId, TextEditor::focusedOutlineColourId);
323 
324     return ed;
325 }
326 
getCurrentTextEditor() const327 TextEditor* Label::getCurrentTextEditor() const noexcept
328 {
329     return editor.get();
330 }
331 
332 //==============================================================================
paint(Graphics & g)333 void Label::paint (Graphics& g)
334 {
335     getLookAndFeel().drawLabel (g, *this);
336 }
337 
mouseUp(const MouseEvent & e)338 void Label::mouseUp (const MouseEvent& e)
339 {
340     if (editSingleClick
341          && isEnabled()
342          && contains (e.getPosition())
343          && ! (e.mouseWasDraggedSinceMouseDown() || e.mods.isPopupMenu()))
344     {
345         showEditor();
346     }
347 }
348 
mouseDoubleClick(const MouseEvent & e)349 void Label::mouseDoubleClick (const MouseEvent& e)
350 {
351     if (editDoubleClick
352          && isEnabled()
353          && ! e.mods.isPopupMenu())
354         showEditor();
355 }
356 
resized()357 void Label::resized()
358 {
359     if (editor != nullptr)
360         editor->setBounds (getLocalBounds());
361 }
362 
focusGained(FocusChangeType cause)363 void Label::focusGained (FocusChangeType cause)
364 {
365     if (editSingleClick
366          && isEnabled()
367          && cause == focusChangedByTabKey)
368         showEditor();
369 }
370 
enablementChanged()371 void Label::enablementChanged()
372 {
373     repaint();
374 }
375 
colourChanged()376 void Label::colourChanged()
377 {
378     repaint();
379 }
380 
setMinimumHorizontalScale(const float newScale)381 void Label::setMinimumHorizontalScale (const float newScale)
382 {
383     if (minimumHorizontalScale != newScale)
384     {
385         minimumHorizontalScale = newScale;
386         repaint();
387     }
388 }
389 
390 //==============================================================================
391 // We'll use a custom focus traverser here to make sure focus goes from the
392 // text editor to another component rather than back to the label itself.
393 class LabelKeyboardFocusTraverser   : public KeyboardFocusTraverser
394 {
395 public:
LabelKeyboardFocusTraverser()396     LabelKeyboardFocusTraverser() {}
397 
getNextComponent(Component * c)398     Component* getNextComponent (Component* c) override     { return KeyboardFocusTraverser::getNextComponent (getComp (c)); }
getPreviousComponent(Component * c)399     Component* getPreviousComponent (Component* c) override { return KeyboardFocusTraverser::getPreviousComponent (getComp (c)); }
400 
getComp(Component * current)401     static Component* getComp (Component* current)
402     {
403         return dynamic_cast<TextEditor*> (current) != nullptr
404                  ? current->getParentComponent() : current;
405     }
406 };
407 
createFocusTraverser()408 KeyboardFocusTraverser* Label::createFocusTraverser()
409 {
410     return new LabelKeyboardFocusTraverser();
411 }
412 
413 //==============================================================================
addListener(Label::Listener * l)414 void Label::addListener    (Label::Listener* l)     { listeners.add (l); }
removeListener(Label::Listener * l)415 void Label::removeListener (Label::Listener* l)     { listeners.remove (l); }
416 
callChangeListeners()417 void Label::callChangeListeners()
418 {
419     Component::BailOutChecker checker (this);
420     listeners.callChecked (checker, [this] (Listener& l) { l.labelTextChanged (this); });
421 
422     if (checker.shouldBailOut())
423         return;
424 
425     if (onTextChange != nullptr)
426         onTextChange();
427 }
428 
429 //==============================================================================
textEditorTextChanged(TextEditor & ed)430 void Label::textEditorTextChanged (TextEditor& ed)
431 {
432     if (editor != nullptr)
433     {
434         jassert (&ed == editor.get());
435 
436         if (! (hasKeyboardFocus (true) || isCurrentlyBlockedByAnotherModalComponent()))
437         {
438             if (lossOfFocusDiscardsChanges)
439                 textEditorEscapeKeyPressed (ed);
440             else
441                 textEditorReturnKeyPressed (ed);
442         }
443     }
444 }
445 
textEditorReturnKeyPressed(TextEditor & ed)446 void Label::textEditorReturnKeyPressed (TextEditor& ed)
447 {
448     if (editor != nullptr)
449     {
450         jassert (&ed == editor.get());
451 
452         WeakReference<Component> deletionChecker (this);
453         bool changed = updateFromTextEditorContents (ed);
454         hideEditor (true);
455 
456         if (changed && deletionChecker != nullptr)
457         {
458             textWasEdited();
459 
460             if (deletionChecker != nullptr)
461                 callChangeListeners();
462         }
463     }
464 }
465 
textEditorEscapeKeyPressed(TextEditor & ed)466 void Label::textEditorEscapeKeyPressed (TextEditor& ed)
467 {
468     if (editor != nullptr)
469     {
470         jassert (&ed == editor.get());
471         ignoreUnused (ed);
472 
473         editor->setText (textValue.toString(), false);
474         hideEditor (true);
475     }
476 }
477 
textEditorFocusLost(TextEditor & ed)478 void Label::textEditorFocusLost (TextEditor& ed)
479 {
480     textEditorTextChanged (ed);
481 }
482 
483 } // namespace juce
484