GTextEditor.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074
  1. #include <AK/StringBuilder.h>
  2. #include <Kernel/KeyCode.h>
  3. #include <LibGUI/GAction.h>
  4. #include <LibGUI/GClipboard.h>
  5. #include <LibGUI/GFontDatabase.h>
  6. #include <LibGUI/GMenu.h>
  7. #include <LibGUI/GPainter.h>
  8. #include <LibGUI/GScrollBar.h>
  9. #include <LibGUI/GTextEditor.h>
  10. #include <LibGUI/GWindow.h>
  11. #include <ctype.h>
  12. #include <fcntl.h>
  13. #include <stdio.h>
  14. #include <unistd.h>
  15. GTextEditor::GTextEditor(Type type, GWidget* parent)
  16. : GScrollableWidget(parent)
  17. , m_type(type)
  18. {
  19. set_frame_shape(FrameShape::Container);
  20. set_frame_shadow(FrameShadow::Sunken);
  21. set_frame_thickness(2);
  22. set_scrollbars_enabled(is_multi_line());
  23. set_font(GFontDatabase::the().get_by_name("Csilla Thin"));
  24. // FIXME: Recompute vertical scrollbar step size on font change.
  25. vertical_scrollbar().set_step(line_height());
  26. m_lines.append(make<Line>());
  27. m_cursor = { 0, 0 };
  28. create_actions();
  29. }
  30. GTextEditor::~GTextEditor()
  31. {
  32. }
  33. void GTextEditor::create_actions()
  34. {
  35. m_undo_action = GAction::create("Undo", { Mod_Ctrl, Key_Z }, GraphicsBitmap::load_from_file("/res/icons/16x16/undo.png"), [&](const GAction&) {
  36. // FIXME: Undo
  37. },
  38. this);
  39. m_redo_action = GAction::create("Redo", { Mod_Ctrl, Key_Y }, GraphicsBitmap::load_from_file("/res/icons/16x16/redo.png"), [&](const GAction&) {
  40. // FIXME: Redo
  41. },
  42. this);
  43. m_cut_action = GAction::create("Cut", { Mod_Ctrl, Key_X }, GraphicsBitmap::load_from_file("/res/icons/cut16.png"), [&](const GAction&) {
  44. cut();
  45. },
  46. this);
  47. m_copy_action = GAction::create("Copy", { Mod_Ctrl, Key_C }, GraphicsBitmap::load_from_file("/res/icons/16x16/edit-copy.png"), [&](const GAction&) {
  48. copy();
  49. },
  50. this);
  51. m_paste_action = GAction::create("Paste", { Mod_Ctrl, Key_V }, GraphicsBitmap::load_from_file("/res/icons/paste16.png"), [&](const GAction&) {
  52. paste();
  53. },
  54. this);
  55. m_delete_action = GAction::create("Delete", { 0, Key_Delete }, GraphicsBitmap::load_from_file("/res/icons/16x16/delete.png"), [&](const GAction&) {
  56. do_delete();
  57. },
  58. this);
  59. }
  60. void GTextEditor::set_text(const StringView& text)
  61. {
  62. if (is_single_line() && text.length() == m_lines[0]->length() && !memcmp(text.characters_without_null_termination(), m_lines[0]->characters(), text.length()))
  63. return;
  64. m_selection.clear();
  65. m_lines.clear();
  66. int start_of_current_line = 0;
  67. auto add_line = [&](int current_position) {
  68. int line_length = current_position - start_of_current_line;
  69. auto line = make<Line>();
  70. if (line_length)
  71. line->set_text(text.substring_view(start_of_current_line, current_position - start_of_current_line));
  72. m_lines.append(move(line));
  73. start_of_current_line = current_position + 1;
  74. };
  75. int i = 0;
  76. for (i = 0; i < text.length(); ++i) {
  77. if (text[i] == '\n')
  78. add_line(i);
  79. }
  80. add_line(i);
  81. update_content_size();
  82. if (is_single_line())
  83. set_cursor(0, m_lines[0]->length());
  84. else
  85. set_cursor(0, 0);
  86. did_update_selection();
  87. update();
  88. }
  89. void GTextEditor::update_content_size()
  90. {
  91. int content_width = 0;
  92. for (auto& line : m_lines)
  93. content_width = max(line->width(font()), content_width);
  94. content_width += m_horizontal_content_padding * 2;
  95. if (is_right_text_alignment(m_text_alignment))
  96. content_width = max(frame_inner_rect().width(), content_width);
  97. int content_height = line_count() * line_height();
  98. set_content_size({ content_width, content_height });
  99. set_size_occupied_by_fixed_elements({ ruler_width(), 0 });
  100. }
  101. GTextPosition GTextEditor::text_position_at(const Point& a_position) const
  102. {
  103. auto position = a_position;
  104. position.move_by(horizontal_scrollbar().value(), vertical_scrollbar().value());
  105. position.move_by(-(m_horizontal_content_padding + ruler_width()), 0);
  106. position.move_by(-frame_thickness(), -frame_thickness());
  107. int line_index = position.y() / line_height();
  108. line_index = max(0, min(line_index, line_count() - 1));
  109. int column_index;
  110. switch (m_text_alignment) {
  111. case TextAlignment::CenterLeft:
  112. column_index = (position.x() + glyph_width() / 2) / glyph_width();
  113. break;
  114. case TextAlignment::CenterRight:
  115. column_index = (position.x() - content_x_for_position({ line_index, 0 }) + glyph_width() / 2) / glyph_width();
  116. break;
  117. default:
  118. ASSERT_NOT_REACHED();
  119. }
  120. column_index = max(0, min(column_index, m_lines[line_index]->length()));
  121. return { line_index, column_index };
  122. }
  123. void GTextEditor::doubleclick_event(GMouseEvent& event)
  124. {
  125. if (event.button() != GMouseButton::Left)
  126. return;
  127. m_triple_click_timer.start();
  128. m_in_drag_select = false;
  129. auto start = text_position_at(event.position());
  130. auto end = start;
  131. auto& line = *m_lines[start.line()];
  132. while (start.column() > 0) {
  133. if (isspace(line.characters()[start.column() - 1]))
  134. break;
  135. start.set_column(start.column() - 1);
  136. }
  137. while (end.column() < line.length()) {
  138. if (isspace(line.characters()[end.column()]))
  139. break;
  140. end.set_column(end.column() + 1);
  141. }
  142. m_selection.set(start, end);
  143. set_cursor(end);
  144. update();
  145. did_update_selection();
  146. }
  147. void GTextEditor::mousedown_event(GMouseEvent& event)
  148. {
  149. if (event.button() != GMouseButton::Left) {
  150. return;
  151. }
  152. if (m_triple_click_timer.is_valid() && m_triple_click_timer.elapsed() < 250) {
  153. m_triple_click_timer = CElapsedTimer();
  154. GTextPosition start;
  155. GTextPosition end;
  156. if (is_multi_line()) {
  157. // select *current* line
  158. start = GTextPosition(m_cursor.line(), 0);
  159. end = GTextPosition(m_cursor.line(), m_lines[m_cursor.line()]->length());
  160. } else {
  161. // select *whole* line
  162. start = GTextPosition(0, 0);
  163. end = GTextPosition(line_count() - 1, m_lines[line_count() - 1]->length());
  164. }
  165. m_selection.set(start, end);
  166. set_cursor(end);
  167. return;
  168. }
  169. if (event.modifiers() & Mod_Shift) {
  170. if (!has_selection())
  171. m_selection.set(m_cursor, {});
  172. } else {
  173. m_selection.clear();
  174. }
  175. m_in_drag_select = true;
  176. set_cursor(text_position_at(event.position()));
  177. if (!(event.modifiers() & Mod_Shift)) {
  178. if (!has_selection())
  179. m_selection.set(m_cursor, {});
  180. }
  181. if (m_selection.start().is_valid() && m_selection.start() != m_cursor)
  182. m_selection.set_end(m_cursor);
  183. // FIXME: Only update the relevant rects.
  184. update();
  185. did_update_selection();
  186. }
  187. void GTextEditor::mouseup_event(GMouseEvent& event)
  188. {
  189. if (event.button() == GMouseButton::Left) {
  190. if (m_in_drag_select) {
  191. m_in_drag_select = false;
  192. }
  193. return;
  194. }
  195. }
  196. void GTextEditor::mousemove_event(GMouseEvent& event)
  197. {
  198. if (m_in_drag_select) {
  199. set_cursor(text_position_at(event.position()));
  200. m_selection.set_end(m_cursor);
  201. did_update_selection();
  202. update();
  203. return;
  204. }
  205. }
  206. int GTextEditor::ruler_width() const
  207. {
  208. if (!m_ruler_visible)
  209. return 0;
  210. // FIXME: Resize based on needed space.
  211. return 5 * font().glyph_width('x') + 4;
  212. }
  213. Rect GTextEditor::ruler_content_rect(int line_index) const
  214. {
  215. if (!m_ruler_visible)
  216. return {};
  217. return {
  218. 0 - ruler_width() + horizontal_scrollbar().value(),
  219. line_index * line_height(),
  220. ruler_width(),
  221. line_height()
  222. };
  223. }
  224. void GTextEditor::paint_event(GPaintEvent& event)
  225. {
  226. GFrame::paint_event(event);
  227. GPainter painter(*this);
  228. painter.add_clip_rect(widget_inner_rect());
  229. painter.add_clip_rect(event.rect());
  230. painter.fill_rect(event.rect(), Color::White);
  231. painter.translate(frame_thickness(), frame_thickness());
  232. Rect ruler_rect { 0, 0, ruler_width(), height() - height_occupied_by_horizontal_scrollbar() };
  233. if (m_ruler_visible) {
  234. painter.fill_rect(ruler_rect, Color::WarmGray);
  235. painter.draw_line(ruler_rect.top_right(), ruler_rect.bottom_right(), Color::DarkGray);
  236. }
  237. painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
  238. if (m_ruler_visible)
  239. painter.translate(ruler_width(), 0);
  240. int first_visible_line = text_position_at(event.rect().top_left()).line();
  241. int last_visible_line = text_position_at(event.rect().bottom_right()).line();
  242. auto selection = normalized_selection();
  243. bool has_selection = selection.is_valid();
  244. if (m_ruler_visible) {
  245. for (int i = first_visible_line; i <= last_visible_line; ++i) {
  246. bool is_current_line = i == m_cursor.line();
  247. auto ruler_line_rect = ruler_content_rect(i);
  248. painter.draw_text(
  249. ruler_line_rect.shrunken(2, 0),
  250. String::number(i),
  251. is_current_line ? Font::default_bold_font() : font(),
  252. TextAlignment::CenterRight,
  253. is_current_line ? Color::DarkGray : Color::MidGray);
  254. }
  255. }
  256. painter.add_clip_rect({ m_ruler_visible ? (ruler_rect.right() + frame_thickness() + 1) : frame_thickness(), frame_thickness(), width() - width_occupied_by_vertical_scrollbar() - ruler_width(), height() - height_occupied_by_horizontal_scrollbar() });
  257. for (int i = first_visible_line; i <= last_visible_line; ++i) {
  258. auto& line = *m_lines[i];
  259. auto line_rect = line_content_rect(i);
  260. // FIXME: Make sure we always fill the entire line.
  261. //line_rect.set_width(exposed_width);
  262. if (is_multi_line() && i == m_cursor.line())
  263. painter.fill_rect(line_rect, Color(230, 230, 230));
  264. painter.draw_text(line_rect, StringView(line.characters(), line.length()), m_text_alignment, Color::Black);
  265. bool line_has_selection = has_selection && i >= selection.start().line() && i <= selection.end().line();
  266. if (line_has_selection) {
  267. int selection_start_column_on_line = selection.start().line() == i ? selection.start().column() : 0;
  268. int selection_end_column_on_line = selection.end().line() == i ? selection.end().column() : line.length();
  269. int selection_left = content_x_for_position({ i, selection_start_column_on_line });
  270. int selection_right = content_x_for_position({ i, selection_end_column_on_line });
  271. ;
  272. Rect selection_rect { selection_left, line_rect.y(), selection_right - selection_left, line_rect.height() };
  273. painter.fill_rect(selection_rect, Color::from_rgb(0x955233));
  274. painter.draw_text(selection_rect, StringView(line.characters() + selection_start_column_on_line, line.length() - selection_start_column_on_line - (line.length() - selection_end_column_on_line)), TextAlignment::CenterLeft, Color::White);
  275. }
  276. }
  277. if (is_focused() && m_cursor_state)
  278. painter.fill_rect(cursor_content_rect(), Color::Red);
  279. }
  280. void GTextEditor::toggle_selection_if_needed_for_event(const GKeyEvent& event)
  281. {
  282. if (event.shift() && !m_selection.is_valid()) {
  283. m_selection.set(m_cursor, {});
  284. did_update_selection();
  285. update();
  286. return;
  287. }
  288. if (!event.shift() && m_selection.is_valid()) {
  289. m_selection.clear();
  290. did_update_selection();
  291. update();
  292. return;
  293. }
  294. }
  295. void GTextEditor::select_all()
  296. {
  297. GTextPosition start_of_document { 0, 0 };
  298. GTextPosition end_of_document { line_count() - 1, m_lines[line_count() - 1]->length() };
  299. m_selection.set(start_of_document, end_of_document);
  300. did_update_selection();
  301. set_cursor(end_of_document);
  302. update();
  303. }
  304. void GTextEditor::keydown_event(GKeyEvent& event)
  305. {
  306. if (is_single_line() && event.key() == KeyCode::Key_Tab)
  307. return GWidget::keydown_event(event);
  308. if (is_single_line() && event.key() == KeyCode::Key_Return) {
  309. if (on_return_pressed)
  310. on_return_pressed();
  311. return;
  312. }
  313. if (event.key() == KeyCode::Key_Escape) {
  314. if (on_escape_pressed)
  315. on_escape_pressed();
  316. return;
  317. }
  318. if (event.key() == KeyCode::Key_Up) {
  319. if (m_cursor.line() > 0) {
  320. int new_line = m_cursor.line() - 1;
  321. int new_column = min(m_cursor.column(), m_lines[new_line]->length());
  322. toggle_selection_if_needed_for_event(event);
  323. set_cursor(new_line, new_column);
  324. if (m_selection.start().is_valid()) {
  325. m_selection.set_end(m_cursor);
  326. did_update_selection();
  327. }
  328. }
  329. return;
  330. }
  331. if (event.key() == KeyCode::Key_Down) {
  332. if (m_cursor.line() < (m_lines.size() - 1)) {
  333. int new_line = m_cursor.line() + 1;
  334. int new_column = min(m_cursor.column(), m_lines[new_line]->length());
  335. toggle_selection_if_needed_for_event(event);
  336. set_cursor(new_line, new_column);
  337. if (m_selection.start().is_valid()) {
  338. m_selection.set_end(m_cursor);
  339. did_update_selection();
  340. }
  341. }
  342. return;
  343. }
  344. if (event.key() == KeyCode::Key_PageUp) {
  345. if (m_cursor.line() > 0) {
  346. int new_line = max(0, m_cursor.line() - visible_content_rect().height() / line_height());
  347. int new_column = min(m_cursor.column(), m_lines[new_line]->length());
  348. toggle_selection_if_needed_for_event(event);
  349. set_cursor(new_line, new_column);
  350. if (m_selection.start().is_valid()) {
  351. m_selection.set_end(m_cursor);
  352. did_update_selection();
  353. }
  354. }
  355. return;
  356. }
  357. if (event.key() == KeyCode::Key_PageDown) {
  358. if (m_cursor.line() < (m_lines.size() - 1)) {
  359. int new_line = min(line_count() - 1, m_cursor.line() + visible_content_rect().height() / line_height());
  360. int new_column = min(m_cursor.column(), m_lines[new_line]->length());
  361. toggle_selection_if_needed_for_event(event);
  362. set_cursor(new_line, new_column);
  363. if (m_selection.start().is_valid()) {
  364. m_selection.set_end(m_cursor);
  365. did_update_selection();
  366. }
  367. }
  368. return;
  369. }
  370. if (event.key() == KeyCode::Key_Left) {
  371. if (m_cursor.column() > 0) {
  372. int new_column = m_cursor.column() - 1;
  373. toggle_selection_if_needed_for_event(event);
  374. set_cursor(m_cursor.line(), new_column);
  375. if (m_selection.start().is_valid()) {
  376. m_selection.set_end(m_cursor);
  377. did_update_selection();
  378. }
  379. } else if (m_cursor.line() > 0) {
  380. int new_line = m_cursor.line() - 1;
  381. int new_column = m_lines[new_line]->length();
  382. toggle_selection_if_needed_for_event(event);
  383. set_cursor(new_line, new_column);
  384. if (m_selection.start().is_valid()) {
  385. m_selection.set_end(m_cursor);
  386. did_update_selection();
  387. }
  388. }
  389. return;
  390. }
  391. if (event.key() == KeyCode::Key_Right) {
  392. if (m_cursor.column() < current_line().length()) {
  393. int new_column = m_cursor.column() + 1;
  394. toggle_selection_if_needed_for_event(event);
  395. set_cursor(m_cursor.line(), new_column);
  396. if (m_selection.start().is_valid()) {
  397. m_selection.set_end(m_cursor);
  398. did_update_selection();
  399. }
  400. } else if (m_cursor.line() != line_count() - 1) {
  401. int new_line = m_cursor.line() + 1;
  402. int new_column = 0;
  403. toggle_selection_if_needed_for_event(event);
  404. set_cursor(new_line, new_column);
  405. if (m_selection.start().is_valid()) {
  406. m_selection.set_end(m_cursor);
  407. did_update_selection();
  408. }
  409. }
  410. return;
  411. }
  412. if (!event.ctrl() && event.key() == KeyCode::Key_Home) {
  413. toggle_selection_if_needed_for_event(event);
  414. set_cursor(m_cursor.line(), 0);
  415. if (m_selection.start().is_valid()) {
  416. m_selection.set_end(m_cursor);
  417. did_update_selection();
  418. }
  419. return;
  420. }
  421. if (!event.ctrl() && event.key() == KeyCode::Key_End) {
  422. toggle_selection_if_needed_for_event(event);
  423. set_cursor(m_cursor.line(), current_line().length());
  424. if (m_selection.start().is_valid()) {
  425. m_selection.set_end(m_cursor);
  426. did_update_selection();
  427. }
  428. return;
  429. }
  430. if (event.ctrl() && event.key() == KeyCode::Key_Home) {
  431. toggle_selection_if_needed_for_event(event);
  432. set_cursor(0, 0);
  433. if (m_selection.start().is_valid()) {
  434. m_selection.set_end(m_cursor);
  435. did_update_selection();
  436. }
  437. return;
  438. }
  439. if (event.ctrl() && event.key() == KeyCode::Key_End) {
  440. toggle_selection_if_needed_for_event(event);
  441. set_cursor(line_count() - 1, m_lines[line_count() - 1]->length());
  442. if (m_selection.start().is_valid()) {
  443. m_selection.set_end(m_cursor);
  444. did_update_selection();
  445. }
  446. return;
  447. }
  448. if (event.modifiers() == Mod_Ctrl && event.key() == KeyCode::Key_A) {
  449. select_all();
  450. return;
  451. }
  452. if (event.key() == KeyCode::Key_Backspace) {
  453. if (is_readonly())
  454. return;
  455. if (has_selection()) {
  456. delete_selection();
  457. did_update_selection();
  458. return;
  459. }
  460. if (m_cursor.column() > 0) {
  461. // Backspace within line
  462. current_line().remove(m_cursor.column() - 1);
  463. update_content_size();
  464. set_cursor(m_cursor.line(), m_cursor.column() - 1);
  465. return;
  466. }
  467. if (m_cursor.column() == 0 && m_cursor.line() != 0) {
  468. // Backspace at column 0; merge with previous line
  469. auto& previous_line = *m_lines[m_cursor.line() - 1];
  470. int previous_length = previous_line.length();
  471. previous_line.append(current_line().characters(), current_line().length());
  472. m_lines.remove(m_cursor.line());
  473. update_content_size();
  474. update();
  475. set_cursor(m_cursor.line() - 1, previous_length);
  476. return;
  477. }
  478. return;
  479. }
  480. if (event.modifiers() == Mod_Shift && event.key() == KeyCode::Key_Delete) {
  481. if (is_readonly())
  482. return;
  483. delete_current_line();
  484. return;
  485. }
  486. if (event.key() == KeyCode::Key_Delete) {
  487. if (is_readonly())
  488. return;
  489. do_delete();
  490. return;
  491. }
  492. if (!is_readonly() && !event.ctrl() && !event.alt() && !event.text().is_empty())
  493. insert_at_cursor_or_replace_selection(event.text());
  494. }
  495. void GTextEditor::delete_current_line()
  496. {
  497. if (has_selection())
  498. return delete_selection();
  499. m_lines.remove(m_cursor.line());
  500. if (m_lines.is_empty())
  501. m_lines.append(make<Line>());
  502. update_content_size();
  503. update();
  504. }
  505. void GTextEditor::do_delete()
  506. {
  507. if (is_readonly())
  508. return;
  509. if (has_selection())
  510. return delete_selection();
  511. if (m_cursor.column() < current_line().length()) {
  512. // Delete within line
  513. current_line().remove(m_cursor.column());
  514. did_change();
  515. update_cursor();
  516. return;
  517. }
  518. if (m_cursor.column() == current_line().length() && m_cursor.line() != line_count() - 1) {
  519. // Delete at end of line; merge with next line
  520. auto& next_line = *m_lines[m_cursor.line() + 1];
  521. int previous_length = current_line().length();
  522. current_line().append(next_line.characters(), next_line.length());
  523. m_lines.remove(m_cursor.line() + 1);
  524. update();
  525. did_change();
  526. set_cursor(m_cursor.line(), previous_length);
  527. return;
  528. }
  529. }
  530. void GTextEditor::insert_at_cursor(const StringView& text)
  531. {
  532. // FIXME: This should obviously not be implemented this way.
  533. for (int i = 0; i < text.length(); ++i) {
  534. insert_at_cursor(text[i]);
  535. }
  536. }
  537. void GTextEditor::insert_at_cursor(char ch)
  538. {
  539. bool at_head = m_cursor.column() == 0;
  540. bool at_tail = m_cursor.column() == current_line().length();
  541. if (ch == '\n') {
  542. if (at_tail || at_head) {
  543. String new_line_contents;
  544. if (m_automatic_indentation_enabled && at_tail) {
  545. int leading_spaces = 0;
  546. auto& old_line = *m_lines[m_cursor.line()];
  547. for (int i = 0; i < old_line.length(); ++i) {
  548. if (old_line.characters()[i] == ' ')
  549. ++leading_spaces;
  550. else
  551. break;
  552. }
  553. if (leading_spaces)
  554. new_line_contents = String::repeated(' ', leading_spaces);
  555. }
  556. m_lines.insert(m_cursor.line() + (at_tail ? 1 : 0), make<Line>(new_line_contents));
  557. update();
  558. did_change();
  559. set_cursor(m_cursor.line() + 1, m_lines[m_cursor.line() + 1]->length());
  560. return;
  561. }
  562. auto new_line = make<Line>();
  563. new_line->append(current_line().characters() + m_cursor.column(), current_line().length() - m_cursor.column());
  564. current_line().truncate(m_cursor.column());
  565. m_lines.insert(m_cursor.line() + 1, move(new_line));
  566. update();
  567. did_change();
  568. set_cursor(m_cursor.line() + 1, 0);
  569. return;
  570. }
  571. if (ch == '\t') {
  572. int next_soft_tab_stop = ((m_cursor.column() + m_soft_tab_width) / m_soft_tab_width) * m_soft_tab_width;
  573. int spaces_to_insert = next_soft_tab_stop - m_cursor.column();
  574. for (int i = 0; i < spaces_to_insert; ++i) {
  575. current_line().insert(m_cursor.column(), ' ');
  576. }
  577. did_change();
  578. set_cursor(m_cursor.line(), next_soft_tab_stop);
  579. return;
  580. }
  581. current_line().insert(m_cursor.column(), ch);
  582. did_change();
  583. set_cursor(m_cursor.line(), m_cursor.column() + 1);
  584. }
  585. int GTextEditor::content_x_for_position(const GTextPosition& position) const
  586. {
  587. auto& line = *m_lines[position.line()];
  588. switch (m_text_alignment) {
  589. case TextAlignment::CenterLeft:
  590. return m_horizontal_content_padding + position.column() * glyph_width();
  591. case TextAlignment::CenterRight:
  592. return content_width() - m_horizontal_content_padding - (line.length() * glyph_width()) + (position.column() * glyph_width());
  593. default:
  594. ASSERT_NOT_REACHED();
  595. }
  596. }
  597. Rect GTextEditor::cursor_content_rect() const
  598. {
  599. if (!m_cursor.is_valid())
  600. return {};
  601. ASSERT(!m_lines.is_empty());
  602. ASSERT(m_cursor.column() <= (current_line().length() + 1));
  603. int cursor_x = content_x_for_position(m_cursor);
  604. if (is_single_line()) {
  605. Rect cursor_rect { cursor_x, 0, 1, font().glyph_height() + 2 };
  606. cursor_rect.center_vertically_within({ {}, frame_inner_rect().size() });
  607. return cursor_rect;
  608. }
  609. return { cursor_x, m_cursor.line() * line_height(), 1, line_height() };
  610. }
  611. Rect GTextEditor::line_widget_rect(int line_index) const
  612. {
  613. auto rect = line_content_rect(line_index);
  614. rect.set_x(frame_thickness());
  615. rect.set_width(frame_inner_rect().width());
  616. rect.move_by(0, -(vertical_scrollbar().value()));
  617. rect.move_by(0, frame_thickness());
  618. rect.intersect(frame_inner_rect());
  619. return rect;
  620. }
  621. void GTextEditor::scroll_cursor_into_view()
  622. {
  623. auto rect = cursor_content_rect();
  624. if (m_cursor.column() == 0)
  625. rect.set_x(content_x_for_position({ m_cursor.line(), 0 }) - 2);
  626. else if (m_cursor.column() == m_lines[m_cursor.line()]->length())
  627. rect.set_x(content_x_for_position({ m_cursor.line(), m_lines[m_cursor.line()]->length() }) + 2);
  628. scroll_into_view(rect, true, true);
  629. }
  630. Rect GTextEditor::line_content_rect(int line_index) const
  631. {
  632. auto& line = *m_lines[line_index];
  633. if (is_single_line()) {
  634. Rect line_rect = { content_x_for_position({ line_index, 0 }), 0, line.length() * glyph_width(), font().glyph_height() + 2 };
  635. line_rect.center_vertically_within({ {}, frame_inner_rect().size() });
  636. return line_rect;
  637. }
  638. return {
  639. content_x_for_position({ line_index, 0 }),
  640. line_index * line_height(),
  641. line.length() * glyph_width(),
  642. line_height()
  643. };
  644. }
  645. void GTextEditor::update_cursor()
  646. {
  647. update(line_widget_rect(m_cursor.line()));
  648. }
  649. void GTextEditor::set_cursor(int line, int column)
  650. {
  651. set_cursor({ line, column });
  652. }
  653. void GTextEditor::set_cursor(const GTextPosition& position)
  654. {
  655. ASSERT(!m_lines.is_empty());
  656. ASSERT(position.line() < m_lines.size());
  657. ASSERT(position.column() <= m_lines[position.line()]->length());
  658. if (m_cursor != position) {
  659. // NOTE: If the old cursor is no longer valid, repaint everything just in case.
  660. auto old_cursor_line_rect = m_cursor.line() < m_lines.size()
  661. ? line_widget_rect(m_cursor.line())
  662. : rect();
  663. m_cursor = position;
  664. m_cursor_state = true;
  665. scroll_cursor_into_view();
  666. update(old_cursor_line_rect);
  667. update_cursor();
  668. }
  669. if (on_cursor_change)
  670. on_cursor_change();
  671. }
  672. void GTextEditor::focusin_event(CEvent&)
  673. {
  674. update_cursor();
  675. start_timer(500);
  676. }
  677. void GTextEditor::focusout_event(CEvent&)
  678. {
  679. stop_timer();
  680. }
  681. void GTextEditor::timer_event(CTimerEvent&)
  682. {
  683. m_cursor_state = !m_cursor_state;
  684. if (is_focused())
  685. update_cursor();
  686. }
  687. GTextEditor::Line::Line()
  688. {
  689. clear();
  690. }
  691. GTextEditor::Line::Line(const StringView& text)
  692. {
  693. set_text(text);
  694. }
  695. void GTextEditor::Line::clear()
  696. {
  697. m_text.clear();
  698. m_text.append(0);
  699. }
  700. void GTextEditor::Line::set_text(const StringView& text)
  701. {
  702. if (text.length() == length() && !memcmp(text.characters_without_null_termination(), characters(), length()))
  703. return;
  704. if (text.is_empty()) {
  705. clear();
  706. return;
  707. }
  708. m_text.resize(text.length() + 1);
  709. memcpy(m_text.data(), text.characters_without_null_termination(), text.length() + 1);
  710. }
  711. int GTextEditor::Line::width(const Font& font) const
  712. {
  713. return font.glyph_width('x') * length();
  714. }
  715. void GTextEditor::Line::append(const char* characters, int length)
  716. {
  717. int old_length = m_text.size() - 1;
  718. m_text.resize(m_text.size() + length);
  719. memcpy(m_text.data() + old_length, characters, length);
  720. m_text.last() = 0;
  721. }
  722. void GTextEditor::Line::append(char ch)
  723. {
  724. insert(length(), ch);
  725. }
  726. void GTextEditor::Line::prepend(char ch)
  727. {
  728. insert(0, ch);
  729. }
  730. void GTextEditor::Line::insert(int index, char ch)
  731. {
  732. if (index == length()) {
  733. m_text.last() = ch;
  734. m_text.append(0);
  735. } else {
  736. m_text.insert(index, move(ch));
  737. }
  738. }
  739. void GTextEditor::Line::remove(int index)
  740. {
  741. if (index == length()) {
  742. m_text.take_last();
  743. m_text.last() = 0;
  744. } else {
  745. m_text.remove(index);
  746. }
  747. }
  748. void GTextEditor::Line::truncate(int length)
  749. {
  750. m_text.resize(length + 1);
  751. m_text.last() = 0;
  752. }
  753. bool GTextEditor::write_to_file(const StringView& path)
  754. {
  755. int fd = open_with_path_length(path.characters_without_null_termination(), path.length(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
  756. if (fd < 0) {
  757. perror("open");
  758. return false;
  759. }
  760. for (int i = 0; i < m_lines.size(); ++i) {
  761. auto& line = *m_lines[i];
  762. if (line.length()) {
  763. ssize_t nwritten = write(fd, line.characters(), line.length());
  764. if (nwritten < 0) {
  765. perror("write");
  766. close(fd);
  767. return false;
  768. }
  769. }
  770. if (i != m_lines.size() - 1) {
  771. char ch = '\n';
  772. ssize_t nwritten = write(fd, &ch, 1);
  773. if (nwritten != 1) {
  774. perror("write");
  775. close(fd);
  776. return false;
  777. }
  778. }
  779. }
  780. close(fd);
  781. return true;
  782. }
  783. String GTextEditor::text() const
  784. {
  785. StringBuilder builder;
  786. for (int i = 0; i < line_count(); ++i) {
  787. auto& line = *m_lines[i];
  788. builder.append(line.characters(), line.length());
  789. if (i != line_count() - 1)
  790. builder.append('\n');
  791. }
  792. return builder.to_string();
  793. }
  794. void GTextEditor::clear()
  795. {
  796. m_lines.clear();
  797. m_lines.append(make<Line>());
  798. m_selection.clear();
  799. did_update_selection();
  800. set_cursor(0, 0);
  801. update();
  802. }
  803. String GTextEditor::selected_text() const
  804. {
  805. if (!has_selection())
  806. return {};
  807. auto selection = normalized_selection();
  808. StringBuilder builder;
  809. for (int i = selection.start().line(); i <= selection.end().line(); ++i) {
  810. auto& line = *m_lines[i];
  811. int selection_start_column_on_line = selection.start().line() == i ? selection.start().column() : 0;
  812. int selection_end_column_on_line = selection.end().line() == i ? selection.end().column() : line.length();
  813. builder.append(line.characters() + selection_start_column_on_line, selection_end_column_on_line - selection_start_column_on_line);
  814. if (i != selection.end().line())
  815. builder.append('\n');
  816. }
  817. return builder.to_string();
  818. }
  819. void GTextEditor::delete_selection()
  820. {
  821. if (!has_selection())
  822. return;
  823. auto selection = normalized_selection();
  824. // First delete all the lines in between the first and last one.
  825. for (int i = selection.start().line() + 1; i < selection.end().line();) {
  826. m_lines.remove(i);
  827. selection.end().set_line(selection.end().line() - 1);
  828. }
  829. if (selection.start().line() == selection.end().line()) {
  830. // Delete within same line.
  831. auto& line = *m_lines[selection.start().line()];
  832. bool whole_line_is_selected = selection.start().column() == 0 && selection.end().column() == line.length();
  833. if (whole_line_is_selected) {
  834. line.clear();
  835. } else {
  836. auto before_selection = String(line.characters(), line.length()).substring(0, selection.start().column());
  837. auto after_selection = String(line.characters(), line.length()).substring(selection.end().column(), line.length() - selection.end().column());
  838. StringBuilder builder(before_selection.length() + after_selection.length());
  839. builder.append(before_selection);
  840. builder.append(after_selection);
  841. line.set_text(builder.to_string());
  842. }
  843. } else {
  844. // Delete across a newline, merging lines.
  845. ASSERT(selection.start().line() == selection.end().line() - 1);
  846. auto& first_line = *m_lines[selection.start().line()];
  847. auto& second_line = *m_lines[selection.end().line()];
  848. auto before_selection = String(first_line.characters(), first_line.length()).substring(0, selection.start().column());
  849. auto after_selection = String(second_line.characters(), second_line.length()).substring(selection.end().column(), second_line.length() - selection.end().column());
  850. StringBuilder builder(before_selection.length() + after_selection.length());
  851. builder.append(before_selection);
  852. builder.append(after_selection);
  853. first_line.set_text(builder.to_string());
  854. m_lines.remove(selection.end().line());
  855. }
  856. if (m_lines.is_empty())
  857. m_lines.append(make<Line>());
  858. m_selection.clear();
  859. did_update_selection();
  860. did_change();
  861. set_cursor(selection.start());
  862. update();
  863. }
  864. void GTextEditor::insert_at_cursor_or_replace_selection(const StringView& text)
  865. {
  866. ASSERT(!is_readonly());
  867. if (has_selection())
  868. delete_selection();
  869. insert_at_cursor(text);
  870. }
  871. void GTextEditor::cut()
  872. {
  873. if (is_readonly())
  874. return;
  875. auto selected_text = this->selected_text();
  876. printf("Cut: \"%s\"\n", selected_text.characters());
  877. GClipboard::the().set_data(selected_text);
  878. delete_selection();
  879. }
  880. void GTextEditor::copy()
  881. {
  882. auto selected_text = this->selected_text();
  883. printf("Copy: \"%s\"\n", selected_text.characters());
  884. GClipboard::the().set_data(selected_text);
  885. }
  886. void GTextEditor::paste()
  887. {
  888. if (is_readonly())
  889. return;
  890. auto paste_text = GClipboard::the().data();
  891. printf("Paste: \"%s\"\n", paste_text.characters());
  892. insert_at_cursor_or_replace_selection(paste_text);
  893. }
  894. void GTextEditor::enter_event(CEvent&)
  895. {
  896. ASSERT(window());
  897. window()->set_override_cursor(GStandardCursor::IBeam);
  898. }
  899. void GTextEditor::leave_event(CEvent&)
  900. {
  901. ASSERT(window());
  902. window()->set_override_cursor(GStandardCursor::None);
  903. }
  904. void GTextEditor::did_change()
  905. {
  906. ASSERT(!is_readonly());
  907. update_content_size();
  908. if (!m_have_pending_change_notification) {
  909. m_have_pending_change_notification = true;
  910. deferred_invoke([this](auto&) {
  911. if (on_change)
  912. on_change();
  913. m_have_pending_change_notification = false;
  914. });
  915. }
  916. }
  917. void GTextEditor::set_readonly(bool readonly)
  918. {
  919. if (m_readonly == readonly)
  920. return;
  921. m_readonly = readonly;
  922. m_cut_action->set_enabled(!is_readonly() && has_selection());
  923. m_delete_action->set_enabled(!is_readonly());
  924. m_paste_action->set_enabled(!is_readonly());
  925. }
  926. void GTextEditor::did_update_selection()
  927. {
  928. m_cut_action->set_enabled(!is_readonly() && has_selection());
  929. m_copy_action->set_enabled(has_selection());
  930. if (on_selection_change)
  931. on_selection_change();
  932. }
  933. void GTextEditor::context_menu_event(GContextMenuEvent& event)
  934. {
  935. if (!m_context_menu) {
  936. m_context_menu = make<GMenu>("GTextEditor context menu");
  937. m_context_menu->add_action(undo_action());
  938. m_context_menu->add_action(redo_action());
  939. m_context_menu->add_separator();
  940. m_context_menu->add_action(cut_action());
  941. m_context_menu->add_action(copy_action());
  942. m_context_menu->add_action(paste_action());
  943. m_context_menu->add_action(delete_action());
  944. }
  945. m_context_menu->popup(event.screen_position());
  946. }
  947. void GTextEditor::set_text_alignment(TextAlignment alignment)
  948. {
  949. if (m_text_alignment == alignment)
  950. return;
  951. m_text_alignment = alignment;
  952. update();
  953. }
  954. void GTextEditor::resize_event(GResizeEvent& event)
  955. {
  956. GScrollableWidget::resize_event(event);
  957. update_content_size();
  958. }