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