From dcbb0ab8521a5e166f257d86884407eb98ef63f0 Mon Sep 17 00:00:00 2001 From: auroux <auroux> Date: Tue, 25 Mar 2008 04:41:19 +0000 Subject: [PATCH] Update to version 0.4.2. --- AUTHORS | 3 +- ChangeLog | 7 + NEWS | 2 +- README | 2 +- configure.in | 2 +- html-doc/manual.html | 93 ++++-- linuxwacom-0.7.0-rotate-patch | 308 ----------------- pixmaps/shapes.png | Bin 0 -> 294 bytes src/Makefile.am | 3 +- src/TODO | 67 ++-- src/main.c | 7 + src/xo-callbacks.c | 182 +++++++--- src/xo-callbacks.h | 7 + src/xo-file.c | 120 +++++-- src/xo-file.h | 1 + src/xo-interface.c | 75 +++-- src/xo-misc.c | 169 ++++++++-- src/xo-misc.h | 5 + src/xo-paint.c | 261 +++++++++++++-- src/xo-paint.h | 5 + src/xo-print.c | 69 ++-- src/xo-print.h | 2 +- src/xo-shapes.c | 613 ++++++++++++++++++++++++++++++++++ src/xo-shapes.h | 25 ++ src/xournal.h | 28 +- xournal.glade | 76 ++++- 26 files changed, 1578 insertions(+), 554 deletions(-) delete mode 100644 linuxwacom-0.7.0-rotate-patch create mode 100644 pixmaps/shapes.png create mode 100644 src/xo-shapes.c create mode 100644 src/xo-shapes.h diff --git a/AUTHORS b/AUTHORS index 5c78305..3d78974 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1 +1,2 @@ -Denis Auroux (auroux@math.mit.edu) +- Denis Auroux (auroux@math.mit.edu) +- various patch contributors as noted in the ChangeLog diff --git a/ChangeLog b/ChangeLog index a1eb927..3d65b56 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +Version 0.4.2 (Mar 25, 2008): + - bugfixes for X.org 7.3; allow XInput and core events in reverse order + - resize selection (patch contributed by Andy Neitzke) + - pressure sensitive pen (variable stroke width) (patch by Andy Neitzke) + - geometric shape recognizer (after an idea by Lukasz Kaiser) (see manual) + - clean up compiler warnings (patch contributed by Danny Kukawka) + Version 0.4.1 (Sep 15, 2007): - bugfix: compatibility with new versions of pdftoppm (thanks to V. Ciancia) - GTK+ 2.11 event processing bugfix diff --git a/NEWS b/NEWS index 8bed444..8adf320 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -Version 0.4.1 (September 15, 2007) +Version 0.4.2 (March 25, 2008) Installation: see INSTALL User's manual: see html-doc/manual.html diff --git a/README b/README index 8bed444..8adf320 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Version 0.4.1 (September 15, 2007) +Version 0.4.2 (March 25, 2008) Installation: see INSTALL User's manual: see html-doc/manual.html diff --git a/configure.in b/configure.in index 3f83ca8..66c9057 100644 --- a/configure.in +++ b/configure.in @@ -1,7 +1,7 @@ dnl Process this file with autoconf to produce a configure script. AC_INIT(configure.in) -AM_INIT_AUTOMAKE(xournal, 0.4.1) +AM_INIT_AUTOMAKE(xournal, 0.4.2) AM_CONFIG_HEADER(config.h) AM_MAINTAINER_MODE diff --git a/html-doc/manual.html b/html-doc/manual.html index 3faea72..f043617 100644 --- a/html-doc/manual.html +++ b/html-doc/manual.html @@ -24,7 +24,7 @@ Xournal User's Manual </h2> <p style="font-size: 0.95em; text-align: center; color: rgb(0,0,0)"> - Version 0.4.1 + Version 0.4.2 </p> <hr /> <p> @@ -156,6 +156,31 @@ the Tools menu): line segments instead of curvy strokes. For simplicity, selecting the ruler when not in pen or highlighter mode automatically selects the pen. </p> +<a name="recognizer"></a> +<h3 class="subsub"><img src="pixmaps/shapes.png"> The shape recognizer</h3> +<p> + The shape recognizer is also a special + operating mode of the pen and highlighter tools. When it is enabled, + Xournal attempts to recognize geometric shapes as they are drawn, and + if successful will replace the drawn strokes accordingly. The shapes + that can be recognized are: line segments, circles, rectangles, arrows, + triangles and quadrilaterals. Polygonal shapes can be drawn in a single + stroke or in a sequence of consecutive strokes. +</p> +<p> + The recognizer is set to be as unobtrusive as possible, and should not + interfere too much with handwriting. (It differs in this and other ways + from another shape recognizer written for Xournal by Lukasz Kaiser). + As a result, it may only recognize shapes if you draw them carefully and + deliberately. Specific tips for better recognition: (1) for circles, + a closed curve that isn't quite round works better + than a rounder curve that doesn't close; (2) for arrows, it is better + to lift the pen before drawing the tip of the arrow, and make sure + the tip consists of two straight line segments; + (3) for very elongated rectangles, + recognition tends to be better if you lift the pen between consecutive + sides. +</p> <h3 class="subsub"><img src="pixmaps/recycled.png"> Default tools</h3> <p> Each tool (pen, eraser, highlighter, text) has a default setting @@ -555,6 +580,17 @@ Bug reports and suggestions can also be submitted on Xournal's <a name="changelog"></a> <h2 class="subtitle">Version history</h2> <p> +Version 0.4.2 (Mar 25, 2008): +<ul> + <li>bugfixes for X.org 7.3; allow XInput and core events in reverse order</li> + <li>resize selection (patch contributed by Andy Neitzke)</li> + <li>pressure sensitive pen (variable stroke width) (patch by Andy Neitzke)</li> + <li>geometric shape recognizer (after an idea by Lukasz Kaiser) + (see <a href="manual.html#recognizer">here</a>)</li> + <li>clean up compiler warnings (patch contributed by Danny Kukawka)</li> +</ul> +</p> +<p> Version 0.4.1 (Sep 15, 2007): <ul> <li> bugfix: compatibility with new versions of pdftoppm (thanks to @@ -790,11 +826,21 @@ The <i>tool</i> attribute can take the values "pen", "highlighter", or or whiteout eraser); a value of "highlighter" indicates that the stroke should be painted in a partially transparent manner (Xournal uses an alpha coefficient of 0.5). +</p> +<p> The <i>color</i> attribute can take one of the standard values "black", "blue", "red", "green", "gray", "lightblue", "lightgreen", "magenta", "orange", "yellow", "white", or can specify a hexadecimal RGBA value in -the format "#rrggbbaa". The <i>width</i> attribute is a floating-point -number and specifies the width of the stroke in points (1/72 in). +the format "#rrggbbaa". +</p> +<p> +The <i>width</i> attribute is a floating-point +number (or a sequence of floating-point numbers starting with version 0.4.2), +and specifies the width of the stroke in points (1/72 in). (For a +variable-width stroke, the <i>width</i> attribute contains a +whitespace-separated succession of floating-point values: first the +nominal brush width, and then the width of each successive segment forming +the stroke.) </p> <p> The list of coordinates is simply a succession of floating-point values, @@ -842,7 +888,7 @@ modern Linux distributions such as Fedora Core 3 or later, or RHEL 4 or later): <ul> <li> the <b>gtk+</b> libraries, version <b>2.4</b> or later - (2.6 recommended) (package gtk2 and dependencies)</li> + (<b>2.6</b> strongly recommended) (package gtk2 and dependencies)</li> <li> <b>libgnomecanvas</b> version <b>2.4</b> or later (package libgnomecanvas and dependencies)</li> <li> <b>libgnomeprint</b> and <b>libgnomeprintui</b> version <b>2.2</b> or later @@ -1016,9 +1062,9 @@ linuxwacom driver, the tablet calibration information is often not updated properly when using xsetwacom). </p> <p> -<b>Important:</b> due to issues with the linuxwacom driver, it is important -to either upgrade your driver to a <a href="manual.html#wacompatch">patched -version</a>, or restrict your choice of settings as follows: +<b>Important:</b> due to issues with old versions of the linuxwacom +driver (< 0.7.6), it is important to upgrade your driver, +or restrict your choice of settings as follows: <ul> <li> your calibration settings should not be changed at runtime (i.e., insert your settings directly into your X server configuration file. @@ -1030,8 +1076,8 @@ Do not add a list of xsetwacom commands to your startup files). </p> <h3 class="subsub">Strokes aren't drawn under the cursor...</h3> <p> -This is due to misfeatures in the linuxwacom driver in versions prior -to 0.7.6. Typically, this will happen in all of the following cases: +This is typically due to misfeatures in the linuxwacom driver in versions +prior to 0.7.6, and will happen in all of the following cases: <ul> <li> the calibration settings have been changed after the X server was started (using xsetwacom) </li> @@ -1042,24 +1088,24 @@ Most of these issues should have been fixed in version 0.7.6 of the wacom driver. Otherwise, you can either disable XInput support in Xournal (in the Options menu), at a price of a severe loss of resolution (and the eraser tip won't be detected -anymore), or apply this <a href="manual.html#wacompatch">patch</a> -to version 0.7.0 of the wacom driver. +anymore), or inspect carefully your X server configuration and make +sure the tablet devices are calibrated and configured properly. </p> <p> I have also had a report that one of the workarounds used by Xournal to bypass a calibration bug in GTK+ can actually entirely prevent strokes from being drawn. If you are being unsuccessful at drawing in Xournal -with XInput enabled, try recompiling after changing the first line of -<tt>src/main.c</tt> to -<pre>#define ENABLE_XINPUT_BUGFIX 0</pre> +with XInput enabled, try recompiling after commenting out the line +<pre>#define ENABLE_XINPUT_BUGFIX</pre> +near the beginning of <tt>src/xournal.h</tt>. If this modification does improve things for you, and if you have a bit of spare time to help investigate the causes of this problem, please contact me. </p> <h3 class="subsub">On-the-fly display rotation</h3> <p> -You need an X server that supports the RANDR extension, and a -recent (0.7.6 or later) or <a href="manual.html#wacompatch">patched</a> +You need an X server that supports the RANDR extension, and a sufficiently +recent (0.7.6 or later) version of the linuxwacom driver to support on-the-fly rotation. </p> <p> @@ -1078,20 +1124,5 @@ otherwise the tablet calibration in Xournal may (and most likely will) become incorrect. Exit Xournal and restart it after the display has been rotated. </p> -<a name="wacompatch"></a> -<h3 class="subsub">Linuxwacom patch for calibration and rotation</h3> -<p> -This patch fixes rotation and calibration issues with the linuxwacom driver -version 0.7.0. -<ul> -<li>The <a href="http://xournal.sourceforge.net/linuxwacom-0.7.0-rotate-patch">patch -file</a> for the linuxwacom source code (also included with the Xournal -distribution).</li> -<li>The <a href="http://xournal.sourceforge.net/linuxwacom-rotate-patch.tar.gz">patched -binaries</a> for the X.org X server.</li> -</ul> -This patch has been included in version 0.7.6 of the linuxwacom driver, -and should now be obsolete. -</p> </body> </html> diff --git a/linuxwacom-0.7.0-rotate-patch b/linuxwacom-0.7.0-rotate-patch deleted file mode 100644 index 9bd6527..0000000 --- a/linuxwacom-0.7.0-rotate-patch +++ /dev/null @@ -1,308 +0,0 @@ -diff -Naur linuxwacom-0.7.0/src/wcmCommon.c linuxwacom-0.7.0-rotate/src/wcmCommon.c ---- linuxwacom-0.7.0/src/wcmCommon.c 2005-09-19 19:43:14.000000000 -0400 -+++ linuxwacom-0.7.0-rotate/src/wcmCommon.c 2005-11-22 16:44:41.000000000 -0500 -@@ -440,6 +440,11 @@ - y = x; - x = common->wcmMaxX - tmp_coord; - } -+ else if (common->wcmRotate == ROTATE_HALF) -+ { -+ x = common->wcmMaxX - x; -+ y = common->wcmMaxY - y; -+ } - - is_absolute = (priv->flags & ABSOLUTE_FLAG); - is_core_pointer = xf86IsCorePointer(local->dev); -diff -Naur linuxwacom-0.7.0/src/xf86Wacom.c linuxwacom-0.7.0-rotate/src/xf86Wacom.c ---- linuxwacom-0.7.0/src/xf86Wacom.c 2005-09-19 19:43:14.000000000 -0400 -+++ linuxwacom-0.7.0-rotate/src/xf86Wacom.c 2005-12-07 22:57:46.000000000 -0500 -@@ -199,7 +199,7 @@ - LocalDevicePtr local = (LocalDevicePtr)pWcm->public.devicePrivate; - WacomDevicePtr priv = (WacomDevicePtr)PRIVATE(pWcm); - WacomCommonPtr common = priv->common; -- int totalWidth = 0, maxHeight = 0, tabletSize = 0; -+ int totalWidth = 0, maxHeight = 0; - double screenRatio, tabletRatio; - char m1[32], m2[32]; - -@@ -335,23 +335,13 @@ - } /* end bounding rect */ - - /* x and y axes */ -- if (priv->twinview == TV_LEFT_RIGHT) -- tabletSize = 2*(priv->bottomX - priv->topX - 2*priv->tvoffsetX); -- else -- tabletSize = priv->bottomX - priv->topX; -- -- InitValuatorAxisStruct(pWcm, 0, 0, tabletSize, /* max val */ -- common->wcmResolX, /* tablet resolution */ -- 0, common->wcmResolX); /* max_res */ -- -- if (priv->twinview == TV_ABOVE_BELOW) -- tabletSize = 2*(priv->bottomY - priv->topY - 2*priv->tvoffsetY); -- else -- tabletSize = priv->bottomY - priv->topY; -- -- InitValuatorAxisStruct(pWcm, 1, 0, tabletSize, /* max val */ -- common->wcmResolY, /* tablet resolution */ -- 0, common->wcmResolY); /* max_res */ -+ InitValuatorAxisStruct(pWcm, 0, priv->topX, priv->bottomX, /* max val */ -+ common->wcmResolX, /* tablet resolution */ -+ 0, common->wcmResolX); /* max_res */ -+ -+ InitValuatorAxisStruct(pWcm, 1, priv->topY, priv->bottomY, /* max val */ -+ common->wcmResolY, /* tablet resolution */ -+ 0, common->wcmResolY); /* max_res */ - - /* pressure */ - InitValuatorAxisStruct(pWcm, 2, 0, -@@ -657,25 +647,40 @@ - static int xf86WcmSetParam(LocalDevicePtr local, int param, int value) - { - WacomDevicePtr priv = (WacomDevicePtr)local->private; -+ WacomDevicePtr tmppriv; - char st[32]; -+ int oldRotation, dev; -+ int tmpTopX, tmpTopY, tmpBottomX, tmpBottomY, oldMaxX, oldMaxY; - - switch (param) - { - case XWACOM_PARAM_TOPX: - xf86ReplaceIntOption(local->options, "TopX", value); - priv->topX = xf86SetIntOption(local->options, "TopX", 0); -+ InitValuatorAxisStruct(local->dev, 0, priv->topX, priv->bottomX, -+ priv->common->wcmResolX, /* tablet resolution */ -+ 0, priv->common->wcmResolX); /* max_res */ - break; - case XWACOM_PARAM_TOPY: - xf86ReplaceIntOption(local->options, "TopY", value); - priv->topY = xf86SetIntOption(local->options, "TopY", 0); -+ InitValuatorAxisStruct(local->dev, 1, priv->topY, priv->bottomY, /* max val */ -+ priv->common->wcmResolY, /* tablet resolution */ -+ 0, priv->common->wcmResolY); /* max_res */ - break; - case XWACOM_PARAM_BOTTOMX: - xf86ReplaceIntOption(local->options, "BottomX", value); - priv->bottomX = xf86SetIntOption(local->options, "BottomX", 0); -+ InitValuatorAxisStruct(local->dev, 0, priv->topX, priv->bottomX, -+ priv->common->wcmResolX, /* tablet resolution */ -+ 0, priv->common->wcmResolX); /* max_res */ - break; - case XWACOM_PARAM_BOTTOMY: - xf86ReplaceIntOption(local->options, "BottomY", value); - priv->bottomY = xf86SetIntOption(local->options, "BottomY", 0); -+ InitValuatorAxisStruct(local->dev, 1, priv->topY, priv->bottomY, /* max val */ -+ priv->common->wcmResolY, /* tablet resolution */ -+ 0, priv->common->wcmResolY); /* max_res */ - break; - case XWACOM_PARAM_BUTTON1: - if ((value < 0) || (value > 19)) return BadValue; -@@ -782,6 +787,12 @@ - "BottomY", priv->common->wcmMaxY); - priv->bottomY = xf86SetIntOption(local->options, - "BottomY", priv->common->wcmMaxY); -+ InitValuatorAxisStruct(local->dev, 0, priv->topX, priv->bottomX, -+ priv->common->wcmResolX, /* tablet resolution */ -+ 0, priv->common->wcmResolX); /* max_res */ -+ InitValuatorAxisStruct(local->dev, 1, priv->topY, priv->bottomY, /* max val */ -+ priv->common->wcmResolY, /* tablet resolution */ -+ 0, priv->common->wcmResolY); /* max_res */ - break; - case XWACOM_PARAM_GIMP: - if ((value != 0) && (value != 1)) return BadValue; -@@ -819,6 +830,103 @@ - xf86ReplaceStrOption(local->options, "TPCButton", "off"); - } - break; -+ case XWACOM_PARAM_ROTATE: -+ if ((value < 0) || (value > 3)) return BadValue; -+ switch(value) { -+ case ROTATE_NONE: -+ xf86ReplaceStrOption(local->options, "Rotate", "NONE"); -+ break; -+ case ROTATE_CW: -+ xf86ReplaceStrOption(local->options, "Rotate", "CW"); -+ break; -+ case ROTATE_CCW: -+ xf86ReplaceStrOption(local->options, "Rotate", "CCW"); -+ break; -+ case ROTATE_HALF: -+ xf86ReplaceStrOption(local->options, "Rotate", "HALF"); -+ break; -+ default: -+ return BadValue; -+ } -+ oldRotation = priv->common->wcmRotate; -+ oldMaxX = priv->common->wcmMaxX; -+ oldMaxY = priv->common->wcmMaxY; -+ priv->common->wcmRotate = value; -+ if (((oldRotation == ROTATE_NONE || oldRotation == ROTATE_HALF) && (value == ROTATE_CW || value == ROTATE_CCW)) || -+ ((oldRotation == ROTATE_CW || oldRotation == ROTATE_CCW) && (value == ROTATE_NONE || value == ROTATE_HALF))) -+ { -+ priv->common->wcmMaxX = oldMaxY; -+ priv->common->wcmMaxY = oldMaxX; -+ } -+ -+ /* rotate all devices at once! else they get misaligned */ -+ for (dev=0; dev < priv->common->wcmNumDevices; dev++) -+ { -+ tmppriv = (WacomDevicePtr)priv->common->wcmDevices[dev]->private; -+ /* recover the unrotated xy-rectangles */ -+ switch (oldRotation) { -+ case ROTATE_CW: -+ tmpTopX = oldMaxY - tmppriv->bottomY; -+ tmpBottomX = oldMaxY - tmppriv->topY; -+ tmpTopY = tmppriv->topX; -+ tmpBottomY = tmppriv->bottomX; -+ break; -+ case ROTATE_CCW: -+ tmpTopX = tmppriv->topY; -+ tmpBottomX = tmppriv->bottomY; -+ tmpTopY = oldMaxX - tmppriv->bottomX; -+ tmpBottomY = oldMaxX - tmppriv->topX; -+ break; -+ case ROTATE_HALF: -+ tmpTopX = oldMaxX - tmppriv->bottomX; -+ tmpBottomX = oldMaxX - tmppriv->topX; -+ tmpTopY = oldMaxY - tmppriv->bottomY; -+ tmpBottomY = oldMaxY - tmppriv->topY; -+ break; -+ default: /* ROTATE_NONE */ -+ tmpTopX = tmppriv->topX; -+ tmpBottomX = tmppriv->bottomX; -+ tmpTopY = tmppriv->topY; -+ tmpBottomY = tmppriv->bottomY; -+ break; -+ } -+ /* and rotate them back */ -+ switch (value) { -+ case ROTATE_CW: -+ tmppriv->topX = tmpTopY; -+ tmppriv->bottomX = tmpBottomY; -+ tmppriv->topY = priv->common->wcmMaxY - tmpBottomX; -+ tmppriv->bottomY = priv->common->wcmMaxY - tmpTopX; -+ break; -+ case ROTATE_CCW: -+ tmppriv->topX = priv->common->wcmMaxX - tmpBottomY; -+ tmppriv->bottomX = priv->common->wcmMaxX - tmpTopY; -+ tmppriv->topY = tmpTopX; -+ tmppriv->bottomY = tmpBottomX; -+ break; -+ case ROTATE_HALF: -+ tmppriv->topX = priv->common->wcmMaxX - tmpBottomX; -+ tmppriv->bottomX = priv->common->wcmMaxX - tmpTopX; -+ tmppriv->topY= priv->common->wcmMaxY - tmpBottomY; -+ tmppriv->bottomY = priv->common->wcmMaxY - tmpTopY; -+ break; -+ default: /* ROTATE_NONE */ -+ tmppriv->topX = tmpTopX; -+ tmppriv->bottomX = tmpBottomX; -+ tmppriv->topY = tmpTopY; -+ tmppriv->bottomY = tmpBottomY; -+ break; -+ } -+ -+ InitValuatorAxisStruct(priv->common->wcmDevices[dev]->dev, -+ 0, tmppriv->topX, tmppriv->bottomX, priv->common->wcmResolX, /* tablet resolution */ -+ 0, priv->common->wcmResolX); /* max_res */ -+ -+ InitValuatorAxisStruct(priv->common->wcmDevices[dev]->dev, -+ 1, tmppriv->topY, tmppriv->bottomY, priv->common->wcmResolY, /* tablet resolution */ -+ 0, priv->common->wcmResolY); /* max_res */ -+ } -+ break; - default: - DBG(10, ErrorF("xf86WcmSetParam invalid param %d\n",param)); - return BadMatch; -diff -Naur linuxwacom-0.7.0/src/xf86Wacom.h linuxwacom-0.7.0-rotate/src/xf86Wacom.h ---- linuxwacom-0.7.0/src/xf86Wacom.h 2005-09-19 19:43:14.000000000 -0400 -+++ linuxwacom-0.7.0-rotate/src/xf86Wacom.h 2005-11-22 17:06:57.000000000 -0500 -@@ -150,6 +150,12 @@ - #define ERASER_PROX 4 - #define OTHER_PROX 1 - -+ -+#define ROTATE_NONE XWACOM_VALUE_ROTATE_NONE -+#define ROTATE_CW XWACOM_VALUE_ROTATE_CW -+#define ROTATE_CCW XWACOM_VALUE_ROTATE_CCW -+#define ROTATE_HALF XWACOM_VALUE_ROTATE_HALF -+ - /****************************************************************************** - * Forward Declarations - *****************************************************************************/ -@@ -412,10 +418,6 @@ - - #define DEVICE_ISDV4 0x000C - --#define ROTATE_NONE 0 --#define ROTATE_CW 1 --#define ROTATE_CCW 2 -- - #define MAX_CHANNELS 2 - #define MAX_USB_EVENTS 32 - -diff -Naur linuxwacom-0.7.0/src/xsetwacom.c linuxwacom-0.7.0-rotate/src/xsetwacom.c ---- linuxwacom-0.7.0/src/xsetwacom.c 2005-09-19 19:43:14.000000000 -0400 -+++ linuxwacom-0.7.0-rotate/src/xsetwacom.c 2005-11-22 17:17:24.000000000 -0500 -@@ -190,6 +190,13 @@ - "on for Tablet PC. ", - XWACOM_PARAM_TPCBUTTON, VALUE_OPTIONAL, - RANGE, 0, 1, BOOLEAN_VALUE, 1 }, -+ -+ { "Rotate", -+ "Sets the rotation of the tablet. " -+ "Values = NONE, CW, CCW, HALF (default is NONE).", -+ XWACOM_PARAM_ROTATE, VALUE_OPTIONAL, -+ RANGE, XWACOM_VALUE_ROTATE_NONE, XWACOM_VALUE_ROTATE_HALF, SINGLE_VALUE, -+ XWACOM_VALUE_ROTATE_NONE }, - - { "FileModel", - "Writes tablet models to /etc/wacom.dat", -@@ -198,7 +205,7 @@ - { "FileOption", - "Writes configuration options to /etc/X11/wcm.dev_name", - XWACOM_PARAM_FILEOPTION, VALUE_OPTIONAL }, -- -+ - { NULL } - }; - -@@ -403,6 +410,23 @@ - !strcasecmp(pszValues[i],"false") || - !strcasecmp(pszValues[i],"relative"))) - nValues[i] = 0; -+ else if (p->nParamID == XWACOM_PARAM_ROTATE) -+ { -+ if (!strcasecmp(pszValues[i],"none")) -+ nValues[i] = XWACOM_VALUE_ROTATE_NONE; -+ else if (!strcasecmp(pszValues[i],"cw")) -+ nValues[i] = XWACOM_VALUE_ROTATE_CW; -+ else if (!strcasecmp(pszValues[i],"ccw")) -+ nValues[i] = XWACOM_VALUE_ROTATE_CCW; -+ else if (!strcasecmp(pszValues[i],"half")) -+ nValues[i] = XWACOM_VALUE_ROTATE_HALF; -+ else -+ { -+ fprintf(stderr,"Set: Value '%s' is " -+ "invalid.\n",pszValues[i]); -+ return 1; -+ } -+ } - else - { - fprintf(stderr,"Set: Value '%s' is " -diff -Naur linuxwacom-0.7.0/src/Xwacom.h linuxwacom-0.7.0-rotate/src/Xwacom.h ---- linuxwacom-0.7.0/src/Xwacom.h 2005-09-19 19:43:14.000000000 -0400 -+++ linuxwacom-0.7.0-rotate/src/Xwacom.h 2005-11-22 17:06:00.000000000 -0500 -@@ -44,5 +44,11 @@ - #define XWACOM_PARAM_GIMP 102 - #define XWACOM_PARAM_MMT 103 - #define XWACOM_PARAM_TPCBUTTON 104 -+#define XWACOM_PARAM_ROTATE 105 -+ -+#define XWACOM_VALUE_ROTATE_NONE 0 -+#define XWACOM_VALUE_ROTATE_CW 1 -+#define XWACOM_VALUE_ROTATE_CCW 2 -+#define XWACOM_VALUE_ROTATE_HALF 3 - - #endif /* __XF86_XWACOM_H */ diff --git a/pixmaps/shapes.png b/pixmaps/shapes.png new file mode 100644 index 0000000000000000000000000000000000000000..902ec3d4a82a1e085199993824e06d7f121141b4 GIT binary patch literal 294 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fY)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmPSvnZd1+TBZkLFzVpx;TbdoK8+yAef*Mdia>@XUTVS z>`p5DerL)2{r-NQd)nIsu6&*^a%b8469slB_?9R7MASI&q<sjPmMZX(L*dl)|DGHZ z|MLLB*`ur?(mlo=K1W$j8vU?mW<FdmDk@r&^<VhY+j^OGD&D%na^3SLOpuZ6Ed5w- z^!Ty8q24kM9^2+f4UY6p2kO1PUewW8@$%LGB`){sS6M4r8TG6vVwf(qCvsxv#KwpB g>znkFQY3(WU)$_fx+3bVA<+2@p00i_>zopr05p$lTL1t6 literal 0 HcmV?d00001 diff --git a/src/Makefile.am b/src/Makefile.am index f61c052..69636d5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,7 +15,8 @@ xournal_SOURCES = \ xo-print.c xo-print.h \ xo-support.c xo-support.h \ xo-interface.c xo-interface.h \ - xo-callbacks.c xo-callbacks.h + xo-callbacks.c xo-callbacks.h \ + xo-shapes.c xo-shapes.h xournal_LDADD = @PACKAGE_LIBS@ diff --git a/src/TODO b/src/TODO index fef98ae..0cff969 100644 --- a/src/TODO +++ b/src/TODO @@ -1,34 +1,58 @@ List of features to be implemented (not in any particular order) ---------------------------------------------------------------- + +- collaborative editing (see discussion with Erik Demaine) +- porting to Win32 and MacOS +- multiple-scenario undo history + +- render page to bitmap: for export, preview, and copy-paste + (render using libart, see how gnomecanvas does it?) + (copy-paste: config option to render only current layer or all below?) +- cut-and-paste of selection into other apps (as bitmap; as SVG?) +- navigation sidebar with bitmap page previews +- bitmap preview for document icon in desktop environments? +- "organizer" side panel (hierarchy of notes), cf. gjots + - paste text directly into xournal, from xournal? (instead of starting a text item and pasting into/from it) -- internationalization / translation of interface -- a command + keyboard shortcut to switch between mappings (1<->2, 1<->3) +- increase width of spinPageNo to fit 3 digits +- a command + keyboard shortcut to switch mappings (1<->2, 1<->3, 2<->3) (A. Rechnitzer Sept 11, 2007) + +- lasso tool +- internationalization / translation of interface +- switch to poppler instead of pdftoppm; with exact float dpi settings +- load PDF pages only on demand (create empty pixmaps at first if can + parse PDF geometry ourselves, else try pdfinfo ??) + (and config option to limit total memory usage for PDF bitmaps) +- ability to select entire page for copy-paste (as bitmap / reorder xournal) +- copy/paste of an entire page (beware if PDF bg is not compatible!) - rewrite printing using GtkPrint + Cairo as GnomePrint replacement (keep GnomePrint option for compatibility with GTK+ <2.10) +- insert images (screen capture or from file or from clipboard), + not as full-page backgrounds (new ITEM type) +- convert to/from Jarnal format; to/from MS Journal format??? + + +- sticky notes (anchor visually text box to a bg location) +- use relative paths for bg documents (e.g. annotated PDF) +- flush display queue when drawing over a slow X server? +- more paper customization (in particular, 1/2 inch graph paper) +- option to map a button to a context menu (incl. tool selection, ...) +- option to map a button to "undo" - xournal_page-shadow.diff (Martin Kiefel Feb 5 2007) -- "organizer" side panel (hierarchy of notes), cf. gjots - xoj2pdf on command line - 'insert blank page after' command (more useful in PDF annot !) - load images as bg if given on command-line (as with PDF on commandline) -- load PDF pages only on demand (create empty pixmaps at first if can - parse PDF geometry ourselves, else try pdfinfo ??) - (and config option to limit total memory usage for PDF bitmaps) - --- switch to poppler lib instead of pdftoppm - lasso selection tool (see shoffsta patch) (http://shoffsta.afraid.org/Projects/Xournal/) - flatten (incl undo/redo...) - enabled only if nlayers>1 -- resize selection - color chooser (papercolor, pen color); maybe more default colors - printing: print-options, save printer settings (throughout a session, and on disk) (maybe a separate config file .xournal/gnome-print-settings) - help index -- pressure sensitivity -- insert images (screen capture or from file or from clipboard), - not as full-page backgrounds (new ITEM type) +- option for highlighter to be always at bottom of its layer - more pen/highlighter shapes (chisel) -- convert to/from Jarnal format; to/from MS Journal format??? - recalibration upon screen resize / compensation for miscalibration (use ConfigureNotify event and XInput? cf "Bugs" tracker 08/2007) - find a better behavior for vertical space tool across page boundaries ? @@ -41,8 +65,6 @@ List of features to be implemented (not in any particular order) - move only what doesn't fit (??? looks hard) option for vert space tool to also move the background?? (PDF: cut-and-crop by running PDF code twice with 2 different clipboxes?) -- copy/paste of an entire page (beware if PDF bg is not compatible!) -- simple drawing tools: rectangles, ellipses - option to save all annotated files within the .xoj - non-antialiased version for handhelds - customize autogenerated save file names @@ -51,7 +73,6 @@ List of features to be implemented (not in any particular order) - display corruption on scroll down when bottom of window is obscured?? (probably a gnomecanvas or X bug -- expose event generated for wrong region, or not processed?) -- bitmap preview for document icon in desktop environments? - autosave at a regular interval in a given location - keep only a few pages of a PDF file in memory at any given time; generate pages by parsing pdf info rather than generating bitmaps for all of them. @@ -66,14 +87,10 @@ List of features to be implemented (not in any particular order) - handwritten stroke search in document (see cellwriter?) - option: export to PDF with incremental pages for successive layers (for presentations) (Daniel Brugarth 8/18/07) -- render page to bitmap: for export, preview, and copy-paste - (render using libart, see how gnomecanvas does it?) - (copy-paste: config option to render only current layer or all below?) -- cut-and-paste of selection into other apps (as bitmap; as SVG?) -- ability to select entire page for copy-paste (as bitmap / reorder xournal) -- Lukasz Kaiser 8/15/07 shapes patch (approximate stroke by geometric shapes) - (make it an optional mode of the pen, like the ruler) - (rewrite without gsl dependency?) - (config file should be loaded from share/... and .xournal/...) - (disconnected shapes: add timestamps to strokes?) - Samuel Hoffstaetter: lasso, gettext localization, sidebar thumbnails, ... +- YoYo Siska patch for desktop mode ?? +- Vivek Ayer: rotate paper wrt screen (for environments where display + rotation doesn't work): gnome_canvas_item_affine_relative(canvas->root, ...) + would rotate all but text items (still need to modify scroll bbox, and + adjust event coordinates by inverse rotation). +- switch to libglade, and allow customization of key shortcuts (accels) diff --git a/src/main.c b/src/main.c index cbe9415..f22125e 100644 --- a/src/main.c +++ b/src/main.c @@ -2,6 +2,7 @@ # include <config.h> #endif +#include <sys/stat.h> #include <string.h> #include <gtk/gtk.h> #include <libgnomecanvas/libgnomecanvas.h> @@ -12,6 +13,8 @@ #include "xo-callbacks.h" #include "xo-misc.h" #include "xo-file.h" +#include "xo-paint.h" +#include "xo-shapes.h" GtkWidget *winMain; GnomeCanvas *canvas; @@ -75,6 +78,8 @@ void init_stuff (int argc, char *argv[]) ui.cur_path.coords = NULL; ui.cur_path_storage_alloc = 0; ui.cur_path.ref_count = 1; + ui.cur_widths = NULL; + ui.cur_widths_storage_alloc = 0; ui.selection = NULL; ui.cursor = NULL; @@ -94,6 +99,8 @@ void init_stuff (int argc, char *argv[]) g_memmove(ui.default_brushes+i, &(ui.brushes[0][i]), sizeof(struct Brush)); ui.cur_mapping = 0; + + reset_recognizer(); // initialize various interface elements diff --git a/src/xo-callbacks.c b/src/xo-callbacks.c index 39204f0..9b1e85b 100644 --- a/src/xo-callbacks.c +++ b/src/xo-callbacks.c @@ -9,6 +9,7 @@ #include <time.h> #include <libgnomeprintui/gnome-print-dialog.h> #include <glib/gstdio.h> +#include <gdk/gdkkeysyms.h> #include "xournal.h" #include "xo-callbacks.h" @@ -18,6 +19,7 @@ #include "xo-file.h" #include "xo-paint.h" #include "xo-print.h" +#include "xo-shapes.h" void on_fileNew_activate (GtkMenuItem *menuitem, @@ -290,7 +292,7 @@ void on_filePrint_activate (GtkMenuItem *menuitem, gpointer user_data) { - GtkWidget *printDialog, *preview; + GtkWidget *printDialog; GnomePrintJob *gpj; int fromPage, toPage; int response; @@ -379,7 +381,6 @@ on_filePrintPDF_activate (GtkMenuItem *menuitem, char *filename, *in_fn; char stime[30]; time_t curtime; - int response; gboolean warn; end_text(); @@ -472,6 +473,7 @@ on_editUndo_activate (GtkMenuItem *menuitem, struct Background *tmp_bg; double tmp_x, tmp_y; gchar *tmpstr; + GnomeCanvasGroup *group; end_text(); reset_focus(); @@ -485,7 +487,7 @@ on_editUndo_activate (GtkMenuItem *menuitem, undo->layer->items = g_list_remove(undo->layer->items, undo->item); undo->layer->nitems--; } - else if (undo->type == ITEM_ERASURE) { + else if (undo->type == ITEM_ERASURE || undo->type == ITEM_RECOGNIZER) { for (list = undo->erasurelist; list!=NULL; list = list->next) { erasure = (struct UndoErasureData *)list->data; // delete all the created items @@ -574,6 +576,11 @@ on_editUndo_activate (GtkMenuItem *menuitem, move_journal_items_by(undo->itemlist, -undo->val_x, -undo->val_y, undo->layer2, undo->layer, undo->auxlist); } + else if (undo->type == ITEM_RESIZESEL) { + resize_journal_items_by(undo->itemlist, + 1/undo->scaling_x, 1/undo->scaling_y, + -undo->val_x/undo->scaling_x, -undo->val_y/undo->scaling_y); + } else if (undo->type == ITEM_PASTE) { for (itemlist = undo->itemlist; itemlist != NULL; itemlist = itemlist->next) { it = (struct Item *)itemlist->data; @@ -622,10 +629,12 @@ on_editUndo_activate (GtkMenuItem *menuitem, g_memmove(&tmp_brush, &(it->brush), sizeof(struct Brush)); g_memmove(&(it->brush), list->data, sizeof(struct Brush)); g_memmove(list->data, &tmp_brush, sizeof(struct Brush)); - if (it->type == ITEM_STROKE && it->canvas_item != NULL) - gnome_canvas_item_set(it->canvas_item, - "fill-color-rgba", it->brush.color_rgba, - "width-units", it->brush.thickness, NULL); + if (it->type == ITEM_STROKE && it->canvas_item != NULL) { + // remark: a variable-width item might have lost its variable-width + group = (GnomeCanvasGroup *) it->canvas_item->parent; + gtk_object_destroy(GTK_OBJECT(it->canvas_item)); + make_canvas_item_one(group, it); + } if (it->type == ITEM_TEXT && it->canvas_item != NULL) gnome_canvas_item_set(it->canvas_item, "fill-color-rgba", it->brush.color_rgba, NULL); @@ -678,6 +687,7 @@ on_editRedo_activate (GtkMenuItem *menuitem, struct Layer *l; double tmp_x, tmp_y; gchar *tmpstr; + GnomeCanvasGroup *group; end_text(); reset_focus(); @@ -690,7 +700,7 @@ on_editRedo_activate (GtkMenuItem *menuitem, redo->layer->items = g_list_append(redo->layer->items, redo->item); redo->layer->nitems++; } - else if (redo->type == ITEM_ERASURE) { + else if (redo->type == ITEM_ERASURE || redo->type == ITEM_RECOGNIZER) { for (list = redo->erasurelist; list!=NULL; list = list->next) { erasure = (struct UndoErasureData *)list->data; target = g_list_find(redo->layer->items, erasure->item); @@ -787,6 +797,10 @@ on_editRedo_activate (GtkMenuItem *menuitem, move_journal_items_by(redo->itemlist, redo->val_x, redo->val_y, redo->layer, redo->layer2, NULL); } + else if (redo->type == ITEM_RESIZESEL) { + resize_journal_items_by(redo->itemlist, + redo->scaling_x, redo->scaling_y, redo->val_x, redo->val_y); + } else if (redo->type == ITEM_PASTE) { for (itemlist = redo->itemlist; itemlist != NULL; itemlist = itemlist->next) { it = (struct Item *)itemlist->data; @@ -828,10 +842,12 @@ on_editRedo_activate (GtkMenuItem *menuitem, g_memmove(&tmp_brush, &(it->brush), sizeof(struct Brush)); g_memmove(&(it->brush), list->data, sizeof(struct Brush)); g_memmove(list->data, &tmp_brush, sizeof(struct Brush)); - if (it->type == ITEM_STROKE && it->canvas_item != NULL) - gnome_canvas_item_set(it->canvas_item, - "fill-color-rgba", it->brush.color_rgba, - "width-units", it->brush.thickness, NULL); + if (it->type == ITEM_STROKE && it->canvas_item != NULL) { + // remark: a variable-width item might have lost its variable-width + group = (GnomeCanvasGroup *) it->canvas_item->parent; + gtk_object_destroy(GTK_OBJECT(it->canvas_item)); + make_canvas_item_one(group, it); + } if (it->type == ITEM_TEXT && it->canvas_item != NULL) gnome_canvas_item_set(it->canvas_item, "fill-color-rgba", it->brush.color_rgba, NULL); @@ -1681,8 +1697,9 @@ on_toolsPen_activate (GtkMenuItem *menuitem, reset_focus(); reset_selection(); ui.toolno[0] = TOOL_PEN; - ui.ruler[0] = FALSE; ui.cur_brush = &(ui.brushes[0][TOOL_PEN]); + ui.cur_brush->ruler = FALSE; + ui.cur_brush->recognizer = FALSE; update_mapping_linkings(TOOL_PEN); update_tool_buttons(); update_tool_menu(); @@ -1710,7 +1727,6 @@ on_toolsEraser_activate (GtkMenuItem *menuitem, reset_focus(); reset_selection(); ui.toolno[0] = TOOL_ERASER; - ui.ruler[0] = FALSE; ui.cur_brush = &(ui.brushes[0][TOOL_ERASER]); update_mapping_linkings(TOOL_ERASER); update_tool_buttons(); @@ -1739,8 +1755,9 @@ on_toolsHighlighter_activate (GtkMenuItem *menuitem, reset_focus(); reset_selection(); ui.toolno[0] = TOOL_HIGHLIGHTER; - ui.ruler[0] = FALSE; ui.cur_brush = &(ui.brushes[0][TOOL_HIGHLIGHTER]); + ui.cur_brush->ruler = FALSE; + ui.cur_brush->recognizer = FALSE; update_mapping_linkings(TOOL_HIGHLIGHTER); update_tool_buttons(); update_tool_menu(); @@ -1767,7 +1784,6 @@ on_toolsText_activate (GtkMenuItem *menuitem, reset_focus(); reset_selection(); ui.toolno[0] = TOOL_TEXT; - ui.ruler[0] = FALSE; ui.cur_brush = &(ui.brushes[0][TOOL_PEN]); update_mapping_linkings(-1); update_tool_buttons(); @@ -1803,7 +1819,6 @@ on_toolsSelectRectangle_activate (GtkMenuItem *menuitem, end_text(); reset_focus(); ui.toolno[0] = TOOL_SELECTRECT; - ui.ruler[0] = FALSE; update_mapping_linkings(-1); update_tool_buttons(); update_tool_menu(); @@ -1831,7 +1846,6 @@ on_toolsVerticalSpace_activate (GtkMenuItem *menuitem, reset_focus(); reset_selection(); ui.toolno[0] = TOOL_VERTSPACE; - ui.ruler[0] = FALSE; update_mapping_linkings(-1); update_tool_buttons(); update_tool_menu(); @@ -2093,7 +2107,6 @@ on_toolsDefaultPen_activate (GtkMenuItem *menuitem, g_memmove(&(ui.brushes[0][TOOL_PEN]), ui.default_brushes+TOOL_PEN, sizeof(struct Brush)); ui.toolno[0] = TOOL_PEN; ui.cur_brush = &(ui.brushes[0][TOOL_PEN]); - ui.ruler[0] = FALSE; update_mapping_linkings(TOOL_PEN); update_tool_buttons(); update_tool_menu(); @@ -2114,7 +2127,6 @@ on_toolsDefaultEraser_activate (GtkMenuItem *menuitem, g_memmove(&(ui.brushes[0][TOOL_ERASER]), ui.default_brushes+TOOL_ERASER, sizeof(struct Brush)); ui.toolno[0] = TOOL_ERASER; ui.cur_brush = &(ui.brushes[0][TOOL_ERASER]); - ui.ruler[0] = FALSE; update_mapping_linkings(TOOL_ERASER); update_tool_buttons(); update_tool_menu(); @@ -2135,7 +2147,6 @@ on_toolsDefaultHighlighter_activate (GtkMenuItem *menuitem, g_memmove(&(ui.brushes[0][TOOL_HIGHLIGHTER]), ui.default_brushes+TOOL_HIGHLIGHTER, sizeof(struct Brush)); ui.toolno[0] = TOOL_HIGHLIGHTER; ui.cur_brush = &(ui.brushes[0][TOOL_HIGHLIGHTER]); - ui.ruler[0] = FALSE; update_mapping_linkings(TOOL_HIGHLIGHTER); update_tool_buttons(); update_tool_menu(); @@ -2153,7 +2164,6 @@ on_toolsDefaultText_activate (GtkMenuItem *menuitem, reset_focus(); reset_selection(); ui.toolno[0] = TOOL_TEXT; - ui.ruler[0] = FALSE; ui.cur_brush = &(ui.brushes[0][TOOL_PEN]); ui.cur_brush->color_no = ui.default_brushes[TOOL_PEN].color_no; ui.cur_brush->color_rgba = ui.default_brushes[TOOL_PEN].color_rgba; @@ -2207,7 +2217,7 @@ void on_toolsRuler_activate (GtkMenuItem *menuitem, gpointer user_data) { - gboolean active; + gboolean active, current; if (GTK_OBJECT_TYPE(menuitem) == GTK_TYPE_CHECK_MENU_ITEM) active = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM (menuitem)); @@ -2215,11 +2225,12 @@ on_toolsRuler_activate (GtkMenuItem *menuitem, active = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON (menuitem)); if (ui.cur_mapping != 0) return; - if (active == ui.ruler[0]) return; + current = (ui.toolno[0] == TOOL_PEN || ui.toolno[0] == TOOL_HIGHLIGHTER) && ui.cur_brush->ruler; + if (active == current) return; end_text(); reset_focus(); - if (active && (ui.toolno[0]!=TOOL_PEN && ui.toolno[0]!=TOOL_HIGHLIGHTER)) { + if (ui.toolno[0]!=TOOL_PEN && ui.toolno[0]!=TOOL_HIGHLIGHTER) { reset_selection(); ui.toolno[0] = TOOL_PEN; ui.cur_brush = &(ui.brushes[0][TOOL_PEN]); @@ -2229,7 +2240,45 @@ on_toolsRuler_activate (GtkMenuItem *menuitem, update_cursor(); } - ui.ruler[0] = active; + ui.cur_brush->ruler = active; + if (active) ui.cur_brush->recognizer = FALSE; + update_mapping_linkings(ui.toolno[0]); + update_ruler_indicator(); +} + + +void +on_toolsReco_activate (GtkMenuItem *menuitem, + gpointer user_data) +{ + gboolean active, current; + + if (GTK_OBJECT_TYPE(menuitem) == GTK_TYPE_CHECK_MENU_ITEM) + active = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM (menuitem)); + else + active = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON (menuitem)); + + if (ui.cur_mapping != 0) return; + current = (ui.toolno[0] == TOOL_PEN || ui.toolno[0] == TOOL_HIGHLIGHTER) && ui.cur_brush->recognizer; + if (active == current) return; + + end_text(); + reset_focus(); + if (ui.toolno[0]!=TOOL_PEN && ui.toolno[0]!=TOOL_HIGHLIGHTER) { + reset_selection(); + ui.toolno[0] = TOOL_PEN; + ui.cur_brush = &(ui.brushes[0][TOOL_PEN]); + update_color_menu(); + update_tool_buttons(); + update_tool_menu(); + update_cursor(); + } + + ui.cur_brush->recognizer = active; + if (active) { + ui.cur_brush->ruler = FALSE; + reset_recognizer(); + } update_mapping_linkings(ui.toolno[0]); update_ruler_indicator(); } @@ -2284,7 +2333,6 @@ on_buttonToolDefault_clicked (GtkToolButton *toolbutton, switch_mapping(0); if (ui.toolno[0] < NUM_STROKE_TOOLS) { g_memmove(&(ui.brushes[0][ui.toolno[0]]), ui.default_brushes+ui.toolno[0], sizeof(struct Brush)); - ui.ruler[0] = FALSE; update_mapping_linkings(ui.toolno[0]); update_thickness_buttons(); update_color_buttons(); @@ -2348,6 +2396,10 @@ on_canvas_button_press_event (GtkWidget *widget, gdk_device_get_state(event->device, event->window, event->axes, NULL); fix_xinput_coords((GdkEvent *)event); } +#ifdef INPUT_DEBUG + printf("DEBUG: ButtonDown (%s) (x,y)=(%.2f,%.2f)\n", + is_core?"core":"xinput", event->x, event->y); +#endif if (!finite(event->x) || !finite(event->y)) return FALSE; // Xorg 7.3 bug if (ui.cur_item_type == ITEM_TEXT && !is_event_within_textview(event)) @@ -2396,7 +2448,7 @@ on_canvas_button_press_event (GtkWidget *widget, gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); on_viewShowLayer_activate(NULL, NULL); - return; + return FALSE; } // switch mappings if needed @@ -2414,7 +2466,8 @@ on_canvas_button_press_event (GtkWidget *widget, } } - // if this can be a selection move, then it takes precedence over anything else + // if this can be a selection move or resize, then it takes precedence over anything else + if (start_resizesel((GdkEvent *)event)) return FALSE; if (start_movesel((GdkEvent *)event)) return FALSE; if (ui.toolno[mapping] != TOOL_SELECTREGION && ui.toolno[mapping] != TOOL_SELECTRECT) @@ -2468,6 +2521,7 @@ on_canvas_button_release_event (GtkWidget *widget, if (ui.cur_item_type == ITEM_STROKE) { finalize_stroke(); + if (ui.cur_brush->recognizer) recognize_patterns(); } else if (ui.cur_item_type == ITEM_ERASURE) { finalize_erasure(); @@ -2478,6 +2532,9 @@ on_canvas_button_release_event (GtkWidget *widget, else if (ui.cur_item_type == ITEM_MOVESEL || ui.cur_item_type == ITEM_MOVESEL_VERT) { finalize_movesel(); } + else if (ui.cur_item_type == ITEM_RESIZESEL) { + finalize_resizesel(); + } else if (ui.cur_item_type == ITEM_HAND) { ui.cur_item_type = ITEM_NONE; } @@ -2512,6 +2569,24 @@ on_canvas_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { + // If zoomed-out and in single page mode, switch pages with PgUp/PgDn. + if (!ui.view_continuous && + (0.96 * ui.zoom * ui.cur_page->height < + GTK_WIDGET(canvas)->allocation.height)) { + if (event->keyval == GDK_Page_Down) { + end_text(); + reset_focus(); + if (ui.pageno == journal.npages-1) { return FALSE; } + do_switch_page(ui.pageno+1, TRUE, FALSE); + } + if (event->keyval == GDK_Page_Up) { + end_text(); + reset_focus(); + if (ui.pageno == 0) { return FALSE; } + do_switch_page(ui.pageno-1, TRUE, FALSE); + } + } + return FALSE; } @@ -2523,21 +2598,37 @@ on_canvas_motion_notify_event (GtkWidget *widget, { gboolean looks_wrong, is_core; double pt[2]; - - if (ui.cur_item_type == ITEM_NONE) return FALSE; // we don't care + + /* we don't care about this event unless some operation is in progress; + or if there's a selection (then we might want to change the mouse + cursor to indicate the possibility of resizing) */ + if (ui.cur_item_type == ITEM_NONE && ui.selection==NULL) return FALSE; is_core = (event->device == gdk_device_get_core_pointer()); if (!ui.use_xinput && !is_core) return FALSE; - if (ui.use_xinput && is_core && !ui.is_corestroke) return FALSE; if (!is_core) fix_xinput_coords((GdkEvent *)event); if (!finite(event->x) || !finite(event->y)) return FALSE; // Xorg 7.3 bug + + if (ui.selection!=NULL && ui.cur_item_type == ITEM_NONE) { + get_pointer_coords((GdkEvent *)event, pt); + update_cursor_for_resize(pt); + return FALSE; + } + + if (ui.use_xinput && is_core && !ui.is_corestroke) return FALSE; if (!is_core) ui.is_corestroke = FALSE; +#ifdef INPUT_DEBUG + printf("DEBUG: MotionNotify (%s) (x,y)=(%.2f,%.2f)\n", + is_core?"core":"xinput", event->x, event->y); +#endif + looks_wrong = !(event->state & (1<<(7+ui.which_mouse_button))); if (looks_wrong) { /* mouse button shouldn't be up... give up */ if (ui.cur_item_type == ITEM_STROKE) { finalize_stroke(); + if (ui.cur_brush->recognizer) recognize_patterns(); } else if (ui.cur_item_type == ITEM_ERASURE) { finalize_erasure(); @@ -2548,6 +2639,9 @@ on_canvas_motion_notify_event (GtkWidget *widget, else if (ui.cur_item_type == ITEM_MOVESEL || ui.cur_item_type == ITEM_MOVESEL_VERT) { finalize_movesel(); } + else if (ui.cur_item_type == ITEM_RESIZESEL) { + finalize_resizesel(); + } switch_mapping(0); return FALSE; } @@ -2569,6 +2663,9 @@ on_canvas_motion_notify_event (GtkWidget *widget, else if (ui.cur_item_type == ITEM_MOVESEL || ui.cur_item_type == ITEM_MOVESEL_VERT) { continue_movesel((GdkEvent *)event); } + else if (ui.cur_item_type == ITEM_RESIZESEL) { + continue_resizesel((GdkEvent *)event); + } else if (ui.cur_item_type == ITEM_HAND) { do_hand((GdkEvent *)event); } @@ -3066,9 +3163,6 @@ on_button2CopyBrush_activate (GtkMenuItem *menuitem, } ui.linked_brush[1] = BRUSH_COPIED; g_memmove(&(ui.brushes[1][ui.toolno[1]]), &(ui.brushes[0][ui.toolno[1]]), sizeof(struct Brush)); - ui.ruler[1] = ui.ruler[0]; - if (ui.toolno[1]!=TOOL_PEN && ui.toolno[1]!=TOOL_HIGHLIGHTER) - ui.ruler[1] = FALSE; } @@ -3156,9 +3250,6 @@ on_button3CopyBrush_activate (GtkMenuItem *menuitem, } ui.linked_brush[2] = BRUSH_COPIED; g_memmove(&(ui.brushes[2][ui.toolno[2]]), &(ui.brushes[0][ui.toolno[2]]), sizeof(struct Brush)); - ui.ruler[2] = ui.ruler[0]; - if (ui.toolno[2]!=TOOL_PEN && ui.toolno[2]!=TOOL_HIGHLIGHTER) - ui.ruler[2] = FALSE; } // the set zoom dialog @@ -3288,7 +3379,6 @@ on_toolsHand_activate (GtkMenuItem *menuitem, reset_focus(); reset_selection(); ui.toolno[0] = TOOL_HAND; - ui.ruler[0] = FALSE; update_mapping_linkings(-1); update_tool_buttons(); update_tool_menu(); @@ -3398,3 +3488,17 @@ on_optionsAutoSavePrefs_activate (GtkMenuItem *menuitem, ui.auto_save_prefs = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM (menuitem)); } +void +on_optionsPressureSensitive_activate (GtkMenuItem *menuitem, + gpointer user_data) +{ + int i; + end_text(); + reset_focus(); + ui.pressure_sensitivity = + gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM (menuitem)); + for (i=0; i<=NUM_BUTTONS; i++) + ui.brushes[i][TOOL_PEN].variable_width = ui.pressure_sensitivity; + update_mappings_menu(); +} + diff --git a/src/xo-callbacks.h b/src/xo-callbacks.h index 73593f9..e42dc95 100644 --- a/src/xo-callbacks.h +++ b/src/xo-callbacks.h @@ -617,3 +617,10 @@ void on_optionsAutoSavePrefs_activate (GtkMenuItem *menuitem, gpointer user_data); +void +on_toolsReco_activate (GtkMenuItem *menuitem, + gpointer user_data); + +void +on_optionsPressureSensitive_activate (GtkMenuItem *menuitem, + gpointer user_data); diff --git a/src/xo-file.c b/src/xo-file.c index 97f3bcf..016fcd4 100644 --- a/src/xo-file.c +++ b/src/xo-file.c @@ -14,6 +14,8 @@ #include <gdk/gdkx.h> #include <X11/Xlib.h> #include <locale.h> +#include <glib.h> +#include <glib/gstdio.h> #include "xournal.h" #include "xo-interface.h" @@ -178,7 +180,11 @@ gboolean save_journal(const char *filename) gzputs(f, color_names[item->brush.color_no]); else gzprintf(f, "#%08x", item->brush.color_rgba); - gzprintf(f, "\" width=\"%.2f\">\n", item->brush.thickness); + gzprintf(f, "\" width=\"%.2f", item->brush.thickness); + if (item->brush.variable_width) + for (i=0;i<item->path->num_points-1;i++) + gzprintf(f, " %.2f", item->widths[i]); + gzprintf(f, "\">\n"); for (i=0;i<2*item->path->num_points;i++) gzprintf(f, "%.2f ", item->path->coords[i]); gzprintf(f, "\n</stroke>\n"); @@ -253,9 +259,10 @@ void xoj_parser_start_element(GMarkupParseContext *context, const gchar **attribute_values, gpointer user_data, GError **error) { int has_attr, i; - char *ptr; + char *ptr, *tmpptr; struct Background *tmpbg; char *tmpbg_filename; + gdouble val; GtkWidget *dialog; if (!strcmp(element_name, "title") || !strcmp(element_name, "xournal")) { @@ -443,6 +450,7 @@ void xoj_parser_start_element(GMarkupParseContext *context, tmpItem->type = ITEM_STROKE; tmpItem->path = NULL; tmpItem->canvas_item = NULL; + tmpItem->widths = NULL; tmpLayer->items = g_list_append(tmpLayer->items, tmpItem); tmpLayer->nitems++; // scan for tool, color, and width attributes @@ -453,6 +461,20 @@ void xoj_parser_start_element(GMarkupParseContext *context, cleanup_numeric((gchar *)*attribute_values); tmpItem->brush.thickness = g_ascii_strtod(*attribute_values, &ptr); if (ptr == *attribute_values) *error = xoj_invalid(); + i = 0; + while (*ptr!=0) { + realloc_cur_widths(i+1); + ui.cur_widths[i] = g_ascii_strtod(ptr, &tmpptr); + if (tmpptr == ptr) break; + ptr = tmpptr; + i++; + } + tmpItem->brush.variable_width = (i>0); + if (i>0) { + tmpItem->brush.variable_width = TRUE; + tmpItem->widths = (gdouble *) g_memdup(ui.cur_widths, i*sizeof(gdouble)); + ui.cur_path.num_points = i+1; + } has_attr |= 1; } else if (!strcmp(*attribute_names, "color")) { @@ -488,6 +510,8 @@ void xoj_parser_start_element(GMarkupParseContext *context, // finish filling the brush info tmpItem->brush.thickness_no = 0; // who cares ? tmpItem->brush.tool_options = 0; // who cares ? + tmpItem->brush.ruler = FALSE; + tmpItem->brush.recognizer = FALSE; if (tmpItem->brush.tool_type == TOOL_HIGHLIGHTER) { if (tmpItem->brush.color_no >= 0) tmpItem->brush.color_rgba &= ui.hiliter_alpha_mask; @@ -608,9 +632,15 @@ void xoj_parser_text(GMarkupParseContext *context, if (ptr == text) break; text_len -= (ptr - text); text = ptr; + if (!finite(ui.cur_path.coords[n])) { + if (n>=2) ui.cur_path.coords[n] = ui.cur_path.coords[n-2]; + else ui.cur_path.coords[n] = 0; + } n++; } - if (n<4 || n&1) { *error = xoj_invalid(); return; } + if (n<4 || n&1 || + (tmpItem->brush.variable_width && (n!=2*ui.cur_path.num_points))) + { *error = xoj_invalid(); return; } // wrong number of points tmpItem->path = gnome_canvas_points_new(n/2); g_memmove(tmpItem->path->coords, ui.cur_path.coords, n*sizeof(double)); } @@ -863,10 +893,8 @@ struct Background *attempt_screenshot_bg(void) struct Background *bg; GdkPixbuf *pix; XEvent x_event; - GError *error = NULL; GdkWindow *window; - int x,y,w,h, status; - unsigned int tmp; + int x,y,w,h; Window x_root, x_win; x_root = gdk_x11_get_default_root_xwindow(); @@ -1166,7 +1194,7 @@ gboolean init_bgpdf(char *pdfname, gboolean create_pages, int file_domain) // create page n, resize it, set its bg void bgpdf_create_page_with_bg(int pageno, struct BgPdfPage *bgpg) { - struct Page *pg; + struct Page *pg = NULL; struct Background *bg; if (journal.npages < pageno) { @@ -1348,6 +1376,9 @@ void init_config_default(void) ui.default_path = NULL; ui.default_font_name = g_strdup(DEFAULT_FONT); ui.default_font_size = DEFAULT_FONT_SIZE; + ui.pressure_sensitivity = FALSE; + ui.width_minimum_multiplier = 0.0; + ui.width_maximum_multiplier = 1.25; // the default UI vertical order ui.vertical_order[0][0] = 1; @@ -1361,10 +1392,8 @@ void init_config_default(void) ui.vertical_order[1][3] = ui.vertical_order[1][4] = -1; ui.toolno[0] = ui.startuptool = TOOL_PEN; - ui.ruler[0] = ui.startupruler = FALSE; for (i=1; i<=NUM_BUTTONS; i++) { ui.toolno[i] = TOOL_ERASER; - ui.ruler[i] = FALSE; } for (i=0; i<=NUM_BUTTONS; i++) ui.linked_brush[i] = BRUSH_LINKED; @@ -1374,6 +1403,9 @@ void init_config_default(void) for (i=0; i < NUM_STROKE_TOOLS; i++) { ui.brushes[0][i].thickness_no = THICKNESS_MEDIUM; ui.brushes[0][i].tool_options = 0; + ui.brushes[0][i].ruler = FALSE; + ui.brushes[0][i].recognizer = FALSE; + ui.brushes[0][i].variable_width = FALSE; } for (i=0; i< NUM_STROKE_TOOLS; i++) for (j=1; j<=NUM_BUTTONS; j++) @@ -1474,6 +1506,15 @@ void save_config_to_file(void) update_keyval("general", "default_path", " default path for open/save (leave blank for current directory)", g_strdup((ui.default_path!=NULL)?ui.default_path:"")); + update_keyval("general", "pressure_sensitivity", + " use pressure sensitivity to control pen stroke width (true/false)", + g_strdup(ui.pressure_sensitivity?"true":"false")); + update_keyval("general", "width_minimum_multiplier", + " minimum width multiplier", + g_strdup_printf("%.2f", ui.width_minimum_multiplier)); + update_keyval("general", "width_maximum_multiplier", + " maximum width multiplier", + g_strdup_printf("%.2f", ui.width_maximum_multiplier)); update_keyval("general", "interface_order", " interface components from top to bottom\n valid values: drawarea menu main_toolbar pen_toolbar statusbar", verbose_vertical_order(ui.vertical_order[0])); @@ -1533,15 +1574,18 @@ void save_config_to_file(void) update_keyval("tools", "startup_tool", " selected tool at startup (pen, eraser, highlighter, selectrect, vertspace, hand)", g_strdup(tool_names[ui.startuptool])); - update_keyval("tools", "startup_ruler", - " ruler mode at startup (true/false) (for pen or highlighter only)", - g_strdup(ui.startupruler?"true":"false")); update_keyval("tools", "pen_color", " default pen color", g_strdup(color_names[ui.default_brushes[TOOL_PEN].color_no])); update_keyval("tools", "pen_thickness", " default pen thickness (fine = 1, medium = 2, thick = 3)", g_strdup_printf("%d", ui.default_brushes[TOOL_PEN].thickness_no)); + update_keyval("tools", "pen_ruler", + " default pen is in ruler mode (true/false)", + g_strdup(ui.default_brushes[TOOL_PEN].ruler?"true":"false")); + update_keyval("tools", "pen_recognizer", + " default pen is in shape recognizer mode (true/false)", + g_strdup(ui.default_brushes[TOOL_PEN].recognizer?"true":"false")); update_keyval("tools", "eraser_thickness", " default eraser thickness (fine = 1, medium = 2, thick = 3)", g_strdup_printf("%d", ui.default_brushes[TOOL_ERASER].thickness_no)); @@ -1554,15 +1598,18 @@ void save_config_to_file(void) update_keyval("tools", "highlighter_thickness", " default highlighter thickness (fine = 1, medium = 2, thick = 3)", g_strdup_printf("%d", ui.default_brushes[TOOL_HIGHLIGHTER].thickness_no)); + update_keyval("tools", "highlighter_ruler", + " default highlighter is in ruler mode (true/false)", + g_strdup(ui.default_brushes[TOOL_HIGHLIGHTER].ruler?"true":"false")); + update_keyval("tools", "highlighter_recognizer", + " default highlighter is in shape recognizer mode (true/false)", + g_strdup(ui.default_brushes[TOOL_HIGHLIGHTER].recognizer?"true":"false")); update_keyval("tools", "btn2_tool", " button 2 tool (pen, eraser, highlighter, text, selectrect, vertspace, hand)", g_strdup(tool_names[ui.toolno[1]])); update_keyval("tools", "btn2_linked", " button 2 brush linked to primary brush (true/false) (overrides all other settings)", g_strdup((ui.linked_brush[1]==BRUSH_LINKED)?"true":"false")); - update_keyval("tools", "btn2_ruler", - " button 2 ruler mode (true/false) (for pen or highlighter only)", - g_strdup(ui.ruler[1]?"true":"false")); update_keyval("tools", "btn2_color", " button 2 brush color (for pen or highlighter only)", g_strdup((ui.toolno[1]<NUM_STROKE_TOOLS)? @@ -1571,6 +1618,14 @@ void save_config_to_file(void) " button 2 brush thickness (pen, eraser, or highlighter only)", g_strdup_printf("%d", (ui.toolno[1]<NUM_STROKE_TOOLS)? ui.brushes[1][ui.toolno[1]].thickness_no:0)); + update_keyval("tools", "btn2_ruler", + " button 2 ruler mode (true/false) (for pen or highlighter only)", + g_strdup(((ui.toolno[1]<NUM_STROKE_TOOLS)? + ui.brushes[1][ui.toolno[1]].ruler:FALSE)?"true":"false")); + update_keyval("tools", "btn2_recognizer", + " button 2 shape recognizer mode (true/false) (pen or highlighter only)", + g_strdup(((ui.toolno[1]<NUM_STROKE_TOOLS)? + ui.brushes[1][ui.toolno[1]].recognizer:FALSE)?"true":"false")); update_keyval("tools", "btn2_erasermode", " button 2 eraser mode (eraser only)", g_strdup_printf("%d", ui.brushes[1][TOOL_ERASER].tool_options)); @@ -1580,9 +1635,6 @@ void save_config_to_file(void) update_keyval("tools", "btn3_linked", " button 3 brush linked to primary brush (true/false) (overrides all other settings)", g_strdup((ui.linked_brush[2]==BRUSH_LINKED)?"true":"false")); - update_keyval("tools", "btn3_ruler", - " button 3 ruler mode (true/false) (for pen or highlighter only)", - g_strdup(ui.ruler[2]?"true":"false")); update_keyval("tools", "btn3_color", " button 3 brush color (for pen or highlighter only)", g_strdup((ui.toolno[2]<NUM_STROKE_TOOLS)? @@ -1591,6 +1643,14 @@ void save_config_to_file(void) " button 3 brush thickness (pen, eraser, or highlighter only)", g_strdup_printf("%d", (ui.toolno[2]<NUM_STROKE_TOOLS)? ui.brushes[2][ui.toolno[2]].thickness_no:0)); + update_keyval("tools", "btn3_ruler", + " button 3 ruler mode (true/false) (for pen or highlighter only)", + g_strdup(((ui.toolno[2]<NUM_STROKE_TOOLS)? + ui.brushes[2][ui.toolno[2]].ruler:FALSE)?"true":"false")); + update_keyval("tools", "btn3_recognizer", + " button 3 shape recognizer mode (true/false) (pen or highlighter only)", + g_strdup(((ui.toolno[2]<NUM_STROKE_TOOLS)? + ui.brushes[2][ui.toolno[2]].recognizer:FALSE)?"true":"false")); update_keyval("tools", "btn3_erasermode", " button 3 eraser mode (eraser only)", g_strdup_printf("%d", ui.brushes[2][TOOL_ERASER].tool_options)); @@ -1727,7 +1787,7 @@ gboolean parse_keyval_vorderlist(const gchar *group, const gchar *key, int *orde { gchar *ret, *p; int tmp[VBOX_MAIN_NITEMS]; - int i, n, found, l; + int i, n, l; ret = g_key_file_get_value(ui.config_data, group, key, NULL); if (ret==NULL) return FALSE; @@ -1795,6 +1855,10 @@ void load_config_from_file(void) parse_keyval_boolean("general", "discard_corepointer", &ui.discard_corepointer); parse_keyval_boolean("general", "use_erasertip", &ui.use_erasertip); parse_keyval_string("general", "default_path", &ui.default_path); + parse_keyval_boolean("general", "pressure_sensitivity", &ui.pressure_sensitivity); + parse_keyval_float("general", "width_minimum_multiplier", &ui.width_minimum_multiplier, 0., 10.); + parse_keyval_float("general", "width_maximum_multiplier", &ui.width_maximum_multiplier, 0., 10.); + parse_keyval_vorderlist("general", "interface_order", ui.vertical_order[0]); parse_keyval_vorderlist("general", "interface_fullscreen", ui.vertical_order[1]); parse_keyval_boolean("general", "interface_lefthanded", &ui.left_handed); @@ -1819,16 +1883,17 @@ void load_config_from_file(void) parse_keyval_enum("tools", "startup_tool", &ui.startuptool, tool_names, NUM_TOOLS); ui.toolno[0] = ui.startuptool; - if (ui.startuptool == TOOL_PEN || ui.startuptool == TOOL_HIGHLIGHTER) { - parse_keyval_boolean("tools", "startup_ruler", &ui.startupruler); - ui.ruler[0] = ui.startupruler; - } parse_keyval_enum("tools", "pen_color", &(ui.brushes[0][TOOL_PEN].color_no), color_names, COLOR_MAX); parse_keyval_int("tools", "pen_thickness", &(ui.brushes[0][TOOL_PEN].thickness_no), 0, 4); + parse_keyval_boolean("tools", "pen_ruler", &(ui.brushes[0][TOOL_PEN].ruler)); + parse_keyval_boolean("tools", "pen_recognizer", &(ui.brushes[0][TOOL_PEN].recognizer)); parse_keyval_int("tools", "eraser_thickness", &(ui.brushes[0][TOOL_ERASER].thickness_no), 1, 3); parse_keyval_int("tools", "eraser_mode", &(ui.brushes[0][TOOL_ERASER].tool_options), 0, 2); parse_keyval_enum("tools", "highlighter_color", &(ui.brushes[0][TOOL_HIGHLIGHTER].color_no), color_names, COLOR_MAX); parse_keyval_int("tools", "highlighter_thickness", &(ui.brushes[0][TOOL_HIGHLIGHTER].thickness_no), 0, 4); + parse_keyval_boolean("tools", "highlighter_ruler", &(ui.brushes[0][TOOL_HIGHLIGHTER].ruler)); + parse_keyval_boolean("tools", "highlighter_recognizer", &(ui.brushes[0][TOOL_HIGHLIGHTER].recognizer)); + ui.brushes[0][TOOL_PEN].variable_width = ui.pressure_sensitivity; for (i=0; i< NUM_STROKE_TOOLS; i++) for (j=1; j<=NUM_BUTTONS; j++) g_memmove(&(ui.brushes[j][i]), &(ui.brushes[0][i]), sizeof(struct Brush)); @@ -1839,12 +1904,10 @@ void load_config_from_file(void) parse_keyval_enum("tools", "btn3_tool", &(ui.toolno[2]), tool_names, NUM_TOOLS); if (parse_keyval_boolean("tools", "btn3_linked", &b)) ui.linked_brush[2] = b?BRUSH_LINKED:BRUSH_STATIC; - for (i=1; i<=NUM_BUTTONS; i++) - if (ui.toolno[i]==TOOL_PEN || ui.toolno[i]==TOOL_HIGHLIGHTER) - ui.ruler[i] = ui.ruler[0]; if (ui.linked_brush[1]!=BRUSH_LINKED) { if (ui.toolno[1]==TOOL_PEN || ui.toolno[1]==TOOL_HIGHLIGHTER) { - parse_keyval_boolean("tools", "btn2_ruler", &(ui.ruler[1])); + parse_keyval_boolean("tools", "btn2_ruler", &(ui.brushes[1][ui.toolno[1]].ruler)); + parse_keyval_boolean("tools", "btn2_recognizer", &(ui.brushes[1][ui.toolno[1]].recognizer)); parse_keyval_enum("tools", "btn2_color", &(ui.brushes[1][ui.toolno[1]].color_no), color_names, COLOR_MAX); } if (ui.toolno[1]<NUM_STROKE_TOOLS) @@ -1854,7 +1917,8 @@ void load_config_from_file(void) } if (ui.linked_brush[2]!=BRUSH_LINKED) { if (ui.toolno[2]==TOOL_PEN || ui.toolno[2]==TOOL_HIGHLIGHTER) { - parse_keyval_boolean("tools", "btn3_ruler", &(ui.ruler[2])); + parse_keyval_boolean("tools", "btn3_ruler", &(ui.brushes[2][ui.toolno[2]].ruler)); + parse_keyval_boolean("tools", "btn3_recognizer", &(ui.brushes[2][ui.toolno[2]].recognizer)); parse_keyval_enum("tools", "btn3_color", &(ui.brushes[2][ui.toolno[2]].color_no), color_names, COLOR_MAX); } if (ui.toolno[2]<NUM_STROKE_TOOLS) diff --git a/src/xo-file.h b/src/xo-file.h index 0cc2e2e..5fcf6e8 100644 --- a/src/xo-file.h +++ b/src/xo-file.h @@ -27,6 +27,7 @@ void add_bgpdf_request(int pageno, double zoom, gboolean printing); void bgpdf_spawn_child(void); void shutdown_bgpdf(void); gboolean init_bgpdf(char *pdfname, gboolean create_pages, int file_domain); +void end_bgpdf_shutdown(void); void bgpdf_create_page_with_bg(int pageno, struct BgPdfPage *bgpg); void bgpdf_update_bg(int pageno, struct BgPdfPage *bgpg); diff --git a/src/xo-interface.c b/src/xo-interface.c index b84a09a..f10b134 100644 --- a/src/xo-interface.c +++ b/src/xo-interface.c @@ -143,6 +143,9 @@ create_winMain (void) GtkWidget *toolsEraser; GtkWidget *toolsHighlighter; GtkWidget *toolsText; + GtkWidget *separator15; + GtkWidget *toolsReco; + GtkWidget *toolsRuler; GtkWidget *separator9; GtkWidget *toolsSelectRegion; GtkWidget *toolsSelectRectangle; @@ -200,13 +203,12 @@ create_winMain (void) GtkWidget *toolsDefaultHighlighter; GtkWidget *toolsDefaultText; GtkWidget *toolsSetAsDefault; - GtkWidget *separator15; - GtkWidget *toolsRuler; GtkWidget *menuOptions; GtkWidget *menuOptions_menu; GtkWidget *optionsUseXInput; GtkWidget *optionsDiscardCoreEvents; GtkWidget *optionsButtonMappings; + GtkWidget *optionsPressureSensitive; GtkWidget *button2_mapping; GtkWidget *button2_mapping_menu; GSList *button2Pen_group = NULL; @@ -287,6 +289,7 @@ create_winMain (void) GtkWidget *buttonEraser; GtkWidget *buttonHighlighter; GtkWidget *buttonText; + GtkWidget *buttonReco; GtkWidget *buttonRuler; GtkWidget *toolitem15; GtkWidget *vseparator5; @@ -873,6 +876,25 @@ create_winMain (void) GTK_ACCEL_VISIBLE); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (toolsText), TRUE); + separator15 = gtk_separator_menu_item_new (); + gtk_widget_show (separator15); + gtk_container_add (GTK_CONTAINER (menuTools_menu), separator15); + gtk_widget_set_sensitive (separator15, FALSE); + + toolsReco = gtk_check_menu_item_new_with_mnemonic ("_Shape Recognizer"); + gtk_widget_show (toolsReco); + gtk_container_add (GTK_CONTAINER (menuTools_menu), toolsReco); + gtk_widget_add_accelerator (toolsReco, "activate", accel_group, + GDK_S, (GdkModifierType) GDK_CONTROL_MASK | GDK_SHIFT_MASK, + GTK_ACCEL_VISIBLE); + + toolsRuler = gtk_check_menu_item_new_with_mnemonic ("Ru_ler"); + gtk_widget_show (toolsRuler); + gtk_container_add (GTK_CONTAINER (menuTools_menu), toolsRuler); + gtk_widget_add_accelerator (toolsRuler, "activate", accel_group, + GDK_L, (GdkModifierType) GDK_CONTROL_MASK | GDK_SHIFT_MASK, + GTK_ACCEL_VISIBLE); + separator9 = gtk_separator_menu_item_new (); gtk_widget_show (separator9); gtk_container_add (GTK_CONTAINER (menuTools_menu), separator9); @@ -1153,22 +1175,10 @@ create_winMain (void) gtk_widget_show (toolsDefaultText); gtk_container_add (GTK_CONTAINER (menuTools_menu), toolsDefaultText); - toolsSetAsDefault = gtk_menu_item_new_with_mnemonic ("_Set As Default"); + toolsSetAsDefault = gtk_menu_item_new_with_mnemonic ("Set As Default"); gtk_widget_show (toolsSetAsDefault); gtk_container_add (GTK_CONTAINER (menuTools_menu), toolsSetAsDefault); - separator15 = gtk_separator_menu_item_new (); - gtk_widget_show (separator15); - gtk_container_add (GTK_CONTAINER (menuTools_menu), separator15); - gtk_widget_set_sensitive (separator15, FALSE); - - toolsRuler = gtk_check_menu_item_new_with_mnemonic ("Ru_ler"); - gtk_widget_show (toolsRuler); - gtk_container_add (GTK_CONTAINER (menuTools_menu), toolsRuler); - gtk_widget_add_accelerator (toolsRuler, "activate", accel_group, - GDK_L, (GdkModifierType) GDK_CONTROL_MASK | GDK_SHIFT_MASK, - GTK_ACCEL_VISIBLE); - menuOptions = gtk_menu_item_new_with_mnemonic ("_Options"); gtk_widget_show (menuOptions); gtk_container_add (GTK_CONTAINER (menubar), menuOptions); @@ -1188,6 +1198,10 @@ create_winMain (void) gtk_widget_show (optionsButtonMappings); gtk_container_add (GTK_CONTAINER (menuOptions_menu), optionsButtonMappings); + optionsPressureSensitive = gtk_check_menu_item_new_with_mnemonic ("_Pressure sensitivity"); + gtk_widget_show (optionsPressureSensitive); + gtk_container_add (GTK_CONTAINER (menuOptions_menu), optionsPressureSensitive); + button2_mapping = gtk_menu_item_new_with_mnemonic ("Button _2 Mapping"); gtk_widget_show (button2_mapping); gtk_container_add (GTK_CONTAINER (menuOptions_menu), button2_mapping); @@ -1576,6 +1590,15 @@ create_winMain (void) gtk_radio_tool_button_set_group (GTK_RADIO_TOOL_BUTTON (buttonText), buttonPen_group); buttonPen_group = gtk_radio_tool_button_get_group (GTK_RADIO_TOOL_BUTTON (buttonText)); + buttonReco = (GtkWidget*) gtk_toggle_tool_button_new (); + gtk_tool_button_set_label (GTK_TOOL_BUTTON (buttonReco), "Shape Recognizer"); + tmp_image = create_pixmap (winMain, "shapes.png"); + gtk_widget_show (tmp_image); + gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (buttonReco), tmp_image); + gtk_widget_show (buttonReco); + gtk_container_add (GTK_CONTAINER (toolbarPen), buttonReco); + gtk_tool_item_set_tooltip (GTK_TOOL_ITEM (buttonReco), tooltips, "Shape Recognizer", NULL); + buttonRuler = (GtkWidget*) gtk_toggle_tool_button_new (); gtk_tool_button_set_label (GTK_TOOL_BUTTON (buttonRuler), "Ruler"); tmp_image = create_pixmap (winMain, "ruler.png"); @@ -2115,6 +2138,12 @@ create_winMain (void) g_signal_connect ((gpointer) toolsText, "toggled", G_CALLBACK (on_toolsText_activate), NULL); + g_signal_connect ((gpointer) toolsReco, "toggled", + G_CALLBACK (on_toolsReco_activate), + NULL); + g_signal_connect ((gpointer) toolsRuler, "toggled", + G_CALLBACK (on_toolsRuler_activate), + NULL); g_signal_connect ((gpointer) toolsSelectRegion, "toggled", G_CALLBACK (on_toolsSelectRegion_activate), NULL); @@ -2223,9 +2252,6 @@ create_winMain (void) g_signal_connect ((gpointer) toolsSetAsDefault, "activate", G_CALLBACK (on_toolsSetAsDefault_activate), NULL); - g_signal_connect ((gpointer) toolsRuler, "toggled", - G_CALLBACK (on_toolsRuler_activate), - NULL); g_signal_connect ((gpointer) optionsUseXInput, "toggled", G_CALLBACK (on_optionsUseXInput_activate), NULL); @@ -2235,6 +2261,9 @@ create_winMain (void) g_signal_connect ((gpointer) optionsButtonMappings, "activate", G_CALLBACK (on_optionsButtonMappings_activate), NULL); + g_signal_connect ((gpointer) optionsPressureSensitive, "activate", + G_CALLBACK (on_optionsPressureSensitive_activate), + NULL); g_signal_connect ((gpointer) button2Pen, "activate", G_CALLBACK (on_button2Pen_activate), NULL); @@ -2388,6 +2417,9 @@ create_winMain (void) g_signal_connect ((gpointer) buttonText, "toggled", G_CALLBACK (on_toolsText_activate), NULL); + g_signal_connect ((gpointer) buttonReco, "toggled", + G_CALLBACK (on_toolsReco_activate), + NULL); g_signal_connect ((gpointer) buttonRuler, "toggled", G_CALLBACK (on_toolsRuler_activate), NULL); @@ -2572,6 +2604,9 @@ create_winMain (void) GLADE_HOOKUP_OBJECT (winMain, toolsEraser, "toolsEraser"); GLADE_HOOKUP_OBJECT (winMain, toolsHighlighter, "toolsHighlighter"); GLADE_HOOKUP_OBJECT (winMain, toolsText, "toolsText"); + GLADE_HOOKUP_OBJECT (winMain, separator15, "separator15"); + GLADE_HOOKUP_OBJECT (winMain, toolsReco, "toolsReco"); + GLADE_HOOKUP_OBJECT (winMain, toolsRuler, "toolsRuler"); GLADE_HOOKUP_OBJECT (winMain, separator9, "separator9"); GLADE_HOOKUP_OBJECT (winMain, toolsSelectRegion, "toolsSelectRegion"); GLADE_HOOKUP_OBJECT (winMain, toolsSelectRectangle, "toolsSelectRectangle"); @@ -2624,13 +2659,12 @@ create_winMain (void) GLADE_HOOKUP_OBJECT (winMain, toolsDefaultHighlighter, "toolsDefaultHighlighter"); GLADE_HOOKUP_OBJECT (winMain, toolsDefaultText, "toolsDefaultText"); GLADE_HOOKUP_OBJECT (winMain, toolsSetAsDefault, "toolsSetAsDefault"); - GLADE_HOOKUP_OBJECT (winMain, separator15, "separator15"); - GLADE_HOOKUP_OBJECT (winMain, toolsRuler, "toolsRuler"); GLADE_HOOKUP_OBJECT (winMain, menuOptions, "menuOptions"); GLADE_HOOKUP_OBJECT (winMain, menuOptions_menu, "menuOptions_menu"); GLADE_HOOKUP_OBJECT (winMain, optionsUseXInput, "optionsUseXInput"); GLADE_HOOKUP_OBJECT (winMain, optionsDiscardCoreEvents, "optionsDiscardCoreEvents"); GLADE_HOOKUP_OBJECT (winMain, optionsButtonMappings, "optionsButtonMappings"); + GLADE_HOOKUP_OBJECT (winMain, optionsPressureSensitive, "optionsPressureSensitive"); GLADE_HOOKUP_OBJECT (winMain, button2_mapping, "button2_mapping"); GLADE_HOOKUP_OBJECT (winMain, button2_mapping_menu, "button2_mapping_menu"); GLADE_HOOKUP_OBJECT (winMain, button2Pen, "button2Pen"); @@ -2704,6 +2738,7 @@ create_winMain (void) GLADE_HOOKUP_OBJECT (winMain, buttonEraser, "buttonEraser"); GLADE_HOOKUP_OBJECT (winMain, buttonHighlighter, "buttonHighlighter"); GLADE_HOOKUP_OBJECT (winMain, buttonText, "buttonText"); + GLADE_HOOKUP_OBJECT (winMain, buttonReco, "buttonReco"); GLADE_HOOKUP_OBJECT (winMain, buttonRuler, "buttonRuler"); GLADE_HOOKUP_OBJECT (winMain, toolitem15, "toolitem15"); GLADE_HOOKUP_OBJECT (winMain, vseparator5, "vseparator5"); diff --git a/src/xo-misc.c b/src/xo-misc.c index 7b170bb..410797c 100644 --- a/src/xo-misc.c +++ b/src/xo-misc.c @@ -15,6 +15,7 @@ #include "xo-misc.h" #include "xo-file.h" #include "xo-paint.h" +#include "xo-shapes.h" // some global constants @@ -95,6 +96,13 @@ void realloc_cur_path(int n) ui.cur_path.coords = g_realloc(ui.cur_path.coords, 2*(n+10)*sizeof(double)); } +void realloc_cur_widths(int n) +{ + if (n <= ui.cur_widths_storage_alloc) return; + ui.cur_widths_storage_alloc = n+10; + ui.cur_widths = g_realloc(ui.cur_widths, (n+10)*sizeof(double)); +} + // undo utility functions void prepare_new_undo(void) @@ -124,6 +132,7 @@ void clear_redo_stack(void) while (redo!=NULL) { if (redo->type == ITEM_STROKE) { gnome_canvas_points_free(redo->item->path); + if (redo->item->brush.variable_width) g_free(redo->item->widths); g_free(redo->item); /* the strokes are unmapped, so there are no associated canvas items */ } @@ -132,12 +141,13 @@ void clear_redo_stack(void) g_free(redo->item->font_name); g_free(redo->item); } - else if (redo->type == ITEM_ERASURE) { + else if (redo->type == ITEM_ERASURE || redo->type == ITEM_RECOGNIZER) { for (list = redo->erasurelist; list!=NULL; list=list->next) { erasure = (struct UndoErasureData *)list->data; for (repl = erasure->replacement_items; repl!=NULL; repl=repl->next) { it = (struct Item *)repl->data; gnome_canvas_points_free(it->path); + if (it->brush.variable_width) g_free(it->widths); g_free(it); } g_list_free(erasure->replacement_items); @@ -160,10 +170,16 @@ void clear_redo_stack(void) else if (redo->type == ITEM_MOVESEL || redo->type == ITEM_REPAINTSEL) { g_list_free(redo->itemlist); g_list_free(redo->auxlist); } + else if (redo->type == ITEM_RESIZESEL) { + g_list_free(redo->itemlist); + } else if (redo->type == ITEM_PASTE) { for (list = redo->itemlist; list!=NULL; list=list->next) { it = (struct Item *)list->data; - if (it->type == ITEM_STROKE) gnome_canvas_points_free(it->path); + if (it->type == ITEM_STROKE) { + gnome_canvas_points_free(it->path); + if (it->brush.variable_width) g_free(it->widths); + } g_free(it); } g_list_free(redo->itemlist); @@ -192,11 +208,13 @@ void clear_undo_stack(void) while (undo!=NULL) { // for strokes, items are already in the journal, so we don't free them // for erasures, we need to free the dead items - if (undo->type == ITEM_ERASURE) { + if (undo->type == ITEM_ERASURE || undo->type == ITEM_RECOGNIZER) { for (list = undo->erasurelist; list!=NULL; list=list->next) { erasure = (struct UndoErasureData *)list->data; - if (erasure->item->type == ITEM_STROKE) + if (erasure->item->type == ITEM_STROKE) { gnome_canvas_points_free(erasure->item->path); + if (erasure->item->brush.variable_width) g_free(erasure->item->widths); + } if (erasure->item->type == ITEM_TEXT) { g_free(erasure->item->text); g_free(erasure->item->font_name); } g_free(erasure->item); @@ -216,6 +234,9 @@ void clear_undo_stack(void) else if (undo->type == ITEM_MOVESEL || undo->type == ITEM_REPAINTSEL) { g_list_free(undo->itemlist); g_list_free(undo->auxlist); } + else if (undo->type == ITEM_RESIZESEL) { + g_list_free(undo->itemlist); + } else if (undo->type == ITEM_PASTE) { g_list_free(undo->itemlist); } @@ -363,6 +384,18 @@ void fix_xinput_coords(GdkEvent *event) #endif } +double get_pressure_multiplier(GdkEvent *event) +{ + double rawpressure; + + if (event->button.device == gdk_device_get_core_pointer() + || event->button.device->num_axes <= 2) return 1.0; + + rawpressure = event->button.axes[2]/(event->button.device->axes[2].max - event->button.device->axes[2].min); + + return ((1-rawpressure)*ui.width_minimum_multiplier + rawpressure*ui.width_maximum_multiplier); +} + void update_item_bbox(struct Item *item) { int i; @@ -403,15 +436,32 @@ void make_page_clipbox(struct Page *pg) void make_canvas_item_one(GnomeCanvasGroup *group, struct Item *item) { - GnomeCanvasItem *i; PangoFontDescription *font_desc; + GnomeCanvasPoints points; + int j; - if (item->type == ITEM_STROKE) - item->canvas_item = gnome_canvas_item_new(group, - gnome_canvas_line_get_type(), "points", item->path, - "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND, - "fill-color-rgba", item->brush.color_rgba, - "width-units", item->brush.thickness, NULL); + if (item->type == ITEM_STROKE) { + if (!item->brush.variable_width) + item->canvas_item = gnome_canvas_item_new(group, + gnome_canvas_line_get_type(), "points", item->path, + "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND, + "fill-color-rgba", item->brush.color_rgba, + "width-units", item->brush.thickness, NULL); + else { + item->canvas_item = gnome_canvas_item_new(group, + gnome_canvas_group_get_type(), NULL); + points.num_points = 2; + points.ref_count = 1; + for (j = 0; j < item->path->num_points-1; j++) { + points.coords = item->path->coords+2*j; + gnome_canvas_item_new((GnomeCanvasGroup *) item->canvas_item, + gnome_canvas_line_get_type(), "points", &points, + "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND, + "fill-color-rgba", item->brush.color_rgba, + "width-units", item->widths[j], NULL); + } + } + } if (item->type == ITEM_TEXT) { font_desc = pango_font_description_from_string(item->font_name); pango_font_description_set_absolute_size(font_desc, @@ -765,7 +815,12 @@ void update_tool_buttons(void) } gtk_toggle_tool_button_set_active( - GTK_TOGGLE_TOOL_BUTTON(GET_COMPONENT("buttonRuler")), ui.ruler[ui.cur_mapping]); + GTK_TOGGLE_TOOL_BUTTON(GET_COMPONENT("buttonRuler")), + ui.toolno[ui.cur_mapping]<NUM_STROKE_TOOLS && ui.cur_brush->ruler); + gtk_toggle_tool_button_set_active( + GTK_TOGGLE_TOOL_BUTTON(GET_COMPONENT("buttonReco")), + ui.toolno[ui.cur_mapping]<NUM_STROKE_TOOLS && ui.cur_brush->recognizer); + update_thickness_buttons(); update_color_buttons(); } @@ -808,15 +863,27 @@ void update_tool_menu(void) } gtk_check_menu_item_set_active( - GTK_CHECK_MENU_ITEM(GET_COMPONENT("toolsRuler")), ui.ruler[0]); + GTK_CHECK_MENU_ITEM(GET_COMPONENT("toolsRuler")), + ui.toolno[0]<NUM_STROKE_TOOLS && ui.brushes[0][ui.toolno[0]].ruler); + gtk_check_menu_item_set_active( + GTK_CHECK_MENU_ITEM(GET_COMPONENT("toolsReco")), + ui.toolno[0]<NUM_STROKE_TOOLS && ui.brushes[0][ui.toolno[0]].recognizer); } void update_ruler_indicator(void) { gtk_toggle_tool_button_set_active( - GTK_TOGGLE_TOOL_BUTTON(GET_COMPONENT("buttonRuler")), ui.ruler[ui.cur_mapping]); + GTK_TOGGLE_TOOL_BUTTON(GET_COMPONENT("buttonRuler")), + ui.toolno[ui.cur_mapping]<NUM_STROKE_TOOLS && ui.cur_brush->ruler); + gtk_toggle_tool_button_set_active( + GTK_TOGGLE_TOOL_BUTTON(GET_COMPONENT("buttonReco")), + ui.toolno[ui.cur_mapping]<NUM_STROKE_TOOLS && ui.cur_brush->recognizer); + gtk_check_menu_item_set_active( + GTK_CHECK_MENU_ITEM(GET_COMPONENT("toolsRuler")), + ui.toolno[0]<NUM_STROKE_TOOLS && ui.brushes[0][ui.toolno[0]].ruler); gtk_check_menu_item_set_active( - GTK_CHECK_MENU_ITEM(GET_COMPONENT("toolsRuler")), ui.ruler[0]); + GTK_CHECK_MENU_ITEM(GET_COMPONENT("toolsReco")), + ui.toolno[0]<NUM_STROKE_TOOLS && ui.brushes[0][ui.toolno[0]].recognizer); } void update_color_menu(void) @@ -985,10 +1052,13 @@ void update_mappings_menu(void) { gtk_widget_set_sensitive(GET_COMPONENT("optionsButtonMappings"), ui.use_xinput); gtk_widget_set_sensitive(GET_COMPONENT("optionsDiscardCoreEvents"), ui.use_xinput); + gtk_widget_set_sensitive(GET_COMPONENT("optionsPressureSensitive"), ui.use_xinput); gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(GET_COMPONENT("optionsButtonMappings")), ui.use_erasertip); gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(GET_COMPONENT("optionsDiscardCoreEvents")), ui.discard_corepointer); + gtk_check_menu_item_set_active( + GTK_CHECK_MENU_ITEM(GET_COMPONENT("optionsPressureSensitive")), ui.pressure_sensitivity); switch(ui.toolno[1]) { case TOOL_PEN: @@ -1099,8 +1169,6 @@ void update_page_stuff(void) GtkSpinButton *spin; struct Page *pg; double vertpos, maxwidth; - struct Layer *layer; - int relscroll; // move the page groups to their rightful locations or hide them if (ui.view_continuous) { @@ -1308,9 +1376,6 @@ void update_mapping_linkings(int toolno) if (ui.linked_brush[i] == BRUSH_LINKED) { if (toolno >= 0 && toolno < NUM_STROKE_TOOLS) g_memmove(&(ui.brushes[i][toolno]), &(ui.brushes[0][toolno]), sizeof(struct Brush)); - ui.ruler[i] = ui.ruler[0]; - if (ui.toolno[i]!=TOOL_PEN && ui.toolno[i]!=TOOL_HIGHLIGHTER) - ui.ruler[i] = FALSE; } if (ui.linked_brush[i] == BRUSH_COPIED && toolno == ui.toolno[i]) { ui.linked_brush[i] = BRUSH_STATIC; @@ -1509,7 +1574,6 @@ gboolean ok_to_close(void) { GtkWidget *dialog; GtkResponseType response; - GList *pagelist; if (ui.saved) return TRUE; dialog = gtk_message_dialog_new(GTK_WINDOW (winMain), GTK_DIALOG_DESTROY_WITH_PARENT, @@ -1540,6 +1604,7 @@ void reset_focus(void) gtk_widget_grab_focus(ui.cur_item->widget); else gtk_widget_grab_focus(GTK_WIDGET(canvas)); + reset_recognizer(); } // selection / clipboard stuff @@ -1607,6 +1672,64 @@ void move_journal_items_by(GList *itemlist, double dx, double dy, } } +void resize_journal_items_by(GList *itemlist, double scaling_x, double scaling_y, + double offset_x, double offset_y) +{ + struct Item *item; + GList *list; + double mean_scaling, temp; + double *pt, *wid; + GnomeCanvasGroup *group; + int i; + + /* geometric mean of x and y scalings = rescaling for stroke widths + and for text font sizes */ + mean_scaling = sqrt(fabs(scaling_x * scaling_y)); + + for (list = itemlist; list != NULL; list = list->next) { + item = (struct Item *)list->data; + if (item->type == ITEM_STROKE) { + item->brush.thickness = item->brush.thickness * mean_scaling; + for (i=0, pt=item->path->coords; i<item->path->num_points; i++, pt+=2) { + pt[0] = pt[0]*scaling_x + offset_x; + pt[1] = pt[1]*scaling_y + offset_y; + } + if (item->brush.variable_width) + for (i=0, wid=item->widths; i<item->path->num_points-1; i++, wid++) + *wid = *wid * mean_scaling; + + item->bbox.left = item->bbox.left*scaling_x + offset_x; + item->bbox.right = item->bbox.right*scaling_x + offset_x; + item->bbox.top = item->bbox.top*scaling_y + offset_y; + item->bbox.bottom = item->bbox.bottom*scaling_y + offset_y; + if (item->bbox.left > item->bbox.right) { + temp = item->bbox.left; + item->bbox.left = item->bbox.right; + item->bbox.right = temp; + } + if (item->bbox.top > item->bbox.bottom) { + temp = item->bbox.top; + item->bbox.top = item->bbox.bottom; + item->bbox.bottom = temp; + } + } + if (item->type == ITEM_TEXT) { + /* must scale about NW corner -- all other points of the text box + are font- and zoom-dependent, so scaling about center of text box + couldn't be undone properly. FIXME? */ + item->font_size *= mean_scaling; + item->bbox.left = item->bbox.left*scaling_x + offset_x; + item->bbox.top = item->bbox.top*scaling_y + offset_y; + } + // redraw the item + if (item->canvas_item!=NULL) { + group = (GnomeCanvasGroup *) item->canvas_item->parent; + gtk_object_destroy(GTK_OBJECT(item->canvas_item)); + make_canvas_item_one(group, item); + } + } +} + // Switch between button mappings /* NOTE ABOUT BUTTON MAPPINGS: ui.cur_mapping is 0 except while a canvas @@ -1635,10 +1758,6 @@ void process_mapping_activate(GtkMenuItem *menuitem, int m, int tool) reset_focus(); ui.toolno[m] = tool; - ui.ruler[m] = FALSE; - if (ui.linked_brush[m] == BRUSH_LINKED - && (tool==TOOL_PEN || tool==TOOL_HIGHLIGHTER)) - ui.ruler[m] = ui.ruler[0]; if (ui.linked_brush[m] == BRUSH_COPIED) { ui.linked_brush[m] = BRUSH_STATIC; update_mappings_menu_linkings(); diff --git a/src/xo-misc.h b/src/xo-misc.h index 3680aa0..4ade904 100644 --- a/src/xo-misc.h +++ b/src/xo-misc.h @@ -3,6 +3,7 @@ struct Page *new_page(struct Page *template); struct Page *new_page_with_bg(struct Background *bg, double width, double height); void realloc_cur_path(int n); +void realloc_cur_widths(int n); void clear_redo_stack(void); void clear_undo_stack(void); void prepare_new_undo(void); @@ -19,6 +20,7 @@ void refstring_unref(struct Refstring *rs); // helper functions void get_pointer_coords(GdkEvent *event, double *ret); +double get_pressure_multiplier(GdkEvent *event); void fix_xinput_coords(GdkEvent *event); void update_item_bbox(struct Item *item); void make_page_clipbox(struct Page *pg); @@ -75,6 +77,9 @@ void reset_focus(void); void reset_selection(void); void move_journal_items_by(GList *itemlist, double dx, double dy, struct Layer *l1, struct Layer *l2, GList *depths); +void resize_journal_items_by(GList *itemlist, double scaling_x, double scaling_y, + double offset_x, double offset_y); + // switch between mappings diff --git a/src/xo-paint.c b/src/xo-paint.c index c81de37..7909941 100644 --- a/src/xo-paint.c +++ b/src/xo-paint.c @@ -55,6 +55,7 @@ void update_cursor(void) GdkPixmap *source, *mask; GdkColor fg = {0, 0, 0, 0}, bg = {0, 65535, 65535, 65535}; + ui.is_sel_cursor = FALSE; if (GTK_WIDGET(canvas)->window == NULL) return; if (ui.cursor!=NULL) { @@ -103,6 +104,49 @@ void update_cursor(void) gdk_window_set_cursor(GTK_WIDGET(canvas)->window, ui.cursor); } +/* adjust the cursor shape if it hovers near a selection box */ + +void update_cursor_for_resize(double *pt) +{ + gboolean in_range_x, in_range_y; + gboolean can_resize_left, can_resize_right, can_resize_bottom, can_resize_top; + gdouble resize_margin; + GdkCursorType newcursor; + + // if we're not even close to the box in some direction, return immediately + resize_margin = RESIZE_MARGIN / ui.zoom; + if (pt[0]<ui.selection->bbox.left-resize_margin || pt[0]>ui.selection->bbox.right+resize_margin + || pt[1]<ui.selection->bbox.top-resize_margin || pt[1]>ui.selection->bbox.bottom+resize_margin) + { + if (ui.is_sel_cursor) update_cursor(); + return; + } + + ui.is_sel_cursor = TRUE; + can_resize_left = (pt[0] < ui.selection->bbox.left+resize_margin); + can_resize_right = (pt[0] > ui.selection->bbox.right-resize_margin); + can_resize_top = (pt[1] < ui.selection->bbox.top+resize_margin); + can_resize_bottom = (pt[1] > ui.selection->bbox.bottom-resize_margin); + + if (can_resize_left) { + if (can_resize_top) newcursor = GDK_TOP_LEFT_CORNER; + else if (can_resize_bottom) newcursor = GDK_BOTTOM_LEFT_CORNER; + else newcursor = GDK_LEFT_SIDE; + } + else if (can_resize_right) { + if (can_resize_top) newcursor = GDK_TOP_RIGHT_CORNER; + else if (can_resize_bottom) newcursor = GDK_BOTTOM_RIGHT_CORNER; + else newcursor = GDK_RIGHT_SIDE; + } + else if (can_resize_top) newcursor = GDK_TOP_SIDE; + else if (can_resize_bottom) newcursor = GDK_BOTTOM_SIDE; + else newcursor = GDK_FLEUR; + + if (ui.cursor!=NULL && ui.cursor->type == newcursor) return; + if (ui.cursor!=NULL) gdk_cursor_unref(ui.cursor); + ui.cursor = gdk_cursor_new(newcursor); + gdk_window_set_cursor(GTK_WIDGET(canvas)->window, ui.cursor); +} /************** painting strokes *************/ @@ -145,13 +189,14 @@ void create_new_stroke(GdkEvent *event) ui.cur_path.num_points = 1; get_pointer_coords(event, ui.cur_path.coords); - if (ui.ruler[ui.cur_mapping]) + if (ui.cur_brush->ruler) { ui.cur_item->canvas_item = gnome_canvas_item_new(ui.cur_layer->group, gnome_canvas_line_get_type(), "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND, "fill-color-rgba", ui.cur_item->brush.color_rgba, "width-units", ui.cur_item->brush.thickness, NULL); - else + ui.cur_item->brush.variable_width = FALSE; + } else ui.cur_item->canvas_item = gnome_canvas_item_new( ui.cur_layer->group, gnome_canvas_group_get_type(), NULL); } @@ -159,9 +204,9 @@ void create_new_stroke(GdkEvent *event) void continue_stroke(GdkEvent *event) { GnomeCanvasPoints seg; - double *pt; + double *pt, current_width; - if (ui.ruler[ui.cur_mapping]) { + if (ui.cur_brush->ruler) { pt = ui.cur_path.coords; } else { realloc_cur_path(ui.cur_path.num_points+1); @@ -170,7 +215,14 @@ void continue_stroke(GdkEvent *event) get_pointer_coords(event, pt+2); - if (ui.ruler[ui.cur_mapping]) + if (ui.cur_item->brush.variable_width) { + realloc_cur_widths(ui.cur_path.num_points); + current_width = ui.cur_item->brush.thickness*get_pressure_multiplier(event); + ui.cur_widths[ui.cur_path.num_points-1] = current_width; + } + else current_width = ui.cur_item->brush.thickness; + + if (ui.cur_brush->ruler) ui.cur_path.num_points = 2; else { if (hypot(pt[0]-pt[2], pt[1]-pt[3]) < PIXEL_MOTION_THRESHOLD/ui.zoom) @@ -186,14 +238,14 @@ void continue_stroke(GdkEvent *event) upon creation the line just copies the contents of the GnomeCanvasPoints into an internal structure */ - if (ui.ruler[ui.cur_mapping]) + if (ui.cur_brush->ruler) gnome_canvas_item_set(ui.cur_item->canvas_item, "points", &seg, NULL); else gnome_canvas_item_new((GnomeCanvasGroup *)ui.cur_item->canvas_item, gnome_canvas_line_get_type(), "points", &seg, "cap-style", GDK_CAP_ROUND, "join-style", GDK_JOIN_ROUND, "fill-color-rgba", ui.cur_item->brush.color_rgba, - "width-units", ui.cur_item->brush.thickness, NULL); + "width-units", current_width, NULL); } void finalize_stroke(void) @@ -202,20 +254,28 @@ void finalize_stroke(void) ui.cur_path.coords[2] = ui.cur_path.coords[0]+0.1; ui.cur_path.coords[3] = ui.cur_path.coords[1]; ui.cur_path.num_points = 2; + ui.cur_item->brush.variable_width = FALSE; } - subdivide_cur_path(); // split the segment so eraser will work + if (!ui.cur_item->brush.variable_width) + subdivide_cur_path(); // split the segment so eraser will work ui.cur_item->path = gnome_canvas_points_new(ui.cur_path.num_points); g_memmove(ui.cur_item->path->coords, ui.cur_path.coords, 2*ui.cur_path.num_points*sizeof(double)); + if (ui.cur_item->brush.variable_width) + ui.cur_item->widths = (gdouble *)g_memdup(ui.cur_widths, + (ui.cur_path.num_points-1)*sizeof(gdouble)); + else ui.cur_item->widths = NULL; update_item_bbox(ui.cur_item); ui.cur_path.num_points = 0; - // destroy the entire group of temporary line segments - gtk_object_destroy(GTK_OBJECT(ui.cur_item->canvas_item)); - // make a new line item to replace it - make_canvas_item_one(ui.cur_layer->group, ui.cur_item); + if (!ui.cur_item->brush.variable_width) { + // destroy the entire group of temporary line segments + gtk_object_destroy(GTK_OBJECT(ui.cur_item->canvas_item)); + // make a new line item to replace it + make_canvas_item_one(ui.cur_layer->group, ui.cur_item); + } // add undo information prepare_new_undo(); @@ -264,6 +324,9 @@ void erase_stroke_portions(struct Item *item, double x, double y, double radius, g_memmove(&newhead->brush, &item->brush, sizeof(struct Brush)); newhead->path = gnome_canvas_points_new(i); g_memmove(newhead->path->coords, item->path->coords, 2*i*sizeof(double)); + if (newhead->brush.variable_width) + newhead->widths = (gdouble *)g_memdup(item->widths, (i-1)*sizeof(gdouble)); + else newhead->widths = NULL; } while (++i < item->path->num_points) { pt+=2; @@ -276,12 +339,17 @@ void erase_stroke_portions(struct Item *item, double x, double y, double radius, newtail->path = gnome_canvas_points_new(item->path->num_points-i); g_memmove(newtail->path->coords, item->path->coords+2*i, 2*(item->path->num_points-i)*sizeof(double)); + if (newtail->brush.variable_width) + newtail->widths = (gdouble *)g_memdup(item->widths+i, + (item->path->num_points-i-1)*sizeof(gdouble)); + else newtail->widths = NULL; newtail->canvas_item = NULL; } } if (item->type == ITEM_STROKE) { // it's inside an erasure list - we destroy it gnome_canvas_points_free(item->path); + if (item->brush.variable_width) g_free(item->widths); if (item->canvas_item != NULL) gtk_object_destroy(GTK_OBJECT(item->canvas_item)); erasure->nrepl--; @@ -349,7 +417,7 @@ void do_eraser(GdkEvent *event, double radius, gboolean whole_strokes) void finalize_erasure(void) { GList *itemlist, *partlist; - struct Item *item, *part; + struct Item *item; prepare_new_undo(); undo->type = ITEM_ERASURE; @@ -499,6 +567,53 @@ gboolean start_movesel(GdkEvent *event) return FALSE; } +gboolean start_resizesel(GdkEvent *event) +{ + double pt[2], resize_margin, hmargin, vmargin; + + if (ui.selection==NULL) return FALSE; + if (ui.cur_layer != ui.selection->layer) return FALSE; + + get_pointer_coords(event, pt); + + if (ui.selection->type == ITEM_SELECTRECT) { + resize_margin = RESIZE_MARGIN/ui.zoom; + hmargin = (ui.selection->bbox.right-ui.selection->bbox.left)*0.3; + if (hmargin>resize_margin) hmargin = resize_margin; + vmargin = (ui.selection->bbox.bottom-ui.selection->bbox.top)*0.3; + if (vmargin>resize_margin) vmargin = resize_margin; + + // make sure the click is within a box slightly bigger than the selection rectangle + if (pt[0]<ui.selection->bbox.left-resize_margin || + pt[0]>ui.selection->bbox.right+resize_margin || + pt[1]<ui.selection->bbox.top-resize_margin || + pt[1]>ui.selection->bbox.bottom+resize_margin) + return FALSE; + + // now, if the click is near the edge, it's a resize operation + // keep track of which edges we're close to, since those are the ones which should move + ui.selection->resizing_left = (pt[0]<ui.selection->bbox.left+hmargin); + ui.selection->resizing_right = (pt[0]>ui.selection->bbox.right-hmargin); + ui.selection->resizing_top = (pt[1]<ui.selection->bbox.top+vmargin); + ui.selection->resizing_bottom = (pt[1]>ui.selection->bbox.bottom-vmargin); + + // we're not near any edge, give up + if (!(ui.selection->resizing_left || ui.selection->resizing_right || + ui.selection->resizing_top || ui.selection->resizing_bottom)) + return FALSE; + + ui.cur_item_type = ITEM_RESIZESEL; + ui.selection->new_y1 = ui.selection->bbox.top; + ui.selection->new_y2 = ui.selection->bbox.bottom; + ui.selection->new_x1 = ui.selection->bbox.left; + ui.selection->new_x2 = ui.selection->bbox.right; + gnome_canvas_item_set(ui.selection->canvas_item, "dash", NULL, NULL); + return TRUE; + } + return FALSE; +} + + void start_vertspace(GdkEvent *event) { double pt[2]; @@ -613,10 +728,25 @@ void continue_movesel(GdkEvent *event) } } +void continue_resizesel(GdkEvent *event) +{ + double pt[2]; + + get_pointer_coords(event, pt); + + if (ui.selection->resizing_top) ui.selection->new_y1 = pt[1]; + if (ui.selection->resizing_bottom) ui.selection->new_y2 = pt[1]; + if (ui.selection->resizing_left) ui.selection->new_x1 = pt[0]; + if (ui.selection->resizing_right) ui.selection->new_x2 = pt[0]; + + gnome_canvas_item_set(ui.selection->canvas_item, + "x1", ui.selection->new_x1, "x2", ui.selection->new_x2, + "y1", ui.selection->new_y1, "y2", ui.selection->new_y2, NULL); +} + void finalize_movesel(void) { GList *list, *link; - struct Item *item; if (ui.selection->items != NULL) { prepare_new_undo(); @@ -650,11 +780,71 @@ void finalize_movesel(void) ui.selection->bbox.top += undo->val_y; ui.selection->bbox.bottom += undo->val_y; make_dashed(ui.selection->canvas_item); + /* update selection box object's offset to be trivial, and its internal + coordinates to agree with those of the bbox; need this since resize + operations will modify the box by setting its coordinates directly */ + gnome_canvas_item_affine_absolute(ui.selection->canvas_item, NULL); + gnome_canvas_item_set(ui.selection->canvas_item, + "x1", ui.selection->bbox.left, "x2", ui.selection->bbox.right, + "y1", ui.selection->bbox.top, "y2", ui.selection->bbox.bottom, NULL); } ui.cur_item_type = ITEM_NONE; update_cursor(); } +#define SCALING_EPSILON 0.001 + +void finalize_resizesel(void) +{ + struct Item *item; + + // build the affine transformation + double offset_x, offset_y, scaling_x, scaling_y; + scaling_x = (ui.selection->new_x2 - ui.selection->new_x1) / + (ui.selection->bbox.right - ui.selection->bbox.left); + scaling_y = (ui.selection->new_y2 - ui.selection->new_y1) / + (ui.selection->bbox.bottom - ui.selection->bbox.top); + // couldn't undo a resize-by-zero... + if (fabs(scaling_x)<SCALING_EPSILON) scaling_x = SCALING_EPSILON; + if (fabs(scaling_y)<SCALING_EPSILON) scaling_y = SCALING_EPSILON; + offset_x = ui.selection->new_x1 - ui.selection->bbox.left * scaling_x; + offset_y = ui.selection->new_y1 - ui.selection->bbox.top * scaling_y; + + if (ui.selection->items != NULL) { + // create the undo information + prepare_new_undo(); + undo->type = ITEM_RESIZESEL; + undo->itemlist = g_list_copy(ui.selection->items); + undo->auxlist = NULL; + + undo->scaling_x = scaling_x; + undo->scaling_y = scaling_y; + undo->val_x = offset_x; + undo->val_y = offset_y; + + // actually do the resize operation + resize_journal_items_by(ui.selection->items, scaling_x, scaling_y, offset_x, offset_y); + } + + if (scaling_x>0) { + ui.selection->bbox.left = ui.selection->new_x1; + ui.selection->bbox.right = ui.selection->new_x2; + } else { + ui.selection->bbox.left = ui.selection->new_x2; + ui.selection->bbox.right = ui.selection->new_x1; + } + if (scaling_y>0) { + ui.selection->bbox.top = ui.selection->new_y1; + ui.selection->bbox.bottom = ui.selection->new_y2; + } else { + ui.selection->bbox.top = ui.selection->new_y2; + ui.selection->bbox.bottom = ui.selection->new_y1; + } + make_dashed(ui.selection->canvas_item); + + ui.cur_item_type = ITEM_NONE; + update_cursor(); +} void selection_delete(void) { @@ -724,6 +914,8 @@ void selection_to_clip(void) + sizeof(struct Brush) // brush + sizeof(int) // num_points + 2*item->path->num_points*sizeof(double); // the points + if (item->brush.variable_width) + bufsz += (item->path->num_points-1)*sizeof(double); // the widths } else if (item->type == ITEM_TEXT) { bufsz+= sizeof(int) // type @@ -749,6 +941,10 @@ void selection_to_clip(void) g_memmove(p, &item->path->num_points, sizeof(int)); p+= sizeof(int); g_memmove(p, item->path->coords, 2*item->path->num_points*sizeof(double)); p+= 2*item->path->num_points*sizeof(double); + if (item->brush.variable_width) { + g_memmove(p, item->widths, (item->path->num_points-1)*sizeof(double)); + p+= (item->path->num_points-1)*sizeof(double); + } } if (item->type == ITEM_TEXT) { g_memmove(p, &item->brush, sizeof(struct Brush)); p+= sizeof(struct Brush); @@ -779,7 +975,6 @@ void clipboard_paste(void) GtkSelectionData *sel_data; unsigned char *p; int nitems, npts, i, len; - GList *list; struct Item *item; double hoffset, voffset, cx, cy; double *pf; @@ -849,6 +1044,11 @@ void clipboard_paste(void) item->path->coords[2*i+1] = pf[2*i+1] + voffset; } p+= 2*item->path->num_points*sizeof(double); + if (item->brush.variable_width) { + g_memmove(p, item->widths, (item->path->num_points-1)*sizeof(double)); + p+= (item->path->num_points-1)*sizeof(double); + } + else item->widths = NULL; update_item_bbox(item); make_canvas_item_one(ui.cur_layer->group, item); } @@ -885,6 +1085,7 @@ void recolor_selection(int color) GList *itemlist; struct Item *item; struct Brush *brush; + GnomeCanvasGroup *group; if (ui.selection == NULL) return; prepare_new_undo(); @@ -903,9 +1104,16 @@ void recolor_selection(int color) // repaint the stroke item->brush.color_no = color; item->brush.color_rgba = predef_colors_rgba[color]; - if (item->canvas_item!=NULL) - gnome_canvas_item_set(item->canvas_item, - "fill-color-rgba", item->brush.color_rgba, NULL); + if (item->canvas_item!=NULL) { + if (!item->brush.variable_width) + gnome_canvas_item_set(item->canvas_item, + "fill-color-rgba", item->brush.color_rgba, NULL); + else { + group = (GnomeCanvasGroup *) item->canvas_item->parent; + gtk_object_destroy(GTK_OBJECT(item->canvas_item)); + make_canvas_item_one(group, item); + } + } } } @@ -914,6 +1122,7 @@ void rethicken_selection(int val) GList *itemlist; struct Item *item; struct Brush *brush; + GnomeCanvasGroup *group; if (ui.selection == NULL) return; prepare_new_undo(); @@ -931,9 +1140,17 @@ void rethicken_selection(int val) // repaint the stroke item->brush.thickness_no = val; item->brush.thickness = predef_thickness[TOOL_PEN][val]; - if (item->canvas_item!=NULL) - gnome_canvas_item_set(item->canvas_item, - "width-units", item->brush.thickness, NULL); + if (item->canvas_item!=NULL) { + if (!item->brush.variable_width) + gnome_canvas_item_set(item->canvas_item, + "width-units", item->brush.thickness, NULL); + else { + group = (GnomeCanvasGroup *) item->canvas_item->parent; + gtk_object_destroy(GTK_OBJECT(item->canvas_item)); + item->brush.variable_width = FALSE; + make_canvas_item_one(group, item); + } + } } } @@ -984,7 +1201,6 @@ void resize_textview(gpointer *toplevel, gpointer *data) void start_text(GdkEvent *event, struct Item *item) { double pt[2]; - gchar *text; GtkTextBuffer *buffer; GnomeCanvasItem *canvas_item; PangoFontDescription *font_desc; @@ -1133,7 +1349,6 @@ void update_text_item_displayfont(struct Item *item) void rescale_text_items(void) { GList *pagelist, *layerlist, *itemlist; - struct Layer *l; for (pagelist = journal.pages; pagelist!=NULL; pagelist = pagelist->next) for (layerlist = ((struct Page *)pagelist->data)->layers; layerlist!=NULL; layerlist = layerlist->next) diff --git a/src/xo-paint.h b/src/xo-paint.h index 4799a7e..d69b881 100644 --- a/src/xo-paint.h +++ b/src/xo-paint.h @@ -1,11 +1,13 @@ void set_cursor_busy(gboolean busy); void update_cursor(void); +void update_cursor_for_resize(double *pt); void create_new_stroke(GdkEvent *event); void continue_stroke(GdkEvent *event); void finalize_stroke(void); void do_eraser(GdkEvent *event, double radius, gboolean whole_strokes); +void finalize_erasure(void); void do_hand(GdkEvent *event); @@ -15,6 +17,9 @@ gboolean start_movesel(GdkEvent *event); void start_vertspace(GdkEvent *event); void continue_movesel(GdkEvent *event); void finalize_movesel(void); +gboolean start_resizesel(GdkEvent *event); +void continue_resizesel(GdkEvent *event); +void finalize_resizesel(void); void selection_delete(void); void selection_to_clip(void); diff --git a/src/xo-print.c b/src/xo-print.c index cfd61e1..6a89886 100644 --- a/src/xo-print.c +++ b/src/xo-print.c @@ -342,7 +342,7 @@ struct PdfObj *get_pdfobj(GString *pdfbuf, struct XrefTable *xref, struct PdfObj struct PdfObj *parse_xref_table(GString *pdfbuf, struct XrefTable *xref, int offs) { - char *p, *q, *eof; + char *p, *eof; struct PdfObj *trailerdict, *obj; int start, len, i; @@ -460,7 +460,7 @@ int pdf_getpageinfo(GString *pdfbuf, struct XrefTable *xref, gboolean pdf_parse_info(GString *pdfbuf, struct PdfInfo *pdfinfo, struct XrefTable *xref) { char *p; - int i, offs; + int offs; struct PdfObj *obj, *pages; xref->n_alloc = xref->last = 0; @@ -765,7 +765,7 @@ int pdf_draw_bitmap_background(struct Page *pg, GString *str, // manipulate Pdf fonts struct PdfFont *new_pdffont(struct XrefTable *xref, GList **fonts, - unsigned char *filename, int font_id, FT_Face face, int glyph_page) + char *filename, int font_id, FT_Face face, int glyph_page) { GList *list; struct PdfFont *font; @@ -830,10 +830,11 @@ void embed_pdffont(GString *pdfbuf, struct XrefTable *xref, struct PdfFont *font gboolean fallback, is_binary; guchar encoding[256]; gushort glyphs[256]; - int i, j, num, len, len1, len2; + int i, j, num, len1, len2; + gsize len; TrueTypeFont *ttfnt; char *tmpfile, *seg1, *seg2; - unsigned char *fontdata, *p; + char *fontdata, *p; char prefix[8]; int nobj_fontprog, nobj_descr, lastchar; @@ -855,12 +856,12 @@ void embed_pdffont(GString *pdfbuf, struct XrefTable *xref, struct PdfFont *font CreateTTFromTTGlyphs(ttfnt, tmpfile, glyphs, encoding, num, 0, NULL, TTCF_AutoName | TTCF_IncludeOS2); CloseTTFont(ttfnt); - if (g_file_get_contents(tmpfile, (char **)&fontdata, &len, NULL) && len>=8) { + if (g_file_get_contents(tmpfile, &fontdata, &len, NULL) && len>=8) { make_xref(xref, xref->last+1, pdfbuf->len); nobj_fontprog = xref->last; g_string_append_printf(pdfbuf, "%d 0 obj\n<< /Length %d /Length1 %d >> stream\n", - nobj_fontprog, len, len); + nobj_fontprog, (int)len, (int)len); g_string_append_len(pdfbuf, fontdata, len); g_string_append(pdfbuf, "endstream\nendobj\n"); g_free(fontdata); @@ -872,14 +873,14 @@ void embed_pdffont(GString *pdfbuf, struct XrefTable *xref, struct PdfFont *font else fallback = TRUE; } else { // embed the font file: Type1 case - if (g_file_get_contents(font->filename, (char **)&fontdata, &len, NULL) && len>=8) { - if (fontdata[0]==0x80 && fontdata[1]==0x01) { + if (g_file_get_contents(font->filename, &fontdata, &len, NULL) && len>=8) { + if (fontdata[0]==(char)0x80 && fontdata[1]==(char)0x01) { is_binary = TRUE; - len1 = pfb_get_length(fontdata+2); - if (fontdata[len1+6]!=0x80 || fontdata[len1+7]!=0x02) fallback = TRUE; + len1 = pfb_get_length((unsigned char *)fontdata+2); + if (fontdata[len1+6]!=(char)0x80 || fontdata[len1+7]!=(char)0x02) fallback = TRUE; else { - len2 = pfb_get_length(fontdata+len1+8); - if (fontdata[len1+len2+12]!=0x80 || fontdata[len1+len2+13]!=0x01) + len2 = pfb_get_length((unsigned char *)fontdata+len1+8); + if (fontdata[len1+len2+12]!=(char)0x80 || fontdata[len1+len2+13]!=(char)0x01) fallback = TRUE; } } @@ -1021,7 +1022,7 @@ void pdf_draw_page(struct Page *pg, GString *str, gboolean *use_hiliter, FcPattern *pattern; int baseline, advance; int glyph_no, glyph_page, current_page; - unsigned char *filename; + char *filename; char tmpstr[200]; int font_id; FT_Face ftface; @@ -1052,10 +1053,18 @@ void pdf_draw_page(struct Page *pg, GString *str, gboolean *use_hiliter, old_rgba = item->brush.color_rgba & ~0xff; old_thickness = item->brush.thickness; pt = item->path->coords; - g_string_append_printf(str, "%.2f %.2f m ", pt[0], pt[1]); - for (i=1, pt+=2; i<item->path->num_points; i++, pt+=2) - g_string_append_printf(str, "%.2f %.2f l ", pt[0], pt[1]); - g_string_append_printf(str,"S\n"); + if (!item->brush.variable_width) { + g_string_append_printf(str, "%.2f %.2f m ", pt[0], pt[1]); + for (i=1, pt+=2; i<item->path->num_points; i++, pt+=2) + g_string_append_printf(str, "%.2f %.2f l ", pt[0], pt[1]); + g_string_append_printf(str,"S\n"); + old_thickness = item->brush.thickness; + } else { + for (i=0; i<item->path->num_points-1; i++, pt+=2) + g_string_append_printf(str, "%.2f w %.2f %.2f m %.2f %.2f l S\n", + item->widths[i], pt[0], pt[1], pt[2], pt[3]); + old_thickness = 0.0; + } if ((item->brush.color_rgba & 0xf0) != 0xf0) // undo transparent g_string_append(str, "Q "); } @@ -1083,7 +1092,7 @@ void pdf_draw_page(struct Page *pg, GString *str, gboolean *use_hiliter, if (!PANGO_IS_FC_FONT(run->item->analysis.font)) continue; fcfont = PANGO_FC_FONT(run->item->analysis.font); pattern = fcfont->font_pattern; - if (FcPatternGetString(pattern, FC_FILE, 0, &filename) != FcResultMatch || + if (FcPatternGetString(pattern, FC_FILE, 0, (unsigned char **)&filename) != FcResultMatch || FcPatternGetInteger(pattern, FC_INDEX, 0, &font_id) != FcResultMatch) continue; ftface = pango_fc_font_lock_face(fcfont); @@ -1164,7 +1173,7 @@ gboolean print_to_pdf(char *filename) GList *pglist; struct Page *pg; char *buf; - unsigned int len; + gsize len; gboolean annot, uses_pdf; gboolean use_hiliter; struct PdfInfo pdfinfo; @@ -1504,13 +1513,23 @@ void print_page(GnomePrintContext *gpc, struct Page *pg, int pageno, if (item->type == ITEM_STROKE) { if (item->brush.thickness != old_thickness) gnome_print_setlinewidth(gpc, item->brush.thickness); - old_thickness = item->brush.thickness; gnome_print_newpath(gpc); pt = item->path->coords; - gnome_print_moveto(gpc, pt[0], -pt[1]); - for (i=1, pt+=2; i<item->path->num_points; i++, pt+=2) - gnome_print_lineto(gpc, pt[0], -pt[1]); - gnome_print_stroke(gpc); + if (!item->brush.variable_width) { + gnome_print_moveto(gpc, pt[0], -pt[1]); + for (i=1, pt+=2; i<item->path->num_points; i++, pt+=2) + gnome_print_lineto(gpc, pt[0], -pt[1]); + gnome_print_stroke(gpc); + old_thickness = item->brush.thickness; + } else { + for (i=0; i<item->path->num_points-1; i++, pt+=2) { + gnome_print_moveto(gpc, pt[0], -pt[1]); + gnome_print_setlinewidth(gpc, item->widths[i]); + gnome_print_lineto(gpc, pt[2], -pt[3]); + gnome_print_stroke(gpc); + } + old_thickness = 0.0; + } } if (item->type == ITEM_TEXT) { layout = gnome_print_pango_create_layout(gpc); diff --git a/src/xo-print.h b/src/xo-print.h index bdfeee7..34e2d6c 100644 --- a/src/xo-print.h +++ b/src/xo-print.h @@ -29,7 +29,7 @@ typedef struct PdfObj { typedef struct PdfFont { int n_obj; gboolean used_in_this_page; - unsigned char *filename; + char *filename; int font_id; gboolean is_truetype; int glyph_page; diff --git a/src/xo-shapes.c b/src/xo-shapes.c new file mode 100644 index 0000000..0efdf69 --- /dev/null +++ b/src/xo-shapes.c @@ -0,0 +1,613 @@ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <math.h> +#include <string.h> +#include <gtk/gtk.h> +#include <libgnomecanvas/libgnomecanvas.h> + +#include "xournal.h" +#include "xo-shapes.h" +#include "xo-paint.h" + +typedef struct Inertia { + double mass, sx, sy, sxx, sxy, syy; +} Inertia; + +typedef struct RecoSegment { + struct Item *item; + int startpt, endpt; + double xcenter, ycenter, angle, radius; + double x1, y1, x2, y2; + gboolean reversed; +} RecoSegment; + +struct RecoSegment recognizer_queue[MAX_POLYGON_SIDES+1]; +int recognizer_queue_length; +struct UndoItem *last_item_checker; // check if queue is stale + +void reset_recognizer(void) +{ + recognizer_queue_length = 0; + last_item_checker = NULL; +} + +/* compute mass and moments of a stroke */ + +void incr_inertia(double *pt, struct Inertia *s, int coef) +{ + double dm; + dm = coef*hypot(pt[2]-pt[0], pt[3]-pt[1]); + s->mass += dm; + s->sx += dm*pt[0]; + s->sy += dm*pt[1]; + s->sxx += dm*pt[0]*pt[0]; + s->syy += dm*pt[1]*pt[1]; + s->sxy += dm*pt[0]*pt[1]; +} + +void calc_inertia(double *pt, int start, int end, struct Inertia *s) +{ + int i; + + s->mass = s->sx = s->sy = s->sxx = s->sxy = s->syy = 0.; + for (i=start, pt+=2*start; i<end; i++, pt+=2) incr_inertia(pt, s, 1); +} + +/* compute normalized quantities */ + +inline double center_x(struct Inertia s) +{ + return s.sx/s.mass; +} + +inline double center_y(struct Inertia s) +{ + return s.sy/s.mass; +} + +inline double I_xx(struct Inertia s) +{ + return (s.sxx - s.sx*s.sx/s.mass)/s.mass; +} + +inline double I_xy(struct Inertia s) +{ + return (s.sxy - s.sx*s.sy/s.mass)/s.mass; +} + +inline double I_yy(struct Inertia s) +{ + return (s.syy - s.sy*s.sy/s.mass)/s.mass; +} + +inline double I_rad(struct Inertia s) +{ + return sqrt(I_xx(s)+I_yy(s)); +} + +inline double I_det(struct Inertia s) +{ + if (s.mass == 0.) return 0.; + double ixx = I_xx(s), iyy = I_yy(s), ixy = I_xy(s); + return 4*(ixx*iyy-ixy*ixy)/(ixx+iyy)/(ixx+iyy); +} + +/* check if something is a polygonal line with at most nsides sides */ + +int find_polygonal(double *pt, int start, int end, int nsides, int *breaks, struct Inertia *ss) +{ + struct Inertia s, s1, s2; + int k, i1, i2, n1, n2; + double det1, det2; + + if (end == start) return 0; // no way + if (nsides <= 0) return 0; + if (end-start<5) nsides = 1; // too small for a polygon + + // look for a linear piece that's big enough + for (k=0; k<nsides; k++) { + i1 = start + (k*(end-start))/nsides; + i2 = start + ((k+1)*(end-start))/nsides; + calc_inertia(pt, i1, i2, &s); + if (I_det(s) < LINE_MAX_DET) break; + } + if (k==nsides) return 0; // failed! + + // grow the linear piece we found + while (1) { + if (i1>start) { + s1 = s; + incr_inertia(pt+2*(i1-1), &s1, 1); + det1 = I_det(s1); + } + else det1 = 1.; + if (i2<end) { + s2 = s; + incr_inertia(pt+2*i2, &s2, 1); + det2 = I_det(s2); + } + else det2 = 1.; + if (det1<det2 && det1<LINE_MAX_DET) { i1--; s=s1; } + else if (det2<det1 && det2<LINE_MAX_DET) { i2++; s=s2; } + else break; + } + + if (i1>start) { + n1 = find_polygonal(pt, start, i1, (i2==end)?(nsides-1):(nsides-2), breaks, ss); + if (n1 == 0) return 0; // it doesn't work + } + else n1 = 0; + + breaks[n1] = i1; + breaks[n1+1] = i2; + ss[n1] = s; + + if (i2<end) { + n2 = find_polygonal(pt, i2, end, nsides-n1-1, breaks+n1+1, ss+n1+1); + if (n2 == 0) return 0; + } + else n2 = 0; + + return n1+n2+1; +} + +/* improve on the polygon found by find_polygonal() */ + +void optimize_polygonal(double *pt, int nsides, int *breaks, struct Inertia *ss) +{ + int i; + double cost, newcost; + struct Inertia s1, s2; + gboolean improved; + + for (i=1; i<nsides; i++) { + // optimize break between sides i and i+1 + cost = I_det(ss[i-1])*I_det(ss[i-1])+I_det(ss[i])*I_det(ss[i]); + s1 = ss[i-1]; s2 = ss[i]; + improved = FALSE; + while (breaks[i]>breaks[i-1]+1) { + // try moving the break to the left + incr_inertia(pt+2*(breaks[i]-1), &s1, -1); + incr_inertia(pt+2*(breaks[i]-1), &s2, 1); + newcost = I_det(s1)*I_det(s1)+I_det(s2)*I_det(s2); + if (newcost >= cost) break; + improved = TRUE; + cost = newcost; + breaks[i]--; + ss[i-1] = s1; + ss[i] = s2; + } + if (improved) continue; + s1 = ss[i-1]; s2 = ss[i]; + while (breaks[i]<breaks[i+1]-1) { + // try moving the break to the right + incr_inertia(pt+2*breaks[i], &s1, 1); + incr_inertia(pt+2*breaks[i], &s2, -1); + newcost = I_det(s1)*I_det(s1)+I_det(s2)*I_det(s2); + if (newcost >= cost) break; + cost = newcost; + breaks[i]++; + ss[i-1] = s1; + ss[i] = s2; + } + } +} + +/* find the geometry of a recognized segment */ + +void get_segment_geometry(double *pt, int start, int end, struct Inertia *s, struct RecoSegment *r) +{ + double a, b, c, lmin, lmax, l; + int i; + + r->xcenter = center_x(*s); + r->ycenter = center_y(*s); + a = I_xx(*s); b = I_xy(*s); c = I_yy(*s); + /* max angle for inertia quadratic form solves: tan(2t) = 2b/(a-c) */ + r->angle = atan2(2*b, a-c)/2; + r->radius = sqrt(3*(a+c)); + + lmin = lmax = 0.; + for (i=start, pt+=2*start; i<=end; i++, pt+=2) { + l = (pt[0]-r->xcenter)*cos(r->angle)+(pt[1]-r->ycenter)*sin(r->angle); + if (l<lmin) lmin = l; + if (l>lmax) lmax = l; + } + r->x1 = r->xcenter + lmin*cos(r->angle); + r->y1 = r->ycenter + lmin*sin(r->angle); + r->x2 = r->xcenter + lmax*cos(r->angle); + r->y2 = r->ycenter + lmax*sin(r->angle); +} + +/* test if we have a circle; inertia has been precomputed by caller */ + +double score_circle(double *pt, int start, int end, struct Inertia *s) +{ + double sum, x0, y0, r0, dm, deltar; + int i; + + if (s->mass == 0.) return 0; + sum = 0.; + x0 = center_x(*s); y0 = center_y(*s); r0 = I_rad(*s); + for (i=start, pt+=2*start; i<end; i++, pt+=2) { + dm = hypot(pt[2]-pt[0], pt[3]-pt[1]); + deltar = hypot(pt[0]-x0, pt[1]-y0) - r0; + sum += dm * fabs(deltar); + } + return sum/(s->mass*r0); +} + +/* replace strokes by various shapes */ + +void make_circle_shape(double x0, double y0, double r) +{ + int npts, i; + struct Item *item; + struct UndoErasureData *erasure; + + npts = (int)(2*r); + if (npts<12) npts = 12; // min. number of points + realloc_cur_path(npts+1); + ui.cur_path.num_points = npts+1; + for (i=0;i<=npts; i++) { + ui.cur_path.coords[2*i] = x0 + r*cos((2*M_PI*i)/npts); + ui.cur_path.coords[2*i+1] = y0 + r*sin((2*M_PI*i)/npts); + } +} + +void calc_edge_isect(struct RecoSegment *r1, struct RecoSegment *r2, double *pt) +{ + double t; + t = (r2->xcenter - r1->xcenter) * sin(r2->angle) - + (r2->ycenter - r1->ycenter) * cos(r2->angle); + t /= sin(r2->angle-r1->angle); + pt[0] = r1->xcenter + t*cos(r1->angle); + pt[1] = r1->ycenter + t*sin(r1->angle); +} + +void remove_recognized_strokes(struct RecoSegment *rs, int num_old_items) +{ + struct Item *old_item; + int i, shift; + struct UndoErasureData *erasure; + + old_item = NULL; + prepare_new_undo(); + undo->type = ITEM_RECOGNIZER; + undo->layer = ui.cur_layer; + undo->erasurelist = NULL; + shift = 0; + + for (i=0; i<num_old_items; i++) { + if (rs[i].item == old_item) continue; // already done + old_item = rs[i].item; + erasure = g_new(struct UndoErasureData, 1); + erasure->item = old_item; + erasure->npos = g_list_index(ui.cur_layer->items, old_item) + (shift++); + erasure->nrepl = 0; + erasure->replacement_items = NULL; + undo->erasurelist = g_list_append(undo->erasurelist, erasure); + if (old_item->canvas_item != NULL) + gtk_object_destroy(GTK_OBJECT(old_item->canvas_item)); + ui.cur_layer->items = g_list_remove(ui.cur_layer->items, old_item); + ui.cur_layer->nitems--; + } +} + +struct Item *insert_recognized_curpath(void) +{ + struct Item *item; + int i; + struct UndoErasureData *erasure; + + erasure = (struct UndoErasureData *)(undo->erasurelist->data); + item = g_new(struct Item, 1); + item->type = ITEM_STROKE; + g_memmove(&(item->brush), &(erasure->item->brush), sizeof(struct Brush)); + item->brush.variable_width = FALSE; + subdivide_cur_path(); + item->path = gnome_canvas_points_new(ui.cur_path.num_points); + g_memmove(item->path->coords, ui.cur_path.coords, 2*ui.cur_path.num_points*sizeof(double)); + item->widths = NULL; + update_item_bbox(item); + ui.cur_path.num_points = 0; + + erasure->nrepl++; + erasure->replacement_items = g_list_append(erasure->replacement_items, item); + ui.cur_layer->items = g_list_append(ui.cur_layer->items, item); + ui.cur_layer->nitems++; + make_canvas_item_one(ui.cur_layer->group, item); + return item; +} + + +/* test if segments form standard shapes */ + +gboolean try_rectangle(void) +{ + struct RecoSegment *rs, *r1, *r2; + int i; + double dist, avg_angle; + + // first, we need whole strokes to combine to 4 segments... + if (recognizer_queue_length<4) return FALSE; + rs = recognizer_queue + recognizer_queue_length - 4; + if (rs->startpt!=0) return FALSE; + + // check edges make angles ~= Pi/2 and vertices roughly match + avg_angle = 0.; + for (i=0; i<=3; i++) { + r1 = rs+i; r2 = rs+(i+1)%4; + if (fabs(fabs(r1->angle-r2->angle)-M_PI/2) > RECTANGLE_ANGLE_TOLERANCE) + return FALSE; + avg_angle += r1->angle; + if (r2->angle > r1->angle) avg_angle += (i+1)*M_PI/2; + else avg_angle -= (i+1)*M_PI/2; + // test if r1 points away from r2 rather than towards it + r1->reversed = ((r1->x2-r1->x1)*(r2->xcenter-r1->xcenter)+ + (r1->y2-r1->y1)*(r2->ycenter-r1->ycenter)) < 0; + } + for (i=0; i<=3; i++) { + r1 = rs+i; r2 = rs+(i+1)%4; + dist = hypot((r1->reversed?r1->x1:r1->x2) - (r2->reversed?r2->x2:r2->x1), + (r1->reversed?r1->y1:r1->y2) - (r2->reversed?r2->y2:r2->y1)); + if (dist > RECTANGLE_LINEAR_TOLERANCE*(r1->radius+r2->radius)) return FALSE; + } + + // make a rectangle of the correct size and slope + avg_angle = avg_angle/4; + if (fabs(avg_angle)<SLANT_TOLERANCE) avg_angle = 0.; + if (fabs(avg_angle)>M_PI/2-SLANT_TOLERANCE) avg_angle = M_PI/2; + realloc_cur_path(5); + ui.cur_path.num_points = 5; + for (i=0; i<=3; i++) rs[i].angle = avg_angle+i*M_PI/2; + for (i=0; i<=3; i++) calc_edge_isect(rs+i, rs+(i+1)%4, ui.cur_path.coords+2*i+2); + ui.cur_path.coords[0] = ui.cur_path.coords[8]; + ui.cur_path.coords[1] = ui.cur_path.coords[9]; + + remove_recognized_strokes(rs, 4); + insert_recognized_curpath(); + return TRUE; +} + +gboolean try_arrow(void) +{ + struct RecoSegment *rs; + int i, j; + double alpha[3], dist, pt[2], tmp, delta; + double x1, y1, x2, y2, angle; + gboolean rev[3]; + + // first, we need whole strokes to combine to nsides segments... + if (recognizer_queue_length<3) return FALSE; + rs = recognizer_queue + recognizer_queue_length - 3; + if (rs->startpt!=0) return FALSE; + + // check arrow head not too big, and orient main segment + for (i=1; i<=2; i++) { + if (rs[i].radius > ARROW_MAXSIZE*rs[0].radius) return FALSE; + rev[i] = (hypot(rs[i].xcenter-rs->x1, rs[i].ycenter-rs->y1) < + hypot(rs[i].xcenter-rs->x2, rs[i].ycenter-rs->y2)); + } + if (rev[1]!=rev[2]) return FALSE; + if (rev[1]) { + x1 = rs->x2; y1 = rs->y2; x2 = rs->x1; y2 = rs->y1; + angle = rs->angle + M_PI; + } + else { + x1 = rs->x1; y1 = rs->y1; x2 = rs->x2; y2 = rs->y2; + angle = rs->angle; + } + + // check arrow head not too big, and angles roughly ok + for (i=1; i<=2; i++) { + rs[i].reversed = FALSE; + alpha[i] = rs[i].angle - angle; + while (alpha[i]<-M_PI/2) { alpha[i]+=M_PI; rs[i].reversed = !rs[i].reversed; } + while (alpha[i]>M_PI/2) { alpha[i]-=M_PI; rs[i].reversed = !rs[i].reversed; } +#ifdef RECOGNIZER_DEBUG + printf("arrow: alpha[%d] = %.1f degrees\n", i, alpha[i]*180/M_PI); +#endif + if (fabs(alpha[i])<ARROW_ANGLE_MIN || fabs(alpha[i])>ARROW_ANGLE_MAX) return FALSE; + } + + // check arrow head segments are roughly symmetric + if (alpha[1]*alpha[2]>0 || fabs(alpha[1]+alpha[2]) > ARROW_ASYMMETRY_MAX_ANGLE) return FALSE; + if (rs[1].radius/rs[2].radius > 1+ARROW_ASYMMETRY_MAX_LINEAR) return FALSE; + if (rs[2].radius/rs[1].radius > 1+ARROW_ASYMMETRY_MAX_LINEAR) return FALSE; + + // check vertices roughly match + calc_edge_isect(rs+1, rs+2, pt); + for (j=1; j<=2; j++) { + dist = hypot(pt[0]-(rs[j].reversed?rs[j].x1:rs[j].x2), + pt[1]-(rs[j].reversed?rs[j].y1:rs[j].y2)); +#ifdef RECOGNIZER_DEBUG + printf("linear tolerance: tip[%d] = %.2f\n", j, dist/rs[j].radius); +#endif + if (dist>ARROW_TIP_LINEAR_TOLERANCE*rs[j].radius) return FALSE; + } + dist = (pt[0]-x2)*sin(angle)-(pt[1]-y2)*cos(angle); + dist /= rs[1].radius + rs[2].radius; +#ifdef RECOGNIZER_DEBUG + printf("sideways gap tolerance = %.2f\n", dist); +#endif + if (fabs(dist)>ARROW_SIDEWAYS_GAP_TOLERANCE) return FALSE; + dist = (pt[0]-x2)*cos(angle)+(pt[1]-y2)*sin(angle); + dist /= rs[1].radius + rs[2].radius; +#ifdef RECOGNIZER_DEBUG + printf("main linear gap = %.2f\n", dist); +#endif + if (dist<ARROW_MAIN_LINEAR_GAP_MIN || dist>ARROW_MAIN_LINEAR_GAP_MAX) return FALSE; + + // make an arrow of the correct size and slope + if (fabs(rs->angle)<SLANT_TOLERANCE) { // nearly horizontal + angle = angle - rs->angle; + y1 = y2 = rs->ycenter; + } + if (rs->angle>M_PI/2-SLANT_TOLERANCE) { // nearly vertical + angle = angle - (rs->angle-M_PI/2); + x1 = x2 = rs->xcenter; + } + if (rs->angle<-M_PI/2+SLANT_TOLERANCE) { // nearly vertical + angle = angle - (rs->angle+M_PI/2); + x1 = x2 = rs->xcenter; + } + delta = fabs(alpha[1]-alpha[2])/2; + dist = (hypot(rs[1].x1-rs[1].x2, rs[1].y1-rs[1].y2) + + hypot(rs[2].x1-rs[2].x2, rs[2].y1-rs[2].y2))/2; + + realloc_cur_path(2); + ui.cur_path.num_points = 2; + ui.cur_path.coords[0] = x1; ui.cur_path.coords[1] = y1; + ui.cur_path.coords[2] = x2; ui.cur_path.coords[3] = y2; + remove_recognized_strokes(rs, 3); + insert_recognized_curpath(); + + realloc_cur_path(3); + ui.cur_path.num_points = 3; + ui.cur_path.coords[0] = x2 - dist*cos(angle+delta); + ui.cur_path.coords[1] = y2 - dist*sin(angle+delta); + ui.cur_path.coords[2] = x2; + ui.cur_path.coords[3] = y2; + ui.cur_path.coords[4] = x2 - dist*cos(angle-delta); + ui.cur_path.coords[5] = y2 - dist*sin(angle-delta); + insert_recognized_curpath(); + + return TRUE; +} + +gboolean try_closed_polygon(int nsides) +{ + struct RecoSegment *rs, *r1, *r2; + int i; + double dist, pt[2]; + + // first, we need whole strokes to combine to nsides segments... + if (recognizer_queue_length<nsides) return FALSE; + rs = recognizer_queue + recognizer_queue_length - nsides; + if (rs->startpt!=0) return FALSE; + + // check vertices roughly match + for (i=0; i<nsides; i++) { + r1 = rs+i; r2 = rs+(i+1)%nsides; + // test if r1 points away from r2 rather than towards it + calc_edge_isect(r1, r2, pt); + r1->reversed = (hypot(pt[0]-r1->x1,pt[1]-r1->y1) < hypot(pt[0]-r1->x2,pt[1]-r1->y2)); + } + for (i=0; i<nsides; i++) { + r1 = rs+i; r2 = rs+(i+1)%nsides; + calc_edge_isect(r1, r2, pt); + dist = hypot((r1->reversed?r1->x1:r1->x2)-pt[0],(r1->reversed?r1->y1:r1->y2)-pt[1]) + + hypot((r2->reversed?r2->x2:r2->x1)-pt[0],(r2->reversed?r2->y2:r2->y1)-pt[1]); + if (dist > POLYGON_LINEAR_TOLERANCE*(r1->radius+r2->radius)) return FALSE; + } + + // make a polygon of the correct size and slope + realloc_cur_path(nsides+1); + ui.cur_path.num_points = nsides+1; + for (i=0; i<nsides; i++) + calc_edge_isect(rs+i, rs+(i+1)%nsides, ui.cur_path.coords+2*i+2); + ui.cur_path.coords[0] = ui.cur_path.coords[2*nsides]; + ui.cur_path.coords[1] = ui.cur_path.coords[2*nsides+1]; + + remove_recognized_strokes(rs, nsides); + insert_recognized_curpath(); + return TRUE; +} + +/* the main pattern recognition function, called after finalize_stroke() */ +void recognize_patterns(void) +{ + struct Item *it; + struct Inertia s, ss[4]; + struct RecoSegment *rs; + int n, i; + int brk[5]; + double score; + + if (!undo || undo->type!=ITEM_STROKE) return; + if (undo->next != last_item_checker) reset_recognizer(); // reset queue + if (last_item_checker!=NULL && ui.cur_layer != last_item_checker->layer) reset_recognizer(); + + it = undo->item; + calc_inertia(it->path->coords, 0, it->path->num_points-1, &s); +#ifdef RECOGNIZER_DEBUG + printf("Mass=%.0f, Center=(%.1f,%.1f), I=(%.0f,%.0f, %.0f), " + "Rad=%.2f, Det=%.4f \n", + s.mass, center_x(s), center_y(s), I_xx(s), I_yy(s), I_xy(s), I_rad(s), I_det(s)); +#endif + + // first see if it's a polygon + n = find_polygonal(it->path->coords, 0, it->path->num_points-1, MAX_POLYGON_SIDES, brk, ss); + if (n>0) { + optimize_polygonal(it->path->coords, n, brk, ss); +#ifdef RECOGNIZER_DEBUG + printf("Polygon, %d edges: ", n); + for (i=0; i<n; i++) + printf("%d-%d (M=%.0f, det=%.4f) ", brk[i], brk[i+1], ss[i].mass, I_det(ss[i])); + printf("\n"); +#endif + /* update recognizer segment queue (most recent at end) */ + while (n+recognizer_queue_length > MAX_POLYGON_SIDES) { + // remove oldest polygonal stroke + i=1; + while (i<recognizer_queue_length && recognizer_queue[i].startpt!=0) i++; + recognizer_queue_length-=i; + g_memmove(recognizer_queue, recognizer_queue+i, + recognizer_queue_length * sizeof(struct RecoSegment)); + } +#ifdef RECOGNIZER_DEBUG + printf("Queue now has %d + %d edges\n", recognizer_queue_length, n); +#endif + rs = recognizer_queue + recognizer_queue_length; + recognizer_queue_length += n; + for (i=0; i<n; i++) { + rs[i].item = it; + rs[i].startpt = brk[i]; + rs[i].endpt = brk[i+1]; + get_segment_geometry(it->path->coords, brk[i], brk[i+1], ss+i, rs+i); + } + if (try_rectangle()) { reset_recognizer(); return; } + if (try_arrow()) { reset_recognizer(); return; } + if (try_closed_polygon(3)) { reset_recognizer(); return; } + if (try_closed_polygon(4)) { reset_recognizer(); return; } + if (n==1) { // current stroke is a line + if (fabs(rs->angle)<SLANT_TOLERANCE) { // nearly horizontal + rs->angle = 0.; + rs->y1 = rs->y2 = rs->ycenter; + } + if (fabs(rs->angle)>M_PI/2-SLANT_TOLERANCE) { // nearly vertical + rs->angle = (rs->angle>0)?(M_PI/2):(-M_PI/2); + rs->x1 = rs->x2 = rs->xcenter; + } + realloc_cur_path(2); + ui.cur_path.num_points = 2; + ui.cur_path.coords[0] = rs->x1; + ui.cur_path.coords[1] = rs->y1; + ui.cur_path.coords[2] = rs->x2; + ui.cur_path.coords[3] = rs->y2; + remove_recognized_strokes(rs, 1); + rs->item = insert_recognized_curpath(); + } + last_item_checker = undo; + return; + } + + // not a polygon: maybe a circle ? + reset_recognizer(); + if (I_det(s)>CIRCLE_MIN_DET) { + score = score_circle(it->path->coords, 0, it->path->num_points-1, &s); +#ifdef RECOGNIZER_DEBUG + printf("Circle score: %.2f\n", score); +#endif + if (score < CIRCLE_MAX_SCORE) { + make_circle_shape(center_x(s), center_y(s), I_rad(s)); + recognizer_queue[0].item = it; + remove_recognized_strokes(recognizer_queue, 1); + insert_recognized_curpath(); + } + } +} + diff --git a/src/xo-shapes.h b/src/xo-shapes.h new file mode 100644 index 0000000..77c12fd --- /dev/null +++ b/src/xo-shapes.h @@ -0,0 +1,25 @@ +// #define RECOGNIZER_DEBUG // uncomment for debug output + +#define MAX_POLYGON_SIDES 4 + +#define LINE_MAX_DET 0.015 // maximum score for line (ideal line = 0) +#define CIRCLE_MIN_DET 0.95 // minimum det. score for circle (ideal circle = 1) +#define CIRCLE_MAX_SCORE 0.10 // max circle score for circle (ideal circle = 0) + +#define SLANT_TOLERANCE (5*M_PI/180) // ignore slanting by +/- 5 degrees +#define RECTANGLE_ANGLE_TOLERANCE (15*M_PI/180) // angle tolerance in rectangles +#define RECTANGLE_LINEAR_TOLERANCE 0.20 // vertex gap tolerance in rectangles +#define POLYGON_LINEAR_TOLERANCE 0.20 // vertex gap tolerance in closed polygons + +#define ARROW_MAXSIZE 0.8 // max size of arrow tip relative to main segment +#define ARROW_ANGLE_MIN (5*M_PI/180) // arrow tip angles relative to main segment +#define ARROW_ANGLE_MAX (50*M_PI/180) +#define ARROW_ASYMMETRY_MAX_ANGLE (30*M_PI/180) +#define ARROW_ASYMMETRY_MAX_LINEAR 1.0 // size imbalance of two legs of tip +#define ARROW_TIP_LINEAR_TOLERANCE 0.30 // gap tolerance on tip segments +#define ARROW_SIDEWAYS_GAP_TOLERANCE 0.25 // gap tolerance in lateral direction +#define ARROW_MAIN_LINEAR_GAP_MIN -0.3 // gap tolerance on main segment +#define ARROW_MAIN_LINEAR_GAP_MAX +0.7 // gap tolerance on main segment + +void recognize_patterns(void); +void reset_recognizer(void); diff --git a/src/xournal.h b/src/xournal.h index 47cd018..aadc1ea 100644 --- a/src/xournal.h +++ b/src/xournal.h @@ -1,6 +1,11 @@ #include <gtk/gtk.h> #include <libgnomecanvas/libgnomecanvas.h> +/* #define INPUT_DEBUG */ +/* uncomment this line if you experience event-processing problems + and want to list the input events received by xournal. Caution, lots + of output (redirect to a file). */ + #define ENABLE_XINPUT_BUGFIX /* comment out this line if you are experiencing calibration problems with XInput and want to try things differently. This will probably break @@ -21,6 +26,7 @@ #define MAX_ZOOM 20.0 #define DISPLAY_DPI_DEFAULT 96.0 #define MIN_ZOOM 0.2 +#define RESIZE_MARGIN 6.0 #define VBOX_MAIN_NITEMS 5 // number of interface items in vboxMain @@ -72,6 +78,7 @@ typedef struct Brush { int thickness_no; double thickness; int tool_options; + gboolean ruler, recognizer, variable_width; } Brush; #define COLOR_BLACK 0 @@ -127,6 +134,7 @@ typedef struct Item { struct Brush brush; // the brush to use, if ITEM_STROKE // 'brush" also contains color info for text items GnomeCanvasPoints *path; + gdouble *widths; GnomeCanvasItem *canvas_item; // the corresponding canvas item, or NULL struct BBox bbox; struct UndoErasureData *erasure; // for temporary use during erasures @@ -161,6 +169,8 @@ typedef struct Item { #define ITEM_TEMP_TEXT 19 #define ITEM_TEXT_EDIT 20 #define ITEM_TEXT_ATTRIB 21 +#define ITEM_RESIZESEL 22 +#define ITEM_RECOGNIZER 23 typedef struct Layer { GList *items; // the items on the layer, from bottom to top @@ -188,6 +198,8 @@ typedef struct Selection { BBox bbox; // the rectangle bbox of the selection struct Layer *layer; // the layer on which the selection lives double anchor_x, anchor_y, last_x, last_y; // for selection motion + gboolean resizing_top, resizing_bottom, resizing_left, resizing_right; // for selection resizing + double new_x1, new_x2, new_y1, new_y2; // for selection resizing GnomeCanvasItem *canvas_item; // if the selection box is on screen GList *items; // the selected items (a list of struct Item) int move_pageno, orig_pageno; // if selection moves to a different page @@ -204,7 +216,6 @@ typedef struct UIData { int toolno[NUM_BUTTONS+1]; // the number of the currently selected tool struct Brush brushes[NUM_BUTTONS+1][NUM_STROKE_TOOLS]; // the current pen, eraser, hiliter struct Brush default_brushes[NUM_STROKE_TOOLS]; // the default ones - gboolean ruler[NUM_BUTTONS+1]; // whether each button is in ruler mode int linked_brush[NUM_BUTTONS+1]; // whether brushes are linked across buttons int cur_mapping; // the current button number for mappings gboolean use_erasertip; @@ -214,11 +225,15 @@ typedef struct UIData { struct Item *cur_item; // the item being drawn, or NULL int cur_item_type; GnomeCanvasPoints cur_path; // the path being drawn + gdouble *cur_widths; // width array for the path being drawn int cur_path_storage_alloc; + int cur_widths_storage_alloc; double zoom; // zoom factor, in pixels per pt gboolean use_xinput; // use input devices instead of core pointer gboolean allow_xinput; // allow use of xinput ? gboolean discard_corepointer; // discard core pointer events in XInput mode + gboolean pressure_sensitivity; // use pen pressure to control stroke width? + double width_minimum_multiplier, width_maximum_multiplier; // calibration for pressure sensitivity gboolean is_corestroke; // this stroke is painted with core pointer int screen_width, screen_height; // initial screen size, for XInput events double hand_refpt[2]; @@ -240,7 +255,6 @@ typedef struct UIData { gboolean print_ruling; // print the paper ruling ? int default_unit; // the default unit for paper sizes int startuptool; // the default tool at startup - gboolean startupruler; int zoom_step_increment; // the increment in the zoom dialog box double zoom_step_factor; // the multiplicative factor in zoom in/out double startup_zoom; @@ -257,6 +271,7 @@ typedef struct UIData { gboolean auto_save_prefs; // auto-save preferences ? gboolean shorten_menus; // shorten menus ? gchar *shorten_menu_items; // which items to hide + gboolean is_sel_cursor; // displaying a selection-related cursor } UIData; #define BRUSH_LINKED 0 @@ -273,15 +288,16 @@ typedef struct UndoErasureData { typedef struct UndoItem { int type; struct Item *item; // for ITEM_STROKE, ITEM_TEXT, ITEM_TEXT_EDIT, ITEM_TEXT_ATTRIB - struct Layer *layer; // for ITEM_STROKE, ITEM_ERASURE, ITEM_PASTE, ITEM_NEW_LAYER, ITEM_DELETE_LAYER, ITEM_MOVESEL, ITEM_TEXT, ITEM_TEXT_EDIT + struct Layer *layer; // for ITEM_STROKE, ITEM_ERASURE, ITEM_PASTE, ITEM_NEW_LAYER, ITEM_DELETE_LAYER, ITEM_MOVESEL, ITEM_TEXT, ITEM_TEXT_EDIT, ITEM_RECOGNIZER struct Layer *layer2; // for ITEM_DELETE_LAYER with val=-1, ITEM_MOVESEL struct Page *page; // for ITEM_NEW_BG_ONE/RESIZE, ITEM_NEW_PAGE, ITEM_NEW_LAYER, ITEM_DELETE_LAYER, ITEM_DELETE_PAGE - GList *erasurelist; // for ITEM_ERASURE - GList *itemlist; // for ITEM_MOVESEL, ITEM_PASTE, ITEM_REPAINTSEL + GList *erasurelist; // for ITEM_ERASURE, ITEM_RECOGNIZER + GList *itemlist; // for ITEM_MOVESEL, ITEM_PASTE, ITEM_REPAINTSEL, ITEM_RESIZESEL GList *auxlist; // for ITEM_REPAINTSEL (brushes), ITEM_MOVESEL (depths) struct Background *bg; // for ITEM_NEW_BG_ONE/RESIZE, ITEM_NEW_DEFAULT_BG int val; // for ITEM_NEW_PAGE, ITEM_NEW_LAYER, ITEM_DELETE_LAYER, ITEM_DELETE_PAGE - double val_x, val_y; // for ITEM_MOVESEL, ITEM_NEW_BG_RESIZE, ITEM_PAPER_RESIZE, ITEM_NEW_DEFAULT_BG, ITEM_TEXT_ATTRIB + double val_x, val_y; // for ITEM_MOVESEL, ITEM_NEW_BG_RESIZE, ITEM_PAPER_RESIZE, ITEM_NEW_DEFAULT_BG, ITEM_TEXT_ATTRIB, ITEM_RESIZESEL + double scaling_x, scaling_y; // for ITEM_RESIZESEL gchar *str; // for ITEM_TEXT_EDIT, ITEM_TEXT_ATTRIB struct Brush *brush; // for ITEM_TEXT_ATTRIB struct UndoItem *next; diff --git a/xournal.glade b/xournal.glade index 47f31a5..0b01c6e 100644 --- a/xournal.glade +++ b/xournal.glade @@ -990,6 +990,34 @@ </widget> </child> + <child> + <widget class="GtkSeparatorMenuItem" id="separator15"> + <property name="visible">True</property> + </widget> + </child> + + <child> + <widget class="GtkCheckMenuItem" id="toolsReco"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Shape Recognizer</property> + <property name="use_underline">True</property> + <property name="active">False</property> + <signal name="toggled" handler="on_toolsReco_activate" last_modification_time="Fri, 21 Mar 2008 16:36:05 GMT"/> + <accelerator key="S" modifiers="GDK_CONTROL_MASK | GDK_SHIFT_MASK" signal="activate"/> + </widget> + </child> + + <child> + <widget class="GtkCheckMenuItem" id="toolsRuler"> + <property name="visible">True</property> + <property name="label" translatable="yes">Ru_ler</property> + <property name="use_underline">True</property> + <property name="active">False</property> + <signal name="toggled" handler="on_toolsRuler_activate" last_modification_time="Thu, 01 Dec 2005 20:54:08 GMT"/> + <accelerator key="L" modifiers="GDK_CONTROL_MASK | GDK_SHIFT_MASK" signal="activate"/> + </widget> + </child> + <child> <widget class="GtkSeparatorMenuItem" id="separator9"> <property name="visible">True</property> @@ -1483,28 +1511,11 @@ <child> <widget class="GtkMenuItem" id="toolsSetAsDefault"> <property name="visible">True</property> - <property name="label" translatable="yes">_Set As Default</property> + <property name="label" translatable="yes">Set As Default</property> <property name="use_underline">True</property> <signal name="activate" handler="on_toolsSetAsDefault_activate" last_modification_time="Thu, 01 Dec 2005 05:36:05 GMT"/> </widget> </child> - - <child> - <widget class="GtkSeparatorMenuItem" id="separator15"> - <property name="visible">True</property> - </widget> - </child> - - <child> - <widget class="GtkCheckMenuItem" id="toolsRuler"> - <property name="visible">True</property> - <property name="label" translatable="yes">Ru_ler</property> - <property name="use_underline">True</property> - <property name="active">False</property> - <signal name="toggled" handler="on_toolsRuler_activate" last_modification_time="Thu, 01 Dec 2005 20:54:08 GMT"/> - <accelerator key="L" modifiers="GDK_CONTROL_MASK | GDK_SHIFT_MASK" signal="activate"/> - </widget> - </child> </widget> </child> </widget> @@ -1549,6 +1560,16 @@ </widget> </child> + <child> + <widget class="GtkCheckMenuItem" id="optionsPressureSensitive"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Pressure sensitivity</property> + <property name="use_underline">True</property> + <property name="active">False</property> + <signal name="activate" handler="on_optionsPressureSensitive_activate" last_modification_time="Fri, 21 Mar 2008 19:00:32 GMT"/> + </widget> + </child> + <child> <widget class="GtkMenuItem" id="button2_mapping"> <property name="visible">True</property> @@ -2413,6 +2434,25 @@ </packing> </child> + <child> + <widget class="GtkToggleToolButton" id="buttonReco"> + <property name="visible">True</property> + <property name="tooltip" translatable="yes">Shape Recognizer</property> + <property name="label" translatable="yes">Shape Recognizer</property> + <property name="use_underline">True</property> + <property name="icon">shapes.png</property> + <property name="visible_horizontal">True</property> + <property name="visible_vertical">True</property> + <property name="is_important">False</property> + <property name="active">False</property> + <signal name="toggled" handler="on_toolsReco_activate" last_modification_time="Thu, 08 Dec 2005 20:49:10 GMT"/> + </widget> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + <child> <widget class="GtkToggleToolButton" id="buttonRuler"> <property name="visible">True</property> -- 2.39.5