Audio plugin host https://kx.studio/carla
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.

2529 lines
73KB

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