]> git.donarmstrong.com Git - xournal.git/commitdiff
Update to version 0.4.2.
authorauroux <auroux>
Tue, 25 Mar 2008 04:41:19 +0000 (04:41 +0000)
committerauroux <auroux>
Tue, 25 Mar 2008 04:41:19 +0000 (04:41 +0000)
26 files changed:
AUTHORS
ChangeLog
NEWS
README
configure.in
html-doc/manual.html
linuxwacom-0.7.0-rotate-patch [deleted file]
pixmaps/shapes.png [new file with mode: 0644]
src/Makefile.am
src/TODO
src/main.c
src/xo-callbacks.c
src/xo-callbacks.h
src/xo-file.c
src/xo-file.h
src/xo-interface.c
src/xo-misc.c
src/xo-misc.h
src/xo-paint.c
src/xo-paint.h
src/xo-print.c
src/xo-print.h
src/xo-shapes.c [new file with mode: 0644]
src/xo-shapes.h [new file with mode: 0644]
src/xournal.h
xournal.glade

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