summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-08-30 17:32:39 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-08-30 17:32:39 +0300
commite43891459ff4640cdc97ca7cb71891a54c0d1989 (patch)
treeed7eab9fb96bb40217351a0b402aaefe7d8fd95b /src
parent3eb85b3d0fdd007ad18c3b522795b0ba3a038d1f (diff)
InputWidget: Undo, copy/cut, select all
Diffstat (limited to 'src')
-rw-r--r--src/ui/documentwidget.c3
-rw-r--r--src/ui/inputwidget.c111
-rw-r--r--src/ui/sidebarwidget.c1
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
32static const int REFRESH_INTERVAL = 256; 32static const int refreshInterval_InputWidget_ = 256;
33static const size_t maxUndo_InputWidget_ = 64;
34
35iDeclareType(InputUndo)
36
37struct Impl_InputUndo {
38 iArray text;
39 size_t cursor;
40};
41
42static void init_InputUndo_(iInputUndo *d, const iArray *text, size_t cursor) {
43 initCopy_Array(&d->text, text);
44 d->cursor = cursor;
45}
46
47static void deinit_InputUndo_(iInputUndo *d) {
48 deinit_Array(&d->text);
49}
33 50
34struct Impl_InputWidget { 51struct 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
52iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) 70iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen)
53 71
72static void clearUndo_InputWidget_(iInputWidget *d) {
73 iForEach(Array, i, &d->undoStack) {
74 deinit_InputUndo_(i.value);
75 }
76 clear_Array(&d->undoStack);
77}
78
54void init_InputWidget(iInputWidget *d, size_t maxLen) { 79void 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
75void deinit_InputWidget(iInputWidget *d) { 101void 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
112static 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
122static 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
84void setMode_InputWidget(iInputWidget *d, enum iInputMode mode) { 135void 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
115void setText_InputWidget(iInputWidget *d, const iString *text) { 166void 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
236static void deleteMarked_InputWidget_(iInputWidget *d) { 288static 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
243static iBool isWordChar_InputWidget_(const iInputWidget *d, size_t pos) { 299static 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
454static void itemClicked_SidebarWidget_(iSidebarWidget *d, size_t index) { 454static 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: {