1 /* Dia -- an diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
3 *
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.
8 *
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.
13 *
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.
17 */
18 #include <config.h>
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <glib.h>
24 #include <math.h>
25
26 #include <gdk/gdkkeysyms.h>
27
28 #include "propinternals.h"
29 #include "text.h"
30 #include "message.h"
31 #include "diarenderer.h"
32 #include "diagramdata.h"
33 #include "objchange.h"
34 #include "textline.h"
35
36 static int text_key_event(Focus *focus, guint keysym,
37 const gchar *str, int strlen,
38 ObjectChange **change);
39
40 enum change_type {
41 TYPE_DELETE_BACKWARD,
42 TYPE_DELETE_FORWARD,
43 TYPE_INSERT_CHAR,
44 TYPE_JOIN_ROW,
45 TYPE_SPLIT_ROW,
46 TYPE_DELETE_ALL
47 };
48
49 struct TextObjectChange {
50 ObjectChange obj_change;
51
52 Text *text;
53 enum change_type type;
54 gunichar ch;
55 int pos;
56 int row;
57 gchar *str;
58 };
59
60 #define CURSOR_HEIGHT_RATIO 20
61
62 /* *** Encapsulation functions for transferring to text_line *** */
63 gchar *
text_get_line(Text * text,int line)64 text_get_line(Text *text, int line)
65 {
66 return text_line_get_string(text->lines[line]);
67 }
68
69 /** Raw sets one line to a given text, not copying, not freeing.
70 */
71 static void
text_set_line(Text * text,int line_no,gchar * line)72 text_set_line(Text *text, int line_no, gchar *line)
73 {
74 text_line_set_string(text->lines[line_no], line);
75 }
76
77 /** Set the text of a line, freeing, copying and mallocing as required.
78 * Updates strlen and row_width entries, but not max_width.
79 */
80 static void
text_set_line_text(Text * text,int line_no,gchar * line)81 text_set_line_text(Text *text, int line_no, gchar *line)
82 {
83 text_set_line(text, line_no, line);
84 }
85
86 /** Delete the line, freeing appropriately and moving stuff up.
87 * This function circumvents the normal free/alloc cycle of
88 * text_set_line_text. */
89 static void
text_delete_line(Text * text,int line_no)90 text_delete_line(Text *text, int line_no)
91 {
92 int i;
93
94 g_free(text->lines[line_no]);
95 for (i = line_no; i < text->numlines - 1; i++) {
96 text->lines[i] = text->lines[i+1];
97 }
98 text->numlines -= 1;
99 text->lines = g_realloc(text->lines, sizeof(TextLine *)*text->numlines);
100 }
101
102 /** Insert a new (empty) line at line_no.
103 * This function circumvents the normal free/alloc cycle of
104 * text_set_line_text. */
105 static void
text_insert_line(Text * text,int line_no)106 text_insert_line(Text *text, int line_no)
107 {
108 int i;
109 text->numlines += 1;
110 text->lines = g_realloc(text->lines, sizeof(char *)*text->numlines);
111
112 for (i = text->numlines - 1; i > line_no; i--) {
113 text->lines[i] = text->lines[i - 1];
114 }
115 text->lines[line_no] = text_line_new("", text->font, text->height);;
116 }
117
118 /** Get the in-diagram width of the given line.
119 * @param text The text object;
120 * @param line_no The index of the line in the text object, starting at 0.
121 * @returns The width in cm of the indicated line.
122 */
123 real
text_get_line_width(Text * text,int line_no)124 text_get_line_width(Text *text, int line_no)
125 {
126 return text_line_get_width(text->lines[line_no]);
127 }
128
129 /** Get the number of characters of the given line.
130 * @param text The text object;
131 * @param line_no The index of the line in the text object, starting at 0.
132 * @returns The number of UTF-8 characters of the indicated line.
133 */
134 int
text_get_line_strlen(Text * text,int line_no)135 text_get_line_strlen(Text *text, int line_no)
136 {
137 return g_utf8_strlen(text_line_get_string(text->lines[line_no]), -1);
138 }
139
140 real
text_get_max_width(Text * text)141 text_get_max_width(Text *text)
142 {
143 return text->max_width;
144 }
145
146 /** Get the *average* ascent of this Text object.
147 * @param a Text object
148 * @returns the average of the ascents of each line (height above baseline)
149 */
150 real
text_get_ascent(Text * text)151 text_get_ascent(Text *text)
152 {
153 return text->ascent;
154 }
155
156 /** Get the *average* descent of this Text object.
157 * @param a Text object
158 * @returns the average of the descents of each line (height below baseline)
159 */
160 real
text_get_descent(Text * text)161 text_get_descent(Text *text)
162 {
163 return text->descent;
164 }
165
166 static ObjectChange *text_create_change(Text *text, enum change_type type,
167 gunichar ch, int pos, int row);
168
169 static void
calc_width(Text * text)170 calc_width(Text *text)
171 {
172 real width;
173 int i;
174
175 width = 0.0;
176 for (i = 0; i < text->numlines; i++) {
177 width = MAX(width, text_get_line_width(text, i));
178 }
179
180 text->max_width = width;
181 }
182
183 static void
calc_ascent_descent(Text * text)184 calc_ascent_descent(Text *text)
185 {
186 real sig_a = 0.0,sig_d = 0.0;
187 guint i;
188
189 for ( i = 0; i < text->numlines; i++) {
190 sig_a += text_line_get_ascent(text->lines[i]);
191 sig_d += text_line_get_descent(text->lines[i]);
192 }
193
194 text->ascent = sig_a / (real)text->numlines;
195 text->descent = sig_d / (real)text->numlines;
196 }
197
198 static void
free_string(Text * text)199 free_string(Text *text)
200 {
201 int i;
202
203 for (i=0;i<text->numlines;i++) {
204 text_line_destroy(text->lines[i]);
205 }
206
207 g_free(text->lines);
208 text->lines = NULL;
209 }
210
211 static void
set_string(Text * text,const char * string)212 set_string(Text *text, const char *string)
213 {
214 int numlines, i;
215 const char *s,*s2;
216
217 s = string;
218
219 numlines = 1;
220 if (s != NULL)
221 while ( (s = g_utf8_strchr(s, -1, '\n')) != NULL ) {
222 numlines++;
223 if (*s) {
224 s = g_utf8_next_char(s);
225 }
226 }
227 text->numlines = numlines;
228 text->lines = g_new0(TextLine *, numlines);
229 for (i = 0; i < numlines; i++) {
230 text->lines[i] = text_line_new("", text->font, text->height);
231 }
232
233 s = string;
234
235 if (string == NULL) {
236 text_set_line_text(text, 0, "");
237 return;
238 }
239
240 for (i = 0; i < numlines; i++) {
241 gchar *string_line;
242 s2 = g_utf8_strchr(s, -1, '\n');
243 if (s2 == NULL) { /* No newline */
244 s2 = s + strlen(s);
245 }
246 string_line = g_strndup(s, s2 - s);
247 text_set_line_text(text, i, string_line);
248 g_free(string_line);
249 s = s2;
250 if (*s) {
251 s = g_utf8_next_char(s);
252 }
253 }
254
255 if (text->cursor_row >= text->numlines) {
256 text->cursor_row = text->numlines - 1;
257 }
258
259 if (text->cursor_pos > text_get_line_strlen(text, text->cursor_row)) {
260 text->cursor_pos = text_get_line_strlen(text, text->cursor_row);
261 }
262 }
263
264 void
text_set_string(Text * text,const char * string)265 text_set_string(Text *text, const char *string)
266 {
267 if (text->lines != NULL)
268 free_string(text);
269
270 set_string(text, string);
271 }
272
273 Text *
new_text(const char * string,DiaFont * font,real height,Point * pos,Color * color,Alignment align)274 new_text(const char *string, DiaFont *font, real height,
275 Point *pos, Color *color, Alignment align)
276 {
277 Text *text;
278
279 text = g_new(Text, 1);
280
281 text->font = dia_font_ref(font);
282 text->height = height;
283
284 text->position = *pos;
285 text->color = *color;
286 text->alignment = align;
287
288 text->cursor_pos = 0;
289 text->cursor_row = 0;
290
291 text->focus.obj = NULL;
292 text->focus.has_focus = FALSE;
293 text->focus.user_data = (void *)text;
294 text->focus.key_event = text_key_event;
295 text->focus.text = text;
296
297 set_string(text, string);
298
299 calc_ascent_descent(text);
300
301 return text;
302 }
303
304 Text *
text_copy(Text * text)305 text_copy(Text *text)
306 {
307 Text *copy;
308 int i;
309
310 copy = g_new(Text, 1);
311 copy->numlines = text->numlines;
312 copy->lines = g_new(TextLine *, text->numlines);
313
314 copy->font = dia_font_copy(text->font);
315 copy->height = text->height;
316 copy->position = text->position;
317 copy->color = text->color;
318 copy->alignment = text->alignment;
319
320 for (i=0;i<text->numlines;i++) {
321 TextLine *text_line = text->lines[i];
322 copy->lines[i] = text_line_new(text_line_get_string(text_line),
323 text_line_get_font(text_line),
324 text_line_get_height(text_line));
325 }
326
327 copy->cursor_pos = 0;
328 copy->cursor_row = 0;
329 copy->focus.obj = NULL;
330 copy->focus.has_focus = FALSE;
331 copy->focus.user_data = (void *)copy;
332 copy->focus.key_event = text_key_event;
333 copy->focus.text = copy;
334
335 copy->ascent = text->ascent;
336 copy->descent = text->descent;
337 copy->max_width = text->max_width;
338
339 return copy;
340 }
341
342 void
text_destroy(Text * text)343 text_destroy(Text *text)
344 {
345 free_string(text);
346 dia_font_unref(text->font);
347 g_free(text);
348 }
349
350 void
text_set_height(Text * text,real height)351 text_set_height(Text *text, real height)
352 {
353 int i;
354 text->height = height;
355 for (i = 0; i < text->numlines; i++) {
356 text_line_set_height(text->lines[i], height);
357 }
358 calc_width(text);
359 calc_ascent_descent(text);
360 }
361
362 void
text_set_font(Text * text,DiaFont * font)363 text_set_font(Text *text, DiaFont *font)
364 {
365 DiaFont *old_font = text->font;
366 int i;
367
368 text->font = dia_font_ref(font);
369 dia_font_unref(old_font);
370
371 for (i = 0; i < text->numlines; i++) {
372 text_line_set_font(text->lines[i], font);
373 }
374
375 calc_width(text);
376 calc_ascent_descent(text);
377 }
378
379 void
text_set_position(Text * text,Point * pos)380 text_set_position(Text *text, Point *pos)
381 {
382 text->position = *pos;
383 }
384
385 void
text_set_color(Text * text,Color * col)386 text_set_color(Text *text, Color *col)
387 {
388 text->color = *col;
389 }
390
391 void
text_set_alignment(Text * text,Alignment align)392 text_set_alignment(Text *text, Alignment align)
393 {
394 text->alignment = align;
395 }
396
397 void
text_calc_boundingbox(Text * text,Rectangle * box)398 text_calc_boundingbox(Text *text, Rectangle *box)
399 {
400 calc_width(text);
401 calc_ascent_descent(text);
402
403 if (box == NULL) return; /* For those who just want the text info
404 updated */
405 box->left = text->position.x;
406 switch (text->alignment) {
407 case ALIGN_LEFT:
408 break;
409 case ALIGN_CENTER:
410 box->left -= text->max_width / 2.0;
411 break;
412 case ALIGN_RIGHT:
413 box->left -= text->max_width;
414 break;
415 }
416
417 box->right = box->left + text->max_width;
418
419 box->top = text->position.y - text->ascent;
420 #if 0
421 box->bottom = box->top + text->height*text->numlines + text->descent;
422 #else
423 /* why should we add one descent? isn't ascent+descent~=height? */
424 box->bottom = box->top + (text->ascent+text->descent+text->height*(text->numlines-1));
425 #endif
426 if (text->focus.has_focus) {
427 real height = text->ascent + text->descent;
428 if (text->cursor_pos == 0) {
429 /* Half the cursor width */
430 box->left -= height/(CURSOR_HEIGHT_RATIO*2);
431 } else {
432 /* Half the cursor width. Assume that
433 if it isn't at position zero, it might be
434 at the last position possible. */
435 box->right += height/(CURSOR_HEIGHT_RATIO*2);
436 }
437
438 /* Account for the size of the cursor top and bottom */
439 box->top -= height/(CURSOR_HEIGHT_RATIO*2);
440 box->bottom += height/CURSOR_HEIGHT_RATIO;
441 }
442 }
443
444 char *
text_get_string_copy(Text * text)445 text_get_string_copy(Text *text)
446 {
447 int num,i;
448 char *str;
449
450 num = 0;
451 for (i=0;i<text->numlines;i++) {
452 /* This is for allocation, so it should not use g_utf8_strlen() */
453 num += strlen(text_get_line(text, i))+1;
454 }
455
456 str = g_malloc(num);
457
458 *str = 0;
459
460 for (i=0;i<text->numlines;i++) {
461 strcat(str, text_get_line(text, i));
462 if (i != (text->numlines-1)) {
463 strcat(str, "\n");
464 }
465 }
466
467 return str;
468 }
469
470 real
text_distance_from(Text * text,Point * point)471 text_distance_from(Text *text, Point *point)
472 {
473 real dx, dy;
474 real topy, bottomy;
475 real left, right;
476 int line;
477
478 topy = text->position.y - text->ascent;
479 bottomy = topy + text->height*text->numlines;
480 if (point->y <= topy) {
481 dy = topy - point->y;
482 line = 0;
483 } else if (point->y >= bottomy) {
484 dy = point->y - bottomy;
485 line = text->numlines - 1;
486 } else {
487 dy = 0.0;
488 line = (int) floor( (point->y - topy) / text->height );
489 }
490
491 left = text->position.x;
492 switch (text->alignment) {
493 case ALIGN_LEFT:
494 break;
495 case ALIGN_CENTER:
496 left -= text_get_line_width(text, line) / 2.0;
497 break;
498 case ALIGN_RIGHT:
499 left -= text_get_line_width(text, line);
500 break;
501 }
502 right = left + text_get_line_width(text, line);
503
504 if (point->x <= left) {
505 dx = left - point->x;
506 } else if (point->x >= right) {
507 dx = point->x - right;
508 } else {
509 dx = 0.0;
510 }
511
512 return dx + dy;
513 }
514
515 void
text_draw(Text * text,DiaRenderer * renderer)516 text_draw(Text *text, DiaRenderer *renderer)
517 {
518 DIA_RENDERER_GET_CLASS(renderer)->draw_text(renderer, text);
519
520 if ((renderer->is_interactive) && (text->focus.has_focus)) {
521 real curs_x, curs_y;
522 real str_width_first;
523 real str_width_whole;
524 Point p1, p2;
525 real height = text->ascent+text->descent;
526 curs_y = text->position.y - text->ascent + text->cursor_row*text->height;
527
528 DIA_RENDERER_GET_CLASS(renderer)->set_font(renderer, text->font, text->height);
529
530 str_width_first =
531 DIA_RENDERER_GET_CLASS(renderer)->get_text_width(renderer,
532 text_get_line(text, text->cursor_row),
533 text->cursor_pos);
534 str_width_whole =
535 DIA_RENDERER_GET_CLASS(renderer)->get_text_width(renderer,
536 text_get_line(text, text->cursor_row),
537 text_get_line_strlen(text, text->cursor_row));
538 curs_x = text->position.x + str_width_first;
539
540 switch (text->alignment) {
541 case ALIGN_LEFT:
542 break;
543 case ALIGN_CENTER:
544 curs_x -= str_width_whole / 2.0;
545 break;
546 case ALIGN_RIGHT:
547 curs_x -= str_width_whole;
548 break;
549 }
550
551 p1.x = curs_x;
552 p1.y = curs_y;
553 p2.x = curs_x;
554 p2.y = curs_y + height;
555
556 DIA_RENDERER_GET_CLASS(renderer)->set_linestyle(renderer, LINESTYLE_SOLID);
557 DIA_RENDERER_GET_CLASS(renderer)->set_linewidth(renderer, height/CURSOR_HEIGHT_RATIO);
558 DIA_RENDERER_GET_CLASS(renderer)->draw_line(renderer, &p1, &p2, &color_black);
559 }
560 }
561
562 void
text_grab_focus(Text * text,DiaObject * object)563 text_grab_focus(Text *text, DiaObject *object)
564 {
565 text->focus.obj = object;
566 request_focus(&text->focus);
567 }
568
569 void
text_set_cursor_at_end(Text * text)570 text_set_cursor_at_end( Text* text )
571 {
572 text->cursor_row = text->numlines - 1 ;
573 text->cursor_pos = text_get_line_strlen(text, text->cursor_row) ;
574 }
575
576 /* The renderer is only used to determine where the click is, so is not
577 * required when no point is given. */
578 void
text_set_cursor(Text * text,Point * clicked_point,DiaRenderer * renderer)579 text_set_cursor(Text *text, Point *clicked_point,
580 DiaRenderer *renderer)
581 {
582 real str_width_whole;
583 real str_width_first;
584 real top;
585 real start_x;
586 int row;
587 int i;
588
589 if (clicked_point != NULL) {
590 top = text->position.y - text->ascent;
591
592 row = (int)floor((clicked_point->y - top) / text->height);
593
594 if (row < 0)
595 row = 0;
596
597 if (row >= text->numlines)
598 row = text->numlines - 1;
599
600 text->cursor_row = row;
601 text->cursor_pos = 0;
602
603 if (!renderer->is_interactive) {
604 message_error("Internal error: Select gives non interactive renderer!\n"
605 "val: %d\n", renderer->is_interactive);
606 return;
607 }
608
609
610 DIA_RENDERER_GET_CLASS(renderer)->set_font(renderer, text->font, text->height);
611 str_width_whole =
612 DIA_RENDERER_GET_CLASS(renderer)->get_text_width(renderer,
613 text_get_line(text, row),
614 text_get_line_strlen(text, row));
615 start_x = text->position.x;
616 switch (text->alignment) {
617 case ALIGN_LEFT:
618 break;
619 case ALIGN_CENTER:
620 start_x -= str_width_whole / 2.0;
621 break;
622 case ALIGN_RIGHT:
623 start_x -= str_width_whole;
624 break;
625 }
626
627 /* Do an ugly linear search for the cursor index:
628 TODO: Change to binary search */
629
630 for (i=0;i<=text_get_line_strlen(text, row);i++) {
631 str_width_first =
632 DIA_RENDERER_GET_CLASS(renderer)->get_text_width(renderer, text_get_line(text, row), i);
633 if (clicked_point->x - start_x >= str_width_first) {
634 text->cursor_pos = i;
635 } else {
636 return;
637 }
638 }
639 text->cursor_pos = text_get_line_strlen(text, row);
640 } else {
641 /* No clicked point, leave cursor where it is */
642 }
643 }
644
645 static void
text_join_lines(Text * text,int first_line)646 text_join_lines(Text *text, int first_line)
647 {
648 gchar *combined_line;
649 int len1;
650
651 len1 = text_get_line_strlen(text, first_line);
652
653 combined_line = g_strconcat(text_get_line(text, first_line),
654 text_get_line(text, first_line + 1), NULL);
655 text_delete_line(text, first_line);
656 text_set_line_text(text, first_line, combined_line);
657 g_free(combined_line);
658
659 text->max_width = MAX(text->max_width, text_get_line_width(text, first_line));
660
661 text->cursor_row = first_line;
662 text->cursor_pos = len1;
663 }
664
665 static void
text_delete_forward(Text * text)666 text_delete_forward(Text *text)
667 {
668 int row;
669 int i;
670 real width;
671 gchar *line;
672 gchar *utf8_before, *utf8_after;
673 gchar *str1, *str;
674
675 row = text->cursor_row;
676
677 if (text->cursor_pos >= text_get_line_strlen(text, row)) {
678 if (row + 1 < text->numlines)
679 text_join_lines(text, row);
680 return;
681 }
682
683 line = text_get_line(text, row);
684 utf8_before = g_utf8_offset_to_pointer(line, (glong)(text->cursor_pos));
685 utf8_after = g_utf8_offset_to_pointer(utf8_before, 1);
686 str1 = g_strndup(line, utf8_before - line);
687 str = g_strconcat(str1, utf8_after, NULL);
688 text_set_line_text(text, row, str);
689 g_free(str1);
690 g_free(str);
691
692 if (text->cursor_pos > text_get_line_strlen(text, text->cursor_row))
693 text->cursor_pos = text_get_line_strlen(text, text->cursor_row);
694
695 width = 0.0;
696 for (i = 0; i < text->numlines; i++) {
697 width = MAX(width, text_get_line_width(text, i));
698 }
699 text->max_width = width;
700 }
701
702 static void
text_delete_backward(Text * text)703 text_delete_backward(Text *text)
704 {
705 int row;
706 int i;
707 real width;
708 gchar *line;
709 gchar *utf8_before, *utf8_after;
710 gchar *str1, *str;
711
712 row = text->cursor_row;
713
714 if (text->cursor_pos <= 0) {
715 if (row > 0)
716 text_join_lines(text, row-1);
717 return;
718 }
719
720 line = text_get_line(text, row);
721 utf8_before = g_utf8_offset_to_pointer(line, (glong)(text->cursor_pos - 1));
722 utf8_after = g_utf8_offset_to_pointer(utf8_before, 1);
723 str1 = g_strndup(line, utf8_before - line);
724 str = g_strconcat(str1, utf8_after, NULL);
725 text_set_line_text(text, row, str);
726 g_free(str);
727 g_free(str1);
728
729 text->cursor_pos --;
730 if (text->cursor_pos > text_get_line_strlen(text, text->cursor_row))
731 text->cursor_pos = text_get_line_strlen(text, text->cursor_row);
732
733 width = 0.0;
734 for (i = 0; i < text->numlines; i++) {
735 width = MAX(width, text_get_line_width(text, i));
736 }
737 text->max_width = width;
738 }
739
740 static void
text_split_line(Text * text)741 text_split_line(Text *text)
742 {
743 int i;
744 char *line;
745 real width;
746 gchar *utf8_before;
747 gchar *str1, *str2;
748
749 /* Split the lines at cursor_pos */
750 line = text_get_line(text, text->cursor_row);
751 text_insert_line(text, text->cursor_row);
752 utf8_before = g_utf8_offset_to_pointer(line, (glong)(text->cursor_pos));
753 str1 = g_strndup(line, utf8_before - line);
754 str2 = g_strdup(utf8_before); /* Must copy before dealloc */
755 text_set_line_text(text, text->cursor_row, str1);
756 text_set_line_text(text, text->cursor_row + 1, str2);
757 g_free(str2);
758 g_free(str1);
759
760 text->cursor_row ++;
761 text->cursor_pos = 0;
762
763 width = 0.0;
764 for (i=0;i<text->numlines;i++) {
765 width = MAX(width, text_get_line_width(text, i));
766 }
767 text->max_width = width;
768 }
769
770 static void
text_insert_char(Text * text,gunichar c)771 text_insert_char(Text *text, gunichar c)
772 {
773 gchar ch[7];
774 int unilen;
775 int row;
776 gchar *line, *str;
777 gchar *utf8_before;
778 gchar *str1;
779
780 /* Make a string of the the char */
781 unilen = g_unichar_to_utf8 (c, ch);
782 ch[unilen] = 0;
783
784 row = text->cursor_row;
785
786 /* Copy the before and after parts with the new char in between */
787 line = text_get_line(text, row);
788 utf8_before = g_utf8_offset_to_pointer(line, (glong)(text->cursor_pos));
789 str1 = g_strndup(line, utf8_before - line);
790 str = g_strconcat(str1, ch, utf8_before, NULL);
791 text_set_line_text(text, row, str);
792 g_free(str);
793 g_free(str1);
794
795 text->cursor_pos++;
796 text->max_width = MAX(text->max_width, text_get_line_width(text, row));
797 }
798
799 gboolean
text_delete_key_handler(Focus * focus,ObjectChange ** change)800 text_delete_key_handler(Focus *focus, ObjectChange ** change)
801 {
802 Text *text;
803 int row, i;
804 const char *utf;
805 gunichar c;
806
807 text = (Text *)focus->user_data;
808 row = text->cursor_row;
809 if (text->cursor_pos >= text_get_line_strlen(text, row)) {
810 if (row+1 < text->numlines) {
811 *change = text_create_change(text, TYPE_JOIN_ROW, 'Q',
812 text->cursor_pos, row);
813 } else {
814 return FALSE;
815 }
816 } else {
817 utf = text_get_line(text, row);
818 for (i = 0; i < text->cursor_pos; i++)
819 utf = g_utf8_next_char (utf);
820 c = g_utf8_get_char (utf);
821 *change = text_create_change (text, TYPE_DELETE_FORWARD, c,
822 text->cursor_pos, text->cursor_row);
823 }
824 text_delete_forward(text);
825 return TRUE;;
826 }
827
828 static int
text_key_event(Focus * focus,guint keyval,const gchar * str,int strlen,ObjectChange ** change)829 text_key_event(Focus *focus, guint keyval, const gchar *str, int strlen,
830 ObjectChange **change)
831 {
832 Text *text;
833 int return_val = FALSE;
834 int row, i;
835 const char *utf;
836 gunichar c;
837
838 *change = NULL;
839
840 text = (Text *)focus->user_data;
841
842 switch(keyval) {
843 case GDK_Up:
844 text->cursor_row--;
845 if (text->cursor_row<0)
846 text->cursor_row = 0;
847
848 if (text->cursor_pos > text_get_line_strlen(text, text->cursor_row))
849 text->cursor_pos = text_get_line_strlen(text, text->cursor_row);
850
851 break;
852 case GDK_Down:
853 text->cursor_row++;
854 if (text->cursor_row >= text->numlines)
855 text->cursor_row = text->numlines - 1;
856
857 if (text->cursor_pos > text_get_line_strlen(text, text->cursor_row))
858 text->cursor_pos = text_get_line_strlen(text, text->cursor_row);
859
860 break;
861 case GDK_Left:
862 text->cursor_pos--;
863 if (text->cursor_pos<0)
864 text->cursor_pos = 0;
865 break;
866 case GDK_Right:
867 text->cursor_pos++;
868 if (text->cursor_pos > text_get_line_strlen(text, text->cursor_row))
869 text->cursor_pos = text_get_line_strlen(text, text->cursor_row);
870 break;
871 case GDK_Home:
872 text->cursor_pos = 0;
873 break;
874 case GDK_End:
875 text->cursor_pos = text_get_line_strlen(text, text->cursor_row);
876 break;
877 case GDK_Delete:
878 return_val = text_delete_key_handler(focus, change);
879 break;
880 case GDK_BackSpace:
881 return_val = TRUE;
882 row = text->cursor_row;
883 if (text->cursor_pos <= 0) {
884 if (row > 0) {
885 *change = text_create_change(text, TYPE_JOIN_ROW, 'Q',
886 text_get_line_strlen(text, row-1), row-1);
887 } else {
888 return_val = FALSE;
889 break;
890 }
891 } else {
892 utf = text_get_line(text, row);
893 for (i = 0; i < (text->cursor_pos - 1); i++)
894 utf = g_utf8_next_char (utf);
895 c = g_utf8_get_char (utf);
896 *change = text_create_change (text, TYPE_DELETE_BACKWARD, c,
897 text->cursor_pos - 1,
898 text->cursor_row);
899 }
900 text_delete_backward(text);
901 break;
902 case GDK_Return:
903 case GDK_KP_Enter:
904 return_val = TRUE;
905 *change = text_create_change(text, TYPE_SPLIT_ROW, 'Q',
906 text->cursor_pos, text->cursor_row);
907 text_split_line(text);
908 break;
909 case GDK_Shift_L:
910 case GDK_Shift_R:
911 case GDK_Control_L:
912 case GDK_Control_R:
913 case GDK_Alt_L:
914 case GDK_Alt_R:
915 case GDK_Meta_L:
916 case GDK_Meta_R:
917 return_val = FALSE; /* no text change for modifiers */
918 break;
919 default:
920 if (str || (strlen>0)) {
921 if (strlen == 1 && *str == '\r')
922 break; /* avoid putting junk into our string */
923 return_val = TRUE;
924 utf = str;
925 for (utf = str; utf && *utf && strlen > 0 ;
926 utf = g_utf8_next_char (utf), strlen--) {
927 c = g_utf8_get_char (utf);
928
929 *change = text_create_change (text, TYPE_INSERT_CHAR, c,
930 text->cursor_pos, text->cursor_row);
931 text_insert_char (text, c);
932 }
933 }
934 break;
935 }
936
937 return return_val;
938 }
939
text_is_empty(Text * text)940 int text_is_empty(Text *text)
941 {
942 int i;
943 for (i = 0; i < text->numlines; i++) {
944 if (text_get_line_strlen(text, i) != 0) {
945 return FALSE;
946 }
947 }
948 return TRUE;
949 }
950
951 int
text_delete_all(Text * text,ObjectChange ** change)952 text_delete_all(Text *text, ObjectChange **change)
953 {
954 if (!text_is_empty(text)) {
955 *change = text_create_change(text, TYPE_DELETE_ALL,
956 0, text->cursor_pos, text->cursor_row);
957
958 text_set_string(text, "");
959 calc_ascent_descent(text);
960 return TRUE;
961 }
962 return FALSE;
963 }
964
965 void
data_add_text(AttributeNode attr,Text * text)966 data_add_text(AttributeNode attr, Text *text)
967 {
968 DataNode composite;
969 char *str;
970
971 composite = data_add_composite(attr, "text");
972
973 str = text_get_string_copy(text);
974 data_add_string(composite_add_attribute(composite, "string"),
975 str);
976 g_free(str);
977 data_add_font(composite_add_attribute(composite, "font"),
978 text->font);
979 data_add_real(composite_add_attribute(composite, "height"),
980 text->height);
981 data_add_point(composite_add_attribute(composite, "pos"),
982 &text->position);
983 data_add_color(composite_add_attribute(composite, "color"),
984 &text->color);
985 data_add_enum(composite_add_attribute(composite, "alignment"),
986 text->alignment);
987 }
988
989
990 Text *
data_text(AttributeNode text_attr)991 data_text(AttributeNode text_attr)
992 {
993 char *string = NULL;
994 DiaFont *font;
995 real height;
996 Point pos = {0.0, 0.0};
997 Color col;
998 Alignment align;
999 AttributeNode attr;
1000 DataNode composite_node;
1001 Text *text;
1002
1003 composite_node = attribute_first_data(text_attr);
1004
1005 attr = composite_find_attribute(text_attr, "string");
1006 if (attr != NULL)
1007 string = data_string(attribute_first_data(attr));
1008
1009 height = 1.0;
1010 attr = composite_find_attribute(text_attr, "height");
1011 if (attr != NULL)
1012 height = data_real(attribute_first_data(attr));
1013
1014 attr = composite_find_attribute(text_attr, "font");
1015 if (attr != NULL) {
1016 font = data_font(attribute_first_data(attr));
1017 } else {
1018 font = dia_font_new_from_style(DIA_FONT_SANS,1.0);
1019 }
1020
1021 attr = composite_find_attribute(text_attr, "pos");
1022 if (attr != NULL)
1023 data_point(attribute_first_data(attr), &pos);
1024
1025 col = color_black;
1026 attr = composite_find_attribute(text_attr, "color");
1027 if (attr != NULL)
1028 data_color(attribute_first_data(attr), &col);
1029
1030 align = ALIGN_LEFT;
1031 attr = composite_find_attribute(text_attr, "alignment");
1032 if (attr != NULL)
1033 align = data_enum(attribute_first_data(attr));
1034
1035 text = new_text(string ? string : "", font, height, &pos, &col, align);
1036 if (font) dia_font_unref(font);
1037 if (string) g_free(string);
1038 return text;
1039 }
1040
1041 void
text_get_attributes(Text * text,TextAttributes * attr)1042 text_get_attributes(Text *text, TextAttributes *attr)
1043 {
1044 DiaFont *old_font;
1045 old_font = attr->font;
1046 attr->font = dia_font_ref(text->font);
1047 if (old_font != NULL) dia_font_unref(old_font);
1048 attr->height = text->height;
1049 attr->position = text->position;
1050 attr->color = text->color;
1051 attr->alignment = text->alignment;
1052 }
1053
1054 void
text_set_attributes(Text * text,TextAttributes * attr)1055 text_set_attributes(Text *text, TextAttributes *attr)
1056 {
1057 if (text->font != attr->font) {
1058 text_set_font(text, attr->font);
1059 }
1060 text_set_height(text, attr->height);
1061 text->position = attr->position;
1062 text->color = attr->color;
1063 text->alignment = attr->alignment;
1064 }
1065
1066 static void
text_change_apply(struct TextObjectChange * change,DiaObject * obj)1067 text_change_apply(struct TextObjectChange *change, DiaObject *obj)
1068 {
1069 Text *text = change->text;
1070 switch (change->type) {
1071 case TYPE_INSERT_CHAR:
1072 text->cursor_pos = change->pos;
1073 text->cursor_row = change->row;
1074 text_insert_char(text, change->ch);
1075 break;
1076 case TYPE_DELETE_BACKWARD:
1077 text->cursor_pos = change->pos+1;
1078 text->cursor_row = change->row;
1079 text_delete_backward(text);
1080 break;
1081 case TYPE_DELETE_FORWARD:
1082 text->cursor_pos = change->pos;
1083 text->cursor_row = change->row;
1084 text_delete_forward(text);
1085 break;
1086 case TYPE_SPLIT_ROW:
1087 text->cursor_pos = change->pos;
1088 text->cursor_row = change->row;
1089 text_split_line(text);
1090 break;
1091 case TYPE_JOIN_ROW:
1092 text_join_lines(text, change->row);
1093 break;
1094 case TYPE_DELETE_ALL:
1095 set_string(text, "");
1096 text->cursor_pos = 0;
1097 text->cursor_row = 0;
1098 break;
1099 }
1100 }
1101
1102 static void
text_change_revert(struct TextObjectChange * change,DiaObject * obj)1103 text_change_revert(struct TextObjectChange *change, DiaObject *obj)
1104 {
1105 Text *text = change->text;
1106 switch (change->type) {
1107 case TYPE_INSERT_CHAR:
1108 text->cursor_pos = change->pos;
1109 text->cursor_row = change->row;
1110 text_delete_forward(text);
1111 break;
1112 case TYPE_DELETE_BACKWARD:
1113 text->cursor_pos = change->pos;
1114 text->cursor_row = change->row;
1115 text_insert_char(text, change->ch);
1116 break;
1117 case TYPE_DELETE_FORWARD:
1118 text->cursor_pos = change->pos;
1119 text->cursor_row = change->row;
1120 text_insert_char(text, change->ch);
1121 text->cursor_pos = change->pos;
1122 text->cursor_row = change->row;
1123 break;
1124 case TYPE_SPLIT_ROW:
1125 text_join_lines(text, change->row);
1126 break;
1127 case TYPE_JOIN_ROW:
1128 text->cursor_pos = change->pos;
1129 text->cursor_row = change->row;
1130 text_split_line(text);
1131 break;
1132 case TYPE_DELETE_ALL:
1133 set_string(text, change->str);
1134 text->cursor_pos = change->pos;
1135 text->cursor_row = change->row;
1136 break;
1137 }
1138 }
1139
1140 static void
text_change_free(struct TextObjectChange * change)1141 text_change_free(struct TextObjectChange *change) {
1142 g_free(change->str);
1143 }
1144
1145 static ObjectChange *
text_create_change(Text * text,enum change_type type,gunichar ch,int pos,int row)1146 text_create_change(Text *text, enum change_type type,
1147 gunichar ch, int pos, int row)
1148 {
1149 struct TextObjectChange *change;
1150
1151 change = g_new0(struct TextObjectChange, 1);
1152
1153 change->obj_change.apply = (ObjectChangeApplyFunc) text_change_apply;
1154 change->obj_change.revert = (ObjectChangeRevertFunc) text_change_revert;
1155 change->obj_change.free = (ObjectChangeFreeFunc) text_change_free;
1156
1157 change->text = text;
1158 change->type = type;
1159 change->ch = ch;
1160 change->pos = pos;
1161 change->row = row;
1162 if (type == TYPE_DELETE_ALL)
1163 change->str = text_get_string_copy(text);
1164 else
1165 change->str = NULL;
1166 return (ObjectChange *)change;
1167 }
1168
1169 gboolean
apply_textattr_properties(GPtrArray * props,Text * text,const gchar * textname,TextAttributes * attrs)1170 apply_textattr_properties(GPtrArray *props,
1171 Text *text, const gchar *textname,
1172 TextAttributes *attrs)
1173 {
1174 TextProperty *textprop =
1175 (TextProperty *)find_prop_by_name_and_type(props,textname,PROP_TYPE_TEXT);
1176
1177 if ((!textprop) ||
1178 ((textprop->common.experience & (PXP_LOADED|PXP_SFO))==0 )) {
1179 /* most likely we're called after the dialog box has been applied */
1180 text_set_attributes(text,attrs);
1181 return TRUE;
1182 }
1183 return FALSE;
1184 }
1185
1186 gboolean
apply_textstr_properties(GPtrArray * props,Text * text,const gchar * textname,const gchar * str)1187 apply_textstr_properties(GPtrArray *props,
1188 Text *text, const gchar *textname,
1189 const gchar *str)
1190 {
1191 TextProperty *textprop =
1192 (TextProperty *)find_prop_by_name_and_type(props,textname,PROP_TYPE_TEXT);
1193
1194 if ((!textprop) ||
1195 ((textprop->common.experience & (PXP_LOADED|PXP_SFO))==0 )) {
1196 /* most likely we're called after the dialog box has been applied */
1197 text_set_string(text,str);
1198 return TRUE;
1199 }
1200 return FALSE;
1201 }
1202
1203