JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_TextEditor.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - 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 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-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
26namespace juce
27{
28
29// a word or space that can't be broken down any further
31{
32 //==============================================================================
33 String atomText;
34 float width;
35 int numChars;
36
37 //==============================================================================
38 bool isWhitespace() const noexcept { return CharacterFunctions::isWhitespace (atomText[0]); }
39 bool isNewLine() const noexcept { return atomText[0] == '\r' || atomText[0] == '\n'; }
40
41 String getText (juce_wchar passwordCharacter) const
42 {
43 if (passwordCharacter == 0)
44 return atomText;
45
46 return String::repeatedString (String::charToString (passwordCharacter),
47 atomText.length());
48 }
49
50 String getTrimmedText (const juce_wchar passwordCharacter) const
51 {
52 if (passwordCharacter == 0)
53 return atomText.substring (0, numChars);
54
55 if (isNewLine())
56 return {};
57
58 return String::repeatedString (String::charToString (passwordCharacter), numChars);
59 }
60
62};
63
64//==============================================================================
65// a run of text with a single font and colour
67{
68public:
70 : font (f), colour (col), passwordChar (passwordCharToUse)
71 {
72 initialiseAtoms (text);
73 }
74
75 UniformTextSection (const UniformTextSection&) = default;
77
78 UniformTextSection& operator= (const UniformTextSection&) = delete;
79
80 void append (UniformTextSection& other)
81 {
82 if (! other.atoms.isEmpty())
83 {
84 int i = 0;
85
86 if (! atoms.isEmpty())
87 {
88 auto& lastAtom = atoms.getReference (atoms.size() - 1);
89
90 if (! CharacterFunctions::isWhitespace (lastAtom.atomText.getLastCharacter()))
91 {
92 auto& first = other.atoms.getReference (0);
93
94 if (! CharacterFunctions::isWhitespace (first.atomText[0]))
95 {
96 lastAtom.atomText += first.atomText;
97 lastAtom.numChars = (uint16) (lastAtom.numChars + first.numChars);
98 lastAtom.width = font.getStringWidthFloat (lastAtom.getText (passwordChar));
99 ++i;
100 }
101 }
102 }
103
104 atoms.ensureStorageAllocated (atoms.size() + other.atoms.size() - i);
105
106 while (i < other.atoms.size())
107 {
108 atoms.add (other.atoms.getReference (i));
109 ++i;
110 }
111 }
112 }
113
115 {
116 auto* section2 = new UniformTextSection ({}, font, colour, passwordChar);
117 int index = 0;
118
119 for (int i = 0; i < atoms.size(); ++i)
120 {
121 auto& atom = atoms.getReference (i);
122 auto nextIndex = index + atom.numChars;
123
124 if (index == indexToBreakAt)
125 {
126 for (int j = i; j < atoms.size(); ++j)
127 section2->atoms.add (atoms.getUnchecked (j));
128
129 atoms.removeRange (i, atoms.size());
130 break;
131 }
132
133 if (indexToBreakAt >= index && indexToBreakAt < nextIndex)
134 {
136 secondAtom.atomText = atom.atomText.substring (indexToBreakAt - index);
137 secondAtom.width = font.getStringWidthFloat (secondAtom.getText (passwordChar));
138 secondAtom.numChars = (uint16) secondAtom.atomText.length();
139
140 section2->atoms.add (secondAtom);
141
142 atom.atomText = atom.atomText.substring (0, indexToBreakAt - index);
143 atom.width = font.getStringWidthFloat (atom.getText (passwordChar));
144 atom.numChars = (uint16) (indexToBreakAt - index);
145
146 for (int j = i + 1; j < atoms.size(); ++j)
147 section2->atoms.add (atoms.getUnchecked (j));
148
149 atoms.removeRange (i + 1, atoms.size());
150 break;
151 }
152
153 index = nextIndex;
154 }
155
156 return section2;
157 }
158
159 void appendAllText (MemoryOutputStream& mo) const
160 {
161 for (auto& atom : atoms)
162 mo << atom.atomText;
163 }
164
165 void appendSubstring (MemoryOutputStream& mo, Range<int> range) const
166 {
167 int index = 0;
168
169 for (auto& atom : atoms)
170 {
171 auto nextIndex = index + atom.numChars;
172
173 if (range.getStart() < nextIndex)
174 {
175 if (range.getEnd() <= index)
176 break;
177
178 auto r = (range - index).getIntersectionWith ({ 0, (int) atom.numChars });
179
180 if (! r.isEmpty())
181 mo << atom.atomText.substring (r.getStart(), r.getEnd());
182 }
183
184 index = nextIndex;
185 }
186 }
187
188 int getTotalLength() const noexcept
189 {
190 int total = 0;
191
192 for (auto& atom : atoms)
193 total += atom.numChars;
194
195 return total;
196 }
197
198 void setFont (const Font& newFont, const juce_wchar passwordCharToUse)
199 {
200 if (font != newFont || passwordChar != passwordCharToUse)
201 {
202 font = newFont;
203 passwordChar = passwordCharToUse;
204
205 for (auto& atom : atoms)
206 atom.width = newFont.getStringWidthFloat (atom.getText (passwordChar));
207 }
208 }
209
210 //==============================================================================
211 Font font;
212 Colour colour;
213 Array<TextAtom> atoms;
214 juce_wchar passwordChar;
215
216private:
217 void initialiseAtoms (const String& textToParse)
218 {
219 auto text = textToParse.getCharPointer();
220
221 while (! text.isEmpty())
222 {
223 size_t numChars = 0;
224 auto start = text;
225
226 // create a whitespace atom unless it starts with non-ws
227 if (text.isWhitespace() && *text != '\r' && *text != '\n')
228 {
229 do
230 {
231 ++text;
232 ++numChars;
233 }
234 while (text.isWhitespace() && *text != '\r' && *text != '\n');
235 }
236 else
237 {
238 if (*text == '\r')
239 {
240 ++text;
241 ++numChars;
242
243 if (*text == '\n')
244 {
245 ++start;
246 ++text;
247 }
248 }
249 else if (*text == '\n')
250 {
251 ++text;
252 ++numChars;
253 }
254 else
255 {
256 while (! (text.isEmpty() || text.isWhitespace()))
257 {
258 ++text;
259 ++numChars;
260 }
261 }
262 }
263
264 TextAtom atom;
265 atom.atomText = String (start, numChars);
266 atom.width = (atom.isNewLine() ? 0.0f : font.getStringWidthFloat (atom.getText (passwordChar)));
267 atom.numChars = (uint16) numChars;
268 atoms.add (atom);
269 }
270 }
271
273};
274
275//==============================================================================
277{
278 Iterator (const TextEditor& ed)
279 : sections (ed.sections),
280 justification (ed.justification),
281 bottomRight ((float) ed.getMaximumTextWidth(), (float) ed.getMaximumTextHeight()),
282 wordWrapWidth ((float) ed.getWordWrapWidth()),
283 passwordCharacter (ed.passwordCharacter),
284 lineSpacing (ed.lineSpacing),
285 underlineWhitespace (ed.underlineWhitespace)
286 {
287 jassert (wordWrapWidth > 0);
288
289 if (! sections.isEmpty())
290 {
291 currentSection = sections.getUnchecked (sectionIndex);
292
293 if (currentSection != nullptr)
294 beginNewLine();
295 }
296
297 lineHeight = ed.currentFont.getHeight();
298 }
299
300 Iterator (const Iterator&) = default;
301 Iterator& operator= (const Iterator&) = delete;
302
303 //==============================================================================
304 bool next()
305 {
306 if (atom == &longAtom && chunkLongAtom (true))
307 return true;
308
309 if (sectionIndex >= sections.size())
310 {
311 moveToEndOfLastAtom();
312 return false;
313 }
314
315 bool forceNewLine = false;
316
317 if (atomIndex >= currentSection->atoms.size() - 1)
318 {
319 if (atomIndex >= currentSection->atoms.size())
320 {
321 if (++sectionIndex >= sections.size())
322 {
323 moveToEndOfLastAtom();
324 return false;
325 }
326
327 atomIndex = 0;
328 currentSection = sections.getUnchecked (sectionIndex);
329 }
330 else
331 {
332 auto& lastAtom = currentSection->atoms.getReference (atomIndex);
333
334 if (! lastAtom.isWhitespace())
335 {
336 // handle the case where the last atom in a section is actually part of the same
337 // word as the first atom of the next section...
338 float right = atomRight + lastAtom.width;
339 float lineHeight2 = lineHeight;
340 float maxDescent2 = maxDescent;
341
342 for (int section = sectionIndex + 1; section < sections.size(); ++section)
343 {
344 auto* s = sections.getUnchecked (section);
345
346 if (s->atoms.size() == 0)
347 break;
348
349 auto& nextAtom = s->atoms.getReference (0);
350
351 if (nextAtom.isWhitespace())
352 break;
353
354 right += nextAtom.width;
355
356 lineHeight2 = jmax (lineHeight2, s->font.getHeight());
357 maxDescent2 = jmax (maxDescent2, s->font.getDescent());
358
359 if (shouldWrap (right))
360 {
361 lineHeight = lineHeight2;
362 maxDescent = maxDescent2;
363
364 forceNewLine = true;
365 break;
366 }
367
368 if (s->atoms.size() > 1)
369 break;
370 }
371 }
372 }
373 }
374
375 bool isInPreviousAtom = false;
376
377 if (atom != nullptr)
378 {
379 atomX = atomRight;
380 indexInText += atom->numChars;
381
382 if (atom->isNewLine())
383 beginNewLine();
384 else
385 isInPreviousAtom = true;
386 }
387
388 atom = &(currentSection->atoms.getReference (atomIndex));
389 atomRight = atomX + atom->width;
390 ++atomIndex;
391
392 if (shouldWrap (atomRight) || forceNewLine)
393 {
394 if (atom->isWhitespace())
395 {
396 // leave whitespace at the end of a line, but truncate it to avoid scrolling
397 atomRight = jmin (atomRight, wordWrapWidth);
398 }
399 else if (shouldWrap (atom->width)) // atom too big to fit on a line, so break it up..
400 {
401 longAtom = *atom;
402 longAtom.numChars = 0;
403 atom = &longAtom;
404 chunkLongAtom (isInPreviousAtom);
405 }
406 else
407 {
408 beginNewLine();
409 atomRight = atomX + atom->width;
410 }
411 }
412
413 return true;
414 }
415
416 void beginNewLine()
417 {
418 lineY += lineHeight * lineSpacing;
419 float lineWidth = 0;
420
421 auto tempSectionIndex = sectionIndex;
422 auto tempAtomIndex = atomIndex;
423 auto* section = sections.getUnchecked (tempSectionIndex);
424
425 lineHeight = section->font.getHeight();
426 maxDescent = section->font.getDescent();
427
428 float nextLineWidth = (atom != nullptr) ? atom->width : 0.0f;
429
430 while (! shouldWrap (nextLineWidth))
431 {
433
434 if (tempSectionIndex >= sections.size())
435 break;
436
437 bool checkSize = false;
438
439 if (tempAtomIndex >= section->atoms.size())
440 {
441 if (++tempSectionIndex >= sections.size())
442 break;
443
444 tempAtomIndex = 0;
445 section = sections.getUnchecked (tempSectionIndex);
446 checkSize = true;
447 }
448
449 if (! isPositiveAndBelow (tempAtomIndex, section->atoms.size()))
450 break;
451
452 auto& nextAtom = section->atoms.getReference (tempAtomIndex);
453 nextLineWidth += nextAtom.width;
454
455 if (shouldWrap (nextLineWidth) || nextAtom.isNewLine())
456 break;
457
458 if (checkSize)
459 {
460 lineHeight = jmax (lineHeight, section->font.getHeight());
461 maxDescent = jmax (maxDescent, section->font.getDescent());
462 }
463
465 }
466
467 atomX = getJustificationOffsetX (lineWidth);
468 }
469
470 float getJustificationOffsetX (float lineWidth) const
471 {
472 if (justification.testFlags (Justification::horizontallyCentred)) return jmax (0.0f, (bottomRight.x - lineWidth) * 0.5f);
473 if (justification.testFlags (Justification::right)) return jmax (0.0f, bottomRight.x - lineWidth);
474
475 return 0;
476 }
477
478 //==============================================================================
479 void draw (Graphics& g, const UniformTextSection*& lastSection, AffineTransform transform) const
480 {
481 if (atom == nullptr)
482 return;
483
484 if (passwordCharacter != 0 || (underlineWhitespace || ! atom->isWhitespace()))
485 {
486 if (lastSection != currentSection)
487 {
488 lastSection = currentSection;
489 g.setColour (currentSection->colour);
490 g.setFont (currentSection->font);
491 }
492
493 jassert (atom->getTrimmedText (passwordCharacter).isNotEmpty());
494
496 ga.addLineOfText (currentSection->font,
497 atom->getTrimmedText (passwordCharacter),
498 atomX, (float) roundToInt (lineY + lineHeight - maxDescent));
499 ga.draw (g, transform);
500 }
501 }
502
503 void drawUnderline (Graphics& g, Range<int> underline, Colour colour, AffineTransform transform) const
504 {
505 auto startX = roundToInt (indexToX (underline.getStart()));
506 auto endX = roundToInt (indexToX (underline.getEnd()));
507 auto baselineY = roundToInt (lineY + currentSection->font.getAscent() + 0.5f);
508
510 g.addTransform (transform);
512 g.fillCheckerBoard ({ (float) endX, (float) baselineY + 1.0f }, 3.0f, 1.0f, colour, Colours::transparentBlack);
513 }
514
515 void drawSelectedText (Graphics& g, Range<int> selected, Colour selectedTextColour, AffineTransform transform) const
516 {
517 if (atom == nullptr)
518 return;
519
520 if (passwordCharacter != 0 || ! atom->isWhitespace())
521 {
523 ga.addLineOfText (currentSection->font,
524 atom->getTrimmedText (passwordCharacter),
525 atomX, (float) roundToInt (lineY + lineHeight - maxDescent));
526
527 if (selected.getEnd() < indexInText + atom->numChars)
528 {
530 ga2.removeRangeOfGlyphs (0, selected.getEnd() - indexInText);
531 ga.removeRangeOfGlyphs (selected.getEnd() - indexInText, -1);
532
533 g.setColour (currentSection->colour);
534 ga2.draw (g, transform);
535 }
536
537 if (selected.getStart() > indexInText)
538 {
540 ga2.removeRangeOfGlyphs (selected.getStart() - indexInText, -1);
541 ga.removeRangeOfGlyphs (0, selected.getStart() - indexInText);
542
543 g.setColour (currentSection->colour);
544 ga2.draw (g, transform);
545 }
546
548 ga.draw (g, transform);
549 }
550 }
551
552 //==============================================================================
553 float indexToX (int indexToFind) const
554 {
555 if (indexToFind <= indexInText || atom == nullptr)
556 return atomX;
557
558 if (indexToFind >= indexInText + atom->numChars)
559 return atomRight;
560
562 g.addLineOfText (currentSection->font,
563 atom->getText (passwordCharacter),
564 atomX, 0.0f);
565
566 if (indexToFind - indexInText >= g.getNumGlyphs())
567 return atomRight;
568
569 return jmin (atomRight, g.getGlyph (indexToFind - indexInText).getLeft());
570 }
571
572 int xToIndex (float xToFind) const
573 {
575 return indexInText;
576
577 if (xToFind >= atomRight)
578 return indexInText + atom->numChars;
579
581 g.addLineOfText (currentSection->font,
582 atom->getText (passwordCharacter),
583 atomX, 0.0f);
584
585 auto numGlyphs = g.getNumGlyphs();
586
587 int j;
588 for (j = 0; j < numGlyphs; ++j)
589 {
590 auto& pg = g.getGlyph (j);
591
592 if ((pg.getLeft() + pg.getRight()) / 2 > xToFind)
593 break;
594 }
595
596 return indexInText + j;
597 }
598
599 //==============================================================================
600 bool getCharPosition (int index, Point<float>& anchor, float& lineHeightFound)
601 {
602 while (next())
603 {
604 if (indexInText + atom->numChars > index)
605 {
606 anchor = { indexToX (index), lineY };
607 lineHeightFound = lineHeight;
608 return true;
609 }
610 }
611
612 anchor = { atomX, lineY };
613 lineHeightFound = lineHeight;
614 return false;
615 }
616
617 float getYOffset()
618 {
619 if (justification.testFlags (Justification::top) || lineY >= bottomRight.y)
620 return 0;
621
622 while (next())
623 {
624 if (lineY >= bottomRight.y)
625 return 0;
626 }
627
628 auto bottom = jmax (0.0f, bottomRight.y - lineY - lineHeight);
629
630 if (justification.testFlags (Justification::bottom))
631 return bottom;
632
633 return bottom * 0.5f;
634 }
635
636 int getTotalTextHeight()
637 {
638 while (next()) {}
639
640 auto height = lineY + lineHeight + getYOffset();
641
642 if (atom != nullptr && atom->isNewLine())
643 height += lineHeight;
644
645 return roundToInt (height);
646 }
647
648 int getTextRight()
649 {
650 float maxWidth = 0.0f;
651
652 while (next())
653 maxWidth = jmax (maxWidth, atomRight);
654
655 return roundToInt (maxWidth);
656 }
657
658 Rectangle<int> getTextBounds (Range<int> range) const
659 {
660 auto startX = indexToX (range.getStart());
661 auto endX = indexToX (range.getEnd());
662
663 return Rectangle<float> (startX, lineY, endX - startX, lineHeight * lineSpacing).getSmallestIntegerContainer();
664 }
665
666 //==============================================================================
667 int indexInText = 0;
668 float lineY = 0, lineHeight = 0, maxDescent = 0;
669 float atomX = 0, atomRight = 0;
670 const TextAtom* atom = nullptr;
671
672private:
673 const OwnedArray<UniformTextSection>& sections;
674 const UniformTextSection* currentSection = nullptr;
675 int sectionIndex = 0, atomIndex = 0;
676 Justification justification;
677 const Point<float> bottomRight;
678 const float wordWrapWidth;
679 const juce_wchar passwordCharacter;
680 const float lineSpacing;
681 const bool underlineWhitespace;
682 TextAtom longAtom;
683
684 bool chunkLongAtom (bool shouldStartNewLine)
685 {
686 const auto numRemaining = longAtom.atomText.length() - longAtom.numChars;
687
688 if (numRemaining <= 0)
689 return false;
690
691 longAtom.atomText = longAtom.atomText.substring (longAtom.numChars);
692 indexInText += longAtom.numChars;
693
695 g.addLineOfText (currentSection->font, atom->getText (passwordCharacter), 0.0f, 0.0f);
696
697 int split;
698 for (split = 0; split < g.getNumGlyphs(); ++split)
699 if (shouldWrap (g.getGlyph (split).getRight()))
700 break;
701
702 const auto numChars = jmax (1, split);
703 longAtom.numChars = (uint16) numChars;
704 longAtom.width = g.getGlyph (numChars - 1).getRight();
705
706 atomX = getJustificationOffsetX (longAtom.width);
707
709 {
710 if (split == numRemaining)
711 beginNewLine();
712 else
713 lineY += lineHeight * lineSpacing;
714 }
715
716 atomRight = atomX + longAtom.width;
717 return true;
718 }
719
720 void moveToEndOfLastAtom()
721 {
722 if (atom != nullptr)
723 {
724 atomX = atomRight;
725
726 if (atom->isNewLine())
727 {
728 atomX = getJustificationOffsetX (0);
729 lineY += lineHeight * lineSpacing;
730 }
731 }
732 }
733
734 bool shouldWrap (const float x) const noexcept
735 {
736 return (x - 0.0001f) >= wordWrapWidth;
737 }
738
740};
741
742
743//==============================================================================
745{
746 InsertAction (TextEditor& ed, const String& newText, int insertPos,
747 const Font& newFont, Colour newColour, int oldCaret, int newCaret)
748 : owner (ed),
749 text (newText),
750 insertIndex (insertPos),
751 oldCaretPos (oldCaret),
752 newCaretPos (newCaret),
753 font (newFont),
754 colour (newColour)
755 {
756 }
757
758 bool perform() override
759 {
760 owner.insert (text, insertIndex, font, colour, nullptr, newCaretPos);
761 return true;
762 }
763
764 bool undo() override
765 {
766 owner.remove ({ insertIndex, insertIndex + text.length() }, nullptr, oldCaretPos);
767 return true;
768 }
769
770 int getSizeInUnits() override
771 {
772 return text.length() + 16;
773 }
774
775private:
776 TextEditor& owner;
777 const String text;
778 const int insertIndex, oldCaretPos, newCaretPos;
779 const Font font;
780 const Colour colour;
781
783};
784
785//==============================================================================
787{
790 : owner (ed),
791 range (rangeToRemove),
792 oldCaretPos (oldCaret),
793 newCaretPos (newCaret)
794 {
795 removedSections.addArray (oldSections);
796 }
797
798 bool perform() override
799 {
800 owner.remove (range, nullptr, newCaretPos);
801 return true;
802 }
803
804 bool undo() override
805 {
806 owner.reinsert (range.getStart(), removedSections);
807 owner.moveCaretTo (oldCaretPos, false);
808 return true;
809 }
810
811 int getSizeInUnits() override
812 {
813 int n = 16;
814
815 for (auto* s : removedSections)
816 n += s->getTotalLength();
817
818 return n;
819 }
820
821private:
822 TextEditor& owner;
823 const Range<int> range;
824 const int oldCaretPos, newCaretPos;
825 OwnedArray<UniformTextSection> removedSections;
826
828};
829
830//==============================================================================
832 public Timer,
833 public Value::Listener
834{
835 TextHolderComponent (TextEditor& ed) : owner (ed)
836 {
837 setWantsKeyboardFocus (false);
838 setInterceptsMouseClicks (false, true);
840
841 owner.getTextValue().addListener (this);
842 }
843
844 ~TextHolderComponent() override
845 {
846 owner.getTextValue().removeListener (this);
847 }
848
849 void paint (Graphics& g) override
850 {
851 owner.drawContent (g);
852 }
853
854 void restartTimer()
855 {
856 startTimer (350);
857 }
858
859 void timerCallback() override
860 {
861 owner.timerCallbackInt();
862 }
863
864 void valueChanged (Value&) override
865 {
866 owner.textWasChangedByValue();
867 }
868
869 TextEditor& owner;
870
871private:
872 std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
873 {
875 }
876
877 JUCE_DECLARE_NON_COPYABLE (TextHolderComponent)
878};
879
880//==============================================================================
882{
883 TextEditorViewport (TextEditor& ed) : owner (ed) {}
884
885 void visibleAreaChanged (const Rectangle<int>&) override
886 {
887 if (! reentrant) // it's rare, but possible to get into a feedback loop as the viewport's scrollbars
888 // appear and disappear, causing the wrap width to change.
889 {
890 auto wordWrapWidth = owner.getWordWrapWidth();
891
892 if (wordWrapWidth != lastWordWrapWidth)
893 {
894 lastWordWrapWidth = wordWrapWidth;
895
896 ScopedValueSetter<bool> svs (reentrant, true);
897 owner.checkLayout();
898 }
899 }
900 }
901
902private:
903 std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
904 {
906 }
907
908 TextEditor& owner;
909 int lastWordWrapWidth = 0;
910 bool reentrant = false;
911
912 JUCE_DECLARE_NON_COPYABLE (TextEditorViewport)
913};
914
915//==============================================================================
916namespace TextEditorDefs
917{
918 const int textChangeMessageId = 0x10003001;
919 const int returnKeyMessageId = 0x10003002;
920 const int escapeKeyMessageId = 0x10003003;
921 const int focusLossMessageId = 0x10003004;
922
923 const int maxActionsPerTransaction = 100;
924
925 static int getCharacterCategory (juce_wchar character) noexcept
926 {
927 return CharacterFunctions::isLetterOrDigit (character)
928 ? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1);
929 }
930}
931
932//==============================================================================
933TextEditor::TextEditor (const String& name, juce_wchar passwordChar)
934 : Component (name),
935 passwordCharacter (passwordChar)
936{
938
939 viewport.reset (new TextEditorViewport (*this));
940 addAndMakeVisible (viewport.get());
941 viewport->setViewedComponent (textHolder = new TextHolderComponent (*this));
942 viewport->setWantsKeyboardFocus (false);
943 viewport->setScrollBarsShown (false, false);
944
946 recreateCaret();
947}
948
950{
951 if (auto* peer = getPeer())
952 peer->refreshTextInputTarget();
953
954 textValue.removeListener (textHolder);
955 textValue.referTo (Value());
956
957 viewport.reset();
958 textHolder = nullptr;
959}
960
961//==============================================================================
963{
964 lastTransactionTime = Time::getApproximateMillisecondCounter();
965 undoManager.beginNewTransaction();
966}
967
968bool TextEditor::undoOrRedo (const bool shouldUndo)
969{
970 if (! isReadOnly())
971 {
973
974 if (shouldUndo ? undoManager.undo()
975 : undoManager.redo())
976 {
977 repaint();
978 textChanged();
980
981 return true;
982 }
983 }
984
985 return false;
986}
987
988bool TextEditor::undo() { return undoOrRedo (true); }
989bool TextEditor::redo() { return undoOrRedo (false); }
990
991//==============================================================================
993 const bool shouldWordWrap)
994{
995 if (multiline != shouldBeMultiLine
996 || wordWrap != (shouldWordWrap && shouldBeMultiLine))
997 {
998 multiline = shouldBeMultiLine;
999 wordWrap = shouldWordWrap && shouldBeMultiLine;
1000
1001 checkLayout();
1002
1003 viewport->setViewPosition (0, 0);
1004 resized();
1006 }
1007}
1008
1010{
1011 return multiline;
1012}
1013
1015{
1016 if (scrollbarVisible != shown)
1017 {
1018 scrollbarVisible = shown;
1019 checkLayout();
1020 }
1021}
1022
1024{
1025 if (readOnly != shouldBeReadOnly)
1026 {
1027 readOnly = shouldBeReadOnly;
1030
1031 if (auto* peer = getPeer())
1032 peer->refreshTextInputTarget();
1033 }
1034}
1035
1037{
1038 clicksOutsideDismissVirtualKeyboard = newValue;
1039}
1040
1042{
1043 return readOnly || ! isEnabled();
1044}
1045
1047{
1048 return ! isReadOnly() && (! clicksOutsideDismissVirtualKeyboard || globalMouseListener.lastMouseDownInEditor());
1049}
1050
1052{
1053 returnKeyStartsNewLine = shouldStartNewLine;
1054}
1055
1060
1062{
1063 popupMenuEnabled = b;
1064}
1065
1067{
1068 selectAllTextWhenFocused = b;
1069}
1070
1072{
1073 if (justification != j)
1074 {
1075 justification = j;
1076
1077 resized();
1078 repaint();
1079 }
1080}
1081
1082//==============================================================================
1084{
1085 currentFont = newFont;
1087}
1088
1090{
1092 currentFont = newFont;
1093
1095
1096 for (auto* uts : sections)
1097 {
1098 uts->setFont (newFont, passwordCharacter);
1099 uts->colour = overallColour;
1100 }
1101
1102 coalesceSimilarSections();
1103 checkLayout();
1105 repaint();
1106}
1107
1109{
1110 for (auto* uts : sections)
1111 uts->colour = newColour;
1112
1115 else
1116 repaint();
1117}
1118
1120{
1121 caret.reset();
1122 recreateCaret();
1123}
1124
1129
1131{
1132 recreateCaret();
1133 repaint();
1134}
1135
1137{
1138 if (caretVisible != shouldCaretBeVisible)
1139 {
1140 caretVisible = shouldCaretBeVisible;
1141 recreateCaret();
1142 }
1143}
1144
1145void TextEditor::recreateCaret()
1146{
1147 if (isCaretVisible())
1148 {
1149 if (caret == nullptr)
1150 {
1151 caret.reset (getLookAndFeel().createCaretComponent (this));
1152 textHolder->addChildComponent (caret.get());
1153 updateCaretPosition();
1154 }
1155 }
1156 else
1157 {
1158 caret.reset();
1159 }
1160}
1161
1162void TextEditor::updateCaretPosition()
1163{
1164 if (caret != nullptr
1165 && getWidth() > 0 && getHeight() > 0)
1166 {
1167 Iterator i (*this);
1168 caret->setCaretPosition (getCaretRectangle().translated (leftIndent,
1169 topIndent + roundToInt (i.getYOffset())) - getTextOffset());
1170
1171 if (auto* handler = getAccessibilityHandler())
1172 handler->notifyAccessibilityEvent (AccessibilityEvent::textSelectionChanged);
1173 }
1174}
1175
1177 : allowedCharacters (chars), maxLength (maxLen)
1178{
1179}
1180
1182{
1183 String t (newInput);
1184
1185 if (allowedCharacters.isNotEmpty())
1186 t = t.retainCharacters (allowedCharacters);
1187
1188 if (maxLength > 0)
1189 t = t.substring (0, maxLength - (ed.getTotalNumChars() - ed.getHighlightedRegion().getLength()));
1190
1191 return t;
1192}
1193
1198
1203
1205{
1206 textToShowWhenEmpty = text;
1207 colourForTextWhenEmpty = colourToUse;
1208}
1209
1211{
1212 if (passwordCharacter != newPasswordCharacter)
1213 {
1214 passwordCharacter = newPasswordCharacter;
1215 applyFontToAllText (currentFont);
1216 }
1217}
1218
1220{
1221 viewport->setScrollBarThickness (newThicknessPixels);
1222}
1223
1224//==============================================================================
1226{
1227 clearInternal (nullptr);
1228 checkLayout();
1229 undoManager.clearUndoHistory();
1230 repaint();
1231}
1232
1234{
1235 auto newLength = newText.length();
1236
1237 if (newLength != getTotalNumChars() || getText() != newText)
1238 {
1240 textValue.removeListener (textHolder);
1241
1242 textValue = newText;
1243
1244 auto oldCursorPos = caretPosition;
1246
1247 clearInternal (nullptr);
1248 insert (newText, 0, currentFont, findColour (textColourId), nullptr, caretPosition);
1249
1250 // if you're adding text with line-feeds to a single-line text editor, it
1251 // ain't gonna look right!
1252 jassert (multiline || ! newText.containsAnyOf ("\r\n"));
1253
1254 if (cursorWasAtEnd && ! isMultiLine())
1256
1257 moveCaretTo (oldCursorPos, false);
1258
1260 textChanged();
1261 else
1262 textValue.addListener (textHolder);
1263
1264 checkLayout();
1266 undoManager.clearUndoHistory();
1267
1268 repaint();
1269 }
1270}
1271
1272//==============================================================================
1273void TextEditor::updateValueFromText()
1274{
1275 if (valueTextNeedsUpdating)
1276 {
1277 valueTextNeedsUpdating = false;
1278 textValue = getText();
1279 }
1280}
1281
1283{
1284 updateValueFromText();
1285 return textValue;
1286}
1287
1288void TextEditor::textWasChangedByValue()
1289{
1290 if (textValue.getValueSource().getReferenceCount() > 1)
1291 setText (textValue.getValue());
1292}
1293
1294//==============================================================================
1296{
1297 checkLayout();
1298
1299 if (listeners.size() != 0 || onTextChange != nullptr)
1300 postCommandMessage (TextEditorDefs::textChangeMessageId);
1301
1302 if (textValue.getValueSource().getReferenceCount() > 1)
1303 {
1304 valueTextNeedsUpdating = false;
1305 textValue = getText();
1306 }
1307
1308 if (auto* handler = getAccessibilityHandler())
1309 handler->notifyAccessibilityEvent (AccessibilityEvent::textChanged);
1310}
1311
1312void TextEditor::setSelection (Range<int> newSelection) noexcept
1313{
1314 if (newSelection != selection)
1315 {
1316 selection = newSelection;
1317
1318 if (auto* handler = getAccessibilityHandler())
1319 handler->notifyAccessibilityEvent (AccessibilityEvent::textSelectionChanged);
1320 }
1321}
1322
1323void TextEditor::returnPressed() { postCommandMessage (TextEditorDefs::returnKeyMessageId); }
1324void TextEditor::escapePressed() { postCommandMessage (TextEditorDefs::escapeKeyMessageId); }
1325
1326void TextEditor::addListener (Listener* l) { listeners.add (l); }
1327void TextEditor::removeListener (Listener* l) { listeners.remove (l); }
1328
1329//==============================================================================
1330void TextEditor::timerCallbackInt()
1331{
1332 checkFocus();
1333
1335
1336 if (now > lastTransactionTime + 200)
1338}
1339
1340void TextEditor::checkFocus()
1341{
1342 if (! wasFocused && hasKeyboardFocus (false) && ! isCurrentlyBlockedByAnotherModalComponent())
1343 wasFocused = true;
1344}
1345
1346void TextEditor::repaintText (Range<int> range)
1347{
1348 if (! range.isEmpty())
1349 {
1350 if (range.getEnd() >= getTotalNumChars())
1351 {
1352 textHolder->repaint();
1353 return;
1354 }
1355
1356 Iterator i (*this);
1357
1358 Point<float> anchor;
1359 auto lh = currentFont.getHeight();
1360 i.getCharPosition (range.getStart(), anchor, lh);
1361
1362 auto y1 = std::trunc (anchor.y);
1363 int y2 = 0;
1364
1365 if (range.getEnd() >= getTotalNumChars())
1366 {
1367 y2 = textHolder->getHeight();
1368 }
1369 else
1370 {
1371 i.getCharPosition (range.getEnd(), anchor, lh);
1372 y2 = (int) (anchor.y + lh * 2.0f);
1373 }
1374
1375 auto offset = i.getYOffset();
1376 textHolder->repaint (0, roundToInt (y1 + offset), textHolder->getWidth(), roundToInt ((float) y2 - y1 + offset));
1377 }
1378}
1379
1380//==============================================================================
1381void TextEditor::moveCaret (const int newCaretPos)
1382{
1383 const auto clamped = std::clamp (newCaretPos, 0, getTotalNumChars());
1384
1385 if (clamped == getCaretPosition())
1386 return;
1387
1388 caretPosition = clamped;
1389
1390 if (hasKeyboardFocus (false))
1391 textHolder->restartTimer();
1392
1394 updateCaretPosition();
1395
1396 if (auto* handler = getAccessibilityHandler())
1397 handler->notifyAccessibilityEvent (AccessibilityEvent::textChanged);
1398}
1399
1401{
1402 return caretPosition;
1403}
1404
1406{
1407 moveCaretTo (newIndex, false);
1408}
1409
1410void TextEditor::moveCaretToEnd()
1411{
1413}
1414
1416 const int desiredCaretY)
1417
1418{
1419 updateCaretPosition();
1420 auto caretRect = getCaretRectangle().translated (leftIndent, topIndent);
1421
1422 auto vx = caretRect.getX() - desiredCaretX;
1423 auto vy = caretRect.getY() - desiredCaretY;
1424
1425 if (desiredCaretX < jmax (1, proportionOfWidth (0.05f)))
1427 else if (desiredCaretX > jmax (0, viewport->getMaximumVisibleWidth() - (wordWrap ? 2 : 10)))
1428 vx += desiredCaretX + (isMultiLine() ? proportionOfWidth (0.2f) : 10) - viewport->getMaximumVisibleWidth();
1429
1430 vx = jlimit (0, jmax (0, textHolder->getWidth() + 8 - viewport->getMaximumVisibleWidth()), vx);
1431
1432 if (! isMultiLine())
1433 {
1434 vy = viewport->getViewPositionY();
1435 }
1436 else
1437 {
1438 vy = jlimit (0, jmax (0, textHolder->getHeight() - viewport->getMaximumVisibleHeight()), vy);
1439
1440 if (desiredCaretY < 0)
1441 vy = jmax (0, desiredCaretY + vy);
1442 else if (desiredCaretY > jmax (0, viewport->getMaximumVisibleHeight() - caretRect.getHeight()))
1443 vy += desiredCaretY + 2 + caretRect.getHeight() - viewport->getMaximumVisibleHeight();
1444 }
1445
1446 viewport->setViewPosition (vx, vy);
1447}
1448
1450{
1451 Point<float> anchor;
1452 auto cursorHeight = currentFont.getHeight(); // (in case the text is empty and the call below doesn't set this value)
1453 getCharPosition (index, anchor, cursorHeight);
1454
1455 return Rectangle<float> { anchor.x, anchor.y, 2.0f, cursorHeight }.getSmallestIntegerContainer() + getTextOffset();
1456}
1457
1458Point<int> TextEditor::getTextOffset() const noexcept
1459{
1460 Iterator i (*this);
1461 auto yOffset = i.getYOffset();
1462
1463 return { getLeftIndent() + borderSize.getLeft() - viewport->getViewPositionX(),
1464 roundToInt ((float) getTopIndent() + (float) borderSize.getTop() + yOffset) - viewport->getViewPositionY() };
1465}
1466
1468{
1470 Iterator i (*this);
1471
1472 while (i.next())
1473 {
1474 if (textRange.intersects ({ i.indexInText,
1475 i.indexInText + i.atom->numChars }))
1476 {
1477 boundingBox.add (i.getTextBounds (textRange));
1478 }
1479 }
1480
1481 boundingBox.offsetAll (getTextOffset());
1482 return boundingBox;
1483}
1484
1485//==============================================================================
1486// Extra space for the cursor at the right-hand-edge
1487constexpr int rightEdgeSpace = 2;
1488
1489int TextEditor::getWordWrapWidth() const
1490{
1491 return wordWrap ? getMaximumTextWidth()
1492 : std::numeric_limits<int>::max();
1493}
1494
1495int TextEditor::getMaximumTextWidth() const
1496{
1497 return jmax (1, viewport->getMaximumVisibleWidth() - leftIndent - rightEdgeSpace);
1498}
1499
1500int TextEditor::getMaximumTextHeight() const
1501{
1502 return jmax (1, viewport->getMaximumVisibleHeight() - topIndent);
1503}
1504
1505void TextEditor::checkLayout()
1506{
1507 if (getWordWrapWidth() > 0)
1508 {
1509 const auto textBottom = Iterator (*this).getTotalTextHeight() + topIndent;
1510 const auto textRight = jmax (viewport->getMaximumVisibleWidth(),
1511 Iterator (*this).getTextRight() + leftIndent + rightEdgeSpace);
1512
1513 textHolder->setSize (textRight, textBottom);
1514 viewport->setScrollBarsShown (scrollbarVisible && multiline && textBottom > viewport->getMaximumVisibleHeight(),
1515 scrollbarVisible && multiline && ! wordWrap && textRight > viewport->getMaximumVisibleWidth());
1516 }
1517}
1518
1519int TextEditor::getTextWidth() const { return textHolder->getWidth(); }
1520int TextEditor::getTextHeight() const { return textHolder->getHeight(); }
1521
1523{
1524 if (leftIndent != newLeftIndent || topIndent != newTopIndent)
1525 {
1526 leftIndent = newLeftIndent;
1527 topIndent = newTopIndent;
1528
1529 resized();
1530 repaint();
1531 }
1532}
1533
1535{
1536 borderSize = border;
1537 resized();
1538}
1539
1541{
1542 return borderSize;
1543}
1544
1546{
1547 keepCaretOnScreen = shouldScrollToShowCursor;
1548}
1549
1551{
1552 updateCaretPosition();
1553
1554 if (keepCaretOnScreen)
1555 {
1556 auto viewPos = viewport->getViewPosition();
1557 auto caretRect = getCaretRectangle().translated (leftIndent, topIndent) - getTextOffset();
1558 auto relativeCursor = caretRect.getPosition() - viewPos;
1559
1560 if (relativeCursor.x < jmax (1, proportionOfWidth (0.05f)))
1561 {
1563 }
1564 else if (relativeCursor.x > jmax (0, viewport->getMaximumVisibleWidth() - (wordWrap ? 2 : 10)))
1565 {
1566 viewPos.x += relativeCursor.x + (isMultiLine() ? proportionOfWidth (0.2f) : 10) - viewport->getMaximumVisibleWidth();
1567 }
1568
1569 viewPos.x = jlimit (0, jmax (0, textHolder->getWidth() + 8 - viewport->getMaximumVisibleWidth()), viewPos.x);
1570
1571 if (! isMultiLine())
1572 {
1573 viewPos.y = (getHeight() - textHolder->getHeight() - topIndent) / -2;
1574 }
1575 else if (relativeCursor.y < 0)
1576 {
1577 viewPos.y = jmax (0, relativeCursor.y + viewPos.y);
1578 }
1579 else if (relativeCursor.y > jmax (0, viewport->getMaximumVisibleHeight() - caretRect.getHeight()))
1580 {
1581 viewPos.y += relativeCursor.y + 2 + caretRect.getHeight() - viewport->getMaximumVisibleHeight();
1582 }
1583
1584 viewport->setViewPosition (viewPos);
1585 }
1586}
1587
1588void TextEditor::moveCaretTo (const int newPosition, const bool isSelecting)
1589{
1590 if (isSelecting)
1591 {
1592 moveCaret (newPosition);
1593
1594 auto oldSelection = selection;
1595
1596 if (dragType == notDragging)
1597 {
1598 if (std::abs (getCaretPosition() - selection.getStart()) < std::abs (getCaretPosition() - selection.getEnd()))
1599 dragType = draggingSelectionStart;
1600 else
1601 dragType = draggingSelectionEnd;
1602 }
1603
1604 if (dragType == draggingSelectionStart)
1605 {
1606 if (getCaretPosition() >= selection.getEnd())
1607 dragType = draggingSelectionEnd;
1608
1609 setSelection (Range<int>::between (getCaretPosition(), selection.getEnd()));
1610 }
1611 else
1612 {
1613 if (getCaretPosition() < selection.getStart())
1614 dragType = draggingSelectionStart;
1615
1616 setSelection (Range<int>::between (getCaretPosition(), selection.getStart()));
1617 }
1618
1619 repaintText (selection.getUnionWith (oldSelection));
1620 }
1621 else
1622 {
1623 dragType = notDragging;
1624
1625 repaintText (selection);
1626
1627 moveCaret (newPosition);
1628 setSelection (Range<int>::emptyRange (getCaretPosition()));
1629 }
1630}
1631
1632int TextEditor::getTextIndexAt (const int x, const int y) const
1633{
1634 const auto offset = getTextOffset();
1635
1636 return indexAtPosition ((float) (x - offset.x),
1637 (float) (y - offset.y));
1638}
1639
1641{
1642 return getTextIndexAt (pt.x, pt.y);
1643}
1644
1646{
1648}
1649
1651{
1652 const auto filtered = inputFilter != nullptr ? inputFilter->filterNewText (*this, t) : t;
1653 const auto newText = isMultiLine() ? filtered.replace ("\r\n", "\n")
1654 : filtered.replaceCharacters ("\r\n", " ");
1655 const auto insertIndex = selection.getStart();
1656 const auto newCaretPos = insertIndex + newText.length();
1657
1658 remove (selection, getUndoManager(),
1659 newText.isNotEmpty() ? newCaretPos - 1 : newCaretPos);
1660
1661 insert (newText, insertIndex, currentFont, findColour (textColourId),
1662 getUndoManager(), newCaretPos);
1663
1664 textChanged();
1665}
1666
1668{
1670 return;
1671
1672 const auto cursorAtStart = newSelection.getEnd() == getHighlightedRegion().getStart()
1673 || newSelection.getEnd() == getHighlightedRegion().getEnd();
1674 moveCaretTo (cursorAtStart ? newSelection.getEnd() : newSelection.getStart(), false);
1675 moveCaretTo (cursorAtStart ? newSelection.getStart() : newSelection.getEnd(), true);
1676}
1677
1678//==============================================================================
1680{
1681 if (passwordCharacter == 0)
1682 {
1684
1685 if (selectedText.isNotEmpty())
1687 }
1688}
1689
1691{
1692 if (! isReadOnly())
1693 {
1695
1696 if (clip.isNotEmpty())
1697 insertTextAtCaret (clip);
1698 }
1699}
1700
1702{
1703 if (! isReadOnly())
1704 {
1705 moveCaret (selection.getEnd());
1707 }
1708}
1709
1710//==============================================================================
1711void TextEditor::drawContent (Graphics& g)
1712{
1713 if (getWordWrapWidth() > 0)
1714 {
1715 g.setOrigin (leftIndent, topIndent);
1716 auto clip = g.getClipBounds();
1717
1718 auto yOffset = Iterator (*this).getYOffset();
1719
1720 AffineTransform transform;
1721
1722 if (yOffset > 0)
1723 {
1724 transform = AffineTransform::translation (0.0f, yOffset);
1725 clip.setY (roundToInt ((float) clip.getY() - yOffset));
1726 }
1727
1728 Iterator i (*this);
1729 Colour selectedTextColour;
1730
1731 if (! selection.isEmpty())
1732 {
1734
1735 g.setColour (findColour (highlightColourId).withMultipliedAlpha (hasKeyboardFocus (true) ? 1.0f : 0.5f));
1736
1737 auto boundingBox = getTextBounds (selection);
1738 boundingBox.offsetAll (-getTextOffset());
1739
1740 g.fillPath (boundingBox.toPath(), transform);
1741 }
1742
1743 const UniformTextSection* lastSection = nullptr;
1744
1745 while (i.next() && i.lineY < (float) clip.getBottom())
1746 {
1747 if (i.lineY + i.lineHeight >= (float) clip.getY())
1748 {
1749 if (selection.intersects ({ i.indexInText, i.indexInText + i.atom->numChars }))
1750 {
1751 i.drawSelectedText (g, selection, selectedTextColour, transform);
1752 lastSection = nullptr;
1753 }
1754 else
1755 {
1756 i.draw (g, lastSection, transform);
1757 }
1758 }
1759 }
1760
1761 for (auto& underlinedSection : underlinedSections)
1762 {
1763 Iterator i2 (*this);
1764
1765 while (i2.next() && i2.lineY < (float) clip.getBottom())
1766 {
1767 if (i2.lineY + i2.lineHeight >= (float) clip.getY()
1768 && underlinedSection.intersects ({ i2.indexInText, i2.indexInText + i2.atom->numChars }))
1769 {
1770 i2.drawUnderline (g, underlinedSection, findColour (textColourId), transform);
1771 }
1772 }
1773 }
1774 }
1775}
1776
1778{
1779 getLookAndFeel().fillTextEditorBackground (g, getWidth(), getHeight(), *this);
1780}
1781
1783{
1784 if (textToShowWhenEmpty.isNotEmpty()
1785 && (! hasKeyboardFocus (false))
1786 && getTotalNumChars() == 0)
1787 {
1788 g.setColour (colourForTextWhenEmpty);
1789 g.setFont (getFont());
1790
1791 Rectangle<int> textBounds (leftIndent,
1792 topIndent,
1793 viewport->getWidth() - leftIndent,
1794 getHeight() - topIndent);
1795
1796 if (! textBounds.isEmpty())
1797 g.drawText (textToShowWhenEmpty, textBounds, justification, true);
1798 }
1799
1800 getLookAndFeel().drawTextEditorOutline (g, getWidth(), getHeight(), *this);
1801}
1802
1803//==============================================================================
1805{
1806 const bool writable = ! isReadOnly();
1807
1808 if (passwordCharacter == 0)
1809 {
1811 m.addItem (StandardApplicationCommandIDs::copy, TRANS ("Copy"), ! selection.isEmpty());
1812 }
1813
1816 m.addSeparator();
1818 m.addSeparator();
1819
1820 if (getUndoManager() != nullptr)
1821 {
1822 m.addItem (StandardApplicationCommandIDs::undo, TRANS ("Undo"), undoManager.canUndo());
1823 m.addItem (StandardApplicationCommandIDs::redo, TRANS ("Redo"), undoManager.canRedo());
1824 }
1825}
1826
1828{
1829 switch (menuItemID)
1830 {
1831 case StandardApplicationCommandIDs::cut: cutToClipboard(); break;
1832 case StandardApplicationCommandIDs::copy: copyToClipboard(); break;
1833 case StandardApplicationCommandIDs::paste: pasteFromClipboard(); break;
1835 case StandardApplicationCommandIDs::selectAll: selectAll(); break;
1836 case StandardApplicationCommandIDs::undo: undo(); break;
1837 case StandardApplicationCommandIDs::redo: redo(); break;
1838 default: break;
1839 }
1840}
1841
1842//==============================================================================
1844{
1845 beginDragAutoRepeat (100);
1847
1848 if (wasFocused || ! selectAllTextWhenFocused)
1849 {
1850 if (! (popupMenuEnabled && e.mods.isPopupMenu()))
1851 {
1852 moveCaretTo (getTextIndexAt (e.getPosition()), e.mods.isShiftDown());
1853
1854 if (auto* peer = getPeer())
1855 peer->closeInputMethodContext();
1856 }
1857 else
1858 {
1859 PopupMenu m;
1861 addPopupMenuItems (m, &e);
1862
1863 menuActive = true;
1864
1867 {
1868 if (auto* editor = safeThis.getComponent())
1869 {
1870 editor->menuActive = false;
1871
1872 if (menuResult != 0)
1873 editor->performPopupMenuAction (menuResult);
1874 }
1875 });
1876 }
1877 }
1878}
1879
1881{
1882 if (wasFocused || ! selectAllTextWhenFocused)
1883 if (! (popupMenuEnabled && e.mods.isPopupMenu()))
1884 moveCaretTo (getTextIndexAt (e.getPosition()), true);
1885}
1886
1888{
1890 textHolder->restartTimer();
1891
1892 if (wasFocused || ! selectAllTextWhenFocused)
1893 if (e.mouseWasClicked() && ! (popupMenuEnabled && e.mods.isPopupMenu()))
1894 moveCaret (getTextIndexAt (e.getPosition()));
1895
1896 wasFocused = true;
1897}
1898
1900{
1902 int tokenStart = 0;
1903
1904 if (e.getNumberOfClicks() > 3)
1905 {
1907 }
1908 else
1909 {
1910 auto t = getText();
1911 auto totalLength = getTotalNumChars();
1912
1913 while (tokenEnd < totalLength)
1914 {
1915 auto c = t[tokenEnd];
1916
1917 // (note the slight bodge here - it's because iswalnum only checks for alphabetic chars in the current locale)
1918 if (CharacterFunctions::isLetterOrDigit (c) || c > 128)
1919 ++tokenEnd;
1920 else
1921 break;
1922 }
1923
1925
1926 while (tokenStart > 0)
1927 {
1928 auto c = t[tokenStart - 1];
1929
1930 // (note the slight bodge here - it's because iswalnum only checks for alphabetic chars in the current locale)
1931 if (CharacterFunctions::isLetterOrDigit (c) || c > 128)
1932 --tokenStart;
1933 else
1934 break;
1935 }
1936
1937 if (e.getNumberOfClicks() > 2)
1938 {
1939 while (tokenEnd < totalLength)
1940 {
1941 auto c = t[tokenEnd];
1942
1943 if (c != '\r' && c != '\n')
1944 ++tokenEnd;
1945 else
1946 break;
1947 }
1948
1949 while (tokenStart > 0)
1950 {
1951 auto c = t[tokenStart - 1];
1952
1953 if (c != '\r' && c != '\n')
1954 --tokenStart;
1955 else
1956 break;
1957 }
1958 }
1959 }
1960
1961 moveCaretTo (tokenEnd, false);
1962 moveCaretTo (tokenStart, true);
1963}
1964
1966{
1967 if (! viewport->useMouseWheelMoveIfNeeded (e, wheel))
1969}
1970
1971//==============================================================================
1972bool TextEditor::moveCaretWithTransaction (const int newPos, const bool selecting)
1973{
1975 moveCaretTo (newPos, selecting);
1976
1977 if (auto* peer = getPeer())
1978 peer->closeInputMethodContext();
1979
1980 return true;
1981}
1982
1983bool TextEditor::moveCaretLeft (bool moveInWholeWordSteps, bool selecting)
1984{
1985 auto pos = getCaretPosition();
1986
1988 pos = findWordBreakBefore (pos);
1989 else
1990 --pos;
1991
1992 return moveCaretWithTransaction (pos, selecting);
1993}
1994
1995bool TextEditor::moveCaretRight (bool moveInWholeWordSteps, bool selecting)
1996{
1997 auto pos = getCaretPosition();
1998
2000 pos = findWordBreakAfter (pos);
2001 else
2002 ++pos;
2003
2004 return moveCaretWithTransaction (pos, selecting);
2005}
2006
2007bool TextEditor::moveCaretUp (bool selecting)
2008{
2009 if (! isMultiLine())
2010 return moveCaretToStartOfLine (selecting);
2011
2012 const auto caretPos = (getCaretRectangle() - getTextOffset()).toFloat();
2013
2014 const auto newY = caretPos.getY() - 1.0f;
2015
2016 if (newY < 0.0f)
2017 return moveCaretToStartOfLine (selecting);
2018
2019 return moveCaretWithTransaction (indexAtPosition (caretPos.getX(), newY), selecting);
2020}
2021
2022bool TextEditor::moveCaretDown (bool selecting)
2023{
2024 if (! isMultiLine())
2025 return moveCaretToEndOfLine (selecting);
2026
2027 const auto caretPos = (getCaretRectangle() - getTextOffset()).toFloat();
2028 return moveCaretWithTransaction (indexAtPosition (caretPos.getX(), caretPos.getBottom() + 1.0f), selecting);
2029}
2030
2031bool TextEditor::pageUp (bool selecting)
2032{
2033 if (! isMultiLine())
2034 return moveCaretToStartOfLine (selecting);
2035
2036 const auto caretPos = (getCaretRectangle() - getTextOffset()).toFloat();
2037 return moveCaretWithTransaction (indexAtPosition (caretPos.getX(), caretPos.getY() - (float) viewport->getViewHeight()), selecting);
2038}
2039
2040bool TextEditor::pageDown (bool selecting)
2041{
2042 if (! isMultiLine())
2043 return moveCaretToEndOfLine (selecting);
2044
2045 const auto caretPos = (getCaretRectangle() - getTextOffset()).toFloat();
2046 return moveCaretWithTransaction (indexAtPosition (caretPos.getX(), caretPos.getBottom() + (float) viewport->getViewHeight()), selecting);
2047}
2048
2049void TextEditor::scrollByLines (int deltaLines)
2050{
2051 viewport->getVerticalScrollBar().moveScrollbarInSteps (deltaLines);
2052}
2053
2054bool TextEditor::scrollDown()
2055{
2056 scrollByLines (-1);
2057 return true;
2058}
2059
2060bool TextEditor::scrollUp()
2061{
2062 scrollByLines (1);
2063 return true;
2064}
2065
2066bool TextEditor::moveCaretToTop (bool selecting)
2067{
2068 return moveCaretWithTransaction (0, selecting);
2069}
2070
2071bool TextEditor::moveCaretToStartOfLine (bool selecting)
2072{
2073 const auto caretPos = (getCaretRectangle() - getTextOffset()).toFloat();
2074 return moveCaretWithTransaction (indexAtPosition (0.0f, caretPos.getCentreY()), selecting);
2075}
2076
2077bool TextEditor::moveCaretToEnd (bool selecting)
2078{
2079 return moveCaretWithTransaction (getTotalNumChars(), selecting);
2080}
2081
2082bool TextEditor::moveCaretToEndOfLine (bool selecting)
2083{
2084 const auto caretPos = (getCaretRectangle() - getTextOffset()).toFloat();
2085 return moveCaretWithTransaction (indexAtPosition ((float) textHolder->getWidth(), caretPos.getCentreY()), selecting);
2086}
2087
2088bool TextEditor::deleteBackwards (bool moveInWholeWordSteps)
2089{
2091 moveCaretTo (findWordBreakBefore (getCaretPosition()), true);
2092 else if (selection.isEmpty() && selection.getStart() > 0)
2093 setSelection ({ selection.getEnd() - 1, selection.getEnd() });
2094
2095 cut();
2096 return true;
2097}
2098
2099bool TextEditor::deleteForwards (bool /*moveInWholeWordSteps*/)
2100{
2101 if (selection.isEmpty() && selection.getStart() < getTotalNumChars())
2102 setSelection ({ selection.getStart(), selection.getStart() + 1 });
2103
2104 cut();
2105 return true;
2106}
2107
2108bool TextEditor::copyToClipboard()
2109{
2111 copy();
2112 return true;
2113}
2114
2115bool TextEditor::cutToClipboard()
2116{
2118 copy();
2119 cut();
2120 return true;
2121}
2122
2123bool TextEditor::pasteFromClipboard()
2124{
2126 paste();
2127 return true;
2128}
2129
2130bool TextEditor::selectAll()
2131{
2133 moveCaretTo (getTotalNumChars(), false);
2134 moveCaretTo (0, true);
2135 return true;
2136}
2137
2138//==============================================================================
2140{
2141 consumeEscAndReturnKeys = shouldBeConsumed;
2142}
2143
2145{
2146 if (isReadOnly() && key != KeyPress ('c', ModifierKeys::commandModifier, 0)
2147 && key != KeyPress ('a', ModifierKeys::commandModifier, 0))
2148 return false;
2149
2151 {
2152 if (key == KeyPress::returnKey)
2153 {
2155
2156 if (returnKeyStartsNewLine)
2157 {
2158 insertTextAtCaret ("\n");
2159 }
2160 else
2161 {
2162 returnPressed();
2163 return consumeEscAndReturnKeys;
2164 }
2165 }
2166 else if (key.isKeyCode (KeyPress::escapeKey))
2167 {
2169 moveCaretTo (getCaretPosition(), false);
2170 escapePressed();
2171 return consumeEscAndReturnKeys;
2172 }
2173 else if (key.getTextCharacter() >= ' '
2174 || (tabKeyUsed && (key.getTextCharacter() == '\t')))
2175 {
2177
2178 lastTransactionTime = Time::getApproximateMillisecondCounter();
2179 }
2180 else
2181 {
2182 return false;
2183 }
2184 }
2185
2186 return true;
2187}
2188
2189bool TextEditor::keyStateChanged (const bool isKeyDown)
2190{
2191 if (! isKeyDown)
2192 return false;
2193
2194 #if JUCE_WINDOWS
2195 if (KeyPress (KeyPress::F4Key, ModifierKeys::altModifier, 0).isCurrentlyDown())
2196 return false; // We need to explicitly allow alt-F4 to pass through on Windows
2197 #endif
2198
2199 if ((! consumeEscAndReturnKeys)
2200 && (KeyPress (KeyPress::escapeKey).isCurrentlyDown()
2201 || KeyPress (KeyPress::returnKey).isCurrentlyDown()))
2202 return false;
2203
2204 // (overridden to avoid forwarding key events to the parent)
2206}
2207
2208//==============================================================================
2210{
2212
2213 if (selectAllTextWhenFocused)
2214 {
2215 moveCaretTo (0, false);
2216 moveCaretTo (getTotalNumChars(), true);
2217 }
2218
2219 checkFocus();
2220
2221 if (cause == FocusChangeType::focusChangedByMouseClick && selectAllTextWhenFocused)
2222 wasFocused = false;
2223
2224 repaint();
2225 updateCaretPosition();
2226}
2227
2229{
2231
2232 wasFocused = false;
2233 textHolder->stopTimer();
2234
2235 underlinedSections.clear();
2236
2237 updateCaretPosition();
2238
2239 postCommandMessage (TextEditorDefs::focusLossMessageId);
2240 repaint();
2241}
2242
2243//==============================================================================
2245{
2246 viewport->setBoundsInset (borderSize);
2247 viewport->setSingleStepSizes (16, roundToInt (currentFont.getHeight()));
2248
2249 checkLayout();
2250
2251 if (isMultiLine())
2252 updateCaretPosition();
2253 else
2255}
2256
2257void TextEditor::handleCommandMessage (const int commandId)
2258{
2260
2261 switch (commandId)
2262 {
2263 case TextEditorDefs::textChangeMessageId:
2264 listeners.callChecked (checker, [this] (Listener& l) { l.textEditorTextChanged (*this); });
2265
2266 if (! checker.shouldBailOut())
2267 NullCheckedInvocation::invoke (onTextChange);
2268
2269 break;
2270
2271 case TextEditorDefs::returnKeyMessageId:
2272 listeners.callChecked (checker, [this] (Listener& l) { l.textEditorReturnKeyPressed (*this); });
2273
2274 if (! checker.shouldBailOut())
2275 NullCheckedInvocation::invoke (onReturnKey);
2276
2277 break;
2278
2279 case TextEditorDefs::escapeKeyMessageId:
2280 listeners.callChecked (checker, [this] (Listener& l) { l.textEditorEscapeKeyPressed (*this); });
2281
2282 if (! checker.shouldBailOut())
2283 NullCheckedInvocation::invoke (onEscapeKey);
2284
2285 break;
2286
2287 case TextEditorDefs::focusLossMessageId:
2288 updateValueFromText();
2289 listeners.callChecked (checker, [this] (Listener& l) { l.textEditorFocusLost (*this); });
2290
2291 if (! checker.shouldBailOut())
2292 NullCheckedInvocation::invoke (onFocusLost);
2293
2294 break;
2295
2296 default:
2298 break;
2299 }
2300}
2301
2307
2309{
2310 return passwordCharacter != 0 ? passwordKeyboard : keyboardType;
2311}
2312
2313//==============================================================================
2314UndoManager* TextEditor::getUndoManager() noexcept
2315{
2316 return readOnly ? nullptr : &undoManager;
2317}
2318
2319void TextEditor::clearInternal (UndoManager* const um)
2320{
2321 remove ({ 0, getTotalNumChars() }, um, caretPosition);
2322}
2323
2324void TextEditor::insert (const String& text, int insertIndex, const Font& font,
2325 Colour colour, UndoManager* um, int caretPositionToMoveTo)
2326{
2327 if (text.isNotEmpty())
2328 {
2329 if (um != nullptr)
2330 {
2331 if (um->getNumActionsInCurrentTransaction() > TextEditorDefs::maxActionsPerTransaction)
2333
2334 um->perform (new InsertAction (*this, text, insertIndex, font, colour,
2335 caretPosition, caretPositionToMoveTo));
2336 }
2337 else
2338 {
2339 repaintText ({ insertIndex, getTotalNumChars() }); // must do this before and after changing the data, in case
2340 // a line gets moved due to word wrap
2341
2342 int index = 0;
2343 int nextIndex = 0;
2344
2345 for (int i = 0; i < sections.size(); ++i)
2346 {
2347 nextIndex = index + sections.getUnchecked (i)->getTotalLength();
2348
2349 if (insertIndex == index)
2350 {
2351 sections.insert (i, new UniformTextSection (text, font, colour, passwordCharacter));
2352 break;
2353 }
2354
2355 if (insertIndex > index && insertIndex < nextIndex)
2356 {
2357 splitSection (i, insertIndex - index);
2358 sections.insert (i + 1, new UniformTextSection (text, font, colour, passwordCharacter));
2359 break;
2360 }
2361
2362 index = nextIndex;
2363 }
2364
2365 if (nextIndex == insertIndex)
2366 sections.add (new UniformTextSection (text, font, colour, passwordCharacter));
2367
2368 coalesceSimilarSections();
2369 totalNumChars = -1;
2370 valueTextNeedsUpdating = true;
2371
2372 checkLayout();
2373 moveCaretTo (caretPositionToMoveTo, false);
2374
2375 repaintText ({ insertIndex, getTotalNumChars() });
2376 }
2377 }
2378}
2379
2380void TextEditor::reinsert (int insertIndex, const OwnedArray<UniformTextSection>& sectionsToInsert)
2381{
2382 int index = 0;
2383 int nextIndex = 0;
2384
2385 for (int i = 0; i < sections.size(); ++i)
2386 {
2387 nextIndex = index + sections.getUnchecked (i)->getTotalLength();
2388
2389 if (insertIndex == index)
2390 {
2391 for (int j = sectionsToInsert.size(); --j >= 0;)
2392 sections.insert (i, new UniformTextSection (*sectionsToInsert.getUnchecked (j)));
2393
2394 break;
2395 }
2396
2397 if (insertIndex > index && insertIndex < nextIndex)
2398 {
2399 splitSection (i, insertIndex - index);
2400
2401 for (int j = sectionsToInsert.size(); --j >= 0;)
2402 sections.insert (i + 1, new UniformTextSection (*sectionsToInsert.getUnchecked (j)));
2403
2404 break;
2405 }
2406
2407 index = nextIndex;
2408 }
2409
2410 if (nextIndex == insertIndex)
2411 for (auto* s : sectionsToInsert)
2412 sections.add (new UniformTextSection (*s));
2413
2414 coalesceSimilarSections();
2415 totalNumChars = -1;
2416 valueTextNeedsUpdating = true;
2417}
2418
2419void TextEditor::remove (Range<int> range, UndoManager* const um, const int caretPositionToMoveTo)
2420{
2421 if (! range.isEmpty())
2422 {
2423 int index = 0;
2424
2425 for (int i = 0; i < sections.size(); ++i)
2426 {
2427 auto nextIndex = index + sections.getUnchecked (i)->getTotalLength();
2428
2429 if (range.getStart() > index && range.getStart() < nextIndex)
2430 {
2431 splitSection (i, range.getStart() - index);
2432 --i;
2433 }
2434 else if (range.getEnd() > index && range.getEnd() < nextIndex)
2435 {
2436 splitSection (i, range.getEnd() - index);
2437 --i;
2438 }
2439 else
2440 {
2441 index = nextIndex;
2442
2443 if (index > range.getEnd())
2444 break;
2445 }
2446 }
2447
2448 index = 0;
2449
2450 if (um != nullptr)
2451 {
2452 Array<UniformTextSection*> removedSections;
2453
2454 for (auto* section : sections)
2455 {
2456 if (range.getEnd() <= range.getStart())
2457 break;
2458
2459 auto nextIndex = index + section->getTotalLength();
2460
2461 if (range.getStart() <= index && range.getEnd() >= nextIndex)
2462 removedSections.add (new UniformTextSection (*section));
2463
2464 index = nextIndex;
2465 }
2466
2467 if (um->getNumActionsInCurrentTransaction() > TextEditorDefs::maxActionsPerTransaction)
2469
2470 um->perform (new RemoveAction (*this, range, caretPosition,
2471 caretPositionToMoveTo, removedSections));
2472 }
2473 else
2474 {
2475 auto remainingRange = range;
2476
2477 for (int i = 0; i < sections.size(); ++i)
2478 {
2479 auto* section = sections.getUnchecked (i);
2480 auto nextIndex = index + section->getTotalLength();
2481
2482 if (remainingRange.getStart() <= index && remainingRange.getEnd() >= nextIndex)
2483 {
2484 sections.remove (i);
2485 remainingRange.setEnd (remainingRange.getEnd() - (nextIndex - index));
2486
2487 if (remainingRange.isEmpty())
2488 break;
2489
2490 --i;
2491 }
2492 else
2493 {
2494 index = nextIndex;
2495 }
2496 }
2497
2498 coalesceSimilarSections();
2499 totalNumChars = -1;
2500 valueTextNeedsUpdating = true;
2501
2502 checkLayout();
2503 moveCaretTo (caretPositionToMoveTo, false);
2504
2505 repaintText ({ range.getStart(), getTotalNumChars() });
2506 }
2507 }
2508}
2509
2510//==============================================================================
2512{
2514 mo.preallocate ((size_t) getTotalNumChars());
2515
2516 for (auto* s : sections)
2517 s->appendAllText (mo);
2518
2519 return mo.toUTF8();
2520}
2521
2523{
2524 if (range.isEmpty())
2525 return {};
2526
2528 mo.preallocate ((size_t) jmin (getTotalNumChars(), range.getLength()));
2529
2530 int index = 0;
2531
2532 for (auto* s : sections)
2533 {
2534 auto nextIndex = index + s->getTotalLength();
2535
2536 if (range.getStart() < nextIndex)
2537 {
2538 if (range.getEnd() <= index)
2539 break;
2540
2541 s->appendSubstring (mo, range - index);
2542 }
2543
2544 index = nextIndex;
2545 }
2546
2547 return mo.toUTF8();
2548}
2549
2551{
2552 return getTextInRange (selection);
2553}
2554
2556{
2557 if (totalNumChars < 0)
2558 {
2559 totalNumChars = 0;
2560
2561 for (auto* s : sections)
2562 totalNumChars += s->getTotalLength();
2563 }
2564
2565 return totalNumChars;
2566}
2567
2569{
2570 return getTotalNumChars() == 0;
2571}
2572
2573void TextEditor::getCharPosition (int index, Point<float>& anchor, float& lineHeight) const
2574{
2575 if (getWordWrapWidth() <= 0)
2576 {
2577 anchor = {};
2578 lineHeight = currentFont.getHeight();
2579 }
2580 else
2581 {
2582 Iterator i (*this);
2583
2584 if (sections.isEmpty())
2585 {
2586 anchor = { i.getJustificationOffsetX (0), 0 };
2587 lineHeight = currentFont.getHeight();
2588 }
2589 else
2590 {
2591 i.getCharPosition (index, anchor, lineHeight);
2592 }
2593 }
2594}
2595
2596int TextEditor::indexAtPosition (const float x, const float y) const
2597{
2598 if (getWordWrapWidth() > 0)
2599 {
2600 for (Iterator i (*this); i.next();)
2601 {
2602 if (y < i.lineY + (i.lineHeight * lineSpacing))
2603 {
2604 if (jmax (0.0f, y) < i.lineY)
2605 return jmax (0, i.indexInText - 1);
2606
2607 if (x <= i.atomX || i.atom->isNewLine())
2608 return i.indexInText;
2609
2610 if (x < i.atomRight)
2611 return i.xToIndex (x);
2612 }
2613 }
2614 }
2615
2616 return getTotalNumChars();
2617}
2618
2619//==============================================================================
2620int TextEditor::findWordBreakAfter (const int position) const
2621{
2622 auto t = getTextInRange ({ position, position + 512 });
2623 auto totalLength = t.length();
2624 int i = 0;
2625
2626 while (i < totalLength && CharacterFunctions::isWhitespace (t[i]))
2627 ++i;
2628
2629 auto type = TextEditorDefs::getCharacterCategory (t[i]);
2630
2631 while (i < totalLength && type == TextEditorDefs::getCharacterCategory (t[i]))
2632 ++i;
2633
2634 while (i < totalLength && CharacterFunctions::isWhitespace (t[i]))
2635 ++i;
2636
2637 return position + i;
2638}
2639
2640int TextEditor::findWordBreakBefore (const int position) const
2641{
2642 if (position <= 0)
2643 return 0;
2644
2645 auto startOfBuffer = jmax (0, position - 512);
2646 auto t = getTextInRange ({ startOfBuffer, position });
2647
2648 int i = position - startOfBuffer;
2649
2650 while (i > 0 && CharacterFunctions::isWhitespace (t [i - 1]))
2651 --i;
2652
2653 if (i > 0)
2654 {
2655 auto type = TextEditorDefs::getCharacterCategory (t [i - 1]);
2656
2657 while (i > 0 && type == TextEditorDefs::getCharacterCategory (t [i - 1]))
2658 --i;
2659 }
2660
2661 jassert (startOfBuffer + i >= 0);
2662 return startOfBuffer + i;
2663}
2664
2665
2666//==============================================================================
2667void TextEditor::splitSection (const int sectionIndex, const int charToSplitAt)
2668{
2669 jassert (sections[sectionIndex] != nullptr);
2670
2671 sections.insert (sectionIndex + 1,
2672 sections.getUnchecked (sectionIndex)->split (charToSplitAt));
2673}
2674
2675void TextEditor::coalesceSimilarSections()
2676{
2677 for (int i = 0; i < sections.size() - 1; ++i)
2678 {
2679 auto* s1 = sections.getUnchecked (i);
2680 auto* s2 = sections.getUnchecked (i + 1);
2681
2682 if (s1->font == s2->font
2683 && s1->colour == s2->colour)
2684 {
2685 s1->append (*s2);
2686 sections.remove (i + 1);
2687 --i;
2688 }
2689 }
2690}
2691
2692//==============================================================================
2694{
2695public:
2696 explicit EditorAccessibilityHandler (TextEditor& textEditorToWrap)
2697 : AccessibilityHandler (textEditorToWrap,
2698 textEditorToWrap.isReadOnly() ? AccessibilityRole::staticText : AccessibilityRole::editableText,
2699 {},
2700 { std::make_unique<TextEditorTextInterface> (textEditorToWrap) }),
2701 textEditor (textEditorToWrap)
2702 {
2703 }
2704
2705 String getHelp() const override { return textEditor.getTooltip(); }
2706
2707private:
2708 class TextEditorTextInterface final : public AccessibilityTextInterface
2709 {
2710 public:
2711 explicit TextEditorTextInterface (TextEditor& editor)
2712 : textEditor (editor)
2713 {
2714 }
2715
2716 bool isDisplayingProtectedText() const override { return textEditor.getPasswordCharacter() != 0; }
2717 bool isReadOnly() const override { return textEditor.isReadOnly(); }
2718
2719 int getTotalNumCharacters() const override { return textEditor.getText().length(); }
2720 Range<int> getSelection() const override { return textEditor.getHighlightedRegion(); }
2721
2722 void setSelection (Range<int> r) override
2723 {
2724 textEditor.setHighlightedRegion (r);
2725 }
2726
2727 String getText (Range<int> r) const override
2728 {
2729 if (isDisplayingProtectedText())
2730 return String::repeatedString (String::charToString (textEditor.getPasswordCharacter()),
2731 getTotalNumCharacters());
2732
2733 return textEditor.getTextInRange (r);
2734 }
2735
2736 void setText (const String& newText) override
2737 {
2738 textEditor.setText (newText);
2739 }
2740
2741 int getTextInsertionOffset() const override { return textEditor.getCaretPosition(); }
2742
2743 RectangleList<int> getTextBounds (Range<int> textRange) const override
2744 {
2745 auto localRects = textEditor.getTextBounds (textRange);
2746 RectangleList<int> globalRects;
2747
2748 std::for_each (localRects.begin(), localRects.end(),
2749 [&] (const Rectangle<int>& r) { globalRects.add (textEditor.localAreaToGlobal (r)); });
2750
2751 return globalRects;
2752 }
2753
2754 int getOffsetAtPoint (Point<int> point) const override
2755 {
2756 return textEditor.getTextIndexAt (textEditor.getLocalPoint (nullptr, point));
2757 }
2758
2759 private:
2760 TextEditor& textEditor;
2761
2762 //==============================================================================
2763 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextEditorTextInterface)
2764 };
2765
2766 TextEditor& textEditor;
2767
2768 //==============================================================================
2769 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EditorAccessibilityHandler)
2770};
2771
2773{
2774 return std::make_unique<EditorAccessibilityHandler> (*this);
2775}
2776
2777} // namespace juce
T clamp(T... args)
Base class for accessible Components.
An abstract interface which represents a UI element that supports a text interface.
Represents a 2D affine-transformation matrix.
static AffineTransform translation(float deltaX, float deltaY) noexcept
Returns a new transform which is a translation.
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:56
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition juce_Array.h:418
Specifies a set of gaps to be left around the sides of a rectangle.
ValueType getTop() const noexcept
Returns the gap that should be left at the top of the region.
ValueType getLeft() const noexcept
Returns the gap that should be left at the left of the region.
static bool isWhitespace(char character) noexcept
Checks whether a character is whitespace.
static bool isLetterOrDigit(char character) noexcept
Checks whether a character is alphabetic or numeric.
Represents a colour, also including a transparency value.
Definition juce_Colour.h:38
A class to keep an eye on a component and check for it being deleted.
Holds a pointer to some type of Component, which automatically becomes null if the component is delet...
The base class for all JUCE user-interface objects.
int proportionOfWidth(float proportion) const noexcept
Returns a proportion of the component's width.
void setInterceptsMouseClicks(bool allowClicksOnThisComponent, bool allowClicksOnChildComponents) noexcept
Changes the default return value for the hitTest() method.
int getHeight() const noexcept
Returns the component's height in pixels.
void addAndMakeVisible(Component *child, int zOrder=-1)
Adds a child component to this one, and also makes the child visible if it isn't already.
FocusChangeType
Enumeration used by the focusGained() and focusLost() methods.
@ focusChangedByMouseClick
Means that the user clicked the mouse to change focus.
bool hasKeyboardFocus(bool trueIfChildIsFocused) const
Returns true if this component currently has the keyboard focus.
bool isCurrentlyBlockedByAnotherModalComponent() const
Checks whether there's a modal component somewhere that's stopping this one from receiving messages.
AccessibilityHandler * getAccessibilityHandler()
Returns the accessibility handler for this component, or nullptr if this component is not accessible.
void setMouseCursor(const MouseCursor &cursorType)
Changes the mouse cursor shape to use when the mouse is over this component.
void postCommandMessage(int commandId)
Dispatches a numbered message to this component.
Rectangle< int > getBounds() const noexcept
Returns this component's bounding box.
void repaint()
Marks the whole component as needing to be redrawn.
void setSize(int newWidth, int newHeight)
Changes the size of the component.
void setColour(int colourID, Colour newColour)
Registers a colour to be used for a particular purpose.
void setWantsKeyboardFocus(bool wantsFocus) noexcept
Sets a flag to indicate whether this component wants keyboard focus or not.
Colour findColour(int colourID, bool inheritFromParent=false) const
Looks for a colour that has been registered with the given colour ID number.
int getWidth() const noexcept
Returns the component's width in pixels.
void mouseWheelMove(const MouseEvent &event, const MouseWheelDetails &wheel) override
Called when the mouse-wheel is moved.
static void JUCE_CALLTYPE beginDragAutoRepeat(int millisecondsBetweenCallbacks)
Ensures that a non-stop stream of mouse-drag events will be sent during the current mouse-drag operat...
bool isEnabled() const noexcept
Returns true if the component (and all its parents) are enabled.
ComponentPeer * getPeer() const
Returns the heavyweight window that contains this component.
LookAndFeel & getLookAndFeel() const noexcept
Finds the appropriate look-and-feel to use for this component.
void addChildComponent(Component *child, int zOrder=-1)
Adds a child component to this one.
void invalidateAccessibilityHandler()
Invalidates the AccessibilityHandler that is currently being used for this component.
Represents a particular font, including its size, style, etc.
Definition juce_Font.h:42
float getHeight() const noexcept
Returns the total height of this font, in pixels.
float getStringWidthFloat(const String &text) const
Returns the total width of a string as it would be drawn using this font.
float getAscent() const
Returns the height of the font above its baseline, in pixels.
A set of glyphs, each with a position.
void addLineOfText(const Font &font, const String &text, float x, float y)
Appends a line of text to the arrangement.
int getNumGlyphs() const noexcept
Returns the total number of glyphs in the arrangement.
PositionedGlyph & getGlyph(int index) noexcept
Returns one of the glyphs from the arrangement.
Uses RAII to save and restore the state of a graphics context.
A graphics context, used for drawing a component or image.
void drawText(const String &text, int x, int y, int width, int height, Justification justificationType, bool useEllipsesIfTooBig=true) const
Draws a line of text within a specified rectangle.
void setFont(const Font &newFont)
Changes the font to use for subsequent text-drawing functions.
void addTransform(const AffineTransform &transform)
Adds a transformation which will be performed on all the graphics operations that the context subsequ...
void fillPath(const Path &path) const
Fills a path using the currently selected colour or brush.
bool reduceClipRegion(int x, int y, int width, int height)
Intersects the current clipping region with another region.
void fillCheckerBoard(Rectangle< float > area, float checkWidth, float checkHeight, Colour colour1, Colour colour2) const
Fills a rectangle with a checkerboard pattern, alternating between two colours.
Rectangle< int > getClipBounds() const
Returns the position of the bounding box for the current clipping region.
void setColour(Colour newColour)
Changes the current drawing colour.
void setOrigin(Point< int > newOrigin)
Moves the position of the context's origin.
Represents a type of justification to be used when positioning graphical items.
@ horizontallyCentred
Indicates that the item should be placed in the centre between the left and right sides of the availa...
@ bottom
Indicates that the item should be aligned against the bottom edge of the available space.
@ top
Indicates that the item should be aligned against the top edge of the available space.
@ right
Indicates that the item should be aligned against the right edge of the available space.
bool testFlags(int flagsToTest) const noexcept
Tests a set of flags for this object.
Represents a key press, including any modifier keys that are needed.
juce_wchar getTextCharacter() const noexcept
Returns the character that is associated with this keypress.
bool isKeyCode(int keyCodeToCompare) const noexcept
Checks whether the KeyPress's key is the same as the one provided, without checking the modifiers.
static const int F4Key
key-code for the F4 key
static const int escapeKey
key-code for the escape key
static const int returnKey
key-code for the return key
Writes data to an internal memory buffer, which grows as required.
void preallocate(size_t bytesToPreallocate)
Increases the internal storage capacity to be able to contain at least the specified amount of data w...
static ModifierKeys currentModifiers
This object represents the last-known state of the keyboard and mouse buttons.
bool isCommandDown() const noexcept
Checks whether the 'command' key flag is set (or 'ctrl' on Windows/Linux).
bool isPopupMenu() const noexcept
Checks whether the user is trying to launch a pop-up menu.
bool isShiftDown() const noexcept
Checks whether the shift key's flag is set.
@ commandModifier
Command key flag - on windows this is the same as the CTRL key flag.
@ altModifier
ALT key flag.
@ IBeamCursor
A vertical I-beam for positioning within text.
@ ParentCursor
Indicates that the component's parent's cursor should be used.
Contains position and status information about a mouse event.
const ModifierKeys mods
The key modifiers associated with the event.
Point< int > getPosition() const noexcept
The position of the mouse when the event occurred.
int getNumberOfClicks() const noexcept
For a click event, the number of times the mouse was clicked in succession.
bool mouseWasClicked() const noexcept
Returns true if the mouse event is part of a click gesture rather than a drag.
An array designed for holding objects.
A pair of (x, y) coordinates.
Definition juce_Point.h:42
ValueType y
The point's Y coordinate.
Definition juce_Point.h:252
ValueType x
The point's X coordinate.
Definition juce_Point.h:251
Class used to create a set of options to pass to the show() method.
Creates and displays a popup-menu.
void addSeparator()
Appends a separator to the menu, to help break it up into sections.
void showMenuAsync(const Options &options)
Runs the menu asynchronously.
void setLookAndFeel(LookAndFeel *newLookAndFeel)
Specifies a look-and-feel for the menu and any sub-menus that it has.
void addItem(Item newItem)
Adds an item to the menu.
float getRight() const noexcept
Returns the position of the glyph's right-hand edge.
float getLeft() const noexcept
Returns the position of the glyph's left-hand edge.
A general-purpose range object, that simply represents any linear range with a start and end point.
Definition juce_Range.h:40
constexpr ValueType getStart() const noexcept
Returns the start of the range.
Definition juce_Range.h:80
constexpr bool isEmpty() const noexcept
Returns true if the range has a length of zero.
Definition juce_Range.h:89
constexpr bool intersects(Range other) const noexcept
Returns true if the given range intersects this one.
Definition juce_Range.h:232
constexpr ValueType getEnd() const noexcept
Returns the end of the range.
Definition juce_Range.h:86
constexpr Range getUnionWith(Range other) const noexcept
Returns the smallest range that contains both this one and the other one.
Definition juce_Range.h:246
constexpr ValueType getLength() const noexcept
Returns the length of the range.
Definition juce_Range.h:83
Maintains a set of rectangles as a complex region.
Manages a rectangle and allows geometric operations to be performed on it.
ValueType getX() const noexcept
Returns the x coordinate of the rectangle's left-hand-side.
Rectangle< int > getSmallestIntegerContainer() const noexcept
Returns the smallest integer-aligned rectangle that completely contains this one.
Rectangle translated(ValueType deltaX, ValueType deltaY) const noexcept
Returns a rectangle which is the same as this one moved by a given amount.
Point< ValueType > getConstrainedPoint(Point< ValueType > point) const noexcept
Returns the nearest point to the specified point that lies within this rectangle.
int getReferenceCount() const noexcept
Returns the object's current reference count.
Helper class providing an RAII-based mechanism for temporarily setting and then re-setting a value.
The JUCE String class!
Definition juce_String.h:53
CharPointerType getCharPointer() const noexcept
Returns the character pointer currently being used to store this string.
static String repeatedString(StringRef stringToRepeat, int numberOfTimesToRepeat)
Creates a string which is a version of a string repeated and joined together.
int length() const noexcept
Returns the number of characters in the string.
bool isEmpty() const noexcept
Returns true if the string contains no characters.
String retainCharacters(StringRef charactersToRetain) const
Returns a version of this string that only retains a fixed set of characters.
static String charToString(juce_wchar character)
Creates a string from a single character.
String substring(int startIndex, int endIndex) const
Returns a subsection of the string.
bool isNotEmpty() const noexcept
Returns true if the string contains at least one character.
static String getTextFromClipboard()
Gets the current clipboard's contents.
static void copyTextToClipboard(const String &text)
Copies a string of text onto the clipboard.
String getHelp() const override
Some help text for the UI element (if required).
Base class for input filters that can be applied to a TextEditor to restrict the text that can be ent...
An input filter for a TextEditor that limits the length of text and/or the characters that it may con...
LengthAndCharacterRestriction(int maxNumChars, const String &allowedCharacters)
Creates a filter that limits the length of text, and/or the characters that it can contain.
String filterNewText(TextEditor &, const String &) override
This method is called whenever text is entered into the editor.
Receives callbacks from a TextEditor component when it changes.
An editable text box.
void paint(Graphics &) override
Components can override this method to draw their content.
void scrollEditorToPositionCaret(int desiredCaretX, int desiredCaretY)
Attempts to scroll the text editor so that the caret ends up at a specified position.
int getCaretPosition() const override
Returns the current index of the caret.
bool isTextInputActive() const override
Returns true if this input target is currently accepting input.
void setScrollbarsShown(bool shouldBeEnabled)
Enables or disables scrollbars (this only applies when in multi-line mode).
void setMultiLine(bool shouldBeMultiLine, bool shouldWordWrap=true)
Puts the editor into either multi- or single-line mode.
@ highlightColourId
The colour with which to fill the background of highlighted sections of the text - this can be transp...
@ textColourId
The colour that will be used when text is added to the editor.
@ highlightedTextColourId
The colour with which to draw the text in highlighted sections.
void setCaretPosition(int newIndex)
Moves the caret to be in front of a given character.
bool isEmpty() const
Returns true if there are no characters in the editor.
bool keyStateChanged(bool) override
Called when a key is pressed or released.
void setBorder(BorderSize< int > border)
Changes the size of border left around the edge of the component.
void mouseWheelMove(const MouseEvent &, const MouseWheelDetails &) override
Called when the mouse-wheel is moved.
void applyColourToAllText(const Colour &newColour, bool changeCurrentTextColour=true)
Applies a colour to all the text in the editor.
RectangleList< int > getTextBounds(Range< int > textRange) const override
Returns the bounding box for a range of text in the editor.
int getTextWidth() const
Returns the total width of the text, as it is currently laid-out.
std::unique_ptr< AccessibilityHandler > createAccessibilityHandler() override
Override this method to return a custom AccessibilityHandler for this component.
void setIndents(int newLeftIndent, int newTopIndent)
Changes the size of the gap at the top and left-edge of the editor.
void insertTextAtCaret(const String &textToInsert) override
Inserts some text at the current caret position.
VirtualKeyboardType getKeyboardType() override
Returns the target's preference for the type of keyboard that would be most appropriate.
virtual void escapePressed()
Can be overridden to intercept escape key presses directly.
void mouseDown(const MouseEvent &) override
Called when a mouse button is pressed.
~TextEditor() override
Destructor.
void setPasswordCharacter(juce_wchar passwordCharacter)
Changes the password character used to disguise the text.
void newTransaction()
Begins a new transaction in the UndoManager.
bool isReadOnly() const noexcept
Returns true if the editor is in read-only mode.
void setTemporaryUnderlining(const Array< Range< int > > &) override
Sets a number of temporarily underlined sections.
const Font & getFont() const noexcept
Returns the font that's currently being used for new text.
std::function< void()> onFocusLost
You can assign a lambda to this callback object to have it called when the editor loses key focus.
std::function< void()> onEscapeKey
You can assign a lambda to this callback object to have it called when the escape key is pressed.
void setFont(const Font &newFont)
Sets the font to use for newly added text.
void focusGained(FocusChangeType) override
Called to indicate that this component has just acquired the keyboard focus.
void clear()
Deletes all the text from the editor.
void copy()
Copies the currently selected region to the clipboard.
int getTotalNumChars() const override
Counts the number of characters in the text.
String getHighlightedText() const
Returns the section of text that is currently selected.
void enablementChanged() override
Callback to indicate that this component has been enabled or disabled.
std::function< void()> onReturnKey
You can assign a lambda to this callback object to have it called when the return key is pressed.
int getLeftIndent() const noexcept
Returns the gap at the left edge of the editor.
Rectangle< int > getCaretRectangleForCharIndex(int index) const override
Get the graphical position of the caret for a particular index in the text.
void setText(const String &newText, bool sendTextChangeMessage=true)
Sets the entire content of the editor.
int getTextHeight() const
Returns the maximum height of the text, as it is currently laid-out.
void paintOverChildren(Graphics &) override
Components can override this method to draw over the top of their children.
void addListener(Listener *newListener)
Registers a listener to be told when things happen to the text.
void mouseDrag(const MouseEvent &) override
Called when the mouse is moved while a button is held down.
void setReadOnly(bool shouldBeReadOnly)
Changes the editor to read-only mode.
int getTextIndexAt(int x, int y) const
Finds the index of the character at a given position.
void setEscapeAndReturnKeysConsumed(bool shouldBeConsumed) noexcept
This can be used to change whether escape and return keypress events are propagated up to the parent ...
int getCharIndexForPoint(Point< int > point) const override
Like getTextIndexAt, but doesn't snap to the beginning/end of the range for points vertically outside...
void setClicksOutsideDismissVirtualKeyboard(bool)
Sets the behaviour of mouse/touch interactions outside this component.
String getTextInRange(const Range< int > &textRange) const override
Returns a section of the contents of the editor.
int getTopIndent() const noexcept
Returns the gap at the top edge of the editor.
void lookAndFeelChanged() override
Called to let the component react to a change in the look-and-feel setting.
virtual void addPopupMenuItems(PopupMenu &menuToAddTo, const MouseEvent *mouseClickEvent)
This adds the items to the popup menu.
Range< int > getHighlightedRegion() const override
Returns the range of characters that are selected.
virtual void returnPressed()
Can be overridden to intercept return key presses directly.
void resized() override
Called when this component's size has been changed.
void setJustification(Justification newJustification)
Modifies the justification of the text within the editor window.
void scrollToMakeSureCursorIsVisible()
Scrolls the minimum distance needed to get the caret into view.
void mouseDoubleClick(const MouseEvent &) override
Called when a mouse button has been double-clicked on a component.
void textChanged()
Used internally to dispatch a text-change message.
void setTextToShowWhenEmpty(const String &text, Colour colourToUse)
When the text editor is empty, it can be set to display a message.
void setInputFilter(InputFilter *newFilter, bool takeOwnership)
Sets an input filter that should be applied to this editor.
virtual void performPopupMenuAction(int menuItemID)
This is called to perform one of the items that was shown on the popup menu.
void focusLost(FocusChangeType) override
Called to indicate that this component has just lost the keyboard focus.
void setInputRestrictions(int maxTextLength, const String &allowedCharacters=String())
Sets limits on the characters that can be entered.
Value & getTextValue()
Returns a Value object that can be used to get or set the text.
bool isCaretVisible() const noexcept
Returns true if the caret is enabled.
void setHighlightedRegion(const Range< int > &newSelection) override
Selects a section of the text.
std::function< void()> onTextChange
You can assign a lambda to this callback object to have it called when the text is changed.
void setCaretVisible(bool shouldBeVisible)
Makes the caret visible or invisible.
void setPopupMenuEnabled(bool menuEnabled)
Allows a right-click menu to appear for the editor.
TextEditor(const String &componentName=String(), juce_wchar passwordCharacter=0)
Creates a new, empty text editor.
void cut()
Deletes the currently selected region.
void setTabKeyUsedAsCharacter(bool shouldTabKeyBeUsed)
Indicates whether the tab key should be accepted and used to input a tab character,...
bool keyPressed(const KeyPress &) override
Called when a key is pressed.
void setReturnKeyStartsNewLine(bool shouldStartNewLine)
Changes the behaviour of the return key.
void mouseUp(const MouseEvent &) override
Called when a mouse button is released.
bool isMultiLine() const
Returns true if the editor is in multi-line mode.
String getText() const
Returns the entire contents of the editor.
void parentHierarchyChanged() override
Called to indicate that the component's parents have changed.
BorderSize< int > getBorder() const
Returns the size of border around the edge of the component.
void removeListener(Listener *listenerToRemove)
Deregisters a listener.
void paste()
Pastes the contents of the clipboard into the editor at the caret position.
void setSelectAllWhenFocused(bool shouldSelectAll)
If set to true, focusing on the editor will highlight all its text.
void applyFontToAllText(const Font &newFont, bool changeCurrentFont=true)
Applies a font to all the text in the editor.
void setScrollBarThickness(int newThicknessPixels)
Changes the size of the scrollbars that are used.
void setScrollToShowCursor(bool shouldScrollToShowCaret)
Used to disable the auto-scrolling which keeps the caret visible.
VirtualKeyboardType
A set of possible on-screen keyboard types, for use in the getKeyboardType() method.
Rectangle< int > getCaretRectangle() const
Returns the position of the caret, relative to the component's origin.
static uint32 getApproximateMillisecondCounter() noexcept
Less-accurate but faster version of getMillisecondCounter().
Makes repeated callbacks to a virtual method at a specified time interval.
Definition juce_Timer.h:52
void stopTimer() noexcept
Stops the timer.
void startTimer(int intervalInMilliseconds) noexcept
Starts the timer and sets the length of interval required.
Manages a list of undo/redo commands.
void beginNewTransaction()
Starts a new group of actions that together will be treated as a single transaction.
bool undo()
Tries to roll-back the last transaction.
void clearUndoHistory()
Deletes all stored actions in the list.
bool canUndo() const
Returns true if there's at least one action in the list to undo.
bool canRedo() const
Returns true if there's at least one action in the list to redo.
Used by the UndoManager class to store an action which can be done and undone.
Receives callbacks when a Value object changes.
Definition juce_Value.h:139
Represents a shared variant value.
Definition juce_Value.h:51
void addListener(Listener *listener)
Adds a listener to receive callbacks when the value changes.
void removeListener(Listener *listener)
Removes a listener that was previously added with addListener().
ValueSource & getValueSource() noexcept
Returns the ValueSource that this value is referring to.
Definition juce_Value.h:218
void referTo(const Value &valueToReferTo)
Makes this object refer to the same underlying ValueSource as another one.
var getValue() const
Returns the current value.
A Viewport is used to contain a larger child component, and allows the child to be automatically scro...
T for_each(T... args)
#define JUCE_LEAK_DETECTOR(OwnerClass)
This macro lets you embed a leak-detecting object inside a class.
#define TRANS(stringLiteral)
Uses the LocalisedStrings class to translate the given string literal.
#define jassert(expression)
Platform-independent assertion macro.
#define JUCE_DECLARE_NON_COPYABLE(className)
This is a shorthand macro for deleting a class's copy constructor and copy assignment operator.
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and JUCE_LEAK_DETECTOR macro for ...
#define jassertfalse
This will always cause an assertion failure.
typedef int
typedef float
@ paste
The command ID that should be used to send a "Paste from clipboard" command.
@ del
The command ID that should be used to send a "Delete" command.
@ redo
The command ID that should be used to send a "redo" command.
@ undo
The command ID that should be used to send a "undo" command.
@ cut
The command ID that should be used to send a "Cut" command.
@ copy
The command ID that should be used to send a "Copy to clipboard" command.
@ selectAll
The command ID that should be used to send a "Select all" command.
JUCE Namespace.
unsigned short uint16
A platform-independent 16-bit unsigned integer type.
wchar_t juce_wchar
A platform-independent 32-bit unicode character type.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
Constrains a value to keep it within a given range.
@ textChanged
Indicates that the visible text of a text element has changed.
@ textSelectionChanged
Indicates that the selection of a text element has changed.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
Returns true if a value is at least zero, and also below a specified upper limit.
int roundToInt(const FloatType value) noexcept
Fast floating-point-to-integer conversion.
Contains status information about a mouse wheel event.
This class is used to invoke a range of text-editor navigation methods on an object,...
int getSizeInUnits() override
Returns a value to indicate how much memory this object takes up.
bool undo() override
Overridden by a subclass to undo the action.
bool perform() override
Overridden by a subclass to perform the action.
bool perform() override
Overridden by a subclass to perform the action.
int getSizeInUnits() override
Returns a value to indicate how much memory this object takes up.
bool undo() override
Overridden by a subclass to undo the action.
void visibleAreaChanged(const Rectangle< int > &) override
Callback method that is called when the visible area changes.
void paint(Graphics &g) override
Components can override this method to draw their content.
void timerCallback() override
The user-defined callback routine that actually gets called periodically.
void valueChanged(Value &) override
Called when a Value object is changed.
T trunc(T... args)
y1