diff options
Diffstat (limited to 'src/ui/inputwidget.c')
-rw-r--r-- | src/ui/inputwidget.c | 123 |
1 files changed, 117 insertions, 6 deletions
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index c5a82680..cb6d35a4 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -30,6 +30,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
30 | #include "app.h" | 30 | #include "app.h" |
31 | 31 | ||
32 | #include <the_Foundation/array.h> | 32 | #include <the_Foundation/array.h> |
33 | #include <the_Foundation/file.h> | ||
34 | #include <the_Foundation/path.h> | ||
33 | #include <SDL_clipboard.h> | 35 | #include <SDL_clipboard.h> |
34 | #include <SDL_timer.h> | 36 | #include <SDL_timer.h> |
35 | 37 | ||
@@ -87,13 +89,21 @@ static void clearInputLines_(iArray *inputLines) { | |||
87 | static void splitToLines_(const iString *text, iArray *inputLines) { | 89 | static void splitToLines_(const iString *text, iArray *inputLines) { |
88 | clearInputLines_(inputLines); | 90 | clearInputLines_(inputLines); |
89 | if (isEmpty_String(text)) { | 91 | if (isEmpty_String(text)) { |
90 | iInputLine line; | 92 | iInputLine empty; |
91 | init_InputLine(&line); | 93 | init_InputLine(&empty); |
92 | pushBack_Array(inputLines, &line); | 94 | pushBack_Array(inputLines, &empty); |
93 | return; | 95 | return; |
94 | } | 96 | } |
95 | size_t index = 0; | 97 | size_t index = 0; |
96 | iRangecc seg = iNullRange; | 98 | iRangecc seg = iNullRange; |
99 | if (startsWith_String(text, "\n")) { /* empty segment ignored at the start */ | ||
100 | iInputLine empty; | ||
101 | init_InputLine(&empty); | ||
102 | setCStr_String(&empty.text, "\n"); | ||
103 | empty.range = (iRanges){ 0, 1 }; | ||
104 | index = 1; | ||
105 | pushBack_Array(inputLines, &empty); | ||
106 | } | ||
97 | while (nextSplit_Rangecc(range_String(text), "\n", &seg)) { | 107 | while (nextSplit_Rangecc(range_String(text), "\n", &seg)) { |
98 | iInputLine line; | 108 | iInputLine line; |
99 | init_InputLine(&line); | 109 | init_InputLine(&line); |
@@ -103,7 +113,14 @@ static void splitToLines_(const iString *text, iArray *inputLines) { | |||
103 | pushBack_Array(inputLines, &line); | 113 | pushBack_Array(inputLines, &line); |
104 | index = line.range.end; | 114 | index = line.range.end; |
105 | } | 115 | } |
106 | if (!endsWith_String(text, "\n")) { | 116 | if (endsWith_String(text, "\n")) { /* empty segment ignored at the end */ |
117 | iInputLine empty; | ||
118 | init_InputLine(&empty); | ||
119 | iInputLine *last = back_Array(inputLines); | ||
120 | empty.range.start = empty.range.end = last->range.end; | ||
121 | pushBack_Array(inputLines, &empty); | ||
122 | } | ||
123 | else { | ||
107 | iInputLine *last = back_Array(inputLines); | 124 | iInputLine *last = back_Array(inputLines); |
108 | removeEnd_String(&last->text, 1); | 125 | removeEnd_String(&last->text, 1); |
109 | last->range.end--; | 126 | last->range.end--; |
@@ -168,7 +185,9 @@ enum iInputWidgetFlag { | |||
168 | markWords_InputWidgetFlag = iBit(8), | 185 | markWords_InputWidgetFlag = iBit(8), |
169 | needUpdateBuffer_InputWidgetFlag = iBit(9), | 186 | needUpdateBuffer_InputWidgetFlag = iBit(9), |
170 | enterKeyEnabled_InputWidgetFlag = iBit(10), | 187 | enterKeyEnabled_InputWidgetFlag = iBit(10), |
171 | enterKeyInsertsLineFeed_InputWidgetFlag = iBit(11), | 188 | enterKeyInsertsLineFeed_InputWidgetFlag |
189 | = iBit(11), | ||
190 | needBackup_InputWidgetFlag = iBit(12), | ||
172 | }; | 191 | }; |
173 | 192 | ||
174 | /*----------------------------------------------------------------------------------------------*/ | 193 | /*----------------------------------------------------------------------------------------------*/ |
@@ -199,10 +218,84 @@ struct Impl_InputWidget { | |||
199 | iTextBuf * buffered; /* pre-rendered static text */ | 218 | iTextBuf * buffered; /* pre-rendered static text */ |
200 | iInputWidgetValidatorFunc validator; | 219 | iInputWidgetValidatorFunc validator; |
201 | void * validatorContext; | 220 | void * validatorContext; |
221 | iString * backupPath; | ||
222 | int backupTimer; | ||
202 | }; | 223 | }; |
203 | 224 | ||
204 | iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) | 225 | iDefineObjectConstructionArgs(InputWidget, (size_t maxLen), maxLen) |
205 | 226 | ||
227 | static void restoreBackup_InputWidget_(iInputWidget *d) { | ||
228 | if (!d->backupPath) return; | ||
229 | iFile *f = new_File(d->backupPath); | ||
230 | if (open_File(f, readOnly_FileMode | text_FileMode)) { | ||
231 | setText_InputWidget(d, collect_String(readString_File(f))); | ||
232 | } | ||
233 | iRelease(f); | ||
234 | } | ||
235 | |||
236 | static void saveBackup_InputWidget_(iInputWidget *d) { | ||
237 | if (!d->backupPath) return; | ||
238 | iFile *f = new_File(d->backupPath); | ||
239 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { | ||
240 | iConstForEach(Array, i, &d->lines) { | ||
241 | const iInputLine *line = i.value; | ||
242 | write_File(f, utf8_String(&line->text)); | ||
243 | } | ||
244 | d->inFlags &= ~needBackup_InputWidgetFlag; | ||
245 | #if !defined (NDEBUG) | ||
246 | iConstForEach(Array, j, &d->lines) { | ||
247 | iAssert(endsWith_String(&((const iInputLine *) j.value)->text, "\n") || | ||
248 | index_ArrayConstIterator(&j) == size_Array(&d->lines) - 1); | ||
249 | } | ||
250 | #endif | ||
251 | } | ||
252 | iRelease(f); | ||
253 | } | ||
254 | |||
255 | static void eraseBackup_InputWidget_(iInputWidget *d) { | ||
256 | if (d->backupPath) { | ||
257 | remove(cstr_String(d->backupPath)); | ||
258 | delete_String(d->backupPath); | ||
259 | d->backupPath = NULL; | ||
260 | } | ||
261 | } | ||
262 | |||
263 | static uint32_t backupTimeout_InputWidget_(uint32_t interval, void *context) { | ||
264 | iInputWidget *d = context; | ||
265 | postCommand_Widget(d, "input.backup"); | ||
266 | return 0; | ||
267 | } | ||
268 | |||
269 | static void restartBackupTimer_InputWidget_(iInputWidget *d) { | ||
270 | if (d->backupPath) { | ||
271 | d->inFlags |= needBackup_InputWidgetFlag; | ||
272 | if (d->backupTimer) { | ||
273 | SDL_RemoveTimer(d->backupTimer); | ||
274 | } | ||
275 | d->backupTimer = SDL_AddTimer(2500, backupTimeout_InputWidget_, d); | ||
276 | } | ||
277 | } | ||
278 | |||
279 | void setBackupFileName_InputWidget(iInputWidget *d, const char *fileName) { | ||
280 | if (fileName == NULL) { | ||
281 | if (d->backupTimer) { | ||
282 | SDL_RemoveTimer(d->backupTimer); | ||
283 | d->backupTimer = 0; | ||
284 | } | ||
285 | eraseBackup_InputWidget_(d); | ||
286 | if (d->backupPath) { | ||
287 | delete_String(d->backupPath); | ||
288 | d->backupPath = NULL; | ||
289 | } | ||
290 | return; | ||
291 | } | ||
292 | if (!d->backupPath) { | ||
293 | d->backupPath = copy_String(dataDir_App()); | ||
294 | } | ||
295 | append_Path(d->backupPath, collectNewCStr_String(fileName)); | ||
296 | restoreBackup_InputWidget_(d); | ||
297 | } | ||
298 | |||
206 | static void clearUndo_InputWidget_(iInputWidget *d) { | 299 | static void clearUndo_InputWidget_(iInputWidget *d) { |
207 | iForEach(Array, i, &d->undoStack) { | 300 | iForEach(Array, i, &d->undoStack) { |
208 | deinit_InputUndo_(i.value); | 301 | deinit_InputUndo_(i.value); |
@@ -635,11 +728,21 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
635 | d->timer = 0; | 728 | d->timer = 0; |
636 | d->cursorVis = 0; | 729 | d->cursorVis = 0; |
637 | d->buffered = NULL; | 730 | d->buffered = NULL; |
731 | d->backupPath = NULL; | ||
732 | d->backupTimer = 0; | ||
638 | //updateLines_InputWidget_(d); | 733 | //updateLines_InputWidget_(d); |
639 | updateMetrics_InputWidget_(d); | 734 | updateMetrics_InputWidget_(d); |
640 | } | 735 | } |
641 | 736 | ||
642 | void deinit_InputWidget(iInputWidget *d) { | 737 | void deinit_InputWidget(iInputWidget *d) { |
738 | if (d->backupTimer) { | ||
739 | SDL_RemoveTimer(d->backupTimer); | ||
740 | } | ||
741 | if (d->inFlags & needBackup_InputWidgetFlag) { | ||
742 | saveBackup_InputWidget_(d); | ||
743 | } | ||
744 | delete_String(d->backupPath); | ||
745 | d->backupPath = NULL; | ||
643 | clearInputLines_(&d->lines); | 746 | clearInputLines_(&d->lines); |
644 | if (isSelected_Widget(d)) { | 747 | if (isSelected_Widget(d)) { |
645 | SDL_StopTextInput(); | 748 | SDL_StopTextInput(); |
@@ -1016,6 +1119,7 @@ static void textOfLinesWasChanged_InputWidget_(iInputWidget *d, iRangei lineRang | |||
1016 | updateLineRangesStartingFrom_InputWidget_(d, lineRange.start); | 1119 | updateLineRangesStartingFrom_InputWidget_(d, lineRange.start); |
1017 | updateVisible_InputWidget_(d); | 1120 | updateVisible_InputWidget_(d); |
1018 | updateMetrics_InputWidget_(d); | 1121 | updateMetrics_InputWidget_(d); |
1122 | restartBackupTimer_InputWidget_(d); | ||
1019 | } | 1123 | } |
1020 | 1124 | ||
1021 | static void insertRange_InputWidget_(iInputWidget *d, iRangecc range) { | 1125 | static void insertRange_InputWidget_(iInputWidget *d, iRangecc range) { |
@@ -1239,6 +1343,7 @@ static void contentsWereChanged_InputWidget_(iInputWidget *d) { | |||
1239 | 1343 | ||
1240 | static void deleteIndexRange_InputWidget_(iInputWidget *d, iRanges deleted) { | 1344 | static void deleteIndexRange_InputWidget_(iInputWidget *d, iRanges deleted) { |
1241 | size_t firstModified = iInvalidPos; | 1345 | size_t firstModified = iInvalidPos; |
1346 | restartBackupTimer_InputWidget_(d); | ||
1242 | for (int i = size_Array(&d->lines) - 1; i >= 0; i--) { | 1347 | for (int i = size_Array(&d->lines) - 1; i >= 0; i--) { |
1243 | iInputLine *line = at_Array(&d->lines, i); | 1348 | iInputLine *line = at_Array(&d->lines, i); |
1244 | if (line->range.end <= deleted.start) { | 1349 | if (line->range.end <= deleted.start) { |
@@ -1545,6 +1650,12 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1545 | contentsWereChanged_InputWidget_(d); | 1650 | contentsWereChanged_InputWidget_(d); |
1546 | return iTrue; | 1651 | return iTrue; |
1547 | } | 1652 | } |
1653 | else if (isCommand_Widget(w, ev, "input.backup")) { | ||
1654 | if (d->inFlags & needBackup_InputWidgetFlag) { | ||
1655 | saveBackup_InputWidget_(d); | ||
1656 | } | ||
1657 | return iTrue; | ||
1658 | } | ||
1548 | else if (isMetricsChange_UserEvent(ev)) { | 1659 | else if (isMetricsChange_UserEvent(ev)) { |
1549 | updateMetrics_InputWidget_(d); | 1660 | updateMetrics_InputWidget_(d); |
1550 | // updateLinesAndResize_InputWidget_(d); | 1661 | // updateLinesAndResize_InputWidget_(d); |
@@ -1708,7 +1819,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1708 | } | 1819 | } |
1709 | return iFalse; | 1820 | return iFalse; |
1710 | case SDLK_ESCAPE: | 1821 | case SDLK_ESCAPE: |
1711 | end_InputWidget(d, iFalse); | 1822 | end_InputWidget(d, iTrue); |
1712 | setFocus_Widget(NULL); | 1823 | setFocus_Widget(NULL); |
1713 | return (d->inFlags & eatEscape_InputWidgetFlag) != 0; | 1824 | return (d->inFlags & eatEscape_InputWidgetFlag) != 0; |
1714 | case SDLK_BACKSPACE: | 1825 | case SDLK_BACKSPACE: |