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.

2753 lines
80KB

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