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.

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