gl-input.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. // Copyright (C) 2004-2022 Artifex Software, Inc.
  2. //
  3. // This file is part of MuPDF.
  4. //
  5. // MuPDF is free software: you can redistribute it and/or modify it under the
  6. // terms of the GNU Affero General Public License as published by the Free
  7. // Software Foundation, either version 3 of the License, or (at your option)
  8. // any later version.
  9. //
  10. // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
  11. // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  12. // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  13. // details.
  14. //
  15. // You should have received a copy of the GNU Affero General Public License
  16. // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
  17. //
  18. // Alternative licensing terms are available from the licensor.
  19. // For commercial licensing, see <https://www.artifex.com/> or contact
  20. // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
  21. // CA 94129, USA, for further information.
  22. #include "gl-app.h"
  23. #include <string.h>
  24. #include <stdio.h>
  25. static char *find_string_location(char *s, char *e, float w, float x)
  26. {
  27. int c;
  28. while (s < e)
  29. {
  30. int n = fz_chartorune(&c, s);
  31. float cw = ui_measure_character(c);
  32. if (w + (cw / 2) >= x)
  33. return s;
  34. w += cw;
  35. s += n;
  36. }
  37. return e;
  38. }
  39. static char *find_input_location(struct line *lines, int n, float left, float top, float x, float y)
  40. {
  41. int i = 0;
  42. if (y > top) i = (y - top) / ui.lineheight;
  43. if (i >= n) i = n - 1;
  44. return find_string_location(lines[i].a, lines[i].b, left, x);
  45. }
  46. static inline int myisalnum(char *s)
  47. {
  48. int cat, c;
  49. fz_chartorune(&c, s);
  50. cat = ucdn_get_general_category(c);
  51. if (cat >= UCDN_GENERAL_CATEGORY_LL && cat <= UCDN_GENERAL_CATEGORY_LU)
  52. return 1;
  53. if (cat >= UCDN_GENERAL_CATEGORY_ND && cat <= UCDN_GENERAL_CATEGORY_NO)
  54. return 1;
  55. return 0;
  56. }
  57. static char *home_line(char *p, char *start)
  58. {
  59. while (p > start)
  60. {
  61. if (p[-1] == '\n' || p[-1] == '\r')
  62. return p;
  63. --p;
  64. }
  65. return p;
  66. }
  67. static char *end_line(char *p, char *end)
  68. {
  69. while (p < end)
  70. {
  71. if (p[0] == '\n' || p[0] == '\r')
  72. return p;
  73. ++p;
  74. }
  75. return p;
  76. }
  77. static char *up_line(char *p, char *start)
  78. {
  79. while (p > start)
  80. {
  81. --p;
  82. if (*p == '\n' || *p == '\r')
  83. return p;
  84. }
  85. return p;
  86. }
  87. static char *down_line(char *p, char *end)
  88. {
  89. while (p < end)
  90. {
  91. if (*p == '\n' || *p == '\r')
  92. return p+1;
  93. ++p;
  94. }
  95. return p;
  96. }
  97. static char *prev_char(char *p, char *start)
  98. {
  99. --p;
  100. while ((*p & 0xC0) == 0x80 && p > start) /* skip middle and final multibytes */
  101. --p;
  102. return p;
  103. }
  104. static char *next_char(char *p)
  105. {
  106. ++p;
  107. while ((*p & 0xC0) == 0x80) /* skip middle and final multibytes */
  108. ++p;
  109. return p;
  110. }
  111. static char *prev_word(char *p, char *start)
  112. {
  113. while (p > start && !myisalnum(prev_char(p, start))) p = prev_char(p, start);
  114. while (p > start && myisalnum(prev_char(p, start))) p = prev_char(p, start);
  115. return p;
  116. }
  117. static char *next_word(char *p, char *end)
  118. {
  119. while (p < end && !myisalnum(p)) p = next_char(p);
  120. while (p < end && myisalnum(p)) p = next_char(p);
  121. return p;
  122. }
  123. static void ui_input_delete_selection(struct input *input)
  124. {
  125. char *p = input->p < input->q ? input->p : input->q;
  126. char *q = input->p > input->q ? input->p : input->q;
  127. memmove(p, q, input->end - q);
  128. input->end -= q - p;
  129. *input->end = 0;
  130. input->p = input->q = p;
  131. }
  132. static void ui_input_paste(struct input *input, const char *buf)
  133. {
  134. int n = (int)strlen(buf);
  135. if (input->widget)
  136. {
  137. char *newtext;
  138. int selStart = input->p - input->text;
  139. int selEnd = input->q - input->text;
  140. if (pdf_edit_text_field_value(ctx, input->widget, input->text, buf, &selStart, &selEnd, &newtext))
  141. {
  142. size_t len = strlen(newtext);
  143. if (len > sizeof(input->text)-1)
  144. len = sizeof(input->text)-1;
  145. memcpy(input->text, newtext, len);
  146. input->text[len] = 0;
  147. fz_free(ctx, newtext);
  148. input->p = input->text + selStart;
  149. input->q = input->p;
  150. input->end = input->text + len;
  151. }
  152. return;
  153. }
  154. if (input->p != input->q)
  155. ui_input_delete_selection(input);
  156. if (input->end + n + 1 < input->text + sizeof(input->text))
  157. {
  158. memmove(input->p + n, input->p, input->end - input->p);
  159. memmove(input->p, buf, n);
  160. input->p += n;
  161. input->end += n;
  162. *input->end = 0;
  163. }
  164. input->q = input->p;
  165. }
  166. static void ui_do_copy(struct input *input)
  167. {
  168. if (input->p != input->q)
  169. {
  170. char buf[sizeof input->text];
  171. char *p = input->p < input->q ? input->p : input->q;
  172. char *q = input->p > input->q ? input->p : input->q;
  173. memmove(buf, p, q - p);
  174. buf[q-p] = 0;
  175. ui_set_clipboard(buf);
  176. }
  177. }
  178. static void ui_do_cut(struct input *input)
  179. {
  180. if (input->p != input->q)
  181. {
  182. ui_do_copy(input);
  183. ui_input_delete_selection(input);
  184. }
  185. }
  186. static void ui_do_paste(struct input *input, int multiline)
  187. {
  188. const char *buf = ui_get_clipboard();
  189. char *p, *oneline = NULL;
  190. if (!buf)
  191. return;
  192. if (multiline)
  193. {
  194. ui_input_paste(input, buf);
  195. return;
  196. }
  197. p = oneline = strdup(buf);
  198. while (*p)
  199. {
  200. if (*p == '\n' || *p == '\r')
  201. *p = ' ';
  202. p++;
  203. }
  204. ui_input_paste(input, oneline);
  205. free(oneline);
  206. }
  207. static int ui_input_key(struct input *input, int multiline)
  208. {
  209. switch (ui.key)
  210. {
  211. case 0:
  212. return UI_INPUT_NONE;
  213. case KEY_LEFT:
  214. if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT)
  215. {
  216. input->q = prev_word(input->q, input->text);
  217. }
  218. else if (ui.mod == GLUT_ACTIVE_CTRL)
  219. {
  220. if (input->p != input->q)
  221. input->p = input->q = input->p < input->q ? input->p : input->q;
  222. else
  223. input->p = input->q = prev_word(input->q, input->text);
  224. }
  225. else if (ui.mod == GLUT_ACTIVE_SHIFT)
  226. {
  227. if (input->q > input->text)
  228. input->q = prev_char(input->q, input->text);
  229. }
  230. else if (ui.mod == 0)
  231. {
  232. if (input->p != input->q)
  233. input->p = input->q = input->p < input->q ? input->p : input->q;
  234. else if (input->q > input->text)
  235. input->p = input->q = prev_char(input->q, input->text);
  236. }
  237. break;
  238. case KEY_RIGHT:
  239. if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT)
  240. {
  241. input->q = next_word(input->q, input->end);
  242. }
  243. else if (ui.mod == GLUT_ACTIVE_CTRL)
  244. {
  245. if (input->p != input->q)
  246. input->p = input->q = input->p > input->q ? input->p : input->q;
  247. else
  248. input->p = input->q = next_word(input->q, input->end);
  249. }
  250. else if (ui.mod == GLUT_ACTIVE_SHIFT)
  251. {
  252. if (input->q < input->end)
  253. input->q = next_char(input->q);
  254. }
  255. else if (ui.mod == 0)
  256. {
  257. if (input->p != input->q)
  258. input->p = input->q = input->p > input->q ? input->p : input->q;
  259. else if (input->q < input->end)
  260. input->p = input->q = next_char(input->q);
  261. }
  262. break;
  263. case KEY_UP:
  264. if (ui.mod & GLUT_ACTIVE_SHIFT)
  265. input->q = up_line(input->q, input->text);
  266. else
  267. input->p = input->q = up_line(input->p, input->text);
  268. break;
  269. case KEY_DOWN:
  270. if (ui.mod & GLUT_ACTIVE_SHIFT)
  271. input->q = down_line(input->q, input->end);
  272. else
  273. input->p = input->q = down_line(input->q, input->end);
  274. break;
  275. case KEY_HOME:
  276. if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT)
  277. input->q = input->text;
  278. else if (ui.mod == GLUT_ACTIVE_SHIFT)
  279. input->q = home_line(input->q, input->text);
  280. else if (ui.mod == GLUT_ACTIVE_CTRL)
  281. input->p = input->q = input->text;
  282. else if (ui.mod == 0)
  283. input->p = input->q = home_line(input->p, input->text);
  284. break;
  285. case KEY_END:
  286. if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT)
  287. input->q = input->end;
  288. else if (ui.mod == GLUT_ACTIVE_SHIFT)
  289. input->q = end_line(input->q, input->end);
  290. else if (ui.mod == GLUT_ACTIVE_CTRL)
  291. input->p = input->q = input->end;
  292. else if (ui.mod == 0)
  293. input->p = input->q = end_line(input->p, input->end);
  294. break;
  295. case KEY_DELETE:
  296. if (ui.mod == GLUT_ACTIVE_SHIFT)
  297. {
  298. ui_do_cut(input);
  299. }
  300. else if (input->p != input->q)
  301. {
  302. ui_input_delete_selection(input);
  303. }
  304. else if (input->p < input->end)
  305. {
  306. char *np = next_char(input->p);
  307. memmove(input->p, np, input->end - np);
  308. input->end -= np - input->p;
  309. *input->end = 0;
  310. input->q = input->p;
  311. }
  312. break;
  313. case KEY_ESCAPE:
  314. ui.focus = NULL;
  315. return UI_INPUT_NONE;
  316. case KEY_ENTER:
  317. if (!multiline)
  318. {
  319. ui.focus = NULL;
  320. return UI_INPUT_ACCEPT;
  321. }
  322. ui_input_paste(input, "\n");
  323. break;
  324. case KEY_BACKSPACE:
  325. if (input->p != input->q)
  326. ui_input_delete_selection(input);
  327. else if (input->p > input->text)
  328. {
  329. char *pp;
  330. if (ui.mod == GLUT_ACTIVE_CTRL)
  331. pp = prev_word(input->p, input->text);
  332. else
  333. pp = prev_char(input->p, input->text);
  334. memmove(pp, input->p, input->end - input->p);
  335. input->end -= input->p - pp;
  336. *input->end = 0;
  337. input->q = input->p = pp;
  338. }
  339. break;
  340. case KEY_CTL_A:
  341. input->p = input->q = input->text;
  342. break;
  343. case KEY_CTL_E:
  344. input->p = input->q = input->end;
  345. break;
  346. case KEY_CTL_W:
  347. if (input->p != input->q)
  348. ui_input_delete_selection(input);
  349. else
  350. {
  351. input->p = prev_word(input->p, input->text);
  352. ui_input_delete_selection(input);
  353. }
  354. break;
  355. case KEY_CTL_U:
  356. input->p = input->q = input->end = input->text;
  357. *input->end = 0;
  358. break;
  359. case KEY_CTL_C:
  360. ui_do_copy(input);
  361. break;
  362. case KEY_CTL_X:
  363. ui_do_cut(input);
  364. break;
  365. case KEY_CTL_V:
  366. ui_do_paste(input, multiline);
  367. break;
  368. case KEY_INSERT:
  369. if (ui.mod == GLUT_ACTIVE_CTRL)
  370. ui_do_copy(input);
  371. if (ui.mod == GLUT_ACTIVE_SHIFT)
  372. ui_do_paste(input, multiline);
  373. break;
  374. default:
  375. if (ui.key >= 32 && ui.plain)
  376. {
  377. int cat = ucdn_get_general_category(ui.key);
  378. if (ui.key == ' ' || (cat >= UCDN_GENERAL_CATEGORY_LL && cat < UCDN_GENERAL_CATEGORY_ZL))
  379. {
  380. char buf[9];
  381. int n = fz_runetochar(buf, ui.key);
  382. buf[n] = 0;
  383. ui_input_paste(input, buf);
  384. }
  385. }
  386. break;
  387. }
  388. return UI_INPUT_EDIT;
  389. }
  390. void ui_input_init(struct input *input, const char *text)
  391. {
  392. fz_strlcpy(input->text, text, sizeof input->text);
  393. input->end = input->text + strlen(input->text);
  394. input->p = input->text;
  395. input->q = input->end;
  396. input->scroll = 0;
  397. }
  398. int ui_input(struct input *input, int width, int height)
  399. {
  400. struct line lines[500];
  401. fz_irect area;
  402. float ax, bx;
  403. int ay, sy;
  404. char *p, *q;
  405. int state;
  406. int i, n;
  407. if (ui.focus == input)
  408. state = ui_input_key(input, height > 1);
  409. else
  410. state = UI_INPUT_NONE;
  411. area = ui_pack(width, ui.lineheight * height + 6);
  412. ui_draw_bevel_rect(area, UI_COLOR_TEXT_BG, 1);
  413. area = fz_expand_irect(area, -2);
  414. if (height > 1)
  415. area.x1 -= ui.lineheight;
  416. n = ui_break_lines(input->text, lines, height > 1 ? nelem(lines) : 1, area.x1-area.x0-2, NULL);
  417. if (height > 1)
  418. ui_scrollbar(area.x1, area.y0, area.x1+ui.lineheight, area.y1, &input->scroll, 1, fz_maxi(0, n-height)+1, NULL);
  419. else
  420. input->scroll = 0;
  421. ax = area.x0 + 2;
  422. bx = area.x1 - 2;
  423. ay = area.y0 + 1;
  424. sy = input->scroll * ui.lineheight;
  425. if (ui_mouse_inside(area))
  426. {
  427. ui.hot = input;
  428. if (!ui.active || ui.active == input)
  429. ui.cursor = GLUT_CURSOR_TEXT;
  430. if (!ui.active && ui.down)
  431. {
  432. input->p = find_input_location(lines, n, ax, ay-sy, ui.x, ui.y);
  433. ui.active = input;
  434. }
  435. }
  436. if (ui.active == input)
  437. {
  438. input->q = find_input_location(lines, n, ax, ay-sy, ui.x, ui.y);
  439. ui.focus = input;
  440. }
  441. p = input->p < input->q ? input->p : input->q;
  442. q = input->p > input->q ? input->p : input->q;
  443. for (i = input->scroll; i < n && i < input->scroll+height; ++i)
  444. {
  445. char *a = lines[i].a, *b = lines[i].b;
  446. if (ui.focus == input)
  447. {
  448. if (p >= a && p <= b && q >= a && q <= b)
  449. {
  450. float px = ax + ui_measure_string_part(a, p);
  451. float qx = px + ui_measure_string_part(p, q);
  452. glColorHex(UI_COLOR_TEXT_SEL_BG);
  453. glRectf(px, ay, qx+1, ay + ui.lineheight);
  454. glColorHex(UI_COLOR_TEXT_FG);
  455. ui_draw_string_part(ax, ay, a, p);
  456. glColorHex(UI_COLOR_TEXT_SEL_FG);
  457. ui_draw_string_part(px, ay, p, q);
  458. glColorHex(UI_COLOR_TEXT_FG);
  459. ui_draw_string_part(qx, ay, q, b);
  460. }
  461. else if (p < a && q >= a && q <= b)
  462. {
  463. float qx = ax + ui_measure_string_part(a, q);
  464. glColorHex(UI_COLOR_TEXT_SEL_BG);
  465. glRectf(ax, ay, qx+1, ay + ui.lineheight);
  466. glColorHex(UI_COLOR_TEXT_SEL_FG);
  467. ui_draw_string_part(ax, ay, a, q);
  468. glColorHex(UI_COLOR_TEXT_FG);
  469. ui_draw_string_part(qx, ay, q, b);
  470. }
  471. else if (p >= a && p <= b && q > b)
  472. {
  473. float px = ax + ui_measure_string_part(a, p);
  474. glColorHex(UI_COLOR_TEXT_SEL_BG);
  475. glRectf(px, ay, bx, ay + ui.lineheight);
  476. glColorHex(UI_COLOR_TEXT_FG);
  477. ui_draw_string_part(ax, ay, a, p);
  478. glColorHex(UI_COLOR_TEXT_SEL_FG);
  479. ui_draw_string_part(px, ay, p, b);
  480. }
  481. else if (p < a && q > b)
  482. {
  483. glColorHex(UI_COLOR_TEXT_SEL_BG);
  484. glRectf(ax, ay, bx, ay + ui.lineheight);
  485. glColorHex(UI_COLOR_TEXT_SEL_FG);
  486. ui_draw_string_part(ax, ay, a, b);
  487. }
  488. else
  489. {
  490. glColorHex(UI_COLOR_TEXT_FG);
  491. ui_draw_string_part(ax, ay, a, b);
  492. }
  493. }
  494. else
  495. {
  496. glColorHex(UI_COLOR_TEXT_FG);
  497. ui_draw_string_part(ax, ay, a, b);
  498. }
  499. ay += ui.lineheight;
  500. }
  501. return state;
  502. }
  503. void ui_readline_init(struct readline *readline, const char *text)
  504. {
  505. int i;
  506. memset(readline->buffer, 0, sizeof readline->buffer);
  507. for (i = 0; i < UI_READLINE_SIZE; i++)
  508. readline->history[i] = &(readline->buffer[i][0]);
  509. readline->used = 0;
  510. readline->current = -1;
  511. ui_input_init(&readline->input, text ? text : "");
  512. }
  513. const char *ui_readline(struct readline *readline, int width)
  514. {
  515. int state;
  516. /* Override key up/down to navigate history. */
  517. switch (ui.key)
  518. {
  519. case KEY_UP:
  520. ui.key = 0;
  521. if (readline->current == -1) /* no history entries */
  522. break;
  523. if (readline->current == 0) /* no older entries */
  524. break;
  525. readline->current--;
  526. ui_input_init(&readline->input, readline->history[readline->current]);
  527. readline->input.p = readline->input.q = readline->input.end;
  528. break;
  529. case KEY_DOWN:
  530. ui.key = 0;
  531. if (readline->current == -1) /* no history entries */
  532. break;
  533. if (readline->current == readline->used) /* no newer entries */
  534. break;
  535. if (readline->current == readline->used - 1) /* at insertion point */
  536. {
  537. readline->current++;
  538. ui_input_init(&readline->input, "");
  539. readline->input.p = readline->input.q = readline->input.end;
  540. break;
  541. }
  542. readline->current++;
  543. ui_input_init(&readline->input, readline->history[readline->current]);
  544. readline->input.p = readline->input.q = readline->input.end;
  545. break;
  546. }
  547. state = ui_input(&readline->input, width, 1);
  548. /* Remember line in history. */
  549. if (state == UI_INPUT_ACCEPT)
  550. {
  551. char *accepted;
  552. if (readline->used == UI_READLINE_SIZE)
  553. {
  554. char *tmp = readline->history[0];
  555. memmove(readline->history, readline->history + 1, (UI_READLINE_SIZE - 1) * sizeof(char *));
  556. readline->history[UI_READLINE_SIZE - 1] = tmp;
  557. fz_strlcpy(readline->history[UI_READLINE_SIZE - 1], readline->input.text, UI_INPUT_SIZE);
  558. accepted = readline->history[UI_READLINE_SIZE - 1];
  559. readline->current = UI_READLINE_SIZE;
  560. }
  561. else
  562. {
  563. fz_strlcpy(readline->history[readline->used], readline->input.text, UI_INPUT_SIZE);
  564. accepted = readline->history[readline->used];
  565. readline->used++;
  566. readline->current = readline->used;
  567. }
  568. return accepted;
  569. }
  570. return NULL;
  571. }