* lib/text.h: Added text_get_line() declaration
[dia.git] / lib / text.c
blob8795e8097bfea0edbe3584a59a34c09ee36439e1
1 /* Dia -- an diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 #include <config.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <glib.h>
24 #include <math.h>
26 #include <gdk/gdkkeysyms.h>
28 #include "propinternals.h"
29 #include "text.h"
30 #include "message.h"
31 #include "diarenderer.h"
32 #include "diagramdata.h"
33 #include "objchange.h"
35 #ifdef USE_TEXTLINE_FOR_LINES
36 #include "textline.h"
37 #endif
39 static int text_key_event(Focus *focus, guint keysym,
40 const gchar *str, int strlen,
41 ObjectChange **change);
43 enum change_type {
44 TYPE_DELETE_BACKWARD,
45 TYPE_DELETE_FORWARD,
46 TYPE_INSERT_CHAR,
47 TYPE_JOIN_ROW,
48 TYPE_SPLIT_ROW,
49 TYPE_DELETE_ALL
52 struct TextObjectChange {
53 ObjectChange obj_change;
55 Text *text;
56 enum change_type type;
57 gunichar ch;
58 int pos;
59 int row;
60 gchar *str;
63 #define CURSOR_HEIGHT_RATIO 20
65 /* *** Encapsulation functions for transferring to text_line *** */
66 static gchar *
67 text_get_line(Text *text, int line)
69 #ifdef USE_TEXTLINE_FOR_LINES
70 return text_line_get_string(text->lines[line]);
71 #else
72 return text->line[line];
73 #endif
76 /** Raw sets one line to a given text, not copying, not freeing.
78 static void
79 text_set_line(Text *text, int line_no, gchar *line)
81 #ifdef USE_TEXTLINE_FOR_LINES
82 text_line_set_string(text->lines[line_no], line);
83 #else
84 text->line[line_no] = line;
85 #endif
88 /** Set the text of a line, freeing, copying and mallocing as required.
89 * Updates strlen and row_width entries, but not max_width.
91 static void
92 text_set_line_text(Text *text, int line_no, gchar *line)
94 #ifdef USE_TEXTLINE_FOR_LINES
95 text_set_line(text, line_no, g_strdup(line));
96 text->strlen[line_no] = strlen(line);
97 #else
98 if (text->line[line_no] != NULL) {
99 g_free(text->line[line_no]);
101 text_set_line(text, line_no, g_strdup(line));
102 text->row_width[line_no] =
103 dia_font_string_width(line, text->font, text->height);
104 text->strlen[line_no] = strlen(line);
105 #endif
108 /** Delete the line, freeing appropriately and moving stuff up.
109 * This function circumvents the normal free/alloc cycle of
110 * text_set_line_text. */
111 static void
112 text_delete_line(Text *text, int line_no)
114 int i;
116 #ifdef USE_TEXTLINE_FOR_LINES
117 g_free(text->lines[line_no]);
118 for (i = line_no; i < text->numlines - 1; i++) {
119 text->lines[i] = text->lines[i+1];
120 text->strlen[i] = text->strlen[i+1];
122 text->numlines -= 1;
123 text->lines = g_realloc(text->lines, sizeof(TextLine *)*text->numlines);
124 text->strlen = g_realloc(text->strlen, sizeof(int)*text->numlines);
125 #else
126 if (text->line[line_no] != NULL) {
127 free(text->line[line_no]);
130 for (i = line_no; i < text->numlines - 1; i++) {
131 text_set_line(text, i, text_get_line(text, i+1));
132 text->strlen[i] = text->strlen[i+1];
133 text->row_width[i] = text->row_width[i+1];
136 text->numlines -= 1;
137 text->line = g_realloc(text->line, sizeof(char *)*text->numlines);
138 text->strlen = g_realloc(text->strlen, sizeof(int)*text->numlines);
139 text->row_width = g_realloc(text->row_width, sizeof(real)*text->numlines);
140 #endif
143 /** Insert a new (empty) line at line_no.
144 * This function circumvents the normal free/alloc cycle of
145 * text_set_line_text. */
146 static void
147 text_insert_line(Text *text, int line_no)
149 int i;
150 #ifdef USE_TEXTLINE_FOR_LINES
151 text->numlines += 1;
152 text->lines = g_realloc(text->lines, sizeof(char *)*text->numlines);
153 text->strlen = g_realloc(text->strlen, sizeof(int)*text->numlines);
155 for (i = text->numlines - 1; i > line_no; i--) {
156 text->lines[i] = text->lines[i - 1];
157 text->strlen[i] = text->strlen[i - 1];
159 text->lines[line_no] = text_line_new("", text->font, text->height);;
160 #else
161 text->numlines += 1;
162 text->line = g_realloc(text->line, sizeof(char *)*text->numlines);
163 text->strlen = g_realloc(text->strlen, sizeof(int)*text->numlines);
164 text->row_width = g_realloc(text->row_width, sizeof(real)*text->numlines);
166 for (i = text->numlines - 1; i > line_no; i--) {
167 text_set_line(text, i, text_get_line(text, i - 1));
168 text->strlen[i] = text->strlen[i - 1];
169 text->row_width[i] = text->row_width[i - 1];
171 text->line[line_no] = NULL;
172 #endif
175 /** Get the in-diagram width of the given line.
176 * @param text The text object;
177 * @param line_no The index of the line in the text object, starting at 0.
178 * @returns The width in cm of the indicated line.
180 real
181 text_get_line_width(Text *text, int line_no)
183 #ifdef USE_TEXTLINE_FOR_LINES
184 return text_line_get_width(text->lines[line_no]);
185 #else
186 return text->row_width[line_no];
187 #endif
190 /** Get the number of characters of the given line.
191 * @param text The text object;
192 * @param line_no The index of the line in the text object, starting at 0.
193 * @returns The number of UTF-8 characters of the indicated line.
196 text_get_line_strlen(Text *text, int line_no)
198 return text->strlen[line_no];
201 real
202 text_get_max_width(Text *text)
204 return text->max_width;
207 /** Get the *average* ascent of this Text object.
208 * @param a Text object
209 * @returns the average of the ascents of each line (height above baseline)
211 real
212 text_get_ascent(Text *text)
214 return text->ascent;
217 /** Get the *average* descent of this Text object.
218 * @param a Text object
219 * @returns the average of the descents of each line (height below baseline)
221 real
222 text_get_descent(Text *text)
224 return text->descent;
227 static ObjectChange *text_create_change(Text *text, enum change_type type,
228 gunichar ch, int pos, int row);
230 static void
231 calc_width(Text *text)
233 real width;
234 int i;
236 width = 0.0;
237 for (i = 0; i < text->numlines; i++) {
238 #ifndef USE_TEXTLINE_FOR_LINES
239 text->row_width[i] =
240 dia_font_string_width(text_get_line(text, i), text->font, text->height);
241 #endif
242 width = MAX(width, text_get_line_width(text, i));
245 text->max_width = width;
248 static void
249 calc_ascent_descent(Text *text)
251 real sig_a = 0.0,sig_d = 0.0;
252 guint i;
254 for ( i = 0; i < text->numlines; i++) {
255 #ifdef USE_TEXTLINE_FOR_LINES
256 sig_a += text_line_get_ascent(text->lines[i]);
257 sig_d += text_line_get_descent(text->lines[i]);
258 #else
259 sig_a += dia_font_ascent(text_get_line(text, i), text->font, text->height);
260 sig_d += dia_font_descent(text_get_line(text, i), text->font, text->height);
261 #endif
264 text->ascent = sig_a / (real)text->numlines;
265 text->descent = sig_d / (real)text->numlines;
268 static void
269 free_string(Text *text)
271 int i;
273 #ifdef USE_TEXTLINE_FOR_LINES
274 for (i=0;i<text->numlines;i++) {
275 text_line_destroy(text->lines[i]);
278 g_free(text->lines);
279 text->lines = NULL;
281 g_free(text->strlen);
282 text->strlen = NULL;
283 #else
284 for (i=0;i<text->numlines;i++) {
285 g_free(text_get_line(text, i));
288 g_free(text->line);
289 text->line = NULL;
291 g_free(text->strlen);
292 text->strlen = NULL;
294 g_free(text->row_width);
295 text->row_width = NULL;
296 #endif
299 static void
300 set_string(Text *text, const char *string)
302 int numlines, i;
303 const char *s,*s2;
305 s = string;
307 numlines = 1;
308 if (s != NULL)
309 while ( (s = g_utf8_strchr(s, -1, '\n')) != NULL ) {
310 numlines++;
311 if (*s) {
312 s = g_utf8_next_char(s);
315 text->numlines = numlines;
316 #ifdef USE_TEXTLINE_FOR_LINES
317 text->lines = g_new0(TextLine *, numlines);
318 for (i = 0; i < numlines; i++) {
319 text->lines[i] = text_line_new("", text->font, text->height);
321 text->strlen = g_new(int, numlines);
322 #else
323 text->line = g_new0(char *, numlines);
324 text->strlen = g_new(int, numlines);
325 text->row_width = g_new(real, numlines);
326 #endif
328 s = string;
330 if (string == NULL) {
331 text_set_line_text(text, 0, "");
332 return;
335 for (i = 0; i < numlines; i++) {
336 gchar *string_line;
337 s2 = g_utf8_strchr(s, -1, '\n');
338 if (s2 == NULL) { /* No newline */
339 s2 = s + strlen(s);
341 string_line = g_strndup(s, s2 - s);
342 text_set_line_text(text, i, string_line);
343 g_free(string_line);
344 s = s2;
345 if (*s) {
346 s = g_utf8_next_char(s);
350 if (text->cursor_row >= text->numlines) {
351 text->cursor_row = text->numlines - 1;
354 if (text->cursor_pos > text_get_line_strlen(text, text->cursor_row)) {
355 text->cursor_pos = text_get_line_strlen(text, text->cursor_row);
359 void
360 text_set_string(Text *text, const char *string)
362 #ifdef USE_TEXTLINE_FOR_LINES
363 if (text->lines != NULL)
364 free_string(text);
365 #else
366 if (text->line != NULL)
367 free_string(text);
368 #endif
370 set_string(text, string);
373 Text *
374 new_text(const char *string, DiaFont *font, real height,
375 Point *pos, Color *color, Alignment align)
377 Text *text;
379 text = g_new(Text, 1);
381 text->font = dia_font_ref(font);
382 text->height = height;
384 text->position = *pos;
385 text->color = *color;
386 text->alignment = align;
388 text->cursor_pos = 0;
389 text->cursor_row = 0;
391 text->focus.obj = NULL;
392 text->focus.has_focus = FALSE;
393 text->focus.user_data = (void *)text;
394 text->focus.key_event = text_key_event;
396 set_string(text, string);
398 calc_ascent_descent(text);
400 return text;
403 Text *
404 text_copy(Text *text)
406 Text *copy;
407 int i;
409 copy = g_new(Text, 1);
410 copy->numlines = text->numlines;
411 #ifdef USE_TEXTLINE_FOR_LINES
412 copy->lines = g_new(TextLine *, text->numlines);
413 copy->strlen = g_new(int, text->numlines);
414 #else
415 copy->line = g_malloc(sizeof(char *)*text->numlines);
416 copy->strlen = g_malloc(sizeof(int)*text->numlines);
417 copy->row_width = g_malloc(sizeof(real)*text->numlines);
418 #endif
420 copy->font = dia_font_ref(text->font);
421 copy->height = text->height;
422 copy->position = text->position;
423 copy->color = text->color;
424 copy->alignment = text->alignment;
426 for (i=0;i<text->numlines;i++) {
427 text_set_line_text(copy, i, text_get_line(text, i));
430 copy->cursor_pos = 0;
431 copy->cursor_row = 0;
432 copy->focus.obj = NULL;
433 copy->focus.has_focus = FALSE;
434 copy->focus.user_data = (void *)copy;
435 copy->focus.key_event = text_key_event;
437 copy->ascent = text->ascent;
438 copy->descent = text->descent;
439 copy->max_width = text->max_width;
441 return copy;
444 void
445 text_destroy(Text *text)
447 free_string(text);
448 dia_font_unref(text->font);
449 g_free(text);
452 void
453 text_set_height(Text *text, real height)
455 int i;
456 text->height = height;
457 #ifdef USE_TEXTLINE_FOR_LINES
458 for (i = 0; i < text->numlines; i++) {
459 text_line_set_height(text->lines[i], height);
461 #endif
462 calc_width(text);
463 calc_ascent_descent(text);
466 void
467 text_set_font(Text *text, DiaFont *font)
469 DiaFont *old_font = text->font;
470 int i;
472 text->font = dia_font_ref(font);
473 dia_font_unref(old_font);
475 #ifdef USE_TEXTLINE_FOR_LINES
476 for (i = 0; i < text->numlines; i++) {
477 text_line_set_font(text->lines[i], font);
479 #endif
481 calc_width(text);
482 calc_ascent_descent(text);
485 void
486 text_set_position(Text *text, Point *pos)
488 text->position = *pos;
491 void
492 text_set_color(Text *text, Color *col)
494 text->color = *col;
497 void
498 text_set_alignment(Text *text, Alignment align)
500 text->alignment = align;
503 void
504 text_calc_boundingbox(Text *text, Rectangle *box)
506 calc_width(text);
507 calc_ascent_descent(text);
509 if (box == NULL) return; /* For those who just want the text info
510 updated */
511 box->left = text->position.x;
512 switch (text->alignment) {
513 case ALIGN_LEFT:
514 break;
515 case ALIGN_CENTER:
516 box->left -= text->max_width / 2.0;
517 break;
518 case ALIGN_RIGHT:
519 box->left -= text->max_width;
520 break;
523 box->right = box->left + text->max_width;
525 box->top = text->position.y - text->ascent;
527 box->bottom = box->top + text->height*text->numlines + text->descent;
529 if (text->focus.has_focus) {
530 real height = text->ascent + text->descent;
531 if (text->cursor_pos == 0) {
532 /* Half the cursor width */
533 box->left -= height/(CURSOR_HEIGHT_RATIO*2);
534 } else {
535 /* Half the cursor width. Assume that
536 if it isn't at position zero, it might be
537 at the last position possible. */
538 box->right += height/(CURSOR_HEIGHT_RATIO*2);
541 /* Account for the size of the cursor top and bottom */
542 box->top -= height/(CURSOR_HEIGHT_RATIO*2);
543 box->bottom += height/CURSOR_HEIGHT_RATIO;
549 char *
550 text_get_string_copy(Text *text)
552 int num,i;
553 char *str;
555 num = 0;
556 for (i=0;i<text->numlines;i++) {
557 num += strlen(text_get_line(text, i))+1;
560 str = g_malloc(num);
562 *str = 0;
564 for (i=0;i<text->numlines;i++) {
565 strcat(str, text_get_line(text, i));
566 if (i != (text->numlines-1)) {
567 strcat(str, "\n");
571 return str;
574 real
575 text_distance_from(Text *text, Point *point)
577 real dx, dy;
578 real topy, bottomy;
579 real left, right;
580 int line;
582 topy = text->position.y - text->ascent;
583 bottomy = topy + text->height*text->numlines;
584 if (point->y <= topy) {
585 dy = topy - point->y;
586 line = 0;
587 } else if (point->y >= bottomy) {
588 dy = point->y - bottomy;
589 line = text->numlines - 1;
590 } else {
591 dy = 0.0;
592 line = (int) floor( (point->y - topy) / text->height );
595 left = text->position.x;
596 switch (text->alignment) {
597 case ALIGN_LEFT:
598 break;
599 case ALIGN_CENTER:
600 left -= text_get_line_width(text, line) / 2.0;
601 break;
602 case ALIGN_RIGHT:
603 left -= text_get_line_width(text, line);
604 break;
606 right = left + text_get_line_width(text, line);
608 if (point->x <= left) {
609 dx = left - point->x;
610 } else if (point->x >= right) {
611 dx = point->x - right;
612 } else {
613 dx = 0.0;
616 return dx + dy;
619 void
620 text_draw(Text *text, DiaRenderer *renderer)
622 DIA_RENDERER_GET_CLASS(renderer)->draw_text(renderer, text);
624 if ((renderer->is_interactive) && (text->focus.has_focus)) {
625 real curs_x, curs_y;
626 real str_width_first;
627 real str_width_whole;
628 Point p1, p2;
629 real height = text->ascent+text->descent;
630 curs_y = text->position.y - text->ascent + text->cursor_row*text->height;
632 DIA_RENDERER_GET_CLASS(renderer)->set_font(renderer, text->font, text->height);
634 str_width_first =
635 DIA_RENDERER_GET_CLASS(renderer)->get_text_width(renderer,
636 text_get_line(text, text->cursor_row),
637 text->cursor_pos);
638 str_width_whole =
639 DIA_RENDERER_GET_CLASS(renderer)->get_text_width(renderer,
640 text_get_line(text, text->cursor_row),
641 text_get_line_strlen(text, text->cursor_row));
642 curs_x = text->position.x + str_width_first;
644 switch (text->alignment) {
645 case ALIGN_LEFT:
646 break;
647 case ALIGN_CENTER:
648 curs_x -= str_width_whole / 2.0;
649 break;
650 case ALIGN_RIGHT:
651 curs_x -= str_width_whole;
652 break;
655 p1.x = curs_x;
656 p1.y = curs_y;
657 p2.x = curs_x;
658 p2.y = curs_y + height;
660 DIA_RENDERER_GET_CLASS(renderer)->set_linestyle(renderer, LINESTYLE_SOLID);
661 DIA_RENDERER_GET_CLASS(renderer)->set_linewidth(renderer, height/CURSOR_HEIGHT_RATIO);
662 DIA_RENDERER_GET_CLASS(renderer)->draw_line(renderer, &p1, &p2, &color_black);
666 void
667 text_grab_focus(Text *text, DiaObject *object)
669 text->focus.obj = object;
670 request_focus(&text->focus);
673 void
674 text_set_cursor_at_end( Text* text )
676 text->cursor_row = text->numlines - 1 ;
677 text->cursor_pos = text_get_line_strlen(text, text->cursor_row) ;
680 /* The renderer is only used to determine where the click is, so is not
681 * required when no point is given. */
682 void
683 text_set_cursor(Text *text, Point *clicked_point,
684 DiaRenderer *renderer)
686 real str_width_whole;
687 real str_width_first;
688 real top;
689 real start_x;
690 int row;
691 int i;
693 if (clicked_point != NULL) {
694 top = text->position.y - text->ascent;
696 row = (int)floor((clicked_point->y - top) / text->height);
698 if (row < 0)
699 row = 0;
701 if (row >= text->numlines)
702 row = text->numlines - 1;
704 text->cursor_row = row;
705 text->cursor_pos = 0;
707 if (!renderer->is_interactive) {
708 message_error("Internal error: Select gives non interactive renderer!\n"
709 "val: %d\n", renderer->is_interactive);
710 return;
714 DIA_RENDERER_GET_CLASS(renderer)->set_font(renderer, text->font, text->height);
715 str_width_whole =
716 DIA_RENDERER_GET_CLASS(renderer)->get_text_width(renderer,
717 text_get_line(text, row),
718 text_get_line_strlen(text, row));
719 start_x = text->position.x;
720 switch (text->alignment) {
721 case ALIGN_LEFT:
722 break;
723 case ALIGN_CENTER:
724 start_x -= str_width_whole / 2.0;
725 break;
726 case ALIGN_RIGHT:
727 start_x -= str_width_whole;
728 break;
731 /* Do an ugly linear search for the cursor index:
732 TODO: Change to binary search */
734 for (i=0;i<=text_get_line_strlen(text, row);i++) {
735 str_width_first =
736 DIA_RENDERER_GET_CLASS(renderer)->get_text_width(renderer, text_get_line(text, row), i);
737 if (clicked_point->x - start_x >= str_width_first) {
738 text->cursor_pos = i;
739 } else {
740 return;
743 text->cursor_pos = text_get_line_strlen(text, row);
744 } else {
745 /* No clicked point, leave cursor where it is */
749 static void
750 text_join_lines(Text *text, int first_line)
752 gchar *combined_line;
753 int len1;
755 len1 = text_get_line_strlen(text, first_line);
757 combined_line = g_strconcat(text_get_line(text, first_line),
758 text_get_line(text, first_line + 1), NULL);
759 text_delete_line(text, first_line);
760 text_set_line_text(text, first_line, combined_line);
761 g_free(combined_line);
763 text->max_width = MAX(text->max_width, text_get_line_width(text, first_line));
765 text->cursor_row = first_line;
766 text->cursor_pos = len1;
769 static void
770 text_delete_forward(Text *text)
772 int row;
773 int i;
774 real width;
775 gchar *line;
776 gchar *utf8_before, *utf8_after;
777 gchar *str1, *str;
779 row = text->cursor_row;
781 if (text->cursor_pos >= text_get_line_strlen(text, row)) {
782 if (row + 1 < text->numlines)
783 text_join_lines(text, row);
784 return;
787 line = text_get_line(text, row);
788 utf8_before = g_utf8_offset_to_pointer(line, (glong)(text->cursor_pos));
789 utf8_after = g_utf8_offset_to_pointer(utf8_before, 1);
790 str1 = g_strndup(line, utf8_before - line);
791 str = g_strconcat(str1, utf8_after, NULL);
792 text_set_line_text(text, row, str);
793 g_free(str1);
794 g_free(str);
796 if (text->cursor_pos > text_get_line_strlen(text, text->cursor_row))
797 text->cursor_pos = text_get_line_strlen(text, text->cursor_row);
799 width = 0.0;
800 for (i = 0; i < text->numlines; i++) {
801 width = MAX(width, text_get_line_width(text, i));
803 text->max_width = width;
806 static void
807 text_delete_backward(Text *text)
809 int row;
810 int i;
811 real width;
812 gchar *line;
813 gchar *utf8_before, *utf8_after;
814 gchar *str1, *str;
816 row = text->cursor_row;
818 if (text->cursor_pos <= 0) {
819 if (row > 0)
820 text_join_lines(text, row-1);
821 return;
824 line = text_get_line(text, row);
825 utf8_before = g_utf8_offset_to_pointer(line, (glong)(text->cursor_pos - 1));
826 utf8_after = g_utf8_offset_to_pointer(utf8_before, 1);
827 str1 = g_strndup(line, utf8_before - line);
828 str = g_strconcat(str1, utf8_after, NULL);
829 text_set_line_text(text, row, str);
830 g_free(str);
831 g_free(str1);
833 text->cursor_pos --;
834 if (text->cursor_pos > text_get_line_strlen(text, text->cursor_row))
835 text->cursor_pos = text_get_line_strlen(text, text->cursor_row);
837 width = 0.0;
838 for (i = 0; i < text->numlines; i++) {
839 width = MAX(width, text_get_line_width(text, i));
841 text->max_width = width;
844 static void
845 text_split_line(Text *text)
847 int i;
848 char *line;
849 real width;
850 gchar *utf8_before;
851 gchar *str1, *str2;
853 /* Split the lines at cursor_pos */
854 line = text_get_line(text, text->cursor_row);
855 text_insert_line(text, text->cursor_row);
856 utf8_before = g_utf8_offset_to_pointer(line, (glong)(text->cursor_pos));
857 str1 = g_strndup(line, utf8_before - line);
858 str2 = g_strdup(utf8_before); /* Must copy before dealloc */
859 text_set_line_text(text, text->cursor_row, str1);
860 text_set_line_text(text, text->cursor_row + 1, str2);
861 g_free(str2);
862 g_free(str1);
864 text->cursor_row ++;
865 text->cursor_pos = 0;
867 width = 0.0;
868 for (i=0;i<text->numlines;i++) {
869 width = MAX(width, text_get_line_width(text, i));
871 text->max_width = width;
874 static void
875 text_insert_char(Text *text, gunichar c)
877 gchar ch[7];
878 int unilen;
879 int row;
880 gchar *line, *str;
881 gchar *utf8_before;
882 gchar *str1;
884 /* Make a string of the the char */
885 unilen = g_unichar_to_utf8 (c, ch);
886 ch[unilen] = 0;
888 row = text->cursor_row;
890 /* Copy the before and after parts with the new char in between */
891 line = text_get_line(text, row);
892 utf8_before = g_utf8_offset_to_pointer(line, (glong)(text->cursor_pos));
893 str1 = g_strndup(line, utf8_before - line);
894 str = g_strconcat(str1, ch, utf8_before, NULL);
895 text_set_line_text(text, row, str);
896 g_free(str);
897 g_free(str1);
899 text->cursor_pos++;
900 text->max_width = MAX(text->max_width, text_get_line_width(text, row));
903 static int
904 text_key_event(Focus *focus, guint keyval, const gchar *str, int strlen,
905 ObjectChange **change)
907 Text *text;
908 int return_val = FALSE;
909 int row, i;
910 const char *utf;
911 gunichar c;
913 *change = NULL;
915 text = (Text *)focus->user_data;
917 switch(keyval) {
918 case GDK_Up:
919 text->cursor_row--;
920 if (text->cursor_row<0)
921 text->cursor_row = 0;
923 if (text->cursor_pos > text_get_line_strlen(text, text->cursor_row))
924 text->cursor_pos = text_get_line_strlen(text, text->cursor_row);
926 break;
927 case GDK_Down:
928 text->cursor_row++;
929 if (text->cursor_row >= text->numlines)
930 text->cursor_row = text->numlines - 1;
932 if (text->cursor_pos > text_get_line_strlen(text, text->cursor_row))
933 text->cursor_pos = text_get_line_strlen(text, text->cursor_row);
935 break;
936 case GDK_Left:
937 text->cursor_pos--;
938 if (text->cursor_pos<0)
939 text->cursor_pos = 0;
940 break;
941 case GDK_Right:
942 text->cursor_pos++;
943 if (text->cursor_pos > text_get_line_strlen(text, text->cursor_row))
944 text->cursor_pos = text_get_line_strlen(text, text->cursor_row);
945 break;
946 case GDK_Home:
947 text->cursor_pos = 0;
948 break;
949 case GDK_End:
950 text->cursor_pos = text_get_line_strlen(text, text->cursor_row);
951 break;
952 case GDK_Delete:
953 return_val = TRUE;
954 row = text->cursor_row;
955 if (text->cursor_pos >= text_get_line_strlen(text, row)) {
956 if (row+1 < text->numlines) {
957 *change = text_create_change(text, TYPE_JOIN_ROW, 'Q',
958 text->cursor_pos, row);
959 } else {
960 return_val = FALSE;
961 break;
963 } else {
964 utf = text_get_line(text, row);
965 for (i = 0; i < text->cursor_pos; i++)
966 utf = g_utf8_next_char (utf);
967 c = g_utf8_get_char (utf);
968 *change = text_create_change (text, TYPE_DELETE_FORWARD, c,
969 text->cursor_pos, text->cursor_row);
971 text_delete_forward(text);
972 break;
973 case GDK_BackSpace:
974 return_val = TRUE;
975 row = text->cursor_row;
976 if (text->cursor_pos <= 0) {
977 if (row > 0) {
978 *change = text_create_change(text, TYPE_JOIN_ROW, 'Q',
979 text_get_line_strlen(text, row-1), row-1);
980 } else {
981 return_val = FALSE;
982 break;
984 } else {
985 utf = text_get_line(text, row);
986 for (i = 0; i < (text->cursor_pos - 1); i++)
987 utf = g_utf8_next_char (utf);
988 c = g_utf8_get_char (utf);
989 *change = text_create_change (text, TYPE_DELETE_BACKWARD, c,
990 text->cursor_pos - 1,
991 text->cursor_row);
993 text_delete_backward(text);
994 break;
995 case GDK_Return:
996 return_val = TRUE;
997 *change = text_create_change(text, TYPE_SPLIT_ROW, 'Q',
998 text->cursor_pos, text->cursor_row);
999 text_split_line(text);
1000 break;
1001 default:
1002 if (str || (strlen>0)) {
1003 return_val = TRUE;
1004 utf = str;
1005 for (utf = str; utf && *utf && strlen > 0 ;
1006 utf = g_utf8_next_char (utf), strlen--) {
1007 c = g_utf8_get_char (utf);
1009 *change = text_create_change (text, TYPE_INSERT_CHAR, c,
1010 text->cursor_pos, text->cursor_row);
1011 text_insert_char (text, c);
1014 break;
1017 return return_val;
1020 int text_is_empty(Text *text)
1022 int i;
1023 for (i = 0; i < text->numlines; i++) {
1024 if (text_get_line_strlen(text, i) != 0) {
1025 return FALSE;
1028 return TRUE;
1032 text_delete_all(Text *text, ObjectChange **change)
1034 if (!text_is_empty(text)) {
1035 *change = text_create_change(text, TYPE_DELETE_ALL,
1036 0, text->cursor_pos, text->cursor_row);
1038 text_set_string(text, "");
1039 calc_ascent_descent(text);
1040 return TRUE;
1042 return FALSE;
1045 void
1046 data_add_text(AttributeNode attr, Text *text)
1048 DataNode composite;
1049 char *str;
1051 composite = data_add_composite(attr, "text");
1053 str = text_get_string_copy(text);
1054 data_add_string(composite_add_attribute(composite, "string"),
1055 str);
1056 g_free(str);
1057 data_add_font(composite_add_attribute(composite, "font"),
1058 text->font);
1059 data_add_real(composite_add_attribute(composite, "height"),
1060 text->height);
1061 data_add_point(composite_add_attribute(composite, "pos"),
1062 &text->position);
1063 data_add_color(composite_add_attribute(composite, "color"),
1064 &text->color);
1065 data_add_enum(composite_add_attribute(composite, "alignment"),
1066 text->alignment);
1070 Text *
1071 data_text(AttributeNode text_attr)
1073 char *string = NULL;
1074 DiaFont *font;
1075 real height;
1076 Point pos = {0.0, 0.0};
1077 Color col;
1078 Alignment align;
1079 AttributeNode attr;
1080 DataNode composite_node;
1081 Text *text;
1083 composite_node = attribute_first_data(text_attr);
1085 attr = composite_find_attribute(text_attr, "string");
1086 if (attr != NULL)
1087 string = data_string(attribute_first_data(attr));
1089 height = 1.0;
1090 attr = composite_find_attribute(text_attr, "height");
1091 if (attr != NULL)
1092 height = data_real(attribute_first_data(attr));
1094 attr = composite_find_attribute(text_attr, "font");
1095 if (attr != NULL) {
1096 font = data_font(attribute_first_data(attr));
1097 } else {
1098 font = dia_font_new_from_style(DIA_FONT_SANS,1.0);
1101 attr = composite_find_attribute(text_attr, "pos");
1102 if (attr != NULL)
1103 data_point(attribute_first_data(attr), &pos);
1105 col = color_black;
1106 attr = composite_find_attribute(text_attr, "color");
1107 if (attr != NULL)
1108 data_color(attribute_first_data(attr), &col);
1110 align = ALIGN_LEFT;
1111 attr = composite_find_attribute(text_attr, "alignment");
1112 if (attr != NULL)
1113 align = data_enum(attribute_first_data(attr));
1115 text = new_text(string ? string : "", font, height, &pos, &col, align);
1116 if (font) dia_font_unref(font);
1117 if (string) g_free(string);
1118 return text;
1121 void
1122 text_get_attributes(Text *text, TextAttributes *attr)
1124 DiaFont *old_font;
1125 old_font = attr->font;
1126 attr->font = dia_font_ref(text->font);
1127 if (old_font != NULL) dia_font_unref(old_font);
1128 attr->height = text->height;
1129 attr->position = text->position;
1130 attr->color = text->color;
1131 attr->alignment = text->alignment;
1134 void
1135 text_set_attributes(Text *text, TextAttributes *attr)
1137 if (text->font != attr->font) {
1138 DiaFont *old_font = text->font;
1139 text->font = dia_font_ref(attr->font);
1140 dia_font_unref(old_font);
1142 text->height = attr->height;
1143 text->position = attr->position;
1144 text->color = attr->color;
1145 text->alignment = attr->alignment;
1148 static void
1149 text_change_apply(struct TextObjectChange *change, DiaObject *obj)
1151 Text *text = change->text;
1152 switch (change->type) {
1153 case TYPE_INSERT_CHAR:
1154 text->cursor_pos = change->pos;
1155 text->cursor_row = change->row;
1156 text_insert_char(text, change->ch);
1157 break;
1158 case TYPE_DELETE_BACKWARD:
1159 text->cursor_pos = change->pos+1;
1160 text->cursor_row = change->row;
1161 text_delete_backward(text);
1162 break;
1163 case TYPE_DELETE_FORWARD:
1164 text->cursor_pos = change->pos;
1165 text->cursor_row = change->row;
1166 text_delete_forward(text);
1167 break;
1168 case TYPE_SPLIT_ROW:
1169 text->cursor_pos = change->pos;
1170 text->cursor_row = change->row;
1171 text_split_line(text);
1172 break;
1173 case TYPE_JOIN_ROW:
1174 text_join_lines(text, change->row);
1175 break;
1176 case TYPE_DELETE_ALL:
1177 set_string(text, "");
1178 text->cursor_pos = 0;
1179 text->cursor_row = 0;
1180 break;
1184 static void
1185 text_change_revert(struct TextObjectChange *change, DiaObject *obj)
1187 Text *text = change->text;
1188 switch (change->type) {
1189 case TYPE_INSERT_CHAR:
1190 text->cursor_pos = change->pos;
1191 text->cursor_row = change->row;
1192 text_delete_forward(text);
1193 break;
1194 case TYPE_DELETE_BACKWARD:
1195 text->cursor_pos = change->pos;
1196 text->cursor_row = change->row;
1197 text_insert_char(text, change->ch);
1198 break;
1199 case TYPE_DELETE_FORWARD:
1200 text->cursor_pos = change->pos;
1201 text->cursor_row = change->row;
1202 text_insert_char(text, change->ch);
1203 text->cursor_pos = change->pos;
1204 text->cursor_row = change->row;
1205 break;
1206 case TYPE_SPLIT_ROW:
1207 text_join_lines(text, change->row);
1208 break;
1209 case TYPE_JOIN_ROW:
1210 text->cursor_pos = change->pos;
1211 text->cursor_row = change->row;
1212 text_split_line(text);
1213 break;
1214 case TYPE_DELETE_ALL:
1215 set_string(text, change->str);
1216 text->cursor_pos = change->pos;
1217 text->cursor_row = change->row;
1218 break;
1222 static void
1223 text_change_free(struct TextObjectChange *change) {
1224 g_free(change->str);
1227 static ObjectChange *
1228 text_create_change(Text *text, enum change_type type,
1229 gunichar ch, int pos, int row)
1231 struct TextObjectChange *change;
1233 change = g_new0(struct TextObjectChange, 1);
1235 change->obj_change.apply = (ObjectChangeApplyFunc) text_change_apply;
1236 change->obj_change.revert = (ObjectChangeRevertFunc) text_change_revert;
1237 change->obj_change.free = (ObjectChangeFreeFunc) text_change_free;
1239 change->text = text;
1240 change->type = type;
1241 change->ch = ch;
1242 change->pos = pos;
1243 change->row = row;
1244 if (type == TYPE_DELETE_ALL)
1245 change->str = text_get_string_copy(text);
1246 else
1247 change->str = NULL;
1248 return (ObjectChange *)change;
1251 gboolean
1252 apply_textattr_properties(GPtrArray *props,
1253 Text *text, const gchar *textname,
1254 TextAttributes *attrs)
1256 TextProperty *textprop =
1257 (TextProperty *)find_prop_by_name_and_type(props,textname,PROP_TYPE_TEXT);
1259 if ((!textprop) ||
1260 ((textprop->common.experience & (PXP_LOADED|PXP_SFO))==0 )) {
1261 /* most likely we're called after the dialog box has been applied */
1262 text_set_attributes(text,attrs);
1263 return TRUE;
1265 return FALSE;
1268 gboolean
1269 apply_textstr_properties(GPtrArray *props,
1270 Text *text, const gchar *textname,
1271 const gchar *str)
1273 TextProperty *textprop =
1274 (TextProperty *)find_prop_by_name_and_type(props,textname,PROP_TYPE_TEXT);
1276 if ((!textprop) ||
1277 ((textprop->common.experience & (PXP_LOADED|PXP_SFO))==0 )) {
1278 /* most likely we're called after the dialog box has been applied */
1279 text_set_string(text,str);
1280 return TRUE;
1282 return FALSE;