The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2546 lines
73KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. // a word or space that can't be broken down any further
  22. struct TextAtom
  23. {
  24. //==============================================================================
  25. String atomText;
  26. float width;
  27. int numChars;
  28. //==============================================================================
  29. bool isWhitespace() const noexcept { return CharacterFunctions::isWhitespace (atomText[0]); }
  30. bool isNewLine() const noexcept { return atomText[0] == '\r' || atomText[0] == '\n'; }
  31. String getText (juce_wchar passwordCharacter) const
  32. {
  33. if (passwordCharacter == 0)
  34. return atomText;
  35. return String::repeatedString (String::charToString (passwordCharacter),
  36. atomText.length());
  37. }
  38. String getTrimmedText (const juce_wchar passwordCharacter) const
  39. {
  40. if (passwordCharacter == 0)
  41. return atomText.substring (0, numChars);
  42. if (isNewLine())
  43. return {};
  44. return String::repeatedString (String::charToString (passwordCharacter), numChars);
  45. }
  46. JUCE_LEAK_DETECTOR (TextAtom)
  47. };
  48. //==============================================================================
  49. // a run of text with a single font and colour
  50. class TextEditor::UniformTextSection
  51. {
  52. public:
  53. UniformTextSection (const String& text, const Font& f, Colour col, juce_wchar passwordChar)
  54. : font (f), colour (col)
  55. {
  56. initialiseAtoms (text, passwordChar);
  57. }
  58. UniformTextSection (const UniformTextSection&) = default;
  59. // VS2013 can't default move constructors
  60. UniformTextSection (UniformTextSection&& other)
  61. : font (std::move (other.font)),
  62. colour (other.colour),
  63. atoms (std::move (other.atoms))
  64. {
  65. }
  66. UniformTextSection& operator= (const UniformTextSection&) = delete;
  67. void append (UniformTextSection& other, const juce_wchar passwordChar)
  68. {
  69. if (! other.atoms.isEmpty())
  70. {
  71. int i = 0;
  72. if (! atoms.isEmpty())
  73. {
  74. auto& lastAtom = atoms.getReference (atoms.size() - 1);
  75. if (! CharacterFunctions::isWhitespace (lastAtom.atomText.getLastCharacter()))
  76. {
  77. auto& first = other.atoms.getReference(0);
  78. if (! CharacterFunctions::isWhitespace (first.atomText[0]))
  79. {
  80. lastAtom.atomText += first.atomText;
  81. lastAtom.numChars = (uint16) (lastAtom.numChars + first.numChars);
  82. lastAtom.width = font.getStringWidthFloat (lastAtom.getText (passwordChar));
  83. ++i;
  84. }
  85. }
  86. }
  87. atoms.ensureStorageAllocated (atoms.size() + other.atoms.size() - i);
  88. while (i < other.atoms.size())
  89. {
  90. atoms.add (other.atoms.getReference(i));
  91. ++i;
  92. }
  93. }
  94. }
  95. UniformTextSection* split (int indexToBreakAt, juce_wchar passwordChar)
  96. {
  97. auto* section2 = new UniformTextSection (String(), font, colour, passwordChar);
  98. int index = 0;
  99. for (int i = 0; i < atoms.size(); ++i)
  100. {
  101. auto& atom = atoms.getReference(i);
  102. auto nextIndex = index + atom.numChars;
  103. if (index == indexToBreakAt)
  104. {
  105. for (int j = i; j < atoms.size(); ++j)
  106. section2->atoms.add (atoms.getUnchecked (j));
  107. atoms.removeRange (i, atoms.size());
  108. break;
  109. }
  110. if (indexToBreakAt >= index && indexToBreakAt < nextIndex)
  111. {
  112. TextAtom secondAtom;
  113. secondAtom.atomText = atom.atomText.substring (indexToBreakAt - index);
  114. secondAtom.width = font.getStringWidthFloat (secondAtom.getText (passwordChar));
  115. secondAtom.numChars = (uint16) secondAtom.atomText.length();
  116. section2->atoms.add (secondAtom);
  117. atom.atomText = atom.atomText.substring (0, indexToBreakAt - index);
  118. atom.width = font.getStringWidthFloat (atom.getText (passwordChar));
  119. atom.numChars = (uint16) (indexToBreakAt - index);
  120. for (int j = i + 1; j < atoms.size(); ++j)
  121. section2->atoms.add (atoms.getUnchecked (j));
  122. atoms.removeRange (i + 1, atoms.size());
  123. break;
  124. }
  125. index = nextIndex;
  126. }
  127. return section2;
  128. }
  129. void appendAllText (MemoryOutputStream& mo) const
  130. {
  131. for (auto& atom : atoms)
  132. mo << atom.atomText;
  133. }
  134. void appendSubstring (MemoryOutputStream& mo, Range<int> range) const
  135. {
  136. int index = 0;
  137. for (auto& atom : atoms)
  138. {
  139. auto nextIndex = index + atom.numChars;
  140. if (range.getStart() < nextIndex)
  141. {
  142. if (range.getEnd() <= index)
  143. break;
  144. auto r = (range - index).getIntersectionWith ({ 0, (int) atom.numChars });
  145. if (! r.isEmpty())
  146. mo << atom.atomText.substring (r.getStart(), r.getEnd());
  147. }
  148. index = nextIndex;
  149. }
  150. }
  151. int getTotalLength() const noexcept
  152. {
  153. int total = 0;
  154. for (auto& atom : atoms)
  155. total += atom.numChars;
  156. return total;
  157. }
  158. void setFont (const Font& newFont, const juce_wchar passwordChar)
  159. {
  160. if (font != newFont)
  161. {
  162. font = newFont;
  163. for (auto& atom : atoms)
  164. atom.width = newFont.getStringWidthFloat (atom.getText (passwordChar));
  165. }
  166. }
  167. //==============================================================================
  168. Font font;
  169. Colour colour;
  170. Array<TextAtom> atoms;
  171. private:
  172. void initialiseAtoms (const String& textToParse, const juce_wchar passwordChar)
  173. {
  174. auto text = textToParse.getCharPointer();
  175. while (! text.isEmpty())
  176. {
  177. size_t numChars = 0;
  178. auto start = text;
  179. // create a whitespace atom unless it starts with non-ws
  180. if (text.isWhitespace() && *text != '\r' && *text != '\n')
  181. {
  182. do
  183. {
  184. ++text;
  185. ++numChars;
  186. }
  187. while (text.isWhitespace() && *text != '\r' && *text != '\n');
  188. }
  189. else
  190. {
  191. if (*text == '\r')
  192. {
  193. ++text;
  194. ++numChars;
  195. if (*text == '\n')
  196. {
  197. ++start;
  198. ++text;
  199. }
  200. }
  201. else if (*text == '\n')
  202. {
  203. ++text;
  204. ++numChars;
  205. }
  206. else
  207. {
  208. while (! (text.isEmpty() || text.isWhitespace()))
  209. {
  210. ++text;
  211. ++numChars;
  212. }
  213. }
  214. }
  215. TextAtom atom;
  216. atom.atomText = String (start, numChars);
  217. atom.width = font.getStringWidthFloat (atom.getText (passwordChar));
  218. atom.numChars = (uint16) numChars;
  219. atoms.add (atom);
  220. }
  221. }
  222. JUCE_LEAK_DETECTOR (UniformTextSection)
  223. };
  224. //==============================================================================
  225. struct TextEditor::Iterator
  226. {
  227. Iterator (const TextEditor& ed)
  228. : sections (ed.sections),
  229. justification (ed.justification),
  230. justificationWidth (ed.getJustificationWidth()),
  231. wordWrapWidth (ed.getWordWrapWidth()),
  232. passwordCharacter (ed.passwordCharacter),
  233. lineSpacing (ed.lineSpacing)
  234. {
  235. jassert (wordWrapWidth > 0);
  236. if (! sections.isEmpty())
  237. {
  238. currentSection = sections.getUnchecked (sectionIndex);
  239. if (currentSection != nullptr)
  240. beginNewLine();
  241. }
  242. }
  243. Iterator (const Iterator&) = default;
  244. Iterator& operator= (const Iterator&) = delete;
  245. //==============================================================================
  246. bool next()
  247. {
  248. if (atom == &tempAtom)
  249. {
  250. auto numRemaining = tempAtom.atomText.length() - tempAtom.numChars;
  251. if (numRemaining > 0)
  252. {
  253. tempAtom.atomText = tempAtom.atomText.substring (tempAtom.numChars);
  254. if (tempAtom.numChars > 0)
  255. lineY += lineHeight * lineSpacing;
  256. indexInText += tempAtom.numChars;
  257. GlyphArrangement g;
  258. g.addLineOfText (currentSection->font, atom->getText (passwordCharacter), 0.0f, 0.0f);
  259. int split;
  260. for (split = 0; split < g.getNumGlyphs(); ++split)
  261. if (shouldWrap (g.getGlyph (split).getRight()))
  262. break;
  263. if (split > 0 && split <= numRemaining)
  264. {
  265. tempAtom.numChars = (uint16) split;
  266. tempAtom.width = g.getGlyph (split - 1).getRight();
  267. atomX = getJustificationOffset (tempAtom.width);
  268. atomRight = atomX + tempAtom.width;
  269. return true;
  270. }
  271. }
  272. }
  273. if (sectionIndex >= sections.size())
  274. {
  275. moveToEndOfLastAtom();
  276. return false;
  277. }
  278. bool forceNewLine = false;
  279. if (atomIndex >= currentSection->atoms.size() - 1)
  280. {
  281. if (atomIndex >= currentSection->atoms.size())
  282. {
  283. if (++sectionIndex >= sections.size())
  284. {
  285. moveToEndOfLastAtom();
  286. return false;
  287. }
  288. atomIndex = 0;
  289. currentSection = sections.getUnchecked (sectionIndex);
  290. }
  291. else
  292. {
  293. auto& lastAtom = currentSection->atoms.getReference (atomIndex);
  294. if (! lastAtom.isWhitespace())
  295. {
  296. // handle the case where the last atom in a section is actually part of the same
  297. // word as the first atom of the next section...
  298. float right = atomRight + lastAtom.width;
  299. float lineHeight2 = lineHeight;
  300. float maxDescent2 = maxDescent;
  301. for (int section = sectionIndex + 1; section < sections.size(); ++section)
  302. {
  303. auto* s = sections.getUnchecked (section);
  304. if (s->atoms.size() == 0)
  305. break;
  306. auto& nextAtom = s->atoms.getReference (0);
  307. if (nextAtom.isWhitespace())
  308. break;
  309. right += nextAtom.width;
  310. lineHeight2 = jmax (lineHeight2, s->font.getHeight());
  311. maxDescent2 = jmax (maxDescent2, s->font.getDescent());
  312. if (shouldWrap (right))
  313. {
  314. lineHeight = lineHeight2;
  315. maxDescent = maxDescent2;
  316. forceNewLine = true;
  317. break;
  318. }
  319. if (s->atoms.size() > 1)
  320. break;
  321. }
  322. }
  323. }
  324. }
  325. if (atom != nullptr)
  326. {
  327. atomX = atomRight;
  328. indexInText += atom->numChars;
  329. if (atom->isNewLine())
  330. beginNewLine();
  331. }
  332. atom = &(currentSection->atoms.getReference (atomIndex));
  333. atomRight = atomX + atom->width;
  334. ++atomIndex;
  335. if (shouldWrap (atomRight) || forceNewLine)
  336. {
  337. if (atom->isWhitespace())
  338. {
  339. // leave whitespace at the end of a line, but truncate it to avoid scrolling
  340. atomRight = jmin (atomRight, wordWrapWidth);
  341. }
  342. else
  343. {
  344. if (shouldWrap (atom->width)) // atom too big to fit on a line, so break it up..
  345. {
  346. tempAtom = *atom;
  347. tempAtom.width = 0;
  348. tempAtom.numChars = 0;
  349. atom = &tempAtom;
  350. if (atomX > justificationOffset)
  351. beginNewLine();
  352. return next();
  353. }
  354. beginNewLine();
  355. atomX = justificationOffset;
  356. atomRight = atomX + atom->width;
  357. return true;
  358. }
  359. }
  360. return true;
  361. }
  362. void beginNewLine()
  363. {
  364. lineY += lineHeight * lineSpacing;
  365. float lineWidth = 0;
  366. auto tempSectionIndex = sectionIndex;
  367. auto tempAtomIndex = atomIndex;
  368. auto* section = sections.getUnchecked (tempSectionIndex);
  369. lineHeight = section->font.getHeight();
  370. maxDescent = section->font.getDescent();
  371. float nextLineWidth = (atom != nullptr) ? atom->width : 0.0f;
  372. while (! shouldWrap (nextLineWidth))
  373. {
  374. lineWidth = nextLineWidth;
  375. if (tempSectionIndex >= sections.size())
  376. break;
  377. bool checkSize = false;
  378. if (tempAtomIndex >= section->atoms.size())
  379. {
  380. if (++tempSectionIndex >= sections.size())
  381. break;
  382. tempAtomIndex = 0;
  383. section = sections.getUnchecked (tempSectionIndex);
  384. checkSize = true;
  385. }
  386. if (! isPositiveAndBelow (tempAtomIndex, section->atoms.size()))
  387. break;
  388. auto& nextAtom = section->atoms.getReference (tempAtomIndex);
  389. nextLineWidth += nextAtom.width;
  390. if (shouldWrap (nextLineWidth) || nextAtom.isNewLine())
  391. break;
  392. if (checkSize)
  393. {
  394. lineHeight = jmax (lineHeight, section->font.getHeight());
  395. maxDescent = jmax (maxDescent, section->font.getDescent());
  396. }
  397. ++tempAtomIndex;
  398. }
  399. justificationOffset = getJustificationOffset (lineWidth);
  400. atomX = justificationOffset;
  401. }
  402. float getJustificationOffset (float lineWidth) const
  403. {
  404. if (justification.getOnlyHorizontalFlags() == Justification::horizontallyCentred)
  405. return jmax (0.0f, (justificationWidth - lineWidth) * 0.5f);
  406. if (justification.getOnlyHorizontalFlags() == Justification::right)
  407. return jmax (0.0f, justificationWidth - lineWidth);
  408. return 0;
  409. }
  410. //==============================================================================
  411. void draw (Graphics& g, const UniformTextSection*& lastSection) const
  412. {
  413. if (passwordCharacter != 0 || ! atom->isWhitespace())
  414. {
  415. if (lastSection != currentSection)
  416. {
  417. lastSection = currentSection;
  418. g.setColour (currentSection->colour);
  419. g.setFont (currentSection->font);
  420. }
  421. jassert (atom->getTrimmedText (passwordCharacter).isNotEmpty());
  422. GlyphArrangement ga;
  423. ga.addLineOfText (currentSection->font,
  424. atom->getTrimmedText (passwordCharacter),
  425. atomX, (float) roundToInt (lineY + lineHeight - maxDescent));
  426. ga.draw (g);
  427. }
  428. }
  429. void addSelection (RectangleList<float>& area, Range<int> selected) const
  430. {
  431. auto startX = indexToX (selected.getStart());
  432. auto endX = indexToX (selected.getEnd());
  433. area.add (startX, lineY, endX - startX, lineHeight * lineSpacing);
  434. }
  435. void drawUnderline (Graphics& g, Range<int> underline, Colour colour) const
  436. {
  437. auto startX = roundToInt (indexToX (underline.getStart()));
  438. auto endX = roundToInt (indexToX (underline.getEnd()));
  439. auto baselineY = roundToInt (lineY + currentSection->font.getAscent() + 0.5f);
  440. Graphics::ScopedSaveState state (g);
  441. g.reduceClipRegion ({ startX, baselineY, endX - startX, 1 });
  442. g.fillCheckerBoard ({ (float) endX, baselineY + 1.0f }, 3.0f, 1.0f, colour, Colours::transparentBlack);
  443. }
  444. void drawSelectedText (Graphics& g, Range<int> selected, Colour selectedTextColour) const
  445. {
  446. if (passwordCharacter != 0 || ! atom->isWhitespace())
  447. {
  448. GlyphArrangement ga;
  449. ga.addLineOfText (currentSection->font,
  450. atom->getTrimmedText (passwordCharacter),
  451. atomX, (float) roundToInt (lineY + lineHeight - maxDescent));
  452. if (selected.getEnd() < indexInText + atom->numChars)
  453. {
  454. GlyphArrangement ga2 (ga);
  455. ga2.removeRangeOfGlyphs (0, selected.getEnd() - indexInText);
  456. ga.removeRangeOfGlyphs (selected.getEnd() - indexInText, -1);
  457. g.setColour (currentSection->colour);
  458. ga2.draw (g);
  459. }
  460. if (selected.getStart() > indexInText)
  461. {
  462. GlyphArrangement ga2 (ga);
  463. ga2.removeRangeOfGlyphs (selected.getStart() - indexInText, -1);
  464. ga.removeRangeOfGlyphs (0, selected.getStart() - indexInText);
  465. g.setColour (currentSection->colour);
  466. ga2.draw (g);
  467. }
  468. g.setColour (selectedTextColour);
  469. ga.draw (g);
  470. }
  471. }
  472. //==============================================================================
  473. float indexToX (int indexToFind) const
  474. {
  475. if (indexToFind <= indexInText)
  476. return atomX;
  477. if (indexToFind >= indexInText + atom->numChars)
  478. return atomRight;
  479. GlyphArrangement g;
  480. g.addLineOfText (currentSection->font,
  481. atom->getText (passwordCharacter),
  482. atomX, 0.0f);
  483. if (indexToFind - indexInText >= g.getNumGlyphs())
  484. return atomRight;
  485. return jmin (atomRight, g.getGlyph (indexToFind - indexInText).getLeft());
  486. }
  487. int xToIndex (float xToFind) const
  488. {
  489. if (xToFind <= atomX || atom->isNewLine())
  490. return indexInText;
  491. if (xToFind >= atomRight)
  492. return indexInText + atom->numChars;
  493. GlyphArrangement g;
  494. g.addLineOfText (currentSection->font,
  495. atom->getText (passwordCharacter),
  496. atomX, 0.0f);
  497. auto numGlyphs = g.getNumGlyphs();
  498. int j;
  499. for (j = 0; j < numGlyphs; ++j)
  500. {
  501. auto& pg = g.getGlyph(j);
  502. if ((pg.getLeft() + pg.getRight()) / 2 > xToFind)
  503. break;
  504. }
  505. return indexInText + j;
  506. }
  507. //==============================================================================
  508. bool getCharPosition (int index, Point<float>& anchor, float& lineHeightFound)
  509. {
  510. while (next())
  511. {
  512. if (indexInText + atom->numChars > index)
  513. {
  514. anchor = { indexToX (index), lineY };
  515. lineHeightFound = lineHeight;
  516. return true;
  517. }
  518. }
  519. anchor = { atomX, lineY };
  520. lineHeightFound = lineHeight;
  521. return false;
  522. }
  523. //==============================================================================
  524. int indexInText = 0;
  525. float lineY = 0, justificationOffset = 0, lineHeight = 0, maxDescent = 0;
  526. float atomX = 0, atomRight = 0;
  527. const TextAtom* atom = nullptr;
  528. const UniformTextSection* currentSection = nullptr;
  529. private:
  530. const OwnedArray<UniformTextSection>& sections;
  531. int sectionIndex = 0, atomIndex = 0;
  532. Justification justification;
  533. const float justificationWidth, wordWrapWidth;
  534. const juce_wchar passwordCharacter;
  535. const float lineSpacing;
  536. TextAtom tempAtom;
  537. void moveToEndOfLastAtom()
  538. {
  539. if (atom != nullptr)
  540. {
  541. atomX = atomRight;
  542. if (atom->isNewLine())
  543. {
  544. atomX = 0.0f;
  545. lineY += lineHeight * lineSpacing;
  546. }
  547. }
  548. }
  549. bool shouldWrap (const float x) const noexcept
  550. {
  551. return (x - 0.0001f) >= wordWrapWidth;
  552. }
  553. JUCE_LEAK_DETECTOR (Iterator)
  554. };
  555. //==============================================================================
  556. struct TextEditor::InsertAction : public UndoableAction
  557. {
  558. InsertAction (TextEditor& ed, const String& newText, int insertPos,
  559. const Font& newFont, Colour newColour, int oldCaret, int newCaret)
  560. : owner (ed),
  561. text (newText),
  562. insertIndex (insertPos),
  563. oldCaretPos (oldCaret),
  564. newCaretPos (newCaret),
  565. font (newFont),
  566. colour (newColour)
  567. {
  568. }
  569. bool perform() override
  570. {
  571. owner.insert (text, insertIndex, font, colour, nullptr, newCaretPos);
  572. return true;
  573. }
  574. bool undo() override
  575. {
  576. owner.remove ({ insertIndex, insertIndex + text.length() }, nullptr, oldCaretPos);
  577. return true;
  578. }
  579. int getSizeInUnits() override
  580. {
  581. return text.length() + 16;
  582. }
  583. private:
  584. TextEditor& owner;
  585. const String text;
  586. const int insertIndex, oldCaretPos, newCaretPos;
  587. const Font font;
  588. const Colour colour;
  589. JUCE_DECLARE_NON_COPYABLE (InsertAction)
  590. };
  591. //==============================================================================
  592. struct TextEditor::RemoveAction : public UndoableAction
  593. {
  594. RemoveAction (TextEditor& ed, Range<int> rangeToRemove, int oldCaret, int newCaret,
  595. const Array<UniformTextSection*>& oldSections)
  596. : owner (ed),
  597. range (rangeToRemove),
  598. oldCaretPos (oldCaret),
  599. newCaretPos (newCaret)
  600. {
  601. removedSections.addArray (oldSections);
  602. }
  603. bool perform() override
  604. {
  605. owner.remove (range, nullptr, newCaretPos);
  606. return true;
  607. }
  608. bool undo() override
  609. {
  610. owner.reinsert (range.getStart(), removedSections);
  611. owner.moveCaretTo (oldCaretPos, false);
  612. return true;
  613. }
  614. int getSizeInUnits() override
  615. {
  616. int n = 16;
  617. for (auto* s : removedSections)
  618. n += s->getTotalLength();
  619. return n;
  620. }
  621. private:
  622. TextEditor& owner;
  623. const Range<int> range;
  624. const int oldCaretPos, newCaretPos;
  625. OwnedArray<UniformTextSection> removedSections;
  626. JUCE_DECLARE_NON_COPYABLE (RemoveAction)
  627. };
  628. //==============================================================================
  629. struct TextEditor::TextHolderComponent : public Component,
  630. public Timer,
  631. public Value::Listener
  632. {
  633. TextHolderComponent (TextEditor& ed) : owner (ed)
  634. {
  635. setWantsKeyboardFocus (false);
  636. setInterceptsMouseClicks (false, true);
  637. setMouseCursor (MouseCursor::ParentCursor);
  638. owner.getTextValue().addListener (this);
  639. }
  640. ~TextHolderComponent() override
  641. {
  642. owner.getTextValue().removeListener (this);
  643. }
  644. void paint (Graphics& g) override
  645. {
  646. owner.drawContent (g);
  647. }
  648. void restartTimer()
  649. {
  650. startTimer (350);
  651. }
  652. void timerCallback() override
  653. {
  654. owner.timerCallbackInt();
  655. }
  656. void valueChanged (Value&) override
  657. {
  658. owner.textWasChangedByValue();
  659. }
  660. TextEditor& owner;
  661. JUCE_DECLARE_NON_COPYABLE (TextHolderComponent)
  662. };
  663. //==============================================================================
  664. struct TextEditor::TextEditorViewport : public Viewport
  665. {
  666. TextEditorViewport (TextEditor& ed) : owner (ed) {}
  667. void visibleAreaChanged (const Rectangle<int>&) override
  668. {
  669. if (! rentrant) // it's rare, but possible to get into a feedback loop as the viewport's scrollbars
  670. // appear and disappear, causing the wrap width to change.
  671. {
  672. auto wordWrapWidth = owner.getWordWrapWidth();
  673. if (wordWrapWidth != lastWordWrapWidth)
  674. {
  675. lastWordWrapWidth = wordWrapWidth;
  676. rentrant = true;
  677. owner.updateTextHolderSize();
  678. rentrant = false;
  679. }
  680. }
  681. }
  682. private:
  683. TextEditor& owner;
  684. float lastWordWrapWidth = 0;
  685. bool rentrant = false;
  686. JUCE_DECLARE_NON_COPYABLE (TextEditorViewport)
  687. };
  688. //==============================================================================
  689. namespace TextEditorDefs
  690. {
  691. const int textChangeMessageId = 0x10003001;
  692. const int returnKeyMessageId = 0x10003002;
  693. const int escapeKeyMessageId = 0x10003003;
  694. const int focusLossMessageId = 0x10003004;
  695. const int maxActionsPerTransaction = 100;
  696. static int getCharacterCategory (juce_wchar character) noexcept
  697. {
  698. return CharacterFunctions::isLetterOrDigit (character)
  699. ? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1);
  700. }
  701. }
  702. //==============================================================================
  703. TextEditor::TextEditor (const String& name, juce_wchar passwordChar)
  704. : Component (name),
  705. passwordCharacter (passwordChar)
  706. {
  707. setMouseCursor (MouseCursor::IBeamCursor);
  708. viewport.reset (new TextEditorViewport (*this));
  709. addAndMakeVisible (viewport.get());
  710. viewport->setViewedComponent (textHolder = new TextHolderComponent (*this));
  711. viewport->setWantsKeyboardFocus (false);
  712. viewport->setScrollBarsShown (false, false);
  713. setWantsKeyboardFocus (true);
  714. recreateCaret();
  715. }
  716. TextEditor::~TextEditor()
  717. {
  718. if (wasFocused)
  719. if (auto* peer = getPeer())
  720. peer->dismissPendingTextInput();
  721. textValue.removeListener (textHolder);
  722. textValue.referTo (Value());
  723. viewport.reset();
  724. textHolder = nullptr;
  725. }
  726. //==============================================================================
  727. void TextEditor::newTransaction()
  728. {
  729. lastTransactionTime = Time::getApproximateMillisecondCounter();
  730. undoManager.beginNewTransaction();
  731. }
  732. bool TextEditor::undoOrRedo (const bool shouldUndo)
  733. {
  734. if (! isReadOnly())
  735. {
  736. newTransaction();
  737. if (shouldUndo ? undoManager.undo()
  738. : undoManager.redo())
  739. {
  740. scrollToMakeSureCursorIsVisible();
  741. repaint();
  742. textChanged();
  743. return true;
  744. }
  745. }
  746. return false;
  747. }
  748. bool TextEditor::undo() { return undoOrRedo (true); }
  749. bool TextEditor::redo() { return undoOrRedo (false); }
  750. //==============================================================================
  751. void TextEditor::setMultiLine (const bool shouldBeMultiLine,
  752. const bool shouldWordWrap)
  753. {
  754. if (multiline != shouldBeMultiLine
  755. || wordWrap != (shouldWordWrap && shouldBeMultiLine))
  756. {
  757. multiline = shouldBeMultiLine;
  758. wordWrap = shouldWordWrap && shouldBeMultiLine;
  759. viewport->setScrollBarsShown (scrollbarVisible && multiline,
  760. scrollbarVisible && multiline);
  761. viewport->setViewPosition (0, 0);
  762. resized();
  763. scrollToMakeSureCursorIsVisible();
  764. }
  765. }
  766. bool TextEditor::isMultiLine() const
  767. {
  768. return multiline;
  769. }
  770. void TextEditor::setScrollbarsShown (bool shown)
  771. {
  772. if (scrollbarVisible != shown)
  773. {
  774. scrollbarVisible = shown;
  775. shown = shown && isMultiLine();
  776. viewport->setScrollBarsShown (shown, shown);
  777. }
  778. }
  779. void TextEditor::setReadOnly (bool shouldBeReadOnly)
  780. {
  781. if (readOnly != shouldBeReadOnly)
  782. {
  783. readOnly = shouldBeReadOnly;
  784. enablementChanged();
  785. }
  786. }
  787. bool TextEditor::isReadOnly() const noexcept
  788. {
  789. return readOnly || ! isEnabled();
  790. }
  791. bool TextEditor::isTextInputActive() const
  792. {
  793. return ! isReadOnly();
  794. }
  795. void TextEditor::setReturnKeyStartsNewLine (bool shouldStartNewLine)
  796. {
  797. returnKeyStartsNewLine = shouldStartNewLine;
  798. }
  799. void TextEditor::setTabKeyUsedAsCharacter (bool shouldTabKeyBeUsed)
  800. {
  801. tabKeyUsed = shouldTabKeyBeUsed;
  802. }
  803. void TextEditor::setPopupMenuEnabled (bool b)
  804. {
  805. popupMenuEnabled = b;
  806. }
  807. void TextEditor::setSelectAllWhenFocused (bool b)
  808. {
  809. selectAllTextWhenFocused = b;
  810. }
  811. void TextEditor::setJustification (Justification j)
  812. {
  813. if (justification != j)
  814. {
  815. justification = j;
  816. resized();
  817. }
  818. }
  819. //==============================================================================
  820. void TextEditor::setFont (const Font& newFont)
  821. {
  822. currentFont = newFont;
  823. scrollToMakeSureCursorIsVisible();
  824. }
  825. void TextEditor::applyFontToAllText (const Font& newFont, bool changeCurrentFont)
  826. {
  827. if (changeCurrentFont)
  828. currentFont = newFont;
  829. auto overallColour = findColour (textColourId);
  830. for (auto* uts : sections)
  831. {
  832. uts->setFont (newFont, passwordCharacter);
  833. uts->colour = overallColour;
  834. }
  835. coalesceSimilarSections();
  836. updateTextHolderSize();
  837. scrollToMakeSureCursorIsVisible();
  838. repaint();
  839. }
  840. void TextEditor::applyColourToAllText (const Colour& newColour, bool changeCurrentTextColour)
  841. {
  842. for (auto* uts : sections)
  843. uts->colour = newColour;
  844. if (changeCurrentTextColour)
  845. setColour (TextEditor::textColourId, newColour);
  846. else
  847. repaint();
  848. }
  849. void TextEditor::lookAndFeelChanged()
  850. {
  851. caret.reset();
  852. recreateCaret();
  853. repaint();
  854. }
  855. void TextEditor::parentHierarchyChanged()
  856. {
  857. lookAndFeelChanged();
  858. }
  859. void TextEditor::enablementChanged()
  860. {
  861. recreateCaret();
  862. repaint();
  863. }
  864. void TextEditor::setCaretVisible (bool shouldCaretBeVisible)
  865. {
  866. if (caretVisible != shouldCaretBeVisible)
  867. {
  868. caretVisible = shouldCaretBeVisible;
  869. recreateCaret();
  870. }
  871. }
  872. void TextEditor::recreateCaret()
  873. {
  874. if (isCaretVisible())
  875. {
  876. if (caret == nullptr)
  877. {
  878. caret.reset (getLookAndFeel().createCaretComponent (this));
  879. textHolder->addChildComponent (caret.get());
  880. updateCaretPosition();
  881. }
  882. }
  883. else
  884. {
  885. caret.reset();
  886. }
  887. }
  888. void TextEditor::updateCaretPosition()
  889. {
  890. if (caret != nullptr)
  891. caret->setCaretPosition (getCaretRectangle().translated (leftIndent, topIndent));
  892. }
  893. TextEditor::LengthAndCharacterRestriction::LengthAndCharacterRestriction (int maxLen, const String& chars)
  894. : allowedCharacters (chars), maxLength (maxLen)
  895. {
  896. }
  897. String TextEditor::LengthAndCharacterRestriction::filterNewText (TextEditor& ed, const String& newInput)
  898. {
  899. String t (newInput);
  900. if (allowedCharacters.isNotEmpty())
  901. t = t.retainCharacters (allowedCharacters);
  902. if (maxLength > 0)
  903. t = t.substring (0, maxLength - (ed.getTotalNumChars() - ed.getHighlightedRegion().getLength()));
  904. return t;
  905. }
  906. void TextEditor::setInputFilter (InputFilter* newFilter, bool takeOwnership)
  907. {
  908. inputFilter.set (newFilter, takeOwnership);
  909. }
  910. void TextEditor::setInputRestrictions (int maxLen, const String& chars)
  911. {
  912. setInputFilter (new LengthAndCharacterRestriction (maxLen, chars), true);
  913. }
  914. void TextEditor::setTextToShowWhenEmpty (const String& text, Colour colourToUse)
  915. {
  916. textToShowWhenEmpty = text;
  917. colourForTextWhenEmpty = colourToUse;
  918. }
  919. void TextEditor::setPasswordCharacter (juce_wchar newPasswordCharacter)
  920. {
  921. if (passwordCharacter != newPasswordCharacter)
  922. {
  923. passwordCharacter = newPasswordCharacter;
  924. applyFontToAllText (currentFont);
  925. }
  926. }
  927. void TextEditor::setScrollBarThickness (int newThicknessPixels)
  928. {
  929. viewport->setScrollBarThickness (newThicknessPixels);
  930. }
  931. //==============================================================================
  932. void TextEditor::clear()
  933. {
  934. clearInternal (nullptr);
  935. updateTextHolderSize();
  936. undoManager.clearUndoHistory();
  937. }
  938. void TextEditor::setText (const String& newText, bool sendTextChangeMessage)
  939. {
  940. auto newLength = newText.length();
  941. if (newLength != getTotalNumChars() || getText() != newText)
  942. {
  943. if (! sendTextChangeMessage)
  944. textValue.removeListener (textHolder);
  945. textValue = newText;
  946. auto oldCursorPos = caretPosition;
  947. bool cursorWasAtEnd = oldCursorPos >= getTotalNumChars();
  948. clearInternal (nullptr);
  949. insert (newText, 0, currentFont, findColour (textColourId), nullptr, caretPosition);
  950. // if you're adding text with line-feeds to a single-line text editor, it
  951. // ain't gonna look right!
  952. jassert (multiline || ! newText.containsAnyOf ("\r\n"));
  953. if (cursorWasAtEnd && ! isMultiLine())
  954. oldCursorPos = getTotalNumChars();
  955. moveCaretTo (oldCursorPos, false);
  956. if (sendTextChangeMessage)
  957. textChanged();
  958. else
  959. textValue.addListener (textHolder);
  960. updateTextHolderSize();
  961. scrollToMakeSureCursorIsVisible();
  962. undoManager.clearUndoHistory();
  963. repaint();
  964. }
  965. }
  966. //==============================================================================
  967. void TextEditor::updateValueFromText()
  968. {
  969. if (valueTextNeedsUpdating)
  970. {
  971. valueTextNeedsUpdating = false;
  972. textValue = getText();
  973. }
  974. }
  975. Value& TextEditor::getTextValue()
  976. {
  977. updateValueFromText();
  978. return textValue;
  979. }
  980. void TextEditor::textWasChangedByValue()
  981. {
  982. if (textValue.getValueSource().getReferenceCount() > 1)
  983. setText (textValue.getValue());
  984. }
  985. //==============================================================================
  986. void TextEditor::textChanged()
  987. {
  988. updateTextHolderSize();
  989. if (listeners.size() != 0 || onTextChange != nullptr)
  990. postCommandMessage (TextEditorDefs::textChangeMessageId);
  991. if (textValue.getValueSource().getReferenceCount() > 1)
  992. {
  993. valueTextNeedsUpdating = false;
  994. textValue = getText();
  995. }
  996. }
  997. void TextEditor::returnPressed() { postCommandMessage (TextEditorDefs::returnKeyMessageId); }
  998. void TextEditor::escapePressed() { postCommandMessage (TextEditorDefs::escapeKeyMessageId); }
  999. void TextEditor::addListener (Listener* l) { listeners.add (l); }
  1000. void TextEditor::removeListener (Listener* l) { listeners.remove (l); }
  1001. //==============================================================================
  1002. void TextEditor::timerCallbackInt()
  1003. {
  1004. checkFocus();
  1005. auto now = Time::getApproximateMillisecondCounter();
  1006. if (now > lastTransactionTime + 200)
  1007. newTransaction();
  1008. }
  1009. void TextEditor::checkFocus()
  1010. {
  1011. if (! wasFocused && hasKeyboardFocus (false) && ! isCurrentlyBlockedByAnotherModalComponent())
  1012. {
  1013. wasFocused = true;
  1014. if (auto* peer = getPeer())
  1015. if (! isReadOnly())
  1016. peer->textInputRequired (peer->globalToLocal (getScreenPosition()), *this);
  1017. }
  1018. }
  1019. void TextEditor::repaintText (Range<int> range)
  1020. {
  1021. if (! range.isEmpty())
  1022. {
  1023. auto lh = currentFont.getHeight();
  1024. auto wordWrapWidth = getWordWrapWidth();
  1025. if (wordWrapWidth > 0)
  1026. {
  1027. Point<float> anchor;
  1028. Iterator i (*this);
  1029. i.getCharPosition (range.getStart(), anchor, lh);
  1030. auto y1 = (int) anchor.y;
  1031. int y2;
  1032. if (range.getEnd() >= getTotalNumChars())
  1033. {
  1034. y2 = textHolder->getHeight();
  1035. }
  1036. else
  1037. {
  1038. i.getCharPosition (range.getEnd(), anchor, lh);
  1039. y2 = (int) (anchor.y + lh * 2.0f);
  1040. }
  1041. textHolder->repaint (0, y1, textHolder->getWidth(), y2 - y1);
  1042. }
  1043. }
  1044. }
  1045. //==============================================================================
  1046. void TextEditor::moveCaret (int newCaretPos)
  1047. {
  1048. if (newCaretPos < 0)
  1049. newCaretPos = 0;
  1050. else
  1051. newCaretPos = jmin (newCaretPos, getTotalNumChars());
  1052. if (newCaretPos != getCaretPosition())
  1053. {
  1054. caretPosition = newCaretPos;
  1055. textHolder->restartTimer();
  1056. scrollToMakeSureCursorIsVisible();
  1057. updateCaretPosition();
  1058. }
  1059. }
  1060. int TextEditor::getCaretPosition() const
  1061. {
  1062. return caretPosition;
  1063. }
  1064. void TextEditor::setCaretPosition (const int newIndex)
  1065. {
  1066. moveCaretTo (newIndex, false);
  1067. }
  1068. void TextEditor::moveCaretToEnd()
  1069. {
  1070. setCaretPosition (std::numeric_limits<int>::max());
  1071. }
  1072. void TextEditor::scrollEditorToPositionCaret (const int desiredCaretX,
  1073. const int desiredCaretY)
  1074. {
  1075. updateCaretPosition();
  1076. auto caretPos = getCaretRectangle();
  1077. auto vx = caretPos.getX() - desiredCaretX;
  1078. auto vy = caretPos.getY() - desiredCaretY;
  1079. if (desiredCaretX < jmax (1, proportionOfWidth (0.05f)))
  1080. vx += desiredCaretX - proportionOfWidth (0.2f);
  1081. else if (desiredCaretX > jmax (0, viewport->getMaximumVisibleWidth() - (wordWrap ? 2 : 10)))
  1082. vx += desiredCaretX + (isMultiLine() ? proportionOfWidth (0.2f) : 10) - viewport->getMaximumVisibleWidth();
  1083. vx = jlimit (0, jmax (0, textHolder->getWidth() + 8 - viewport->getMaximumVisibleWidth()), vx);
  1084. if (! isMultiLine())
  1085. {
  1086. vy = viewport->getViewPositionY();
  1087. }
  1088. else
  1089. {
  1090. vy = jlimit (0, jmax (0, textHolder->getHeight() - viewport->getMaximumVisibleHeight()), vy);
  1091. if (desiredCaretY < 0)
  1092. vy = jmax (0, desiredCaretY + vy);
  1093. else if (desiredCaretY > jmax (0, viewport->getMaximumVisibleHeight() - topIndent - caretPos.getHeight()))
  1094. vy += desiredCaretY + 2 + caretPos.getHeight() + topIndent - viewport->getMaximumVisibleHeight();
  1095. }
  1096. viewport->setViewPosition (vx, vy);
  1097. }
  1098. Rectangle<int> TextEditor::getCaretRectangle()
  1099. {
  1100. return getCaretRectangleFloat().getSmallestIntegerContainer();
  1101. }
  1102. Rectangle<float> TextEditor::getCaretRectangleFloat() const
  1103. {
  1104. Point<float> anchor;
  1105. auto cursorHeight = currentFont.getHeight(); // (in case the text is empty and the call below doesn't set this value)
  1106. getCharPosition (caretPosition, anchor, cursorHeight);
  1107. return { anchor.x, anchor.y, 2.0f, cursorHeight };
  1108. }
  1109. //==============================================================================
  1110. enum { rightEdgeSpace = 2 };
  1111. float TextEditor::getWordWrapWidth() const
  1112. {
  1113. return wordWrap ? getJustificationWidth()
  1114. : std::numeric_limits<float>::max();
  1115. }
  1116. float TextEditor::getJustificationWidth() const
  1117. {
  1118. return (float) (viewport->getMaximumVisibleWidth() - (leftIndent + rightEdgeSpace + 1));
  1119. }
  1120. void TextEditor::updateTextHolderSize()
  1121. {
  1122. if (getWordWrapWidth() > 0)
  1123. {
  1124. float maxWidth = getJustificationWidth();
  1125. Iterator i (*this);
  1126. while (i.next())
  1127. maxWidth = jmax (maxWidth, i.atomRight);
  1128. auto w = leftIndent + roundToInt (maxWidth);
  1129. auto h = topIndent + roundToInt (jmax (i.lineY + i.lineHeight, currentFont.getHeight()));
  1130. textHolder->setSize (w + rightEdgeSpace, h + 1); // (allows a bit of space for the cursor to be at the right-hand-edge)
  1131. }
  1132. }
  1133. int TextEditor::getTextWidth() const { return textHolder->getWidth(); }
  1134. int TextEditor::getTextHeight() const { return textHolder->getHeight(); }
  1135. void TextEditor::setIndents (int newLeftIndent, int newTopIndent)
  1136. {
  1137. leftIndent = newLeftIndent;
  1138. topIndent = newTopIndent;
  1139. }
  1140. void TextEditor::setBorder (const BorderSize<int>& border)
  1141. {
  1142. borderSize = border;
  1143. resized();
  1144. }
  1145. BorderSize<int> TextEditor::getBorder() const
  1146. {
  1147. return borderSize;
  1148. }
  1149. void TextEditor::setScrollToShowCursor (const bool shouldScrollToShowCursor)
  1150. {
  1151. keepCaretOnScreen = shouldScrollToShowCursor;
  1152. }
  1153. void TextEditor::scrollToMakeSureCursorIsVisible()
  1154. {
  1155. updateCaretPosition();
  1156. if (keepCaretOnScreen)
  1157. {
  1158. auto viewPos = viewport->getViewPosition();
  1159. auto caretRect = getCaretRectangle();
  1160. auto relativeCursor = caretRect.getPosition() - viewPos;
  1161. if (relativeCursor.x < jmax (1, proportionOfWidth (0.05f)))
  1162. {
  1163. viewPos.x += relativeCursor.x - proportionOfWidth (0.2f);
  1164. }
  1165. else if (relativeCursor.x > jmax (0, viewport->getMaximumVisibleWidth() - (wordWrap ? 2 : 10)))
  1166. {
  1167. viewPos.x += relativeCursor.x + (isMultiLine() ? proportionOfWidth (0.2f) : 10) - viewport->getMaximumVisibleWidth();
  1168. }
  1169. viewPos.x = jlimit (0, jmax (0, textHolder->getWidth() + 8 - viewport->getMaximumVisibleWidth()), viewPos.x);
  1170. if (! isMultiLine())
  1171. {
  1172. viewPos.y = (getHeight() - textHolder->getHeight() - topIndent) / -2;
  1173. }
  1174. else if (relativeCursor.y < 0)
  1175. {
  1176. viewPos.y = jmax (0, relativeCursor.y + viewPos.y);
  1177. }
  1178. else if (relativeCursor.y > jmax (0, viewport->getMaximumVisibleHeight() - topIndent - caretRect.getHeight()))
  1179. {
  1180. viewPos.y += relativeCursor.y + 2 + caretRect.getHeight() + topIndent - viewport->getMaximumVisibleHeight();
  1181. }
  1182. viewport->setViewPosition (viewPos);
  1183. }
  1184. }
  1185. void TextEditor::moveCaretTo (const int newPosition, const bool isSelecting)
  1186. {
  1187. if (isSelecting)
  1188. {
  1189. moveCaret (newPosition);
  1190. auto oldSelection = selection;
  1191. if (dragType == notDragging)
  1192. {
  1193. if (std::abs (getCaretPosition() - selection.getStart()) < std::abs (getCaretPosition() - selection.getEnd()))
  1194. dragType = draggingSelectionStart;
  1195. else
  1196. dragType = draggingSelectionEnd;
  1197. }
  1198. if (dragType == draggingSelectionStart)
  1199. {
  1200. if (getCaretPosition() >= selection.getEnd())
  1201. dragType = draggingSelectionEnd;
  1202. selection = Range<int>::between (getCaretPosition(), selection.getEnd());
  1203. }
  1204. else
  1205. {
  1206. if (getCaretPosition() < selection.getStart())
  1207. dragType = draggingSelectionStart;
  1208. selection = Range<int>::between (getCaretPosition(), selection.getStart());
  1209. }
  1210. repaintText (selection.getUnionWith (oldSelection));
  1211. }
  1212. else
  1213. {
  1214. dragType = notDragging;
  1215. repaintText (selection);
  1216. moveCaret (newPosition);
  1217. selection = Range<int>::emptyRange (getCaretPosition());
  1218. }
  1219. }
  1220. int TextEditor::getTextIndexAt (const int x, const int y)
  1221. {
  1222. return indexAtPosition ((float) (x + viewport->getViewPositionX() - leftIndent - borderSize.getLeft()),
  1223. (float) (y + viewport->getViewPositionY() - topIndent - borderSize.getTop()));
  1224. }
  1225. void TextEditor::insertTextAtCaret (const String& t)
  1226. {
  1227. String newText (inputFilter != nullptr ? inputFilter->filterNewText (*this, t) : t);
  1228. if (isMultiLine())
  1229. newText = newText.replace ("\r\n", "\n");
  1230. else
  1231. newText = newText.replaceCharacters ("\r\n", " ");
  1232. const int insertIndex = selection.getStart();
  1233. const int newCaretPos = insertIndex + newText.length();
  1234. remove (selection, getUndoManager(),
  1235. newText.isNotEmpty() ? newCaretPos - 1 : newCaretPos);
  1236. insert (newText, insertIndex, currentFont, findColour (textColourId),
  1237. getUndoManager(), newCaretPos);
  1238. textChanged();
  1239. }
  1240. void TextEditor::setHighlightedRegion (const Range<int>& newSelection)
  1241. {
  1242. moveCaretTo (newSelection.getStart(), false);
  1243. moveCaretTo (newSelection.getEnd(), true);
  1244. }
  1245. //==============================================================================
  1246. void TextEditor::copy()
  1247. {
  1248. if (passwordCharacter == 0)
  1249. {
  1250. auto selectedText = getHighlightedText();
  1251. if (selectedText.isNotEmpty())
  1252. SystemClipboard::copyTextToClipboard (selectedText);
  1253. }
  1254. }
  1255. void TextEditor::paste()
  1256. {
  1257. if (! isReadOnly())
  1258. {
  1259. auto clip = SystemClipboard::getTextFromClipboard();
  1260. if (clip.isNotEmpty())
  1261. insertTextAtCaret (clip);
  1262. }
  1263. }
  1264. void TextEditor::cut()
  1265. {
  1266. if (! isReadOnly())
  1267. {
  1268. moveCaret (selection.getEnd());
  1269. insertTextAtCaret (String());
  1270. }
  1271. }
  1272. //==============================================================================
  1273. void TextEditor::drawContent (Graphics& g)
  1274. {
  1275. if (getWordWrapWidth() > 0)
  1276. {
  1277. g.setOrigin (leftIndent, topIndent);
  1278. auto clip = g.getClipBounds();
  1279. Colour selectedTextColour;
  1280. Iterator i (*this);
  1281. if (! selection.isEmpty())
  1282. {
  1283. Iterator i2 (i);
  1284. RectangleList<float> selectionArea;
  1285. while (i2.next() && i2.lineY < clip.getBottom())
  1286. {
  1287. if (i2.lineY + i2.lineHeight >= clip.getY()
  1288. && selection.intersects ({ i2.indexInText, i2.indexInText + i2.atom->numChars }))
  1289. {
  1290. i2.addSelection (selectionArea, selection);
  1291. }
  1292. }
  1293. g.setColour (findColour (highlightColourId).withMultipliedAlpha (hasKeyboardFocus (true) ? 1.0f : 0.5f));
  1294. g.fillRectList (selectionArea);
  1295. selectedTextColour = findColour (highlightedTextColourId);
  1296. }
  1297. const UniformTextSection* lastSection = nullptr;
  1298. while (i.next() && i.lineY < clip.getBottom())
  1299. {
  1300. if (i.lineY + i.lineHeight >= clip.getY())
  1301. {
  1302. if (selection.intersects ({ i.indexInText, i.indexInText + i.atom->numChars }))
  1303. {
  1304. i.drawSelectedText (g, selection, selectedTextColour);
  1305. lastSection = nullptr;
  1306. }
  1307. else
  1308. {
  1309. i.draw (g, lastSection);
  1310. }
  1311. }
  1312. }
  1313. for (auto& underlinedSection : underlinedSections)
  1314. {
  1315. Iterator i2 (*this);
  1316. while (i2.next() && i2.lineY < clip.getBottom())
  1317. {
  1318. if (i2.lineY + i2.lineHeight >= clip.getY()
  1319. && underlinedSection.intersects ({ i2.indexInText, i2.indexInText + i2.atom->numChars }))
  1320. {
  1321. i2.drawUnderline (g, underlinedSection, findColour (textColourId));
  1322. }
  1323. }
  1324. }
  1325. }
  1326. }
  1327. void TextEditor::paint (Graphics& g)
  1328. {
  1329. getLookAndFeel().fillTextEditorBackground (g, getWidth(), getHeight(), *this);
  1330. }
  1331. void TextEditor::paintOverChildren (Graphics& g)
  1332. {
  1333. if (textToShowWhenEmpty.isNotEmpty()
  1334. && (! hasKeyboardFocus (false))
  1335. && getTotalNumChars() == 0)
  1336. {
  1337. g.setColour (colourForTextWhenEmpty);
  1338. g.setFont (getFont());
  1339. if (isMultiLine())
  1340. g.drawText (textToShowWhenEmpty, getLocalBounds(),
  1341. Justification::centred, true);
  1342. else
  1343. g.drawText (textToShowWhenEmpty,
  1344. leftIndent, 0, viewport->getWidth() - leftIndent, getHeight(),
  1345. Justification::centredLeft, true);
  1346. }
  1347. getLookAndFeel().drawTextEditorOutline (g, getWidth(), getHeight(), *this);
  1348. }
  1349. //==============================================================================
  1350. void TextEditor::addPopupMenuItems (PopupMenu& m, const MouseEvent*)
  1351. {
  1352. const bool writable = ! isReadOnly();
  1353. if (passwordCharacter == 0)
  1354. {
  1355. m.addItem (StandardApplicationCommandIDs::cut, TRANS("Cut"), writable);
  1356. m.addItem (StandardApplicationCommandIDs::copy, TRANS("Copy"), ! selection.isEmpty());
  1357. }
  1358. m.addItem (StandardApplicationCommandIDs::paste, TRANS("Paste"), writable);
  1359. m.addItem (StandardApplicationCommandIDs::del, TRANS("Delete"), writable);
  1360. m.addSeparator();
  1361. m.addItem (StandardApplicationCommandIDs::selectAll, TRANS("Select All"));
  1362. m.addSeparator();
  1363. if (getUndoManager() != nullptr)
  1364. {
  1365. m.addItem (StandardApplicationCommandIDs::undo, TRANS("Undo"), undoManager.canUndo());
  1366. m.addItem (StandardApplicationCommandIDs::redo, TRANS("Redo"), undoManager.canRedo());
  1367. }
  1368. }
  1369. void TextEditor::performPopupMenuAction (const int menuItemID)
  1370. {
  1371. switch (menuItemID)
  1372. {
  1373. case StandardApplicationCommandIDs::cut: cutToClipboard(); break;
  1374. case StandardApplicationCommandIDs::copy: copyToClipboard(); break;
  1375. case StandardApplicationCommandIDs::paste: pasteFromClipboard(); break;
  1376. case StandardApplicationCommandIDs::del: cut(); break;
  1377. case StandardApplicationCommandIDs::selectAll: selectAll(); break;
  1378. case StandardApplicationCommandIDs::undo: undo(); break;
  1379. case StandardApplicationCommandIDs::redo: redo(); break;
  1380. default: break;
  1381. }
  1382. }
  1383. //==============================================================================
  1384. void TextEditor::mouseDown (const MouseEvent& e)
  1385. {
  1386. beginDragAutoRepeat (100);
  1387. newTransaction();
  1388. if (wasFocused || ! selectAllTextWhenFocused)
  1389. {
  1390. if (! (popupMenuEnabled && e.mods.isPopupMenu()))
  1391. {
  1392. moveCaretTo (getTextIndexAt (e.x, e.y),
  1393. e.mods.isShiftDown());
  1394. }
  1395. else
  1396. {
  1397. PopupMenu m;
  1398. m.setLookAndFeel (&getLookAndFeel());
  1399. addPopupMenuItems (m, &e);
  1400. menuActive = true;
  1401. SafePointer<TextEditor> safeThis (this);
  1402. m.showMenuAsync (PopupMenu::Options(),
  1403. [safeThis] (int menuResult)
  1404. {
  1405. if (auto* editor = safeThis.getComponent())
  1406. {
  1407. editor->menuActive = false;
  1408. if (menuResult != 0)
  1409. editor->performPopupMenuAction (menuResult);
  1410. }
  1411. });
  1412. }
  1413. }
  1414. }
  1415. void TextEditor::mouseDrag (const MouseEvent& e)
  1416. {
  1417. if (wasFocused || ! selectAllTextWhenFocused)
  1418. if (! (popupMenuEnabled && e.mods.isPopupMenu()))
  1419. moveCaretTo (getTextIndexAt (e.x, e.y), true);
  1420. }
  1421. void TextEditor::mouseUp (const MouseEvent& e)
  1422. {
  1423. newTransaction();
  1424. textHolder->restartTimer();
  1425. if (wasFocused || ! selectAllTextWhenFocused)
  1426. if (e.mouseWasClicked() && ! (popupMenuEnabled && e.mods.isPopupMenu()))
  1427. moveCaret (getTextIndexAt (e.x, e.y));
  1428. wasFocused = true;
  1429. }
  1430. void TextEditor::mouseDoubleClick (const MouseEvent& e)
  1431. {
  1432. int tokenEnd = getTextIndexAt (e.x, e.y);
  1433. int tokenStart = 0;
  1434. if (e.getNumberOfClicks() > 3)
  1435. {
  1436. tokenEnd = getTotalNumChars();
  1437. }
  1438. else
  1439. {
  1440. auto t = getText();
  1441. auto totalLength = getTotalNumChars();
  1442. while (tokenEnd < totalLength)
  1443. {
  1444. auto c = t[tokenEnd];
  1445. // (note the slight bodge here - it's because iswalnum only checks for alphabetic chars in the current locale)
  1446. if (CharacterFunctions::isLetterOrDigit (c) || c > 128)
  1447. ++tokenEnd;
  1448. else
  1449. break;
  1450. }
  1451. tokenStart = tokenEnd;
  1452. while (tokenStart > 0)
  1453. {
  1454. auto c = t[tokenStart - 1];
  1455. // (note the slight bodge here - it's because iswalnum only checks for alphabetic chars in the current locale)
  1456. if (CharacterFunctions::isLetterOrDigit (c) || c > 128)
  1457. --tokenStart;
  1458. else
  1459. break;
  1460. }
  1461. if (e.getNumberOfClicks() > 2)
  1462. {
  1463. while (tokenEnd < totalLength)
  1464. {
  1465. auto c = t[tokenEnd];
  1466. if (c != '\r' && c != '\n')
  1467. ++tokenEnd;
  1468. else
  1469. break;
  1470. }
  1471. while (tokenStart > 0)
  1472. {
  1473. auto c = t[tokenStart - 1];
  1474. if (c != '\r' && c != '\n')
  1475. --tokenStart;
  1476. else
  1477. break;
  1478. }
  1479. }
  1480. }
  1481. moveCaretTo (tokenEnd, false);
  1482. moveCaretTo (tokenStart, true);
  1483. }
  1484. void TextEditor::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
  1485. {
  1486. if (! viewport->useMouseWheelMoveIfNeeded (e, wheel))
  1487. Component::mouseWheelMove (e, wheel);
  1488. }
  1489. //==============================================================================
  1490. bool TextEditor::moveCaretWithTransaction (const int newPos, const bool selecting)
  1491. {
  1492. newTransaction();
  1493. moveCaretTo (newPos, selecting);
  1494. return true;
  1495. }
  1496. bool TextEditor::moveCaretLeft (bool moveInWholeWordSteps, bool selecting)
  1497. {
  1498. auto pos = getCaretPosition();
  1499. if (moveInWholeWordSteps)
  1500. pos = findWordBreakBefore (pos);
  1501. else
  1502. --pos;
  1503. return moveCaretWithTransaction (pos, selecting);
  1504. }
  1505. bool TextEditor::moveCaretRight (bool moveInWholeWordSteps, bool selecting)
  1506. {
  1507. auto pos = getCaretPosition();
  1508. if (moveInWholeWordSteps)
  1509. pos = findWordBreakAfter (pos);
  1510. else
  1511. ++pos;
  1512. return moveCaretWithTransaction (pos, selecting);
  1513. }
  1514. bool TextEditor::moveCaretUp (bool selecting)
  1515. {
  1516. if (! isMultiLine())
  1517. return moveCaretToStartOfLine (selecting);
  1518. auto caretPos = getCaretRectangleFloat();
  1519. return moveCaretWithTransaction (indexAtPosition (caretPos.getX(), caretPos.getY() - 1.0f), selecting);
  1520. }
  1521. bool TextEditor::moveCaretDown (bool selecting)
  1522. {
  1523. if (! isMultiLine())
  1524. return moveCaretToEndOfLine (selecting);
  1525. auto caretPos = getCaretRectangleFloat();
  1526. return moveCaretWithTransaction (indexAtPosition (caretPos.getX(), caretPos.getBottom() + 1.0f), selecting);
  1527. }
  1528. bool TextEditor::pageUp (bool selecting)
  1529. {
  1530. if (! isMultiLine())
  1531. return moveCaretToStartOfLine (selecting);
  1532. auto caretPos = getCaretRectangleFloat();
  1533. return moveCaretWithTransaction (indexAtPosition (caretPos.getX(), caretPos.getY() - viewport->getViewHeight()), selecting);
  1534. }
  1535. bool TextEditor::pageDown (bool selecting)
  1536. {
  1537. if (! isMultiLine())
  1538. return moveCaretToEndOfLine (selecting);
  1539. auto caretPos = getCaretRectangleFloat();
  1540. return moveCaretWithTransaction (indexAtPosition (caretPos.getX(), caretPos.getBottom() + viewport->getViewHeight()), selecting);
  1541. }
  1542. void TextEditor::scrollByLines (int deltaLines)
  1543. {
  1544. viewport->getVerticalScrollBar().moveScrollbarInSteps (deltaLines);
  1545. }
  1546. bool TextEditor::scrollDown()
  1547. {
  1548. scrollByLines (-1);
  1549. return true;
  1550. }
  1551. bool TextEditor::scrollUp()
  1552. {
  1553. scrollByLines (1);
  1554. return true;
  1555. }
  1556. bool TextEditor::moveCaretToTop (bool selecting)
  1557. {
  1558. return moveCaretWithTransaction (0, selecting);
  1559. }
  1560. bool TextEditor::moveCaretToStartOfLine (bool selecting)
  1561. {
  1562. auto caretPos = getCaretRectangleFloat();
  1563. return moveCaretWithTransaction (indexAtPosition (0.0f, caretPos.getY()), selecting);
  1564. }
  1565. bool TextEditor::moveCaretToEnd (bool selecting)
  1566. {
  1567. return moveCaretWithTransaction (getTotalNumChars(), selecting);
  1568. }
  1569. bool TextEditor::moveCaretToEndOfLine (bool selecting)
  1570. {
  1571. auto caretPos = getCaretRectangleFloat();
  1572. return moveCaretWithTransaction (indexAtPosition ((float) textHolder->getWidth(), caretPos.getY()), selecting);
  1573. }
  1574. bool TextEditor::deleteBackwards (bool moveInWholeWordSteps)
  1575. {
  1576. if (moveInWholeWordSteps)
  1577. moveCaretTo (findWordBreakBefore (getCaretPosition()), true);
  1578. else if (selection.isEmpty() && selection.getStart() > 0)
  1579. selection = { selection.getEnd() - 1, selection.getEnd() };
  1580. cut();
  1581. return true;
  1582. }
  1583. bool TextEditor::deleteForwards (bool /*moveInWholeWordSteps*/)
  1584. {
  1585. if (selection.isEmpty() && selection.getStart() < getTotalNumChars())
  1586. selection = { selection.getStart(), selection.getStart() + 1 };
  1587. cut();
  1588. return true;
  1589. }
  1590. bool TextEditor::copyToClipboard()
  1591. {
  1592. newTransaction();
  1593. copy();
  1594. return true;
  1595. }
  1596. bool TextEditor::cutToClipboard()
  1597. {
  1598. newTransaction();
  1599. copy();
  1600. cut();
  1601. return true;
  1602. }
  1603. bool TextEditor::pasteFromClipboard()
  1604. {
  1605. newTransaction();
  1606. paste();
  1607. return true;
  1608. }
  1609. bool TextEditor::selectAll()
  1610. {
  1611. newTransaction();
  1612. moveCaretTo (getTotalNumChars(), false);
  1613. moveCaretTo (0, true);
  1614. return true;
  1615. }
  1616. //==============================================================================
  1617. void TextEditor::setEscapeAndReturnKeysConsumed (bool shouldBeConsumed) noexcept
  1618. {
  1619. consumeEscAndReturnKeys = shouldBeConsumed;
  1620. }
  1621. bool TextEditor::keyPressed (const KeyPress& key)
  1622. {
  1623. if (isReadOnly() && key != KeyPress ('c', ModifierKeys::commandModifier, 0)
  1624. && key != KeyPress ('a', ModifierKeys::commandModifier, 0))
  1625. return false;
  1626. if (! TextEditorKeyMapper<TextEditor>::invokeKeyFunction (*this, key))
  1627. {
  1628. if (key == KeyPress::returnKey)
  1629. {
  1630. newTransaction();
  1631. if (returnKeyStartsNewLine)
  1632. {
  1633. insertTextAtCaret ("\n");
  1634. }
  1635. else
  1636. {
  1637. returnPressed();
  1638. return consumeEscAndReturnKeys;
  1639. }
  1640. }
  1641. else if (key.isKeyCode (KeyPress::escapeKey))
  1642. {
  1643. newTransaction();
  1644. moveCaretTo (getCaretPosition(), false);
  1645. escapePressed();
  1646. return consumeEscAndReturnKeys;
  1647. }
  1648. else if (key.getTextCharacter() >= ' '
  1649. || (tabKeyUsed && (key.getTextCharacter() == '\t')))
  1650. {
  1651. insertTextAtCaret (String::charToString (key.getTextCharacter()));
  1652. lastTransactionTime = Time::getApproximateMillisecondCounter();
  1653. }
  1654. else
  1655. {
  1656. return false;
  1657. }
  1658. }
  1659. return true;
  1660. }
  1661. bool TextEditor::keyStateChanged (const bool isKeyDown)
  1662. {
  1663. if (! isKeyDown)
  1664. return false;
  1665. #if JUCE_WINDOWS
  1666. if (KeyPress (KeyPress::F4Key, ModifierKeys::altModifier, 0).isCurrentlyDown())
  1667. return false; // We need to explicitly allow alt-F4 to pass through on Windows
  1668. #endif
  1669. if ((! consumeEscAndReturnKeys)
  1670. && (KeyPress (KeyPress::escapeKey).isCurrentlyDown()
  1671. || KeyPress (KeyPress::returnKey).isCurrentlyDown()))
  1672. return false;
  1673. // (overridden to avoid forwarding key events to the parent)
  1674. return ! ModifierKeys::currentModifiers.isCommandDown();
  1675. }
  1676. //==============================================================================
  1677. void TextEditor::focusGained (FocusChangeType)
  1678. {
  1679. newTransaction();
  1680. if (selectAllTextWhenFocused)
  1681. {
  1682. moveCaretTo (0, false);
  1683. moveCaretTo (getTotalNumChars(), true);
  1684. }
  1685. // When caret position changes, we check focus automatically, to
  1686. // show any native keyboard if needed. If the position does not
  1687. // change though, we need to check focus manually.
  1688. if (getTotalNumChars() == 0)
  1689. checkFocus();
  1690. repaint();
  1691. updateCaretPosition();
  1692. }
  1693. void TextEditor::focusLost (FocusChangeType)
  1694. {
  1695. newTransaction();
  1696. wasFocused = false;
  1697. textHolder->stopTimer();
  1698. underlinedSections.clear();
  1699. if (auto* peer = getPeer())
  1700. peer->dismissPendingTextInput();
  1701. updateCaretPosition();
  1702. postCommandMessage (TextEditorDefs::focusLossMessageId);
  1703. repaint();
  1704. }
  1705. //==============================================================================
  1706. void TextEditor::resized()
  1707. {
  1708. viewport->setBoundsInset (borderSize);
  1709. viewport->setSingleStepSizes (16, roundToInt (currentFont.getHeight()));
  1710. updateTextHolderSize();
  1711. if (isMultiLine())
  1712. updateCaretPosition();
  1713. else
  1714. scrollToMakeSureCursorIsVisible();
  1715. }
  1716. void TextEditor::handleCommandMessage (const int commandId)
  1717. {
  1718. Component::BailOutChecker checker (this);
  1719. switch (commandId)
  1720. {
  1721. case TextEditorDefs::textChangeMessageId:
  1722. listeners.callChecked (checker, [this] (Listener& l) { l.textEditorTextChanged (*this); });
  1723. if (! checker.shouldBailOut() && onTextChange != nullptr)
  1724. onTextChange();
  1725. break;
  1726. case TextEditorDefs::returnKeyMessageId:
  1727. listeners.callChecked (checker, [this] (Listener& l) { l.textEditorReturnKeyPressed (*this); });
  1728. if (! checker.shouldBailOut() && onReturnKey != nullptr)
  1729. onReturnKey();
  1730. break;
  1731. case TextEditorDefs::escapeKeyMessageId:
  1732. listeners.callChecked (checker, [this] (Listener& l) { l.textEditorEscapeKeyPressed (*this); });
  1733. if (! checker.shouldBailOut() && onEscapeKey != nullptr)
  1734. onEscapeKey();
  1735. break;
  1736. case TextEditorDefs::focusLossMessageId:
  1737. updateValueFromText();
  1738. listeners.callChecked (checker, [this] (Listener& l) { l.textEditorFocusLost (*this); });
  1739. if (! checker.shouldBailOut() && onFocusLost != nullptr)
  1740. onFocusLost();
  1741. break;
  1742. default:
  1743. jassertfalse;
  1744. break;
  1745. }
  1746. }
  1747. void TextEditor::setTemporaryUnderlining (const Array<Range<int>>& newUnderlinedSections)
  1748. {
  1749. underlinedSections = newUnderlinedSections;
  1750. repaint();
  1751. }
  1752. //==============================================================================
  1753. UndoManager* TextEditor::getUndoManager() noexcept
  1754. {
  1755. return readOnly ? nullptr : &undoManager;
  1756. }
  1757. void TextEditor::clearInternal (UndoManager* const um)
  1758. {
  1759. remove ({ 0, getTotalNumChars() }, um, caretPosition);
  1760. }
  1761. void TextEditor::insert (const String& text, int insertIndex, const Font& font,
  1762. Colour colour, UndoManager* um, int caretPositionToMoveTo)
  1763. {
  1764. if (text.isNotEmpty())
  1765. {
  1766. if (um != nullptr)
  1767. {
  1768. if (um->getNumActionsInCurrentTransaction() > TextEditorDefs::maxActionsPerTransaction)
  1769. newTransaction();
  1770. um->perform (new InsertAction (*this, text, insertIndex, font, colour,
  1771. caretPosition, caretPositionToMoveTo));
  1772. }
  1773. else
  1774. {
  1775. repaintText ({ insertIndex, getTotalNumChars() }); // must do this before and after changing the data, in case
  1776. // a line gets moved due to word wrap
  1777. int index = 0;
  1778. int nextIndex = 0;
  1779. for (int i = 0; i < sections.size(); ++i)
  1780. {
  1781. nextIndex = index + sections.getUnchecked (i)->getTotalLength();
  1782. if (insertIndex == index)
  1783. {
  1784. sections.insert (i, new UniformTextSection (text, font, colour, passwordCharacter));
  1785. break;
  1786. }
  1787. if (insertIndex > index && insertIndex < nextIndex)
  1788. {
  1789. splitSection (i, insertIndex - index);
  1790. sections.insert (i + 1, new UniformTextSection (text, font, colour, passwordCharacter));
  1791. break;
  1792. }
  1793. index = nextIndex;
  1794. }
  1795. if (nextIndex == insertIndex)
  1796. sections.add (new UniformTextSection (text, font, colour, passwordCharacter));
  1797. coalesceSimilarSections();
  1798. totalNumChars = -1;
  1799. valueTextNeedsUpdating = true;
  1800. updateTextHolderSize();
  1801. moveCaretTo (caretPositionToMoveTo, false);
  1802. repaintText ({ insertIndex, getTotalNumChars() });
  1803. }
  1804. }
  1805. }
  1806. void TextEditor::reinsert (int insertIndex, const OwnedArray<UniformTextSection>& sectionsToInsert)
  1807. {
  1808. int index = 0;
  1809. int nextIndex = 0;
  1810. for (int i = 0; i < sections.size(); ++i)
  1811. {
  1812. nextIndex = index + sections.getUnchecked (i)->getTotalLength();
  1813. if (insertIndex == index)
  1814. {
  1815. for (int j = sectionsToInsert.size(); --j >= 0;)
  1816. sections.insert (i, new UniformTextSection (*sectionsToInsert.getUnchecked(j)));
  1817. break;
  1818. }
  1819. if (insertIndex > index && insertIndex < nextIndex)
  1820. {
  1821. splitSection (i, insertIndex - index);
  1822. for (int j = sectionsToInsert.size(); --j >= 0;)
  1823. sections.insert (i + 1, new UniformTextSection (*sectionsToInsert.getUnchecked(j)));
  1824. break;
  1825. }
  1826. index = nextIndex;
  1827. }
  1828. if (nextIndex == insertIndex)
  1829. for (auto* s : sectionsToInsert)
  1830. sections.add (new UniformTextSection (*s));
  1831. coalesceSimilarSections();
  1832. totalNumChars = -1;
  1833. valueTextNeedsUpdating = true;
  1834. }
  1835. void TextEditor::remove (Range<int> range, UndoManager* const um, const int caretPositionToMoveTo)
  1836. {
  1837. if (! range.isEmpty())
  1838. {
  1839. int index = 0;
  1840. for (int i = 0; i < sections.size(); ++i)
  1841. {
  1842. auto nextIndex = index + sections.getUnchecked(i)->getTotalLength();
  1843. if (range.getStart() > index && range.getStart() < nextIndex)
  1844. {
  1845. splitSection (i, range.getStart() - index);
  1846. --i;
  1847. }
  1848. else if (range.getEnd() > index && range.getEnd() < nextIndex)
  1849. {
  1850. splitSection (i, range.getEnd() - index);
  1851. --i;
  1852. }
  1853. else
  1854. {
  1855. index = nextIndex;
  1856. if (index > range.getEnd())
  1857. break;
  1858. }
  1859. }
  1860. index = 0;
  1861. if (um != nullptr)
  1862. {
  1863. Array<UniformTextSection*> removedSections;
  1864. for (auto* section : sections)
  1865. {
  1866. if (range.getEnd() <= range.getStart())
  1867. break;
  1868. auto nextIndex = index + section->getTotalLength();
  1869. if (range.getStart() <= index && range.getEnd() >= nextIndex)
  1870. removedSections.add (new UniformTextSection (*section));
  1871. index = nextIndex;
  1872. }
  1873. if (um->getNumActionsInCurrentTransaction() > TextEditorDefs::maxActionsPerTransaction)
  1874. newTransaction();
  1875. um->perform (new RemoveAction (*this, range, caretPosition,
  1876. caretPositionToMoveTo, removedSections));
  1877. }
  1878. else
  1879. {
  1880. auto remainingRange = range;
  1881. for (int i = 0; i < sections.size(); ++i)
  1882. {
  1883. auto* section = sections.getUnchecked (i);
  1884. auto nextIndex = index + section->getTotalLength();
  1885. if (remainingRange.getStart() <= index && remainingRange.getEnd() >= nextIndex)
  1886. {
  1887. sections.remove (i);
  1888. remainingRange.setEnd (remainingRange.getEnd() - (nextIndex - index));
  1889. if (remainingRange.isEmpty())
  1890. break;
  1891. --i;
  1892. }
  1893. else
  1894. {
  1895. index = nextIndex;
  1896. }
  1897. }
  1898. coalesceSimilarSections();
  1899. totalNumChars = -1;
  1900. valueTextNeedsUpdating = true;
  1901. moveCaretTo (caretPositionToMoveTo, false);
  1902. repaintText ({ range.getStart(), getTotalNumChars() });
  1903. }
  1904. }
  1905. }
  1906. //==============================================================================
  1907. String TextEditor::getText() const
  1908. {
  1909. MemoryOutputStream mo;
  1910. mo.preallocate ((size_t) getTotalNumChars());
  1911. for (auto* s : sections)
  1912. s->appendAllText (mo);
  1913. return mo.toUTF8();
  1914. }
  1915. String TextEditor::getTextInRange (const Range<int>& range) const
  1916. {
  1917. if (range.isEmpty())
  1918. return {};
  1919. MemoryOutputStream mo;
  1920. mo.preallocate ((size_t) jmin (getTotalNumChars(), range.getLength()));
  1921. int index = 0;
  1922. for (auto* s : sections)
  1923. {
  1924. auto nextIndex = index + s->getTotalLength();
  1925. if (range.getStart() < nextIndex)
  1926. {
  1927. if (range.getEnd() <= index)
  1928. break;
  1929. s->appendSubstring (mo, range - index);
  1930. }
  1931. index = nextIndex;
  1932. }
  1933. return mo.toUTF8();
  1934. }
  1935. String TextEditor::getHighlightedText() const
  1936. {
  1937. return getTextInRange (selection);
  1938. }
  1939. int TextEditor::getTotalNumChars() const
  1940. {
  1941. if (totalNumChars < 0)
  1942. {
  1943. totalNumChars = 0;
  1944. for (auto* s : sections)
  1945. totalNumChars += s->getTotalLength();
  1946. }
  1947. return totalNumChars;
  1948. }
  1949. bool TextEditor::isEmpty() const
  1950. {
  1951. return getTotalNumChars() == 0;
  1952. }
  1953. void TextEditor::getCharPosition (int index, Point<float>& anchor, float& lineHeight) const
  1954. {
  1955. if (getWordWrapWidth() <= 0)
  1956. {
  1957. anchor = {};
  1958. lineHeight = currentFont.getHeight();
  1959. }
  1960. else
  1961. {
  1962. Iterator i (*this);
  1963. if (sections.isEmpty())
  1964. {
  1965. anchor = { i.getJustificationOffset (0), 0 };
  1966. lineHeight = currentFont.getHeight();
  1967. }
  1968. else
  1969. {
  1970. i.getCharPosition (index, anchor, lineHeight);
  1971. }
  1972. }
  1973. }
  1974. int TextEditor::indexAtPosition (const float x, const float y)
  1975. {
  1976. if (getWordWrapWidth() > 0)
  1977. {
  1978. for (Iterator i (*this); i.next();)
  1979. {
  1980. if (y < i.lineY + i.lineHeight)
  1981. {
  1982. if (y < i.lineY)
  1983. return jmax (0, i.indexInText - 1);
  1984. if (x <= i.atomX || i.atom->isNewLine())
  1985. return i.indexInText;
  1986. if (x < i.atomRight)
  1987. return i.xToIndex (x);
  1988. }
  1989. }
  1990. }
  1991. return getTotalNumChars();
  1992. }
  1993. //==============================================================================
  1994. int TextEditor::findWordBreakAfter (const int position) const
  1995. {
  1996. auto t = getTextInRange ({ position, position + 512 });
  1997. auto totalLength = t.length();
  1998. int i = 0;
  1999. while (i < totalLength && CharacterFunctions::isWhitespace (t[i]))
  2000. ++i;
  2001. auto type = TextEditorDefs::getCharacterCategory (t[i]);
  2002. while (i < totalLength && type == TextEditorDefs::getCharacterCategory (t[i]))
  2003. ++i;
  2004. while (i < totalLength && CharacterFunctions::isWhitespace (t[i]))
  2005. ++i;
  2006. return position + i;
  2007. }
  2008. int TextEditor::findWordBreakBefore (const int position) const
  2009. {
  2010. if (position <= 0)
  2011. return 0;
  2012. auto startOfBuffer = jmax (0, position - 512);
  2013. auto t = getTextInRange ({ startOfBuffer, position });
  2014. int i = position - startOfBuffer;
  2015. while (i > 0 && CharacterFunctions::isWhitespace (t [i - 1]))
  2016. --i;
  2017. if (i > 0)
  2018. {
  2019. auto type = TextEditorDefs::getCharacterCategory (t [i - 1]);
  2020. while (i > 0 && type == TextEditorDefs::getCharacterCategory (t [i - 1]))
  2021. --i;
  2022. }
  2023. jassert (startOfBuffer + i >= 0);
  2024. return startOfBuffer + i;
  2025. }
  2026. //==============================================================================
  2027. void TextEditor::splitSection (const int sectionIndex, const int charToSplitAt)
  2028. {
  2029. jassert (sections[sectionIndex] != nullptr);
  2030. sections.insert (sectionIndex + 1,
  2031. sections.getUnchecked (sectionIndex)->split (charToSplitAt, passwordCharacter));
  2032. }
  2033. void TextEditor::coalesceSimilarSections()
  2034. {
  2035. for (int i = 0; i < sections.size() - 1; ++i)
  2036. {
  2037. auto* s1 = sections.getUnchecked (i);
  2038. auto* s2 = sections.getUnchecked (i + 1);
  2039. if (s1->font == s2->font
  2040. && s1->colour == s2->colour)
  2041. {
  2042. s1->append (*s2, passwordCharacter);
  2043. sections.remove (i + 1);
  2044. --i;
  2045. }
  2046. }
  2047. }
  2048. } // namespace juce