juce_TextEditor.cpp 73 KB

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