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

2625 lines
75KB

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