1 /* Dia -- an diagram creation/manipulation program
2  * Copyright (C) 1998 Alexander Larsson
3  *
4  * Chronogram objects support
5  * Copyright (C) 2000, 2001 Cyrille Chepelov
6  *
7  * Ultimately forked from Flowchart toolbox -- objects for drawing flowcharts.
8  * Copyright (C) 1999 James Henstridge.
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28 
29 #include <assert.h>
30 #include <math.h>
31 #include <string.h>
32 #include <glib.h>
33 
34 #include "intl.h"
35 #include "object.h"
36 #include "element.h"
37 #include "connectionpoint.h"
38 #include "diarenderer.h"
39 #include "attributes.h"
40 #include "text.h"
41 #include "widgets.h"
42 #include "message.h"
43 #include "connpoint_line.h"
44 #include "color.h"
45 #include "properties.h"
46 
47 #include "chronogram.h"
48 #include "pixmaps/chronoref.xpm"
49 
50 #define DEFAULT_WIDTH 7.0
51 #define DEFAULT_HEIGHT 5.0
52 
53 typedef struct _Chronoref {
54   Element element;
55 
56   real main_lwidth;
57   real light_lwidth;
58   Color color;
59   real start_time;
60   real end_time;
61   real time_step;
62   real time_lstep;
63 
64   DiaFont *font;
65   real font_size;
66   Color font_color;
67 
68   ConnPointLine *scale; /* not saved ; num_connections derived from
69 			   start_time, end_time, time_step. */
70   real majgrad_height,mingrad_height;
71   real firstmaj,firstmin; /* in time units */
72   real firstmaj_x,firstmin_x,majgrad,mingrad; /* in dia graphic units */
73   char spec[10];
74 } Chronoref;
75 
76 static real chronoref_distance_from(Chronoref *chronoref, Point *point);
77 static void chronoref_select(Chronoref *chronoref, Point *clicked_point,
78 		       DiaRenderer *interactive_renderer);
79 static ObjectChange* chronoref_move_handle(Chronoref *chronoref, Handle *handle,
80 					   Point *to, ConnectionPoint *cp,
81 					   HandleMoveReason reason,
82 			    ModifierKeys modifiers);
83 static ObjectChange* chronoref_move(Chronoref *chronoref, Point *to);
84 static void chronoref_draw(Chronoref *chronoref, DiaRenderer *renderer);
85 static void chronoref_update_data(Chronoref *chronoref);
86 static DiaObject *chronoref_create(Point *startpoint,
87 			  void *user_data,
88 			  Handle **handle1,
89 			  Handle **handle2);
90 static void chronoref_destroy(Chronoref *chronoref);
91 static DiaObject *chronoref_load(ObjectNode obj_node, int version,
92                               const char *filename);
93 static PropDescription *chronoref_describe_props(Chronoref *chronoref);
94 static void chronoref_get_props(Chronoref *chronoref,
95                                  GPtrArray *props);
96 static void chronoref_set_props(Chronoref *chronoref,
97                                  GPtrArray *props);
98 
99 
100 
101 static ObjectTypeOps chronoref_type_ops =
102 {
103   (CreateFunc) chronoref_create,
104   (LoadFunc)   chronoref_load/*using properties*/,
105   (SaveFunc)   object_save_using_properties,
106   (GetDefaultsFunc)   NULL,
107   (ApplyDefaultsFunc) NULL
108 };
109 
110 DiaObjectType chronoref_type =
111 {
112   "chronogram - reference",  /* name */
113   0,                 /* version */
114   (char **) chronoref_xpm, /* pixmap */
115 
116   &chronoref_type_ops      /* ops */
117 };
118 
119 
120 static ObjectOps chronoref_ops = {
121   (DestroyFunc)         chronoref_destroy,
122   (DrawFunc)            chronoref_draw,
123   (DistanceFunc)        chronoref_distance_from,
124   (SelectFunc)          chronoref_select,
125   (CopyFunc)            object_copy_using_properties,
126   (MoveFunc)            chronoref_move,
127   (MoveHandleFunc)      chronoref_move_handle,
128   (GetPropertiesFunc)   object_create_props_dialog,
129   (ApplyPropertiesDialogFunc) object_apply_props_from_dialog,
130   (ObjectMenuFunc)      NULL,
131   (DescribePropsFunc)   chronoref_describe_props,
132   (GetPropsFunc)        chronoref_get_props,
133   (SetPropsFunc)        chronoref_set_props,
134   (TextEditFunc) 0,
135   (ApplyPropertiesListFunc) object_apply_props,
136 };
137 
138 static PropNumData time_range = { -32767.0, 32768.0, 0.1};
139 static PropNumData step_range = { 0.0, 1000.0, 0.1};
140 
141 static PropDescription chronoref_props[] = {
142   ELEMENT_COMMON_PROPERTIES,
143   PROP_MULTICOL_BEGIN("chronoref"),
144 
145   PROP_MULTICOL_COLUMN("time"),
146   PROP_FRAME_BEGIN("time",0,N_("Time data")),
147   { "start_time",PROP_TYPE_REAL,PROP_FLAG_VISIBLE,
148     N_("Start time"),NULL,&time_range},
149   { "end_time",PROP_TYPE_REAL,PROP_FLAG_VISIBLE,
150     N_("End time"),NULL,&time_range},
151   { "time_step",PROP_TYPE_REAL,PROP_FLAG_VISIBLE,
152     N_("Major time step"),NULL,&step_range},
153   { "time_lstep",PROP_TYPE_REAL,PROP_FLAG_VISIBLE,
154     N_("Minor time step"),NULL,&step_range},
155   PROP_FRAME_END("time",0),
156 
157   PROP_MULTICOL_COLUMN("aspect"),
158   PROP_FRAME_BEGIN("aspect",0,N_("Aspect")),
159   { "color", PROP_TYPE_COLOUR, PROP_FLAG_VISIBLE,
160     N_("Line color"),NULL},
161   { "main_lwidth", PROP_TYPE_LENGTH, PROP_FLAG_VISIBLE,
162     N_("Line width"),NULL, &prop_std_line_width_data},
163   { "light_lwidth", PROP_TYPE_LENGTH, PROP_FLAG_VISIBLE,
164     N_("Minor step line width"),NULL, &prop_std_line_width_data},
165 
166   { "font", PROP_TYPE_FONT, PROP_FLAG_VISIBLE, N_("Font"), NULL, NULL },
167   { "font_size", PROP_TYPE_FONTSIZE, PROP_FLAG_VISIBLE,
168     N_("Font size"), NULL, &prop_std_text_height_data },
169   { "font_color", PROP_TYPE_COLOUR, PROP_FLAG_VISIBLE,
170     N_("Text color"), NULL, NULL },
171   PROP_FRAME_END("aspect",0),
172 
173   PROP_MULTICOL_END("chronoref"),
174   {NULL}
175 };
176 
177 static PropDescription *
chronoref_describe_props(Chronoref * chronoref)178 chronoref_describe_props(Chronoref *chronoref)
179 {
180   if (chronoref_props[0].quark == 0) {
181     prop_desc_list_calculate_quarks(chronoref_props);
182   }
183   return chronoref_props;
184 }
185 
186 static PropOffset chronoref_offsets[] = {
187   ELEMENT_COMMON_PROPERTIES_OFFSETS,
188   PROP_OFFSET_MULTICOL_BEGIN("chronref"),
189 
190   PROP_OFFSET_MULTICOL_COLUMN("time"),
191   PROP_OFFSET_FRAME_BEGIN("time"),
192   { "start_time",PROP_TYPE_REAL, offsetof(Chronoref,start_time)},
193   { "end_time",PROP_TYPE_REAL, offsetof(Chronoref,end_time)},
194   { "time_step",PROP_TYPE_REAL, offsetof(Chronoref,time_step)},
195   { "time_lstep",PROP_TYPE_REAL, offsetof(Chronoref,time_lstep)},
196   PROP_OFFSET_FRAME_END("time"),
197 
198   PROP_OFFSET_MULTICOL_COLUMN("aspect"),
199   PROP_OFFSET_FRAME_BEGIN("aspect"),
200   { "color", PROP_TYPE_COLOUR, offsetof(Chronoref,color)},
201   { "main_lwidth", PROP_TYPE_LENGTH, offsetof(Chronoref,main_lwidth)},
202   { "light_lwidth", PROP_TYPE_LENGTH, offsetof(Chronoref,light_lwidth)},
203   { "font", PROP_TYPE_FONT, offsetof(Chronoref,font)},
204   { "font_size", PROP_TYPE_FONTSIZE, offsetof(Chronoref,font_size)},
205   { "font_color", PROP_TYPE_COLOUR, offsetof(Chronoref,font_color)},
206   PROP_OFFSET_FRAME_END("aspect"),
207 
208   PROP_OFFSET_MULTICOL_END("chronref"),
209   {NULL}
210 };
211 
212 static void
chronoref_get_props(Chronoref * chronoref,GPtrArray * props)213 chronoref_get_props(Chronoref *chronoref, GPtrArray *props)
214 {
215   object_get_props_from_offsets(&chronoref->element.object,
216                                 chronoref_offsets,props);
217 }
218 
219 static void
chronoref_set_props(Chronoref * chronoref,GPtrArray * props)220 chronoref_set_props(Chronoref *chronoref, GPtrArray *props)
221 {
222   object_set_props_from_offsets(&chronoref->element.object,
223                                 chronoref_offsets,props);
224   chronoref_update_data(chronoref);
225 }
226 
227 static real
chronoref_distance_from(Chronoref * chronoref,Point * point)228 chronoref_distance_from(Chronoref *chronoref, Point *point)
229 {
230   DiaObject *obj = &chronoref->element.object;
231   return distance_rectangle_point(&obj->bounding_box, point);
232 }
233 
234 static void
chronoref_select(Chronoref * chronoref,Point * clicked_point,DiaRenderer * interactive_renderer)235 chronoref_select(Chronoref *chronoref, Point *clicked_point,
236 	   DiaRenderer *interactive_renderer)
237 {
238   element_update_handles(&chronoref->element);
239 }
240 
241 static ObjectChange*
chronoref_move_handle(Chronoref * chronoref,Handle * handle,Point * to,ConnectionPoint * cp,HandleMoveReason reason,ModifierKeys modifiers)242 chronoref_move_handle(Chronoref *chronoref, Handle *handle,
243 		      Point *to, ConnectionPoint *cp,
244 		      HandleMoveReason reason, ModifierKeys modifiers)
245 {
246   g_assert(chronoref!=NULL);
247   g_assert(handle!=NULL);
248   g_assert(to!=NULL);
249 
250   element_move_handle(&chronoref->element, handle->id, to, cp,
251 		      reason, modifiers);
252   chronoref_update_data(chronoref);
253 
254   return NULL;
255 }
256 
257 static ObjectChange*
chronoref_move(Chronoref * chronoref,Point * to)258 chronoref_move(Chronoref *chronoref, Point *to)
259 {
260   chronoref->element.corner = *to;
261   chronoref_update_data(chronoref);
262 
263   return NULL;
264 }
265 
266 static void
chronoref_draw(Chronoref * chronoref,DiaRenderer * renderer)267 chronoref_draw(Chronoref *chronoref, DiaRenderer *renderer)
268 {
269   DiaRendererClass *renderer_ops = DIA_RENDERER_GET_CLASS (renderer);
270   Element *elem;
271   Point lr_corner;
272   real t;
273   Point p1,p2,p3;
274 
275   assert(renderer != NULL);
276 
277   elem = &chronoref->element;
278 
279   renderer_ops->set_linestyle(renderer, LINESTYLE_SOLID);
280   renderer_ops->set_linejoin(renderer, LINEJOIN_MITER);
281 
282 
283   lr_corner.x = elem->corner.x + elem->width;
284   lr_corner.y = elem->corner.y + elem->height;
285 
286   p1.y = p2.y = elem->corner.y;
287 
288   renderer_ops->set_font(renderer, chronoref->font, chronoref->font_size);
289   p3.y = p2.y + chronoref->majgrad_height +
290     dia_font_ascent("1",chronoref->font, chronoref->font_size);
291 
292   renderer_ops->set_linewidth(renderer, chronoref->light_lwidth);
293   if (chronoref->time_lstep > 0.0) {
294     p2.y = p1.y + chronoref->mingrad_height;
295     for (t = chronoref->firstmaj, p1.x = chronoref->firstmin_x;
296 	 p1.x <= lr_corner.x;
297 	 t += chronoref->time_lstep, p1.x += chronoref->mingrad) {
298       p2.x = p1.x;
299 
300       renderer_ops->draw_line(renderer,&p1,&p2,&chronoref->color);
301     }
302   }
303 
304   renderer_ops->set_linewidth(renderer, chronoref->main_lwidth);
305   if (chronoref->time_step > 0.0) {
306     p2.y = p1.y + chronoref->majgrad_height;
307 
308     for (t = chronoref->firstmaj, p1.x = chronoref->firstmaj_x;
309 	 p1.x <= lr_corner.x;
310 	 t += chronoref->time_step, p1.x += chronoref->majgrad) {
311       char time[10];
312       p3.x = p2.x = p1.x;
313 
314       renderer_ops->draw_line(renderer,&p1,&p2,&chronoref->color);
315       g_snprintf(time,sizeof(time),chronoref->spec,t);
316       renderer_ops->draw_string(renderer,time,&p3,ALIGN_CENTER,
317 				 &chronoref->font_color);
318     }
319   }
320   p1.x = elem->corner.x;
321   p2.x = lr_corner.x;
322   p1.y = p2.y = elem->corner.y;
323 
324   renderer_ops->draw_line(renderer,&p1,&p2,&chronoref->color);
325 }
326 
327 static void
chronoref_update_data(Chronoref * chronoref)328 chronoref_update_data(Chronoref *chronoref)
329 {
330   Element *elem = &chronoref->element;
331   DiaObject *obj = &elem->object;
332   real time_span,t;
333   Point p1,p2;
334   Point ur_corner;
335   int shouldbe,i;
336   real labelwidth;
337   char biglabel[10];
338   ElementBBExtras *extra = &elem->extra_spacing;
339 
340   chronoref->majgrad_height = elem->height;
341   chronoref->mingrad_height = elem->height / 3.0;
342 
343   /* build i = -log_{10}(time_step), then make a %.if format out of it. */
344   t = 1;
345   i = 0;
346 
347   while (t > chronoref->time_step) {
348     t /= 10;
349     i++;
350   }
351   g_snprintf(chronoref->spec,sizeof(chronoref->spec),"%%.%df",i);
352   g_snprintf(biglabel,sizeof(biglabel),chronoref->spec,
353 	   MIN(-ABS(chronoref->start_time),-ABS(chronoref->end_time)));
354 
355   labelwidth = dia_font_string_width(biglabel,chronoref->font,
356                                      chronoref->font_size);
357 
358   /* Now, update the drawing helper counters */
359   time_span = chronoref->end_time - chronoref->start_time;
360   if (time_span == 0) {
361     chronoref->end_time = chronoref->start_time + .1;
362     time_span = .1;
363   } else if (time_span < 0) {
364     chronoref->start_time = chronoref->end_time;
365     time_span = -time_span;
366     chronoref->end_time = chronoref->start_time + time_span;
367   }
368 
369   chronoref->firstmaj = chronoref->time_step *
370     ceil(chronoref->start_time / chronoref->time_step);
371   if (chronoref->firstmaj < chronoref->start_time)
372     chronoref->firstmaj += chronoref->time_step;
373   chronoref->firstmin = chronoref->time_lstep *
374     ceil(chronoref->start_time / chronoref->time_lstep);
375   if (chronoref->firstmin < chronoref->start_time)
376     chronoref->firstmin += chronoref->time_lstep;
377 
378   chronoref->firstmaj_x = elem->corner.x +
379     elem->width*((chronoref->firstmaj-chronoref->start_time)/time_span);
380   chronoref->firstmin_x = elem->corner.x +
381     elem->width*((chronoref->firstmin-chronoref->start_time)/time_span);
382   chronoref->majgrad = (chronoref->time_step * elem->width) / time_span;
383   chronoref->mingrad = (chronoref->time_lstep * elem->width) / time_span;
384 
385   extra->border_trans = chronoref->main_lwidth/2;
386   element_update_boundingbox(elem);
387 
388   /* fix boundingbox for special extras: */
389   obj->bounding_box.left -= (chronoref->font_size + labelwidth)/2;
390   obj->bounding_box.bottom += chronoref->font_size;
391   obj->bounding_box.right += (chronoref->font_size + labelwidth)/2;
392 
393   obj->position = elem->corner;
394 
395   element_update_handles(elem);
396 
397   /* Update connections: */
398   ur_corner.x = elem->corner.x + elem->width;
399   ur_corner.y = elem->corner.y;
400 
401   shouldbe = (int)(ceil((chronoref->end_time-chronoref->firstmin)/
402 			   chronoref->time_lstep));
403   if (shouldbe == 0) shouldbe++;
404   if (shouldbe < 0) shouldbe = 0;
405   shouldbe++; /* off by one.. */
406 
407   connpointline_adjust_count(chronoref->scale,shouldbe,&ur_corner);
408   connpointline_update(chronoref->scale);
409 
410   point_copy(&p1,&elem->corner); point_copy(&p2,&ur_corner);
411   p1.x -= chronoref->mingrad;
412   p2.x += chronoref->mingrad;
413   connpointline_putonaline(chronoref->scale,&p1,&p2);
414 }
415 
416 static DiaObject *
chronoref_create(Point * startpoint,void * user_data,Handle ** handle1,Handle ** handle2)417 chronoref_create(Point *startpoint,
418 	   void *user_data,
419 	   Handle **handle1,
420 	   Handle **handle2)
421 {
422   Chronoref *chronoref;
423   Element *elem;
424   DiaObject *obj;
425 
426   chronoref = g_new0(Chronoref,1);
427   elem = &(chronoref->element);
428 
429   obj =  &(chronoref->element.object);
430   obj->type = &chronoref_type;
431   obj->ops = &chronoref_ops;
432 
433   chronoref->scale = connpointline_create(obj,0);
434 
435   elem->corner = *startpoint;
436   elem->width = 20.0;
437   elem->height = 1.0;
438 
439   element_init(elem, 8, 0);
440 
441   chronoref->font = dia_font_new_from_style (DIA_FONT_SANS,1.0);
442   chronoref->font_size = 1.0;
443   chronoref->font_color = color_black;
444   chronoref->start_time = 0.0;
445   chronoref->end_time = 20.0;
446   chronoref->time_step = 5.0;
447   chronoref->time_lstep = 1.0;
448   chronoref->color = color_black;
449   chronoref->main_lwidth = .1;
450   chronoref->light_lwidth = .05;
451 
452   chronoref_update_data(chronoref);
453 
454   *handle1 = NULL;
455   *handle2 = obj->handles[7];
456   return &chronoref->element.object;
457 }
458 
459 static void
chronoref_destroy(Chronoref * chronoref)460 chronoref_destroy(Chronoref *chronoref)
461 {
462   dia_font_unref(chronoref->font);
463   connpointline_destroy(chronoref->scale);
464   element_destroy(&chronoref->element);
465 }
466 
467 static DiaObject *
chronoref_load(ObjectNode obj_node,int version,const char * filename)468 chronoref_load(ObjectNode obj_node, int version, const char *filename)
469 {
470   return object_load_using_properties(&chronoref_type,
471                                       obj_node,version,filename);
472 }
473