-Denis Auroux (auroux@math.mit.edu)
+- Denis Auroux (auroux@math.mit.edu)
+- various patch contributors as noted in the ChangeLog
+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
-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
-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
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
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>
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
<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
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,
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
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.
</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>
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>
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>
+++ /dev/null
-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 */
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@
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 ?
- 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
- 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.
- 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)
# include <config.h>
#endif
+#include <sys/stat.h>
#include <string.h>
#include <gtk/gtk.h>
#include <libgnomecanvas/libgnomecanvas.h>
#include "xo-callbacks.h"
#include "xo-misc.h"
#include "xo-file.h"
+#include "xo-paint.h"
+#include "xo-shapes.h"
GtkWidget *winMain;
GnomeCanvas *canvas;
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;
g_memmove(ui.default_brushes+i, &(ui.brushes[0][i]), sizeof(struct Brush));
ui.cur_mapping = 0;
+
+ reset_recognizer();
// initialize various interface elements
#include <time.h>
#include <libgnomeprintui/gnome-print-dialog.h>
#include <glib/gstdio.h>
+#include <gdk/gdkkeysyms.h>
#include "xournal.h"
#include "xo-callbacks.h"
#include "xo-file.h"
#include "xo-paint.h"
#include "xo-print.h"
+#include "xo-shapes.h"
void
on_fileNew_activate (GtkMenuItem *menuitem,
on_filePrint_activate (GtkMenuItem *menuitem,
gpointer user_data)
{
- GtkWidget *printDialog, *preview;
+ GtkWidget *printDialog;
GnomePrintJob *gpj;
int fromPage, toPage;
int response;
char *filename, *in_fn;
char stime[30];
time_t curtime;
- int response;
gboolean warn;
end_text();
struct Background *tmp_bg;
double tmp_x, tmp_y;
gchar *tmpstr;
+ GnomeCanvasGroup *group;
end_text();
reset_focus();
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
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;
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);
struct Layer *l;
double tmp_x, tmp_y;
gchar *tmpstr;
+ GnomeCanvasGroup *group;
end_text();
reset_focus();
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);
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;
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);
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();
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();
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();
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();
end_text();
reset_focus();
ui.toolno[0] = TOOL_SELECTRECT;
- ui.ruler[0] = FALSE;
update_mapping_linkings(-1);
update_tool_buttons();
update_tool_menu();
reset_focus();
reset_selection();
ui.toolno[0] = TOOL_VERTSPACE;
- ui.ruler[0] = FALSE;
update_mapping_linkings(-1);
update_tool_buttons();
update_tool_menu();
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();
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();
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();
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;
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));
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]);
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();
}
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();
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))
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
on_viewShowLayer_activate(NULL, NULL);
- return;
+ return FALSE;
}
// switch mappings if needed
}
}
- // 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)
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();
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;
}
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;
}
{
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();
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;
}
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);
}
}
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;
}
}
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
reset_focus();
reset_selection();
ui.toolno[0] = TOOL_HAND;
- ui.ruler[0] = FALSE;
update_mapping_linkings(-1);
update_tool_buttons();
update_tool_menu();
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();
+}
+
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);
#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"
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");
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")) {
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
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")) {
// 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;
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));
}
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();
// 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) {
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;
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;
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++)
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]));
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));
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)?
" 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));
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)?
" 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));
{
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;
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);
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));
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)
}
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)
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);
GtkWidget *toolsEraser;
GtkWidget *toolsHighlighter;
GtkWidget *toolsText;
+ GtkWidget *separator15;
+ GtkWidget *toolsReco;
+ GtkWidget *toolsRuler;
GtkWidget *separator9;
GtkWidget *toolsSelectRegion;
GtkWidget *toolsSelectRectangle;
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;
GtkWidget *buttonEraser;
GtkWidget *buttonHighlighter;
GtkWidget *buttonText;
+ GtkWidget *buttonReco;
GtkWidget *buttonRuler;
GtkWidget *toolitem15;
GtkWidget *vseparator5;
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);
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);
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);
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");
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);
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);
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);
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);
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");
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");
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");
#include "xo-misc.h"
#include "xo-file.h"
#include "xo-paint.h"
+#include "xo-shapes.h"
// some global constants
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)
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 */
}
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);
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);
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);
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);
}
#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;
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,
}
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();
}
}
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)
{
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:
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) {
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;
{
GtkWidget *dialog;
GtkResponseType response;
- GList *pagelist;
if (ui.saved) return TRUE;
dialog = gtk_message_dialog_new(GTK_WINDOW (winMain), GTK_DIALOG_DESTROY_WITH_PARENT,
gtk_widget_grab_focus(ui.cur_item->widget);
else
gtk_widget_grab_focus(GTK_WIDGET(canvas));
+ reset_recognizer();
}
// selection / clipboard stuff
}
}
+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
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();
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);
// 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);
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
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) {
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 *************/
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);
}
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);
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)
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)
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();
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;
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--;
void finalize_erasure(void)
{
GList *itemlist, *partlist;
- struct Item *item, *part;
+ struct Item *item;
prepare_new_undo();
undo->type = ITEM_ERASURE;
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];
}
}
+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();
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)
{
+ 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
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);
GtkSelectionData *sel_data;
unsigned char *p;
int nitems, npts, i, len;
- GList *list;
struct Item *item;
double hoffset, voffset, cx, cy;
double *pf;
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);
}
GList *itemlist;
struct Item *item;
struct Brush *brush;
+ GnomeCanvasGroup *group;
if (ui.selection == NULL) return;
prepare_new_undo();
// 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);
+ }
+ }
}
}
GList *itemlist;
struct Item *item;
struct Brush *brush;
+ GnomeCanvasGroup *group;
if (ui.selection == NULL) return;
prepare_new_undo();
// 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);
+ }
+ }
}
}
void start_text(GdkEvent *event, struct Item *item)
{
double pt[2];
- gchar *text;
GtkTextBuffer *buffer;
GnomeCanvasItem *canvas_item;
PangoFontDescription *font_desc;
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)
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);
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);
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;
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;
// 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;
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;
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);
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;
}
}
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;
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 ");
}
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);
GList *pglist;
struct Page *pg;
char *buf;
- unsigned int len;
+ gsize len;
gboolean annot, uses_pdf;
gboolean use_hiliter;
struct PdfInfo pdfinfo;
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);
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;
--- /dev/null
+#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();
+ }
+ }
+}
+
--- /dev/null
+// #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);
#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
#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
int thickness_no;
double thickness;
int tool_options;
+ gboolean ruler, recognizer, variable_width;
} Brush;
#define COLOR_BLACK 0
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
#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
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
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;
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];
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;
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
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;
</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>
<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>
</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>
</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>