diff --git a/demo/adg-demo.c b/demo/adg-demo.c index 097cdc29..73502226 100644 --- a/demo/adg-demo.c +++ b/demo/adg-demo.c @@ -1,1196 +1,1196 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "demo.h" #include #include #include #define SQRT3 1.732050808 #define CHAMFER 0.3 /* Whether render the boxes to highlight the extents of every entity */ static gboolean show_extents = FALSE; typedef struct _DemoPart DemoPart; struct _DemoPart { /* Dimensions */ gdouble A; gdouble B; gdouble C; gdouble DHOLE; gdouble LHOLE; gdouble D1; gdouble D2; gdouble D3; gdouble D4; gdouble D5; gdouble D6; gdouble D7; gdouble RD34; gdouble RD56; gdouble LD2; gdouble LD3; gdouble LD5; gdouble LD6; gdouble LD7; gboolean GROOVE; gdouble ZGROOVE; gdouble DGROOVE; gdouble LGROOVE; /* Metadata */ gchar *TITLE; gchar *DRAWING; gchar *AUTHOR; gchar *DATE; /* User interface widgets */ AdgGtkArea *area; GHashTable *widgets; GtkButton *apply; GtkButton *reset; /* Data models */ AdgPath *body; AdgPath *hole; AdgPath *axis; /* Special entities */ AdgTitleBlock *title_block; AdgEdges *edges; }; static void _adg_version(void) { g_print("adg-demo " PACKAGE_VERSION "\n"); exit(0); } static void parse_args(gint *p_argc, gchar **p_argv[]) { GOptionEntry entries[] = { {"version", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) _adg_version, _("Display version information"), NULL}, {"show-extents", 'E', 0, G_OPTION_ARG_NONE, &show_extents, _("Show the boundary boxes of every entity"), NULL}, {NULL} }; GError *error; error = NULL; gtk_init_with_args(p_argc, p_argv, _("- ADG demonstration program"), entries, GETTEXT_PACKAGE, &error); if (error != NULL) { g_error("%s", error->message); g_assert_not_reached(); } } /** * _adg_error: * @message: a custom error message * @parent_window: the parent window or %NULL * * Convenient function that presents an error dialog and waits the user * to close this modal dialog. **/ void _adg_error(const gchar *message, GtkWindow *parent_window) { GtkWidget *dialog = gtk_message_dialog_new(parent_window, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", message); gtk_window_set_title(GTK_WINDOW(dialog), _("Error from adg-demo")); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } static void _adg_path_add_groove(AdgPath *path, const DemoPart *part) { AdgModel *model; CpmlPair pair; model = ADG_MODEL(path); pair.x = part->ZGROOVE; pair.y = part->D1 / 2; adg_path_line_to(path, &pair); adg_model_set_named_pair(model, "DGROOVEI_X", &pair); pair.y = part->D3 / 2; adg_model_set_named_pair(model, "DGROOVEY_POS", &pair); pair.y = part->DGROOVE / 2; adg_path_line_to(path, &pair); adg_model_set_named_pair(model, "DGROOVEI_Y", &pair); pair.x += part->LGROOVE; adg_path_line_to(path, &pair); pair.y = part->D3 / 2; adg_model_set_named_pair(model, "DGROOVEX_POS", &pair); pair.y = part->D1 / 2; adg_path_line_to(path, &pair); adg_model_set_named_pair(model, "DGROOVEF_X", &pair); } static void _adg_part_define_title_block(DemoPart *part) { g_object_set(part->title_block, "title", part->TITLE, "author", part->AUTHOR, "date", part->DATE, "drawing", part->DRAWING, "logo", adg_logo_new(), "projection", adg_projection_new(ADG_PROJECTION_SCHEME_FIRST_ANGLE), "size", "A4", NULL); } static void _adg_part_define_hole(DemoPart *part) { AdgPath *path; AdgModel *model; CpmlPair pair, edge; path = part->hole; model = ADG_MODEL(path); pair.x = part->LHOLE; pair.y = 0; adg_path_move_to(path, &pair); adg_model_set_named_pair(model, "LHOLE", &pair); pair.y = part->DHOLE / 2; pair.x -= pair.y / SQRT3; adg_path_line_to(path, &pair); cpml_pair_copy(&edge, &pair); pair.x = 0; adg_path_line_to(path, &pair); adg_model_set_named_pair(model, "DHOLE", &pair); pair.y = (part->D1 + part->DHOLE) / 4; adg_path_line_to(path, &pair); adg_path_curve_to_explicit(path, part->LHOLE / 2, part->DHOLE / 2, part->LHOLE + 2, part->D1 / 2, part->LHOLE + 2, 0); adg_path_reflect(path, NULL); adg_path_join(path); adg_path_close(path); /* No need to incomodate an AdgEdge model for two reasons: * it is only a single line and it is always needed */ adg_path_move_to(path, &edge); edge.y = -edge.y; adg_path_line_to(path, &edge); } static void _adg_part_define_body(DemoPart *part) { AdgModel *model; AdgPath *path; CpmlPair pair, tmp; const CpmlPrimitive *primitive; path = part->body; model = ADG_MODEL(path); pair.x = 0; pair.y = part->D1 / 2; adg_path_move_to(path, &pair); adg_model_set_named_pair(model, "D1I", &pair); if (part->GROOVE) { _adg_path_add_groove(path, part); } pair.x = part->A - part->B - part->LD2; adg_path_line_to(path, &pair); adg_model_set_named_pair(model, "D1F", &pair); pair.y = part->D3 / 2; adg_model_set_named_pair(model, "D2_POS", &pair); pair.x += (part->D1 - part->D2) / 2; pair.y = part->D2 / 2; adg_path_line_to(path, &pair); adg_model_set_named_pair(model, "D2I", &pair); pair.x = part->A - part->B; adg_path_line_to(path, &pair); adg_path_fillet(path, 0.4); pair.x = part->A - part->B; pair.y = part->D3 / 2; adg_path_line_to(path, &pair); adg_model_set_named_pair(model, "D3I", &pair); pair.x = part->A; adg_model_set_named_pair(model, "East", &pair); pair.x = 0; adg_model_set_named_pair(model, "West", &pair); adg_path_chamfer(path, CHAMFER, CHAMFER); pair.x = part->A - part->B + part->LD3; pair.y = part->D3 / 2; adg_path_line_to(path, &pair); primitive = adg_path_over_primitive(path); cpml_primitive_put_point(primitive, 0, &tmp); adg_model_set_named_pair(model, "D3I_X", &tmp); cpml_primitive_put_point(primitive, -1, &tmp); adg_model_set_named_pair(model, "D3I_Y", &tmp); adg_path_chamfer(path, CHAMFER, CHAMFER); pair.y = part->D4 / 2; adg_path_line_to(path, &pair); primitive = adg_path_over_primitive(path); cpml_primitive_put_point(primitive, 0, &tmp); adg_model_set_named_pair(model, "D3F_Y", &tmp); cpml_primitive_put_point(primitive, -1, &tmp); adg_model_set_named_pair(model, "D3F_X", &tmp); adg_path_fillet(path, part->RD34); pair.x += part->RD34; adg_model_set_named_pair(model, "D4I", &pair); pair.x = part->A - part->C - part->LD5; adg_path_line_to(path, &pair); adg_model_set_named_pair(model, "D4F", &pair); pair.y = part->D3 / 2; adg_model_set_named_pair(model, "D4_POS", &pair); primitive = adg_path_over_primitive(path); cpml_primitive_put_point(primitive, 0, &tmp); tmp.x += part->RD34; adg_model_set_named_pair(model, "RD34", &tmp); tmp.x -= cos(G_PI_4) * part->RD34, tmp.y -= sin(G_PI_4) * part->RD34, adg_model_set_named_pair(model, "RD34_R", &tmp); tmp.x += part->RD34, tmp.y += part->RD34, adg_model_set_named_pair(model, "RD34_XY", &tmp); pair.x += (part->D4 - part->D5) / 2; pair.y = part->D5 / 2; adg_path_line_to(path, &pair); adg_model_set_named_pair(model, "D5I", &pair); pair.x = part->A - part->C; adg_path_line_to(path, &pair); adg_path_fillet(path, 0.2); pair.y = part->D6 / 2; adg_path_line_to(path, &pair); primitive = adg_path_over_primitive(path); cpml_primitive_put_point(primitive, 0, &tmp); adg_model_set_named_pair(model, "D5F", &tmp); adg_path_fillet(path, 0.1); pair.x += part->LD6; adg_path_line_to(path, &pair); adg_model_set_named_pair(model, "D6F", &pair); primitive = adg_path_over_primitive(path); cpml_primitive_put_point(primitive, 0, &tmp); adg_model_set_named_pair(model, "D6I_X", &tmp); primitive = adg_path_over_primitive(path); cpml_primitive_put_point(primitive, -1, &tmp); adg_model_set_named_pair(model, "D6I_Y", &tmp); pair.x = part->A - part->LD7; pair.y -= (part->C - part->LD7 - part->LD6) / SQRT3; adg_path_line_to(path, &pair); adg_model_set_named_pair(model, "D67", &pair); pair.y = part->D7 / 2; adg_path_line_to(path, &pair); pair.x = part->A; adg_path_line_to(path, &pair); adg_model_set_named_pair(model, "D7F", &pair); adg_path_reflect(path, NULL); adg_path_join(path); adg_path_close(path); } static void _adg_part_define_axis(DemoPart *part) { AdgPath *path; path = part->axis; /* XXX: actually the end points can extend outside the body * only in local space. The proper extension values should be * expressed in global space but actually is impossible to * combine local and global space in the AdgPath API. */ adg_path_move_to_explicit(path, -1, 0); adg_path_line_to_explicit(path, part->A + 1, 0); } static void _adg_part_lock(DemoPart *part) { gtk_widget_set_sensitive(GTK_WIDGET(part->apply), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(part->reset), FALSE); } static void _adg_part_unlock(DemoPart *part) { gtk_widget_set_sensitive(GTK_WIDGET(part->apply), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(part->reset), TRUE); } static void _adg_part_link(DemoPart *part, gpointer data, GObject *widget) { const gchar *edit_signal; g_assert(GTK_IS_WIDGET(widget)); g_object_ref(widget); g_hash_table_insert(part->widgets, data, widget); edit_signal = GTK_IS_TOGGLE_BUTTON(widget) ? "toggled" : "changed"; g_signal_connect_swapped(widget, edit_signal, G_CALLBACK(_adg_part_unlock), part); } static void _adg_part_ui_to_boolean(DemoPart *part, gboolean *data) { GtkWidget *widget = g_hash_table_lookup(part->widgets, data); g_assert(GTK_IS_TOGGLE_BUTTON(widget)); *data = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); } static void _adg_part_ui_to_double(DemoPart *part, gdouble *data) { GtkWidget *widget; GtkSpinButton *spin_button; widget = g_hash_table_lookup(part->widgets, data); g_assert(GTK_IS_SPIN_BUTTON(widget)); spin_button = GTK_SPIN_BUTTON(widget); gtk_spin_button_update(spin_button); *data = gtk_spin_button_get_value(spin_button); } static void _adg_part_ui_to_string(DemoPart *part, gchar **data) { GtkWidget *widget = g_hash_table_lookup(part->widgets, data); g_assert(GTK_IS_ENTRY(widget)); g_free(*data); *data = g_strdup(gtk_entry_get_text(GTK_ENTRY(widget))); } static void _adg_part_boolean_to_ui(DemoPart *part, gboolean *data) { GtkWidget *widget = g_hash_table_lookup(part->widgets, data); g_assert(GTK_IS_TOGGLE_BUTTON(widget)); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), *data); } static void _adg_part_double_to_ui(DemoPart *part, gdouble *data) { GtkWidget *widget = g_hash_table_lookup(part->widgets, data); g_assert(GTK_IS_SPIN_BUTTON(widget)); gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), *data); } static void _adg_part_string_to_ui(DemoPart *part, gchar **data) { GtkWidget *widget = g_hash_table_lookup(part->widgets, data); g_assert(GTK_IS_ENTRY(widget)); gtk_entry_set_text(GTK_ENTRY(widget), *data); } static void _adg_demo_canvas_add_dimensions(AdgCanvas *canvas, AdgModel *model) { AdgLDim *ldim; AdgADim *adim; AdgRDim *rdim; AdgStyle *style, *diameter; style = adg_dress_get_fallback(ADG_DRESS_DIMENSION); diameter = adg_style_clone(style); adg_dim_style_set_number_format(ADG_DIM_STYLE(diameter), ADG_UTF8_DIAMETER "%g"); /* NORTH */ ldim = adg_ldim_new_full_from_model(model, "-D3I_X", "-D3F_X", "-D3F_Y", ADG_DIR_UP); adg_dim_set_outside(ADG_DIM(ldim), ADG_THREE_STATE_OFF); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); ldim = adg_ldim_new_full_from_model(model, "-D6I_X", "-D67", "-East", ADG_DIR_UP); adg_dim_set_level(ADG_DIM(ldim), 0); adg_ldim_switch_extension1(ldim, FALSE); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); ldim = adg_ldim_new_full_from_model(model, "-D6I_X", "-D7F", "-East", ADG_DIR_UP); adg_dim_set_limits(ADG_DIM(ldim), "-0.06", NULL); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); adim = adg_adim_new_full_from_model(model, "-D6I_Y", "-D6F", "-D6F", "-D67", "-D6F"); adg_dim_set_level(ADG_DIM(adim), 2); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(adim)); rdim = adg_rdim_new_full_from_model(model, "-RD34", "-RD34_R", "-RD34_XY"); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(rdim)); ldim = adg_ldim_new_full_from_model(model, "-DGROOVEI_X", "-DGROOVEF_X", "-DGROOVEX_POS", ADG_DIR_UP); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); ldim = adg_ldim_new_full_from_model(model, "D2I", "-D2I", "-D2_POS", ADG_DIR_LEFT); adg_dim_set_limits(ADG_DIM(ldim), "-0.1", NULL); adg_dim_set_outside(ADG_DIM(ldim), ADG_THREE_STATE_OFF); adg_entity_set_style(ADG_ENTITY(ldim), ADG_DRESS_DIMENSION, diameter); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); ldim = adg_ldim_new_full_from_model(model, "DGROOVEI_Y", "-DGROOVEI_Y", "-DGROOVEY_POS", ADG_DIR_LEFT); adg_dim_set_limits(ADG_DIM(ldim), "-0.1", NULL); adg_dim_set_outside(ADG_DIM(ldim), ADG_THREE_STATE_OFF); adg_entity_set_style(ADG_ENTITY(ldim), ADG_DRESS_DIMENSION, diameter); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); /* SOUTH */ adim = adg_adim_new_full_from_model(model, "D1F", "D1I", "D2I", "D1F", "D1F"); adg_dim_set_level(ADG_DIM(adim), 2); adg_adim_switch_extension2(adim, FALSE); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(adim)); ldim = adg_ldim_new_full_from_model(model, "D1I", "LHOLE", "West", ADG_DIR_DOWN); adg_ldim_switch_extension1(ldim, FALSE); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); ldim = adg_ldim_new_full_from_model(model, "D1I", "DGROOVEI_X", "West", ADG_DIR_DOWN); adg_ldim_switch_extension1(ldim, FALSE); adg_dim_set_level(ADG_DIM(ldim), 2); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); ldim = adg_ldim_new_full_from_model(model, "D4F", "D6I_X", "D4_POS", ADG_DIR_DOWN); adg_dim_set_limits(ADG_DIM(ldim), NULL, "+0.2"); adg_dim_set_outside(ADG_DIM(ldim), ADG_THREE_STATE_OFF); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); ldim = adg_ldim_new_full_from_model(model, "D1F", "D3I_X", "D2_POS", ADG_DIR_DOWN); adg_dim_set_level(ADG_DIM(ldim), 2); adg_ldim_switch_extension2(ldim, FALSE); adg_dim_set_outside(ADG_DIM(ldim), ADG_THREE_STATE_OFF); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); ldim = adg_ldim_new_full_from_model(model, "D3I_X", "D7F", "East", ADG_DIR_DOWN); adg_dim_set_limits(ADG_DIM(ldim), NULL, "+0.1"); adg_dim_set_level(ADG_DIM(ldim), 2); adg_dim_set_outside(ADG_DIM(ldim), ADG_THREE_STATE_OFF); adg_ldim_switch_extension2(ldim, FALSE); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); ldim = adg_ldim_new_full_from_model(model, "D1I", "D7F", "D3F_Y", ADG_DIR_DOWN); adg_dim_set_limits(ADG_DIM(ldim), "-0.05", "+0.05"); adg_dim_set_level(ADG_DIM(ldim), 3); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); adim = adg_adim_new_full_from_model(model, "D4F", "D4I", "D5I", "D4F", "D4F"); adg_dim_set_level(ADG_DIM(adim), 1.5); adg_adim_switch_extension2(adim, FALSE); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(adim)); /* EAST */ ldim = adg_ldim_new_full_from_model(model, "D6F", "-D6F", "East", ADG_DIR_RIGHT); adg_dim_set_limits(ADG_DIM(ldim), "-0.1", NULL); adg_dim_set_level(ADG_DIM(ldim), 4); adg_entity_set_style(ADG_ENTITY(ldim), ADG_DRESS_DIMENSION, diameter); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); ldim = adg_ldim_new_full_from_model(model, "D4F", "-D4F", "East", ADG_DIR_RIGHT); adg_dim_set_level(ADG_DIM(ldim), 3); adg_entity_set_style(ADG_ENTITY(ldim), ADG_DRESS_DIMENSION, diameter); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); ldim = adg_ldim_new_full_from_model(model, "D5F", "-D5F", "East", ADG_DIR_RIGHT); adg_dim_set_limits(ADG_DIM(ldim), "-0.1", NULL); adg_dim_set_level(ADG_DIM(ldim), 2); adg_entity_set_style(ADG_ENTITY(ldim), ADG_DRESS_DIMENSION, diameter); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); ldim = adg_ldim_new_full_from_model(model, "D7F", "-D7F", "East", ADG_DIR_RIGHT); adg_entity_set_style(ADG_ENTITY(ldim), ADG_DRESS_DIMENSION, diameter); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); /* WEST */ ldim = adg_ldim_new_full_from_model(model, "DHOLE", "-DHOLE", "-West", ADG_DIR_LEFT); adg_entity_set_style(ADG_ENTITY(ldim), ADG_DRESS_DIMENSION, diameter); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); ldim = adg_ldim_new_full_from_model(model, "D1I", "-D1I", "-West", ADG_DIR_LEFT); adg_dim_set_limits(ADG_DIM(ldim), "-0.05", "+0.05"); adg_dim_set_level(ADG_DIM(ldim), 2); adg_entity_set_style(ADG_ENTITY(ldim), ADG_DRESS_DIMENSION, diameter); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); ldim = adg_ldim_new_full_from_model(model, "D3I_Y", "-D3I_Y", "-West", ADG_DIR_LEFT); adg_dim_set_limits(ADG_DIM(ldim), "-0.25", NULL); adg_dim_set_level(ADG_DIM(ldim), 3); adg_entity_set_style(ADG_ENTITY(ldim), ADG_DRESS_DIMENSION, diameter); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(ldim)); } static void _adg_demo_canvas_add_axis(AdgCanvas *canvas, AdgTrail *trail) { AdgStroke *stroke = adg_stroke_new(trail); adg_stroke_set_line_dress(stroke, ADG_DRESS_LINE_AXIS); adg_container_add(ADG_CONTAINER(canvas), ADG_ENTITY(stroke)); } static AdgCanvas * _adg_canvas_init(AdgCanvas *canvas, DemoPart *part) { AdgContainer *container; AdgEntity *entity; container = (AdgContainer *) canvas; adg_canvas_set_paper(canvas, GTK_PAPER_NAME_A4, GTK_PAGE_ORIENTATION_LANDSCAPE); adg_canvas_set_title_block(canvas, part->title_block); entity = ADG_ENTITY(adg_stroke_new(ADG_TRAIL(part->body))); adg_container_add(container, entity); entity = ADG_ENTITY(adg_hatch_new(ADG_TRAIL(part->hole))); adg_container_add(container, entity); entity = ADG_ENTITY(adg_stroke_new(ADG_TRAIL(part->hole))); adg_container_add(container, entity); entity = ADG_ENTITY(adg_stroke_new(ADG_TRAIL(part->edges))); adg_container_add(container, entity); _adg_demo_canvas_add_dimensions(canvas, ADG_MODEL(part->body)); _adg_demo_canvas_add_axis(canvas, ADG_TRAIL(part->axis)); return canvas; } static GtkRadioButton * _adg_group_get_active(GtkRadioButton *radio_group) { GSList *list = gtk_radio_button_get_group(radio_group); while (list) { if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(list->data))) return list->data; list = list->next; } return NULL; } static void _adg_do_edit(DemoPart *part) { _adg_part_ui_to_double(part, &part->A); _adg_part_ui_to_double(part, &part->B); _adg_part_ui_to_double(part, &part->C); _adg_part_ui_to_double(part, &part->DHOLE); _adg_part_ui_to_double(part, &part->LHOLE); _adg_part_ui_to_double(part, &part->D1); _adg_part_ui_to_double(part, &part->D2); _adg_part_ui_to_double(part, &part->LD2); _adg_part_ui_to_double(part, &part->D3); _adg_part_ui_to_double(part, &part->LD3); _adg_part_ui_to_double(part, &part->D4); //_adg_part_ui_to_double(part, &part->D5); _adg_part_ui_to_double(part, &part->D6); //_adg_part_ui_to_double(part, &part->LD6); _adg_part_ui_to_double(part, &part->D7); //_adg_part_ui_to_double(part, &part->LD7); _adg_part_ui_to_boolean(part, &part->GROOVE); _adg_part_ui_to_double(part, &part->ZGROOVE); _adg_part_ui_to_double(part, &part->DGROOVE); _adg_part_ui_to_double(part, &part->LGROOVE); _adg_part_ui_to_string(part, &part->TITLE); _adg_part_ui_to_string(part, &part->DRAWING); _adg_part_ui_to_string(part, &part->AUTHOR); _adg_part_ui_to_string(part, &part->DATE); _adg_part_lock(part); adg_model_reset(ADG_MODEL(part->body)); adg_model_reset(ADG_MODEL(part->hole)); adg_model_reset(ADG_MODEL(part->axis)); adg_model_reset(ADG_MODEL(part->edges)); _adg_part_define_title_block(part); _adg_part_define_body(part); _adg_part_define_hole(part); _adg_part_define_axis(part); adg_model_changed(ADG_MODEL(part->body)); adg_model_changed(ADG_MODEL(part->hole)); adg_model_changed(ADG_MODEL(part->axis)); adg_model_changed(ADG_MODEL(part->edges)); gtk_widget_queue_draw(GTK_WIDGET(part->area)); } static void _adg_do_reset(DemoPart *part) { _adg_part_double_to_ui(part, &part->A); _adg_part_double_to_ui(part, &part->B); _adg_part_double_to_ui(part, &part->C); _adg_part_double_to_ui(part, &part->DHOLE); _adg_part_double_to_ui(part, &part->LHOLE); _adg_part_double_to_ui(part, &part->D1); _adg_part_double_to_ui(part, &part->D2); _adg_part_double_to_ui(part, &part->LD2); _adg_part_double_to_ui(part, &part->D3); _adg_part_double_to_ui(part, &part->LD3); _adg_part_double_to_ui(part, &part->D4); //_adg_part_double_to_ui(part, &part->D5); _adg_part_double_to_ui(part, &part->D6); //_adg_part_double_to_ui(part, &part->LD6); _adg_part_double_to_ui(part, &part->D7); //_adg_part_double_to_ui(part, &part->LD7); _adg_part_boolean_to_ui(part, &part->GROOVE); _adg_part_double_to_ui(part, &part->ZGROOVE); _adg_part_double_to_ui(part, &part->DGROOVE); _adg_part_double_to_ui(part, &part->LGROOVE); _adg_part_string_to_ui(part, &part->TITLE); _adg_part_string_to_ui(part, &part->DRAWING); _adg_part_string_to_ui(part, &part->AUTHOR); _adg_part_string_to_ui(part, &part->DATE); _adg_part_lock(part); } static void _adg_do_save_as(GtkWindow *window, GtkResponseType response, AdgCanvas *canvas) { GtkRadioButton *type_radio; gchar *file, *suffix; cairo_surface_type_t type; GError *error; if (response != GTK_RESPONSE_OK) { gtk_widget_hide(GTK_WIDGET(window)); return; } file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(window)); if (file == NULL) return; type_radio = _adg_group_get_active(g_object_get_data(G_OBJECT(window), "type-group")); g_assert(GTK_IS_RADIO_BUTTON(type_radio)); suffix = gtk_widget_get_tooltip_markup(GTK_WIDGET(type_radio)); if (suffix != NULL) { /* Avoid double extension on the filename */ if (! g_str_has_suffix(file, suffix)) { gchar *tmp = file; file = g_strconcat(file, suffix, NULL); g_free(tmp); } g_free(suffix); } error = NULL; type = adg_type_from_filename(file); if (type == CAIRO_SURFACE_TYPE_XLIB) { g_set_error(&error, ADG_CANVAS_ERROR, ADG_CANVAS_ERROR_SURFACE, "%s", _("Requested format not supported")); } else { adg_canvas_export(canvas, type, file, &error); } g_free(file); if (error != NULL) { _adg_error(error->message, window); g_error_free(error); } gtk_widget_hide(GTK_WIDGET(window)); } static void _adg_print_prepare(GtkPrintOperation *operation, GtkPrintContext *context) { gtk_print_operation_set_n_pages(operation, 1); } static void _adg_print_page(GtkPrintOperation *operation, GtkPrintContext *context, gint page_nr, AdgEntity *canvas) { cairo_t *cr = gtk_print_context_get_cairo_context(context); cairo_matrix_t old_map; adg_matrix_copy(&old_map, adg_entity_get_global_map(canvas));; adg_entity_set_global_map(canvas, adg_matrix_identity()); adg_entity_render(canvas, cr); adg_entity_set_global_map(canvas, &old_map); } static void _adg_do_print(GtkWidget *button, AdgCanvas *canvas) { static GtkPrintSettings *settings = NULL; GtkWindow *window; GtkPrintOperation *operation; GtkPageSetup *page_setup; GError *error; window = (GtkWindow *) gtk_widget_get_toplevel(button); operation = gtk_print_operation_new(); page_setup = adg_canvas_get_page_setup(canvas); error = NULL; if (settings) gtk_print_operation_set_print_settings(operation, settings); if (page_setup) gtk_print_operation_set_default_page_setup(operation, page_setup); g_signal_connect(operation, "begin-print", G_CALLBACK(_adg_print_prepare), NULL); g_signal_connect(operation, "draw-page", G_CALLBACK(_adg_print_page), canvas); gtk_print_operation_set_use_full_page(operation, FALSE); gtk_print_operation_set_unit(operation, GTK_UNIT_POINTS); #if GTK_CHECK_VERSION(2, 18, 0) gtk_print_operation_set_embed_page_setup(operation, TRUE); #endif switch (gtk_print_operation_run(operation, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, window, &error)) { case GTK_PRINT_OPERATION_RESULT_APPLY: if (settings) g_object_unref(settings); settings = gtk_print_operation_get_print_settings(operation); if (settings) g_object_ref(settings); break; default: break; } g_object_unref(operation); if (error) _adg_error(error->message, window); } static gboolean _adg_button_press(AdgGtkArea *area, GdkEventButton *event) { AdgCanvas *canvas; if (event->button != 1 && event->button != 3) return FALSE; canvas = adg_gtk_area_get_canvas(area); g_return_val_if_fail(ADG_IS_CANVAS(canvas), FALSE); if (event->button == 1) { /* Restore the original zoom */ adg_gtk_area_reset(area); } else if (event->button == 3) { adg_canvas_autoscale(canvas); gtk_widget_queue_draw((GtkWidget *) area); } return FALSE; } static DemoPart * _adg_part_new(GtkBuilder *builder) { DemoPart *part; GObject *object, *toggle_object; part = g_new0(DemoPart, 1); part->widgets = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref); part->area = (AdgGtkArea *) gtk_builder_get_object(builder, "mainCanvas"); part->apply = (GtkButton *) gtk_builder_get_object(builder, "btnApply"); part->reset = (GtkButton *) gtk_builder_get_object(builder, "btnReset"); part->body = adg_path_new(); part->hole = adg_path_new(); part->axis = adg_path_new(); part->title_block = adg_title_block_new(); part->edges = adg_edges_new_with_source(ADG_TRAIL(part->body)); g_assert(ADG_GTK_IS_AREA(part->area)); g_assert(GTK_IS_BUTTON(part->apply)); g_assert(GTK_IS_BUTTON(part->reset)); _adg_part_link(part, &part->A, gtk_builder_get_object(builder, "editA")); _adg_part_link(part, &part->B, gtk_builder_get_object(builder, "editB")); _adg_part_link(part, &part->C, gtk_builder_get_object(builder, "editC")); _adg_part_link(part, &part->D1, gtk_builder_get_object(builder, "editD1")); _adg_part_link(part, &part->D2, gtk_builder_get_object(builder, "editD2")); _adg_part_link(part, &part->LD2, gtk_builder_get_object(builder, "editLD2")); _adg_part_link(part, &part->D3, gtk_builder_get_object(builder, "editD3")); _adg_part_link(part, &part->LD3, gtk_builder_get_object(builder, "editLD3")); _adg_part_link(part, &part->D4, gtk_builder_get_object(builder, "editD4")); //_adg_part_link(part, &part->D5, gtk_builder_get_object(builder, "editD5")); _adg_part_link(part, &part->D6, gtk_builder_get_object(builder, "editD6")); //_adg_part_link(part, &part->LD6, gtk_builder_get_object(builder, "editLD6")); _adg_part_link(part, &part->D7, gtk_builder_get_object(builder, "editD7")); //_adg_part_link(part, &part->LD7, gtk_builder_get_object(builder, "editLD7")); _adg_part_link(part, &part->DHOLE, gtk_builder_get_object(builder, "editDHOLE")); _adg_part_link(part, &part->LHOLE, gtk_builder_get_object(builder, "editLHOLE")); g_signal_connect(part->area, "button-press-event", G_CALLBACK(_adg_button_press), NULL); toggle_object = gtk_builder_get_object(builder, "editGROOVE"); _adg_part_link(part, &part->GROOVE, toggle_object); object = gtk_builder_get_object(builder, "editZGROOVE"); _adg_part_link(part, &part->ZGROOVE, object); g_signal_connect(toggle_object, "toggled", G_CALLBACK(adg_gtk_toggle_button_sensitivize), object); object = gtk_builder_get_object(builder, "editZGROOVELabel"); g_signal_connect(toggle_object, "toggled", G_CALLBACK(adg_gtk_toggle_button_sensitivize), object); object = gtk_builder_get_object(builder, "editDGROOVE"); _adg_part_link(part, &part->DGROOVE, object); g_signal_connect(toggle_object, "toggled", G_CALLBACK(adg_gtk_toggle_button_sensitivize), object); object = gtk_builder_get_object(builder, "editDGROOVELabel"); g_signal_connect(toggle_object, "toggled", G_CALLBACK(adg_gtk_toggle_button_sensitivize), object); object = gtk_builder_get_object(builder, "editLGROOVE"); _adg_part_link(part, &part->LGROOVE, object); g_signal_connect(toggle_object, "toggled", G_CALLBACK(adg_gtk_toggle_button_sensitivize), object); object = gtk_builder_get_object(builder, "editLGROOVELabel"); g_signal_connect(toggle_object, "toggled", G_CALLBACK(adg_gtk_toggle_button_sensitivize), object); _adg_part_link(part, &part->TITLE, gtk_builder_get_object(builder, "editTITLE")); _adg_part_link(part, &part->DRAWING, gtk_builder_get_object(builder, "editDRAWING")); _adg_part_link(part, &part->AUTHOR, gtk_builder_get_object(builder, "editAUTHOR")); _adg_part_link(part, &part->DATE, gtk_builder_get_object(builder, "editDATE")); part->D5 = 4.5; part->RD34 = 1; part->LD5 = 5; part->LD6 = 1; part->LD7 = 0.5; _adg_do_edit(part); return part; } static void _adg_part_destroy(DemoPart *part) { g_hash_table_destroy(part->widgets); g_free(part); } static GtkWidget * _adg_help_window(GtkBuilder *builder) { GtkWidget *window; window = (GtkWidget *) gtk_builder_get_object(builder, "wndHelp"); g_assert(GTK_IS_MESSAGE_DIALOG(window)); g_signal_connect(window, "response", G_CALLBACK(gtk_widget_hide), NULL); return window; } static GtkWidget * _adg_about_window(GtkBuilder *builder) { GtkWidget *window; GList *icon_list, *last_node; window = (GtkWidget *) gtk_builder_get_object(builder, "wndAbout"); g_assert(GTK_IS_ABOUT_DIALOG(window)); g_signal_connect(window, "response", G_CALLBACK(gtk_widget_hide), NULL); icon_list = gtk_window_get_default_icon_list(); last_node = g_list_last(icon_list); if (last_node != NULL) { /* The last icon is supposed to be the largest one: * check adg_gtk_use_default_icons() implementation. */ GdkPixbuf *last_icon = last_node->data; gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(window), last_icon); } g_list_free(icon_list); return window; } static GtkWidget * _adg_edit_window(GtkBuilder *builder, DemoPart *part) { GtkWidget *window; window = (GtkWidget *) gtk_builder_get_object(builder, "wndEdit"); g_assert(GTK_IS_DIALOG(window)); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_MOUSE); g_signal_connect_swapped(part->apply, "clicked", G_CALLBACK(_adg_do_edit), part); g_signal_connect_swapped(part->reset, "clicked", G_CALLBACK(_adg_do_reset), part); g_signal_connect(window, "response", G_CALLBACK(adg_gtk_window_hide_here), NULL); return window; } static GtkWidget * _adg_save_as_window(GtkBuilder *builder, AdgCanvas *canvas) { GtkWidget *window; GtkWidget *type_group; window = (GtkWidget *) gtk_builder_get_object(builder, "wndSaveAs"); g_assert(GTK_IS_WINDOW(window)); type_group = (GtkWidget *) gtk_builder_get_object(builder, "saveAsPng"); g_assert(GTK_IS_RADIO_BUTTON(type_group)); g_object_set_data(G_OBJECT(window), "type-group", type_group); /* Set the default destination file */ if (GTK_IS_FILE_CHOOSER(window)) { GtkFileChooser *file_chooser; const gchar *dir; file_chooser = (GtkFileChooser *) window; #if GLIB_CHECK_VERSION(2, 14, 0) dir = g_get_user_special_dir(G_USER_DIRECTORY_DOCUMENTS); #else dir = NULL; #endif if (dir == NULL) dir = g_get_home_dir(); gtk_file_chooser_set_current_folder(file_chooser, dir); gtk_file_chooser_set_current_name(file_chooser, "adg-demo"); } g_signal_connect(window, "response", G_CALLBACK(_adg_do_save_as), canvas); return window; } static GtkWidget * _adg_main_window(GtkBuilder *builder) { gchar *path; GtkWidget *window; DemoPart *part; AdgCanvas *canvas; GtkWidget *button_edit, *button_save_as, *button_print; GtkWidget *button_help, *button_about, *button_quit; path = _demo_file("icons"); adg_gtk_use_default_icons(path); g_free(path); window = (GtkWidget *) gtk_builder_get_object(builder, "wndMain"); part = _adg_part_new(builder); canvas = adg_canvas_new(); _adg_canvas_init(canvas, part); adg_gtk_area_set_canvas(part->area, canvas); adg_canvas_autoscale(canvas); button_help = (GtkWidget *) gtk_builder_get_object(builder, "mainHelp"); g_assert(GTK_IS_BUTTON(button_help)); button_about = (GtkWidget *) gtk_builder_get_object(builder, "mainAbout"); g_assert(GTK_IS_BUTTON(button_about)); g_assert(GTK_IS_WINDOW(window)); button_edit = (GtkWidget *) gtk_builder_get_object(builder, "mainEdit"); g_assert(GTK_IS_BUTTON(button_edit)); button_save_as = (GtkWidget *) gtk_builder_get_object(builder, "mainSaveAs"); g_assert(GTK_IS_BUTTON(button_save_as)); button_print = (GtkWidget *) gtk_builder_get_object(builder, "mainPrint"); g_assert(GTK_IS_BUTTON(button_print)); button_quit = (GtkWidget *) gtk_builder_get_object(builder, "mainQuit"); g_assert(GTK_IS_BUTTON(button_quit)); g_signal_connect_swapped(button_help, "clicked", G_CALLBACK(gtk_dialog_run), _adg_help_window(builder)); g_signal_connect_swapped(button_about, "clicked", G_CALLBACK(gtk_dialog_run), _adg_about_window(builder)); g_signal_connect_swapped(button_edit, "clicked", G_CALLBACK(gtk_dialog_run), _adg_edit_window(builder, part)); g_signal_connect_swapped(button_save_as, "clicked", G_CALLBACK(gtk_dialog_run), _adg_save_as_window(builder, canvas)); g_signal_connect(button_print, "clicked", G_CALLBACK(_adg_do_print), canvas); g_signal_connect(button_quit, "clicked", G_CALLBACK(gtk_main_quit), NULL); g_signal_connect(window, "delete-event", G_CALLBACK(gtk_main_quit), NULL); g_signal_connect_swapped(window, "destroy", G_CALLBACK(_adg_part_destroy), part); return window; } int main(gint argc, gchar **argv) { gchar *path; GtkBuilder *builder; GError *error; GtkWidget *main_window; _demo_init(argc, argv); parse_args(&argc, &argv); adg_switch_extents(show_extents); path = _demo_file("adg-demo.ui"); if (path == NULL) { g_printerr(_("adg-demo.ui not found!\n")); return 1; } builder = gtk_builder_new(); error = NULL; /* Ensure AdgGtkLayout (referenced by adg-demo.ui) is registered * to possibly avoid an "Invalid object type" error */ g_type_ensure(ADG_GTK_TYPE_LAYOUT); gtk_builder_set_translation_domain(builder, GETTEXT_PACKAGE); gtk_builder_add_from_file(builder, path, &error); g_free(path); if (error != NULL) { g_critical("%s", error->message); g_error_free(error); return 2; } main_window = _adg_main_window(builder); g_object_unref(builder); gtk_widget_show_all(main_window); gtk_main(); return 0; } diff --git a/demo/cpml-demo.c b/demo/cpml-demo.c index 479b4c13..a77e6dc4 100644 --- a/demo/cpml-demo.c +++ b/demo/cpml-demo.c @@ -1,754 +1,754 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "demo.h" #include #include #include static void parse_args (gint *p_argc, gchar **p_argv[]); static cairo_path_t * duplicate_and_stroke (cairo_t *cr); static void stroke_and_destroy (cairo_t *cr, cairo_path_t *path); static void browsing (GtkWidget *widget, cairo_t *cr); static void browsing_segment (GtkToggleButton*togglebutton, gpointer user_data); static void browsing_primitive (GtkToggleButton*togglebutton, gpointer user_data); static void browsing_reset (GtkButton *button, gpointer user_data); static void browsing_next (GtkButton *button, gpointer user_data); static gboolean arcs (GtkWidget *widget, cairo_t *cr); static void arc3p (cairo_t *cr, double x1, double y1, double x2, double y2, double x3, double y3); static gboolean intersections (GtkWidget *widget, cairo_t *cr); static void algorithm_changed (GtkRadioButton *button, GtkWidget *area); static gboolean offset_curves (GtkWidget *widget, cairo_t *cr); static gboolean offset_segments (GtkWidget *widget, cairo_t *cr); #ifdef GTK2_ENABLED static void browsing_gtk2(GtkWidget *widget, GdkEventExpose *event, gpointer user_data) { cairo_t *cr = gdk_cairo_create(widget->window); browsing(widget, cr); cairo_destroy(cr); } static void arcs_gtk2(GtkWidget *widget, GdkEventExpose *event, gpointer user_data) { cairo_t *cr = gdk_cairo_create(widget->window); arcs(widget, cr); cairo_destroy(cr); } static void intersections_gtk2(GtkWidget *widget) { cairo_t *cr = gdk_cairo_create(widget->window); intersections(widget, cr); cairo_destroy(cr); } static void offset_curves_gtk2(GtkWidget *widget) { cairo_t *cr = gdk_cairo_create(widget->window); offset_curves(widget, cr); cairo_destroy(cr); } static void offset_segments_gtk2(GtkWidget *widget) { cairo_t *cr = gdk_cairo_create(widget->window); offset_segments(widget, cr); cairo_destroy(cr); } #endif static void circle_callback (cairo_t *cr); static void piston_callback (cairo_t *cr); static void curve1_callback (cairo_t *cr); static void line1_callback (cairo_t *cr); static struct { GtkWidget *area; cairo_path_t *cairo_path; gboolean use_segment; CpmlSegment segment; CpmlPrimitive primitive; } browsing_data = { NULL, }; static CpmlPair bezier_samples[][4] = { { { 0, 0 }, { 0, 40 }, { 120, 40 }, { 120, 0 } }, /* Simmetric low */ { { 40, 0 }, { 40, 160 }, { 80, 160 }, { 80, 0 } }, /* Simmetric high */ { { 0, 0 }, { 33.1371, 33.1371 }, { 86.8629, 33.1371 }, { 120, 0 } }, /* Arc approximation */ { { 0, 0 }, { 70, 120 }, { 50, 120 }, { 120, 0 } }, /* Twisted controls */ { { 0, 0 }, { 0, 120 }, { 60, 120 }, { 120, 0 } }, /* Vertical p1-p2 */ { { 0, 0 }, { 60, 120 }, { 120, 120 }, { 120, 0 } },/* Vertical p3-p4 */ { { 0, 120 }, { 120, 120 }, { 120, 60 }, { 0, 0 } },/* Horizontal p1-p2 */ { { 0, 120 }, { 120, 60 }, { 120, 0 }, { 0, 0 } }, /* Horizontal p3-p4 */ { { 0, 0 }, { 0, 120 }, { 120, 120 }, { 120, 0 } }, /* Down */ { { 0, 120 }, { 120, 120 }, { 120, 0 }, { 0, 0 } }, /* Right */ { { 0, 120 }, { 0, 0 }, { 120, 0 }, { 120, 120 } }, /* Up */ { { 120, 120 }, { 0, 120 }, { 0, 0 }, { 120, 0 } }, /* Left */ { { 0, 60 }, { 60, 120 }, { 120, 60 }, { 60, 0 } }, /* Down-right */ { { 60, 120 }, { 120, 60 }, { 60, 0 }, { 0, 60 } }, /* Up-right */ { { 120, 60 }, { 60, 0 }, { 0, 60 }, { 60, 120 } }, /* Up-left */ { { 60, 0 }, { 0, 60 }, { 60, 120 }, { 120, 60 } }, /* Down-left*/ { { 0, 0 }, { 60, 0 }, { 60, 120 }, { 120, 120 } }, /* Step left */ { { 120, 0 }, { 60, 0 }, { 60, 120 }, { 0, 120 } }, /* Step right */ { { 0, 0 }, { 60, 90 }, { 90, 120 }, { 120, 90 } }, /* Unbalanced opened */ { { 0, 0 }, { 40, 120 }, { 120, 120 }, { 60, 80 } } /* Unbalanced closed */ }; static void (*path_samples[]) (cairo_t *cr) = { circle_callback, piston_callback, curve1_callback, line1_callback, }; static void switch_page(GtkTreeSelection *selection, GtkNotebook *notebook) { GList *list = gtk_tree_selection_get_selected_rows(selection, NULL); if (list != NULL) { GtkTreePath *path = g_list_first(list)->data; gint page = path != NULL ? *gtk_tree_path_get_indices(path) : -1; g_list_free_full(list, (GDestroyNotify) gtk_tree_path_free); if (page >= 0) gtk_notebook_set_current_page(notebook, page); } } int main(gint argc, gchar **argv) { gchar *path; GtkBuilder *builder; GError *error; GtkWidget *window; _demo_init(argc, argv); parse_args(&argc, &argv); /* Prepend the package icons path */ path = _demo_file("icons"); gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), path); g_free(path); path = _demo_file("cpml-demo.ui"); if (path == NULL) { g_printerr(_("cpml-demo.ui not found!\n")); return 1; } builder = gtk_builder_new(); error = NULL; gtk_builder_set_translation_domain(builder, GETTEXT_PACKAGE); gtk_builder_add_from_file(builder, path, &error); g_free(path); if (error != NULL) { g_print("%s\n", error->message); return 2; } window = (GtkWidget *) gtk_builder_get_object(builder, "wndMain"); /* Connect signals */ g_signal_connect(window, "delete-event", G_CALLBACK(gtk_main_quit), NULL); g_signal_connect(gtk_tree_view_get_selection(GTK_TREE_VIEW(gtk_builder_get_object(builder, "tvPages"))), "changed", G_CALLBACK(switch_page), gtk_builder_get_object(builder, "nbPages")); g_signal_connect(gtk_builder_get_object(builder, "optBrowsingSegment"), "toggled", G_CALLBACK(browsing_segment), NULL); g_signal_connect(gtk_builder_get_object(builder, "optBrowsingPrimitive"), "toggled", G_CALLBACK(browsing_primitive), NULL); g_signal_connect(gtk_builder_get_object(builder, "btnBrowsingReset"), "clicked", G_CALLBACK(browsing_reset), NULL); g_signal_connect(gtk_builder_get_object(builder, "btnBrowsingNext"), "clicked", G_CALLBACK(browsing_next), NULL); g_signal_connect(gtk_builder_get_object(builder, "optAlgorithmDefault"), "toggled", G_CALLBACK(algorithm_changed), gtk_builder_get_object(builder, "areaOffsetCurves")); g_signal_connect(gtk_builder_get_object(builder, "optAlgorithmBaioca"), "toggled", G_CALLBACK(algorithm_changed), gtk_builder_get_object(builder, "areaOffsetCurves")); g_signal_connect(gtk_builder_get_object(builder, "optAlgorithmHandcraft"), "toggled", G_CALLBACK(algorithm_changed), gtk_builder_get_object(builder, "areaOffsetCurves")); g_signal_connect(gtk_builder_get_object(builder, "optAlgorithmGeometrical"), "toggled", G_CALLBACK(algorithm_changed), gtk_builder_get_object(builder, "areaOffsetCurves")); #ifdef GTK2_ENABLED g_signal_connect(gtk_builder_get_object(builder, "areaBrowsing"), "expose-event", G_CALLBACK(browsing_gtk2), NULL); g_signal_connect(gtk_builder_get_object(builder, "areaArcs"), "expose-event", G_CALLBACK(arcs_gtk2), NULL); g_signal_connect(gtk_builder_get_object(builder, "areaIntersections"), "expose-event", G_CALLBACK(intersections_gtk2), NULL); g_signal_connect(gtk_builder_get_object(builder, "areaOffsetCurves"), "expose-event", G_CALLBACK(offset_curves_gtk2), NULL); g_signal_connect(gtk_builder_get_object(builder, "areaOffsetSegments"), "expose-event", G_CALLBACK(offset_segments_gtk2), NULL); g_signal_connect(gtk_builder_get_object(builder, "btnQuit"), "clicked", G_CALLBACK(gtk_main_quit), NULL); #else g_signal_connect(gtk_builder_get_object(builder, "areaBrowsing"), "draw", G_CALLBACK(browsing), NULL); g_signal_connect(gtk_builder_get_object(builder, "areaArcs"), "draw", G_CALLBACK(arcs), NULL); g_signal_connect(gtk_builder_get_object(builder, "areaIntersections"), "draw", G_CALLBACK(intersections), NULL); g_signal_connect(gtk_builder_get_object(builder, "areaOffsetCurves"), "draw", G_CALLBACK(offset_curves), NULL); g_signal_connect(gtk_builder_get_object(builder, "areaOffsetSegments"), "draw", G_CALLBACK(offset_segments), NULL); g_signal_connect(gtk_builder_get_object(builder, "btnQuit"), "clicked", G_CALLBACK(gtk_main_quit), NULL); #endif g_object_unref(builder); gtk_widget_show_all(window); gtk_main(); return 0; } /********************************************** * Command line options parser **********************************************/ static void version(void) { g_print("cpml-demo " PACKAGE_VERSION "\n"); exit(0); } static void parse_args(gint *p_argc, gchar **p_argv[]) { GError *error = NULL; GOptionEntry entries[] = { {"version", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) version, _("Display version information"), NULL}, {NULL} }; gtk_init_with_args(p_argc, p_argv, _("- CPML demonstration program"), entries, GETTEXT_PACKAGE, &error); if (error != NULL) { gint error_code = error->code; g_printerr("%s\n", error->message); g_error_free(error); exit(error_code); } } static cairo_path_t * duplicate_and_stroke(cairo_t *cr) { cairo_path_t *path = cairo_copy_path(cr); cairo_set_line_width(cr, 2.); cairo_stroke(cr); return path; } static void stroke_and_destroy(cairo_t *cr, cairo_path_t *path) { cairo_append_path(cr, path); cairo_path_destroy(path); cairo_set_line_width(cr, 1.); cairo_stroke(cr); } static void browsing(GtkWidget *widget, cairo_t *cr) { if (browsing_data.area == NULL) { int n; /* Initialize browsing_data */ browsing_data.area = widget; browsing_data.use_segment = TRUE; /* Append all the path samples to the cairo context */ cairo_save(cr); cairo_translate(cr, 270.5, -120.5); for (n = 0; n < G_N_ELEMENTS(path_samples); ++n) { if ((n & 1) == 0) { cairo_translate(cr, -270., 240.); } else { cairo_translate(cr, 270., 0.); } (path_samples[n]) (cr); } cairo_restore(cr); browsing_data.cairo_path = cairo_copy_path(cr); cpml_segment_from_cairo(&browsing_data.segment, browsing_data.cairo_path); cpml_primitive_from_segment(&browsing_data.primitive, &browsing_data.segment); } else { cairo_append_path(cr, browsing_data.cairo_path); } cairo_set_line_width(cr, 2.); cairo_stroke(cr); cairo_set_source_rgb(cr, 1., 0.4, 0.); cairo_set_line_width(cr, 5.); if (browsing_data.use_segment) cpml_segment_to_cairo(&browsing_data.segment, cr); else cpml_primitive_to_cairo(&browsing_data.primitive, cr); cairo_stroke(cr); } static void browsing_segment(GtkToggleButton *togglebutton, gpointer user_data) { if (!gtk_toggle_button_get_active(togglebutton)) return; browsing_data.use_segment = TRUE; gtk_widget_queue_draw(browsing_data.area); } static void browsing_primitive(GtkToggleButton*togglebutton, gpointer user_data) { if (!gtk_toggle_button_get_active(togglebutton)) return; browsing_data.use_segment = FALSE; gtk_widget_queue_draw(browsing_data.area); } static void browsing_reset(GtkButton *button, gpointer user_data) { if (browsing_data.use_segment) { cpml_segment_reset(&browsing_data.segment); cpml_primitive_from_segment(&browsing_data.primitive, &browsing_data.segment); } else { cpml_primitive_reset(&browsing_data.primitive); } gtk_widget_queue_draw(browsing_data.area); } static void browsing_next(GtkButton *button, gpointer user_data) { if (browsing_data.use_segment) { cpml_segment_next(&browsing_data.segment); cpml_primitive_from_segment(&browsing_data.primitive, &browsing_data.segment); } else { cpml_primitive_next(&browsing_data.primitive); } gtk_widget_queue_draw(browsing_data.area); } static gboolean arcs(GtkWidget *widget, cairo_t *cr) { cairo_translate(cr, 100.5, 100.5); arc3p(cr, 0, 0, 0, 120, 120, 120); cairo_translate(cr, 200, 0.); arc3p(cr, 0, 0, 120, 0, 120, 120); cairo_translate(cr, 200, 0.); arc3p(cr, 60, 0, 0, 120, 120, 120); cairo_translate(cr, -400, 200); arc3p(cr, 0, 50, -2, 85, 120, 0); cairo_translate(cr, 200, 0); arc3p(cr, -2, 85, 0, 50, 120, 0); return FALSE; } static void arc3p(cairo_t *cr, double x1, double y1, double x2, double y2, double x3, double y3) { CpmlPrimitive arc; cairo_path_data_t p[4]; CpmlPair center; double r, start, end; arc.segment = NULL; arc.org = &p[0]; arc.data = &p[1]; p[1].header.type = CPML_ARC; p[1].header.length = 3; p[0].point.x = x1; p[0].point.y = y1; p[2].point.x = x2; p[2].point.y = y2; p[3].point.x = x3; p[3].point.y = y3; cpml_primitive_to_cairo(&arc, cr); cairo_set_line_width(cr, 1.); cairo_stroke(cr); /* Add an arc generated by cairo, just for reference */ if (!cpml_arc_info(&arc, ¢er, &r, &start, &end)) { g_warning(_("Unable to get arc info (%lf, %lf) (%lf, %lf) (%lf, %lf)\n"), x1, y1, x2, y2, x3, y3); return; } if (start < end) cairo_arc(cr, center.x, center.y, r-5., start, end); else cairo_arc_negative(cr, center.x, center.y, r-5., start, end); /* Show the inscribed triangle */ cairo_move_to(cr, x1, y1); cairo_line_to(cr, x2, y2); cairo_line_to(cr, x3, y3); cairo_set_line_width(cr, 0.5); cairo_stroke(cr); } static gboolean intersections(GtkWidget *widget, cairo_t *cr) { cairo_path_t *path; CpmlSegment segment1, segment2; CpmlPair intersection; cairo_translate(cr, 10.5, 120.5); line1_callback(cr); path = cairo_copy_path(cr); cairo_set_line_width(cr, 1.); cairo_stroke(cr); cpml_segment_from_cairo(&segment1, path); cpml_segment_from_cairo(&segment2, path); while (cpml_segment_next(&segment2)) { cpml_segment_put_intersections(&segment1, &segment2, 1, &intersection); cairo_arc(cr, intersection.x, intersection.y, 2.5, 0, 2 * M_PI); cairo_fill(cr); cpml_segment_next(&segment1); } cairo_path_destroy(path); return FALSE; } static void algorithm_changed(GtkRadioButton *button, GtkWidget *area) { const gchar *button_name; CpmlCurveOffsetAlgorithm new_algorithm; if (! gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) return; button_name = gtk_widget_get_name(GTK_WIDGET(button)); if (g_strcmp0(button_name, "DEFAULT") == 0) { new_algorithm = CPML_CURVE_OFFSET_ALGORITHM_DEFAULT; } else if (g_strcmp0(button_name, "BAIOCA") == 0) { new_algorithm = CPML_CURVE_OFFSET_ALGORITHM_BAIOCA; } else if (g_strcmp0(button_name, "HANDCRAFT") == 0) { new_algorithm = CPML_CURVE_OFFSET_ALGORITHM_HANDCRAFT; } else if (g_strcmp0(button_name, "GEOMETRICAL") == 0) { new_algorithm = CPML_CURVE_OFFSET_ALGORITHM_GEOMETRICAL; } else { g_warning("Unknown offset algorithm name (%s)", button_name); new_algorithm = CPML_CURVE_OFFSET_ALGORITHM_NONE; } cpml_curve_offset_algorithm(new_algorithm); gtk_widget_queue_draw(area); } static gboolean offset_curves(GtkWidget *widget, cairo_t *cr) { gint n; CpmlPair *bezier; cairo_path_t *path, *path_copy; CpmlSegment segment; CpmlPrimitive primitive; CpmlPair pair; double t; /* Add the Bézier curve samples */ for (n = 0; n < G_N_ELEMENTS(bezier_samples); ++n) { bezier = &bezier_samples[n][0]; /* The samples are arranged in a 4x? matrix of 200x150 cells */ if (n == 0) cairo_translate(cr, 25., 25.); else if (n % 4 == 0) cairo_translate(cr, -600., 150.); else cairo_translate(cr, 200., 0.); /* Draw the Bézier curve */ cairo_move_to(cr, bezier[0].x, bezier[0].y); cairo_curve_to(cr, bezier[1].x, bezier[1].y, bezier[2].x, bezier[2].y, bezier[3].x, bezier[3].y); /* Create a copy, to be used after */ path_copy = cairo_copy_path(cr); path = duplicate_and_stroke(cr); cpml_segment_from_cairo(&segment, path); cpml_segment_offset(&segment, 20.); stroke_and_destroy(cr, path); cpml_segment_from_cairo(&segment, path_copy); cpml_primitive_from_segment(&primitive, &segment); /* Draw the rays for visual debugging */ cairo_set_line_width(cr, 1.); for (t = 0; t < 1; t += 0.1) { cpml_curve_put_pair_at_time(&primitive, t, &pair); cairo_new_sub_path(cr); cairo_arc(cr, pair.x, pair.y, 2.5, 0, M_PI*2); cairo_fill(cr); cairo_move_to(cr, pair.x, pair.y); cpml_curve_put_offset_at_time(&primitive, t, 20., &pair); cairo_line_to(cr, pair.x, pair.y); cairo_stroke(cr); } cairo_path_destroy(path_copy); } return FALSE; } static gboolean offset_segments(GtkWidget *widget, cairo_t *cr) { cairo_path_t *path; CpmlSegment segment; int n; cairo_translate(cr, 270.5, -120.5); /* Offset the path samples */ for (n = 0; n < G_N_ELEMENTS(path_samples); ++n) { if ((n & 1) == 0) { cairo_translate(cr, -270., 240.); } else { cairo_translate(cr, 270., 0.); } /* Call the path callback */ (path_samples[n]) (cr); path = duplicate_and_stroke(cr); cpml_segment_from_cairo(&segment, path); cpml_segment_offset(&segment, 15.); stroke_and_destroy(cr, path); } return FALSE; } static void circle_callback(cairo_t *cr) { cairo_new_sub_path(cr); cairo_arc(cr, 120., 0., 100., 0., M_PI*2); } static void piston_callback(cairo_t *cr) { cairo_path_t *old_path, *path; cairo_matrix_t matrix; CpmlSegment segment; /* Save the previous path, if any */ old_path = cairo_copy_path(cr); cairo_new_path(cr); cairo_move_to(cr, 0., 46.5); cairo_line_to(cr, 210., 46.5); cairo_line_to(cr, 222.5, 35.); cairo_line_to(cr, 270., 35.); cairo_line_to(cr, 270., 56.); cairo_line_to(cr, 273., 59.); cairo_line_to(cr, 302., 59.); cairo_line_to(cr, 305., 56.); cairo_arc(cr, 325., 52.5, 20., G_PI, 3. * G_PI_2); cairo_line_to(cr, 400., 32.5); cairo_line_to(cr, 410., 22.5); cairo_line_to(cr, 450., 22.5); cairo_arc_negative(cr, 452., 34., 2., G_PI, G_PI_2); cairo_line_to(cr, 460., 36.); cairo_line_to(cr, 470., 30.); cairo_line_to(cr, 472., 12.5); /* Mirror a reversed copy of the current path on the y = 0 axis */ path = cairo_copy_path(cr); cpml_segment_from_cairo(&segment, path); cpml_segment_reverse(&segment); cairo_matrix_init_scale(&matrix, 1., -1.); cpml_segment_transform(&segment, &matrix); /* Join the mirrored path to the old path... */ path->data[0].header.type = CPML_LINE; cairo_append_path(cr, path); cairo_path_destroy(path); /* ...and close the shape */ cairo_close_path(cr); /* Save the resulting path and clear the path memory */ path = cairo_copy_path(cr); cairo_new_path(cr); /* Restore the previous path and reappend the new path */ cairo_append_path(cr, old_path); cairo_path_destroy(old_path); cairo_append_path(cr, path); cairo_path_destroy(path); } static void curve1_callback(cairo_t *cr) { cairo_move_to(cr, 30., 0.); cairo_curve_to(cr, 120., 120., 180., 100., 180., 20.); cairo_curve_to(cr, 180., -20., 50., 40., 150., 40.); cairo_curve_to(cr, 220., 40., 190., -60., 150., -60.); cairo_curve_to(cr, 100., -60., 80., -40., 60., -60.); } static void line1_callback(cairo_t *cr) { cairo_move_to(cr, 0, -50); cairo_line_to(cr, 100, 50); cairo_move_to(cr, 100, -50); cairo_line_to(cr, 0, 50); cairo_move_to(cr, 120, -50); cairo_line_to(cr, 200, -10); cairo_move_to(cr, 120, 50); cairo_line_to(cr, 200, 10); cairo_move_to(cr, 220, 0); cairo_line_to(cr, 280, 0); cairo_move_to(cr, 270, -40); cairo_line_to(cr, 270, 20); cairo_move_to(cr, 320, 60); cairo_line_to(cr, 380, 60); cairo_move_to(cr, 300, -40); cairo_line_to(cr, 340, 0); cairo_move_to(cr, 480, 10); cairo_line_to(cr, 400, 40); cairo_move_to(cr, 400, 40); cairo_line_to(cr, 450, -40); } diff --git a/demo/demo.c b/demo/demo.c index 1a0b5f54..2c942158 100644 --- a/demo/demo.c +++ b/demo/demo.c @@ -1,109 +1,109 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "demo.h" #include #if GLIB_CHECK_VERSION(2, 58, 0) static gchar * _demo_absolutepath(const gchar *path) { return g_canonicalize_filename(path, NULL); } #else #include #include static gchar * _demo_absolutepath(const gchar *path) { char abspath[PATH_MAX]; return g_strdup(realpath(path, abspath)); } #endif /* Base folder all installed files should be referred to. */ static gchar *pkg_data_dir = NULL; static gchar * _demo_basedir(const gchar *program) { gchar *dir, *absdir; dir = g_path_get_dirname(program); if (dir == NULL) { return NULL; } absdir = _demo_absolutepath(dir); g_free(dir); return absdir; } void _demo_init(gint argc, gchar *argv[]) { gchar *basedir; if (argc < 1 || argv == NULL || argv[0] == NULL) { g_error(_("Invalid arguments: arg[0] not set")); g_assert_not_reached(); } basedir = _demo_basedir(argv[0]); if (basedir == NULL) { /* This should never happen but... just in case */ pkg_data_dir = "."; return; } g_print("basedir = '%s'\nbuilddir= '%s'\n", basedir, BUILDDIR); if (! g_str_has_prefix(basedir, BUILDDIR)) { /* Installed program: set pkg_data_dir */ #ifdef G_OS_WIN32 gchar *parent = g_path_get_dirname(dirname); /* XXX: `pkg_data_dir` will be leaked... who cares? */ pkg_data_dir = g_build_filename(parent, PKGDATADIR, NULL); g_free(parent); #else pkg_data_dir = PKGDATADIR; #endif } g_free(basedir); } gchar * _demo_file(const gchar *file_name) { if (pkg_data_dir == NULL) { /* Running uninstalled: look up the file in BUILDDIR first and * SRCDIR later, returning the first match */ return adg_find_file(file_name, BUILDDIR, SRCDIR, NULL); } /* Running installed: look up the file only in `pkg_data_dir` */ return adg_find_file(file_name, pkg_data_dir, NULL); } diff --git a/demo/demo.h b/demo/demo.h index 5ff6c490..70712d0d 100644 --- a/demo/demo.h +++ b/demo/demo.h @@ -1,44 +1,44 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /* Needed for localization support */ #include /* Force the reinclusion of adg.h */ #undef __ADG_H__ #ifndef __DEMO_H__ #define __DEMO_H__ G_BEGIN_DECLS extern gboolean is_installed; extern gchar * basedir; void _demo_init (gint argc, gchar *argv[]); gchar * _demo_file (const gchar *file_name); G_END_DECLS #endif /* __DEMO_H__ */ diff --git a/src/adg/adg-adim-private.h b/src/adg/adg-adim-private.h index d660b401..1395df69 100644 --- a/src/adg/adg-adim-private.h +++ b/src/adg/adg-adim-private.h @@ -1,82 +1,82 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_ADIM_PRIVATE_H__ #define __ADG_ADIM_PRIVATE_H__ G_BEGIN_DECLS /* * The cairo.data array is structured in the following way: * * [0] = MOVE_TO * [1] = arc start * [2] = ARC_TO * [3] = arc middle * [4] = arc end * [5] = MOVE_TO * [6] = first extension line start * [7] = LINE_TO * [8] = first extension line end * [9] = MOVE_TO * [10] = second extension line start * [11] = LINE_TO * [12] = second extension line end */ typedef struct _AdgADimPrivate AdgADimPrivate; struct _AdgADimPrivate { AdgPoint *org1; AdgPoint *org2; gboolean has_extension1; gboolean has_extension2; AdgTrail *trail; AdgMarker *marker1; AdgMarker *marker2; gdouble angle1, angle2; struct { CpmlPair base1, base12, base2; } point; struct { CpmlPair from1, from2; CpmlPair base1, base12, base2; CpmlPair to1, to2; } shift; struct { cairo_matrix_t global_map; } quote; struct { cairo_path_t path; cairo_path_data_t data[13]; } cairo; }; G_END_DECLS #endif /* __ADG_ADIM_PRIVATE_H__ */ diff --git a/src/adg/adg-adim.c b/src/adg/adg-adim.c index 2409306d..eb4f725b 100644 --- a/src/adg/adg-adim.c +++ b/src/adg/adg-adim.c @@ -1,1215 +1,1215 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-adim * @short_description: Angular dimensions * * The #AdgADim entity defines an angular dimension. * * Since: 1.0 */ /** * AdgADim: * * All fields are privates and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-container.h" #include "adg-alignment.h" #include "adg-model.h" #include "adg-point.h" #include "adg-trail.h" #include "adg-marker.h" #include "adg-style.h" #include "adg-dim-style.h" #include "adg-dress.h" #include "adg-textual.h" #include "adg-toy-text.h" #include "adg-dim.h" #include "adg-dim-private.h" #include "adg-adim.h" #include "adg-adim-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_adim_parent_class) #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_adim_parent_class) G_DEFINE_TYPE_WITH_PRIVATE(AdgADim, adg_adim, ADG_TYPE_DIM) enum { PROP_0, PROP_ORG1, PROP_ORG2, PROP_HAS_EXTENSION1, PROP_HAS_EXTENSION2 }; static void _adg_dispose (GObject *object); static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void _adg_global_changed (AdgEntity *entity); static void _adg_local_changed (AdgEntity *entity); static void _adg_invalidate (AdgEntity *entity); static void _adg_arrange (AdgEntity *entity); static void _adg_render (AdgEntity *entity, cairo_t *cr); static gchar * _adg_default_value (AdgDim *dim); static gboolean _adg_compute_geometry (AdgDim *dim); static void _adg_update_entities (AdgADim *adim); static void _adg_unset_trail (AdgADim *adim); static void _adg_dispose_trail (AdgADim *adim); static void _adg_dispose_markers (AdgADim *adim); static gboolean _adg_get_info (AdgADim *adim, CpmlVector vector[], CpmlPair *center, gdouble *distance); static cairo_path_t * _adg_trail_callback (AdgTrail *trail, gpointer user_data); static void adg_adim_class_init(AdgADimClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; AdgDimClass *dim_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; dim_class = (AdgDimClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; entity_class->global_changed = _adg_global_changed; entity_class->local_changed = _adg_local_changed; entity_class->invalidate = _adg_invalidate; entity_class->arrange = _adg_arrange; entity_class->render = _adg_render; dim_class->default_value = _adg_default_value; dim_class->compute_geometry = _adg_compute_geometry; param = g_param_spec_boxed("org1", P_("First Origin"), P_("Where the first line comes from: this point is used toghether with \"ref1\" to align the first extension line"), ADG_TYPE_POINT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_ORG1, param); param = g_param_spec_boxed("org2", P_("Second Origin"), P_("Where the second line comes from: this point is used toghether with \"ref2\" to align the second extension line"), ADG_TYPE_POINT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_ORG2, param); param = g_param_spec_boolean("has-extension1", P_("Has First Extension Line flag"), P_("Show (TRUE) or hide (FALSE) the first extension line"), TRUE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_HAS_EXTENSION1, param); param = g_param_spec_boolean("has-extension2", P_("Has Second Extension Line flag"), P_("Show (TRUE) or hide (FALSE) the second extension line"), TRUE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_HAS_EXTENSION2, param); } static void adg_adim_init(AdgADim *adim) { AdgADimPrivate *data; AdgStyle *style; AdgDimStyle *dim_style; cairo_path_data_t move_to, line_to, arc_to; data = adg_adim_get_instance_private(adim); move_to.header.type = CPML_MOVE; move_to.header.length = 2; line_to.header.type = CPML_LINE; line_to.header.length = 2; arc_to.header.type = CPML_ARC; arc_to.header.length = 3; data->org1 = NULL; data->org2 = NULL; data->has_extension1 = TRUE; data->has_extension2 = TRUE; data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA; data->cairo.path.data = data->cairo.data; data->cairo.path.num_data = G_N_ELEMENTS(data->cairo.data); data->cairo.path.data[0] = move_to; data->cairo.path.data[2] = arc_to; data->cairo.path.data[5] = move_to; data->cairo.path.data[7] = line_to; data->cairo.path.data[9] = move_to; data->cairo.path.data[11] = line_to; data->trail = NULL; data->marker1 = NULL; data->marker2 = NULL; /* Override the default style of the dimension dress to express angles in * sexagesimal units */ style = adg_dress_get_fallback(ADG_DRESS_DIMENSION); dim_style = (AdgDimStyle *) adg_style_clone(style); adg_dim_style_set_decimals(dim_style, 0); adg_dim_style_set_rounding(dim_style, 3); adg_dim_style_set_number_arguments(dim_style, "Dm"); adg_dim_style_set_number_format(dim_style, "%g°(%g')"); adg_entity_set_style((AdgEntity *) adim, ADG_DRESS_DIMENSION, (AdgStyle *) dim_style); } static void _adg_dispose(GObject *object) { AdgEntity *entity; AdgADim *adim; AdgADimPrivate *data; entity = (AdgEntity *) object; adim = (AdgADim *) entity; data = adg_adim_get_instance_private(adim); _adg_dispose_trail(adim); _adg_dispose_markers(adim); if (data->org1 != NULL) data->org1 = adg_entity_point(entity, data->org1, NULL); if (data->org2 != NULL) data->org2 = adg_entity_point(entity, data->org2, NULL); if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgADimPrivate *data = adg_adim_get_instance_private((AdgADim *) object); switch (prop_id) { case PROP_ORG1: g_value_set_boxed(value, data->org1); break; case PROP_ORG2: g_value_set_boxed(value, data->org2); break; case PROP_HAS_EXTENSION1: g_value_set_boolean(value, data->has_extension1); break; case PROP_HAS_EXTENSION2: g_value_set_boolean(value, data->has_extension2); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgEntity *entity = (AdgEntity *) object; AdgADimPrivate *data = adg_adim_get_instance_private((AdgADim *) object); switch (prop_id) { case PROP_ORG1: data->org1 = adg_entity_point(entity, data->org1, g_value_get_boxed(value)); break; case PROP_ORG2: data->org2 = adg_entity_point(entity, data->org2, g_value_get_boxed(value)); break; case PROP_HAS_EXTENSION1: data->has_extension1 = g_value_get_boolean(value); break; case PROP_HAS_EXTENSION2: data->has_extension2 = g_value_get_boolean(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_adim_new: * * Creates a new - undefined - angular dimension. You must, at least, * define the first line by setting #AdgADim:org1 (start point) and * #AdgDim:ref1 (end point), the second line by setting #AdgADim:org2 * (start point) and #AdgDim:ref2 (end point) and the position of * the quote in #AdgDim:pos. * * Returns: the newly created angular dimension entity * * Since: 1.0 **/ AdgADim * adg_adim_new(void) { return g_object_new(ADG_TYPE_ADIM, NULL); } /** * adg_adim_new_full: * @ref1: (allow-none): first reference point * @ref2: (allow-none): second reference point * @org1: (allow-none): first origin point * @org2: (allow-none): second origin point * @pos: (allow-none): the position point * * Creates a new angular dimension, specifing all the needed * properties in one shot using #CpmlPair. * * Returns: the newly created angular dimension entity * * Since: 1.0 **/ AdgADim * adg_adim_new_full(const CpmlPair *ref1, const CpmlPair *ref2, const CpmlPair *org1, const CpmlPair *org2, const CpmlPair *pos) { AdgADim *adim; AdgDim *dim; adim = adg_adim_new(); dim = (AdgDim *) adim; if (ref1 != NULL) adg_dim_set_ref1_from_pair(dim, ref1); if (ref2 != NULL) adg_dim_set_ref2_from_pair(dim, ref2); if (pos != NULL) adg_dim_set_pos_from_pair(dim, pos); if (org1 != NULL) adg_adim_set_org1_from_pair(adim, org1); if (org2 != NULL) adg_adim_set_org2_from_pair(adim, org2); return adim; } /** * adg_adim_new_full_explicit: * @ref1_x: the x coordinate of end point of the first line * @ref1_y: the y coordinate of end point of the first line * @ref2_x: the x coordinate of end point of the second line * @ref2_y: the y coordinate of end point of the second line * @org1_x: the x coordinate of start point of the first line * @org1_y: the y coordinate of start point of the first line * @org2_x: the x coordinate of start point of the second line * @org2_y: the y coordinate of start point of the second line * @pos_x: the x coordinate of the position reference * @pos_y: the y coordinate of the position reference * * Wrappes adg_adim_new_full() with explicit values. * * Returns: the newly created linear dimension entity * * Since: 1.0 **/ AdgADim * adg_adim_new_full_explicit(gdouble ref1_x, gdouble ref1_y, gdouble ref2_x, gdouble ref2_y, gdouble org1_x, gdouble org1_y, gdouble org2_x, gdouble org2_y, gdouble pos_x, gdouble pos_y) { CpmlPair ref1, ref2, org1, org2, pos; ref1.x = ref1_x; ref1.y = ref1_y; ref2.x = ref2_x; ref2.y = ref2_y; org1.x = org1_x; org1.y = org1_y; org2.x = org2_x; org2.y = org2_y; pos.x = pos_x; pos.y = pos_y; return adg_adim_new_full(&ref1, &ref2, &org1, &org2, &pos); } /** * adg_adim_new_full_from_model: * @model: (transfer none): the model from which the named pairs are taken * @ref1: (allow-none): the end point of the first line * @ref2: (allow-none): the end point of the second line * @org1: (allow-none): the origin of the first line * @org2: (allow-none): the origin of the second line * @pos: (allow-none): the position reference * * Creates a new angular dimension, specifing all the needed properties * in one shot and using named pairs from @model. * * Returns: the newly created angular dimension entity * * Since: 1.0 **/ AdgADim * adg_adim_new_full_from_model(AdgModel *model, const gchar *ref1, const gchar *ref2, const gchar *org1, const gchar *org2, const gchar *pos) { AdgADim *adim; AdgDim *dim; adim = adg_adim_new(); dim = (AdgDim *) adim; if (ref1 != NULL) adg_dim_set_ref1_from_model(dim, model, ref1); if (ref2 != NULL) adg_dim_set_ref2_from_model(dim, model, ref2); if (pos != NULL) adg_dim_set_pos_from_model(dim, model, pos); if (org1 != NULL) adg_adim_set_org1_from_model(adim, model, org1); if (org2 != NULL) adg_adim_set_org2_from_model(adim, model, org2); return adim; } /** * adg_adim_set_org1: * @adim: an #AdgADim * @org1: the new point to use as first reference * * Sets the #AdgADim:org1 property to @org1. The old point * is silently discarded, unreferencing its model if that * point was bound to a named pair (hence, possibly destroying * the model if this was the last reference). * * @org1 can be NULL, in which case * the point is destroyed. * * Since: 1.0 **/ void adg_adim_set_org1(AdgADim *adim, const AdgPoint *org1) { g_return_if_fail(ADG_IS_ADIM(adim)); g_object_set(adim, "org1", org1, NULL); } /** * adg_adim_set_org1_explicit: * @adim: an #AdgADim * @x: x coordinate of the first reference point * @y: y coordinate of the first reference point * * Sets the #AdgADim:org1 property to the (@x, @y) explicit * coordinates. The old point is silently discarded, * unreferencing its model if that point was bound to a named * pair (hence, possibly destroying the model if this was the * last reference). * * Since: 1.0 **/ void adg_adim_set_org1_explicit(AdgADim *adim, gdouble x, gdouble y) { AdgPoint *point = adg_point_new(); adg_point_set_pair_explicit(point, x, y); adg_adim_set_org1(adim, point); adg_point_destroy(point); } /** * adg_adim_set_org1_from_pair: * @adim: an #AdgADim * @org1: the coordinates pair of the first reference point * * Convenient function to set the #AdgADim:org1 property using a * pair instead of explicit coordinates. * * Since: 1.0 **/ void adg_adim_set_org1_from_pair(AdgADim *adim, const CpmlPair *org1) { g_return_if_fail(org1 != NULL); adg_adim_set_org1_explicit(adim, org1->x, org1->y); } /** * adg_adim_set_org1_from_model: * @adim: an #AdgADim * @model: the source #AdgModel * @org1: a named pair in @model * * Binds #AdgADim:org1 to the @org1 named pair of @model. If @model * is NULL, the point will be unset. In any * case, the old point is silently discarded, unreferencing its * model if that point was bound to a named pair (hence, possibly * destroying the model if this was the last reference). * * The assignment is lazy so @org1 could be not be present in @model. * Anyway, at the first access to this point an error will be raised * if the named pair is still missing. * * Since: 1.0 **/ void adg_adim_set_org1_from_model(AdgADim *adim, AdgModel *model, const gchar *org1) { AdgPoint *point = adg_point_new(); adg_point_set_pair_from_model(point, model, org1); adg_adim_set_org1(adim, point); adg_point_destroy(point); } /** * adg_adim_get_org1: * @adim: an #AdgADim * * Gets the #AdgADim:org1 point. The returned point is internally owned * and must not be freed or modified. * * Returns: (transfer none): the first reference point * * Since: 1.0 **/ AdgPoint * adg_adim_get_org1(AdgADim *adim) { AdgADimPrivate *data; g_return_val_if_fail(ADG_IS_ADIM(adim), NULL); data = adg_adim_get_instance_private(adim); return data->org1; } /** * adg_adim_set_org2: * @adim: an #AdgADim * @org2: the new point to use as first reference * * Sets the #AdgADim:org2 property to @org2. The old point * is silently discarded, unreferencing its model if that * point was bound to a named pair (hence, possibly destroying * the model if this was the last reference). * * @org2 can be NULL, in which case * the point is destroyed. * * Since: 1.0 **/ void adg_adim_set_org2(AdgADim *adim, const AdgPoint *org2) { g_return_if_fail(ADG_IS_ADIM(adim)); g_object_set(adim, "org2", org2, NULL); } /** * adg_adim_set_org2_explicit: * @adim: an #AdgADim * @x: x coordinate of the first reference point * @y: y coordinate of the first reference point * * Sets the #AdgADim:org2 property to the (@x, @y) explicit * coordinates. The old point is silently discarded, * unreferencing its model if that point was bound to a named * pair (hence, possibly destroying the model if this was the * last reference). * * Since: 1.0 **/ void adg_adim_set_org2_explicit(AdgADim *adim, gdouble x, gdouble y) { AdgPoint *point = adg_point_new(); adg_point_set_pair_explicit(point, x, y); adg_adim_set_org2(adim, point); adg_point_destroy(point); } /** * adg_adim_set_org2_from_pair: * @adim: an #AdgADim * @org2: the coordinates pair of the first reference point * * Convenient function to set the #AdgADim:org2 property using a * pair instead of explicit coordinates. * * Since: 1.0 **/ void adg_adim_set_org2_from_pair(AdgADim *adim, const CpmlPair *org2) { g_return_if_fail(org2 != NULL); adg_adim_set_org2_explicit(adim, org2->x, org2->y); } /** * adg_adim_set_org2_from_model: * @adim: an #AdgADim * @model: the source #AdgModel * @org2: a named pair in @model * * Binds #AdgADim:org2 to the @org2 named pair of @model. If @model * is NULL, the point will be unset. In any * case, the old point is silently discarded, unreferencing its * model if that point was bound to a named pair (hence, possibly * destroying the model if this was the last reference). * * The assignment is lazy so @org2 could be not be present in @model. * Anyway, at the first access to this point an error will be raised * if the named pair is still missing. * * Since: 1.0 **/ void adg_adim_set_org2_from_model(AdgADim *adim, AdgModel *model, const gchar *org2) { AdgPoint *point = adg_point_new(); adg_point_set_pair_from_model(point, model, org2); adg_adim_set_org2(adim, point); adg_point_destroy(point); } /** * adg_adim_get_org2: * @adim: an #AdgADim * * Gets the #AdgADim:org2 point. The returned point is internally owned * and must not be freed or modified. * * Returns: (transfer none): the second reference point * * Since: 1.0 **/ AdgPoint * adg_adim_get_org2(AdgADim *adim) { AdgADimPrivate *data; g_return_val_if_fail(ADG_IS_ADIM(adim), NULL); data = adg_adim_get_instance_private(adim); return data->org2; } /** * adg_adim_switch_extension1: * @adim: an #AdgADim entity * @new_state: the new state * * Shows (if @new_state is TRUE) or hides * (if @new_state is FALSE) the first * extension line of @adim. * * Since: 1.0 **/ void adg_adim_switch_extension1(AdgADim *adim, gboolean new_state) { g_return_if_fail(ADG_IS_ADIM(adim)); g_return_if_fail(adg_is_boolean_value(new_state)); g_object_set(adim, "has-extension1", new_state, NULL); } /** * adg_adim_has_extension1: * @adim: an #AdgADim entity * * Checks if @adim should render the first extension line. * * Returns: TRUE on first extension line presents, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_adim_has_extension1(AdgADim *adim) { AdgADimPrivate *data; g_return_val_if_fail(ADG_IS_ADIM(adim), FALSE); data = adg_adim_get_instance_private(adim); return data->has_extension1; } /** * adg_adim_switch_extension2: * @adim: an #AdgADim entity * @new_state: the new new_state * * Shows (if @new_state is TRUE) or hides * (if @new_state is FALSE) the second * extension line of @adim. * * Since: 1.0 **/ void adg_adim_switch_extension2(AdgADim *adim, gboolean new_state) { g_return_if_fail(ADG_IS_ADIM(adim)); g_return_if_fail(adg_is_boolean_value(new_state)); g_object_set(adim, "has-extension2", new_state, NULL); } /** * adg_adim_has_extension2: * @adim: an #AdgADim entity * * Checks if @adim should render the second extension line. * * Returns: TRUE on first extension line presents, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_adim_has_extension2(AdgADim *adim) { AdgADimPrivate *data; g_return_val_if_fail(ADG_IS_ADIM(adim), FALSE); data = adg_adim_get_instance_private(adim); return data->has_extension2; } static void _adg_global_changed(AdgEntity *entity) { AdgADimPrivate *data = adg_adim_get_instance_private((AdgADim *) entity); _adg_unset_trail((AdgADim *) entity); if (_ADG_OLD_ENTITY_CLASS->global_changed) _ADG_OLD_ENTITY_CLASS->global_changed(entity); if (data->marker1 != NULL) adg_entity_global_changed((AdgEntity *) data->marker1); if (data->marker2 != NULL) adg_entity_global_changed((AdgEntity *) data->marker2); } static void _adg_local_changed(AdgEntity *entity) { _adg_unset_trail((AdgADim *) entity); if (_ADG_OLD_ENTITY_CLASS->local_changed) _ADG_OLD_ENTITY_CLASS->local_changed(entity); } static void _adg_invalidate(AdgEntity *entity) { AdgADim *adim = (AdgADim *) entity; AdgADimPrivate *data = adg_adim_get_instance_private(adim); _adg_dispose_trail(adim); _adg_dispose_markers(adim); _adg_unset_trail(adim); if (data->org1) adg_point_invalidate(data->org1); if (data->org2) adg_point_invalidate(data->org2); if (_ADG_OLD_ENTITY_CLASS->invalidate) _ADG_OLD_ENTITY_CLASS->invalidate(entity); } static void _adg_arrange(AdgEntity *entity) { AdgDim *dim; AdgADim *adim; AdgADimPrivate *data; AdgAlignment *quote; const cairo_matrix_t *global, *local; CpmlPair ref1, ref2, base1, base12, base2; CpmlPair pair; CpmlExtents extents; AdgEntity *marker_entity; if (_ADG_OLD_ENTITY_CLASS->arrange) _ADG_OLD_ENTITY_CLASS->arrange(entity); dim = (AdgDim *) entity; if (! adg_dim_compute_geometry(dim)) return; adim = (AdgADim *) entity; data = adg_adim_get_instance_private(adim); quote = adg_dim_get_quote(dim); _adg_update_entities(adim); if (data->cairo.path.status == CAIRO_STATUS_SUCCESS) { AdgEntity *quote_entity = (AdgEntity *) quote; adg_entity_set_global_map(quote_entity, &data->quote.global_map); return; } global = adg_entity_get_global_matrix(entity); local = adg_entity_get_local_matrix(entity); extents.is_defined = FALSE; cpml_pair_copy(&ref1, (CpmlPair *) adg_dim_get_ref1(dim)); cpml_pair_copy(&ref2, (CpmlPair *) adg_dim_get_ref2(dim)); cpml_pair_copy(&base1, &data->point.base1); cpml_pair_copy(&base12, &data->point.base12); cpml_pair_copy(&base2, &data->point.base2); /* Apply the local matrix to the relevant points */ cpml_pair_transform(&ref1, local); cpml_pair_transform(&ref2, local); cpml_pair_transform(&base1, local); cpml_pair_transform(&base12, local); cpml_pair_transform(&base2, local); /* Combine points and global shifts to build the path */ pair.x = ref1.x + data->shift.from1.x; pair.y = ref1.y + data->shift.from1.y; cpml_pair_to_cairo(&pair, &data->cairo.data[6]); pair.x = base1.x + data->shift.base1.x; pair.y = base1.y + data->shift.base1.y; cpml_pair_to_cairo(&pair, &data->cairo.data[1]); pair.x += data->shift.to1.x; pair.y += data->shift.to1.y; cpml_pair_to_cairo(&pair, &data->cairo.data[8]); pair.x = base12.x + data->shift.base12.x; pair.y = base12.y + data->shift.base12.y; cpml_pair_to_cairo(&pair, &data->cairo.data[3]); pair.x = ref2.x + data->shift.from2.x; pair.y = ref2.y + data->shift.from2.y; cpml_pair_to_cairo(&pair, &data->cairo.data[10]); pair.x = base2.x + data->shift.base2.x; pair.y = base2.y + data->shift.base2.y; cpml_pair_to_cairo(&pair, &data->cairo.data[4]); pair.x += data->shift.to2.x; pair.y += data->shift.to2.y; cpml_pair_to_cairo(&pair, &data->cairo.data[12]); /* Play with header lengths to show or hide the extension lines */ if (data->has_extension1) { data->cairo.data[7].header.length = data->has_extension2 ? 2 : 6; } else { data->cairo.data[2].header.length = data->has_extension2 ? 7 : 11; } data->cairo.path.status = CAIRO_STATUS_SUCCESS; /* Arrange the quote */ if (quote != NULL) { AdgEntity *quote_entity; gdouble angle; cairo_matrix_t map; quote_entity = (AdgEntity *) quote; angle = adg_dim_quote_angle(dim, (data->angle1 + data->angle2) / 2 + G_PI_2); cpml_pair_from_cairo(&pair, &data->cairo.data[3]); adg_alignment_set_factor_explicit(quote, 0.5, 0); cairo_matrix_init_translate(&map, pair.x, pair.y); cairo_matrix_rotate(&map, angle); adg_entity_set_global_map(quote_entity, &map); adg_entity_arrange(quote_entity); cpml_extents_add(&extents, adg_entity_get_extents(quote_entity)); adg_matrix_copy(&data->quote.global_map, &map); } /* Arrange the trail */ if (data->trail != NULL) { CpmlExtents trail_extents; cpml_extents_copy(&trail_extents, adg_trail_get_extents(data->trail)); cpml_extents_transform(&trail_extents, global); cpml_extents_add(&extents, &trail_extents); } else { _adg_dispose_markers(adim); } /* Arrange the markers */ if (data->marker1 != NULL) { marker_entity = (AdgEntity *) data->marker1; adg_marker_set_segment(data->marker1, data->trail, 1); adg_entity_local_changed(marker_entity); adg_entity_arrange(marker_entity); cpml_extents_add(&extents, adg_entity_get_extents(marker_entity)); } if (data->marker2 != NULL) { marker_entity = (AdgEntity *) data->marker2; adg_marker_set_segment(data->marker2, data->trail, 1); adg_entity_local_changed(marker_entity); adg_entity_arrange(marker_entity); cpml_extents_add(&extents, adg_entity_get_extents(marker_entity)); } adg_entity_set_extents(entity, &extents); } static void _adg_render(AdgEntity *entity, cairo_t *cr) { AdgDim *dim; AdgADim *adim; AdgADimPrivate *data; AdgDimStyle *dim_style; AdgDress dress; const cairo_path_t *cairo_path; dim = (AdgDim *) entity; if (! adg_dim_compute_geometry(dim)) { /* Entity not arranged, probably due to undefined pair found */ return; } adim = (AdgADim *) entity; data = adg_adim_get_instance_private(adim); dim_style = adg_dim_get_dim_style(dim); adg_style_apply((AdgStyle *) dim_style, entity, cr); adg_entity_render((AdgEntity *) adg_dim_get_quote(dim), cr); if (data->marker1 != NULL) adg_entity_render((AdgEntity *) data->marker1, cr); if (data->marker2 != NULL) adg_entity_render((AdgEntity *) data->marker2, cr); cairo_transform(cr, adg_entity_get_global_matrix(entity)); dress = adg_dim_style_get_line_dress(dim_style); adg_entity_apply_dress(entity, dress, cr); cairo_path = adg_trail_get_cairo_path(data->trail); cairo_append_path(cr, cairo_path); cairo_stroke(cr); } static gchar * _adg_default_value(AdgDim *dim) { AdgADim *adim; AdgADimPrivate *data; gdouble angle; if (! adg_dim_compute_geometry(dim)) { return g_strdup("undef"); } adim = (AdgADim *) dim; data = adg_adim_get_instance_private(adim); angle = (data->angle2 - data->angle1) * 180 / G_PI; return adg_dim_get_text(dim, angle); } /* With "geometry" is considered any data (point, vector or angle) * that can be cached: this is strictly related on how the arrange() * method works */ static gboolean _adg_compute_geometry(AdgDim *dim) { AdgADim *adim; AdgADimPrivate *data; AdgDimStyle *dim_style; gdouble from_offset, to_offset; gdouble spacing, level; CpmlVector vector[3]; CpmlPair center; gdouble distance; adim = (AdgADim *) dim; if (! _adg_get_info(adim, vector, ¢er, &distance)) return FALSE; data = adg_adim_get_instance_private(adim); dim_style = adg_dim_get_dim_style(dim); from_offset = adg_dim_style_get_from_offset(dim_style); to_offset = adg_dim_style_get_to_offset(dim_style); spacing = adg_dim_style_get_baseline_spacing(dim_style); level = adg_dim_get_level((AdgDim *) adim); /* shift.from1 */ cpml_vector_set_length(&vector[0], from_offset); cpml_pair_copy(&data->shift.from1, &vector[0]); /* shift.base1 */ cpml_vector_set_length(&vector[0], level * spacing); cpml_pair_copy(&data->shift.base1, &vector[0]); /* shift.to1 */ cpml_vector_set_length(&vector[0], to_offset); cpml_pair_copy(&data->shift.to1, &vector[0]); /* shift.from2 */ cpml_vector_set_length(&vector[2], from_offset); cpml_pair_copy(&data->shift.from2, &vector[2]); /* shift.base2 */ cpml_vector_set_length(&vector[2], level * spacing); cpml_pair_copy(&data->shift.base2, &vector[2]); /* shift.to2 */ cpml_vector_set_length(&vector[2], to_offset); cpml_pair_copy(&data->shift.to2, &vector[2]); /* shift.base12 */ cpml_vector_set_length(&vector[1], level * spacing); cpml_pair_copy(&data->shift.base12, &vector[1]); /* Distance can be 0, so the following will leave the * vector array in undefined state */ /* point.base1 */ cpml_vector_set_length(&vector[0], distance); data->point.base1.x = vector[0].x + center.x; data->point.base1.y = vector[0].y + center.y; /* point.base2 */ cpml_vector_set_length(&vector[2], distance); data->point.base2.x = vector[2].x + center.x; data->point.base2.y = vector[2].y + center.y; /* point.base12 */ cpml_vector_set_length(&vector[1], distance); data->point.base12.x = vector[1].x + center.x; data->point.base12.y = vector[1].y + center.y; return TRUE; } static void _adg_update_entities(AdgADim *adim) { AdgEntity *entity = (AdgEntity *) adim; AdgADimPrivate *data = adg_adim_get_instance_private(adim); AdgDimStyle *dim_style = adg_dim_get_dim_style((AdgDim *) adim); if (data->trail == NULL) data->trail = adg_trail_new(_adg_trail_callback, adim); if (data->marker1 == NULL) { data->marker1 = adg_dim_style_marker1_new(dim_style); adg_entity_set_parent((AdgEntity *) data->marker1, entity); } if (data->marker2 == NULL) { data->marker2 = adg_dim_style_marker2_new(dim_style); adg_entity_set_parent((AdgEntity *) data->marker2, entity); } } static void _adg_unset_trail(AdgADim *adim) { AdgADimPrivate *data = adg_adim_get_instance_private(adim); if (data->trail != NULL) adg_model_clear((AdgModel *) data->trail); data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA; } static void _adg_dispose_trail(AdgADim *adim) { AdgADimPrivate *data = adg_adim_get_instance_private(adim); if (data->trail != NULL) { g_object_unref(data->trail); data->trail = NULL; } } static void _adg_dispose_markers(AdgADim *adim) { AdgADimPrivate *data = adg_adim_get_instance_private(adim); if (data->marker1 != NULL) { g_object_unref(data->marker1); data->marker1 = NULL; } if (data->marker2 != NULL) { g_object_unref(data->marker2); data->marker2 = NULL; } } static gboolean _adg_get_info(AdgADim *adim, CpmlVector vector[], CpmlPair *center, gdouble *distance) { AdgDim *dim; AdgADimPrivate *data; AdgPoint *ref1_point, *ref2_point, *pos_point; const CpmlPair *ref1, *ref2, *pos; const CpmlPair *org1, *org2; gdouble factor; dim = (AdgDim *) adim; data = adg_adim_get_instance_private(adim); ref1_point = adg_dim_get_ref1(dim); ref2_point = adg_dim_get_ref2(dim); pos_point = adg_dim_get_pos(dim); if (! adg_point_update(ref1_point)) { adg_dim_geometry_missing(dim, "ref1"); return FALSE; } if (! adg_point_update(ref2_point)) { adg_dim_geometry_missing(dim, "ref2"); return FALSE; } if (! adg_point_update(pos_point)) { adg_dim_geometry_missing(dim, "pos"); return FALSE; } if (! adg_point_update(data->org1)) { adg_dim_geometry_missing(dim, "org1"); return FALSE; } if (! adg_point_update(data->org2)) { adg_dim_geometry_missing(dim, "org2"); return FALSE; } ref1 = (CpmlPair *) ref1_point; ref2 = (CpmlPair *) ref2_point; pos = (CpmlPair *) pos_point; org1 = (CpmlPair *) data->org1; org2 = (CpmlPair *) data->org2; /* Check if the given points have valid coordinates */ if (cpml_pair_equal(ref1, org1)) { adg_dim_geometry_coincident(dim, "ref1", "org1", ref1); return FALSE; } if (cpml_pair_equal(ref2, org2)) { adg_dim_geometry_coincident(dim, "ref2", "org2", ref2); return FALSE; } vector[0].x = ref1->x - org1->x; vector[0].y = ref1->y - org1->y; vector[2].x = ref2->x - org2->x; vector[2].y = ref2->y - org2->y; factor = vector[0].x * vector[2].y - vector[0].y * vector[2].x; if (factor == 0) { adg_dim_set_geometry_notice(dim, _("Trying to set an angular dimension on parallel lines")); return FALSE; } factor = ((ref1->y - ref2->y) * vector[2].x - (ref1->x - ref2->x) * vector[2].y) / factor; center->x = ref1->x + vector[0].x * factor; center->y = ref1->y + vector[0].y * factor; *distance = cpml_pair_distance(center, pos); data->angle1 = cpml_vector_angle(&vector[0]); data->angle2 = cpml_vector_angle(&vector[2]); while (data->angle2 < data->angle1) data->angle2 += G_PI * 2; cpml_vector_from_angle(&vector[1], (data->angle1 + data->angle2) / 2); return TRUE; } static cairo_path_t * _adg_trail_callback(AdgTrail *trail, gpointer user_data) { AdgADim *adim = (AdgADim *) user_data; AdgADimPrivate *data = adg_adim_get_instance_private(adim); return &data->cairo.path; } diff --git a/src/adg/adg-adim.h b/src/adg/adg-adim.h index bca433fe..f53d643a 100644 --- a/src/adg/adg-adim.h +++ b/src/adg/adg-adim.h @@ -1,109 +1,109 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_ADIM_H__ #define __ADG_ADIM_H__ G_BEGIN_DECLS #define ADG_TYPE_ADIM (adg_adim_get_type()) #define ADG_ADIM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_ADIM, AdgADim)) #define ADG_ADIM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_ADIM, AdgADimClass)) #define ADG_IS_ADIM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_ADIM)) #define ADG_IS_ADIM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_ADIM)) #define ADG_ADIM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_ADIM, AdgADimClass)) typedef struct _AdgADim AdgADim; typedef struct _AdgADimClass AdgADimClass; struct _AdgADim { /*< private >*/ AdgDim parent; }; struct _AdgADimClass { /*< private >*/ AdgDimClass parent_class; }; GType adg_adim_get_type (void); AdgADim * adg_adim_new (void); AdgADim * adg_adim_new_full (const CpmlPair *ref1, const CpmlPair *ref2, const CpmlPair *org1, const CpmlPair *org2, const CpmlPair *pos); AdgADim * adg_adim_new_full_explicit (gdouble ref1_x, gdouble ref1_y, gdouble ref2_x, gdouble ref2_y, gdouble org1_x, gdouble org1_y, gdouble org2_x, gdouble org2_y, gdouble pos_x, gdouble pos_y); AdgADim * adg_adim_new_full_from_model (AdgModel *model, const gchar *ref1, const gchar *ref2, const gchar *org1, const gchar *org2, const gchar *pos); void adg_adim_set_org1 (AdgADim *adim, const AdgPoint *org1); void adg_adim_set_org1_explicit (AdgADim *adim, gdouble x, gdouble y); void adg_adim_set_org1_from_pair (AdgADim *adim, const CpmlPair *org1); void adg_adim_set_org1_from_model (AdgADim *adim, AdgModel *model, const gchar *org1); AdgPoint * adg_adim_get_org1 (AdgADim *adim); void adg_adim_set_org2 (AdgADim *adim, const AdgPoint *org2); void adg_adim_set_org2_explicit (AdgADim *adim, gdouble x, gdouble y); void adg_adim_set_org2_from_pair (AdgADim *adim, const CpmlPair *org2); void adg_adim_set_org2_from_model (AdgADim *adim, AdgModel *model, const gchar *org2); AdgPoint * adg_adim_get_org2 (AdgADim *adim); void adg_adim_switch_extension1 (AdgADim *adim, gboolean new_state); gboolean adg_adim_has_extension1 (AdgADim *adim); void adg_adim_switch_extension2 (AdgADim *adim, gboolean new_state); gboolean adg_adim_has_extension2 (AdgADim *adim); G_END_DECLS #endif /* __ADG_ADIM_H__ */ diff --git a/src/adg/adg-alignment-private.h b/src/adg/adg-alignment-private.h index 340fd231..047b716a 100644 --- a/src/adg/adg-alignment-private.h +++ b/src/adg/adg-alignment-private.h @@ -1,37 +1,37 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_ALIGNMENT_PRIVATE_H__ #define __ADG_ALIGNMENT_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgAlignmentPrivate AdgAlignmentPrivate; struct _AdgAlignmentPrivate { CpmlPair factor; CpmlVector shift; }; G_END_DECLS #endif /* __ADG_ALIGNMENT_PRIVATE_H__ */ diff --git a/src/adg/adg-alignment.c b/src/adg/adg-alignment.c index 6f1f993d..66a6fd55 100644 --- a/src/adg/adg-alignment.c +++ b/src/adg/adg-alignment.c @@ -1,327 +1,327 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-alignment * @short_description: Base class for entity that can contain other entities * * The #AdgAlignment is an entity that can contains more sub-entities, * much in the same way as the #AdgContainer does, but allowing the * displacement of its content with an arbitrary fraction dependent * on the content itself. * * This shift is computed by multiplying the #AdgAlignment:factor * property with the extents of the bare content, with "bare" meaning * the children entities as they are rendered on the global matrix * *without* rotation components. * * To specify the alignment fraction, use adg_alignment_set_factor() and * related methods or directly set the #AdgAlignment:factor property. * For example, to center the children either in x and y, you can call * adg_alignment_set_factor_explicit(alignment, 0.5, 0.5). To align them * on the right, specify a (0, 1) factor. * * Since: 1.0 **/ /** * AdgAlignment: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-entity.h" #include "adg-container.h" #include "adg-alignment.h" #include "adg-alignment-private.h" #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_alignment_parent_class) G_DEFINE_TYPE_WITH_PRIVATE(AdgAlignment, adg_alignment, ADG_TYPE_CONTAINER) enum { PROP_0, PROP_FACTOR }; static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void _adg_arrange (AdgEntity *entity); static void _adg_render (AdgEntity *entity, cairo_t *cr); static void adg_alignment_class_init(AdgAlignmentClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; entity_class->arrange = _adg_arrange; entity_class->render = _adg_render; param = g_param_spec_boxed("factor", P_("Factor"), P_("Portion of extents, either in x and y, the content will be displaced: a (0.5, 0.5) factor means the origin is the middle point of the extents"), CPML_TYPE_PAIR, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FACTOR, param); } static void adg_alignment_init(AdgAlignment *alignment) { AdgAlignmentPrivate *data = adg_alignment_get_instance_private(alignment); data->factor.x = 0; data->factor.y = 0; data->shift.x = 0; data->shift.y = 0; } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgAlignmentPrivate *data = adg_alignment_get_instance_private((AdgAlignment *) object); switch (prop_id) { case PROP_FACTOR: g_value_set_boxed(value, &data->factor); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgAlignmentPrivate *data = adg_alignment_get_instance_private((AdgAlignment *) object); const CpmlPair *pair; switch (prop_id) { case PROP_FACTOR: pair = g_value_get_boxed(value); if (! cpml_pair_equal(&data->factor, pair)) { cpml_pair_copy(&data->factor, pair); adg_entity_invalidate((AdgEntity *) object); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); } } /** * adg_alignment_new: * @factor: the alignment factor * * Creates a new alignment container with the specified factor. * * Returns: the newly created alignment or NULL on errors. * * Since: 1.0 **/ AdgAlignment * adg_alignment_new(const CpmlPair *factor) { return g_object_new(ADG_TYPE_ALIGNMENT, "factor", factor, NULL); } /** * adg_alignment_new_explicit: * @x_factor: x component of the factor * @y_factor: y component of the factor * * Convenient function that creates a new alignment accepting explicit * factor values. * * Returns: the newly created alignment * * Since: 1.0 **/ AdgAlignment * adg_alignment_new_explicit(gdouble x_factor, gdouble y_factor) { CpmlPair factor; factor.x = x_factor; factor.y = y_factor; return adg_alignment_new(&factor); } /** * adg_alignment_set_factor: * @alignment: an #AdgAlignment container * @factor: the new factor * * Sets a the #AdgAlignment:factor property to @factor on @alignment. * The factor is applied to the @alignment extents to compute the * displacement of the content, providing a way to for instance center * the content either vertically or horizontally. A pair factor * of (0.5, 0) means the content will be centered * horizontally in reference to the normal flow without @alignment. * * Since: 1.0 **/ void adg_alignment_set_factor(AdgAlignment *alignment, const CpmlPair *factor) { g_return_if_fail(ADG_IS_ALIGNMENT(alignment)); g_object_set(alignment, "factor", factor, NULL); } /** * adg_alignment_set_factor_explicit: * @alignment: an #AdgAlignment container * @x_factor: x component of the factor * @y_factor: y component of the factor * * Convenient wrapper around adg_alignment_set_factor() that accepts * explicit factors instead of an #CpmlPair value. * * Since: 1.0 **/ void adg_alignment_set_factor_explicit(AdgAlignment *alignment, gdouble x_factor, gdouble y_factor) { CpmlPair factor; factor.x = x_factor; factor.y = y_factor; adg_alignment_set_factor(alignment, &factor); } /** * adg_alignment_get_factor: * @alignment: an #AdgAlignment container * * Gets the value of the #AdgAlignment:factor property. The returned * pair is owned by @alignment and must not be modified or freed. * * Returns: the factor pair * * Since: 1.0 **/ const CpmlPair * adg_alignment_get_factor(AdgAlignment *alignment) { AdgAlignmentPrivate *data; g_return_val_if_fail(ADG_IS_ALIGNMENT(alignment), NULL); data = adg_alignment_get_instance_private(alignment); return &data->factor; } static void _adg_arrange(AdgEntity *entity) { AdgAlignmentPrivate *data; const CpmlExtents *extents; CpmlExtents new_extents; cairo_matrix_t ctm, ctm_inverted, old_map; if (_ADG_OLD_ENTITY_CLASS->arrange == NULL) return; data = adg_alignment_get_instance_private((AdgAlignment *) entity); extents = adg_entity_get_extents(entity); data->shift.x = 0; data->shift.y = 0; /* The shift is performed only when relevant */ if (data->factor.x != 0 || data->factor.y != 0) { /* Force the ctm to be the identity matrix */ adg_matrix_copy(&old_map, adg_entity_get_global_map(entity)); adg_matrix_copy(&ctm, adg_entity_get_global_map(entity)); adg_matrix_transform(&ctm, adg_entity_get_local_matrix(entity), ADG_TRANSFORM_AFTER); adg_matrix_copy(&ctm_inverted, &ctm); cairo_matrix_invert(&ctm_inverted); adg_entity_transform_global_map(entity, &ctm_inverted, ADG_TRANSFORM_AFTER); adg_entity_global_changed(entity); /* Calculating the shift */ _ADG_OLD_ENTITY_CLASS->arrange(entity); extents = adg_entity_get_extents(entity); if (extents->is_defined) { data->shift.x = -extents->size.x * data->factor.x; data->shift.y = -extents->size.y * data->factor.y; cpml_vector_transform(&data->shift, &ctm); } /* Restore the old global map */ adg_entity_set_global_map(entity, &old_map); adg_entity_global_changed(entity); } /* Add the shift to the extents */ _ADG_OLD_ENTITY_CLASS->arrange(entity); cpml_extents_copy(&new_extents, adg_entity_get_extents(entity)); new_extents.org.x += data->shift.x; new_extents.org.y += data->shift.y; adg_entity_set_extents(entity, &new_extents); } static void _adg_render(AdgEntity *entity, cairo_t *cr) { AdgAlignmentPrivate *data; if (_ADG_OLD_ENTITY_CLASS->render == NULL) return; data = adg_alignment_get_instance_private((AdgAlignment *) entity); cairo_translate(cr, data->shift.x, data->shift.y); _ADG_OLD_ENTITY_CLASS->render(entity, cr); } diff --git a/src/adg/adg-alignment.h b/src/adg/adg-alignment.h index f3f960b5..465cb452 100644 --- a/src/adg/adg-alignment.h +++ b/src/adg/adg-alignment.h @@ -1,71 +1,71 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_ALIGNMENT_H__ #define __ADG_ALIGNMENT_H__ G_BEGIN_DECLS #define ADG_TYPE_ALIGNMENT (adg_alignment_get_type()) #define ADG_ALIGNMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_ALIGNMENT, AdgAlignment)) #define ADG_ALIGNMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_ALIGNMENT, AdgAlignmentClass)) #define ADG_IS_ALIGNMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_ALIGNMENT)) #define ADG_IS_ALIGNMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_ALIGNMENT)) #define ADG_ALIGNMENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_ALIGNMENT, AdgAlignmentClass)) typedef struct _AdgAlignment AdgAlignment; typedef struct _AdgAlignmentClass AdgAlignmentClass; struct _AdgAlignment { /*< private >*/ AdgContainer parent; }; struct _AdgAlignmentClass { /*< private >*/ AdgContainerClass parent_class; }; GType adg_alignment_get_type (void); AdgAlignment * adg_alignment_new (const CpmlPair *factor); AdgAlignment * adg_alignment_new_explicit (gdouble x_factor, gdouble y_factor); void adg_alignment_set_factor (AdgAlignment *alignment, const CpmlPair *factor); void adg_alignment_set_factor_explicit (AdgAlignment *alignment, gdouble x_factor, gdouble y_factor); const CpmlPair *adg_alignment_get_factor (AdgAlignment *alignment); G_END_DECLS #endif /* __ADG_ALIGNMENT_H__ */ diff --git a/src/adg/adg-arrow-private.h b/src/adg/adg-arrow-private.h index d5ba7ab3..e67b32e6 100644 --- a/src/adg/adg-arrow-private.h +++ b/src/adg/adg-arrow-private.h @@ -1,36 +1,36 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_ARROW_PRIVATE_H__ #define __ADG_ARROW_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgArrowPrivate AdgArrowPrivate; struct _AdgArrowPrivate { gdouble angle; }; G_END_DECLS #endif /* __ADG_ARROW_PRIVATE_H__ */ diff --git a/src/adg/adg-arrow.c b/src/adg/adg-arrow.c index c9982c60..d48f04c9 100644 --- a/src/adg/adg-arrow.c +++ b/src/adg/adg-arrow.c @@ -1,291 +1,291 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-arrow * @short_description: Arrow rendering related stuff * * Contains parameters on how to draw arrows, providing a way to register a * custom rendering callback. * * * By default, the #AdgEntity:local-mix property is set to #ADG_MIX_PARENT * on #AdgArrow entities. * * * Since: 1.0 **/ /** * AdgArrow: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-model.h" #include "adg-trail.h" #include "adg-path.h" #include "adg-marker.h" #include "adg-arrow.h" #include "adg-arrow-private.h" G_DEFINE_TYPE_WITH_PRIVATE(AdgArrow, adg_arrow, ADG_TYPE_MARKER) enum { PROP_0, PROP_ANGLE }; static void _adg_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void _adg_arrange (AdgEntity *entity); static void _adg_render (AdgEntity *entity, cairo_t *cr); static AdgModel * _adg_create_model (AdgMarker *marker); static void adg_arrow_class_init(AdgArrowClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; AdgMarkerClass *marker_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; marker_class = (AdgMarkerClass *) klass; gobject_class->set_property = _adg_set_property; gobject_class->get_property = _adg_get_property; entity_class->arrange = _adg_arrange; entity_class->render = _adg_render; marker_class->create_model = _adg_create_model; param = g_param_spec_double("angle", P_("Arrow Angle"), P_("The opening angle of the arrow"), -G_PI, G_PI, G_PI / 6, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_ANGLE, param); } static void adg_arrow_init(AdgArrow *arrow) { AdgArrowPrivate *data = adg_arrow_get_instance_private(arrow); data->angle = G_PI/6; adg_entity_set_local_mix((AdgEntity *) arrow, ADG_MIX_PARENT); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgArrowPrivate *data = adg_arrow_get_instance_private((AdgArrow *) object); switch (prop_id) { case PROP_ANGLE: g_value_set_double(value, data->angle); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgArrowPrivate *data = adg_arrow_get_instance_private((AdgArrow *) object); switch (prop_id) { case PROP_ANGLE: data->angle = cpml_angle(g_value_get_double(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_arrow_new: * * Creates a new undefined arrow entity. The position must be defined * by setting the #AdgMarker:trail and #AdgMarker:pos properties. * * Returns: the newly created arrow entity * * Since: 1.0 **/ AdgArrow * adg_arrow_new(void) { return g_object_new(ADG_TYPE_ARROW, NULL); } /** * adg_arrow_new_with_trail: * @trail: the #AdgTrail where the arrow should be added * @pos: the position ratio on @trail * * Creates a new arrow on the first segment on @trail at position * @pos, where @pos is a ratio of the @trail length (being 0 the * start point, 1 the end point, 0.5 the middle point and so on). * By default, an arrow as #AdgEntity:local-mix set to #ADG_MIX_PARENT. * * Returns: the newly created arrow entity * * Since: 1.0 **/ AdgArrow * adg_arrow_new_with_trail(AdgTrail *trail, gdouble pos) { return g_object_new(ADG_TYPE_ARROW, "local-mix", ADG_MIX_PARENT, "trail", trail, "n-segment", 1, "pos", pos, NULL); } /** * adg_arrow_set_angle: * @arrow: an #AdgArrow * @angle: the new angle * * Sets a new angle: @angle will be the new opening angle of @arrow. * Changing the arrow angle will invalidate @arrow. * * Since: 1.0 **/ void adg_arrow_set_angle(AdgArrow *arrow, gdouble angle) { g_return_if_fail(ADG_IS_ARROW(arrow)); g_object_set(arrow, "angle", angle, NULL); } /** * adg_arrow_get_angle: * @arrow: an #AdgArrow * * Gets the current angle of @arrow. * * Returns: the arrow angle, in radians * * Since: 1.0 **/ gdouble adg_arrow_get_angle(AdgArrow *arrow) { AdgArrowPrivate *data; g_return_val_if_fail(ADG_IS_ARROW(arrow), 0); data = adg_arrow_get_instance_private(arrow); return data->angle; } static void _adg_arrange(AdgEntity *entity) { AdgModel *model; const CpmlExtents *extents; CpmlExtents new_extents; model = adg_marker_model((AdgMarker *) entity); if (model == NULL) return; extents = adg_trail_get_extents((AdgTrail *) model); if (extents == NULL) return; cpml_extents_copy(&new_extents, extents); cpml_extents_transform(&new_extents, adg_entity_get_local_matrix(entity)); adg_entity_set_extents(entity, &new_extents); } static void _adg_render(AdgEntity *entity, cairo_t *cr) { AdgModel *model; const cairo_path_t *cairo_path; model = adg_marker_model((AdgMarker *) entity); if (model == NULL) return; cairo_path = adg_trail_get_cairo_path((AdgTrail *) model); if (cairo_path != NULL) { cairo_save(cr); cairo_transform(cr, adg_entity_get_global_matrix(entity)); cairo_transform(cr, adg_entity_get_local_matrix(entity)); cairo_append_path(cr, cairo_path); cairo_restore(cr); cairo_fill(cr); } } static AdgModel * _adg_create_model(AdgMarker *marker) { AdgArrowPrivate *data; AdgPath *path; CpmlPair p1, p2; data = adg_arrow_get_instance_private((AdgArrow *) marker); path = adg_path_new(); cpml_vector_from_angle(&p1, data->angle / 2); p2.x = p1.x; p2.y = -p1.y; adg_path_move_to_explicit(path, 0, 0); adg_path_line_to(path, &p1); adg_path_line_to(path, &p2); adg_path_close(path); return (AdgModel *) path; } diff --git a/src/adg/adg-arrow.h b/src/adg/adg-arrow.h index 7b085fa1..df141060 100644 --- a/src/adg/adg-arrow.h +++ b/src/adg/adg-arrow.h @@ -1,65 +1,65 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_ARROW_H__ #define __ADG_ARROW_H__ G_BEGIN_DECLS #define ADG_TYPE_ARROW (adg_arrow_get_type()) #define ADG_ARROW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_ARROW, AdgArrow)) #define ADG_ARROW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_ARROW, AdgArrowClass)) #define ADG_IS_ARROW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_ARROW)) #define ADG_IS_ARROW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_ARROW)) #define ADG_ARROW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_ARROW, AdgArrowClass)) typedef struct _AdgArrow AdgArrow; typedef struct _AdgArrowClass AdgArrowClass; struct _AdgArrow { /*< private >*/ AdgMarker parent; }; struct _AdgArrowClass { /*< private >*/ AdgMarkerClass parent_class; }; GType adg_arrow_get_type (void); AdgArrow * adg_arrow_new (void); AdgArrow * adg_arrow_new_with_trail (AdgTrail *trail, gdouble pos); void adg_arrow_set_angle (AdgArrow *arrow, gdouble angle); gdouble adg_arrow_get_angle (AdgArrow *arrow); G_END_DECLS #endif /* __ADG_ARROW_H__ */ diff --git a/src/adg/adg-cairo-fallback.c b/src/adg/adg-cairo-fallback.c index b9b04161..441a359a 100644 --- a/src/adg/adg-cairo-fallback.c +++ b/src/adg/adg-cairo-fallback.c @@ -1,78 +1,78 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "adg-internal.h" #include #include #include "adg-cairo-fallback.h" #if CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 14, 0) void cairo_surface_set_device_scale(cairo_surface_t *surface, double x_scale, double y_scale) { } #endif /* cairo < 1.14.0 */ #ifdef ADG_MISSING_GBOXED_MATRIX GType cairo_gobject_matrix_get_type(void) { static GType matrix_type = 0; if (G_UNLIKELY(matrix_type == 0)) matrix_type = g_boxed_type_register_static("CairoMatrix", (GBoxedCopyFunc) cairo_gobject_cairo_matrix_copy, g_free); return matrix_type; } cairo_matrix_t * cairo_gobject_cairo_matrix_copy(const cairo_matrix_t *matrix) { return cpml_memdup(matrix, sizeof(cairo_matrix_t)); } #endif /* ADG_MISSING_GBOXED_MATRIX */ #ifdef ADG_MISSING_GBOXED_PATTERN GType cairo_gobject_pattern_get_type(void) { static GType pattern_type = 0; if (G_UNLIKELY(pattern_type == 0)) pattern_type = g_boxed_type_register_static("CairoPattern", (GBoxedCopyFunc) cairo_pattern_reference, (GBoxedFreeFunc) cairo_pattern_destroy); return pattern_type; } #endif /* ADG_MISSING_GBOXED_PATTERN */ diff --git a/src/adg/adg-cairo-fallback.h b/src/adg/adg-cairo-fallback.h index fc257cc9..4af7a528 100644 --- a/src/adg/adg-cairo-fallback.h +++ b/src/adg/adg-cairo-fallback.h @@ -1,71 +1,71 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_CAIRO_FALLBACK_H__ #define __ADG_CAIRO_FALLBACK_H__ G_BEGIN_DECLS /* Provide some GObject wrappers around cairo_matrix_t and cairo_pattern_t * if cairo-gobject support has not been provided by cairo itself or * if the cairo version is too old to provide them. */ #if CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 14, 0) #warning "Media scaling with adg_canvas_set_factor() not supported" void cairo_surface_set_device_scale (cairo_surface_t *surface, double x_scale, double y_scale); #endif /* cairo < 1.14.0 */ #ifndef CAIRO_GOBJECT_TYPE_MATRIX #define ADG_MISSING_GBOXED_MATRIX 1 #define CAIRO_GOBJECT_TYPE_MATRIX (cairo_gobject_matrix_get_type()) GType cairo_gobject_matrix_get_type (void); cairo_matrix_t *cairo_gobject_cairo_matrix_copy (const cairo_matrix_t *matrix); #endif /* CAIRO_GOBJECT_TYPE_MATRIX */ #ifndef CAIRO_GOBJECT_TYPE_PATTERN #define ADG_MISSING_GBOXED_PATTERN 1 #define CAIRO_GOBJECT_TYPE_PATTERN (cairo_gobject_pattern_get_type()) GType cairo_gobject_pattern_get_type (void); cairo_pattern_t * cairo_gobject_cairo_pattern_copy (const cairo_pattern_t *pattern); #endif /* CAIRO_GOBJECT_TYPE_PATTERN */ G_END_DECLS #endif /* __ADG_CAIRO_FALLBACK_H__ */ diff --git a/src/adg/adg-canvas-private.h b/src/adg/adg-canvas-private.h index ce4ec143..01f840ef 100644 --- a/src/adg/adg-canvas-private.h +++ b/src/adg/adg-canvas-private.h @@ -1,44 +1,44 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_CANVAS_PRIVATE_H__ #define __ADG_CANVAS_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgCanvasPrivate AdgCanvasPrivate; struct _AdgCanvasPrivate { CpmlPair size; gdouble factor; gchar **scales; AdgDress background_dress; AdgDress frame_dress; AdgTitleBlock *title_block; gdouble top_margin, right_margin, bottom_margin, left_margin; gboolean has_frame; gdouble top_padding, right_padding, bottom_padding, left_padding; }; G_END_DECLS #endif /* __ADG_CANVAS_PRIVATE_H__ */ diff --git a/src/adg/adg-canvas.c b/src/adg/adg-canvas.c index 1253d4a1..87b9b4b4 100644 --- a/src/adg/adg-canvas.c +++ b/src/adg/adg-canvas.c @@ -1,1888 +1,1888 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-canvas * @short_description: The drawing container * * The canvas is the toplevel entity of an ADG drawing. It can be * bound to a GTK+ widget, such as #AdgGtkArea, or manually rendered * to a custom surface. * * Tipically, the canvas contains the description and properties of * the media used, such as such as size (if relevant), margins, * border and paddings. This approach clearly follows the block model * of the CSS specification. * * The paddings specify the distance between the entities contained * by the canvas and the border. The margins specify the distance * between the canvas border and the media extents. * * The canvas (hence the media) size can be explicitely specified * by directly writing to the #AdgCanvas:size property or using any * valid setter, such as adg_canvas_set_size(), * adg_canvas_set_size_explicit() or the convenient * adg_canvas_set_paper() GTK+ wrapper. You can also set explicitely * only one dimension and let the other one be computed automatically. * This can be done by setting it to 0. * The ratio between the media unit (typically points on printing * surfaces and pixels on video surfaces) can be changed with * adg_canvas_set_factor(). * * By default either width and height are autocalculated, i.e. they * are initially set to 0. In this case the arrange() phase is executed. * Margins and paddings are then added to the extents to get the * border coordinates and the final bounding box. * * Instead, when the size is explicitely set, the final bounding * box is forcibly set to this value without taking the canvas * extents into account. The margins are then subtracted to get * the coordinates of the border. In this case the paddings are * simply ignoredby the arrange phase. They are still used by * adg_canvas_autoscale() though, if called. * * Since: 1.0 **/ /** * AdgCanvas: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ /** * ADG_CANVAS_ERROR: * * Error domain for canvas processing. Errors in this domain will be from the * #AdgCanvasError enumeration. See #GError for information on error domains. * * Since: 1.0 **/ /** * AdgCanvasError: * @ADG_CANVAS_ERROR_SURFACE: Invalid surface type. * @ADG_CANVAS_ERROR_CAIRO: The underlying cairo library returned an error. * * Error codes returned by #AdgCanvas methods. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-container.h" #include "adg-table.h" #include "adg-title-block.h" #include "adg-style.h" #include "adg-color-style.h" #include "adg-dress.h" #include "adg-param-dress.h" #include #include "adg-canvas-private.h" #ifdef CAIRO_HAS_PS_SURFACE #include #endif #ifdef CAIRO_HAS_PDF_SURFACE #include #endif #ifdef CAIRO_HAS_SVG_SURFACE #include #endif #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_canvas_parent_class) #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_canvas_parent_class) G_DEFINE_TYPE_WITH_PRIVATE(AdgCanvas, adg_canvas, ADG_TYPE_CONTAINER) enum { PROP_0, PROP_SIZE, PROP_FACTOR, PROP_SCALES, PROP_BACKGROUND_DRESS, PROP_FRAME_DRESS, PROP_TITLE_BLOCK, PROP_TOP_MARGIN, PROP_RIGHT_MARGIN, PROP_BOTTOM_MARGIN, PROP_LEFT_MARGIN, PROP_HAS_FRAME, PROP_TOP_PADDING, PROP_RIGHT_PADDING, PROP_BOTTOM_PADDING, PROP_LEFT_PADDING }; static void _adg_dispose (GObject *object); static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void _adg_global_changed (AdgEntity *entity); static void _adg_local_changed (AdgEntity *entity); static void _adg_invalidate (AdgEntity *entity); static void _adg_arrange (AdgEntity *entity); static void _adg_render (AdgEntity *entity, cairo_t *cr); static void _adg_apply_paddings (AdgCanvas *canvas, CpmlExtents *extents); static void _adg_update_margin (AdgCanvas *canvas, gdouble *margin, gdouble *side, gdouble new_margin); /** * adg_canvas_error_quark: * * Registers an error quark specific for #AdgCanvas. * * Returns: The error quark used for #AdgCanvas errors. * * Since: 1.0 **/ GQuark adg_canvas_error_quark(void) { static GQuark q; if G_UNLIKELY (q == 0) q = g_quark_from_static_string("adg-canvas-error-quark"); return q; } static void adg_canvas_class_init(AdgCanvasClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; entity_class->global_changed = _adg_global_changed; entity_class->local_changed = _adg_local_changed; entity_class->invalidate = _adg_invalidate; entity_class->arrange = _adg_arrange; entity_class->render = _adg_render; param = g_param_spec_boxed("size", P_("Canvas Size"), P_("The size set on this canvas: use 0 to have an automatic dimension based on the canvas extents"), CPML_TYPE_PAIR, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_SIZE, param); param = g_param_spec_double("factor", P_("Factor"), P_("Global space units per point (1 point == 1/72 inch)"), 0, G_MAXDOUBLE, 1, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FACTOR, param); param = g_param_spec_boxed("scales", P_("Valid Scales"), P_("List of scales to be tested while autoscaling the drawing"), G_TYPE_STRV, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_SCALES, param); param = adg_param_spec_dress("background-dress", P_("Background Dress"), P_("The color dress to use for the canvas background"), ADG_DRESS_COLOR_BACKGROUND, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_BACKGROUND_DRESS, param); param = adg_param_spec_dress("frame-dress", P_("Frame Dress"), P_("Line dress to use while drawing the frame around the canvas"), ADG_DRESS_LINE_FRAME, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FRAME_DRESS, param); param = g_param_spec_object("title-block", P_("Title Block"), P_("The title block to assign to this canvas"), ADG_TYPE_TITLE_BLOCK, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_TITLE_BLOCK, param); param = g_param_spec_double("top-margin", P_("Top Margin"), P_("The margin (in global space) to leave above the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_TOP_MARGIN, param); param = g_param_spec_double("right-margin", P_("Right Margin"), P_("The margin (in global space) to leave empty at the right of the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_RIGHT_MARGIN, param); param = g_param_spec_double("bottom-margin", P_("Bottom Margin"), P_("The margin (in global space) to leave empty below the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_BOTTOM_MARGIN, param); param = g_param_spec_double("left-margin", P_("Left Margin"), P_("The margin (in global space) to leave empty at the left of the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LEFT_MARGIN, param); param = g_param_spec_boolean("has-frame", P_("Has Frame Flag"), P_("If enabled, a frame using the frame dress will be drawn around the canvas extents, taking into account the margins"), TRUE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_HAS_FRAME, param); param = g_param_spec_double("top-padding", P_("Top Padding"), P_("The padding (in global space) to leave empty above between the drawing and the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_TOP_PADDING, param); param = g_param_spec_double("right-padding", P_("Right Padding"), P_("The padding (in global space) to leave empty at the right between the drawing and the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_RIGHT_PADDING, param); param = g_param_spec_double("bottom-padding", P_("Bottom Padding"), P_("The padding (in global space) to leave empty below between the drawing and the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_BOTTOM_PADDING, param); param = g_param_spec_double("left-padding", P_("Left Padding"), P_("The padding (in global space) to leave empty at the left between the drawing and the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LEFT_PADDING, param); } static void adg_canvas_init(AdgCanvas *canvas) { AdgCanvasPrivate *data = adg_canvas_get_instance_private(canvas); const gchar *scales[] = { "10:1", "5:1", "3:1", "2:1", "1:1", "1:2", "1:3", "1:5", "1:10", NULL }; data->size.x = 0; data->size.y = 0; data->factor = 1; data->scales = g_strdupv((gchar **) scales); data->background_dress = ADG_DRESS_COLOR_BACKGROUND; data->frame_dress = ADG_DRESS_LINE_FRAME; data->title_block = NULL; data->top_margin = 15; data->right_margin = 15; data->bottom_margin = 15; data->left_margin = 15; data->has_frame = TRUE; data->top_padding = 15; data->right_padding = 15; data->bottom_padding = 15; data->left_padding = 15; } static void _adg_dispose(GObject *object) { AdgCanvas *canvas = (AdgCanvas *) object; AdgCanvasPrivate *data = adg_canvas_get_instance_private(canvas); if (data->title_block) { g_object_unref(data->title_block); data->title_block = NULL; } if (data->scales != NULL) { g_strfreev(data->scales); data->scales = NULL; } if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgCanvasPrivate *data = adg_canvas_get_instance_private((AdgCanvas *) object); switch (prop_id) { case PROP_SIZE: g_value_set_boxed(value, &data->size); break; case PROP_FACTOR: g_value_set_double(value, data->factor); break; case PROP_SCALES: g_value_set_boxed(value, data->scales); break; case PROP_BACKGROUND_DRESS: g_value_set_enum(value, data->background_dress); break; case PROP_FRAME_DRESS: g_value_set_enum(value, data->frame_dress); break; case PROP_TITLE_BLOCK: g_value_set_object(value, data->title_block); break; case PROP_TOP_MARGIN: g_value_set_double(value, data->top_margin); break; case PROP_RIGHT_MARGIN: g_value_set_double(value, data->right_margin); break; case PROP_BOTTOM_MARGIN: g_value_set_double(value, data->bottom_margin); break; case PROP_LEFT_MARGIN: g_value_set_double(value, data->left_margin); break; case PROP_HAS_FRAME: g_value_set_boolean(value, data->has_frame); break; case PROP_TOP_PADDING: g_value_set_double(value, data->top_padding); break; case PROP_RIGHT_PADDING: g_value_set_double(value, data->right_padding); break; case PROP_BOTTOM_PADDING: g_value_set_double(value, data->bottom_padding); break; case PROP_LEFT_PADDING: g_value_set_double(value, data->left_padding); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgCanvas *canvas = (AdgCanvas *) object; AdgCanvasPrivate *data = adg_canvas_get_instance_private(canvas); AdgTitleBlock *title_block; gdouble factor; switch (prop_id) { case PROP_SIZE: cpml_pair_copy(&data->size, g_value_get_boxed(value)); break; case PROP_FACTOR: factor = g_value_get_double(value); g_return_if_fail(factor > 0); data->factor = factor; break; case PROP_SCALES: g_strfreev(data->scales); data->scales = g_value_dup_boxed(value); break; case PROP_BACKGROUND_DRESS: data->background_dress = g_value_get_enum(value); break; case PROP_FRAME_DRESS: data->frame_dress = g_value_get_enum(value); break; case PROP_TITLE_BLOCK: title_block = g_value_get_object(value); if (title_block) { g_object_ref(title_block); adg_entity_set_parent((AdgEntity *) title_block, (AdgEntity *) canvas); } if (data->title_block) g_object_unref(data->title_block); data->title_block = title_block; break; case PROP_TOP_MARGIN: _adg_update_margin(canvas, &data->top_margin, &data->size.y, g_value_get_double(value)); break; case PROP_RIGHT_MARGIN: _adg_update_margin(canvas, &data->right_margin, &data->size.x, g_value_get_double(value)); break; case PROP_BOTTOM_MARGIN: _adg_update_margin(canvas, &data->bottom_margin, &data->size.y, g_value_get_double(value)); break; case PROP_LEFT_MARGIN: _adg_update_margin(canvas, &data->left_margin, &data->size.x, g_value_get_double(value)); break; case PROP_HAS_FRAME: data->has_frame = g_value_get_boolean(value); break; case PROP_TOP_PADDING: data->top_padding = g_value_get_double(value); break; case PROP_RIGHT_PADDING: data->right_padding = g_value_get_double(value); break; case PROP_BOTTOM_PADDING: data->bottom_padding = g_value_get_double(value); break; case PROP_LEFT_PADDING: data->left_padding = g_value_get_double(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_canvas_new: * * Creates a new empty canvas object. * * Returns: (transfer full): the newly created canvas. * * Since: 1.0 **/ AdgCanvas * adg_canvas_new(void) { return g_object_new(ADG_TYPE_CANVAS, NULL); } /** * adg_canvas_set_size: * @canvas: an #AdgCanvas * @size: (transfer none): the new size for the canvas * * Sets a specific size on @canvas. The x and/or y * component of @size can be set to 0, in which case * the exact value will be autocalculated, that is the * size component returned by adg_entity_get_extents() * on @canvas will be used instead. * * Since: 1.0 **/ void adg_canvas_set_size(AdgCanvas *canvas, const CpmlPair *size) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(size != NULL); g_object_set(canvas, "size", size, NULL); } /** * adg_canvas_set_size_explicit: * @canvas: an #AdgCanvas * @x: the new width of the canvas or 0 to reset * @y: the new height of the canvas or 0 to reset * * A convenient function to set the size of @canvas using * explicit coordinates. Check adg_canvas_set_size() for * further details. * * Since: 1.0 **/ void adg_canvas_set_size_explicit(AdgCanvas *canvas, gdouble x, gdouble y) { CpmlPair size; size.x = x; size.y = y; adg_canvas_set_size(canvas, &size); } /** * adg_canvas_get_size: * @canvas: an #AdgCanvas * * Gets the specific size set on @canvas. The x and/or y * components of the returned #CpmlPair could be 0, in which * case the size returned by adg_entity_get_extents() on @canvas * will be used instead. * * Returns: (transfer none): the explicit size set on this canvas or NULL on errors. * * Since: 1.0 **/ const CpmlPair * adg_canvas_get_size(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL); data = adg_canvas_get_instance_private(canvas); return &data->size; } /** * adg_canvas_set_factor: * @canvas: an #AdgCanvas * @factor: the new factor: must be greater than 0 * * The ADG library is intentionally unit agnostic, i.e. the global space is * represented in whatever unit you want. There are a couple of cairo APIs * that explicitely requires points though, most notably * cairo_pdf_surface_set_size() and cairo_ps_surface_set_size(). * * On PDF and postscript surfaces, the AdgCanvas:factor property will be the * number typography points per global space units: * https://en.wikipedia.org/wiki/Point_(typography) * * For other surfaces, the factor will be used to scale the final surface to * the specific number of pixels. * * As an example, if you are working in millimeters you will set a factor of * 2.83465 on PDF and postscript surfaces (1 mm = 2.83465 points) so the * drawing will show the correct scale on paper. For X11 and PNG surfaces you * will set it depending on the resolution you want to get, e.g. if the * drawing is 100x200 mm and you want a 1000x2000 image, just set it to 10. * * Since: 1.0 **/ void adg_canvas_set_factor(AdgCanvas *canvas, double factor) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(factor > 0); g_object_set(canvas, "factor", factor, NULL); } /** * adg_canvas_get_factor: * @canvas: an #AdgCanvas * * Gets the current factor of @canvas. See adg_canvas_set_factor() to learn * what a factor is in this context. * * Returns: the canvas factor or 0 on error. * * Since: 1.0 **/ double adg_canvas_get_factor(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->factor; } /** * adg_canvas_set_scales: * @canvas: an #AdgCanvas * @...: NULL terminated list of strings * * Sets the scales allowed by @canvas. Every scale identifies a * specific factor to be applied to the local matrix of @canvas. * When adg_canvas_autoscale() will be called, the greatest * scale that can render every entity inside a box of * #AdgCanvas:size dimensions will be applied. The drawing will * be centered inside that box. * * Every scale should be expressed with a string in the form of * "x:y", where x and y are positive integers that identifies * numerator an denominator of a fraction. That string itself * will be put into the title block when used. * * Since: 1.0 **/ void adg_canvas_set_scales(AdgCanvas *canvas, ...) { va_list var_args; va_start(var_args, canvas); adg_canvas_set_scales_valist(canvas, var_args); va_end(var_args); } /** * adg_canvas_set_scales_valist: * @canvas: an #AdgCanvas * @var_args: NULL terminated list of strings * * Vararg variant of adg_canvas_set_scales(). * * Since: 1.0 **/ void adg_canvas_set_scales_valist(AdgCanvas *canvas, va_list var_args) { gchar **scales; const gchar *scale; gint n; g_return_if_fail(ADG_IS_CANVAS(canvas)); scales = NULL; n = 0; while ((scale = va_arg(var_args, const gchar *)) != NULL) { ++n; scales = g_realloc(scales, sizeof(const gchar *) * (n + 1)); scales[n-1] = g_strdup(scale); scales[n] = NULL; } g_object_set(canvas, "scales", scales, NULL); g_strfreev(scales); } /** * adg_canvas_set_scales_array: (rename-to adg_canvas_set_scales) * @canvas: an #AdgCanvas * @scales: (array zero-terminated=1) (allow-none): NULL terminated array of scales * * Array variant of adg_canvas_set_scales(). * * Since: 1.0 **/ void adg_canvas_set_scales_array(AdgCanvas *canvas, gchar **scales) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "scales", scales, NULL); } /** * adg_canvas_get_scales: * @canvas: an #AdgCanvas * * Gets the list of scales set on @canvas: check adg_canvas_set_scales() * to get an idea of what scales are supposed to be. * * If no scales are set, NULL is returned. * * Returns: (element-type utf8) (transfer none): the NULL terminated list of valid scales. * * Since: 1.0 **/ gchar ** adg_canvas_get_scales(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL); data = adg_canvas_get_instance_private(canvas); return data->scales; } /** * adg_canvas_autoscale: * @canvas: an #AdgCanvas * * Applies one scale per time, in the order they have been provided * in the adg_canvas_set_scales() call, until the drawing can be * entirely contained into the current paper. If successful, the * scale of the title block is changed accordingly and the drawing * is centered inside the paper. * * The paddings are taken into account while computing the drawing * extents. * * Since: 1.0 **/ void adg_canvas_autoscale(AdgCanvas *canvas) { AdgCanvasPrivate *data; gchar **p_scale; AdgEntity *entity; cairo_matrix_t map; CpmlExtents extents; AdgTitleBlock *title_block; CpmlPair delta; g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(_ADG_OLD_ENTITY_CLASS->arrange != NULL); data = adg_canvas_get_instance_private(canvas); entity = (AdgEntity *) canvas; title_block = data->title_block; /* Manually calling the arrange() method instead of emitting the "arrange" * signal does not invalidate the global matrix: let's do it right now */ adg_entity_global_changed(entity); for (p_scale = data->scales; p_scale != NULL && *p_scale != NULL; ++p_scale) { const gchar *scale = *p_scale; gdouble factor = adg_scale_factor(scale); if (factor <= 0) continue; cairo_matrix_init_scale(&map, factor, factor); adg_entity_set_local_map(entity, &map); adg_entity_local_changed(entity); /* Arrange the entities inside the canvas, but not the canvas itself, * just to get the bounding box of the drawing without the paper */ _ADG_OLD_ENTITY_CLASS->arrange(entity); cpml_extents_copy(&extents, adg_entity_get_extents(entity)); /* Just in case @canvas is empty */ if (! extents.is_defined) return; _adg_apply_paddings(canvas, &extents); if (title_block != NULL) adg_title_block_set_scale(title_block, scale); /* Bail out if paper size is not specified or invalid */ if (data->size.x <= 0 || data->size.y <= 0) break; /* If the drawing extents are fully contained inside the paper size, * center the drawing in the paper and bail out */ delta.x = data->size.x - extents.size.x; delta.y = data->size.y - extents.size.y; if (delta.x >= 0 && delta.y >= 0) { cairo_matrix_t transform; cairo_matrix_init_translate(&transform, delta.x / 2 - extents.org.x, delta.y / 2 - extents.org.y); adg_entity_transform_local_map(entity, &transform, ADG_TRANSFORM_AFTER); break; } } } /** * adg_canvas_set_background_dress: * @canvas: an #AdgCanvas * @dress: the new #AdgDress to use * * Sets a new background dress for rendering @canvas: the new * dress must be a color dress. * * Since: 1.0 **/ void adg_canvas_set_background_dress(AdgCanvas *canvas, AdgDress dress) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "background-dress", dress, NULL); } /** * adg_canvas_get_background_dress: * @canvas: an #AdgCanvas * * Gets the background dress to be used in rendering @canvas. * * Returns: the current background dress. * * Since: 1.0 **/ AdgDress adg_canvas_get_background_dress(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), ADG_DRESS_UNDEFINED); data = adg_canvas_get_instance_private(canvas); return data->background_dress; } /** * adg_canvas_set_frame_dress: * @canvas: an #AdgCanvas * @dress: the new #AdgDress to use * * Sets the #AdgCanvas:frame-dress property of @canvas to @dress: * the new dress must be a line dress. * * Since: 1.0 **/ void adg_canvas_set_frame_dress(AdgCanvas *canvas, AdgDress dress) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "frame-dress", dress, NULL); } /** * adg_canvas_get_frame_dress: * @canvas: an #AdgCanvas * * Gets the frame dress to be used in rendering the border of @canvas. * * Returns: the current frame dress. * * Since: 1.0 **/ AdgDress adg_canvas_get_frame_dress(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), ADG_DRESS_UNDEFINED); data = adg_canvas_get_instance_private(canvas); return data->frame_dress; } /** * adg_canvas_set_title_block: * @canvas: an #AdgCanvas * @title_block: (transfer full): a title block * * Sets the #AdgCanvas:title-block property of @canvas to @title_block. * * Although a title block entity could be added to @canvas in the usual * way, that is using the adg_container_add() method, assigning a title * block with adg_canvas_set_title_block() is somewhat different: * * - @title_block will be automatically attached to the bottom right * corner of to the @canvas frame (this could be accomplished in the * usual way too, by resetting the right and bottom paddings); * - the @title_block boundary box is not taken into account while * computing the extents of @canvas. * * Since: 1.0 **/ void adg_canvas_set_title_block(AdgCanvas *canvas, AdgTitleBlock *title_block) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(title_block == NULL || ADG_IS_TITLE_BLOCK(title_block)); g_object_set(canvas, "title-block", title_block, NULL); } /** * adg_canvas_get_title_block: * @canvas: an #AdgCanvas * * Gets the #AdgTitleBlock object of @canvas: check * adg_canvas_set_title_block() for details. * * The returned entity is owned by @canvas and should not be * modified or freed. * * Returns: (transfer none): the title block object or NULL. * * Since: 1.0 **/ AdgTitleBlock * adg_canvas_get_title_block(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL); data = adg_canvas_get_instance_private(canvas); return data->title_block; } /** * adg_canvas_set_top_margin: * @canvas: an #AdgCanvas * @value: the new margin, in global space * * Changes the top margin of @canvas by setting #AdgCanvas:top-margin * to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_top_margin(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "top-margin", value, NULL); } /** * adg_canvas_get_top_margin: * @canvas: an #AdgCanvas * * Gets the top margin (in global space) of @canvas. * * Returns: the requested margin or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_top_margin(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->top_margin; } /** * adg_canvas_set_right_margin: * @canvas: an #AdgCanvas * @value: the new margin, in global space * * Changes the right margin of @canvas by setting * #AdgCanvas:right-margin to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_right_margin(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "right-margin", value, NULL); } /** * adg_canvas_get_right_margin: * @canvas: an #AdgCanvas * * Gets the right margin (in global space) of @canvas. * * Returns: the requested margin or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_right_margin(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->right_margin; } /** * adg_canvas_set_bottom_margin: * @canvas: an #AdgCanvas * @value: the new margin, in global space * * Changes the bottom margin of @canvas by setting * #AdgCanvas:bottom-margin to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_bottom_margin(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "bottom-margin", value, NULL); } /** * adg_canvas_get_bottom_margin: * @canvas: an #AdgCanvas * * Gets the bottom margin (in global space) of @canvas. * * Returns: the requested margin or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_bottom_margin(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->bottom_margin; } /** * adg_canvas_set_left_margin: * @canvas: an #AdgCanvas * @value: the new margin, in global space * * Changes the left margin of @canvas by setting * #AdgCanvas:left-margin to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_left_margin(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "left-margin", value, NULL); } /** * adg_canvas_get_left_margin: * @canvas: an #AdgCanvas * * Gets the left margin (in global space) of @canvas. * * Returns: the requested margin or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_left_margin(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->left_margin; } /** * adg_canvas_set_margins: * @canvas: an #AdgCanvas * @top: top margin, in global space * @right: right margin, in global space * @bottom: bottom margin, in global space * @left: left margin, in global space * * Convenient function to set all the margins at once. * * Since: 1.0 **/ void adg_canvas_set_margins(AdgCanvas *canvas, gdouble top, gdouble right, gdouble bottom, gdouble left) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "top-margin", top, "right-margin", right, "bottom-margin", bottom, "left-margin", left, NULL); } /** * adg_canvas_get_margins: * @canvas: an #AdgCanvas * @top: (out) (nullable): where to store the top margin or NULL * @right: (out) (nullable): where to store the right margin or NULL * @bottom: (out) (nullable): where to store the bottom margin or NULL * @left: (out) (nullable): where to store the left margin or NULL * * Convenient function to get all the margins at once. * * Since: 1.0 **/ void adg_canvas_get_margins(AdgCanvas *canvas, gdouble *top, gdouble *right, gdouble *bottom, gdouble *left) { AdgCanvasPrivate *data; g_return_if_fail(ADG_IS_CANVAS(canvas)); data = adg_canvas_get_instance_private(canvas); if (top != NULL) { *top = data->top_margin; } if (right != NULL) { *right = data->right_margin; } if (bottom != NULL) { *bottom = data->bottom_margin; } if (left != NULL) { *left = data->left_margin; } } /** * adg_canvas_apply_margins: * @canvas: an #AdgCanvas * @extents: where apply the margins * * A convenient function to apply the margins of @canvas to the * arbitrary #CpmlExtents struct @extents. "Apply" means @extents * are enlarged of the specific margin values. * * Since: 1.0 **/ void adg_canvas_apply_margins(AdgCanvas *canvas, CpmlExtents *extents) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(extents != NULL); if (extents->is_defined) { AdgCanvasPrivate *data = adg_canvas_get_instance_private(canvas); extents->org.x -= data->left_margin; extents->org.y -= data->top_margin; extents->size.x += data->left_margin + data->right_margin; extents->size.y += data->top_margin + data->bottom_margin; } } /** * adg_canvas_switch_frame: * @canvas: an #AdgCanvas * @new_state: the new flag status * * Sets a new status on the #AdgCanvas:has-frame * property: TRUE means a border around * the canvas extents (less the margins) should be rendered. * * Since: 1.0 **/ void adg_canvas_switch_frame(AdgCanvas *canvas, gboolean new_state) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "has-frame", new_state, NULL); } /** * adg_canvas_has_frame: * @canvas: an #AdgCanvas * * Gets the current status of the #AdgCanvas:has-frame property, * that is whether a border around the canvas extents (less the * margins) should be rendered (TRUE) or not * (FALSE). * * Returns: the current status of the frame flag. * * Since: 1.0 **/ gboolean adg_canvas_has_frame(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), FALSE); data = adg_canvas_get_instance_private(canvas); return data->has_frame; } /** * adg_canvas_set_top_padding: * @canvas: an #AdgCanvas * @value: the new padding, in global space * * Changes the top padding of @canvas by setting * #AdgCanvas:top-padding to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_top_padding(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "top-padding", value, NULL); } /** * adg_canvas_get_top_padding: * @canvas: an #AdgCanvas * * Gets the top padding (in global space) of @canvas. * * Returns: the requested padding or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_top_padding(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->top_padding; } /** * adg_canvas_set_right_padding: * @canvas: an #AdgCanvas * @value: the new padding, in global space * * Changes the right padding of @canvas by setting #AdgCanvas:right-padding * to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_right_padding(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "right-padding", value, NULL); } /** * adg_canvas_get_right_padding: * @canvas: an #AdgCanvas * * Gets the right padding (in global space) of @canvas. * * Returns: the requested padding or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_right_padding(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->right_padding; } /** * adg_canvas_set_bottom_padding: * @canvas: an #AdgCanvas * @value: the new padding, in global space * * Changes the bottom padding of @canvas by setting * #AdgCanvas:bottom-padding to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_bottom_padding(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "bottom-padding", value, NULL); } /** * adg_canvas_get_bottom_padding: * @canvas: an #AdgCanvas * * Gets the bottom padding (in global space) of @canvas. * * Returns: the requested padding or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_bottom_padding(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->bottom_padding; } /** * adg_canvas_set_left_padding: * @canvas: an #AdgCanvas * @value: the new padding, in global space * * Changes the left padding of @canvas by setting * #AdgCanvas:left-padding to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_left_padding(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "left-padding", value, NULL); } /** * adg_canvas_get_left_padding: * @canvas: an #AdgCanvas * * Gets the left padding (in global space) of @canvas. * * Returns: the requested padding or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_left_padding(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->left_padding; } /** * adg_canvas_set_paddings: * @canvas: an #AdgCanvas * @top: top padding, in global space * @right: right padding, in global space * @bottom: bottom padding, in global space * @left: left padding, in global space * * Convenient function to set all the paddings at once. * * Since: 1.0 **/ void adg_canvas_set_paddings(AdgCanvas *canvas, gdouble top, gdouble right, gdouble bottom, gdouble left) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "top-padding", top, "right-padding", right, "bottom-padding", bottom, "left-padding", left, NULL); } /** * adg_canvas_get_paddings: * @canvas: an #AdgCanvas * @top: (out) (nullable): where to store the top padding or NULL * @right: (out) (nullable): where to store the right padding or NULL * @bottom: (out) (nullable): where to store the bottom padding or NULL * @left: (out) (nullable): where to store the left padding or NULL * * Convenient function to get all the paddings at once. * * Since: 1.0 **/ void adg_canvas_get_paddings(AdgCanvas *canvas, gdouble *top, gdouble *right, gdouble *bottom, gdouble *left) { AdgCanvasPrivate *data; g_return_if_fail(ADG_IS_CANVAS(canvas)); data = adg_canvas_get_instance_private(canvas); if (top != NULL) { *top = data->top_padding; } if (right != NULL) { *right = data->right_padding; } if (bottom != NULL) { *bottom = data->bottom_padding; } if (left != NULL) { *left = data->left_padding; } } static void _adg_global_changed(AdgEntity *entity) { AdgCanvasPrivate *data = adg_canvas_get_instance_private((AdgCanvas *) entity); if (_ADG_OLD_ENTITY_CLASS->global_changed) _ADG_OLD_ENTITY_CLASS->global_changed(entity); if (data->title_block) adg_entity_global_changed((AdgEntity *) data->title_block); } static void _adg_local_changed(AdgEntity *entity) { AdgCanvasPrivate *data = adg_canvas_get_instance_private((AdgCanvas *) entity); AdgTitleBlock *title_block = data->title_block; if (_ADG_OLD_ENTITY_CLASS->local_changed) _ADG_OLD_ENTITY_CLASS->local_changed(entity); if (title_block != NULL) { const gchar *scale = adg_title_block_get_scale(title_block); if (scale != NULL && scale[0] != '\0') { const cairo_matrix_t *map = adg_entity_get_local_map(entity); gdouble factor = adg_scale_factor(scale); if (map->xx != factor || map->yy != factor) adg_title_block_set_scale(title_block, "---"); } adg_entity_local_changed((AdgEntity *) title_block); } } static void _adg_invalidate(AdgEntity *entity) { AdgCanvasPrivate *data = adg_canvas_get_instance_private((AdgCanvas *) entity); if (_ADG_OLD_ENTITY_CLASS->invalidate) _ADG_OLD_ENTITY_CLASS->invalidate(entity); if (data->title_block) adg_entity_invalidate((AdgEntity *) data->title_block); } static void _adg_arrange(AdgEntity *entity) { AdgCanvas *canvas; AdgCanvasPrivate *data; CpmlExtents extents; if (_ADG_OLD_ENTITY_CLASS->arrange) _ADG_OLD_ENTITY_CLASS->arrange(entity); cpml_extents_copy(&extents, adg_entity_get_extents(entity)); /* The extents should be defined, otherwise there is no drawing */ g_return_if_fail(extents.is_defined); canvas = (AdgCanvas *) entity; data = adg_canvas_get_instance_private(canvas); _adg_apply_paddings(canvas, &extents); if (data->size.x > 0 || data->size.y > 0) { const cairo_matrix_t *global = adg_entity_get_global_matrix(entity); CpmlExtents paper; paper.org.x = 0; paper.org.y = 0; paper.size.x = data->size.x; paper.size.y = data->size.y; cairo_matrix_transform_point(global, &paper.org.x, &paper.org.y); cairo_matrix_transform_distance(global, &paper.size.x, &paper.size.y); if (data->size.x > 0) { extents.org.x = paper.org.x; extents.size.x = paper.size.x; } if (data->size.y > 0) { extents.org.y = paper.org.y; extents.size.y = paper.size.y; } } if (data->title_block) { AdgEntity *title_block_entity; const CpmlExtents *title_block_extents; CpmlPair shift; title_block_entity = (AdgEntity *) data->title_block; adg_entity_arrange(title_block_entity); title_block_extents = adg_entity_get_extents(title_block_entity); shift.x = extents.org.x + extents.size.x - title_block_extents->org.x - title_block_extents->size.x; shift.y = extents.org.y + extents.size.y - title_block_extents->org.y - title_block_extents->size.y; /* The following block could be optimized by skipping tiny shift, * usually left by rounding errors */ if (shift.x != 0 || shift.y != 0) { cairo_matrix_t unglobal, map; adg_matrix_copy(&unglobal, adg_entity_get_global_matrix(entity)); cairo_matrix_invert(&unglobal); cairo_matrix_transform_distance(&unglobal, &shift.x, &shift.y); cairo_matrix_init_translate(&map, shift.x, shift.y); adg_entity_transform_global_map(title_block_entity, &map, ADG_TRANSFORM_AFTER); adg_entity_global_changed(title_block_entity); adg_entity_arrange(title_block_entity); title_block_extents = adg_entity_get_extents(title_block_entity); cpml_extents_add(&extents, title_block_extents); } } /* Impose the new extents */ adg_entity_set_extents(entity, &extents); } static void _adg_render(AdgEntity *entity, cairo_t *cr) { AdgCanvasPrivate *data = adg_canvas_get_instance_private((AdgCanvas *) entity); const CpmlExtents *extents = adg_entity_get_extents(entity); cairo_save(cr); /* Background fill */ cairo_rectangle(cr, extents->org.x - data->left_margin, extents->org.y - data->top_margin, extents->size.x + data->left_margin + data->right_margin, extents->size.y + data->top_margin + data->bottom_margin); adg_entity_apply_dress(entity, data->background_dress, cr); cairo_fill(cr); /* Frame line */ if (data->has_frame) { cairo_rectangle(cr, extents->org.x, extents->org.y, extents->size.x, extents->size.y); cairo_transform(cr, adg_entity_get_global_matrix(entity)); adg_entity_apply_dress(entity, data->frame_dress, cr); cairo_stroke(cr); } cairo_restore(cr); if (data->title_block) adg_entity_render((AdgEntity *) data->title_block, cr); if (_ADG_OLD_ENTITY_CLASS->render) _ADG_OLD_ENTITY_CLASS->render(entity, cr); } static void _adg_apply_paddings(AdgCanvas *canvas, CpmlExtents *extents) { AdgCanvasPrivate *data = adg_canvas_get_instance_private(canvas); extents->org.x -= data->left_padding; extents->size.x += data->left_padding + data->right_padding; extents->org.y -= data->top_padding; extents->size.y += data->top_padding + data->bottom_padding; } /** * adg_canvas_export: * @canvas: an #AdgCanvas * @type: (type gint): the export format * @file: the name of the resulting file * @gerror: (allow-none): return location for errors * * A helper function that provides a bare export functionality. * It basically exports the drawing in @canvas in the @file * in the @type format. Any error will be reported in @gerror, * if not NULL. * * Returns: TRUE on success, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_canvas_export(AdgCanvas *canvas, cairo_surface_type_t type, const gchar *file, GError **gerror) { AdgEntity *entity; const CpmlExtents *extents; gdouble top, bottom, left, right, width, height, factor; cairo_surface_t *surface; cairo_t *cr; cairo_status_t status; g_return_val_if_fail(ADG_IS_CANVAS(canvas), FALSE); g_return_val_if_fail(file != NULL, FALSE); g_return_val_if_fail(gerror == NULL || *gerror == NULL, FALSE); entity = (AdgEntity *) canvas; adg_entity_arrange(entity); extents = adg_entity_get_extents(entity); factor = adg_canvas_get_factor(canvas); top = factor * adg_canvas_get_top_margin(canvas); bottom = factor * adg_canvas_get_bottom_margin(canvas); left = factor * adg_canvas_get_left_margin(canvas); right = factor * adg_canvas_get_right_margin(canvas); width = factor * extents->size.x + left + right; height = factor * extents->size.y + top + bottom; switch (type) { #ifdef CAIRO_HAS_PNG_FUNCTIONS case CAIRO_SURFACE_TYPE_IMAGE: surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height); break; #endif #ifdef CAIRO_HAS_PDF_SURFACE case CAIRO_SURFACE_TYPE_PDF: surface = cairo_pdf_surface_create(file, width, height); break; #endif #ifdef CAIRO_HAS_PS_SURFACE case CAIRO_SURFACE_TYPE_PS: surface = cairo_ps_surface_create(file, width, height); break; #endif #ifdef CAIRO_HAS_SVG_SURFACE case CAIRO_SURFACE_TYPE_SVG: surface = cairo_svg_surface_create(file, width, height); break; #endif default: surface = NULL; break; } if (surface == NULL) { g_set_error(gerror, ADG_CANVAS_ERROR, ADG_CANVAS_ERROR_SURFACE, "unable to handle surface type '%d'", type); return FALSE; } cairo_surface_set_device_offset(surface, left, top); cairo_surface_set_device_scale(surface, factor, factor); cr = cairo_create(surface); cairo_surface_destroy(surface); adg_entity_render(ADG_ENTITY(canvas), cr); if (cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE) { status = cairo_surface_write_to_png(surface, file); } else { cairo_show_page(cr); status = cairo_status(cr); } cairo_destroy(cr); if (status != CAIRO_STATUS_SUCCESS) { g_set_error(gerror, ADG_CANVAS_ERROR, ADG_CANVAS_ERROR_CAIRO, "cairo reported '%s'", cairo_status_to_string(status)); return FALSE; } return TRUE; } #if GTK3_ENABLED || GTK2_ENABLED #include static void _adg_update_margin(AdgCanvas *canvas, gdouble *margin, gdouble *side, gdouble new_margin) { GtkPageSetup *page_setup; gdouble old_margin; old_margin = *margin; *margin = new_margin; page_setup = adg_canvas_get_page_setup(canvas); if (page_setup == NULL) return; *side += old_margin - new_margin; } /** * adg_canvas_set_paper: * @canvas: an #AdgCanvas * @paper_name: a paper name * @orientation: the page orientation * * A convenient function to set size and margins of @canvas * using a @paper_name and an @orientation value. This should * be a PWG 5101.1-2002 paper name and it will be passed as * is to gtk_paper_size_new(), so use any valid name accepted * by that function. * * This has the same effect as creating a #GtkPageSetup object * and binding it to @canvas with adg_canvas_set_page_setup(): * check its documentation for knowing the implications. * * To reset the size to its default behavior (i.e. autocalculate * it from the entities) just call adg_canvas_set_size_explicit() * passing 0, 0. * * If you want to use your own margins on a named paper size, * set them after the call to this function. * * Since: 1.0 **/ void adg_canvas_set_paper(AdgCanvas *canvas, const gchar *paper_name, GtkPageOrientation orientation) { GtkPageSetup *page_setup; GtkPaperSize *paper_size; g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(paper_name != NULL); page_setup = gtk_page_setup_new(); paper_size = gtk_paper_size_new(paper_name); gtk_page_setup_set_paper_size(page_setup, paper_size); gtk_page_setup_set_orientation(page_setup, orientation); gtk_paper_size_free(paper_size); adg_canvas_set_page_setup(canvas, page_setup); g_object_unref(page_setup); } /** * adg_canvas_set_page_setup: * @canvas: an #AdgCanvas * @page_setup: (allow-none) (transfer none): the page setup * * A convenient function to setup the page of @canvas so it can * also be subsequentially used for printing. It is allowed to * pass NULL as @page_setup to restore the * default page setup. * * When a canvas is bound to a page setup, the paper size is kept * constant. This implies increasing or decreasing the margins * respectively decreases or increases the page size of the * relevant dimension, e.g. increasing the right margin decreases * the width (the x component of the size). * * A reference to @page_setup is added, so there is no need to keep * alive this object after a call to this function. @page_setup can * be retrieved at any time with adg_canvas_get_page_setup(). * * The size and margins provided by @page_setup are immediately * used to set size and margins of @canvas. This means using * NULL as @page_setup just releases the * reference to the previous @page_setup object... all the page * settings are still retained. * * If you want to use your own margins on a page setup, * set them on canvas after the call to this * function or set them on @page_setup before. * * * // By default, canvas does not have an explicit size * adg_canvas_set_page_setup(canvas, a4); * g_object_unref(a4); * // Now canvas has size and margins specified by a4 * // (and a4 should be a prefilled GtkPageSetup object). * adg_canvas_set_page_setup(canvas, NULL); * // Now canvas is no more bound to a4 and that object (if * // not referenced anywhere else) can be garbage-collected. * // The page setup of canvas has not changed though. * adg_canvas_set_size_explicit(canvas, 0, 0); * // Now the page size of canvas has been restored to * // their default behavior. * * * Since: 1.0 **/ void adg_canvas_set_page_setup(AdgCanvas *canvas, GtkPageSetup *page_setup) { gdouble top, right, bottom, left; CpmlPair size; g_return_if_fail(ADG_IS_CANVAS(canvas)); if (page_setup == NULL) { /* By convention, NULL resets the default page but * does not change any other property */ g_object_set_data((GObject *) canvas, "_adg_page_setup", NULL); return; } g_return_if_fail(GTK_IS_PAGE_SETUP(page_setup)); top = gtk_page_setup_get_top_margin(page_setup, GTK_UNIT_POINTS); right = gtk_page_setup_get_right_margin(page_setup, GTK_UNIT_POINTS); bottom = gtk_page_setup_get_bottom_margin(page_setup, GTK_UNIT_POINTS); left = gtk_page_setup_get_left_margin(page_setup, GTK_UNIT_POINTS); size.x = gtk_page_setup_get_page_width(page_setup, GTK_UNIT_POINTS); size.y = gtk_page_setup_get_page_height(page_setup, GTK_UNIT_POINTS); adg_canvas_set_size(canvas, &size); adg_canvas_set_margins(canvas, top, right, bottom, left); g_object_ref(page_setup); g_object_set_data_full((GObject *) canvas, "_adg_page_setup", page_setup, g_object_unref); } /** * adg_canvas_get_page_setup: * @canvas: an #AdgCanvas * * If adg_canvas_set_page_setup() is called, a #GtkPageSetup object * is created and bound to @canvas. This metho returns a pointer * to that internal object or NULL if * adg_canvas_set_page_setup() has not been called before. * * Returns: (allow-none) (transfer none): the #GtkPageSetup with size and margins of @canvas of NULL on no setup found or errors. * * Since: 1.0 **/ GtkPageSetup * adg_canvas_get_page_setup(AdgCanvas *canvas) { g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL); return g_object_get_data((GObject *) canvas, "_adg_page_setup"); } #else static void _adg_update_margin(AdgCanvas *canvas, gdouble *margin, gdouble *side, gdouble new_margin) { *margin = new_margin; } #endif diff --git a/src/adg/adg-color-style-private.h b/src/adg/adg-color-style-private.h index a2c8ce5f..e504b3ed 100644 --- a/src/adg/adg-color-style-private.h +++ b/src/adg/adg-color-style-private.h @@ -1,39 +1,39 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_COLOR_STYLE_PRIVATE_H__ #define __ADG_COLOR_STYLE_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgColorStylePrivate AdgColorStylePrivate; struct _AdgColorStylePrivate { gdouble red; gdouble green; gdouble blue; gdouble alpha; }; G_END_DECLS #endif /* __ADG_COLOR_STYLE_PRIVATE_H__ */ diff --git a/src/adg/adg-color-style.c b/src/adg/adg-color-style.c index 218b9cc2..5afdbc35 100644 --- a/src/adg/adg-color-style.c +++ b/src/adg/adg-color-style.c @@ -1,414 +1,414 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-color-style * @short_description: RGBA color information * * A style containing a single color expressed in RGB format. * #AdgColorStyle also supports the alpha compositing that should be * expressed with a double value between 0 and 1, where 0 is the * "completely transparent" value while 1 is "fully opaque". * * Since: 1.0 **/ /** * AdgColorStyle: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-style.h" #include "adg-color-style.h" #include "adg-color-style.h" #include "adg-color-style-private.h" G_DEFINE_TYPE_WITH_PRIVATE(AdgColorStyle, adg_color_style, ADG_TYPE_STYLE) enum { PROP_0, PROP_RED, PROP_GREEN, PROP_BLUE, PROP_ALPHA }; static void _adg_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void _adg_apply (AdgStyle *style, AdgEntity *entity, cairo_t *cr); static void adg_color_style_class_init(AdgColorStyleClass *klass) { GObjectClass *gobject_class; AdgStyleClass *style_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; style_class = (AdgStyleClass *) klass; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; style_class->apply = _adg_apply; param = g_param_spec_double("red", P_("Red Channel"), P_("The red value, where 0 means no red and 1 is full red"), 0, 1, 0, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_RED, param); param = g_param_spec_double("green", P_("Green Channel"), P_("The green value, where 0 means no green and 1 is full green"), 0, 1, 0, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_GREEN, param); param = g_param_spec_double("blue", P_("Blue Channel"), P_("The blue value, where 0 means no blue and 1 is full blue"), 0, 1, 0, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_BLUE, param); param = g_param_spec_double("alpha", P_("Alpha Channel"), P_("The alpha value, where 0 means completely transparent and 1 is fully opaque"), 0, 1, 1, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_ALPHA, param); } static void adg_color_style_init(AdgColorStyle *color_style) { AdgColorStylePrivate *data = adg_color_style_get_instance_private(color_style); data->red = 0; data->green = 0; data->blue = 0; data->alpha = 1; } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgColorStylePrivate *data = adg_color_style_get_instance_private((AdgColorStyle *) object); switch (prop_id) { case PROP_RED: g_value_set_double(value, data->red); break; case PROP_GREEN: g_value_set_double(value, data->green); break; case PROP_BLUE: g_value_set_double(value, data->blue); break; case PROP_ALPHA: g_value_set_double(value, data->alpha); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgColorStylePrivate *data = adg_color_style_get_instance_private((AdgColorStyle *) object); switch (prop_id) { case PROP_RED: data->red = g_value_get_double(value); break; case PROP_GREEN: data->green = g_value_get_double(value); break; case PROP_BLUE: data->blue = g_value_get_double(value); break; case PROP_ALPHA: data->alpha = g_value_get_double(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_color_style_new: * * Constructs a new color initialized to opaque black. * * Returns: a new color style * * Since: 1.0 **/ AdgColorStyle * adg_color_style_new(void) { return g_object_new(ADG_TYPE_COLOR_STYLE, NULL); } /** * adg_color_style_set_red: * @color_style: an #AdgColorStyle * @red: the new value * * Sets a new value for the red channel, where 0 means no red and * 1 is full red. * * Since: 1.0 **/ void adg_color_style_set_red(AdgColorStyle *color_style, gdouble red) { g_return_if_fail(ADG_IS_COLOR_STYLE(color_style)); g_object_set(color_style, "red", red, NULL); } /** * adg_color_style_get_red: * @color_style: an #AdgColorStyle * * Gets the current value of the red channel, where 0 means no red and * 1 is full red. * * Returns: the requested red value * * Since: 1.0 **/ gdouble adg_color_style_get_red(AdgColorStyle *color_style) { AdgColorStylePrivate *data; g_return_val_if_fail(ADG_IS_COLOR_STYLE(color_style), 0.); data = adg_color_style_get_instance_private(color_style); return data->red; } /** * adg_color_style_set_green: * @color_style: an #AdgColorStyle * @green: the new value * * Sets a new value for the green channel, where 0 means no green and * 1 is full green. * * Since: 1.0 **/ void adg_color_style_set_green(AdgColorStyle *color_style, gdouble green) { g_return_if_fail(ADG_IS_COLOR_STYLE(color_style)); g_object_set(color_style, "green", green, NULL); } /** * adg_color_style_get_green: * @color_style: an #AdgColorStyle * * Gets the current value of the green channel, where 0 means no green and * 1 is full green. * * Returns: the requested green value * * Since: 1.0 **/ gdouble adg_color_style_get_green(AdgColorStyle *color_style) { AdgColorStylePrivate *data; g_return_val_if_fail(ADG_IS_COLOR_STYLE(color_style), 0.); data = adg_color_style_get_instance_private(color_style); return data->green; } /** * adg_color_style_set_blue: * @color_style: an #AdgColorStyle * @blue: the new value * * Sets a new value for the blue channel, where 0 means no blue and * 1 is full blue. * * Since: 1.0 **/ void adg_color_style_set_blue(AdgColorStyle *color_style, gdouble blue) { g_return_if_fail(ADG_IS_COLOR_STYLE(color_style)); g_object_set(color_style, "blue", blue, NULL); } /** * adg_color_style_get_blue: * @color_style: an #AdgColorStyle * * Gets the current value of the blue channel, where 0 means no blue and * 1 is full blue. * * Returns: the requested blue value * * Since: 1.0 **/ gdouble adg_color_style_get_blue(AdgColorStyle *color_style) { AdgColorStylePrivate *data; g_return_val_if_fail(ADG_IS_COLOR_STYLE(color_style), 0.); data = adg_color_style_get_instance_private(color_style); return data->blue; } /** * adg_color_style_set_rgb: * @color_style: an #AdgColorStyle * @red: the red channel value * @green: the green channel value * @blue: the blue channel value * * Sets the RGB channels at once. * * Since: 1.0 **/ void adg_color_style_set_rgb(AdgColorStyle *color_style, gdouble red, gdouble green, gdouble blue) { g_return_if_fail(ADG_IS_COLOR_STYLE(color_style)); g_object_set(color_style, "red", red, "green", green, "blue", blue, NULL); } /** * adg_color_style_put_rgb: * @color_style: an #AdgColorStyle * @red: where to store the red channel value * @green: where to store the green channel value * @blue: where to store the blue channel value * * Gets the values of the red, green and blue channels of @color_style. * Any of the pointer can be NULL, in which case * the value is not returned. * * Since: 1.0 **/ void adg_color_style_put_rgb(AdgColorStyle *color_style, gdouble *red, gdouble *green, gdouble *blue) { AdgColorStylePrivate *data; g_return_if_fail(ADG_IS_COLOR_STYLE(color_style)); data = adg_color_style_get_instance_private(color_style); if (red != NULL) *red = data->red; if (green != NULL) *green = data->green; if (blue != NULL) *blue = data->blue; } /** * adg_color_style_set_alpha: * @color_style: an #AdgColorStyle * @alpha: the new alpha * * Sets a new color alpha value, where 0 means completely transparent * and 1 is fully opaque. * * Since: 1.0 **/ void adg_color_style_set_alpha(AdgColorStyle *color_style, gdouble alpha) { g_return_if_fail(ADG_IS_COLOR_STYLE(color_style)); g_object_set(color_style, "alpha", alpha, NULL); } /** * adg_color_style_get_alpha: * @color_style: an #AdgColorStyle * * Gets the alpha channel value, where 0 means completely transparent * and 1 is fully opaque. * * Returns: the requested alpha value * * Since: 1.0 **/ gdouble adg_color_style_get_alpha(AdgColorStyle *color_style) { AdgColorStylePrivate *data; g_return_val_if_fail(ADG_IS_COLOR_STYLE(color_style), 0.); data = adg_color_style_get_instance_private(color_style); return data->alpha; } static void _adg_apply(AdgStyle *style, AdgEntity *entity, cairo_t *cr) { AdgColorStylePrivate *data = adg_color_style_get_instance_private((AdgColorStyle *) style); if (data->alpha == 1.) cairo_set_source_rgb(cr, data->red, data->green, data->blue); else cairo_set_source_rgba(cr, data->red, data->green, data->blue, data->alpha); } diff --git a/src/adg/adg-color-style.h b/src/adg/adg-color-style.h index d89d8d84..27362754 100644 --- a/src/adg/adg-color-style.h +++ b/src/adg/adg-color-style.h @@ -1,81 +1,81 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_COLOR_STYLE_H__ #define __ADG_COLOR_STYLE_H__ G_BEGIN_DECLS #define ADG_TYPE_COLOR_STYLE (adg_color_style_get_type()) #define ADG_COLOR_STYLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_COLOR_STYLE, AdgColorStyle)) #define ADG_COLOR_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_COLOR_STYLE, AdgColorStyleClass)) #define ADG_IS_COLOR_STYLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_COLOR_STYLE)) #define ADG_IS_COLOR_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_COLOR_STYLE)) #define ADG_COLOR_STYLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_COLOR_STYLE, AdgColorStyleClass)) typedef struct _AdgColorStyle AdgColorStyle; typedef struct _AdgColorStyleClass AdgColorStyleClass; struct _AdgColorStyle { /*< private >*/ AdgStyle parent; }; struct _AdgColorStyleClass { /*< private >*/ AdgStyleClass parent_class; }; GType adg_color_style_get_type (void); AdgColorStyle * adg_color_style_new (void); void adg_color_style_set_red (AdgColorStyle *color_style, gdouble red); gdouble adg_color_style_get_red (AdgColorStyle *color_style); void adg_color_style_set_green (AdgColorStyle *color_style, gdouble green); gdouble adg_color_style_get_green (AdgColorStyle *color_style); void adg_color_style_set_blue (AdgColorStyle *color_style, gdouble blue); gdouble adg_color_style_get_blue (AdgColorStyle *color_style); void adg_color_style_set_rgb (AdgColorStyle *color_style, gdouble red, gdouble green, gdouble blue); void adg_color_style_put_rgb (AdgColorStyle *color_style, gdouble *red, gdouble *green, gdouble *blue); void adg_color_style_set_alpha (AdgColorStyle *color_style, gdouble alpha); gdouble adg_color_style_get_alpha (AdgColorStyle *color_style); G_END_DECLS #endif /* __ADG_COLOR_STYLE_H__ */ diff --git a/src/adg/adg-container-private.h b/src/adg/adg-container-private.h index c3772b26..0e1b7334 100644 --- a/src/adg/adg-container-private.h +++ b/src/adg/adg-container-private.h @@ -1,36 +1,36 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_CONTAINER_PRIVATE_H__ #define __ADG_CONTAINER_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgContainerPrivate AdgContainerPrivate; struct _AdgContainerPrivate { GSList *children; }; G_END_DECLS #endif /* __ADG_CONTAINER_PRIVATE_H__ */ diff --git a/src/adg/adg-container.c b/src/adg/adg-container.c index f16118fb..38d87f12 100644 --- a/src/adg/adg-container.c +++ b/src/adg/adg-container.c @@ -1,571 +1,571 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-container * @short_description: Base class for entity that can contain other entities * * The #AdgContainer is an entity that can contains more sub-entities. * Moreover, it can apply a common transformation to local and/or global * maps: see http://adg.entidi.com/home/details/ for further details. * * Adding an entity to a container will create a strong reference to the * container in the entity and a weak reference to the entity on the * container. This way the container will be able to destroy its children * when destroyed and it will be able to update its children when an entity * is destroyed. * * Since: 1.0 **/ /** * AdgContainer: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ /** * AdgContainerClass: * @children: virtual method that gets the list of entities (that is * #AdgEntity and derived instances) owned by the container. * @add: signal that adds a new entity to the container. * @remove: signal that removes a specific entity from the container. * * #AdgContainer effectively stores a #GSList of children into its * private data and keeps a reference to every child it owns. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-container.h" #include "adg-container-private.h" #define _ADG_PARENT_OBJECT_CLASS ((GObjectClass *) adg_container_parent_class) #define _ADG_PARENT_ENTITY_CLASS ((AdgEntityClass *) adg_container_parent_class) G_DEFINE_TYPE_WITH_PRIVATE(AdgContainer, adg_container, ADG_TYPE_ENTITY) enum { PROP_0, PROP_CHILD }; enum { ADD, REMOVE, LAST_SIGNAL }; static void _adg_dispose (GObject *object); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void _adg_destroy (AdgEntity *entity); static void _adg_global_changed (AdgEntity *entity); static void _adg_local_changed (AdgEntity *entity); static void _adg_invalidate (AdgEntity *entity); static void _adg_arrange (AdgEntity *entity); static void _adg_add_extents (AdgEntity *entity, CpmlExtents *extents); static void _adg_render (AdgEntity *entity, cairo_t *cr); static GSList * _adg_children (AdgContainer *container); static void _adg_add (AdgContainer *container, AdgEntity *entity); static void _adg_remove (AdgContainer *container, AdgEntity *entity); static void _adg_remove_from_list (gpointer container, GObject *entity); static guint _adg_signals[LAST_SIGNAL] = { 0 }; static void adg_container_class_init(AdgContainerClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->set_property = _adg_set_property; entity_class->destroy = _adg_destroy; entity_class->global_changed = _adg_global_changed; entity_class->local_changed = _adg_local_changed; entity_class->invalidate = _adg_invalidate; entity_class->arrange = _adg_arrange; entity_class->render = _adg_render; klass->children = _adg_children; klass->add = _adg_add; klass->remove = _adg_remove; param = g_param_spec_object("child", P_("Child"), P_("Can be used to add a new child to the container"), ADG_TYPE_ENTITY, G_PARAM_WRITABLE); g_object_class_install_property(gobject_class, PROP_CHILD, param); /** * AdgContainer::add: * @container: an #AdgContainer * @entity: the #AdgEntity to add * * Adds @entity to @container. @entity must not be inside another * container or the operation will fail. * * Since: 1.0 **/ _adg_signals[ADD] = g_signal_new("add", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AdgContainerClass, add), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, ADG_TYPE_ENTITY); /** * AdgContainer::remove: * @container: an #AdgContainer * @entity: the #AdgEntity to remove * * Removes @entity from @container. * * Since: 1.0 **/ _adg_signals[REMOVE] = g_signal_new("remove", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AdgContainerClass, remove), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, ADG_TYPE_ENTITY); } static void adg_container_init(AdgContainer *container) { AdgContainerPrivate *data = adg_container_get_instance_private(container); data->children = NULL; } static void _adg_dispose(GObject *object) { AdgContainer *container = (AdgContainer *) object; AdgContainerPrivate *data = adg_container_get_instance_private(container); /* Remove all the children from the container: these will emit * a "remove" signal for every child and will drop all the * references from the children to this container (and, obviously, * from the container to the children). */ while (data->children != NULL) adg_container_remove(container, (AdgEntity *) data->children->data); if (_ADG_PARENT_OBJECT_CLASS->dispose) _ADG_PARENT_OBJECT_CLASS->dispose(object); } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgContainer *container = (AdgContainer *) object; switch (prop_id) { case PROP_CHILD: adg_container_add(container, g_value_get_object(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); } } /** * adg_container_new: * * Creates a new container entity. * * Returns: the newly created container entity * * Since: 1.0 **/ AdgContainer * adg_container_new(void) { return g_object_new(ADG_TYPE_CONTAINER, NULL); } /** * adg_container_add: * @container: an #AdgContainer * @entity: an #AdgEntity * * Emits a #AdgContainer::add signal on @container passing @entity * as argument. @entity must be added to only one container at a time, * you can't place the same entity inside two different containers. * * Once @entity has been added, the floating reference will be removed * and @container will own a reference to @entity. This means the only * proper way to destroy @entity is to call adg_container_remove(). * * Since: 1.0 **/ void adg_container_add(AdgContainer *container, AdgEntity *entity) { g_return_if_fail(ADG_IS_CONTAINER(container)); g_return_if_fail(ADG_IS_ENTITY(entity)); g_signal_emit(container, _adg_signals[ADD], 0, entity); } /** * adg_container_remove: * @container: an #AdgContainer * @entity: an #AdgEntity * * Emits a #AdgContainer::remove signal on @container passing * @entity as argument. @entity must be inside @container. * * Note that @container will own a reference to @entity and it * may be the last reference held: this means removing an entity * from its container can destroy it. * * If you want to use @entity again, you need to add a reference * to it, using g_object_ref(), before removing it from @container. * The following typical example shows you how to properly move * entity from container1 * to container2: * * * g_object_ref(entity); * adg_container_remove(container1, entity); * adg_container_add(container2, entity) * g_object_unref(entity); * * * Since: 1.0 **/ void adg_container_remove(AdgContainer *container, AdgEntity *entity) { g_return_if_fail(ADG_IS_CONTAINER(container)); g_return_if_fail(ADG_IS_ENTITY(entity)); g_signal_emit(container, _adg_signals[REMOVE], 0, entity); } /** * adg_container_children: * @container: an #AdgContainer * * Gets the children list of @container. This list must be manually * freed with g_slist_free() when no longer user. * * The returned list is ordered from the most recently added child * to the oldest one. * * Returns: (element-type AdgEntity) (transfer container): a newly allocated #GSList of #AdgEntity or NULL on no children or errors * * Since: 1.0 **/ GSList * adg_container_children(AdgContainer *container) { AdgContainerClass *klass; g_return_val_if_fail(ADG_IS_CONTAINER(container), NULL); klass = ADG_CONTAINER_GET_CLASS(container); if (klass->children == NULL) return NULL; return klass->children(container); } /** * adg_container_foreach: * @container: an #AdgContainer * @callback: (scope call): a callback * @user_data: callback user data * * Invokes @callback on each child of @container. * The callback should be declared as: * * * void callback(AdgEntity *entity, gpointer user_data); * * * Since: 1.0 **/ void adg_container_foreach(AdgContainer *container, GCallback callback, gpointer user_data) { GSList *children; g_return_if_fail(ADG_IS_CONTAINER(container)); g_return_if_fail(callback != NULL); children = adg_container_children(container); while (children != NULL) { if (children->data != NULL) ((void (*) (gpointer, gpointer)) callback) (children->data, user_data); children = g_slist_delete_link(children, children); } } /** * adg_container_propagate: * @container: an #AdgContainer * @signal_id: the signal id * @detail: the detail * @...: parameters to be passed to the signal, followed by a pointer * to the allocated memory where to store the return type: if * the signal is %G_TYPE_NONE (void return type), this trailing * pointer should be omitted * * Emits the specified signal to all the children of @container * using g_signal_emit_valist() calls. * * Since: 1.0 **/ void adg_container_propagate(AdgContainer *container, guint signal_id, GQuark detail, ...) { va_list var_args; va_start(var_args, detail); adg_container_propagate_valist(container, signal_id, detail, var_args); va_end(var_args); } /** * adg_container_propagate_by_name: * @container: an #AdgContainer * @detailed_signal: a string of the form "signal-name::detail". * @...: parameters to be passed to the signal, followed by a pointer * to the allocated memory where to store the return type: if * the signal is %G_TYPE_NONE (void return type), this trailing * pointer should be omitted * * Emits the specified signal to all the children of @container * using g_signal_emit_valist() calls. * * Since: 1.0 **/ void adg_container_propagate_by_name(AdgContainer *container, const gchar *detailed_signal, ...) { guint signal_id; GQuark detail = 0; va_list var_args; if (!g_signal_parse_name(detailed_signal, G_TYPE_FROM_INSTANCE(container), &signal_id, &detail, FALSE)) { g_warning(_("%s: signal '%s' is invalid for instance %p"), G_STRLOC, detailed_signal, container); return; } va_start(var_args, detailed_signal); adg_container_propagate_valist(container, signal_id, detail, var_args); va_end(var_args); } /** * adg_container_propagate_valist: * @container: an #AdgContainer * @signal_id: the signal id * @detail: the detail * @var_args: parameters to be passed to the signal, followed by a * pointer to the allocated memory where to store the * return type: if the signal is %G_TYPE_NONE (void return * type), this trailing pointer should be omitted * * Emits the specified signal to all the children of @container * using g_signal_emit_valist() calls. * * Since: 1.0 **/ void adg_container_propagate_valist(AdgContainer *container, guint signal_id, GQuark detail, va_list var_args) { GSList *children; va_list var_copy; g_return_if_fail(ADG_IS_CONTAINER(container)); children = adg_container_children(container); while (children != NULL) { if (children->data != NULL) { G_VA_COPY(var_copy, var_args); g_signal_emit_valist(children->data, signal_id, detail, var_copy); } children = g_slist_delete_link(children, children); } } static void _adg_destroy(AdgEntity *entity) { adg_container_propagate_by_name((AdgContainer *) entity, "destroy"); if (_ADG_PARENT_ENTITY_CLASS->destroy) _ADG_PARENT_ENTITY_CLASS->destroy(entity); } static void _adg_global_changed(AdgEntity *entity) { if (_ADG_PARENT_ENTITY_CLASS->global_changed) _ADG_PARENT_ENTITY_CLASS->global_changed(entity); adg_container_propagate_by_name((AdgContainer *) entity, "global-changed"); } static void _adg_local_changed(AdgEntity *entity) { if (_ADG_PARENT_ENTITY_CLASS->local_changed) _ADG_PARENT_ENTITY_CLASS->local_changed(entity); adg_container_propagate_by_name((AdgContainer *) entity, "local-changed"); } static void _adg_invalidate(AdgEntity *entity) { adg_container_propagate_by_name((AdgContainer *) entity, "invalidate"); } static void _adg_arrange(AdgEntity *entity) { AdgContainer *container = (AdgContainer *) entity; CpmlExtents extents = { 0 }; adg_container_propagate_by_name(container, "arrange", NULL); adg_container_foreach(container, G_CALLBACK(_adg_add_extents), &extents); adg_entity_set_extents(entity, &extents); } static void _adg_add_extents(AdgEntity *entity, CpmlExtents *extents) { if (! adg_entity_has_floating(entity)) { cpml_extents_add(extents, adg_entity_get_extents(entity)); } } static void _adg_render(AdgEntity *entity, cairo_t *cr) { adg_container_propagate_by_name((AdgContainer *) entity, "render", cr); } static GSList * _adg_children(AdgContainer *container) { AdgContainerPrivate *data = adg_container_get_instance_private(container); /* The NULL case is already managed by GLib */ return g_slist_copy(data->children); } static void _adg_add(AdgContainer *container, AdgEntity *entity) { const AdgEntity *old_parent; AdgContainerPrivate *data; old_parent = adg_entity_get_parent(entity); if (old_parent != NULL) { g_warning(_("Attempting to add an entity with type %s to a container " "of type %s, but the entity is already inside a container " "of type %s"), g_type_name(G_OBJECT_TYPE(entity)), g_type_name(G_OBJECT_TYPE(container)), g_type_name(G_OBJECT_TYPE(old_parent))); return; } data = adg_container_get_instance_private(container); data->children = g_slist_prepend(data->children, entity); g_object_ref_sink(entity); adg_entity_set_parent(entity, (AdgEntity *) container); g_object_weak_ref((GObject *) entity, _adg_remove_from_list, container); } static void _adg_remove_from_list(gpointer container, GObject *entity) { AdgContainerPrivate *data = adg_container_get_instance_private((AdgContainer *) container); data->children = g_slist_remove(data->children, entity); } static void _adg_remove(AdgContainer *container, AdgEntity *entity) { AdgContainerPrivate *data = adg_container_get_instance_private(container); GSList *node = g_slist_find(data->children, entity); if (node == NULL) { g_warning(_("Attempting to remove an entity with type %s from a " "container of type %s, but the entity is not present"), g_type_name(G_OBJECT_TYPE(entity)), g_type_name(G_OBJECT_TYPE(container))); return; } g_object_weak_unref((GObject *) entity, _adg_remove_from_list, container); data->children = g_slist_delete_link(data->children, node); adg_entity_set_parent(entity, NULL); g_object_unref(entity); } diff --git a/src/adg/adg-container.h b/src/adg/adg-container.h index d736bc7f..8982f1a1 100644 --- a/src/adg/adg-container.h +++ b/src/adg/adg-container.h @@ -1,91 +1,91 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_CONTAINER_H__ #define __ADG_CONTAINER_H__ G_BEGIN_DECLS #define ADG_TYPE_CONTAINER (adg_container_get_type()) #define ADG_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_CONTAINER, AdgContainer)) #define ADG_CONTAINER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_CONTAINER, AdgContainerClass)) #define ADG_IS_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_CONTAINER)) #define ADG_IS_CONTAINER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_CONTAINER)) #define ADG_CONTAINER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_CONTAINER, AdgContainerClass)) typedef struct _AdgContainer AdgContainer; typedef struct _AdgContainerClass AdgContainerClass; struct _AdgContainer { /*< private >*/ AdgEntity parent; }; struct _AdgContainerClass { /*< private >*/ AdgEntityClass parent_class; /*< public >*/ /* Virtual table */ GSList * (*children) (AdgContainer *container); /* Signals */ void (*add) (AdgContainer *container, AdgEntity *entity); void (*remove) (AdgContainer *container, AdgEntity *entity); }; GType adg_container_get_type (void); AdgContainer * adg_container_new (void); GSList * adg_container_children (AdgContainer *container); void adg_container_add (AdgContainer *container, AdgEntity *entity); void adg_container_remove (AdgContainer *container, AdgEntity *entity); void adg_container_foreach (AdgContainer *container, GCallback callback, gpointer user_data); void adg_container_propagate (AdgContainer *container, guint signal_id, GQuark detail, ...); void adg_container_propagate_by_name (AdgContainer *container, const gchar *detailed_signal, ...); void adg_container_propagate_valist (AdgContainer *container, guint signal_id, GQuark detail, va_list var_args); G_END_DECLS #endif /* __ADG_CONTAINER_H__ */ diff --git a/src/adg/adg-dash-private.h b/src/adg/adg-dash-private.h index 1212f782..d5cd4999 100644 --- a/src/adg/adg-dash-private.h +++ b/src/adg/adg-dash-private.h @@ -1,36 +1,36 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_DASH_PRIVATE_H__ #define __ADG_DASH_PRIVATE_H__ G_BEGIN_DECLS struct _AdgDash { gint num_dashes; gdouble *dashes; gdouble offset; }; G_END_DECLS #endif /* __ADG_DASH_PRIVATE_H__ */ diff --git a/src/adg/adg-dash.c b/src/adg/adg-dash.c index 905230b2..f4142065 100644 --- a/src/adg/adg-dash.c +++ b/src/adg/adg-dash.c @@ -1,373 +1,373 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-dash * @Section_Id:AdgDash * @title: AdgDash * @short_description: Dash pattern for line stroking * * The #AdgDash boxed type wraps the values needed by cairo to * univoquely identify a dash pattern, an array of positive values. Each * value provides the length of alternate "on" and "off" portions of the * stroke. The offset specifies an offset into the pattern at which the * stroke begins. * * Each "on" segment will have caps applied as if the segment were a * separate sub-path. In particular, it is valid to use an "on" length of * 0 with %CAIRO_LINE_CAP_ROUND or %CAIRO_LINE_CAP_SQUARE in order to * distribute dots or squares along a path. * * Check the cairo_set_dash() documentation for further details: * http://www.cairographics.org/manual/cairo-cairo-t.html#cairo-set-dash * * Since: 1.0 **/ /** * AdgDash: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-dash.h" #include "adg-dash-private.h" #include GType adg_dash_get_type(void) { static GType dash_type = 0; if (G_UNLIKELY(dash_type == 0)) dash_type = g_boxed_type_register_static("AdgDash", (GBoxedCopyFunc) adg_dash_dup, (GBoxedFreeFunc) adg_dash_destroy); return dash_type; } /** * adg_dash_dup: * @src: an #AdgDash instance * * Duplicates @src. The returned value must be freed with adg_dash_destroy() * when no longer needed. * * Returns: (transfer full): the duplicate of @src. * * Since: 1.0 **/ AdgDash * adg_dash_dup(const AdgDash *src) { AdgDash *dash; g_return_val_if_fail(src != NULL, NULL); dash = cpml_memdup(src, sizeof(AdgDash)); dash->dashes = cpml_memdup(src->dashes, sizeof(gdouble) * src->num_dashes); return dash; } /** * adg_dash_new: * * Creates a new empty dash pattern. * * Returns: (transfer full): the newly created dash pattern. * * Since: 1.0 **/ AdgDash * adg_dash_new(void) { AdgDash *dash = g_new(AdgDash, 1); dash->dashes = NULL; dash->num_dashes = 0; dash->offset = 0; return dash; } /** * adg_dash_new_with_dashes: * @num_dashes: the number of dashes to set * @...: lengths (a list of double values) of each dash * * Creates a new dash pattern, presetting some dashes on it. * * Returns: (transfer full): the newly created dash pattern. * * Since: 1.0 **/ AdgDash * adg_dash_new_with_dashes(gint num_dashes, ...) { AdgDash *dash; va_list var_args; va_start(var_args, num_dashes); dash = adg_dash_new_with_dashes_valist(num_dashes, var_args); va_end(var_args); return dash; } /** * adg_dash_new_with_dashes_valist: * @num_dashes: number of dashes to append * @var_args: a va_list containing @num_dashes list of #gdouble * * Variadic version of adg_dash_new_with_dashes(). * * Returns: (transfer full): the newly created dash pattern. * * Since: 1.0 **/ AdgDash * adg_dash_new_with_dashes_valist(gint num_dashes, va_list var_args) { AdgDash *dash = adg_dash_new(); adg_dash_append_dashes_valist(dash, num_dashes, var_args); return dash; } /** * adg_dash_new_with_dashes_array: (rename-to adg_dash_new_with_dashes) * @num_dashes: number of dashes to append * @dashes: (array length=num_dashes): array of @num_dashes gdoubles * * Array version of adg_dash_new_with_dashes(). * * Returns: (transfer full): the newly created dash pattern. * * Since: 1.0 **/ AdgDash * adg_dash_new_with_dashes_array(gint num_dashes, const gdouble *dashes) { AdgDash *dash = adg_dash_new(); adg_dash_append_dashes_array(dash, num_dashes, dashes); return dash; } /** * adg_dash_append_dash: * @dash: an #AdgDash instance * @length: the length value * * Appends to the @dash pattern a new dash of the specified @length value. * * Since: 1.0 **/ void adg_dash_append_dash(AdgDash *dash, gdouble length) { g_return_if_fail(dash != NULL); ++ dash->num_dashes; dash->dashes = g_realloc(dash->dashes, sizeof(gdouble) * dash->num_dashes); dash->dashes[dash->num_dashes - 1] = length; } /** * adg_dash_append_dashes: * @dash: an #AdgDash instance * @num_dashes: number of dashes to append * @...: a @num_dashes list of #gdouble * * Appends to the current @dash pattern @num_dashes number of dashes. * The length of each dash must be specified as gdouble in the arguments. * * Since: 1.0 **/ void adg_dash_append_dashes(AdgDash *dash, gint num_dashes, ...) { va_list var_args; va_start(var_args, num_dashes); adg_dash_append_dashes_valist(dash, num_dashes, var_args); va_end(var_args); } /** * adg_dash_append_dashes_valist: * @dash: an #AdgDash instance * @num_dashes: number of dashes to append * @var_args: a va_list containing @num_dashes list of #gdouble * * Variadic version of adg_dash_append_dashes(). * * Since: 1.0 **/ void adg_dash_append_dashes_valist(AdgDash *dash, gint num_dashes, va_list var_args) { gdouble length; g_return_if_fail(dash != NULL); g_return_if_fail(num_dashes > 0); while (num_dashes --) { length = va_arg(var_args, gdouble); adg_dash_append_dash(dash, length); } } /** * adg_dash_append_dashes_array: (rename-to adg_dash_append_dashes) * @dash: an #AdgDash instance * @num_dashes: number of dashes to append * @dashes: (array length=num_dashes): array of @num_dashes gdoubles * * Array version of adg_dash_append_dashes(). * * Since: 1.0 **/ void adg_dash_append_dashes_array(AdgDash *dash, gint num_dashes, const gdouble *dashes) { g_return_if_fail(dash != NULL); if (num_dashes > 0) { gint old_dashes = dash->num_dashes; dash->num_dashes += num_dashes; dash->dashes = g_realloc(dash->dashes, sizeof(gdouble) * dash->num_dashes); memcpy(dash->dashes + old_dashes, dashes, sizeof(gdouble) * num_dashes); } } /** * adg_dash_get_num_dashes: * @dash: an #AdgDash instance * * Gets the number of dashes stored inside this dash pattern. * * Returns: the number of dashes or -1 if @dash is invalid. * * Since: 1.0 **/ gint adg_dash_get_num_dashes(const AdgDash *dash) { g_return_val_if_fail(dash != NULL, -1); return dash->num_dashes; } /** * adg_dash_get_dashes: * @dash: an #AdgDash instance * * Gets the array of gdoubles containing the length of each dash of the * pattern of @dash. * * Returns: the array of lengths or NULL on invalid @dash. The array is owned by @dash and must not be modified or freed. * * Since: 1.0 **/ const gdouble * adg_dash_get_dashes(const AdgDash *dash) { g_return_val_if_fail(dash != NULL, NULL); return dash->dashes; } /** * adg_dash_clear_dashes: * @dash: an #AdgDash instance * * Resets the dashes of @dash, effectively clearing the pattern. * * Since: 1.0 **/ void adg_dash_clear_dashes(AdgDash *dash) { g_return_if_fail(dash != NULL); g_free(dash->dashes); dash->dashes = NULL; dash->num_dashes = 0; } /** * adg_dash_set_offset: * @dash: an #AdgDash instance * @offset: the new offset value * * Sets the pattern offset of @dash to @offset. * * Since: 1.0 **/ void adg_dash_set_offset(AdgDash *dash, gdouble offset) { g_return_if_fail(dash != NULL); dash->offset = offset; } /** * adg_dash_get_offset: * @dash: an #AdgDash instance * * Gets the offset of the pattern in @dash. * * Returns: the offset of @dash or 0 on invalid @dash. * * Since: 1.0 **/ gdouble adg_dash_get_offset(const AdgDash *dash) { g_return_val_if_fail(dash != NULL, 0); return dash->offset; } /** * adg_dash_destroy: * @dash: an #AdgDash instance * * Destroys @dash, freeing every resource owned by it. After the destruction * @dash cannot be used anymore. * * Since: 1.0 **/ void adg_dash_destroy(AdgDash *dash) { g_return_if_fail(dash != NULL); g_free(dash->dashes); g_free(dash); } diff --git a/src/adg/adg-dash.h b/src/adg/adg-dash.h index d20c6e68..0e486122 100644 --- a/src/adg/adg-dash.h +++ b/src/adg/adg-dash.h @@ -1,70 +1,70 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_DASH_H__ #define __ADG_DASH_H__ G_BEGIN_DECLS #define ADG_TYPE_DASH (adg_dash_get_type()) typedef struct _AdgDash AdgDash; GType adg_dash_get_type (void); AdgDash * adg_dash_dup (const AdgDash *src); AdgDash * adg_dash_new (void); AdgDash * adg_dash_new_with_dashes (gint num_dashes, ...); AdgDash * adg_dash_new_with_dashes_valist (gint num_dashes, va_list var_args); AdgDash * adg_dash_new_with_dashes_array (gint num_dashes, const gdouble *dashes); void adg_dash_append_dash (AdgDash *dash, gdouble length); void adg_dash_append_dashes (AdgDash *dash, gint num_dashes, ...); void adg_dash_append_dashes_valist (AdgDash *dash, gint num_dashes, va_list var_args); void adg_dash_append_dashes_array (AdgDash *dash, gint num_dashes, const gdouble *dashes); gint adg_dash_get_num_dashes (const AdgDash *dash); const gdouble * adg_dash_get_dashes (const AdgDash *dash); void adg_dash_clear_dashes (AdgDash *dash); void adg_dash_set_offset (AdgDash *dash, gdouble offset); gdouble adg_dash_get_offset (const AdgDash *dash); void adg_dash_destroy (AdgDash *dash); G_END_DECLS #endif /* __ADG_DASH_H__ */ diff --git a/src/adg/adg-dim-private.h b/src/adg/adg-dim-private.h index 17a11837..3601456d 100644 --- a/src/adg/adg-dim-private.h +++ b/src/adg/adg-dim-private.h @@ -1,70 +1,70 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_DIM_PRIVATE_H__ #define __ADG_DIM_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgDimPrivate AdgDimPrivate; typedef struct _AdgDimReplaceData AdgDimReplaceData; struct _AdgDimPrivate { AdgDress dim_dress; AdgPoint *ref1; AdgPoint *ref2; AdgPoint *pos; gdouble level; AdgThreeState outside; AdgThreeState detached; gchar *value; gchar *min; gchar *max; AdgDimStyle *dim_style; struct { AdgAlignment *entity; AdgTextual *value; AdgTextual *min; AdgTextual *max; } quote; struct { gboolean computed; gchar *notice; } geometry; }; struct _AdgDimReplaceData { AdgDimStyle *dim_style; gdouble value; const gchar *format; const gchar *argument; AdgThreeState valorized; /* Just for conveniency */ GRegex *regex; }; G_END_DECLS #endif /* __ADG_DIM_PRIVATE_H__ */ diff --git a/src/adg/adg-dim-style-private.h b/src/adg/adg-dim-style-private.h index a42a2e03..047948a0 100644 --- a/src/adg/adg-dim-style-private.h +++ b/src/adg/adg-dim-style-private.h @@ -1,63 +1,63 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_DIM_STYLE_PRIVATE_H__ #define __ADG_DIM_STYLE_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgMarkerData AdgMarkerData; typedef struct _AdgDimStylePrivate AdgDimStylePrivate; struct _AdgMarkerData { GType type; guint n_properties; const char **names; GValue *values; }; struct _AdgDimStylePrivate { AdgMarkerData marker1; AdgMarkerData marker2; AdgDress color_dress; AdgDress value_dress; AdgDress min_dress; AdgDress max_dress; AdgDress line_dress; AdgDress marker_dress; gdouble from_offset; gdouble to_offset; gdouble beyond; gdouble baseline_spacing; gdouble limits_spacing; CpmlPair quote_shift; CpmlPair limits_shift; gchar *number_format; gchar *number_arguments; gchar *number_tag; gint decimals; gint rounding; }; G_END_DECLS #endif /* __ADG_DIM_STYLE_PRIVATE_H__ */ diff --git a/src/adg/adg-dim-style.c b/src/adg/adg-dim-style.c index fb7a2fd0..cbaf02a5 100644 --- a/src/adg/adg-dim-style.c +++ b/src/adg/adg-dim-style.c @@ -1,1495 +1,1495 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-dim-style * @short_description: Dimension style related stuff * * Contains parameters on how to build dimensions such as the format of the * label, the different font styles (for value and limits), line style, * offsets of the various dimension components etc. * * A typical use case is to set the quote of angular dimensions in sexagesimal * units. To do this, use something similar to the following code: * * |[ * AdgDimStyle *dim_style = ADG_DIM_STYLE(adg_style_clone(adg_dress_get_fallback(adg_dress_dimension))); * * // Set the decimals and rounding properties * adg_dim_style_set_decimals(dim_style, 0); * adg_dim_style_set_rounding(dim_style, 3); * * // Set the arguments and the format of the quote number * adg_dim_style_set_number_arguments(dim_style, "Dm"); * adg_dim_style_set_number_format(dim_style, "%g°(%g')"); * ]| * * After that you can apply the new style to the * ADG_DRESS_DIMENSION of the entity you want to change, * e.g.: * * |[ * adg_entity_set_style(my_angular_dimension, * ADG_DRESS_DIMENSION, * ADG_STYLE(dim_style)); * ]| * * Since: 1.0 */ /** * AdgDimStyle: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-style.h" #include "adg-font-style.h" #include "adg-dash.h" #include "adg-line-style.h" #include "adg-model.h" #include "adg-trail.h" #include "adg-marker.h" #include "adg-dress.h" #include "adg-param-dress.h" #include "adg-dim-style.h" #include "adg-dim-style-private.h" #include #include #define VALID_FORMATS "aieDdMmSs" G_DEFINE_TYPE_WITH_PRIVATE(AdgDimStyle, adg_dim_style, ADG_TYPE_STYLE) enum { PROP_0, PROP_MARKER1, PROP_MARKER2, PROP_COLOR_DRESS, PROP_VALUE_DRESS, PROP_MIN_DRESS, PROP_MAX_DRESS, PROP_LINE_DRESS, PROP_FROM_OFFSET, PROP_TO_OFFSET, PROP_BEYOND, PROP_BASELINE_SPACING, PROP_LIMITS_SPACING, PROP_QUOTE_SHIFT, PROP_LIMITS_SHIFT, PROP_NUMBER_FORMAT, PROP_NUMBER_ARGUMENTS, PROP_NUMBER_TAG, PROP_DECIMALS, PROP_ROUNDING, }; static void _adg_finalize (GObject *object); static void _adg_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static AdgStyle * _adg_clone (AdgStyle *style); static void _adg_apply (AdgStyle *style, AdgEntity *entity, cairo_t *cr); static AdgMarker * _adg_marker_new (const AdgMarkerData *marker_data); static void _adg_marker_data_set (AdgMarkerData *marker_data, AdgMarker *marker); static void _adg_marker_data_unset (AdgMarkerData *marker_data); static void adg_dim_style_class_init(AdgDimStyleClass *klass) { GObjectClass *gobject_class; AdgStyleClass *style_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; style_class = (AdgStyleClass *) klass; gobject_class->finalize = _adg_finalize; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; style_class->clone = _adg_clone; style_class->apply = _adg_apply; param = g_param_spec_object("marker1", P_("First Marker"), P_("The template entity to use as first marker"), ADG_TYPE_MARKER, G_PARAM_WRITABLE); g_object_class_install_property(gobject_class, PROP_MARKER1, param); param = g_param_spec_object("marker2", P_("Second Marker"), P_("The template entity to use as second marker"), ADG_TYPE_MARKER, G_PARAM_WRITABLE); g_object_class_install_property(gobject_class, PROP_MARKER2, param); param = adg_param_spec_dress("color-dress", P_("Color Dress"), P_("Color dress for the whole dimension"), ADG_DRESS_COLOR_DIMENSION, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_COLOR_DRESS, param); param = adg_param_spec_dress("value-dress", P_("Value Dress"), P_("Font dress for the nominal value of the dimension"), ADG_DRESS_FONT_QUOTE_TEXT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_VALUE_DRESS, param); param = adg_param_spec_dress("min-dress", P_("Minimum Limit Dress"), P_("Font dress for the lower limit value"), ADG_DRESS_FONT_QUOTE_ANNOTATION, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_MIN_DRESS, param); param = adg_param_spec_dress("max-dress", P_("Maximum Limit Dress"), P_("Font dress for the upper limit value"), ADG_DRESS_FONT_QUOTE_ANNOTATION, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_MAX_DRESS, param); param = adg_param_spec_dress("line-dress", P_("Line Dress"), P_("Line dress for the baseline and the extension lines"), ADG_DRESS_LINE_DIMENSION, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LINE_DRESS, param); param = g_param_spec_double("from-offset", P_("From Offset"), P_("Offset (in global space) of the extension lines from the path to the quote"), 0, G_MAXDOUBLE, 5, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FROM_OFFSET, param); param = g_param_spec_double("to-offset", P_("To Offset"), P_("How many extend (in global space) the extension lines after hitting the baseline"), 0, G_MAXDOUBLE, 5, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_TO_OFFSET, param); param = g_param_spec_double("beyond", P_("Beyond Length"), P_("How much the baseline should be extended (in global space) beyond the extension lines on dimensions with outside markers"), 0, G_MAXDOUBLE, 20, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_BEYOND, param); param = g_param_spec_double("baseline-spacing", P_("Baseline Spacing"), P_("Distance between two consecutive baselines while stacking dimensions"), 0, G_MAXDOUBLE, 32, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_BASELINE_SPACING, param); param = g_param_spec_double("limits-spacing", P_("Limits Spacing"), P_("Distance between limits/tolerances"), -G_MAXDOUBLE, G_MAXDOUBLE, 2, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LIMITS_SPACING, param); param = g_param_spec_boxed("quote-shift", P_("Quote Shift"), P_("Used to specify a smooth displacement (in global space) of the quote by taking as reference the perfect compact position (the middle of the baseline on common linear dimension, for instance)"), CPML_TYPE_PAIR, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_QUOTE_SHIFT, param); param = g_param_spec_boxed("limits-shift", P_("Limits Shift"), P_("Used to specify a smooth displacement (in global space) for the limits/tolerances by taking as reference the perfect compact position"), CPML_TYPE_PAIR, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LIMITS_SHIFT, param); param = g_param_spec_string("number-format", P_("Number Format"), P_("The format (in printf style) of the numeric component of the basic value"), "%-.7g", G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_NUMBER_FORMAT, param); param = g_param_spec_string("number-arguments", P_("Number Arguments"), P_("The arguments to pass to the format function: see adg_dim_style_set_number_arguments() for further details"), "d", G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_NUMBER_ARGUMENTS, param); param = g_param_spec_string("number-tag", P_("Number Tag"), P_("The tag to substitute inside the value template string"), "<>", G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_NUMBER_TAG, param); param = g_param_spec_int("decimals", P_("Rounded Value Decimals"), P_("Number of significant decimals to use in the format string for rounded values (-1 to disable)"), -1, G_MAXINT, 2, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_DECIMALS, param); param = g_param_spec_int("rounding", P_("Raw Value Decimals"), P_("Number of significant decimals the raw value must be rounded to (-1 to disable)"), -1, G_MAXINT, 6, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_ROUNDING, param); } static void adg_dim_style_init(AdgDimStyle *dim_style) { AdgDimStylePrivate *data = adg_dim_style_get_instance_private(dim_style); data->marker1.type = 0; data->marker1.n_properties = 0; data->marker1.names = NULL; data->marker1.values = NULL; data->marker2.type = 0; data->marker2.n_properties = 0; data->marker2.names = NULL; data->marker2.values = NULL; data->color_dress = ADG_DRESS_COLOR_DIMENSION; data->value_dress = ADG_DRESS_FONT_QUOTE_TEXT; data->min_dress = ADG_DRESS_FONT_QUOTE_ANNOTATION; data->max_dress = ADG_DRESS_FONT_QUOTE_ANNOTATION; data->line_dress = ADG_DRESS_LINE_DIMENSION; data->marker_dress = ADG_DRESS_UNDEFINED; data->from_offset = 6; data->to_offset = 6; data->beyond = 20; data->baseline_spacing = 32; data->limits_spacing = 0; data->quote_shift.x = 4; data->quote_shift.y = -1; data->limits_shift.x = +2; data->limits_shift.y = +2; data->number_format = g_strdup("%-.7g"); data->number_arguments = g_strdup("d"); data->number_tag = g_strdup("<>"); data->decimals = 2; data->rounding = 6; } static void _adg_finalize(GObject *object) { AdgDimStylePrivate *data = adg_dim_style_get_instance_private((AdgDimStyle *) object); _adg_marker_data_unset(&data->marker1); _adg_marker_data_unset(&data->marker2); g_free(data->number_format); data->number_format = NULL; g_free(data->number_arguments); data->number_arguments = NULL; g_free(data->number_tag); data->number_tag = NULL; } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgDimStylePrivate *data = adg_dim_style_get_instance_private((AdgDimStyle *) object); switch (prop_id) { case PROP_COLOR_DRESS: g_value_set_enum(value, data->color_dress); break; case PROP_VALUE_DRESS: g_value_set_enum(value, data->value_dress); break; case PROP_MIN_DRESS: g_value_set_enum(value, data->min_dress); break; case PROP_MAX_DRESS: g_value_set_enum(value, data->max_dress); break; case PROP_LINE_DRESS: g_value_set_enum(value, data->line_dress); break; case PROP_FROM_OFFSET: g_value_set_double(value, data->from_offset); break; case PROP_TO_OFFSET: g_value_set_double(value, data->to_offset); break; case PROP_BEYOND: g_value_set_double(value, data->beyond); break; case PROP_BASELINE_SPACING: g_value_set_double(value, data->baseline_spacing); break; case PROP_LIMITS_SPACING: g_value_set_double(value, data->limits_spacing); break; case PROP_QUOTE_SHIFT: g_value_set_boxed(value, &data->quote_shift); break; case PROP_LIMITS_SHIFT: g_value_set_boxed(value, &data->limits_shift); break; case PROP_NUMBER_FORMAT: g_value_set_string(value, data->number_format); break; case PROP_NUMBER_ARGUMENTS: g_value_set_string(value, data->number_arguments); break; case PROP_NUMBER_TAG: g_value_set_string(value, data->number_tag); break; case PROP_DECIMALS: g_value_set_int(value, data->decimals); break; case PROP_ROUNDING: g_value_set_int(value, data->rounding); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgDimStyle *dim_style = (AdgDimStyle *) object; AdgDimStylePrivate *data = adg_dim_style_get_instance_private(dim_style); switch (prop_id) { case PROP_MARKER1: _adg_marker_data_set(&data->marker1, g_value_get_object(value)); break; case PROP_MARKER2: _adg_marker_data_set(&data->marker2, g_value_get_object(value)); break; case PROP_COLOR_DRESS: data->color_dress = g_value_get_enum(value); break; case PROP_VALUE_DRESS: data->value_dress = g_value_get_enum(value); break; case PROP_MIN_DRESS: data->min_dress = g_value_get_enum(value); break; case PROP_MAX_DRESS: data->max_dress = g_value_get_enum(value); break; case PROP_LINE_DRESS: data->line_dress = g_value_get_enum(value); break; case PROP_FROM_OFFSET: data->from_offset = g_value_get_double(value); break; case PROP_TO_OFFSET: data->to_offset = g_value_get_double(value); break; case PROP_BEYOND: data->beyond = g_value_get_double(value); break; case PROP_BASELINE_SPACING: data->baseline_spacing = g_value_get_double(value); break; case PROP_LIMITS_SPACING: data->limits_spacing = g_value_get_double(value); break; case PROP_QUOTE_SHIFT: cpml_pair_copy(&data->quote_shift, g_value_get_boxed(value)); break; case PROP_LIMITS_SHIFT: cpml_pair_copy(&data->limits_shift, g_value_get_boxed(value)); break; case PROP_NUMBER_FORMAT: g_free(data->number_format); data->number_format = g_value_dup_string(value); break; case PROP_NUMBER_ARGUMENTS: { const gchar *arguments = g_value_get_string(value); g_return_if_fail(arguments == NULL || strspn(arguments, VALID_FORMATS) == strlen(arguments)); g_free(data->number_arguments); data->number_arguments = g_strdup(arguments); break; } case PROP_NUMBER_TAG: g_free(data->number_tag); data->number_tag = g_value_dup_string(value); break; case PROP_DECIMALS: data->decimals = g_value_get_int(value); break; case PROP_ROUNDING: data->rounding = g_value_get_int(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_dim_style_new: * * Constructs a new empty dimension style initialized with default params. * * Returns: (transfer full): a newly created dimension style. * * Since: 1.0 **/ AdgDimStyle * adg_dim_style_new(void) { return g_object_new(ADG_TYPE_DIM_STYLE, NULL); } /** * adg_dim_style_set_marker1: * @dim_style: an #AdgStyle * @marker: an #AdgMarker derived entity * * Uses @marker as entity template to generate a new marker entity * when a call to adg_dim_style_marker1_new() is made. It is allowed * to pass NULL as @marker, in which case the * template data of the first marker are unset. * * This method duplicates internally the property values of @marker, * so any further change to @marker does not affect @dim_style anymore. * This also means @marker could be destroyed without problems after * this call because @dim_style uses only its property values and does * not add any references to @marker. * * Since: 1.0 **/ void adg_dim_style_set_marker1(AdgDimStyle *dim_style, AdgMarker *marker) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "marker1", marker, NULL); } /** * adg_dim_style_marker1_new: * @dim_style: an #AdgDimStyle * * Creates a new marker entity by cloning the #AdgDimStyle:marker1 * object. The returned entity should be unreferenced with * g_object_unref() when no longer needed. * * Returns: (transfer full): a newly created marker or NULL if the #AdgDimStyle:marker1 property is not set or on errors. * * Since: 1.0 **/ AdgMarker * adg_dim_style_marker1_new(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), NULL); data = adg_dim_style_get_instance_private(dim_style); return _adg_marker_new(&data->marker1); } /** * adg_dim_style_set_marker2: * @dim_style: an #AdgStyle * @marker: an #AdgMarker derived entity * * Uses @marker as entity template to generate a new marker entity * when a call to adg_dim_style_marker2_new() is made. It is allowed * to pass NULL as @marker, in which case the * template data of the second marker are unset. * * This method duplicates internally the property values of @marker, * so any further change to @marker does not affect @dim_style anymore. * This also means @marker could be destroyed without problems after * this call because @dim_style uses only its property values and does * not add any references to @marker. * * Since: 1.0 **/ void adg_dim_style_set_marker2(AdgDimStyle *dim_style, AdgMarker *marker) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "marker2", marker, NULL); } /** * adg_dim_style_marker2_new: * @dim_style: an #AdgDimStyle * * Creates a new marker entity by cloning the #AdgDimStyle:marker2 * object. The returned entity should be unreferenced with * g_object_unref() when no longer needed. * * Returns: (transfer full): a newly created marker or NULL if the #AdgDimStyle:marker2 property is not set or on errors. * * Since: 1.0 **/ AdgMarker * adg_dim_style_marker2_new(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), NULL); data = adg_dim_style_get_instance_private(dim_style); return _adg_marker_new(&data->marker2); } /** * adg_dim_style_set_color_dress: * @dim_style: an #AdgDimStyle object * @dress: the new color dress * * Sets a new color dress on @dim_style. * * Since: 1.0 **/ void adg_dim_style_set_color_dress(AdgDimStyle *dim_style, AdgDress dress) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "color-dress", dress, NULL); } /** * adg_dim_style_get_color_dress: * @dim_style: an #AdgDimStyle object * * Gets the @dim_style color dress to be used. This dress should be * intended as a fallback color as it could be overriden by more * specific dresses, such as a color explicitely specified on the * #AdgDimStyle:value-dress. * * Returns: (transfer none): the color dress. * * Since: 1.0 **/ AdgDress adg_dim_style_get_color_dress(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), ADG_DRESS_UNDEFINED); data = adg_dim_style_get_instance_private(dim_style); return data->color_dress; } /** * adg_dim_style_set_value_dress: * @dim_style: an #AdgDimStyle object * @dress: the new basic value font style * * Sets a new dress on @dim_style for the basic value. * * Since: 1.0 **/ void adg_dim_style_set_value_dress(AdgDimStyle *dim_style, AdgDress dress) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "value-dress", dress, NULL); } /** * adg_dim_style_get_value_dress: * @dim_style: an #AdgDimStyle object * * Gets the font dress to be used for the basic value of dimensions * with @dim_style. * * Returns: (transfer none): the font dress. * * Since: 1.0 **/ AdgDress adg_dim_style_get_value_dress(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), ADG_DRESS_UNDEFINED); data = adg_dim_style_get_instance_private(dim_style); return data->value_dress; } /** * adg_dim_style_set_min_dress: * @dim_style: an #AdgDimStyle object * @dress: the new lower limit dress * * Sets a new dress on @dim_style for the lower limit value. * * Since: 1.0 **/ void adg_dim_style_set_min_dress(AdgDimStyle *dim_style, AdgDress dress) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "min-dress", dress, NULL); } /** * adg_dim_style_get_min_dress: * @dim_style: an #AdgDimStyle object * * Gets the @dim_style dress to be used for the lower limit. * * Returns: (transfer none): the lower limit dress. * * Since: 1.0 **/ AdgDress adg_dim_style_get_min_dress(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), ADG_DRESS_UNDEFINED); data = adg_dim_style_get_instance_private(dim_style); return data->min_dress; } /** * adg_dim_style_set_max_dress: * @dim_style: an #AdgDimStyle object * @dress: the new upper limit dress * * Sets a new dress on @dim_style for the upper limit value. * * Since: 1.0 **/ void adg_dim_style_set_max_dress(AdgDimStyle *dim_style, AdgDress dress) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "max-dress", dress, NULL); } /** * adg_dim_style_get_max_dress: * @dim_style: an #AdgDimStyle object * * Gets the @dim_style dress to be used for the upper limit. * * Returns: (transfer none): the upper limit dress. * * Since: 1.0 **/ AdgDress adg_dim_style_get_max_dress(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), ADG_DRESS_UNDEFINED); data = adg_dim_style_get_instance_private(dim_style); return data->max_dress; } /** * adg_dim_style_set_line_dress: * @dim_style: an #AdgDimStyle object * @dress: the new line dress * * Sets a new line dress on @dim_style. * * Since: 1.0 **/ void adg_dim_style_set_line_dress(AdgDimStyle *dim_style, AdgDress dress) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "line-dress", dress, NULL); } /** * adg_dim_style_get_line_dress: * @dim_style: an #AdgDimStyle object * * Gets the line dress to be used for rendering the base and * the extension lines with @dim_style. * * Returns: (transfer none): the line dress. * * Since: 1.0 **/ AdgDress adg_dim_style_get_line_dress(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), ADG_DRESS_UNDEFINED); data = adg_dim_style_get_instance_private(dim_style); return data->line_dress; } /** * adg_dim_style_set_from_offset: * @dim_style: an #AdgDimStyle object * @offset: the new offset * * Sets a new value in the #AdgDimStyle:from-offset property. * * Since: 1.0 **/ void adg_dim_style_set_from_offset(AdgDimStyle *dim_style, gdouble offset) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "from-offset", offset, NULL); } /** * adg_dim_style_get_from_offset: * @dim_style: an #AdgDimStyle object * * Gets the distance (in global space) the extension lines must keep from the * sensed points. * * Returns: the requested distance. * * Since: 1.0 **/ gdouble adg_dim_style_get_from_offset(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), 0); data = adg_dim_style_get_instance_private(dim_style); return data->from_offset; } /** * adg_dim_style_set_to_offset: * @dim_style: an #AdgDimStyle object * @offset: the new offset * * Sets a new value in the #AdgDimStyle:to-offset property. * * Since: 1.0 **/ void adg_dim_style_set_to_offset(AdgDimStyle *dim_style, gdouble offset) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "to-offset", offset, NULL); } /** * adg_dim_style_get_to_offset: * @dim_style: an #AdgDimStyle object * * Gets how much (in global space) the extension lines must extend after * crossing the baseline. * * Returns: the requested distance. * * Since: 1.0 **/ gdouble adg_dim_style_get_to_offset(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), 0); data = adg_dim_style_get_instance_private(dim_style); return data->to_offset; } /** * adg_dim_style_set_beyond: * @dim_style: an #AdgDimStyle object * @beyond: the new length * * Sets a new value in the #AdgDimStyle:beyond property. * * Since: 1.0 **/ void adg_dim_style_set_beyond(AdgDimStyle *dim_style, gdouble beyond) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "beyond", beyond, NULL); } /** * adg_dim_style_get_beyond: * @dim_style: an #AdgDimStyle object * * Gets how much (in global space) the baseline should extend beyond * the extension lines on dimension with outside markers. * * Returns: the requested beyond length. * * Since: 1.0 **/ gdouble adg_dim_style_get_beyond(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), 0); data = adg_dim_style_get_instance_private(dim_style); return data->beyond; } /** * adg_dim_style_set_baseline_spacing: * @dim_style: an #AdgDimStyle object * @spacing: the new spacing * * Sets a new value in the #AdgDimStyle:baseline-spacing value. * * Since: 1.0 **/ void adg_dim_style_set_baseline_spacing(AdgDimStyle *dim_style, gdouble spacing) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "baseline-spacing", spacing, NULL); } /** * adg_dim_style_get_baseline_spacing: * @dim_style: an #AdgDimStyle object * * Gets the distance between two consecutive baselines * while stacking dimensions. * * Returns: the requested spacing. * * Since: 1.0 **/ gdouble adg_dim_style_get_baseline_spacing(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), 0); data = adg_dim_style_get_instance_private(dim_style); return data->baseline_spacing; } /** * adg_dim_style_set_limits_spacing: * @dim_style: an #AdgDimStyle object * @spacing: the new spacing * * Sets a new #AdgDimStyle:limits-spacing value. * * Since: 1.0 **/ void adg_dim_style_set_limits_spacing(AdgDimStyle *dim_style, gdouble spacing) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "limits-spacing", spacing, NULL); } /** * adg_dim_style_get_limits_spacing: * @dim_style: an #AdgDimStyle object * * Gets the distance (in global space) between the limits/tolerances. * * Returns: the requested spacing. * * Since: 1.0 **/ gdouble adg_dim_style_get_limits_spacing(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), 0); data = adg_dim_style_get_instance_private(dim_style); return data->limits_spacing; } /** * adg_dim_style_set_quote_shift: * @dim_style: an #AdgDimStyle object * @shift: the new displacement * * Sets a new #AdgDimStyle:quote-shift value. * * Since: 1.0 **/ void adg_dim_style_set_quote_shift(AdgDimStyle *dim_style, const CpmlPair *shift) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "quote-shift", shift, NULL); } /** * adg_dim_style_get_quote_shift: * @dim_style: an #AdgDimStyle object * * Gets the smooth displacement of the quote. The returned pointer refers * to an internal allocated struct and must not be modified or freed. * * Returns: (transfer none): the requested shift. * * Since: 1.0 **/ const CpmlPair * adg_dim_style_get_quote_shift(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), NULL); data = adg_dim_style_get_instance_private(dim_style); return &data->quote_shift; } /** * adg_dim_style_set_limits_shift: * @dim_style: an #AdgDimStyle object * @shift: the new displacement * * Sets a new #AdgDimStyle:limits-shift value. * * Since: 1.0 **/ void adg_dim_style_set_limits_shift(AdgDimStyle *dim_style, const CpmlPair *shift) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "limits-shift", shift, NULL); } /** * adg_dim_style_get_limits_shift: * @dim_style: an #AdgDimStyle object * * Gets the smooth displacement for the limits. The returned pointer * refers to an internal allocated struct and must not be modified or freed. * * Returns: (transfer none): the requested shift. * * Since: 1.0 **/ const CpmlPair * adg_dim_style_get_limits_shift(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), NULL); data = adg_dim_style_get_instance_private(dim_style); return &data->limits_shift; } /** * adg_dim_style_set_number_format: * @dim_style: an #AdgDimStyle object * @format: the new format to adopt * * Sets a new value in the #AdgDimStyle:number-format property. * * @format is similar to a printf() style format string but only 'e', 'E', * 'f', 'F', 'g' and 'G' specifiers are allowed. For reference, the * implementation leverages the g_ascii_formatd() GLib function. If you want * to use special characters (%"(", %")" and %"%") as literals you need to * escape them with a backslash. * * You can use round parenthesis to group together one or more % directives * with other related literals. In that case, when the value bound to the * directive will be 0, the whole group will disappear. Some example: * * |[ * AdgDim *dim; * AdgDimStyle *dim_style; * gchar *text; * * dim = ADG_DIM(adg_ldim_new()); * dim_style = ADG_DIM_STYLE(adg_entity_style(ADG_ENTITY(dim), ADG_DRESS_DIMENSION)); * * adg_dim_style_set_number_arguments(dim_style, "dD"); * adg_dim_style_set_number_format(dim_style, "(%g)( truncated to %g)"); * * text = adg_dim_get_text(dim, 1.2); * g_print("%s\n", text); // Prints "1.2 truncated to 1" * g_free(text); * * text = adg_dim_get_text(dim, 0.2); * g_print("%s\n", text); // Prints "0.2" * g_free(text); * * text = adg_dim_get_text(dim, 0); * g_print("%s\n", text); // Prints "" * g_free(text); * ]| * * Groups can be nested and can have more than one value, in which case all * the values contained in a group must be 0 in order to let it disappear. * This comes in handy for removing trailing 0 fields. A typical example is * the sexagesimal representation of angles: * * |[ * adg_dim_style_set_number_arguments(dim_style, "DMs"); * adg_dim_style_set_number_format(dim_style, "%g°(%g'(%g\"))"); * * text = adg_dim_get_text(dim, 1.5); * g_print("%s\n", text); // Prints "1°30'" * g_free(text); * * text = adg_dim_get_text(dim, 2.002777); * g_print("%s\n", text); // Prints "2°0'10\"" * g_free(text); * ]| * * Since: 1.0 **/ void adg_dim_style_set_number_format(AdgDimStyle *dim_style, const gchar *format) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "number-format", format, NULL); } /** * adg_dim_style_get_number_format: * @dim_style: an #AdgDimStyle object * * Gets the number format (in printf style) of this quoting style. The * returned pointer refers to internally managed text that must not be * modified or freed. * * Returns: (transfer none): the requested format. * * Since: 1.0 **/ const gchar * adg_dim_style_get_number_format(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), NULL); data = adg_dim_style_get_instance_private(dim_style); return data->number_format; } /** * adg_dim_style_set_number_arguments: * @dim_style: an #AdgDimStyle object * @arguments: the arguments to pass to the formatting function * * A string that identifies the arguments to pass to the formatting function. * See adg_dim_style_convert() to know the allowed character that can be * included into this string. * * The number of arguments (i.e. the @arguments length) must match the * #AdgDimStyle:number-format property (i.e. the number of % directives * included in that property). See adg_dim_style_set_number_format() for more * technical details and some examples. * * Since: 1.0 **/ void adg_dim_style_set_number_arguments(AdgDimStyle *dim_style, const gchar *arguments) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "number-arguments", arguments, NULL); } /** * adg_dim_style_get_number_arguments: * @dim_style: an #AdgDimStyle object * * Gets the arguments used by the formatting function. See * adg_dim_style_set_number_arguments() for details on what this means. * * Returns: (transfer none): the requested arguments. * * Since: 1.0 **/ const gchar * adg_dim_style_get_number_arguments(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), NULL); data = adg_dim_style_get_instance_private(dim_style); return data->number_arguments; } /** * adg_dim_style_set_number_tag: * @dim_style: an #AdgDimStyle object * @tag: the new tag * * Sets a new tag in the #AdgDimStyle:number-tag property. * * Since: 1.0 **/ void adg_dim_style_set_number_tag(AdgDimStyle *dim_style, const gchar *tag) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "number-tag", tag, NULL); } /** * adg_dim_style_get_number_tag: * @dim_style: an #AdgDimStyle object * * Gets the number tag of @dim_style. This tag will be used while * generating the set values of the dimensions bound to this style: * check the #AdgDim:value documentation for further details. * * The returned pointer refers to internally managed text that * must not be modified or freed. * * Returns: (transfer none): the requested tag. * * Since: 1.0 **/ const gchar * adg_dim_style_get_number_tag(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), NULL); data = adg_dim_style_get_instance_private(dim_style); return data->number_tag; } /** * adg_dim_style_set_decimals: * @dim_style: an #AdgDimStyle object * @decimals: number of significant decimals * * Sets a new value in the #AdgDimStyle:decimals property. * * Since: 1.0 **/ void adg_dim_style_set_decimals(AdgDimStyle *dim_style, gint decimals) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "decimals", decimals, NULL); } /** * adg_dim_style_get_decimals: * @dim_style: an #AdgDimStyle object * * Gets the decimals the value of a dimension will be rounded to before the * rendering. * * Returns: the number of significant decimals or -2 on errors. * * Since: 1.0 **/ gint adg_dim_style_get_decimals(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), -2); data = adg_dim_style_get_instance_private(dim_style); return data->decimals; } /** * adg_dim_style_set_rounding: * @dim_style: an #AdgDimStyle object * @rounding: number of significant decimals of the raw value * * Sets a new value in the #AdgDimStyle:rounding property. * * Since: 1.0 **/ void adg_dim_style_set_rounding(AdgDimStyle *dim_style, gint rounding) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "rounding", rounding, NULL); } /** * adg_dim_style_get_rounding: * @dim_style: an #AdgDimStyle object * * Gets the number of decimals the raw value must be rounded to. * * Returns: the number of significant decimals or -2 on errors. * * Since: 1.0 **/ gint adg_dim_style_get_rounding(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), -2); data = adg_dim_style_get_instance_private(dim_style); return data->rounding; } /** * adg_dim_style_convert: * @dim_style: an #AdgDimStyle object * @value: (inout): the value to convert * @format: the convertion to apply * * Converts @value using the specific @format algorithm and store the * result in @value itself. If @value is %NULL, nothing is performed. * * In the following the allowed values for @format: * - 'a': the raw @value, i.e. no conversion is performed; * - 'i': the raw number of minutes, i.e. the fractional part of @value x 60 * - 'e': the raw number of seconds, i.e. the fractional part of the raw * number of minutes x 60 * - 'D': the truncated value of the raw value ('a'); * - 'd': the rounded value of the raw value ('a'); * - 'M': the truncated value of the raw number of minutes ('i'); * - 'm': the rounded value of the raw number of minutes ('i'); * - 'S': the truncated value of the raw number of seconds ('e'); * - 's': the rounded value of the raw number of seconds ('e'); * * The rounding is done according to the number of decimal specified in * #AdgDimStyle:decimals. * * Returns: %TRUE if @value has been converted, %FALSE on errors. * * Since: 1.0 **/ gboolean adg_dim_style_convert(AdgDimStyle *dim_style, gdouble *value, gchar format) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), FALSE); /* Inout parameter not provided: just return FALSE */ if (value == NULL) { return FALSE; } data = adg_dim_style_get_instance_private(dim_style); /* Round the raw value, if requested */ if (data->rounding > -1) { gdouble coefficient = pow(10, data->rounding); *value = round(*value * coefficient) / coefficient; } switch (format) { case 'a': /* Raw value */ break; case 'i': /* Raw minutes */ *value = (*value - (gint) *value) * 60; break; case 'e': /* Raw seconds */ adg_dim_style_convert(dim_style, value, 'i'); *value = (*value - (gint) *value) * 60; break; case 'D': /* Truncated value */ *value = (gint) *value; break; case 'd': /* Rounded value */ *value = adg_round(*value, data->decimals); break; case 'M': /* Truncated minutes */ adg_dim_style_convert(dim_style, value, 'i'); *value = (gint) *value; break; case 'm': /* Rounded minutes */ adg_dim_style_convert(dim_style, value, 'i'); *value = adg_round(*value, data->decimals); break; case 'S': /* Truncated seconds */ adg_dim_style_convert(dim_style, value, 'e'); *value = (gint) *value; break; case 's': /* Rounded seconds */ adg_dim_style_convert(dim_style, value, 'e'); *value = adg_round(*value, data->decimals); break; default: g_return_val_if_reached(FALSE); return FALSE; } return TRUE; } static AdgStyle * _adg_clone(AdgStyle *style) { AdgDimStyle *dim_style; AdgMarker *marker; dim_style = (AdgDimStyle *) adg_object_clone(G_OBJECT(style)); /* Manually clone the marker because the underlying properties are * writable only */ marker = adg_dim_style_marker1_new((AdgDimStyle *) style); adg_dim_style_set_marker1(dim_style, marker); g_object_unref(marker); marker = adg_dim_style_marker2_new((AdgDimStyle *) style); adg_dim_style_set_marker2(dim_style, marker); g_object_unref(marker); return (AdgStyle *) dim_style; } static void _adg_apply(AdgStyle *style, AdgEntity *entity, cairo_t *cr) { AdgDimStylePrivate *data = adg_dim_style_get_instance_private((AdgDimStyle *) style); adg_entity_apply_dress(entity, data->color_dress, cr); } static AdgMarker * _adg_marker_new(const AdgMarkerData *marker_data) { if (marker_data->type == 0) return NULL; return (AdgMarker *) g_object_new_with_properties(marker_data->type, marker_data->n_properties, marker_data->names, marker_data->values); } static void _adg_marker_data_set(AdgMarkerData *marker_data, AdgMarker *marker) { g_return_if_fail(marker == NULL || ADG_IS_MARKER(marker)); /* Free the previous marker data, if any */ _adg_marker_data_unset(marker_data); if (marker) { GObject *object; GParamSpec **specs; GParamSpec *spec; guint n; GValue *value; object = (GObject *) marker; specs = g_object_class_list_properties(G_OBJECT_GET_CLASS(marker), &marker_data->n_properties); marker_data->type = G_TYPE_FROM_INSTANCE(marker); marker_data->names = g_new0(const gchar *, marker_data->n_properties); marker_data->values = g_new0(GValue, marker_data->n_properties); for (n = 0; n < marker_data->n_properties; ++n) { spec = specs[n]; /* Using intern strings because `name` is const. * GObject properties are internally managed using non-static * GQuark, so g_intern_string() is the way to go */ marker_data->names[n] = g_intern_string(spec->name); value = &marker_data->values[n]; g_value_init(value, spec->value_type); g_object_get_property(object, spec->name, value); } g_free(specs); } } static void _adg_marker_data_unset(AdgMarkerData *marker_data) { guint n; for (n = 0; n < marker_data->n_properties; ++n) { g_value_unset(&marker_data->values[n]); } g_free(marker_data->names); g_free(marker_data->values); marker_data->type = 0; marker_data->n_properties = 0; marker_data->names = NULL; marker_data->values = NULL; } diff --git a/src/adg/adg-dim-style.h b/src/adg/adg-dim-style.h index 5159857b..131a2b15 100644 --- a/src/adg/adg-dim-style.h +++ b/src/adg/adg-dim-style.h @@ -1,124 +1,124 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_DIM_STYLE_H__ #define __ADG_DIM_STYLE_H__ G_BEGIN_DECLS #define ADG_TYPE_DIM_STYLE (adg_dim_style_get_type()) #define ADG_DIM_STYLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_DIM_STYLE, AdgDimStyle)) #define ADG_DIM_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_DIM_STYLE, AdgDimStyleClass)) #define ADG_IS_DIM_STYLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_DIM_STYLE)) #define ADG_IS_DIM_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_DIM_STYLE)) #define ADG_DIM_STYLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_DIM_STYLE, AdgDimStyleClass)) typedef struct _AdgDimStyle AdgDimStyle; typedef struct _AdgDimStyleClass AdgDimStyleClass; struct _AdgDimStyle { /*< private >*/ AdgStyle parent; }; struct _AdgDimStyleClass { /*< private >*/ AdgStyleClass parent_class; }; GType adg_dim_style_get_type (void); AdgDimStyle * adg_dim_style_new (void); void adg_dim_style_set_marker1 (AdgDimStyle *dim_style, AdgMarker *marker); AdgMarker * adg_dim_style_marker1_new (AdgDimStyle *dim_style); void adg_dim_style_set_marker2 (AdgDimStyle *dim_style, AdgMarker *marker); AdgMarker * adg_dim_style_marker2_new (AdgDimStyle *dim_style); void adg_dim_style_set_color_dress (AdgDimStyle *dim_style, AdgDress dress); AdgDress adg_dim_style_get_color_dress (AdgDimStyle *dim_style); void adg_dim_style_set_value_dress (AdgDimStyle *dim_style, AdgDress dress); AdgDress adg_dim_style_get_value_dress (AdgDimStyle *dim_style); void adg_dim_style_set_min_dress (AdgDimStyle *dim_style, AdgDress dress); AdgDress adg_dim_style_get_min_dress (AdgDimStyle *dim_style); void adg_dim_style_set_max_dress (AdgDimStyle *dim_style, AdgDress dress); AdgDress adg_dim_style_get_max_dress (AdgDimStyle *dim_style); void adg_dim_style_set_line_dress (AdgDimStyle *dim_style, AdgDress dress); AdgDress adg_dim_style_get_line_dress (AdgDimStyle *dim_style); void adg_dim_style_set_from_offset (AdgDimStyle *dim_style, gdouble offset); gdouble adg_dim_style_get_from_offset (AdgDimStyle *dim_style); void adg_dim_style_set_to_offset (AdgDimStyle *dim_style, gdouble offset); gdouble adg_dim_style_get_to_offset (AdgDimStyle *dim_style); void adg_dim_style_set_beyond (AdgDimStyle *dim_style, gdouble beyond); gdouble adg_dim_style_get_beyond (AdgDimStyle *dim_style); void adg_dim_style_set_baseline_spacing (AdgDimStyle *dim_style, gdouble spacing); gdouble adg_dim_style_get_baseline_spacing (AdgDimStyle *dim_style); void adg_dim_style_set_limits_spacing(AdgDimStyle *dim_style, gdouble spacing); gdouble adg_dim_style_get_limits_spacing(AdgDimStyle *dim_style); void adg_dim_style_set_quote_shift (AdgDimStyle *dim_style, const CpmlPair *shift); const CpmlPair *adg_dim_style_get_quote_shift (AdgDimStyle *dim_style); void adg_dim_style_set_limits_shift (AdgDimStyle *dim_style, const CpmlPair *shift); const CpmlPair *adg_dim_style_get_limits_shift (AdgDimStyle *dim_style); void adg_dim_style_set_number_format (AdgDimStyle *dim_style, const gchar *format); const gchar * adg_dim_style_get_number_format (AdgDimStyle *dim_style); void adg_dim_style_set_number_arguments (AdgDimStyle *dim_style, const gchar *arguments); const gchar * adg_dim_style_get_number_arguments (AdgDimStyle *dim_style); void adg_dim_style_set_number_tag (AdgDimStyle *dim_style, const gchar *tag); const gchar * adg_dim_style_get_number_tag (AdgDimStyle *dim_style); void adg_dim_style_set_decimals (AdgDimStyle *dim_style, gint decimals); gint adg_dim_style_get_decimals (AdgDimStyle *dim_style); void adg_dim_style_set_rounding (AdgDimStyle *dim_style, gint rounding); gint adg_dim_style_get_rounding (AdgDimStyle *dim_style); gboolean adg_dim_style_convert (AdgDimStyle *dim_style, gdouble *value, gchar format); G_END_DECLS #endif /* __ADG_DIM_STYLE_H__ */ diff --git a/src/adg/adg-dim.c b/src/adg/adg-dim.c index 4b02ab3f..2adc0256 100644 --- a/src/adg/adg-dim.c +++ b/src/adg/adg-dim.c @@ -1,1844 +1,1844 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-dim * @short_description: Root abstract class for all dimension entities * * The #AdgDim class is the base stub of all the dimension entities. * * Since: 1.0 **/ /** * AdgDim: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ /** * AdgDimClass: * @quote_angle: virtual method that must return the rotation angle of the * quote (in radians) of the current dimension. * @default_value: abstract virtual method that must return the default value * (as a newly allocated string to be freed with g_free()) of * the current dimension. * * The default implementation of @quote_angle flips the quote if it should be * rotated in the bottom right half of the circle, that is: * * * if 1/3 PI <= angle <= -3/4 PI; then angle += PI. * * * The virtual method @default_value instead *must* be implemented by any * derived class. The default implementation will trigger an error if called. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-text-internal.h" #include "adg-container.h" #include "adg-alignment.h" #include "adg-model.h" #include "adg-trail.h" #include "adg-point.h" #include "adg-marker.h" #include "adg-dim-style.h" #include "adg-dress.h" #include "adg-param-dress.h" #include "adg-dim.h" #include "adg-dim-private.h" #include #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_dim_parent_class) #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_dim_parent_class) /* A convenience macro for ORing two AdgThreeState values */ #define OR_3S(a,b) ( \ ((a) == ADG_THREE_STATE_ON || (b) == ADG_THREE_STATE_ON) ? ADG_THREE_STATE_ON : \ ((a) == ADG_THREE_STATE_UNKNOWN && (b) == ADG_THREE_STATE_UNKNOWN) ? ADG_THREE_STATE_UNKNOWN : \ ADG_THREE_STATE_OFF ) G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(AdgDim, adg_dim, ADG_TYPE_ENTITY) enum { PROP_0, PROP_DIM_DRESS, PROP_REF1, PROP_REF2, PROP_POS, PROP_LEVEL, PROP_OUTSIDE, PROP_DETACHED, PROP_VALUE, PROP_MIN, PROP_MAX }; static void _adg_dispose (GObject *object); static void _adg_finalize (GObject *object); static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void _adg_global_changed (AdgEntity *entity); static void _adg_local_changed (AdgEntity *entity); static void _adg_invalidate (AdgEntity *entity); static void _adg_arrange (AdgEntity *entity); static gboolean _adg_compute_geometry (AdgDim *dim); static gchar * _adg_default_value (AdgDim *dim); static gdouble _adg_quote_angle (gdouble angle); static gboolean _adg_set_outside (AdgDim *dim, AdgThreeState outside); static gboolean _adg_set_detached (AdgDim *dim, AdgThreeState detached); static gboolean _adg_set_value (AdgDim *dim, const gchar *value); static gboolean _adg_set_min (AdgDim *dim, const gchar *min); static gboolean _adg_set_max (AdgDim *dim, const gchar *max); static gboolean _adg_replace (const GMatchInfo *match_info, GString *result, gpointer user_data); static gchar * _adg_text_expand (AdgDimReplaceData *data); static void adg_dim_class_init(AdgDimClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->finalize = _adg_finalize; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; entity_class->global_changed = _adg_global_changed; entity_class->local_changed = _adg_local_changed; entity_class->invalidate = _adg_invalidate; entity_class->arrange = _adg_arrange; klass->compute_geometry = _adg_compute_geometry; klass->quote_angle = _adg_quote_angle; klass->default_value = _adg_default_value; param = adg_param_spec_dress("dim-dress", P_("Dimension Dress"), P_("The dress to use for rendering this dimension"), ADG_DRESS_DIMENSION, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_DIM_DRESS, param); param = g_param_spec_boxed("ref1", P_("First Reference"), P_("First reference point of the dimension"), ADG_TYPE_POINT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_REF1, param); param = g_param_spec_boxed("ref2", P_("Second Reference"), P_("Second reference point of the dimension"), ADG_TYPE_POINT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_REF2, param); param = g_param_spec_boxed("pos", P_("Position"), P_("The reference position of the quote: it will be combined with \"level\" to get the real quote position"), ADG_TYPE_POINT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_POS, param); param = g_param_spec_double("level", P_("Level"), P_("The dimension level, that is the factor to multiply the baseline spacing (defined in the dimension style) to get the offset from pos where the quote should be rendered"), -G_MAXDOUBLE, G_MAXDOUBLE, 1.0, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LEVEL, param); param = g_param_spec_enum("outside", P_("Outside"), P_("Whether the arrows must be inside the extension lines (ADG_THREE_STATE_OFF), must be extended outside the extension lines (ADG_THREE_STATE_ON) or should be automatically handled depending on the available space"), ADG_TYPE_THREE_STATE, ADG_THREE_STATE_UNKNOWN, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_OUTSIDE, param); param = g_param_spec_enum("detached", P_("Detached Quote"), P_("Where the quote must be positioned: in the middle of the base line (ADG_THREE_STATE_OFF), near the pos point (ADG_THREE_STATE_ON) or should be automatically deducted depending on the available space"), ADG_TYPE_THREE_STATE, ADG_THREE_STATE_UNKNOWN, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_DETACHED, param); param = g_param_spec_string("value", P_("Value Template"), P_("The template string to be used for generating the set value of the quote"), NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); g_object_class_install_property(gobject_class, PROP_VALUE, param); param = g_param_spec_string("min", P_("Minimum Value or Low Tolerance"), P_("The minimum value allowed or the lowest tolerance from value (depending of the dimension style): set to NULL to suppress"), NULL, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_MIN, param); param = g_param_spec_string("max", P_("Maximum Value or High Tolerance"), P_("The maximum value allowed or the highest tolerance from value (depending of the dimension style): set to NULL to suppress"), NULL, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_MAX, param); } static void adg_dim_init(AdgDim *dim) { AdgDimPrivate *data = adg_dim_get_instance_private(dim); data->dim_dress = ADG_DRESS_DIMENSION; data->ref1 = NULL; data->ref2 = NULL; data->pos = NULL; data->level = 1; data->outside = ADG_THREE_STATE_UNKNOWN; data->detached = ADG_THREE_STATE_UNKNOWN; data->min = NULL; data->max = NULL; data->geometry.computed = FALSE; data->geometry.notice = NULL; #if 0 /* This one is G_PARAM_CONSTRUCT, so set by property inizialization */ data->value = NULL #endif } static void _adg_dispose(GObject *object) { AdgEntity *entity = (AdgEntity *) object; AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) object); if (data->quote.entity) { g_object_unref(data->quote.entity); data->quote.entity = NULL; } if (data->ref1) data->ref1 = adg_entity_point(entity, data->ref1, NULL); if (data->ref2) data->ref2 = adg_entity_point(entity, data->ref2, NULL); if (data->pos) data->pos = adg_entity_point(entity, data->pos, NULL); if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_finalize(GObject *object) { AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) object); g_free(data->value); g_free(data->min); g_free(data->max); if (_ADG_OLD_OBJECT_CLASS->finalize) _ADG_OLD_OBJECT_CLASS->finalize(object); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) object); switch (prop_id) { case PROP_DIM_DRESS: g_value_set_enum(value, data->dim_dress); break; case PROP_REF1: g_value_set_boxed(value, data->ref1); break; case PROP_REF2: g_value_set_boxed(value, data->ref2); break; case PROP_POS: g_value_set_boxed(value, data->pos); break; case PROP_LEVEL: g_value_set_double(value, data->level); break; case PROP_OUTSIDE: g_value_set_enum(value, data->outside); break; case PROP_DETACHED: g_value_set_enum(value, data->detached); break; case PROP_VALUE: g_value_set_string(value, data->value); break; case PROP_MIN: g_value_set_string(value, data->min); break; case PROP_MAX: g_value_set_string(value, data->max); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgEntity *entity = (AdgEntity *) object; AdgDim *dim = (AdgDim *) object; AdgDimPrivate *data = adg_dim_get_instance_private(dim); switch (prop_id) { case PROP_DIM_DRESS: data->dim_dress = g_value_get_enum(value); break; case PROP_REF1: data->ref1 = adg_entity_point(entity, data->ref1, g_value_get_boxed(value)); break; case PROP_REF2: data->ref2 = adg_entity_point(entity, data->ref2, g_value_get_boxed(value)); break; case PROP_POS: data->pos = adg_entity_point(entity, data->pos, g_value_get_boxed(value)); break; case PROP_LEVEL: data->level = g_value_get_double(value); break; case PROP_OUTSIDE: _adg_set_outside(dim, g_value_get_enum(value)); break; case PROP_DETACHED: _adg_set_detached(dim, g_value_get_enum(value)); break; case PROP_VALUE: _adg_set_value(dim, g_value_get_string(value)); break; case PROP_MIN: _adg_set_min(dim, g_value_get_string(value)); break; case PROP_MAX: _adg_set_max(dim, g_value_get_string(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_dim_get_dim_style: * @dim: an #AdgDim * * Gets the internal cached dim style of @dim. * * Returns: (transfer none): the internal AdgDimStyle style. * * Since: 1.0 **/ AdgDimStyle * adg_dim_get_dim_style(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->dim_style; } /** * adg_dim_set_dim_dress: * @dim: an #AdgDim * @dress: the new #AdgDress to use * * Sets a new dimension dress to @dim. The new dress must be * related to the original dress for this property: you cannot * set a dress used for line styles to a dress managing fonts. * * The check is done by calling adg_dress_are_related() with * @dress and the previous dress as arguments. Check out its * documentation for details on what is a related dress. * * Since: 1.0 **/ void adg_dim_set_dim_dress(AdgDim *dim, AdgDress dress) { g_return_if_fail(ADG_IS_DIM(dim)); g_object_set(dim, "dim-dress", dress, NULL); } /** * adg_dim_get_dim_dress: * @dim: an #AdgDim * * Gets the dimension dress to be used in rendering @dim. * * Returns: (transfer none): the current dimension dress. * * Since: 1.0 **/ AdgDress adg_dim_get_dim_dress(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), ADG_DRESS_UNDEFINED); data = adg_dim_get_instance_private(dim); return data->dim_dress; } /** * adg_dim_set_ref1: * @dim: an #AdgDim * @ref1: the new point to use as first reference * * Sets the #AdgDim:ref1 property to @ref1. The old point * is silently discarded, unreferencing its model if that * point was bound to a named pair (hence, possibly destroying * the model if this was the last reference). * * @ref1 can be NULL, in which case the * point is destroyed. * * Since: 1.0 **/ void adg_dim_set_ref1(AdgDim *dim, const AdgPoint *ref1) { g_return_if_fail(ADG_IS_DIM(dim)); g_object_set(dim, "ref1", ref1, NULL); } /** * adg_dim_set_ref1_explicit: * @dim: an #AdgDim * @x: x coordinate of the first reference point * @y: y coordinate of the first reference point * * Sets the #AdgDim:ref1 property to the (@x, @y) explicit * coordinates. The old point is silently discarded, * unreferencing its model if that point was bound to a named * pair (hence, possibly destroying the model if this was the * last reference). * * Since: 1.0 **/ void adg_dim_set_ref1_explicit(AdgDim *dim, gdouble x, gdouble y) { AdgPoint *point = adg_point_new(); adg_point_set_pair_explicit(point, x, y); adg_dim_set_ref1(dim, point); adg_point_destroy(point); } /** * adg_dim_set_ref1_from_pair: * @dim: an #AdgDim * @ref1: the coordinates pair of the first reference point * * Convenient function to set the #AdgDim:ref1 property using a * pair instead of explicit coordinates. * * Since: 1.0 **/ void adg_dim_set_ref1_from_pair(AdgDim *dim, const CpmlPair *ref1) { g_return_if_fail(ref1 != NULL); adg_dim_set_ref1_explicit(dim, ref1->x, ref1->y); } /** * adg_dim_set_ref1_from_model: * @dim: an #AdgDim * @model: the source #AdgModel * @ref1: a named pair in @model * * Binds #AdgDim:ref1 to the @ref1 named pair of @model. If @model * is NULL, the point will be unset. In any case, * the old point is silently discarded, unreferencing its model * if that point was bound to a named pair (hence, possibly destroying * the model if this was the last reference). * * The assignment is lazy so @ref1 could be not be present in @model. * Anyway, at the first access to this point an error will be raised * if the named pair is still missing. * * Since: 1.0 **/ void adg_dim_set_ref1_from_model(AdgDim *dim, AdgModel *model, const gchar *ref1) { AdgPoint *point = adg_point_new(); adg_point_set_pair_from_model(point, model, ref1); adg_dim_set_ref1(dim, point); adg_point_destroy(point); } /** * adg_dim_get_ref1: * @dim: an #AdgDim * * Gets the #AdgDim:ref1 point of @dim. * * The returned point is internally owned and must not be freed * or modified. Anyway it is not const because a call to * adg_point_update() with the returned value must be able to * modify the internal cache. * * Returns: (transfer none): the first reference point. * * Since: 1.0 **/ AdgPoint * adg_dim_get_ref1(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->ref1; } /** * adg_dim_set_ref2: * @dim: an #AdgDim * @ref2: the new point to use as second reference * * Sets the #AdgDim:ref2 property to @ref2. The old point * is silently discarded, unreferencing its model if that * point was bound to a named pair (hence, possibly destroying * the model if it was the last reference). * * @ref2 can be NULL, in which case * the point is destroyed. * * Since: 1.0 **/ void adg_dim_set_ref2(AdgDim *dim, const AdgPoint *ref2) { g_return_if_fail(ADG_IS_DIM(dim)); g_object_set(dim, "ref2", ref2, NULL); } /** * adg_dim_set_ref2_explicit: * @dim: an #AdgDim * @x: x coordinate of the second reference point * @y: y coordinate of the second reference point * * Sets the #AdgDim:ref2 property to the (@x, @y) explicit * coordinates. The old point is silently discarded, * unreferencing its model if that point was bound to a named * pair (hence, possibly destroying the model if this was the * last reference). * * Since: 1.0 **/ void adg_dim_set_ref2_explicit(AdgDim *dim, gdouble x, gdouble y) { AdgPoint *point = adg_point_new(); adg_point_set_pair_explicit(point, x, y); adg_dim_set_ref2(dim, point); adg_point_destroy(point); } /** * adg_dim_set_ref2_from_pair: * @dim: an #AdgDim * @ref2: the coordinates pair of the second reference point * * Convenient function to set the #AdgDim:ref2 property using a * pair instead of explicit coordinates. * * Since: 1.0 **/ void adg_dim_set_ref2_from_pair(AdgDim *dim, const CpmlPair *ref2) { g_return_if_fail(ref2 != NULL); adg_dim_set_ref2_explicit(dim, ref2->x, ref2->y); } /** * adg_dim_set_ref2_from_model: * @dim: an #AdgDim * @model: the source #AdgModel * @ref2: a named pair in @model * * Binds #AdgDim:ref2 to the @ref2 named pair of @model. If @model * is NULL, the point will be unset. In any * case, the old point is silently discarded, unreferencing its * model if that point was bound to a named pair (hence, possibly * destroying the model if this was the last reference). * * The assignment is lazy so @ref2 could be not be present in @model. * Anyway, at the first access to this point an error will be raised * if the named pair is still missing. * * Since: 1.0 **/ void adg_dim_set_ref2_from_model(AdgDim *dim, AdgModel *model, const gchar *ref2) { AdgPoint *point = adg_point_new(); adg_point_set_pair_from_model(point, model, ref2); adg_dim_set_ref2(dim, point); adg_point_destroy(point); } /** * adg_dim_get_ref2: * @dim: an #AdgDim * * Gets the #AdgDim:ref2 point of @dim. * * The returned point is internally owned and must not be freed * or modified. Anyway it is not const because a call to * adg_point_update() with the returned value must be able to * modify the internal cache. * * Returns: (transfer none): the second reference point. * * Since: 1.0 **/ AdgPoint * adg_dim_get_ref2(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->ref2; } /** * adg_dim_set_pos: * @dim: an #AdgDim * @pos: the new point to use as position * * Sets the #AdgDim:pos property of @dim to @pos. The old point * is silently discarded, unreferencing its model if that * point was bound to a named pair (hence, possibly destroying * the model if it was the last reference). * * @pos can be NULL, in which case the * point is destroyed. * * Since: 1.0 **/ void adg_dim_set_pos(AdgDim *dim, const AdgPoint *pos) { g_return_if_fail(ADG_IS_DIM(dim)); g_object_set(dim, "pos", pos, NULL); } /** * adg_dim_set_pos_explicit: * @dim: an #AdgDim * @x: x coordinate of the position * @y: y coordinate of the position * * Sets the #AdgDim:pos property to the (@x, @y) explicit * coordinates. The old point is silently discarded, * unreferencing its model if that point was bound to a named * pair (hence, possibly destroying the model if this was the * last reference). * * Since: 1.0 **/ void adg_dim_set_pos_explicit(AdgDim *dim, gdouble x, gdouble y) { AdgPoint *point = adg_point_new(); adg_point_set_pair_explicit(point, x, y); adg_dim_set_pos(dim, point); adg_point_destroy(point); } /** * adg_dim_set_pos_from_pair: * @dim: an #AdgDim * @pos: the coordinates pair of the position point * * Convenient function to set the #AdgDim:pos property using a * pair instead of explicit coordinates. * * Since: 1.0 **/ void adg_dim_set_pos_from_pair(AdgDim *dim, const CpmlPair *pos) { g_return_if_fail(pos != NULL); adg_dim_set_pos_explicit(dim, pos->x, pos->y); } /** * adg_dim_set_pos_from_model: * @dim: an #AdgDim * @model: the source #AdgModel * @pos: a named pair in @model * * Binds #AdgDim:pos to the @pos named pair of @model. If @model * is NULL, the point will be unset. In any * case, the old point is silently discarded, unreferencing its * model if that point was bound to a named pair (hence, * possibly destroying the model if this was the last reference). * * The assignment is lazy so @pos could be not be present in @model. * Anyway, at the first access to this point an error will be raised * if the named pair is still missing. * * Since: 1.0 **/ void adg_dim_set_pos_from_model(AdgDim *dim, AdgModel *model, const gchar *pos) { AdgPoint *point = adg_point_new(); adg_point_set_pair_from_model(point, model, pos); adg_dim_set_pos(dim, point); adg_point_destroy(point); } /** * adg_dim_get_pos: * @dim: an #AdgDim * * Gets the #AdgDim:pos point of @dim. * * The returned point is internally owned and must not be freed * or modified. Anyway it is not const because a call to * adg_point_update() with the returned value must be able to * modify the internal cache. * * Returns: (transfer none): the position point. * * Since: 1.0 **/ AdgPoint * adg_dim_get_pos(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->pos; } /** * adg_dim_set_level: * @dim: an #AdgDim * @level: the new level * * Sets a new level for this dimension. The level is used to * stack the quotes using a spacing value from dim_style * (specified in global space). * * Since: 1.0 **/ void adg_dim_set_level(AdgDim *dim, gdouble level) { AdgDimPrivate *data; g_return_if_fail(ADG_IS_DIM(dim)); data = adg_dim_get_instance_private(dim); data->level = level; g_object_notify((GObject *) dim, "level"); } /** * adg_dim_get_level: * @dim: an #AdgDim * * Gets the level of this dimension. * * Returns: the level value. * * Since: 1.0 **/ gdouble adg_dim_get_level(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), 0); data = adg_dim_get_instance_private(dim); return data->level; } /** * adg_dim_set_outside: * @dim: an #AdgDim * @outside: the new outside state * * Sets a new state for the #AdgDim:outside flag: check the property * documentation for further details. * * Since: 1.0 **/ void adg_dim_set_outside(AdgDim *dim, AdgThreeState outside) { g_return_if_fail(ADG_IS_DIM(dim)); if (_adg_set_outside(dim, outside)) g_object_notify((GObject *) dim, "outside"); } /** * adg_dim_get_outside: * @dim: an #AdgDim * * Gets the state of the #AdgDim:outside property: check the property * documentation for further details. * * Returns: the current flag state. * * Since: 1.0 **/ AdgThreeState adg_dim_get_outside(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), ADG_THREE_STATE_UNKNOWN); data = adg_dim_get_instance_private(dim); return data->outside; } /** * adg_dim_set_detached: * @dim: an #AdgDim * @detached: the new detached state * * Sets a new state for the #AdgDim:detached flag: check the property * documentation for further details. * * This is used only by dimensions where detaching has meaning. * In some cases, such as with #AdgRDim dimensions, this property is * not used. * * Since: 1.0 **/ void adg_dim_set_detached(AdgDim *dim, AdgThreeState detached) { g_return_if_fail(ADG_IS_DIM(dim)); if (_adg_set_detached(dim, detached)) g_object_notify((GObject *) dim, "detached"); } /** * adg_dim_get_detached: * @dim: an #AdgDim * * Gets the state of the #AdgDim:detached property: check the property * documentation for further details. * * Returns: the current flag state. * * Since: 1.0 **/ AdgThreeState adg_dim_get_detached(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), ADG_THREE_STATE_UNKNOWN); data = adg_dim_get_instance_private(dim); return data->detached; } /** * adg_dim_set_value: * @dim: an #AdgDim * @value: (allow-none): the value text * * Explicitely sets the text to use as value. If @value * is NULL or was never set, an automatic * text is calculated using the format specified in the current * #AdgDimStyle and getting its value by calling * the default_value virtual method. * * Inside the template string, the "<>" tag (or whatever specified * by the #AdgDimStyle:number-tag property) is substituted with the * string returned by default_value. * * Since: 1.0 **/ void adg_dim_set_value(AdgDim *dim, const gchar *value) { g_return_if_fail(ADG_IS_DIM(dim)); if (_adg_set_value(dim, value)) g_object_notify((GObject *) dim, "value"); } /** * adg_dim_get_value: * @dim: an #AdgDim * * Gets the value text. The string is internally owned and * must not be freed or modified. * * Returns: (transfer none): the value text. * * Since: 1.0 **/ const gchar * adg_dim_get_value(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->value; } /** * adg_dim_get_text: * @dim: an #AdgDim * @value: the raw value of the quote * * Gets the final text to show as nominal value into the quote. The string is * the same returned by adg_dim_get_value() with the tag properly expanded. * * The string substituted to the tag is formatted according to the * #AdgDimStyle:number-format and #AdgDimStyle:number-arguments properties. * See the #AdgDimStyle documentation for further details. * * Returns: (transfer full): the final text of the quote. * * Since: 1.0 **/ gchar * adg_dim_get_text(AdgDim *dim, gdouble value) { AdgDimStyle *dim_style; const gchar *format; const gchar *arguments; AdgDimReplaceData data; gchar *raw, *result; GRegex *regex; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); dim_style = adg_dim_get_dim_style(dim); if (dim_style == NULL) { dim_style = (AdgDimStyle *) adg_entity_style((AdgEntity *) dim, adg_dim_get_dim_dress(dim)); } format = adg_dim_style_get_number_format(dim_style); if (format == NULL) { return NULL; } arguments = adg_dim_style_get_number_arguments(dim_style); if (arguments == NULL) { return g_strdup(format); } /* Expand the values */ data.dim_style = dim_style; data.value = value; data.format = format; data.argument = arguments; data.regex = g_regex_new("(?NULL * as @min to disable it. * * Since: 1.0 **/ void adg_dim_set_min(AdgDim *dim, const gchar *min) { g_return_if_fail(ADG_IS_DIM(dim)); if (_adg_set_min(dim, min)) g_object_notify((GObject *) dim, "min"); } /** * adg_dim_get_min: * @dim: an #AdgDim * * Gets the minimum value text or NULL * on minimum value disabled. * * The string is internally owned and must not be freed or modified. * * Returns: (transfer none): the mimimum value text. * * Since: 1.0 **/ const gchar * adg_dim_get_min(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->min; } /** * adg_dim_set_max: * @dim: an #AdgDim * @max: (allow-none): the new maximum value * * Sets the maximum value. Use NULL * as @max to disable it. * * Since: 1.0 **/ void adg_dim_set_max(AdgDim *dim, const gchar *max) { g_return_if_fail(ADG_IS_DIM(dim)); if (_adg_set_max(dim, max)) g_object_notify((GObject *) dim, "max"); } /** * adg_dim_get_max: * @dim: an #AdgDim * * Gets the maximum value text or NULL * on maximum value disabled. * * The string is internally owned and must not be freed or modified. * * Returns: (transfer none): the maximum value text. * * Since: 1.0 **/ const gchar * adg_dim_get_max(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->max; } /** * adg_dim_get_quote: * @dim: an #AdgDim * * Gets the quote entity, if any. This function is valid only after * the #AdgDim implementation of the arrange() virtual method has * been called. * * The returned entity is owned by @dim and should not be * modified or freed. * * * This function is only useful in new dimension implementations. * * * Returns: (transfer none): the quote entity. * * Since: 1.0 **/ AdgAlignment * adg_dim_get_quote(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->quote.entity; } /** * adg_dim_quote_angle: * @dim: an #AdgDim * @angle: an angle (in radians) * * * This function is only useful in new dimension implementations. * * * Converts @angle accordling to the style of @dim. Any quote angle * should be validated by this method because every dimensioning * style has its own convention regardling the text rotation. * * Returns: the angle to use (always in radians). * * Since: 1.0 **/ gdouble adg_dim_quote_angle(AdgDim *dim, gdouble angle) { AdgDimClass *klass; g_return_val_if_fail(ADG_IS_DIM(dim), angle); klass = ADG_DIM_GET_CLASS(dim); if (klass->quote_angle == NULL) return angle; return klass->quote_angle(angle); } /** * adg_dim_has_geometry: * @dim: an #AdgDim * * * This function is only useful in new dimension implementations. * * * Checks if the geometry data of @dim has already been computed. * * Returns: TRUE if the geometry has already been computed, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_dim_has_geometry(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), FALSE); data = adg_dim_get_instance_private(dim); return data->geometry.computed; } /** * adg_dim_switch_geometry: * @dim: an #AdgDim * @computed: the new computed state * * * This function is only useful in new dimension implementations. * * * Sets the computed state of @dim to @computed. This is an internal flag that * keeps track of when the geometry data is up to date. * * Since: 1.0 **/ void adg_dim_switch_geometry(AdgDim *dim, gboolean computed) { AdgDimPrivate *data; g_return_if_fail(ADG_IS_DIM(dim)); data = adg_dim_get_instance_private(dim); data->geometry.computed = computed; } /** * adg_dim_get_geometry_notice: * @dim: an #AdgDim * * Gets the geometry message of @dim, i.e. a notice that explains why a * geometry computation has failed. This message can be used for debugging * purposes, e.g. to know why it is not possible to draw a dimension. * * Returns: the geometry notice or NULL on no notification. * * Since: 1.0 **/ const gchar * adg_dim_get_geometry_notice(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->geometry.notice; } /** * adg_dim_set_geometry_notice: * @dim: an #AdgDim * @notice: new notice message * * * This function is only useful in new dimension implementations. * * * Sets the geometry message of @dim to @notice whenever a geometry * computation has failed. This message can later be read with * adg_dim_get_geometry_notice(): see its documentation for further details. * * Since: 1.0 **/ void adg_dim_set_geometry_notice(AdgDim *dim, const gchar *notice) { AdgDimPrivate *data; g_return_if_fail(ADG_IS_DIM(dim)); data = adg_dim_get_instance_private(dim); if (data->geometry.notice != NULL) g_free(data->geometry.notice); data->geometry.notice = g_strdup(notice); } /** * adg_dim_geometry_missing: * @dim: an #AdgDim * @subject: what is missing * * * This function is only useful in new dimension implementations. * * * Wrapper around adg_dim_set_geometry_notice() that sets a default * notification message when a reference is missing. * * Since: 1.0 **/ void adg_dim_geometry_missing(AdgDim *dim, const gchar *subject) { gchar *notice; g_return_if_fail(subject != NULL); notice = g_strdup_printf(_("'%s' is missing"), subject); adg_dim_set_geometry_notice(dim, notice); g_free(notice); } /** * adg_dim_geometry_coincident: * @dim: an #AdgDim * @first: the name of the first point * @second: the name of the second point * @pos: the coordinates of both points * * * This function is only useful in new dimension implementations. * * * Wrapper around adg_dim_set_geometry_notice() that sets a default * notification message when two references that must be different are * coincidents. * * Since: 1.0 **/ void adg_dim_geometry_coincident(AdgDim *dim, const gchar *first, const gchar *second, const CpmlPair *pos) { gchar *notice; g_return_if_fail(first != NULL); g_return_if_fail(second != NULL); g_return_if_fail(pos != NULL); notice = g_strdup_printf(_("'%s' and '%s' cannot be coincident (%lf, %lf)"), first, second, pos->x, pos->y); adg_dim_set_geometry_notice(dim, notice); g_free(notice); } /** * adg_dim_compute_geometry: * @dim: an #AdgDim * * * This function is only useful in new dimension implementations. * * * Updates the geometry data of @dim, i.e. a set of support data (coordinates, * offsets, shifts) needed in the arrange() phase to build up the entity. * * The data is cached, so further calls just return TRUE. Use * adg_entity_invalidate() to force a recomputation. * * Returns: TRUE if the geometry has already been computed, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_dim_compute_geometry(AdgDim *dim) { AdgDimPrivate *data; AdgDimClass *klass; g_return_val_if_fail(ADG_IS_DIM(dim), FALSE); data = adg_dim_get_instance_private(dim); if (data->geometry.computed) return TRUE; /* compute_geometry virtual method explicitely set to NULL means the * entity does not have any geometry data, so just set computed to TRUE */ klass = ADG_DIM_GET_CLASS(dim); if (klass->compute_geometry != NULL && ! klass->compute_geometry(dim)) return FALSE; data->geometry.computed = TRUE; return TRUE; } static void _adg_global_changed(AdgEntity *entity) { AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) entity); if (_ADG_OLD_ENTITY_CLASS->global_changed) _ADG_OLD_ENTITY_CLASS->global_changed(entity); if (data->quote.entity) adg_entity_global_changed((AdgEntity *) data->quote.entity); } static void _adg_local_changed(AdgEntity *entity) { AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) entity); if (_ADG_OLD_ENTITY_CLASS->local_changed) _ADG_OLD_ENTITY_CLASS->local_changed(entity); if (data->quote.entity) adg_entity_local_changed((AdgEntity *) data->quote.entity); } static void _adg_invalidate(AdgEntity *entity) { AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) entity); if (data->quote.value) { g_object_unref(data->quote.value); data->quote.value = NULL; } if (data->quote.entity) adg_entity_invalidate((AdgEntity *) data->quote.entity); if (data->ref1) adg_point_invalidate(data->ref1); if (data->ref2) adg_point_invalidate(data->ref2); if (data->pos) adg_point_invalidate(data->pos); data->geometry.computed = FALSE; if (data->geometry.notice != NULL) { g_free(data->geometry.notice); data->geometry.notice = NULL; } if (_ADG_OLD_ENTITY_CLASS->invalidate) _ADG_OLD_ENTITY_CLASS->invalidate(entity); } static void _adg_arrange(AdgEntity *entity) { AdgDim *dim; AdgDimPrivate *data; AdgEntity *quote_entity; AdgContainer *quote_container; AdgEntity *value_entity; AdgEntity *min_entity; AdgEntity *max_entity; const CpmlPair *shift; cairo_matrix_t map; dim = (AdgDim *) entity; data = adg_dim_get_instance_private(dim); /* Resolve the dim style */ if (data->dim_style == NULL) data->dim_style = (AdgDimStyle *) adg_entity_style(entity, data->dim_dress); if (data->quote.entity == NULL) data->quote.entity = g_object_new(ADG_TYPE_ALIGNMENT, "local-mix", ADG_MIX_NONE, "parent", dim, NULL); quote_entity = (AdgEntity *) data->quote.entity; quote_container = (AdgContainer *) data->quote.entity; if (data->quote.value == NULL) { AdgDimClass *klass; AdgDress dress; const gchar *tag; gchar *value; gchar *text; klass = ADG_DIM_GET_CLASS(dim); dress = adg_dim_style_get_value_dress(data->dim_style); tag = adg_dim_style_get_number_tag(data->dim_style); value = klass->default_value ? klass->default_value(dim) : NULL; data->quote.value = g_object_new(ADG_TYPE_BEST_TEXT, "local-mix", ADG_MIX_PARENT, "font-dress", dress, NULL); adg_container_add(quote_container, (AdgEntity *) data->quote.value); if (data->value) text = adg_string_replace(data->value, tag, value); else text = g_strdup(value); g_free(value); adg_textual_set_text(data->quote.value, text); g_free(text); } if (data->quote.min == NULL && data->min != NULL) { AdgDress dress = adg_dim_style_get_min_dress(data->dim_style); data->quote.min = g_object_new(ADG_TYPE_BEST_TEXT, "local-mix", ADG_MIX_PARENT, "font-dress", dress, NULL); adg_container_add(quote_container, (AdgEntity *) data->quote.min); adg_textual_set_text(data->quote.min, data->min); } if (data->quote.max == NULL && data->max != NULL) { AdgDress dress = adg_dim_style_get_max_dress(data->dim_style); data->quote.max = g_object_new(ADG_TYPE_BEST_TEXT, "local-mix", ADG_MIX_PARENT, "font-dress", dress, NULL); adg_container_add(quote_container, (AdgEntity *) data->quote.max); adg_textual_set_text(data->quote.max, data->max); } value_entity = (AdgEntity *) data->quote.value; min_entity = (AdgEntity *) data->quote.min; max_entity = (AdgEntity *) data->quote.max; shift = adg_dim_style_get_quote_shift(data->dim_style); adg_entity_set_global_map(quote_entity, adg_matrix_identity()); adg_entity_global_changed(quote_entity); cairo_matrix_init_translate(&map, 0, shift->y); adg_entity_set_global_map(value_entity, &map); adg_entity_arrange(value_entity); /* Limit values (min and max) */ if (min_entity != NULL || max_entity != NULL) { const CpmlPair *limits_shift; gdouble spacing; CpmlPair size; CpmlPair org_min, org_max; limits_shift = adg_dim_style_get_limits_shift(data->dim_style); spacing = adg_dim_style_get_limits_spacing(data->dim_style); size = adg_entity_get_extents(value_entity)->size; org_min.x = size.x + limits_shift->x; org_min.y = -size.y / 2 + limits_shift->y; org_max = org_min; if (min_entity && max_entity) { /* Prearrange the min entity to get its extents */ adg_entity_arrange(min_entity); size = adg_entity_get_extents(min_entity)->size; org_min.y += (size.y + spacing) / 2; org_max.y = org_min.y - size.y - spacing; } if (min_entity != NULL) { cairo_matrix_init_translate(&map, org_min.x, org_min.y); adg_entity_set_global_map(min_entity, &map); adg_entity_arrange(min_entity); } if (max_entity != NULL) { cairo_matrix_init_translate(&map, org_max.x, org_max.y); adg_entity_set_global_map(max_entity, &map); adg_entity_arrange(max_entity); } } } static gboolean _adg_compute_geometry(AdgDim *dim) { g_warning(_("AdgDim::compute_geometry not implemented for '%s'"), g_type_name(G_TYPE_FROM_INSTANCE(dim))); return FALSE; } static gchar * _adg_default_value(AdgDim *dim) { g_warning(_("AdgDim::default_value not implemented for '%s'"), g_type_name(G_TYPE_FROM_INSTANCE(dim))); return g_strdup("undef"); } static gdouble _adg_quote_angle(gdouble angle) { angle = cpml_angle(angle); if (angle > G_PI / 3 || angle <= -G_PI_4 * 3) angle = cpml_angle(angle + G_PI); return angle; } static gboolean _adg_set_outside(AdgDim *dim, AdgThreeState outside) { AdgDimPrivate *data; g_return_val_if_fail(adg_is_enum_value(outside, ADG_TYPE_THREE_STATE), FALSE); data = adg_dim_get_instance_private(dim); if (data->outside == outside) return FALSE; data->outside = outside; return TRUE; } static gboolean _adg_set_detached(AdgDim *dim, AdgThreeState detached) { AdgDimPrivate *data; g_return_val_if_fail(adg_is_enum_value(detached, ADG_TYPE_THREE_STATE), FALSE); data = adg_dim_get_instance_private(dim); if (data->detached == detached) return FALSE; data->detached = detached; return TRUE; } static gboolean _adg_set_value(AdgDim *dim, const gchar *value) { AdgDimPrivate *data = adg_dim_get_instance_private(dim); if (g_strcmp0(value, data->value) == 0) return FALSE; g_free(data->value); data->value = g_strdup(value); if (data->quote.value) { g_object_unref(data->quote.value); data->quote.value = NULL; } return TRUE; } static gboolean _adg_set_min(AdgDim *dim, const gchar *min) { AdgDimPrivate *data = adg_dim_get_instance_private(dim); if (g_strcmp0(min, data->min) == 0) return FALSE; g_free(data->min); data->min = g_strdup(min); if (data->quote.min) { g_object_unref(data->quote.min); data->quote.min = NULL; } return TRUE; } static gboolean _adg_set_max(AdgDim *dim, const gchar *max) { AdgDimPrivate *data = adg_dim_get_instance_private(dim); if (g_strcmp0(max, data->max) == 0) return FALSE; g_free(data->max); data->max = g_strdup(max); if (data->quote.max) { g_object_unref(data->quote.max); data->quote.max = NULL; } return TRUE; } static gboolean _adg_replace(const GMatchInfo *match_info, GString *result, gpointer user_data) { AdgDimReplaceData *data; gdouble value; gchar *format; gchar buffer[256]; data = (AdgDimReplaceData *) user_data; value = data->value; if (! adg_dim_style_convert(data->dim_style, &value, *data->argument)) { /* Conversion failed: invalid argument? */ g_return_val_if_reached(TRUE); return TRUE; } format = g_match_info_fetch(match_info, 0); /* This should never happen */ g_return_val_if_fail(format != NULL, TRUE); /* Consume the recently used argument */ ++ data->argument; g_ascii_formatd(buffer, 256, format, value); g_free(format); g_string_append(result, buffer); /* Set the valorized flag */ if (value != 0) { data->valorized = ADG_THREE_STATE_ON; } else if (data->valorized == ADG_THREE_STATE_UNKNOWN) { data->valorized = ADG_THREE_STATE_OFF; } return FALSE; } static gchar * _adg_text_expand(AdgDimReplaceData *data) { GString *result; const gchar *bog, *eog; gchar *string; AdgThreeState valorized; gssize len; valorized = ADG_THREE_STATE_UNKNOWN; result = g_string_new(""); eog = adg_unescaped_strchr(data->format, ')'); /* Expand eventual groups found in the same nesting level */ while ((bog = adg_unescaped_strchr(data->format, '(')) != NULL) { /* If eog precedes bog, it means that bog is in another nest */ if (eog != NULL && eog < bog) { break; } len = bog - data->format; /* Parse template before the bog */ data->valorized = ADG_THREE_STATE_UNKNOWN; string = g_regex_replace_eval(data->regex, data->format, len, 0, 0, _adg_replace, data, NULL); valorized = OR_3S(valorized, data->valorized); data->format += len+1; g_string_append(result, string); g_free(string); /* Recursively expand the group */ string = _adg_text_expand(data); valorized = OR_3S(valorized, data->valorized); g_string_append(result, string); g_free(string); /* Ensure there is a matching closing parenthesis */ if (*data->format != ')') { g_string_free(result, TRUE); g_return_val_if_reached(NULL); return NULL; } /* Skip the closing parenthesis */ ++ data->format; eog = adg_unescaped_strchr(data->format, ')'); } /* Expand until closing parenthesis (End Of Group) or '\0' */ len = eog == NULL ? strlen(data->format) : (eog - data->format); data->valorized = ADG_THREE_STATE_UNKNOWN; string = g_regex_replace_eval(data->regex, data->format, len, 0, 0, _adg_replace, data, NULL); data->format += len; g_string_append(result, string); g_free(string); /* Store the final valorized state */ valorized = OR_3S(valorized, data->valorized); data->valorized = valorized; /* Drop the result only if we are inside a group */ if (*data->format && valorized == ADG_THREE_STATE_OFF) { g_string_free(result, TRUE); return g_strdup(""); } return g_string_free(result, FALSE); } diff --git a/src/adg/adg-dim.h b/src/adg/adg-dim.h index 127957a5..0ee5c4df 100644 --- a/src/adg/adg-dim.h +++ b/src/adg/adg-dim.h @@ -1,140 +1,140 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_DIM_H__ #define __ADG_DIM_H__ G_BEGIN_DECLS #define ADG_TYPE_DIM (adg_dim_get_type()) #define ADG_DIM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_DIM, AdgDim)) #define ADG_DIM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_DIM, AdgDimClass)) #define ADG_IS_DIM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_DIM)) #define ADG_IS_DIM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_DIM)) #define ADG_DIM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_DIM, AdgDimClass)) typedef struct _AdgDim AdgDim; typedef struct _AdgDimClass AdgDimClass; struct _AdgDim { /*< private >*/ AdgEntity parent; }; struct _AdgDimClass { /*< private >*/ AdgEntityClass parent_class; /*< public >*/ /* Virtual table */ gboolean (*compute_geometry) (AdgDim *dim); gdouble (*quote_angle) (gdouble angle); gchar * (*default_value) (AdgDim *dim); }; GType adg_dim_get_type (void); AdgDimStyle * adg_dim_get_dim_style (AdgDim *dim); AdgDress adg_dim_get_dim_dress (AdgDim *dim); void adg_dim_set_dim_dress (AdgDim *dim, AdgDress dress); void adg_dim_set_ref1 (AdgDim *dim, const AdgPoint *ref1); void adg_dim_set_ref1_explicit (AdgDim *dim, gdouble x, gdouble y); void adg_dim_set_ref1_from_pair (AdgDim *dim, const CpmlPair *ref1); void adg_dim_set_ref1_from_model (AdgDim *dim, AdgModel *model, const gchar *ref1); AdgPoint * adg_dim_get_ref1 (AdgDim *dim); void adg_dim_set_ref2 (AdgDim *dim, const AdgPoint *ref2); void adg_dim_set_ref2_explicit (AdgDim *dim, gdouble x, gdouble y); void adg_dim_set_ref2_from_pair (AdgDim *dim, const CpmlPair *ref2); void adg_dim_set_ref2_from_model (AdgDim *dim, AdgModel *model, const gchar *ref2); AdgPoint * adg_dim_get_ref2 (AdgDim *dim); void adg_dim_set_pos (AdgDim *dim, const AdgPoint *pos); void adg_dim_set_pos_explicit (AdgDim *dim, gdouble x, gdouble y); void adg_dim_set_pos_from_pair (AdgDim *dim, const CpmlPair *pos); void adg_dim_set_pos_from_model (AdgDim *dim, AdgModel *model, const gchar *pos); AdgPoint * adg_dim_get_pos (AdgDim *dim); void adg_dim_set_level (AdgDim *dim, gdouble level); gdouble adg_dim_get_level (AdgDim *dim); void adg_dim_set_outside (AdgDim *dim, AdgThreeState outside); AdgThreeState adg_dim_get_outside (AdgDim *dim); void adg_dim_set_detached (AdgDim *dim, AdgThreeState detached); AdgThreeState adg_dim_get_detached (AdgDim *dim); void adg_dim_set_value (AdgDim *dim, const gchar *value); const gchar * adg_dim_get_value (AdgDim *dim); gchar * adg_dim_get_text (AdgDim *dim, gdouble value); void adg_dim_set_limits (AdgDim *dim, const gchar *min, const gchar *max); void adg_dim_set_min (AdgDim *dim, const gchar *min); const gchar * adg_dim_get_min (AdgDim *dim); void adg_dim_set_max (AdgDim *dim, const gchar *max); const gchar * adg_dim_get_max (AdgDim *dim); gdouble adg_dim_quote_angle (AdgDim *dim, gdouble angle); AdgAlignment * adg_dim_get_quote (AdgDim *dim); gboolean adg_dim_has_geometry (AdgDim *dim); void adg_dim_switch_geometry (AdgDim *dim, gboolean computed); const gchar * adg_dim_get_geometry_notice (AdgDim *dim); void adg_dim_set_geometry_notice (AdgDim *dim, const gchar *notice); void adg_dim_geometry_missing (AdgDim *dim, const gchar *subject); void adg_dim_geometry_coincident (AdgDim *dim, const gchar *first, const gchar *second, const CpmlPair *pos); gboolean adg_dim_compute_geometry (AdgDim *dim); G_END_DECLS #endif /* __ADG_DIM_H__ */ diff --git a/src/adg/adg-dress-private.h b/src/adg/adg-dress-private.h index 35c250fe..d5375ff0 100644 --- a/src/adg/adg-dress-private.h +++ b/src/adg/adg-dress-private.h @@ -1,37 +1,37 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_DRESS_PRIVATE_H__ #define __ADG_DRESS_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgDressPrivate AdgDressPrivate; struct _AdgDressPrivate { AdgStyle *fallback; GType ancestor_type; }; G_END_DECLS #endif /* __ADG_DRESS_PRIVATE_H__ */ diff --git a/src/adg/adg-dress.c b/src/adg/adg-dress.c index 95afce48..07b97f7e 100644 --- a/src/adg/adg-dress.c +++ b/src/adg/adg-dress.c @@ -1,650 +1,650 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-dress * @Section_Id:Dresses * @title: AdgDress * @short_description: The ADG way to associate styles to entities * * The dress is a virtualization of an #AdgStyle instance. #AdgEntity * objects do not directly refer to #AdgStyle but use #AdgDress values * instead. This allows some advanced operations, such as overriding * a dress only in a specific entity branch of the hierarchy or * customize multiple entities at once. * * Since: 1.0 **/ /** * AdgDress: * @ADG_DRESS_UNDEFINED: undefined dress, used for notifying invalid * dresses. * @ADG_DRESS_COLOR: default built-in color. This is a * pass-through dress, that is it does not * change the cairo context when it is * applied. This dress will be resolved to an * #AdgColorStyle instance. * @ADG_DRESS_COLOR_BACKGROUND: default built-in color to be used as the * #AdgCanvas background. This dress will be * resolved to an #AdgColorStyle instance. * @ADG_DRESS_COLOR_STROKE: default built-in color for #AdgStroke * entities. This dress will be resolved to * an #AdgColorStyle instance. * @ADG_DRESS_COLOR_DIMENSION: built-in color used by default in * #AdgDimStyle. This dress will be resolved * to an #AdgColorStyle instance. * @ADG_DRESS_COLOR_ANNOTATION: built-in color used for rendering * helper entities such as #AdgToyText, * #AdgTable and #AdgTitleBlock. This dress * will be resolved to an #AdgColorStyle * instance. * @ADG_DRESS_COLOR_FILL: built-in color used by default by * #AdgFillStyle based styles. This dress * will be resolved to an #AdgColorStyle * instance. * @ADG_DRESS_COLOR_AXIS: default built-in color for stroking * #ADG_DRESS_LINE_AXIS lines. This dress * will be resolved to an #AdgColorStyle * instance. * @ADG_DRESS_COLOR_HIDDEN: default built-in color for stroking * #ADG_DRESS_LINE_HIDDEN lines. This dress * will be resolved to an #AdgColorStyle * instance. * @ADG_DRESS_LINE: default built-in line. This is a * pass-through dress, that is it does not * change the cairo context when it is * applied. This dress will be resolved to * an #AdgLineStyle instance. * @ADG_DRESS_LINE_STROKE: built-in line type to be used by default * for rendering #AdgStroke entities. * This dress will be resolved to an * #AdgLineStyle instance. * @ADG_DRESS_LINE_DIMENSION: built-in line type used by default for * rendering base and extension lines of * dimensions. This dress will be resolved * to an #AdgLineStyle instance. * @ADG_DRESS_LINE_FILL: built-in line type used by #AdgFillStyle. * This dress will be resolved to an * #AdgLineStyle instance. * @ADG_DRESS_LINE_GRID: built-in line type used for rendering * the grid of #AdgTable entities, that is * the frame of the cells. This dress will * be resolved to an #AdgLineStyle instance. * @ADG_DRESS_LINE_FRAME: built-in line type used for rendering the * frame of #AdgTable entities, that is the * frame around the whole table. This dress * will be resolved to an #AdgLineStyle * instance. * @ADG_DRESS_LINE_AXIS: built-in line type used for rendering axis * and centerlines. This dress will be * resolved to an #AdgLineStyle instance. * @ADG_DRESS_LINE_HIDDEN: built-in line type used for rendering * hidden lines and edges. This dress will be * resolved to an #AdgLineStyle instance. * @ADG_DRESS_FONT: default built-in font. This dress will be * resolved to an #AdgFontStyle instance. * @ADG_DRESS_FONT_TEXT: built-in font used by default for * rendering common text such as #AdgToyText * or the value of #AdgTable entities. This * dress will be resolved to an #AdgFontStyle * instance. * @ADG_DRESS_FONT_ANNOTATION: built-in font used for rendering auxiliary * text, such as the titles on #AdgTable * entities. This dress will be resolved to * an #AdgFontStyle instance. * @ADG_DRESS_FONT_QUOTE_TEXT: built-in font used for rendering regular * text on dimension entities, such as the * nominal value and the notes of a quote. * This dress will be resolved to an * #AdgFontStyle instance. * @ADG_DRESS_FONT_QUOTE_ANNOTATION: built-in font used for rendering auxiliary * text on dimension entities, such as the * min and max limits of a quote. This dress * will be resolved to an #AdgFontStyle * instance. * @ADG_DRESS_DIMENSION: default built-in for dimensions. This * dress will be resolved to an #AdgDimStyle * instance. * @ADG_DRESS_FILL: default built-in for filling. This is a * pass-through dress, that is it does not * change the cairo context when it is * applied. This dress will be resolved to an * #AdgFillStyle derived instance. * @ADG_DRESS_FILL_HATCH: built-in dress used by default by * #AdgHatch instances. This dress will be * resolved to an #AdgFillStyle derived * instance. * @ADG_DRESS_TABLE: default built-in for tables. This dress * will be resolved to an #AdgTableStyle * derived instance. * * An index representing a virtual #AdgStyle. The ADG comes equipped * with some built-in dress. * * Since: 1.0 **/ /** * ADG_TYPE_DRESS: * * The type used to express a dress index. It is defined only for GObject * internal and should not be used directly (at least, as far as I know). * * Since: 1.0 **/ /** * ADG_VALUE_HOLDS_DRESS: * @value: a #GValue * * Checks whether a #GValue is actually holding an #AdgDress value or not. * * Returns: TRUE is @value is holding an #AdgDress, FALSE otherwise. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-dash.h" #include "adg-model.h" #include "adg-trail.h" #include "adg-marker.h" #include "adg-arrow.h" #include "adg-style.h" #include "adg-color-style.h" #include "adg-line-style.h" #include "adg-text-internal.h" #include "adg-dim-style.h" #include "adg-fill-style.h" #include "adg-ruled-fill.h" #include "adg-table-style.h" #include "adg-dress.h" #include "adg-dress-private.h" #define MM *2.83464566927 static GArray * _adg_data_array (void); static void _adg_data_register (AdgDress dress, AdgStyle *fallback, GType ancestor_type); static void _adg_data_register_builtins (void); static AdgDressPrivate *_adg_data_lookup (AdgDress dress); /** * adg_dress_from_name: * @name: the name of a dress * * Gets the dress bound to a @name string. No warnings are raised * if the dress is not found. * * Returns: the #AdgDress value or #ADG_DRESS_UNDEFINED if not found. * * Since: 1.0 **/ AdgDress adg_dress_from_name(const gchar *name) { GEnumClass *dress_class = g_type_class_ref(ADG_TYPE_DRESS); GEnumValue *enum_value = g_enum_get_value_by_name(dress_class, name); g_type_class_unref(dress_class); return enum_value != NULL ? enum_value->value : ADG_DRESS_UNDEFINED; } /** * adg_dress_are_related: * @dress1: an #AdgDress * @dress2: another #AdgDress * * Checks whether @dress1 and @dress2 are related, that is * if they have the same ancestor type as returned by * adg_dress_get_ancestor_type(). * * Returns: TRUE if the dresses are related, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_dress_are_related(AdgDress dress1, AdgDress dress2) { GType ancestor_type1, ancestor_type2; ancestor_type1 = adg_dress_get_ancestor_type(dress1); if (ancestor_type1 <= 0) return FALSE; ancestor_type2 = adg_dress_get_ancestor_type(dress2); if (ancestor_type2 <= 0) return FALSE; return ancestor_type1 == ancestor_type2; } /** * adg_dress_set: * @dress: a pointer to an #AdgDress * @src: the source dress * * Copies @src in @dress. This operation can be successful only if * @dress is #ADG_DRESS_UNDEFINED or if it contains a dress related * to @src, i.e. adg_dress_are_related() returns TRUE. * * Returns: TRUE on copy done, FALSE on copy failed or not needed. * * Since: 1.0 **/ gboolean adg_dress_set(AdgDress *dress, AdgDress src) { if (*dress == src) return FALSE; if (*dress != ADG_DRESS_UNDEFINED && !adg_dress_are_related(*dress, src)) return FALSE; *dress = src; return TRUE; } /** * adg_dress_get_name: * @dress: an #AdgDress * * Gets the name associated to @dress. No warnings are raised if * @dress is not found. * * Returns: the requested name or NULL if not found. * * Since: 1.0 **/ const gchar * adg_dress_get_name(AdgDress dress) { GEnumClass *dress_class = g_type_class_ref(ADG_TYPE_DRESS); GEnumValue *enum_value = g_enum_get_value(dress_class, dress); g_type_class_unref(dress_class); return enum_value != NULL ? enum_value->value_name : NULL; } /** * adg_dress_get_ancestor_type: * @dress: an #AdgDress * * Gets the base type that should be present in every #AdgStyle * acceptable by @dress. No warnings are raised if @dress * is not found. * * Returns: the ancestor type or 0 on errors. * * Since: 1.0 **/ GType adg_dress_get_ancestor_type(AdgDress dress) { AdgDressPrivate *data = _adg_data_lookup(dress); return data != NULL ? data->ancestor_type : 0; } /** * adg_dress_set_fallback: * @dress: an #AdgDress * @fallback: (transfer full): the new fallback style * * Associates a new @fallback style to @dress. If the dress is * not valid, a warning message is raised and the function fails. * * @fallback is checked for compatibily with @dress. Any dress holds * an ancestor type: if this type is not found in the @fallback * hierarchy, a warning message is raised and the function fails. * * After a succesfull call, the reference to the previous fallback * (if any) is dropped while a new reference to @fallback is added. * * Since: 1.0 **/ void adg_dress_set_fallback(AdgDress dress, AdgStyle *fallback) { AdgDressPrivate *data = _adg_data_lookup(dress); g_return_if_fail(data != NULL); if (data->fallback == fallback) return; /* Check if the new fallback style is compatible with this dress */ if (fallback != NULL && !adg_dress_style_is_compatible(dress, fallback)) { g_warning(_("%s: the fallback style of '%s' dress (%d) must be a '%s' derived type, but a '%s' has been provided"), G_STRLOC, adg_dress_get_name(dress), dress, g_type_name(data->ancestor_type), g_type_name(G_TYPE_FROM_INSTANCE(fallback))); return; } if (data->fallback != NULL) g_object_unref(data->fallback); data->fallback = fallback; if (data->fallback != NULL) g_object_ref(data->fallback); } /** * adg_dress_get_fallback: * @dress: an #AdgDress * * Gets the fallback style associated to @dress. No warnings * are raised if the dress is not found. The returned style * is owned by dress and should not be freed or modified. * * Returns: (transfer none): the requested #AdgStyle derived instance or NULL if not set. * * Since: 1.0 **/ AdgStyle * adg_dress_get_fallback(AdgDress dress) { AdgDressPrivate *data = _adg_data_lookup(dress); return data != NULL ? data->fallback : NULL; } /** * adg_dress_style_is_compatible: * @dress: an #AdgDress * @style: (transfer none): the #AdgStyle to check * * Checks whether @style is compatible with @dress, that is if * @style has the ancestor style type (as returned by * adg_dress_get_ancestor_type()) in its hierarchy. * * Returns: TRUE if @dress can accept @style, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_dress_style_is_compatible(AdgDress dress, AdgStyle *style) { GType ancestor_type = adg_dress_get_ancestor_type(dress); g_return_val_if_fail(ancestor_type > 0, FALSE); g_return_val_if_fail(ADG_IS_STYLE(style), FALSE); return G_TYPE_CHECK_INSTANCE_TYPE(style, ancestor_type); } static GArray * _adg_data_array(void) { /* The following register keeps track of the metadata bound to every * #AdgDress value, such as the fallback style and the ancestor type. * * The AdgDress value is cohincident with the index of its metadata * inside this register, that is if %ADG_DRESS_COLOR_BACKGROUND is 2, * array->data[2] will contain its metadata. */ static GArray *array = NULL; if (G_UNLIKELY(array == NULL)) { array = g_array_new(FALSE, FALSE, sizeof(AdgDressPrivate)); _adg_data_register_builtins(); } return array; } static void _adg_data_register(AdgDress dress, AdgStyle *fallback, GType ancestor_type) { GArray *array = _adg_data_array(); AdgDressPrivate data; data.fallback = fallback; data.ancestor_type = ancestor_type; g_array_insert_vals(array, dress, cpml_memdup(&data, sizeof(data)), 1); } static void _adg_data_register_builtins(void) { AdgDash *dash; AdgMarker *arrow1, *arrow2; _adg_data_register(ADG_DRESS_UNDEFINED, NULL, G_TYPE_INVALID); /* Predefined colors */ _adg_data_register(ADG_DRESS_COLOR, NULL, ADG_TYPE_COLOR_STYLE); _adg_data_register(ADG_DRESS_COLOR_BACKGROUND, g_object_new(ADG_TYPE_COLOR_STYLE, "blue", 1., "green", 1., "red", 1., NULL), ADG_TYPE_COLOR_STYLE); _adg_data_register(ADG_DRESS_COLOR_STROKE, g_object_new(ADG_TYPE_COLOR_STYLE, NULL), ADG_TYPE_COLOR_STYLE); _adg_data_register(ADG_DRESS_COLOR_DIMENSION, g_object_new(ADG_TYPE_COLOR_STYLE, "red", 0., "green", 0.4, "blue", 0.6, NULL), ADG_TYPE_COLOR_STYLE); _adg_data_register(ADG_DRESS_COLOR_ANNOTATION, g_object_new(ADG_TYPE_COLOR_STYLE, "red", 0.4, "green", 0.4, "blue", 0.2, NULL), ADG_TYPE_COLOR_STYLE); _adg_data_register(ADG_DRESS_COLOR_FILL, g_object_new(ADG_TYPE_COLOR_STYLE, "red", 0.25, "green", 0.25, "blue", 0.25, NULL), ADG_TYPE_COLOR_STYLE); _adg_data_register(ADG_DRESS_COLOR_AXIS, g_object_new(ADG_TYPE_COLOR_STYLE, "red", 0., "green", 0.75, "blue", 0.25, NULL), ADG_TYPE_COLOR_STYLE); _adg_data_register(ADG_DRESS_COLOR_HIDDEN, g_object_new(ADG_TYPE_COLOR_STYLE, "red", 0.5, "green", 0.5, "blue", 0.5, NULL), ADG_TYPE_COLOR_STYLE); /* Predefined lines */ _adg_data_register(ADG_DRESS_LINE, NULL, ADG_TYPE_LINE_STYLE); _adg_data_register(ADG_DRESS_LINE_STROKE, g_object_new(ADG_TYPE_LINE_STYLE, "color-dress", ADG_DRESS_COLOR_STROKE, "width", 1.5, NULL), ADG_TYPE_LINE_STYLE); _adg_data_register(ADG_DRESS_LINE_DIMENSION, g_object_new(ADG_TYPE_LINE_STYLE, "width", 0.5, NULL), ADG_TYPE_LINE_STYLE); _adg_data_register(ADG_DRESS_LINE_FILL, g_object_new(ADG_TYPE_LINE_STYLE, "color-dress", ADG_DRESS_COLOR_FILL, "width", 0.5, NULL), ADG_TYPE_LINE_STYLE); _adg_data_register(ADG_DRESS_LINE_GRID, g_object_new(ADG_TYPE_LINE_STYLE, "antialias", CAIRO_ANTIALIAS_NONE, "width", 1., NULL), ADG_TYPE_LINE_STYLE); _adg_data_register(ADG_DRESS_LINE_FRAME, g_object_new(ADG_TYPE_LINE_STYLE, "color-dress", ADG_DRESS_COLOR_ANNOTATION, "antialias", CAIRO_ANTIALIAS_NONE, "width", 2., NULL), ADG_TYPE_LINE_STYLE); dash = adg_dash_new_with_dashes(4, 2 MM, 2 MM, 10 MM, 2 MM); _adg_data_register(ADG_DRESS_LINE_AXIS, g_object_new(ADG_TYPE_LINE_STYLE, "color-dress", ADG_DRESS_COLOR_AXIS, "width", 0.25 MM, "dash", dash, NULL), ADG_TYPE_LINE_STYLE); adg_dash_destroy(dash); dash = adg_dash_new_with_dashes(2, 6 MM, 3 MM); _adg_data_register(ADG_DRESS_LINE_HIDDEN, g_object_new(ADG_TYPE_LINE_STYLE, "color-dress", ADG_DRESS_COLOR_HIDDEN, "width", 0.25 MM, "dash", dash, NULL), ADG_TYPE_LINE_STYLE); adg_dash_destroy(dash); /* Predefined fonts */ _adg_data_register(ADG_DRESS_FONT, g_object_new(ADG_TYPE_BEST_FONT_STYLE, "family", "Serif", "size", 14., NULL), ADG_TYPE_BEST_FONT_STYLE); _adg_data_register(ADG_DRESS_FONT_TEXT, g_object_new(ADG_TYPE_BEST_FONT_STYLE, "color-dress", ADG_DRESS_COLOR_ANNOTATION, "family", "Sans", "weight", CAIRO_FONT_WEIGHT_BOLD, "size", 12., NULL), ADG_TYPE_BEST_FONT_STYLE); _adg_data_register(ADG_DRESS_FONT_ANNOTATION, g_object_new(ADG_TYPE_BEST_FONT_STYLE, "color-dress", ADG_DRESS_COLOR_ANNOTATION, "family", "Sans", "size", 8., NULL), ADG_TYPE_BEST_FONT_STYLE); _adg_data_register(ADG_DRESS_FONT_QUOTE_TEXT, g_object_new(ADG_TYPE_BEST_FONT_STYLE, "family", "Sans", "weight", CAIRO_FONT_WEIGHT_BOLD, "size", 12., NULL), ADG_TYPE_BEST_FONT_STYLE); _adg_data_register(ADG_DRESS_FONT_QUOTE_ANNOTATION, g_object_new(ADG_TYPE_BEST_FONT_STYLE, "family", "Sans", "size", 8., NULL), ADG_TYPE_BEST_FONT_STYLE); /* Predefined dimension styles */ arrow1 = (AdgMarker *) adg_arrow_new(); arrow2 = (AdgMarker *) adg_arrow_new(); adg_marker_set_pos(arrow2, 1); _adg_data_register(ADG_DRESS_DIMENSION, g_object_new(ADG_TYPE_DIM_STYLE, "marker1", arrow1, "marker2", arrow2, NULL), ADG_TYPE_DIM_STYLE); g_object_unref(arrow1); g_object_unref(arrow2); /* Predefined fill styles */ _adg_data_register(ADG_DRESS_FILL, NULL, ADG_TYPE_FILL_STYLE); _adg_data_register(ADG_DRESS_FILL_HATCH, g_object_new(ADG_TYPE_RULED_FILL, "line-dress", ADG_DRESS_LINE_FILL, NULL), ADG_TYPE_FILL_STYLE); /* Predefined table styles */ _adg_data_register(ADG_DRESS_TABLE, g_object_new(ADG_TYPE_TABLE_STYLE, NULL), ADG_TYPE_TABLE_STYLE); } static AdgDressPrivate * _adg_data_lookup(AdgDress dress) { GArray *array = _adg_data_array(); if (dress >= array->len) return NULL; return ((AdgDressPrivate *) array->data) + dress; } diff --git a/src/adg/adg-dress.h b/src/adg/adg-dress.h index 2b801d4f..0fecaa1d 100644 --- a/src/adg/adg-dress.h +++ b/src/adg/adg-dress.h @@ -1,51 +1,51 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_DRESS_H__ #define __ADG_DRESS_H__ G_BEGIN_DECLS #define ADG_VALUE_HOLDS_DRESS(value) (G_TYPE_CHECK_VALUE_TYPE((value), ADG_TYPE_DRESS)) AdgDress adg_dress_from_name (const gchar *name); gboolean adg_dress_set (AdgDress *dress, AdgDress src); gboolean adg_dress_are_related (AdgDress dress1, AdgDress dress2); const gchar * adg_dress_get_name (AdgDress dress); GType adg_dress_get_ancestor_type (AdgDress dress); void adg_dress_set_fallback (AdgDress dress, AdgStyle *fallback); AdgStyle * adg_dress_get_fallback (AdgDress dress); gboolean adg_dress_style_is_compatible (AdgDress dress, AdgStyle *style); G_END_DECLS #endif /* __ADG_DRESS_H__ */ diff --git a/src/adg/adg-edges-private.h b/src/adg/adg-edges-private.h index afa197ab..e63bc7ec 100644 --- a/src/adg/adg-edges-private.h +++ b/src/adg/adg-edges-private.h @@ -1,43 +1,43 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_EDGES_PRIVATE_H__ #define __ADG_EDGES_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgEdgesPrivate AdgEdgesPrivate; struct _AdgEdgesPrivate { AdgTrail *source; gdouble axis_angle; gdouble critical_angle; struct { cairo_path_t path; GArray *array; } cairo; }; G_END_DECLS #endif /* __ADG_EDGES_PRIVATE_H__ */ diff --git a/src/adg/adg-edges.c b/src/adg/adg-edges.c index 0fccf2e2..f9a68fe5 100644 --- a/src/adg/adg-edges.c +++ b/src/adg/adg-edges.c @@ -1,630 +1,630 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-edges * @short_description: A model with the edges of another model * * The #AdgEdges can be used to render the edges of a yet existing * #AdgTrail source. It is useful for any part made by revolution, * where the shape is symmetric along a specific axis and thus the * edge lines can be easily computed. * * The trail can be set by changing the #AdgEdges:source property * or the relevant APIs. If the trail changes, a recomputation * can be forced by calling the adg_model_clear() method. * * The angle of the axis is implied to pass through the (0,0) point * and has an angle of #AdgEdges:axis-angle radiants. The default * is a 0 radiant angle, meaning the y=0 axis is assumed. * * Since: 1.0 **/ /** * AdgEdges: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-model.h" #include "adg-trail.h" #include #include "adg-edges.h" #include "adg-edges-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_edges_parent_class) #define _ADG_OLD_MODEL_CLASS ((AdgModelClass *) adg_edges_parent_class) #define DEFAULT_CRITICAL_ANGLE (G_PI / 180) G_DEFINE_TYPE_WITH_PRIVATE(AdgEdges, adg_edges, ADG_TYPE_TRAIL) enum { PROP_0, PROP_SOURCE, PROP_CRITICAL_ANGLE, PROP_AXIS_ANGLE }; static void _adg_dispose (GObject *object); static void _adg_finalize (GObject *object); static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void _adg_clear (AdgModel *model); static cairo_path_t * _adg_get_cairo_path (AdgTrail *trail); static void _adg_unset_source (AdgEdges *edges); static void _adg_clear_cairo_path (AdgEdges *edges); static GSList * _adg_get_vertices (GSList *vertices, CpmlSegment *segment, gdouble threshold); static GSList * _adg_optimize_vertices (GSList *vertices); static GArray * _adg_path_build (const GSList *vertices); static void _adg_path_transform (GArray *path_data, const cairo_matrix_t*map); static void adg_edges_class_init(AdgEdgesClass *klass) { GObjectClass *gobject_class; AdgModelClass *model_class; AdgTrailClass *trail_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; model_class = (AdgModelClass *) klass; trail_class = (AdgTrailClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->finalize = _adg_finalize; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; model_class->clear = _adg_clear; trail_class->get_cairo_path = _adg_get_cairo_path; param = g_param_spec_object("source", P_("Source"), P_("The source from which the edges should be computed from"), ADG_TYPE_TRAIL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); g_object_class_install_property(gobject_class, PROP_SOURCE, param); param = g_param_spec_double("axis-angle", P_("Axis Angle"), P_("The angle of the axis of the source trail: it is implied this axis passes through (0,0)"), -G_PI, G_PI, 0, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_AXIS_ANGLE, param); param = g_param_spec_double("critical-angle", P_("Critical Angle"), P_("The angle that defines which corner generates an edge (if the corner angle is greater than this critical angle) and which edge is ignored"), 0, G_PI, DEFAULT_CRITICAL_ANGLE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_CRITICAL_ANGLE, param); } static void adg_edges_init(AdgEdges *edges) { AdgEdgesPrivate *data = adg_edges_get_instance_private(edges); data->source = NULL; data->critical_angle = DEFAULT_CRITICAL_ANGLE; data->axis_angle = 0; data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA; data->cairo.array = NULL; } static void _adg_dispose(GObject *object) { AdgEdges *edges = (AdgEdges *) object; adg_edges_set_source(edges, NULL); if (_ADG_OLD_OBJECT_CLASS->dispose != NULL) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_finalize(GObject *object) { _adg_clear_cairo_path((AdgEdges *) object); if (_ADG_OLD_OBJECT_CLASS->finalize != NULL) _ADG_OLD_OBJECT_CLASS->finalize(object); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgEdges *edges = (AdgEdges *) object; AdgEdgesPrivate *data = adg_edges_get_instance_private(edges); switch (prop_id) { case PROP_SOURCE: g_value_set_object(value, data->source); break; case PROP_AXIS_ANGLE: g_value_set_double(value, data->axis_angle); break; case PROP_CRITICAL_ANGLE: g_value_set_double(value, data->critical_angle); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgEdges *edges = (AdgEdges *) object; AdgEdgesPrivate *data = adg_edges_get_instance_private(edges); gpointer tmp_pointer; gdouble tmp_double; switch (prop_id) { case PROP_SOURCE: tmp_pointer = data->source; data->source = g_value_get_object(value); if (tmp_pointer != data->source) { if (data->source) g_object_weak_ref((GObject *) data->source, (GWeakNotify) _adg_unset_source, object); if (tmp_pointer) g_object_weak_unref((GObject *) tmp_pointer, (GWeakNotify) _adg_unset_source, object); } _adg_clear((AdgModel *) object); break; case PROP_AXIS_ANGLE: tmp_double = g_value_get_double(value); if (data->axis_angle != tmp_double) { data->axis_angle = tmp_double; _adg_clear_cairo_path(edges); } break; case PROP_CRITICAL_ANGLE: tmp_double = g_value_get_double(value); if (data->critical_angle != tmp_double) { data->critical_angle = tmp_double; _adg_clear_cairo_path(edges); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_edges_new: * * Creates a new undefined model to keep track of the edges of * another model. You should at least set the referred #AdgTrail * with adg_edges_set_source(). * * Returns: the newly created edges model * * Since: 1.0 **/ AdgEdges * adg_edges_new(void) { return g_object_new(ADG_TYPE_EDGES, NULL); } /** * adg_edges_new_with_source: * @source: (transfer none): the new source #AdgTrail * * Creates a new edges model explicitely specifying the source trail. * The returned object will own a weak reference on @source. * * Returns: the newly created edges model * * Since: 1.0 **/ AdgEdges * adg_edges_new_with_source(AdgTrail *source) { return g_object_new(ADG_TYPE_EDGES, "source", source, NULL); } /** * adg_edges_get_source: * @edges: an #AdgEdges * * Gets the source #AdgTrail of this @edges model. * The returned object is owned by @edges and should not be * freed or modified. * * Returns: (transfer none): the requested #AdgTrail or NULL on errors. * * Since: 1.0 **/ AdgTrail * adg_edges_get_source(AdgEdges *edges) { AdgEdgesPrivate *data; g_return_val_if_fail(ADG_IS_EDGES(edges), NULL); data = adg_edges_get_instance_private(edges); return data->source; } /** * adg_edges_set_source: * @edges: an #AdgEdges * @source: (transfer none): the new source #AdgTrail * * Sets @source as the source trail for @edges. * After the call, @edges will own a weak reference on @source. * * Since: 1.0 **/ void adg_edges_set_source(AdgEdges *edges, AdgTrail *source) { g_return_if_fail(ADG_IS_EDGES(edges)); g_object_set(edges, "source", source, NULL); } /** * adg_edges_set_axis_angle: * @edges: an #AdgEdges * @angle: the new angle (in radians) * * Sets the axis angle of @edges to @angle, basically setting * the #AdgEdges:axis-angle property. All the resulting edge * lines will be normal to this axis. * * It is implied the axis will pass through the (0,0) point, * so the underlying trail should be constructed accordingly. * * Since: 1.0 **/ void adg_edges_set_axis_angle(AdgEdges *edges, gdouble angle) { g_return_if_fail(ADG_IS_EDGES(edges)); g_object_set(edges, "axis-angle", angle, NULL); } /** * adg_edges_get_axis_angle: * @edges: an #AdgEdges * * Gets the angle of the supposed axis of @edges. Refer to * adg_edges_set_axis_angle() for details of what this parameter * is used for. * * Returns: the value (in radians) of the axis angle * * Since: 1.0 **/ gdouble adg_edges_get_axis_angle(AdgEdges *edges) { AdgEdgesPrivate *data; g_return_val_if_fail(ADG_IS_EDGES(edges), 0); data = adg_edges_get_instance_private(edges); return data->axis_angle; } /** * adg_edges_set_critical_angle: * @edges: an #AdgEdges * @angle: the new angle (in radians) * * Sets the critical angle of @edges to @angle, basically setting * the #AdgEdges:critical-angle property. * * The critical angle defines what corner should generate an edge and * what not. Typical values are close to 0, being 0 the lowest angle * where every corner generates an edge. * * Since: 1.0 **/ void adg_edges_set_critical_angle(AdgEdges *edges, gdouble angle) { g_return_if_fail(ADG_IS_EDGES(edges)); g_object_set(edges, "critical-angle", angle, NULL); } /** * adg_edges_get_critical_angle: * @edges: an #AdgEdges * * Gets the current critical angle of @edges. Refer to * adg_edges_set_critical_angle() for details of what this parameter * is used for. * * Returns: the value (in radians) of the critical angle * * Since: 1.0 **/ gdouble adg_edges_get_critical_angle(AdgEdges *edges) { AdgEdgesPrivate *data; g_return_val_if_fail(ADG_IS_EDGES(edges), 0); data = adg_edges_get_instance_private(edges); return data->critical_angle; } static void _adg_clear(AdgModel *model) { _adg_clear_cairo_path((AdgEdges *) model); if (_ADG_OLD_MODEL_CLASS->clear != NULL) _ADG_OLD_MODEL_CLASS->clear(model); } static cairo_path_t * _adg_get_cairo_path(AdgTrail *trail) { AdgEdges *edges; AdgEdgesPrivate *data; gdouble threshold; CpmlSegment segment; GSList *vertices; cairo_matrix_t map; edges = (AdgEdges *) trail; data = adg_edges_get_instance_private(edges); /* Check for cached path */ if (data->cairo.path.status == CAIRO_STATUS_SUCCESS) return &data->cairo.path; _adg_clear_cairo_path((AdgEdges *) trail); if (data->source != NULL) { gint n; /* The threshold is squared because the _adg_get_vertices() * function uses cpml_pair_squared_distance() against the * two vectors of every corner to avoid sqrt()ing everything */ threshold = sin(data->critical_angle); threshold *= threshold * 2; vertices = NULL; for (n = 1; adg_trail_put_segment(data->source, n, &segment); ++ n) { vertices = _adg_get_vertices(vertices, &segment, threshold); } /* Rotate all the vertices so the axis will always be on y=0: * this is mainly needed to not complicate the _adg_path_build() * code which assumes the y=0 axis is in effect */ cairo_matrix_init_rotate(&map, -data->axis_angle); g_slist_foreach(vertices, (GFunc) cpml_pair_transform, &map); vertices = _adg_optimize_vertices(vertices); data->cairo.array = _adg_path_build(vertices); g_slist_foreach(vertices, (GFunc) g_free, NULL); g_slist_free(vertices); /* Reapply the inverse of the previous transformation to * move the vertices to their original positions */ cairo_matrix_invert(&map); _adg_path_transform(data->cairo.array, &map); data->cairo.path.status = CAIRO_STATUS_SUCCESS; data->cairo.path.data = (cairo_path_data_t *) (data->cairo.array)->data; data->cairo.path.num_data = (data->cairo.array)->len; } return &data->cairo.path; } static void _adg_unset_source(AdgEdges *edges) { AdgEdgesPrivate *data = adg_edges_get_instance_private(edges); data->source = NULL; } static void _adg_clear_cairo_path(AdgEdges *edges) { AdgEdgesPrivate *data = adg_edges_get_instance_private(edges); if (data->cairo.array != NULL) { g_array_free(data->cairo.array, TRUE); data->cairo.array = NULL; } data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA; data->cairo.path.data = NULL; data->cairo.path.num_data = 0; } /** * _adg_get_vertices: * @vertices: a #GSList * @segment: a #CpmlSegment * @threshold: a theshold value * * Collects a list of #CpmlPair corners where the angle has a minimum * threshold incidence of @threshold. The threshold is considered as * the squared distance between the two unit vectors, the one before * and the one after every corner. * * Returns: the original #GSList with new vertices appended. * * Since: 1.0 **/ static GSList * _adg_get_vertices(GSList *vertices, CpmlSegment *segment, gdouble threshold) { CpmlPrimitive primitive; CpmlVector old, new; CpmlPair pair; cpml_primitive_from_segment(&primitive, segment); /* The first vector starts undefined, so it will always be * included (the squared distance between any vector and an * undefined vector will always be greater than threshold) */ old.x = old.y = 0; do { cpml_vector_set_length(&old, 1); cpml_primitive_put_vector_at(&primitive, 0, &new); cpml_vector_set_length(&new, 1); /* Vertical vectors are always added, as they represent * a vertical side and could be filleted, thus skipping * the edge detection */ if (new.x == 0 || cpml_pair_squared_distance(&old, &new) > threshold) { cpml_primitive_put_pair_at(&primitive, 0, &pair); vertices = g_slist_append(vertices, cpml_pair_dup(&pair)); } cpml_primitive_put_vector_at(&primitive, 1, &old); } while (cpml_primitive_next(&primitive)); return vertices; } /* Removes adjacent vertices lying on the same edge */ static GSList * _adg_optimize_vertices(GSList *vertices) { GSList *vertex, *old_vertex; CpmlPair *pair, *old_pair; /* Check for empty list */ if (vertices == NULL) return vertices; old_vertex = vertices; while ((vertex = old_vertex->next) != NULL) { pair = vertex->data; old_pair = old_vertex->data; if (pair->x != old_pair->x) { old_vertex = vertex; continue; } if (old_pair->y < pair->y) { /* Preserve the old vertex and remove the current one */ g_free(pair); vertices = g_slist_delete_link(vertices, vertex); } else { /* Preserve the current vertex and remove the old one */ g_free(old_pair); vertices = g_slist_delete_link(vertices, old_vertex); old_vertex = vertex; } } return vertices; } static GArray * _adg_path_build(const GSList *vertices) { cairo_path_data_t line[4]; GArray *array; const GSList *vertex, *vertex2; const CpmlPair *pair, *pair2; line[0].header.type = CPML_MOVE; line[0].header.length = 2; line[2].header.type = CPML_LINE; line[2].header.length = 2; array = g_array_new(FALSE, FALSE, sizeof(cairo_path_data_t)); vertex = vertices; while (vertex != NULL) { pair = vertex->data; vertex = vertex->next; vertex2 = vertex; while (vertex2 != NULL) { pair2 = vertex2->data; if (pair->x == pair2->x) { /* Opposite vertex found: append a line in the path * and quit from this loop */ cpml_pair_to_cairo(pair, &line[1]); cpml_pair_to_cairo(pair2, &line[3]); array = g_array_append_vals(array, line, G_N_ELEMENTS(line)); break; } vertex2 = vertex2->next; } } return array; } static void _adg_path_transform(GArray *path_data, const cairo_matrix_t *map) { guint n; cairo_path_data_t *data; /* Only the odd items are transformed: the even ones are either * header items, CPML_MOVE and CPML_LINE alternatively */ for (n = 1; n < path_data->len; n += 2) { data = &g_array_index(path_data, cairo_path_data_t, n); cairo_matrix_transform_point(map, &data->point.x, &data->point.y); } } diff --git a/src/adg/adg-edges.h b/src/adg/adg-edges.h index 81174957..3aa173f7 100644 --- a/src/adg/adg-edges.h +++ b/src/adg/adg-edges.h @@ -1,71 +1,71 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_EDGES_H__ #define __ADG_EDGES_H__ G_BEGIN_DECLS #define ADG_TYPE_EDGES (adg_edges_get_type()) #define ADG_EDGES(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_EDGES, AdgEdges)) #define ADG_EDGES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_EDGES, AdgEdgesClass)) #define ADG_IS_EDGES(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_EDGES)) #define ADG_IS_EDGES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_EDGES)) #define ADG_EDGES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_EDGES, AdgEdgesClass)) typedef struct _AdgEdges AdgEdges; typedef struct _AdgEdgesClass AdgEdgesClass; struct _AdgEdges { /*< private >*/ AdgTrail parent; }; struct _AdgEdgesClass { /*< private >*/ AdgTrailClass parent_class; }; GType adg_edges_get_type (void); AdgEdges * adg_edges_new (void); AdgEdges * adg_edges_new_with_source (AdgTrail *source); void adg_edges_set_source (AdgEdges *edges, AdgTrail *source); AdgTrail * adg_edges_get_source (AdgEdges *edges); void adg_edges_set_axis_angle (AdgEdges *edges, gdouble angle); gdouble adg_edges_get_axis_angle (AdgEdges *edges); void adg_edges_set_critical_angle (AdgEdges *edges, gdouble angle); gdouble adg_edges_get_critical_angle (AdgEdges *edges); G_END_DECLS #endif /* __ADG_EDGES_H__ */ diff --git a/src/adg/adg-entity-private.h b/src/adg/adg-entity-private.h index 87cc0de0..29c542d1 100644 --- a/src/adg/adg-entity-private.h +++ b/src/adg/adg-entity-private.h @@ -1,53 +1,53 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_ENTITY_PRIVATE_H__ #define __ADG_ENTITY_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgEntityPrivate AdgEntityPrivate; struct _AdgEntityPrivate { gboolean floating; AdgEntity *parent; cairo_matrix_t global_map; cairo_matrix_t local_map; AdgMix local_mix; GHashTable *hash_styles; struct { gboolean is_defined; cairo_matrix_t matrix; } global; struct { gboolean is_defined; cairo_matrix_t matrix; } local; CpmlExtents extents; }; G_END_DECLS #endif /* __ADG_ENTITY_PRIVATE_H__ */ diff --git a/src/adg/adg-entity.c b/src/adg/adg-entity.c index 11c29c0f..abc8d792 100644 --- a/src/adg/adg-entity.c +++ b/src/adg/adg-entity.c @@ -1,1401 +1,1401 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-entity * @short_description: The base class for renderable objects * * This abstract class provides the base for all renderable objects. * * To create a drawing you usually create entities by calling their * specific constructors and add them to a single #AdgCanvas * instance. When cleaning up, you therefore need to destroy only * that canvas instance with adg_entity_destroy(): this in turn * will destroy every contained entity. This is pretty similar to * how the GTK+ world works: you add #GtkWidget instances to a * single #GtkWindow and destroy only that window when finished. * * To provide a proper #AdgEntity derived type, you must at least * implement its arrange() and render() virtual methods. Also, if * you are using some sort of caching, ensure to clear it in the * invalidate() method. * * Since: 1.0 **/ /** * AdgEntity: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ /** * AdgEntityClass: * @destroy: when a destroy request has been explicitely requested * @parent_set: called whenever the parent of an entity has changed * @global_changed: the global matrix has been invalidated * @local_changed: the local matrix has been invalidated * @invalidate: invalidating callback, used to clear the internal cache * @arrange: prepare the layout and fill the extents struct * @render: rendering callback, it must be implemented by every entity * * Any entity (if not abstract) must implement at least the @render method. * The other signal handlers can be overriden to provide custom behaviors * and usually must chain up the original handler. * * Since: 1.0 **/ /** * AdgEntityCallback: * @entity: an #AdgEntity * @user_data: a general purpose pointer * * Callback used when inspecting or browsing entities. For example, * it is passed to adg_model_foreach_dependency() to perform an * operation on all the entities depending on an #AdgModel. * * Since: 1.0 **/ #include "adg-internal.h" #if GTK3_ENABLED || GTK2_ENABLED #include #endif #include "adg-container.h" #include "adg-table.h" #include "adg-title-block.h" #include #include "adg-dress.h" #include "adg-style.h" #include "adg-model.h" #include "adg-point.h" #include "adg-cairo-fallback.h" #include "adg-entity-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_entity_parent_class) G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(AdgEntity, adg_entity, G_TYPE_INITIALLY_UNOWNED) enum { PROP_0, PROP_FLOATING, PROP_PARENT, PROP_GLOBAL_MAP, PROP_LOCAL_MAP, PROP_LOCAL_MIX }; enum { DESTROY, PARENT_SET, GLOBAL_CHANGED, LOCAL_CHANGED, INVALIDATE, ARRANGE, RENDER, LAST_SIGNAL }; static void _adg_dispose (GObject *object); static void _adg_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void _adg_destroy (AdgEntity *entity); static void _adg_set_parent (AdgEntity *entity, AdgEntity *parent); static void _adg_global_changed (AdgEntity *entity); static void _adg_local_changed (AdgEntity *entity); static void _adg_real_invalidate (AdgEntity *entity); static void _adg_real_arrange (AdgEntity *entity); static void _adg_real_render (AdgEntity *entity, cairo_t *cr); static guint _adg_signals[LAST_SIGNAL] = { 0 }; static gboolean _adg_show_extents = FALSE; static void adg_entity_class_init(AdgEntityClass *klass) { GObjectClass *gobject_class; GParamSpec *param; GClosure *closure; GType param_types[1]; gobject_class = (GObjectClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; klass->destroy = _adg_destroy; klass->parent_set = NULL; klass->global_changed = _adg_global_changed; klass->local_changed = _adg_local_changed; klass->invalidate = NULL; klass->arrange= NULL; klass->render = NULL; param = g_param_spec_boolean("floating", P_("Floating Entity"), P_("Flag that includes (FALSE) or excludes (TRUE) this entity from the computation of the parent entity extents"), FALSE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FLOATING, param); param = g_param_spec_object("parent", P_("Parent Entity"), P_("The parent entity of this entity or NULL if this is a top-level entity"), ADG_TYPE_ENTITY, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_PARENT, param); param = g_param_spec_boxed("global-map", P_("Global Map"), P_("The transformation to be combined with the parent ones to get the global matrix"), CAIRO_GOBJECT_TYPE_MATRIX, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_GLOBAL_MAP, param); param = g_param_spec_boxed("local-map", P_("Local Map"), P_("The local transformation that could be used to compute the local matrix in the way specified by the #AdgEntity:local-mix property"), CAIRO_GOBJECT_TYPE_MATRIX, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LOCAL_MAP, param); param = g_param_spec_enum("local-mix", P_("Local Mix Method"), P_("Define how the local maps of the entity and its ancestors should be combined to get the local matrix"), ADG_TYPE_MIX, ADG_MIX_ANCESTORS, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LOCAL_MIX, param); /** * AdgEntity::destroy: * @entity: an #AdgEntity * * Emitted to explicitely destroy @entity. It unreferences * @entity so that will be destroyed, unless the caller owns * an additional references added with g_object_ref(). * * In the usual case, this is equivalent of calling * g_object_unref() on @entity but, for composite entities or * containers, the destroy signal is propagated to the children. * * Since: 1.0 **/ _adg_signals[DESTROY] = g_signal_new("destroy", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AdgEntityClass, destroy), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * AdgEntity::parent-set: * @entity: an #AdgEntity * @old_parent: the old parent * * Emitted after the parent entity has changed. The new parent * can be inspected using adg_entity_get_parent(). * * It is allowed for both old and new parent to * be NULL. * * Since: 1.0 **/ _adg_signals[PARENT_SET] = g_signal_new("parent-set", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AdgEntityClass, parent_set), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, ADG_TYPE_ENTITY); /** * AdgEntity::global-changed: * @entity: an #AdgEntity * * Emitted when the global map of @entity or any of its parent * has changed. The default handler will compute the new global * matrix, updating the internal cache. * * This signal has lazy emission, i.e. it is emitted only when * the global matrix is requested, typically in the arrange phase. * * Since: 1.0 **/ _adg_signals[GLOBAL_CHANGED] = g_signal_new("global-changed", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AdgEntityClass, global_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * AdgEntity::local-changed: * @entity: an #AdgEntity * * Emitted when the local map of @entity or any of its parent * has changed. The default handler will compute the new local * matrix, updating the internal cache. * * This signal has lazy emission, i.e. it is emitted only when * the local matrix is requested, typically in the arrange phase. * * Since: 1.0 **/ _adg_signals[LOCAL_CHANGED] = g_signal_new("local-changed", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AdgEntityClass, local_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * AdgEntity::invalidate: * @entity: an #AdgEntity * * Invalidates the whole @entity, that is resets all the cache * (if present) built during the #AdgEntity::arrange signal. * The resulting state is a clean entity, similar to what you * have just before the first rendering. * * Since: 1.0 **/ closure = g_cclosure_new(G_CALLBACK(_adg_real_invalidate), NULL, NULL); _adg_signals[INVALIDATE] = g_signal_newv("invalidate", ADG_TYPE_ENTITY, G_SIGNAL_RUN_LAST, closure, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, param_types); /** * AdgEntity::arrange: * @entity: an #AdgEntity * * Arranges the layout of @entity, updating the cache if necessary, * and computes the extents of @entity. * * Since: 1.0 **/ closure = g_cclosure_new(G_CALLBACK(_adg_real_arrange), NULL, NULL); _adg_signals[ARRANGE] = g_signal_newv("arrange", ADG_TYPE_ENTITY, G_SIGNAL_RUN_LAST, closure, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, param_types); /** * AdgEntity::render: * @entity: an #AdgEntity * @cr: a #cairo_t drawing context * * Causes the rendering of @entity on @cr. A render signal will * automatically emit #AdgEntity::arrange just before the real * rendering on the cairo context. * * Since: 1.0 **/ closure = g_cclosure_new(G_CALLBACK(_adg_real_render), NULL, NULL); param_types[0] = G_TYPE_POINTER; _adg_signals[RENDER] = g_signal_newv("render", ADG_TYPE_ENTITY, G_SIGNAL_RUN_LAST, closure, NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, param_types); } static void adg_entity_init(AdgEntity *entity) { AdgEntityPrivate *data = adg_entity_get_instance_private(entity); data->floating = FALSE; data->parent = NULL; cairo_matrix_init_identity(&data->global_map); cairo_matrix_init_identity(&data->local_map); data->local_mix = ADG_MIX_ANCESTORS; data->hash_styles = NULL; data->global.is_defined = FALSE; adg_matrix_copy(&data->global.matrix, adg_matrix_null()); data->local.is_defined = FALSE; adg_matrix_copy(&data->local.matrix, adg_matrix_null()); data->extents.is_defined = FALSE; } static void _adg_dispose(GObject *object) { AdgEntity *entity = (AdgEntity *) object; AdgEntityPrivate *data = adg_entity_get_instance_private(entity); /* This call will emit a "notify" signal for parent. * Consequentially, the references to the old parent is dropped. */ adg_entity_set_parent(entity, NULL); if (data->hash_styles != NULL) { g_hash_table_destroy(data->hash_styles); data->hash_styles = NULL; } if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgEntity *entity = (AdgEntity *) object; AdgEntityPrivate *data = adg_entity_get_instance_private(entity); switch (prop_id) { case PROP_FLOATING: g_value_set_boolean(value, data->floating); break; case PROP_PARENT: g_value_set_object(value, data->parent); break; case PROP_GLOBAL_MAP: g_value_set_boxed(value, &data->global_map); break; case PROP_LOCAL_MAP: g_value_set_boxed(value, &data->local_map); break; case PROP_LOCAL_MIX: g_value_set_enum(value, data->local_mix); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgEntityPrivate *data = adg_entity_get_instance_private((AdgEntity *) object); switch (prop_id) { case PROP_FLOATING: data->floating = g_value_get_boolean(value); break; case PROP_PARENT: _adg_set_parent((AdgEntity *) object, (AdgEntity *) g_value_get_object(value)); break; case PROP_GLOBAL_MAP: adg_matrix_copy(&data->global_map, g_value_get_boxed(value)); data->global.is_defined = FALSE; break; case PROP_LOCAL_MAP: adg_matrix_copy(&data->local_map, g_value_get_boxed(value)); data->local.is_defined = FALSE; break; case PROP_LOCAL_MIX: data->local_mix = g_value_get_enum(value); data->local.is_defined = FALSE; break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_switch_extents: * @state: new extents state * * Strokes (if @state is TRUE) a rectangle * around every entity to show their extents. Useful for * debugging purposes. * * Since: 1.0 **/ void adg_switch_extents(gboolean state) { _adg_show_extents = state; } /** * adg_entity_destroy: * @entity: an #AdgEntity * * Emits the #AdgEntity::destroy signal on @entity and on all of * its children, if any. * * Since: 1.0 **/ void adg_entity_destroy(AdgEntity *entity) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_signal_emit(entity, _adg_signals[DESTROY], 0); } /** * adg_entity_switch_floating: * @entity: an #AdgEntity * @new_state: the new floating state * * Sets or resets the floating state of @entity. * * By default all entities are not floating. When an entity is "floating", its * extents does not concur on increasing the extents of its own container. In * other words, during the arrange phase #AdgContainer only considers the * non-floating children to compute its extents. In particular, this affects * how adg_canvas_autoscale() works: all floating entities are not taken into * consideration. * * A typical example is the title block or any other annotation not dependent * from the model for positioning. * * Since: 1.0 **/ void adg_entity_switch_floating(AdgEntity *entity, gboolean new_state) { AdgEntityPrivate *data; g_return_if_fail(ADG_IS_ENTITY(entity)); g_return_if_fail(adg_is_boolean_value(new_state)); data = adg_entity_get_instance_private(entity); data->floating = new_state; } /** * adg_entity_has_floating: * @entity: an #AdgEntity * * Checks if @entity has the floating state enabled. * * See adg_entity_switch_floating() for a description of what the floating * state is. * * Returns: the current state of the floating flag. * * Since: 1.0 **/ gboolean adg_entity_has_floating(AdgEntity *entity) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), FALSE); data = adg_entity_get_instance_private(entity); return data->floating; } /** * adg_entity_get_canvas: * @entity: an #AdgEntity * * Walks on the @entity hierarchy and gets the first parent of @entity, * that is the first #AdgCanvas instance. The returned object is * owned by @entity and should not be freed or modified. * * Returns: (transfer none): the requested canvas or NULL on errors or if there is no #AdgCanvas in the @entity hierarchy. * * Since: 1.0 **/ AdgCanvas * adg_entity_get_canvas(AdgEntity *entity) { g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); while (entity) { if (ADG_IS_CANVAS(entity)) return (AdgCanvas *) entity; entity = adg_entity_get_parent(entity); } return NULL; } /** * adg_entity_set_parent: * @entity: an #AdgEntity * @parent: the parent entity * * * This function is only useful in entity implementations. * * * Sets a new parent on @entity. Changing the @parent of an entity * emits the #AdgEntity::parent-set signal on it. * * There is no reference management at this level: they should be * handled at a higher level, e.g. by #AdgContainer. * * Since: 1.0 **/ void adg_entity_set_parent(AdgEntity *entity, AdgEntity *parent) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_object_set(entity, "parent", parent, NULL); } /** * adg_entity_get_parent: * @entity: an #AdgEntity * * Gets the parent of @entity. The returned object is owned * by @entity and should not be freed or modified. * * Returns: (transfer none): the parent entity or NULL on errors or if @entity is a toplevel. * * Since: 1.0 **/ AdgEntity * adg_entity_get_parent(AdgEntity *entity) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); data = adg_entity_get_instance_private(entity); return data->parent; } /** * adg_entity_set_global_map: * @entity: an #AdgEntity object * @map: the new map * * Sets the new global transformation of @entity to @map: * the old map is discarded. If @map is NULL, * the global map is left unchanged. * * Since: 1.0 **/ void adg_entity_set_global_map(AdgEntity *entity, const cairo_matrix_t *map) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_object_set(entity, "global-map", map, NULL); } /** * adg_entity_transform_global_map: * @entity: an #AdgEntity object * @transformation: the transformation to apply * @mode: how @transformation should be applied * * Convenient function to change the global map of @entity by * applying @tranformation using the @mode operator. This is * logically equivalent to the following: * * * cairo_matrix_t map; * adg_matrix_copy(&map, adg_entity_get_global_map(entity)); * adg_matrix_transform(&map, transformation, mode); * adg_entity_set_global_map(entity, &map); * * * Since: 1.0 **/ void adg_entity_transform_global_map(AdgEntity *entity, const cairo_matrix_t *transformation, AdgTransformMode mode) { AdgEntityPrivate *data; cairo_matrix_t map; g_return_if_fail(ADG_IS_ENTITY(entity)); g_return_if_fail(transformation != NULL); data = adg_entity_get_instance_private(entity); adg_matrix_copy(&map, &data->global_map); adg_matrix_transform(&map, transformation, mode); g_object_set(entity, "global-map", &map, NULL); } /** * adg_entity_get_global_map: * @entity: an #AdgEntity object * * Gets the transformation to be used to compute the global matrix * of @entity. * * Returns: the requested map or NULL on errors. * * Since: 1.0 **/ const cairo_matrix_t * adg_entity_get_global_map(AdgEntity *entity) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); data = adg_entity_get_instance_private(entity); return &data->global_map; } /** * adg_entity_get_global_matrix: * @entity: an #AdgEntity object * * Gets the current global matrix of @entity. The returned value * is owned by @entity and should not be changed or freed. * * The global matrix is computed in the arrange() phase by * combining all the global maps of the @entity hierarchy using * the %ADG_MIX_ANCESTORS method. * * Returns: the global matrix or NULL on errors. * * Since: 1.0 **/ const cairo_matrix_t * adg_entity_get_global_matrix(AdgEntity *entity) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); data = adg_entity_get_instance_private(entity); return &data->global.matrix; } /** * adg_entity_set_local_map: * @entity: an #AdgEntity object * @map: the new map * * Sets the new local transformation of @entity to @map: * the old map is discarded. If @map is NULL, * the local map is left unchanged. * * Since: 1.0 **/ void adg_entity_set_local_map(AdgEntity *entity, const cairo_matrix_t *map) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_object_set(entity, "local-map", map, NULL); } /** * adg_entity_transform_local_map: * @entity: an #AdgEntity object * @transformation: the transformation to apply * @mode: how @transformation should be applied * * Convenient function to change the local map of @entity by * applying @tranformation using the @mode operator. This is * logically equivalent to the following: * * * cairo_matrix_t map; * adg_matrix_copy(&map, adg_entity_get_local_map(entity)); * adg_matrix_transform(&map, transformation, mode); * adg_entity_set_local_map(entity, &map); * * * Since: 1.0 **/ void adg_entity_transform_local_map(AdgEntity *entity, const cairo_matrix_t *transformation, AdgTransformMode mode) { AdgEntityPrivate *data; cairo_matrix_t map; g_return_if_fail(ADG_IS_ENTITY(entity)); g_return_if_fail(transformation != NULL); data = adg_entity_get_instance_private(entity); adg_matrix_copy(&map, &data->local_map); adg_matrix_transform(&map, transformation, mode); g_object_set(entity, "local-map", &map, NULL); } /** * adg_entity_get_local_map: * @entity: an #AdgEntity object * * Gets the transformation to be used to compute the local matrix * of @entity and store it in @map. * * Returns: the requested map or NULL on errors. * * Since: 1.0 **/ const cairo_matrix_t * adg_entity_get_local_map(AdgEntity *entity) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); data = adg_entity_get_instance_private(entity); return &data->local_map; } /** * adg_entity_get_local_matrix: * @entity: an #AdgEntity object * * Gets the current local matrix of @entity. The returned value * is owned by @entity and should not be changed or freed. * * The local matrix is computed in the arrange() phase by * combining all the local maps of the @entity hierarchy using * the method specified by the #AdgEntity:local-mix property. * * Returns: the local matrix or NULL on errors. * * Since: 1.0 **/ const cairo_matrix_t * adg_entity_get_local_matrix(AdgEntity *entity) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); data = adg_entity_get_instance_private(entity); return &data->local.matrix; } /** * adg_entity_set_local_mix: * @entity: an #AdgEntity object * @local_mix: new mix method * * Sets a new local mix method on @entity. The * #AdgEntity:local-mix property defines how the local * matrix must be computed: check out the #AdgMix * documentation to know what are the availables methods * and how they affect the local matrix computation. * * Setting a different local mix method emits an * #AdgEntity::local-changed signal on @entity. * * Since: 1.0 **/ void adg_entity_set_local_mix(AdgEntity *entity, AdgMix local_mix) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_object_set(entity, "local-mix", local_mix, NULL); } /** * adg_entity_get_local_mix: * @entity: an #AdgEntity object * * Gets the local mix method of @entity. Check out the * adg_entity_set_local_mix() documentation to know what the * local mix method is used for. * * Returns: the local mix method of @entity or %ADG_MIX_UNDEFINED on errors * * Since: 1.0 **/ AdgMix adg_entity_get_local_mix(AdgEntity *entity) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), ADG_MIX_UNDEFINED); data = adg_entity_get_instance_private(entity); return data->local_mix; } /** * adg_entity_set_extents: * @entity: an #AdgEntity * @extents: the new extents * * * This function is only useful in entity implementations. * * * Sets a new bounding box for @entity. @extents can * be NULL, in which case the extents are unset. * * Since: 1.0 **/ void adg_entity_set_extents(AdgEntity *entity, const CpmlExtents *extents) { AdgEntityPrivate *data; g_return_if_fail(ADG_IS_ENTITY(entity)); data = adg_entity_get_instance_private(entity); if (extents == NULL) data->extents.is_defined = FALSE; else cpml_extents_copy(&data->extents, extents); } /** * adg_entity_get_extents: * @entity: an #AdgEntity * * Gets the bounding box of @entity. The returned struct is * owned by @entity and should not modified or freed. * * This struct specifies the surface portion (in global space * of @entity) occupied by the entity without taking into * account rendering properties such as line thickness or caps. * * The #AdgEntity::arrange signal should be emitted before * this call (either explicitely trought adg_entity_arrange() * or implicitely with adg_entity_render()) in order to get * an up to date boundary box. * * Returns: the bounding box of @entity or NULL on errors. * * Since: 1.0 **/ const CpmlExtents * adg_entity_get_extents(AdgEntity *entity) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); data = adg_entity_get_instance_private(entity); return &data->extents; } /** * adg_entity_set_style: * @entity: an #AdgEntity * @dress: a dress style * @style: the new style to use * * Overrides the style of @dress for @entity and its children. * If @style is NULL, any previous * override is removed. * * The new style must still be compatible with @dress: check out * the adg_dress_style_is_compatible() documentation to know * what a compatible style means. * * Since: 1.0 **/ void adg_entity_set_style(AdgEntity *entity, AdgDress dress, AdgStyle *style) { AdgEntityPrivate *data; gpointer p_dress; AdgStyle *old_style; g_return_if_fail(ADG_IS_ENTITY(entity)); data = adg_entity_get_instance_private(entity); if (data->hash_styles == NULL && style == NULL) return; if (data->hash_styles == NULL) data->hash_styles = g_hash_table_new_full(NULL, NULL, NULL, g_object_unref); p_dress = GINT_TO_POINTER(dress); old_style = g_hash_table_lookup(data->hash_styles, p_dress); if (style == old_style) return; if (style == NULL) { g_hash_table_remove(data->hash_styles, p_dress); return; } if (!adg_dress_style_is_compatible(dress, style)) { GType ancestor_type = adg_dress_get_ancestor_type(dress); g_warning(_("%s: '%s' is not compatible with '%s' for '%s' dress (%d)"), G_STRLOC, g_type_name(G_TYPE_FROM_INSTANCE(style)), g_type_name(ancestor_type), adg_dress_get_name(dress), dress); return; } g_object_ref(style); g_hash_table_replace(data->hash_styles, p_dress, style); } /** * adg_entity_get_style: * @entity: an #AdgEntity * @dress: the dress of the style to get * * Gets the overriden @dress style from @entity. This is a kind * of accessor function: for rendering purpose use adg_entity_style() * instead. The returned object is owned by @entity and should not be * freed or modified. * * Returns: (transfer none): the requested style or NULL if the @dress style is not overriden. * * Since: 1.0 **/ AdgStyle * adg_entity_get_style(AdgEntity *entity, AdgDress dress) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); data = adg_entity_get_instance_private(entity); if (data->hash_styles == NULL) return NULL; return g_hash_table_lookup(data->hash_styles, GINT_TO_POINTER(dress)); } /** * adg_entity_style: * @entity: an #AdgEntity * @dress: the dress of the style to get * * Gets the style to be used for @entity. @dress specifies which * "family" of style to get. * * The following sequence of checks is performed to get the proper * style, stopping at the first succesfull result: * * * check if the style is directly overriden by this entity, * as returned by adg_entity_get_style(); * check if @entity has a parent, in which case returns the * adg_entity_style() of the parent; * returns the main style with adg_dress_get_fallback(). * * * The returned object is owned by @entity and should not be * freed or modified. * * Returns: (transfer none): the requested style or NULL for transparent dresses or errors. * * Since: 1.0 **/ AdgStyle * adg_entity_style(AdgEntity *entity, AdgDress dress) { AdgStyle *style; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); style = adg_entity_get_style(entity, dress); if (style == NULL) { AdgEntityPrivate *data = adg_entity_get_instance_private(entity); if (data->parent != NULL) style = adg_entity_style(data->parent, dress); else style = adg_dress_get_fallback(dress); } return style; } /** * adg_entity_apply_dress: * @entity: an #AdgEntity * @dress: the dress style to apply * @cr: a #cairo_t drawing context * * Convenient function to apply a @dress style (as returned by * adg_entity_style()) to the @cr cairo context. * * Since: 1.0 **/ void adg_entity_apply_dress(AdgEntity *entity, AdgDress dress, cairo_t *cr) { AdgStyle *style; g_return_if_fail(ADG_IS_ENTITY(entity)); g_return_if_fail(cr != NULL); style = adg_entity_style(entity, dress); if (style != NULL) adg_style_apply(style, entity, cr); } /** * adg_entity_global_changed: * @entity: an #AdgEntity * * Emits the #AdgEntity::global-changed signal on @entity and on all of * its children, if any. * * Since: 1.0 **/ void adg_entity_global_changed(AdgEntity *entity) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_signal_emit(entity, _adg_signals[GLOBAL_CHANGED], 0); } /** * adg_entity_local_changed: * @entity: an #AdgEntity * * Emits the #AdgEntity::local-changed signal on @entity and on all of * its children, if any. * * Since: 1.0 **/ void adg_entity_local_changed(AdgEntity *entity) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_signal_emit(entity, _adg_signals[LOCAL_CHANGED], 0); } /** * adg_entity_invalidate: * @entity: an #AdgEntity * * Emits the #AdgEntity::invalidate signal on @entity and on all of * its children, if any, clearing the eventual cache stored by the * #AdgEntity::arrange signal and setting the entity state similary * to the just initialized entity. * * Since: 1.0 **/ void adg_entity_invalidate(AdgEntity *entity) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_signal_emit(entity, _adg_signals[INVALIDATE], 0); } /** * adg_entity_arrange: * @entity: an #AdgEntity * * Emits the #AdgEntity::arrange signal on @entity and all its children, * if any. The arrange call is implicitely called by the * #AdgEntity::render signal but not by adg_entity_get_extents(). * * Since: 1.0 **/ void adg_entity_arrange(AdgEntity *entity) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_signal_emit(entity, _adg_signals[ARRANGE], 0); } /** * adg_entity_render: * @entity: an #AdgEntity * @cr: a #cairo_t drawing context * * Emits the #AdgEntity::render signal on @entity and on all of its * children, if any, causing the rendering to the @cr cairo context. * * Since: 1.0 **/ void adg_entity_render(AdgEntity *entity, cairo_t *cr) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_signal_emit(entity, _adg_signals[RENDER], 0, cr); } /** * adg_entity_point: * @entity: an #AdgEntity * @point: the #AdgPoint to define * @new_point: the new #AdgPoint value * * * This function is only useful in entity implementations. * * * A convenient method to set an #AdgPoint owned by @entity. * @old_point is the old value while @new_point is the new value. * It can be used for changing a private #AdgPoint struct, such as: * * * data->point = adg_entity_point(entity, data->point, new_point); * * * This function takes care of the dependencies between @entity and * the eventual models bound to the old and new points. * * @old_point can be NULL, in which case a * clone of @new_point will be returned. Also @new_point can * be NULL, in which case @old_point * is destroyed and NULL will be returned. * * Returns: (transfer full): the new properly defined point * * Since: 1.0 **/ AdgPoint * adg_entity_point(AdgEntity *entity, AdgPoint *old_point, const AdgPoint *new_point) { AdgPoint *point; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); point = NULL; if (! adg_point_equal(old_point, new_point)) { AdgModel *old_model, *new_model; old_model = old_point != NULL ? adg_point_get_model(old_point) : NULL; new_model = new_point != NULL ? adg_point_get_model(new_point) : NULL; if (new_model != old_model) { /* Handle model-entity dependencies */ if (new_model != NULL) adg_model_add_dependency(new_model, entity); if (old_model != NULL) adg_model_remove_dependency(old_model, entity); } if (new_point != NULL) point = adg_point_dup(new_point); if (old_point != NULL) adg_point_destroy(old_point); } return point; } static void _adg_destroy(AdgEntity *entity) { GObject *object = (GObject *) entity; g_object_run_dispose(object); g_object_unref(object); } static void _adg_set_parent(AdgEntity *entity, AdgEntity *parent) { AdgEntityPrivate *data = adg_entity_get_instance_private(entity); AdgEntity *old_parent = data->parent; data->parent = parent; data->global.is_defined = FALSE; data->local.is_defined = FALSE; g_signal_emit(entity, _adg_signals[PARENT_SET], 0, old_parent); } static void _adg_global_changed(AdgEntity *entity) { AdgEntityPrivate *data = adg_entity_get_instance_private(entity); const cairo_matrix_t *map = &data->global_map; cairo_matrix_t *matrix = &data->global.matrix; if (data->parent) { adg_matrix_copy(matrix, adg_entity_get_global_matrix(data->parent)); adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE); } else { adg_matrix_copy(matrix, map); } } static void _adg_local_changed(AdgEntity *entity) { AdgEntityPrivate *data = adg_entity_get_instance_private(entity); const cairo_matrix_t *map = &data->local_map; cairo_matrix_t *matrix = &data->local.matrix; switch (data->local_mix) { case ADG_MIX_DISABLED: adg_matrix_copy(matrix, adg_matrix_identity()); break; case ADG_MIX_NONE: adg_matrix_copy(matrix, map); break; case ADG_MIX_ANCESTORS: if (data->parent) { adg_matrix_copy(matrix, adg_entity_get_local_matrix(data->parent)); adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE); } else { adg_matrix_copy(matrix, map); } break; case ADG_MIX_ANCESTORS_NORMALIZED: if (data->parent) { adg_matrix_copy(matrix, adg_entity_get_local_matrix(data->parent)); adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE); } else { adg_matrix_copy(matrix, map); } adg_matrix_normalize(matrix); break; case ADG_MIX_PARENT: if (data->parent) { adg_matrix_copy(matrix, adg_entity_get_local_map(data->parent)); adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE); } else { adg_matrix_copy(matrix, map); } break; case ADG_MIX_PARENT_NORMALIZED: if (data->parent) { adg_matrix_copy(matrix, adg_entity_get_local_map(data->parent)); adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE); } else { adg_matrix_copy(matrix, map); } adg_matrix_normalize(matrix); break; case ADG_MIX_UNDEFINED: g_warning(_("%s: requested to mix the maps using an undefined mix method"), G_STRLOC); break; default: g_return_if_reached(); break; } } static void _adg_real_invalidate(AdgEntity *entity) { AdgEntityClass *klass = ADG_ENTITY_GET_CLASS(entity); AdgEntityPrivate *data = adg_entity_get_instance_private(entity); /* Do not raise any warning if invalidate() is not defined, * assuming entity does not have additional cache to be cleared */ if (klass->invalidate) klass->invalidate(entity); data->extents.is_defined = FALSE; } static void _adg_real_arrange(AdgEntity *entity) { AdgEntityClass *klass = ADG_ENTITY_GET_CLASS(entity); AdgEntityPrivate *data = adg_entity_get_instance_private(entity); /* Update the global matrix, if required */ if (!data->global.is_defined) { data->global.is_defined = TRUE; g_signal_emit(entity, _adg_signals[GLOBAL_CHANGED], 0); } /* Update the local matrix, if required */ if (!data->local.is_defined) { data->local.is_defined = TRUE; g_signal_emit(entity, _adg_signals[LOCAL_CHANGED], 0); } /* The arrange() method must be defined */ if (klass->arrange == NULL) { g_warning(_("%s: 'arrange' method not implemented for type '%s'"), G_STRLOC, g_type_name(G_OBJECT_TYPE(entity))); data->extents.is_defined = FALSE; return; } klass->arrange(entity); } static void _adg_real_render(AdgEntity *entity, cairo_t *cr) { AdgEntityClass *klass = ADG_ENTITY_GET_CLASS(entity); /* The render method must be defined */ if (klass->render == NULL) { g_warning(_("%s: 'render' method not implemented for type '%s'"), G_STRLOC, g_type_name(G_OBJECT_TYPE(entity))); return; } /* Before the rendering, the entity should be arranged */ g_signal_emit(entity, _adg_signals[ARRANGE], 0); cairo_save(cr); klass->render(entity, cr); cairo_restore(cr); if (_adg_show_extents) { AdgEntityPrivate *data = adg_entity_get_instance_private(entity); CpmlExtents *extents = &data->extents; if (extents->is_defined) { cairo_save(cr); cairo_set_source_rgba(cr, 0.15, 0.15, 0.15, 0.15); cairo_rectangle(cr, extents->org.x, extents->org.y, extents->size.x, extents->size.y); cairo_fill(cr); cairo_restore(cr); } } } diff --git a/src/adg/adg-entity.h b/src/adg/adg-entity.h index 98edb413..04e0a679 100644 --- a/src/adg/adg-entity.h +++ b/src/adg/adg-entity.h @@ -1,125 +1,125 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_ENTITY_H__ #define __ADG_ENTITY_H__ G_BEGIN_DECLS #define ADG_TYPE_ENTITY (adg_entity_get_type()) #define ADG_ENTITY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_ENTITY, AdgEntity)) #define ADG_ENTITY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_ENTITY, AdgEntityClass)) #define ADG_IS_ENTITY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_ENTITY)) #define ADG_IS_ENTITY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_ENTITY)) #define ADG_ENTITY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_ENTITY, AdgEntityClass)) typedef struct _AdgEntity AdgEntity; typedef struct _AdgEntityClass AdgEntityClass; struct _AdgEntity { /*< private >*/ GInitiallyUnowned parent; }; struct _AdgEntityClass { /*< private >*/ GInitiallyUnownedClass parent_class; /*< public >*/ /* Signals */ void (*destroy) (AdgEntity *entity); void (*parent_set) (AdgEntity *entity, AdgEntity *old_parent); void (*global_changed) (AdgEntity *entity); void (*local_changed) (AdgEntity *entity); void (*invalidate) (AdgEntity *entity); void (*arrange) (AdgEntity *entity); void (*render) (AdgEntity *entity, cairo_t *cr); }; void adg_switch_extents (gboolean state); GType adg_entity_get_type (void); void adg_entity_destroy (AdgEntity *entity); void adg_entity_switch_floating (AdgEntity *entity, gboolean new_state); gboolean adg_entity_has_floating (AdgEntity *entity); AdgCanvas * adg_entity_get_canvas (AdgEntity *entity); void adg_entity_set_parent (AdgEntity *entity, AdgEntity *parent); AdgEntity * adg_entity_get_parent (AdgEntity *entity); void adg_entity_set_global_map (AdgEntity *entity, const cairo_matrix_t *map); void adg_entity_transform_global_map (AdgEntity *entity, const cairo_matrix_t *transformation, AdgTransformMode mode); const cairo_matrix_t * adg_entity_get_global_map (AdgEntity *entity); const cairo_matrix_t * adg_entity_get_global_matrix (AdgEntity *entity); void adg_entity_set_local_map (AdgEntity *entity, const cairo_matrix_t *map); void adg_entity_transform_local_map (AdgEntity *entity, const cairo_matrix_t *transformation, AdgTransformMode mode); const cairo_matrix_t * adg_entity_get_local_map (AdgEntity *entity); const cairo_matrix_t * adg_entity_get_local_matrix (AdgEntity *entity); void adg_entity_set_local_mix (AdgEntity *entity, AdgMix local_mix); AdgMix adg_entity_get_local_mix (AdgEntity *entity); void adg_entity_set_extents (AdgEntity *entity, const CpmlExtents *extents); const CpmlExtents * adg_entity_get_extents (AdgEntity *entity); void adg_entity_set_style (AdgEntity *entity, AdgDress dress, AdgStyle *style); AdgStyle * adg_entity_get_style (AdgEntity *entity, AdgDress dress); AdgStyle * adg_entity_style (AdgEntity *entity, AdgDress dress); void adg_entity_apply_dress (AdgEntity *entity, AdgDress dress, cairo_t *cr); void adg_entity_global_changed (AdgEntity *entity); void adg_entity_local_changed (AdgEntity *entity); void adg_entity_invalidate (AdgEntity *entity); void adg_entity_arrange (AdgEntity *entity); void adg_entity_render (AdgEntity *entity, cairo_t *cr); AdgPoint * adg_entity_point (AdgEntity *entity, AdgPoint *point, const AdgPoint *new_point); G_END_DECLS #endif /* __ADG_ENTITY_H__ */ diff --git a/src/adg/adg-enums.c b/src/adg/adg-enums.c index a4693ea4..9579a4be 100644 --- a/src/adg/adg-enums.c +++ b/src/adg/adg-enums.c @@ -1,119 +1,119 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-enums * @Section_Id:enumerations * @title: Standard enumerations * @short_description: Public enumerated types used throughout ADG * * All the enumerations are available in GType notation, and so are * the standard type macros. * * Since: 1.0 **/ /** * AdgThreeState: * @ADG_THREE_STATE_OFF: disabled, switched off, disactive * @ADG_THREE_STATE_ON: enabled, switched on, active * @ADG_THREE_STATE_UNKNOWN: unknown / indefinite / automatic state * * A generic three state flags to be used whenever needed. Could be * used when a boolean is requested but also an indefinite / default * state should be caught. * * Since: 1.0 **/ /** * AdgTransformMode: * @ADG_TRANSFORM_NONE: do not apply any transformation: the * matrix is returned unchanged * @ADG_TRANSFORM_BEFORE: apply the transformation before the * matrix: the result is equivalent to * cairo_matrix_multiply(matrix, transformation, matrix) * @ADG_TRANSFORM_AFTER: apply the transformation after the * matrix: the result is equivalent to * cairo_matrix_multiply(matrix, matrix, transformation) * @ADG_TRANSFORM_BEFORE_NORMALIZED: same as %ADG_TRANSFORM_BEFORE but * normalizing the transformation with * adg_matrix_normalize() before applying it * @ADG_TRANSFORM_AFTER_NORMALIZED: same as %ADG_TRANSFORM_AFTER but * normalizing the transformation with * adg_matrix_normalize() before applying it * * Specifies the mode a generic transformation should be applied on * a matrix. Although used in different places, the function performing * the dirty work is always adg_matrix_transform(). * * Since: 1.0 **/ /** * AdgMix: * @ADG_MIX_UNDEFINED: undefined method, mainly used to return an * error condition * @ADG_MIX_DISABLED: the maps are completely ignored: the matrix is * always set to the identity matrix * @ADG_MIX_NONE: the matrix is set to the entity map * @ADG_MIX_ANCESTORS: sequentially apply every map on the matrix using * %ADG_TRANSFORM_BEFORE #AdgTransformMode, starting * from the elder ancestor in the hierarchy * (probably an #AdgCanvas) up to the current entity * @ADG_MIX_ANCESTORS_NORMALIZED: * perform the same operations as %ADG_MIX_ANCESTORS * but normalize the resulting matrix with * adg_matrix_normalize() before returning it * @ADG_MIX_PARENT: similar to %ADG_MIX_ANCESTORS but taking into account * only the parent and ignoring any other ancestors; * if the entity has no parent, it returns the * entity map as in %ADG_MIX_NONE * @ADG_MIX_PARENT_NORMALIZED: * perform the same operations as %ADG_MIX_PARENT * but normalize the resulting matrix with * adg_matrix_normalize() before returning it * * Specifies how the maps of an #AdgEntity instance and its ancestors * should be combined to get the matrix. The full logic is implemented * by the #AdgEntity::local-changed default handler to compute the * local matrix, while the global matrix is always computed using the * #ADG_MIX_ANCESTORS method. * * Since: 1.0 **/ /** * AdgProjectionScheme: * @ADG_PROJECTION_SCHEME_UNDEFINED: there is a single view or the views are * not related with a consistent scheme * @ADG_PROJECTION_SCHEME_FIRST_ANGLE: the views are generated as if the object * were sitting on the paper and, from the * front view, it is rolled to the right to * show the left side or rolled up to show * its bottom * @ADG_PROJECTION_SCHEME_THIRD_ANGLE: the views are generated as if the object * were a box to be unfolded * * Specifies how the views are positioned relative to each other. * * Since: 1.0 **/ diff --git a/src/adg/adg-enums.h b/src/adg/adg-enums.h index 4fb00a4f..8dc290cc 100644 --- a/src/adg/adg-enums.h +++ b/src/adg/adg-enums.h @@ -1,102 +1,102 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_ENUMS_H__ #define __ADG_ENUMS_H__ G_BEGIN_DECLS /* This header file should follow this (inconsistent) style to cope * with glib-mkenums. At the time of writing, the regex used is * /\s*typedef\s+enum\s*({)?.../. * * Check the sources for up to date information and further details: * http://git.gnome.org/browse/glib/tree/gobject/glib-mkenums.in */ typedef enum { ADG_THREE_STATE_OFF, ADG_THREE_STATE_ON, ADG_THREE_STATE_UNKNOWN } AdgThreeState; typedef enum { ADG_TRANSFORM_NONE, ADG_TRANSFORM_BEFORE, ADG_TRANSFORM_AFTER, ADG_TRANSFORM_BEFORE_NORMALIZED, ADG_TRANSFORM_AFTER_NORMALIZED } AdgTransformMode; typedef enum { ADG_MIX_UNDEFINED, ADG_MIX_DISABLED, ADG_MIX_NONE, ADG_MIX_ANCESTORS, ADG_MIX_ANCESTORS_NORMALIZED, ADG_MIX_PARENT, ADG_MIX_PARENT_NORMALIZED } AdgMix; typedef enum { ADG_PROJECTION_SCHEME_UNDEFINED, ADG_PROJECTION_SCHEME_FIRST_ANGLE, ADG_PROJECTION_SCHEME_THIRD_ANGLE } AdgProjectionScheme; typedef enum { ADG_DRESS_UNDEFINED, ADG_DRESS_COLOR, ADG_DRESS_COLOR_BACKGROUND, ADG_DRESS_COLOR_STROKE, ADG_DRESS_COLOR_DIMENSION, ADG_DRESS_COLOR_ANNOTATION, ADG_DRESS_COLOR_FILL, ADG_DRESS_COLOR_AXIS, ADG_DRESS_COLOR_HIDDEN, ADG_DRESS_LINE, ADG_DRESS_LINE_STROKE, ADG_DRESS_LINE_DIMENSION, ADG_DRESS_LINE_FILL, ADG_DRESS_LINE_GRID, ADG_DRESS_LINE_FRAME, ADG_DRESS_LINE_AXIS, ADG_DRESS_LINE_HIDDEN, ADG_DRESS_FONT, ADG_DRESS_FONT_TEXT, ADG_DRESS_FONT_ANNOTATION, ADG_DRESS_FONT_QUOTE_TEXT, ADG_DRESS_FONT_QUOTE_ANNOTATION, ADG_DRESS_DIMENSION, ADG_DRESS_FILL, ADG_DRESS_FILL_HATCH, ADG_DRESS_TABLE } AdgDress; G_END_DECLS #endif /* __ADG_ENUMS_H__ */ diff --git a/src/adg/adg-fill-style-private.h b/src/adg/adg-fill-style-private.h index daf89d8b..fd9bc8ae 100644 --- a/src/adg/adg-fill-style-private.h +++ b/src/adg/adg-fill-style-private.h @@ -1,36 +1,36 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_FILL_STYLE_PRIVATE_H__ #define __ADG_FILL_STYLE_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgFillStylePrivate AdgFillStylePrivate; struct _AdgFillStylePrivate { cairo_pattern_t *pattern; CpmlExtents extents; }; G_END_DECLS #endif /* __ADG_FILL_STYLE_PRIVATE_H__ */ diff --git a/src/adg/adg-fill-style.c b/src/adg/adg-fill-style.c index 4b8c00c5..308e26ee 100644 --- a/src/adg/adg-fill-style.c +++ b/src/adg/adg-fill-style.c @@ -1,290 +1,290 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-fill-style * @short_description: Generic pattern fill * * A style defining a generic fill based on cairo_pattern_t. * * Since: 1.0 **/ /** * AdgFillStyle: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ /** * AdgFillStyleClass: * @set_extents: virtual method that specifies where a specific fill style * must be applied. It is called by #AdgHatch in the rendering * phase passing with its boundary box as argument. * * The default set_extents implementation simply sets * the extents owned by the fill style instance to the one provided, so the * last call has precedence. Any derived class can override it to customize * this behavior, for example to keep the greatest boundary box instead of * the last one. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-style.h" #include "adg-cairo-fallback.h" #include "adg-fill-style.h" #include "adg-fill-style-private.h" G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(AdgFillStyle, adg_fill_style, ADG_TYPE_STYLE) enum { PROP_0, PROP_PATTERN }; static void _adg_finalize (GObject *object); static void _adg_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void _adg_apply (AdgStyle *style, AdgEntity *entity, cairo_t *cr); static void _adg_set_extents (AdgFillStyle *fill_style, const CpmlExtents *extents); static void adg_fill_style_class_init(AdgFillStyleClass *klass) { GObjectClass *gobject_class; AdgStyleClass *style_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; style_class = (AdgStyleClass *) klass; gobject_class->finalize = _adg_finalize; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; style_class->apply = _adg_apply; klass->set_extents = _adg_set_extents; param = g_param_spec_boxed("pattern", P_("Pattern"), P_("The cairo pattern set for this entity"), CAIRO_GOBJECT_TYPE_PATTERN, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_PATTERN, param); } static void adg_fill_style_init(AdgFillStyle *fill_style) { AdgFillStylePrivate *data = adg_fill_style_get_instance_private(fill_style); data->pattern = NULL; } static void _adg_finalize(GObject *object) { AdgFillStylePrivate *data = adg_fill_style_get_instance_private((AdgFillStyle *) object); if (data->pattern != NULL) { cairo_pattern_destroy(data->pattern); data->pattern = NULL; } } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgFillStylePrivate *data = adg_fill_style_get_instance_private((AdgFillStyle *) object); switch (prop_id) { case PROP_PATTERN: g_value_set_boxed(value, data->pattern); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgFillStylePrivate *data = adg_fill_style_get_instance_private((AdgFillStyle *) object); cairo_pattern_t *old_pattern; switch (prop_id) { case PROP_PATTERN: old_pattern = data->pattern; data->pattern = g_value_get_boxed(value); if (data->pattern) cairo_pattern_reference(data->pattern); if (old_pattern) cairo_pattern_destroy(old_pattern); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_fill_style_set_pattern: * @fill_style: an #AdgFillStyle * @pattern: the new pattern * * * This function is only useful in new fill implementations. * * * Sets a new pattern on @fill_style. A new reference is added to * @pattern with cairo_pattern_reference() and the old pattern * (if any) is unreferenced with cairo_pattern_destroy(). * * Since: 1.0 **/ void adg_fill_style_set_pattern(AdgFillStyle *fill_style, cairo_pattern_t *pattern) { g_return_if_fail(ADG_IS_FILL_STYLE(fill_style)); g_object_set(fill_style, "pattern", pattern, NULL); } /** * adg_fill_style_get_pattern: * @fill_style: an #AdgFillStyle * * Gets the current pattern binded to @fill_style. * * Returns: (transfer none): the current pattern * * Since: 1.0 **/ cairo_pattern_t * adg_fill_style_get_pattern(AdgFillStyle *fill_style) { AdgFillStylePrivate *data; g_return_val_if_fail(ADG_IS_FILL_STYLE(fill_style), NULL); data = adg_fill_style_get_instance_private(fill_style); return data->pattern; } /** * adg_fill_style_set_extents: * @fill_style: an #AdgFillStyle * @extents: the new extents * * * This function is only useful in new fill style implementations. * * * Forcibly sets new extents on @fill_style. Any fill style class * that want to make some kind of customization can override the * set_extents virtual method to intercept * any extents change. * * Sets new extents on @fill_style. These extents are usually set * by the arrange() method of the entity using this filling style. * * Since: 1.0 **/ void adg_fill_style_set_extents(AdgFillStyle *fill_style, const CpmlExtents *extents) { AdgFillStyleClass *klass; g_return_if_fail(ADG_IS_FILL_STYLE(fill_style)); g_return_if_fail(extents != NULL); klass = ADG_FILL_STYLE_GET_CLASS(fill_style); if (klass->set_extents) klass->set_extents(fill_style, extents); } /** * adg_fill_style_get_extents: * @fill_style: an #AdgFillStyle * * Stores a copy of the extents of @fill_style in @extents. * This struct specifies the maximum portion (in global space) * this fill style should be applied: it will clamped by the * entities as needed. * * Returns: (transfer none): the extents of @fill_style or NULL on errors. * * Since: 1.0 **/ const CpmlExtents * adg_fill_style_get_extents(AdgFillStyle *fill_style) { AdgFillStylePrivate *data; g_return_val_if_fail(ADG_IS_FILL_STYLE(fill_style), NULL); data = adg_fill_style_get_instance_private(fill_style); return &data->extents; } static void _adg_apply(AdgStyle *style, AdgEntity *entity, cairo_t *cr) { AdgFillStylePrivate *data = adg_fill_style_get_instance_private((AdgFillStyle *) style); if (data->pattern == NULL) g_warning(_("%s: pattern undefined for type '%s'"), G_STRLOC, g_type_name(G_OBJECT_TYPE(style))); else cairo_set_source(cr, data->pattern); } static void _adg_set_extents(AdgFillStyle *fill_style, const CpmlExtents *extents) { AdgFillStylePrivate *data = adg_fill_style_get_instance_private(fill_style); cpml_extents_copy(&data->extents, extents); } diff --git a/src/adg/adg-fill-style.h b/src/adg/adg-fill-style.h index 30088298..ff8ac516 100644 --- a/src/adg/adg-fill-style.h +++ b/src/adg/adg-fill-style.h @@ -1,70 +1,70 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_FILL_STYLE_H__ #define __ADG_FILL_STYLE_H__ G_BEGIN_DECLS #define ADG_TYPE_FILL_STYLE (adg_fill_style_get_type()) #define ADG_FILL_STYLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_FILL_STYLE, AdgFillStyle)) #define ADG_FILL_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_FILL_STYLE, AdgFillStyleClass)) #define ADG_IS_FILL_STYLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_FILL_STYLE)) #define ADG_IS_FILL_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_FILL_STYLE)) #define ADG_FILL_STYLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_FILL_STYLE, AdgFillStyleClass)) typedef struct _AdgFillStyle AdgFillStyle; typedef struct _AdgFillStyleClass AdgFillStyleClass; struct _AdgFillStyle { /*< private >*/ AdgStyle parent; }; struct _AdgFillStyleClass { /*< private >*/ AdgStyleClass parent_class; /*< public >*/ /* Virtual table */ void (*set_extents) (AdgFillStyle *fill_style, const CpmlExtents *extents); }; GType adg_fill_style_get_type (void); void adg_fill_style_set_pattern (AdgFillStyle *fill_style, cairo_pattern_t *pattern); cairo_pattern_t * adg_fill_style_get_pattern (AdgFillStyle *fill_style); void adg_fill_style_set_extents (AdgFillStyle *fill_style, const CpmlExtents *extents); const CpmlExtents *adg_fill_style_get_extents (AdgFillStyle *fill_style); G_END_DECLS #endif /* __ADG_FILL_STYLE_H__ */ diff --git a/src/adg/adg-font-style-private.h b/src/adg/adg-font-style-private.h index 1b41006e..c599c40e 100644 --- a/src/adg/adg-font-style-private.h +++ b/src/adg/adg-font-style-private.h @@ -1,47 +1,47 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_FONT_STYLE_PRIVATE_H__ #define __ADG_FONT_STYLE_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgFontStylePrivate AdgFontStylePrivate; struct _AdgFontStylePrivate { AdgDress color_dress; gchar *family; cairo_font_slant_t slant; cairo_font_weight_t weight; gdouble size; cairo_antialias_t antialias; cairo_subpixel_order_t subpixel_order; cairo_hint_style_t hint_style; cairo_hint_metrics_t hint_metrics; cairo_font_face_t *face; cairo_scaled_font_t *font; }; G_END_DECLS #endif /* __ADG_FONT_STYLE_PRIVATE_H__ */ diff --git a/src/adg/adg-font-style.c b/src/adg/adg-font-style.c index 7ff74c2f..6157eeb3 100644 --- a/src/adg/adg-font-style.c +++ b/src/adg/adg-font-style.c @@ -1,771 +1,771 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-font-style * @short_description: Font style related stuff * * Contains parameters on how to draw texts such as font family, slanting, * weight, hinting and so on. * * Since: 1.0 */ /** * AdgFontStyle: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-style.h" #include "adg-dress.h" #include "adg-param-dress.h" #include "adg-font-style.h" #include "adg-font-style-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_font_style_parent_class) G_DEFINE_TYPE_WITH_PRIVATE(AdgFontStyle, adg_font_style, ADG_TYPE_STYLE) enum { PROP_0, PROP_COLOR_DRESS, PROP_FAMILY, PROP_SLANT, PROP_WEIGHT, PROP_SIZE, PROP_ANTIALIAS, PROP_SUBPIXEL_ORDER, PROP_HINT_STYLE, PROP_HINT_METRICS }; static void _adg_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void _adg_invalidate (AdgStyle *style); static void _adg_apply (AdgStyle *style, AdgEntity *entity, cairo_t *cr); static void adg_font_style_class_init(AdgFontStyleClass *klass) { GObjectClass *gobject_class; AdgStyleClass *style_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; style_class = (AdgStyleClass *) klass; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; style_class->invalidate = _adg_invalidate; style_class->apply = _adg_apply; param = adg_param_spec_dress("color-dress", P_("Color Dress"), P_("The fallback color dress to bind to this style"), ADG_DRESS_COLOR, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_COLOR_DRESS, param); param = g_param_spec_string("family", P_("Font Family"), P_("The font family name, encoded in UTF-8"), NULL, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FAMILY, param); param = g_param_spec_int("slant", P_("Font Slant"), P_("Variant of a font face based on its slant"), G_MININT, G_MAXINT, CAIRO_FONT_SLANT_NORMAL, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_SLANT, param); param = g_param_spec_int("weight", P_("Font Weight"), P_("Variant of a font face based on its weight"), G_MININT, G_MAXINT, CAIRO_FONT_WEIGHT_NORMAL, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_WEIGHT, param); param = g_param_spec_double("size", P_("Font Size"), P_("Font size in user space units"), 0, G_MAXDOUBLE, 10, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_SIZE, param); param = g_param_spec_int("antialias", P_("Font Antialiasing Mode"), P_("Type of antialiasing to do when rendering text"), G_MININT, G_MAXINT, CAIRO_ANTIALIAS_DEFAULT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_ANTIALIAS, param); param = g_param_spec_int("subpixel-order", P_("Font Subpixel Order"), P_("The order of color elements within each pixel on the display device when rendering with subpixel antialiasing mode"), G_MININT, G_MAXINT, CAIRO_SUBPIXEL_ORDER_DEFAULT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_SUBPIXEL_ORDER, param); param = g_param_spec_int("hint-style", P_("Type of Hinting"), P_("How outlines must fit to the pixel grid in order to improve the glyph appearance"), G_MININT, G_MAXINT, CAIRO_HINT_STYLE_DEFAULT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_HINT_STYLE, param); param = g_param_spec_int("hint-metrics", P_("Font Metric Hinting"), P_("Whether to hint font metrics, that is align them to integer values in device space"), G_MININT, G_MAXINT, CAIRO_HINT_METRICS_DEFAULT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_HINT_METRICS, param); } static void adg_font_style_init(AdgFontStyle *font_style) { AdgFontStylePrivate *data = adg_font_style_get_instance_private(font_style); data->color_dress = ADG_DRESS_COLOR; data->family = NULL; data->slant = CAIRO_FONT_SLANT_NORMAL; data->weight = CAIRO_FONT_WEIGHT_NORMAL; data->size = 10; data->antialias = CAIRO_ANTIALIAS_DEFAULT; data->subpixel_order = CAIRO_SUBPIXEL_ORDER_DEFAULT; data->hint_style = CAIRO_HINT_STYLE_DEFAULT; data->hint_metrics = CAIRO_HINT_METRICS_DEFAULT; data->font = NULL; } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgFontStylePrivate *data = adg_font_style_get_instance_private((AdgFontStyle *) object); switch (prop_id) { case PROP_COLOR_DRESS: g_value_set_enum(value, data->color_dress); break; case PROP_FAMILY: g_value_set_string(value, data->family); break; case PROP_SLANT: g_value_set_int(value, data->slant); break; case PROP_WEIGHT: g_value_set_int(value, data->weight); break; case PROP_SIZE: g_value_set_double(value, data->size); break; case PROP_ANTIALIAS: g_value_set_int(value, data->antialias); break; case PROP_SUBPIXEL_ORDER: g_value_set_int(value, data->subpixel_order); break; case PROP_HINT_STYLE: g_value_set_int(value, data->hint_style); break; case PROP_HINT_METRICS: g_value_set_int(value, data->hint_metrics); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgStyle *style = (AdgStyle *) object; AdgFontStyle *font_style = (AdgFontStyle *) object; AdgFontStylePrivate *data = adg_font_style_get_instance_private(font_style); switch (prop_id) { case PROP_COLOR_DRESS: data->color_dress = g_value_get_enum(value); break; case PROP_FAMILY: g_free(data->family); data->family = g_value_dup_string(value); adg_style_invalidate(style); break; case PROP_SLANT: data->slant = g_value_get_int(value); adg_style_invalidate(style); break; case PROP_WEIGHT: data->weight = g_value_get_int(value); adg_style_invalidate(style); break; case PROP_SIZE: data->size = g_value_get_double(value); adg_style_invalidate(style); break; case PROP_ANTIALIAS: data->antialias = g_value_get_int(value); adg_style_invalidate(style); break; case PROP_SUBPIXEL_ORDER: data->subpixel_order = g_value_get_int(value); adg_style_invalidate(style); break; case PROP_HINT_STYLE: data->hint_style = g_value_get_int(value); adg_style_invalidate(style); break; case PROP_HINT_METRICS: data->hint_metrics = g_value_get_int(value); adg_style_invalidate(style); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_font_style_new: * * Constructs a new font style initialized with default params. * * Returns: (transfer full): a newly created font style * * Since: 1.0 **/ AdgFontStyle * adg_font_style_new(void) { return g_object_new(ADG_TYPE_FONT_STYLE, NULL); } /** * adg_font_style_new_options: * @font_style: an #AdgFontStyle object * * Creates a new set of #cairo_font_options_t filled with the values * picked from @font_style. The returned value must be freed with * cairo_font_options_destroy(). * * Returns: (transfer full): a newly allocated list of cairo font options. * * Since: 1.0 **/ cairo_font_options_t * adg_font_style_new_options(AdgFontStyle *font_style) { AdgFontStylePrivate *data; cairo_font_options_t *options; g_return_val_if_fail(ADG_IS_FONT_STYLE(font_style), NULL); data = adg_font_style_get_instance_private(font_style); options = cairo_font_options_create(); /* Check for cached font */ if (data->font != NULL) { cairo_scaled_font_get_font_options(data->font, options); } else { cairo_font_options_set_antialias(options, data->antialias); cairo_font_options_set_subpixel_order(options, data->subpixel_order); cairo_font_options_set_hint_style(options, data->hint_style); cairo_font_options_set_hint_metrics(options, data->hint_metrics); } return options; } /** * adg_font_style_get_scaled_font: * @font_style: an #AdgFontStyle object * @ctm: the current transformation matrix * * Gets the scaled font of @font_style. The returned font is * owned by @font_style and must not be destroyed by the caller. * * Returns: (transfer none): the scaled font. * * Since: 1.0 **/ cairo_scaled_font_t * adg_font_style_get_scaled_font(AdgFontStyle *font_style, const cairo_matrix_t *ctm) { AdgFontStylePrivate *data; cairo_font_options_t *options; cairo_matrix_t matrix; g_return_val_if_fail(ADG_IS_FONT_STYLE(font_style), NULL); g_return_val_if_fail(ctm != NULL, NULL); data = adg_font_style_get_instance_private(font_style); /* Check for cached font */ if (data->font != NULL) { cairo_matrix_t font_ctm; cairo_scaled_font_get_ctm(data->font, &font_ctm); /* The scaled font is valid only if the two ctm match */ if (ctm->xx == font_ctm.xx && ctm->yy == font_ctm.yy && ctm->xy == font_ctm.xy && ctm->yx == font_ctm.yx) return data->font; /* No valid cache found: rebuild the scaled font */ adg_style_invalidate((AdgStyle *) font_style); } if (data->face == NULL) { const gchar *family = data->family != NULL ? data->family : ""; data->face = cairo_toy_font_face_create(family, data->slant, data->weight); } cairo_matrix_init_scale(&matrix, data->size, data->size); options = adg_font_style_new_options(font_style); data->font = cairo_scaled_font_create(data->face, &matrix, ctm, options); cairo_font_options_destroy(options); return data->font; } /** * adg_font_style_set_color_dress: * @font_style: an #AdgFontStyle * @dress: (transfer none): the new color dress to use * * Sets a new color dress on @font_style. The new dress * should be related to the original dress: you cannot * set a dress used for font styles to a dress managing * fonts. * * The validation of the new dress is done by calling * adg_dress_are_related() with @dress and the previous * dress as arguments: check out its documentation for * details on what is a related dress. * * Since: 1.0 **/ void adg_font_style_set_color_dress(AdgFontStyle *font_style, AdgDress dress) { g_return_if_fail(ADG_IS_FONT_STYLE(font_style)); g_object_set(font_style, "color-dress", dress, NULL); } /** * adg_font_style_get_color_dress: * @font_style: an #AdgFontStyle * * Gets the color dress used by @font_style. * * Returns: (transfer none): the current color dress. * * Since: 1.0 **/ AdgDress adg_font_style_get_color_dress(AdgFontStyle *font_style) { AdgFontStylePrivate *data; g_return_val_if_fail(ADG_IS_FONT_STYLE(font_style), ADG_DRESS_UNDEFINED); data = adg_font_style_get_instance_private(font_style); return data->color_dress; } /** * adg_font_style_set_family: * @font_style: an #AdgFontStyle object * @family: (transfer none): the new family * * Sets a new family. * * Since: 1.0 **/ void adg_font_style_set_family(AdgFontStyle *font_style, const gchar *family) { g_return_if_fail(ADG_IS_FONT_STYLE(font_style)); g_object_set(font_style, "family", family, NULL); } /** * adg_font_style_get_family: * @font_style: an #AdgFontStyle object * * Gets the family of @font_style. The returned pointer refers to * internally managed text that must not be modified or freed. * * Returns: (transfer none): the requested family. * * Since: 1.0 **/ const gchar * adg_font_style_get_family(AdgFontStyle *font_style) { AdgFontStylePrivate *data; g_return_val_if_fail(ADG_IS_FONT_STYLE(font_style), NULL); data = adg_font_style_get_instance_private(font_style); return data->family; } /** * adg_font_style_set_slant: * @font_style: an #AdgFontStyle object * @slant: (type gint): the new slant * * Sets a new slant variant on @font_style. * * Since: 1.0 **/ void adg_font_style_set_slant(AdgFontStyle *font_style, cairo_font_slant_t slant) { g_return_if_fail(ADG_IS_FONT_STYLE(font_style)); g_object_set(font_style, "slant", slant, NULL); } /** * adg_font_style_get_slant: * @font_style: an #AdgFontStyle object * * Gets the slant variant of @font_style. * * Returns: (type gint): the slant variant. * * Since: 1.0 **/ cairo_font_slant_t adg_font_style_get_slant(AdgFontStyle *font_style) { AdgFontStylePrivate *data; g_return_val_if_fail(ADG_IS_FONT_STYLE(font_style), CAIRO_FONT_SLANT_NORMAL); data = adg_font_style_get_instance_private(font_style); return data->slant; } /** * adg_font_style_set_weight: * @font_style: an #AdgFontStyle object * @weight: (type gint): the new weight * * Sets a new weight variant on @font_style. * * Since: 1.0 **/ void adg_font_style_set_weight(AdgFontStyle *font_style, cairo_font_weight_t weight) { g_return_if_fail(ADG_IS_FONT_STYLE(font_style)); g_object_set(font_style, "weight", weight, NULL); } /** * adg_font_style_get_weight: * @font_style: an #AdgFontStyle object * * Gets the weight variant of @font_style. * * Returns: (type gint): the weight variant. * * Since: 1.0 **/ cairo_font_weight_t adg_font_style_get_weight(AdgFontStyle *font_style) { AdgFontStylePrivate *data; g_return_val_if_fail(ADG_IS_FONT_STYLE(font_style), CAIRO_FONT_WEIGHT_NORMAL); data = adg_font_style_get_instance_private(font_style); return data->weight; } /** * adg_font_style_set_size: * @font_style: an #AdgFontStyle object * @size: the new size * * Sets a new size (in global space) on @font_style. * * Since: 1.0 **/ void adg_font_style_set_size(AdgFontStyle *font_style, gdouble size) { g_return_if_fail(ADG_IS_FONT_STYLE(font_style)); g_object_set(font_style, "size", size, NULL); } /** * adg_font_style_get_size: * @font_style: an #AdgFontStyle object * * Gets the size (in global space) of @font_style. * * Returns: the size variant. * * Since: 1.0 **/ gdouble adg_font_style_get_size(AdgFontStyle *font_style) { AdgFontStylePrivate *data; g_return_val_if_fail(ADG_IS_FONT_STYLE(font_style), 0.); data = adg_font_style_get_instance_private(font_style); return data->size; } /** * adg_font_style_set_antialias: * @font_style: an #AdgFontStyle object * @antialias: (type gint): the new antialias mode * * Sets a new antialias mode. * * Since: 1.0 **/ void adg_font_style_set_antialias(AdgFontStyle *font_style, cairo_antialias_t antialias) { g_return_if_fail(ADG_IS_FONT_STYLE(font_style)); g_object_set(font_style, "antialias", antialias, NULL); } /** * adg_font_style_get_antialias: * @font_style: an #AdgFontStyle object * * Gets the antialias mode used. * * Returns: (type gint): the requested antialias mode. * * Since: 1.0 **/ cairo_antialias_t adg_font_style_get_antialias(AdgFontStyle *font_style) { AdgFontStylePrivate *data; g_return_val_if_fail(ADG_IS_FONT_STYLE(font_style), CAIRO_ANTIALIAS_DEFAULT); data = adg_font_style_get_instance_private(font_style); return data->antialias; } /** * adg_font_style_set_subpixel_order: * @font_style: an #AdgFontStyle object * @subpixel_order: (type gint): the new subpixel order mode * * Sets a new subpixel order mode. * * Since: 1.0 **/ void adg_font_style_set_subpixel_order(AdgFontStyle *font_style, cairo_subpixel_order_t subpixel_order) { g_return_if_fail(ADG_IS_FONT_STYLE(font_style)); g_object_set(font_style, "subpixel-order", subpixel_order, NULL); } /** * adg_font_style_get_subpixel_order: * @font_style: an #AdgFontStyle object * * Gets the subpixel order mode used, that is the order of color elements * within each pixel on the display device when rendering with an * antialiasing mode of %CAIRO_ANTIALIAS_SUBPIXEL. * * Returns: (type gint): the requested subpixel order mode. * * Since: 1.0 **/ cairo_subpixel_order_t adg_font_style_get_subpixel_order(AdgFontStyle *font_style) { AdgFontStylePrivate *data; g_return_val_if_fail(ADG_IS_FONT_STYLE(font_style), CAIRO_SUBPIXEL_ORDER_DEFAULT); data = adg_font_style_get_instance_private(font_style); return data->subpixel_order; } /** * adg_font_style_set_hint_style: * @font_style: an #AdgFontStyle object * @hint_style: (type gint): the new hint style mode * * Sets a new hint style mode. * * Since: 1.0 **/ void adg_font_style_set_hint_style(AdgFontStyle *font_style, cairo_hint_style_t hint_style) { g_return_if_fail(ADG_IS_FONT_STYLE(font_style)); g_object_set(font_style, "hint-style", hint_style, NULL); } /** * adg_font_style_get_hint_style: * @font_style: an #AdgFontStyle object * * Gets the hint style mode used, that is how to fit outlines * to the pixel grid in order to improve the appearance of the result. * * Returns: (type gint): the requested hint style mode. * * Since: 1.0 **/ cairo_hint_style_t adg_font_style_get_hint_style(AdgFontStyle *font_style) { AdgFontStylePrivate *data; g_return_val_if_fail(ADG_IS_FONT_STYLE(font_style), CAIRO_HINT_STYLE_DEFAULT); data = adg_font_style_get_instance_private(font_style); return data->hint_style; } /** * adg_font_style_set_hint_metrics: * @font_style: an #AdgFontStyle object * @hint_metrics: (type gint): the new hint metrics state * * Sets a new hint metrics state. * * Since: 1.0 **/ void adg_font_style_set_hint_metrics(AdgFontStyle *font_style, cairo_hint_metrics_t hint_metrics) { g_return_if_fail(ADG_IS_FONT_STYLE(font_style)); g_object_set(font_style, "hint-metrics", hint_metrics, NULL); } /** * adg_font_style_get_hint_metrics: * @font_style: an #AdgFontStyle object * * Gets the state on whether to hint font metrics. * * Returns: (type gint): the requested hint metrics state. * * Since: 1.0 **/ cairo_hint_metrics_t adg_font_style_get_hint_metrics(AdgFontStyle *font_style) { AdgFontStylePrivate *data; g_return_val_if_fail(ADG_IS_FONT_STYLE(font_style), CAIRO_HINT_METRICS_DEFAULT); data = adg_font_style_get_instance_private(font_style); return data->hint_metrics; } static void _adg_invalidate(AdgStyle *style) { AdgFontStyle *font_style = (AdgFontStyle *) style; AdgFontStylePrivate *data = adg_font_style_get_instance_private(font_style); if (data->font != NULL) { cairo_scaled_font_destroy(data->font); data->font = NULL; } if (data->face == NULL) { cairo_font_face_destroy(data->face); data->face = NULL; } } static void _adg_apply(AdgStyle *style, AdgEntity *entity, cairo_t *cr) { AdgFontStyle *font_style; AdgFontStylePrivate *data; cairo_matrix_t ctm; cairo_scaled_font_t *font; font_style = (AdgFontStyle *) style; data = adg_font_style_get_instance_private(font_style); adg_entity_apply_dress(entity, data->color_dress, cr); cairo_get_matrix(cr, &ctm); font = adg_font_style_get_scaled_font((AdgFontStyle *) style, &ctm); cairo_set_scaled_font(cr, font); } diff --git a/src/adg/adg-font-style.h b/src/adg/adg-font-style.h index 97c169ba..6b16ae56 100644 --- a/src/adg/adg-font-style.h +++ b/src/adg/adg-font-style.h @@ -1,100 +1,100 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_FONT_STYLE_H__ #define __ADG_FONT_STYLE_H__ G_BEGIN_DECLS #define ADG_TYPE_FONT_STYLE (adg_font_style_get_type()) #define ADG_FONT_STYLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_FONT_STYLE, AdgFontStyle)) #define ADG_FONT_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_FONT_STYLE, AdgFontStyleClass)) #define ADG_IS_FONT_STYLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_FONT_STYLE)) #define ADG_IS_FONT_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_FONT_STYLE)) #define ADG_FONT_STYLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_FONT_STYLE, AdgFontStyleClass)) typedef struct _AdgFontStyle AdgFontStyle; typedef struct _AdgFontStyleClass AdgFontStyleClass; struct _AdgFontStyle { /*< private >*/ AdgStyle parent; }; struct _AdgFontStyleClass { /*< private >*/ AdgStyleClass parent_class; }; GType adg_font_style_get_type (void); AdgFontStyle * adg_font_style_new (void); cairo_font_options_t * adg_font_style_new_options (AdgFontStyle *font_style); cairo_scaled_font_t * adg_font_style_get_scaled_font (AdgFontStyle *font_style, const cairo_matrix_t *ctm); void adg_font_style_set_color_dress (AdgFontStyle *font_style, AdgDress dress); AdgDress adg_font_style_get_color_dress (AdgFontStyle *font_style); void adg_font_style_set_family (AdgFontStyle *font_style, const gchar *family); const gchar * adg_font_style_get_family (AdgFontStyle *font_style); void adg_font_style_set_slant (AdgFontStyle *font_style, cairo_font_slant_t slant); cairo_font_slant_t adg_font_style_get_slant (AdgFontStyle *font_style); void adg_font_style_set_weight (AdgFontStyle *font_style, cairo_font_weight_t weight); cairo_font_weight_t adg_font_style_get_weight (AdgFontStyle *font_style); void adg_font_style_set_size (AdgFontStyle *font_style, gdouble size); gdouble adg_font_style_get_size (AdgFontStyle *font_style); void adg_font_style_set_antialias (AdgFontStyle *font_style, cairo_antialias_t antialias); cairo_antialias_t adg_font_style_get_antialias (AdgFontStyle *font_style); void adg_font_style_set_subpixel_order (AdgFontStyle *font_style, cairo_subpixel_order_t subpixel_order); cairo_subpixel_order_t adg_font_style_get_subpixel_order (AdgFontStyle *font_style); void adg_font_style_set_hint_style (AdgFontStyle *font_style, cairo_hint_style_t hint_style); cairo_hint_style_t adg_font_style_get_hint_style (AdgFontStyle *font_style); void adg_font_style_set_hint_metrics (AdgFontStyle *font_style, cairo_hint_metrics_t hint_metrics); cairo_hint_metrics_t adg_font_style_get_hint_metrics (AdgFontStyle *font_style); G_END_DECLS #endif /* __ADG_FONT_STYLE_H__ */ diff --git a/src/adg/adg-forward-declarations.h b/src/adg/adg-forward-declarations.h index 03ebef53..d8f40dec 100644 --- a/src/adg/adg-forward-declarations.h +++ b/src/adg/adg-forward-declarations.h @@ -1,49 +1,49 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_FORWARD_DECLARATIONS_H__ #define __ADG_FORWARD_DECLARATIONS_H__ G_BEGIN_DECLS /* Forward declarations are kept in a separate file so it can be * easily skipped by gtkdoc-scan: gtk-doc is inclined to mess up * sections when forward declarations are used. */ /* Needed by adg-entity.h */ typedef struct _AdgCanvas AdgCanvas; typedef struct _AdgStyle AdgStyle; typedef struct _AdgPoint AdgPoint; /* Needed by adg-table.h */ typedef struct _AdgTableRow AdgTableRow; typedef struct _AdgTableCell AdgTableCell; G_END_DECLS #endif /* __ADG_FORWARD_DECLARATIONS_H__ */ diff --git a/src/adg/adg-gtk-area-private.h b/src/adg/adg-gtk-area-private.h index 14dec6f4..6c1cb8ff 100644 --- a/src/adg/adg-gtk-area-private.h +++ b/src/adg/adg-gtk-area-private.h @@ -1,43 +1,43 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_GTK_AREA_PRIVATE_H__ #define __ADG_GTK_AREA_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgGtkAreaPrivate AdgGtkAreaPrivate; struct _AdgGtkAreaPrivate { AdgCanvas *canvas; gdouble factor; gboolean autozoom; cairo_matrix_t render_map; gboolean initialized; CpmlExtents extents; gdouble x_event, y_event; }; G_END_DECLS #endif /* __ADG_GTK_AREA_PRIVATE_H__ */ diff --git a/src/adg/adg-gtk-area.c b/src/adg/adg-gtk-area.c index bc987489..19c90ef2 100644 --- a/src/adg/adg-gtk-area.c +++ b/src/adg/adg-gtk-area.c @@ -1,1046 +1,1046 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-gtk-area * @short_description: A widget specifically designed to contain * an ADG canvas * * This is a #GtkDrawingArea derived object that provides an easy way * to show an ADG based canvas. The associated canvas can be set * directly with the adg_gtk_area_new_with_canvas() constructor * function or by using adg_gtk_area_set_canvas(). * * The minimum size of the widget will depend on the canvas content. * * The default implementation reacts to some mouse events: if you drag * the mouse keeping the wheel pressed, the canvas will be translated * (in local space by default and in global space if SHIFT * is pressed); if the mouse wheel is rotated, the canvas will be scaled * up or down according to the wheel direction by the factor specified * in the #AdgGtkArea:factor property (again, in local space by default * and in global space if SHIFT is pressed). The * adg_gtk_area_get_zoom() method could be used to retrieve the current * zoom coefficient. * * A new transformation layer is present between the global space * and the rendering: the #AdgGtkArea:render-map matrix. This * transformation is applied just before the rendering and it is * used to align and/or apply the zoom coefficient to the canvas * without affecting the other layers. Local transformations, * instead, are directly applied to the local matrix of the canvas. * * Since: 1.0 **/ /** * AdgGtkArea: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ /** * AdgGtkAreaClass: * @canvas_changed: signals that a new #AdgCanvas is bound to this widget. * @extents_changed: signals that the extents on the underling #AdgCanvas * has been changed. * * The default @canvas_changed resets the internal initialization flag, so at * the first call to the size_allocate method the zoom * factor is set to 1. * * The default @extents_changed signal does not do anything: it is intended as * a hook for derived class for refreshing GUI elements (such as scrollbars) * whenever the boundary box changes. * * Since: 1.0 **/ #include "adg-internal.h" #include #include "adg-container.h" #include "adg-table.h" #include "adg-title-block.h" #include #include "adg-gtk-utils.h" #include "adg-cairo-fallback.h" #include "adg-gtk-area.h" #include "adg-gtk-area-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_gtk_area_parent_class) #define _ADG_OLD_WIDGET_CLASS ((GtkWidgetClass *) adg_gtk_area_parent_class) G_DEFINE_TYPE_WITH_PRIVATE(AdgGtkArea, adg_gtk_area, GTK_TYPE_DRAWING_AREA) enum { PROP_0, PROP_CANVAS, PROP_FACTOR, PROP_AUTOZOOM, PROP_RENDER_MAP }; enum { CANVAS_CHANGED, EXTENTS_CHANGED, LAST_SIGNAL }; static guint _adg_signals[LAST_SIGNAL] = { 0 }; static const CpmlExtents * _adg_get_extents(AdgGtkArea *area) { AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private(area); CpmlExtents old_extents = data->extents; AdgCanvas *canvas = data->canvas; data->extents.is_defined = FALSE; if (ADG_IS_CANVAS(canvas)) { const CpmlExtents *extents; AdgEntity *entity; entity = (AdgEntity *) canvas; adg_entity_arrange(entity); extents = adg_entity_get_extents(entity); if (extents != NULL) { data->extents = *extents; adg_canvas_apply_margins(canvas, &data->extents); cpml_extents_transform(&data->extents, &data->render_map); } } if (!cpml_extents_equal(&data->extents, &old_extents)) g_signal_emit(area, _adg_signals[EXTENTS_CHANGED], 0, &old_extents); return &data->extents; } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) object); switch (prop_id) { case PROP_CANVAS: g_value_set_object(value, data->canvas); break; case PROP_FACTOR: g_value_set_double(value, data->factor); break; case PROP_AUTOZOOM: g_value_set_boolean(value, data->autozoom); break; case PROP_RENDER_MAP: g_value_set_boxed(value, &data->render_map); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) object); AdgCanvas *new_canvas, *old_canvas; switch (prop_id) { case PROP_CANVAS: new_canvas = g_value_get_object(value); old_canvas = data->canvas; if (new_canvas != old_canvas) { if (new_canvas != NULL) g_object_ref(new_canvas); if (old_canvas != NULL) g_object_unref(old_canvas); data->canvas = new_canvas; g_signal_emit(object, _adg_signals[CANVAS_CHANGED], 0, old_canvas); } break; case PROP_FACTOR: data->factor = g_value_get_double(value); break; case PROP_AUTOZOOM: data->autozoom = g_value_get_boolean(value); break; case PROP_RENDER_MAP: adg_matrix_copy(&data->render_map, g_value_get_boxed(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_dispose(GObject *object) { AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) object); if (data->canvas) { g_object_unref(data->canvas); data->canvas = NULL; } if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } /** * _adg_size_allocate: * @widget: an #AdgGtkArea widget * @allocation: the new allocation struct * * Scales the drawing according to the new allocation if * #AdgGtkArea:autozoom is TRUE. * * TODO: the current implementation initially centers the canvas * on the allocation space. Further allocations (due to a * window resizing, for example) use the top/left corner of the * canvas as reference point. Plan different policies for either * those situations. * * Since: 1.0 **/ static void _adg_size_allocate(GtkWidget *widget, GtkAllocation *allocation) { AdgGtkArea *area = (AdgGtkArea *) widget; AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private(area); const CpmlExtents *sheet; CpmlVector size; gdouble factor; if (_ADG_OLD_WIDGET_CLASS->size_allocate) _ADG_OLD_WIDGET_CLASS->size_allocate(widget, allocation); sheet = _adg_get_extents(area); if (!sheet->is_defined || sheet->size.x <= 0 || sheet->size.y <= 0) return; size.x = allocation->width; size.y = allocation->height; if (data->autozoom) { /* Adjust the zoom according to the allocation and sheet size */ factor = MIN(size.x / sheet->size.x, size.y / sheet->size.y); } else if (!data->initialized) { /* First allocation with autozoom off: keep the current zoom */ factor = 1; } else { /* Not the first allocation and autozoom off: keep the old map */ return; } if (!data->initialized) { /* TODO: plan different attachment policies other than centering */ cairo_matrix_init_translate(&data->render_map, (size.x - sheet->size.x) / 2 - sheet->org.x, (size.y - sheet->size.y) / 2 - sheet->org.y); data->initialized = TRUE; } /* TODO: plan other reference points other than left/top (x0, y0) */ data->render_map.x0 *= factor; data->render_map.y0 *= factor; data->render_map.xx *= factor; data->render_map.yy *= factor; } static gboolean _adg_get_map(GtkWidget *widget, gboolean local_space, cairo_matrix_t *map, cairo_matrix_t *inverted) { AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) widget); AdgEntity *entity = (AdgEntity *) data->canvas; if (entity == NULL) return FALSE; if (local_space) { adg_matrix_copy(map, adg_entity_get_local_map(entity)); /* The inverted map is subject to the global matrix */ adg_matrix_copy(inverted, adg_entity_get_global_matrix(entity)); adg_matrix_transform(inverted, map, ADG_TRANSFORM_BEFORE); } else { adg_matrix_copy(map, adg_entity_get_global_map(entity)); adg_matrix_copy(inverted, map); } return cairo_matrix_invert(inverted) == CAIRO_STATUS_SUCCESS; } static void _adg_set_map(GtkWidget *widget, gboolean local_space, const cairo_matrix_t *map) { AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) widget); AdgEntity *entity = (AdgEntity *) data->canvas; if (entity == NULL) return; if (local_space) { /* TODO: this forcibly overwrites any local transformation */ adg_entity_set_local_map(entity, map); } else { adg_matrix_transform(&data->render_map, map, ADG_TRANSFORM_BEFORE); } /* This will emit the extents-changed signal when applicable */ _adg_get_extents((AdgGtkArea *) widget); } static gboolean _adg_scroll_event(GtkWidget *widget, GdkEventScroll *event) { gboolean zoom_in, zoom_out, local_space, global_space; cairo_matrix_t map, inverted; zoom_in = event->direction == GDK_SCROLL_UP; zoom_out = event->direction == GDK_SCROLL_DOWN; local_space = (event->state & ADG_GTK_MODIFIERS) == 0; global_space = (event->state & ADG_GTK_MODIFIERS) == GDK_SHIFT_MASK; if ((zoom_in || zoom_out) && (local_space || global_space) && _adg_get_map(widget, local_space, &map, &inverted)) { AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) widget); double factor = zoom_in ? data->factor : 1. / data->factor; gdouble x = event->x; gdouble y = event->y; cairo_matrix_transform_point(&inverted, &x, &y); cairo_matrix_scale(&map, factor, factor); cairo_matrix_translate(&map, x/factor - x, y/factor - y); _adg_set_map(widget, local_space, &map); gtk_widget_queue_draw(widget); /* Avoid to chain up the default handler: * this event has been grabbed by this function */ return TRUE; } if (_ADG_OLD_WIDGET_CLASS->scroll_event == NULL) return FALSE; return _ADG_OLD_WIDGET_CLASS->scroll_event(widget, event); } static gboolean _adg_button_press_event(GtkWidget *widget, GdkEventButton *event) { if (event->type == GDK_BUTTON_PRESS && event->button == 2) { AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) widget); /* Remember the starting coordinates of a (probable) translation */ data->x_event = event->x; data->y_event = event->y; } if (_ADG_OLD_WIDGET_CLASS->button_press_event == NULL) return FALSE; return _ADG_OLD_WIDGET_CLASS->button_press_event(widget, event); } static gboolean _adg_motion_notify_event(GtkWidget *widget, GdkEventMotion *event) { gboolean translating, local_space, global_space; cairo_matrix_t map, inverted; translating = (event->state & GDK_BUTTON2_MASK) == GDK_BUTTON2_MASK; local_space = (event->state & ADG_GTK_MODIFIERS) == 0; global_space = (event->state & ADG_GTK_MODIFIERS) == GDK_SHIFT_MASK; if (translating && (local_space || global_space) && _adg_get_map(widget, local_space, &map, &inverted)) { AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) widget); gdouble x = event->x - data->x_event; gdouble y = event->y - data->y_event; cairo_matrix_transform_distance(&inverted, &x, &y); cairo_matrix_translate(&map, x, y); data->x_event = event->x; data->y_event = event->y; _adg_set_map(widget, local_space, &map); gtk_widget_queue_draw(widget); /* Avoid to chain up the default handler: * this event has been grabbed by this function */ return TRUE; } if (_ADG_OLD_WIDGET_CLASS->motion_notify_event == NULL) return FALSE; return _ADG_OLD_WIDGET_CLASS->motion_notify_event(widget, event); } static void _adg_canvas_changed(AdgGtkArea *area, AdgCanvas *old_canvas) { AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private(area); data->initialized = FALSE; } #ifdef GTK2_ENABLED static void _adg_size_request(GtkWidget *widget, GtkRequisition *requisition) { AdgGtkArea *area = (AdgGtkArea *) widget; const CpmlExtents *extents = _adg_get_extents(area); if (extents->is_defined) { requisition->width = extents->size.x; requisition->height = extents->size.y; } } static gboolean _adg_expose_event(GtkWidget *widget, GdkEventExpose *event) { AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) widget); AdgCanvas *canvas = data->canvas; if (canvas != NULL && event->window != NULL) { cairo_t *cr = gdk_cairo_create(event->window); cairo_transform(cr, &data->render_map); adg_entity_render((AdgEntity *) canvas, cr); cairo_destroy(cr); } if (_ADG_OLD_WIDGET_CLASS->expose_event == NULL) return FALSE; return _ADG_OLD_WIDGET_CLASS->expose_event(widget, event); } #endif #ifdef GTK3_ENABLED static void _adg_get_preferred_height(GtkWidget *widget, gint *minimum_height, gint *natural_height) { AdgGtkArea *area; const CpmlExtents *extents; area = (AdgGtkArea *) widget; extents = _adg_get_extents(area); if (extents->is_defined) { *minimum_height = extents->size.y; *natural_height = *minimum_height; } } static void _adg_get_preferred_height_for_width(GtkWidget *widget, gint width, gint *minimum_height, gint *natural_height) { AdgGtkArea *area; const CpmlExtents *extents; area = (AdgGtkArea *) widget; extents = _adg_get_extents(area); if (extents->is_defined && extents->size.x > 0) { *minimum_height = extents->size.y; *natural_height = *minimum_height * width / extents->size.x; } } static void _adg_get_preferred_width(GtkWidget *widget, gint *minimum_width, gint *natural_width) { AdgGtkArea *area; const CpmlExtents *extents; area = (AdgGtkArea *) widget; extents = _adg_get_extents(area); if (extents->is_defined) { *minimum_width = extents->size.x; *natural_width = *minimum_width; } } static void _adg_get_preferred_width_for_height(GtkWidget *widget, gint height, gint *minimum_width, gint *natural_width) { AdgGtkArea *area; const CpmlExtents *extents; area = (AdgGtkArea *) widget; extents = _adg_get_extents(area); if (extents->is_defined && extents->size.y > 0) { *minimum_width = extents->size.x; *natural_width = *minimum_width * height / extents->size.y; } } static gboolean _adg_draw(GtkWidget *widget, cairo_t *cr) { AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private((AdgGtkArea *) widget); AdgCanvas *canvas = data->canvas; if (canvas != NULL) { cairo_transform(cr, &data->render_map); adg_entity_render((AdgEntity *) canvas, cr); } return FALSE; } #endif static void adg_gtk_area_class_init(AdgGtkAreaClass *klass) { GObjectClass *gobject_class; GtkWidgetClass *widget_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; widget_class = (GtkWidgetClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; #ifdef GTK2_ENABLED widget_class->size_request = _adg_size_request; widget_class->expose_event = _adg_expose_event; #endif #ifdef GTK3_ENABLED widget_class->get_preferred_height = _adg_get_preferred_height; widget_class->get_preferred_height_for_width = _adg_get_preferred_height_for_width; widget_class->get_preferred_width = _adg_get_preferred_width; widget_class->get_preferred_width_for_height = _adg_get_preferred_width_for_height; widget_class->draw = _adg_draw; #endif widget_class->size_allocate = _adg_size_allocate; widget_class->scroll_event = _adg_scroll_event; widget_class->button_press_event = _adg_button_press_event; widget_class->motion_notify_event = _adg_motion_notify_event; klass->canvas_changed = _adg_canvas_changed; param = g_param_spec_object("canvas", P_("Canvas"), P_("The canvas to be shown"), ADG_TYPE_CANVAS, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); g_object_class_install_property(gobject_class, PROP_CANVAS, param); param = g_param_spec_double("factor", P_("Factor"), P_("The factor to use while zooming in and out (usually with the mouse wheel)"), 1., G_MAXDOUBLE, 1.05, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FACTOR, param); param = g_param_spec_boolean("autozoom", P_("Autozoom"), P_("When enabled, automatically adjust the zoom in global space at every size allocation"), FALSE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_AUTOZOOM, param); param = g_param_spec_boxed("render-map", P_("Render Map"), P_("The transformation to be applied on the canvas before rendering it"), CAIRO_GOBJECT_TYPE_MATRIX, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_RENDER_MAP, param); /** * AdgGtkArea::canvas-changed: * @area: an #AdgGtkArea * @old_canvas: (type AdgCanvas*): the old #AdgCanvas object * * Emitted after the canvas bound to @area has been changed. The old * canvas accessible from @old_canvas while the new canvas can be got * with the usual API, e.g. adg_gtk_area_get_canvas(). * * Since: 1.0 **/ _adg_signals[CANVAS_CHANGED] = g_signal_new("canvas-changed", ADG_GTK_TYPE_AREA, G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET(AdgGtkAreaClass, canvas_changed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, ADG_TYPE_CANVAS); /** * AdgGtkArea::extents-changed: * @area: an #AdgGtkArea * @old_extents: the old #CpmlExtents struct * * Emitted when the extents of @area have been changed. * The old extents are always compared to the new ones, * so when the extents are recalculated but the result * is the same the signal is not emitted. * * The extents of #AdgGtkArea are subject to the render * map, so changing the #AdgGtkArea:render-map property * will emit this signal too. * * Since: 1.0 **/ _adg_signals[EXTENTS_CHANGED] = g_signal_new("extents-changed", ADG_GTK_TYPE_AREA, G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET(AdgGtkAreaClass, extents_changed), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); } static void adg_gtk_area_init(AdgGtkArea *area) { AdgGtkAreaPrivate *data = adg_gtk_area_get_instance_private(area); data->canvas = NULL; data->factor = 1.05; data->autozoom = FALSE; cairo_matrix_init_identity(&data->render_map); data->initialized = FALSE; data->x_event = 0; data->y_event = 0; /* Enable GDK events to catch wheel rotation and drag */ gtk_widget_add_events((GtkWidget *) area, GDK_BUTTON_PRESS_MASK | GDK_BUTTON2_MOTION_MASK | GDK_SCROLL_MASK); } /** * adg_gtk_area_new: * * Creates a new empty #AdgGtkArea. The widget is useful only after * an #AdgCanvas has been added either using the #AdgGtkArea:canvas * property or with adg_gtk_area_set_canvas(). * * Returns: the newly created widget * * Since: 1.0 **/ GtkWidget * adg_gtk_area_new(void) { return g_object_new(ADG_GTK_TYPE_AREA, NULL); } /** * adg_gtk_area_new_with_canvas: * @canvas: the #AdgCanvas shown by this widget * * Creates a new #AdgGtkArea and sets the #AdgGtkArea:canvas property * to @canvas. * * Returns: the newly created widget * * Since: 1.0 **/ GtkWidget * adg_gtk_area_new_with_canvas(AdgCanvas *canvas) { g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL); return g_object_new(ADG_GTK_TYPE_AREA, "canvas", canvas, NULL); } /** * adg_gtk_area_set_canvas: * @area: an #AdgGtkArea * @canvas: the new #AdgCanvas * * Sets a new canvas on @area. The old canvas, if presents, is * unreferenced. * * Since: 1.0 **/ void adg_gtk_area_set_canvas(AdgGtkArea *area, AdgCanvas *canvas) { g_return_if_fail(ADG_GTK_IS_AREA(area)); g_object_set(area, "canvas", canvas, NULL); } /** * adg_gtk_area_get_canvas: * @area: an #AdgGtkArea * * Gets the canvas associated to @area. The returned canvas * is owned by @area and should not be modified or freed. * * Returns: (transfer none): the requested #AdgCanvas object or NULL on errors. * * Since: 1.0 **/ AdgCanvas * adg_gtk_area_get_canvas(AdgGtkArea *area) { AdgGtkAreaPrivate *data; g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL); data = adg_gtk_area_get_instance_private(area); return data->canvas; } /** * adg_gtk_area_set_render_map: * @area: an #AdgGtkArea object * @map: the new map * * Sets the new render transformation of @area to @map: the * old map is discarded. If @map is NULL, * the render map is left unchanged. * * * The render map is an implementation detail and this function * is expected to be used only by #AdgGtkArea derived objects. * * * Since: 1.0 **/ void adg_gtk_area_set_render_map(AdgGtkArea *area, const cairo_matrix_t *map) { g_return_if_fail(ADG_GTK_IS_AREA(area)); g_object_set(area, "render-map", map, NULL); } /** * adg_gtk_area_transform_render_map: * @area: an #AdgGtkArea object * @transformation: the transformation to apply * @mode: how @transformation should be applied * * Convenient function to change the render map of @area by * applying @tranformation using the @mode operator. This is * logically equivalent to the following: * * * cairo_matrix_t map; * adg_matrix_copy(&map, adg_gtk_area_get_render_map(area)); * adg_matrix_transform(&map, transformation, mode); * adg_gtk_area_set_render_map(area, &map); * * * * The render map is an implementation detail and this function * is expected to be used only by #AdgGtkArea derived objects. * * * Since: 1.0 **/ void adg_gtk_area_transform_render_map(AdgGtkArea *area, const cairo_matrix_t *transformation, AdgTransformMode mode) { AdgGtkAreaPrivate *data; cairo_matrix_t map; g_return_if_fail(ADG_GTK_IS_AREA(area)); g_return_if_fail(transformation != NULL); data = adg_gtk_area_get_instance_private(area); adg_matrix_copy(&map, &data->render_map); adg_matrix_transform(&map, transformation, mode); g_object_set(area, "render-map", &map, NULL); } /** * adg_gtk_area_get_render_map: * @area: an #AdgGtkArea object * * Gets the render map. * * Returns: the requested map or NULL on errors. * * Since: 1.0 **/ const cairo_matrix_t * adg_gtk_area_get_render_map(AdgGtkArea *area) { AdgGtkAreaPrivate *data; g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL); data = adg_gtk_area_get_instance_private(area); return &data->render_map; } /** * adg_gtk_area_get_extents: * @area: an #AdgGtkArea * * Gets the extents of the canvas bound to @area. The returned * struct is owned by @area and should not modified or freed. * * The extents of an #AdgGtkArea instance are the extents of * its canvas (as returned by adg_entity_get_extents()) with * the margins added to it and the #AdgGtkArea:render-map * transformation applied. * * If @area does not have any canvas associated to it or the * canvas is invalid or empty, an undefined #CpmlExtents * struct will be returned. * * The canvas will be updated, meaning adg_entity_arrange() * is called before the extents computation. * * Returns: the extents of the @area canvas or NULL on errors. * * Since: 1.0 **/ const CpmlExtents * adg_gtk_area_get_extents(AdgGtkArea *area) { g_return_val_if_fail(ADG_GTK_IS_AREA(area), NULL); return _adg_get_extents(area); } /** * adg_gtk_area_get_zoom: * @area: an #AdgGtkArea * * Gets the last zoom coefficient applied on the canvas of @area. * If the #AdgGtkArea:autozoom property is FALSE, * the value returned should be always 1. * * Returns: the current zoom coefficient. * * Since: 1.0 **/ gdouble adg_gtk_area_get_zoom(AdgGtkArea *area) { AdgGtkAreaPrivate *data; g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.); data = adg_gtk_area_get_instance_private(area); return data->render_map.xx; } /** * adg_gtk_area_set_factor: * @area: an #AdgGtkArea * @factor: the new zoom factor * * Sets a new zoom factor to @area. If the factor is less than * 1, it will be clamped to 1. * * Since: 1.0 **/ void adg_gtk_area_set_factor(AdgGtkArea *area, gdouble factor) { g_return_if_fail(ADG_GTK_IS_AREA(area)); g_object_set(area, "factor", factor, NULL); } /** * adg_gtk_area_get_factor: * @area: an #AdgGtkArea * * Gets the zoom factor associated to @area. The zoom factor is * directly used to zoom in (that is, the default zoom factor of * 1.05 will zoom of 5% every iteration) and it is reversed while * zooming out (that is, the default factor will be 1/1.05). * * Returns: the requested zoom factor or 0 on error * * Since: 1.0 **/ gdouble adg_gtk_area_get_factor(AdgGtkArea *area) { AdgGtkAreaPrivate *data; g_return_val_if_fail(ADG_GTK_IS_AREA(area), 0.); data = adg_gtk_area_get_instance_private(area); return data->factor; } /** * adg_gtk_area_switch_autozoom: * @area: an #AdgGtkArea * @state: the new autozoom state * * Sets the #AdgGtkArea:autozoom property of @area to @state. When the * autozoom feature is enabled, @area reacts to any size allocation * by adjusting its zoom coefficient in global space. This means the * drawing will fill the available space (keeping its aspect ratio) * when resizing the window. * * Since: 1.0 **/ void adg_gtk_area_switch_autozoom(AdgGtkArea *area, gboolean state) { g_return_if_fail(ADG_GTK_IS_AREA(area)); g_object_set(area, "autozoom", state, NULL); } /** * adg_gtk_area_has_autozoom: * @area: an #AdgGtkArea * * Gets the current state of the #AdgGtkArea:autozoom property on * the @area object. * * Returns: the current autozoom state * * Since: 1.0 **/ gboolean adg_gtk_area_has_autozoom(AdgGtkArea *area) { AdgGtkAreaPrivate *data; g_return_val_if_fail(ADG_GTK_IS_AREA(area), FALSE); data = adg_gtk_area_get_instance_private(area); return data->autozoom; } /** * adg_gtk_area_reset: * @area: an #AdgGtkArea * * Forcibly resets the zoom ratio and position of the canvas bound * to @area. This means the canvas will be scaled and centered on * the current available space. **/ void adg_gtk_area_reset(AdgGtkArea *area) { AdgGtkAreaPrivate *data; GtkWidget *parent; const CpmlExtents *sheet; GtkAllocation allocation; CpmlPair size; gdouble zoom; g_return_if_fail(ADG_GTK_IS_AREA(area)); data = adg_gtk_area_get_instance_private(area); cairo_matrix_init_identity(&data->render_map); sheet = _adg_get_extents(area); if (!sheet->is_defined || sheet->size.x <= 0 || sheet->size.y <= 0) return; parent = gtk_widget_get_parent((GtkWidget *) area); gtk_widget_get_allocation(parent, &allocation); size.x = allocation.width; size.y = allocation.height; zoom = MIN(size.x / sheet->size.x, size.y / sheet->size.y); cairo_matrix_scale(&data->render_map, zoom, zoom); cairo_matrix_translate(&data->render_map, (size.x / zoom - sheet->size.x) / 2 - sheet->org.x, (size.y / zoom - sheet->size.y) / 2 - sheet->org.y); /* Trigger a resize trying to hide the scrollbars on the parent */ gtk_widget_queue_resize(parent); } /** * adg_gtk_area_canvas_changed: * @area: an #AdgGtkArea * @old_canvas: the old canvas bound to @area * * Emits the #AdgGtkArea::canvas-changed signal on @area. * * Since: 1.0 **/ void adg_gtk_area_canvas_changed(AdgGtkArea *area, AdgCanvas *old_canvas) { g_return_if_fail(ADG_GTK_IS_AREA(area)); g_signal_emit(area, _adg_signals[CANVAS_CHANGED], 0, old_canvas); } /** * adg_gtk_area_extents_changed: * @area: an #AdgGtkArea * @old_extents: the old extents of @area * * Emits the #AdgGtkArea::extents-changed signal on @area. * * Since: 1.0 **/ void adg_gtk_area_extents_changed(AdgGtkArea *area, const CpmlExtents *old_extents) { g_return_if_fail(ADG_GTK_IS_AREA(area)); g_signal_emit(area, _adg_signals[EXTENTS_CHANGED], 0, old_extents); } diff --git a/src/adg/adg-gtk-area.h b/src/adg/adg-gtk-area.h index a77c358f..eb31df77 100644 --- a/src/adg/adg-gtk-area.h +++ b/src/adg/adg-gtk-area.h @@ -1,95 +1,95 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_GTK_AREA_H__ #define __ADG_GTK_AREA_H__ G_BEGIN_DECLS #define ADG_GTK_TYPE_AREA (adg_gtk_area_get_type()) #define ADG_GTK_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_GTK_TYPE_AREA, AdgGtkArea)) #define ADG_GTK_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_GTK_TYPE_AREA, AdgGtkAreaClass)) #define ADG_GTK_IS_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_GTK_TYPE_AREA)) #define ADG_GTK_IS_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_GTK_TYPE_AREA)) #define ADG_GTK_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_GTK_TYPE_AREA, AdgGtkAreaClass)) typedef struct _AdgGtkArea AdgGtkArea; typedef struct _AdgGtkAreaClass AdgGtkAreaClass; struct _AdgGtkArea { /*< private >*/ GtkDrawingArea parent; }; struct _AdgGtkAreaClass { /*< private >*/ GtkDrawingAreaClass parent_class; /*< public >*/ /* Signals */ void (*canvas_changed) (AdgGtkArea *area, AdgCanvas *old_canvas); void (*extents_changed) (AdgGtkArea *area, const CpmlExtents *old_extents); }; GType adg_gtk_area_get_type (void); GtkWidget * adg_gtk_area_new (void); GtkWidget * adg_gtk_area_new_with_canvas (AdgCanvas *canvas); void adg_gtk_area_set_canvas (AdgGtkArea *area, AdgCanvas *canvas); AdgCanvas * adg_gtk_area_get_canvas (AdgGtkArea *area); const CpmlExtents * adg_gtk_area_get_extents (AdgGtkArea *area); gdouble adg_gtk_area_get_zoom (AdgGtkArea *area); void adg_gtk_area_set_factor (AdgGtkArea *area, gdouble factor); gdouble adg_gtk_area_get_factor (AdgGtkArea *area); void adg_gtk_area_switch_autozoom (AdgGtkArea *area, gboolean state); gboolean adg_gtk_area_has_autozoom (AdgGtkArea *area); void adg_gtk_area_reset (AdgGtkArea *area); void adg_gtk_area_canvas_changed (AdgGtkArea *area, AdgCanvas *old_canvas); void adg_gtk_area_set_render_map (AdgGtkArea *area, const cairo_matrix_t *map); void adg_gtk_area_transform_render_map (AdgGtkArea *area, const cairo_matrix_t *transformation, AdgTransformMode mode); const cairo_matrix_t* adg_gtk_area_get_render_map (AdgGtkArea *area); void adg_gtk_area_extents_changed (AdgGtkArea *area, const CpmlExtents *old_extents); G_END_DECLS #endif /* __ADG_GTK_AREA_H__ */ diff --git a/src/adg/adg-gtk-layout-private.h b/src/adg/adg-gtk-layout-private.h index ed25d21d..300e33d7 100644 --- a/src/adg/adg-gtk-layout-private.h +++ b/src/adg/adg-gtk-layout-private.h @@ -1,41 +1,41 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_GTK_LAYOUT_PRIVATE_H__ #define __ADG_GTK_LAYOUT_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgGtkLayoutPrivate AdgGtkLayoutPrivate; struct _AdgGtkLayoutPrivate { GtkAdjustment *hadjustment; GtkAdjustment *vadjustment; gboolean policy_stored; GtkPolicyType hpolicy, vpolicy; CpmlExtents viewport; }; G_END_DECLS #endif /* __ADG_GTK_LAYOUT_PRIVATE_H__ */ diff --git a/src/adg/adg-gtk-layout.c b/src/adg/adg-gtk-layout.c index e13672d5..2506f4fe 100644 --- a/src/adg/adg-gtk-layout.c +++ b/src/adg/adg-gtk-layout.c @@ -1,591 +1,591 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-gtk-layout * @short_description: A scrollable AdgGtkArea based widget * * This is an #AdgGtkArea derived object with scrolling capabilities. * It means an #AdgGtkLayout object can be added directly to a * #GtkScrolledWindow container without the need for an intermediate * #GtkViewport container. * * Since: 1.0 **/ /** * AdgGtkLayout: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include #include "adg-container.h" #include "adg-table.h" #include "adg-title-block.h" #include #include "adg-gtk-area.h" #include "adg-gtk-layout.h" #include "adg-gtk-layout-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_gtk_layout_parent_class) #define _ADG_OLD_WIDGET_CLASS ((GtkWidgetClass *) adg_gtk_layout_parent_class) #define _ADG_OLD_AREA_CLASS ((AdgGtkAreaClass *) adg_gtk_layout_parent_class) #ifdef GTK2_ENABLED enum { PROP_0, PROP_HADJUSTMENT, PROP_VADJUSTMENT }; G_DEFINE_TYPE_WITH_PRIVATE(AdgGtkLayout, adg_gtk_layout, ADG_GTK_TYPE_AREA) static void _adg_set_scroll_adjustments(GtkWidget *widget, GtkAdjustment *hadjustment, GtkAdjustment *vadjustment) { g_object_set(widget, "hadjustment", hadjustment, "vadjustment", vadjustment, NULL); } #endif #ifdef GTK3_ENABLED enum { PROP_0, PROP_HADJUSTMENT, PROP_VADJUSTMENT, PROP_HSCROLL_POLICY, PROP_VSCROLL_POLICY }; G_DEFINE_TYPE_WITH_CODE(AdgGtkLayout, adg_gtk_layout, ADG_GTK_TYPE_AREA, G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, NULL) G_ADD_PRIVATE(AdgGtkLayout)) #endif static void _adg_set_parent_size(AdgGtkLayout *layout) { GtkWidget *widget; AdgGtkLayoutPrivate *data; GtkWidget *parent; const CpmlExtents *sheet; GtkScrolledWindow *scrolled_window; widget = (GtkWidget *) layout; /* When the widget is realized it is too late to suggest a size */ if (gtk_widget_get_realized(widget)) return; data = adg_gtk_layout_get_instance_private(layout); parent = gtk_widget_get_parent(widget); if (!GTK_IS_WIDGET(parent)) return; sheet = adg_gtk_area_get_extents((AdgGtkArea *) layout); if (sheet == NULL || !sheet->is_defined) return; gtk_widget_set_size_request(parent, sheet->size.x + 2, sheet->size.y + 2); if (GTK_IS_SCROLLED_WINDOW(parent) && !data->policy_stored) { scrolled_window = (GtkScrolledWindow *) parent; gtk_scrolled_window_get_policy(scrolled_window, &data->hpolicy, &data->vpolicy); gtk_scrolled_window_set_policy(scrolled_window, GTK_POLICY_NEVER, GTK_POLICY_NEVER); data->policy_stored = TRUE; } } static void _adg_parent_set(GtkWidget *widget, GtkWidget *old_parent) { AdgGtkLayout *layout = (AdgGtkLayout *) widget; if (_ADG_OLD_WIDGET_CLASS->parent_set != NULL) _ADG_OLD_WIDGET_CLASS->parent_set(widget, old_parent); _adg_set_parent_size(layout); } /** * _adg_update_adjustments: * @layout: an #AdgGtkLayout * * Updates the scrollbars according to the new extents of the canvas * of @area and to the current viewport. * * The algorithm uses three local #CpmlExtents variables: the * viewport (what physically shown by the graphic device), * the sheet (the extents of the drawing, margins * included) and surface (a helper variable that is the * union of the previous two extents). **/ static void _adg_update_adjustments(AdgGtkLayout *layout) { AdgGtkArea *area = (AdgGtkArea *) layout; AdgCanvas *canvas = adg_gtk_area_get_canvas(area); const CpmlExtents *sheet, *viewport; AdgGtkLayoutPrivate *data; GtkAdjustment *hadj, *vadj; CpmlExtents surface; if (canvas == NULL) return; sheet = adg_gtk_area_get_extents(area); if (sheet == NULL || !sheet->is_defined) return; data = adg_gtk_layout_get_instance_private(layout); hadj = data->hadjustment; vadj = data->vadjustment; viewport = &data->viewport; surface = *sheet; cpml_extents_add(&surface, viewport); if (data->policy_stored) { /* Restore the original policy for the scrollbars */ GtkWidget *parent; GtkScrolledWindow *scrolled_window; parent = gtk_widget_get_parent((GtkWidget *) layout); scrolled_window = (GtkScrolledWindow *) parent; gtk_scrolled_window_set_policy(scrolled_window, data->hpolicy, data->vpolicy); data->policy_stored = TRUE; } g_object_set(hadj, "lower", surface.org.x, "upper", surface.org.x + surface.size.x, "page-size", viewport->size.x, "value", viewport->org.x, NULL); g_object_set(vadj, "lower", surface.org.y, "upper", surface.org.y + surface.size.y, "page-size", viewport->size.y, "value", viewport->org.y, NULL); } static void _adg_size_allocate(GtkWidget *widget, GtkAllocation *allocation) { AdgGtkLayout *layout = (AdgGtkLayout *) widget; AdgGtkLayoutPrivate *data = adg_gtk_layout_get_instance_private(layout); if (_ADG_OLD_WIDGET_CLASS->size_allocate != NULL) _ADG_OLD_WIDGET_CLASS->size_allocate(widget, allocation); /* Resize the viewport on a new allocation. * TODO: plan other policies instead of forcibly set only the * size field on the viewport struct, such as modifying the * org to keep the sheet centered in the allocation space. */ data->viewport.size.x = allocation->width; data->viewport.size.y = allocation->height; data->viewport.is_defined = TRUE; _adg_update_adjustments(layout); } static void _adg_canvas_changed(AdgGtkArea *area, AdgCanvas *old_canvas) { AdgGtkLayout *layout = (AdgGtkLayout *) area; AdgGtkLayoutPrivate *data = adg_gtk_layout_get_instance_private(layout); if (_ADG_OLD_AREA_CLASS->canvas_changed != NULL) _ADG_OLD_AREA_CLASS->canvas_changed(area, old_canvas); /* By convention, expect the origin of a new canvas to be * on the top/left corner of the allocation area */ data->viewport.org.x = 0; data->viewport.org.y = 0; _adg_set_parent_size(layout); } static void _adg_extents_changed(AdgGtkArea *area, const CpmlExtents *old_extents) { if (_ADG_OLD_AREA_CLASS->extents_changed != NULL) _ADG_OLD_AREA_CLASS->extents_changed(area, old_extents); _adg_update_adjustments((AdgGtkLayout *) area); } static void _adg_value_changed(AdgGtkLayout *layout) { GtkWidget *widget; AdgGtkArea *area; AdgGtkLayoutPrivate *data; CpmlPair org; cairo_matrix_t map; widget = (GtkWidget *) layout; if (!gtk_widget_get_realized(widget)) return; area = (AdgGtkArea *) layout; data = adg_gtk_layout_get_instance_private(layout); org.x = gtk_adjustment_get_value(data->hadjustment); org.y = gtk_adjustment_get_value(data->vadjustment); cairo_matrix_init_translate(&map, data->viewport.org.x - org.x, data->viewport.org.y - org.y); adg_gtk_area_transform_render_map(area, &map, ADG_TRANSFORM_BEFORE); gtk_widget_queue_draw(widget); _adg_update_adjustments(layout); } static void _adg_set_adjustment(AdgGtkLayout *layout, GtkAdjustment **dst, GtkAdjustment *src) { GCallback value_changed; if (*dst == src) return; value_changed = G_CALLBACK(_adg_value_changed); if (*dst != NULL) { /* Release the old adjustment */ g_signal_handlers_disconnect_by_func(*dst, (gpointer) value_changed, layout); g_object_unref(*dst); } g_signal_connect_swapped(src, "value-changed", value_changed, layout); g_object_ref_sink(src); *dst = src; } static void _adg_dispose(GObject *object) { AdgGtkLayoutPrivate *data = adg_gtk_layout_get_instance_private((AdgGtkLayout *) object); if (data->hadjustment != NULL) { g_object_unref(data->hadjustment); data->hadjustment = NULL; } if (data->vadjustment != NULL) { g_object_unref(data->vadjustment); data->vadjustment = NULL; } if (_ADG_OLD_OBJECT_CLASS->dispose != NULL) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgGtkLayoutPrivate *data = adg_gtk_layout_get_instance_private((AdgGtkLayout *) object); switch (prop_id) { case PROP_HADJUSTMENT: g_value_set_object(value, data->hadjustment); break; case PROP_VADJUSTMENT: g_value_set_object(value, data->vadjustment); break; #ifdef GTK3_ENABLED case PROP_HSCROLL_POLICY: g_value_set_enum(value, data->hpolicy); break; case PROP_VSCROLL_POLICY: g_value_set_enum(value, data->vpolicy); break; #endif default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgGtkLayout *layout = (AdgGtkLayout *) object; AdgGtkLayoutPrivate *data = adg_gtk_layout_get_instance_private(layout); GtkAdjustment *adjustment; switch (prop_id) { case PROP_HADJUSTMENT: adjustment = g_value_get_object(value); if (adjustment == NULL) adjustment = (GtkAdjustment *) gtk_adjustment_new(0, 0, 0, 0, 0, 0); _adg_set_adjustment(layout, &data->hadjustment, adjustment); break; case PROP_VADJUSTMENT: adjustment = g_value_get_object(value); if (adjustment == NULL) adjustment = (GtkAdjustment *) gtk_adjustment_new(0, 0, 0, 0, 0, 0); _adg_set_adjustment(layout, &data->vadjustment, adjustment); break; #ifdef GTK3_ENABLED case PROP_HSCROLL_POLICY: data->hpolicy = g_value_get_enum(value); break; case PROP_VSCROLL_POLICY: data->vpolicy = g_value_get_enum(value); break; #endif default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void adg_gtk_layout_class_init(AdgGtkLayoutClass *klass) { GObjectClass *gobject_class; GtkWidgetClass *widget_class; AdgGtkAreaClass *area_class; gobject_class = (GObjectClass *) klass; widget_class = (GtkWidgetClass *) klass; area_class = (AdgGtkAreaClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; widget_class->parent_set = _adg_parent_set; widget_class->size_allocate = _adg_size_allocate; area_class->canvas_changed = _adg_canvas_changed; area_class->extents_changed = _adg_extents_changed; #ifdef GTK2_ENABLED g_object_class_install_property(gobject_class, PROP_HADJUSTMENT, g_param_spec_object("hadjustment", P_("Horizontal adjustment"), P_("The GtkAdjustment that determines the values of the horizontal position for this viewport"), GTK_TYPE_ADJUSTMENT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property(gobject_class, PROP_VADJUSTMENT, g_param_spec_object("vadjustment", P_("Vertical adjustment"), P_("The GtkAdjustment that determines the values of the vertical position for this viewport"), GTK_TYPE_ADJUSTMENT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); /** * AdgGtkLayout::set-scroll-adjustments: * @layout: an #AdgGtkLayout * @hadjustment: the horizontal #GtkAdjustment * @vadjustment: the vertical #GtkAdjustment * * Emitted when the adjustments of the scroll bars must be changed. * * Since: 1.0 **/ widget_class->set_scroll_adjustments_signal = g_signal_new_class_handler("set-scroll-adjustments", ADG_GTK_TYPE_LAYOUT, G_SIGNAL_RUN_LAST, G_CALLBACK(_adg_set_scroll_adjustments), NULL, NULL, adg_marshal_VOID__OBJECT_OBJECT, G_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT); #endif #ifdef GTK3_ENABLED g_object_class_override_property(gobject_class, PROP_HADJUSTMENT, "hadjustment"); g_object_class_override_property(gobject_class, PROP_HSCROLL_POLICY, "hscroll-policy"); g_object_class_override_property(gobject_class, PROP_VADJUSTMENT, "vadjustment"); g_object_class_override_property(gobject_class, PROP_VSCROLL_POLICY, "vscroll-policy"); #endif } static void adg_gtk_layout_init(AdgGtkLayout *layout) { AdgGtkLayoutPrivate *data = adg_gtk_layout_get_instance_private(layout); data->hadjustment = NULL; data->vadjustment = NULL; data->policy_stored = FALSE; data->viewport.is_defined = FALSE; } /** * adg_gtk_layout_new: * * Creates a new empty #AdgGtkLayout. The widget is useful only after * an #AdgCanvas has been added either using the #AdgGtkArea:canvas * property or with adg_gtk_area_set_canvas(). * * Returns: (transfer full): the newly created widget. * * Since: 1.0 **/ GtkWidget * adg_gtk_layout_new(void) { return g_object_new(ADG_GTK_TYPE_LAYOUT, NULL); } /** * adg_gtk_layout_new_with_canvas: * @canvas: the #AdgCanvas shown by this widget * * Creates a new #AdgGtkLayout and sets the #AdgGtkArea:canvas * property to @canvas. * * Returns: (transfer full): the newly created widget. * * Since: 1.0 **/ GtkWidget * adg_gtk_layout_new_with_canvas(AdgCanvas *canvas) { g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL); return g_object_new(ADG_GTK_TYPE_LAYOUT, "canvas", canvas, NULL); } /** * adg_gtk_layout_set_hadjustment: * @layout: an #AdgGtkLayout * @hadjustment: the new adjustment * * Sets the new horizontal adjustment for @layout to @hadjustment. * The old adjustment, if present, is unreferenced. * * This is basically the same as manually setting the * #GtkScrollable:hadjustment property with g_object_set(). * * Since: 1.0 **/ void adg_gtk_layout_set_hadjustment(AdgGtkLayout *layout, GtkAdjustment *hadjustment) { g_return_if_fail(ADG_GTK_IS_LAYOUT(layout)); g_object_set(layout, "hadjustment", hadjustment, NULL); } /** * adg_gtk_layout_get_hadjustment: * @layout: an #AdgGtkLayout * * Retrieves the current horizontal adjustment of @layout. * * The returned alignment is owned by @layout and should * not be modified or freed. * * Returns: (transfer none): the alignment of @layout. * * Since: 1.0 **/ GtkAdjustment * adg_gtk_layout_get_hadjustment(AdgGtkLayout *layout) { AdgGtkLayoutPrivate *data; g_return_val_if_fail(ADG_GTK_IS_LAYOUT(layout), NULL); data = adg_gtk_layout_get_instance_private(layout); return data->hadjustment; } /** * adg_gtk_layout_set_vadjustment: * @layout: an #AdgGtkLayout * @vadjustment: the new adjustment * * Sets the new vertical adjustment for @layout to @vadjustment. * The old adjustment, if present, is unreferenced. * * This is basically the same as manually setting the * #GtkScrollable:vadjustment property with g_object_set(). * * Since: 1.0 **/ void adg_gtk_layout_set_vadjustment(AdgGtkLayout *layout, GtkAdjustment *vadjustment) { g_return_if_fail(ADG_GTK_IS_LAYOUT(layout)); g_object_set(layout, "vadjustment", vadjustment, NULL); } /** * adg_gtk_layout_get_vadjustment: * @layout: an #AdgGtkLayout * * Retrieves the current vertical adjustment of @layout. * * The returned alignment is owned by @layout and should * not be modified or freed. * * Returns: (transfer none): the alignment of @layout. * * Since: 1.0 **/ GtkAdjustment * adg_gtk_layout_get_vadjustment(AdgGtkLayout *layout) { AdgGtkLayoutPrivate *data; g_return_val_if_fail(ADG_GTK_IS_LAYOUT(layout), NULL); data = adg_gtk_layout_get_instance_private(layout); return data->vadjustment; } diff --git a/src/adg/adg-gtk-layout.h b/src/adg/adg-gtk-layout.h index 3398bafd..e49bae8f 100644 --- a/src/adg/adg-gtk-layout.h +++ b/src/adg/adg-gtk-layout.h @@ -1,68 +1,68 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_GTK_LAYOUT_H__ #define __ADG_GTK_LAYOUT_H__ G_BEGIN_DECLS #define ADG_GTK_TYPE_LAYOUT (adg_gtk_layout_get_type()) #define ADG_GTK_LAYOUT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_GTK_TYPE_LAYOUT, AdgGtkLayout)) #define ADG_GTK_LAYOUT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_GTK_TYPE_LAYOUT, AdgGtkLayoutClass)) #define ADG_GTK_IS_LAYOUT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_GTK_TYPE_LAYOUT)) #define ADG_GTK_IS_LAYOUT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_GTK_TYPE_LAYOUT)) #define ADG_GTK_LAYOUT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_GTK_TYPE_LAYOUT, AdgGtkLayoutClass)) typedef struct _AdgGtkLayout AdgGtkLayout; typedef struct _AdgGtkLayoutClass AdgGtkLayoutClass; struct _AdgGtkLayout { /*< private >*/ AdgGtkArea parent; }; struct _AdgGtkLayoutClass { /*< private >*/ AdgGtkAreaClass parent_class; }; GType adg_gtk_layout_get_type (void); GtkWidget * adg_gtk_layout_new (void); GtkWidget * adg_gtk_layout_new_with_canvas (AdgCanvas *canvas); void adg_gtk_layout_set_hadjustment (AdgGtkLayout *layout, GtkAdjustment *hadjustment); GtkAdjustment * adg_gtk_layout_get_hadjustment (AdgGtkLayout *layout); void adg_gtk_layout_set_vadjustment (AdgGtkLayout *layout, GtkAdjustment *vadjustment); GtkAdjustment * adg_gtk_layout_get_vadjustment (AdgGtkLayout *layout); G_END_DECLS #endif /* __ADG_GTK_LAYOUT_H__ */ diff --git a/src/adg/adg-gtk-utils.c b/src/adg/adg-gtk-utils.c index 66b33c67..02dfcf76 100644 --- a/src/adg/adg-gtk-utils.c +++ b/src/adg/adg-gtk-utils.c @@ -1,208 +1,208 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-gtk-utils * @Section_Id:gtk-utilities * @title: GTK+ utilities * @short_description: Assorted macros and backward compatible fallbacks * * Collection of macros and functions that do not fit inside any other topic. * * Since: 1.0 **/ /** * ADG_GTK_MODIFIERS: * * A GDK mask of the key/mouse modifiers accepted by the GTK+ widgets * of the ADG library. This means the state of the specified modifiers * is always checked: for example %GDK_CONTROL_MASK and %GDK_SHIFT_MASK * are included, hence keeping CTRL * and SHIFT pressed is different from keeping * only SHIFT pressed. %GDK_LOCK_MASK instead is not * considered, so having it enabled or disabled does not make any * difference while monitoring the status SHIFT * or CTRL. * * Since: 1.0 **/ #include "adg-internal.h" #include #include "adg-gtk-utils.h" #if GTK_CHECK_VERSION(2, 14, 0) #else /** * gtk_widget_get_window: * @widget: a #GtkWidget * * Returns the widget's window if it is realized * or NULL otherwise. * * This is a fallback API for GTK+ prior to 2.14. * * Return value: @widget's window. * * Since: 1.0 **/ GdkWindow * gtk_widget_get_window(GtkWidget *widget) { g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); return widget->window; } #endif #if GTK_CHECK_VERSION(2, 20, 0) #else /** * gtk_widget_get_realized: * @widget: a #GtkWidget * * Determines whether @widget is realized. * * Return value: %TRUE if @widget is realized, %FALSE otherwise * * Since: 1.0 **/ gboolean gtk_widget_get_realized(GtkWidget *widget) { g_return_val_if_fail(GTK_IS_WIDGET(widget), FALSE); return GTK_WIDGET_REALIZED(widget); } #endif /** * adg_gtk_window_hide_here: * @window: a #GtkWindow * * A convenient function that hides @window and tries to store the * current position. Any subsequent call to gtk_widget_show() will * hopefully reopen the window at the same position. * * It can be used instead of gtk_widget_hide() or by connecting it * to a #GtkDialog::response signal, for instance: * * * g_signal_connect(dialog, "response", * G_CALLBACK(adg_gtk_window_hide_here), NULL); * * * Since: 1.0 **/ void adg_gtk_window_hide_here(GtkWindow *window) { gint x, y; g_return_if_fail(GTK_IS_WINDOW(window)); gtk_window_get_position(window, &x, &y); gtk_widget_hide((GtkWidget *) window); gtk_window_set_position(window, GTK_WIN_POS_NONE); gtk_window_move(window, x, y); } /** * adg_gtk_toggle_button_sensitivize: * @toggle_button: a #GtkToggleButton * @widget: the #GtkWidget * * Assigns the value of the #GtkToggleButton:active property of * @toggle_button to the #GtkWidget:sensitive property of @widget. * Useful to set or reset the sensitiveness of @widget depending * of the state of a check button, for example: * * * g_signal_connect(toggle_button, "toggled", * G_CALLBACK(adg_gtk_toggle_button_sensitivize), widget1); * g_signal_connect(toggle_button, "toggled", * G_CALLBACK(adg_gtk_toggle_button_sensitivize), widget2); * g_signal_connect(toggle_button, "toggled", * G_CALLBACK(adg_gtk_toggle_button_sensitivize), widget3); * * * Since: 1.0 **/ void adg_gtk_toggle_button_sensitivize(GtkToggleButton *toggle_button, GtkWidget *widget) { gboolean is_active; g_return_if_fail(GTK_IS_TOGGLE_BUTTON(toggle_button)); g_return_if_fail(GTK_IS_WIDGET(widget)); is_active = gtk_toggle_button_get_active(toggle_button); gtk_widget_set_sensitive(widget, is_active); } /** * adg_gtk_use_default_icons: * @dir: the directory where the icons should be installed * * Sets the default icon list of every #GtkWindow to a hand-coded * list of ADG icons. Check gtk gtk_window_set_default_icon_list() * for further details. **/ void adg_gtk_use_default_icons(const gchar *dir) { const gchar **p_file; const gchar *files[] = { "adg-16.png", "adg-32.png", "adg-48.png", "adg-64.png", "adg-128.png", NULL }; GList *list; gchar *path; GdkPixbuf *icon; list = NULL; for (p_file = files; *p_file != NULL; ++p_file) { path = adg_find_file(*p_file, dir, NULL); if (path == NULL) continue; icon = gdk_pixbuf_new_from_file(path, NULL); g_free(path); if (icon != NULL) list = g_list_append(list, icon); } if (list != NULL) { gtk_window_set_default_icon_list(list); g_list_free(list); } } diff --git a/src/adg/adg-gtk-utils.h b/src/adg/adg-gtk-utils.h index 646dac45..0724adcc 100644 --- a/src/adg/adg-gtk-utils.h +++ b/src/adg/adg-gtk-utils.h @@ -1,53 +1,53 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_GTK_UTILS_H__ #define __ADG_GTK_UTILS_H__ #define ADG_GTK_MODIFIERS (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK) G_BEGIN_DECLS #if GTK_CHECK_VERSION(2, 14, 0) #else GdkWindow * gtk_widget_get_window (GtkWidget *widget); #endif #if GTK_CHECK_VERSION(2, 20, 0) #else gboolean gtk_widget_get_realized (GtkWidget *widget); #endif void adg_gtk_window_hide_here (GtkWindow *window); void adg_gtk_toggle_button_sensitivize (GtkToggleButton*toggle_button, GtkWidget *widget); void adg_gtk_use_default_icons (const gchar *dir); G_END_DECLS #endif /* __ADG_GTK_UTILS_H__ */ diff --git a/src/adg/adg-hatch-private.h b/src/adg/adg-hatch-private.h index 69ad2e2b..84576b48 100644 --- a/src/adg/adg-hatch-private.h +++ b/src/adg/adg-hatch-private.h @@ -1,36 +1,36 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_HATCH_PRIVATE_H__ #define __ADG_HATCH_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgHatchPrivate AdgHatchPrivate; struct _AdgHatchPrivate { AdgDress fill_dress; }; G_END_DECLS #endif /* __ADG_HATCH_PRIVATE_H__ */ diff --git a/src/adg/adg-hatch.c b/src/adg/adg-hatch.c index 690edfc4..9bb15b6a 100644 --- a/src/adg/adg-hatch.c +++ b/src/adg/adg-hatch.c @@ -1,224 +1,224 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-hatch * @short_description: A hatched region * * The #AdgHatch object is used to fill a closed #AdgTrail model * with some sort of pattern. * * Since: 1.0 **/ /** * AdgHatch: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-model.h" #include "adg-trail.h" #include "adg-stroke.h" #include "adg-style.h" #include "adg-fill-style.h" #include "adg-dress.h" #include "adg-param-dress.h" #include "adg-hatch.h" #include "adg-hatch-private.h" G_DEFINE_TYPE_WITH_PRIVATE(AdgHatch, adg_hatch, ADG_TYPE_STROKE) enum { PROP_0, PROP_FILL_DRESS }; static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void _adg_render (AdgEntity *entity, cairo_t *cr); static void adg_hatch_class_init(AdgHatchClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; entity_class->render = _adg_render; param = adg_param_spec_dress("fill-dress", P_("Fill Dress"), P_("The dress to use for filling this entity"), ADG_DRESS_FILL_HATCH, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FILL_DRESS, param); } static void adg_hatch_init(AdgHatch *hatch) { AdgHatchPrivate *data = adg_hatch_get_instance_private(hatch); data->fill_dress = ADG_DRESS_FILL_HATCH; } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgHatchPrivate *data = adg_hatch_get_instance_private((AdgHatch *) object); switch (prop_id) { case PROP_FILL_DRESS: g_value_set_enum(value, data->fill_dress); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgHatch *hatch = (AdgHatch *) object; AdgHatchPrivate *data = adg_hatch_get_instance_private(hatch); switch (prop_id) { case PROP_FILL_DRESS: data->fill_dress = g_value_get_enum(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_hatch_new: * @trail: the #AdgTrail to hatch * * Creates a new hatch entity. @trail can be NULL, * in which case an empty hatch is created. * * Returns: the newly created hatch entity * * Since: 1.0 **/ AdgHatch * adg_hatch_new(AdgTrail *trail) { return g_object_new(ADG_TYPE_HATCH, "trail", trail, NULL); } /** * adg_hatch_set_fill_dress: * @hatch: an #AdgHatch * @dress: the new #AdgDress to use * * Sets a new line dress for rendering @hatch. The new dress * must be related to the original dress for this property: * you cannot set a dress used for line styles to a dress * managing fonts. * * The check is done by calling adg_dress_are_related() with * @dress and the previous dress as arguments. Check out its * documentation for details on what is a related dress. * * Since: 1.0 **/ void adg_hatch_set_fill_dress(AdgHatch *hatch, AdgDress dress) { g_return_if_fail(ADG_IS_HATCH(hatch)); g_object_set(hatch, "fill-dress", dress, NULL); } /** * adg_hatch_get_fill_dress: * @hatch: an #AdgHatch * * Gets the line dress to be used in rendering @hatch. * * Returns: (transfer none): the current line dress. * * Since: 1.0 **/ AdgDress adg_hatch_get_fill_dress(AdgHatch *hatch) { AdgHatchPrivate *data; g_return_val_if_fail(ADG_IS_HATCH(hatch), ADG_DRESS_UNDEFINED); data = adg_hatch_get_instance_private(hatch); return data->fill_dress; } static void _adg_render(AdgEntity *entity, cairo_t *cr) { AdgHatch *hatch = (AdgHatch *) entity; AdgStroke *stroke = (AdgStroke *) entity; AdgTrail *trail = adg_stroke_get_trail(stroke); const cairo_path_t *cairo_path = adg_trail_get_cairo_path(trail); if (cairo_path != NULL) { AdgHatchPrivate *data = adg_hatch_get_instance_private(hatch); AdgFillStyle *fill_style = (AdgFillStyle *) adg_entity_style(entity, data->fill_dress); adg_fill_style_set_extents(fill_style, adg_entity_get_extents(entity)); cairo_save(cr); cairo_transform(cr, adg_entity_get_global_matrix(entity)); cairo_transform(cr, adg_entity_get_local_matrix(entity)); cairo_append_path(cr, cairo_path); cairo_restore(cr); adg_style_apply((AdgStyle *) fill_style, entity, cr); cairo_fill(cr); } } diff --git a/src/adg/adg-hatch.h b/src/adg/adg-hatch.h index a432101f..bef17ba9 100644 --- a/src/adg/adg-hatch.h +++ b/src/adg/adg-hatch.h @@ -1,64 +1,64 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_HATCH_H__ #define __ADG_HATCH_H__ G_BEGIN_DECLS #define ADG_TYPE_HATCH (adg_hatch_get_type()) #define ADG_HATCH(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_HATCH, AdgHatch)) #define ADG_HATCH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_HATCH, AdgHatchClass)) #define ADG_IS_HATCH(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_HATCH)) #define ADG_IS_HATCH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_HATCH)) #define ADG_HATCH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_HATCH, AdgHatchClass)) typedef struct _AdgHatch AdgHatch; typedef struct _AdgHatchClass AdgHatchClass; struct _AdgHatch { /*< private >*/ AdgStroke parent; }; struct _AdgHatchClass { /*< private >*/ AdgStrokeClass parent_class; }; GType adg_hatch_get_type (void); AdgHatch * adg_hatch_new (AdgTrail *trail); void adg_hatch_set_fill_dress (AdgHatch *hatch, AdgDress dress); AdgDress adg_hatch_get_fill_dress (AdgHatch *hatch); G_END_DECLS #endif /* __ADG_HATCH_H__ */ diff --git a/src/adg/adg-internal.h b/src/adg/adg-internal.h index 982df042..a40a399d 100644 --- a/src/adg/adg-internal.h +++ b/src/adg/adg-internal.h @@ -1,96 +1,96 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /* * This header is included by every .c files of the library to * enable the inclusion of the internal headers and initialize * some common stuff (above all, localization). */ #ifndef __ADG_INTERNAL_H__ #define __ADG_INTERNAL_H__ /* The following define enables the inclusion of internal headers */ #undef __ADG_H__ #define __ADG_H__ #include #define G_LOG_DOMAIN PACKAGE #ifdef CAIRO_GOBJECT_ENABLED #include #endif #include /* The following headers are autogenerated, so they could be hosted * in a different directory on VPATH builds (in other words, angle * brackets are used instead of quotes) */ #include #include /* ADG headers basically included by every source file */ #include "adg-forward-declarations.h" #include "adg-enums.h" #include "adg-utils.h" #include "adg-matrix.h" #include "adg-entity.h" #ifdef ENABLE_NLS #include #ifndef GETTEXT_PACKAGE #error You must define GETTEXT_PACKAGE before including adg-internal.h. Did you forget to include config.h? #endif #ifdef gettext_noop #define N_(String) gettext_noop(String) #else #define N_(String) (String) #endif #define _(String) _adg_dgettext(GETTEXT_PACKAGE, String) #define P_(String) _adg_dpgettext(GETTEXT_PACKAGE, "Property\004" String, 9) #define Q_(String) _adg_dpgettext(GETTEXT_PACKAGE, String, 0) #define C_(Context,String) _adg_dpgettext(GETTEXT_PACKAGE, Context "\004" String, strlen(Context) + 1) #define NC_(Context,String) N_(String) #else /* !ENABLE_NLS */ #define _(String) (String) #define P_(String) (String) #define Q_(String) (String) #define N_(String) (String) #define C_(Context,String) (String) #define NC_(Context, String) (String) #endif const gchar * _adg_dgettext (const gchar *domain, const gchar *msgid) G_GNUC_FORMAT(2); const gchar * _adg_dpgettext (const gchar *domain, const gchar *msgctxtid, gsize msgidoffset) G_GNUC_FORMAT(2); #endif /* __ADG_INTERNAL_H__ */ diff --git a/src/adg/adg-ldim-private.h b/src/adg/adg-ldim-private.h index d60abcd7..316da5e4 100644 --- a/src/adg/adg-ldim-private.h +++ b/src/adg/adg-ldim-private.h @@ -1,87 +1,87 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_LDIM_PRIVATE_H__ #define __ADG_LDIM_PRIVATE_H__ G_BEGIN_DECLS /* * The cairo.data array is structured in the following way: * * [0] = MOVE_TO * [1] = baseline start * [2] = LINE_TO * [3] = baseline end * [4] = MOVE_TO * [5] = first outside line start * [6] = LINE_TO * [7] = first outside line end * [8] = MOVE_TO * [9] = second outside line end * [10] = LINE_TO * [11] = second outside line start * [12] = MOVE_TO * [13] = first extension line start * [14] = LINE_TO * [15] = first extension line end * [16] = MOVE_TO * [17] = second extension line start * [18] = LINE_TO * [19] = second extension line end */ typedef struct _AdgLDimPrivate AdgLDimPrivate; struct _AdgLDimPrivate { double direction; gboolean has_extension1; gboolean has_extension2; AdgTrail *trail; AdgMarker *marker1; AdgMarker *marker2; struct { gboolean is_arranged; CpmlPair base1, base2; gdouble distance; } geometry; struct { gboolean is_arranged; CpmlPair from, base, to; } shift; struct { cairo_matrix_t global_map; } quote; struct { cairo_path_t path; cairo_path_data_t data[20]; } cairo; }; G_END_DECLS #endif /* __ADG_LDIM_PRIVATE_H__ */ diff --git a/src/adg/adg-ldim.c b/src/adg/adg-ldim.c index 4b69de58..5d064065 100644 --- a/src/adg/adg-ldim.c +++ b/src/adg/adg-ldim.c @@ -1,1088 +1,1088 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-ldim * @short_description: Linear dimensions * * The #AdgLDim entity represents a linear dimension. * * Since: 1.0 **/ /** * AdgLDim: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-container.h" #include "adg-alignment.h" #include "adg-model.h" #include "adg-point.h" #include "adg-trail.h" #include "adg-marker.h" #include "adg-style.h" #include "adg-dim-style.h" #include "adg-textual.h" #include "adg-toy-text.h" #include "adg-dim.h" #include "adg-dim-private.h" #include #include "adg-ldim.h" #include "adg-ldim-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_ldim_parent_class) #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_ldim_parent_class) G_DEFINE_TYPE_WITH_PRIVATE(AdgLDim, adg_ldim, ADG_TYPE_DIM) enum { PROP_0, PROP_DIRECTION, PROP_HAS_EXTENSION1, PROP_HAS_EXTENSION2 }; static void _adg_dispose (GObject *object); static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void _adg_global_changed (AdgEntity *entity); static void _adg_local_changed (AdgEntity *entity); static void _adg_invalidate (AdgEntity *entity); static void _adg_arrange (AdgEntity *entity); static void _adg_render (AdgEntity *entity, cairo_t *cr); static gchar * _adg_default_value (AdgDim *dim); static gboolean _adg_compute_geometry (AdgDim *dim); static void _adg_update_shift (AdgLDim *ldim); static void _adg_update_entities (AdgLDim *ldim); static void _adg_choose_flags (AdgLDim *ldim, gboolean *to_outside, gboolean *to_detach); static void _adg_update_quote (AdgLDim *ldim, CpmlPair *base1, CpmlPair *base2, CpmlPair *pos, gboolean detach, gboolean outside, gdouble gap); static void _adg_update_extents (AdgLDim *ldim); static void _adg_unset_trail (AdgLDim *ldim); static void _adg_dispose_trail (AdgLDim *ldim); static void _adg_dispose_markers (AdgLDim *ldim); static cairo_path_t * _adg_trail_callback (AdgTrail *trail, gpointer user_data); static void adg_ldim_class_init(AdgLDimClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; AdgDimClass *dim_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; dim_class = (AdgDimClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; entity_class->global_changed = _adg_global_changed; entity_class->local_changed = _adg_local_changed; entity_class->invalidate = _adg_invalidate; entity_class->arrange = _adg_arrange; entity_class->render = _adg_render; dim_class->default_value = _adg_default_value; dim_class->compute_geometry = _adg_compute_geometry; param = g_param_spec_double("direction", P_("Direction"), P_("The inclination angle of the extension lines"), -G_PI, G_PI, ADG_DIR_RIGHT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); g_object_class_install_property(gobject_class, PROP_DIRECTION, param); param = g_param_spec_boolean("has-extension1", P_("Has First Extension Line flag"), P_("Show (TRUE) or hide (FALSE) the first extension line"), TRUE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_HAS_EXTENSION1, param); param = g_param_spec_boolean("has-extension2", P_("Has Second Extension Line flag"), P_("Show (TRUE) or hide (FALSE) the second extension line"), TRUE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_HAS_EXTENSION2, param); } static void adg_ldim_init(AdgLDim *ldim) { AdgLDimPrivate *data = adg_ldim_get_instance_private(ldim); cairo_path_data_t move_to, line_to; move_to.header.type = CPML_MOVE; move_to.header.length = 2; line_to.header.type = CPML_LINE; line_to.header.length = 2; data->direction = ADG_DIR_RIGHT; data->has_extension1 = TRUE; data->has_extension2 = TRUE; data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA; data->cairo.path.data = data->cairo.data; data->cairo.path.num_data = G_N_ELEMENTS(data->cairo.data); data->cairo.path.data[0] = move_to; data->cairo.path.data[2] = line_to; data->cairo.path.data[4] = move_to; data->cairo.path.data[6] = line_to; data->cairo.path.data[8] = move_to; data->cairo.path.data[10] = line_to; data->cairo.path.data[12] = move_to; data->cairo.path.data[14] = line_to; data->cairo.path.data[16] = move_to; data->cairo.path.data[18] = line_to; data->trail = NULL; data->marker1 = NULL; data->marker2 = NULL; } static void _adg_dispose(GObject *object) { AdgLDim *ldim = (AdgLDim *) object; _adg_dispose_trail(ldim); _adg_dispose_markers(ldim); if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgLDimPrivate *data = adg_ldim_get_instance_private((AdgLDim *) object); switch (prop_id) { case PROP_DIRECTION: g_value_set_double(value, data->direction); break; case PROP_HAS_EXTENSION1: g_value_set_boolean(value, data->has_extension1); break; case PROP_HAS_EXTENSION2: g_value_set_boolean(value, data->has_extension2); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgLDimPrivate *data = adg_ldim_get_instance_private((AdgLDim *) object); switch (prop_id) { case PROP_DIRECTION: data->direction = cpml_angle(g_value_get_double(value)); break; case PROP_HAS_EXTENSION1: data->has_extension1 = g_value_get_boolean(value); break; case PROP_HAS_EXTENSION2: data->has_extension2 = g_value_get_boolean(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_ldim_new: * * Creates a new - undefined - linear dimension. You must, at least, * define the start of the dimension in #AdgDim:ref1, the end in * #AdgDim:ref2 and the position of the quote in #AdgDim:pos using * any valid #AdgDim method. The director of the dimension (that is, * if it is horizontal, vertical or oblique at a specific angle) * should be specified with adg_ldim_set_direction(). * * Returns: the newly created linear dimension entity * * Since: 1.0 **/ AdgLDim * adg_ldim_new(void) { return g_object_new(ADG_TYPE_LDIM, NULL); } /** * adg_ldim_new_full: * @ref1: (allow-none): the first reference point * @ref2: (allow-none): the second reference point * @pos: (allow-none): the position reference * @direction: angle where to extend the dimension * * Creates a new linear dimension, specifing all the needed properties in * one shot. * * Returns: the newly created linear dimension entity * * Since: 1.0 **/ AdgLDim * adg_ldim_new_full(const CpmlPair *ref1, const CpmlPair *ref2, const CpmlPair *pos, gdouble direction) { AdgLDim *ldim; AdgDim *dim; ldim = adg_ldim_new(); dim = (AdgDim *) ldim; if (ref1 != NULL) adg_dim_set_ref1_from_pair(dim, ref1); if (ref2 != NULL) adg_dim_set_ref2_from_pair(dim, ref2); if (pos != NULL) adg_dim_set_pos_from_pair(dim, pos); adg_ldim_set_direction(ldim, direction); return ldim; } /** * adg_ldim_new_full_explicit: * @ref1_x: the x coordinate of the first reference point * @ref1_y: the y coordinate of the first reference point * @ref2_x: the x coordinate of the second reference point * @ref2_y: the y coordinate of the second reference point * @pos_x: the x coordinate of the position reference * @pos_y: the y coordinate of the position reference * @direction: angle where to extend the dimension * * Wrappes adg_ldim_new_full() with explicit values. * * Returns: the newly created linear dimension entity * * Since: 1.0 **/ AdgLDim * adg_ldim_new_full_explicit(gdouble ref1_x, gdouble ref1_y, gdouble ref2_x, gdouble ref2_y, gdouble pos_x, gdouble pos_y, gdouble direction) { CpmlPair ref1; CpmlPair ref2; CpmlPair pos; ref1.x = ref1_x; ref1.y = ref1_y; ref2.x = ref2_x; ref2.y = ref2_y; pos.x = pos_x; pos.y = pos_y; return adg_ldim_new_full(&ref1, &ref2, &pos, direction); } /** * adg_ldim_new_full_from_model: * @model: (transfer none): the model from which the named pairs are taken * @ref1: (allow-none): the first reference point * @ref2: (allow-none): the second reference point * @pos: (allow-none): the position reference * @direction: angle where to extend the dimension * * Creates a new linear dimension, specifing all the needed properties in * one shot and using named pairs from @model. * * Returns: the newly created linear dimension entity * * Since: 1.0 **/ AdgLDim * adg_ldim_new_full_from_model(AdgModel *model, const gchar *ref1, const gchar *ref2, const gchar *pos, gdouble direction) { AdgLDim *ldim; AdgDim *dim; g_return_val_if_fail(model != NULL, NULL); ldim = adg_ldim_new(); dim = (AdgDim *) ldim; if (ref1 != NULL) adg_dim_set_ref1_from_model(dim, model, ref1); if (ref2 != NULL) adg_dim_set_ref2_from_model(dim, model, ref2); if (pos != NULL) adg_dim_set_pos_from_model(dim, model, pos); adg_ldim_set_direction(ldim, direction); return (AdgLDim *) dim; } /** * adg_ldim_set_direction: * @ldim: an #AdgLDim entity * @direction: an angle value, in radians * * Sets the direction angle where to extend @ldim. * @direction is normalized by cpml_angle() before being used. * * Since: 1.0 **/ void adg_ldim_set_direction(AdgLDim *ldim, gdouble direction) { g_return_if_fail(ADG_IS_LDIM(ldim)); g_object_set(ldim, "direction", direction, NULL); } /** * adg_ldim_get_direction: * @ldim: an #AdgLDim entity * * Gets the direction where @ldim will extend. * * Returns: the direction angle in radians * * Since: 1.0 **/ gdouble adg_ldim_get_direction(AdgLDim *ldim) { AdgLDimPrivate *data; g_return_val_if_fail(ADG_IS_LDIM(ldim), 0); data = adg_ldim_get_instance_private(ldim); return data->direction; } /** * adg_ldim_switch_extension1: * @ldim: an #AdgLDim entity * @new_state: the new state * * Shows (if @new_state is TRUE) or hide (if * @new_state is FALSE) the first extension * line of @ldim. * * Since: 1.0 **/ void adg_ldim_switch_extension1(AdgLDim *ldim, gboolean new_state) { g_return_if_fail(ADG_IS_LDIM(ldim)); g_return_if_fail(adg_is_boolean_value(new_state)); g_object_set(ldim, "has-extension1", new_state, NULL); } /** * adg_ldim_has_extension1: * @ldim: an #AdgLDim entity * * Checks if @ldim should render also the first extension line. * * Returns: TRUE on first extension line presents, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_ldim_has_extension1(AdgLDim *ldim) { AdgLDimPrivate *data; g_return_val_if_fail(ADG_IS_LDIM(ldim), FALSE); data = adg_ldim_get_instance_private(ldim); return data->has_extension1; } /** * adg_ldim_switch_extension2: * @ldim: an #AdgLDim entity * @new_state: the new new_state * * Shows (if @new_state is TRUE) or hide (if * @new_state is FALSE) the second extension * line of @ldim. * * Since: 1.0 **/ void adg_ldim_switch_extension2(AdgLDim *ldim, gboolean new_state) { g_return_if_fail(ADG_IS_LDIM(ldim)); g_return_if_fail(adg_is_boolean_value(new_state)); g_object_set(ldim, "has-extension2", new_state, NULL); } /** * adg_ldim_has_extension2: * @ldim: an #AdgLDim entity * * Checks if @ldim should render also the second extension line. * * Returns: TRUE on first extension line presents, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_ldim_has_extension2(AdgLDim *ldim) { AdgLDimPrivate *data; g_return_val_if_fail(ADG_IS_LDIM(ldim), FALSE); data = adg_ldim_get_instance_private(ldim); return data->has_extension2; } static void _adg_global_changed(AdgEntity *entity) { AdgLDimPrivate *data = adg_ldim_get_instance_private((AdgLDim *) entity); _adg_unset_trail((AdgLDim *) entity); if (_ADG_OLD_ENTITY_CLASS->global_changed) _ADG_OLD_ENTITY_CLASS->global_changed(entity); if (data->marker1) adg_entity_global_changed((AdgEntity *) data->marker1); if (data->marker2) adg_entity_global_changed((AdgEntity *) data->marker2); } static void _adg_local_changed(AdgEntity *entity) { _adg_unset_trail((AdgLDim *) entity); if (_ADG_OLD_ENTITY_CLASS->local_changed) _ADG_OLD_ENTITY_CLASS->local_changed(entity); } static void _adg_invalidate(AdgEntity *entity) { AdgLDim *ldim = (AdgLDim *) entity; _adg_dispose_trail(ldim); _adg_dispose_markers(ldim); _adg_unset_trail(ldim); if (_ADG_OLD_ENTITY_CLASS->invalidate) _ADG_OLD_ENTITY_CLASS->invalidate(entity); } static void _adg_arrange(AdgEntity *entity) { AdgLDim *ldim; AdgDim *dim; AdgLDimPrivate *data; AdgAlignment *quote; AdgDimStyle *dim_style; gboolean outside, detach; const cairo_matrix_t *local; CpmlPair ref1, ref2, pos, base1, base2; CpmlPair pair; if (_ADG_OLD_ENTITY_CLASS->arrange) _ADG_OLD_ENTITY_CLASS->arrange(entity); dim = (AdgDim *) entity; if (! adg_dim_compute_geometry(dim)) return; ldim = (AdgLDim *) entity; data = adg_ldim_get_instance_private(ldim); quote = adg_dim_get_quote(dim); _adg_update_entities(ldim); /* Check for cached result */ if (data->cairo.path.status == CAIRO_STATUS_SUCCESS) { adg_entity_set_global_map((AdgEntity *) quote, &data->quote.global_map); return; } _adg_choose_flags(ldim, &outside, &detach); dim_style = adg_dim_get_dim_style(dim); local = adg_entity_get_local_matrix(entity); cpml_pair_copy(&ref1, (CpmlPair *) adg_dim_get_ref1(dim)); cpml_pair_copy(&ref2, (CpmlPair *) adg_dim_get_ref2(dim)); cpml_pair_copy(&pos, (CpmlPair *) adg_dim_get_pos(dim)); cpml_pair_copy(&base1, &data->geometry.base1); cpml_pair_copy(&base2, &data->geometry.base2); cpml_pair_transform(&ref1, local); cpml_pair_transform(&ref2, local); cpml_pair_transform(&pos, local); cpml_pair_transform(&base1, local); base1.x += data->shift.base.x; base1.y += data->shift.base.y; cpml_pair_transform(&base2, local); base2.x += data->shift.base.x; base2.y += data->shift.base.y; pair.x = ref1.x + data->shift.from.x; pair.y = ref1.y + data->shift.from.y; cpml_pair_to_cairo(&pair, &data->cairo.data[13]); cpml_pair_to_cairo(&base1, &data->cairo.data[1]); pair.x = base1.x + data->shift.to.x; pair.y = base1.y + data->shift.to.y; cpml_pair_to_cairo(&pair, &data->cairo.data[15]); pair.x = ref2.x + data->shift.from.x; pair.y = ref2.y + data->shift.from.y; cpml_pair_to_cairo(&pair, &data->cairo.data[17]); cpml_pair_to_cairo(&base2, &data->cairo.data[3]); pair.x = base2.x + data->shift.to.x; pair.y = base2.y + data->shift.to.y; cpml_pair_to_cairo(&pair, &data->cairo.data[19]); /* Calculate the outside segments */ if (outside) { gdouble beyond; CpmlVector vector; beyond = adg_dim_style_get_beyond(dim_style); cpml_pair_from_cairo(&pair, &data->cairo.data[1]); cpml_pair_from_cairo(&vector, &data->cairo.data[3]); vector.x -= pair.x; vector.y -= pair.y; cpml_vector_set_length(&vector, beyond); cpml_pair_from_cairo(&pair, &data->cairo.data[1]); cpml_pair_to_cairo(&pair, &data->cairo.data[5]); pair.x -= vector.x; pair.y -= vector.y; cpml_pair_to_cairo(&pair, &data->cairo.data[7]); cpml_pair_from_cairo(&pair, &data->cairo.data[3]); cpml_pair_to_cairo(&pair, &data->cairo.data[11]); pair.x += vector.x; pair.y += vector.y; cpml_pair_to_cairo(&pair, &data->cairo.data[9]); data->cairo.data[2].header.length = 2; } else { data->cairo.data[2].header.length = 10; } data->cairo.data[10].header.length = 2; _adg_update_quote(ldim, &base1, &base2, &pos, detach, outside, adg_dim_style_get_quote_shift(dim_style)->x); /* Play with header lengths to show or hide the extension lines */ if (data->has_extension1) { data->cairo.data[14].header.length = data->has_extension2 ? 2 : 6; } else { gint n = ! outside && ! detach ? 2 : 10; data->cairo.data[14].header.length = 2; data->cairo.data[n].header.length += 4; if (!data->has_extension2) data->cairo.data[n].header.length += 4; } data->cairo.path.status = CAIRO_STATUS_SUCCESS; /* Update the markers, if present */ if (data->trail != NULL) { if (data->marker1 != NULL) adg_marker_set_segment(data->marker1, data->trail, outside ? 2 : 1); if (data->marker2 != NULL) adg_marker_set_segment(data->marker2, data->trail, outside ? 3 : 1); } else { _adg_dispose_markers(ldim); } _adg_update_extents(ldim); } static void _adg_render(AdgEntity *entity, cairo_t *cr) { AdgLDim *ldim; AdgDim *dim; AdgLDimPrivate *data; AdgDimStyle *dim_style; AdgDress dress; const cairo_path_t *cairo_path; dim = (AdgDim *) entity; if (! adg_dim_compute_geometry(dim)) return; ldim = (AdgLDim *) entity; data = adg_ldim_get_instance_private(ldim); dim_style = adg_dim_get_dim_style(dim); adg_style_apply((AdgStyle *) dim_style, entity, cr); adg_entity_render((AdgEntity *) adg_dim_get_quote(dim), cr); if (data->marker1) adg_entity_render((AdgEntity *) data->marker1, cr); if (data->marker2) adg_entity_render((AdgEntity *) data->marker2, cr); cairo_transform(cr, adg_entity_get_global_matrix(entity)); dress = adg_dim_style_get_line_dress(dim_style); adg_entity_apply_dress(entity, dress, cr); cairo_path = adg_trail_get_cairo_path(data->trail); cairo_append_path(cr, cairo_path); cairo_stroke(cr); } static gchar * _adg_default_value(AdgDim *dim) { AdgLDim *ldim; AdgLDimPrivate *data; gdouble value; if (! adg_dim_compute_geometry(dim)) return g_strdup("undef"); ldim = (AdgLDim *) dim; data = adg_ldim_get_instance_private(ldim); value = data->geometry.distance; return adg_dim_get_text(dim, value); } static gboolean _adg_compute_geometry(AdgDim *dim) { AdgLDim *ldim; AdgLDimPrivate *data; AdgPoint *ref1_point, *ref2_point, *pos_point; const CpmlPair *ref1, *ref2, *pos; CpmlVector baseline, extension; gdouble d, k; ref1_point = adg_dim_get_ref1(dim); if (! adg_point_update(ref1_point)) { adg_dim_geometry_missing(dim, "ref1"); return FALSE; } ref2_point = adg_dim_get_ref2(dim); if (! adg_point_update(ref2_point)) { adg_dim_geometry_missing(dim, "ref2"); return FALSE; } pos_point = adg_dim_get_pos(dim); if (! adg_point_update(pos_point)) { adg_dim_geometry_missing(dim, "pos"); return FALSE; } ref1 = (CpmlPair *) ref1_point; ref2 = (CpmlPair *) ref2_point; if (cpml_pair_equal(ref1, ref2)) { adg_dim_geometry_coincident(dim, "ref1", "ref2", ref1); return FALSE; } pos = (CpmlPair *) pos_point; ldim = (AdgLDim *) dim; data = adg_ldim_get_instance_private(ldim); cpml_vector_from_angle(&extension, data->direction); cpml_pair_copy(&baseline, &extension); cpml_vector_normal(&baseline); d = extension.y * baseline.x - extension.x * baseline.y; g_return_val_if_fail(d != 0, FALSE); k = ((pos->y - ref1->y) * baseline.x - (pos->x - ref1->x) * baseline.y) / d; data->geometry.base1.x = ref1->x + k * extension.x; data->geometry.base1.y = ref1->y + k * extension.y; k = ((pos->y - ref2->y) * baseline.x - (pos->x - ref2->x) * baseline.y) / d; data->geometry.base2.x = ref2->x + k * extension.x; data->geometry.base2.y = ref2->y + k * extension.y; data->geometry.distance = cpml_pair_distance(&data->geometry.base1, &data->geometry.base2); _adg_update_shift(ldim); return TRUE; } static void _adg_update_shift(AdgLDim *ldim) { AdgLDimPrivate *data = adg_ldim_get_instance_private(ldim); AdgDimStyle *dim_style; gdouble from_offset, to_offset; gdouble baseline_spacing, level; CpmlVector vector; if (data->shift.is_arranged) return; dim_style = adg_dim_get_dim_style((AdgDim *) ldim); from_offset = adg_dim_style_get_from_offset(dim_style); to_offset = adg_dim_style_get_to_offset(dim_style); baseline_spacing = adg_dim_style_get_baseline_spacing(dim_style); level = adg_dim_get_level((AdgDim *) ldim); cpml_vector_from_angle(&vector, data->direction); cpml_vector_set_length(&vector, from_offset); cpml_pair_copy(&data->shift.from, &vector); cpml_vector_set_length(&vector, to_offset); cpml_pair_copy(&data->shift.to, &vector); cpml_vector_set_length(&vector, level * baseline_spacing); cpml_pair_copy(&data->shift.base, &vector); } static void _adg_update_entities(AdgLDim *ldim) { AdgEntity *entity = (AdgEntity *) ldim; AdgLDimPrivate *data = adg_ldim_get_instance_private(ldim); AdgDimStyle *dim_style = adg_dim_get_dim_style((AdgDim *) ldim); if (data->trail == NULL) data->trail = adg_trail_new(_adg_trail_callback, ldim); if (data->marker1 == NULL) { data->marker1 = adg_dim_style_marker1_new(dim_style); adg_entity_set_parent((AdgEntity *) data->marker1, entity); } if (data->marker2 == NULL) { data->marker2 = adg_dim_style_marker2_new(dim_style); adg_entity_set_parent((AdgEntity *) data->marker2, entity); } } static void _adg_choose_flags(AdgLDim *ldim, gboolean *to_outside, gboolean *to_detach) { AdgDim *dim; AdgThreeState outside, detached; AdgLDimPrivate *data; const cairo_matrix_t *local, *global; gdouble local_factor, global_factor; gdouble available_space, markers_space, quote_space; dim = (AdgDim *) ldim; outside = adg_dim_get_outside(dim); detached = adg_dim_get_detached(dim); *to_outside = outside == ADG_THREE_STATE_ON; *to_detach = detached == ADG_THREE_STATE_ON; /* On explicit flags, no further investigation is required */ if (outside != ADG_THREE_STATE_UNKNOWN && detached != ADG_THREE_STATE_UNKNOWN) return; data = adg_ldim_get_instance_private(ldim); local = adg_entity_get_local_matrix((AdgEntity *) ldim); global = adg_entity_get_global_matrix((AdgEntity *) ldim); local_factor = fabs(local->xx + local->yy) / 2; global_factor = fabs(global->xx + global->yy) / 2; available_space = data->geometry.distance * local_factor * global_factor; markers_space = 0; if (outside != ADG_THREE_STATE_ON) { if (data->marker1 != NULL) markers_space += adg_marker_get_size(data->marker1); if (data->marker2 != NULL) markers_space += adg_marker_get_size(data->marker2); markers_space *= global_factor; } if (detached == ADG_THREE_STATE_ON) { /* Leave at least 0.25 markers_space between the markers */ quote_space = markers_space * 0.25; } else { AdgEntity *quote = (AdgEntity *) adg_dim_get_quote(dim); adg_entity_arrange(quote); quote_space = adg_entity_get_extents(quote)->size.x; } if (outside == ADG_THREE_STATE_UNKNOWN && detached == ADG_THREE_STATE_UNKNOWN) { /* Both flags need to be choosed */ if (quote_space + markers_space < available_space) { *to_detach = FALSE; *to_outside = FALSE; } else if (quote_space < available_space) { *to_detach = FALSE; *to_outside = TRUE; } else { *to_detach = TRUE; *to_outside = markers_space * 1.25 > available_space; } } else if (outside == ADG_THREE_STATE_UNKNOWN) { /* Only the outside flag may be guessed */ *to_outside = quote_space + markers_space > available_space; } else { /* Only the detached flag may be guessed */ *to_detach = quote_space + markers_space > available_space; } } static void _adg_update_quote(AdgLDim *ldim, CpmlPair *base1, CpmlPair *base2, CpmlPair *pos, gboolean detach, gboolean outside, gdouble gap) { AdgLDimPrivate *data; AdgEntity *quote_entity; CpmlPair middle, factor, org; CpmlVector dir; cairo_matrix_t map; quote_entity = (AdgEntity *) adg_dim_get_quote((AdgDim *) ldim); if (quote_entity == NULL) return; data = adg_ldim_get_instance_private(ldim); middle.x = (base1->x + base2->x) / 2; middle.y = (base1->y + base2->y) / 2; dir.x = base2->x - base1->x; dir.y = base2->y - base1->y; cpml_vector_set_length(&dir, 1); if (detach) { /* Detached quote: position the quote at "pos" */ CpmlPair tmp_pair, quote_end; CpmlVector vector; gdouble distance1, distance2, quote_size; gboolean on_side1; cairo_path_data_t *to_extend; /* Set "org" to the properly converted "pos" coordinates */ org.x = pos->x + data->shift.base.x; org.y = pos->y + data->shift.base.y; /* Check in which side of the base line the quote must be rendered. * This is done by checking if the (pos..middle) vector is closer * to the base line vector or to its inverse, that is if they are * concordants or discordants. For this purpose, an algorithm * based on squared distances is used. */ tmp_pair.x = org.x - middle.x; tmp_pair.y = org.y - middle.y; vector.x = -dir.x; vector.y = -dir.y; distance2 = cpml_pair_squared_distance(&dir, &tmp_pair); distance1 = cpml_pair_squared_distance(&vector, &tmp_pair); on_side1 = distance2 > distance1; /* Properly align the quote, depending on its side */ factor.x = on_side1 ? 1 : 0; if (! on_side1) vector = dir; /* Add a gap between the quote and the extension line */ org.x += vector.x * gap; org.y += vector.y * gap; /* Calculate the end point (on the base line) of the quote */ quote_size = adg_entity_get_extents(quote_entity)->size.x; quote_end.x = org.x + vector.x * quote_size; quote_end.y = org.y + vector.y * quote_size; /* Extends the base line to include the "quote_end" pair, * so a detached quote is properly underlined */ if (outside) { to_extend = &data->cairo.data[on_side1 ? 7 : 9]; } else { to_extend = &data->cairo.data[9]; cpml_pair_to_cairo(&middle, &data->cairo.data[9]); cpml_pair_to_cairo(on_side1 ? base1 : base2, &data->cairo.data[11]); data->cairo.data[2].header.length = 6; } /* Extend the base line only if needed */ cpml_pair_from_cairo(&tmp_pair, to_extend); distance1 = cpml_pair_squared_distance("e_end, &middle); distance2 = cpml_pair_squared_distance(&tmp_pair, &middle); if (distance1 > distance2) cpml_pair_to_cairo("e_end, to_extend); } else { /* Center the quote in the middle of the base line */ factor.x = 0.5; cpml_pair_copy(&org, &middle); } factor.y = 0; adg_alignment_set_factor((AdgAlignment *) quote_entity, &factor); cairo_matrix_init(&map, dir.x, dir.y, -dir.y, dir.x, org.x, org.y); adg_entity_set_global_map(quote_entity, &map); adg_entity_arrange(quote_entity); adg_matrix_copy(&data->quote.global_map, &map); } static void _adg_update_extents(AdgLDim *ldim) { AdgLDimPrivate *data = adg_ldim_get_instance_private(ldim); AdgEntity *quote = (AdgEntity *) adg_dim_get_quote((AdgDim *) ldim); CpmlExtents new_extents; const CpmlExtents *extents; AdgEntity *marker_entity; new_extents.is_defined = FALSE; /* The quote is always present (otherwise something bad happened) */ extents = adg_entity_get_extents(quote); if (extents != NULL) cpml_extents_add(&new_extents, extents); if (data->trail != NULL) { extents = adg_trail_get_extents(data->trail); if (extents != NULL) cpml_extents_add(&new_extents, extents); } if (data->marker1 != NULL) { marker_entity = (AdgEntity *) data->marker1; adg_entity_local_changed(marker_entity); adg_entity_arrange(marker_entity); extents = adg_entity_get_extents(marker_entity); if (extents != NULL) cpml_extents_add(&new_extents, extents); } if (data->marker2 != NULL) { marker_entity = (AdgEntity *) data->marker2; adg_entity_local_changed(marker_entity); adg_entity_arrange(marker_entity); extents = adg_entity_get_extents(marker_entity); if (extents != NULL) cpml_extents_add(&new_extents, extents); } adg_entity_set_extents((AdgEntity *) ldim, &new_extents); } static void _adg_unset_trail(AdgLDim *ldim) { AdgLDimPrivate *data = adg_ldim_get_instance_private(ldim); if (data->trail != NULL) adg_model_clear((AdgModel *) data->trail); data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA; } static void _adg_dispose_trail(AdgLDim *ldim) { AdgLDimPrivate *data = adg_ldim_get_instance_private(ldim); if (data->trail != NULL) { g_object_unref(data->trail); data->trail = NULL; } } static void _adg_dispose_markers(AdgLDim *ldim) { AdgLDimPrivate *data = adg_ldim_get_instance_private(ldim); if (data->marker1 != NULL) { g_object_unref(data->marker1); data->marker1 = NULL; } if (data->marker2 != NULL) { g_object_unref(data->marker2); data->marker2 = NULL; } } static cairo_path_t * _adg_trail_callback(AdgTrail *trail, gpointer user_data) { AdgLDim *ldim = (AdgLDim *) user_data; AdgLDimPrivate *data = adg_ldim_get_instance_private(ldim); return &data->cairo.path; } diff --git a/src/adg/adg-ldim.h b/src/adg/adg-ldim.h index c720c977..22938102 100644 --- a/src/adg/adg-ldim.h +++ b/src/adg/adg-ldim.h @@ -1,87 +1,87 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_LDIM_H__ #define __ADG_LDIM_H__ G_BEGIN_DECLS #define ADG_TYPE_LDIM (adg_ldim_get_type()) #define ADG_LDIM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_LDIM, AdgLDim)) #define ADG_LDIM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_LDIM, AdgLDimClass)) #define ADG_IS_LDIM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_LDIM)) #define ADG_IS_LDIM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_LDIM)) #define ADG_LDIM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_LDIM, AdgLDimClass)) typedef struct _AdgLDim AdgLDim; typedef struct _AdgLDimClass AdgLDimClass; struct _AdgLDim { /*< private >*/ AdgDim parent; }; struct _AdgLDimClass { /*< private >*/ AdgDimClass parent_class; }; GType adg_ldim_get_type (void); AdgLDim * adg_ldim_new (void); AdgLDim * adg_ldim_new_full (const CpmlPair *ref1, const CpmlPair *ref2, const CpmlPair *pos, gdouble direction); AdgLDim * adg_ldim_new_full_explicit (gdouble ref1_x, gdouble ref1_y, gdouble ref2_x, gdouble ref2_y, gdouble pos_x, gdouble pos_y, gdouble direction); AdgLDim * adg_ldim_new_full_from_model (AdgModel *model, const gchar *ref1, const gchar *ref2, const gchar *pos, gdouble direction); void adg_ldim_set_direction (AdgLDim *ldim, gdouble direction); gdouble adg_ldim_get_direction (AdgLDim *ldim); void adg_ldim_switch_extension1 (AdgLDim *ldim, gboolean new_state); gboolean adg_ldim_has_extension1 (AdgLDim *ldim); void adg_ldim_switch_extension2 (AdgLDim *ldim, gboolean new_state); gboolean adg_ldim_has_extension2 (AdgLDim *ldim); G_END_DECLS #endif /* __ADG_LDIM_H__ */ diff --git a/src/adg/adg-line-style-private.h b/src/adg/adg-line-style-private.h index bb9a86b2..27518351 100644 --- a/src/adg/adg-line-style-private.h +++ b/src/adg/adg-line-style-private.h @@ -1,42 +1,42 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_LINE_STYLE_PRIVATE_H__ #define __ADG_LINE_STYLE_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgLineStylePrivate AdgLineStylePrivate; struct _AdgLineStylePrivate { AdgDress color_dress; double width; cairo_line_cap_t cap; cairo_line_join_t join; double miter_limit; cairo_antialias_t antialias; AdgDash *dash; }; G_END_DECLS #endif /* __ADG_LINE_STYLE_PRIVATE_H__ */ diff --git a/src/adg/adg-line-style.c b/src/adg/adg-line-style.c index ba3dd88b..053e0979 100644 --- a/src/adg/adg-line-style.c +++ b/src/adg/adg-line-style.c @@ -1,604 +1,604 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-line-style * @short_description: Line style related stuff * * Contains parameters on how to draw lines such as width, cap mode, join mode * and dash composition, if used. * * Since: 1.0 **/ /** * AdgLineStyle: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-style.h" #include "adg-dash.h" #include "adg-dress.h" #include "adg-param-dress.h" #include "adg-line-style.h" #include "adg-line-style-private.h" G_DEFINE_TYPE_WITH_PRIVATE(AdgLineStyle, adg_line_style, ADG_TYPE_STYLE) enum { PROP_0, PROP_COLOR_DRESS, PROP_WIDTH, PROP_CAP, PROP_JOIN, PROP_MITER_LIMIT, PROP_ANTIALIAS, PROP_DASH }; static void _adg_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static gboolean _adg_change_dash (AdgLineStyle *style, const AdgDash *dash); static void _adg_apply (AdgStyle *style, AdgEntity *entity, cairo_t *cr); static void adg_line_style_class_init(AdgLineStyleClass *klass) { GObjectClass *gobject_class; AdgStyleClass *style_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; style_class = (AdgStyleClass *) klass; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; style_class->apply = _adg_apply; param = adg_param_spec_dress("color-dress", P_("Color Dress"), P_("The color dress to bind to this line style"), ADG_DRESS_COLOR, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_COLOR_DRESS, param); param = g_param_spec_double("width", P_("Line Width"), P_("The line thickness in device unit"), 0., G_MAXDOUBLE, 2., G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_WIDTH, param); param = g_param_spec_int("cap", P_("Line Cap"), P_("The line cap mode"), G_MININT, G_MAXINT, CAIRO_LINE_CAP_ROUND, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_CAP, param); param = g_param_spec_int("join", P_("Line Join"), P_("The line join mode"), G_MININT, G_MAXINT, CAIRO_LINE_JOIN_MITER, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_JOIN, param); param = g_param_spec_double("miter-limit", P_("Miter Limit"), P_("Whether the lines should be joined with a bevel instead of a miter"), 0., G_MAXDOUBLE, 10., G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_MITER_LIMIT, param); param = g_param_spec_int("antialias", P_("Antialiasing Mode"), P_("Type of antialiasing to do when rendering lines"), G_MININT, G_MAXINT, CAIRO_ANTIALIAS_DEFAULT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_ANTIALIAS, param); param = g_param_spec_boxed("dash", P_("Dash Pattern"), P_("The dash pattern to be used while stroking a path"), ADG_TYPE_DASH, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_DASH, param); } static void adg_line_style_init(AdgLineStyle *line_style) { AdgLineStylePrivate *data = adg_line_style_get_instance_private(line_style); data->color_dress = ADG_DRESS_COLOR; data->width = 2.; data->cap = CAIRO_LINE_CAP_ROUND; data->join = CAIRO_LINE_JOIN_MITER; data->miter_limit = 10.; data->antialias = CAIRO_ANTIALIAS_DEFAULT; data->dash = NULL; } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgLineStylePrivate *data = adg_line_style_get_instance_private((AdgLineStyle *) object); switch (prop_id) { case PROP_COLOR_DRESS: g_value_set_enum(value, data->color_dress); break; case PROP_WIDTH: g_value_set_double(value, data->width); break; case PROP_CAP: g_value_set_int(value, data->cap); break; case PROP_JOIN: g_value_set_int(value, data->join); break; case PROP_MITER_LIMIT: g_value_set_double(value, data->miter_limit); break; case PROP_ANTIALIAS: g_value_set_int(value, data->antialias); break; case PROP_DASH: g_value_set_boxed(value, data->dash); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgLineStyle *line_style = (AdgLineStyle *) object; AdgLineStylePrivate *data = adg_line_style_get_instance_private(line_style); switch (prop_id) { case PROP_COLOR_DRESS: data->color_dress = g_value_get_enum(value); break; case PROP_WIDTH: data->width = g_value_get_double(value); break; case PROP_CAP: data->cap = g_value_get_int(value); break; case PROP_JOIN: data->join = g_value_get_int(value); break; case PROP_MITER_LIMIT: data->miter_limit = g_value_get_double(value); break; case PROP_ANTIALIAS: data->antialias = g_value_get_int(value); break; case PROP_DASH: _adg_change_dash(line_style, g_value_get_boxed(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_line_style_new: * * Constructs a new line style initialized with default params. * * Returns: (transfer full): a new line style. * * Since: 1.0 **/ AdgLineStyle * adg_line_style_new(void) { return g_object_new(ADG_TYPE_LINE_STYLE, NULL); } /** * adg_line_style_set_color_dress: * @line_style: an #AdgLineStyle * @dress: the new color dress to use * * Sets a new color dress on @line_style. The new dress * should be related to the original dress: you cannot * set a dress used for line styles to a dress managing * fonts. * * The validation of the new dress is done by calling * adg_dress_are_related() with @dress and the previous * dress as arguments: check out its documentation for * details on what is a related dress. * * Since: 1.0 **/ void adg_line_style_set_color_dress(AdgLineStyle *line_style, AdgDress dress) { g_return_if_fail(ADG_IS_LINE_STYLE(line_style)); g_object_set(line_style, "color-dress", dress, NULL); } /** * adg_line_style_get_color_dress: * @line_style: an #AdgLineStyle * * Gets the color dress used by @line_style. * * Returns: (transfer none): the current color dress. * * Since: 1.0 **/ AdgDress adg_line_style_get_color_dress(AdgLineStyle *line_style) { AdgLineStylePrivate *data; g_return_val_if_fail(ADG_IS_LINE_STYLE(line_style), ADG_DRESS_UNDEFINED); data = adg_line_style_get_instance_private(line_style); return data->color_dress; } /** * adg_line_style_set_width: * @line_style: an #AdgLineStyle object * @width: the new width * * Sets a new line thickness value. * * Since: 1.0 **/ void adg_line_style_set_width(AdgLineStyle *line_style, gdouble width) { g_return_if_fail(ADG_IS_LINE_STYLE(line_style)); g_object_set(line_style, "width", width, NULL); } /** * adg_line_style_get_width: * @line_style: an #AdgLineStyle object * * Gets the line thickness value (in global space). * * Returns: the requested width. * * Since: 1.0 **/ gdouble adg_line_style_get_width(AdgLineStyle *line_style) { AdgLineStylePrivate *data; g_return_val_if_fail(ADG_IS_LINE_STYLE(line_style), 0.); data = adg_line_style_get_instance_private(line_style); return data->width; } /** * adg_line_style_set_cap: * @line_style: an #AdgLineStyle object * @cap: (type gint): the new cap mode * * Sets a new line cap mode. * * Since: 1.0 **/ void adg_line_style_set_cap(AdgLineStyle *line_style, cairo_line_cap_t cap) { AdgLineStylePrivate *data; g_return_if_fail(ADG_IS_LINE_STYLE(line_style)); data = adg_line_style_get_instance_private(line_style); data->cap = cap; g_object_notify((GObject *) line_style, "cap"); } /** * adg_line_style_get_cap: * @line_style: an #AdgLineStyle object * * Gets the line cap mode. * * Returns: (type gint): the requested line cap mode. * * Since: 1.0 **/ cairo_line_cap_t adg_line_style_get_cap(AdgLineStyle *line_style) { AdgLineStylePrivate *data; g_return_val_if_fail(ADG_IS_LINE_STYLE(line_style), CAIRO_LINE_CAP_BUTT); data = adg_line_style_get_instance_private(line_style); return data->cap; } /** * adg_line_style_set_join: * @line_style: an #AdgLineStyle object * @join: (type gint): the new join mode * * Sets a new line join mode. * * Since: 1.0 **/ void adg_line_style_set_join(AdgLineStyle *line_style, cairo_line_join_t join) { AdgLineStylePrivate *data; g_return_if_fail(ADG_IS_LINE_STYLE(line_style)); data = adg_line_style_get_instance_private(line_style); data->join = join; g_object_notify((GObject *) line_style, "join"); } /** * adg_line_style_get_join: * @line_style: an #AdgLineStyle object * * Gets the line join mode. * * Returns: (type gint): the requested line join mode. * * Since: 1.0 **/ cairo_line_join_t adg_line_style_get_join(AdgLineStyle *line_style) { AdgLineStylePrivate *data; g_return_val_if_fail(ADG_IS_LINE_STYLE(line_style), CAIRO_LINE_JOIN_MITER); data = adg_line_style_get_instance_private(line_style); return data->join; } /** * adg_line_style_set_miter_limit: * @line_style: an #AdgLineStyle object * @miter_limit: the new miter limit * * Sets a new miter limit value. * * Since: 1.0 **/ void adg_line_style_set_miter_limit(AdgLineStyle *line_style, gdouble miter_limit) { g_return_if_fail(ADG_IS_LINE_STYLE(line_style)); g_object_set(line_style, "miter-limit", miter_limit, NULL); } /** * adg_line_style_get_miter_limit: * @line_style: an #AdgLineStyle object * * Gets the line miter limit value. The miter limit is used to determine * whether the lines should be joined with a bevel instead of a miter. * * Returns: the requested miter limit * * Since: 1.0 **/ gdouble adg_line_style_get_miter_limit(AdgLineStyle *line_style) { AdgLineStylePrivate *data; g_return_val_if_fail(ADG_IS_LINE_STYLE(line_style), 0.); data = adg_line_style_get_instance_private(line_style); return data->miter_limit; } /** * adg_line_style_set_antialias: * @line_style: an #AdgLineStyle object * @antialias: (type gint): the new antialias mode * * Sets a new antialias mode. * * Since: 1.0 **/ void adg_line_style_set_antialias(AdgLineStyle *line_style, cairo_antialias_t antialias) { AdgLineStylePrivate *data; g_return_if_fail(ADG_IS_LINE_STYLE(line_style)); data = adg_line_style_get_instance_private(line_style); data->antialias = antialias; g_object_notify((GObject *) line_style, "antialias"); } /** * adg_line_style_get_antialias: * @line_style: an #AdgLineStyle object * * Gets the antialias mode used. * * Returns: (type gint): the requested antialias mode. * * Since: 1.0 **/ cairo_antialias_t adg_line_style_get_antialias(AdgLineStyle *line_style) { AdgLineStylePrivate *data; g_return_val_if_fail(ADG_IS_LINE_STYLE(line_style), CAIRO_ANTIALIAS_DEFAULT); data = adg_line_style_get_instance_private(line_style); return data->antialias; } /** * adg_line_style_set_dash: * @line_style: an #AdgLineStyle object * @dash: the new #AdgDash pattern * * Sets the dash pattern of @line_style to @dash: all future rendering with * this line style will use this pattern. * * The @line_style will embed a copy of @dash: this means that, after this * call, @dash can be freed (with adg_dash_destroy()) if no more needed. * * Since: 1.0 **/ void adg_line_style_set_dash(AdgLineStyle *line_style, const AdgDash *dash) { g_return_if_fail(ADG_IS_LINE_STYLE(line_style)); if (_adg_change_dash(line_style, dash)) g_object_notify((GObject *) line_style, "dash"); } /** * adg_line_style_get_dash: * @line_style: an #AdgLineStyle object * * Gets the dash pattern currently active on @line_style. * A NULL value is returned when no dash pattern is active. * * The returned pattern is owned by @line_style: you are not allowed to modify * or free it. If something needs to be changed, work on a duplicate and reset * the new pattern, such as: * * * AdgDash *dash, *new_dash; * * dash = adg_line_style_get_dash(line_style); * if (dash == NULL) * new_dash = adg_dash_new(); * else * new_dash = adg_dash_dup(dash); * * // ...modify new_dash as needed... * * adg_line_style_set_dash(line_style, new_dash); * adg_dash_destroy(new_dash); * * * * Getting #AdgLineStyle:dash via the #GObject property mechanism returns * a duplicate instead, so you must free it when done. * * * * g_object_get(line_style, "dash", &dash, NULL); * // Here dash is a duplicate: modifying it will not affect line_style * adg_dash_destroy(dash); * * * Returns: the current dash pattern or NULL on errors. * * Since: 1.0 **/ const AdgDash * adg_line_style_get_dash(AdgLineStyle *line_style) { AdgLineStylePrivate *data; g_return_val_if_fail(ADG_IS_LINE_STYLE(line_style), NULL); data = adg_line_style_get_instance_private(line_style); return data->dash; } static gboolean _adg_change_dash(AdgLineStyle *line_style, const AdgDash *dash) { AdgLineStylePrivate *data = adg_line_style_get_instance_private(line_style); if (data->dash == dash) return FALSE; if (data->dash != NULL) { adg_dash_destroy(data->dash); data->dash = NULL; } if (dash != NULL) { data->dash = adg_dash_dup(dash); } return TRUE; } static void _adg_apply(AdgStyle *style, AdgEntity *entity, cairo_t *cr) { AdgLineStylePrivate *data = adg_line_style_get_instance_private((AdgLineStyle *) style); adg_entity_apply_dress(entity, data->color_dress, cr); cairo_set_line_width(cr, data->width); cairo_set_line_cap(cr, data->cap); cairo_set_line_join(cr, data->join); cairo_set_miter_limit(cr, data->miter_limit); cairo_set_antialias(cr, data->antialias); if (data->dash != NULL) { cairo_set_dash(cr, adg_dash_get_dashes(data->dash), adg_dash_get_num_dashes(data->dash), adg_dash_get_offset(data->dash)); } } diff --git a/src/adg/adg-line-style.h b/src/adg/adg-line-style.h index af8136bb..bf245564 100644 --- a/src/adg/adg-line-style.h +++ b/src/adg/adg-line-style.h @@ -1,85 +1,85 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_LINE_STYLE_H__ #define __ADG_LINE_STYLE_H__ G_BEGIN_DECLS #define ADG_TYPE_LINE_STYLE (adg_line_style_get_type()) #define ADG_LINE_STYLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_LINE_STYLE, AdgLineStyle)) #define ADG_LINE_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_LINE_STYLE, AdgLineStyleClass)) #define ADG_IS_LINE_STYLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_LINE_STYLE)) #define ADG_IS_LINE_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_LINE_STYLE)) #define ADG_LINE_STYLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_LINE_STYLE, AdgLineStyleClass)) typedef struct _AdgLineStyle AdgLineStyle; typedef struct _AdgLineStyleClass AdgLineStyleClass; struct _AdgLineStyle { /*< private >*/ AdgStyle parent; }; struct _AdgLineStyleClass { /*< private >*/ AdgStyleClass parent_class; }; GType adg_line_style_get_type (void); AdgLineStyle * adg_line_style_new (void); void adg_line_style_set_color_dress (AdgLineStyle *line_style, AdgDress dress); AdgDress adg_line_style_get_color_dress (AdgLineStyle *line_style); void adg_line_style_set_width (AdgLineStyle *line_style, gdouble width); gdouble adg_line_style_get_width (AdgLineStyle *line_style); void adg_line_style_set_cap (AdgLineStyle *line_style, cairo_line_cap_t cap); cairo_line_cap_t adg_line_style_get_cap (AdgLineStyle *line_style); void adg_line_style_set_join (AdgLineStyle *line_style, cairo_line_join_t join); cairo_line_join_t adg_line_style_get_join (AdgLineStyle *line_style); void adg_line_style_set_miter_limit (AdgLineStyle *line_style, gdouble miter_limit); gdouble adg_line_style_get_miter_limit (AdgLineStyle *line_style); void adg_line_style_set_antialias (AdgLineStyle *line_style, cairo_antialias_t antialias); cairo_antialias_t adg_line_style_get_antialias (AdgLineStyle *line_style); void adg_line_style_set_dash (AdgLineStyle *line_style, const AdgDash *dash); const AdgDash * adg_line_style_get_dash (AdgLineStyle *line_style); G_END_DECLS #endif /* __ADG_LINE_STYLE_H__ */ diff --git a/src/adg/adg-logo-private.h b/src/adg/adg-logo-private.h index af2b7a0b..4d5a80a6 100644 --- a/src/adg/adg-logo-private.h +++ b/src/adg/adg-logo-private.h @@ -1,44 +1,44 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_LOGO_PRIVATE_H__ #define __ADG_LOGO_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgLogoClassPrivate AdgLogoClassPrivate; typedef struct _AdgLogoPrivate AdgLogoPrivate; struct _AdgLogoClassPrivate { AdgPath *symbol; AdgPath *screen; AdgPath *frame; CpmlExtents extents; }; struct _AdgLogoPrivate { AdgDress symbol_dress; AdgDress screen_dress; AdgDress frame_dress; }; G_END_DECLS #endif /* __ADG_LOGO_PRIVATE_H__ */ diff --git a/src/adg/adg-logo.c b/src/adg/adg-logo.c index ef5e9088..69ed9274 100644 --- a/src/adg/adg-logo.c +++ b/src/adg/adg-logo.c @@ -1,468 +1,468 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-logo * @short_description: The ADG default logo * * The #AdgLogo is an entity representing the default ADG logo. * * Since: 1.0 **/ /** * AdgLogo: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-model.h" #include "adg-trail.h" #include "adg-path.h" #include "adg-dress.h" #include "adg-param-dress.h" #include "adg-logo.h" #include "adg-logo-private.h" G_DEFINE_TYPE_WITH_PRIVATE(AdgLogo, adg_logo, ADG_TYPE_ENTITY) enum { PROP_0, PROP_SYMBOL_DRESS, PROP_SCREEN_DRESS, PROP_FRAME_DRESS }; static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void _adg_arrange (AdgEntity *entity); static void _adg_render (AdgEntity *entity, cairo_t *cr); static void _adg_arrange_class (AdgLogoClass *logo_class); static void adg_logo_class_init(AdgLogoClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; GParamSpec *param; AdgLogoClassPrivate *data_class; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; entity_class->arrange = _adg_arrange; entity_class->render = _adg_render; param = adg_param_spec_dress("symbol-dress", P_("Symbol Dress"), P_("The line dress to use for rendering the symbol of the logo"), ADG_DRESS_LINE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_SYMBOL_DRESS, param); param = adg_param_spec_dress("screen-dress", P_("Screen Dress"), P_("The line dress to use for rendering the screen shape around the logo"), ADG_DRESS_LINE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_SCREEN_DRESS, param); param = adg_param_spec_dress("frame-dress", P_("Frame Dress"), P_("The line dress to use for rendering the frame"), ADG_DRESS_LINE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FRAME_DRESS, param); /* Initialize the private class data: the allocated struct is * never freed as this type is registered statically, hence * never destroyed. A better approach would be to use the old * type initialization (no G_TYPE_DEFINE and friends) that * allows to specify a custom class finalization method */ data_class = g_new(AdgLogoClassPrivate, 1); data_class->symbol = NULL; data_class->screen = NULL; data_class->frame = NULL; data_class->extents.is_defined = FALSE; klass->data_class = data_class; } static void adg_logo_init(AdgLogo *logo) { AdgLogoPrivate *data = adg_logo_get_instance_private(logo); data->symbol_dress = ADG_DRESS_LINE; data->screen_dress = ADG_DRESS_LINE; data->frame_dress = ADG_DRESS_LINE; } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgLogoPrivate *data = adg_logo_get_instance_private((AdgLogo *) object); switch (prop_id) { case PROP_SYMBOL_DRESS: g_value_set_enum(value, data->symbol_dress); break; case PROP_SCREEN_DRESS: g_value_set_enum(value, data->screen_dress); break; case PROP_FRAME_DRESS: g_value_set_enum(value, data->frame_dress); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgLogoPrivate *data = adg_logo_get_instance_private((AdgLogo *) object); switch (prop_id) { case PROP_SYMBOL_DRESS: data->symbol_dress = g_value_get_enum(value); break; case PROP_SCREEN_DRESS: data->screen_dress = g_value_get_enum(value); break; case PROP_FRAME_DRESS: data->frame_dress = g_value_get_enum(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_logo_new: * * Creates a new logo entity. * * Returns: (transfer full): the newly created logo entity. * * Since: 1.0 **/ AdgLogo * adg_logo_new(void) { return g_object_new(ADG_TYPE_LOGO, NULL); } /** * adg_logo_set_symbol_dress: * @logo: an #AdgLogo * @dress: the new #AdgDress to use * * Sets a new line dress for rendering the symbol of @logo. The * new dress must be a line dress: the check is done by calling * adg_dress_are_related() with @dress and the old dress as * arguments. Check out its documentation for further details. * * The default dress is a transparent line dress: the rendering * callback will stroke the symbol using the default color with * a predefined thickness. * * Since: 1.0 **/ void adg_logo_set_symbol_dress(AdgLogo *logo, AdgDress dress) { g_return_if_fail(ADG_IS_LOGO(logo)); g_object_set(logo, "symbol-dress", dress, NULL); } /** * adg_logo_get_symbol_dress: * @logo: an #AdgLogo * * Gets the line dress to be used in stroking the symbol of @logo. * * Returns: (transfer none): the requested line dress. * * Since: 1.0 **/ AdgDress adg_logo_get_symbol_dress(AdgLogo *logo) { AdgLogoPrivate *data; g_return_val_if_fail(ADG_IS_LOGO(logo), ADG_DRESS_UNDEFINED); data = adg_logo_get_instance_private(logo); return data->symbol_dress; } /** * adg_logo_set_screen_dress: * @logo: an #AdgLogo * @dress: the new #AdgDress to use * * Sets a new line dress for rendering the screen of @logo. * The new dress must be a line dress: the check is done by * calling adg_dress_are_related() with @dress and the old * dress as arguments. Check out its documentation for * further details. * * The default dress is a transparent line dress: the rendering * callback will stroke the screen using the default color with * a predefined thickness. * * Since: 1.0 **/ void adg_logo_set_screen_dress(AdgLogo *logo, AdgDress dress) { g_return_if_fail(ADG_IS_LOGO(logo)); g_object_set(logo, "screen-dress", dress, NULL); } /** * adg_logo_get_screen_dress: * @logo: an #AdgLogo * * Gets the line dress to be used in stroking the screen shape of @logo. * * Returns: (transfer none): the requested line dress. * * Since: 1.0 **/ AdgDress adg_logo_get_screen_dress(AdgLogo *logo) { AdgLogoPrivate *data; g_return_val_if_fail(ADG_IS_LOGO(logo), ADG_DRESS_UNDEFINED); data = adg_logo_get_instance_private(logo); return data->screen_dress; } /** * adg_logo_set_frame_dress: * @logo: an #AdgLogo * @dress: the new #AdgDress to use * * Sets a new line dress for rendering the frame of @logo. * The new dress must be a line dress: the check is done by * calling adg_dress_are_related() with @dress and the old * dress as arguments. Check out its documentation for * further details. * * The default dress is a transparent line dress: the rendering * callback will stroke the frame using the default color with * a predefined thickness. * * Since: 1.0 **/ void adg_logo_set_frame_dress(AdgLogo *logo, AdgDress dress) { g_return_if_fail(ADG_IS_LOGO(logo)); g_object_set(logo, "frame-dress", dress, NULL); } /** * adg_logo_get_frame_dress: * @logo: an #AdgLogo * * Gets the line dress to be used in stroking the frame of @logo. * * Returns: (transfer none): the requested line dress. * * Since: 1.0 **/ AdgDress adg_logo_get_frame_dress(AdgLogo *logo) { AdgLogoPrivate *data; g_return_val_if_fail(ADG_IS_LOGO(logo), ADG_DRESS_UNDEFINED); data = adg_logo_get_instance_private(logo); return data->frame_dress; } static void _adg_arrange(AdgEntity *entity) { AdgLogoClass *logo_class; AdgLogoClassPrivate *data_class; CpmlExtents extents; logo_class = ADG_LOGO_GET_CLASS(entity); data_class = logo_class->data_class; _adg_arrange_class(logo_class); cpml_extents_copy(&extents, &data_class->extents); cpml_extents_transform(&extents, adg_entity_get_local_matrix(entity)); cpml_extents_transform(&extents, adg_entity_get_global_matrix(entity)); adg_entity_set_extents(entity, &extents); } static void _adg_arrange_class(AdgLogoClass *logo_class) { AdgLogoClassPrivate *data_class; CpmlExtents *extents; data_class = logo_class->data_class; extents = &data_class->extents; if (data_class->symbol == NULL) { AdgPath *path = adg_path_new(); adg_path_move_to_explicit(path, 8, 26); adg_path_line_to_explicit(path, 24, 10); adg_path_arc_to_explicit(path, 32, 18, 24, 26); adg_path_line_to_explicit(path, 24, 11); adg_path_move_to_explicit(path, 40, 10); adg_path_arc_to_explicit(path, 32, 18, 40, 26); adg_path_line_to_explicit(path, 40, 22); adg_path_line_to_explicit(path, 38, 22); adg_path_move_to_explicit(path, 23, 22); adg_path_line_to_explicit(path, 16, 22); data_class->symbol = path; extents->is_defined = FALSE; } if (data_class->screen == NULL) { AdgPath *path = adg_path_new(); adg_path_move_to_explicit(path, 3, 3); adg_path_line_to_explicit(path, 47, 3); adg_path_fillet(path, 10); adg_path_line_to_explicit(path, 47, 33); adg_path_fillet(path, 10); adg_path_line_to_explicit(path, 3, 33); adg_path_fillet(path, 10); adg_path_close(path); adg_path_fillet(path, 10); data_class->screen = path; extents->is_defined = FALSE; } if (data_class->frame == NULL) { AdgPath *path = adg_path_new(); adg_path_move_to_explicit(path, 0, 0); adg_path_line_to_explicit(path, 50, 0); adg_path_line_to_explicit(path, 50, 36); adg_path_line_to_explicit(path, 0, 36); adg_path_close(path); data_class->frame = path; extents->is_defined = FALSE; } if (!data_class->extents.is_defined) { cpml_extents_add(extents, adg_trail_get_extents((AdgTrail *) data_class->symbol)); cpml_extents_add(extents, adg_trail_get_extents((AdgTrail *) data_class->screen)); cpml_extents_add(extents, adg_trail_get_extents((AdgTrail *) data_class->frame)); } } static void _adg_render(AdgEntity *entity, cairo_t *cr) { AdgLogoClassPrivate *data_class = ADG_LOGO_GET_CLASS(entity)->data_class; AdgLogoPrivate *data = adg_logo_get_instance_private((AdgLogo *) entity); const cairo_path_t *cairo_path; cairo_transform(cr, adg_entity_get_global_matrix(entity)); cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); cairo_path = adg_trail_get_cairo_path((AdgTrail *) data_class->symbol); if (cairo_path != NULL) { cairo_save(cr); cairo_transform(cr, adg_entity_get_local_matrix(entity)); cairo_append_path(cr, cairo_path); cairo_restore(cr); cairo_set_line_width(cr, 3); adg_entity_apply_dress(entity, data->symbol_dress, cr); cairo_stroke(cr); } cairo_path = adg_trail_get_cairo_path((AdgTrail *) data_class->screen); if (cairo_path != NULL) { cairo_save(cr); cairo_transform(cr, adg_entity_get_local_matrix(entity)); cairo_append_path(cr, cairo_path); cairo_restore(cr); cairo_set_line_width(cr, 2); adg_entity_apply_dress(entity, data->screen_dress, cr); cairo_stroke(cr); } cairo_path = adg_trail_get_cairo_path((AdgTrail *) data_class->frame); if (cairo_path != NULL) { cairo_save(cr); cairo_transform(cr, adg_entity_get_local_matrix(entity)); cairo_append_path(cr, cairo_path); cairo_restore(cr); cairo_set_line_width(cr, 2); cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE); adg_entity_apply_dress(entity, data->frame_dress, cr); cairo_stroke(cr); } } diff --git a/src/adg/adg-logo.h b/src/adg/adg-logo.h index 14fca3fd..40bdc019 100644 --- a/src/adg/adg-logo.h +++ b/src/adg/adg-logo.h @@ -1,71 +1,71 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_LOGO_H__ #define __ADG_LOGO_H__ G_BEGIN_DECLS #define ADG_TYPE_LOGO (adg_logo_get_type()) #define ADG_LOGO(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_LOGO, AdgLogo)) #define ADG_LOGO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_LOGO, AdgLogoClass)) #define ADG_IS_LOGO(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_LOGO)) #define ADG_IS_LOGO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_LOGO)) #define ADG_LOGO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_LOGO, AdgLogoClass)) typedef struct _AdgLogo AdgLogo; typedef struct _AdgLogoClass AdgLogoClass; struct _AdgLogo { /*< private >*/ AdgEntity parent; }; struct _AdgLogoClass { /*< private >*/ AdgEntityClass parent_class; gpointer data_class; }; GType adg_logo_get_type (void); AdgLogo * adg_logo_new (void); void adg_logo_set_symbol_dress (AdgLogo *logo, AdgDress dress); AdgDress adg_logo_get_symbol_dress (AdgLogo *logo); void adg_logo_set_screen_dress (AdgLogo *logo, AdgDress dress); AdgDress adg_logo_get_screen_dress (AdgLogo *logo); void adg_logo_set_frame_dress (AdgLogo *logo, AdgDress dress); AdgDress adg_logo_get_frame_dress (AdgLogo *logo); G_END_DECLS #endif /* __ADG_LOGO_H__ */ diff --git a/src/adg/adg-marker-private.h b/src/adg/adg-marker-private.h index 2909d792..05b62a1d 100644 --- a/src/adg/adg-marker-private.h +++ b/src/adg/adg-marker-private.h @@ -1,42 +1,42 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_MARKER_PRIVATE_H__ #define __ADG_MARKER_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgMarkerPrivate AdgMarkerPrivate; struct _AdgMarkerPrivate { AdgTrail *trail; guint n_segment; CpmlSegment *backup_segment; CpmlSegment segment; gdouble pos; gdouble size; AdgModel *model; }; G_END_DECLS #endif /* __ADG_MARKER_PRIVATE_H__ */ diff --git a/src/adg/adg-marker.c b/src/adg/adg-marker.c index 3254f443..0bde205f 100644 --- a/src/adg/adg-marker.c +++ b/src/adg/adg-marker.c @@ -1,757 +1,757 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-marker * @short_description: Base entity for markers * * A marker is an entity to be applied at the start or end of a segment. * Typical examples include arrows, ticks, dots and so on. * * The #AdgMarker:trail and #AdgMarker:n-segment properties specify the * segment where the marker should be applied. Similarly to the * #AdgStroke type, if the associated trail is destroyed the above * properties are unset. * * The local map is used internally to align the marker to the trail end, * so adg_entity_set_local_map() and friends is reserved. Therefore, if * the trail is modified and the marker had no way to know it, you should * call adg_entity_local_changed() to update the marker position. * * Use adg_marker_set_pos() to select the position where the marker * should be put: 0 means the start point of the segment while 1 * means the end point. * * The #AdgMarker:model property and APIs are intended only for marker * implementation purposes. * * Since: 1.0 **/ /** * AdgMarker: * * All fields are privates and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ /** * AdgMarkerClass: * @create_model: abstract virtual method that creates a model template for * all the markers used by this class. * * The @create_model method must be implemented by any #AdgMarker derived * classes. The derived classes are expected to apply a single model * (the one returned by this method) to every path endings by using * different transformations. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-model.h" #include "adg-trail.h" #include #include "adg-marker.h" #include "adg-marker-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_marker_parent_class) #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_marker_parent_class) G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(AdgMarker, adg_marker, ADG_TYPE_ENTITY) enum { PROP_0, PROP_TRAIL, PROP_N_SEGMENT, PROP_POS, PROP_SIZE, PROP_MODEL }; static void _adg_dispose (GObject *object); static void _adg_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void _adg_local_changed (AdgEntity *entity); static void _adg_invalidate (AdgEntity *entity); static void _adg_clear_trail (AdgMarker *marker); static gboolean _adg_set_segment (AdgMarker *marker, AdgTrail *trail, guint n_segment); static AdgModel * _adg_create_model (AdgMarker *marker); static void adg_marker_class_init(AdgMarkerClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->set_property = _adg_set_property; gobject_class->get_property = _adg_get_property; entity_class->local_changed = _adg_local_changed; entity_class->invalidate = _adg_invalidate; klass->create_model = _adg_create_model; param = g_param_spec_object("trail", P_("Trail"), P_("The subject AdgTrail for this marker"), ADG_TYPE_TRAIL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); g_object_class_install_property(gobject_class, PROP_TRAIL, param); param = g_param_spec_uint("n-segment", P_("Segment Index"), P_("The segment on trail where this marker should be applied (where 0 means undefined segment, 1 the first segment and so on)"), 0, G_MAXUINT, 0, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_N_SEGMENT, param); param = g_param_spec_double("pos", P_("Position"), P_("The position ratio inside the segment where to put the marker (0 means the start point while 1 means the end point)"), 0, 1, 0, G_PARAM_READWRITE|G_PARAM_CONSTRUCT); g_object_class_install_property(gobject_class, PROP_POS, param); param = g_param_spec_double("size", P_("Marker Size"), P_("The size (in global space) of the marker"), 0, G_MAXDOUBLE, 10, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_SIZE, param); param = g_param_spec_object("model", P_("Model"), P_("A general purpose model usable by the marker implementations"), ADG_TYPE_MODEL, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_MODEL, param); } static void adg_marker_init(AdgMarker *marker) { AdgMarkerPrivate *data = adg_marker_get_instance_private(marker); data->trail = NULL; data->n_segment = 0; data->backup_segment = NULL; memset(&data->segment, 0, sizeof(data->segment)); data->pos = 0; data->size = 10; data->model = NULL; } static void _adg_dispose(GObject *object) { AdgMarker *marker = (AdgMarker *) object; adg_marker_set_model(marker, NULL); adg_marker_set_segment(marker, NULL, 0); if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgMarkerPrivate *data = adg_marker_get_instance_private((AdgMarker *) object); switch (prop_id) { case PROP_TRAIL: g_value_set_object(value, data->trail); break; case PROP_N_SEGMENT: g_value_set_uint(value, data->n_segment); break; case PROP_POS: g_value_set_double(value, data->pos); break; case PROP_SIZE: g_value_set_double(value, data->size); break; case PROP_MODEL: g_value_set_object(value, data->model); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgMarker *marker = (AdgMarker *) object; AdgMarkerPrivate *data = adg_marker_get_instance_private(marker); AdgModel *old_model; switch (prop_id) { case PROP_TRAIL: _adg_set_segment(marker, g_value_get_object(value), data->n_segment); break; case PROP_N_SEGMENT: _adg_set_segment(marker, data->trail, g_value_get_uint(value)); break; case PROP_POS: data->pos = g_value_get_double(value); break; case PROP_SIZE: data->size = g_value_get_double(value); break; case PROP_MODEL: old_model = data->model; data->model = g_value_get_object(value); if (data->model) { g_object_ref((GObject *) data->model); adg_entity_local_changed((AdgEntity *) object); } if (old_model) g_object_unref(old_model); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_marker_set_trail: * @marker: an #AdgMarker * @trail: the new trail to use * * Sets the #AdgMarker:trail property to @trail. It is allowed to * pass NULL to clear the current trail. * * This method could fail unexpectedly if the segment index specified * by the #AdgMarker:n-segment property is not present inside the new * segment: if you want to set a new segment it is more convenient to * change both properties (#AdgMarker:trail and #AdgMarker:n-segment) * at once with adg_marker_set_segment(). * * Since: 1.0 **/ void adg_marker_set_trail(AdgMarker *marker, AdgTrail *trail) { g_return_if_fail(ADG_IS_MARKER(marker)); g_object_set(marker, "trail", trail, NULL); } /** * adg_marker_get_trail: * @marker: an #AdgMarker * * Gets the trail where this marker should be applied. * The returned object is owned by @marker and should not be * freed or modified. * * Returns: (transfer none): the requested trail or NULL on errors. * * Since: 1.0 **/ AdgTrail * adg_marker_get_trail(AdgMarker *marker) { AdgMarkerPrivate *data; g_return_val_if_fail(ADG_IS_MARKER(marker), NULL); data = adg_marker_get_instance_private(marker); return data->trail; } /** * adg_marker_set_n_segment: * @marker: an #AdgMarker * @n_segment: the new segment index * * Sets the #AdgMarker:n-segment property to @n_segment. The trail * is unchanged. If you want to set both properties at once (as * usually requested to refer to a specific segment), * adg_marker_set_segment() should be more convenient. * * Since: 1.0 **/ void adg_marker_set_n_segment(AdgMarker *marker, guint n_segment) { g_return_if_fail(ADG_IS_MARKER(marker)); g_object_set(marker, "n-segment", n_segment, NULL); } /** * adg_marker_get_n_segment: * @marker: an #AdgMarker * * Returns the segment of the associated trail where this marker * will be applied, where 1 is the first segment. * * Returns: an index greather than 0 on success or 0 on errors. * * Since: 1.0 **/ guint adg_marker_get_n_segment(AdgMarker *marker) { AdgMarkerPrivate *data; g_return_val_if_fail(ADG_IS_MARKER(marker), 0); data = adg_marker_get_instance_private(marker); return data->n_segment; } /** * adg_marker_set_segment: * @marker: an #AdgMarker * @trail: the #AdgTrail * @n_segment: a segment index * * Sets a new segment where the marker should be applied at once. * A dependency between @trail and @marker is added, so when @trail * changes @marker is invalidated. * * A callback is added to #AdgModel::remove-dependency so manually * removing the dependency (such as when @trail is destroyed) will * unlink @marker from it. * * Since: 1.0 **/ void adg_marker_set_segment(AdgMarker *marker, AdgTrail *trail, guint n_segment) { g_return_if_fail(ADG_IS_MARKER(marker)); /* To avoid referring to an inexistent trail/n-segment couple, the * "n-segment" property is reset before to avoid segment validation */ g_object_set(marker, "n-segment", 0, "trail", trail, "n-segment", n_segment, NULL); } /** * adg_marker_get_segment: * @marker: an #AdgMarker * * * This function is only useful in marker implementations. * * * Gets the segment where the marker will be applied. This segment * is eventually a modified version of the backup segment, after * having applied the marker. * * Returns: the segment or NULL on errors. * * Since: 1.0 **/ const CpmlSegment * adg_marker_get_segment(AdgMarker *marker) { AdgMarkerPrivate *data; g_return_val_if_fail(ADG_IS_MARKER(marker), NULL); data = adg_marker_get_instance_private(marker); return &data->segment; } /** * adg_marker_backup_segment: * @marker: an #AdgMarker * * * This function is only useful in marker implementations. * * * Duplicates the current subject segment for backup purpose: this * segment can be accessed by adg_marker_get_backup_segment(). * * A current segment should exist (i.e. both #AdgMarker:trail and * #AdgMarker:n-segment properties must be properly set) or this * method will fail without further processing. * * When the subject segment is changed (either by changing * #AdgMarker:trail or #AdgMarker:n-segment) the original segment * is automatically restored. * * Since: 1.0 **/ void adg_marker_backup_segment(AdgMarker *marker) { AdgMarkerPrivate *data; g_return_if_fail(ADG_IS_MARKER(marker)); data = adg_marker_get_instance_private(marker); if (data->n_segment > 0) { g_return_if_fail(data->trail != NULL); g_free(data->backup_segment); /* Backup the segment, if a segment to backup exists */ if (adg_trail_put_segment(data->trail, data->n_segment, &data->segment)) data->backup_segment = cpml_segment_deep_dup(&data->segment); else data->backup_segment = NULL; } } /** * adg_marker_get_backup_segment: * @marker: an #AdgMarker * * * This function is only useful in marker implementations. * * * Gets the original segment where the marker has been applied. * Applying a marker could modify the underlying trail, usually * by trimming the original segment of a #AdgMarker:size dependent * length from the ends. The marker instance holds a copy of the * original segment, generated by adg_marker_backup_segment(), * to be used in recomputation, for example when the marker * changes its size. * * When the subject segment is changed (either by changing * #AdgMarker:trail or #AdgMarker:n-segment) the original segment * is automatically restored. * * Returns: the original segment or NULL on errors. * * Since: 1.0 **/ const CpmlSegment * adg_marker_get_backup_segment(AdgMarker *marker) { AdgMarkerPrivate *data; g_return_val_if_fail(ADG_IS_MARKER(marker), NULL); data = adg_marker_get_instance_private(marker); return data->backup_segment; } /** * adg_marker_set_pos: * @marker: an #AdgMarker * @pos: the new pos * * Sets a new position on @marker. Check out adg_marker_get_pos() for * details on what @pos represents. * * Since: 1.0 **/ void adg_marker_set_pos(AdgMarker *marker, gdouble pos) { g_return_if_fail(ADG_IS_MARKER(marker)); g_object_set(marker, "pos", pos, NULL); } /** * adg_marker_get_pos: * @marker: an #AdgMarker * * Gets the current position of @marker. The returned value is a ratio * position referred to the segment associated to @marker: 0 means the * start point and 1 means the end point of the segment. * * Returns: the marker position. * * Since: 1.0 **/ gdouble adg_marker_get_pos(AdgMarker *marker) { AdgMarkerPrivate *data; g_return_val_if_fail(ADG_IS_MARKER(marker), 0); data = adg_marker_get_instance_private(marker); return data->pos; } /** * adg_marker_set_size: * @marker: an #AdgMarker * @size: the new size * * Sets a new size on @marker. The @size is an implementation-dependent * property: it has meaning only when used by an #AdgMarker derived type. * * Since: 1.0 **/ void adg_marker_set_size(AdgMarker *marker, gdouble size) { g_return_if_fail(ADG_IS_MARKER(marker)); g_object_set(marker, "size", size, NULL); } /** * adg_marker_get_size: * @marker: an #AdgMarker * * Gets the current size of @marker. * * Returns: the marker size, in global space * * Since: 1.0 **/ gdouble adg_marker_get_size(AdgMarker *marker) { AdgMarkerPrivate *data; g_return_val_if_fail(ADG_IS_MARKER(marker), 0); data = adg_marker_get_instance_private(marker); return data->size; } /** * adg_marker_set_model: * @marker: an #AdgMarker * @model: a new #AdgModel * * * This function is only useful in marker implementations. * * * Sets a new model for @marker. The reference to the old model (if an * old model was present) is dropped while a new reference is added to * @model. * * Since: 1.0 **/ void adg_marker_set_model(AdgMarker *marker, AdgModel *model) { g_return_if_fail(ADG_IS_MARKER(marker)); g_object_set(marker, "model", model, NULL); } /** * adg_marker_get_model: * @marker: an #AdgMarker * * * This function is only useful in marker implementations. * * * Gets the current model of @marker. This is an accessor method: * if you need to get the model for rendering, use adg_marker_model() * instead. The returned object is owned by @marker and should not be * freed or modified. * * Returns: (transfer none): the cached model or NULL on errors. * * Since: 1.0 **/ AdgModel * adg_marker_get_model(AdgMarker *marker) { AdgMarkerPrivate *data; g_return_val_if_fail(ADG_IS_MARKER(marker), NULL); data = adg_marker_get_instance_private(marker); return data->model; } /** * adg_marker_model: * @marker: an #AdgMarker * * * This function is only useful in marker implementations. * * * Gets the model of @marker. If the model is not found, it is * automatically created by calling the create_model * virtual method. The returned object is owned by @marker and * should not be freed or modified. * * Returns: (transfer none): the current model or NULL on errors. * * Since: 1.0 **/ AdgModel * adg_marker_model(AdgMarker *marker) { AdgMarkerPrivate *data; g_return_val_if_fail(ADG_IS_MARKER(marker), NULL); data = adg_marker_get_instance_private(marker); if (data->model == NULL) { /* Model not found: regenerate it */ AdgMarkerClass *marker_class = ADG_MARKER_GET_CLASS(marker); if (marker_class->create_model) adg_marker_set_model(marker, marker_class->create_model(marker)); } return data->model; } static void _adg_local_changed(AdgEntity *entity) { AdgMarkerPrivate *data = adg_marker_get_instance_private((AdgMarker *) entity); /* On invalid segments, segment.data is not set: do not crash */ if (data->segment.data != NULL) { CpmlPair pair; CpmlVector vector; cairo_matrix_t map; cpml_segment_put_pair_at(&data->segment, data->pos, &pair); cpml_segment_put_vector_at(&data->segment, data->pos, &vector); cpml_vector_set_length(&vector, data->size); if (data->pos > 0.5) { vector.x = -vector.x; vector.y = -vector.y; } cairo_matrix_init(&map, vector.x, vector.y, -vector.y, vector.x, pair.x, pair.y); adg_entity_set_local_map(entity, &map); } if (_ADG_OLD_ENTITY_CLASS->local_changed) _ADG_OLD_ENTITY_CLASS->local_changed(entity); } static void _adg_invalidate(AdgEntity *entity) { adg_marker_set_model((AdgMarker *) entity, NULL); } static void _adg_clear_trail(AdgMarker *marker) { AdgMarkerPrivate *data = adg_marker_get_instance_private(marker); if (data->trail && data->backup_segment) { /* Restore the original segment in the old trail */ cpml_segment_copy_data(&data->segment, data->backup_segment); g_free(data->backup_segment); data->backup_segment = NULL; } data->trail = NULL; data->n_segment = 0; } static gboolean _adg_set_segment(AdgMarker *marker, AdgTrail *trail, guint n_segment) { AdgMarkerPrivate *data; CpmlSegment segment = { 0 }; g_return_val_if_fail(trail == NULL || ADG_IS_TRAIL(trail), FALSE); /* Segment validation, but only if trail and n_segment are specified */ if (trail && n_segment > 0 && !adg_trail_put_segment(trail, n_segment, &segment)) return FALSE; data = adg_marker_get_instance_private(marker); /* Do not try to cache results! Although @trail and @n_segment * could be the same, the internal CpmlSegment could change. * This is the case when AdgLDim arranges the layout and changes * the path data (to force outside arrows for example) reusing * the same CpmlPath. In other words, do not do this: * * if (trail == data->trail && n_segment == data->n_segment) * return FALSE; * * Incidentally, on a 64 bit platform this issue has been never * exposed. Avoiding the cache will solve the issue 33: * * http://dev.entidi.com/p/adg/issues/33/ */ if (trail != data->trail) { AdgEntity *entity = (AdgEntity *) marker; if (data->trail) adg_model_remove_dependency((AdgModel *) data->trail, entity); data->trail = trail; if (trail) { adg_model_add_dependency((AdgModel *) trail, entity); g_signal_connect_swapped(trail, "remove-dependency", G_CALLBACK(_adg_clear_trail), marker); } } cpml_segment_copy(&data->segment, &segment); data->n_segment = n_segment; return TRUE; } static AdgModel * _adg_create_model(AdgMarker *marker) { g_warning(_("%s: 'create_model' method not implemented for type '%s'"), G_STRLOC, g_type_name(G_OBJECT_TYPE(marker))); return NULL; } diff --git a/src/adg/adg-marker.h b/src/adg/adg-marker.h index 66fe0a0a..2815b324 100644 --- a/src/adg/adg-marker.h +++ b/src/adg/adg-marker.h @@ -1,86 +1,86 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_MARKER_H__ #define __ADG_MARKER_H__ G_BEGIN_DECLS #define ADG_TYPE_MARKER (adg_marker_get_type()) #define ADG_MARKER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_MARKER, AdgMarker)) #define ADG_MARKER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_MARKER, AdgMarkerClass)) #define ADG_IS_MARKER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_MARKER)) #define ADG_IS_MARKER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_MARKER)) #define ADG_MARKER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_MARKER, AdgMarkerClass)) typedef struct _AdgMarker AdgMarker; typedef struct _AdgMarkerClass AdgMarkerClass; struct _AdgMarker { /*< private >*/ AdgEntity parent; }; struct _AdgMarkerClass { /*< private >*/ AdgEntityClass parent_class; /*< public >*/ /* Virtual table */ AdgModel * (*create_model) (AdgMarker *marker); }; GType adg_marker_get_type (void); void adg_marker_set_trail (AdgMarker *marker, AdgTrail *trail); AdgTrail * adg_marker_get_trail (AdgMarker *marker); void adg_marker_set_n_segment (AdgMarker *marker, guint n_segment); guint adg_marker_get_n_segment (AdgMarker *marker); void adg_marker_set_segment (AdgMarker *marker, AdgTrail *trail, guint n_segment); const CpmlSegment * adg_marker_get_segment (AdgMarker *marker); void adg_marker_backup_segment (AdgMarker *marker); const CpmlSegment * adg_marker_get_backup_segment (AdgMarker *marker); void adg_marker_set_pos (AdgMarker *marker, gdouble pos); gdouble adg_marker_get_pos (AdgMarker *marker); void adg_marker_set_size (AdgMarker *marker, gdouble size); gdouble adg_marker_get_size (AdgMarker *marker); void adg_marker_set_model (AdgMarker *marker, AdgModel *model); AdgModel * adg_marker_get_model (AdgMarker *marker); AdgModel * adg_marker_model (AdgMarker *marker); G_END_DECLS #endif /* __ADG_MARKER_H__ */ diff --git a/src/adg/adg-matrix.c b/src/adg/adg-matrix.c index de2755d7..b463b33f 100644 --- a/src/adg/adg-matrix.c +++ b/src/adg/adg-matrix.c @@ -1,247 +1,247 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-matrix * @Section_Id:Matrix * @title: Matrix * @short_description: cairo matrix enhancements and utilities * * The following functions augment the cairo_matrix_t available methods * providing some useful addition. * * Since: 1.0 **/ #include "adg-internal.h" #include #include /** * adg_matrix_identity: * * A convenient constant providing an identity matrix. * * Returns: (transfer none): a pointer to the identity matrix * * Since: 1.0 **/ const cairo_matrix_t * adg_matrix_identity(void) { static cairo_matrix_t *identity_matrix = NULL; if (G_UNLIKELY(identity_matrix == NULL)) { identity_matrix = g_new(cairo_matrix_t, 1); cairo_matrix_init_identity(identity_matrix); } return identity_matrix; } /** * adg_matrix_null: * * A convenient constant providing an null matrix, that is a matrix * where all components are 0. * * Returns: (transfer none): a pointer to the null matrix * * Since: 1.0 **/ const cairo_matrix_t * adg_matrix_null(void) { static cairo_matrix_t *null_matrix = NULL; if (G_UNLIKELY(null_matrix == NULL)) null_matrix = g_new0(cairo_matrix_t, 1); return null_matrix; } /** * adg_matrix_copy: * @matrix: (out caller-allocates): the destination #cairo_matrix_t * @src: the source #cairo_matrix_t * * Copies @src to @matrix. * * Since: 1.0 **/ void adg_matrix_copy(cairo_matrix_t *matrix, const cairo_matrix_t *src) { g_return_if_fail(matrix != NULL); g_return_if_fail(src != NULL); memcpy(matrix, src, sizeof(cairo_matrix_t)); } /** * adg_matrix_dup: * @matrix: the souce #cairo_matrix_t * * Duplicates @matrix. * * Returns: (transfer full): a duplicate of @matrix that must be freed with g_free() when no longer needed. * * Since: 1.0 **/ cairo_matrix_t * adg_matrix_dup(const cairo_matrix_t *matrix) { g_return_val_if_fail(matrix != NULL, NULL); return cpml_memdup(matrix, sizeof(cairo_matrix_t)); } /** * adg_matrix_equal: * @matrix1: the first operand * @matrix2: the second operand * * Compares @matrix1 and @matrix2 and returns TRUE * if the matrices are equal. * * Returns: TRUE if @matrix1 is equal to @matrix2, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_matrix_equal(const cairo_matrix_t *matrix1, const cairo_matrix_t *matrix2) { g_return_val_if_fail(matrix1 != NULL, FALSE); g_return_val_if_fail(matrix2 != NULL, FALSE); return matrix1->xx == matrix2->xx && matrix1->yx == matrix2->yx && matrix1->xy == matrix2->xy && matrix1->yy == matrix2->yy && matrix1->x0 == matrix2->x0 && matrix1->y0 == matrix2->y0; } /** * adg_matrix_normalize: * @matrix: (inout): the source/destination #cairo_matrix_t * * Gets rid of the scaling component of a matrix. * * Returns: TRUE on success, FALSE on errors. * * Since: 1.0 **/ gboolean adg_matrix_normalize(cairo_matrix_t *matrix) { gdouble k; g_return_val_if_fail(matrix != NULL, FALSE); if (matrix->xx != matrix->yy || matrix->xy != -matrix->yx) { g_warning(_("%s: normalization of anamorphic matrices not supported"), G_STRLOC); return FALSE; } if (matrix->xy == 0) { k = matrix->xx; } else if (matrix->xx == 0) { k = matrix->xy; } else { k = sqrt(matrix->xx * matrix->xx + matrix->xy * matrix->xy); } g_return_val_if_fail(k != 0, FALSE); matrix->xx /= k; matrix->xy /= k; matrix->yy /= k; matrix->yx /= k; return TRUE; } /** * adg_matrix_transform: * @matrix: (inout): the source/destination #cairo_matrix_t * @transformation: the transformation to apply * @mode: (in): how @transformation should be applied * * Modifies @matrix applying @transformation in the way specified by * @mode. * * Since: 1.0 **/ void adg_matrix_transform(cairo_matrix_t *matrix, const cairo_matrix_t *transformation, AdgTransformMode mode) { cairo_matrix_t normalized; g_return_if_fail(matrix != NULL); g_return_if_fail(transformation != NULL); switch (mode) { case ADG_TRANSFORM_NONE: break; case ADG_TRANSFORM_BEFORE: cairo_matrix_multiply(matrix, transformation, matrix); break; case ADG_TRANSFORM_AFTER: cairo_matrix_multiply(matrix, matrix, transformation); break; case ADG_TRANSFORM_BEFORE_NORMALIZED: adg_matrix_copy(&normalized, transformation); adg_matrix_normalize(&normalized); cairo_matrix_multiply(matrix, &normalized, matrix); break; case ADG_TRANSFORM_AFTER_NORMALIZED: adg_matrix_copy(&normalized, transformation); adg_matrix_normalize(&normalized); cairo_matrix_multiply(matrix, matrix, &normalized); break; default: g_return_if_reached(); } } /** * adg_matrix_dump: * @matrix: an #cairo_matrix_t * * Dumps the specified @matrix to stdout. Useful for debugging purposes. * * Since: 1.0 **/ void adg_matrix_dump(const cairo_matrix_t *matrix) { g_return_if_fail(matrix != NULL); g_print("[%8.3lf %8.3lf] [%8.3lf]\n" "[%8.3lf %8.3lf] [%8.3lf]\n", matrix->xx, matrix->xy, matrix->x0, matrix->yx, matrix->yy, matrix->y0); } diff --git a/src/adg/adg-matrix.h b/src/adg/adg-matrix.h index b354ff74..88a0d6fd 100644 --- a/src/adg/adg-matrix.h +++ b/src/adg/adg-matrix.h @@ -1,50 +1,50 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_MATRIX_H__ #define __ADG_MATRIX_H__ G_BEGIN_DECLS const cairo_matrix_t * adg_matrix_identity (void); const cairo_matrix_t * adg_matrix_null (void); void adg_matrix_copy (cairo_matrix_t *matrix, const cairo_matrix_t *src); cairo_matrix_t *adg_matrix_dup (const cairo_matrix_t *matrix); gboolean adg_matrix_equal (const cairo_matrix_t *matrix1, const cairo_matrix_t *matrix2); gboolean adg_matrix_normalize (cairo_matrix_t *matrix); void adg_matrix_transform (cairo_matrix_t *matrix, const cairo_matrix_t *transformation, AdgTransformMode mode); void adg_matrix_dump (const cairo_matrix_t *matrix); G_END_DECLS #endif /* __ADG_MATRIX_H__ */ diff --git a/src/adg/adg-model-private.h b/src/adg/adg-model-private.h index 1f969675..423086b7 100644 --- a/src/adg/adg-model-private.h +++ b/src/adg/adg-model-private.h @@ -1,44 +1,44 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_MODEL_PRIVATE_H__ #define __ADG_MODEL_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgModelPrivate AdgModelPrivate; typedef struct _AdgWrapperHelper AdgWrapperHelper; struct _AdgModelPrivate { GSList *dependencies; GHashTable *named_pairs; }; struct _AdgWrapperHelper { AdgNamedPairFunc callback; AdgModel *model; gpointer user_data; }; G_END_DECLS #endif /* __ADG_MODEL_PRIVATE_H__ */ diff --git a/src/adg/adg-model.c b/src/adg/adg-model.c index 493f6bae..2b6d387c 100644 --- a/src/adg/adg-model.c +++ b/src/adg/adg-model.c @@ -1,767 +1,767 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-model * @short_description: The base class of the ADG model infrastructure * * A model is a conceptual representation of something. From an ADG * user point of view, it is a collection of data and rules that * defines how an object should be represented on a drawing. * * Because #AdgModel instances are only a conceptual idea, they are * not renderable (that is, #AdgModel is not derived from #AdgEntity). * Instead, it must be passed as subject to entities such as #AdgStroke * or #AdgHatch. * * The relationships between model and view are handled by dependencies: * whenever an #AdgModel changes (that is the #AdgModel::changed signal is * emitted), every dependency of the model (#AdgEntity instances) is * invalidated with adg_entity_invalidate(). * * To help the interaction between model and view another concept is * introduced: named pairs. This provides a way to abstract real values (the * coordinates stored in #CpmlPair) by accessing them using a string. To easily * the access of named pairs from the view, use #AdgPoint instead of #CpmlPair. * * Since: 1.0 **/ /** * AdgModel: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ /** * AdgModelClass: * @named_pair: virtual method that returns the #CpmlPair bound to a * given name. * @set_named_pair: signal for defining or undefining a new named pair. * @clear: signal for removing the internal cache data, if any. * @reset: signal used to redefine a model from scratch. * @add_dependency: signal for adding a new dependency. * @remove_dependency: signal used to remove an old dependency. * @changed: signal for emitting an #AdgModel::changed signal. * * * The default @named_pair implementation looks up the #CpmlPair in an internal * #GHashTable that uses the pair name as key and the #CpmlPair struct as value. * * The default @set_named_pair implementation can be used for either adding * (if the #CpmlPair is not NULL) or removing (if #CpmlPair * is NULL) an item from the named pairs hash table. * * The default handler for @clear signals does not do anything. * * The default @reset involves the clearing of the internal cache data * (done by emitting the #AdgModel::clear signal) and the destruction of the * internal named pair hash table. * * The default @add_dependency and @remove_dependency implementations add and * remove items from an internal #GSList of #AdgEntity. * * The default handler of the @changed signal calls adg_entity_invalidate() * on every dependency by using adg_model_foreach_dependency(). * * Since: 1.0 **/ /** * AdgDependencyFunc: * @model: the #AdgModel * @entity: the #AdgEntity dependent on @model * @user_data: a general purpose pointer * * Callback used by adg_model_foreach_dependency(). * * Since: 1.0 **/ /** * AdgNamedPairFunc: * @model: the #AdgModel * @name: the name of the named pair * @pair: an #CpmlPair * @user_data: a general purpose pointer * * Callback used by adg_model_foreach_named_pair(). * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-model.h" #include "adg-model-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_model_parent_class) G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(AdgModel, adg_model, G_TYPE_OBJECT) enum { PROP_0, PROP_DEPENDENCY }; enum { ADD_DEPENDENCY, REMOVE_DEPENDENCY, SET_NAMED_PAIR, CLEAR, RESET, CHANGED, LAST_SIGNAL }; static void _adg_dispose (GObject *object); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void _adg_add_dependency (AdgModel *model, AdgEntity *entity); static void _adg_remove_dependency (AdgModel *model, AdgEntity *entity); static const CpmlPair * _adg_named_pair (AdgModel *model, const gchar *name); static void _adg_reset (AdgModel *model); static void _adg_set_named_pair (AdgModel *model, const gchar *name, const CpmlPair *pair); static void _adg_changed (AdgModel *model); static void _adg_named_pair_wrapper (gpointer key, gpointer value, gpointer user_data); static void _adg_invalidate_wrapper (AdgModel *model, AdgEntity *entity, gpointer user_data); static guint _adg_signals[LAST_SIGNAL] = { 0 }; static void adg_model_class_init(AdgModelClass *klass) { GObjectClass *gobject_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->set_property = _adg_set_property; klass->add_dependency = _adg_add_dependency; klass->remove_dependency = _adg_remove_dependency; klass->named_pair = _adg_named_pair; klass->set_named_pair = _adg_set_named_pair; klass->clear = NULL; klass->reset = _adg_reset; klass->changed = _adg_changed; param = g_param_spec_object("dependency", P_("Dependency"), P_("Can be used to add a new dependency from this model (this entity will be invalidated on model changed)"), ADG_TYPE_ENTITY, G_PARAM_WRITABLE); g_object_class_install_property(gobject_class, PROP_DEPENDENCY, param); /** * AdgModel::add-dependency: * @model: an #AdgModel * @entity: an #AdgEntity that depends on @model * * Adds @entity to @model. After that @entity will depend on @model, * that is #AdgModel::changed on @model will invalidate @entity. * * Since: 1.0 **/ _adg_signals[ADD_DEPENDENCY] = g_signal_new("add-dependency", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AdgModelClass, add_dependency), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, ADG_TYPE_ENTITY); /** * AdgModel::remove-dependency: * @model: an #AdgModel * @entity: the #AdgEntity that does not depend on @model anymore * * Removes the @entity from @model, that is @entity will not depend * on @model anymore. * * Since: 1.0 **/ _adg_signals[REMOVE_DEPENDENCY] = g_signal_new("remove-dependency", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AdgModelClass, remove_dependency), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, ADG_TYPE_ENTITY); /** * AdgModel::set-named-pair: * @model: an #AdgModel * @name: an arbitrary name * @pair: an #CpmlPair * * Adds, updates or deletes a named pair, accordling to the given * parameters. * * If @pair is NULL, the @name named pair is * searched and deleted. If it is not found, a warning is raised. * * Otherwise, the @name named pair is searched: if it is found, * its data are updated with @pair. If it is not found, a new * named pair is created using @name and @pair. * * Since: 1.0 **/ _adg_signals[SET_NAMED_PAIR] = g_signal_new("set-named-pair", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AdgModelClass, set_named_pair), NULL, NULL, adg_marshal_VOID__STRING_POINTER, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_POINTER); /** * AdgModel::clear: * @model: an #AdgModel * * * This signal is only useful in model implementations. * * * Removes any information from @model cached by the implementation * code. Useful to force a recomputation of the cache when something * in the model has changed. * * Since: 1.0 **/ _adg_signals[CLEAR] = g_signal_new("clear", ADG_TYPE_MODEL, G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET(AdgModelClass, clear), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * AdgModel::reset: * @model: an #AdgModel * * Resets the state of @model by destroying any named pair * associated to it. This step also involves the emission of the * #AdgModel::clear signal. * * This signal is intended to be used while redefining the model. * A typical usage would be in these terms: * * * adg_model_reset(model); * // Definition of model. This also requires the redefinition of * // the named pairs because the old ones have been destroyed. * ... * adg_model_changed(model); * * * Since: 1.0 **/ _adg_signals[RESET] = g_signal_new("reset", ADG_TYPE_MODEL, G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET(AdgModelClass, reset), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * AdgModel::changed: * @model: an #AdgModel * * Notificates that the model has changed. By default, all the * dependent entities are invalidated. * * Since: 1.0 **/ _adg_signals[CHANGED] = g_signal_new("changed", ADG_TYPE_MODEL, G_SIGNAL_RUN_LAST|G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET(AdgModelClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void adg_model_init(AdgModel *model) { AdgModelPrivate *data = adg_model_get_instance_private(model); data->dependencies = NULL; } static void _adg_dispose(GObject *object) { static gboolean is_disposed = FALSE; if (G_UNLIKELY(!is_disposed)) { AdgModel *model = (AdgModel *) object; AdgModelPrivate *data = adg_model_get_instance_private(model); AdgEntity *entity; /* Remove all the dependencies: this will emit a * "remove-dependency" signal for every dependency, dropping * all references from entities to this model */ while (data->dependencies != NULL) { entity = (AdgEntity *) data->dependencies->data; adg_model_remove_dependency(model, entity); } g_signal_emit(model, _adg_signals[RESET], 0); } if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_DEPENDENCY: g_signal_emit(object, _adg_signals[ADD_DEPENDENCY], 0, g_value_get_object(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); } } /** * adg_model_add_dependency: * @model: an #AdgModel * @entity: an #AdgEntity * * * This function is only useful in entity implementations. * * * Emits a #AdgModel::add-dependency signal on @model passing @entity * as argument. This will add a reference to @entity owned by @model. * * Since: 1.0 **/ void adg_model_add_dependency(AdgModel *model, AdgEntity *entity) { g_return_if_fail(ADG_IS_MODEL(model)); g_return_if_fail(ADG_IS_ENTITY(entity)); g_object_set(model, "dependency", entity, NULL); } /** * adg_model_remove_dependency: * @model: an #AdgModel * @entity: an #AdgEntity * * * This function is only useful in entity implementations. * * * Emits a #AdgModel::remove-dependency signal on @model passing * @entity as argument. @entity must be inside @model. * * Note that @model will own a reference to @entity and it * may be the last reference held: this means removing an entity * from the model can destroy it. * * Since: 1.0 **/ void adg_model_remove_dependency(AdgModel *model, AdgEntity *entity) { g_return_if_fail(ADG_IS_MODEL(model)); g_return_if_fail(ADG_IS_ENTITY(entity)); g_signal_emit(model, _adg_signals[REMOVE_DEPENDENCY], 0, entity); } /** * adg_model_get_dependencies: * @model: an #AdgModel * * Gets the list of entities dependending on @model. This list * is owned by @model and must not be modified or freed. * * Returns: (transfer none) (element-type Adg.Entity): a #GSList of dependencies or NULL on error. * * Since: 1.0 **/ const GSList * adg_model_get_dependencies(AdgModel *model) { AdgModelPrivate *data; g_return_val_if_fail(ADG_IS_MODEL(model), NULL); data = adg_model_get_instance_private(model); return data->dependencies; } /** * adg_model_foreach_dependency: * @model: an #AdgModel * @callback: (scope call): the entity callback * @user_data: general purpose user data passed "as is" to @callback * * Invokes @callback on each entity linked to @model. * * Since: 1.0 **/ void adg_model_foreach_dependency(AdgModel *model, AdgDependencyFunc callback, gpointer user_data) { AdgModelPrivate *data; GSList *dependency; AdgEntity *entity; g_return_if_fail(ADG_IS_MODEL(model)); g_return_if_fail(callback != NULL); data = adg_model_get_instance_private(model); dependency = data->dependencies; while (dependency) { entity = dependency->data; if (entity != NULL && ADG_IS_ENTITY(entity)) callback(model, entity, user_data); dependency = dependency->next; } } /** * adg_model_set_named_pair: * @model: an #AdgModel * @name: the name to associate to the pair * @pair: the #CpmlPair * * * This function is only useful in model definitions, such as * inside an #AdgTrailCallback function or while constructing * an #AdgPath instance. * * * Emits a #AdgModel::set-named-pair signal on @model passing * @name and @pair as arguments. * * Since: 1.0 **/ void adg_model_set_named_pair(AdgModel *model, const gchar *name, const CpmlPair *pair) { g_return_if_fail(ADG_IS_MODEL(model)); g_return_if_fail(name != NULL); g_signal_emit(model, _adg_signals[SET_NAMED_PAIR], 0, name, pair); } /** * adg_model_set_named_pair_explicit: * @model: an #AdgModel * @name: the name to associate to the pair * @x: the x coordinate of the point * @y: the y coordinate of the point * * * This function is only useful in model definitions, such as * inside an #AdgTrailCallback function or while constructing * an #AdgPath instance. * * * Convenient wrapper on adg_model_set_named_pair() that accepts * explicit coordinates. * * Since: 1.0 **/ void adg_model_set_named_pair_explicit(AdgModel *model, const gchar *name, gdouble x, gdouble y) { CpmlPair pair; pair.x = x; pair.y = y; adg_model_set_named_pair(model, name, &pair); } /** * adg_model_get_named_pair: * @model: an #AdgModel * @name: the name of the pair to get * * Gets the @name named pair associated to @model. The returned * pair is owned by @model and must not be modified or freed. * * Returns: the requested #CpmlPair or NULL if not found. * * Since: 1.0 **/ const CpmlPair * adg_model_get_named_pair(AdgModel *model, const gchar *name) { AdgModelClass *klass; g_return_val_if_fail(ADG_IS_MODEL(model), NULL); g_return_val_if_fail(name != NULL, NULL); klass = ADG_MODEL_GET_CLASS(model); if (klass->named_pair == NULL) return NULL; return klass->named_pair(model, name); } /** * adg_model_foreach_named_pair: * @model: an #AdgModel * @callback: (scope call): the named pair callback * @user_data: general purpose user data passed "as is" to @callback * * Invokes @callback for each named pair set on @model. This can * be used, for example, to retrieve all the named pairs of a @model * or to duplicate a transformed version of every named pair. * * Since: 1.0 **/ void adg_model_foreach_named_pair(AdgModel *model, AdgNamedPairFunc callback, gpointer user_data) { AdgModelPrivate *data; AdgWrapperHelper helper; g_return_if_fail(ADG_IS_MODEL(model)); g_return_if_fail(callback != NULL); data = adg_model_get_instance_private(model); if (data->named_pairs == NULL) return; helper.callback = callback; helper.model = model; helper.user_data = user_data; g_hash_table_foreach(data->named_pairs, _adg_named_pair_wrapper, &helper); } /** * adg_model_clear: * @model: an #AdgModel * * * This function is only useful new model implementations. * * * Emits the #AdgModel::clear signal on @model. * * Since: 1.0 **/ void adg_model_clear(AdgModel *model) { g_return_if_fail(ADG_IS_MODEL(model)); g_signal_emit(model, _adg_signals[CLEAR], 0); } /** * adg_model_reset: * @model: an #AdgModel * * Emits the #AdgModel::reset signal on @model. * * Since: 1.0 **/ void adg_model_reset(AdgModel *model) { g_return_if_fail(ADG_IS_MODEL(model)); g_signal_emit(model, _adg_signals[RESET], 0); } /** * adg_model_changed: * @model: an #AdgModel * * * This function is only useful in entity implementations. * * * Emits the #AdgModel::changed signal on @model. * * Since: 1.0 **/ void adg_model_changed(AdgModel *model) { g_return_if_fail(ADG_IS_MODEL(model)); g_signal_emit(model, _adg_signals[CHANGED], 0); } static void _adg_add_dependency(AdgModel *model, AdgEntity *entity) { AdgModelPrivate *data; /* Do not add NULL values */ if (entity == NULL) return; data = adg_model_get_instance_private(model); /* The prepend operation is more efficient */ data->dependencies = g_slist_prepend(data->dependencies, entity); g_object_ref(entity); } static void _adg_remove_dependency(AdgModel *model, AdgEntity *entity) { AdgModelPrivate *data = adg_model_get_instance_private(model); GSList *node = g_slist_find(data->dependencies, entity); if (node == NULL) { g_warning(_("%s: attempting to remove the nonexistent dependency " "on the entity with type %s from a model of type %s"), G_STRLOC, g_type_name(G_OBJECT_TYPE(entity)), g_type_name(G_OBJECT_TYPE(model))); return; } data->dependencies = g_slist_delete_link(data->dependencies, node); g_object_unref(entity); } static void _adg_reset(AdgModel *model) { AdgModelPrivate *data = adg_model_get_instance_private(model); adg_model_clear(model); if (data->named_pairs) { g_hash_table_destroy(data->named_pairs); data->named_pairs = NULL; } } static void _adg_set_named_pair(AdgModel *model, const gchar *name, const CpmlPair *pair) { AdgModelPrivate *data = adg_model_get_instance_private(model); GHashTable **hash = &data->named_pairs; gchar *key; CpmlPair *value; if (pair == NULL) { /* Delete mode: raise a warning if @name is not found */ if (*hash == NULL || !g_hash_table_remove(*hash, name)) g_warning(_("%s: attempting to remove nonexistent '%s' named pair"), G_STRLOC, name); return; } /* Insert or update mode */ key = g_strdup(name); value = cpml_pair_dup(pair); if (*hash == NULL) *hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); g_hash_table_insert(*hash, key, value); } static const CpmlPair * _adg_named_pair(AdgModel *model, const gchar *name) { AdgModelPrivate *data = adg_model_get_instance_private(model); if (data->named_pairs == NULL) return NULL; return g_hash_table_lookup(data->named_pairs, name); } static void _adg_changed(AdgModel *model) { /* Invalidate all the entities dependent on this model */ adg_model_foreach_dependency(model, _adg_invalidate_wrapper, NULL); } static void _adg_named_pair_wrapper(gpointer key, gpointer value, gpointer user_data) { const gchar *name; CpmlPair *pair; AdgWrapperHelper *helper; name = key; pair = value; helper = user_data; helper->callback(helper->model, name, pair, helper->user_data); } static void _adg_invalidate_wrapper(AdgModel *model, AdgEntity *entity, gpointer user_data) { adg_entity_invalidate(entity); } diff --git a/src/adg/adg-model.h b/src/adg/adg-model.h index 35e0e6d7..b18f6dbd 100644 --- a/src/adg/adg-model.h +++ b/src/adg/adg-model.h @@ -1,108 +1,108 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_MODEL_H__ #define __ADG_MODEL_H__ G_BEGIN_DECLS #define ADG_TYPE_MODEL (adg_model_get_type()) #define ADG_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_MODEL, AdgModel)) #define ADG_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_MODEL, AdgModel)) #define ADG_IS_MODEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_MODEL)) #define ADG_IS_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_MODEL)) #define ADG_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_MODEL, AdgModelClass)) typedef struct _AdgModel AdgModel; typedef struct _AdgModelClass AdgModelClass; typedef void (*AdgDependencyFunc) (AdgModel *model, AdgEntity *entity, gpointer user_data); typedef void (*AdgNamedPairFunc) (AdgModel *model, const gchar *name, CpmlPair *pair, gpointer user_data); struct _AdgModel { /*< private >*/ GObject parent; }; struct _AdgModelClass { /*< private >*/ GObjectClass parent_class; /*< public >*/ /* Virtual table */ const CpmlPair * (*named_pair) (AdgModel *model, const gchar *name); /* Signals */ void (*set_named_pair) (AdgModel *model, const gchar *name, const CpmlPair *pair); void (*clear) (AdgModel *model); void (*reset) (AdgModel *model); void (*add_dependency) (AdgModel *model, AdgEntity *entity); void (*remove_dependency) (AdgModel *model, AdgEntity *entity); void (*changed) (AdgModel *model); }; GType adg_model_get_type (void); void adg_model_add_dependency (AdgModel *model, AdgEntity *entity); void adg_model_remove_dependency (AdgModel *model, AdgEntity *entity); const GSList * adg_model_get_dependencies (AdgModel *model); void adg_model_foreach_dependency (AdgModel *model, AdgDependencyFunc callback, gpointer user_data); void adg_model_set_named_pair (AdgModel *model, const gchar *name, const CpmlPair *pair); void adg_model_set_named_pair_explicit (AdgModel *model, const gchar *name, gdouble x, gdouble y); const CpmlPair *adg_model_get_named_pair (AdgModel *model, const gchar *name); void adg_model_foreach_named_pair (AdgModel *model, AdgNamedPairFunc callback, gpointer user_data); void adg_model_clear (AdgModel *model); void adg_model_reset (AdgModel *model); void adg_model_changed (AdgModel *model); G_END_DECLS #endif /* __ADG_MODEL_H__ */ diff --git a/src/adg/adg-pango-style-private.h b/src/adg/adg-pango-style-private.h index 8b3ec51f..9f0643bb 100644 --- a/src/adg/adg-pango-style-private.h +++ b/src/adg/adg-pango-style-private.h @@ -1,37 +1,37 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_PANGO_STYLE_PRIVATE_H__ #define __ADG_PANGO_STYLE_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgPangoStylePrivate AdgPangoStylePrivate; struct _AdgPangoStylePrivate { PangoFontDescription *font_description; gint spacing; }; G_END_DECLS #endif /* __ADG_PANGO_STYLE_PRIVATE_H__ */ diff --git a/src/adg/adg-pango-style.c b/src/adg/adg-pango-style.c index 214ec2a2..c1b6f0d9 100644 --- a/src/adg/adg-pango-style.c +++ b/src/adg/adg-pango-style.c @@ -1,293 +1,293 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-pango-style * @short_description: A font style with pango support * * Adds pango support to the #AdgFontStyle class. * * Since: 1.0 */ /** * AdgPangoStyle: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include #include "adg-style.h" #include "adg-dress.h" #include "adg-font-style.h" #include "adg-pango-style.h" #include "adg-pango-style-private.h" #define _ADG_OLD_STYLE_CLASS ((AdgStyleClass *) adg_pango_style_parent_class) G_DEFINE_TYPE_WITH_PRIVATE(AdgPangoStyle, adg_pango_style, ADG_TYPE_FONT_STYLE) enum { PROP_0, PROP_SPACING }; static void _adg_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void _adg_invalidate (AdgStyle *style); static void _adg_apply (AdgStyle *style, AdgEntity *entity, cairo_t *cr); static void adg_pango_style_class_init(AdgPangoStyleClass *klass) { GObjectClass *gobject_class; AdgStyleClass *style_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; style_class = (AdgStyleClass *) klass; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; style_class->invalidate = _adg_invalidate; style_class->apply = _adg_apply; param = g_param_spec_int("spacing", P_("Spacing"), P_("Amount of spacing between lines on multiline text"), G_MININT, G_MAXINT, 0, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_SPACING, param); } static void adg_pango_style_init(AdgPangoStyle *pango_style) { AdgPangoStylePrivate *data = adg_pango_style_get_instance_private(pango_style); data->font_description = NULL; data->spacing = 0; } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgPangoStylePrivate *data = adg_pango_style_get_instance_private((AdgPangoStyle *) object); switch (prop_id) { case PROP_SPACING: g_value_set_int(value, data->spacing); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgPangoStylePrivate *data = adg_pango_style_get_instance_private((AdgPangoStyle *) object); switch (prop_id) { case PROP_SPACING: data->spacing = g_value_get_int(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_pango_style_new: * * Constructs a new pango style initialized with default params. * * Returns: a newly created pango style * * Since: 1.0 **/ AdgPangoStyle * adg_pango_style_new(void) { return g_object_new(ADG_TYPE_PANGO_STYLE, NULL); } /** * adg_pango_style_get_description: * @pango_style: an #AdgPangoStyle object * * Gets the #PangoFontDescription of @pango_style. The returned font is * owned by @pango_style and must not be destroyed by the caller. * * Returns: the font description * * Since: 1.0 **/ PangoFontDescription * adg_pango_style_get_description(AdgPangoStyle *pango_style) { AdgPangoStylePrivate *data; g_return_val_if_fail(ADG_IS_PANGO_STYLE(pango_style), NULL); data = adg_pango_style_get_instance_private(pango_style); if (data->font_description == NULL) { AdgFontStyle *font_style; const gchar *family; cairo_font_slant_t slant; cairo_font_weight_t weight; gdouble size; font_style = (AdgFontStyle *) pango_style; family = adg_font_style_get_family(font_style); slant = adg_font_style_get_slant(font_style); weight = adg_font_style_get_weight(font_style); size = adg_font_style_get_size(font_style); data->font_description = pango_font_description_new(); pango_font_description_set_family(data->font_description, family); switch (slant) { case CAIRO_FONT_SLANT_NORMAL: pango_font_description_set_style(data->font_description, PANGO_STYLE_NORMAL); break; case CAIRO_FONT_SLANT_ITALIC: pango_font_description_set_style(data->font_description, PANGO_STYLE_ITALIC); break; case CAIRO_FONT_SLANT_OBLIQUE: pango_font_description_set_style(data->font_description, PANGO_STYLE_OBLIQUE); break; default: g_warning(_("Unhandled slant value (%d)"), slant); } switch (weight) { case CAIRO_FONT_WEIGHT_NORMAL: pango_font_description_set_weight(data->font_description, PANGO_WEIGHT_NORMAL); break; case CAIRO_FONT_WEIGHT_BOLD: pango_font_description_set_weight(data->font_description, PANGO_WEIGHT_BOLD); break; default: g_warning(_("Unhandled weight value (%d)"), weight); } pango_font_description_set_size(data->font_description, size * PANGO_SCALE); } return data->font_description; } /** * adg_pango_style_set_spacing: * @pango_style: an #AdgPangoStyle object * @spacing: the new spacing * * Sets the new spacing to @spacing on @pango_style. * * Since: 1.0 **/ void adg_pango_style_set_spacing(AdgPangoStyle *pango_style, gint spacing) { g_return_if_fail(ADG_IS_PANGO_STYLE(pango_style)); g_object_set(pango_style, "spacing", spacing, NULL); } /** * adg_pango_style_get_spacing: * @pango_style: an #AdgPangoStyle object * * Gets the spacing of @pango_style. * * Returns: (type gint): the current spacing value. * * Since: 1.0 **/ gint adg_pango_style_get_spacing(AdgPangoStyle *pango_style) { AdgPangoStylePrivate *data; g_return_val_if_fail(ADG_IS_PANGO_STYLE(pango_style), 0); data = adg_pango_style_get_instance_private(pango_style); return data->spacing; } static void _adg_invalidate(AdgStyle *style) { AdgPangoStyle *pango_style = (AdgPangoStyle *) style; AdgPangoStylePrivate *data = adg_pango_style_get_instance_private(pango_style); if (data->font_description != NULL) { pango_font_description_free(data->font_description); data->font_description = NULL; } if (_ADG_OLD_STYLE_CLASS->invalidate != NULL) _ADG_OLD_STYLE_CLASS->invalidate(style); } static void _adg_apply(AdgStyle *style, AdgEntity *entity, cairo_t *cr) { AdgFontStyle *font_style; AdgDress color_dress; font_style = (AdgFontStyle *) style; color_dress = adg_font_style_get_color_dress(font_style); adg_entity_apply_dress(entity, color_dress, cr); } diff --git a/src/adg/adg-pango-style.h b/src/adg/adg-pango-style.h index 5e70456e..305e034b 100644 --- a/src/adg/adg-pango-style.h +++ b/src/adg/adg-pango-style.h @@ -1,66 +1,66 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_PANGO_STYLE__ #define __ADG_PANGO_STYLE__ G_BEGIN_DECLS #define ADG_TYPE_PANGO_STYLE (adg_pango_style_get_type()) #define ADG_PANGO_STYLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_PANGO_STYLE, AdgPangoStyle)) #define ADG_PANGO_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_PANGO_STYLE, AdgPangoStyleClass)) #define ADG_IS_PANGO_STYLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_PANGO_STYLE)) #define ADG_IS_PANGO_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_PANGO_STYLE)) #define ADG_PANGO_STYLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_PANGO_STYLE, AdgPangoStyleClass)) typedef struct _AdgPangoStyle AdgPangoStyle; typedef struct _AdgPangoStyleClass AdgPangoStyleClass; struct _AdgPangoStyle { /*< private >*/ AdgFontStyle parent; }; struct _AdgPangoStyleClass { /*< private >*/ AdgFontStyleClass parent_class; }; GType adg_pango_style_get_type (void); AdgPangoStyle * adg_pango_style_new (void); PangoFontDescription * adg_pango_style_get_description (AdgPangoStyle *pango_style); gint adg_pango_style_get_spacing (AdgPangoStyle *pango_style); void adg_pango_style_set_spacing (AdgPangoStyle *pango_style, gint spacing); G_END_DECLS #endif /* __ADG_PANGO_STYLE_H__ */ diff --git a/src/adg/adg-param-dress.c b/src/adg/adg-param-dress.c index 5f3d1d39..ffe305a9 100644 --- a/src/adg/adg-param-dress.c +++ b/src/adg/adg-param-dress.c @@ -1,168 +1,168 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-param-dress * @Section_Id:AdgParamDress * @title: AdgParamDress * @short_description: Metadata for dress specification * * The %ADG_TYPE_PARAM_DRESS type is a parameter specification * compatible with %G_TYPE_PARAM_ENUM that provides additional * validation: it rejects values that are incompatibles (that * is, that are not related) with the current one. Check the * adg_dress_are_related() documentation for details on what * related means. * * Internally, the value setting is performed by calling the * adg_dress_set() API. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-dress.h" #include "adg-param-dress.h" #define ADG_PARAM_SPEC_DRESS(pspec) (G_TYPE_CHECK_INSTANCE_CAST((pspec), ADG_TYPE_PARAM_DRESS, AdgParamSpecDress)) typedef struct _AdgParamSpecDress AdgParamSpecDress; struct _AdgParamSpecDress { GParamSpecEnum parent; AdgDress source_dress; }; static void _adg_param_dress_init (GParamSpec *pspec); static void _adg_param_dress_set_default (GParamSpec *pspec, GValue *value); static gboolean _adg_param_dress_validate (GParamSpec *pspec, GValue *value); static gint _adg_param_dress_cmp (GParamSpec *pspec, const GValue *value1, const GValue *value2); GType adg_param_dress_get_type(void) { static GType type = 0; if (G_UNLIKELY(type == 0)) { /* const */ GParamSpecTypeInfo pspec_info = { sizeof(AdgParamSpecDress), /* instance_size */ 0, /* n_preallocs */ _adg_param_dress_init, G_TYPE_INVALID, /* value_type */ NULL, /* finalize */ _adg_param_dress_set_default, _adg_param_dress_validate, _adg_param_dress_cmp, }; pspec_info.value_type = ADG_TYPE_DRESS; type = g_param_type_register_static(g_intern_static_string("AdgParamDress"), &pspec_info); } return type; } /** * adg_param_spec_dress: * @name: canonical name * @nick: nickname of the param * @blurb: brief desciption * @dress: the #AdgDress dress * @flags: a combination of #GParamFlags * * Creates a param spec to hold a dress value. This is similar to * g_param_spec_enum() but rejects a new dress value if it is not * related with the old one. The setting is performed via * adg_dress_set(), so check its documentation for details. * * Returns: (transfer full): the newly allocated #GParamSpec. * * Since: 1.0 **/ GParamSpec * adg_param_spec_dress(const gchar *name, const gchar *nick, const gchar *blurb, AdgDress dress, GParamFlags flags) { AdgParamSpecDress *dspec; dspec = g_param_spec_internal(ADG_TYPE_PARAM_DRESS, name, nick, blurb, flags); dspec->source_dress = dress; return (GParamSpec *) dspec; } static void _adg_param_dress_init(GParamSpec *pspec) { AdgParamSpecDress *dspec = ADG_PARAM_SPEC_DRESS(pspec); dspec->source_dress = ADG_DRESS_UNDEFINED; } static void _adg_param_dress_set_default(GParamSpec *pspec, GValue *value) { value->data[0].v_long = ADG_PARAM_SPEC_DRESS(pspec)->source_dress; } static gboolean _adg_param_dress_validate(GParamSpec *pspec, GValue *value) { AdgParamSpecDress *dspec; AdgDress *dress; AdgDress wanted_dress; dspec = ADG_PARAM_SPEC_DRESS(pspec); dress = (AdgDress *) &value->data[0].v_long; wanted_dress = *dress; /* Fallback to the source dress, returned in case of errors */ *dress = dspec->source_dress; /* This method will fail (that is, it leaves *dress untouched) * if the current *dress value (source_dress) and wanted_dress * are not related */ adg_dress_set(dress, wanted_dress); return *dress != wanted_dress; } static gint _adg_param_dress_cmp(GParamSpec *pspec, const GValue *value1, const GValue *value2) { glong v1 = value1->data[0].v_long; glong v2 = value2->data[0].v_long; return v1 < v2 ? -1 : (v1 > v2); } diff --git a/src/adg/adg-param-dress.h b/src/adg/adg-param-dress.h index 8e3b077a..0bc36385 100644 --- a/src/adg/adg-param-dress.h +++ b/src/adg/adg-param-dress.h @@ -1,46 +1,46 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_PARAM_DRESS_H__ #define __ADG_PARAM_DRESS_H__ G_BEGIN_DECLS #define ADG_TYPE_PARAM_DRESS (adg_param_dress_get_type()) #define ADG_IS_PARAM_DRESS(pspec) (G_TYPE_CHECK_INSTANCE_TYPE((pspec), ADG_TYPE_PARAM_DRESS)) GType adg_param_dress_get_type (void); GParamSpec * adg_param_spec_dress (const gchar *name, const gchar *nick, const gchar *blurb, AdgDress dress, GParamFlags flags); G_END_DECLS #endif /* __ADG_PARAM_DRESS_H__ */ diff --git a/src/adg/adg-path-private.h b/src/adg/adg-path-private.h index 502fe05e..87ee196b 100644 --- a/src/adg/adg-path-private.h +++ b/src/adg/adg-path-private.h @@ -1,74 +1,74 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_PATH_PRIVATE_H__ #define __ADG_PATH_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgNamedPair AdgNamedPair; typedef enum _AdgAction AdgAction; typedef struct _AdgOperation AdgOperation; typedef struct _AdgPathPrivate AdgPathPrivate; struct _AdgNamedPair { const gchar *name; CpmlPair pair; }; enum _AdgAction { ADG_ACTION_NONE, ADG_ACTION_CHAMFER, ADG_ACTION_FILLET }; struct _AdgOperation{ AdgAction action; union { struct { gdouble delta1, delta2; } chamfer; struct { gdouble radius; } fillet; } data; }; struct _AdgPathPrivate { gboolean cp_is_valid; CpmlPair cp; struct { cairo_path_t path; GArray *array; } cairo; CpmlPrimitive last; CpmlPrimitive over; AdgOperation operation; }; G_END_DECLS #endif /* __ADG_PATH_PRIVATE_H__ */ diff --git a/src/adg/adg-path.c b/src/adg/adg-path.c index 30cf620c..13d07bb7 100644 --- a/src/adg/adg-path.c +++ b/src/adg/adg-path.c @@ -1,1592 +1,1592 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-path * @short_description: The basic model representing a generic path * * The #AdgPath model represents a virtual #cairo_path_t: this class * implements methods to create the path and provides additional * operations specific to technical drawings. * * #AdgPath overrides the get_cairo_path method * of the parent #AdgTrail class, avoiding the need of an * #AdgTrailCallback. The path is constructed programmatically: keep * in mind any method that modifies the path will invalidate the * #cairo_path_t returned by adg_trail_get_cairo_path(). * * Although some of the provided methods are clearly based on the * original cairo path manipulation API, their behavior could be * sligthly different. This is intentional, because the ADG provides * additional path manipulation algorithms, sometime quite complex, * and a more restrictive filter on the path quality is required. * Also, the ADG is designed to be used by technicians while cairo * targets a broader range of developers. * * As an example, following the rule of the less surprise, some * cairo functions guess the current point when it is not defined, * while the #AdgPath methods trigger a warning without other effect. * Furthermore, after cairo_close_path() a %CPML_MOVE primitive to * the starting point of the segment is automatically added by cairo; * in ADG, after an adg_path_close() the current point is unset. * * Since: 1.0 **/ /** * AdgPath: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-model.h" #include "adg-trail.h" #include "adg-path.h" #include "adg-path-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_path_parent_class) #define _ADG_OLD_MODEL_CLASS ((AdgModelClass *) adg_path_parent_class) #define REMAPPED(ptr, from, to) \ (ptr) == NULL ? NULL : \ (gpointer) ((guint8 *) (ptr) - (guint8 *) (from) + (guint8 *) (to)) G_DEFINE_TYPE_WITH_PRIVATE(AdgPath, adg_path, ADG_TYPE_TRAIL) static void _adg_finalize (GObject *object); static void _adg_clear (AdgModel *model); static void _adg_clear_parent (AdgModel *model); static void _adg_changed (AdgModel *model); static cairo_path_t * _adg_get_cairo_path (AdgTrail *trail); static cairo_path_t * _adg_read_cairo_path (AdgPath *path); static gint _adg_primitive_length (CpmlPrimitiveType type); static void _adg_primitive_remap (CpmlPrimitive *primitive, gpointer to, const CpmlPrimitive *old, gconstpointer from); static void _adg_rescan (AdgPath *path); static void _adg_append_primitive (AdgPath *path, CpmlPrimitive *primitive); static void _adg_clear_operation (AdgPath *path); static gboolean _adg_append_operation (AdgPath *path, gint action, ...); static void _adg_do_operation (AdgPath *path, cairo_path_data_t *path_data); static void _adg_do_action (AdgPath *path, AdgAction action, CpmlPrimitive *primitive); static void _adg_do_chamfer (AdgPath *path, CpmlPrimitive *current); static void _adg_do_fillet (AdgPath *path, CpmlPrimitive *current); static gboolean _adg_is_convex (const CpmlPrimitive *primitive1, const CpmlPrimitive *primitive2); static const gchar * _adg_action_name (AdgAction action); static void _adg_get_named_pair (AdgModel *model, const gchar *name, CpmlPair *pair, gpointer user_data); static void _adg_dup_reverse_named_pairs (AdgModel *model, const cairo_matrix_t *matrix); static void adg_path_class_init(AdgPathClass *klass) { GObjectClass *gobject_class; AdgModelClass *model_class; AdgTrailClass *trail_class; gobject_class = (GObjectClass *) klass; model_class = (AdgModelClass *) klass; trail_class = (AdgTrailClass *) klass; gobject_class->finalize = _adg_finalize; model_class->clear = _adg_clear; model_class->changed = _adg_changed; trail_class->get_cairo_path = _adg_get_cairo_path; } static void adg_path_init(AdgPath *path) { AdgPathPrivate *data = adg_path_get_instance_private(path); data->cp_is_valid = FALSE; data->cp.x = 0; data->cp.y = 0; data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA; data->cairo.path.data = NULL; data->cairo.path.num_data = 0; data->cairo.array = g_array_new(FALSE, FALSE, sizeof(cairo_path_data_t)); data->last.segment = NULL; data->last.org = NULL; data->last.data = NULL; data->over.segment = NULL; data->over.org = NULL; data->over.data = NULL; data->operation.action = ADG_ACTION_NONE; } static void _adg_finalize(GObject *object) { AdgPath *path = (AdgPath *) object; AdgPathPrivate *data = adg_path_get_instance_private(path); g_array_free(data->cairo.array, TRUE); _adg_clear_operation(path); if (_ADG_OLD_OBJECT_CLASS->finalize) _ADG_OLD_OBJECT_CLASS->finalize(object); } /** * adg_path_new: * * Creates a new path model. The path should be constructed * programmatically by using the methods provided by #AdgPath. * * Returns: the newly created path model * * Since: 1.0 **/ AdgPath * adg_path_new(void) { return g_object_new(ADG_TYPE_PATH, NULL); } /** * adg_path_get_current_point: * @path: an #AdgPath * * Gets the current point of @path, which is conceptually the * final point reached by the path so far. * * If there is no defined current point, NULL is returned. * It is possible to check this in advance with * adg_path_has_current_point(). * * Most #AdgPath methods alter the current point and most of them * expect a current point to be defined otherwise will fail triggering * a warning. Check the description of every method for specific details. * * Returns: (transfer none): the current point or NULL on no current point set or errors. * * Since: 1.0 **/ const CpmlPair * adg_path_get_current_point(AdgPath *path) { AdgPathPrivate *data; g_return_val_if_fail(ADG_IS_PATH(path), NULL); data = adg_path_get_instance_private(path); if (!data->cp_is_valid) return NULL; return &data->cp; } /** * adg_path_has_current_point: * @path: an #AdgPath * * Returns whether a current point is defined on @path. * See adg_path_get_current_point() for details on the current point. * * Returns: whether a current point is defined * * Since: 1.0 **/ gboolean adg_path_has_current_point(AdgPath *path) { AdgPathPrivate *data; g_return_val_if_fail(ADG_IS_PATH(path), FALSE); data = adg_path_get_instance_private(path); return data->cp_is_valid; } /** * adg_path_last_primitive: * @path: an #AdgPath * * Gets the last primitive appended to @path. The %CPML_MOVE type is * not considered a full-fledged primitive, i.e. adg_path_move_to() * or similar does not change the last primitive. * * The returned struct is owned by @path and should not be freed or * modified. * * Returns: (transfer none): a pointer to the last appended primitive or NULL on no last primitive or on errors. * * Since: 1.0 **/ const CpmlPrimitive * adg_path_last_primitive(AdgPath *path) { AdgPathPrivate *data; g_return_val_if_fail(ADG_IS_PATH(path), NULL); data = adg_path_get_instance_private(path); /* Directly return NULL instead of returning an undefined primitive */ if (data->last.org == NULL || data->last.data == NULL) return NULL; return &data->last; } /** * adg_path_over_primitive: * @path: an #AdgPath * * Gets the primitive before the last one appended to @path. The * "over" term comes from forth, where the OVER * operator works on the stack in the same way as * adg_path_over_primitive() works on @path. The %CPML_MOVE type is * not considered a full-fledged primitive, i.e. adg_path_move_to() * or similar does not change the over primitive. * * The returned struct is owned by @path and should not be freed or * modified. * * Returns: (transfer none): a pointer to the primitive before the last appended one or NULL on errors. * * Since: 1.0 **/ const CpmlPrimitive * adg_path_over_primitive(AdgPath *path) { AdgPathPrivate *data; g_return_val_if_fail(ADG_IS_PATH(path), NULL); data = adg_path_get_instance_private(path); /* Directly return NULL instead of returning an undefined primitive */ if (data->over.org == NULL || data->over.data == NULL) return NULL; return &data->over; } /** * adg_path_append: * @path: an #AdgPath * @type: (type CpmlPrimitiveType): a #cairo_data_type_t value * @...: point data, specified as #CpmlPair pointers * * Generic method to append a primitive to @path. The number of #CpmlPair * pointers to pass as @Varargs depends on @type: %CPML_CLOSE does not * require any pair, %CPML_MOVE and %CPML_LINE require one pair, * %CPML_ARC two pairs, %CPML_CURVE three pairs and so on. * * All the needed pairs must be not NULL pointers, * otherwise the function will fail. The pairs in excess, if any, are ignored. * * Since: 1.0 **/ void adg_path_append(AdgPath *path, gint type, ...) { va_list var_args; va_start(var_args, type); adg_path_append_valist(path, (CpmlPrimitiveType) type, var_args); va_end(var_args); } /** * adg_path_append_valist: * @path: an #AdgPath * @type: a #cairo_data_type_t value * @var_args: point data, specified as #CpmlPair pointers * * va_list version of adg_path_append(). * * Since: 1.0 **/ void adg_path_append_valist(AdgPath *path, CpmlPrimitiveType type, va_list var_args) { GPtrArray *array; const CpmlPair *pair; gint length; length = _adg_primitive_length(type); if (length == 0) return; array = g_ptr_array_sized_new(4); while (-- length) { pair = va_arg(var_args, const CpmlPair *); if (pair == NULL) { g_ptr_array_free(array, TRUE); g_return_if_reached(); return; } g_ptr_array_add(array, (gpointer) pair); } /* The array must be NULL terminated */ g_ptr_array_add(array, NULL); adg_path_append_array(path, type, (const CpmlPair **) array->pdata); g_ptr_array_free(array, TRUE); } /** * adg_path_append_array: (rename-to adg_path_append) * @path: an #AdgPath * @type: a #cairo_data_type_t value * @pairs: (array zero-terminated=1) (element-type Cpml.Pair) (transfer none): point data, specified as a NULL terminated array of #CpmlPair pointers. * * A bindingable version of adg_path_append() that uses a * NULL terminated array of pairs instead of variable * argument list and friends. * * Furthermore, because of the list is NULL terminated, * an arbitrary number of pairs can be passed in @pairs. This allows to embed * in a primitive element more data pairs than requested, something impossible * to do with adg_path_append() and adg_path_append_valist(). * * Since: 1.0 **/ void adg_path_append_array(AdgPath *path, CpmlPrimitiveType type, const CpmlPair **pairs) { gint length; GArray *array; const CpmlPair **pair; cairo_path_data_t path_data; g_return_if_fail(ADG_IS_PATH(path)); g_return_if_fail(pairs != NULL); length = _adg_primitive_length(type); if (length == 0) return; array = g_array_new(FALSE, FALSE, sizeof(path_data)); for (pair = pairs; *pair != NULL; ++ pair) { cpml_pair_to_cairo(*pair, &path_data); g_array_append_val(array, path_data); } if (array->len < length - 1) { /* Not enough pairs have been provided */ g_warning(_("%s: null pair caught while parsing arguments"), G_STRLOC); } else { AdgPathPrivate *data = adg_path_get_instance_private(path); CpmlPrimitive primitive; cairo_path_data_t org; /* Save a copy of the current point as primitive origin */ cpml_pair_to_cairo(&data->cp, &org); /* Prepend the cairo header */ path_data.header.type = type; path_data.header.length = array->len + 1; g_array_prepend_val(array, path_data); /* Append a new primitive to @path */ primitive.segment = NULL; primitive.org = &org; primitive.data = (cairo_path_data_t *) array->data; _adg_append_primitive(path, &primitive); } g_array_free(array, TRUE); } /** * adg_path_append_primitive: * @path: an #AdgPath * @primitive: the #CpmlPrimitive to append * * Appends @primitive to @path. The primitive to add is considered the * continuation of the current path so the org * component of @primitive is not used. Anyway the current point is * checked against it: they must be equal or the function will fail * without further processing. * * Since: 1.0 **/ void adg_path_append_primitive(AdgPath *path, const CpmlPrimitive *primitive) { AdgPathPrivate *data; CpmlPrimitive *primitive_dup; g_return_if_fail(ADG_IS_PATH(path)); g_return_if_fail(primitive != NULL); g_return_if_fail(primitive->org != NULL); g_return_if_fail(primitive->data != NULL); data = adg_path_get_instance_private(path); g_return_if_fail(primitive->org->point.x == data->cp.x && primitive->org->point.y == data->cp.y); /* The primitive data could be modified by pending operations: * work on a copy */ primitive_dup = cpml_primitive_deep_dup(primitive); _adg_append_primitive(path, primitive_dup); g_free(primitive_dup); } /** * adg_path_append_segment: * @path: an #AdgPath * @segment: the #CpmlSegment to append * * Appends @segment to @path. * * Since: 1.0 **/ void adg_path_append_segment(AdgPath *path, const CpmlSegment *segment) { g_return_if_fail(ADG_IS_PATH(path)); g_return_if_fail(segment != NULL); if (segment->num_data > 0) { AdgPathPrivate *data; g_return_if_fail(segment->data != NULL); data = adg_path_get_instance_private(path); _adg_clear_parent((AdgModel *) path); data->cairo.array = g_array_append_vals(data->cairo.array, segment->data, segment->num_data); _adg_rescan(path); } } /** * adg_path_append_cairo_path: * @path: an #AdgPath * @cairo_path: the #cairo_path_t path to append * * Appends a whole #cairo_path_t to @path. * * Since: 1.0 **/ void adg_path_append_cairo_path(AdgPath *path, const cairo_path_t *cairo_path) { AdgPathPrivate *data; g_return_if_fail(ADG_IS_PATH(path)); g_return_if_fail(cairo_path != NULL); data = adg_path_get_instance_private(path); _adg_clear_parent((AdgModel *) path); data->cairo.array = g_array_append_vals(data->cairo.array, cairo_path->data, cairo_path->num_data); _adg_rescan(path); } /** * adg_path_append_trail: * @path: an #AdgPath * @trail: an #AdgTrail instance * * Appends the content of @trail to @path. It is similar to * adg_path_append_cairo_path() but it also appends to @path the named pairs * eventually defined in @trail. * * Since: 1.0 **/ void adg_path_append_trail(AdgPath *path, AdgTrail *trail) { GSList *named_pairs; AdgNamedPair *named_pair; g_return_if_fail(ADG_IS_PATH(path)); g_return_if_fail(ADG_IS_TRAIL(trail)); adg_path_append_cairo_path(path, adg_trail_get_cairo_path(trail)); /* Populate named_pairs with all the named pairs of trail */ named_pairs = NULL; adg_model_foreach_named_pair((AdgModel *)trail, _adg_get_named_pair, &named_pairs); /* Readd the pairs to path */ while (named_pairs) { named_pair = (AdgNamedPair *) named_pairs->data; adg_model_set_named_pair((AdgModel *) path, named_pair->name, &named_pair->pair); named_pairs = g_slist_delete_link(named_pairs, named_pairs); } } /** * adg_path_remove_primitive: * @path: an #AdgPath * * Removes the last primitive from @path. * * Since: 1.0 **/ void adg_path_remove_primitive(AdgPath *path) { AdgPathPrivate *data; const CpmlPrimitive *over; guint len; g_return_if_fail(ADG_IS_PATH(path)); data = adg_path_get_instance_private(path); over = adg_path_over_primitive(path); if (over) { cairo_path_data_t *end = over->data + over->data->header.length; len = end - (cairo_path_data_t *) (data->cairo.array)->data; } else { len = 0; } /* Resize the data array */ g_array_set_size(data->cairo.array, len); /* Rescan path to compute the new over and last primitives */ _adg_clear_parent((AdgModel *) path); _adg_rescan(path); } /** * adg_path_move_to: * @path: an #AdgPath * @pair: the destination coordinates * * Begins a new segment. After this call the current point will be @pair. * * Since: 1.0 **/ void adg_path_move_to(AdgPath *path, const CpmlPair *pair) { adg_path_append(path, CPML_MOVE, pair); } /** * adg_path_move_to_explicit: * @path: an #AdgPath * @x: the new x coordinate * @y: the new y coordinate * * Convenient function to call adg_path_move_to() using explicit * coordinates instead of #CpmlPair. * * Since: 1.0 **/ void adg_path_move_to_explicit(AdgPath *path, gdouble x, gdouble y) { CpmlPair p; p.x = x; p.y = y; adg_path_append(path, CPML_MOVE, &p); } /** * adg_path_line_to: * @path: an #AdgPath * @pair: the destination coordinates * * Adds a line to @path from the current point to @pair. After this * call the current point will be @pair. * * If @path has no current point before this call, this function will * trigger a warning without other effect. * * Since: 1.0 **/ void adg_path_line_to(AdgPath *path, const CpmlPair *pair) { adg_path_append(path, CPML_LINE, pair); } /** * adg_path_line_to_explicit: * @path: an #AdgPath * @x: the new x coordinate * @y: the new y coordinate * * Convenient function to call adg_path_line_to() using explicit * coordinates instead of #CpmlPair. * * Since: 1.0 **/ void adg_path_line_to_explicit(AdgPath *path, gdouble x, gdouble y) { CpmlPair p; p.x = x; p.y = y; adg_path_append(path, CPML_LINE, &p); } /** * adg_path_arc_to: * @path: an #AdgPath * @through: an arbitrary point on the arc * @pair: the destination coordinates * * Adds an arc to the path from the current point to @pair, passing * through @through. After this call the current point will be @pair. * * If @path has no current point before this call, this function will * trigger a warning without other effect. * * Since: 1.0 **/ void adg_path_arc_to(AdgPath *path, const CpmlPair *through, const CpmlPair *pair) { adg_path_append(path, CPML_ARC, through, pair); } /** * adg_path_arc_to_explicit: * @path: an #AdgPath * @x1: the x coordinate of an intermediate point * @y1: the y coordinate of an intermediate point * @x2: the x coordinate of the end of the arc * @y2: the y coordinate of the end of the arc * * Convenient function to call adg_path_arc_to() using explicit * coordinates instead of #CpmlPair. * * Since: 1.0 **/ void adg_path_arc_to_explicit(AdgPath *path, gdouble x1, gdouble y1, gdouble x2, gdouble y2) { CpmlPair p[2]; p[0].x = x1; p[0].y = y1; p[1].x = x2; p[1].y = y2; adg_path_append(path, CPML_ARC, &p[0], &p[1]); } /** * adg_path_curve_to: * @path: an #AdgPath * @control1: the first control point of the curve * @control2: the second control point of the curve * @pair: the destination coordinates * * Adds a cubic Bézier curve to the path from the current point to * position @pair, using @control1 and @control2 as control points. * After this call the current point will be @pair. * * If @path has no current point before this call, this function will * trigger a warning without other effect. * * Since: 1.0 **/ void adg_path_curve_to(AdgPath *path, const CpmlPair *control1, const CpmlPair *control2, const CpmlPair *pair) { adg_path_append(path, CPML_CURVE, control1, control2, pair); } /** * adg_path_curve_to_explicit: * @path: an #AdgPath * @x1: the x coordinate of the first control point * @y1: the y coordinate of the first control point * @x2: the x coordinate of the second control point * @y2: the y coordinate of the second control point * @x3: the x coordinate of the end of the curve * @y3: the y coordinate of the end of the curve * * Convenient function to call adg_path_curve_to() using explicit * coordinates instead of #CpmlPair. * * Since: 1.0 **/ void adg_path_curve_to_explicit(AdgPath *path, gdouble x1, gdouble y1, gdouble x2, gdouble y2, gdouble x3, gdouble y3) { CpmlPair p[3]; p[0].x = x1; p[0].y = y1; p[1].x = x2; p[1].y = y2; p[2].x = x3; p[2].y = y3; adg_path_append(path, CPML_CURVE, &p[0], &p[1], &p[2]); } /** * adg_path_close: * @path: an #AdgPath * * Adds a line segment to the path from the current point to the * beginning of the current segment, (the most recent point passed * to an adg_path_move_to()), and closes this segment. * After this call the current point will be unset. * * The behavior of adg_path_close() is distinct from simply calling * adg_path_line_to() with the coordinates of the segment starting * point. When a closed segment is stroked, there are no caps on the * ends. Instead, there is a line join connecting the final and * initial primitive of the segment. * * If @path has no current point before this call, this function will * trigger a warning without other effect. * * Since: 1.0 **/ void adg_path_close(AdgPath *path) { adg_path_append(path, CPML_CLOSE); } /** * adg_path_arc: * @path: an #AdgPath * @center: coordinates of the center of the arc * @r: the radius of the arc * @start: the start angle, in radians * @end: the end angle, in radians * * A more usual way to add an arc to @path. After this call, the current * point will be the computed end point of the arc. The arc will be * rendered in increasing angle, accordling to @start and @end. This means * if @start is less than @end, the arc will be rendered in clockwise * direction (accordling to the default cairo coordinate system) while if * @start is greather than @end, the arc will be rendered in couterclockwise * direction. * * By explicitely setting the whole arc data, the start point could be * different from the current point. In this case, if @path has no * current point before the call a %CPML_MOVE to the start point of * the arc will be automatically prepended to the arc. If @path has a * current point, a %CPML_LINE to the start point of the arc will be * used instead of the "move to" primitive. * * Since: 1.0 **/ void adg_path_arc(AdgPath *path, const CpmlPair *center, gdouble r, gdouble start, gdouble end) { AdgPathPrivate *data; CpmlPair p[3]; g_return_if_fail(ADG_IS_PATH(path)); g_return_if_fail(center != NULL); data = adg_path_get_instance_private(path); cpml_vector_from_angle(&p[0], start); cpml_vector_from_angle(&p[1], (start+end) / 2); cpml_vector_from_angle(&p[2], end); cpml_vector_set_length(&p[0], r); cpml_vector_set_length(&p[1], r); cpml_vector_set_length(&p[2], r); p[0].x += center->x; p[0].y += center->y; p[1].x += center->x; p[1].y += center->y; p[2].x += center->x; p[2].y += center->y; if (!data->cp_is_valid) adg_path_append(path, CPML_MOVE, &p[0]); else if (p[0].x != data->cp.x || p[0].y != data->cp.y) adg_path_append(path, CPML_LINE, &p[0]); adg_path_append(path, CPML_ARC, &p[1], &p[2]); } /** * adg_path_arc_explicit: * @path: an #AdgPath * @xc: x position of the center of the arc * @yc: y position of the center of the arc * @r: the radius of the arc * @start: the start angle, in radians * @end: the end angle, in radians * * Convenient function to call adg_path_arc() using explicit * coordinates instead of #CpmlPair. * * Since: 1.0 **/ void adg_path_arc_explicit(AdgPath *path, gdouble xc, gdouble yc, gdouble r, gdouble start, gdouble end) { CpmlPair center; center.x = xc; center.y = yc; adg_path_arc(path, ¢er, r, start, end); } /** * adg_path_chamfer: * @path: an #AdgPath * @delta1: the distance from the intersection point of the current primitive * @delta2: the distance from the intersection point of the next primitive * * A binary action that generates a chamfer between two primitives. * The first primitive involved is the current primitive, the second will * be the next primitive appended to @path after this call. The second * primitive is required: if the chamfer operation is not properly * terminated (by not providing the second primitive), any API accessing * the path in reading mode will raise a warning. * * An exception is a chamfer after a %CPML_CLOSE primitive. In this case, * the second primitive is not required: the current close path is used * as first operand while the first primitive of the current segment is * used as second operand. * * The chamfer operation requires two lengths: @delta1 specifies the * "quantity" to trim on the first primitive while @delta2 is the same * applied on the second primitive. The term "quantity" means the length * of the portion to cut out from the original primitive (that is the * primitive as would be without the chamfer). * * Since: 1.0 **/ void adg_path_chamfer(AdgPath *path, gdouble delta1, gdouble delta2) { g_return_if_fail(ADG_IS_PATH(path)); if (!_adg_append_operation(path, ADG_ACTION_CHAMFER, delta1, delta2)) return; } /** * adg_path_fillet: * @path: an #AdgPath * @radius: the radius of the fillet * * A binary action that joins to primitives with an arc. * The first primitive involved is the current primitive, the second will * be the next primitive appended to @path after this call. The second * primitive is required: if the fillet operation is not properly * terminated (by not providing the second primitive), any API accessing * the path in reading mode will raise a warning. * * An exception is a fillet after a %CPML_CLOSE primitive. In this case, * the second primitive is not required: the current close path is used * as first operand while the first primitive of the current segment is * used as second operand. * * Since: 1.0 **/ void adg_path_fillet(AdgPath *path, gdouble radius) { g_return_if_fail(ADG_IS_PATH(path)); if (!_adg_append_operation(path, ADG_ACTION_FILLET, radius)) return; } /** * adg_path_join: * @path: an #AdgPath * * Joins all the segments of @path. After the call there will be only one * single segment. * * This operation is roughly equivalent to converting embedded %CPML_MOVE * primitives into %CPML_LINE ones. * * Since: 1.0 **/ void adg_path_join(AdgPath *path) { cairo_path_t *cairo_path; cairo_path_data_t *data; gboolean pen_down; g_return_if_fail(ADG_IS_PATH(path)); cairo_path = _adg_read_cairo_path(path); pen_down = FALSE; data = cairo_path->data; while (data - cairo_path->data < cairo_path->num_data) { if (data->header.type != CPML_MOVE) { pen_down = TRUE; } else if (pen_down) { data->header.type = CPML_LINE; } data += data->header.length; } } /** * adg_path_reflect: * @path: an #AdgPath * @vector: (allow-none): the slope of the axis * * Reflects the first segment or @path around the axis passing * through (0, 0) and with a @vector slope. The internal segment * is duplicated and the proper transformation (computed from * @vector) to mirror the segment is applied on all its points. * The result is then reversed with cpml_segment_reverse() and * appended to the original path with adg_path_append_segment(). * * For convenience, if @vector is NULL the * path is reversed around the x axis (y = 0). * * Since: 1.0 **/ void adg_path_reflect(AdgPath *path, const CpmlVector *vector) { AdgModel *model; AdgTrail *trail; cairo_matrix_t matrix; CpmlSegment segment, *dup_segment; gint n; g_return_if_fail(ADG_IS_PATH(path)); g_return_if_fail(vector == NULL || vector->x != 0 || vector->y != 0); model = (AdgModel *) path; trail = (AdgTrail *) path; if (vector == NULL) { cairo_matrix_init_scale(&matrix, 1, -1); } else { CpmlVector slope; gdouble cos2angle, sin2angle; cpml_pair_copy(&slope, vector); cpml_vector_set_length(&slope, 1); if (slope.x == 0 && slope.y == 0) { g_warning(_("%s: the axis of the reflection is not known"), G_STRLOC); return; } sin2angle = 2. * vector->x * vector->y; cos2angle = 2. * vector->x * vector->x - 1; cairo_matrix_init(&matrix, cos2angle, sin2angle, sin2angle, -cos2angle, 0, 0); } for (n = adg_trail_n_segments(trail); n > 0; --n) { adg_trail_put_segment(trail, n, &segment); /* No need to reverse an empty segment */ if (segment.num_data == 0 || segment.num_data == 0) continue; dup_segment = cpml_segment_deep_dup(&segment); if (dup_segment == NULL) return; cpml_segment_reverse(dup_segment); cpml_segment_transform(dup_segment, &matrix); dup_segment->data[0].header.type = CPML_MOVE; adg_path_append_segment(path, dup_segment); g_free(dup_segment); } _adg_dup_reverse_named_pairs(model, &matrix); } /** * adg_path_reflect_explicit: * @path: an #AdgPath * @x: the vector x component * @y: the vector y component * * Convenient function to call adg_path_reflect() using explicit * vector components instead of #CpmlVector. * * Since: 1.0 **/ void adg_path_reflect_explicit(AdgPath *path, gdouble x, gdouble y) { CpmlVector vector; vector.x = x; vector.y = y; adg_path_reflect(path, &vector); } static void _adg_clear(AdgModel *model) { AdgPath *path = (AdgPath *) model; AdgPathPrivate *data = adg_path_get_instance_private(path); g_array_set_size(data->cairo.array, 0); _adg_clear_operation(path); _adg_clear_parent(model); } static void _adg_clear_parent(AdgModel *model) { if (_ADG_OLD_MODEL_CLASS->clear) _ADG_OLD_MODEL_CLASS->clear(model); } static void _adg_changed(AdgModel *model) { _adg_clear_parent(model); if (_ADG_OLD_MODEL_CLASS->changed) _ADG_OLD_MODEL_CLASS->changed(model); } static cairo_path_t * _adg_get_cairo_path(AdgTrail *trail) { _adg_clear_parent((AdgModel *) trail); return _adg_read_cairo_path((AdgPath *) trail); } static cairo_path_t * _adg_read_cairo_path(AdgPath *path) { AdgPathPrivate *data = adg_path_get_instance_private(path); cairo_path_t *cairo_path = &data->cairo.path; GArray *array = data->cairo.array; /* Always regenerate the cairo_path_t as it is a trivial operation */ cairo_path->status = CAIRO_STATUS_SUCCESS; cairo_path->data = (cairo_path_data_t *) array->data; cairo_path->num_data = array->len; return cairo_path; } static gint _adg_primitive_length(CpmlPrimitiveType type) { switch (type) { case CPML_CLOSE: return 1; case CPML_MOVE: return 2; default: return cpml_primitive_type_get_n_points(type); } } static void _adg_primitive_remap(CpmlPrimitive *primitive, gpointer to, const CpmlPrimitive *old, gconstpointer from) { primitive->org = REMAPPED(old->org, from, to); primitive->segment = REMAPPED(old->segment, from, to); primitive->data = REMAPPED(old->data, from, to); } static void _adg_rescan(AdgPath *path) { AdgPathPrivate *data = adg_path_get_instance_private(path); CpmlPrimitive *last = &data->last; CpmlPrimitive *over = &data->over; CpmlSegment segment; CpmlPrimitive current; last->segment = NULL; last->org = NULL; last->data = NULL; over->segment = NULL; over->org = NULL; over->data = NULL; /* When no data is present, just bail out */ if (! cpml_segment_from_cairo(&segment, _adg_read_cairo_path(path))) { data->cp_is_valid = FALSE; return; } do { cpml_primitive_from_segment(¤t, &segment); do { cpml_primitive_copy(over, last); cpml_primitive_copy(last, ¤t); } while (cpml_primitive_next(¤t)); } while (cpml_segment_next(&segment)); /* Save the last point in the current point */ data->cp_is_valid = last->data && last->data->header.type != CPML_CLOSE; if (data->cp_is_valid) { CpmlPrimitiveType type = last->data->header.type; size_t n = type == CPML_MOVE ? 1 : cpml_primitive_type_get_n_points(type) - 1; cpml_pair_from_cairo(&data->cp, &last->data[n]); } } static void _adg_append_primitive(AdgPath *path, CpmlPrimitive *current) { AdgPathPrivate *data = adg_path_get_instance_private(path); cairo_path_data_t *path_data = current->data; int length = path_data->header.length; CpmlPrimitiveType type = path_data->header.type; gconstpointer old_data; gpointer new_data; /* Execute any pending operation */ _adg_do_operation(path, path_data); /* Append the path data to the internal path array */ old_data = (data->cairo.array)->data; data->cairo.array = g_array_append_vals(data->cairo.array, path_data, length); new_data = (data->cairo.array)->data; /* Set path data to point to the recently appended cairo_path_data_t * primitive: the first struct is the header */ path_data = (cairo_path_data_t *) new_data + (data->cairo.array)->len - length; if (type == CPML_MOVE) { /* Remap last and over, but do not change their content */ _adg_primitive_remap(&data->last, new_data, &data->last, old_data); _adg_primitive_remap(&data->over, new_data, &data->over, old_data); } else { /* Store the last primitive into over */ _adg_primitive_remap(&data->over, new_data, &data->last, old_data); /* Set the last primitive for subsequent binary operations */ /* TODO: the assumption path_data - 1 is the last point is not true * e.g. when there are embedded data in primitives */ data->last.org = data->cp_is_valid ? path_data - 1 : NULL; data->last.segment = NULL; data->last.data = path_data; } data->cp_is_valid = type != CPML_CLOSE; if (data->cp_is_valid) { /* Save the last point in the current point */ size_t n = type == CPML_MOVE ? 1 : cpml_primitive_type_get_n_points(type) - 1; cpml_pair_from_cairo(&data->cp, &path_data[n]); } /* Invalidate cairo_path: should be recomputed */ _adg_clear_parent((AdgModel *) path); } static void _adg_clear_operation(AdgPath *path) { AdgPathPrivate *data = adg_path_get_instance_private(path); AdgOperation *operation = &data->operation; if (operation->action != ADG_ACTION_NONE) { g_warning(_("%s: a '%s' operation is still active while clearing the path"), G_STRLOC, _adg_action_name(operation->action)); operation->action = ADG_ACTION_NONE; } data->cp_is_valid = FALSE; data->last.data = NULL; data->over.data = NULL; } static gboolean _adg_append_operation(AdgPath *path, gint action, ...) { AdgPathPrivate *data = adg_path_get_instance_private(path); AdgAction real_action = (AdgAction) action; AdgOperation *operation; va_list var_args; if (data->last.data == NULL) { g_warning(_("%s: requested a '%s' operation on a path without current primitive"), G_STRLOC, _adg_action_name(real_action)); return FALSE; } operation = &data->operation; if (operation->action != ADG_ACTION_NONE) { g_warning(_("%s: requested a '%s' operation while a '%s' operation was active"), G_STRLOC, _adg_action_name(real_action), _adg_action_name(operation->action)); /* XXX: http://dev.entidi.com/p/adg/issues/50/ */ return FALSE; } va_start(var_args, action); switch (real_action) { case ADG_ACTION_CHAMFER: operation->data.chamfer.delta1 = va_arg(var_args, double); operation->data.chamfer.delta2 = va_arg(var_args, double); break; case ADG_ACTION_FILLET: operation->data.fillet.radius = va_arg(var_args, double); break; case ADG_ACTION_NONE: va_end(var_args); return TRUE; default: g_warning(_("%s: %d path operation not recognized"), G_STRLOC, real_action); va_end(var_args); return FALSE; } operation->action = real_action; va_end(var_args); if (data->last.data[0].header.type == CPML_CLOSE) { /* Special case: an action with the close primitive should * be resolved now by changing the close primitive to a * line-to and using it as second operand and use the first * primitive of the current segment as first operand */ guint length; cairo_path_data_t *path_data; CpmlSegment segment; CpmlPrimitive current; length = data->cairo.array->len; /* Ensure the close path primitive is not the only data */ g_return_val_if_fail(length > 1, FALSE); /* Allocate one more item once for all to accept the * conversion from a close to line-to primitive */ data->cairo.array = g_array_set_size(data->cairo.array, length + 1); path_data = (cairo_path_data_t *) data->cairo.array->data; --data->cairo.array->len; /* Set segment and current (the first primitive of segment) */ cpml_segment_from_cairo(&segment, _adg_read_cairo_path(path)); while (cpml_segment_next(&segment)) ; cpml_primitive_from_segment(¤t, &segment); /* Convert close path to a line-to primitive */ ++data->cairo.array->len; path_data[length - 1].header.type = CPML_LINE; path_data[length - 1].header.length = 2; path_data[length] = *current.org; data->last.segment = &segment; data->last.org = &path_data[length - 2]; data->last.data = &path_data[length - 1]; _adg_do_action(path, real_action, ¤t); } return TRUE; } static void _adg_do_operation(AdgPath *path, cairo_path_data_t *path_data) { AdgPathPrivate *data = adg_path_get_instance_private(path); AdgAction action = data->operation.action; CpmlSegment segment; CpmlPrimitive current; cairo_path_data_t current_org; cpml_segment_from_cairo(&segment, _adg_read_cairo_path(path)); /* Construct the current primitive, that is the primitive to be * mixed with the last primitive with the specified operation. * Its org is a copy of the end point of the last primitive: it can be * modified without affecting anything else. It is expected the operation * functions will add to @path the primitives required but NOT to add * @current, as this one will be inserted automatically. */ current.segment = &segment; current.org = ¤t_org; current.data = path_data; cpml_pair_to_cairo(&data->cp, ¤t_org); _adg_do_action(path, action, ¤t); } static void _adg_do_action(AdgPath *path, AdgAction action, CpmlPrimitive *primitive) { switch (action) { case ADG_ACTION_NONE: return; case ADG_ACTION_CHAMFER: _adg_do_chamfer(path, primitive); break; case ADG_ACTION_FILLET: _adg_do_fillet(path, primitive); break; default: g_return_if_reached(); } } static void _adg_do_chamfer(AdgPath *path, CpmlPrimitive *current) { AdgPathPrivate *data = adg_path_get_instance_private(path); CpmlPrimitive *last = &data->last; gdouble delta1 = data->operation.data.chamfer.delta1; gdouble len1 = cpml_primitive_get_length(last); gdouble delta2, len2; CpmlPair pair; if (delta1 >= len1) { g_warning(_("%s: first chamfer delta of %lf is greather than the available %lf length"), G_STRLOC, delta1, len1); return; } delta2 = data->operation.data.chamfer.delta2; len2 = cpml_primitive_get_length(current); if (delta2 >= len2) { g_warning(_("%s: second chamfer delta of %lf is greather than the available %lf length"), G_STRLOC, delta1, len1); return; } /* Change the end point of the last primitive */ cpml_primitive_put_pair_at(last, 1. - delta1 / len1, &pair); cpml_primitive_set_point(last, -1, &pair); /* Change the start point of the current primitive */ cpml_primitive_put_pair_at(current, delta2 / len2, &pair); cpml_primitive_set_point(current, 0, &pair); /* Add the chamfer line */ data->operation.action = ADG_ACTION_NONE; adg_path_append(path, CPML_LINE, &pair); } static void _adg_do_fillet(AdgPath *path, CpmlPrimitive *current) { AdgPathPrivate *data = adg_path_get_instance_private(path); CpmlPrimitive *last = &data->last; CpmlPrimitive *current_dup = cpml_primitive_deep_dup(current); CpmlPrimitive *last_dup = cpml_primitive_deep_dup(last); gdouble radius = data->operation.data.fillet.radius; gdouble offset = _adg_is_convex(last_dup, current_dup) ? -radius : radius; gdouble pos; CpmlPair center, vector, p[3]; /* Find the center of the fillet from the intersection between * the last and current primitives offseted by radius */ cpml_primitive_offset(current_dup, offset); cpml_primitive_offset(last_dup, offset); if (cpml_primitive_put_intersections(current_dup, last_dup, 1, ¢er) == 0) { g_warning(_("%s: fillet with radius of %lf is not applicable here"), G_STRLOC, radius); g_free(current_dup); g_free(last_dup); return; } /* Compute the start point of the fillet */ pos = cpml_primitive_get_closest_pos(last_dup, ¢er); cpml_primitive_put_vector_at(last_dup, pos, &vector); cpml_vector_set_length(&vector, offset); cpml_vector_normal(&vector); p[0].x = center.x - vector.x; p[0].y = center.y - vector.y; /* Compute the mid point of the fillet */ cpml_pair_from_cairo(&vector, current->org); vector.x -= center.x; vector.y -= center.y; cpml_vector_set_length(&vector, radius); p[1].x = center.x + vector.x; p[1].y = center.y + vector.y; /* Compute the end point of the fillet */ pos = cpml_primitive_get_closest_pos(current_dup, ¢er); cpml_primitive_put_vector_at(current_dup, pos, &vector); cpml_vector_set_length(&vector, offset); cpml_vector_normal(&vector); p[2].x = center.x - vector.x; p[2].y = center.y - vector.y; g_free(current_dup); g_free(last_dup); /* Change the end point of the last primitive */ cpml_primitive_set_point(last, -1, &p[0]); /* Change the start point of the current primitive */ cpml_primitive_set_point(current, 0, &p[2]); /* Add the fillet arc */ data->operation.action = ADG_ACTION_NONE; adg_path_append(path, CPML_ARC, &p[1], &p[2]); } static gboolean _adg_is_convex(const CpmlPrimitive *primitive1, const CpmlPrimitive *primitive2) { CpmlVector v1, v2; gdouble angle1, angle2; cpml_primitive_put_vector_at(primitive1, -1, &v1); cpml_primitive_put_vector_at(primitive2, 0, &v2); /* Probably there is a smarter way to get this without trygonometry */ angle1 = cpml_vector_angle(&v1); angle2 = cpml_vector_angle(&v2); if (angle1 > angle2) angle1 -= G_PI*2; return angle2-angle1 > G_PI; } static const gchar * _adg_action_name(AdgAction action) { switch (action) { case ADG_ACTION_NONE: return "NULL"; case ADG_ACTION_CHAMFER: return "CHAMFER"; case ADG_ACTION_FILLET: return "FILLET"; } return "undefined"; } static void _adg_get_named_pair(AdgModel *model, const gchar *name, CpmlPair *pair, gpointer user_data) { GSList **named_pairs; AdgNamedPair *named_pair; named_pairs = user_data; named_pair = g_new(AdgNamedPair, 1); named_pair->name = name; named_pair->pair = *pair; *named_pairs = g_slist_prepend(*named_pairs, named_pair); } static void _adg_dup_reverse_named_pairs(AdgModel *model, const cairo_matrix_t *matrix) { AdgNamedPair *old_named_pair; AdgNamedPair named_pair; GSList *named_pairs; /* Populate named_pairs with all the named pairs of model */ named_pairs = NULL; adg_model_foreach_named_pair(model, _adg_get_named_pair, &named_pairs); /* Readd the pairs applying the reversing transformation matrix to * their coordinates and prepending a "-" to their name */ while (named_pairs) { old_named_pair = named_pairs->data; named_pair.name = g_strdup_printf("-%s", old_named_pair->name); named_pair.pair = old_named_pair->pair; cpml_pair_transform(&named_pair.pair, matrix); adg_model_set_named_pair(model, named_pair.name, &named_pair.pair); g_free((gpointer) named_pair.name); named_pairs = g_slist_delete_link(named_pairs, named_pairs); } } diff --git a/src/adg/adg-path.h b/src/adg/adg-path.h index 75da31c6..6c7de7f1 100644 --- a/src/adg/adg-path.h +++ b/src/adg/adg-path.h @@ -1,140 +1,140 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_PATH_H__ #define __ADG_PATH_H__ G_BEGIN_DECLS #define ADG_TYPE_PATH (adg_path_get_type()) #define ADG_PATH(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_PATH, AdgPath)) #define ADG_PATH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_PATH, AdgPathClass)) #define ADG_IS_PATH(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_PATH)) #define ADG_IS_PATH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_PATH)) #define ADG_PATH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_PATH, AdgPathClass)) typedef struct _AdgPath AdgPath; typedef struct _AdgPathClass AdgPathClass; struct _AdgPath { /*< private >*/ AdgTrail parent; }; struct _AdgPathClass { /*< private >*/ AdgTrailClass parent_class; }; GType adg_path_get_type (void); AdgPath * adg_path_new (void); const CpmlPair *adg_path_get_current_point (AdgPath *path); gboolean adg_path_has_current_point (AdgPath *path); const CpmlPrimitive * adg_path_last_primitive (AdgPath *path); const CpmlPrimitive * adg_path_over_primitive (AdgPath *path); void adg_path_append (AdgPath *path, gint type, ...); void adg_path_append_valist (AdgPath *path, CpmlPrimitiveType type, va_list var_args); void adg_path_append_array (AdgPath *path, CpmlPrimitiveType type, const CpmlPair**pairs); void adg_path_append_primitive (AdgPath *path, const CpmlPrimitive *primitive); void adg_path_append_segment (AdgPath *path, const CpmlSegment *segment); void adg_path_append_cairo_path (AdgPath *path, const cairo_path_t *cairo_path); void adg_path_append_trail (AdgPath *path, AdgTrail *trail); void adg_path_remove_primitive (AdgPath *path); void adg_path_move_to (AdgPath *path, const CpmlPair *pair); void adg_path_move_to_explicit (AdgPath *path, gdouble x, gdouble y); void adg_path_line_to (AdgPath *path, const CpmlPair *pair); void adg_path_line_to_explicit (AdgPath *path, gdouble x, gdouble y); void adg_path_arc_to (AdgPath *path, const CpmlPair *through, const CpmlPair *pair); void adg_path_arc_to_explicit (AdgPath *path, gdouble x1, gdouble y1, gdouble x2, gdouble y2); void adg_path_curve_to (AdgPath *path, const CpmlPair *control1, const CpmlPair *control2, const CpmlPair *pair); void adg_path_curve_to_explicit (AdgPath *path, gdouble x1, gdouble y1, gdouble x2, gdouble y2, gdouble x3, gdouble y3); void adg_path_close (AdgPath *path); void adg_path_arc (AdgPath *path, const CpmlPair *center, gdouble r, gdouble start, gdouble end); void adg_path_arc_explicit (AdgPath *path, gdouble xc, gdouble yc, gdouble r, gdouble start, gdouble end); void adg_path_chamfer (AdgPath *path, gdouble delta1, gdouble delta2); void adg_path_fillet (AdgPath *path, gdouble radius); void adg_path_join (AdgPath *path); void adg_path_reflect (AdgPath *path, const CpmlVector *vector); void adg_path_reflect_explicit (AdgPath *path, gdouble x, gdouble y); G_END_DECLS #endif /* __ADG_PATH_H__ */ diff --git a/src/adg/adg-point.c b/src/adg/adg-point.c index 9ec875e8..f5abf04c 100644 --- a/src/adg/adg-point.c +++ b/src/adg/adg-point.c @@ -1,454 +1,454 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-point * @Section_Id:AdgPoint * @title: AdgPoint * @short_description: A struct holding x, y coordinates * (either named or explicit) * * AdgPoint is an opaque structure that manages 2D coordinates, * either set explicitely through adg_point_set_pair() and * adg_point_set_pair_explicit() or taken from a model with * adg_point_set_pair_from_model(). It can be thought as an * #CpmlPair on steroid, because it adds named pair support to * a simple pair, enabling coordinates depending on #AdgModel. * * Since: 1.0 **/ /** * AdgPoint: * * This is an opaque struct: all its fields are privates. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-model.h" #include #include "adg-point.h" struct _AdgPoint { CpmlPair pair; AdgModel *model; gchar *name; gboolean up_to_date; }; GType adg_point_get_type(void) { static GType type = 0; if (G_UNLIKELY(type == 0)) type = g_boxed_type_register_static("AdgPoint", (GBoxedCopyFunc) adg_point_dup, (GBoxedFreeFunc) adg_point_destroy); return type; } /** * adg_point_new: * * Creates a new empty #AdgPoint. The returned pointer * should be freed with adg_point_destroy() when no longer needed. * * Returns: a newly created #AdgPoint * * Since: 1.0 **/ AdgPoint * adg_point_new(void) { return g_new0(AdgPoint, 1); } /** * adg_point_dup: * @src: an #AdgPoint * * Duplicates @src. This operation also adds a new reference * to the internal model if @src is linked to a named pair. * * The returned value should be freed with adg_point_destroy() * when no longer needed. * * Returns: the duplicated #AdgPoint struct or NULL on errors. * * Since: 1.0 **/ AdgPoint * adg_point_dup(const AdgPoint *src) { AdgPoint *point; g_return_val_if_fail(src != NULL, NULL); if (src->model) g_object_ref(src->model); point = cpml_memdup(src, sizeof(AdgPoint)); point->name = g_strdup(src->name); return point; } /** * adg_point_destroy: * @point: an #AdgPoint * * Destroys the @point instance, unreferencing the internal model if * @point is linked to a named pair. * * Since: 1.0 **/ void adg_point_destroy(AdgPoint *point) { g_return_if_fail(point != NULL); adg_point_unset(point); g_free(point); } /** * adg_point_copy: * @point: an #AdgPoint * @src: the source point to copy * * Copies @src into @point. If the old content of @point was linked * to the named pair of a model, the reference to that model is * dropped. Similary, if @src is a named pair, a new reference to * the new model is added. * * Since: 1.0 **/ void adg_point_copy(AdgPoint *point, const AdgPoint *src) { g_return_if_fail(point != NULL); g_return_if_fail(src != NULL); if (src->model != NULL) g_object_ref(src->model); if (point->model != NULL) g_object_unref(point->model); if (point->name != NULL) g_free(point->name); memcpy(point, src, sizeof(AdgPoint)); point->name = g_strdup(src->name); } /** * adg_point_set_pair: * @point: an #AdgPoint * @pair: the #CpmlPair to use * * Sets an explicit pair in @point by using the given @pair. If * @point was linked to a named pair in a model, this link is * dropped before setting the pair. * * Since: 1.0 **/ void adg_point_set_pair(AdgPoint *point, const CpmlPair *pair) { g_return_if_fail(point != NULL); g_return_if_fail(pair != NULL); adg_point_set_pair_explicit(point, pair->x, pair->y); } /** * adg_point_set_pair_explicit: * @point: an #AdgPoint * @x: the x coordinate of the point * @y: the y coordinate of the point * * Works in the same way of adg_point_set_pair() but accept direct numbers * instead of an #CpmlPair structure. * * Since: 1.0 **/ void adg_point_set_pair_explicit(AdgPoint *point, gdouble x, gdouble y) { g_return_if_fail(point != NULL); adg_point_unset(point); point->pair.x = x; point->pair.y = y; point->up_to_date = TRUE; } /** * adg_point_set_pair_from_model: * @point: an #AdgPoint * @model: the #AdgModel * @name: the id of a named pair in @model * * Links the @name named pair of @model to @point, so any subsequent * call to adg_point_get_pair() will return the named pair value. * A new reference is added to @model while the previous model (if any) * is unreferenced. * * Since: 1.0 **/ void adg_point_set_pair_from_model(AdgPoint *point, AdgModel *model, const gchar *name) { g_return_if_fail(point != NULL); g_return_if_fail(ADG_IS_MODEL(model)); g_return_if_fail(name != NULL); /* Return if the new named pair is the same of the old one */ if (model == point->model && strcmp(point->name, name) == 0) return; g_object_ref(model); if (point->model) { /* Remove the old named pair */ g_object_unref(point->model); g_free(point->name); } /* Set the new named pair */ point->up_to_date = FALSE; point->model = model; point->name = g_strdup(name); } /** * adg_point_invalidate: * @point: an #AdgPoint * * Invalidates @point, forcing a refresh of its internal #CpmlPair if * the point is linked to a named pair. If @point is explicitely set, * this function has no effect. * * Since: 1.0 **/ void adg_point_invalidate(AdgPoint *point) { g_return_if_fail(point != NULL); if (point->model != NULL) point->up_to_date = FALSE; } /** * adg_point_unset: * @point: a pointer to an #AdgPoint * * Unsets @point by resetting the internal up_to_date * flag and (eventually) unlinking it from the named pair it is bound * to. After this call the content of @point is undefined, so a * subsequent call to adg_point_get_pair() will * return NULL raising a warning. * * Since: 1.0 **/ void adg_point_unset(AdgPoint *point) { g_return_if_fail(point != NULL); if (point->model) { /* Remove the old named pair */ g_object_unref(point->model); g_free(point->name); } point->up_to_date = FALSE; point->model = NULL; point->name = NULL; } /** * adg_point_update: * @point: a pointer to an #AdgPoint * * Updates the internal #CpmlPair of @point. The internal * implementation is protected against multiple calls so it * can be called more times without harms. * * Returns: TRUE if @point has been updated or FALSE on errors, i.e. when it is bound to a non-existent named pair. * * Since: 1.0 **/ gboolean adg_point_update(AdgPoint *point) { AdgModel *model; const CpmlPair *pair; g_return_val_if_fail(point != NULL, FALSE); if (point->up_to_date) return TRUE; model = point->model; if (model == NULL) { /* A point with explicit coordinates not up to date * is an unexpected condition */ g_warning(_("%s: trying to get a pair from an undefined point"), G_STRLOC); return FALSE; } pair = adg_model_get_named_pair(model, point->name); if (pair == NULL) return FALSE; cpml_pair_copy(&point->pair, pair); point->up_to_date = TRUE; return TRUE; } /** * adg_point_get_pair: * @point: an #AdgPoint * * #AdgPoint is an evolution of the pair concept but internally the * relevant data is still stored in an #CpmlPair struct. This function * returns a copy of the internally owned pair. * * * The #CpmlPair is the first field of an #AdgPoint struct so casting * is allowed between them and, in fact, it is often more convenient * than calling this function. Just remember to update the internal * pair by using adg_point_update() before. * * * Returns: (transfer full): the pair of @point or NULL if the named pair does not exist. * * Since: 1.0 **/ CpmlPair * adg_point_get_pair(AdgPoint *point) { g_return_val_if_fail(point != NULL, NULL); if (! adg_point_update(point)) return NULL; return cpml_pair_dup(& point->pair); } /** * adg_point_get_model: * @point: an #AdgPoint * * Gets the source model of the named pair bound to @point, or * returns NULL if @point is an explicit * pair. The returned value is owned by @point. * * Returns: (transfer none): an #AdgModel or NULL. * * Since: 1.0 **/ AdgModel * adg_point_get_model(const AdgPoint *point) { g_return_val_if_fail(point != NULL, NULL); return point->model; } /** * adg_point_get_name: * @point: an #AdgPoint * * Gets the name of the named pair bound to @point, or * returns NULL if @point is an explicit * pair. The returned value is owned by @point and should not * be modified or freed. * * Returns: the name of the named pair or NULL. * * Since: 1.0 **/ const gchar * adg_point_get_name(const AdgPoint *point) { g_return_val_if_fail(point != NULL, NULL); return point->name; } /** * adg_point_equal: * @point1: the first point to compare * @point2: the second point to compare * * Compares @point1 and @point2 and returns TRUE * if the points are equals. The comparison is made by checking also * the named pairs they are bound to. If you want to compare only * their coordinates, use cpml_pair_equal() directly on the * #AdgPoint structs: * * * if (adg_point_update(point1) && * adg_point_update(point2) && * cpml_pair_equal((CpmlPair *) point1, (CpmlPair *) point2)) * { * ... * } * * * NULL points are handled gracefully. * * Returns: TRUE if @point1 is equal to @point2, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_point_equal(const AdgPoint *point1, const AdgPoint *point2) { if (point1 == point2) return TRUE; if (point1 == NULL || point2 == NULL) return FALSE; /* Check if the points are not bound to the same model * or if only one of the two points is explicit */ if (point1->model != point2->model) return FALSE; /* Handle points bound to named pairs */ if (point1->model != NULL) return g_strcmp0(point1->name, point2->name) == 0; /* Handle points with explicit coordinates */ return cpml_pair_equal(&point1->pair, &point2->pair); } diff --git a/src/adg/adg-point.h b/src/adg/adg-point.h index e4bf6b13..dfa42a4c 100644 --- a/src/adg/adg-point.h +++ b/src/adg/adg-point.h @@ -1,62 +1,62 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_POINT_H__ #define __ADG_POINT_H__ G_BEGIN_DECLS #define ADG_TYPE_POINT (adg_point_get_type()) GType adg_point_get_type (void); AdgPoint * adg_point_new (void); AdgPoint * adg_point_dup (const AdgPoint *src); void adg_point_destroy (AdgPoint *point); void adg_point_copy (AdgPoint *point, const AdgPoint *src); void adg_point_set_pair (AdgPoint *point, const CpmlPair *pair); void adg_point_set_pair_explicit (AdgPoint *point, gdouble x, gdouble y); void adg_point_set_pair_from_model (AdgPoint *point, AdgModel *model, const gchar *name); void adg_point_invalidate (AdgPoint *point); void adg_point_unset (AdgPoint *point); gboolean adg_point_update (AdgPoint *point); CpmlPair * adg_point_get_pair (AdgPoint *point); AdgModel * adg_point_get_model (const AdgPoint *point); const gchar * adg_point_get_name (const AdgPoint *point); gboolean adg_point_equal (const AdgPoint *point1, const AdgPoint *point2); G_END_DECLS #endif /* __ADG_POINT_H__ */ diff --git a/src/adg/adg-projection-private.h b/src/adg/adg-projection-private.h index c4e29a47..574af4b4 100644 --- a/src/adg/adg-projection-private.h +++ b/src/adg/adg-projection-private.h @@ -1,44 +1,44 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_PROJECTION_PRIVATE_H__ #define __ADG_PROJECTION_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgProjectionClassPrivate AdgProjectionClassPrivate; typedef struct _AdgProjectionPrivate AdgProjectionPrivate; struct _AdgProjectionClassPrivate { AdgProjectionScheme scheme; AdgPath *symbol; AdgPath *axis; CpmlExtents extents; }; struct _AdgProjectionPrivate { AdgDress symbol_dress; AdgDress axis_dress; AdgProjectionScheme scheme; }; G_END_DECLS #endif /* __ADG_PROJECTION_PRIVATE_H__ */ diff --git a/src/adg/adg-projection.c b/src/adg/adg-projection.c index aff4d94a..3f5647f1 100644 --- a/src/adg/adg-projection.c +++ b/src/adg/adg-projection.c @@ -1,468 +1,468 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-projection * @short_description: The standard symbol for specifying the projection scheme * * The #AdgProjection is an entity representing the standard symbol * of the projection scheme. * * Since: 1.0 **/ /** * AdgProjection: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-model.h" #include "adg-trail.h" #include "adg-path.h" #include "adg-dress.h" #include "adg-param-dress.h" #include "adg-projection.h" #include "adg-projection-private.h" G_DEFINE_TYPE_WITH_PRIVATE(AdgProjection, adg_projection, ADG_TYPE_ENTITY) enum { PROP_0, PROP_SYMBOL_DRESS, PROP_AXIS_DRESS, PROP_SCHEME }; static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void _adg_arrange (AdgEntity *entity); static void _adg_render (AdgEntity *entity, cairo_t *cr); static void _adg_arrange_class (AdgProjectionClass *projection_class, AdgProjectionScheme scheme); static void adg_projection_class_init(AdgProjectionClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; GParamSpec *param; AdgProjectionClassPrivate *data_class; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; entity_class->arrange = _adg_arrange; entity_class->render = _adg_render; param = adg_param_spec_dress("symbol-dress", P_("Symbol Dress"), P_("The line dress to use for rendering the views of the projection"), ADG_DRESS_LINE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_SYMBOL_DRESS, param); param = adg_param_spec_dress("axis-dress", P_("Axis Dress"), P_("The line dress to use for rendering the axis of the projection scheme"), ADG_DRESS_LINE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_AXIS_DRESS, param); param = g_param_spec_enum("scheme", P_("Projection Scheme"), P_("The projection scheme to be represented"), ADG_TYPE_PROJECTION_SCHEME, ADG_PROJECTION_SCHEME_UNDEFINED, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_SCHEME, param); /* Initialize the private class data: the allocated struct is * never freed as this type is registered statically, hence * never destroyed. A better approach would be to use the old * type initialization (no G_TYPE_DEFINE and friends) that * allows to specify a custom class finalization method */ data_class = g_new(AdgProjectionClassPrivate, 1); data_class->scheme = ADG_PROJECTION_SCHEME_UNDEFINED; data_class->symbol = NULL; data_class->axis = NULL; data_class->extents.is_defined = FALSE; klass->data_class = data_class; } static void adg_projection_init(AdgProjection *projection) { AdgProjectionPrivate *data = adg_projection_get_instance_private(projection); data->symbol_dress = ADG_DRESS_LINE; data->axis_dress = ADG_DRESS_LINE; data->scheme = ADG_PROJECTION_SCHEME_UNDEFINED; } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgProjectionPrivate *data = adg_projection_get_instance_private((AdgProjection *) object); switch (prop_id) { case PROP_SYMBOL_DRESS: g_value_set_enum(value, data->symbol_dress); break; case PROP_AXIS_DRESS: g_value_set_enum(value, data->axis_dress); break; case PROP_SCHEME: g_value_set_enum(value, data->scheme); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgProjectionPrivate *data = adg_projection_get_instance_private((AdgProjection *) object); switch (prop_id) { case PROP_SYMBOL_DRESS: data->symbol_dress = g_value_get_enum(value); break; case PROP_AXIS_DRESS: data->axis_dress = g_value_get_enum(value); break; case PROP_SCHEME: data->scheme = g_value_get_enum(value); adg_entity_invalidate((AdgEntity *) object); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_projection_new: * @scheme: the scheme represented by this projection * * Creates a new projection entity representing the selected @scheme. * If @scheme is invalid, a projection symbol without a scheme is * returned, that is #AdgProjection:scheme is set to * #ADG_PROJECTION_SCHEME_UNDEFINED, and a warning is raised. * * Returns: (transfer full): the newly created projection entity. * * Since: 1.0 **/ AdgProjection * adg_projection_new(AdgProjectionScheme scheme) { return g_object_new(ADG_TYPE_PROJECTION, "scheme", scheme, NULL); } /** * adg_projection_set_symbol_dress: * @projection: an #AdgProjection * @dress: the new #AdgDress to use * * Sets a new line dress for rendering the symbol of @projection. The * new dress must be a line dress: the check is done by calling * adg_dress_are_related() with @dress and the old dress as * arguments. Check out its documentation for further details. * * The default dress is a transparent line dress: the rendering * callback will stroke the symbol using the default color with * a predefined thickness. * * Since: 1.0 **/ void adg_projection_set_symbol_dress(AdgProjection *projection, AdgDress dress) { g_return_if_fail(ADG_IS_PROJECTION(projection)); g_object_set(projection, "symbol-dress", dress, NULL); } /** * adg_projection_get_symbol_dress: * @projection: an #AdgProjection * * Gets the line dress to be used in stroking the symbol of @projection. * * Returns: (transfer none): the requested line dress. * * Since: 1.0 **/ AdgDress adg_projection_get_symbol_dress(AdgProjection *projection) { AdgProjectionPrivate *data; g_return_val_if_fail(ADG_IS_PROJECTION(projection), ADG_DRESS_UNDEFINED); data = adg_projection_get_instance_private(projection); return data->symbol_dress; } /** * adg_projection_set_axis_dress: * @projection: an #AdgProjection * @dress: the new #AdgDress to use * * Sets a new line dress for rendering the axis of @projection. * The new dress must be a line dress: the check is done by * calling adg_dress_are_related() with @dress and the old * dress as arguments. Check out its documentation for * further details. * * The default dress is a transparent line dress: the rendering * callback will stroke the axis using the default line style. * * Since: 1.0 **/ void adg_projection_set_axis_dress(AdgProjection *projection, AdgDress dress) { g_return_if_fail(ADG_IS_PROJECTION(projection)); g_object_set(projection, "axis-dress", dress, NULL); } /** * adg_projection_get_axis_dress: * @projection: an #AdgProjection * * Gets the line dress to be used in stroking the axis of @projection. * * Returns: (transfer none): the requested line dress. * * Since: 1.0 **/ AdgDress adg_projection_get_axis_dress(AdgProjection *projection) { AdgProjectionPrivate *data; g_return_val_if_fail(ADG_IS_PROJECTION(projection), ADG_DRESS_UNDEFINED); data = adg_projection_get_instance_private(projection); return data->axis_dress; } /** * adg_projection_set_scheme: * @projection: an #AdgProjection * @scheme: the new projection scheme * * Sets a new scheme on @projection. If @scheme is different * from the old one, @projection is invalidated. * * Since: 1.0 **/ void adg_projection_set_scheme(AdgProjection *projection, AdgProjectionScheme scheme) { g_return_if_fail(ADG_IS_PROJECTION(projection)); g_object_set(projection, "scheme", scheme, NULL); } /** * adg_projection_get_scheme: * @projection: an #AdgProjection * * Gets the scheme represented by @projection. * * Returns: (transfer none): the scheme of @projection * * Since: 1.0 **/ AdgProjectionScheme adg_projection_get_scheme(AdgProjection *projection) { AdgProjectionPrivate *data; g_return_val_if_fail(ADG_IS_PROJECTION(projection), ADG_PROJECTION_SCHEME_UNDEFINED); data = adg_projection_get_instance_private(projection); return data->scheme; } static void _adg_arrange(AdgEntity *entity) { AdgProjectionPrivate *data = adg_projection_get_instance_private((AdgProjection *) entity); AdgProjectionClass *projection_class = ADG_PROJECTION_GET_CLASS(entity); AdgProjectionClassPrivate *data_class = projection_class->data_class; CpmlExtents extents; _adg_arrange_class(projection_class, data->scheme); cpml_extents_copy(&extents, &data_class->extents); cpml_extents_transform(&extents, adg_entity_get_local_matrix(entity)); cpml_extents_transform(&extents, adg_entity_get_global_matrix(entity)); adg_entity_set_extents(entity, &extents); } static void _adg_arrange_class(AdgProjectionClass *projection_class, AdgProjectionScheme scheme) { AdgProjectionClassPrivate *data_class; AdgPath *symbol, *axis; data_class = projection_class->data_class; if (data_class->scheme == scheme) return; if (data_class->symbol != NULL) g_object_unref(data_class->symbol); if (data_class->axis != NULL) g_object_unref(data_class->axis); data_class->scheme = scheme; switch(scheme) { case ADG_PROJECTION_SCHEME_UNDEFINED: symbol = NULL; axis = NULL; break; case ADG_PROJECTION_SCHEME_FIRST_ANGLE: symbol = adg_path_new(); adg_path_move_to_explicit(symbol, 4, 19); adg_path_line_to_explicit(symbol, 24, 24); adg_path_line_to_explicit(symbol, 24, 4); adg_path_line_to_explicit(symbol, 4, 9); adg_path_close(symbol); adg_path_move_to_explicit(symbol, 49, 14); adg_path_arc_to_explicit(symbol, 29, 14, 49, 14); adg_path_move_to_explicit(symbol, 44, 14); adg_path_arc_to_explicit(symbol, 34, 14, 44, 14); axis = adg_path_new(); adg_path_move_to_explicit(axis, 0, 14); adg_path_line_to_explicit(axis, 53, 14); adg_path_move_to_explicit(axis, 39, 0); adg_path_line_to_explicit(axis, 39, 28); break; case ADG_PROJECTION_SCHEME_THIRD_ANGLE: symbol = adg_path_new(); adg_path_move_to_explicit(symbol, 29, 19); adg_path_line_to_explicit(symbol, 49, 24); adg_path_line_to_explicit(symbol, 49, 4); adg_path_line_to_explicit(symbol, 29, 9); adg_path_close(symbol); adg_path_move_to_explicit(symbol, 24, 14); adg_path_arc_to_explicit(symbol, 4, 14, 24, 14); adg_path_move_to_explicit(symbol, 19, 14); adg_path_arc_to_explicit(symbol, 9, 14, 19, 14); axis = adg_path_new(); adg_path_move_to_explicit(axis, 0, 14); adg_path_line_to_explicit(axis, 53, 14); adg_path_move_to_explicit(axis, 14, 0); adg_path_line_to_explicit(axis, 14, 28); break; default: g_return_if_reached(); break; } data_class->symbol = symbol; data_class->axis = axis; data_class->extents.is_defined = FALSE; if (axis != NULL) cpml_extents_add(&data_class->extents, adg_trail_get_extents((AdgTrail *) axis)); if (symbol != NULL) cpml_extents_add(&data_class->extents, adg_trail_get_extents((AdgTrail *) symbol)); } static void _adg_render(AdgEntity *entity, cairo_t *cr) { AdgProjectionPrivate *data = adg_projection_get_instance_private((AdgProjection *) entity); AdgProjectionClassPrivate *data_class = ADG_PROJECTION_GET_CLASS(entity)->data_class; const cairo_path_t *cairo_path; cairo_transform(cr, adg_entity_get_global_matrix(entity)); if (data_class->symbol != NULL) { cairo_path = adg_trail_get_cairo_path((AdgTrail *) data_class->symbol); cairo_save(cr); cairo_transform(cr, adg_entity_get_local_matrix(entity)); cairo_append_path(cr, cairo_path); cairo_restore(cr); cairo_set_line_width(cr, 2); adg_entity_apply_dress(entity, data->symbol_dress, cr); cairo_stroke(cr); } if (data_class->axis != NULL) { const gdouble dashes[] = { 5, 2, 1, 2 }; cairo_path = adg_trail_get_cairo_path((AdgTrail *) data_class->axis); cairo_save(cr); cairo_transform(cr, adg_entity_get_local_matrix(entity)); cairo_append_path(cr, cairo_path); cairo_restore(cr); cairo_set_line_width(cr, 1); cairo_set_dash(cr, dashes, G_N_ELEMENTS(dashes), -1.5); adg_entity_apply_dress(entity, data->axis_dress, cr); cairo_stroke(cr); } } diff --git a/src/adg/adg-projection.h b/src/adg/adg-projection.h index 72c6b33b..f1ace966 100644 --- a/src/adg/adg-projection.h +++ b/src/adg/adg-projection.h @@ -1,72 +1,72 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_PROJECTION_H__ #define __ADG_PROJECTION_H__ G_BEGIN_DECLS #define ADG_TYPE_PROJECTION (adg_projection_get_type()) #define ADG_PROJECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_PROJECTION, AdgProjection)) #define ADG_PROJECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_PROJECTION, AdgProjectionClass)) #define ADG_IS_PROJECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_PROJECTION)) #define ADG_IS_PROJECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_PROJECTION)) #define ADG_PROJECTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_PROJECTION, AdgProjectionClass)) typedef struct _AdgProjection AdgProjection; typedef struct _AdgProjectionClass AdgProjectionClass; struct _AdgProjection { /*< private >*/ AdgEntity parent; }; struct _AdgProjectionClass { /*< private >*/ AdgEntityClass parent_class; gpointer data_class; }; GType adg_projection_get_type (void); AdgProjection * adg_projection_new (AdgProjectionScheme scheme); void adg_projection_set_symbol_dress (AdgProjection *projection, AdgDress dress); AdgDress adg_projection_get_symbol_dress (AdgProjection *projection); void adg_projection_set_axis_dress (AdgProjection *projection, AdgDress dress); AdgDress adg_projection_get_axis_dress (AdgProjection *projection); void adg_projection_set_scheme (AdgProjection *projection, AdgProjectionScheme scheme); AdgProjectionScheme adg_projection_get_scheme (AdgProjection *projection); G_END_DECLS #endif /* __ADG_PROJECTION_H__ */ diff --git a/src/adg/adg-rdim-private.h b/src/adg/adg-rdim-private.h index e312079c..18f0eb2a 100644 --- a/src/adg/adg-rdim-private.h +++ b/src/adg/adg-rdim-private.h @@ -1,70 +1,70 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_RDIM_PRIVATE_H__ #define __ADG_RDIM_PRIVATE_H__ G_BEGIN_DECLS /* * The cairo.data array is structured in the following way: * * [0] = MOVE_TO * [1] = baseline start * [2] = LINE_TO * [3] = baseline end * [4] = MOVE_TO * [5] = outside line start * [6] = LINE_TO * [7] = outside line end */ typedef struct _AdgRDimPrivate AdgRDimPrivate; struct _AdgRDimPrivate { AdgTrail *trail; AdgMarker *marker; gdouble radius; gdouble angle; struct { CpmlPair base; } point; struct { CpmlPair base; } shift; struct { cairo_matrix_t global_map; } quote; struct { cairo_path_t path; cairo_path_data_t data[8]; } cairo; }; G_END_DECLS #endif /* __ADG_RDIM_PRIVATE_H__ */ diff --git a/src/adg/adg-rdim.c b/src/adg/adg-rdim.c index 006689eb..d7500eef 100644 --- a/src/adg/adg-rdim.c +++ b/src/adg/adg-rdim.c @@ -1,616 +1,616 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-rdim * @short_description: Radial dimensions * * The #AdgRDim entity represents a radial dimension. * * Since: 1.0 **/ /** * AdgRDim: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-container.h" #include "adg-alignment.h" #include "adg-model.h" #include "adg-point.h" #include "adg-trail.h" #include "adg-marker.h" #include "adg-style.h" #include "adg-dim-style.h" #include "adg-dress.h" #include "adg-textual.h" #include "adg-toy-text.h" #include "adg-dim.h" #include "adg-dim-private.h" #include #include "adg-rdim.h" #include "adg-rdim-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_rdim_parent_class) #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_rdim_parent_class) G_DEFINE_TYPE_WITH_PRIVATE(AdgRDim, adg_rdim, ADG_TYPE_DIM) enum { PROP_0, }; static void _adg_dispose (GObject *object); static void _adg_global_changed (AdgEntity *entity); static void _adg_local_changed (AdgEntity *entity); static void _adg_invalidate (AdgEntity *entity); static void _adg_arrange (AdgEntity *entity); static void _adg_render (AdgEntity *entity, cairo_t *cr); static gchar * _adg_default_value (AdgDim *dim); static gboolean _adg_compute_geometry (AdgDim *dim); static void _adg_update_entities (AdgRDim *rdim); static void _adg_clear_trail (AdgRDim *rdim); static void _adg_dispose_trail (AdgRDim *rdim); static void _adg_dispose_marker (AdgRDim *rdim); static cairo_path_t * _adg_trail_callback (AdgTrail *trail, gpointer user_data); static void adg_rdim_class_init(AdgRDimClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; AdgDimClass *dim_class; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; dim_class = (AdgDimClass *) klass; gobject_class->dispose = _adg_dispose; entity_class->global_changed = _adg_global_changed; entity_class->local_changed = _adg_local_changed; entity_class->invalidate = _adg_invalidate; entity_class->arrange = _adg_arrange; entity_class->render = _adg_render; dim_class->default_value = _adg_default_value; dim_class->compute_geometry = _adg_compute_geometry; } static void adg_rdim_init(AdgRDim *rdim) { AdgRDimPrivate *data = adg_rdim_get_instance_private(rdim); AdgStyle *style; AdgDimStyle *dim_style; cairo_path_data_t move_to, line_to; move_to.header.type = CPML_MOVE; move_to.header.length = 2; line_to.header.type = CPML_LINE; line_to.header.length = 2; data->trail = NULL; data->marker = NULL; data->radius = -1.; data->angle = 0.; data->shift.base.x = data->shift.base.y = 0; cairo_matrix_init_identity(&data->quote.global_map); data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA; data->cairo.path.data = data->cairo.data; data->cairo.path.num_data = G_N_ELEMENTS(data->cairo.data); data->cairo.path.data[0] = move_to; data->cairo.path.data[2] = line_to; data->cairo.path.data[4] = move_to; data->cairo.path.data[6] = line_to; /* Override the default dimension style to prefix the quote with an R */ style = adg_dress_get_fallback(ADG_DRESS_DIMENSION); dim_style = (AdgDimStyle *) adg_style_clone(style); adg_dim_style_set_number_format(dim_style, "R%g"); adg_entity_set_style((AdgEntity *) rdim, ADG_DRESS_DIMENSION, (AdgStyle *) dim_style); } static void _adg_dispose(GObject *object) { AdgRDim *rdim = (AdgRDim *) object; _adg_dispose_trail(rdim); _adg_dispose_marker(rdim); if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } /** * adg_rdim_new: * * Creates a new uninitialized radial dimension. To be useful, you * need at least define the center of the arc to quote in #AdgDim:ref1, * a point on the arc in #AdgDim:ref2 and the position of the quote * in #AdgDim:pos using any valid #AdgDim method. * * Returns: a newly created quote * * Since: 1.0 **/ AdgRDim * adg_rdim_new(void) { return g_object_new(ADG_TYPE_RDIM, NULL); } /** * adg_rdim_new_full: * @center: (allow-none): center of the arc to quote * @radius: (allow-none): where the quote must be applied on the arc * @pos: (allow-none): position of the quote text * * Creates a new quote by specifying explicitely all the needed * data to get a valid quote. * * Returns: the newly created quote. * * Since: 1.0 **/ AdgRDim * adg_rdim_new_full(const CpmlPair *center, const CpmlPair *radius, const CpmlPair *pos) { AdgRDim *rdim; AdgDim *dim; rdim = adg_rdim_new(); dim = (AdgDim *) rdim; if (center != NULL) adg_dim_set_ref1_from_pair(dim, center); if (radius != NULL) adg_dim_set_ref2_from_pair(dim, radius); if (pos != NULL) adg_dim_set_pos_from_pair(dim, pos); return rdim; } /** * adg_rdim_new_full_explicit: * @center_x: x coordinate of the center of the arc to quote * @center_y: y coordinate of the center of the arc to quote * @radius_x: x coordiante where the quote must be applied on the arc * @radius_y: y coordiante where the quote must be applied on the arc * @pos_x: x coordinate of the quote text * @pos_y: y coordinate of the quote text * * Does the same job of adg_rdim_new_full() but using specific * coordinates instead of #CpmlPair structures. * * Returns: the newly created quote. * * Since: 1.0 **/ AdgRDim * adg_rdim_new_full_explicit(gdouble center_x, gdouble center_y, gdouble radius_x, gdouble radius_y, gdouble pos_x, gdouble pos_y) { CpmlPair center, radius, pos; center.x = center_x; center.y = center_y; radius.x = radius_x; radius.y = radius_y; pos.x = pos_x; pos.y = pos_y; return adg_rdim_new_full(¢er, &radius, &pos); } /** * adg_rdim_new_full_from_model: * @model: (transfer none): the model from which the named pairs are taken * @center: (allow-none): the center point of the arc to quote * @radius: (allow-none): an arbitrary point on the arc * @pos: (allow-none): the position reference * * Creates a new radial dimension, specifing all the needed properties in * one shot and using named pairs from @model. * * Returns: the newly created radial dimension entity * * Since: 1.0 **/ AdgRDim * adg_rdim_new_full_from_model(AdgModel *model, const gchar *center, const gchar *radius, const gchar *pos) { AdgRDim *rdim; AdgDim *dim; g_return_val_if_fail(model != NULL, NULL); rdim = adg_rdim_new(); dim = (AdgDim *) rdim; if (center != NULL) adg_dim_set_ref1_from_model(dim, model, center); if (radius != NULL) adg_dim_set_ref2_from_model(dim, model, radius); if (pos != NULL) adg_dim_set_pos_from_model(dim, model, pos); return rdim; } static void _adg_global_changed(AdgEntity *entity) { AdgRDim *rdim = (AdgRDim *) entity; AdgRDimPrivate *data = adg_rdim_get_instance_private(rdim); _adg_clear_trail(rdim); if (_ADG_OLD_ENTITY_CLASS->global_changed) _ADG_OLD_ENTITY_CLASS->global_changed(entity); if (data->marker != NULL) adg_entity_global_changed((AdgEntity *) data->marker); } static void _adg_local_changed(AdgEntity *entity) { _adg_clear_trail((AdgRDim *) entity); if (_ADG_OLD_ENTITY_CLASS->local_changed) _ADG_OLD_ENTITY_CLASS->local_changed(entity); } static void _adg_invalidate(AdgEntity *entity) { AdgRDim *rdim = (AdgRDim *) entity; _adg_dispose_trail(rdim); _adg_dispose_marker(rdim); _adg_clear_trail(rdim); if (_ADG_OLD_ENTITY_CLASS->invalidate) _ADG_OLD_ENTITY_CLASS->invalidate(entity); } static void _adg_arrange(AdgEntity *entity) { AdgRDim *rdim; AdgDim *dim; AdgRDimPrivate *data; AdgAlignment *quote; AdgEntity *quote_entity; gboolean outside; const cairo_matrix_t *global, *local; CpmlPair ref2, base; CpmlPair pair; CpmlExtents extents; if (_ADG_OLD_ENTITY_CLASS->arrange != NULL) _ADG_OLD_ENTITY_CLASS->arrange(entity); dim = (AdgDim *) entity; if (! adg_dim_compute_geometry(dim)) return; rdim = (AdgRDim *) entity; data = adg_rdim_get_instance_private(rdim); quote = adg_dim_get_quote(dim); quote_entity = (AdgEntity *) quote; _adg_update_entities(rdim); /* Check for cached result */ if (data->cairo.path.status == CAIRO_STATUS_SUCCESS) { adg_entity_set_global_map(quote_entity, &data->quote.global_map); return; } outside = adg_dim_get_outside(dim); if (outside == ADG_THREE_STATE_UNKNOWN) outside = ADG_THREE_STATE_OFF; global = adg_entity_get_global_matrix(entity); local = adg_entity_get_local_matrix(entity); extents.is_defined = FALSE; cpml_pair_copy(&ref2, (CpmlPair *) adg_dim_get_ref2(dim)); cpml_pair_copy(&base, &data->point.base); cpml_pair_transform(&ref2, local); cpml_pair_transform(&base, local); base.x += data->shift.base.x; base.y += data->shift.base.y; /* baseline start */ cpml_pair_to_cairo(&base, &data->cairo.data[1]); /* baseline end */ cpml_pair_to_cairo(&ref2, &data->cairo.data[3]); if (outside) { AdgDimStyle *dim_style; gdouble beyond; CpmlVector vector; dim_style = adg_dim_get_dim_style(dim); beyond = adg_dim_style_get_beyond(dim_style); vector.x = ref2.x - base.x; vector.y = ref2.y - base.y; cpml_vector_set_length(&vector, beyond); pair.x = ref2.x + vector.x; pair.y = ref2.y + vector.y; data->cairo.data[2].header.length = 2; /* Outside segment start */ cpml_pair_to_cairo(&pair, &data->cairo.data[5]); /* Outside segment end */ cpml_pair_to_cairo(&ref2, &data->cairo.data[7]); } else { data->cairo.data[2].header.length = 6; } /* Arrange the quote */ if (quote != NULL) { cairo_matrix_t map; gdouble x_align; gdouble quote_angle = adg_dim_quote_angle(dim, data->angle); x_align = cpml_angle_distance(quote_angle, data->angle) > G_PI_2 ? 0 : 1; adg_alignment_set_factor_explicit(quote, x_align, 0); cpml_pair_from_cairo(&pair, &data->cairo.data[1]); cairo_matrix_init_translate(&map, pair.x, pair.y); cairo_matrix_rotate(&map, quote_angle); adg_entity_set_global_map(quote_entity, &map); adg_entity_arrange(quote_entity); cpml_extents_add(&extents, adg_entity_get_extents(quote_entity)); adg_matrix_copy(&data->quote.global_map, &map); } data->cairo.path.status = CAIRO_STATUS_SUCCESS; /* Arrange the trail */ if (data->trail != NULL) { CpmlExtents trail_extents; cpml_extents_copy(&trail_extents, adg_trail_get_extents(data->trail)); cpml_extents_transform(&trail_extents, global); cpml_extents_add(&extents, &trail_extents); } else { _adg_dispose_marker(rdim); } /* Arrange the marker */ if (data->marker != NULL) { AdgEntity *marker_entity = (AdgEntity *) data->marker; adg_marker_set_segment(data->marker, data->trail, outside ? 2 : 1); adg_entity_local_changed(marker_entity); adg_entity_arrange(marker_entity); cpml_extents_add(&extents, adg_entity_get_extents(marker_entity)); } adg_entity_set_extents(entity, &extents); } static void _adg_render(AdgEntity *entity, cairo_t *cr) { AdgDim *dim; AdgRDimPrivate *data; AdgDimStyle *dim_style; AdgDress dress; const cairo_path_t *cairo_path; dim = (AdgDim *) entity; if (! adg_dim_compute_geometry(dim)) { /* Entity not arranged, probably due to undefined pair found */ return; } data = adg_rdim_get_instance_private((AdgRDim *) dim); dim_style = adg_dim_get_dim_style(dim); adg_style_apply((AdgStyle *) dim_style, entity, cr); adg_entity_render((AdgEntity *) adg_dim_get_quote(dim), cr); if (data->marker != NULL) adg_entity_render((AdgEntity *) data->marker, cr); cairo_transform(cr, adg_entity_get_global_matrix(entity)); dress = adg_dim_style_get_line_dress(dim_style); adg_entity_apply_dress(entity, dress, cr); cairo_path = adg_trail_get_cairo_path(data->trail); cairo_append_path(cr, cairo_path); cairo_stroke(cr); } static gchar * _adg_default_value(AdgDim *dim) { AdgRDimPrivate *data; if (! adg_dim_compute_geometry(dim)) return g_strdup("undef"); data = adg_rdim_get_instance_private((AdgRDim *) dim); return adg_dim_get_text(dim, data->radius); } static gboolean _adg_compute_geometry(AdgDim *dim) { AdgRDimPrivate *data; AdgDimStyle *dim_style; AdgPoint *ref1_point, *ref2_point, *pos_point; const CpmlPair *ref1, *ref2, *pos; gdouble spacing, level, pos_distance; CpmlVector vector; ref1_point = adg_dim_get_ref1(dim); if (! adg_point_update(ref1_point)) { adg_dim_geometry_missing(dim, "ref1"); return FALSE; } ref2_point = adg_dim_get_ref2(dim); if (! adg_point_update(ref2_point)) { adg_dim_geometry_missing(dim, "ref2"); return FALSE; } pos_point = adg_dim_get_pos(dim); if (! adg_point_update(pos_point)) { adg_dim_geometry_missing(dim, "pos"); return FALSE; } ref1 = (CpmlPair *) ref1_point; ref2 = (CpmlPair *) ref2_point; if (cpml_pair_equal(ref1, ref2)) { adg_dim_geometry_coincident(dim, "ref1", "ref2", ref1); return FALSE; } data = adg_rdim_get_instance_private((AdgRDim *) dim); pos = (CpmlPair *) pos_point; dim_style = adg_dim_get_dim_style(dim); spacing = adg_dim_style_get_baseline_spacing(dim_style); level = adg_dim_get_level(dim); pos_distance = cpml_pair_distance(pos, ref1); vector.x = ref2->x - ref1->x; vector.y = ref2->y - ref1->y; if (cpml_pair_squared_distance(pos, ref1) < cpml_pair_squared_distance(pos, ref2)) { vector.x = -vector.x; vector.y = -vector.y; } /* radius */ data->radius = cpml_pair_distance(&vector, NULL); /* angle */ data->angle = cpml_vector_angle(&vector); /* point.base */ cpml_pair_copy(&data->point.base, &vector); cpml_vector_set_length(&data->point.base, pos_distance); data->point.base.x += ref1->x; data->point.base.y += ref1->y; /* shift.base */ cpml_pair_copy(&data->shift.base, &vector); cpml_vector_set_length(&data->shift.base, spacing * level); return TRUE; } static void _adg_update_entities(AdgRDim *rdim) { AdgEntity *entity = (AdgEntity *) rdim; AdgRDimPrivate *data = adg_rdim_get_instance_private(rdim); AdgDimStyle *dim_style = adg_dim_get_dim_style((AdgDim *) rdim); if (data->trail == NULL) data->trail = adg_trail_new(_adg_trail_callback, rdim); if (data->marker == NULL) { data->marker = adg_dim_style_marker2_new(dim_style); adg_entity_set_parent((AdgEntity *) data->marker, entity); } } static void _adg_clear_trail(AdgRDim *rdim) { AdgRDimPrivate *data = adg_rdim_get_instance_private(rdim); if (data->trail != NULL) adg_model_clear((AdgModel *) data->trail); data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA; } static void _adg_dispose_trail(AdgRDim *rdim) { AdgRDimPrivate *data = adg_rdim_get_instance_private(rdim); if (data->trail != NULL) { g_object_unref(data->trail); data->trail = NULL; } } static void _adg_dispose_marker(AdgRDim *rdim) { AdgRDimPrivate *data = adg_rdim_get_instance_private(rdim); if (data->marker != NULL) { g_object_unref(data->marker); data->marker = NULL; } } static cairo_path_t * _adg_trail_callback(AdgTrail *trail, gpointer user_data) { AdgRDimPrivate *data = adg_rdim_get_instance_private((AdgRDim *) user_data); return &data->cairo.path; } diff --git a/src/adg/adg-rdim.h b/src/adg/adg-rdim.h index 64e6c33f..e71bd0b5 100644 --- a/src/adg/adg-rdim.h +++ b/src/adg/adg-rdim.h @@ -1,74 +1,74 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_RDIM_H__ #define __ADG_RDIM_H__ G_BEGIN_DECLS #define ADG_TYPE_RDIM (adg_rdim_get_type()) #define ADG_RDIM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_RDIM, AdgRDim)) #define ADG_RDIM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_RDIM, AdgRDimClass)) #define ADG_IS_RDIM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_RDIM)) #define ADG_IS_RDIM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_RDIM)) #define ADG_RDIM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_RDIM, AdgRDimClass)) typedef struct _AdgRDim AdgRDim; typedef struct _AdgRDimClass AdgRDimClass; struct _AdgRDim { /*< private >*/ AdgDim parent; }; struct _AdgRDimClass { /*< private >*/ AdgDimClass parent_class; }; GType adg_rdim_get_type (void); AdgRDim * adg_rdim_new (void); AdgRDim * adg_rdim_new_full (const CpmlPair *center, const CpmlPair *radius, const CpmlPair *pos); AdgRDim * adg_rdim_new_full_explicit (gdouble center_x, gdouble center_y, gdouble radius_x, gdouble radius_y, gdouble pos_x, gdouble pos_y); AdgRDim * adg_rdim_new_full_from_model (AdgModel *model, const gchar *center, const gchar *radius, const gchar *pos); G_END_DECLS #endif /* __ADG_RDIM_H__ */ diff --git a/src/adg/adg-ruled-fill-private.h b/src/adg/adg-ruled-fill-private.h index 7d4c10a7..e6ca9e0e 100644 --- a/src/adg/adg-ruled-fill-private.h +++ b/src/adg/adg-ruled-fill-private.h @@ -1,37 +1,37 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_RULED_FILL_PRIVATE_H__ #define __ADG_RULED_FILL_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgRuledFillPrivate AdgRuledFillPrivate; struct _AdgRuledFillPrivate { AdgDress line_dress; gdouble spacing; gdouble angle; }; G_END_DECLS #endif /* __ADG_RULED_FILL_PRIVATE_H__ */ diff --git a/src/adg/adg-ruled-fill.c b/src/adg/adg-ruled-fill.c index 87f07708..73ad2657 100644 --- a/src/adg/adg-ruled-fill.c +++ b/src/adg/adg-ruled-fill.c @@ -1,468 +1,468 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-ruled-fill * @short_description: A style composed of evenly spaced lines * * The spacing between the lines could be changed using the * adg_ruled_fill_set_spacing() method. The angle of the lines should * be changed with adg_ruled_fill_set_angle(). * * Since: 1.0 **/ /** * AdgRuledFill: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-style.h" #include "adg-fill-style.h" #include "adg-dress.h" #include "adg-param-dress.h" #include "adg-ruled-fill.h" #include "adg-ruled-fill-private.h" #include #define _ADG_OLD_STYLE_CLASS ((AdgStyleClass *) adg_ruled_fill_parent_class) #define _ADG_OLD_FILL_STYLE_CLASS ((AdgFillStyleClass *) adg_ruled_fill_parent_class) G_DEFINE_TYPE_WITH_PRIVATE(AdgRuledFill, adg_ruled_fill, ADG_TYPE_FILL_STYLE) enum { PROP_0, PROP_LINE_DRESS, PROP_SPACING, PROP_ANGLE }; static void _adg_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void _adg_apply (AdgStyle *style, AdgEntity *entity, cairo_t *cr); static void _adg_set_extents (AdgFillStyle *fill_style, const CpmlExtents *extents); static cairo_pattern_t *_adg_create_pattern (AdgRuledFill *ruled_fill, AdgEntity *entity, cairo_t *cr); static void _adg_draw_lines (const CpmlPair *spacing, const CpmlPair *size, cairo_t *cr); static void adg_ruled_fill_class_init(AdgRuledFillClass *klass) { GObjectClass *gobject_class; AdgStyleClass *style_class; AdgFillStyleClass *fill_style_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; style_class = (AdgStyleClass *) klass; fill_style_class = (AdgFillStyleClass *) klass; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; style_class->apply = _adg_apply; fill_style_class->set_extents = _adg_set_extents; param = adg_param_spec_dress("line-dress", P_("Line Dress"), P_("Dress to be used for rendering the lines"), ADG_DRESS_LINE_FILL, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LINE_DRESS, param); param = g_param_spec_double("spacing", P_("Spacing"), P_("The spacing in global spaces between the lines"), 0, G_MAXDOUBLE, 16, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_SPACING, param); param = g_param_spec_double("angle", P_("Angle"), P_("The angle (in radians) of the lines"), 0, G_PI, G_PI_4, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_ANGLE, param); } static void adg_ruled_fill_init(AdgRuledFill *ruled_fill) { AdgRuledFillPrivate *data = adg_ruled_fill_get_instance_private(ruled_fill); data->line_dress = ADG_DRESS_LINE_FILL; data->angle = G_PI_4; data->spacing = 16; } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgRuledFillPrivate *data = adg_ruled_fill_get_instance_private((AdgRuledFill *) object); switch (prop_id) { case PROP_LINE_DRESS: g_value_set_enum(value, data->line_dress); break; case PROP_SPACING: g_value_set_double(value, data->spacing); break; case PROP_ANGLE: g_value_set_double(value, data->angle); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgRuledFillPrivate *data = adg_ruled_fill_get_instance_private((AdgRuledFill *) object); switch (prop_id) { case PROP_LINE_DRESS: data->line_dress = g_value_get_enum(value); break; case PROP_SPACING: data->spacing = g_value_get_double(value); adg_fill_style_set_pattern((AdgFillStyle *) object, NULL); break; case PROP_ANGLE: data->angle = g_value_get_double(value); adg_fill_style_set_pattern((AdgFillStyle *) object, NULL); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_ruled_fill_new: * * Constructs a new empty ruled fill style initialized with default params. * * Returns: a newly created ruled fill style * * Since: 1.0 **/ AdgRuledFill * adg_ruled_fill_new(void) { return g_object_new(ADG_TYPE_RULED_FILL, NULL); } /** * adg_ruled_fill_set_line_dress: * @ruled_fill: an #AdgRuledFill object * @dress: the new line dress * * Sets a new line dress on @ruled_fill. * * Since: 1.0 **/ void adg_ruled_fill_set_line_dress(AdgRuledFill *ruled_fill, AdgDress dress) { g_return_if_fail(ADG_IS_RULED_FILL(ruled_fill)); g_object_set(ruled_fill, "line-dress", dress, NULL); } /** * adg_ruled_fill_get_line_dress: * @ruled_fill: an #AdgRuledFill object * * Gets the @ruled_fill dress to be used for rendering the lines. * * Returns: (transfer none): the line dress. * * Since: 1.0 **/ AdgDress adg_ruled_fill_get_line_dress(AdgRuledFill *ruled_fill) { AdgRuledFillPrivate *data; g_return_val_if_fail(ADG_IS_RULED_FILL(ruled_fill), ADG_DRESS_UNDEFINED); data = adg_ruled_fill_get_instance_private(ruled_fill); return data->line_dress; } /** * adg_ruled_fill_set_spacing: * @ruled_fill: an #AdgRuledFill * @spacing: the new spacing * * Sets a new spacing on @ruled_fill. * * Since: 1.0 **/ void adg_ruled_fill_set_spacing(AdgRuledFill *ruled_fill, gdouble spacing) { g_return_if_fail(ADG_IS_RULED_FILL(ruled_fill)); g_object_set(ruled_fill, "spacing", spacing, NULL); } /** * adg_ruled_fill_get_spacing: * @ruled_fill: an #AdgRuledFill * * Gets the current spacing of @ruled_fill. * * Returns: the spacing (in global space) * * Since: 1.0 **/ gdouble adg_ruled_fill_get_spacing(AdgRuledFill *ruled_fill) { AdgRuledFillPrivate *data; g_return_val_if_fail(ADG_IS_RULED_FILL(ruled_fill), 0); data = adg_ruled_fill_get_instance_private(ruled_fill); return data->spacing; } /** * adg_ruled_fill_set_angle: * @ruled_fill: an #AdgRuledFill * @angle: the new angle * * Sets a new angle on @ruled_fill. * * Since: 1.0 **/ void adg_ruled_fill_set_angle(AdgRuledFill *ruled_fill, gdouble angle) { g_return_if_fail(ADG_IS_RULED_FILL(ruled_fill)); g_object_set(ruled_fill, "angle", angle, NULL); } /** * adg_ruled_fill_get_angle: * @ruled_fill: an #AdgRuledFill * * Gets the current angle of @ruled_fill. * * Returns: the angle (in radians) * * Since: 1.0 **/ gdouble adg_ruled_fill_get_angle(AdgRuledFill *ruled_fill) { AdgRuledFillPrivate *data; g_return_val_if_fail(ADG_IS_RULED_FILL(ruled_fill), 0); data = adg_ruled_fill_get_instance_private(ruled_fill); return data->angle; } static void _adg_apply(AdgStyle *style, AdgEntity *entity, cairo_t *cr) { AdgFillStyle *fill_style; cairo_pattern_t *pattern; const CpmlExtents *extents; fill_style = (AdgFillStyle *) style; pattern = adg_fill_style_get_pattern(fill_style); extents = adg_fill_style_get_extents(fill_style); if (pattern == NULL) { pattern = _adg_create_pattern((AdgRuledFill *) style, entity, cr); if (pattern == NULL) return; adg_fill_style_set_pattern(fill_style, pattern); cairo_pattern_destroy(pattern); } cairo_translate(cr, extents->org.x, extents->org.y); if (_ADG_OLD_STYLE_CLASS->apply) _ADG_OLD_STYLE_CLASS->apply(style, entity, cr); } static void _adg_set_extents(AdgFillStyle *fill_style, const CpmlExtents *extents) { CpmlExtents old, new; cpml_extents_copy(&old, adg_fill_style_get_extents(fill_style)); /* The pattern is invalidated (and thus regenerated) only * when the new extents are wider than the old ones */ if (old.size.x >= extents->size.x && old.size.y >= extents->size.y) { new.org = extents->org; new.size = old.size; } else { cpml_extents_copy(&new, extents); adg_fill_style_set_pattern(fill_style, NULL); } if (_ADG_OLD_FILL_STYLE_CLASS->set_extents) _ADG_OLD_FILL_STYLE_CLASS->set_extents(fill_style, &new); } static cairo_pattern_t * _adg_create_pattern(AdgRuledFill *ruled_fill, AdgEntity *entity, cairo_t *cr) { AdgFillStyle *fill_style; const CpmlExtents *extents; AdgRuledFillPrivate *data; AdgStyle *line_style; cairo_pattern_t *pattern; cairo_surface_t *surface; CpmlPair spacing; cairo_t *context; fill_style = (AdgFillStyle *) ruled_fill; extents = adg_fill_style_get_extents(fill_style); /* Check for valid extents */ if (!extents->is_defined) return NULL; data = adg_ruled_fill_get_instance_private(ruled_fill); line_style = adg_entity_style(entity, data->line_dress); surface = cairo_surface_create_similar(cairo_get_target(cr), CAIRO_CONTENT_COLOR_ALPHA, extents->size.x, extents->size.y); pattern = cairo_pattern_create_for_surface(surface); /* The pattern holds a reference to the surface, so * there is no need to hold another reference here */ cairo_surface_destroy(surface); spacing.x = cos(data->angle) * data->spacing; spacing.y = sin(data->angle) * data->spacing; context = cairo_create(surface); adg_style_apply(line_style, entity, context); _adg_draw_lines(&spacing, &extents->size, context); cairo_destroy(context); return pattern; } static void _adg_draw_lines(const CpmlPair *spacing, const CpmlPair *size, cairo_t *cr) { CpmlPair step, step1, step2; CpmlPair p1, p2; /* There should be some sort of spacing and a destination area */ if ((spacing->x == 0 && spacing->y == 0) || (size->x <= 0 && size->y <= 0)) return; /* Revert spacings if needed to inspect only the x >= 0 cases */ cpml_pair_copy(&step, spacing); if (spacing->x < 0 || (spacing->x == 0 && spacing->y < 0)) { step.x = -step.x; step.y = -step.y; } p1.x = step.x / 2; p2.y = step.y / 2; p1.y = step.y == 0 ? p2.y : 0; p2.x = step.x == 0 ? p1.x : 0; if (step.y < 0) { p1.y += size->y; p2.y += size->y; } step2.x = 0; step2.y = step.y; if (step.x != 0) { step1.x = step.x; step1.y = 0; while (p1.x < size->x) { if (p2.y <= 0 || p2.y >= size->y) { step2.x = step.x; step2.y = 0; } cairo_move_to(cr, p1.x, p1.y); cairo_line_to(cr, p2.x, p2.y); p1.x += step1.x; p1.y += step1.y; p2.x += step2.x; p2.y += step2.y; } } if (step.y != 0) { step1.x = 0; step1.y = step.y; while (p1.y >= 0 && p1.y <= size->y) { if (p2.y <= 0 || p2.y >= size->y) { step2.x = step.x; step2.y = 0; } cairo_move_to(cr, p1.x, p1.y); cairo_line_to(cr, p2.x, p2.y); p1.x += step1.x; p1.y += step1.y; p2.x += step2.x; p2.y += step2.y; } } cairo_stroke(cr); } diff --git a/src/adg/adg-ruled-fill.h b/src/adg/adg-ruled-fill.h index e7dd141a..af991b69 100644 --- a/src/adg/adg-ruled-fill.h +++ b/src/adg/adg-ruled-fill.h @@ -1,69 +1,69 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_RULED_FILL_H__ #define __ADG_RULED_FILL_H__ G_BEGIN_DECLS #define ADG_TYPE_RULED_FILL (adg_ruled_fill_get_type()) #define ADG_RULED_FILL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_RULED_FILL, AdgRuledFill)) #define ADG_RULED_FILL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_RULED_FILL, AdgRuledFillClass)) #define ADG_IS_RULED_FILL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_RULED_FILL)) #define ADG_IS_RULED_FILL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_RULED_FILL)) #define ADG_RULED_FILL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_RULED_FILL, AdgRuledFillClass)) typedef struct _AdgRuledFill AdgRuledFill; typedef struct _AdgRuledFillClass AdgRuledFillClass; struct _AdgRuledFill { /*< private >*/ AdgFillStyle parent; }; struct _AdgRuledFillClass { /*< private >*/ AdgFillStyleClass parent_class; }; GType adg_ruled_fill_get_type (void); AdgRuledFill * adg_ruled_fill_new (void); void adg_ruled_fill_set_line_dress (AdgRuledFill *ruled_fill, AdgDress dress); AdgDress adg_ruled_fill_get_line_dress (AdgRuledFill *ruled_fill); void adg_ruled_fill_set_spacing (AdgRuledFill *ruled_fill, gdouble spacing); gdouble adg_ruled_fill_get_spacing (AdgRuledFill *ruled_fill); void adg_ruled_fill_set_angle (AdgRuledFill *ruled_fill, gdouble angle); gdouble adg_ruled_fill_get_angle (AdgRuledFill *ruled_fill); G_END_DECLS #endif /* __ADG_RULED_FILL_H__ */ diff --git a/src/adg/adg-stroke-private.h b/src/adg/adg-stroke-private.h index 387f305f..2efec72c 100644 --- a/src/adg/adg-stroke-private.h +++ b/src/adg/adg-stroke-private.h @@ -1,37 +1,37 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_STROKE_PRIVATE_H__ #define __ADG_STROKE_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgStrokePrivate AdgStrokePrivate; struct _AdgStrokePrivate { AdgDress line_dress; AdgTrail *trail; }; G_END_DECLS #endif /* __ADG_STROKE_PRIVATE_H__ */ diff --git a/src/adg/adg-stroke.c b/src/adg/adg-stroke.c index 5c612478..b074aa41 100644 --- a/src/adg/adg-stroke.c +++ b/src/adg/adg-stroke.c @@ -1,360 +1,360 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-stroke * @short_description: A stroked entity * * The #AdgStroke object is a stroked representation of an #AdgTrail model. * * Since: 1.0 **/ /** * AdgStroke: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-style.h" #include "adg-dash.h" #include "adg-line-style.h" #include "adg-model.h" #include "adg-trail.h" #include "adg-dress.h" #include "adg-param-dress.h" #include "adg-stroke.h" #include "adg-stroke-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_stroke_parent_class) #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_stroke_parent_class) G_DEFINE_TYPE_WITH_PRIVATE(AdgStroke, adg_stroke, ADG_TYPE_ENTITY) enum { PROP_0, PROP_LINE_DRESS, PROP_TRAIL }; static void _adg_dispose (GObject *object); static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void _adg_global_changed (AdgEntity *entity); static void _adg_local_changed (AdgEntity *entity); static void _adg_arrange (AdgEntity *entity); static void _adg_render (AdgEntity *entity, cairo_t *cr); static void _adg_unset_trail (AdgStroke *stroke); static void adg_stroke_class_init(AdgStrokeClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; entity_class->global_changed = _adg_global_changed; entity_class->local_changed = _adg_local_changed; entity_class->arrange = _adg_arrange; entity_class->render = _adg_render; param = adg_param_spec_dress("line-dress", P_("Line Dress"), P_("The dress to use for stroking this entity"), ADG_DRESS_LINE_STROKE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LINE_DRESS, param); param = g_param_spec_object("trail", P_("Trail"), P_("The trail to be stroked"), ADG_TYPE_TRAIL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); g_object_class_install_property(gobject_class, PROP_TRAIL, param); } static void adg_stroke_init(AdgStroke *stroke) { AdgStrokePrivate *data = adg_stroke_get_instance_private(stroke); data->line_dress = ADG_DRESS_LINE_STROKE; data->trail = NULL; } static void _adg_dispose(GObject *object) { AdgStroke *stroke = (AdgStroke *) object; adg_stroke_set_trail(stroke, NULL); if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgStrokePrivate *data = adg_stroke_get_instance_private((AdgStroke *) object); switch (prop_id) { case PROP_LINE_DRESS: g_value_set_enum(value, data->line_dress); break; case PROP_TRAIL: g_value_set_object(value, data->trail); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgStrokePrivate *data = adg_stroke_get_instance_private((AdgStroke *) object); AdgTrail *old_trail; switch (prop_id) { case PROP_LINE_DRESS: data->line_dress = g_value_get_enum(value); break; case PROP_TRAIL: old_trail = data->trail; data->trail = g_value_get_object(value); if (data->trail != old_trail) { if (data->trail != NULL) { g_object_ref(data->trail); g_object_weak_ref((GObject *) data->trail, (GWeakNotify) _adg_unset_trail, object); adg_model_add_dependency((AdgModel *) data->trail, (AdgEntity *) object); } if (old_trail != NULL) { g_object_unref(data->trail); g_object_weak_unref((GObject *) old_trail, (GWeakNotify) _adg_unset_trail, object); adg_model_remove_dependency((AdgModel *) old_trail, (AdgEntity *) object); } } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_stroke_new: * @trail: the #AdgTrail to stroke * * Creates a new stroke entity based on the @trail model. * @trail can be NULL, in which case * an empty stroke is created. * * Returns: the newly created stroke entity * * Since: 1.0 **/ AdgStroke * adg_stroke_new(AdgTrail *trail) { return g_object_new(ADG_TYPE_STROKE, "trail", trail, NULL); } /** * adg_stroke_set_line_dress: * @stroke: an #AdgStroke * @dress: the new #AdgDress to use * * Sets a new line dress for rendering @stroke. The new dress * must be related to the original dress for this property: * you cannot set a dress used for line styles to a dress * managing fonts. * * The check is done by calling adg_dress_are_related() with * @dress and the previous dress as arguments. Check out its * documentation for details on what is a related dress. * * Since: 1.0 **/ void adg_stroke_set_line_dress(AdgStroke *stroke, AdgDress dress) { g_return_if_fail(ADG_IS_STROKE(stroke)); g_object_set(stroke, "line-dress", dress, NULL); } /** * adg_stroke_get_line_dress: * @stroke: an #AdgStroke * * Gets the line dress to be used in rendering @stroke. * * Returns: (transfer none): the current line dress. * * Since: 1.0 **/ AdgDress adg_stroke_get_line_dress(AdgStroke *stroke) { AdgStrokePrivate *data; g_return_val_if_fail(ADG_IS_STROKE(stroke), ADG_DRESS_UNDEFINED); data = adg_stroke_get_instance_private(stroke); return data->line_dress; } /** * adg_stroke_set_trail: * @stroke: an #AdgStroke * @trail: (allow-none) (transfer none): the new #AdgTrail to bind * * Sets @trail as the new trail to be stroked by @stroke. * * Since: 1.0 **/ void adg_stroke_set_trail(AdgStroke *stroke, AdgTrail *trail) { g_return_if_fail(ADG_IS_STROKE(stroke)); g_object_set(stroke, "trail", trail, NULL); } /** * adg_stroke_get_trail: * @stroke: an #AdgStroke * * Gets the #AdgTrail bound to this @stroke entity. * The returned trail is owned by @stroke and should not * be freed or modified. * * Returns: (transfer none): the requested #AdgTrail or NULL on errors. * * Since: 1.0 **/ AdgTrail * adg_stroke_get_trail(AdgStroke *stroke) { AdgStrokePrivate *data; g_return_val_if_fail(ADG_IS_STROKE(stroke), NULL); data = adg_stroke_get_instance_private(stroke); return data->trail; } static void _adg_global_changed(AdgEntity *entity) { if (_ADG_OLD_ENTITY_CLASS->global_changed) _ADG_OLD_ENTITY_CLASS->global_changed(entity); adg_entity_invalidate(entity); } static void _adg_local_changed(AdgEntity *entity) { if (_ADG_OLD_ENTITY_CLASS->local_changed) _ADG_OLD_ENTITY_CLASS->local_changed(entity); adg_entity_invalidate(entity); } static void _adg_arrange(AdgEntity *entity) { AdgStrokePrivate *data; const CpmlExtents *trail_extents; CpmlExtents extents; /* Check for cached result */ if (adg_entity_get_extents(entity)->is_defined) return; data = adg_stroke_get_instance_private((AdgStroke *) entity); trail_extents = adg_trail_get_extents(data->trail); /* Ensure a trail is bound to this entity */ if (trail_extents == NULL) return; cpml_extents_copy(&extents, trail_extents); cpml_extents_transform(&extents, adg_entity_get_local_matrix(entity)); cpml_extents_transform(&extents, adg_entity_get_global_matrix(entity)); adg_entity_set_extents(entity, &extents); } static void _adg_render(AdgEntity *entity, cairo_t *cr) { AdgStrokePrivate *data = adg_stroke_get_instance_private((AdgStroke *) entity); const cairo_path_t *cairo_path = adg_trail_get_cairo_path(data->trail); if (cairo_path != NULL) { cairo_transform(cr, adg_entity_get_global_matrix(entity)); cairo_save(cr); cairo_transform(cr, adg_entity_get_local_matrix(entity)); cairo_append_path(cr, cairo_path); cairo_restore(cr); adg_entity_apply_dress(entity, data->line_dress, cr); cairo_stroke(cr); } } static void _adg_unset_trail(AdgStroke *stroke) { g_object_set(stroke, "trail", NULL, NULL); } diff --git a/src/adg/adg-stroke.h b/src/adg/adg-stroke.h index 83ef765b..47a7b37d 100644 --- a/src/adg/adg-stroke.h +++ b/src/adg/adg-stroke.h @@ -1,67 +1,67 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_STROKE_H__ #define __ADG_STROKE_H__ G_BEGIN_DECLS #define ADG_TYPE_STROKE (adg_stroke_get_type()) #define ADG_STROKE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_STROKE, AdgStroke)) #define ADG_STROKE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_STROKE, AdgStrokeClass)) #define ADG_IS_STROKE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_STROKE)) #define ADG_IS_STROKE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_STROKE)) #define ADG_STROKE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_STROKE, AdgStrokeClass)) typedef struct _AdgStroke AdgStroke; typedef struct _AdgStrokeClass AdgStrokeClass; struct _AdgStroke { /*< private >*/ AdgEntity parent; }; struct _AdgStrokeClass { /*< private >*/ AdgEntityClass parent_class; }; GType adg_stroke_get_type (void); AdgStroke * adg_stroke_new (AdgTrail *trail); void adg_stroke_set_line_dress (AdgStroke *stroke, AdgDress dress); AdgDress adg_stroke_get_line_dress (AdgStroke *stroke); void adg_stroke_set_trail (AdgStroke *stroke, AdgTrail *trail); AdgTrail * adg_stroke_get_trail (AdgStroke *stroke); G_END_DECLS #endif /* __ADG_STROKE_H__ */ diff --git a/src/adg/adg-style.c b/src/adg/adg-style.c index e20cdf68..002954be 100644 --- a/src/adg/adg-style.c +++ b/src/adg/adg-style.c @@ -1,224 +1,224 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-style * @short_description: The base class of all styling objects * * This is the fundamental abstract class for styles. * * Since: 1.0 **/ /** * AdgStyle: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ /** * AdgStyleClass: * @invalidate: virtual method to reset the style. * @apply: abstract virtual to apply a style to a cairo context. * * The default @invalidate handler does not do anything. * * The virtual method @apply *must* be implemented by any derived class. * The default implementation will trigger an error if called. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-style.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_style_parent_class) G_DEFINE_ABSTRACT_TYPE(AdgStyle, adg_style, G_TYPE_OBJECT) enum { INVALIDATE, APPLY, LAST_SIGNAL }; static void _adg_dispose (GObject *object); static void _adg_apply (AdgStyle *style, AdgEntity *entity, cairo_t *cr); static guint _adg_signals[LAST_SIGNAL] = { 0 }; static void adg_style_class_init(AdgStyleClass *klass) { GObjectClass *gobject_class; gobject_class = (GObjectClass *) klass; gobject_class->dispose = _adg_dispose; klass->clone = (AdgStyle *(*)(AdgStyle *)) adg_object_clone; klass->invalidate = NULL; klass->apply = _adg_apply; /** * AdgStyle::invalidate: * @style: an #AdgStyle * * Invalidates the @style, that is resets all the cache, if any, * retained by the internal implementation. * * This signal is emitted while disposing @style, so be sure it * can be called more than once without harms. Furthermore it * will be emitted from property setter code of new implementations * to force the recomputation of the cache. * * Since: 1.0 **/ _adg_signals[INVALIDATE] = g_signal_new("invalidate", G_OBJECT_CLASS_TYPE(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(AdgStyleClass, invalidate), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * AdgStyle::apply: * @style: an #AdgStyle * @entity: the caller #AdgEntity * @cr: the #cairo_t context * * Applies @style to @cr so the next rendering operations will be * done accordling to this style directives. The @entity parameter * is used to resolve the internal dresses of @style, if any. * * Since: 1.0 **/ _adg_signals[APPLY] = g_signal_new("apply", G_OBJECT_CLASS_TYPE(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(AdgStyleClass, apply), NULL, NULL, adg_marshal_VOID__OBJECT_POINTER, G_TYPE_NONE, 2, ADG_TYPE_ENTITY, G_TYPE_POINTER); } static void adg_style_init(AdgStyle *style) { } static void _adg_dispose(GObject *object) { g_signal_emit(object, _adg_signals[INVALIDATE], 0); if (_ADG_OLD_OBJECT_CLASS->dispose != NULL) _ADG_OLD_OBJECT_CLASS->dispose(object); } /** * adg_style_invalidate: * @style: an #AdgStyle derived style * * Emits the #AdgStyle::invalidate signal on @style. This signal * is always emitted while disposing @style, so be sure it * can be called more than once without harms. * * * This function is only useful in new style implementations. * * * Since: 1.0 **/ void adg_style_invalidate(AdgStyle *style) { g_return_if_fail(ADG_IS_STYLE(style)); g_signal_emit(style, _adg_signals[INVALIDATE], 0); } /** * adg_style_clone: * @style: (transfer none): an #AdgStyle derived style * * Clones @style. Useful for customizing styles. * * Returns: (transfer full): a newly created style. * * Since: 1.0 **/ AdgStyle * adg_style_clone(AdgStyle *style) { AdgStyleClass *klass; g_return_val_if_fail(ADG_IS_STYLE(style), NULL); klass = ADG_STYLE_GET_CLASS(style); if (klass->clone == NULL) return NULL; return klass->clone(style); } /** * adg_style_apply: * @style: an #AdgStyle derived style * @entity: the caller #AdgEntity * @cr: the subject cairo context * * Emits the #AdgStyle::apply signal on @style, passing @entity and * @cr as parameters to the signal. * * Since: 1.0 **/ void adg_style_apply(AdgStyle *style, AdgEntity *entity, cairo_t *cr) { g_return_if_fail(ADG_IS_STYLE(style)); g_return_if_fail(ADG_IS_ENTITY(entity)); g_return_if_fail(cr != NULL); g_signal_emit(style, _adg_signals[APPLY], 0, entity, cr); } static void _adg_apply(AdgStyle *style, AdgEntity *entity, cairo_t *cr) { /* The apply method must be defined */ g_warning(_("%s: 'apply' method not implemented for type '%s'"), G_STRLOC, g_type_name(G_OBJECT_TYPE(style))); } diff --git a/src/adg/adg-style.h b/src/adg/adg-style.h index ec00da6a..bcba697f 100644 --- a/src/adg/adg-style.h +++ b/src/adg/adg-style.h @@ -1,74 +1,74 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_STYLE_H__ #define __ADG_STYLE_H__ G_BEGIN_DECLS #define ADG_TYPE_STYLE (adg_style_get_type()) #define ADG_STYLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_STYLE, AdgStyle)) #define ADG_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_STYLE, AdgStyleClass)) #define ADG_IS_STYLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_STYLE)) #define ADG_IS_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_STYLE)) #define ADG_STYLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_STYLE, AdgStyleClass)) typedef struct _AdgStyleClass AdgStyleClass; struct _AdgStyle { /*< private >*/ GObject parent; }; struct _AdgStyleClass { /*< private >*/ GObjectClass parent_class; /*< public >*/ /* Virtual table */ AdgStyle * (*clone) (AdgStyle *style); /* Signals */ void (*invalidate) (AdgStyle *style); void (*apply) (AdgStyle *style, AdgEntity *entity, cairo_t *cr); }; GType adg_style_get_type (void); AdgStyle * adg_style_clone (AdgStyle *style); void adg_style_invalidate (AdgStyle *style); void adg_style_apply (AdgStyle *style, AdgEntity *entity, cairo_t *cr); G_END_DECLS #endif /* __ADG_STYLE_H__ */ diff --git a/src/adg/adg-table-cell.c b/src/adg/adg-table-cell.c index 2c746412..786a9d82 100644 --- a/src/adg/adg-table-cell.c +++ b/src/adg/adg-table-cell.c @@ -1,950 +1,950 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-table-cell * @Section_Id:AdgTableCell * @title: AdgTableCell * @short_description: A single cell of a table * * The #AdgTableCell is a boxed type, the basic component of an * #AdgTable entity. It must be added to an #AdgTableRow that, * in cascade, will be added to an #AdgTable entity. * * Any cell can be filled with a title and a value: the font to be * used will be picked up from the #AdgTableStyle got by resolving * the #AdgTable:table-dress property. * * The default title is placed at the upper left corner of the cell * while the value is centered up to the bottom edge of the cell. * Anyway the text positioning can be customized by using the * adg_table_cell_set_value_pos() method. * * Some convenient functions to easily create title and value entities * with plain text are provided: adg_table_cell_new_full(), * adg_table_cell_set_text_title() and adg_table_cell_set_text_value(). * When using these methods keep in mind the underlying #AdgToyText * entities will be displaced accordingly to the * #AdgTableStyle:cell-padding value, not used when setting the * entities through other APIs. * * Since: 1.0 **/ /** * AdgTableCell: * * An opaque structure referring to the cell of an #AdgTableRow. * Any row can have an unlimited number of cells. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-text-internal.h" #include "adg-container.h" #include "adg-alignment.h" #include "adg-textual.h" #include "adg-style.h" #include "adg-table-style.h" #include "adg-table.h" #include "adg-table-row.h" #include "adg-table-cell.h" struct _AdgTableCell { AdgTableRow *row; gdouble width; gboolean has_frame; AdgEntity *title; AdgEntity *value; CpmlPair value_factor; CpmlExtents extents; }; static AdgTableCell * _adg_cell_new (void); static void _adg_cell_invalidate (AdgTableCell *table_cell); static gboolean _adg_cell_set_title (AdgTableCell *table_cell, AdgEntity *title); static gboolean _adg_cell_set_value (AdgTableCell *table_cell, AdgEntity *value); static void _adg_cell_set_value_pos (AdgTableCell *table_cell, const CpmlPair *from_factor, const CpmlPair *to_factor); GType adg_table_cell_get_type(void) { static GType cell_type = 0; if (G_UNLIKELY(cell_type == 0)) cell_type = g_boxed_type_register_static("AdgTableCell", (GBoxedCopyFunc) adg_table_cell_dup, (GBoxedFreeFunc) adg_table_cell_free); return cell_type; } /** * adg_table_cell_dup: * @src: an #AdgTableCell structure * * Duplicates @src. The returned duplicate should be freed * with adg_table_cell_free() when no longer needed. * * Returns: (transfer full): a duplicate of @src. * * Since: 1.0 **/ AdgTableCell * adg_table_cell_dup(const AdgTableCell *src) { g_return_val_if_fail(src != NULL, NULL); return cpml_memdup(src, sizeof(AdgTableCell)); } /** * adg_table_cell_new: * @table_row: a valid #AdgTableRow * * Creates a new empty cell without a frame and appends it at the * end of the cells yet present in @table_row. You can add content * to the cell by using adg_table_cell_set_title() and * adg_table_cell_set_value() or enable the frame with * adg_table_cell_switch_frame(). * * Returns: (transfer full): the newly created cell or NULL on errors. * * Since: 1.0 **/ AdgTableCell * adg_table_cell_new(AdgTableRow *table_row) { AdgTableCell *table_cell; g_return_val_if_fail(table_row != NULL, NULL); table_cell = _adg_cell_new(); table_cell->row = table_row; adg_table_row_insert(table_row, table_cell, NULL); _adg_cell_invalidate(table_cell); return table_cell; } /** * adg_table_cell_new_before: * @before_cell: a valid #AdgTableCell * * Creates a new cell and inserts it rigthly before the @table_cell cell. * * Returns: (transfer full): the newly created cell or NULL on errors. * * Since: 1.0 **/ AdgTableCell * adg_table_cell_new_before(AdgTableCell *before_cell) { AdgTableRow *table_row; AdgTableCell *table_cell; g_return_val_if_fail(before_cell != NULL, NULL); table_row = before_cell->row; g_return_val_if_fail(table_row != NULL, NULL); table_cell = _adg_cell_new(); table_cell->row = table_row; adg_table_row_insert(table_row, table_cell, NULL); _adg_cell_invalidate(table_cell); return table_cell; } /** * adg_table_cell_new_with_width: * @table_row: a valid #AdgTableRow * @width: the cell width * * A convenient wrapper to adg_table_cell_new() that allows to * specify the @width of @table_row all at once. * * Returns: (transfer full): the newly created cell or NULL on errors. * * Since: 1.0 **/ AdgTableCell * adg_table_cell_new_with_width(AdgTableRow *table_row, gdouble width) { AdgTableCell *table_cell; g_return_val_if_fail(width >= 0, NULL); table_cell = adg_table_cell_new(table_row); if (table_cell != NULL) adg_table_cell_set_width(table_cell, width); return table_cell; } /** * adg_table_cell_new_full: * @table_row: a valid #AdgTableRow * @width: the cell width * @name: (nullable): the name to bound to this cell * @title: (nullable): the title text * @has_frame: whether to draw or not the frame * * A convenient function to add a cell and specifies some common * used properties at once. * * If @name is NULL, the created cell will * not be a named cell. Check adg_table_set_cell() for further * details on what a named cell is supposed to be. * * @title can be NULL, in which case no * title entity will be created. * * Returns: (transfer full): the newly created cell or NULL on errors. * * Since: 1.0 **/ AdgTableCell * adg_table_cell_new_full(AdgTableRow *table_row, gdouble width, const gchar *name, const gchar *title, gboolean has_frame) { AdgTableCell *table_cell; g_return_val_if_fail(width >= 0, NULL); table_cell = adg_table_cell_new(table_row); if (table_cell == NULL) return NULL; adg_table_cell_set_width(table_cell, width); adg_table_cell_switch_frame(table_cell, has_frame); if (title) { adg_table_cell_set_text_title(table_cell, title); } if (name) { AdgTable *table = adg_table_row_get_table(table_row); adg_table_set_cell(table, name, table_cell); } return table_cell; } /** * adg_table_cell_dispose: * @table_cell: a valid #AdgTableCell struct * * Disposes @table_cell. * * Since: 1.0 **/ void adg_table_cell_dispose(AdgTableCell *table_cell) { g_return_if_fail(table_cell != NULL); _adg_cell_set_title(table_cell, NULL); _adg_cell_set_value(table_cell, NULL); } /** * adg_table_cell_free: * @table_cell: an #AdgTableCell structure * * Releases all the memory allocated by @table_cell, itself included. * * Since: 1.0 **/ void adg_table_cell_free(AdgTableCell *table_cell) { AdgTableRow *table_row; g_return_if_fail(table_cell != NULL); table_row = table_cell->row; if (table_cell->row != NULL) { AdgTable *table = adg_table_row_get_table(table_row); adg_table_row_remove(table_row, table_cell); if (table) adg_table_set_cell(table, NULL, table_cell); } adg_table_cell_dispose(table_cell); g_free(table_cell); } /** * adg_table_cell_get_row: * @table_cell: a valid #AdgTableCell * * Gets the row container of @table_cell. The returned #AdgTableRow * is owned by @table_cell and must not be modified or freed. * * Returns: (transfer none): the container row. * * Since: 1.0 **/ AdgTableRow * adg_table_cell_get_row(AdgTableCell *table_cell) { g_return_val_if_fail(table_cell != NULL, NULL); return table_cell->row; } /** * adg_table_cell_get_table: * @table_cell: a valid #AdgTableCell * * A convenient function that gets the table that contains * @table_cell. The returned #AdgTable is owned by @table_cell * and must not be modified or freed. * * Returns: (transfer none): the container table. * * Since: 1.0 **/ AdgTable * adg_table_cell_get_table(AdgTableCell *table_cell) { AdgTableRow *table_row = adg_table_cell_get_row(table_cell); if (table_row == NULL) return NULL; return adg_table_row_get_table(table_row); } /** * adg_table_cell_set_title: * @table_cell: a valid #AdgTableCell * @title: (nullable): the new title entity * * Sets @title as the new title entity of @table_cell. The top left * corner of the bounding box of @title will be cohincident to * the top left corner of the cell extents, taking into accounts * eventual padding spaces specified by the table style. * * The old internal entity is unrefenrenced while the @title (if * not NULL) is refenenced with * g_object_ref_sink(). * * @title can be NULL, in which case the old * entity is removed. * * Since: 1.0 **/ void adg_table_cell_set_title(AdgTableCell *table_cell, AdgEntity *title) { g_return_if_fail(table_cell != NULL); g_return_if_fail(title == NULL || ADG_IS_ENTITY(title)); if (_adg_cell_set_title(table_cell, title)) _adg_cell_invalidate(table_cell); } /** * adg_table_cell_set_text_title: * @table_cell: a valid #AdgTableCell * @title: a text string * * Convenient function to set a the title of a cell using an #AdgToyText * entity with the font dress picked from #AdgTable:table-dress with * a call to adg_table_style_get_title_dress(). * * Since: 1.0 **/ void adg_table_cell_set_text_title(AdgTableCell *table_cell, const gchar *title) { AdgTable *table; AdgEntity *entity; AdgTableStyle *table_style; const CpmlPair *padding; AdgDress table_dress, font_dress; cairo_matrix_t map; g_return_if_fail(table_cell != NULL); if (title == NULL) adg_table_cell_set_title(table_cell, NULL); if (table_cell->title) { gchar *old_title; gboolean unchanged; if (ADG_IS_TEXTUAL(table_cell->title)) old_title = adg_textual_dup_text((AdgTextual *) table_cell->title); else old_title = NULL; unchanged = g_strcmp0(title, old_title) == 0; g_free(old_title); if (unchanged) return; } table = adg_table_cell_get_table(table_cell); table_dress = adg_table_get_table_dress(table); table_style = (AdgTableStyle *) adg_entity_style((AdgEntity *) table, table_dress); padding = adg_table_style_get_cell_padding(table_style); font_dress = adg_table_style_get_title_dress(table_style); entity = g_object_new(ADG_TYPE_BEST_TEXT, "text", title, "font-dress", font_dress, NULL); cairo_matrix_init_translate(&map, padding->x, padding->y); adg_entity_set_global_map(entity, &map); adg_table_cell_set_title(table_cell, entity); } /** * adg_table_cell_title: * @table_cell: a valid #AdgTableCell * * Gets the current title of @table_cell. The returned string is owned * by @table_cell and must not be modified or freed. * * Returns: (transfer none): the title entity or NULL for undefined title. * * Since: 1.0 **/ AdgEntity * adg_table_cell_title(AdgTableCell *table_cell) { g_return_val_if_fail(table_cell != NULL, NULL); return table_cell->title; } /** * adg_table_cell_set_value: * @table_cell: a valid #AdgTableCell * @value: the new value entity * * Sets @value as the new value entity of @table_cell. The bottom middle * point of the bounding box of @value will be cohincident to the * bottom middle point of the cell extents, taking into accounts * eventual padding spaces specified by the table style. * * The old internal entity is unrefenrenced while the @value (if * not NULL) is referenced with * g_object_ref_sink(). * * @value can be NULL, in which case the old entity * is removed. * * Since: 1.0 **/ void adg_table_cell_set_value(AdgTableCell *table_cell, AdgEntity *value) { g_return_if_fail(table_cell != NULL); g_return_if_fail(value == NULL || ADG_IS_ENTITY(value)); if (_adg_cell_set_value(table_cell, value)) _adg_cell_invalidate(table_cell); } /** * adg_table_cell_set_text_value: * @table_cell: a valid #AdgTableCell * @value: (nullable): a text string * * Convenient function to set a the value of a cell using an #AdgToyText * entity with a value font dress picked from #AdgTable:table-dress with * a call to adg_table_style_get_value_dress(). * * @value can be NULL, in which case the old entity * is removed. * * Since: 1.0 **/ void adg_table_cell_set_text_value(AdgTableCell *table_cell, const gchar *value) { AdgTable *table; AdgEntity *entity; AdgTableStyle *table_style; const CpmlPair *padding; AdgDress table_dress, font_dress; cairo_matrix_t map; g_return_if_fail(table_cell != NULL); if (value == NULL) { adg_table_cell_set_value(table_cell, NULL); return; } if (table_cell->value) { gchar *old_value; gboolean unchanged; if (ADG_IS_TEXTUAL(table_cell->value)) old_value = adg_textual_dup_text((AdgTextual *) table_cell->value); else old_value = NULL; unchanged = g_strcmp0(value, old_value) == 0; g_free(old_value); if (unchanged) return; } table = adg_table_cell_get_table(table_cell); table_dress = adg_table_get_table_dress(table); table_style = (AdgTableStyle *) adg_entity_style((AdgEntity *) table, table_dress); padding = adg_table_style_get_cell_padding(table_style); font_dress = adg_table_style_get_value_dress(table_style); entity = g_object_new(ADG_TYPE_BEST_TEXT, "text", value, "font-dress", font_dress, NULL); cairo_matrix_init_translate(&map, 0, -padding->y); adg_entity_set_global_map(entity, &map); adg_table_cell_set_value(table_cell, entity); } /** * adg_table_cell_value: * @table_cell: a valid #AdgTableCell * * Gets the current value of @table_cell. The returned string is owned * by @table_cell and must not be modified or freed. * * Returns: (transfer none): the value entity or NULL for undefined value. * * Since: 1.0 **/ AdgEntity * adg_table_cell_value(AdgTableCell *table_cell) { g_return_val_if_fail(table_cell != NULL, NULL); return table_cell->value; } /** * adg_table_cell_set_value_pos: * @table_cell: a valid #AdgTableCell * @from_factor: the alignment factor on the value entity * @to_factor: the alignment factor on the cell * * Sets a new custom position for the value entity of @table_cell. The * @from_factor specifies the source point (as a fraction of the * value extents) while the @to_factor is the destination point * (specified as a fraction of the cell extents) the source point * must be moved to. * * Since: 1.0 **/ void adg_table_cell_set_value_pos(AdgTableCell *table_cell, const CpmlPair *from_factor, const CpmlPair *to_factor) { g_return_if_fail(table_cell != NULL); _adg_cell_set_value_pos(table_cell, from_factor, to_factor); } /** * adg_table_cell_set_value_pos_explicit: * @table_cell: a valid #AdgTableCell * @from_x: the x alignment factor on the entity * @from_y: the y alignment factor on the entity * @to_x: the x alignment factor on the cell * @to_y: the y alignment factor on the cell * * A convenient wrapper around adg_table_cell_set_value_pos() * that uses explicit factors instead of #CpmlPair. * * Since: 1.0 **/ void adg_table_cell_set_value_pos_explicit(AdgTableCell *table_cell, gdouble from_x, gdouble from_y, gdouble to_x, gdouble to_y) { CpmlPair from, to; from.x = from_x; from.y = from_y; to.x = to_x; to.y = to_y; adg_table_cell_set_value_pos(table_cell, &from, &to); } /** * adg_table_cell_set_width: * @table_cell: a valid #AdgTableCell * @width: the new width * * Sets a new width on @table_cell. The extents on the whole table * will be invalidated, so will be recomputed in the next * arrange() phase. * * A positive @width value specifies the width of this cell in global * space: if the width of its content (that is, either the title or the * value entity) will be greater than @width, it will be rendered * outside the cell boundary box, luckely overwriting the adiacent * cells. * * Using 0 as @width means the width of the cell will be automatically * adjusted to the maximum width of its content. * * Negative width values are not allowed: this condition will raise * a warning without any further processing. * * Since: 1.0 **/ void adg_table_cell_set_width(AdgTableCell *table_cell, gdouble width) { g_return_if_fail(table_cell != NULL); g_return_if_fail(width >= 0); if (table_cell->width != width) { table_cell->width = width; _adg_cell_invalidate(table_cell); } } /** * adg_table_cell_get_width: * @table_cell: a valid #AdgTableCell * * Gets the width of @table_cell. * * Returns: the requested width or 0 on errors. * * Since: 1.0 **/ gdouble adg_table_cell_get_width(AdgTableCell *table_cell) { g_return_val_if_fail(table_cell != NULL, 0.); return table_cell->width; } /** * adg_table_cell_switch_frame: * @table_cell: a valid #AdgTableCell * @has_frame: whether to draw or not the frame * * Sets the frame flag of @table_cell: if @has_frame is * TRUE, a frame around @table_cell * will be rendered using the #AdgTableStyle:frame-dress * dress of the table style. * * Since: 1.0 **/ void adg_table_cell_switch_frame(AdgTableCell *table_cell, gboolean has_frame) { g_return_if_fail(table_cell != NULL); if (table_cell->has_frame != has_frame) { AdgTable *table = adg_table_cell_get_table(table_cell); table_cell->has_frame = has_frame; adg_table_invalidate_grid(table); } } /** * adg_table_cell_has_frame: * @table_cell: a valid #AdgTableCell * * Gets the frame flag of @table_cell. * * Returns: the frame flag. * * Since: 1.0 **/ gboolean adg_table_cell_has_frame(AdgTableCell *table_cell) { g_return_val_if_fail(table_cell != NULL, FALSE); return table_cell->has_frame; } /** * adg_table_cell_get_extents: * @table_cell: a valid #AdgTableCell * * Gets the extents of @table_cell. This function is useful only after the * arrange() phase as in the other situation the extents will likely * be not up to date. * * Returns: the extents of @table_cell or NULL on errors. * * Since: 1.0 **/ const CpmlExtents * adg_table_cell_get_extents(AdgTableCell *table_cell) { g_return_val_if_fail(table_cell != NULL, NULL); return &table_cell->extents; } /** * adg_table_cell_size_request: * @table_cell: a valid #AdgTableCell * @row_extents: the extents of the container #AdgTableRow * * Computes the minimum space needed to properly render @table_cell * and updates the size component of the internal #CpmlExtents struct, * returning it to the caller. The returned #CpmlPair is owned by * @table_cell and should not be modified or freed. * * Returns: (transfer none): the minimum size required. * * Since: 1.0 **/ const CpmlPair * adg_table_cell_size_request(AdgTableCell *table_cell, const CpmlExtents *row_extents) { CpmlVector *size; AdgAlignment *title_alignment; AdgAlignment *value_alignment; AdgTable *table; size = &table_cell->extents.size; if (table_cell->title) { title_alignment = (AdgAlignment *) adg_entity_get_parent(table_cell->title); adg_entity_arrange((AdgEntity *) title_alignment); } else { title_alignment = NULL; } if (table_cell->value) { value_alignment = (AdgAlignment *) adg_entity_get_parent(table_cell->value); adg_entity_arrange((AdgEntity *) value_alignment); } else { value_alignment = NULL; } table = adg_table_cell_get_table(table_cell); size->y = row_extents->size.y; if (table_cell->width == 0) { AdgTableStyle *table_style = (AdgTableStyle *) adg_table_get_table_style(table); const CpmlExtents *extents; /* The width depends on the cell content (default = 0) */ size->x = 0; if (title_alignment) { extents = adg_entity_get_extents((AdgEntity *) title_alignment); size->x = extents->size.x; } if (value_alignment) { extents = adg_entity_get_extents((AdgEntity *) value_alignment); if (extents->size.x > size->x) size->x = extents->size.x; } size->x += adg_table_style_get_cell_spacing(table_style)->x * 2; } else { size->x = table_cell->width; } return size; } /** * adg_table_cell_arrange: * @table_cell: an #AdgTableCell * @layout: the new extents to use * * Rearranges the underlying #AdgTableCell owned by @table_cell using * the new extents provided in @layout. If the x or y size component * of @layout is negative, the value holded by the internal extents * struct is not overriden. * * * table_cell->extents must be up to date if @layout->size.x or * @layout->size.y is negative in order to have a valid size. * * * Returns: the extents of @table_cell or NULL on errors. * * Since: 1.0 **/ const CpmlExtents * adg_table_cell_arrange(AdgTableCell *table_cell, const CpmlExtents *layout) { CpmlExtents *extents; AdgAlignment *alignment; cairo_matrix_t map; /* Set the new extents */ extents = &table_cell->extents; extents->org = layout->org; if (layout->size.x > 0) extents->size.x = layout->size.x; if (layout->size.y > 0) extents->size.y = layout->size.y; extents->is_defined = TRUE; if (table_cell->title) { alignment = (AdgAlignment *) adg_entity_get_parent(table_cell->title); cairo_matrix_init_translate(&map, extents->org.x, extents->org.y); adg_entity_set_global_map((AdgEntity *) alignment, &map); } if (table_cell->value) { CpmlPair to; alignment = (AdgAlignment *) adg_entity_get_parent(table_cell->value); to.x = extents->size.x * table_cell->value_factor.x + extents->org.x; to.y = extents->size.y * table_cell->value_factor.y + extents->org.y; cairo_matrix_init_translate(&map, to.x, to.y); adg_entity_set_global_map((AdgEntity *) alignment, &map); } return extents; } static AdgTableCell * _adg_cell_new(void) { AdgTableCell *table_cell; table_cell = g_new(AdgTableCell, 1); table_cell->row = NULL; table_cell->width = 0.; table_cell->has_frame = FALSE; table_cell->title = NULL; table_cell->value = NULL; table_cell->extents.is_defined = FALSE; table_cell->value_factor.x = 0.5; table_cell->value_factor.y = 1; return table_cell; } static void _adg_cell_invalidate(AdgTableCell *table_cell) { AdgTable *table = adg_table_cell_get_table(table_cell); if (table) adg_entity_invalidate((AdgEntity *) table); } static gboolean _adg_cell_set_title(AdgTableCell *table_cell, AdgEntity *title) { AdgEntity *alignment; if (table_cell->title == title) return FALSE; if (table_cell->title) { alignment = adg_entity_get_parent(table_cell->title); g_object_unref(alignment); } table_cell->title = title; if (title) { AdgEntity *table = (AdgEntity *) adg_table_cell_get_table(table_cell); alignment = (AdgEntity *) adg_alignment_new_explicit(0, -1); g_object_ref_sink(alignment); adg_entity_set_parent(alignment, table); adg_container_add((AdgContainer *) alignment, title); } return TRUE; } static gboolean _adg_cell_set_value(AdgTableCell *table_cell, AdgEntity *value) { AdgEntity *alignment; if (table_cell->value == value) return FALSE; if (table_cell->value) { alignment = adg_entity_get_parent(table_cell->value); adg_container_remove((AdgContainer *) alignment, table_cell->value); g_object_unref(alignment); } table_cell->value = value; if (value) { AdgEntity *table = (AdgEntity *) adg_table_cell_get_table(table_cell); alignment = (AdgEntity *) adg_alignment_new_explicit(0.5, 0); g_object_ref_sink(alignment); adg_entity_set_parent(alignment, table); adg_container_add((AdgContainer *) alignment, value); } return TRUE; } static void _adg_cell_set_value_pos(AdgTableCell *table_cell, const CpmlPair *from_factor, const CpmlPair *to_factor) { AdgAlignment *alignment; if (table_cell->value == NULL) return; alignment = (AdgAlignment *) adg_entity_get_parent(table_cell->value); if (from_factor) adg_alignment_set_factor(alignment, from_factor); if (to_factor) table_cell->value_factor = *to_factor; } diff --git a/src/adg/adg-table-cell.h b/src/adg/adg-table-cell.h index 66e6c6ea..e4d359f8 100644 --- a/src/adg/adg-table-cell.h +++ b/src/adg/adg-table-cell.h @@ -1,88 +1,88 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_TABLE_CELL_H__ #define __ADG_TABLE_CELL_H__ G_BEGIN_DECLS #define ADG_TYPE_TABLE_CELL (adg_table_cell_get_type()) GType adg_table_cell_get_type (void); AdgTableCell * adg_table_cell_dup (const AdgTableCell *src); AdgTableCell * adg_table_cell_new (AdgTableRow *table_row); AdgTableCell * adg_table_cell_new_before (AdgTableCell *before_cell); AdgTableCell * adg_table_cell_new_with_width (AdgTableRow *table_row, gdouble width); AdgTableCell * adg_table_cell_new_full (AdgTableRow *table_row, gdouble width, const gchar *name, const gchar *title, gboolean has_frame); void adg_table_cell_dispose (AdgTableCell *table_cell); void adg_table_cell_free (AdgTableCell *table_cell); AdgTableRow * adg_table_cell_get_row (AdgTableCell *table_cell); AdgTable * adg_table_cell_get_table (AdgTableCell *table_cell); void adg_table_cell_set_title (AdgTableCell *table_cell, AdgEntity *title); void adg_table_cell_set_text_title (AdgTableCell *table_cell, const gchar *title); AdgEntity * adg_table_cell_title (AdgTableCell *table_cell); void adg_table_cell_set_value (AdgTableCell *table_cell, AdgEntity *value); void adg_table_cell_set_text_value (AdgTableCell *table_cell, const gchar *value); AdgEntity * adg_table_cell_value (AdgTableCell *table_cell); void adg_table_cell_set_value_pos (AdgTableCell *table_cell, const CpmlPair *from_factor, const CpmlPair *to_factor); void adg_table_cell_set_value_pos_explicit (AdgTableCell *table_cell, gdouble from_x, gdouble from_y, gdouble to_x, gdouble to_y); void adg_table_cell_set_width (AdgTableCell *table_cell, gdouble width); gdouble adg_table_cell_get_width (AdgTableCell *table_cell); void adg_table_cell_switch_frame (AdgTableCell *table_cell, gboolean has_frame); gboolean adg_table_cell_has_frame (AdgTableCell *table_cell); const CpmlExtents * adg_table_cell_get_extents (AdgTableCell *table_cell); const CpmlPair *adg_table_cell_size_request (AdgTableCell *table_cell, const CpmlExtents *row_extents); const CpmlExtents * adg_table_cell_arrange (AdgTableCell *table_cell, const CpmlExtents *layout); G_END_DECLS #endif /* __ADG_TABLE_CELL_H__ */ diff --git a/src/adg/adg-table-private.h b/src/adg/adg-table-private.h index 2d202038..04507a4c 100644 --- a/src/adg/adg-table-private.h +++ b/src/adg/adg-table-private.h @@ -1,50 +1,50 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_TABLE_PRIVATE_H__ #define __ADG_TABLE_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgProxyData AdgProxyData; typedef struct _AdgTablePrivate AdgTablePrivate; struct _AdgProxyData { guint signal_id; GQuark detail; va_list var_args; }; struct _AdgTablePrivate { AdgDress table_dress; gboolean has_frame; AdgTableStyle *table_style; AdgStroke *grid; AdgStroke *frame; GSList *rows; GHashTable *cell_names; }; G_END_DECLS #endif /* __ADG_TABLE_PRIVATE_H__ */ diff --git a/src/adg/adg-table-row.c b/src/adg/adg-table-row.c index 19c2a1ae..c6b3a0af 100644 --- a/src/adg/adg-table-row.c +++ b/src/adg/adg-table-row.c @@ -1,461 +1,461 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-table-row * @Section_Id:AdgTableRow * @title: AdgTableRow * @short_description: A boxed type representing a table row * * The #AdgTableRow is a boxed type containing a single row of cells * of an #AdgTable object. * * Every row is segmented into different cells. It must be populated * by using the #AdgTableCell APIs, such as adg_table_cell_new() or * adg_table_cell_new_before(). * * Since: 1.0 **/ /** * AdgTableRow: * * An opaque structure referring to a row of an #AdgTable. Any * table can have an unlimited number of rows. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-style.h" #include "adg-table-style.h" #include "adg-table.h" #include "adg-table-row.h" #include "adg-table-cell.h" struct _AdgTableRow { AdgTable *table; GSList *cells; gdouble height; CpmlExtents extents; }; static AdgTableRow * _adg_row_new (AdgTable *table); GType adg_table_row_get_type(void) { static GType row_type = 0; if (G_UNLIKELY(row_type == 0)) row_type = g_boxed_type_register_static("AdgTableRow", (GBoxedCopyFunc) adg_table_row_dup, (GBoxedFreeFunc) adg_table_row_free); return row_type; } /** * adg_table_row_dup: * @src: an #AdgTableRow structure * * Duplicates @src. The returned duplicate should be freed * with adg_table_row_free() when no longer needed. * * Returns: (transfer full): a duplicate of @src. * * Since: 1.0 **/ AdgTableRow * adg_table_row_dup(const AdgTableRow *src) { g_return_val_if_fail(src != NULL, NULL); return cpml_memdup(src, sizeof(AdgTableRow)); } /** * adg_table_row_new: * @table: an #AdgTable * * Creates a new empty row and appends it at the end of the rows * already present in @table. By default, the height of this new * row will be the fallback value provided by the table style: * you can override it by using adg_table_row_set_height(). * * Returns: (transfer full): the newly created row or NULL on errors. * * Since: 1.0 **/ AdgTableRow * adg_table_row_new(AdgTable *table) { AdgTableRow *table_row; g_return_val_if_fail(ADG_IS_TABLE(table), NULL); table_row = _adg_row_new(table); adg_table_insert(table, table_row, NULL); adg_entity_invalidate((AdgEntity *) table); return table_row; } /** * adg_table_row_new_before: * @before_row: a valid #AdgTableRow * * Creates a new empty row with default height and inserts it * just before @before_row. * * Returns: (transfer full): the newly created row or NULL on errors. * * Since: 1.0 **/ AdgTableRow * adg_table_row_new_before(AdgTableRow *before_row) { AdgTableRow *table_row; AdgTable *table; g_return_val_if_fail(before_row != NULL, NULL); table = (AdgTable *) before_row->table; g_return_val_if_fail(ADG_IS_TABLE(table), NULL); table_row = _adg_row_new(table); adg_table_insert(table, table_row, before_row); adg_entity_invalidate((AdgEntity *) before_row->table); return table_row; } /** * adg_table_row_free: * @table_row: an #AdgTableRow structure * * Releases all the memory allocated by @table_row, itself included. * * Since: 1.0 **/ void adg_table_row_free(AdgTableRow *table_row) { AdgTable *table; g_return_if_fail(table_row != NULL); g_slist_foreach(table_row->cells, (GFunc) adg_table_cell_free, NULL); g_slist_free(table_row->cells); table = table_row->table; if (table != NULL) adg_table_remove(table, table_row); g_free(table_row); } /** * adg_table_row_insert: * @table_row: a valid #AdgTableRow * @table_cell: the #AdgTableCell to insert * @before_cell: (allow-none): an #AdgTableRow * * Inserts @table_cell inside @table_row. If @before_cell * is specified, @table_cell is inserted before it. * * Since: 1.0 **/ void adg_table_row_insert(AdgTableRow *table_row, AdgTableCell *table_cell, AdgTableCell *before_cell) { g_return_if_fail(table_row != NULL); g_return_if_fail(table_cell != NULL); if (before_cell == NULL) { table_row->cells = g_slist_append(table_row->cells, table_cell); } else { GSList *before = g_slist_find(table_row->cells, before_cell); /* This MUST be present, otherwise something really bad happened */ g_return_if_fail(before != NULL); table_row->cells = g_slist_insert_before(table_row->cells, before, table_cell); } } /** * adg_table_row_remove: * @table_row: a valid #AdgTableRow * @table_cell: the #AdgTableCell to remove * * Removes @table_cell from list of cells of @table_row. * * Since: 1.0 **/ void adg_table_row_remove(AdgTableRow *table_row, AdgTableCell *table_cell) { g_return_if_fail(table_row != NULL); g_return_if_fail(table_cell != NULL); table_row->cells = g_slist_remove(table_row->cells, table_cell); } /** * adg_table_row_foreach: * @table_row: an #AdgTableRow * @callback: (scope call): a callback * @user_data: callback user data * * Invokes @callback on each cell of @table_row. * The callback should be declared as: * * * void callback(AdgTableCell *table_cell, gpointer user_data); * * * Since: 1.0 **/ void adg_table_row_foreach(AdgTableRow *table_row, GCallback callback, gpointer user_data) { g_return_if_fail(table_row != NULL); g_return_if_fail(callback != NULL); g_slist_foreach(table_row->cells, (GFunc) callback, user_data); } /** * adg_table_row_get_table: * @table_row: a valid #AdgTableRow * * Returns the container table of @table_row. The returned table * is owned by @table_row and must not be modified or freed. * * Returns: (transfer none): the requested table or NULL on errors. * * Since: 1.0 **/ AdgTable * adg_table_row_get_table(AdgTableRow *table_row) { g_return_val_if_fail(table_row != NULL, NULL); return table_row->table; } /** * adg_table_row_set_height: * @table_row: a valid #AdgTableRow * @height: the new height * * Sets a new height on @table_row. The extents will be invalidated to * recompute the whole layout of the table. Specifying 0 in * @height will use the default height set in the table style. * * Since: 1.0 **/ void adg_table_row_set_height(AdgTableRow *table_row, gdouble height) { g_return_if_fail(table_row != NULL); table_row->height = height; adg_entity_invalidate((AdgEntity *) table_row->table); } /** * adg_table_row_get_height: * @table_row: a valid #AdgTableRow * * Gets the height of @table_row. * * Returns: the requested height or 0 on errors. * * Since: 1.0 **/ gdouble adg_table_row_get_height(AdgTableRow *table_row) { g_return_val_if_fail(table_row != NULL, 0.); return table_row->height; } /** * adg_table_row_get_extents: * @table_row: a valid #AdgTableRow * * Gets the extents of @table_row. This function is useful only after * the arrange() phase as in the other situation the extents will * likely be not up to date. * * Returns: (transfer none): the extents of @table_row or NULL on errors. * * Since: 1.0 **/ const CpmlExtents * adg_table_row_get_extents(AdgTableRow *table_row) { g_return_val_if_fail(table_row != NULL, NULL); return &table_row->extents; } /** * adg_table_row_size_request: * @table_row: a valid #AdgTableRow * * Computes the minimum space needed to properly render @table_row * and updates the size component of the internal #CpmlExtents struct, * returning it to the caller. The returned #CpmlPair is owned by * @table_row and should not be modified or freed. * * Returns: (transfer none): the minimum size required. * * Since: 1.0 **/ const CpmlPair * adg_table_row_size_request(AdgTableRow *table_row) { AdgTableStyle *table_style; const CpmlPair *spacing; gdouble xpad; CpmlExtents *extents; CpmlVector *size; AdgTableCell *cell; GSList *cell_node; const CpmlPair *cell_size; g_return_val_if_fail(table_row != NULL, NULL); table_style = (AdgTableStyle *) adg_table_get_table_style(table_row->table); spacing = adg_table_style_get_cell_spacing(table_style); xpad = spacing ? spacing->x : 0; extents = &table_row->extents; size = &extents->size; size->x = 0; if (table_row->height == 0) size->y = adg_table_style_get_row_height(table_style); else size->y = table_row->height; /* Compute the row width by summing every cell width */ for (cell_node = table_row->cells; cell_node; cell_node = cell_node->next) { cell = cell_node->data; cell_size = adg_table_cell_size_request(cell, extents); size->x += cell_size->x + xpad; } if (size->x > 0) size->x += xpad; return size; } /** * adg_table_row_arrange: * @table_row: an #AdgTableRow * @layout: the new extents to use * * Rearranges the underlying #AdgTableCell owned by @table_row using * the new extents provided in @layout. If the x or y size component * of @layout is negative, the value holded by the internal extents * struct is not overriden. * * * table_row->extents must be up to date if @layout->size.x or * @layout->size.y is negative in order to have a valid size. * * * Returns: (transfer none): the extents of @table_row or NULL on errors. * * Since: 1.0 **/ const CpmlExtents * adg_table_row_arrange(AdgTableRow *table_row, const CpmlExtents *layout) { CpmlExtents *extents; CpmlExtents cell_layout; const CpmlExtents *cell_extents; AdgTableStyle *table_style; const CpmlPair *spacing; gdouble xpad; AdgTableCell *cell; GSList *cell_node; g_return_val_if_fail(table_row != NULL, NULL); g_return_val_if_fail(layout != NULL, NULL); g_return_val_if_fail(layout->is_defined, NULL); /* Set the new extents */ extents = &table_row->extents; extents->org = layout->org; if (layout->size.x > 0) extents->size.x = layout->size.x; if (layout->size.y > 0) extents->size.y = layout->size.y; extents->is_defined = TRUE; table_style = (AdgTableStyle *) adg_table_get_table_style(table_row->table); spacing = adg_table_style_get_cell_spacing(table_style); xpad = spacing ? spacing->x : 0; /* Propagate the arrange to the table cells */ cell_layout.org.x = extents->org.x + xpad; cell_layout.org.y = extents->org.y; cell_layout.size.x = -1; cell_layout.size.y = extents->size.y; for (cell_node = table_row->cells; cell_node; cell_node = cell_node->next) { cell = cell_node->data; cell_extents = adg_table_cell_arrange(cell, &cell_layout); cell_layout.org.x += cell_extents->size.x + xpad; } return extents; } static AdgTableRow * _adg_row_new(AdgTable *table) { AdgTableRow *table_row = g_new(AdgTableRow, 1); table_row->table = table; table_row->cells = NULL; table_row->height = 0; table_row->extents.is_defined = FALSE; return table_row; } diff --git a/src/adg/adg-table-row.h b/src/adg/adg-table-row.h index 7c8cdaab..b2efe527 100644 --- a/src/adg/adg-table-row.h +++ b/src/adg/adg-table-row.h @@ -1,63 +1,63 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_TABLE_ROW_H__ #define __ADG_TABLE_ROW_H__ G_BEGIN_DECLS #define ADG_TYPE_TABLE_ROW (adg_table_row_get_type()) GType adg_table_row_get_type (void); AdgTableRow * adg_table_row_dup (const AdgTableRow *src); AdgTableRow * adg_table_row_new (AdgTable *table); AdgTableRow * adg_table_row_new_before (AdgTableRow *before_row); void adg_table_row_free (AdgTableRow *table_row); void adg_table_row_insert (AdgTableRow *table_row, AdgTableCell *table_cell, AdgTableCell *before_cell); void adg_table_row_remove (AdgTableRow *table_row, AdgTableCell *table_cell); void adg_table_row_foreach (AdgTableRow *table_row, GCallback callback, gpointer user_data); AdgTable * adg_table_row_get_table (AdgTableRow *table_row); void adg_table_row_set_height (AdgTableRow *table_row, gdouble height); gdouble adg_table_row_get_height (AdgTableRow *table_row); const CpmlExtents * adg_table_row_get_extents (AdgTableRow *table_row); const CpmlPair *adg_table_row_size_request (AdgTableRow *table_row); const CpmlExtents * adg_table_row_arrange (AdgTableRow *table_row, const CpmlExtents *layout); G_END_DECLS #endif /* __ADG_TABLE_ROW_H__ */ diff --git a/src/adg/adg-table-style-private.h b/src/adg/adg-table-style-private.h index 1931d683..90241146 100644 --- a/src/adg/adg-table-style-private.h +++ b/src/adg/adg-table-style-private.h @@ -1,43 +1,43 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __ADG_TABLE_STYLE_PRIVATE_H__ #define __ADG_TABLE_STYLE_PRIVATE_H__ G_BEGIN_DECLS typedef struct _AdgTableStylePrivate AdgTableStylePrivate; struct _AdgTableStylePrivate { AdgDress color_dress; AdgDress grid_dress; AdgDress frame_dress; AdgDress title_dress; AdgDress value_dress; gdouble row_height; CpmlPair cell_padding; CpmlPair cell_spacing; }; G_END_DECLS #endif /* __ADG_TABLE_STYLE_PRIVATE_H__ */ diff --git a/src/adg/adg-table-style.c b/src/adg/adg-table-style.c index e8c78bf1..363df251 100644 --- a/src/adg/adg-table-style.c +++ b/src/adg/adg-table-style.c @@ -1,592 +1,592 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-table-style * @short_description: Customization of table rendering * * Contains parameters on how to build tables such as the lines to * * Since: 1.0 */ /** * AdgTableStyle: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-style.h" #include "adg-dress.h" #include "adg-param-dress.h" #include "adg-table-style.h" #include "adg-table-style-private.h" G_DEFINE_TYPE_WITH_PRIVATE(AdgTableStyle, adg_table_style, ADG_TYPE_STYLE) enum { PROP_0, PROP_COLOR_DRESS, PROP_GRID_DRESS, PROP_FRAME_DRESS, PROP_TITLE_DRESS, PROP_VALUE_DRESS, PROP_ROW_HEIGHT, PROP_CELL_PADDING, PROP_CELL_SPACING }; static void _adg_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void _adg_apply (AdgStyle *style, AdgEntity *entity, cairo_t *cr); static void adg_table_style_class_init(AdgTableStyleClass *klass) { GObjectClass *gobject_class; AdgStyleClass *style_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; style_class = (AdgStyleClass *) klass; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; style_class->apply = _adg_apply; param = adg_param_spec_dress("color-dress", P_("Color Dress"), P_("Fallback color dress, used when no specific dresses are selected"), ADG_DRESS_COLOR_ANNOTATION, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_COLOR_DRESS, param); param = adg_param_spec_dress("grid-dress", P_("Grid Dress"), P_("Line dress to use while rendering the grid of the table"), ADG_DRESS_LINE_GRID, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_GRID_DRESS, param); param = adg_param_spec_dress("frame-dress", P_("Frame Dress"), P_("Line dress to use while drawing the table frame"), ADG_DRESS_LINE_FRAME, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FRAME_DRESS, param); param = adg_param_spec_dress("title-dress", P_("Title Dress"), P_("Font dress to use for titles"), ADG_DRESS_FONT_ANNOTATION, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_TITLE_DRESS, param); param = adg_param_spec_dress("value-dress", P_("Value Dress"), P_("Font dress to use for values inside the cells"), ADG_DRESS_FONT_TEXT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_VALUE_DRESS, param); param = g_param_spec_double("row-height", P_("Row Height"), P_("The fallback row height when not explicitely specified while creating a new row"), 0, G_MAXDOUBLE, 30, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_ROW_HEIGHT, param); param = g_param_spec_boxed("cell-padding", P_("Cell Padding"), P_("How much space from the bounding box must left inside every cell"), CPML_TYPE_PAIR, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_CELL_PADDING, param); param = g_param_spec_boxed("cell-spacing", P_("Cell Spacing"), P_("How much space to left between the cells"), CPML_TYPE_PAIR, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_CELL_SPACING, param); } static void adg_table_style_init(AdgTableStyle *table_style) { AdgTableStylePrivate *data = adg_table_style_get_instance_private(table_style); data->color_dress = ADG_DRESS_COLOR_ANNOTATION, data->grid_dress = ADG_DRESS_LINE_GRID; data->frame_dress = ADG_DRESS_LINE_FRAME; data->title_dress = ADG_DRESS_FONT_ANNOTATION; data->value_dress = ADG_DRESS_FONT_TEXT; data->row_height = 30; data->cell_padding.x = 3; data->cell_padding.y = 3; data->cell_spacing.x = 0; data->cell_spacing.y = 0; table_style->data = data; } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgTableStylePrivate *data = ((AdgTableStyle *) object)->data; switch (prop_id) { case PROP_COLOR_DRESS: g_value_set_enum(value, data->color_dress); break; case PROP_GRID_DRESS: g_value_set_enum(value, data->grid_dress); break; case PROP_FRAME_DRESS: g_value_set_enum(value, data->frame_dress); break; case PROP_TITLE_DRESS: g_value_set_enum(value, data->title_dress); break; case PROP_VALUE_DRESS: g_value_set_enum(value, data->value_dress); break; case PROP_ROW_HEIGHT: g_value_set_double(value, data->row_height); break; case PROP_CELL_PADDING: g_value_set_boxed(value, &data->cell_padding); break; case PROP_CELL_SPACING: g_value_set_boxed(value, &data->cell_spacing); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgTableStylePrivate *data = ((AdgTableStyle *) object)->data; switch (prop_id) { case PROP_COLOR_DRESS: data->color_dress = g_value_get_enum(value); break; case PROP_GRID_DRESS: data->grid_dress = g_value_get_enum(value); break; case PROP_FRAME_DRESS: data->frame_dress = g_value_get_enum(value); break; case PROP_TITLE_DRESS: data->title_dress = g_value_get_enum(value); break; case PROP_VALUE_DRESS: data->value_dress = g_value_get_enum(value); break; case PROP_ROW_HEIGHT: data->row_height = g_value_get_double(value); break; case PROP_CELL_PADDING: cpml_pair_copy(&data->cell_padding, g_value_get_boxed(value)); break; case PROP_CELL_SPACING: cpml_pair_copy(&data->cell_spacing, g_value_get_boxed(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_table_style_new: * * Constructs a new empty table style initialized with default params. * * Returns: (transfer full): a new table style. * * Since: 1.0 **/ AdgTableStyle * adg_table_style_new(void) { return g_object_new(ADG_TYPE_TABLE_STYLE, NULL); } /** * adg_table_style_set_color_dress: * @table_style: an #AdgTableStyle object * @dress: the new color dress * * Sets a new color dress on @table_style. * * Since: 1.0 **/ void adg_table_style_set_color_dress(AdgTableStyle *table_style, AdgDress dress) { g_return_if_fail(ADG_IS_TABLE_STYLE(table_style)); g_object_set(table_style, "color-dress", dress, NULL); } /** * adg_table_style_get_color_dress: * @table_style: an #AdgTableStyle object * * Gets the @table_style color dress to be used. This dress should be * intended as a fallback color as it could be overriden by more * specific dresses, such as a color explicitely specified on the * #AdgTableStyle:value-dress. * * Returns: (transfer none): the color dress. * * Since: 1.0 **/ AdgDress adg_table_style_get_color_dress(AdgTableStyle *table_style) { AdgTableStylePrivate *data; g_return_val_if_fail(ADG_IS_TABLE_STYLE(table_style), ADG_DRESS_UNDEFINED); data = table_style->data; return data->color_dress; } /** * adg_table_style_set_frame_dress: * @table_style: an #AdgTableStyle object * @dress: the new line dress * * Sets a new line dress on @table_style for rendering the frames. * * Since: 1.0 **/ void adg_table_style_set_frame_dress(AdgTableStyle *table_style, AdgDress dress) { g_return_if_fail(ADG_IS_TABLE_STYLE(table_style)); g_object_set(table_style, "frame-dress", dress, NULL); } /** * adg_table_style_get_frame_dress: * @table_style: an #AdgTableStyle object * * Gets the line dress to be used for rendering the frames with * @table_style. * * Returns: (transfer none): the line dress. * * Since: 1.0 **/ AdgDress adg_table_style_get_frame_dress(AdgTableStyle *table_style) { AdgTableStylePrivate *data; g_return_val_if_fail(ADG_IS_TABLE_STYLE(table_style), ADG_DRESS_UNDEFINED); data = table_style->data; return data->frame_dress; } /** * adg_table_style_set_grid_dress: * @table_style: an #AdgTableStyle object * @dress: the new line dress * * Sets a new line dress on @table_style for rendering the grids. * * Since: 1.0 **/ void adg_table_style_set_grid_dress(AdgTableStyle *table_style, AdgDress dress) { g_return_if_fail(ADG_IS_TABLE_STYLE(table_style)); g_object_set(table_style, "grid-dress", dress, NULL); } /** * adg_table_style_get_grid_dress: * @table_style: an #AdgTableStyle object * * Gets the line dress to be used for rendering the grids with * @table_style. * * Returns: (transfer none): the line dress. * * Since: 1.0 **/ AdgDress adg_table_style_get_grid_dress(AdgTableStyle *table_style) { AdgTableStylePrivate *data; g_return_val_if_fail(ADG_IS_TABLE_STYLE(table_style), ADG_DRESS_UNDEFINED); data = table_style->data; return data->grid_dress; } /** * adg_table_style_set_title_dress: * @table_style: an #AdgTableStyle object * @dress: the new font dress * * Sets a new font dress on @table_style for rendering cell titles. * * Since: 1.0 **/ void adg_table_style_set_title_dress(AdgTableStyle *table_style, AdgDress dress) { g_return_if_fail(ADG_IS_TABLE_STYLE(table_style)); g_object_set(table_style, "title-dress", dress, NULL); } /** * adg_table_style_get_title_dress: * @table_style: an #AdgTableStyle object * * Gets the font dress to be used for rendering cell titles * with @table_style. * * Returns: (transfer none): the font dress. * * Since: 1.0 **/ AdgDress adg_table_style_get_title_dress(AdgTableStyle *table_style) { AdgTableStylePrivate *data; g_return_val_if_fail(ADG_IS_TABLE_STYLE(table_style), ADG_DRESS_UNDEFINED); data = table_style->data; return data->title_dress; } /** * adg_table_style_set_value_dress: * @table_style: an #AdgTableStyle object * @dress: the new font dress * * Sets a new font dress on @table_style for rendering cell values. * * Since: 1.0 **/ void adg_table_style_set_value_dress(AdgTableStyle *table_style, AdgDress dress) { g_return_if_fail(ADG_IS_TABLE_STYLE(table_style)); g_object_set(table_style, "value-dress", dress, NULL); } /** * adg_table_style_get_value_dress: * @table_style: an #AdgTableStyle object * * Gets the font dress to be used for rendering cell values * with @table_style. * * Returns: (transfer none): the font dress. * * Since: 1.0 **/ AdgDress adg_table_style_get_value_dress(AdgTableStyle *table_style) { AdgTableStylePrivate *data; g_return_val_if_fail(ADG_IS_TABLE_STYLE(table_style), ADG_DRESS_UNDEFINED); data = table_style->data; return data->value_dress; } /** * adg_table_style_set_row_height: * @table_style: an #AdgTableStyle object * @height: the new row heigth fallback * * Sets a new #AdgTableStyle:row-height fallback. @height must * be a valid row height greather than 0 or a warning will be * raised and this function will fail. * * Since: 1.0 **/ void adg_table_style_set_row_height(AdgTableStyle *table_style, gdouble height) { g_return_if_fail(ADG_IS_TABLE_STYLE(table_style)); g_object_set(table_style, "row-height", height, NULL); } /** * adg_table_style_get_row_height: * @table_style: an #AdgTableStyle object * * Gets the row height fallback value. * * Returns: the fallback row height or 0 on errors. * * Since: 1.0 **/ gdouble adg_table_style_get_row_height(AdgTableStyle *table_style) { AdgTableStylePrivate *data; g_return_val_if_fail(ADG_IS_TABLE_STYLE(table_style), 0); data = table_style->data; return data->row_height; } /** * adg_table_style_set_cell_padding: * @table_style: an #AdgTableStyle object * @padding: the new padding values * * Sets new #AdgTableStyle:cell-padding values. * * Since: 1.0 **/ void adg_table_style_set_cell_padding(AdgTableStyle *table_style, const CpmlPair *padding) { g_return_if_fail(ADG_IS_TABLE_STYLE(table_style)); g_object_set(table_style, "cell-padding", padding, NULL); } /** * adg_table_style_get_cell_padding: * @table_style: an #AdgTableStyle object * * Gets the padding values in x and y to be left clear inside the cells. * The returned pointer refers to an internal allocated struct and * must not be modified or freed. * * The cell padding is a symmetric value, that is the padding on the * left will always be equal to the padding on the right and the top * will always be equal to the bottom. * * Returns: (transfer none): the cell padding values or NULL on errors. * * Since: 1.0 **/ const CpmlPair * adg_table_style_get_cell_padding(AdgTableStyle *table_style) { AdgTableStylePrivate *data; g_return_val_if_fail(ADG_IS_TABLE_STYLE(table_style), NULL); data = table_style->data; return &data->cell_padding; } /** * adg_table_style_set_cell_spacing: * @table_style: an #AdgTableStyle object * @spacing: the new spacing values * * Sets new #AdgTableStyle:cell-spacing values. * * Since: 1.0 **/ void adg_table_style_set_cell_spacing(AdgTableStyle *table_style, const CpmlPair *spacing) { g_return_if_fail(ADG_IS_TABLE_STYLE(table_style)); g_object_set(table_style, "cell-spacing", spacing, NULL); } /** * adg_table_style_get_cell_spacing: * @table_style: an #AdgTableStyle object * * Gets the spacing values in x and y to be left between the cell * boundary boxes. The returned pointer refers to an internal * allocated struct and must not be modified or freed. * * The cell spacing is a symmetric value, that is the spacing on the * left will always be equal to the spacing on the right and the top * will always be equal to the bottom. * * Returns: (transfer none): the cell spacing values or NULL on errors. * * Since: 1.0 **/ const CpmlPair * adg_table_style_get_cell_spacing(AdgTableStyle *table_style) { AdgTableStylePrivate *data; g_return_val_if_fail(ADG_IS_TABLE_STYLE(table_style), NULL); data = table_style->data; return &data->cell_spacing; } static void _adg_apply(AdgStyle *style, AdgEntity *entity, cairo_t *cr) { AdgTableStylePrivate *data = ((AdgTableStyle *) style)->data; adg_entity_apply_dress(entity, data->color_dress, cr); } diff --git a/src/adg/adg-table-style.h b/src/adg/adg-table-style.h index 96172bf8..82d7ea01 100644 --- a/src/adg/adg-table-style.h +++ b/src/adg/adg-table-style.h @@ -1,86 +1,86 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #if !defined(__ADG_H__) #error "Only can be included directly." #endif #ifndef __ADG_TABLE_STYLE_H__ #define __ADG_TABLE_STYLE_H__ G_BEGIN_DECLS #define ADG_TYPE_TABLE_STYLE (adg_table_style_get_type()) #define ADG_TABLE_STYLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ADG_TYPE_TABLE_STYLE, AdgTableStyle)) #define ADG_TABLE_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ADG_TYPE_TABLE_STYLE, AdgTableStyleClass)) #define ADG_IS_TABLE_STYLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ADG_TYPE_TABLE_STYLE)) #define ADG_IS_TABLE_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ADG_TYPE_TABLE_STYLE)) #define ADG_TABLE_STYLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ADG_TYPE_TABLE_STYLE, AdgTableStyleClass)) typedef struct _AdgTableStyle AdgTableStyle; typedef struct _AdgTableStyleClass AdgTableStyleClass; struct _AdgTableStyle { /*< private >*/ AdgStyle parent; gpointer data; }; struct _AdgTableStyleClass { /*< private >*/ AdgStyleClass parent_class; }; GType adg_table_style_get_type (void); AdgTableStyle * adg_table_style_new (void); void adg_table_style_set_color_dress (AdgTableStyle *table_style, AdgDress dress); AdgDress adg_table_style_get_color_dress (AdgTableStyle *table_style); void adg_table_style_set_frame_dress (AdgTableStyle *table_style, AdgDress dress); AdgDress adg_table_style_get_frame_dress (AdgTableStyle *table_style); void adg_table_style_set_grid_dress (AdgTableStyle *table_style, AdgDress dress); AdgDress adg_table_style_get_grid_dress (AdgTableStyle *table_style); void adg_table_style_set_title_dress (AdgTableStyle *table_style, AdgDress dress); AdgDress adg_table_style_get_title_dress (AdgTableStyle *table_style); void adg_table_style_set_value_dress (AdgTableStyle *table_style, AdgDress dress); AdgDress adg_table_style_get_value_dress (AdgTableStyle *table_style); void adg_table_style_set_row_height (AdgTableStyle *table_style, gdouble height); gdouble adg_table_style_get_row_height (AdgTableStyle *table_style); void adg_table_style_set_cell_padding(AdgTableStyle *table_style, const CpmlPair *padding); const CpmlPair *adg_table_style_get_cell_padding(AdgTableStyle *table_style); void adg_table_style_set_cell_spacing(AdgTableStyle *table_style, const CpmlPair *spacing); const CpmlPair *adg_table_style_get_cell_spacing(AdgTableStyle *table_style); G_END_DECLS #endif /* __ADG_TABLE_STYLE_H__ */ diff --git a/src/adg/adg-table.c b/src/adg/adg-table.c index fca6497d..d1aea516 100644 --- a/src/adg/adg-table.c +++ b/src/adg/adg-table.c @@ -1,827 +1,827 @@ /* ADG - Automatic Drawing Generation - * Copyright (C) 2007-2020 Nicola Fontana + * Copyright (C) 2007-2021 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-table * @short_description: A tabular entity * * The #AdgTable is the entity to be used for rendering data arranged * in tabular evironments. * * To define a table, you should add to it a serie of one or more * #AdgTableRow by using the #AdgTableRow specific APIs. * * * By default, the #AdgEntity:local-mix property is set to * #ADG_MIX_DISABLED on #AdgTable entities. * * * Since: 1.0 **/ /** * AdgTable: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-model.h" #include "adg-trail.h" #include "adg-style.h" #include "adg-table-style.h" #include "adg-path.h" #include "adg-stroke.h" #include "adg-container.h" #include "adg-alignment.h" #include "adg-dress.h" #include "adg-param-dress.h" #include "adg-table.h" #include "adg-table-private.h" #include "adg-table-row.h" #include "adg-table-cell.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_table_parent_class) #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_table_parent_class) G_DEFINE_TYPE_WITH_PRIVATE(AdgTable, adg_table, ADG_TYPE_ENTITY) enum { PROP_0, PROP_TABLE_DRESS, PROP_HAS_FRAME }; typedef struct { GCallback callback; gpointer user_data; } AdgClosure; static void _adg_dispose (GObject *object); static void _adg_finalize (GObject *object); static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void _adg_destroy (AdgEntity *entity); static void _adg_global_changed (AdgEntity *entity); static void _adg_local_changed (AdgEntity *entity); static void _adg_invalidate (AdgEntity *entity); static void _adg_arrange (AdgEntity *entity); static void _adg_arrange_grid (AdgEntity *entity); static void _adg_arrange_frame (AdgEntity *entity, const CpmlExtents *extents); static void _adg_render (AdgEntity *entity, cairo_t *cr); static void _adg_propagate (AdgTable *table, const gchar *detailed_signal, ...); static void _adg_foreach_row (AdgTableRow *table_row, const AdgClosure *closure); static void _adg_append_frame (AdgTableCell *table_cell, AdgPath *path); static void _adg_proxy_signal (AdgTableCell *table_cell, AdgProxyData *proxy_data); static gboolean _adg_value_match (gpointer key, gpointer value, gpointer user_data); static void adg_table_class_init(AdgTableClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->finalize = _adg_finalize; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; entity_class->destroy = _adg_destroy; entity_class->global_changed = _adg_global_changed; entity_class->local_changed = _adg_local_changed; entity_class->invalidate = _adg_invalidate; entity_class->arrange = _adg_arrange; entity_class->render = _adg_render; param = adg_param_spec_dress("table-dress", P_("Table Dress"), P_("The dress to use for stroking this entity"), ADG_DRESS_TABLE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_TABLE_DRESS, param); param = g_param_spec_boolean("has-frame", P_("Has Frame Flag"), P_("If enabled, a frame using the proper dress found in this table style will be drawn around the table extents"), TRUE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_HAS_FRAME, param); } static void adg_table_init(AdgTable *table) { AdgTablePrivate *data = adg_table_get_instance_private(table); data->table_dress = ADG_DRESS_TABLE; data->has_frame = TRUE; data->table_style = NULL; data->grid = NULL; data->frame = NULL; data->rows = NULL; data->cell_names = NULL; adg_entity_set_local_mix((AdgEntity *) table, ADG_MIX_DISABLED); } static void _adg_dispose(GObject *object) { AdgTable *table = (AdgTable *) object; AdgTablePrivate *data = adg_table_get_instance_private(table); adg_table_invalidate_grid(table); if (data->frame) { g_object_unref(data->frame); data->frame = NULL; } if (data->rows) { adg_table_foreach_cell(table, (GCallback) adg_table_cell_dispose, NULL); data->rows = NULL; } if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_finalize(GObject *object) { AdgTablePrivate *data = adg_table_get_instance_private((AdgTable *) object); if (data->rows)