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.

2642 lines
77KB

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