1 /* Dia -- an diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
3 * Association updates Copyright(c) 2004 David Klotzbach
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 /*--------------------------------------------------------------------------**
23 ** In the fall of 2004, I started to use the UML portion of this dia **
24 ** program in earnest. I had been using several commercial programs in my **
25 ** work and wanted something I could use for my "home" projects. Even **
26 ** though Dia was advertised has having complete support for the UML static **
27 ** structures, I found that I was used to having a design tool that was **
28 ** much closer to the language as described by the OMG and the Three **
30 ** Always willing to give back to the culture that has supported me for the **
31 ** last 30+ years, I have endeavored to make the UML portion of Dia more **
32 ** compliant with the standard as is is currently described. **
33 ** My changes do not include any of the enhancements as described in **
36 ** Association change Oct 31, 2004 - Dave Klotzbach **
37 ** dklotzbach@foxvalley.net **
39 ** Now for a description of what I have done here in Association. To begin **
40 ** with, the implementation of Association is darn near complete. However, **
41 ** as described in "The Unified Modeling Language Users Guide", the roles **
42 ** that an association may have do have "visibility" attributes, and these **
43 ** attributes apply independently to each of the roles. **
45 ** What is still missing from this version are the concepts of **
46 ** "Qualification", "Interface Specifier" and "Association Classes" **
47 **--------------------------------------------------------------------------*/
49 /* DO NOT USE THIS OBJECT AS A BASIS FOR A NEW OBJECT. */
62 #include "orth_conn.h"
63 #include "diarenderer.h"
64 #include "attributes.h"
68 #include "properties.h"
70 #include "pixmaps/association.xpm"
72 extern char visible_char
[]; /* The definitions are in UML.c. Used here to avoid getting out of sync */
74 typedef struct _Association Association
;
75 typedef struct _AssociationState AssociationState
;
76 typedef struct _AssociationPropertiesDialog AssociationPropertiesDialog
;
82 } AssociationDirection
;
90 typedef struct _AssociationEnd
{
91 gchar
*role
; /* Can be NULL */
92 gchar
*multiplicity
; /* Can be NULL */
100 UMLVisibility visibility
; /* This value is only relevant if role is not null */
103 AggregateType aggregate
; /* Note: Can only be != NONE on ONE side! */
106 struct _AssociationState
{
107 ObjectState obj_state
;
110 AssociationDirection direction
;
115 UMLVisibility visibility
; /* This value is only relevant if role is not null */
118 AggregateType aggregate
;
123 struct _Association
{
127 Alignment text_align
;
133 AssociationDirection direction
;
135 AssociationEnd end
[2];
137 AssociationPropertiesDialog
* properties_dialog
;
140 struct _AssociationPropertiesDialog
{
145 GtkOptionMenu
*dir_omenu
;
149 GtkEntry
*multiplicity
;
150 GtkMenu
*attr_visible
;
151 GtkOptionMenu
*attr_visible_button
;
152 GtkToggleButton
*draw_arrow
;
153 GtkToggleButton
*aggregate
;
154 GtkToggleButton
*composition
;
158 #define ASSOCIATION_WIDTH 0.1
159 #define ASSOCIATION_TRIANGLESIZE 0.8
160 #define ASSOCIATION_DIAMONDLEN 1.4
161 #define ASSOCIATION_DIAMONDWIDTH 0.7
162 #define ASSOCIATION_FONTHEIGHT 0.8
163 #define ASSOCIATION_END_SPACE 0.2
164 static DiaFont
*assoc_font
= NULL
;
166 static real
association_distance_from(Association
*assoc
, Point
*point
);
167 static void association_select(Association
*assoc
, Point
*clicked_point
,
168 DiaRenderer
*interactive_renderer
);
169 static ObjectChange
* association_move_handle(Association
*assoc
, Handle
*handle
,
170 Point
*to
, ConnectionPoint
*cp
,
171 HandleMoveReason reason
, ModifierKeys modifiers
);
172 static ObjectChange
* association_move(Association
*assoc
, Point
*to
);
173 static void association_draw(Association
*assoc
, DiaRenderer
*renderer
);
174 static DiaObject
*association_create(Point
*startpoint
,
178 static void association_destroy(Association
*assoc
);
179 static DiaObject
*association_copy(Association
*assoc
);
180 static GtkWidget
*association_get_properties(Association
*assoc
, gboolean is_default
);
181 static ObjectChange
*association_apply_properties(Association
*assoc
);
182 static DiaMenu
*association_get_object_menu(Association
*assoc
,
183 Point
*clickedpoint
);
184 static PropDescription
*association_describe_props(Association
*assoc
);
185 static void association_get_props(Association
*assoc
, GPtrArray
*props
);
186 static void association_set_props(Association
*assoc
, GPtrArray
*props
);
188 static AssociationState
*association_get_state(Association
*assoc
);
189 static void association_set_state(Association
*assoc
,
190 AssociationState
*state
);
192 static void association_save(Association
*assoc
, ObjectNode obj_node
,
193 const char *filename
);
194 static DiaObject
*association_load(ObjectNode obj_node
, int version
,
195 const char *filename
);
197 static void association_update_data(Association
*assoc
);
198 static coord
get_aggregate_pos_diff(AssociationEnd
*end
);
200 static ObjectTypeOps association_type_ops
=
202 (CreateFunc
) association_create
,
203 (LoadFunc
) association_load
,
204 (SaveFunc
) association_save
207 DiaObjectType association_type
=
209 "UML - Association", /* name */
210 /* Version 0 had no autorouting and so shouldn't have it set by default. */
212 (char **) association_xpm
, /* pixmap */
214 &association_type_ops
, /* ops */
215 NULL
, /* pixmap_file */
216 0 /* default_user_data */
219 static ObjectOps association_ops
= {
220 (DestroyFunc
) association_destroy
,
221 (DrawFunc
) association_draw
,
222 (DistanceFunc
) association_distance_from
,
223 (SelectFunc
) association_select
,
224 (CopyFunc
) association_copy
,
225 (MoveFunc
) association_move
,
226 (MoveHandleFunc
) association_move_handle
,
227 (GetPropertiesFunc
) association_get_properties
,
228 (ApplyPropertiesFunc
) association_apply_properties
,
229 (ObjectMenuFunc
) association_get_object_menu
,
230 (DescribePropsFunc
) association_describe_props
,
231 (GetPropsFunc
) association_get_props
,
232 (SetPropsFunc
) association_set_props
235 static PropDescription association_props
[] = {
236 ORTHCONN_COMMON_PROPERTIES
,
237 { "name", PROP_TYPE_STRING
, PROP_FLAG_VISIBLE
,
238 N_("Name:"), NULL
, NULL
},
242 static PropDescription
*
243 association_describe_props(Association
*assoc
)
245 if (association_props
[0].quark
== 0) {
246 prop_desc_list_calculate_quarks(association_props
);
248 return association_props
;
251 static PropOffset association_offsets
[] = {
252 ORTHCONN_COMMON_PROPERTIES_OFFSETS
,
253 { "name", PROP_TYPE_STRING
, offsetof(Association
, name
) },
258 association_get_props(Association
*assoc
, GPtrArray
*props
)
260 object_get_props_from_offsets(&assoc
->orth
.object
,
261 association_offsets
,props
);
265 association_set_props(Association
*assoc
, GPtrArray
*props
)
267 object_set_props_from_offsets(&assoc
->orth
.object
,
268 association_offsets
, props
);
269 association_update_data(assoc
);
273 association_distance_from(Association
*assoc
, Point
*point
)
275 OrthConn
*orth
= &assoc
->orth
;
276 return orthconn_distance_from(orth
, point
, ASSOCIATION_WIDTH
);
280 association_select(Association
*assoc
, Point
*clicked_point
,
281 DiaRenderer
*interactive_renderer
)
283 orthconn_update_data(&assoc
->orth
);
287 association_move_handle(Association
*assoc
, Handle
*handle
,
288 Point
*to
, ConnectionPoint
*cp
,
289 HandleMoveReason reason
, ModifierKeys modifiers
)
291 ObjectChange
*change
;
293 assert(handle
!=NULL
);
296 change
= orthconn_move_handle(&assoc
->orth
, handle
, to
, cp
, reason
, modifiers
);
297 association_update_data(assoc
);
303 association_move(Association
*assoc
, Point
*to
)
305 ObjectChange
*change
;
307 change
= orthconn_move(&assoc
->orth
, to
);
308 association_update_data(assoc
);
314 association_draw(Association
*assoc
, DiaRenderer
*renderer
)
316 DiaRendererClass
*renderer_ops
= DIA_RENDERER_GET_CLASS (renderer
);
317 OrthConn
*orth
= &assoc
->orth
;
322 Arrow startarrow
, endarrow
;
324 points
= &orth
->points
[0];
327 renderer_ops
->set_linewidth(renderer
, ASSOCIATION_WIDTH
);
328 renderer_ops
->set_linestyle(renderer
, LINESTYLE_SOLID
);
329 renderer_ops
->set_linejoin(renderer
, LINEJOIN_MITER
);
330 renderer_ops
->set_linecaps(renderer
, LINECAPS_BUTT
);
332 startarrow
.length
= ASSOCIATION_TRIANGLESIZE
;
333 startarrow
.width
= ASSOCIATION_TRIANGLESIZE
;
334 if (assoc
->end
[0].arrow
) {
335 startarrow
.type
= ARROW_LINES
;
336 } else if (assoc
->end
[0].aggregate
!= AGGREGATE_NONE
) {
337 startarrow
.length
= ASSOCIATION_DIAMONDLEN
;
338 startarrow
.width
= ASSOCIATION_TRIANGLESIZE
*0.6;
339 startarrow
.type
= assoc
->end
[0].aggregate
== AGGREGATE_NORMAL
?
340 ARROW_HOLLOW_DIAMOND
: ARROW_FILLED_DIAMOND
;
342 startarrow
.type
= ARROW_NONE
;
344 endarrow
.length
= ASSOCIATION_TRIANGLESIZE
;
345 endarrow
.width
= ASSOCIATION_TRIANGLESIZE
;
346 if (assoc
->end
[1].arrow
) {
347 endarrow
.type
= ARROW_LINES
;
348 } else if (assoc
->end
[1].aggregate
!= AGGREGATE_NONE
) {
349 endarrow
.length
= ASSOCIATION_DIAMONDLEN
;
350 endarrow
.width
= ASSOCIATION_TRIANGLESIZE
*0.6;
351 endarrow
.type
= assoc
->end
[1].aggregate
== AGGREGATE_NORMAL
?
352 ARROW_HOLLOW_DIAMOND
: ARROW_FILLED_DIAMOND
;
354 endarrow
.type
= ARROW_NONE
;
356 renderer_ops
->draw_polyline_with_arrows(renderer
, points
, n
,
359 &startarrow
, &endarrow
);
362 renderer_ops
->set_font(renderer
, assoc_font
, ASSOCIATION_FONTHEIGHT
);
364 if (assoc
->name
!= NULL
) {
365 pos
= assoc
->text_pos
;
366 renderer_ops
->draw_string(renderer
, assoc
->name
,
367 &pos
, assoc
->text_align
,
372 renderer_ops
->set_fillstyle(renderer
, FILLSTYLE_SOLID
);
374 switch (assoc
->direction
) {
378 poly
[0].x
= assoc
->text_pos
.x
+ assoc
->text_width
+ 0.1;
379 if (assoc
->text_align
== ALIGN_CENTER
)
380 poly
[0].x
-= assoc
->text_width
/2.0;
381 poly
[0].y
= assoc
->text_pos
.y
;
382 poly
[1].x
= poly
[0].x
;
383 poly
[1].y
= poly
[0].y
- ASSOCIATION_FONTHEIGHT
*0.5;
384 poly
[2].x
= poly
[0].x
+ ASSOCIATION_FONTHEIGHT
*0.5;
385 poly
[2].y
= poly
[0].y
- ASSOCIATION_FONTHEIGHT
*0.5*0.5;
386 renderer_ops
->fill_polygon(renderer
, poly
, 3, &color_black
);
389 poly
[0].x
= assoc
->text_pos
.x
- 0.2;
390 if (assoc
->text_align
== ALIGN_CENTER
)
391 poly
[0].x
-= assoc
->text_width
/2.0;
392 poly
[0].y
= assoc
->text_pos
.y
;
393 poly
[1].x
= poly
[0].x
;
394 poly
[1].y
= poly
[0].y
- ASSOCIATION_FONTHEIGHT
*0.5;
395 poly
[2].x
= poly
[0].x
- ASSOCIATION_FONTHEIGHT
*0.5;
396 poly
[2].y
= poly
[0].y
- ASSOCIATION_FONTHEIGHT
*0.5*0.5;
397 renderer_ops
->fill_polygon(renderer
, poly
, 3, &color_black
);
403 AssociationEnd
*end
= &assoc
->end
[i
];
406 if (end
->role
!= NULL
) {
407 gchar
*role_name
= g_strdup_printf ("%c%s", visible_char
[(int) end
->visibility
], end
->role
);
408 renderer_ops
->draw_string(renderer
,
414 pos
.y
+= ASSOCIATION_FONTHEIGHT
;
416 if (end
->multiplicity
!= NULL
) {
417 renderer_ops
->draw_string(renderer
, end
->multiplicity
,
418 &pos
, end
->text_align
,
425 association_state_free(ObjectState
*ostate
)
427 AssociationState
*state
= (AssociationState
*)ostate
;
432 g_free(state
->end
[i
].role
);
433 g_free(state
->end
[i
].multiplicity
);
437 static AssociationState
*
438 association_get_state(Association
*assoc
)
443 AssociationState
*state
= g_new0(AssociationState
, 1);
445 state
->obj_state
.free
= association_state_free
;
447 state
->name
= g_strdup(assoc
->name
);
448 state
->direction
= assoc
->direction
;
451 end
= &assoc
->end
[i
];
452 state
->end
[i
].role
= g_strdup(end
->role
);
453 state
->end
[i
].multiplicity
= g_strdup(end
->multiplicity
);
454 state
->end
[i
].arrow
= end
->arrow
;
455 state
->end
[i
].aggregate
= end
->aggregate
;
456 state
->end
[i
].visibility
= end
->visibility
;
463 association_set_state(Association
*assoc
, AssociationState
*state
)
469 assoc
->name
= state
->name
;
470 assoc
->text_width
= 0.0;
472 assoc
->descent
= 0.0;
473 if (assoc
->name
!= NULL
) {
475 dia_font_string_width(assoc
->name
, assoc_font
, ASSOCIATION_FONTHEIGHT
);
477 dia_font_ascent(assoc
->name
, assoc_font
, ASSOCIATION_FONTHEIGHT
);
479 dia_font_descent(assoc
->name
, assoc_font
, ASSOCIATION_FONTHEIGHT
);
482 assoc
->direction
= state
->direction
;
485 end
= &assoc
->end
[i
];
487 g_free(end
->multiplicity
);
488 end
->role
= state
->end
[i
].role
;
489 end
->multiplicity
= state
->end
[i
].multiplicity
;
490 end
->arrow
= state
->end
[i
].arrow
;
491 end
->aggregate
= state
->end
[i
].aggregate
;
492 end
->visibility
= state
->end
[i
].visibility
;
494 end
->text_width
= 0.0;
495 end
->role_ascent
= 0.0;
496 end
->role_descent
= 0.0;
497 end
->multi_ascent
= 0.0;
498 end
->multi_descent
= 0.0;
499 if (end
->role
!= NULL
) {
501 dia_font_string_width(end
->role
, assoc_font
, ASSOCIATION_FONTHEIGHT
);
503 dia_font_ascent(end
->role
, assoc_font
, ASSOCIATION_FONTHEIGHT
);
505 dia_font_ascent(end
->role
, assoc_font
, ASSOCIATION_FONTHEIGHT
);
507 if (end
->multiplicity
!= NULL
) {
508 end
->text_width
= MAX(end
->text_width
,
509 dia_font_string_width(end
->multiplicity
,
511 ASSOCIATION_FONTHEIGHT
) );
512 end
->role_ascent
= dia_font_ascent(end
->multiplicity
,
513 assoc_font
,ASSOCIATION_FONTHEIGHT
);
514 end
->role_descent
= dia_font_descent(end
->multiplicity
,
515 assoc_font
,ASSOCIATION_FONTHEIGHT
);
521 association_update_data(assoc
);
525 association_update_data_end(Association
*assoc
, int endnum
)
527 OrthConn
*orth
= &assoc
->orth
;
528 DiaObject
*obj
= &orth
->object
;
529 Point
*points
= orth
->points
;
533 int n
= orth
->numpoints
- 1, fp
, sp
;
535 /* Find the first and second points depending on which end: */
539 dir
= assoc
->orth
.orientation
[n
-1];
543 dir
= assoc
->orth
.orientation
[0];
546 /* If the points are the same, find a better candidate: */
547 if (points
[fp
].x
== points
[sp
].x
&& points
[fp
].y
== points
[sp
].y
) {
548 sp
+= (endnum
? -1 : 1);
553 if (points
[fp
].y
!= points
[sp
].y
)
559 /* Update the text-points of the ends: */
560 end
= &assoc
->end
[endnum
];
561 end
->text_pos
= points
[fp
];
564 end
->text_pos
.y
-= end
->role_descent
;
565 if (points
[fp
].x
< points
[sp
].x
) {
566 end
->text_align
= ALIGN_LEFT
;
567 end
->text_pos
.x
+= (get_aggregate_pos_diff(end
) + ASSOCIATION_END_SPACE
);
569 end
->text_align
= ALIGN_RIGHT
;
570 end
->text_pos
.x
-= (get_aggregate_pos_diff(end
) + ASSOCIATION_END_SPACE
);
574 if (end
->arrow
|| end
->aggregate
!= AGGREGATE_NONE
)
575 end
->text_pos
.x
+= ASSOCIATION_DIAMONDWIDTH
/ 2;
576 end
->text_pos
.x
+= ASSOCIATION_END_SPACE
;
578 end
->text_pos
.y
+= end
->role_ascent
;
579 if (points
[fp
].y
> points
[sp
].y
) {
581 end
->text_pos
.y
-= ASSOCIATION_FONTHEIGHT
;
582 if (end
->multiplicity
!=NULL
)
583 end
->text_pos
.y
-= ASSOCIATION_FONTHEIGHT
;
586 end
->text_align
= ALIGN_LEFT
;
589 /* Add the text recangle to the bounding box: */
590 rect
.left
= end
->text_pos
.x
591 - (end
->text_align
== ALIGN_LEFT
? 0 : end
->text_width
);
592 rect
.right
= rect
.left
+ end
->text_width
;
593 rect
.top
= end
->text_pos
.y
- end
->role_ascent
;
594 rect
.bottom
= rect
.top
+ 2*ASSOCIATION_FONTHEIGHT
;
596 rectangle_union(&obj
->bounding_box
, &rect
);
600 association_update_data(Association
*assoc
)
602 /* FIXME: The ascent and descent computation logic here is
603 fundamentally slow. */
605 OrthConn
*orth
= &assoc
->orth
;
606 DiaObject
*obj
= &orth
->object
;
607 PolyBBExtras
*extra
= &orth
->extra_spacing
;
613 orthconn_update_data(orth
);
616 extra
->start_long
= (assoc
->end
[0].aggregate
== AGGREGATE_NONE
?
617 ASSOCIATION_WIDTH
/2.0:
618 (ASSOCIATION_WIDTH
+ ASSOCIATION_DIAMONDLEN
)/2.0);
619 extra
->middle_trans
= ASSOCIATION_WIDTH
/2.0;
621 extra
->end_long
= (assoc
->end
[1].aggregate
== AGGREGATE_NONE
?
622 ASSOCIATION_WIDTH
/2.0:
623 (ASSOCIATION_WIDTH
+ ASSOCIATION_DIAMONDLEN
)/2.0);
625 if (assoc
->end
[0].arrow
)
626 extra
->start_trans
= MAX(extra
->start_trans
, ASSOCIATION_TRIANGLESIZE
);
627 if (assoc
->end
[1].arrow
)
628 extra
->end_trans
= MAX(extra
->end_trans
, ASSOCIATION_TRIANGLESIZE
);
630 orthconn_update_boundingbox(orth
);
633 num_segm
= assoc
->orth
.numpoints
- 1;
634 points
= assoc
->orth
.points
;
637 if ((num_segm
% 2) == 0) { /* If no middle segment, use horizontal */
638 if (assoc
->orth
.orientation
[i
]==VERTICAL
)
641 dir
= assoc
->orth
.orientation
[i
];
642 /* also adapt for degenerated segement */
643 if (VERTICAL
== dir
&& points
[i
].y
== points
[i
+1].y
)
645 else if (HORIZONTAL
== dir
&& points
[i
].x
== points
[i
+1].x
)
650 assoc
->text_align
= ALIGN_CENTER
;
651 assoc
->text_pos
.x
= 0.5*(points
[i
].x
+points
[i
+1].x
);
652 assoc
->text_pos
.y
= points
[i
].y
- assoc
->descent
;
655 assoc
->text_align
= ALIGN_LEFT
;
656 assoc
->text_pos
.x
= points
[i
].x
+ 0.1;
657 assoc
->text_pos
.y
= 0.5*(points
[i
].y
+points
[i
+1].y
) - assoc
->descent
;
661 /* Add the text recangle to the bounding box: */
662 rect
.left
= assoc
->text_pos
.x
;
663 if (assoc
->text_align
== ALIGN_CENTER
)
664 rect
.left
-= assoc
->text_width
/2.0;
665 rect
.right
= rect
.left
+ assoc
->text_width
;
666 rect
.top
= assoc
->text_pos
.y
- assoc
->ascent
;
667 rect
.bottom
= rect
.top
+ ASSOCIATION_FONTHEIGHT
;
669 rectangle_union(&obj
->bounding_box
, &rect
);
671 association_update_data_end(assoc
, 0);
672 association_update_data_end(assoc
, 1);
675 static coord
get_aggregate_pos_diff(AssociationEnd
*end
)
679 width
= ASSOCIATION_TRIANGLESIZE
;
681 switch(end
->aggregate
){
682 case AGGREGATE_COMPOSITION
:
683 case AGGREGATE_NORMAL
:
684 if(width
!=0) width
= MAX(ASSOCIATION_TRIANGLESIZE
, ASSOCIATION_DIAMONDLEN
);
685 else width
= ASSOCIATION_DIAMONDLEN
;
693 association_create(Point
*startpoint
,
704 if (assoc_font
== NULL
) {
705 assoc_font
= dia_font_new_from_style(DIA_FONT_MONOSPACE
, ASSOCIATION_FONTHEIGHT
);
708 assoc
= g_malloc0(sizeof(Association
));
712 obj
->type
= &association_type
;
714 obj
->ops
= &association_ops
;
716 orthconn_init(orth
, startpoint
);
719 assoc
->direction
= ASSOC_NODIR
;
721 assoc
->end
[i
].role
= NULL
;
722 assoc
->end
[i
].multiplicity
= NULL
;
723 assoc
->end
[i
].arrow
= FALSE
;
724 assoc
->end
[i
].aggregate
= AGGREGATE_NONE
;
725 assoc
->end
[i
].text_width
= 0.0;
728 assoc
->text_width
= 0.0;
729 assoc
->properties_dialog
= NULL
;
731 user_d
= GPOINTER_TO_INT(user_data
);
737 assoc
->end
[1].aggregate
= AGGREGATE_NORMAL
;
741 association_update_data(assoc
);
743 *handle1
= orth
->handles
[0];
744 *handle2
= orth
->handles
[orth
->numpoints
-2];
746 return &assoc
->orth
.object
;
749 static ObjectChange
*
750 association_add_segment_callback(DiaObject
*obj
, Point
*clicked
, gpointer data
)
752 ObjectChange
*change
;
753 change
= orthconn_add_segment((OrthConn
*)obj
, clicked
);
754 association_update_data((Association
*)obj
);
758 static ObjectChange
*
759 association_delete_segment_callback(DiaObject
*obj
, Point
*clicked
, gpointer data
)
761 ObjectChange
*change
;
762 change
= orthconn_delete_segment((OrthConn
*)obj
, clicked
);
763 association_update_data((Association
*)obj
);
768 static DiaMenuItem object_menu_items
[] = {
769 { N_("Add segment"), association_add_segment_callback
, NULL
, 1 },
770 { N_("Delete segment"), association_delete_segment_callback
, NULL
, 1 },
771 ORTHCONN_COMMON_MENUS
,
774 static DiaMenu object_menu
= {
776 sizeof(object_menu_items
)/sizeof(DiaMenuItem
),
782 association_get_object_menu(Association
*assoc
, Point
*clickedpoint
)
787 /* Set entries sensitive/selected etc here */
788 object_menu_items
[0].active
= orthconn_can_add_segment(orth
, clickedpoint
);
789 object_menu_items
[1].active
= orthconn_can_delete_segment(orth
, clickedpoint
);
790 orthconn_update_object_menu(orth
, clickedpoint
, &object_menu_items
[2]);
795 association_destroy(Association
*assoc
)
799 orthconn_destroy(&assoc
->orth
);
804 g_free(assoc
->end
[i
].role
);
805 g_free(assoc
->end
[i
].multiplicity
);
808 if (assoc
->properties_dialog
!= NULL
) {
809 gtk_widget_destroy(assoc
->properties_dialog
->dialog
);
810 g_free(assoc
->properties_dialog
);
815 association_copy(Association
*assoc
)
817 Association
*newassoc
;
818 OrthConn
*orth
, *neworth
;
824 newassoc
= g_malloc0(sizeof(Association
));
825 neworth
= &newassoc
->orth
;
826 newobj
= &neworth
->object
;
828 orthconn_copy(orth
, neworth
);
830 newassoc
->name
= g_strdup(assoc
->name
);
831 newassoc
->direction
= assoc
->direction
;
833 newassoc
->end
[i
] = assoc
->end
[i
];
834 newassoc
->end
[i
].role
=
835 (assoc
->end
[i
].role
!= NULL
)?g_strdup(assoc
->end
[i
].role
):NULL
;
836 newassoc
->end
[i
].multiplicity
=
837 (assoc
->end
[i
].multiplicity
!= NULL
)?g_strdup(assoc
->end
[i
].multiplicity
):NULL
;
840 newassoc
->text_width
= assoc
->text_width
;
841 newassoc
->properties_dialog
= NULL
;
843 association_update_data(newassoc
);
845 return &newassoc
->orth
.object
;
850 association_save(Association
*assoc
, ObjectNode obj_node
,
851 const char *filename
)
857 orthconn_save(&assoc
->orth
, obj_node
);
859 data_add_string(new_attribute(obj_node
, "name"),
861 data_add_enum(new_attribute(obj_node
, "direction"),
864 attr
= new_attribute(obj_node
, "ends");
866 composite
= data_add_composite(attr
, NULL
);
868 data_add_string(composite_add_attribute(composite
, "role"),
870 data_add_string(composite_add_attribute(composite
, "multiplicity"),
871 assoc
->end
[i
].multiplicity
);
872 data_add_boolean(composite_add_attribute(composite
, "arrow"),
873 assoc
->end
[i
].arrow
);
874 data_add_enum(composite_add_attribute(composite
, "aggregate"),
875 assoc
->end
[i
].aggregate
);
876 data_add_enum(composite_add_attribute(composite
, "visibility"),
877 assoc
->end
[i
].visibility
);
882 association_load(ObjectNode obj_node
, int version
, const char *filename
)
891 if (assoc_font
== NULL
) {
892 assoc_font
= dia_font_new_from_style(DIA_FONT_MONOSPACE
,
893 ASSOCIATION_FONTHEIGHT
);
896 assoc
= g_new0(Association
, 1);
901 obj
->type
= &association_type
;
902 obj
->ops
= &association_ops
;
904 orthconn_load(orth
, obj_node
);
907 attr
= object_find_attribute(obj_node
, "name");
909 assoc
->name
= data_string(attribute_first_data(attr
));
911 assoc
->text_width
= 0.0;
912 if (assoc
->name
!= NULL
) {
914 dia_font_string_width(assoc
->name
, assoc_font
, ASSOCIATION_FONTHEIGHT
);
917 assoc
->direction
= ASSOC_NODIR
;
918 attr
= object_find_attribute(obj_node
, "direction");
920 assoc
->direction
= data_enum(attribute_first_data(attr
));
922 attr
= object_find_attribute(obj_node
, "ends");
923 composite
= attribute_first_data(attr
);
926 assoc
->end
[i
].role
= NULL
;
927 attr
= composite_find_attribute(composite
, "role");
929 assoc
->end
[i
].role
= data_string(attribute_first_data(attr
));
931 if ( assoc
->end
[i
].role
!= NULL
932 && 0 == strcmp(assoc
->end
[i
].role
, "")) {
933 g_free(assoc
->end
[i
].role
);
934 assoc
->end
[i
].role
= NULL
;
937 assoc
->end
[i
].multiplicity
= NULL
;
938 attr
= composite_find_attribute(composite
, "multiplicity");
940 assoc
->end
[i
].multiplicity
= data_string(attribute_first_data(attr
));
942 if ( assoc
->end
[i
].multiplicity
!= NULL
943 && 0 == strcmp(assoc
->end
[i
].multiplicity
, "")) {
944 g_free(assoc
->end
[i
].multiplicity
);
945 assoc
->end
[i
].multiplicity
= NULL
;
948 assoc
->end
[i
].arrow
= FALSE
;
949 attr
= composite_find_attribute(composite
, "arrow");
951 assoc
->end
[i
].arrow
= data_boolean(attribute_first_data(attr
));
953 assoc
->end
[i
].aggregate
= AGGREGATE_NONE
;
954 attr
= composite_find_attribute(composite
, "aggregate");
956 assoc
->end
[i
].aggregate
= data_enum(attribute_first_data(attr
));
958 assoc
->end
[i
].visibility
= FALSE
;
959 attr
= composite_find_attribute(composite
, "visibility");
961 assoc
->end
[i
].visibility
= data_enum( attribute_first_data(attr
) );
963 assoc
->end
[i
].text_width
= 0.0;
964 if (assoc
->end
[i
].role
!= NULL
) {
965 assoc
->end
[i
].text_width
=
966 dia_font_string_width(assoc
->end
[i
].role
, assoc_font
,
967 ASSOCIATION_FONTHEIGHT
);
969 if (assoc
->end
[i
].multiplicity
!= NULL
) {
970 assoc
->end
[i
].text_width
=
971 MAX(assoc
->end
[i
].text_width
,
972 dia_font_string_width(assoc
->end
[i
].multiplicity
,
973 assoc_font
, ASSOCIATION_FONTHEIGHT
) );
975 composite
= data_next(composite
);
978 assoc
->properties_dialog
= NULL
;
980 association_set_state(assoc
, association_get_state(assoc
));
982 return &assoc
->orth
.object
;
985 static ObjectChange
*
986 association_apply_properties(Association
*assoc
)
988 AssociationPropertiesDialog
*prop_dialog
;
992 ObjectState
*old_state
;
994 prop_dialog
= assoc
->properties_dialog
;
996 old_state
= (ObjectState
*)association_get_state(assoc
);
998 /* Read from dialog and put in object: */
1000 str
= gtk_entry_get_text(prop_dialog
->name
);
1001 if (str
&& strlen (str
) != 0)
1002 assoc
->name
= g_strdup (str
);
1006 assoc
->text_width
= 0.0;
1008 if (assoc
->name
!= NULL
) {
1010 dia_font_string_width(assoc
->name
, assoc_font
, ASSOCIATION_FONTHEIGHT
);
1013 menuitem
= gtk_menu_get_active(prop_dialog
->dir_menu
);
1015 GPOINTER_TO_INT(gtk_object_get_user_data(GTK_OBJECT(menuitem
)));
1018 AssociationEnd
*end
= &assoc
->end
[i
];
1020 end
->visibility
= (UMLVisibility
)
1021 GPOINTER_TO_INT (gtk_object_get_user_data (
1022 GTK_OBJECT (gtk_menu_get_active (prop_dialog
->end
[i
].attr_visible
))));
1026 str
= gtk_entry_get_text(prop_dialog
->end
[i
].role
);
1027 if (str
&& strlen (str
) != 0)
1028 end
->role
= g_strdup (str
);
1033 g_free(end
->multiplicity
);
1034 str
= gtk_entry_get_text(prop_dialog
->end
[i
].multiplicity
);
1035 if (strlen (str
) != 0)
1036 end
->multiplicity
= g_strdup(str
);
1038 end
->multiplicity
= NULL
;
1040 end
->text_width
= 0.0;
1042 if (end
->role
!= NULL
) {
1044 dia_font_string_width(end
->role
, assoc_font
, ASSOCIATION_FONTHEIGHT
);
1046 if (end
->multiplicity
!= NULL
) {
1048 MAX(end
->text_width
,
1049 dia_font_string_width(end
->multiplicity
,
1050 assoc_font
, ASSOCIATION_FONTHEIGHT
) );
1053 end
->arrow
= prop_dialog
->end
[i
].draw_arrow
->active
;
1055 end
->aggregate
= AGGREGATE_NONE
;
1056 if (prop_dialog
->end
[i
].aggregate
->active
)
1057 end
->aggregate
= AGGREGATE_NORMAL
;
1058 if (prop_dialog
->end
[i
].composition
->active
)
1059 end
->aggregate
= AGGREGATE_COMPOSITION
;
1063 association_set_state(assoc
, association_get_state(assoc
));
1064 return new_object_state_change(&assoc
->orth
.object
, old_state
,
1065 (GetStateFunc
)association_get_state
,
1066 (SetStateFunc
)association_set_state
);
1070 fill_in_dialog(Association
*assoc
)
1072 AssociationPropertiesDialog
*prop_dialog
;
1075 prop_dialog
= assoc
->properties_dialog
;
1077 if (assoc
->name
!= NULL
)
1078 gtk_entry_set_text(prop_dialog
->name
, assoc
->name
);
1080 gtk_entry_set_text(prop_dialog
->name
, "");
1082 gtk_option_menu_set_history(prop_dialog
->dir_omenu
, assoc
->direction
);
1085 if (assoc
->end
[i
].role
!= NULL
)
1086 gtk_entry_set_text(prop_dialog
->end
[i
].role
, assoc
->end
[i
].role
);
1088 gtk_entry_set_text(prop_dialog
->end
[i
].role
, "");
1090 if (assoc
->end
[i
].multiplicity
!= NULL
)
1091 gtk_entry_set_text(prop_dialog
->end
[i
].multiplicity
,
1092 assoc
->end
[i
].multiplicity
);
1094 gtk_entry_set_text(prop_dialog
->end
[i
].multiplicity
, "");
1096 gtk_option_menu_set_history(prop_dialog
->end
[i
].attr_visible_button
,
1097 (gint
)assoc
->end
[i
].visibility
);
1098 gtk_toggle_button_set_active(prop_dialog
->end
[i
].draw_arrow
,
1099 assoc
->end
[i
].arrow
);
1100 gtk_toggle_button_set_active(prop_dialog
->end
[i
].aggregate
,
1101 assoc
->end
[i
].aggregate
== AGGREGATE_NORMAL
);
1102 gtk_toggle_button_set_active(prop_dialog
->end
[i
].composition
,
1103 assoc
->end
[i
].aggregate
== AGGREGATE_COMPOSITION
);
1108 mutex_aggregate_callback(GtkWidget
*widget
,
1109 AssociationPropertiesDialog
*prop_dialog
)
1112 GtkToggleButton
*button
;
1114 button
= GTK_TOGGLE_BUTTON(widget
);
1116 if (!button
->active
)
1120 if (prop_dialog
->end
[i
].aggregate
!= button
) {
1121 gtk_toggle_button_set_active(prop_dialog
->end
[i
].aggregate
, 0);
1123 if (prop_dialog
->end
[i
].composition
!= button
) {
1124 gtk_toggle_button_set_active(prop_dialog
->end
[i
].composition
, 0);
1130 association_get_properties(Association
*assoc
, gboolean is_default
)
1132 AssociationPropertiesDialog
*prop_dialog
;
1137 GtkWidget
*split_hbox
;
1143 GtkWidget
*menuitem
;
1144 GtkWidget
*checkbox
;
1148 if (assoc
->properties_dialog
== NULL
) {
1150 prop_dialog
= g_new(AssociationPropertiesDialog
, 1);
1151 assoc
->properties_dialog
= prop_dialog
;
1153 dialog
= gtk_vbox_new(FALSE
, 0);
1154 gtk_object_ref(GTK_OBJECT(dialog
));
1155 gtk_object_sink(GTK_OBJECT(dialog
));
1156 prop_dialog
->dialog
= dialog
;
1159 hbox
= gtk_hbox_new(FALSE
, 5);
1160 label
= gtk_label_new(_("Name:"));
1161 gtk_box_pack_start (GTK_BOX (hbox
), label
, FALSE
, TRUE
, 0);
1162 entry
= gtk_entry_new();
1163 prop_dialog
->name
= GTK_ENTRY(entry
);
1164 gtk_box_pack_start (GTK_BOX (hbox
), entry
, TRUE
, TRUE
, 0);
1165 gtk_widget_grab_focus(entry
);
1166 gtk_widget_show (label
);
1167 gtk_widget_show (entry
);
1168 gtk_box_pack_start (GTK_BOX (dialog
), hbox
, TRUE
, TRUE
, 0);
1169 gtk_widget_show(hbox
);
1171 /* Direction entry: */
1172 hbox
= gtk_hbox_new(FALSE
, 5);
1173 label
= gtk_label_new(_("Direction:"));
1174 gtk_box_pack_start (GTK_BOX (hbox
), label
, FALSE
, TRUE
, 0);
1176 omenu
= gtk_option_menu_new ();
1177 menu
= gtk_menu_new ();
1178 prop_dialog
->dir_menu
= GTK_MENU(menu
);
1179 prop_dialog
->dir_omenu
= GTK_OPTION_MENU(omenu
);
1183 menuitem
= gtk_radio_menu_item_new_with_label (group
, _("None"));
1184 gtk_object_set_user_data(GTK_OBJECT(menuitem
),
1185 GINT_TO_POINTER(ASSOC_NODIR
));
1186 group
= gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (menuitem
));
1187 gtk_menu_append (GTK_MENU (menu
), menuitem
);
1188 gtk_widget_show (menuitem
);
1190 menuitem
= gtk_radio_menu_item_new_with_label (group
, _("From A to B"));
1191 gtk_object_set_user_data(GTK_OBJECT(menuitem
),
1192 GINT_TO_POINTER(ASSOC_RIGHT
));
1193 group
= gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (menuitem
));
1194 gtk_menu_append (GTK_MENU (menu
), menuitem
);
1195 gtk_widget_show (menuitem
);
1197 menuitem
= gtk_radio_menu_item_new_with_label (group
, _("From B to A"));
1198 gtk_object_set_user_data(GTK_OBJECT(menuitem
),
1199 GINT_TO_POINTER(ASSOC_LEFT
));
1200 group
= gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (menuitem
));
1201 gtk_menu_append (GTK_MENU (menu
), menuitem
);
1202 gtk_widget_show (menuitem
);
1204 gtk_option_menu_set_menu (GTK_OPTION_MENU (omenu
), menu
);
1205 gtk_box_pack_start (GTK_BOX (hbox
), omenu
, FALSE
, TRUE
, 0);
1207 gtk_widget_show (label
);
1208 gtk_widget_show (omenu
);
1209 gtk_box_pack_start (GTK_BOX (dialog
), hbox
, TRUE
, TRUE
, 0);
1210 gtk_widget_show(hbox
);
1212 split_hbox
= gtk_hbox_new(TRUE
, 5);
1213 gtk_box_pack_start (GTK_BOX (dialog
), split_hbox
, TRUE
, TRUE
, 0);
1214 gtk_widget_show(split_hbox
);
1216 group
= NULL
; /* For the radio-buttons */
1224 frame
= gtk_frame_new(str
);
1226 vbox
= gtk_vbox_new(FALSE
, 5);
1227 /* End 'i' into vbox: */
1229 label
= gtk_label_new(_("Side A"));
1231 label
= gtk_label_new(_("Side B"));
1233 gtk_box_pack_start (GTK_BOX (vbox
), label
, TRUE
, TRUE
, 0);
1236 hbox
= gtk_hbox_new(FALSE
, 5);
1237 label
= gtk_label_new(_("Role:"));
1238 gtk_box_pack_start (GTK_BOX (hbox
), label
, FALSE
, TRUE
, 0);
1239 entry
= gtk_entry_new();
1240 prop_dialog
->end
[i
].role
= GTK_ENTRY(entry
);
1241 gtk_box_pack_start (GTK_BOX (hbox
), entry
, TRUE
, TRUE
, 0);
1242 gtk_widget_show (label
);
1243 gtk_widget_show (entry
);
1244 gtk_box_pack_start (GTK_BOX (vbox
), hbox
, TRUE
, TRUE
, 0);
1245 gtk_widget_show(hbox
);
1247 /* Multiplicity entry: */
1248 hbox
= gtk_hbox_new(FALSE
, 5);
1249 label
= gtk_label_new(_("Multiplicity:"));
1250 gtk_box_pack_start (GTK_BOX (hbox
), label
, FALSE
, TRUE
, 0);
1251 entry
= gtk_entry_new();
1252 prop_dialog
->end
[i
].multiplicity
= GTK_ENTRY(entry
);
1253 gtk_box_pack_start (GTK_BOX (hbox
), entry
, TRUE
, TRUE
, 0);
1254 gtk_widget_show (label
);
1255 gtk_widget_show (entry
);
1256 gtk_box_pack_start (GTK_BOX (vbox
), hbox
, TRUE
, TRUE
, 0);
1257 gtk_widget_show(hbox
);
1259 hbox
= gtk_hbox_new(FALSE
, 5);
1260 label
= gtk_label_new(_("Visibility:"));
1261 gtk_box_pack_start (GTK_BOX (hbox
), label
, FALSE
, TRUE
, 0);
1263 omenu
= gtk_option_menu_new ();
1264 menu
= gtk_menu_new ();
1265 prop_dialog
->end
[i
].attr_visible
= GTK_MENU(menu
);
1266 prop_dialog
->end
[i
].attr_visible_button
= GTK_OPTION_MENU(omenu
);
1269 menuitem
= gtk_radio_menu_item_new_with_label (group
, _("Public"));
1271 gtk_signal_connect (GTK_OBJECT (menuitem), "activate",
1272 GTK_SIGNAL_FUNC (attributes_update), umlclass);
1274 gtk_object_set_user_data(GTK_OBJECT(menuitem
),
1275 GINT_TO_POINTER(UML_PUBLIC
) );
1276 group
= gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (menuitem
));
1277 gtk_menu_append (GTK_MENU (menu
), menuitem
);
1278 gtk_widget_show (menuitem
);
1279 menuitem
= gtk_radio_menu_item_new_with_label (group
, _("Private"));
1281 gtk_signal_connect (GTK_OBJECT (menuitem), "activate",
1282 GTK_SIGNAL_FUNC (attributes_update), umlclass);
1284 gtk_object_set_user_data(GTK_OBJECT(menuitem
),
1285 GINT_TO_POINTER(UML_PRIVATE
) );
1286 group
= gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (menuitem
));
1287 gtk_menu_append (GTK_MENU (menu
), menuitem
);
1288 gtk_widget_show (menuitem
);
1289 menuitem
= gtk_radio_menu_item_new_with_label (group
, _("Protected"));
1291 gtk_signal_connect (GTK_OBJECT (menuitem), "activate",
1292 GTK_SIGNAL_FUNC (attributes_update), umlclass);
1294 gtk_object_set_user_data(GTK_OBJECT(menuitem
),
1295 GINT_TO_POINTER(UML_PROTECTED
) );
1296 group
= gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (menuitem
));
1297 gtk_menu_append (GTK_MENU (menu
), menuitem
);
1298 gtk_widget_show (menuitem
);
1299 menuitem
= gtk_radio_menu_item_new_with_label (group
, _("Implementation"));
1301 gtk_signal_connect (GTK_OBJECT (menuitem), "activate",
1302 GTK_SIGNAL_FUNC (attributes_update), umlclass);
1304 gtk_object_set_user_data(GTK_OBJECT(menuitem
),
1305 GINT_TO_POINTER(UML_IMPLEMENTATION
) );
1306 group
= gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (menuitem
));
1307 gtk_menu_append (GTK_MENU (menu
), menuitem
);
1308 gtk_widget_show (menuitem
);
1310 gtk_option_menu_set_menu (GTK_OPTION_MENU (omenu
), menu
);
1311 gtk_box_pack_start (GTK_BOX (hbox
), omenu
, FALSE
, TRUE
, 0);
1312 gtk_widget_show(label
);
1313 gtk_widget_show(omenu
);
1314 gtk_box_pack_start (GTK_BOX (vbox
), hbox
, TRUE
, TRUE
, 0);
1315 gtk_widget_show(hbox
);
1318 checkbox
= gtk_check_button_new_with_label(_("Show arrow"));
1319 prop_dialog
->end
[i
].draw_arrow
= GTK_TOGGLE_BUTTON( checkbox
);
1320 gtk_widget_show(checkbox
);
1321 gtk_box_pack_start (GTK_BOX (vbox
), checkbox
, TRUE
, TRUE
, 0);
1324 checkbox
= gtk_check_button_new_with_label(_("Aggregate"));
1325 prop_dialog
->end
[i
].aggregate
= GTK_TOGGLE_BUTTON( checkbox
);
1326 gtk_signal_connect(GTK_OBJECT(checkbox
), "toggled",
1327 (GtkSignalFunc
) mutex_aggregate_callback
, prop_dialog
);
1328 gtk_widget_show(checkbox
);
1329 gtk_box_pack_start (GTK_BOX (vbox
), checkbox
, TRUE
, TRUE
, 0);
1332 checkbox
= gtk_check_button_new_with_label(_("Composition"));
1333 prop_dialog
->end
[i
].composition
= GTK_TOGGLE_BUTTON( checkbox
);
1334 gtk_signal_connect(GTK_OBJECT(checkbox
), "toggled",
1335 (GtkSignalFunc
) mutex_aggregate_callback
, prop_dialog
);
1336 gtk_widget_show(checkbox
);
1337 gtk_box_pack_start (GTK_BOX (vbox
), checkbox
, TRUE
, TRUE
, 0);
1339 gtk_container_set_border_width(GTK_CONTAINER(vbox
), 5);
1340 gtk_container_add(GTK_CONTAINER(frame
), vbox
);
1341 gtk_box_pack_start (GTK_BOX (split_hbox
), frame
, TRUE
, TRUE
, 0);
1342 gtk_widget_show(vbox
);
1343 gtk_widget_show(frame
);
1347 fill_in_dialog(assoc
);
1348 gtk_widget_show (assoc
->properties_dialog
->dialog
);
1350 return assoc
->properties_dialog
->dialog
;