diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-08-30 17:32:39 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-08-30 17:32:39 +0300 |
commit | e43891459ff4640cdc97ca7cb71891a54c0d1989 (patch) | |
tree | ed7eab9fb96bb40217351a0b402aaefe7d8fd95b | |
parent | 3eb85b3d0fdd007ad18c3b522795b0ba3a038d1f (diff) |
InputWidget: Undo, copy/cut, select all
-rw-r--r-- | src/ui/documentwidget.c | 3 | ||||
-rw-r--r-- | src/ui/inputwidget.c | 111 | ||||
-rw-r--r-- | src/ui/sidebarwidget.c | 1 |
3 files changed, 105 insertions, 10 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index b954040a..676c94bd 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -1097,7 +1097,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1097 | : "Not trusted")); | 1097 | : "Not trusted")); |
1098 | return iTrue; | 1098 | return iTrue; |
1099 | } | 1099 | } |
1100 | else if (equal_Command(cmd, "copy") && document_App() == d) { | 1100 | else if (equal_Command(cmd, "copy") && document_App() == d && !focus_Widget()) { |
1101 | iString *copied; | 1101 | iString *copied; |
1102 | if (d->selectMark.start) { | 1102 | if (d->selectMark.start) { |
1103 | iRangecc mark = d->selectMark; | 1103 | iRangecc mark = d->selectMark; |
@@ -1441,6 +1441,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
1441 | case drag_ClickResult: { | 1441 | case drag_ClickResult: { |
1442 | /* Begin selecting a range of text. */ | 1442 | /* Begin selecting a range of text. */ |
1443 | if (!d->selecting) { | 1443 | if (!d->selecting) { |
1444 | setFocus_Widget(NULL); /* TODO: Focus this document? */ | ||
1444 | d->selecting = iTrue; | 1445 | d->selecting = iTrue; |
1445 | d->selectMark.start = d->selectMark.end = | 1446 | d->selectMark.start = d->selectMark.end = |
1446 | sourceLoc_DocumentWidget_(d, d->click.startPos); | 1447 | sourceLoc_DocumentWidget_(d, d->click.startPos); |
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 3daecfa8..9884ff7d 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -29,7 +29,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
29 | #include <SDL_clipboard.h> | 29 | #include <SDL_clipboard.h> |
30 | #include <SDL_timer.h> | 30 | #include <SDL_timer.h> |
31 | 31 | ||
32 | static const int REFRESH_INTERVAL = 256; | 32 | static const int refreshInterval_InputWidget_ = 256; |
33 | static const size_t maxUndo_InputWidget_ = 64; | ||
34 | |||
35 | iDeclareType(InputUndo) | ||
36 | |||
37 | struct Impl_InputUndo { | ||
38 | iArray text; | ||
39 | size_t cursor; | ||
40 | }; | ||
41 | |||
42 | static void init_InputUndo_(iInputUndo *d, const iArray *text, size_t cursor) { | ||
43 | initCopy_Array(&d->text, text); | ||
44 | d->cursor = cursor; | ||
45 | } | ||
46 | |||
47 | static void deinit_InputUndo_(iInputUndo *d) { | ||
48 | deinit_Array(&d->text); | ||
49 | } | ||
33 | 50 | ||
34 | struct Impl_InputWidget { | 51 | struct Impl_InputWidget { |
35 | iWidget widget; | 52 | iWidget widget; |
@@ -44,6 +61,7 @@ struct Impl_InputWidget { | |||
44 | size_t cursor; | 61 | size_t cursor; |
45 | size_t lastCursor; | 62 | size_t lastCursor; |
46 | iRanges mark; | 63 | iRanges mark; |
64 | iArray undoStack; | ||
47 | int font; | 65 | int font; |
48 | iClick click; | 66 | iClick click; |
49 | uint32_t timer; | 67 | uint32_t timer; |
@@ -51,6 +69,13 @@ struct Impl_InputWidget { | |||
51 | 69 | ||
52 | iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) | 70 | iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) |
53 | 71 | ||
72 | static void clearUndo_InputWidget_(iInputWidget *d) { | ||
73 | iForEach(Array, i, &d->undoStack) { | ||
74 | deinit_InputUndo_(i.value); | ||
75 | } | ||
76 | clear_Array(&d->undoStack); | ||
77 | } | ||
78 | |||
54 | void init_InputWidget(iInputWidget *d, size_t maxLen) { | 79 | void init_InputWidget(iInputWidget *d, size_t maxLen) { |
55 | iWidget *w = &d->widget; | 80 | iWidget *w = &d->widget; |
56 | init_Widget(w); | 81 | init_Widget(w); |
@@ -58,6 +83,7 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
58 | init_Array(&d->text, sizeof(iChar)); | 83 | init_Array(&d->text, sizeof(iChar)); |
59 | init_Array(&d->oldText, sizeof(iChar)); | 84 | init_Array(&d->oldText, sizeof(iChar)); |
60 | init_String(&d->hint); | 85 | init_String(&d->hint); |
86 | init_Array(&d->undoStack, sizeof(iInputUndo)); | ||
61 | d->font = uiInput_FontId; | 87 | d->font = uiInput_FontId; |
62 | d->cursor = 0; | 88 | d->cursor = 0; |
63 | iZap(d->mark); | 89 | iZap(d->mark); |
@@ -73,6 +99,8 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
73 | } | 99 | } |
74 | 100 | ||
75 | void deinit_InputWidget(iInputWidget *d) { | 101 | void deinit_InputWidget(iInputWidget *d) { |
102 | clearUndo_InputWidget_(d); | ||
103 | deinit_Array(&d->undoStack); | ||
76 | if (d->timer) { | 104 | if (d->timer) { |
77 | SDL_RemoveTimer(d->timer); | 105 | SDL_RemoveTimer(d->timer); |
78 | } | 106 | } |
@@ -81,6 +109,29 @@ void deinit_InputWidget(iInputWidget *d) { | |||
81 | deinit_Array(&d->text); | 109 | deinit_Array(&d->text); |
82 | } | 110 | } |
83 | 111 | ||
112 | static void pushUndo_InputWidget_(iInputWidget *d) { | ||
113 | iInputUndo undo; | ||
114 | init_InputUndo_(&undo, &d->text, d->cursor); | ||
115 | pushBack_Array(&d->undoStack, &undo); | ||
116 | if (size_Array(&d->undoStack) > maxUndo_InputWidget_) { | ||
117 | deinit_InputUndo_(front_Array(&d->undoStack)); | ||
118 | popFront_Array(&d->undoStack); | ||
119 | } | ||
120 | } | ||
121 | |||
122 | static iBool popUndo_InputWidget_(iInputWidget *d) { | ||
123 | if (!isEmpty_Array(&d->undoStack)) { | ||
124 | iInputUndo *undo = back_Array(&d->undoStack); | ||
125 | setCopy_Array(&d->text, &undo->text); | ||
126 | d->cursor = undo->cursor; | ||
127 | deinit_InputUndo_(undo); | ||
128 | popBack_Array(&d->undoStack); | ||
129 | iZap(d->mark); | ||
130 | return iTrue; | ||
131 | } | ||
132 | return iFalse; | ||
133 | } | ||
134 | |||
84 | void setMode_InputWidget(iInputWidget *d, enum iInputMode mode) { | 135 | void setMode_InputWidget(iInputWidget *d, enum iInputMode mode) { |
85 | d->mode = mode; | 136 | d->mode = mode; |
86 | } | 137 | } |
@@ -113,6 +164,7 @@ void setHint_InputWidget(iInputWidget *d, const char *hintText) { | |||
113 | } | 164 | } |
114 | 165 | ||
115 | void setText_InputWidget(iInputWidget *d, const iString *text) { | 166 | void setText_InputWidget(iInputWidget *d, const iString *text) { |
167 | clearUndo_InputWidget_(d); | ||
116 | clear_Array(&d->text); | 168 | clear_Array(&d->text); |
117 | iConstForEach(String, i, text) { | 169 | iConstForEach(String, i, text) { |
118 | pushBack_Array(&d->text, &i.value); | 170 | pushBack_Array(&d->text, &i.value); |
@@ -148,7 +200,7 @@ void begin_InputWidget(iInputWidget *d) { | |||
148 | SDL_StartTextInput(); | 200 | SDL_StartTextInput(); |
149 | setFlags_Widget(w, selected_WidgetFlag, iTrue); | 201 | setFlags_Widget(w, selected_WidgetFlag, iTrue); |
150 | refresh_Widget(w); | 202 | refresh_Widget(w); |
151 | d->timer = SDL_AddTimer(REFRESH_INTERVAL, refreshTimer_, d); | 203 | d->timer = SDL_AddTimer(refreshInterval_InputWidget_, refreshTimer_, d); |
152 | d->enterPressed = iFalse; | 204 | d->enterPressed = iFalse; |
153 | if (d->selectAllOnFocus) { | 205 | if (d->selectAllOnFocus) { |
154 | d->mark = (iRanges){ 0, size_Array(&d->text) }; | 206 | d->mark = (iRanges){ 0, size_Array(&d->text) }; |
@@ -233,11 +285,15 @@ static iRanges mark_InputWidget_(const iInputWidget *d) { | |||
233 | return (iRanges){ iMin(d->mark.start, d->mark.end), iMax(d->mark.start, d->mark.end) }; | 285 | return (iRanges){ iMin(d->mark.start, d->mark.end), iMax(d->mark.start, d->mark.end) }; |
234 | } | 286 | } |
235 | 287 | ||
236 | static void deleteMarked_InputWidget_(iInputWidget *d) { | 288 | static iBool deleteMarked_InputWidget_(iInputWidget *d) { |
237 | const iRanges m = mark_InputWidget_(d); | 289 | const iRanges m = mark_InputWidget_(d); |
238 | removeRange_Array(&d->text, m); | 290 | if (!isEmpty_Range(&m)) { |
239 | setCursor_InputWidget(d, m.start); | 291 | removeRange_Array(&d->text, m); |
240 | iZap(d->mark); | 292 | setCursor_InputWidget(d, m.start); |
293 | iZap(d->mark); | ||
294 | return iTrue; | ||
295 | } | ||
296 | return iFalse; | ||
241 | } | 297 | } |
242 | 298 | ||
243 | static iBool isWordChar_InputWidget_(const iInputWidget *d, size_t pos) { | 299 | static iBool isWordChar_InputWidget_(const iInputWidget *d, size_t pos) { |
@@ -325,8 +381,21 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
325 | const int mods = keyMods_Sym(ev->key.keysym.mod); | 381 | const int mods = keyMods_Sym(ev->key.keysym.mod); |
326 | if (mods == KMOD_PRIMARY) { | 382 | if (mods == KMOD_PRIMARY) { |
327 | switch (key) { | 383 | switch (key) { |
384 | case 'c': | ||
385 | case 'x': | ||
386 | if (!isEmpty_Range(&d->mark)) { | ||
387 | const iRanges m = mark_InputWidget_(d); | ||
388 | SDL_SetClipboardText(cstrCollect_String( | ||
389 | newUnicodeN_String(constAt_Array(&d->text, m.start), size_Range(&m)))); | ||
390 | if (key == 'x') { | ||
391 | pushUndo_InputWidget_(d); | ||
392 | deleteMarked_InputWidget_(d); | ||
393 | } | ||
394 | } | ||
395 | return iTrue; | ||
328 | case 'v': | 396 | case 'v': |
329 | if (SDL_HasClipboardText()) { | 397 | if (SDL_HasClipboardText()) { |
398 | pushUndo_InputWidget_(d); | ||
330 | char *text = SDL_GetClipboardText(); | 399 | char *text = SDL_GetClipboardText(); |
331 | iString *paste = collect_String(newCStr_String(text)); | 400 | iString *paste = collect_String(newCStr_String(text)); |
332 | SDL_free(text); | 401 | SDL_free(text); |
@@ -335,6 +404,11 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
335 | } | 404 | } |
336 | } | 405 | } |
337 | return iTrue; | 406 | return iTrue; |
407 | case 'z': | ||
408 | if (popUndo_InputWidget_(d)) { | ||
409 | refresh_Widget(w); | ||
410 | } | ||
411 | return iTrue; | ||
338 | } | 412 | } |
339 | } | 413 | } |
340 | d->lastCursor = d->cursor; | 414 | d->lastCursor = d->cursor; |
@@ -350,14 +424,17 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
350 | return iTrue; | 424 | return iTrue; |
351 | case SDLK_BACKSPACE: | 425 | case SDLK_BACKSPACE: |
352 | if (!isEmpty_Range(&d->mark)) { | 426 | if (!isEmpty_Range(&d->mark)) { |
427 | pushUndo_InputWidget_(d); | ||
353 | deleteMarked_InputWidget_(d); | 428 | deleteMarked_InputWidget_(d); |
354 | } | 429 | } |
355 | else if (mods & KMOD_ALT) { | 430 | else if (mods & KMOD_ALT) { |
431 | pushUndo_InputWidget_(d); | ||
356 | d->mark.start = d->cursor; | 432 | d->mark.start = d->cursor; |
357 | d->mark.end = skipWord_InputWidget_(d, d->cursor, -1); | 433 | d->mark.end = skipWord_InputWidget_(d, d->cursor, -1); |
358 | deleteMarked_InputWidget_(d); | 434 | deleteMarked_InputWidget_(d); |
359 | } | 435 | } |
360 | else if (d->cursor > 0) { | 436 | else if (d->cursor > 0) { |
437 | pushUndo_InputWidget_(d); | ||
361 | remove_Array(&d->text, --d->cursor); | 438 | remove_Array(&d->text, --d->cursor); |
362 | } | 439 | } |
363 | refresh_Widget(w); | 440 | refresh_Widget(w); |
@@ -366,27 +443,32 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
366 | if (mods != KMOD_CTRL) break; | 443 | if (mods != KMOD_CTRL) break; |
367 | case SDLK_DELETE: | 444 | case SDLK_DELETE: |
368 | if (!isEmpty_Range(&d->mark)) { | 445 | if (!isEmpty_Range(&d->mark)) { |
446 | pushUndo_InputWidget_(d); | ||
369 | deleteMarked_InputWidget_(d); | 447 | deleteMarked_InputWidget_(d); |
370 | } | 448 | } |
371 | else if (mods & KMOD_ALT) { | 449 | else if (mods & KMOD_ALT) { |
450 | pushUndo_InputWidget_(d); | ||
372 | d->mark.start = d->cursor; | 451 | d->mark.start = d->cursor; |
373 | d->mark.end = skipWord_InputWidget_(d, d->cursor, +1); | 452 | d->mark.end = skipWord_InputWidget_(d, d->cursor, +1); |
374 | deleteMarked_InputWidget_(d); | 453 | deleteMarked_InputWidget_(d); |
375 | } | 454 | } |
376 | else if (d->cursor < size_Array(&d->text)) { | 455 | else if (d->cursor < size_Array(&d->text)) { |
456 | pushUndo_InputWidget_(d); | ||
377 | remove_Array(&d->text, d->cursor); | 457 | remove_Array(&d->text, d->cursor); |
378 | refresh_Widget(w); | ||
379 | } | 458 | } |
459 | refresh_Widget(w); | ||
380 | return iTrue; | 460 | return iTrue; |
381 | case SDLK_k: | 461 | case SDLK_k: |
382 | if (mods == KMOD_CTRL) { | 462 | if (mods == KMOD_CTRL) { |
383 | if (!isEmpty_Range(&d->mark)) { | 463 | if (!isEmpty_Range(&d->mark)) { |
464 | pushUndo_InputWidget_(d); | ||
384 | deleteMarked_InputWidget_(d); | 465 | deleteMarked_InputWidget_(d); |
385 | } | 466 | } |
386 | else { | 467 | else { |
468 | pushUndo_InputWidget_(d); | ||
387 | removeN_Array(&d->text, d->cursor, size_Array(&d->text) - d->cursor); | 469 | removeN_Array(&d->text, d->cursor, size_Array(&d->text) - d->cursor); |
388 | refresh_Widget(w); | ||
389 | } | 470 | } |
471 | refresh_Widget(w); | ||
390 | return iTrue; | 472 | return iTrue; |
391 | } | 473 | } |
392 | break; | 474 | break; |
@@ -396,8 +478,17 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
396 | refresh_Widget(w); | 478 | refresh_Widget(w); |
397 | return iTrue; | 479 | return iTrue; |
398 | case SDLK_a: | 480 | case SDLK_a: |
481 | #if defined (iPlatformApple) | ||
482 | if (mods == KMOD_PRIMARY) { | ||
483 | d->mark.start = 0; | ||
484 | d->mark.end = curMax; | ||
485 | d->cursor = curMax; | ||
486 | return iTrue; | ||
487 | } | ||
488 | #endif | ||
489 | /* fall through for Emacs-style Home/End */ | ||
399 | case SDLK_e: | 490 | case SDLK_e: |
400 | if (!(mods & ~(KMOD_CTRL | KMOD_SHIFT))) { | 491 | if (mods == KMOD_CTRL || mods == (KMOD_CTRL | KMOD_SHIFT)) { |
401 | setCursor_InputWidget(d, key == 'a' ? 0 : curMax); | 492 | setCursor_InputWidget(d, key == 'a' ? 0 : curMax); |
402 | refresh_Widget(w); | 493 | refresh_Widget(w); |
403 | return iTrue; | 494 | return iTrue; |
@@ -433,6 +524,8 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
433 | return iTrue; | 524 | return iTrue; |
434 | } | 525 | } |
435 | else if (ev->type == SDL_TEXTINPUT && isFocused_Widget(w)) { | 526 | else if (ev->type == SDL_TEXTINPUT && isFocused_Widget(w)) { |
527 | pushUndo_InputWidget_(d); | ||
528 | deleteMarked_InputWidget_(d); | ||
436 | const iString *uni = collectNewCStr_String(ev->text.text); | 529 | const iString *uni = collectNewCStr_String(ev->text.text); |
437 | iConstForEach(String, i, uni) { | 530 | iConstForEach(String, i, uni) { |
438 | insertChar_InputWidget_(d, i.value); | 531 | insertChar_InputWidget_(d, i.value); |
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index 28f7c37d..011b235b 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c | |||
@@ -452,6 +452,7 @@ static void updateMouseHover_SidebarWidget_(iSidebarWidget *d) { | |||
452 | } | 452 | } |
453 | 453 | ||
454 | static void itemClicked_SidebarWidget_(iSidebarWidget *d, size_t index) { | 454 | static void itemClicked_SidebarWidget_(iSidebarWidget *d, size_t index) { |
455 | setFocus_Widget(NULL); | ||
455 | const iSidebarItem *item = constAt_Array(&d->items, index); | 456 | const iSidebarItem *item = constAt_Array(&d->items, index); |
456 | switch (d->mode) { | 457 | switch (d->mode) { |
457 | case documentOutline_SidebarMode: { | 458 | case documentOutline_SidebarMode: { |