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