GTextEditor.cpp 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428
  1. #include <AK/QuickSort.h>
  2. #include <AK/StringBuilder.h>
  3. #include <Kernel/KeyCode.h>
  4. #include <LibDraw/Palette.h>
  5. #include <LibGUI/GAction.h>
  6. #include <LibGUI/GClipboard.h>
  7. #include <LibGUI/GFontDatabase.h>
  8. #include <LibGUI/GMenu.h>
  9. #include <LibGUI/GPainter.h>
  10. #include <LibGUI/GScrollBar.h>
  11. #include <LibGUI/GTextEditor.h>
  12. #include <LibGUI/GWindow.h>
  13. #include <ctype.h>
  14. #include <fcntl.h>
  15. #include <stdio.h>
  16. #include <unistd.h>
  17. //#define DEBUG_GTEXTEDITOR
  18. GTextEditor::GTextEditor(GWidget* parent)
  19. : GTextEditor(Type::MultiLine, parent)
  20. {
  21. }
  22. GTextEditor::GTextEditor(Type type, GWidget* parent)
  23. : GScrollableWidget(parent)
  24. , m_type(type)
  25. {
  26. set_document(GTextDocument::create());
  27. set_frame_shape(FrameShape::Container);
  28. set_frame_shadow(FrameShadow::Sunken);
  29. set_frame_thickness(2);
  30. set_scrollbars_enabled(is_multi_line());
  31. set_font(GFontDatabase::the().get_by_name("Csilla Thin"));
  32. // FIXME: Recompute vertical scrollbar step size on font change.
  33. vertical_scrollbar().set_step(line_height());
  34. m_cursor = { 0, 0 };
  35. create_actions();
  36. }
  37. GTextEditor::~GTextEditor()
  38. {
  39. if (m_document)
  40. m_document->unregister_client(*this);
  41. }
  42. void GTextEditor::create_actions()
  43. {
  44. m_undo_action = GCommonActions::make_undo_action([&](auto&) { undo(); }, this);
  45. m_redo_action = GCommonActions::make_redo_action([&](auto&) { redo(); }, this);
  46. m_undo_action->set_enabled(false);
  47. m_redo_action->set_enabled(false);
  48. m_cut_action = GCommonActions::make_cut_action([&](auto&) { cut(); }, this);
  49. m_copy_action = GCommonActions::make_copy_action([&](auto&) { copy(); }, this);
  50. m_paste_action = GCommonActions::make_paste_action([&](auto&) { paste(); }, this);
  51. m_delete_action = GCommonActions::make_delete_action([&](auto&) { do_delete(); }, this);
  52. }
  53. void GTextEditor::set_text(const StringView& text)
  54. {
  55. if (is_single_line() && text.length() == line(0).length() && !memcmp(text.characters_without_null_termination(), line(0).characters(), text.length()))
  56. return;
  57. m_selection.clear();
  58. document().set_text(text);
  59. update_content_size();
  60. recompute_all_visual_lines();
  61. if (is_single_line())
  62. set_cursor(0, line(0).length());
  63. else
  64. set_cursor(0, 0);
  65. did_update_selection();
  66. update();
  67. }
  68. void GTextEditor::update_content_size()
  69. {
  70. int content_width = 0;
  71. int content_height = 0;
  72. for (auto& line : m_line_visual_data) {
  73. content_width = max(line.visual_rect.width(), content_width);
  74. content_height += line.visual_rect.height();
  75. }
  76. content_width += m_horizontal_content_padding * 2;
  77. if (is_right_text_alignment(m_text_alignment))
  78. content_width = max(frame_inner_rect().width(), content_width);
  79. set_content_size({ content_width, content_height });
  80. set_size_occupied_by_fixed_elements({ ruler_width(), 0 });
  81. }
  82. GTextPosition GTextEditor::text_position_at(const Point& a_position) const
  83. {
  84. auto position = a_position;
  85. position.move_by(horizontal_scrollbar().value(), vertical_scrollbar().value());
  86. position.move_by(-(m_horizontal_content_padding + ruler_width()), 0);
  87. position.move_by(-frame_thickness(), -frame_thickness());
  88. size_t line_index = 0;
  89. if (is_line_wrapping_enabled()) {
  90. for (size_t i = 0; i < line_count(); ++i) {
  91. auto& rect = m_line_visual_data[i].visual_rect;
  92. if (position.y() >= rect.top() && position.y() <= rect.bottom()) {
  93. line_index = i;
  94. break;
  95. }
  96. if (position.y() > rect.bottom())
  97. line_index = line_count() - 1;
  98. }
  99. } else {
  100. line_index = (size_t)(position.y() / line_height());
  101. }
  102. line_index = max((size_t)0, min(line_index, line_count() - 1));
  103. size_t column_index;
  104. switch (m_text_alignment) {
  105. case TextAlignment::CenterLeft:
  106. column_index = (position.x() + glyph_width() / 2) / glyph_width();
  107. if (is_line_wrapping_enabled()) {
  108. for_each_visual_line(line_index, [&](const Rect& rect, const StringView&, size_t start_of_line) {
  109. if (rect.contains_vertically(position.y())) {
  110. column_index += start_of_line;
  111. return IterationDecision::Break;
  112. }
  113. return IterationDecision::Continue;
  114. });
  115. }
  116. break;
  117. case TextAlignment::CenterRight:
  118. // FIXME: Support right-aligned line wrapping, I guess.
  119. ASSERT(!is_line_wrapping_enabled());
  120. column_index = (position.x() - content_x_for_position({ line_index, 0 }) + glyph_width() / 2) / glyph_width();
  121. break;
  122. default:
  123. ASSERT_NOT_REACHED();
  124. }
  125. column_index = max((size_t)0, min(column_index, line(line_index).length()));
  126. return { line_index, column_index };
  127. }
  128. void GTextEditor::doubleclick_event(GMouseEvent& event)
  129. {
  130. if (event.button() != GMouseButton::Left)
  131. return;
  132. // NOTE: This ensures that spans are updated before we look at them.
  133. flush_pending_change_notification_if_needed();
  134. m_triple_click_timer.start();
  135. m_in_drag_select = false;
  136. auto start = text_position_at(event.position());
  137. auto end = start;
  138. auto& line = this->line(start.line());
  139. if (!document().has_spans()) {
  140. while (start.column() > 0) {
  141. if (isspace(line.characters()[start.column() - 1]))
  142. break;
  143. start.set_column(start.column() - 1);
  144. }
  145. while (end.column() < line.length()) {
  146. if (isspace(line.characters()[end.column()]))
  147. break;
  148. end.set_column(end.column() + 1);
  149. }
  150. } else {
  151. for (auto& span : document().spans()) {
  152. if (!span.range.contains(start))
  153. continue;
  154. start = span.range.start();
  155. end = span.range.end();
  156. end.set_column(end.column() + 1);
  157. break;
  158. }
  159. }
  160. m_selection.set(start, end);
  161. set_cursor(end);
  162. update();
  163. did_update_selection();
  164. }
  165. void GTextEditor::mousedown_event(GMouseEvent& event)
  166. {
  167. if (event.button() != GMouseButton::Left) {
  168. return;
  169. }
  170. if (m_triple_click_timer.is_valid() && m_triple_click_timer.elapsed() < 250) {
  171. m_triple_click_timer = CElapsedTimer();
  172. GTextPosition start;
  173. GTextPosition end;
  174. if (is_multi_line()) {
  175. // select *current* line
  176. start = GTextPosition(m_cursor.line(), 0);
  177. end = GTextPosition(m_cursor.line(), line(m_cursor.line()).length());
  178. } else {
  179. // select *whole* line
  180. start = GTextPosition(0, 0);
  181. end = GTextPosition(line_count() - 1, line(line_count() - 1).length());
  182. }
  183. m_selection.set(start, end);
  184. set_cursor(end);
  185. return;
  186. }
  187. if (event.modifiers() & Mod_Shift) {
  188. if (!has_selection())
  189. m_selection.set(m_cursor, {});
  190. } else {
  191. m_selection.clear();
  192. }
  193. m_in_drag_select = true;
  194. set_cursor(text_position_at(event.position()));
  195. if (!(event.modifiers() & Mod_Shift)) {
  196. if (!has_selection())
  197. m_selection.set(m_cursor, {});
  198. }
  199. if (m_selection.start().is_valid() && m_selection.start() != m_cursor)
  200. m_selection.set_end(m_cursor);
  201. // FIXME: Only update the relevant rects.
  202. update();
  203. did_update_selection();
  204. }
  205. void GTextEditor::mouseup_event(GMouseEvent& event)
  206. {
  207. if (event.button() == GMouseButton::Left) {
  208. if (m_in_drag_select) {
  209. m_in_drag_select = false;
  210. }
  211. return;
  212. }
  213. }
  214. void GTextEditor::mousemove_event(GMouseEvent& event)
  215. {
  216. if (m_in_drag_select) {
  217. set_cursor(text_position_at(event.position()));
  218. m_selection.set_end(m_cursor);
  219. did_update_selection();
  220. update();
  221. return;
  222. }
  223. }
  224. int GTextEditor::ruler_width() const
  225. {
  226. if (!m_ruler_visible)
  227. return 0;
  228. // FIXME: Resize based on needed space.
  229. return 5 * font().glyph_width('x') + 4;
  230. }
  231. Rect GTextEditor::ruler_content_rect(size_t line_index) const
  232. {
  233. if (!m_ruler_visible)
  234. return {};
  235. return {
  236. 0 - ruler_width() + horizontal_scrollbar().value(),
  237. line_content_rect(line_index).y(),
  238. ruler_width(),
  239. line_content_rect(line_index).height()
  240. };
  241. }
  242. Rect GTextEditor::ruler_rect_in_inner_coordinates() const
  243. {
  244. return { 0, 0, ruler_width(), height() - height_occupied_by_horizontal_scrollbar() };
  245. }
  246. Rect GTextEditor::visible_text_rect_in_inner_coordinates() const
  247. {
  248. return {
  249. m_horizontal_content_padding + (m_ruler_visible ? (ruler_rect_in_inner_coordinates().right() + 1) : 0),
  250. 0,
  251. frame_inner_rect().width() - (m_horizontal_content_padding * 2) - width_occupied_by_vertical_scrollbar() - ruler_width(),
  252. frame_inner_rect().height() - height_occupied_by_horizontal_scrollbar()
  253. };
  254. }
  255. void GTextEditor::paint_event(GPaintEvent& event)
  256. {
  257. Color widget_background_color = palette().color(background_role());
  258. // NOTE: This ensures that spans are updated before we look at them.
  259. flush_pending_change_notification_if_needed();
  260. GFrame::paint_event(event);
  261. GPainter painter(*this);
  262. painter.add_clip_rect(widget_inner_rect());
  263. painter.add_clip_rect(event.rect());
  264. painter.fill_rect(event.rect(), widget_background_color);
  265. painter.translate(frame_thickness(), frame_thickness());
  266. auto ruler_rect = ruler_rect_in_inner_coordinates();
  267. if (m_ruler_visible) {
  268. painter.fill_rect(ruler_rect, widget_background_color.darkened(0.85f));
  269. painter.draw_line(ruler_rect.top_right(), ruler_rect.bottom_right(), widget_background_color.darkened(0.5f));
  270. }
  271. painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
  272. if (m_ruler_visible)
  273. painter.translate(ruler_width(), 0);
  274. size_t first_visible_line = text_position_at(event.rect().top_left()).line();
  275. size_t last_visible_line = text_position_at(event.rect().bottom_right()).line();
  276. auto selection = normalized_selection();
  277. bool has_selection = selection.is_valid();
  278. if (m_ruler_visible) {
  279. for (size_t i = first_visible_line; i <= last_visible_line; ++i) {
  280. bool is_current_line = i == m_cursor.line();
  281. auto ruler_line_rect = ruler_content_rect(i);
  282. painter.draw_text(
  283. ruler_line_rect.shrunken(2, 0).translated(0, m_line_spacing / 2),
  284. String::number(i + 1),
  285. is_current_line ? Font::default_bold_font() : font(),
  286. TextAlignment::TopRight,
  287. is_current_line ? widget_background_color.darkened(0.6f) : widget_background_color.darkened(0.7f));
  288. }
  289. }
  290. Rect text_clip_rect {
  291. (m_ruler_visible ? (ruler_rect_in_inner_coordinates().right() + frame_thickness() + 1) : frame_thickness()),
  292. frame_thickness(),
  293. width() - width_occupied_by_vertical_scrollbar() - ruler_width(),
  294. height() - height_occupied_by_horizontal_scrollbar()
  295. };
  296. painter.add_clip_rect(text_clip_rect);
  297. for (size_t line_index = first_visible_line; line_index <= last_visible_line; ++line_index) {
  298. auto& line = this->line(line_index);
  299. bool physical_line_has_selection = has_selection && line_index >= selection.start().line() && line_index <= selection.end().line();
  300. size_t first_visual_line_with_selection = 0;
  301. size_t last_visual_line_with_selection = 0;
  302. if (physical_line_has_selection) {
  303. if (selection.start().line() < line_index)
  304. first_visual_line_with_selection = 0;
  305. else
  306. first_visual_line_with_selection = visual_line_containing(line_index, selection.start().column());
  307. if (selection.end().line() > line_index)
  308. last_visual_line_with_selection = m_line_visual_data[line_index].visual_line_breaks.size();
  309. else
  310. last_visual_line_with_selection = visual_line_containing(line_index, selection.end().column());
  311. }
  312. size_t selection_start_column_within_line = selection.start().line() == line_index ? selection.start().column() : 0;
  313. size_t selection_end_column_within_line = selection.end().line() == line_index ? selection.end().column() : line.length();
  314. size_t visual_line_index = 0;
  315. for_each_visual_line(line_index, [&](const Rect& visual_line_rect, const StringView& visual_line_text, size_t start_of_visual_line) {
  316. if (is_multi_line() && line_index == m_cursor.line())
  317. painter.fill_rect(visual_line_rect, widget_background_color.darkened(0.9f));
  318. #ifdef DEBUG_GTEXTEDITOR
  319. painter.draw_rect(visual_line_rect, Color::Cyan);
  320. #endif
  321. if (!document().has_spans()) {
  322. // Fast-path for plain text
  323. painter.draw_text(visual_line_rect, visual_line_text, m_text_alignment, palette().color(foreground_role()));
  324. } else {
  325. int advance = font().glyph_width(' ') + font().glyph_spacing();
  326. Rect character_rect = { visual_line_rect.location(), { font().glyph_width(' '), line_height() } };
  327. for (size_t i = 0; i < visual_line_text.length(); ++i) {
  328. const Font* font = &this->font();
  329. Color color;
  330. Optional<Color> background_color;
  331. GTextPosition physical_position(line_index, start_of_visual_line + i);
  332. // FIXME: This is *horribly* inefficient.
  333. for (auto& span : document().spans()) {
  334. if (!span.range.contains(physical_position))
  335. continue;
  336. color = span.color;
  337. if (span.font)
  338. font = span.font;
  339. background_color = span.background_color;
  340. break;
  341. }
  342. if (background_color.has_value())
  343. painter.fill_rect(character_rect, background_color.value());
  344. painter.draw_text(character_rect, visual_line_text.substring_view(i, 1), *font, m_text_alignment, color);
  345. character_rect.move_by(advance, 0);
  346. }
  347. }
  348. bool physical_line_has_selection = has_selection && line_index >= selection.start().line() && line_index <= selection.end().line();
  349. if (physical_line_has_selection) {
  350. bool current_visual_line_has_selection = (line_index != selection.start().line() && line_index != selection.end().line())
  351. || (visual_line_index >= first_visual_line_with_selection && visual_line_index <= last_visual_line_with_selection);
  352. if (current_visual_line_has_selection) {
  353. bool selection_begins_on_current_visual_line = visual_line_index == first_visual_line_with_selection;
  354. bool selection_ends_on_current_visual_line = visual_line_index == last_visual_line_with_selection;
  355. int selection_left = selection_begins_on_current_visual_line
  356. ? content_x_for_position({ line_index, (size_t)selection_start_column_within_line })
  357. : m_horizontal_content_padding;
  358. int selection_right = selection_ends_on_current_visual_line
  359. ? content_x_for_position({ line_index, (size_t)selection_end_column_within_line })
  360. : visual_line_rect.right() + 1;
  361. Rect selection_rect {
  362. selection_left,
  363. visual_line_rect.y(),
  364. selection_right - selection_left,
  365. visual_line_rect.height()
  366. };
  367. painter.fill_rect(selection_rect, palette().selection());
  368. size_t start_of_selection_within_visual_line = (size_t)max(0, (int)selection_start_column_within_line - (int)start_of_visual_line);
  369. size_t end_of_selection_within_visual_line = selection_end_column_within_line - start_of_visual_line;
  370. StringView visual_selected_text {
  371. visual_line_text.characters_without_null_termination() + start_of_selection_within_visual_line,
  372. end_of_selection_within_visual_line - start_of_selection_within_visual_line
  373. };
  374. painter.draw_text(selection_rect, visual_selected_text, TextAlignment::CenterLeft, palette().selection_text());
  375. }
  376. }
  377. ++visual_line_index;
  378. return IterationDecision::Continue;
  379. });
  380. }
  381. if (is_focused() && m_cursor_state)
  382. painter.fill_rect(cursor_content_rect(), Color::Red);
  383. }
  384. void GTextEditor::toggle_selection_if_needed_for_event(const GKeyEvent& event)
  385. {
  386. if (event.shift() && !m_selection.is_valid()) {
  387. m_selection.set(m_cursor, {});
  388. did_update_selection();
  389. update();
  390. return;
  391. }
  392. if (!event.shift() && m_selection.is_valid()) {
  393. m_selection.clear();
  394. did_update_selection();
  395. update();
  396. return;
  397. }
  398. }
  399. void GTextEditor::select_all()
  400. {
  401. GTextPosition start_of_document { 0, 0 };
  402. GTextPosition end_of_document { line_count() - 1, line(line_count() - 1).length() };
  403. m_selection.set(start_of_document, end_of_document);
  404. did_update_selection();
  405. set_cursor(end_of_document);
  406. update();
  407. }
  408. void GTextEditor::get_selection_line_boundaries(size_t& first_line, size_t& last_line)
  409. {
  410. auto selection = normalized_selection();
  411. if (!selection.is_valid()) {
  412. first_line = m_cursor.line();
  413. last_line = m_cursor.line();
  414. return;
  415. }
  416. first_line = selection.start().line();
  417. last_line = selection.end().line();
  418. if (first_line != last_line && selection.end().column() == 0)
  419. last_line -= 1;
  420. }
  421. void GTextEditor::move_selected_lines_up()
  422. {
  423. size_t first_line;
  424. size_t last_line;
  425. get_selection_line_boundaries(first_line, last_line);
  426. if (first_line == 0)
  427. return;
  428. auto& lines = document().lines();
  429. lines.insert((int)last_line, lines.take((int)first_line - 1));
  430. m_cursor = { first_line - 1, 0 };
  431. if (has_selection()) {
  432. m_selection.set_start({ first_line - 1, 0 });
  433. m_selection.set_end({ last_line - 1, line(last_line - 1).length() });
  434. }
  435. did_change();
  436. update();
  437. }
  438. void GTextEditor::move_selected_lines_down()
  439. {
  440. size_t first_line;
  441. size_t last_line;
  442. get_selection_line_boundaries(first_line, last_line);
  443. auto& lines = document().lines();
  444. if (last_line >= (size_t)(lines.size() - 1))
  445. return;
  446. lines.insert((int)first_line, lines.take((int)last_line + 1));
  447. m_cursor = { first_line + 1, 0 };
  448. if (has_selection()) {
  449. m_selection.set_start({ first_line + 1, 0 });
  450. m_selection.set_end({ last_line + 1, line(last_line + 1).length() });
  451. }
  452. did_change();
  453. update();
  454. }
  455. void GTextEditor::sort_selected_lines()
  456. {
  457. if (is_readonly())
  458. return;
  459. if (!has_selection())
  460. return;
  461. size_t first_line;
  462. size_t last_line;
  463. get_selection_line_boundaries(first_line, last_line);
  464. auto& lines = document().lines();
  465. auto start = lines.begin() + (int)first_line;
  466. auto end = lines.begin() + (int)last_line + 1;
  467. quick_sort(start, end, [](auto& a, auto& b) {
  468. return strcmp(a.characters(), b.characters()) < 0;
  469. });
  470. did_change();
  471. update();
  472. }
  473. void GTextEditor::keydown_event(GKeyEvent& event)
  474. {
  475. if (is_single_line() && event.key() == KeyCode::Key_Tab)
  476. return GWidget::keydown_event(event);
  477. if (is_single_line() && event.key() == KeyCode::Key_Return) {
  478. if (on_return_pressed)
  479. on_return_pressed();
  480. return;
  481. }
  482. if (event.key() == KeyCode::Key_Escape) {
  483. if (on_escape_pressed)
  484. on_escape_pressed();
  485. return;
  486. }
  487. if (event.key() == KeyCode::Key_Up) {
  488. if (m_cursor.line() > 0) {
  489. if (event.ctrl() && event.shift()) {
  490. move_selected_lines_up();
  491. return;
  492. }
  493. size_t new_line = m_cursor.line() - 1;
  494. size_t new_column = min(m_cursor.column(), line(new_line).length());
  495. toggle_selection_if_needed_for_event(event);
  496. set_cursor(new_line, new_column);
  497. if (event.shift() && m_selection.start().is_valid()) {
  498. m_selection.set_end(m_cursor);
  499. did_update_selection();
  500. }
  501. }
  502. return;
  503. }
  504. if (event.key() == KeyCode::Key_Down) {
  505. if (m_cursor.line() < (line_count() - 1)) {
  506. if (event.ctrl() && event.shift()) {
  507. move_selected_lines_down();
  508. return;
  509. }
  510. size_t new_line = m_cursor.line() + 1;
  511. size_t new_column = min(m_cursor.column(), line(new_line).length());
  512. toggle_selection_if_needed_for_event(event);
  513. set_cursor(new_line, new_column);
  514. if (event.shift() && m_selection.start().is_valid()) {
  515. m_selection.set_end(m_cursor);
  516. did_update_selection();
  517. }
  518. }
  519. return;
  520. }
  521. if (event.key() == KeyCode::Key_PageUp) {
  522. if (m_cursor.line() > 0) {
  523. size_t new_line = max((size_t)0, m_cursor.line() - (size_t)visible_content_rect().height() / (size_t)line_height());
  524. size_t new_column = min(m_cursor.column(), line(new_line).length());
  525. toggle_selection_if_needed_for_event(event);
  526. set_cursor(new_line, new_column);
  527. if (event.shift() && m_selection.start().is_valid()) {
  528. m_selection.set_end(m_cursor);
  529. did_update_selection();
  530. }
  531. }
  532. return;
  533. }
  534. if (event.key() == KeyCode::Key_PageDown) {
  535. if (m_cursor.line() < (line_count() - 1)) {
  536. int new_line = min(line_count() - 1, m_cursor.line() + visible_content_rect().height() / line_height());
  537. int new_column = min(m_cursor.column(), lines()[new_line].length());
  538. toggle_selection_if_needed_for_event(event);
  539. set_cursor(new_line, new_column);
  540. if (event.shift() && m_selection.start().is_valid()) {
  541. m_selection.set_end(m_cursor);
  542. did_update_selection();
  543. }
  544. }
  545. return;
  546. }
  547. if (event.key() == KeyCode::Key_Left) {
  548. if (event.ctrl() && document().has_spans()) {
  549. // FIXME: Do something nice when the document has no spans.
  550. auto span = document().first_non_skippable_span_before(m_cursor);
  551. GTextPosition new_cursor = !span.has_value()
  552. ? GTextPosition(0, 0)
  553. : span.value().range.start();
  554. toggle_selection_if_needed_for_event(event);
  555. set_cursor(new_cursor);
  556. if (event.shift() && m_selection.start().is_valid()) {
  557. m_selection.set_end(m_cursor);
  558. did_update_selection();
  559. }
  560. return;
  561. }
  562. if (m_cursor.column() > 0) {
  563. int new_column = m_cursor.column() - 1;
  564. toggle_selection_if_needed_for_event(event);
  565. set_cursor(m_cursor.line(), new_column);
  566. if (event.shift() && m_selection.start().is_valid()) {
  567. m_selection.set_end(m_cursor);
  568. did_update_selection();
  569. }
  570. } else if (m_cursor.line() > 0) {
  571. int new_line = m_cursor.line() - 1;
  572. int new_column = lines()[new_line].length();
  573. toggle_selection_if_needed_for_event(event);
  574. set_cursor(new_line, new_column);
  575. if (event.shift() && m_selection.start().is_valid()) {
  576. m_selection.set_end(m_cursor);
  577. did_update_selection();
  578. }
  579. }
  580. return;
  581. }
  582. if (event.key() == KeyCode::Key_Right) {
  583. if (event.ctrl() && document().has_spans()) {
  584. // FIXME: Do something nice when the document has no spans.
  585. auto span = document().first_non_skippable_span_after(m_cursor);
  586. GTextPosition new_cursor = !span.has_value()
  587. ? document().spans().last().range.end()
  588. : span.value().range.start();
  589. toggle_selection_if_needed_for_event(event);
  590. set_cursor(new_cursor);
  591. if (event.shift() && m_selection.start().is_valid()) {
  592. m_selection.set_end(m_cursor);
  593. did_update_selection();
  594. }
  595. return;
  596. }
  597. int new_line = m_cursor.line();
  598. int new_column = m_cursor.column();
  599. if (m_cursor.column() < current_line().length()) {
  600. new_line = m_cursor.line();
  601. new_column = m_cursor.column() + 1;
  602. } else if (m_cursor.line() != line_count() - 1) {
  603. new_line = m_cursor.line() + 1;
  604. new_column = 0;
  605. }
  606. toggle_selection_if_needed_for_event(event);
  607. set_cursor(new_line, new_column);
  608. if (event.shift() && m_selection.start().is_valid()) {
  609. m_selection.set_end(m_cursor);
  610. did_update_selection();
  611. }
  612. return;
  613. }
  614. if (!event.ctrl() && event.key() == KeyCode::Key_Home) {
  615. size_t first_nonspace_column = current_line().first_non_whitespace_column();
  616. toggle_selection_if_needed_for_event(event);
  617. if (m_cursor.column() == first_nonspace_column)
  618. set_cursor(m_cursor.line(), 0);
  619. else
  620. set_cursor(m_cursor.line(), first_nonspace_column);
  621. if (event.shift() && m_selection.start().is_valid()) {
  622. m_selection.set_end(m_cursor);
  623. did_update_selection();
  624. }
  625. return;
  626. }
  627. if (!event.ctrl() && event.key() == KeyCode::Key_End) {
  628. toggle_selection_if_needed_for_event(event);
  629. set_cursor(m_cursor.line(), current_line().length());
  630. if (event.shift() && m_selection.start().is_valid()) {
  631. m_selection.set_end(m_cursor);
  632. did_update_selection();
  633. }
  634. return;
  635. }
  636. if (event.ctrl() && event.key() == KeyCode::Key_Home) {
  637. toggle_selection_if_needed_for_event(event);
  638. set_cursor(0, 0);
  639. if (event.shift() && m_selection.start().is_valid()) {
  640. m_selection.set_end(m_cursor);
  641. did_update_selection();
  642. }
  643. return;
  644. }
  645. if (event.ctrl() && event.key() == KeyCode::Key_End) {
  646. toggle_selection_if_needed_for_event(event);
  647. set_cursor(line_count() - 1, lines()[line_count() - 1].length());
  648. if (event.shift() && m_selection.start().is_valid()) {
  649. m_selection.set_end(m_cursor);
  650. did_update_selection();
  651. }
  652. return;
  653. }
  654. if (event.modifiers() == Mod_Ctrl && event.key() == KeyCode::Key_A) {
  655. select_all();
  656. return;
  657. }
  658. if (event.alt() && event.shift() && event.key() == KeyCode::Key_S) {
  659. sort_selected_lines();
  660. return;
  661. }
  662. if (event.key() == KeyCode::Key_Backspace) {
  663. if (is_readonly())
  664. return;
  665. if (has_selection()) {
  666. delete_selection();
  667. did_update_selection();
  668. return;
  669. }
  670. if (m_cursor.column() > 0) {
  671. int erase_count = 1;
  672. if (current_line().first_non_whitespace_column() >= m_cursor.column()) {
  673. int new_column;
  674. if (m_cursor.column() % m_soft_tab_width == 0)
  675. new_column = m_cursor.column() - m_soft_tab_width;
  676. else
  677. new_column = (m_cursor.column() / m_soft_tab_width) * m_soft_tab_width;
  678. erase_count = m_cursor.column() - new_column;
  679. }
  680. // Backspace within line
  681. GTextRange erased_range({ m_cursor.line(), m_cursor.column() - erase_count }, m_cursor);
  682. auto erased_text = document().text_in_range(erased_range);
  683. execute<RemoveTextCommand>(erased_text, erased_range);
  684. return;
  685. }
  686. if (m_cursor.column() == 0 && m_cursor.line() != 0) {
  687. // Backspace at column 0; merge with previous line
  688. size_t previous_length = line(m_cursor.line() - 1).length();
  689. GTextRange erased_range({ m_cursor.line() - 1, previous_length }, m_cursor);
  690. execute<RemoveTextCommand>("\n", erased_range);
  691. return;
  692. }
  693. return;
  694. }
  695. if (event.modifiers() == Mod_Shift && event.key() == KeyCode::Key_Delete) {
  696. if (is_readonly())
  697. return;
  698. delete_current_line();
  699. return;
  700. }
  701. if (event.key() == KeyCode::Key_Delete) {
  702. if (is_readonly())
  703. return;
  704. do_delete();
  705. return;
  706. }
  707. if (!is_readonly() && !event.ctrl() && !event.alt() && !event.text().is_empty())
  708. insert_at_cursor_or_replace_selection(event.text());
  709. }
  710. void GTextEditor::delete_current_line()
  711. {
  712. if (has_selection())
  713. return delete_selection();
  714. GTextPosition start;
  715. GTextPosition end;
  716. if (m_cursor.line() == 0 && line_count() == 1) {
  717. start = { 0, 0 };
  718. end = { 0, line(0).length() };
  719. } else if (m_cursor.line() == line_count() - 1) {
  720. start = { m_cursor.line() - 1, line(m_cursor.line()).length() };
  721. end = { m_cursor.line(), line(m_cursor.line()).length() };
  722. } else {
  723. start = { m_cursor.line(), 0 };
  724. end = { m_cursor.line() + 1, 0 };
  725. }
  726. GTextRange erased_range(start, end);
  727. execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range);
  728. }
  729. void GTextEditor::do_delete()
  730. {
  731. if (is_readonly())
  732. return;
  733. if (has_selection())
  734. return delete_selection();
  735. if (m_cursor.column() < current_line().length()) {
  736. // Delete within line
  737. GTextRange erased_range(m_cursor, { m_cursor.line(), m_cursor.column() + 1 });
  738. execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range);
  739. return;
  740. }
  741. if (m_cursor.column() == current_line().length() && m_cursor.line() != line_count() - 1) {
  742. // Delete at end of line; merge with next line
  743. GTextRange erased_range(m_cursor, { m_cursor.line() + 1, 0 });
  744. execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range);
  745. return;
  746. }
  747. }
  748. int GTextEditor::content_x_for_position(const GTextPosition& position) const
  749. {
  750. auto& line = this->line(position.line());
  751. int x_offset = -1;
  752. switch (m_text_alignment) {
  753. case TextAlignment::CenterLeft:
  754. for_each_visual_line(position.line(), [&](const Rect&, const StringView& view, size_t start_of_visual_line) {
  755. if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) <= view.length())) {
  756. x_offset = (position.column() - start_of_visual_line) * glyph_width();
  757. return IterationDecision::Break;
  758. }
  759. return IterationDecision::Continue;
  760. });
  761. return m_horizontal_content_padding + x_offset;
  762. case TextAlignment::CenterRight:
  763. // FIXME
  764. ASSERT(!is_line_wrapping_enabled());
  765. return content_width() - m_horizontal_content_padding - (line.length() * glyph_width()) + (position.column() * glyph_width());
  766. default:
  767. ASSERT_NOT_REACHED();
  768. }
  769. }
  770. Rect GTextEditor::content_rect_for_position(const GTextPosition& position) const
  771. {
  772. if (!position.is_valid())
  773. return {};
  774. ASSERT(!lines().is_empty());
  775. ASSERT(position.column() <= (current_line().length() + 1));
  776. int x = content_x_for_position(position);
  777. if (is_single_line()) {
  778. Rect rect { x, 0, 1, font().glyph_height() + 2 };
  779. rect.center_vertically_within({ {}, frame_inner_rect().size() });
  780. return rect;
  781. }
  782. Rect rect;
  783. for_each_visual_line(position.line(), [&](const Rect& visual_line_rect, const StringView& view, size_t start_of_visual_line) {
  784. if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) <= view.length())) {
  785. // NOTE: We have to subtract the horizontal padding here since it's part of the visual line rect
  786. // *and* included in what we get from content_x_for_position().
  787. rect = {
  788. visual_line_rect.x() + x - (m_horizontal_content_padding),
  789. visual_line_rect.y(),
  790. 1,
  791. line_height()
  792. };
  793. return IterationDecision::Break;
  794. }
  795. return IterationDecision::Continue;
  796. });
  797. return rect;
  798. }
  799. Rect GTextEditor::cursor_content_rect() const
  800. {
  801. return content_rect_for_position(m_cursor);
  802. }
  803. Rect GTextEditor::line_widget_rect(size_t line_index) const
  804. {
  805. auto rect = line_content_rect(line_index);
  806. rect.set_x(frame_thickness());
  807. rect.set_width(frame_inner_rect().width());
  808. rect.move_by(0, -(vertical_scrollbar().value()));
  809. rect.move_by(0, frame_thickness());
  810. rect.intersect(frame_inner_rect());
  811. return rect;
  812. }
  813. void GTextEditor::scroll_position_into_view(const GTextPosition& position)
  814. {
  815. auto rect = content_rect_for_position(position);
  816. if (position.column() == 0)
  817. rect.set_x(content_x_for_position({ position.line(), 0 }) - 2);
  818. else if (position.column() == line(position.line()).length())
  819. rect.set_x(content_x_for_position({ position.line(), line(position.line()).length() }) + 2);
  820. scroll_into_view(rect, true, true);
  821. }
  822. void GTextEditor::scroll_cursor_into_view()
  823. {
  824. scroll_position_into_view(m_cursor);
  825. }
  826. Rect GTextEditor::line_content_rect(size_t line_index) const
  827. {
  828. auto& line = this->line(line_index);
  829. if (is_single_line()) {
  830. Rect line_rect = { content_x_for_position({ line_index, 0 }), 0, (int)line.length() * glyph_width(), font().glyph_height() + 2 };
  831. line_rect.center_vertically_within({ {}, frame_inner_rect().size() });
  832. return line_rect;
  833. }
  834. if (is_line_wrapping_enabled())
  835. return m_line_visual_data[line_index].visual_rect;
  836. return {
  837. content_x_for_position({ line_index, 0 }),
  838. (int)line_index * line_height(),
  839. (int)line.length() * glyph_width(),
  840. line_height()
  841. };
  842. }
  843. void GTextEditor::update_cursor()
  844. {
  845. update(line_widget_rect(m_cursor.line()));
  846. }
  847. void GTextEditor::set_cursor(size_t line, size_t column)
  848. {
  849. set_cursor({ line, column });
  850. }
  851. void GTextEditor::set_cursor(const GTextPosition& a_position)
  852. {
  853. ASSERT(!lines().is_empty());
  854. GTextPosition position = a_position;
  855. if (position.line() >= line_count())
  856. position.set_line(line_count() - 1);
  857. if (position.column() > lines()[position.line()].length())
  858. position.set_column(lines()[position.line()].length());
  859. if (m_cursor != position) {
  860. // NOTE: If the old cursor is no longer valid, repaint everything just in case.
  861. auto old_cursor_line_rect = m_cursor.line() < line_count()
  862. ? line_widget_rect(m_cursor.line())
  863. : rect();
  864. m_cursor = position;
  865. m_cursor_state = true;
  866. scroll_cursor_into_view();
  867. update(old_cursor_line_rect);
  868. update_cursor();
  869. }
  870. cursor_did_change();
  871. if (on_cursor_change)
  872. on_cursor_change();
  873. }
  874. void GTextEditor::focusin_event(CEvent&)
  875. {
  876. update_cursor();
  877. start_timer(500);
  878. }
  879. void GTextEditor::focusout_event(CEvent&)
  880. {
  881. stop_timer();
  882. }
  883. void GTextEditor::timer_event(CTimerEvent&)
  884. {
  885. m_cursor_state = !m_cursor_state;
  886. if (is_focused())
  887. update_cursor();
  888. }
  889. bool GTextEditor::write_to_file(const StringView& path)
  890. {
  891. int fd = open_with_path_length(path.characters_without_null_termination(), path.length(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
  892. if (fd < 0) {
  893. perror("open");
  894. return false;
  895. }
  896. // Compute the final file size and ftruncate() to make writing fast.
  897. // FIXME: Remove this once the kernel is smart enough to do this instead.
  898. off_t file_size = 0;
  899. for (size_t i = 0; i < line_count(); ++i)
  900. file_size += line(i).length();
  901. file_size += line_count() - 1;
  902. int rc = ftruncate(fd, file_size);
  903. if (rc < 0) {
  904. perror("ftruncate");
  905. return false;
  906. }
  907. for (size_t i = 0; i < line_count(); ++i) {
  908. auto& line = this->line(i);
  909. if (line.length()) {
  910. ssize_t nwritten = write(fd, line.characters(), line.length());
  911. if (nwritten < 0) {
  912. perror("write");
  913. close(fd);
  914. return false;
  915. }
  916. }
  917. if (i != line_count() - 1) {
  918. char ch = '\n';
  919. ssize_t nwritten = write(fd, &ch, 1);
  920. if (nwritten != 1) {
  921. perror("write");
  922. close(fd);
  923. return false;
  924. }
  925. }
  926. }
  927. close(fd);
  928. return true;
  929. }
  930. String GTextEditor::text() const
  931. {
  932. StringBuilder builder;
  933. for (size_t i = 0; i < line_count(); ++i) {
  934. auto& line = this->line(i);
  935. builder.append(line.characters(), line.length());
  936. if (i != line_count() - 1)
  937. builder.append('\n');
  938. }
  939. return builder.to_string();
  940. }
  941. void GTextEditor::clear()
  942. {
  943. document().remove_all_lines();
  944. document().append_line(make<GTextDocumentLine>(document()));
  945. m_selection.clear();
  946. did_update_selection();
  947. set_cursor(0, 0);
  948. update();
  949. }
  950. String GTextEditor::selected_text() const
  951. {
  952. if (!has_selection())
  953. return {};
  954. return document().text_in_range(m_selection);
  955. }
  956. void GTextEditor::delete_selection()
  957. {
  958. auto selection = normalized_selection();
  959. execute<RemoveTextCommand>(selected_text(), selection);
  960. m_selection.clear();
  961. did_update_selection();
  962. did_change();
  963. set_cursor(selection.start());
  964. update();
  965. }
  966. void GTextEditor::insert_at_cursor_or_replace_selection(const StringView& text)
  967. {
  968. ASSERT(!is_readonly());
  969. if (has_selection())
  970. delete_selection();
  971. execute<InsertTextCommand>(text, m_cursor);
  972. }
  973. void GTextEditor::cut()
  974. {
  975. if (is_readonly())
  976. return;
  977. auto selected_text = this->selected_text();
  978. printf("Cut: \"%s\"\n", selected_text.characters());
  979. GClipboard::the().set_data(selected_text);
  980. delete_selection();
  981. }
  982. void GTextEditor::copy()
  983. {
  984. auto selected_text = this->selected_text();
  985. printf("Copy: \"%s\"\n", selected_text.characters());
  986. GClipboard::the().set_data(selected_text);
  987. }
  988. void GTextEditor::paste()
  989. {
  990. if (is_readonly())
  991. return;
  992. auto paste_text = GClipboard::the().data();
  993. printf("Paste: \"%s\"\n", paste_text.characters());
  994. insert_at_cursor_or_replace_selection(paste_text);
  995. }
  996. void GTextEditor::enter_event(CEvent&)
  997. {
  998. ASSERT(window());
  999. window()->set_override_cursor(GStandardCursor::IBeam);
  1000. }
  1001. void GTextEditor::leave_event(CEvent&)
  1002. {
  1003. ASSERT(window());
  1004. window()->set_override_cursor(GStandardCursor::None);
  1005. }
  1006. void GTextEditor::did_change()
  1007. {
  1008. update_content_size();
  1009. recompute_all_visual_lines();
  1010. m_undo_action->set_enabled(can_undo());
  1011. m_redo_action->set_enabled(can_redo());
  1012. if (!m_has_pending_change_notification) {
  1013. m_has_pending_change_notification = true;
  1014. deferred_invoke([this](auto&) {
  1015. if (!m_has_pending_change_notification)
  1016. return;
  1017. if (on_change)
  1018. on_change();
  1019. m_has_pending_change_notification = false;
  1020. });
  1021. }
  1022. }
  1023. void GTextEditor::set_readonly(bool readonly)
  1024. {
  1025. if (m_readonly == readonly)
  1026. return;
  1027. m_readonly = readonly;
  1028. m_cut_action->set_enabled(!is_readonly() && has_selection());
  1029. m_delete_action->set_enabled(!is_readonly());
  1030. m_paste_action->set_enabled(!is_readonly());
  1031. }
  1032. void GTextEditor::did_update_selection()
  1033. {
  1034. m_cut_action->set_enabled(!is_readonly() && has_selection());
  1035. m_copy_action->set_enabled(has_selection());
  1036. if (on_selection_change)
  1037. on_selection_change();
  1038. if (is_line_wrapping_enabled()) {
  1039. // FIXME: Try to repaint less.
  1040. update();
  1041. }
  1042. }
  1043. void GTextEditor::context_menu_event(GContextMenuEvent& event)
  1044. {
  1045. if (!m_context_menu) {
  1046. m_context_menu = GMenu::construct();
  1047. m_context_menu->add_action(undo_action());
  1048. m_context_menu->add_action(redo_action());
  1049. m_context_menu->add_separator();
  1050. m_context_menu->add_action(cut_action());
  1051. m_context_menu->add_action(copy_action());
  1052. m_context_menu->add_action(paste_action());
  1053. m_context_menu->add_action(delete_action());
  1054. if (!m_custom_context_menu_actions.is_empty()) {
  1055. m_context_menu->add_separator();
  1056. for (auto& action : m_custom_context_menu_actions) {
  1057. m_context_menu->add_action(action);
  1058. }
  1059. }
  1060. }
  1061. m_context_menu->popup(event.screen_position());
  1062. }
  1063. void GTextEditor::set_text_alignment(TextAlignment alignment)
  1064. {
  1065. if (m_text_alignment == alignment)
  1066. return;
  1067. m_text_alignment = alignment;
  1068. update();
  1069. }
  1070. void GTextEditor::resize_event(GResizeEvent& event)
  1071. {
  1072. GScrollableWidget::resize_event(event);
  1073. update_content_size();
  1074. recompute_all_visual_lines();
  1075. }
  1076. void GTextEditor::set_selection(const GTextRange& selection)
  1077. {
  1078. if (m_selection == selection)
  1079. return;
  1080. m_selection = selection;
  1081. set_cursor(m_selection.end());
  1082. scroll_position_into_view(normalized_selection().start());
  1083. update();
  1084. }
  1085. void GTextEditor::clear_selection()
  1086. {
  1087. if (!has_selection())
  1088. return;
  1089. m_selection.clear();
  1090. update();
  1091. }
  1092. void GTextEditor::recompute_all_visual_lines()
  1093. {
  1094. int y_offset = 0;
  1095. for (size_t line_index = 0; line_index < line_count(); ++line_index) {
  1096. recompute_visual_lines(line_index);
  1097. m_line_visual_data[line_index].visual_rect.set_y(y_offset);
  1098. y_offset += m_line_visual_data[line_index].visual_rect.height();
  1099. }
  1100. update_content_size();
  1101. }
  1102. void GTextEditor::ensure_cursor_is_valid()
  1103. {
  1104. auto new_cursor = m_cursor;
  1105. if (new_cursor.line() >= line_count())
  1106. new_cursor.set_line(line_count() - 1);
  1107. if (new_cursor.column() > line(new_cursor.line()).length())
  1108. new_cursor.set_column(line(new_cursor.line()).length());
  1109. if (m_cursor != new_cursor)
  1110. set_cursor(new_cursor);
  1111. }
  1112. size_t GTextEditor::visual_line_containing(size_t line_index, size_t column) const
  1113. {
  1114. size_t visual_line_index = 0;
  1115. for_each_visual_line(line_index, [&](const Rect&, const StringView& view, size_t start_of_visual_line) {
  1116. if (column >= start_of_visual_line && ((column - start_of_visual_line) < view.length()))
  1117. return IterationDecision::Break;
  1118. ++visual_line_index;
  1119. return IterationDecision::Continue;
  1120. });
  1121. return visual_line_index;
  1122. }
  1123. void GTextEditor::recompute_visual_lines(size_t line_index)
  1124. {
  1125. auto& line = document().line(line_index);
  1126. auto& visual_data = m_line_visual_data[line_index];
  1127. visual_data.visual_line_breaks.clear_with_capacity();
  1128. int available_width = visible_text_rect_in_inner_coordinates().width();
  1129. if (is_line_wrapping_enabled()) {
  1130. int line_width_so_far = 0;
  1131. for (size_t i = 0; i < line.length(); ++i) {
  1132. auto ch = line.characters()[i];
  1133. auto glyph_width = font().glyph_width(ch);
  1134. if ((line_width_so_far + glyph_width) > available_width) {
  1135. visual_data.visual_line_breaks.append(i);
  1136. line_width_so_far = glyph_width;
  1137. continue;
  1138. }
  1139. line_width_so_far += glyph_width;
  1140. }
  1141. }
  1142. visual_data.visual_line_breaks.append(line.length());
  1143. if (is_line_wrapping_enabled())
  1144. visual_data.visual_rect = { m_horizontal_content_padding, 0, available_width, visual_data.visual_line_breaks.size() * line_height() };
  1145. else
  1146. visual_data.visual_rect = { m_horizontal_content_padding, 0, font().width(line.view()), line_height() };
  1147. }
  1148. template<typename Callback>
  1149. void GTextEditor::for_each_visual_line(size_t line_index, Callback callback) const
  1150. {
  1151. auto editor_visible_text_rect = visible_text_rect_in_inner_coordinates();
  1152. size_t start_of_line = 0;
  1153. size_t visual_line_index = 0;
  1154. auto& line = document().line(line_index);
  1155. auto& visual_data = m_line_visual_data[line_index];
  1156. for (auto visual_line_break : visual_data.visual_line_breaks) {
  1157. auto visual_line_view = StringView(line.characters() + start_of_line, visual_line_break - start_of_line);
  1158. Rect visual_line_rect {
  1159. visual_data.visual_rect.x(),
  1160. visual_data.visual_rect.y() + ((int)visual_line_index * line_height()),
  1161. font().width(visual_line_view),
  1162. line_height()
  1163. };
  1164. if (is_right_text_alignment(text_alignment()))
  1165. visual_line_rect.set_right_without_resize(editor_visible_text_rect.right());
  1166. if (!is_multi_line())
  1167. visual_line_rect.center_vertically_within(editor_visible_text_rect);
  1168. if (callback(visual_line_rect, visual_line_view, start_of_line) == IterationDecision::Break)
  1169. break;
  1170. start_of_line = visual_line_break;
  1171. ++visual_line_index;
  1172. }
  1173. }
  1174. void GTextEditor::set_line_wrapping_enabled(bool enabled)
  1175. {
  1176. if (m_line_wrapping_enabled == enabled)
  1177. return;
  1178. m_line_wrapping_enabled = enabled;
  1179. horizontal_scrollbar().set_visible(!m_line_wrapping_enabled);
  1180. update_content_size();
  1181. recompute_all_visual_lines();
  1182. update();
  1183. }
  1184. void GTextEditor::add_custom_context_menu_action(GAction& action)
  1185. {
  1186. m_custom_context_menu_actions.append(action);
  1187. }
  1188. void GTextEditor::did_change_font()
  1189. {
  1190. vertical_scrollbar().set_step(line_height());
  1191. GWidget::did_change_font();
  1192. }
  1193. void GTextEditor::document_did_append_line()
  1194. {
  1195. m_line_visual_data.append(make<LineVisualData>());
  1196. recompute_all_visual_lines();
  1197. update();
  1198. }
  1199. void GTextEditor::document_did_remove_line(size_t line_index)
  1200. {
  1201. m_line_visual_data.remove(line_index);
  1202. recompute_all_visual_lines();
  1203. update();
  1204. }
  1205. void GTextEditor::document_did_remove_all_lines()
  1206. {
  1207. m_line_visual_data.clear();
  1208. recompute_all_visual_lines();
  1209. update();
  1210. }
  1211. void GTextEditor::document_did_insert_line(size_t line_index)
  1212. {
  1213. m_line_visual_data.insert(line_index, make<LineVisualData>());
  1214. recompute_all_visual_lines();
  1215. update();
  1216. }
  1217. void GTextEditor::document_did_change()
  1218. {
  1219. did_change();
  1220. update();
  1221. }
  1222. void GTextEditor::document_did_set_text()
  1223. {
  1224. m_line_visual_data.clear();
  1225. for (size_t i = 0; i < m_document->line_count(); ++i)
  1226. m_line_visual_data.append(make<LineVisualData>());
  1227. document_did_change();
  1228. }
  1229. void GTextEditor::document_did_set_cursor(const GTextPosition& position)
  1230. {
  1231. set_cursor(position);
  1232. }
  1233. void GTextEditor::set_document(GTextDocument& document)
  1234. {
  1235. if (m_document.ptr() == &document)
  1236. return;
  1237. if (m_document)
  1238. m_document->unregister_client(*this);
  1239. m_document = document;
  1240. m_line_visual_data.clear();
  1241. for (size_t i = 0; i < m_document->line_count(); ++i) {
  1242. m_line_visual_data.append(make<LineVisualData>());
  1243. }
  1244. m_cursor = { 0, 0 };
  1245. if (has_selection())
  1246. m_selection.clear();
  1247. recompute_all_visual_lines();
  1248. update();
  1249. m_document->register_client(*this);
  1250. }
  1251. void GTextEditor::flush_pending_change_notification_if_needed()
  1252. {
  1253. if (!m_has_pending_change_notification)
  1254. return;
  1255. if (on_change)
  1256. on_change();
  1257. m_has_pending_change_notification = false;
  1258. }