aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2021-10-25 15:54:24 +0200
committerPřemysl Eric Janouch <p@janouch.name>2021-10-28 05:57:27 +0200
commit4f01392de56f44e33a7e0989a340cced73147a5a (patch)
treeb2c7db8c3269cbf7e83b9d30c1bf37ee8b14f976
parent6cd6ddbd1c25b9b78aaa0b1690b0ca748e2da810 (diff)
downloadlogdiag-4f01392de56f44e33a7e0989a340cced73147a5a.tar.gz
logdiag-4f01392de56f44e33a7e0989a340cced73147a5a.tar.xz
logdiag-4f01392de56f44e33a7e0989a340cced73147a5a.zip
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.
-rw-r--r--liblogdiag/ld-diagram-view.c213
-rw-r--r--liblogdiag/ld-diagram-view.h7
-rw-r--r--share/gui/window-main.ui4
-rw-r--r--share/logdiag.manifest11
-rw-r--r--share/logdiag.rc2
-rw-r--r--src/ld-window-main.c94
6 files changed, 277 insertions, 54 deletions
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 @@
<menuitem action="Save" />
<menuitem action="SaveAs" />
<separator />
-<!--
- <menuitem action="Export" />
+ <menuitem action="Print" />
<separator />
--->
<menuitem action="Quit" />
</menu>
<menu action="EditMenu">
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 @@
+<?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>
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 <windows.h>
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..."), "<Shift><Ctrl>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"), "<Ctrl>P",
+ N_("Print the diagram"),
+ G_CALLBACK (on_action_print)},
+
{"Quit", GTK_STOCK_QUIT, N_("_Quit"), "<Ctrl>Q",
N_("Quit the application"),
G_CALLBACK (on_action_quit)},
@@ -995,6 +998,87 @@ on_action_save_as (GtkAction *action, LdWindowMain *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)
{
if (may_quit (self))