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.

2626 lines
76KB

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