diff --git a/liblogdiag/ld-diagram-view.c b/liblogdiag/ld-diagram-view.c index 23cbb4c..3ff7599 100644 --- a/liblogdiag/ld-diagram-view.c +++ b/liblogdiag/ld-diagram-view.c @@ -2,7 +2,7 @@ * ld-diagram-view.c * * This file is a part of logdiag. - * Copyright 2010, 2011, 2012, 2015 Přemysl Eric Janouch + * Copyright 2010 - 2021 Přemysl Eric Janouch * * See the file LICENSE for licensing information. * @@ -335,7 +335,7 @@ static void oper_select_begin (LdDiagramView *self, const LdPoint *point); static void oper_select_end (LdDiagramView *self); static void oper_select_get_rectangle (LdDiagramView *self, LdRectangle *rect); static void oper_select_queue_draw (LdDiagramView *self); -static void oper_select_draw (GtkWidget *widget, DrawData *data); +static void oper_select_draw (DrawData *data); static void oper_select_motion (LdDiagramView *self, const LdPoint *point); static void oper_move_selection_begin (LdDiagramView *self, @@ -374,13 +374,19 @@ static void on_drag_leave (GtkWidget *widget, GdkDragContext *drag_ctx, guint time, gpointer user_data); static gboolean on_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data); -static void draw_grid (GtkWidget *widget, DrawData *data); -static void draw_diagram (GtkWidget *widget, DrawData *data); -static void draw_terminal (GtkWidget *widget, DrawData *data); +static void draw_grid (DrawData *data); +static void draw_diagram (DrawData *data); +static void draw_terminal (DrawData *data); static void draw_object (LdDiagramObject *diagram_object, DrawData *data); static void draw_symbol (LdDiagramSymbol *diagram_symbol, DrawData *data); static void draw_connection (LdDiagramConnection *connection, DrawData *data); +/* Export. */ + +static void get_diagram_bounds (LdDiagramView *self, LdRectangle *rect); +static gboolean get_object_bounds (LdDiagramView *self, LdDiagramObject *object, + LdRectangle *rect); + G_DEFINE_TYPE_WITH_CODE (LdDiagramView, ld_diagram_view, GTK_TYPE_DRAWING_AREA, G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, @@ -1045,6 +1051,27 @@ ld_diagram_view_diagram_to_widget_coords (LdDiagramView *self, *wy = scale * (dy - self->priv->y) + 0.5 * allocation.height; } +static void +ld_diagram_view_diagram_to_widget_coords_rect (LdDiagramView *self, + const LdRectangle *area, LdRectangle *rect) +{ + gdouble x1, x2, y1, y2; + + ld_diagram_view_diagram_to_widget_coords (self, + area->x, + area->y, + &x1, &y1); + ld_diagram_view_diagram_to_widget_coords (self, + area->x + area->width, + area->y + area->height, + &x2, &y2); + + rect->x = floor (x1); + rect->y = floor (y1); + rect->width = ceil (x2) - rect->x; + rect->height = ceil (y2) - rect->y; +} + /** * ld_diagram_view_get_x: * @self: an #LdDiagramView object. @@ -1612,14 +1639,12 @@ get_symbol_clip_area (LdDiagramView *self, } static gboolean -get_symbol_area (LdDiagramView *self, LdDiagramSymbol *symbol, +get_symbol_area_in_diagram_units (LdDiagramView *self, LdDiagramSymbol *symbol, LdRectangle *rect) { gdouble object_x, object_y; LdSymbol *library_symbol; LdRectangle area; - gdouble x1, x2; - gdouble y1, y2; gint rotation; g_object_get (symbol, "x", &object_x, "y", &object_y, @@ -1633,24 +1658,23 @@ get_symbol_area (LdDiagramView *self, LdDiagramSymbol *symbol, rotate_symbol_area (&area, rotation); - ld_diagram_view_diagram_to_widget_coords (self, - object_x + area.x, - object_y + area.y, - &x1, &y1); - ld_diagram_view_diagram_to_widget_coords (self, - object_x + area.x + area.width, - object_y + area.y + area.height, - &x2, &y2); + rect->x = object_x + area.x; + rect->y = object_y + area.y; + rect->width = (rect->x + area.width) - rect->x; + rect->height = (rect->y + area.height) - rect->y; + return TRUE; +} - x1 = floor (x1); - y1 = floor (y1); - x2 = ceil (x2); - y2 = ceil (y2); +static gboolean +get_symbol_area (LdDiagramView *self, LdDiagramSymbol *symbol, + LdRectangle *rect) +{ + LdRectangle intermediate; - rect->x = x1; - rect->y = y1; - rect->width = x2 - x1; - rect->height = y2 - y1; + if (!get_symbol_area_in_diagram_units (self, symbol, &intermediate)) + return FALSE; + + ld_diagram_view_diagram_to_widget_coords_rect (self, &intermediate, rect); return TRUE; } @@ -1782,7 +1806,7 @@ get_connection_clip_area (LdDiagramView *self, } static gboolean -get_connection_area (LdDiagramView *self, +get_connection_area_in_diagram_units (LdDiagramView *self, LdDiagramConnection *connection, LdRectangle *rect) { gdouble x_origin, y_origin; @@ -1799,20 +1823,13 @@ get_connection_area (LdDiagramView *self, g_object_get (connection, "x", &x_origin, "y", &y_origin, NULL); - ld_diagram_view_diagram_to_widget_coords (self, - x_origin + points->points[0].x, - y_origin + points->points[0].y, - &x, &y); - - x_max = x_min = x; - y_max = y_min = y; + x_max = x_min = x_origin + points->points[0].x; + y_max = y_min = y_origin + points->points[0].y; for (i = 1; i < points->length; i++) { - ld_diagram_view_diagram_to_widget_coords (self, - x_origin + points->points[i].x, - y_origin + points->points[i].y, - &x, &y); + x = x_origin + points->points[i].x; + y = y_origin + points->points[i].y; if (x < x_min) x_min = x; @@ -1834,6 +1851,19 @@ get_connection_area (LdDiagramView *self, return TRUE; } +static gboolean +get_connection_area (LdDiagramView *self, + LdDiagramConnection *connection, LdRectangle *rect) +{ + LdRectangle intermediate; + + if (!get_connection_area_in_diagram_units (self, connection, &intermediate)) + return FALSE; + + ld_diagram_view_diagram_to_widget_coords_rect (self, &intermediate, rect); + return TRUE; +} + /* ===== Operations ======================================================== */ @@ -2101,7 +2131,7 @@ oper_select_queue_draw (LdDiagramView *self) } static void -oper_select_draw (GtkWidget *widget, DrawData *data) +oper_select_draw (DrawData *data) { static const double dashes[] = {3, 5}; SelectData *select_data; @@ -2664,19 +2694,19 @@ on_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data) cairo_paint (data.cr); if (data.self->priv->show_grid) - draw_grid (widget, &data); + draw_grid (&data); - draw_diagram (widget, &data); - draw_terminal (widget, &data); + draw_diagram (&data); + draw_terminal (&data); if (data.self->priv->operation == OPER_SELECT) - oper_select_draw (widget, &data); + oper_select_draw (&data); return FALSE; } static void -draw_grid (GtkWidget *widget, DrawData *data) +draw_grid (DrawData *data) { gdouble grid_step; gint grid_factor; @@ -2738,7 +2768,7 @@ draw_grid (GtkWidget *widget, DrawData *data) } static void -draw_terminal (GtkWidget *widget, DrawData *data) +draw_terminal (DrawData *data) { LdDiagramViewPrivate *priv; LdPoint widget_coords; @@ -2760,7 +2790,7 @@ draw_terminal (GtkWidget *widget, DrawData *data) } static void -draw_diagram (GtkWidget *widget, DrawData *data) +draw_diagram (DrawData *data) { GList *objects, *iter; @@ -2905,5 +2935,98 @@ draw_connection (LdDiagramConnection *connection, DrawData *data) draw_connection_end: ld_point_array_free (points); - return; +} + + +/* ===== Export ============================================================ */ + +static void +get_diagram_bounds (LdDiagramView *self, LdRectangle *rect) +{ + GList *objects; + gboolean initialized = FALSE; + LdRectangle partial; + gdouble x2, y2; + + g_return_if_fail (LD_IS_DIAGRAM_VIEW (self)); + g_return_if_fail (rect != NULL); + + memset (rect, 0, sizeof *rect); + objects = (GList *) ld_diagram_get_objects (self->priv->diagram); + for (; objects != NULL; objects = objects->next) + { + if (!get_object_bounds (self, objects->data, &partial)) + continue; + + if (!initialized) + { + *rect = partial; + initialized = TRUE; + continue; + } + + x2 = MAX (partial.x + partial.width, rect->x + rect->width); + y2 = MAX (partial.y + partial.height, rect->y + rect->height); + rect->x = MIN (rect->x, partial.x); + rect->y = MIN (rect->y, partial.y); + rect->width = x2 - rect->x; + rect->height = y2 - rect->y; + } +} + +static gboolean +get_object_bounds (LdDiagramView *self, LdDiagramObject *object, + LdRectangle *rect) +{ + if (LD_IS_DIAGRAM_SYMBOL (object)) + return get_symbol_area_in_diagram_units (self, + LD_DIAGRAM_SYMBOL (object), rect); + if (LD_IS_DIAGRAM_CONNECTION (object)) + return get_connection_area_in_diagram_units (self, + LD_DIAGRAM_CONNECTION (object), rect); + return FALSE; +} + +/** + * ld_diagram_view_get_export_bounds: + * @self: an #LdDiagramView object. + * @rect: (out): diagram boundaries. + * + * Get the smallest rectangular area containing all objects in the diagram. + * The diagram object itself doesn't have any idea of how symbols are rendered. + * + * Return value: export units per diagram unit. + */ +gdouble +ld_diagram_view_get_export_bounds (LdDiagramView *self, LdRectangle *rect) +{ + LdRectangle intermediate; + + get_diagram_bounds (self, &intermediate); + ld_diagram_view_diagram_to_widget_coords_rect (self, &intermediate, rect); + return ld_diagram_view_get_scale_in_px (self); +} + +/** + * ld_diagram_view_export: + * @self: an #LdDiagramView object. + * @cr: Cairo context to draw on. + * @clip: the clip area (the function itself does not clip). + * + * Get the smallest rectangular area containing all objects in the diagram. + * The diagram object itself doesn't have any idea of how symbols are rendered. + */ +void +ld_diagram_view_export (LdDiagramView *self, cairo_t *cr, + const LdRectangle *clip) +{ + DrawData data; + + data.cr = cr; + data.self = self; + /* FIXME: Various functions call this directly, this export is a hack. */ + data.scale = ld_diagram_view_get_scale_in_px (data.self); + data.exposed_rect = *clip; + + draw_diagram (&data); } diff --git a/liblogdiag/ld-diagram-view.h b/liblogdiag/ld-diagram-view.h index d107989..f2b0bf9 100644 --- a/liblogdiag/ld-diagram-view.h +++ b/liblogdiag/ld-diagram-view.h @@ -2,7 +2,7 @@ * ld-diagram-view.h * * This file is a part of logdiag. - * Copyright 2010, 2011 Přemysl Eric Janouch + * Copyright 2010 - 2021 Přemysl Eric Janouch * * See the file LICENSE for licensing information. * @@ -96,6 +96,11 @@ void ld_diagram_view_set_show_grid (LdDiagramView *self, gboolean show_grid); void ld_diagram_view_add_object_begin (LdDiagramView *self, LdDiagramObject *object); +gdouble ld_diagram_view_get_export_bounds (LdDiagramView *self, + LdRectangle *rect); +void ld_diagram_view_export (LdDiagramView *self, + cairo_t *cr, const LdRectangle *clip); + G_END_DECLS diff --git a/share/gui/window-main.ui b/share/gui/window-main.ui index 8209cd4..f826698 100644 --- a/share/gui/window-main.ui +++ b/share/gui/window-main.ui @@ -6,10 +6,8 @@ - diff --git a/share/logdiag.manifest b/share/logdiag.manifest new file mode 100644 index 0000000..ace298f --- /dev/null +++ b/share/logdiag.manifest @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/share/logdiag.rc b/share/logdiag.rc index 30684e2..cff1571 100644 --- a/share/logdiag.rc +++ b/share/logdiag.rc @@ -1 +1,3 @@ +#include LD_ICON ICON "logdiag.ico" +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "logdiag.manifest" diff --git a/src/ld-window-main.c b/src/ld-window-main.c index c68ff3d..6616168 100644 --- a/src/ld-window-main.c +++ b/src/ld-window-main.c @@ -106,6 +106,9 @@ static void on_action_new (GtkAction *action, LdWindowMain *self); static void on_action_open (GtkAction *action, LdWindowMain *self); static void on_action_save (GtkAction *action, LdWindowMain *self); static void on_action_save_as (GtkAction *action, LdWindowMain *self); +static void on_action_print (GtkAction *action, LdWindowMain *self); +static void on_action_print_draw_page (GtkPrintOperation *operation, + GtkPrintContext *context, int page_nr, LdWindowMain *self); static void on_action_quit (GtkAction *action, LdWindowMain *self); static void on_action_user_guide (GtkAction *action, LdWindowMain *self); static void on_action_about (GtkAction *action, LdWindowMain *self); @@ -146,11 +149,11 @@ static GtkActionEntry wm_action_entries[] = {"SaveAs", GTK_STOCK_SAVE_AS, N_("Save _As..."), "S", N_("Save the current diagram with another name"), G_CALLBACK (on_action_save_as)}, -/* - * {"Export", NULL, N_("_Export"), NULL, - * N_("Export the diagram"), - * NULL}, - */ + + {"Print", GTK_STOCK_PRINT, N_("_Print"), "P", + N_("Print the diagram"), + G_CALLBACK (on_action_print)}, + {"Quit", GTK_STOCK_QUIT, N_("_Quit"), "Q", N_("Quit the application"), G_CALLBACK (on_action_quit)}, @@ -994,6 +997,87 @@ on_action_save_as (GtkAction *action, LdWindowMain *self) diagram_show_save_as_dialog (self); } +static void +on_action_print (GtkAction *action, LdWindowMain *self) +{ + static GtkPrintSettings *settings = NULL; + GError *error = NULL; + GtkPrintOperation *print; + GtkPrintOperationResult res; + gchar *name; + + print = gtk_print_operation_new (); + gtk_print_operation_set_n_pages (print, 1); + gtk_print_operation_set_embed_page_setup (print, TRUE); + gtk_print_operation_set_unit (print, GTK_UNIT_MM); + + name = diagram_get_name (self); + gtk_print_operation_set_job_name (print, name); + g_free (name); + + if (settings != NULL) + gtk_print_operation_set_print_settings (print, settings); + g_signal_connect (print, "draw-page", + G_CALLBACK (on_action_print_draw_page), self); + + /* On Windows, it is not possible to get a print preview from the system + * print dialog. But in Windows XP previews do not work at all--unreadable + * EMFs come out. Windows 10 errors out with "A sharing violation occurred + * while accessing" the temporary EMF file on our first run of + * GtkPrintOperation, and following that it opens the previews up in + * fucking Paint, so there is no point in trying. It lacks a stage + * or controls for setting up page parameters anyway. + */ + res = gtk_print_operation_run (print, + GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, + GTK_WINDOW (self), &error); + if (res == GTK_PRINT_OPERATION_RESULT_APPLY) + { + if (settings != NULL) + g_object_unref (settings); + settings + = g_object_ref (gtk_print_operation_get_print_settings (print)); + } + if (error) + display_and_free_error (self, _("Error"), error); + + g_object_unref (print); +} + +static void +on_action_print_draw_page (GtkPrintOperation *operation, + GtkPrintContext *context, int page_nr, LdWindowMain *self) +{ + cairo_t *cr; + LdDiagramView *view; + gdouble area_width_mm, area_height_mm; + gdouble diagram_width_mm, diagram_height_mm; + gdouble diagram_to_export_units, scale; + LdRectangle bounds; + + cr = gtk_print_context_get_cairo_context (context); + view = self->priv->view; + + area_width_mm = gtk_print_context_get_width (context); + area_height_mm = gtk_print_context_get_height (context); + diagram_to_export_units = ld_diagram_view_get_export_bounds (view, &bounds); + + /* Scale for the view's constant, measured in milimetres. */ + scale = 1 / diagram_to_export_units * LD_DIAGRAM_VIEW_BASE_UNIT_LENGTH; + diagram_width_mm = bounds.width * scale; + diagram_height_mm = bounds.height * scale; + + /* Scale to fit the paper. */ + if (area_width_mm < diagram_width_mm) + scale *= area_width_mm / diagram_width_mm; + if (area_height_mm < diagram_height_mm) + scale *= area_height_mm / diagram_height_mm; + + cairo_scale (cr, scale, scale); + cairo_translate (cr, -bounds.x, -bounds.y); + ld_diagram_view_export (view, cr, &bounds); +} + static void on_action_quit (GtkAction *action, LdWindowMain *self) {