Add basic print functionality

Sadly, the line width depends on the widget's DPI, which seems to
even cause uneven lines on Windows, where virtual printers claim
high DPI.  It might also be an unrelated problem.

Similarly, selected objects are exported highlighted.

Other than that, it works quite well.

Add a manifest to make the print dialog look nice with the older
GTK+ bundle we depend upon.

The RC file could theoretically be scanned for /\s+"([^"]+)"\s*$/,
unescaped, and the results configure_file()-stamped.
This commit is contained in:
Přemysl Eric Janouch 2021-10-25 15:54:24 +02:00
parent 6cd6ddbd1c
commit 4f01392de5
Signed by: p
GPG Key ID: A0420B94F92B9493
6 changed files with 277 additions and 54 deletions

View File

@ -2,7 +2,7 @@
* ld-diagram-view.c * ld-diagram-view.c
* *
* This file is a part of logdiag. * 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. * 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_end (LdDiagramView *self);
static void oper_select_get_rectangle (LdDiagramView *self, LdRectangle *rect); static void oper_select_get_rectangle (LdDiagramView *self, LdRectangle *rect);
static void oper_select_queue_draw (LdDiagramView *self); 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_select_motion (LdDiagramView *self, const LdPoint *point);
static void oper_move_selection_begin (LdDiagramView *self, 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); guint time, gpointer user_data);
static gboolean on_draw (GtkWidget *widget, cairo_t *cr, 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_grid (DrawData *data);
static void draw_diagram (GtkWidget *widget, DrawData *data); static void draw_diagram (DrawData *data);
static void draw_terminal (GtkWidget *widget, DrawData *data); static void draw_terminal (DrawData *data);
static void draw_object (LdDiagramObject *diagram_object, DrawData *data); static void draw_object (LdDiagramObject *diagram_object, DrawData *data);
static void draw_symbol (LdDiagramSymbol *diagram_symbol, DrawData *data); static void draw_symbol (LdDiagramSymbol *diagram_symbol, DrawData *data);
static void draw_connection (LdDiagramConnection *connection, 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_DEFINE_TYPE_WITH_CODE (LdDiagramView, ld_diagram_view, GTK_TYPE_DRAWING_AREA,
G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, 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; *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: * ld_diagram_view_get_x:
* @self: an #LdDiagramView object. * @self: an #LdDiagramView object.
@ -1612,14 +1639,12 @@ get_symbol_clip_area (LdDiagramView *self,
} }
static gboolean static gboolean
get_symbol_area (LdDiagramView *self, LdDiagramSymbol *symbol, get_symbol_area_in_diagram_units (LdDiagramView *self, LdDiagramSymbol *symbol,
LdRectangle *rect) LdRectangle *rect)
{ {
gdouble object_x, object_y; gdouble object_x, object_y;
LdSymbol *library_symbol; LdSymbol *library_symbol;
LdRectangle area; LdRectangle area;
gdouble x1, x2;
gdouble y1, y2;
gint rotation; gint rotation;
g_object_get (symbol, "x", &object_x, "y", &object_y, 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); rotate_symbol_area (&area, rotation);
ld_diagram_view_diagram_to_widget_coords (self, rect->x = object_x + area.x;
object_x + area.x, rect->y = object_y + area.y;
object_y + area.y, rect->width = (rect->x + area.width) - rect->x;
&x1, &y1); rect->height = (rect->y + area.height) - rect->y;
ld_diagram_view_diagram_to_widget_coords (self, return TRUE;
object_x + area.x + area.width, }
object_y + area.y + area.height,
&x2, &y2);
x1 = floor (x1); static gboolean
y1 = floor (y1); get_symbol_area (LdDiagramView *self, LdDiagramSymbol *symbol,
x2 = ceil (x2); LdRectangle *rect)
y2 = ceil (y2); {
LdRectangle intermediate;
rect->x = x1; if (!get_symbol_area_in_diagram_units (self, symbol, &intermediate))
rect->y = y1; return FALSE;
rect->width = x2 - x1;
rect->height = y2 - y1; ld_diagram_view_diagram_to_widget_coords_rect (self, &intermediate, rect);
return TRUE; return TRUE;
} }
@ -1782,7 +1806,7 @@ get_connection_clip_area (LdDiagramView *self,
} }
static gboolean static gboolean
get_connection_area (LdDiagramView *self, get_connection_area_in_diagram_units (LdDiagramView *self,
LdDiagramConnection *connection, LdRectangle *rect) LdDiagramConnection *connection, LdRectangle *rect)
{ {
gdouble x_origin, y_origin; 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); g_object_get (connection, "x", &x_origin, "y", &y_origin, NULL);
ld_diagram_view_diagram_to_widget_coords (self, x_max = x_min = x_origin + points->points[0].x;
x_origin + points->points[0].x, y_max = y_min = y_origin + points->points[0].y;
y_origin + points->points[0].y,
&x, &y);
x_max = x_min = x;
y_max = y_min = y;
for (i = 1; i < points->length; i++) for (i = 1; i < points->length; i++)
{ {
ld_diagram_view_diagram_to_widget_coords (self, x = x_origin + points->points[i].x;
x_origin + points->points[i].x, y = y_origin + points->points[i].y;
y_origin + points->points[i].y,
&x, &y);
if (x < x_min) if (x < x_min)
x_min = x; x_min = x;
@ -1834,6 +1851,19 @@ get_connection_area (LdDiagramView *self,
return TRUE; 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 ======================================================== */ /* ===== Operations ======================================================== */
@ -2101,7 +2131,7 @@ oper_select_queue_draw (LdDiagramView *self)
} }
static void static void
oper_select_draw (GtkWidget *widget, DrawData *data) oper_select_draw (DrawData *data)
{ {
static const double dashes[] = {3, 5}; static const double dashes[] = {3, 5};
SelectData *select_data; SelectData *select_data;
@ -2664,19 +2694,19 @@ on_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data)
cairo_paint (data.cr); cairo_paint (data.cr);
if (data.self->priv->show_grid) if (data.self->priv->show_grid)
draw_grid (widget, &data); draw_grid (&data);
draw_diagram (widget, &data); draw_diagram (&data);
draw_terminal (widget, &data); draw_terminal (&data);
if (data.self->priv->operation == OPER_SELECT) if (data.self->priv->operation == OPER_SELECT)
oper_select_draw (widget, &data); oper_select_draw (&data);
return FALSE; return FALSE;
} }
static void static void
draw_grid (GtkWidget *widget, DrawData *data) draw_grid (DrawData *data)
{ {
gdouble grid_step; gdouble grid_step;
gint grid_factor; gint grid_factor;
@ -2738,7 +2768,7 @@ draw_grid (GtkWidget *widget, DrawData *data)
} }
static void static void
draw_terminal (GtkWidget *widget, DrawData *data) draw_terminal (DrawData *data)
{ {
LdDiagramViewPrivate *priv; LdDiagramViewPrivate *priv;
LdPoint widget_coords; LdPoint widget_coords;
@ -2760,7 +2790,7 @@ draw_terminal (GtkWidget *widget, DrawData *data)
} }
static void static void
draw_diagram (GtkWidget *widget, DrawData *data) draw_diagram (DrawData *data)
{ {
GList *objects, *iter; GList *objects, *iter;
@ -2905,5 +2935,98 @@ draw_connection (LdDiagramConnection *connection, DrawData *data)
draw_connection_end: draw_connection_end:
ld_point_array_free (points); 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);
} }

View File

@ -2,7 +2,7 @@
* ld-diagram-view.h * ld-diagram-view.h
* *
* This file is a part of logdiag. * 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. * 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, void ld_diagram_view_add_object_begin (LdDiagramView *self,
LdDiagramObject *object); 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 G_END_DECLS

View File

@ -6,10 +6,8 @@
<menuitem action="Save" /> <menuitem action="Save" />
<menuitem action="SaveAs" /> <menuitem action="SaveAs" />
<separator /> <separator />
<!-- <menuitem action="Print" />
<menuitem action="Export" />
<separator /> <separator />
-->
<menuitem action="Quit" /> <menuitem action="Quit" />
</menu> </menu>
<menu action="EditMenu"> <menu action="EditMenu">

11
share/logdiag.manifest Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity name="logdiag" version="1.0.0.0" type="win32" />
<dependency>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Windows.Common-Controls"
version="6.0.0.0" type="win32" processorArchitecture="*"
publicKeyToken="6595b64144ccf1df" language="*" />
</dependentAssembly>
</dependency>
</assembly>

View File

@ -1 +1,3 @@
#include <windows.h>
LD_ICON ICON "logdiag.ico" LD_ICON ICON "logdiag.ico"
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "logdiag.manifest"

View File

@ -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_open (GtkAction *action, LdWindowMain *self);
static void on_action_save (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_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_quit (GtkAction *action, LdWindowMain *self);
static void on_action_user_guide (GtkAction *action, LdWindowMain *self); static void on_action_user_guide (GtkAction *action, LdWindowMain *self);
static void on_action_about (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..."), "<Shift><Ctrl>S", {"SaveAs", GTK_STOCK_SAVE_AS, N_("Save _As..."), "<Shift><Ctrl>S",
N_("Save the current diagram with another name"), N_("Save the current diagram with another name"),
G_CALLBACK (on_action_save_as)}, G_CALLBACK (on_action_save_as)},
/*
* {"Export", NULL, N_("_Export"), NULL, {"Print", GTK_STOCK_PRINT, N_("_Print"), "<Ctrl>P",
* N_("Export the diagram"), N_("Print the diagram"),
* NULL}, G_CALLBACK (on_action_print)},
*/
{"Quit", GTK_STOCK_QUIT, N_("_Quit"), "<Ctrl>Q", {"Quit", GTK_STOCK_QUIT, N_("_Quit"), "<Ctrl>Q",
N_("Quit the application"), N_("Quit the application"),
G_CALLBACK (on_action_quit)}, G_CALLBACK (on_action_quit)},
@ -994,6 +997,87 @@ on_action_save_as (GtkAction *action, LdWindowMain *self)
diagram_show_save_as_dialog (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 static void
on_action_quit (GtkAction *action, LdWindowMain *self) on_action_quit (GtkAction *action, LdWindowMain *self)
{ {