summaryrefslogtreecommitdiff
path: root/src/contrib/SDL-3.2.20/test/testime.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/contrib/SDL-3.2.20/test/testime.c')
-rw-r--r--src/contrib/SDL-3.2.20/test/testime.c1155
1 files changed, 1155 insertions, 0 deletions
diff --git a/src/contrib/SDL-3.2.20/test/testime.c b/src/contrib/SDL-3.2.20/test/testime.c
new file mode 100644
index 0000000..ea56d3c
--- /dev/null
+++ b/src/contrib/SDL-3.2.20/test/testime.c
@@ -0,0 +1,1155 @@
1/*
2 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
3
4 This software is provided 'as-is', without any express or implied
5 warranty. In no event will the authors be held liable for any damages
6 arising from the use of this software.
7
8 Permission is granted to anyone to use this software for any purpose,
9 including commercial applications, and to alter it and redistribute it
10 freely.
11*/
12
13/* A simple program to test the Input Method support in SDL.
14 *
15 * This uses the GNU Unifont to display non-ASCII characters, available at:
16 * http://unifoundry.com/unifont.html
17 *
18 * An example of IME support with TrueType fonts is available in the SDL_ttf example code:
19 * https://github.com/libsdl-org/SDL_ttf/blob/main/examples/editbox.h
20 */
21#include <SDL3/SDL.h>
22#include <SDL3/SDL_main.h>
23#include <SDL3/SDL_test_font.h>
24
25#include <SDL3/SDL_test_common.h>
26#include "testutils.h"
27
28#define DEFAULT_FONT "unifont-15.1.05.hex"
29#define MAX_TEXT_LENGTH 256
30
31#define WINDOW_WIDTH 640
32#define WINDOW_HEIGHT 480
33
34#define MARGIN 32.0f
35#define LINE_HEIGHT (FONT_CHARACTER_SIZE + 4.0f)
36#define CURSOR_BLINK_INTERVAL_MS 500
37
38typedef struct
39{
40 SDL_Window *window;
41 SDL_Renderer *renderer;
42 int rendererID;
43 bool settings_visible;
44 SDL_Texture *settings_icon;
45 SDL_FRect settings_rect;
46 SDL_PropertiesID text_settings;
47 SDL_FRect textRect;
48 SDL_FRect markedRect;
49 char text[MAX_TEXT_LENGTH];
50 char markedText[MAX_TEXT_LENGTH];
51 int cursor;
52 int cursor_length;
53 bool cursor_visible;
54 Uint64 last_cursor_change;
55 char **candidates;
56 int num_candidates;
57 int selected_candidate;
58 bool horizontal_candidates;
59} WindowState;
60
61static SDLTest_CommonState *state;
62static WindowState *windowstate;
63static const SDL_Color lineColor = { 0, 0, 0, 255 };
64static const SDL_Color backColor = { 255, 255, 255, 255 };
65static const SDL_Color textColor = { 0, 0, 0, 255 };
66static SDL_BlendMode highlight_mode;
67
68static const struct
69{
70 const char *label;
71 const char *setting;
72 int value;
73} settings[] = {
74 { "Text", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT },
75 { "Name", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT_NAME },
76 { "E-mail", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT_EMAIL },
77 { "Username", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT_USERNAME },
78 { "Password (hidden)", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN },
79 { "Password (visible)", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE },
80 { "Number", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_NUMBER },
81 { "Numeric PIN (hidden)", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN },
82 { "Numeric PIN (visible)", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE },
83 { "", NULL },
84 { "No capitalization", SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_NONE },
85 { "Capitalize sentences", SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_SENTENCES },
86 { "Capitalize words", SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_WORDS },
87 { "All caps", SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_LETTERS },
88 { "", NULL },
89 { "Auto-correct OFF", SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN, false },
90 { "Auto-correct ON", SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN, true },
91 { "Multiline OFF", SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN, false },
92 { "Multiline ON", SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN, true }
93};
94
95#define UNIFONT_MAX_CODEPOINT 0x1ffff
96#define UNIFONT_NUM_GLYPHS 0x20000
97#define UNIFONT_REPLACEMENT 0xFFFD
98/* Using 512x512 textures that are supported everywhere. */
99#define UNIFONT_TEXTURE_WIDTH 512
100#define UNIFONT_GLYPH_SIZE 16
101#define UNIFONT_GLYPH_BORDER 1
102#define UNIFONT_GLYPH_AREA (UNIFONT_GLYPH_BORDER + UNIFONT_GLYPH_SIZE + UNIFONT_GLYPH_BORDER)
103#define UNIFONT_GLYPHS_IN_ROW (UNIFONT_TEXTURE_WIDTH / UNIFONT_GLYPH_AREA)
104#define UNIFONT_GLYPHS_IN_TEXTURE (UNIFONT_GLYPHS_IN_ROW * UNIFONT_GLYPHS_IN_ROW)
105#define UNIFONT_NUM_TEXTURES ((UNIFONT_NUM_GLYPHS + UNIFONT_GLYPHS_IN_TEXTURE - 1) / UNIFONT_GLYPHS_IN_TEXTURE)
106#define UNIFONT_TEXTURE_SIZE (UNIFONT_TEXTURE_WIDTH * UNIFONT_TEXTURE_WIDTH * 4)
107#define UNIFONT_TEXTURE_PITCH (UNIFONT_TEXTURE_WIDTH * 4)
108#define UNIFONT_DRAW_SCALE 2.0f
109static struct UnifontGlyph
110{
111 Uint8 width;
112 Uint8 data[32];
113} * unifontGlyph;
114static SDL_Texture **unifontTexture;
115static Uint8 unifontTextureLoaded[UNIFONT_NUM_TEXTURES] = { 0 };
116
117/* Unifont loading code start */
118
119static Uint8 dehex(char c)
120{
121 if (c >= '0' && c <= '9') {
122 return c - '0';
123 } else if (c >= 'a' && c <= 'f') {
124 return c - 'a' + 10;
125 } else if (c >= 'A' && c <= 'F') {
126 return c - 'A' + 10;
127 }
128 return 255;
129}
130
131static Uint8 dehex2(char c1, char c2)
132{
133 return (dehex(c1) << 4) | dehex(c2);
134}
135
136static Uint8 validate_hex(const char *cp, size_t len, Uint32 *np)
137{
138 Uint32 n = 0;
139 for (; len > 0; cp++, len--) {
140 Uint8 c = dehex(*cp);
141 if (c == 255) {
142 return 0;
143 }
144 n = (n << 4) | c;
145 }
146 if (np) {
147 *np = n;
148 }
149 return 1;
150}
151
152static int unifont_init(const char *fontname)
153{
154 Uint8 hexBuffer[65];
155 Uint32 numGlyphs = 0;
156 int lineNumber = 1;
157 size_t bytesRead;
158 SDL_IOStream *hexFile;
159 const size_t unifontGlyphSize = UNIFONT_NUM_GLYPHS * sizeof(struct UnifontGlyph);
160 const size_t unifontTextureSize = UNIFONT_NUM_TEXTURES * state->num_windows * sizeof(void *);
161 char *filename;
162
163 /* Allocate memory for the glyph data so the file can be closed after initialization. */
164 unifontGlyph = (struct UnifontGlyph *)SDL_malloc(unifontGlyphSize);
165 if (!unifontGlyph) {
166 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Failed to allocate %d KiB for glyph data.", (int)(unifontGlyphSize + 1023) / 1024);
167 return -1;
168 }
169 SDL_memset(unifontGlyph, 0, unifontGlyphSize);
170
171 /* Allocate memory for texture pointers for all renderers. */
172 unifontTexture = (SDL_Texture **)SDL_malloc(unifontTextureSize);
173 if (!unifontTexture) {
174 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Failed to allocate %d KiB for texture pointer data.", (int)(unifontTextureSize + 1023) / 1024);
175 return -1;
176 }
177 SDL_memset(unifontTexture, 0, unifontTextureSize);
178
179 filename = GetResourceFilename(NULL, fontname);
180 if (!filename) {
181 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory");
182 return -1;
183 }
184 hexFile = SDL_IOFromFile(filename, "rb");
185 SDL_free(filename);
186 if (!hexFile) {
187 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Failed to open font file: %s", fontname);
188 return -1;
189 }
190
191 /* Read all the glyph data into memory to make it accessible later when textures are created. */
192 do {
193 int i, codepointHexSize;
194 size_t bytesOverread;
195 Uint8 glyphWidth;
196 Uint32 codepoint;
197
198 bytesRead = SDL_ReadIO(hexFile, hexBuffer, 9);
199 if (numGlyphs > 0 && bytesRead == 0) {
200 break; /* EOF */
201 }
202 if ((numGlyphs == 0 && bytesRead == 0) || (numGlyphs > 0 && bytesRead < 9)) {
203 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Unexpected end of hex file.");
204 return -1;
205 }
206
207 /* Looking for the colon that separates the codepoint and glyph data at position 2, 4, 6 and 8. */
208 if (hexBuffer[2] == ':') {
209 codepointHexSize = 2;
210 } else if (hexBuffer[4] == ':') {
211 codepointHexSize = 4;
212 } else if (hexBuffer[6] == ':') {
213 codepointHexSize = 6;
214 } else if (hexBuffer[8] == ':') {
215 codepointHexSize = 8;
216 } else {
217 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Could not find codepoint and glyph data separator symbol in hex file on line %d.", lineNumber);
218 return -1;
219 }
220
221 if (!validate_hex((const char *)hexBuffer, codepointHexSize, &codepoint)) {
222 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Malformed hexadecimal number in hex file on line %d.", lineNumber);
223 return -1;
224 }
225 if (codepoint > UNIFONT_MAX_CODEPOINT) {
226 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "unifont: Codepoint on line %d exceeded limit of 0x%x.", lineNumber, UNIFONT_MAX_CODEPOINT);
227 }
228
229 /* If there was glyph data read in the last file read, move it to the front of the buffer. */
230 bytesOverread = 8 - codepointHexSize;
231 if (codepointHexSize < 8) {
232 SDL_memmove(hexBuffer, hexBuffer + codepointHexSize + 1, bytesOverread);
233 }
234 bytesRead = SDL_ReadIO(hexFile, hexBuffer + bytesOverread, 33 - bytesOverread);
235
236 if (bytesRead < (33 - bytesOverread)) {
237 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Unexpected end of hex file.");
238 return -1;
239 }
240 if (hexBuffer[32] == '\n') {
241 glyphWidth = 8;
242 } else {
243 glyphWidth = 16;
244 bytesRead = SDL_ReadIO(hexFile, hexBuffer + 33, 32);
245 if (bytesRead < 32) {
246 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Unexpected end of hex file.");
247 return -1;
248 }
249 }
250
251 if (!validate_hex((const char *)hexBuffer, glyphWidth * 4, NULL)) {
252 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Malformed hexadecimal glyph data in hex file on line %d.", lineNumber);
253 return -1;
254 }
255
256 if (codepoint <= UNIFONT_MAX_CODEPOINT) {
257 if (unifontGlyph[codepoint].width > 0) {
258 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "unifont: Ignoring duplicate codepoint 0x%08" SDL_PRIx32 " in hex file on line %d.", codepoint, lineNumber);
259 } else {
260 unifontGlyph[codepoint].width = glyphWidth;
261 /* Pack the hex data into a more compact form. */
262 for (i = 0; i < glyphWidth * 2; i++) {
263 unifontGlyph[codepoint].data[i] = dehex2(hexBuffer[i * 2], hexBuffer[i * 2 + 1]);
264 }
265 numGlyphs++;
266 }
267 }
268
269 lineNumber++;
270 } while (bytesRead > 0);
271
272 SDL_CloseIO(hexFile);
273 SDL_Log("unifont: Loaded %" SDL_PRIu32 " glyphs.", numGlyphs);
274 return 0;
275}
276
277static void unifont_make_rgba(const Uint8 *src, Uint8 *dst, Uint8 width)
278{
279 int i, j;
280 Uint8 *row = dst;
281
282 for (i = 0; i < width * 2; i++) {
283 Uint8 data = src[i];
284 for (j = 0; j < 8; j++) {
285 if (data & 0x80) {
286 row[0] = textColor.r;
287 row[1] = textColor.g;
288 row[2] = textColor.b;
289 row[3] = textColor.a;
290 } else {
291 row[0] = 0;
292 row[1] = 0;
293 row[2] = 0;
294 row[3] = 0;
295 }
296 data <<= 1;
297 row += 4;
298 }
299
300 if (width == 8 || (width == 16 && i % 2 == 1)) {
301 dst += UNIFONT_TEXTURE_PITCH;
302 row = dst;
303 }
304 }
305}
306
307static int unifont_load_texture(Uint32 textureID)
308{
309 int i;
310 Uint8 *textureRGBA;
311
312 if (textureID >= UNIFONT_NUM_TEXTURES) {
313 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Tried to load out of range texture %" SDL_PRIu32, textureID);
314 return -1;
315 }
316
317 textureRGBA = (Uint8 *)SDL_malloc(UNIFONT_TEXTURE_SIZE);
318 if (!textureRGBA) {
319 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Failed to allocate %d MiB for a texture.", UNIFONT_TEXTURE_SIZE / 1024 / 1024);
320 return -1;
321 }
322 SDL_memset(textureRGBA, 0, UNIFONT_TEXTURE_SIZE);
323
324 /* Copy the glyphs into memory in RGBA format. */
325 for (i = 0; i < UNIFONT_GLYPHS_IN_TEXTURE; i++) {
326 Uint32 codepoint = UNIFONT_GLYPHS_IN_TEXTURE * textureID + i;
327 if (unifontGlyph[codepoint].width > 0) {
328 const Uint32 cInTex = codepoint % UNIFONT_GLYPHS_IN_TEXTURE;
329 const size_t offset = ((size_t)cInTex / UNIFONT_GLYPHS_IN_ROW) * UNIFONT_TEXTURE_PITCH * UNIFONT_GLYPH_AREA + (cInTex % UNIFONT_GLYPHS_IN_ROW) * UNIFONT_GLYPH_AREA * 4;
330 unifont_make_rgba(unifontGlyph[codepoint].data, textureRGBA + offset, unifontGlyph[codepoint].width);
331 }
332 }
333
334 /* Create textures and upload the RGBA data from above. */
335 for (i = 0; i < state->num_windows; ++i) {
336 SDL_Renderer *renderer = state->renderers[i];
337 SDL_Texture *tex = unifontTexture[UNIFONT_NUM_TEXTURES * i + textureID];
338 if (state->windows[i] == NULL || renderer == NULL || tex != NULL) {
339 continue;
340 }
341 tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, UNIFONT_TEXTURE_WIDTH, UNIFONT_TEXTURE_WIDTH);
342 if (tex == NULL) {
343 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Failed to create texture %" SDL_PRIu32 " for renderer %d.", textureID, i);
344 return -1;
345 }
346 unifontTexture[UNIFONT_NUM_TEXTURES * i + textureID] = tex;
347 SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
348 if (!SDL_UpdateTexture(tex, NULL, textureRGBA, UNIFONT_TEXTURE_PITCH)) {
349 SDL_Log("unifont error: Failed to update texture %" SDL_PRIu32 " data for renderer %d.", textureID, i);
350 }
351 }
352
353 SDL_free(textureRGBA);
354 unifontTextureLoaded[textureID] = 1;
355 return -1;
356}
357
358static int unifont_glyph_width(Uint32 codepoint)
359{
360 if (codepoint > UNIFONT_MAX_CODEPOINT ||
361 unifontGlyph[codepoint].width == 0) {
362 codepoint = UNIFONT_REPLACEMENT;
363 }
364 return unifontGlyph[codepoint].width;
365}
366
367static int unifont_draw_glyph(Uint32 codepoint, int rendererID, SDL_FRect *dst)
368{
369 SDL_Texture *texture;
370 Uint32 textureID;
371 SDL_FRect srcrect;
372 srcrect.w = srcrect.h = (float)UNIFONT_GLYPH_SIZE;
373
374 if (codepoint > UNIFONT_MAX_CODEPOINT ||
375 unifontGlyph[codepoint].width == 0) {
376 codepoint = UNIFONT_REPLACEMENT;
377 }
378
379 textureID = codepoint / UNIFONT_GLYPHS_IN_TEXTURE;
380 if (!unifontTextureLoaded[textureID]) {
381 if (unifont_load_texture(textureID) < 0) {
382 return 0;
383 }
384 }
385 texture = unifontTexture[UNIFONT_NUM_TEXTURES * rendererID + textureID];
386 if (texture) {
387 const Uint32 cInTex = codepoint % UNIFONT_GLYPHS_IN_TEXTURE;
388 srcrect.x = (float)(cInTex % UNIFONT_GLYPHS_IN_ROW * UNIFONT_GLYPH_AREA);
389 srcrect.y = (float)(cInTex / UNIFONT_GLYPHS_IN_ROW * UNIFONT_GLYPH_AREA);
390 SDL_RenderTexture(state->renderers[rendererID], texture, &srcrect, dst);
391 }
392 return unifontGlyph[codepoint].width;
393}
394
395static void unifont_cleanup(void)
396{
397 int i, j;
398 for (i = 0; i < state->num_windows; ++i) {
399 SDL_Renderer *renderer = state->renderers[i];
400 if (state->windows[i] == NULL || !renderer) {
401 continue;
402 }
403 for (j = 0; j < UNIFONT_NUM_TEXTURES; j++) {
404 SDL_Texture *tex = unifontTexture[UNIFONT_NUM_TEXTURES * i + j];
405 if (tex) {
406 SDL_DestroyTexture(tex);
407 }
408 }
409 }
410
411 for (j = 0; j < UNIFONT_NUM_TEXTURES; j++) {
412 unifontTextureLoaded[j] = 0;
413 }
414
415 SDL_free(unifontTexture);
416 SDL_free(unifontGlyph);
417}
418
419/* Unifont code end */
420
421static size_t utf8_length(unsigned char c)
422{
423 c = (unsigned char)(0xff & c);
424 if (c < 0x80) {
425 return 1;
426 } else if ((c >> 5) == 0x6) {
427 return 2;
428 } else if ((c >> 4) == 0xe) {
429 return 3;
430 } else if ((c >> 3) == 0x1e) {
431 return 4;
432 }
433 return 0;
434}
435
436static Uint32 utf8_decode(const char *p, size_t len)
437{
438 Uint32 codepoint = 0;
439 size_t i = 0;
440 if (!len) {
441 return 0;
442 }
443
444 for (; i < len; ++i) {
445 if (i == 0) {
446 codepoint = (0xff >> len) & *p;
447 } else {
448 codepoint <<= 6;
449 codepoint |= 0x3f & *p;
450 }
451 if (!*p) {
452 return 0;
453 }
454 p++;
455 }
456
457 return codepoint;
458}
459
460static WindowState *GetWindowStateForWindowID(SDL_WindowID windowID)
461{
462 int i;
463 SDL_Window *window = SDL_GetWindowFromID(windowID);
464
465 for (i = 0; i < state->num_windows; ++i) {
466 if (windowstate[i].window == window) {
467 return &windowstate[i];
468 }
469 }
470 return NULL;
471}
472
473static void InitInput(WindowState *ctx)
474{
475 /* Prepare a rect for text input */
476 ctx->textRect.x = ctx->textRect.y = 100.0f;
477 ctx->textRect.w = DEFAULT_WINDOW_WIDTH - 2 * ctx->textRect.x;
478 ctx->textRect.h = 50.0f;
479 ctx->markedRect = ctx->textRect;
480
481 ctx->text_settings = SDL_CreateProperties();
482
483 SDL_StartTextInputWithProperties(ctx->window, ctx->text_settings);
484}
485
486
487static void ClearCandidates(WindowState *ctx)
488{
489 int i;
490
491 for (i = 0; i < ctx->num_candidates; ++i) {
492 SDL_free(ctx->candidates[i]);
493 }
494 SDL_free(ctx->candidates);
495 ctx->candidates = NULL;
496 ctx->num_candidates = 0;
497}
498
499static void SaveCandidates(WindowState *ctx, SDL_Event *event)
500{
501 int i;
502
503 ClearCandidates(ctx);
504
505 ctx->num_candidates = event->edit_candidates.num_candidates;
506 if (ctx->num_candidates > 0) {
507 ctx->candidates = (char **)SDL_malloc(ctx->num_candidates * sizeof(*ctx->candidates));
508 if (!ctx->candidates) {
509 ctx->num_candidates = 0;
510 return;
511 }
512 for (i = 0; i < ctx->num_candidates; ++i) {
513 ctx->candidates[i] = SDL_strdup(event->edit_candidates.candidates[i]);
514 }
515 ctx->selected_candidate = event->edit_candidates.selected_candidate;
516 ctx->horizontal_candidates = event->edit_candidates.horizontal;
517 }
518}
519
520static void DrawCandidates(WindowState *ctx, SDL_FRect *cursorRect)
521{
522 SDL_Renderer *renderer = ctx->renderer;
523 int rendererID = ctx->rendererID;
524 int i;
525 int output_w = 0, output_h = 0;
526 float w = 0.0f, h = 0.0f;
527 SDL_FRect candidatesRect, dstRect, underlineRect;
528
529 if (ctx->num_candidates == 0) {
530 return;
531 }
532
533 /* Calculate the size of the candidate list */
534 for (i = 0; i < ctx->num_candidates; ++i) {
535 if (!ctx->candidates[i]) {
536 continue;
537 }
538
539 if (ctx->horizontal_candidates) {
540 const char *utext = ctx->candidates[i];
541 Uint32 codepoint;
542 size_t len;
543 float advance = 0.0f;
544
545 if (i > 0) {
546 advance += unifont_glyph_width(' ') * UNIFONT_DRAW_SCALE;
547 }
548 while ((codepoint = utf8_decode(utext, len = utf8_length(*utext))) != 0) {
549 advance += unifont_glyph_width(codepoint) * UNIFONT_DRAW_SCALE;
550 utext += len;
551 }
552 w += advance;
553 h = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE;
554 } else {
555 const char *utext = ctx->candidates[i];
556 Uint32 codepoint;
557 size_t len;
558 float advance = 0.0f;
559
560 while ((codepoint = utf8_decode(utext, len = utf8_length(*utext))) != 0) {
561 advance += unifont_glyph_width(codepoint) * UNIFONT_DRAW_SCALE;
562 utext += len;
563 }
564 w = SDL_max(w, advance);
565 if (i > 0) {
566 h += 2.0f;
567 }
568 h += UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE;
569 }
570 }
571
572 /* Position the candidate window */
573 SDL_GetCurrentRenderOutputSize(renderer, &output_w, &output_h);
574 candidatesRect.x = cursorRect->x;
575 candidatesRect.y = cursorRect->y + cursorRect->h + 2.0f;
576 candidatesRect.w = 1.0f + 2.0f + w + 2.0f + 1.0f;
577 candidatesRect.h = 1.0f + 2.0f + h + 2.0f + 1.0f;
578 if ((candidatesRect.x + candidatesRect.w) > output_w) {
579 candidatesRect.x = (output_w - candidatesRect.w);
580 if (candidatesRect.x < 0.0f) {
581 candidatesRect.x = 0.0f;
582 }
583 }
584
585 /* Draw the candidate background */
586 SDL_SetRenderDrawColor(renderer, 0xAA, 0xAA, 0xAA, 0xFF);
587 SDL_RenderFillRect(renderer, &candidatesRect);
588 SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
589 SDL_RenderRect(renderer, &candidatesRect);
590
591 /* Draw the candidates */
592 dstRect.x = candidatesRect.x + 3.0f;
593 dstRect.y = candidatesRect.y + 3.0f;
594 for (i = 0; i < ctx->num_candidates; ++i) {
595 if (!ctx->candidates[i]) {
596 continue;
597 }
598
599 dstRect.w = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE;
600 dstRect.h = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE;
601
602 if (ctx->horizontal_candidates) {
603 const char *utext = ctx->candidates[i];
604 Uint32 codepoint;
605 size_t len;
606 float start;
607
608 if (i > 0) {
609 dstRect.x += unifont_draw_glyph(' ', rendererID, &dstRect) * UNIFONT_DRAW_SCALE;
610 }
611
612 start = dstRect.x + 2 * unifont_glyph_width(' ') * UNIFONT_DRAW_SCALE;
613 while ((codepoint = utf8_decode(utext, len = utf8_length(*utext))) != 0) {
614 dstRect.x += unifont_draw_glyph(codepoint, rendererID, &dstRect) * UNIFONT_DRAW_SCALE;
615 utext += len;
616 }
617
618 if (i == ctx->selected_candidate) {
619 underlineRect.x = start;
620 underlineRect.y = dstRect.y + dstRect.h - 2;
621 underlineRect.h = 2;
622 underlineRect.w = dstRect.x - start;
623
624 SDL_SetRenderDrawColor(renderer, lineColor.r, lineColor.g, lineColor.b, lineColor.a);
625 SDL_RenderFillRect(renderer, &underlineRect);
626 }
627 } else {
628 const char *utext = ctx->candidates[i];
629 Uint32 codepoint;
630 size_t len;
631 float start;
632
633 dstRect.x = candidatesRect.x + 3.0f;
634
635 start = dstRect.x + 2 * unifont_glyph_width(' ') * UNIFONT_DRAW_SCALE;
636 while ((codepoint = utf8_decode(utext, len = utf8_length(*utext))) != 0) {
637 dstRect.x += unifont_draw_glyph(codepoint, rendererID, &dstRect) * UNIFONT_DRAW_SCALE;
638 utext += len;
639 }
640
641 if (i == ctx->selected_candidate) {
642 underlineRect.x = start;
643 underlineRect.y = dstRect.y + dstRect.h - 2;
644 underlineRect.h = 2;
645 underlineRect.w = dstRect.x - start;
646
647 SDL_SetRenderDrawColor(renderer, lineColor.r, lineColor.g, lineColor.b, lineColor.a);
648 SDL_RenderFillRect(renderer, &underlineRect);
649 }
650
651 if (i > 0) {
652 dstRect.y += 2.0f;
653 }
654 dstRect.y += dstRect.h;
655 }
656 }
657}
658
659static void UpdateTextInputArea(WindowState *ctx, const SDL_FRect *cursorRect)
660{
661 SDL_Rect rect;
662 int cursor_offset = (int)(cursorRect->x - ctx->textRect.x);
663
664 rect.x = (int)ctx->textRect.x;
665 rect.y = (int)ctx->textRect.y;
666 rect.w = (int)ctx->textRect.w;
667 rect.h = (int)ctx->textRect.h;
668 SDL_SetTextInputArea(ctx->window, &rect, cursor_offset);
669}
670
671static void CleanupVideo(void)
672{
673 int i;
674
675 for (i = 0; i < state->num_windows; ++i) {
676 WindowState *ctx = &windowstate[i];
677
678 SDL_StopTextInput(ctx->window);
679 ClearCandidates(ctx);
680 SDL_DestroyProperties(ctx->text_settings);
681 }
682 unifont_cleanup();
683}
684
685static void DrawSettingsButton(WindowState *ctx)
686{
687 SDL_Renderer *renderer = ctx->renderer;
688
689 SDL_RenderTexture(renderer, ctx->settings_icon, NULL, &ctx->settings_rect);
690}
691
692static void ToggleSettings(WindowState *ctx)
693{
694 if (ctx->settings_visible) {
695 ctx->settings_visible = false;
696 SDL_StartTextInputWithProperties(ctx->window, ctx->text_settings);
697 } else {
698 SDL_StopTextInput(ctx->window);
699 ctx->settings_visible = true;
700 }
701}
702
703static int GetDefaultSetting(SDL_PropertiesID props, const char *setting)
704{
705 if (SDL_strcmp(setting, SDL_PROP_TEXTINPUT_TYPE_NUMBER) == 0) {
706 return SDL_TEXTINPUT_TYPE_TEXT;
707 }
708
709 if (SDL_strcmp(setting, SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER) == 0) {
710 switch (SDL_GetNumberProperty(props, SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT)) {
711 case SDL_TEXTINPUT_TYPE_TEXT:
712 return SDL_CAPITALIZE_SENTENCES;
713 case SDL_TEXTINPUT_TYPE_TEXT_NAME:
714 return SDL_CAPITALIZE_WORDS;
715 default:
716 return SDL_CAPITALIZE_NONE;
717 }
718 }
719
720 if (SDL_strcmp(setting, SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN) == 0) {
721 return true;
722 }
723
724 if (SDL_strcmp(setting, SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN) == 0) {
725 return true;
726 }
727
728 SDL_assert(!"Unknown setting");
729 return 0;
730}
731
732static void DrawSettings(WindowState *ctx)
733{
734 SDL_Renderer *renderer = ctx->renderer;
735 SDL_FRect checkbox;
736 int i;
737
738 checkbox.x = MARGIN;
739 checkbox.y = MARGIN;
740 checkbox.w = (float)FONT_CHARACTER_SIZE;
741 checkbox.h = (float)FONT_CHARACTER_SIZE;
742
743 for (i = 0; i < SDL_arraysize(settings); ++i) {
744 if (settings[i].setting) {
745 int value = (int)SDL_GetNumberProperty(ctx->text_settings, settings[i].setting, GetDefaultSetting(ctx->text_settings, settings[i].setting));
746 if (value == settings[i].value) {
747 SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255);
748 SDL_RenderFillRect(renderer, &checkbox);
749 }
750 SDL_SetRenderDrawColor(renderer, backColor.r, backColor.g, backColor.b, backColor.a);
751 SDL_RenderRect(renderer, &checkbox);
752 SDLTest_DrawString(renderer, checkbox.x + checkbox.w + 8.0f, checkbox.y, settings[i].label);
753 }
754 checkbox.y += LINE_HEIGHT;
755 }
756}
757
758static void ClickSettings(WindowState *ctx, float x, float y)
759{
760 int setting = (int)SDL_floorf((y - MARGIN) / LINE_HEIGHT);
761 if (setting >= 0 && setting < SDL_arraysize(settings)) {
762 SDL_SetNumberProperty(ctx->text_settings, settings[setting].setting, settings[setting].value);
763 }
764}
765
766static void RedrawWindow(WindowState *ctx)
767{
768 SDL_Renderer *renderer = ctx->renderer;
769 int rendererID = ctx->rendererID;
770 SDL_FRect drawnTextRect, cursorRect, underlineRect;
771 char text[MAX_TEXT_LENGTH];
772
773 DrawSettingsButton(ctx);
774
775 if (ctx->settings_visible) {
776 DrawSettings(ctx);
777 return;
778 }
779
780 /* Hide the text if it's a password */
781 switch ((SDL_TextInputType)SDL_GetNumberProperty(ctx->text_settings, SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT)) {
782 case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN:
783 case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN: {
784 size_t len = SDL_utf8strlen(ctx->text);
785 SDL_memset(text, '*', len);
786 text[len] = '\0';
787 break;
788 }
789 default:
790 SDL_strlcpy(text, ctx->text, sizeof(text));
791 break;
792 }
793
794 SDL_SetRenderDrawColor(renderer, backColor.r, backColor.g, backColor.b, backColor.a);
795 SDL_RenderFillRect(renderer, &ctx->textRect);
796
797 /* Initialize the drawn text rectangle for the cursor */
798 drawnTextRect.x = ctx->textRect.x;
799 drawnTextRect.y = ctx->textRect.y + (ctx->textRect.h - UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE) / 2;
800 drawnTextRect.w = 0.0f;
801 drawnTextRect.h = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE;
802
803 if (text[0]) {
804 char *utext = text;
805 Uint32 codepoint;
806 size_t len;
807 SDL_FRect dstrect;
808
809 dstrect.x = ctx->textRect.x;
810 dstrect.y = ctx->textRect.y + (ctx->textRect.h - UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE) / 2;
811 dstrect.w = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE;
812 dstrect.h = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE;
813 drawnTextRect.y = dstrect.y;
814 drawnTextRect.h = dstrect.h;
815
816 while ((codepoint = utf8_decode(utext, len = utf8_length(*utext))) != 0) {
817 float advance = unifont_draw_glyph(codepoint, rendererID, &dstrect) * UNIFONT_DRAW_SCALE;
818 dstrect.x += advance;
819 drawnTextRect.w += advance;
820 utext += len;
821 }
822 }
823
824 /* The marked text rectangle is the text area that hasn't been filled by committed text */
825 ctx->markedRect.x = ctx->textRect.x + drawnTextRect.w;
826 ctx->markedRect.w = ctx->textRect.w - drawnTextRect.w;
827
828 /* Update the drawn text rectangle for composition text, after the committed text */
829 drawnTextRect.x += drawnTextRect.w;
830 drawnTextRect.w = 0;
831
832 /* Set the cursor to the new location, we'll update it as we go, below */
833 cursorRect = drawnTextRect;
834 cursorRect.w = 2;
835 cursorRect.h = drawnTextRect.h;
836
837 if (ctx->markedText[0]) {
838 int i = 0;
839 char *utext = ctx->markedText;
840 Uint32 codepoint;
841 size_t len;
842 SDL_FRect dstrect;
843
844 dstrect.x = drawnTextRect.x;
845 dstrect.y = ctx->textRect.y + (ctx->textRect.h - UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE) / 2;
846 dstrect.w = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE;
847 dstrect.h = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE;
848 drawnTextRect.y = dstrect.y;
849 drawnTextRect.h = dstrect.h;
850
851 while ((codepoint = utf8_decode(utext, len = utf8_length(*utext))) != 0) {
852 float advance = unifont_draw_glyph(codepoint, rendererID, &dstrect) * UNIFONT_DRAW_SCALE;
853 dstrect.x += advance;
854 drawnTextRect.w += advance;
855 if (i < ctx->cursor) {
856 cursorRect.x += advance;
857 }
858 i++;
859 utext += len;
860 }
861
862 if (ctx->cursor_length > 0) {
863 cursorRect.w = ctx->cursor_length * UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE;
864 }
865
866 cursorRect.y = drawnTextRect.y;
867 cursorRect.h = drawnTextRect.h;
868
869 underlineRect = ctx->markedRect;
870 underlineRect.y = drawnTextRect.y + drawnTextRect.h - 2;
871 underlineRect.h = 2;
872 underlineRect.w = drawnTextRect.w;
873
874 SDL_SetRenderDrawColor(renderer, lineColor.r, lineColor.g, lineColor.b, lineColor.a);
875 SDL_RenderFillRect(renderer, &underlineRect);
876 }
877
878 /* Draw the cursor */
879 Uint64 now = SDL_GetTicks();
880 if ((now - ctx->last_cursor_change) >= CURSOR_BLINK_INTERVAL_MS) {
881 ctx->cursor_visible = !ctx->cursor_visible;
882 ctx->last_cursor_change = now;
883 }
884 if (ctx->cursor_length > 0) {
885 /* We'll show a highlight */
886 SDL_SetRenderDrawBlendMode(renderer, highlight_mode);
887 SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
888 SDL_RenderFillRect(renderer, &cursorRect);
889 SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
890 } else if (ctx->cursor_visible) {
891 SDL_SetRenderDrawColor(renderer, lineColor.r, lineColor.g, lineColor.b, lineColor.a);
892 SDL_RenderFillRect(renderer, &cursorRect);
893 }
894
895 /* Draw the candidates */
896 DrawCandidates(ctx, &cursorRect);
897
898 /* Update the area used to draw composition UI */
899 UpdateTextInputArea(ctx, &cursorRect);
900}
901
902static void Redraw(void)
903{
904 int i;
905 for (i = 0; i < state->num_windows; ++i) {
906 SDL_Renderer *renderer = state->renderers[i];
907 if (state->windows[i] == NULL) {
908 continue;
909 }
910 SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
911 SDL_RenderClear(renderer);
912
913 RedrawWindow(&windowstate[i]);
914
915 SDL_RenderPresent(renderer);
916 }
917}
918
919int main(int argc, char *argv[])
920{
921 bool render_composition = false;
922 bool render_candidates = false;
923 int i, done;
924 SDL_Event event;
925 char *fontname = NULL;
926
927 /* Initialize test framework */
928 state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO);
929 if (!state) {
930 return 1;
931 }
932
933 /* Parse commandline */
934 for (i = 1; i < argc;) {
935 int consumed;
936
937 consumed = SDLTest_CommonArg(state, i);
938 if (SDL_strcmp(argv[i], "--font") == 0) {
939 if (*argv[i + 1]) {
940 fontname = argv[i + 1];
941 consumed = 2;
942 }
943 } else if (SDL_strcmp(argv[i], "--render-composition") == 0) {
944 render_composition = true;
945 consumed = 1;
946 } else if (SDL_strcmp(argv[i], "--render-candidates") == 0) {
947 render_candidates = true;
948 consumed = 1;
949 }
950 if (consumed <= 0) {
951 static const char *options[] = { "[--font fontfile] [--render-composition] [--render-candidates]", NULL };
952 SDLTest_CommonLogUsage(state, argv[0], options);
953 return 1;
954 }
955
956 i += consumed;
957 }
958
959 if (render_composition && render_candidates) {
960 SDL_SetHint(SDL_HINT_IME_IMPLEMENTED_UI, "composition,candidates");
961 } else if (render_composition) {
962 SDL_SetHint(SDL_HINT_IME_IMPLEMENTED_UI, "composition");
963 } else if (render_candidates) {
964 SDL_SetHint(SDL_HINT_IME_IMPLEMENTED_UI, "candidates");
965 }
966
967 if (!SDLTest_CommonInit(state)) {
968 return 2;
969 }
970
971 windowstate = (WindowState *)SDL_calloc(state->num_windows, sizeof(*windowstate));
972 if (!windowstate) {
973 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't allocate window state: %s", SDL_GetError());
974 return -1;
975 }
976
977 fontname = GetResourceFilename(fontname, DEFAULT_FONT);
978
979 if (unifont_init(fontname) < 0) {
980 return -1;
981 }
982
983 SDL_Log("Using font: %s", fontname);
984
985 /* Initialize window state */
986 for (i = 0; i < state->num_windows; ++i) {
987 WindowState *ctx = &windowstate[i];
988 SDL_Window *window = state->windows[i];
989 SDL_Renderer *renderer = state->renderers[i];
990 int icon_w = 0, icon_h = 0;
991
992 SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX);
993
994 ctx->window = window;
995 ctx->renderer = renderer;
996 ctx->rendererID = i;
997 ctx->settings_icon = LoadTexture(renderer, "icon.bmp", true, &icon_w, &icon_h);
998 ctx->settings_rect.x = (float)WINDOW_WIDTH - icon_w - MARGIN;
999 ctx->settings_rect.y = MARGIN;
1000 ctx->settings_rect.w = (float)icon_w;
1001 ctx->settings_rect.h = (float)icon_h;
1002
1003 InitInput(ctx);
1004
1005 SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
1006 SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF);
1007 SDL_RenderClear(renderer);
1008 }
1009 highlight_mode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR,
1010 SDL_BLENDFACTOR_ZERO,
1011 SDL_BLENDOPERATION_ADD,
1012 SDL_BLENDFACTOR_ZERO,
1013 SDL_BLENDFACTOR_ONE,
1014 SDL_BLENDOPERATION_ADD);
1015
1016 /* Main render loop */
1017 done = 0;
1018 while (!done) {
1019 /* Check for events */
1020 while (SDL_PollEvent(&event)) {
1021 SDLTest_CommonEvent(state, &event, &done);
1022 switch (event.type) {
1023 case SDL_EVENT_MOUSE_BUTTON_UP: {
1024 SDL_FPoint point;
1025 WindowState *ctx = GetWindowStateForWindowID(event.button.windowID);
1026 if (!ctx) {
1027 break;
1028 }
1029
1030 SDL_ConvertEventToRenderCoordinates(ctx->renderer, &event);
1031 point.x = event.button.x;
1032 point.y = event.button.y;
1033 if (SDL_PointInRectFloat(&point, &ctx->settings_rect)) {
1034 ToggleSettings(ctx);
1035 } else if (ctx->settings_visible) {
1036 ClickSettings(ctx, point.x, point.y);
1037 }
1038 break;
1039 }
1040 case SDL_EVENT_KEY_DOWN: {
1041 WindowState *ctx = GetWindowStateForWindowID(event.key.windowID);
1042 if (!ctx) {
1043 break;
1044 }
1045
1046 switch (event.key.key) {
1047 case SDLK_RETURN:
1048 ctx->text[0] = 0x00;
1049 break;
1050 case SDLK_BACKSPACE:
1051 /* Only delete text if not in editing mode. */
1052 if (!ctx->markedText[0]) {
1053 size_t textlen = SDL_strlen(ctx->text);
1054
1055 do {
1056 if (textlen == 0) {
1057 break;
1058 }
1059 if (!(ctx->text[textlen - 1] & 0x80)) {
1060 /* One byte */
1061 ctx->text[textlen - 1] = 0x00;
1062 break;
1063 }
1064 if ((ctx->text[textlen - 1] & 0xC0) == 0x80) {
1065 /* Byte from the multibyte sequence */
1066 ctx->text[textlen - 1] = 0x00;
1067 textlen--;
1068 }
1069 if ((ctx->text[textlen - 1] & 0xC0) == 0xC0) {
1070 /* First byte of multibyte sequence */
1071 ctx->text[textlen - 1] = 0x00;
1072 break;
1073 }
1074 } while (1);
1075 }
1076 break;
1077 default:
1078 break;
1079 }
1080
1081 if (done) {
1082 break;
1083 }
1084
1085 SDL_Log("Keyboard: scancode 0x%08X = %s, keycode 0x%08" SDL_PRIX32 " = %s",
1086 event.key.scancode,
1087 SDL_GetScancodeName(event.key.scancode),
1088 SDL_static_cast(Uint32, event.key.key),
1089 SDL_GetKeyName(event.key.key));
1090 break;
1091 }
1092 case SDL_EVENT_TEXT_INPUT: {
1093 WindowState *ctx = GetWindowStateForWindowID(event.text.windowID);
1094 if (!ctx) {
1095 break;
1096 }
1097
1098 if (event.text.text[0] == '\0' || event.text.text[0] == '\n' || ctx->markedRect.w < 0) {
1099 break;
1100 }
1101
1102 SDL_Log("Keyboard: text input \"%s\"", event.text.text);
1103
1104 if (SDL_strlen(ctx->text) + SDL_strlen(event.text.text) < sizeof(ctx->text)) {
1105 SDL_strlcat(ctx->text, event.text.text, sizeof(ctx->text));
1106 }
1107
1108 SDL_Log("text inputted: %s", ctx->text);
1109
1110 /* After text inputted, we can clear up markedText because it */
1111 /* is committed */
1112 ctx->markedText[0] = 0;
1113 break;
1114 }
1115 case SDL_EVENT_TEXT_EDITING: {
1116 WindowState *ctx = GetWindowStateForWindowID(event.edit.windowID);
1117 if (!ctx) {
1118 break;
1119 }
1120
1121 SDL_Log("text editing \"%s\", selected range (%" SDL_PRIs32 ", %" SDL_PRIs32 ")",
1122 event.edit.text, event.edit.start, event.edit.length);
1123
1124 SDL_strlcpy(ctx->markedText, event.edit.text, sizeof(ctx->markedText));
1125 ctx->cursor = event.edit.start;
1126 ctx->cursor_length = event.edit.length;
1127 break;
1128 }
1129 case SDL_EVENT_TEXT_EDITING_CANDIDATES: {
1130 WindowState *ctx = GetWindowStateForWindowID(event.edit.windowID);
1131 if (!ctx) {
1132 break;
1133 }
1134
1135 SDL_Log("text candidates:");
1136 for (i = 0; i < event.edit_candidates.num_candidates; ++i) {
1137 SDL_Log("%c%s", i == event.edit_candidates.selected_candidate ? '>' : ' ', event.edit_candidates.candidates[i]);
1138 }
1139
1140 ClearCandidates(ctx);
1141 SaveCandidates(ctx, &event);
1142 break;
1143 }
1144 default:
1145 break;
1146 }
1147 }
1148
1149 Redraw();
1150 }
1151 SDL_free(fontname);
1152 CleanupVideo();
1153 SDLTest_CommonQuit(state);
1154 return 0;
1155}