Audio plugin host https://kx.studio/carla
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.

juce_TextEditor.cpp 73KB

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