]> git.lizzy.rs Git - nothing.git/blob - src/game/level/level_editor/rect_layer.c
(#1048) Implement copy-pasting for PointLayer
[nothing.git] / src / game / level / level_editor / rect_layer.c
1 #include "game/camera.h"
2 #include "system/lt.h"
3 #include "system/stacktrace.h"
4 #include "system/nth_alloc.h"
5 #include "system/log.h"
6 #include "math/rect.h"
7 #include "color.h"
8 #include "rect_layer.h"
9 #include "dynarray.h"
10 #include "system/line_stream.h"
11 #include "color_picker.h"
12 #include "system/str.h"
13 #include "ui/edit_field.h"
14 #include "undo_history.h"
15
16 #define RECT_LAYER_SELECTION_THICCNESS 10.0f
17 #define RECT_LAYER_ID_LABEL_SIZE vec(3.0f, 3.0f)
18 #define CREATE_AREA_THRESHOLD 10.0
19
20 // TODO: RectLayer does not support copy-pasting
21
22 typedef enum {
23     RECT_LAYER_IDLE = 0,
24     RECT_LAYER_CREATE,
25     // TODO(#955): Rectangles in Level Editor have only one resize anchor to work with
26     RECT_LAYER_RESIZE,
27     RECT_LAYER_MOVE,
28     RECT_LAYER_ID_RENAME,
29     RECT_LAYER_RECOLOR
30 } RectLayerState;
31
32 struct RectLayer {
33     Lt *lt;
34     RectLayerState state;
35     Dynarray *ids;
36     Dynarray *rects;
37     Dynarray *colors;
38     ColorPicker color_picker;
39     Vec create_begin;
40     Vec create_end;
41     int selection;
42     Vec move_anchor;
43     Edit_field *id_edit_field;
44     // TODO(#1043): RectLayer should use intermediate values instead of previous ones
45     Color inter_color;
46     Rect inter_rect;
47 };
48
49 typedef enum {
50     UNDO_ADD,
51     UNDO_DELETE,
52     UNDO_UPDATE
53 } UndoType;
54
55 typedef struct {
56     UndoType type;
57     RectLayer *layer;
58     Rect rect;
59     Color color;
60     char id[RECT_LAYER_ID_MAX_SIZE];
61     size_t index;
62 } UndoContext;
63
64 static
65 UndoContext create_undo_context(RectLayer *rect_layer, UndoType type)
66 {
67     trace_assert(rect_layer);
68
69     size_t index = type == UNDO_ADD ? dynarray_count(rect_layer->rects) - 1 : (size_t) rect_layer->selection;
70
71     UndoContext undo_context;
72     undo_context.type = type;
73     undo_context.layer = rect_layer;
74     dynarray_copy_to(rect_layer->rects, &undo_context.rect, index);
75     dynarray_copy_to(rect_layer->colors, &undo_context.color, index);
76     dynarray_copy_to(rect_layer->ids, undo_context.id, index);
77     undo_context.index = index;
78
79     return undo_context;
80 }
81
82 static
83 void rect_layer_undo(void *context, size_t context_size)
84 {
85     trace_assert(context);
86     trace_assert(sizeof(UndoContext) == context_size);
87
88     UndoContext *undo_context = context;
89     RectLayer *rect_layer = undo_context->layer;
90
91     switch (undo_context->type) {
92     case UNDO_ADD: {
93         dynarray_delete_at(rect_layer->rects, undo_context->index);
94         dynarray_delete_at(rect_layer->colors, undo_context->index);
95         dynarray_delete_at(rect_layer->ids, undo_context->index);
96         rect_layer->selection = -1;
97     } break;
98
99     case UNDO_DELETE: {
100         dynarray_insert_before(rect_layer->rects, undo_context->index, &undo_context->rect);
101         dynarray_insert_before(rect_layer->colors, undo_context->index, &undo_context->color);
102         dynarray_insert_before(rect_layer->ids, undo_context->index, &undo_context->id);
103         rect_layer->selection = -1;
104     } break;
105
106     case UNDO_UPDATE: {
107         dynarray_replace_at(rect_layer->rects, undo_context->index, &undo_context->rect);
108         dynarray_replace_at(rect_layer->colors, undo_context->index, &undo_context->color);
109         dynarray_replace_at(rect_layer->ids, undo_context->index, &undo_context->id);
110     } break;
111     }
112 }
113
114 #define UNDO_PUSH(LAYER, HISTORY, UNDO_TYPE)                            \
115     do {                                                                \
116         UndoContext context = create_undo_context(LAYER, UNDO_TYPE);    \
117         undo_history_push(                                              \
118             HISTORY,                                                    \
119             rect_layer_undo,                                            \
120             &context,                                                   \
121             sizeof(context));                                           \
122     } while(0)
123
124
125 static int rect_layer_add_rect(RectLayer *layer,
126                                Rect rect,
127                                Color color,
128                                UndoHistory *undo_history)
129 {
130     trace_assert(layer);
131
132     if (dynarray_push(layer->rects, &rect) < 0) {
133         return -1;
134     }
135
136     if (dynarray_push(layer->colors, &color) < 0) {
137         return -1;
138     }
139
140     char id[RECT_LAYER_ID_MAX_SIZE];
141     for (size_t i = 0; i < RECT_LAYER_ID_MAX_SIZE - 1; ++i) {
142         id[i] = (char) ('a' + rand() % ('z' - 'a' + 1));
143     }
144     id[RECT_LAYER_ID_MAX_SIZE - 1] = '\0';
145
146     if (dynarray_push(layer->ids, id)) {
147         return -1;
148     }
149
150     UNDO_PUSH(layer, undo_history, UNDO_ADD);
151
152     return 0;
153 }
154
155 // TODO(#956): rect_layer_rect_at doesn't return rectangles according to some z-order
156 static int rect_layer_rect_at(RectLayer *layer, Vec position)
157 {
158     trace_assert(layer);
159
160     const size_t n = dynarray_count(layer->rects);
161     Rect *rects = dynarray_data(layer->rects);
162
163     for (size_t i = 0; i < n; ++i) {
164         if (rect_contains_point(rects[i], position)) {
165             return (int) i;
166         }
167     }
168
169     return -1;
170 }
171
172 static Rect rect_layer_resize_anchor(const Camera *camera, Rect boundary_rect)
173 {
174     const Rect overlay_rect =
175         rect_scale(
176             camera_rect(camera, boundary_rect),
177             RECT_LAYER_SELECTION_THICCNESS * 0.5f);
178
179     return rect(
180         overlay_rect.x + overlay_rect.w,
181         overlay_rect.y + overlay_rect.h,
182         RECT_LAYER_SELECTION_THICCNESS * 2.0f,
183         RECT_LAYER_SELECTION_THICCNESS * 2.0f);
184 }
185
186 static int rect_layer_delete_rect_at(RectLayer *layer,
187                                      size_t i,
188                                      UndoHistory *undo_history)
189 {
190     trace_assert(layer);
191
192     UNDO_PUSH(layer, undo_history, UNDO_DELETE);
193
194     dynarray_delete_at(layer->rects, i);
195     dynarray_delete_at(layer->colors, i);
196     dynarray_delete_at(layer->ids, i);
197
198     return 0;
199 }
200
201 static int rect_layer_event_idle(RectLayer *layer,
202                                  const SDL_Event *event,
203                                  const Camera *camera,
204                                  UndoHistory *undo_history)
205 {
206     trace_assert(layer);
207     trace_assert(event);
208     trace_assert(camera);
209
210     int color_changed = 0;
211     if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
212         return -1;
213     }
214
215     if (color_changed) {
216         if (layer->selection >= 0) {
217             dynarray_copy_to(layer->colors, &layer->inter_color, (size_t)layer->selection);
218             layer->state = RECT_LAYER_RECOLOR;
219         }
220         return 0;
221     }
222
223     switch (event->type) {
224     case SDL_MOUSEBUTTONDOWN: {
225         switch (event->button.button) {
226         case SDL_BUTTON_LEFT: {
227             Point position = camera_map_screen(
228                 camera,
229                 event->button.x,
230                 event->button.y);
231             int rect_at_position =
232                 rect_layer_rect_at(layer, position);
233
234             Rect *rects = dynarray_data(layer->rects);
235             Color *colors = dynarray_data(layer->colors);
236
237             if (rect_at_position >= 0) {
238                 layer->selection = rect_at_position;
239                 layer->state = RECT_LAYER_MOVE;
240                 layer->move_anchor =
241                     vec_sub(
242                         position,
243                         vec(
244                             rects[layer->selection].x,
245                             rects[layer->selection].y));
246                 layer->color_picker =
247                     create_color_picker_from_rgba(colors[rect_at_position]);
248
249                 dynarray_copy_to(layer->rects, &layer->inter_rect, (size_t) rect_at_position);
250             } else if (layer->selection >= 0 && rect_contains_point(
251                            rect_layer_resize_anchor(
252                                camera,
253                                rects[layer->selection]),
254                            vec(
255                                (float) event->button.x,
256                                (float) event->button.y))) {
257                 layer->state = RECT_LAYER_RESIZE;
258                 dynarray_copy_to(layer->rects, &layer->inter_rect, (size_t) layer->selection);
259             } else {
260                 layer->selection = rect_at_position;
261
262                 if (layer->selection < 0) {
263                     layer->state = RECT_LAYER_CREATE;
264                     layer->create_begin = position;
265                     layer->create_end = position;
266                 }
267             }
268         } break;
269         }
270     } break;
271
272     case SDL_KEYDOWN: {
273         switch (event->key.keysym.sym) {
274         case SDLK_DELETE: {
275             if (layer->selection >= 0) {
276                 rect_layer_delete_rect_at(layer, (size_t) layer->selection, undo_history);
277                 layer->selection = -1;
278             }
279         } break;
280
281         case SDLK_F2: {
282             if (layer->selection >= 0) {
283                 const char *ids = dynarray_data(layer->ids);
284                 Color *colors = dynarray_data(layer->colors);
285
286                 edit_field_restyle(
287                     layer->id_edit_field,
288                     RECT_LAYER_ID_LABEL_SIZE,
289                     color_invert(colors[layer->selection]));
290
291                 layer->state = RECT_LAYER_ID_RENAME;
292                 edit_field_replace(
293                     layer->id_edit_field,
294                     ids + layer->selection * RECT_LAYER_ID_MAX_SIZE);
295                 SDL_StartTextInput();
296             }
297         } break;
298         }
299     } break;
300     }
301
302     return 0;
303 }
304
305 static int rect_layer_event_create(RectLayer *layer,
306                                    const SDL_Event *event,
307                                    const Camera *camera,
308                                    UndoHistory *undo_history)
309 {
310     trace_assert(layer);
311     trace_assert(event);
312     trace_assert(camera);
313
314     switch (event->type) {
315     case SDL_MOUSEBUTTONUP: {
316         switch (event->button.button) {
317         case SDL_BUTTON_LEFT: {
318             const Rect real_rect =
319                 rect_from_points(
320                     layer->create_begin,
321                     layer->create_end);
322             const float area = real_rect.w * real_rect.h;
323
324             if (area >= CREATE_AREA_THRESHOLD) {
325                 rect_layer_add_rect(
326                     layer,
327                     real_rect,
328                     color_picker_rgba(&layer->color_picker),
329                     undo_history);
330             } else {
331                 log_info("The area is too small %f. Such small box won't be created.\n", area);
332             }
333             layer->state = RECT_LAYER_IDLE;
334         } break;
335         }
336     } break;
337
338     case SDL_MOUSEMOTION: {
339         layer->create_end = camera_map_screen(
340             camera,
341             event->motion.x,
342             event->motion.y);
343     } break;
344     }
345     return 0;
346 }
347
348 static int rect_layer_event_resize(RectLayer *layer,
349                                    const SDL_Event *event,
350                                    const Camera *camera,
351                                    UndoHistory *undo_history)
352 {
353     trace_assert(layer);
354     trace_assert(event);
355     trace_assert(camera);
356     trace_assert(layer->selection >= 0);
357
358     switch (event->type) {
359     case SDL_MOUSEMOTION: {
360         layer->inter_rect = rect_from_points(
361             vec(layer->inter_rect.x, layer->inter_rect.y),
362             vec_sum(
363                 camera_map_screen(
364                     camera,
365                     event->button.x,
366                     event->button.y),
367                 vec(RECT_LAYER_SELECTION_THICCNESS * -0.5f,
368                     RECT_LAYER_SELECTION_THICCNESS * -0.5f)));
369     } break;
370
371     case SDL_MOUSEBUTTONUP: {
372         layer->state = RECT_LAYER_IDLE;
373         UNDO_PUSH(layer, undo_history, UNDO_UPDATE);
374         dynarray_replace_at(layer->rects, (size_t) layer->selection, &layer->inter_rect);
375     } break;
376     }
377
378     return 0;
379 }
380
381 static int rect_layer_event_move(RectLayer *layer,
382                                  const SDL_Event *event,
383                                  const Camera *camera,
384                                  UndoHistory *undo_history)
385 {
386     trace_assert(layer);
387     trace_assert(event);
388     trace_assert(camera);
389     trace_assert(layer->selection >= 0);
390
391     switch (event->type) {
392     case SDL_MOUSEMOTION: {
393         Point position = vec_sub(
394             camera_map_screen(
395                 camera,
396                 event->button.x,
397                 event->button.y),
398             layer->move_anchor);
399
400         trace_assert(layer->selection >= 0);
401
402         layer->inter_rect.x = position.x;
403         layer->inter_rect.y = position.y;
404     } break;
405
406     case SDL_MOUSEBUTTONUP: {
407         layer->state = RECT_LAYER_IDLE;
408         UNDO_PUSH(layer, undo_history, UNDO_UPDATE);
409         dynarray_replace_at(layer->rects, (size_t) layer->selection, &layer->inter_rect);
410     } break;
411     }
412     return 0;
413 }
414
415 static int rect_layer_event_id_rename(RectLayer *layer,
416                                       const SDL_Event *event,
417                                       const Camera *camera,
418                                       UndoHistory *undo_history)
419 {
420     trace_assert(layer);
421     trace_assert(event);
422     trace_assert(camera);
423     trace_assert(layer->selection >= 0);
424
425     switch (event->type) {
426     case SDL_KEYDOWN: {
427         switch (event->key.keysym.sym) {
428         case SDLK_RETURN: {
429             UNDO_PUSH(layer, undo_history, UNDO_UPDATE);
430
431             char *id = dynarray_pointer_at(layer->ids, (size_t)layer->selection);
432             memset(id, 0, RECT_LAYER_ID_MAX_SIZE);
433             memcpy(id, edit_field_as_text(layer->id_edit_field), RECT_LAYER_ID_MAX_SIZE - 1);
434             layer->state = RECT_LAYER_IDLE;
435             SDL_StopTextInput();
436         } break;
437
438         case SDLK_ESCAPE: {
439             layer->state = RECT_LAYER_IDLE;
440             SDL_StopTextInput();
441         } break;
442         }
443     } break;
444     }
445
446     return edit_field_event(layer->id_edit_field, event);
447 }
448
449 LayerPtr rect_layer_as_layer(RectLayer *rect_layer)
450 {
451     LayerPtr layer = {
452         .type = LAYER_RECT,
453         .ptr = rect_layer
454     };
455     return layer;
456 }
457
458 RectLayer *create_rect_layer(void)
459 {
460     Lt *lt = create_lt();
461
462     RectLayer *layer = PUSH_LT(lt, nth_calloc(1, sizeof(RectLayer)), free);
463     if (layer == NULL) {
464         RETURN_LT(lt, NULL);
465     }
466     layer->lt = lt;
467
468     layer->ids = PUSH_LT(
469         lt,
470         create_dynarray(sizeof(char) * RECT_LAYER_ID_MAX_SIZE),
471         destroy_dynarray);
472     if (layer->ids == NULL) {
473         RETURN_LT(lt, NULL);
474     }
475
476     layer->rects = PUSH_LT(
477         lt,
478         create_dynarray(sizeof(Rect)),
479         destroy_dynarray);
480     if (layer->rects == NULL) {
481         RETURN_LT(lt, NULL);
482     }
483
484     layer->colors = PUSH_LT(
485         lt,
486         create_dynarray(sizeof(Color)),
487         destroy_dynarray);
488     if (layer->colors == NULL) {
489         RETURN_LT(lt, NULL);
490     }
491
492     layer->id_edit_field = PUSH_LT(
493         lt,
494         create_edit_field(
495             RECT_LAYER_ID_LABEL_SIZE,
496             COLOR_BLACK),
497         destroy_edit_field);
498     if (layer->id_edit_field == NULL) {
499         RETURN_LT(lt, NULL);
500     }
501
502     layer->color_picker = create_color_picker_from_rgba(rgba(1.0f, 0.0f, 0.0f, 1.0f));
503     layer->selection = -1;
504
505     return layer;
506 }
507
508 RectLayer *create_rect_layer_from_line_stream(LineStream *line_stream)
509 {
510     trace_assert(line_stream);
511
512     RectLayer *layer = create_rect_layer();
513     if (layer == NULL) {
514         return NULL;
515     }
516
517     const char *line = line_stream_next(line_stream);
518     if (line == NULL) {
519         RETURN_LT(layer->lt, NULL);
520     }
521
522     size_t count = 0;
523     if (sscanf(line, "%zu", &count) < 0) {
524         RETURN_LT(layer->lt, NULL);
525     }
526
527     for (size_t i = 0; i < count; ++i) {
528         line = line_stream_next(line_stream);
529         if (line == NULL) {
530             RETURN_LT(layer->lt, NULL);
531         }
532
533         char hex[7];
534         Rect rect;
535         char id[RECT_LAYER_ID_MAX_SIZE];
536
537         if (sscanf(line,
538                    "%"STRINGIFY(RECT_LAYER_ID_MAX_SIZE)"s%f%f%f%f%6s\n",
539                    id,
540                    &rect.x, &rect.y,
541                    &rect.w, &rect.h,
542                    hex) < 0) {
543             RETURN_LT(layer->lt, NULL);
544         }
545
546         Color color = hexstr(hex);
547
548         dynarray_push(layer->rects, &rect);
549         dynarray_push(layer->ids, id);
550         dynarray_push(layer->colors, &color);
551     }
552
553     return layer;
554 }
555
556 void destroy_rect_layer(RectLayer *layer)
557 {
558     trace_assert(layer);
559     RETURN_LT0(layer->lt);
560 }
561
562 int rect_layer_render(const RectLayer *layer, Camera *camera, int active)
563 {
564     trace_assert(layer);
565     trace_assert(camera);
566
567     const size_t n = dynarray_count(layer->rects);
568     Rect *rects = dynarray_data(layer->rects);
569     Color *colors = dynarray_data(layer->colors);
570     const char *ids = dynarray_data(layer->ids);
571
572     // The Rectangles
573     for (size_t i = 0; i < n; ++i) {
574         Rect rect = rects[i];
575         Color color = colors[i];
576
577         if (layer->selection == (int) i) {
578             if (layer->state == RECT_LAYER_RESIZE || layer->state == RECT_LAYER_MOVE) {
579                 rect = layer->inter_rect;
580             }
581
582             if (layer->state == RECT_LAYER_RECOLOR) {
583                 color = layer->inter_color;
584             }
585         }
586
587         if (camera_fill_rect(
588                 camera,
589                 rect,
590                 color_scale(
591                     color,
592                     rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) {
593             return -1;
594         }
595
596         // Selection Overlay
597         if (active && (size_t) layer->selection == i) {
598             const Rect overlay_rect =
599                 rect_scale(
600                     camera_rect(camera, rect),
601                     RECT_LAYER_SELECTION_THICCNESS * 0.5f);
602             const Color overlay_color = color_invert(color);
603
604             // Main Rectangle
605             if (camera_fill_rect(
606                     camera,
607                     rect,
608                     color_scale(
609                         color,
610                         rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) {
611                 return -1;
612             }
613
614             if (camera_draw_thicc_rect_screen(
615                     camera,
616                     overlay_rect,
617                     overlay_color,
618                     RECT_LAYER_SELECTION_THICCNESS) < 0) {
619                 return -1;
620             }
621
622             // Rectangle Id
623             if (layer->state == RECT_LAYER_ID_RENAME) {
624                 // ID renaming Edit Field
625                 if (edit_field_render_world(
626                         layer->id_edit_field,
627                         camera,
628                         rect_position(rect)) < 0) {
629                     return -1;
630                 }
631             } else {
632                 // Id text
633                 if (camera_render_text(
634                         camera,
635                         ids + layer->selection * RECT_LAYER_ID_MAX_SIZE,
636                         RECT_LAYER_ID_LABEL_SIZE,
637                         color_invert(color),
638                         rect_position(rect)) < 0) {
639                     return -1;
640                 }
641             }
642
643             // Resize Anchor
644             if (camera_fill_rect_screen(
645                     camera,
646                     rect_layer_resize_anchor(camera, rect),
647                     overlay_color) < 0) {
648                 return -1;
649             }
650         }
651     }
652
653     // Proto Rectangle
654     const Color color = color_picker_rgba(&layer->color_picker);
655     if (layer->state == RECT_LAYER_CREATE) {
656         if (camera_fill_rect(camera, rect_from_points(layer->create_begin, layer->create_end), color) < 0) {
657             return -1;
658         }
659     }
660
661     if (active && color_picker_render(&layer->color_picker, camera) < 0) {
662         return -1;
663     }
664
665     return 0;
666 }
667
668 static
669 int rect_layer_event_recolor(RectLayer *layer,
670                              const SDL_Event *event,
671                              const Camera *camera,
672                              UndoHistory *undo_history)
673 {
674     trace_assert(layer);
675     trace_assert(event);
676     trace_assert(camera);
677     trace_assert(undo_history);
678     trace_assert(layer->selection >= 0);
679
680     int color_changed = 0;
681     if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
682         return -1;
683     }
684
685     if (color_changed) {
686         layer->inter_color = color_picker_rgba(&layer->color_picker);
687
688         if (!color_picker_drag(&layer->color_picker)) {
689             UNDO_PUSH(layer, undo_history, UNDO_UPDATE);
690             dynarray_replace_at(layer->colors, (size_t) layer->selection, &layer->inter_color);
691             layer->state = RECT_LAYER_IDLE;
692         }
693     }
694
695     return 0;
696 }
697
698 int rect_layer_event(RectLayer *layer,
699                      const SDL_Event *event,
700                      const Camera *camera,
701                      UndoHistory *undo_history)
702 {
703     trace_assert(layer);
704     trace_assert(event);
705     trace_assert(undo_history);
706
707     switch (layer->state) {
708     case RECT_LAYER_IDLE:
709         return rect_layer_event_idle(layer, event, camera, undo_history);
710
711     case RECT_LAYER_CREATE:
712         return rect_layer_event_create(layer, event, camera, undo_history);
713
714     case RECT_LAYER_RESIZE:
715         return rect_layer_event_resize(layer, event, camera, undo_history);
716
717     case RECT_LAYER_MOVE:
718         return rect_layer_event_move(layer, event, camera, undo_history);
719
720     case RECT_LAYER_ID_RENAME:
721         return rect_layer_event_id_rename(layer, event, camera, undo_history);
722
723     case RECT_LAYER_RECOLOR:
724         return rect_layer_event_recolor(layer, event, camera, undo_history);
725     }
726
727     return 0;
728 }
729
730 size_t rect_layer_count(const RectLayer *layer)
731 {
732     return dynarray_count(layer->rects);
733 }
734
735 const Rect *rect_layer_rects(const RectLayer *layer)
736 {
737     return dynarray_data(layer->rects);
738 }
739
740 const Color *rect_layer_colors(const RectLayer *layer)
741 {
742     return dynarray_data(layer->colors);
743 }
744
745 const char *rect_layer_ids(const RectLayer *layer)
746 {
747     return dynarray_data(layer->ids);
748 }
749
750 int rect_layer_dump_stream(const RectLayer *layer, FILE *filedump)
751 {
752     trace_assert(layer);
753     trace_assert(filedump);
754
755     size_t n = dynarray_count(layer->ids);
756     char *ids = dynarray_data(layer->ids);
757     Rect *rects = dynarray_data(layer->rects);
758     Color *colors = dynarray_data(layer->colors);
759
760     fprintf(filedump, "%zd\n", n);
761     for (size_t i = 0; i < n; ++i) {
762         fprintf(filedump, "%s %f %f %f %f ",
763                 ids + RECT_LAYER_ID_MAX_SIZE * i,
764                 rects[i].x, rects[i].y, rects[i].w, rects[i].h);
765         color_hex_to_stream(colors[i], filedump);
766         fprintf(filedump, "\n");
767     }
768
769     return 0;
770 }