gcsx_editbox.cpp 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734
  1. /* GCSx
  2. ** EDITBOX.CPP
  3. **
  4. ** Multi-line textbox window
  5. */
  6. /*****************************************************************************
  7. ** Copyright (C) 2003-2006 Janson
  8. **
  9. ** This program is free software; you can redistribute it and/or modify
  10. ** it under the terms of the GNU General Public License as published by
  11. ** the Free Software Foundation; either version 2 of the License, or
  12. ** (at your option) any later version.
  13. **
  14. ** This program is distributed in the hope that it will be useful,
  15. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ** GNU General Public License for more details.
  18. **
  19. ** You should have received a copy of the GNU General Public License
  20. ** along with this program; if not, write to the Free Software
  21. ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
  22. *****************************************************************************/
  23. #include "all.h"
  24. const string EditBox::wordChars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
  25. void EditBox::setDirtyAndBlink() { start_func
  26. insertBlink = 1;
  27. insertBlinkMs = SDL_GetTicks();
  28. setDirtyRange(insertion.vRow, insertion.vRow);
  29. }
  30. void EditBox::setDirtyRange(int first, int last) { start_func
  31. setDirty();
  32. if (vDirtyFirst == -1) {
  33. vDirtyFirst = first;
  34. vDirtyLast = last;
  35. }
  36. else {
  37. if (first < vDirtyFirst) vDirtyFirst = first;
  38. if (last > vDirtyLast) vDirtyLast = last;
  39. }
  40. }
  41. void EditBox::goLeft(TextPoint& point) { start_func
  42. --point.column;
  43. if (point.column < 0) {
  44. --point.vRow;
  45. if (point.vRow < 0) {
  46. // End of all text- cancel all movement
  47. ++point.vRow;
  48. ++point.column;
  49. }
  50. else point.column = (*findVLine(point.vRow)).length;
  51. }
  52. }
  53. void EditBox::goRight(TextPoint& point) { start_func
  54. ++point.column;
  55. if (point.column > (*findVLine(point.vRow)).length) {
  56. ++point.vRow;
  57. if (point.vRow >= vNumLines) {
  58. // End of all text- cancel all movement
  59. --point.vRow;
  60. --point.column;
  61. }
  62. else point.column = 0;
  63. }
  64. }
  65. void EditBox::vLineToDLine(TextPoint& point) { start_func
  66. list<VLine>::iterator VL = findVLine(point.vRow);
  67. assert(VL != lineMap.end());
  68. point.vRow = (*VL).dLine;
  69. point.column += (*VL).colStart;
  70. }
  71. void EditBox::dLineToVLine(TextPoint& point) { start_func
  72. list<VLine>::iterator VL = findDLine(point.vRow, &point.vRow);
  73. while (1) {
  74. assert(VL != lineMap.end());
  75. if (point.column - (*VL).colStart <= (*VL).length) break;
  76. ++point.vRow;
  77. ++VL;
  78. }
  79. point.column -= (*VL).colStart;
  80. if (point.column < 0) point.column = 0;
  81. point.pixelX = fontWidth((*(*VL).dLineI).substr((*VL).colStart, point.column), font);
  82. }
  83. void EditBox::vLineToDLineAllPoints() { start_func
  84. vLineToDLine(insertion);
  85. if (isSelect) {
  86. vLineToDLine(selectBegin);
  87. vLineToDLine(selectEnd);
  88. }
  89. }
  90. void EditBox::dLineToVLineAllPoints() { start_func
  91. dLineToVLine(insertion);
  92. if (isSelect) {
  93. dLineToVLine(selectBegin);
  94. dLineToVLine(selectEnd);
  95. }
  96. }
  97. void EditBox::resize(int newWidth, int newHeight, int newViewWidth, int newViewHeight, int fromParent) { start_func
  98. Window::resize(newWidth, newHeight, newViewWidth, newViewHeight, fromParent);
  99. // Always rewrap even if wordwrap off, if wrappedTo = -1
  100. if ((viewWidth != wrappedTo) && ((wordwrap) || (wrappedTo == -1))) {
  101. if (wrappedTo == -1) {
  102. insertion.vRow = 0;
  103. insertion.column = 0;
  104. insertion.pixelX = 0;
  105. isSelect = 0;
  106. fillLineMap();
  107. wrappedTo = viewWidth;
  108. }
  109. else {
  110. vLineToDLineAllPoints();
  111. fillLineMap();
  112. wrappedTo = viewWidth;
  113. dLineToVLineAllPoints();
  114. }
  115. setDirty();
  116. vDirtyFirst = 0;
  117. vDirtyLast = vNumLines - 1;
  118. Window::resize(GUI_EDITBOX_INNERMARGIN * 2 + GUI_EDITBOX_LEFTPAD + GUI_EDITBOX_RIGHTPAD + vLongestLength,
  119. GUI_EDITBOX_INNERMARGIN * 2 + vNumLines * lineHeight);
  120. }
  121. }
  122. int EditBox::addLineWrapped(int dLine, list<string>::iterator& iter, list<VLine>::iterator& insertAt) { start_func
  123. // Wrap at width of window
  124. int wrapAt = viewWidth - GUI_EDITBOX_INNERMARGIN * 2 - GUI_EDITBOX_LEFTPAD - GUI_EDITBOX_RIGHTPAD;
  125. int size = (*iter).size();
  126. int count = 0;
  127. int pos = 0;
  128. int skip;
  129. do {
  130. skip = 0;
  131. VLine newMap;
  132. newMap.dLine = dLine;
  133. newMap.dLineI = iter;
  134. newMap.colStart = pos;
  135. // Start with what's left
  136. newMap.length = size - pos;
  137. // Don't wrap 0 or 1 characters
  138. if ((wordwrap) && (newMap.length > 1)) {
  139. // Wrap text
  140. string str = newMap.getStr();
  141. // (always max out at MAX_LINELENGTH)
  142. int wrapMax = max(2, min(breakLineBefore(str, wrapAt), (int)MAX_LINELENGTH));
  143. // Any cut?
  144. if (wrapMax < newMap.length) {
  145. // Move back until character after wrap is space and before wrap is not
  146. int wrapMin = wrapMax;
  147. while ((wrapMin) && ((str[wrapMin] != ' ') || (str[wrapMin - 1] == ' '))) {
  148. --wrapMin;
  149. }
  150. // If we still have at least one character, wrap at word boundary found
  151. if (wrapMin > 0) wrapMax = wrapMin;
  152. }
  153. newMap.length = wrapMax;
  154. // Skip over all spaces
  155. while (str[wrapMax++] == ' ') ++skip;
  156. }
  157. // Skip over what we've used
  158. pos += newMap.length + skip;
  159. lineMap.insert(insertAt, newMap);
  160. int width = fontWidth(newMap.getStr(), font);
  161. if (width > vLongestLength) vLongestLength = width;
  162. ++count;
  163. // (loop back if anything left OR if we skipped spaces)
  164. } while ((pos < size) || (skip));
  165. return count;
  166. }
  167. void EditBox::fillLineMap() { start_func
  168. lineMap.clear();
  169. lastSpot = lineMap.end();
  170. lastSpotLine = -1;
  171. if (contents->empty())
  172. contents->push_back(blankString);
  173. dNumLines = 0;
  174. vNumLines = 0;
  175. vLongestLength = 0;
  176. list<string>::iterator end = contents->end();
  177. list<VLine>::iterator insertAt = lineMap.end();
  178. for (list<string>::iterator pos = contents->begin(); pos != end; ++pos) {
  179. vNumLines += addLineWrapped(dNumLines++, pos, insertAt);
  180. }
  181. }
  182. void EditBox::dumpLineMap() { start_func
  183. // @TODO: temporary function for debugging
  184. list<VLine>::iterator start = lineMap.begin();
  185. list<VLine>::iterator end = lineMap.end();
  186. int pos = 0;
  187. for (; start != end; ++start) {
  188. debugStdout("[%d] d%d c%d l%d | %s", pos++, (*start).dLine, (*start).colStart, (*start).length, (*start).getStr().c_str());
  189. }
  190. list<string>::iterator start2 = contents->begin();
  191. list<string>::iterator end2 = contents->end();
  192. pos = 0;
  193. for (; start2 != end2; ++start2) {
  194. debugStdout("(%d) %s", pos++, (*start2).c_str());
  195. }
  196. }
  197. void EditBox::eraseLineMap(int dLineFirst, int count, int* getVLineAt, int* getVLineCount) { start_func
  198. int vlNum;
  199. list<VLine>::iterator start = findDLine(dLineFirst, &vlNum);
  200. list<VLine>::iterator end = start;
  201. // Count elements being erased
  202. int vCount = 0;
  203. list<VLine>::iterator final = lineMap.end();
  204. for (; end != final; ++end) {
  205. if ((*end).dLine == dLineFirst + count) break;
  206. ++vCount;
  207. // (carry lastspot along for the ride)
  208. if ((lastSpotLine >= 0) && (lastSpot == end)) {
  209. ++lastSpot;
  210. if (lastSpot == final) lastSpotLine = -1;
  211. else ++lastSpotLine;
  212. }
  213. }
  214. // (final adjust to lastspot- safe if spot is -1)
  215. if (lastSpotLine >= vlNum) {
  216. assert(lastSpotLine >= vlNum + vCount);
  217. lastSpotLine -= vCount;
  218. }
  219. lineMap.erase(start, end);
  220. vNumLines -= vCount;
  221. dNumLines -= count;
  222. // Decrement dline references
  223. for (; end != final; ++end) {
  224. (*end).dLine -= count;
  225. }
  226. if (getVLineAt) *getVLineAt = vlNum;
  227. if (getVLineCount) *getVLineCount = vCount;
  228. #ifndef NDEBUG
  229. if (lastSpotLine >= 0) {
  230. vlNum = lastSpotLine;
  231. start = lastSpot;
  232. lastSpot = lineMap.end();
  233. lastSpotLine = -1;
  234. end = findVLine(vlNum);
  235. assert(start == end);
  236. }
  237. #endif
  238. }
  239. void EditBox::insertLineMap(int dLineAt, int count, int* getVLineAt, int* getVLineCount) { start_func
  240. // Position in VLines to add at
  241. int vlNum;
  242. list<VLine>::iterator insertAt = findDLine(dLineAt, &vlNum);
  243. if (vlNum == -1) vlNum = vNumLines;
  244. // Position in DLines to add from
  245. list<string>::iterator insertFrom;
  246. if (dLineAt == 0)
  247. insertFrom = contents->begin();
  248. else {
  249. list<VLine>::iterator insertAt2 = insertAt;
  250. --insertAt2;
  251. insertFrom = (*insertAt2).dLineI;
  252. ++insertFrom;
  253. }
  254. int vCount = 0;
  255. for (int did = 0; did < count; ++did) {
  256. vCount += addLineWrapped(dLineAt++, insertFrom, insertAt);
  257. ++insertFrom;
  258. ++dNumLines;
  259. }
  260. vNumLines += vCount;
  261. // (-1 safely ignored here)
  262. if (lastSpotLine >= vlNum) lastSpotLine += vCount;
  263. // Increment dline references
  264. list<VLine>::iterator end = lineMap.end();
  265. for (; insertAt != end; ++insertAt) {
  266. (*insertAt).dLine += count;
  267. }
  268. if (getVLineAt) *getVLineAt = vlNum;
  269. if (getVLineCount) *getVLineCount = vCount;
  270. }
  271. EditBox::EditBox(list<string>* storage, int myFont, int isReadOnly) : Window() { start_func
  272. readonly = isReadOnly;
  273. font = myFont;
  274. contents = storage;
  275. wordwrap = 1;
  276. autoIndent = 0;
  277. wrappedTo = -1;
  278. lastSpot = lineMap.end();
  279. lastSpotLine = -1;
  280. lineHeight = fontHeight(font);
  281. insertYOffset = 0;
  282. insertHeight = lineHeight;
  283. myFrame = NULL;
  284. myScroll = NULL;
  285. open = 0;
  286. haveFocus = 0;
  287. }
  288. void EditBox::prepOpen() { start_func
  289. // Simply "resizes", which forces init if not done yet
  290. resize(width, height);
  291. }
  292. EditBox::~EditBox() { start_func
  293. }
  294. int EditBox::event(int hasFocus, const SDL_Event* event) { start_func
  295. TextPoint point;
  296. list<VLine>::iterator pointVLine;
  297. string::size_type foundStr;
  298. string clipStorage;
  299. switch (event->type) {
  300. case SDL_CLOSE:
  301. open = 0;
  302. myFrame = NULL;
  303. myScroll = NULL;
  304. return 1;
  305. case SDL_MOUSEBUTTONDOWN:
  306. if (event->button.button == SDL_BUTTON_LEFT) {
  307. point = insertWhere(event->button.x, event->button.y);
  308. insertionState(point, SDL_GetModState() & KMOD_SHIFT);
  309. return 1;
  310. }
  311. break;
  312. case SDL_MOUSEBUTTONDBL:
  313. // Where clicked
  314. point = insertWhere(event->button.x, event->button.y);
  315. pointVLine = findVLine(point.vRow);
  316. // Find first word character to highlight
  317. --point.column;
  318. if (point.column < 0) point.column = 0;
  319. foundStr = (*pointVLine).getStr().find_last_not_of(wordChars, point.column);
  320. if (foundStr == string::npos) point.column = 0;
  321. else point.column = foundStr + 1;
  322. insertionState(point, 0);
  323. // Find last character to highlight
  324. foundStr = (*pointVLine).getStr().find_first_not_of(wordChars, point.column);
  325. if (foundStr == string::npos) point.column = (*pointVLine).length;
  326. else point.column = foundStr;
  327. insertionState(point, 1);
  328. return 1;
  329. case SDL_MOUSEMOTION:
  330. if (event->motion.state & SDL_BUTTON_LMASK) {
  331. point = insertWhere((Sint16)event->motion.x, (Sint16)event->motion.y);
  332. insertionState(point, 1);
  333. return 1;
  334. }
  335. break;
  336. case SDL_MOUSEFOCUS:
  337. if (event->user.code & 1) {
  338. selectMouse(MOUSE_BEAM);
  339. }
  340. else {
  341. selectMouse(MOUSE_NORMAL);
  342. }
  343. return 1;
  344. case SDL_INPUTFOCUS:
  345. if (event->user.code & 1) {
  346. if (!haveFocus) {
  347. haveFocus = 1;
  348. if (isSelect) setDirtyRange(selectBegin.vRow, selectEnd.vRow);
  349. setDirtyAndBlink();
  350. }
  351. }
  352. else {
  353. if (haveFocus) {
  354. haveFocus = 0;
  355. if (isSelect) setDirtyRange(selectBegin.vRow, selectEnd.vRow);
  356. else setDirtyRange(insertion.vRow, insertion.vRow);
  357. }
  358. }
  359. return 1;
  360. case SDL_SPECIAL:
  361. if ((event->user.code & SDL_IDLE) && (haveFocus)) {
  362. Uint32 ticks = SDL_GetTicks();
  363. // (catch wraparound)
  364. if (ticks < insertBlinkMs) insertBlinkMs = SDL_GetTicks();
  365. else if (ticks - insertBlinkMs > DELAY_CURSOR_BLINK) {
  366. insertBlink = !insertBlink;
  367. insertBlinkMs = SDL_GetTicks();
  368. setDirtyRange(insertion.vRow, insertion.vRow);
  369. }
  370. return 1;
  371. }
  372. break;
  373. case SDL_COMMAND:
  374. switch (event->user.code) {
  375. case EDIT_COPY:
  376. copyText(clipStorage);
  377. if (clipStorage.size()) clipboardCopy(clipStorage);
  378. return 1;
  379. case EDIT_CUT:
  380. copyText(clipStorage);
  381. if (clipStorage.size()) {
  382. if (!readonly) pasteText(blankString);
  383. clipboardCopy(clipStorage);
  384. }
  385. return 1;
  386. case EDIT_PASTE:
  387. if ((!readonly) && (canConvertClipboard(CLIPBOARD_TEXT_MULTI))) {
  388. clipboardPasteTextMulti(clipStorage);
  389. pasteText(clipStorage);
  390. }
  391. return 1;
  392. case EDIT_SELECTALL:
  393. selectAll();
  394. return 1;
  395. }
  396. break;
  397. case SDL_KEYDOWN: {
  398. int drag = 0;
  399. Sint32 key;
  400. if (event->key.keysym.mod & KMOD_SHIFT) {
  401. drag = 1; // We'll check this if we care about SHIFT
  402. key = combineKey(event->key.keysym.sym, event->key.keysym.mod & ~KMOD_SHIFT);
  403. }
  404. else {
  405. key = combineKey(event->key.keysym.sym, event->key.keysym.mod);
  406. }
  407. switch (key) {
  408. case SDLK_DELETE:
  409. if ((drag) || (readonly)) break;
  410. keyDelete();
  411. return 1;
  412. case SDLK_BACKSPACE:
  413. if ((drag) || (readonly)) break;
  414. keyBackspace();
  415. return 1;
  416. case combineKey(SDLK_DELETE, KMOD_CTRL):
  417. if ((drag) || (readonly)) break;
  418. // If no selection, delete to end of line
  419. if (!isSelect) {
  420. point = insertion;
  421. pointVLine = findVLine(point.vRow);
  422. point.column = (*pointVLine).length;
  423. insertionState(point, 1);
  424. }
  425. // Delete current selection
  426. keyDelete();
  427. return 1;
  428. case SDLK_RIGHT:
  429. point = insertion;
  430. goRight(point);
  431. insertionState(point, drag);
  432. return 1;
  433. case SDLK_DOWN:
  434. point = insertion;
  435. ++point.vRow;
  436. if (point.vRow < vNumLines) {
  437. pointVLine = findVLine(point.vRow);
  438. if (point.column > (*pointVLine).length) point.column = (*pointVLine).length;
  439. // @TODO: currently goes to same column on next row- NOT
  440. // same pixel position; also, remember "virtual" column
  441. // to use for all up/down movement
  442. insertionState(point, drag);
  443. }
  444. return 1;
  445. case SDLK_LEFT:
  446. point = insertion;
  447. goLeft(point);
  448. insertionState(point, drag);
  449. return 1;
  450. case SDLK_UP:
  451. point = insertion;
  452. --point.vRow;
  453. if (point.vRow >= 0) {
  454. pointVLine = findVLine(point.vRow);
  455. if (point.column > (*pointVLine).length) point.column = (*pointVLine).length;
  456. // @TODO: currently goes to same column on previous row- NOT
  457. // same pixel position; also, remember "virtual" column
  458. // to use for all up/down movement
  459. insertionState(point, drag);
  460. }
  461. return 1;
  462. case SDLK_END:
  463. point = insertion;
  464. pointVLine = findVLine(point.vRow);
  465. point.column = (*pointVLine).length;
  466. insertionState(point, drag);
  467. return 1;
  468. case SDLK_HOME:
  469. point = insertion;
  470. point.column = 0;
  471. insertionState(point, drag);
  472. return 1;
  473. case combineKey(SDLK_UP, KMOD_CTRL):
  474. if (myFrame) {
  475. myFrame->scrollBy(0, lineHeight);
  476. }
  477. if (myScroll) {
  478. myScroll->scrollBy(0, lineHeight);
  479. }
  480. return 1;
  481. case combineKey(SDLK_DOWN, KMOD_CTRL):
  482. if (myFrame) {
  483. myFrame->scrollBy(0, -lineHeight);
  484. }
  485. if (myScroll) {
  486. myScroll->scrollBy(0, -lineHeight);
  487. }
  488. return 1;
  489. case SDLK_PAGEUP:
  490. point = insertion;
  491. if (point.vRow > 0) {
  492. point.vRow -= max(1, (viewHeight / lineHeight) - 1);
  493. pointVLine = findVLine(point.vRow);
  494. if (point.column > (*pointVLine).length) point.column = (*pointVLine).length;
  495. // @TODO: currently goes to same column on target row- NOT
  496. // same pixel position; also, remember "virtual" column
  497. // to use for all up/down movement
  498. insertionState(point, drag);
  499. }
  500. return 1;
  501. case SDLK_PAGEDOWN:
  502. point = insertion;
  503. if (point.vRow < vNumLines - 1) {
  504. point.vRow += max(1, (viewHeight / lineHeight) - 1);
  505. pointVLine = findVLine(point.vRow);
  506. if (point.column > (*pointVLine).length) point.column = (*pointVLine).length;
  507. // @TODO: currently goes to same column on target row- NOT
  508. // same pixel position; also, remember "virtual" column
  509. // to use for all up/down movement
  510. insertionState(point, drag);
  511. }
  512. return 1;
  513. case combineKey(SDLK_PAGEUP, KMOD_CTRL):
  514. if (myFrame) {
  515. myFrame->scrollBy(0, max(1, (viewHeight / lineHeight) - 1) * lineHeight);
  516. }
  517. if (myScroll) {
  518. myScroll->scrollBy(0, max(1, (viewHeight / lineHeight) - 1) * lineHeight);
  519. }
  520. return 1;
  521. case combineKey(SDLK_PAGEDOWN, KMOD_CTRL):
  522. if (myFrame) {
  523. myFrame->scrollBy(0, max(1, (viewHeight / lineHeight) - 1) * -lineHeight);
  524. }
  525. if (myScroll) {
  526. myScroll->scrollBy(0, max(1, (viewHeight / lineHeight) - 1) * -lineHeight);
  527. }
  528. return 1;
  529. case combineKey(SDLK_HOME, KMOD_CTRL):
  530. point.vRow = 0;
  531. point.column = 0;
  532. insertionState(point, drag);
  533. return 1;
  534. case combineKey(SDLK_END, KMOD_CTRL):
  535. point.vRow = vNumLines - 1;
  536. pointVLine = findVLine(point.vRow);
  537. point.column = (*pointVLine).length;
  538. insertionState(point, drag);
  539. return 1;
  540. case combineKey(SDLK_RIGHT, KMOD_CTRL):
  541. // Current position
  542. point = insertion;
  543. pointVLine = findVLine(point.vRow);
  544. // Last character? Next line
  545. if (point.column == (*pointVLine).length) {
  546. // Last character of last line?
  547. if (point.vRow == vNumLines - 1) return 1;
  548. goRight(point);
  549. pointVLine = findVLine(point.vRow);
  550. foundStr = 0;
  551. }
  552. else {
  553. // Next not word character
  554. foundStr = (*pointVLine).getStr().find_first_not_of(wordChars, point.column);
  555. if (foundStr == string::npos) foundStr = (*pointVLine).length;
  556. }
  557. // Next word character
  558. foundStr = (*pointVLine).getStr().find_first_of(wordChars, foundStr);
  559. if (foundStr == string::npos) foundStr = (*pointVLine).length;
  560. point.column = foundStr;
  561. insertionState(point, drag);
  562. return 1;
  563. case combineKey(SDLK_LEFT, KMOD_CTRL):
  564. // Current position
  565. point = insertion;
  566. pointVLine = findVLine(point.vRow);
  567. if (point.column == 0) {
  568. point.column = 0;
  569. goLeft(point);
  570. }
  571. else {
  572. // Previous word character then not word character
  573. foundStr = (*pointVLine).getStr().find_last_of(wordChars, point.column - 1);
  574. if (foundStr == string::npos) {
  575. // Last character, previous line
  576. point.column = 0;
  577. goLeft(point);
  578. }
  579. else {
  580. foundStr = (*pointVLine).getStr().find_last_not_of(wordChars, foundStr);
  581. if (foundStr == string::npos) foundStr = 0;
  582. else if (foundStr > 0) ++foundStr;
  583. point.column = foundStr;
  584. }
  585. }
  586. insertionState(point, drag);
  587. return 1;
  588. case SDLK_TAB:
  589. if (readonly) break;
  590. if (isSelect) {
  591. // indent/unindent block if a selection
  592. int dStart = (*findVLine(selectBegin.vRow)).dLine;
  593. int dEnd = (*findVLine(selectEnd.vRow)).dLine;
  594. if (drag)
  595. unindent(dStart, dEnd - dStart + 1);
  596. else
  597. indent(dStart, dEnd - dStart + 1);
  598. }
  599. else {
  600. if (drag) {
  601. // Go left to first multiple of TAB_SIZE as long as only spaces in our way
  602. string line = (*findVLine(insertion.vRow)).getStr();
  603. if (line[insertion.column - 1] == ' ') keyBackspace();
  604. while ((insertion.column % TAB_SIZE) && (line[insertion.column - 1] == ' ')) {
  605. keyBackspace();
  606. }
  607. }
  608. else {
  609. // Insert spaces until multiple of TAB_SIZE
  610. pasteText(" ");
  611. while (insertion.column % TAB_SIZE) {
  612. pasteText(" ");
  613. }
  614. }
  615. }
  616. return 1;
  617. case SDLK_RETURN:
  618. case SDLK_KP_ENTER: {
  619. if (readonly) break;
  620. // (get line now, for auto-indent later)
  621. string line = *(*findVLine(insertion.vRow)).dLineI;
  622. string toInsert("\n");
  623. pasteText(toInsert);
  624. // Auto-indent to match previous line
  625. string::size_type pos = line.find_first_not_of(' ', 0);
  626. if (pos == string::npos) pos = line.size();
  627. // Adjust indent by counting ([{ vs }])
  628. if (autoIndent) {
  629. int adjust = 0;
  630. for (int c = line.size() - 1; c >= 0; --c) {\
  631. if ((line[c] == '(') || (line[c] == '[') || (line[c] == '{'))
  632. ++adjust;
  633. else if ((line[c] == ')') || (line[c] == ']') || (line[c] == '}'))
  634. --adjust;
  635. }
  636. if (adjust > 0)
  637. pos += TAB_SIZE;
  638. }
  639. if (pos > 0) {
  640. string spaces(pos, ' ');
  641. pasteText(spaces);
  642. }
  643. return 1;
  644. }
  645. default:
  646. if (readonly) break;
  647. if ((event->key.keysym.unicode) && !(event->key.keysym.mod & KMOD_ALT)) {
  648. if (event->key.keysym.unicode & 0xFF80) {
  649. key = '?';
  650. }
  651. else {
  652. key = event->key.keysym.unicode;
  653. if (key < 32) break;
  654. }
  655. string toInsert(1, key);
  656. pasteText(toInsert);
  657. if ((autoIndent) && ((key == '}') || (key == ']'))) {
  658. // Previous line must exist
  659. list<VLine>::iterator curV = findVLine(insertion.vRow);
  660. if ((*curV).dLine > 0) {
  661. // Indent on current
  662. list<string>::iterator dl = (*curV).dLineI;
  663. string line = *dl;
  664. string::size_type pos = line.find_first_not_of(' ', 0);
  665. // Indent on previous must not be greater
  666. // @TODO: also, if same indent and previous had the extra {, don't unindent
  667. --dl;
  668. line = *dl;
  669. if (line.find_first_not_of(' ', 0) <= pos) {
  670. // Unindent curent line
  671. unindent((*curV).dLine, 1);
  672. }
  673. }
  674. }
  675. return 1;
  676. }
  677. break;
  678. }
  679. break;
  680. }
  681. }
  682. return 0;
  683. }
  684. void EditBox::load() { start_func
  685. if (open) {
  686. // Force reinit
  687. wrappedTo = -1;
  688. prepOpen();
  689. }
  690. }
  691. void EditBox::apply() { start_func
  692. // (nothing)
  693. }
  694. list<EditBox::VLine>::iterator EditBox::findDLine(int dLine, int* getVLineNum) { start_func
  695. // Out of range?
  696. if ((dLine < 0) || (dLine >= dNumLines)) {
  697. if (getVLineNum) *getVLineNum = -1;
  698. return lineMap.end();
  699. }
  700. // Special case
  701. if (dLine == 0) {
  702. if (getVLineNum) *getVLineNum = 0;
  703. return lineMap.begin();
  704. }
  705. // Starts at beginning, end, or lastSpot, whichever is closest
  706. // Beginning
  707. int startLine = 0;
  708. int vLine = 0;
  709. list<VLine>::iterator startIter = lineMap.begin();
  710. // Is end closer?
  711. if (dNumLines - dLine < dLine) {
  712. vLine = vNumLines;
  713. startLine = dNumLines;
  714. startIter = lineMap.end();
  715. }
  716. // Is lastSpot closer?
  717. if (lastSpotLine >= 0) {
  718. if (abs((*lastSpot).dLine - dLine) < abs(startLine - dLine)) {
  719. vLine = lastSpotLine;
  720. startLine = (*lastSpot).dLine;
  721. startIter = lastSpot;
  722. }
  723. }
  724. // Iterate! Backwards first, stop at PREVIOUS dline
  725. // (safe because we special-cased line 0 previously)
  726. while (startLine >= dLine) {
  727. --startIter;
  728. --vLine;
  729. startLine = (*startIter).dLine;
  730. }
  731. // Forwards? (at least once)
  732. // This way we know we're at the first instance of a given dline
  733. while (startLine < dLine) {
  734. ++startIter;
  735. ++vLine;
  736. startLine = (*startIter).dLine;
  737. }
  738. if (getVLineNum) *getVLineNum = vLine;
  739. return startIter;
  740. }
  741. list<EditBox::VLine>::iterator EditBox::findVLine(int line) { start_func
  742. // Out of range?
  743. if ((line < 0) || (line >= vNumLines)) return lineMap.end();
  744. // Starts at beginning, end, or lastSpot, whichever is closest
  745. // Beginning
  746. int startLine = 0;
  747. list<VLine>::iterator startIter = lineMap.begin();
  748. // Is end closer?
  749. if (vNumLines - line < line) {
  750. startLine = vNumLines;
  751. startIter = lineMap.end();
  752. }
  753. // Is lastSpot closer?
  754. if (lastSpotLine >= 0) {
  755. if (abs(lastSpotLine - line) < abs(startLine - line)) {
  756. startLine = lastSpotLine;
  757. startIter = lastSpot;
  758. }
  759. }
  760. // Iterate! Forwards?
  761. while (startLine < line) {
  762. ++startLine;
  763. ++startIter;
  764. }
  765. // Backwards?
  766. while (startLine > line) {
  767. --startLine;
  768. --startIter;
  769. }
  770. // Retain for next time
  771. if (startLine != lastSpotLine) {
  772. lastSpotLine = startLine;
  773. lastSpot = startIter;
  774. }
  775. return startIter;
  776. }
  777. void EditBox::paintText(const string& text, const string& entireLine, int posInLine, int x, int y, SDL_Surface* destSurface, int isSelected) const { start_func
  778. drawText(text, guiRGB[isSelected ? COLOR_TEXTBOX : COLOR_TEXT], x, y, destSurface, font);
  779. }
  780. void EditBox::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
  781. assert(destSurface);
  782. if (visible) {
  783. // If dirty, redraw range or all
  784. if (dirty) {
  785. // Always redraw entire width
  786. Rect toDraw;
  787. getRect(toDraw);
  788. toDraw.x += xOffset;
  789. toDraw.y += yOffset;
  790. // Total dirty- as is
  791. if (!totalDirty) {
  792. // Not total dirty- do only dirty lines
  793. toDraw.y += vDirtyFirst * lineHeight + GUI_EDITBOX_INNERMARGIN;
  794. toDraw.h = (vDirtyLast - vDirtyFirst + 1) * lineHeight;
  795. }
  796. dirty = totalDirty = 0;
  797. vDirtyFirst = vDirtyLast = -1;
  798. // Add our redraw into display rect and clip
  799. boundRects(toDisplay, toDraw);
  800. intersectRects(toDisplay, clipArea);
  801. }
  802. // Anything to draw?
  803. if (toDisplay.w) {
  804. SDL_SetClipRect(destSurface, &toDisplay);
  805. xOffset += x;
  806. yOffset += y;
  807. // This widget attempts to only redraw lines within the
  808. // dirtied area.
  809. // Background
  810. SDL_FillRect(destSurface, &toDisplay, guiPacked[COLOR_TEXTBOX]);
  811. // Draw all lines we can see
  812. int pos = (toDisplay.y - yOffset - GUI_EDITBOX_INNERMARGIN) / lineHeight;
  813. if (pos < 0) pos = 0;
  814. if (pos >= vNumLines) pos = vNumLines - 1;
  815. // (add in height - 1 so it rounds up)
  816. int last = (toDisplay.y + toDisplay.h - yOffset - GUI_EDITBOX_INNERMARGIN + lineHeight - 1) / lineHeight;
  817. if (last < 0) last = 0;
  818. if (last >= vNumLines) last = vNumLines - 1;
  819. // Need an iterator pointing at first line
  820. list<VLine>::iterator spot = findVLine(pos);
  821. for (; pos <= last; ++pos, ++spot) {
  822. VLine& vl = *spot;
  823. int drewText = 0;
  824. if (haveFocus) {
  825. if ((isSelect) && (pos >= selectBegin.vRow) && (pos <= selectEnd.vRow)) {
  826. int beginX = pos == selectBegin.vRow ? selectBegin.pixelX : 0;
  827. int endX = pos == selectEnd.vRow ? selectEnd.pixelX : vLongestLength;
  828. // (can have an "invisible" selection at end or beginning of a row)
  829. if (endX > beginX) {
  830. int beginCol = pos == selectBegin.vRow ? selectBegin.column : 0;
  831. int endCol = pos == selectEnd.vRow ? selectEnd.column : vl.length;
  832. if ((beginCol > 0) || (endCol < vl.length)) {
  833. // Paint all text, to show unselected area
  834. paintText(vl.getStr(), *vl.dLineI, vl.colStart,
  835. xOffset + GUI_EDITBOX_INNERMARGIN + GUI_EDITBOX_LEFTPAD,
  836. lineHeight * pos + yOffset + GUI_EDITBOX_INNERMARGIN, destSurface);
  837. }
  838. drawGradient(xOffset + GUI_EDITBOX_INNERMARGIN + GUI_EDITBOX_LEFTPAD + beginX,
  839. lineHeight * pos + yOffset + GUI_EDITBOX_INNERMARGIN,
  840. endX - beginX, lineHeight,
  841. guiRGB[COLOR_SELECTION1], guiRGB[COLOR_SELECTION2], destSurface);
  842. // Overwrite selected text
  843. paintText((*vl.dLineI).substr(vl.colStart + beginCol, endCol - beginCol),
  844. *vl.dLineI, vl.colStart + beginCol,
  845. xOffset + GUI_EDITBOX_INNERMARGIN + GUI_EDITBOX_LEFTPAD + beginX,
  846. lineHeight * pos + yOffset + GUI_EDITBOX_INNERMARGIN, destSurface, 1);
  847. drewText = 1;
  848. }
  849. }
  850. }
  851. // Paint text if didn't do during selection
  852. if (!drewText) {
  853. paintText(vl.getStr(), *vl.dLineI, vl.colStart,
  854. xOffset + GUI_EDITBOX_INNERMARGIN + GUI_EDITBOX_LEFTPAD,
  855. lineHeight * pos + yOffset + GUI_EDITBOX_INNERMARGIN, destSurface);
  856. }
  857. if ((haveFocus) && (insertBlink) && (insertion.vRow == pos)) {
  858. // Insertion point
  859. drawRect(xOffset + GUI_EDITBOX_INNERMARGIN + GUI_EDITBOX_LEFTPAD + insertion.pixelX - 1,
  860. lineHeight * pos + yOffset + GUI_EDITBOX_INNERMARGIN + insertYOffset,
  861. GUI_EDITBOX_INSERTPOINTWIDTH, insertHeight,
  862. guiPacked[COLOR_CURSOR], destSurface);
  863. }
  864. }
  865. }
  866. }
  867. }
  868. Window::CommandSupport EditBox::supportsCommand(int code) const { start_func
  869. switch (code) {
  870. case EDIT_COPY:
  871. case EDIT_CUT:
  872. if (isSelect) return Window::COMMAND_ENABLE;
  873. return Window::COMMAND_DISABLE;
  874. case EDIT_PASTE:
  875. if (canConvertClipboard(CLIPBOARD_TEXT_MULTI)) return Window::COMMAND_ENABLE;
  876. return Window::COMMAND_DISABLE;
  877. case EDIT_SELECTALL:
  878. return Window::COMMAND_ENABLE;
  879. }
  880. return Window::COMMAND_HIDE;
  881. }
  882. void EditBox::addTo(Dialog* dialog, int showWidth, int showLines) { start_func
  883. assert(!myFrame);
  884. assert(!myScroll);
  885. assert(dialog);
  886. // With this set, when we get a resize() (from our parents, below) we'll
  887. // initialize everything automatically
  888. wrappedTo = -1;
  889. // Set to standard size
  890. if (showWidth <= 0) {
  891. string standard(GUI_EDITBOX_DEFAULTWIDTH, 'X');
  892. showWidth = fontWidth(standard, font);
  893. }
  894. if (showLines <= 0) {
  895. showLines = GUI_EDITBOX_DEFAULTLINES;
  896. }
  897. myScroll = new WidgetScroll(WidgetScroll::FRAMETYPE_BEVEL, this, showWidth, showLines * lineHeight);
  898. dialog->addWidget(myScroll);
  899. // Just in case parents didn't resize us, this triggers initialization
  900. prepOpen();
  901. }
  902. EditBox::TextPoint EditBox::insertWhere(int x, int y) { start_func
  903. // Determine row
  904. int row = (y - GUI_EDITBOX_INNERMARGIN) / lineHeight;
  905. if (row < 0) row = 0;
  906. if (row >= vNumLines) row = vNumLines - 1;
  907. return insertWhereRow(x, row);
  908. }
  909. EditBox::TextPoint EditBox::insertWhereRow(int x, int row) { start_func
  910. TextPoint result;
  911. result.vRow = row;
  912. // Adjust by insertion point width for a bit of a fudge
  913. x -= GUI_EDITBOX_INNERMARGIN + GUI_EDITBOX_LEFTPAD - GUI_EDITBOX_INSERTPOINTWIDTH;
  914. result.column = breakLineBefore((*findVLine(row)).getStr(), x);
  915. return result;
  916. }
  917. int EditBox::breakLineBefore(const string& line, int xPixel) { start_func
  918. // (special case of 0 pixel would automatically drop to 0 during algorithm,
  919. // but less-than-0 needs to be covered anyways)
  920. if (xPixel <= 0) return 0;
  921. // Start at end of line (special case)
  922. int widthPixels = fontWidth(line, font);
  923. int widthChars = line.size();
  924. if (widthPixels <= xPixel) return widthChars;
  925. // 1 = too long, -1 = too short
  926. int lastResult = 1;
  927. while (1) {
  928. // Amount we're off in pixels (negative = move left)
  929. int changePixels = xPixel - widthPixels;
  930. if (!changePixels) changePixels = 1;
  931. // Estimated change in characters
  932. int changeChars = changePixels * widthChars / widthPixels;
  933. // Change must be non-zero
  934. if (!changeChars) changeChars = changePixels > 0 ? 1 : -1;
  935. widthChars += changeChars;
  936. // Result (1 too long, -1 too short)
  937. widthPixels = fontWidth(line.substr(0, widthChars), font);
  938. int thisResult = widthPixels <= xPixel ? -1 : 1;
  939. // We're done when the result is "too short", previous result was "too long",
  940. // and we just moved a single character back
  941. if ((thisResult < lastResult) && (changeChars == -1)) return widthChars;
  942. lastResult = thisResult;
  943. }
  944. }
  945. void EditBox::keyBackspace() { start_func
  946. if (!isSelect) {
  947. TextPoint point = insertion;
  948. // If only spaces to our left and non-space to our right, unindent
  949. string line = (*findVLine(point.vRow)).getStr();
  950. string::size_type pos = line.find_first_not_of(' ', 0);
  951. if ((point.column > 0) && (((int)pos == point.column) || ((pos == string::npos) && (point.column == (int)line.size())))) {
  952. // Go left to first multiple of TAB_SIZE
  953. goLeft(point);
  954. while (point.column % TAB_SIZE) {
  955. goLeft(point);
  956. }
  957. }
  958. else {
  959. // Go left one character
  960. goLeft(point);
  961. }
  962. insertionState(point, 1);
  963. }
  964. pasteText(blankString);
  965. }
  966. void EditBox::keyDelete() { start_func
  967. if (!isSelect) {
  968. TextPoint point = insertion;
  969. // If right-most character, delete all spaces at beginning of next line as well
  970. string line = (*findVLine(point.vRow)).getStr();
  971. if (point.column == (int)line.size()) {
  972. // Go right until a non-space or end of next line
  973. goRight(point);
  974. string line = (*findVLine(point.vRow)).getStr();
  975. int max = line.size();
  976. while ((point.column < max) && (line[point.column] == ' ')) {
  977. goRight(point);
  978. }
  979. }
  980. else {
  981. // Go right one character
  982. goRight(point);
  983. }
  984. insertionState(point, 1);
  985. }
  986. pasteText(blankString);
  987. }
  988. void EditBox::modifyDLine(int dLine) { start_func
  989. int erased, added, modLine;
  990. eraseLineMap(dLine, 1, &modLine, &erased);
  991. insertLineMap(dLine, 1, NULL, &added);
  992. int change = erased - added;
  993. if ((change) && (insertion.vRow >= dLine)) {
  994. // Scroll to ensure same view
  995. if (myFrame) {
  996. myFrame->scrollBy(0, change * lineHeight);
  997. }
  998. if (myScroll) {
  999. myScroll->scrollBy(0, change * lineHeight);
  1000. }
  1001. }
  1002. // Resize
  1003. resize(GUI_EDITBOX_INNERMARGIN * 2 + GUI_EDITBOX_LEFTPAD + GUI_EDITBOX_RIGHTPAD + vLongestLength,
  1004. GUI_EDITBOX_INNERMARGIN * 2 + vNumLines * lineHeight);
  1005. if (added == erased)
  1006. setDirtyRange(modLine, modLine + added - 1);
  1007. else
  1008. setDirtyRange(modLine, vNumLines - 1);
  1009. }
  1010. void EditBox::doIndent(int dLine, int count, int function) { start_func
  1011. // Clip range
  1012. if (dLine < 0) {
  1013. count += dLine;
  1014. dLine = 0;
  1015. }
  1016. if (dLine + count > dNumLines)
  1017. count = dNumLines - dLine;
  1018. if (count <= 0)
  1019. return;
  1020. contentModifiedPre(EDITBOX_MODIFY_LINE, dLine, count);
  1021. contentModifiedPre(EDITBOX_MODIFY_DONE, 0, 0);
  1022. vLineToDLineAllPoints();
  1023. list<VLine>::iterator vl = findDLine(dLine);
  1024. assert(vl != lineMap.end());
  1025. list<string>::iterator dl = (*vl).dLineI;
  1026. int dlNum = dLine;
  1027. debugStdout("start");
  1028. do {
  1029. assert(dl != contents->end());
  1030. string& mod = *dl;
  1031. int trim;
  1032. if (function) {
  1033. // Indent
  1034. mod = string(TAB_SIZE, ' ') + mod;
  1035. trim = TAB_SIZE;
  1036. }
  1037. else {
  1038. // Unindent
  1039. trim = mod.find_first_not_of(' ', 0);
  1040. if (trim == (int)string::npos)
  1041. trim = mod.size();
  1042. trim = min((int)TAB_SIZE, trim);
  1043. mod = mod.substr(trim, string::npos);
  1044. trim = -trim;
  1045. }
  1046. modifyDLine(dlNum);
  1047. // (affects dline position before converting back to vlines)
  1048. if (insertion.vRow == dlNum) insertion.column = max(0, insertion.column + trim);
  1049. if (selectBegin.vRow == dlNum) selectBegin.column = max(0, selectBegin.column + trim);
  1050. if (selectEnd.vRow == dlNum) selectEnd.column = max(0, selectEnd.column + trim);
  1051. ++dl;
  1052. ++dlNum;
  1053. } while (--count);
  1054. dLineToVLineAllPoints();
  1055. contentModifiedPost(EDITBOX_MODIFY_LINE, dLine, count);
  1056. contentModifiedPost(EDITBOX_MODIFY_DONE, 0, 0);
  1057. }
  1058. void EditBox::indent(int dLine, int count) { start_func
  1059. doIndent(dLine, count, 1);
  1060. }
  1061. void EditBox::unindent(int dLine, int count) { start_func
  1062. doIndent(dLine, count, 0);
  1063. }
  1064. void EditBox::getCursorData(TextPoint& ins, TextPoint& selBegin, TextPoint& selEnd) const { start_func
  1065. ins = insertion;
  1066. if (isSelect) {
  1067. selBegin = selectBegin;
  1068. selEnd = selectEnd;
  1069. }
  1070. else {
  1071. selBegin = selEnd = ins;
  1072. }
  1073. }
  1074. void EditBox::setCursorData(TextPoint& ins, TextPoint& selBegin, TextPoint& selEnd) { start_func
  1075. if ((selBegin.vRow != selEnd.vRow) || (selBegin.column != selEnd.column)) {
  1076. if ((selBegin.vRow == ins.vRow) && (selBegin.column == ins.column)) {
  1077. insertionState(selEnd, 0);
  1078. insertionState(selBegin, 1);
  1079. }
  1080. else if ((selEnd.vRow == ins.vRow) && (selEnd.column == ins.column)) {
  1081. insertionState(selBegin, 0);
  1082. insertionState(selEnd, 1);
  1083. }
  1084. else {
  1085. insertionState(ins, 0);
  1086. }
  1087. }
  1088. else {
  1089. insertionState(ins, 0);
  1090. }
  1091. }
  1092. const EditBox::TextPoint& EditBox::insertionState() const { start_func
  1093. return insertion;
  1094. }
  1095. const EditBox::TextPoint& EditBox::insertionState(TextPoint& newPos, int drag) { start_func
  1096. // Clip
  1097. if (newPos.vRow < 0) newPos.vRow = 0;
  1098. if (newPos.vRow >= vNumLines) newPos.vRow = vNumLines - 1;
  1099. VLine& vl = *findVLine(newPos.vRow);
  1100. if (newPos.column < 0) newPos.column = 0;
  1101. if (newPos.column > vl.length) newPos.column = vl.length;
  1102. // First, find correct pixelX
  1103. newPos.pixelX = fontWidth((*vl.dLineI).substr(vl.colStart, newPos.column), font);
  1104. // Drag?
  1105. if (drag) {
  1106. int vDirtyFirst;
  1107. int vDirtyLast;
  1108. // No current selection?
  1109. if (!isSelect) {
  1110. selectBegin = selectEnd = insertion;
  1111. }
  1112. // If selection beginning matches insert point
  1113. if ((selectBegin.vRow == insertion.vRow) && (selectBegin.column == insertion.column)) {
  1114. // Drag beginning point
  1115. vDirtyFirst = selectBegin.vRow;
  1116. vDirtyLast = newPos.vRow;
  1117. selectBegin = insertion = newPos;
  1118. }
  1119. else {
  1120. // Drag end point
  1121. vDirtyFirst = selectEnd.vRow;
  1122. vDirtyLast = newPos.vRow;
  1123. selectEnd = insertion = newPos;
  1124. }
  1125. // No selection?
  1126. if ((selectBegin.vRow == selectEnd.vRow) && (selectBegin.column == selectEnd.column)) {
  1127. isSelect = 0;
  1128. }
  1129. else {
  1130. isSelect = 1;
  1131. // Ensure in proper order
  1132. if ((selectEnd.vRow < selectBegin.vRow) ||
  1133. ((selectEnd.vRow == selectBegin.vRow) &&
  1134. (selectEnd.column < selectBegin.column))) {
  1135. swap(selectBegin, selectEnd);
  1136. }
  1137. }
  1138. // Dirty
  1139. if (vDirtyFirst > vDirtyLast) swap(vDirtyFirst, vDirtyLast);
  1140. setDirtyRange(vDirtyFirst, vDirtyLast);
  1141. }
  1142. else {
  1143. // Dirty previous
  1144. if (isSelect) setDirtyRange(selectBegin.vRow, selectEnd.vRow);
  1145. else setDirtyRange(insertion.vRow, insertion.vRow);
  1146. // Reset selection
  1147. isSelect = 0;
  1148. // Insertion point
  1149. insertion = newPos;
  1150. }
  1151. // Scroll
  1152. int scrollLeft = insertion.pixelX + GUI_EDITBOX_INNERMARGIN + GUI_EDITBOX_LEFTPAD - 1 - GUI_EDITBOX_SCROLLAHEAD;
  1153. if (scrollLeft < 0) scrollLeft = 0;
  1154. if (myFrame) {
  1155. myFrame->scrollToView(scrollLeft,
  1156. insertion.vRow * lineHeight + GUI_EDITBOX_INNERMARGIN,
  1157. GUI_EDITBOX_INSERTPOINTWIDTH + GUI_EDITBOX_SCROLLAHEAD * 2, lineHeight);
  1158. }
  1159. if (myScroll) {
  1160. myScroll->scrollToView(scrollLeft,
  1161. insertion.vRow * lineHeight + GUI_EDITBOX_INNERMARGIN,
  1162. GUI_EDITBOX_INSERTPOINTWIDTH + GUI_EDITBOX_SCROLLAHEAD * 2, lineHeight);
  1163. }
  1164. // (dirties current row)
  1165. setDirtyAndBlink();
  1166. return insertion;
  1167. }
  1168. void EditBox::selectAll() { start_func
  1169. // First point
  1170. TextPoint point;
  1171. point.vRow = 0;
  1172. point.column = 0;
  1173. insertionState(point, 0);
  1174. // Last point
  1175. point.vRow = vNumLines - 1;
  1176. point.column = (*findVLine(point.vRow)).length;
  1177. insertionState(point, 1);
  1178. }
  1179. void EditBox::contentModifyHelper(int vLine, int vCount) { start_func
  1180. // vcount can be positive (inserted lines) or negative (removed lines)
  1181. // in addition, any line can have been modified 'in place'
  1182. TextPoint newInsertion = insertion;
  1183. TextPoint newBegin = selectBegin;
  1184. TextPoint newEnd = selectEnd;
  1185. // Insertion
  1186. if (vCount > 0) {
  1187. if (newInsertion.vRow >= vLine) {
  1188. newInsertion.vRow += vCount;
  1189. // Scroll to ensure same view
  1190. if (myFrame) {
  1191. myFrame->scrollBy(0, -vCount * lineHeight);
  1192. }
  1193. if (myScroll) {
  1194. myScroll->scrollBy(0, -vCount * lineHeight);
  1195. }
  1196. }
  1197. if (isSelect) {
  1198. if (newBegin.vRow >= vLine) {
  1199. newBegin.vRow += vCount;
  1200. }
  1201. if (newEnd.vRow >= vLine) {
  1202. newEnd.vRow += vCount;
  1203. }
  1204. }
  1205. }
  1206. // Deletion
  1207. else if (vCount < 0) {
  1208. if (newInsertion.vRow >= vLine) {
  1209. if (newInsertion.vRow >= vLine - vCount) {
  1210. // past affected area
  1211. newInsertion.vRow += vCount;
  1212. // Scroll to ensure same view
  1213. if (myFrame) {
  1214. myFrame->scrollBy(0, -vCount * lineHeight);
  1215. }
  1216. if (myScroll) {
  1217. myScroll->scrollBy(0, -vCount * lineHeight);
  1218. }
  1219. }
  1220. else {
  1221. // within affected area
  1222. newInsertion.vRow = vLine;
  1223. newInsertion.column = 0;
  1224. if (newInsertion.vRow >= vNumLines) {
  1225. newInsertion.vRow = vNumLines - 1;
  1226. newInsertion.column = 99999;
  1227. }
  1228. }
  1229. }
  1230. if (isSelect) {
  1231. if (newBegin.vRow >= vLine) {
  1232. if (newBegin.vRow >= vLine - vCount) {
  1233. // past affected area
  1234. newBegin.vRow += vCount;
  1235. }
  1236. else {
  1237. // within affected area
  1238. newBegin.vRow = vLine;
  1239. newBegin.column = 0;
  1240. if (newBegin.vRow >= vNumLines) {
  1241. newBegin.vRow = vNumLines - 1;
  1242. newBegin.column = MAX_LINELENGTH + 1;
  1243. }
  1244. }
  1245. }
  1246. if (newEnd.vRow >= vLine) {
  1247. if (newEnd.vRow >= vLine - vCount) {
  1248. // past affected area
  1249. newEnd.vRow += vCount;
  1250. }
  1251. else {
  1252. // within affected area
  1253. newEnd.vRow = vLine;
  1254. newEnd.column = 0;
  1255. if (newEnd.vRow >= vNumLines) {
  1256. newEnd.vRow = vNumLines - 1;
  1257. newEnd.column = MAX_LINELENGTH + 1;
  1258. }
  1259. }
  1260. }
  1261. }
  1262. }
  1263. // Verify columns
  1264. int len = (*findVLine(newInsertion.vRow)).length;
  1265. if (len < newInsertion.column) newInsertion.column = len;
  1266. if (isSelect) {
  1267. len = (*findVLine(newBegin.vRow)).length;
  1268. if (len < newBegin.column) newBegin.column = len;
  1269. len = (*findVLine(newEnd.vRow)).length;
  1270. if (len < newEnd.column) newEnd.column = len;
  1271. }
  1272. // Resize
  1273. resize(GUI_EDITBOX_INNERMARGIN * 2 + GUI_EDITBOX_LEFTPAD + GUI_EDITBOX_RIGHTPAD + vLongestLength,
  1274. GUI_EDITBOX_INNERMARGIN * 2 + vNumLines * lineHeight);
  1275. // Set new positions
  1276. if (isSelect) {
  1277. if ((newBegin.column == newInsertion.column) && (newBegin.vRow == newInsertion.vRow))
  1278. insertionState(newEnd, 0);
  1279. else
  1280. insertionState(newBegin, 0);
  1281. insertionState(newInsertion, 1);
  1282. }
  1283. else
  1284. insertionState(newInsertion, 0);
  1285. }
  1286. void EditBox::contentModify(int dLine, int countLines) { start_func
  1287. int modLine, erased, added;
  1288. eraseLineMap(dLine, countLines, &modLine, &erased);
  1289. insertLineMap(dLine, countLines, &modLine, &added);
  1290. contentModifyHelper(modLine + min(added, erased), added - erased);
  1291. if (added == erased)
  1292. setDirtyRange(modLine, modLine + added - 1);
  1293. else
  1294. setDirtyRange(modLine, vNumLines - 1);
  1295. }
  1296. void EditBox::contentInsert(int dLine, int countLines) { start_func
  1297. int modLine, added;
  1298. insertLineMap(dLine, countLines, &modLine, &added);
  1299. contentModifyHelper(modLine, added);
  1300. setDirtyRange(modLine, vNumLines - 1);
  1301. }
  1302. void EditBox::contentDelete(int dLine, int countLines) { start_func
  1303. int modLine, erased;
  1304. eraseLineMap(dLine, countLines, &modLine, &erased);
  1305. contentModifyHelper(modLine, -erased);
  1306. setDirtyRange(modLine, vNumLines - 1);
  1307. }
  1308. void EditBox::pasteText(const string& text) { start_func
  1309. // Most of our work is done on dlines, not vlines
  1310. VLine* insertionVL = &*findVLine(insertion.vRow);
  1311. VLine* beginVL = &*findVLine(isSelect ? selectBegin.vRow : insertion.vRow);
  1312. VLine* endVL = &*findVLine(isSelect ? selectEnd.vRow : insertion.vRow);
  1313. // Count lines being inserted
  1314. string::size_type spos = 0;
  1315. int insertedDLines = 0;
  1316. while ((spos = text.find_first_of('\n', spos)) != string::npos) {
  1317. ++insertedDLines;
  1318. ++spos;
  1319. }
  1320. // Start by alerting to incoming modifications
  1321. int r1 = -1, r2 = 0, i1 = -1, m1;
  1322. if (beginVL->dLine != endVL->dLine) contentModifiedPre(EDITBOX_REMOVE_LINES, r1 = beginVL->dLine + 1, r2 = endVL->dLine - beginVL->dLine);
  1323. if (insertedDLines) contentModifiedPre(EDITBOX_INSERT_LINES, i1 = beginVL->dLine + 1, insertedDLines);
  1324. contentModifiedPre(EDITBOX_MODIFY_LINE, m1 = beginVL->dLine, 1);
  1325. contentModifiedPre(EDITBOX_MODIFY_DONE, 0, 0);
  1326. // First, clear selection, if any
  1327. if (isSelect) {
  1328. list<string>::iterator pos = beginVL->dLineI;
  1329. list<string>::iterator end = endVL->dLineI;
  1330. // One row?
  1331. if (pos == end) {
  1332. (*pos).erase(beginVL->colStart + selectBegin.column, endVL->colStart + selectEnd.column - beginVL->colStart - selectBegin.column);
  1333. }
  1334. else {
  1335. list<string>::iterator first = pos;
  1336. // To end of first line
  1337. (*pos).erase(beginVL->colStart + selectBegin.column, (*pos).size());
  1338. // Lines in between
  1339. ++pos;
  1340. list<string>::iterator next = pos;
  1341. for (; pos != end; pos = next) {
  1342. ++next;
  1343. contents->erase(pos);
  1344. }
  1345. // Last line- start to middle
  1346. (*pos).erase(0, endVL->colStart + selectEnd.column);
  1347. // Combine first and last lines
  1348. // Combine
  1349. *first += *pos;
  1350. contents->erase(pos);
  1351. }
  1352. // selectBegin is still entirely valid
  1353. insertion = selectBegin;
  1354. insertionVL = beginVL;
  1355. isSelect = 0;
  1356. }
  1357. // Insert
  1358. spos = 0;
  1359. string::size_type prev = spos;
  1360. int insertAt = insertionVL->colStart + insertion.column;
  1361. list<string>::iterator insertStr = insertionVL->dLineI;
  1362. // Split at every \n
  1363. int inserted = 0;
  1364. while ((spos = text.find_first_of('\n', spos)) != string::npos) {
  1365. // Insert new line containing text after insertion point
  1366. list<string>::iterator nextStr = insertStr;
  1367. ++nextStr;
  1368. contents->insert(nextStr, (*insertStr).substr(insertAt, string::npos));
  1369. // Delete text we moved forward
  1370. (*insertStr).erase(insertAt, string::npos);
  1371. // Insert text
  1372. (*insertStr).insert(insertAt, text.substr(prev, spos - prev));
  1373. // Move insertion point to beginning of next (newly added) line
  1374. insertAt = 0;
  1375. ++insertStr;
  1376. ++spos;
  1377. prev = spos;
  1378. ++inserted;
  1379. }
  1380. // One final portion to insert
  1381. int lenToInsert = text.substr(prev, string::npos).size();
  1382. (*insertStr).insert(insertAt, text.substr(prev, lenToInsert));
  1383. // (will disappear after calling erase)
  1384. int iVLdL = insertionVL->dLine;
  1385. // Handles modifying both numlines and linemap
  1386. int eCount;
  1387. eraseLineMap(beginVL->dLine, endVL->dLine - beginVL->dLine + 1, NULL, &eCount);
  1388. // Handles longest length, modifying both numlines and linemap
  1389. int iStart, iCount;
  1390. insertLineMap(iVLdL, inserted + 1, &iStart, &iCount);
  1391. // Resize
  1392. resize(GUI_EDITBOX_INNERMARGIN * 2 + GUI_EDITBOX_LEFTPAD + GUI_EDITBOX_RIGHTPAD + vLongestLength,
  1393. GUI_EDITBOX_INNERMARGIN * 2 + vNumLines * lineHeight);
  1394. // Move insertion point
  1395. TextPoint newinsert = insertion;
  1396. newinsert.column = insertAt + lenToInsert;
  1397. newinsert.vRow = iStart + iCount - 1;
  1398. insertionVL = &*findVLine(newinsert.vRow);
  1399. while (newinsert.column < insertionVL->colStart) {
  1400. insertionVL = &*findVLine(--newinsert.vRow);
  1401. }
  1402. newinsert.column -= insertionVL->colStart;
  1403. insertionState(newinsert);
  1404. // Alert to finished modifications
  1405. if (r1 >= 0) contentModifiedPost(EDITBOX_REMOVE_LINES, r1, r2);
  1406. if (i1 >= 0) contentModifiedPost(EDITBOX_INSERT_LINES, i1, insertedDLines);
  1407. contentModifiedPost(EDITBOX_MODIFY_LINE, m1, 1);
  1408. contentModifiedPost(EDITBOX_MODIFY_DONE, 0, 0);
  1409. // dirty
  1410. if (eCount == iCount)
  1411. setDirtyRange(iStart, iStart + eCount - 1);
  1412. else
  1413. setDirtyRange(iStart, vNumLines - 1);
  1414. }
  1415. void EditBox::copyText(string& buffer) { start_func
  1416. if (isSelect) {
  1417. VLine* beginVL = &*findVLine(selectBegin.vRow);
  1418. VLine* endVL = &*findVLine(selectEnd.vRow);
  1419. list<string>::iterator pos = beginVL->dLineI;
  1420. list<string>::iterator end = endVL->dLineI;
  1421. // One row?
  1422. if (pos == end) {
  1423. buffer = (*pos).substr(beginVL->colStart + selectBegin.column, endVL->colStart + selectEnd.column - beginVL->colStart - selectBegin.column);
  1424. }
  1425. else {
  1426. // To end of first line
  1427. buffer = (*pos).substr(beginVL->colStart + selectBegin.column, string::npos);
  1428. // Lines in between
  1429. for (++pos; pos != end; ++pos) {
  1430. buffer += "\n";
  1431. buffer += *pos;
  1432. }
  1433. // Last line- start to middle
  1434. buffer += "\n";
  1435. buffer += (*pos).substr(0, endVL->colStart + selectEnd.column);
  1436. }
  1437. }
  1438. else {
  1439. buffer = blankString;
  1440. }
  1441. }
  1442. Window::WindowType EditBox::windowType() const { start_func
  1443. return WINDOW_CLIENT;
  1444. }
  1445. void EditBox::contentModifiedPre(ContentChangeType type, int firstRow, int numRows) { start_func
  1446. // (default does nothing)
  1447. }
  1448. void EditBox::contentModifiedPost(ContentChangeType type, int firstRow, int numRows) { start_func
  1449. // (default does nothing)
  1450. }
  1451. void EditBox::updateTitlebar() { start_func
  1452. if (myFrame) {
  1453. myFrame->setTitle("(no title)");
  1454. }
  1455. }
  1456. FrameWindow* EditBox::runWindowed() { start_func
  1457. assert(!myScroll);
  1458. // Prevent duplication
  1459. if (myFrame) {
  1460. desktop->bringToTop(myFrame);
  1461. return myFrame;
  1462. }
  1463. // With this set, when we get a resize() (from our parents, below) we'll
  1464. // initialize everything automatically
  1465. wrappedTo = -1;
  1466. // We remember the frame pointer even though it'll delete itself
  1467. myFrame = new FrameWindow(blankString, FrameWindow::RESIZING_NORMAL, FrameWindow::FRAMETYPE_BEVEL_TEXT, this);
  1468. open = 1;
  1469. // Cascade
  1470. myFrame->show(FrameWindow::SHOW_CASCADE, FrameWindow::SHOW_CASCADE, FrameWindow::SHOW_CASCADE, FrameWindow::SHOW_CASCADE);
  1471. // Just in case parents didn't resize us, this triggers initialization
  1472. prepOpen();
  1473. // Scroll to cursor
  1474. insertionState(insertion, isSelect);
  1475. updateTitlebar();
  1476. return myFrame;
  1477. }