From f942466ba4d2ef356c296487ade75d495af31b96 Mon Sep 17 00:00:00 2001
From: Christophe Geuzaine <cgeuzaine@ulg.ac.be>
Date: Fri, 11 Mar 2005 08:56:38 +0000
Subject: [PATCH] - cleaned up (somewhat) + finished the euler
 angles/quaternion handling

- added a new little dialog in gui to specify the rotations/scales/translations by
  hand. This is similar to what I did in the Motif version of Gmsh a looooong time
  ago, and I was really missing this capability... (e.g. being able to specify
  exact rotation angles)
---
 Common/Context.cpp         | 84 +++++++++++++++++---------------
 Common/Context.h           | 19 ++++----
 Common/DefaultOptions.h    |  4 ++
 Common/Makefile            | 15 +++---
 Common/Options.cpp         | 56 ++++++++++++++++++++-
 Common/Options.h           |  2 +
 Fltk/Callbacks.cpp         | 58 ++++++++++++++--------
 Fltk/Callbacks.h           |  5 ++
 Fltk/GUI.cpp               | 99 ++++++++++++++++++++++++++++++++++++--
 Fltk/GUI.h                 |  6 +++
 Fltk/Message.cpp           |  4 +-
 Fltk/Opengl.cpp            | 30 +-----------
 Fltk/Opengl_Window.cpp     | 71 +++++++++++++++------------
 Geo/MinMax.cpp             | 26 ++--------
 Graphics/Draw.cpp          | 22 +--------
 Graphics/Draw.h            |  4 --
 doc/VERSIONS               |  6 +--
 doc/texinfo/shortcuts.texi |  2 +
 18 files changed, 325 insertions(+), 188 deletions(-)

diff --git a/Common/Context.cpp b/Common/Context.cpp
index ea0d07974c..29a92dedc8 100644
--- a/Common/Context.cpp
+++ b/Common/Context.cpp
@@ -1,4 +1,4 @@
-// $Id: Context.cpp,v 1.54 2005-01-01 19:35:27 geuzaine Exp $
+// $Id: Context.cpp,v 1.55 2005-03-11 08:56:37 geuzaine Exp $
 //
 // Copyright (C) 1997-2005 C. Geuzaine, J.-F. Remacle
 //
@@ -21,48 +21,15 @@
 
 #include "Gmsh.h"
 #include "Numeric.h"
-#include "Geo.h"
-#include "Mesh.h"
-#include "Draw.h"
 #include "Context.h"
-#include "Options.h"
 #include "DefaultOptions.h"
 #include "Trackball.h"
 
-/*
-00 01 02 03    0  1  2  3
-10 11 12 13    4  5  6  7
-20 21 22 23    8  9  10 11
-30 31 32 33    12 13 14 15
-*/
-
-void Context_T::buildRotmatrix(void)
+void Context_T::buildRotationMatrix(void)
 {
   if(useTrackball) {
     build_rotmatrix(rot, quaternion);
-
-    // get Euler angles from rotation matrix
-    r[1] = asin(rot[8]); // Calculate Y-axis angle
-    double C =  cos(r[1]);
-    r[1] *=  180. / Pi;
-    if(fabs(C) > 0.005){ // Gimball lock?
-      double tmpx =  rot[10] / C; // No, so get X-axis angle
-      double tmpy = -rot[9] / C;
-      r[0] = atan2(tmpy, tmpx) * 180. / Pi;
-      tmpx =  rot[0] / C; // Get Z-axis angle
-      tmpy = -rot[4] / C;
-      r[2] = atan2(tmpy, tmpx) * 180. / Pi;
-    }
-    else{ // Gimball lock has occurred
-      r[0] = 0.; // Set X-axis angle to zero
-      double tmpx = rot[5]; // And calculate Z-axis angle
-      double tmpy = rot[1];
-      r[2] = atan2(tmpy, tmpx) * 180. / Pi;
-    }
-    // return only positive angles in [0,360]
-    if(r[0] < 0.) r[0] += 360.;
-    if(r[1] < 0.) r[1] += 360.;
-    if(r[2] < 0.) r[2] += 360.;
+    setEulerAnglesFromRotationMatrix();
   }
   else {
     double x = r[0] * Pi / 180.;
@@ -80,9 +47,7 @@ void Context_T::buildRotmatrix(void)
     rot[4] =-C*F; rot[5] =-BD*F+A*E; rot[6] = AD*F+B*E; rot[7] = 0.;
     rot[8] = D;   rot[9] =-B*C;	     rot[10] = A*C;     rot[11] = 0.;
     rot[12] = 0.; rot[13] = 0.;      rot[14] = 0.;      rot[15] = 1.;
-
-    // get the quaternion from the Euler angles
-    // todo
+    setQuaternionFromEulerAngles();
   }
 }
 
@@ -100,3 +65,44 @@ void Context_T::setQuaternion(double q0, double q1, double q2, double q3)
   quaternion[2] = q2;
   quaternion[3] = q3;
 }
+
+void Context_T::setQuaternionFromEulerAngles()
+{
+  double x = r[0] * Pi / 180.;
+  double y = r[1] * Pi / 180.;
+  double z = r[2] * Pi / 180.;
+  double xx[3] = {1.,0.,0.};
+  double yy[3] = {0.,1.,0.};
+  double zz[3] = {0.,0.,1.};
+  double q1[4], q2[4], q3[4], tmp[4];
+  axis_to_quat(xx, -x, q1);
+  axis_to_quat(yy, -y, q2);
+  axis_to_quat(zz, -z, q3);
+  add_quats(q1, q2, tmp);
+  add_quats(tmp, q3, quaternion);
+}
+
+void Context_T::setEulerAnglesFromRotationMatrix()
+{
+  r[1] = asin(rot[8]); // Calculate Y-axis angle
+  double C =  cos(r[1]);
+  r[1] *=  180. / Pi;
+  if(fabs(C) > 0.005){ // Gimball lock?
+    double tmpx =  rot[10] / C; // No, so get X-axis angle
+    double tmpy = -rot[9] / C;
+    r[0] = atan2(tmpy, tmpx) * 180. / Pi;
+    tmpx =  rot[0] / C; // Get Z-axis angle
+    tmpy = -rot[4] / C;
+    r[2] = atan2(tmpy, tmpx) * 180. / Pi;
+  }
+  else{ // Gimball lock has occurred
+    r[0] = 0.; // Set X-axis angle to zero
+    double tmpx = rot[5]; // And calculate Z-axis angle
+    double tmpy = rot[1];
+    r[2] = atan2(tmpy, tmpx) * 180. / Pi;
+  }
+  // return only positive angles in [0,360]
+  if(r[0] < 0.) r[0] += 360.;
+  if(r[1] < 0.) r[1] += 360.;
+  if(r[2] < 0.) r[2] += 360.;
+}
diff --git a/Common/Context.h b/Common/Context.h
index a1d7ffe10a..b606bf9516 100644
--- a/Common/Context.h
+++ b/Common/Context.h
@@ -78,6 +78,7 @@ public :
   int opt_position[2];        // position of the option window on the screen
   int vis_position[2];        // position of the visibility window on the screen
   int clip_position[2];       // position of the clipping planes window on the screen
+  int manip_position[2];      // position of the manipulator window on the screen
   int stat_position[2];       // position of the statistics window on the screen
   int ctx_position[2];        // position of the geo/mesh context windows on the screen
   int solver_position[2];     // position of the solver windows on the screen
@@ -90,12 +91,10 @@ public :
   int nopopup;                // never popup dialogs in scripts (use default values instead)
 
   double rot[16];             // current rotation matrix 
-  double r[3];                // position angles (if succ. rot. along x, y and z) 
+  double r[3];                // current Euler angles (in degrees!) 
   double t[3], s[3];          // current translation and scale 
   double clip_factor;         // clipping plane distance factor
-  int rlock[3], tlock[3], slock[3];
-                              // locks for r, t and s 
-  double quaternion[4];       // the actual quaternion used for "trackball" rotating 
+  double quaternion[4];       // current quaternion used for "trackball" rotation
   int useTrackball;           // do or do not use the trackball for rotations 
   double rotation_center[3];  // point around which to rotate the scene
   int rotation_center_cg;     // rotate around the center of mass instead of rotation_center[]
@@ -103,8 +102,8 @@ public :
   double max[3];              // x, y and z max for the current geometry 
   double cg[3];               // "center of mass" of the current geometry
   double range[3];            // maximum range in the three directions 
-  double lc, lc_middle;       // characteristic lengths for the whole problem, 
-  double lc_order;            // and never used in mesh generation (->only for geo/post) 
+  double lc;                  // characteristic length for the whole problem (never
+                              // used in mesh generation ->only for geo/post) 
 
   int db;                     // double buffer? 
   int ortho;                  // orthogonal projection? 
@@ -240,9 +239,11 @@ public :
   } color;
   
   // trackball functions 
-  void buildRotmatrix(void);
-  void setQuaternion (double p1x, double p1y, double p2x, double p2y);
-  void addQuaternion (double p1x, double p1y, double p2x, double p2y);
+  void buildRotationMatrix(void);
+  void setQuaternion(double p1x, double p1y, double p2x, double p2y);
+  void addQuaternion(double p1x, double p1y, double p2x, double p2y);
+  void setQuaternionFromEulerAngles(void);
+  void setEulerAnglesFromRotationMatrix(void);
 };
 
 #endif
diff --git a/Common/DefaultOptions.h b/Common/DefaultOptions.h
index c363e089b6..4871a25fab 100644
--- a/Common/DefaultOptions.h
+++ b/Common/DefaultOptions.h
@@ -571,6 +571,10 @@ StringXNumber GeneralOptions_Number[] = {
   { F|O, "LineWidth" , opt_general_line_width , 1.0 , 
     "Display width of lines (in pixels)" },
 
+  { F|S, "ManipulatorPositionX" , opt_general_manip_position0 , 650. , 
+    "Horizontal position (in pixels) of the upper left corner of the manipulator window" }, 
+  { F|S, "ManipulatorPositionY" , opt_general_manip_position1 , 150. , 
+    "Vertical position (in pixels) of the upper left corner of the manipulator window" }, 
   { F|S, "MenuPositionX" , opt_general_menu_position0 , 800. , 
     "Horizontal position (in pixels) of the upper left corner of the menu window" }, 
   { F|S, "MenuPositionY" , opt_general_menu_position1 , 50. ,
diff --git a/Common/Makefile b/Common/Makefile
index 29191811a2..aea51f29d4 100644
--- a/Common/Makefile
+++ b/Common/Makefile
@@ -1,4 +1,4 @@
-# $Id: Makefile,v 1.74 2005-02-20 06:36:52 geuzaine Exp $
+# $Id: Makefile,v 1.75 2005-03-11 08:56:37 geuzaine Exp $
 #
 # Copyright (C) 1997-2005 C. Geuzaine, J.-F. Remacle
 #
@@ -64,13 +64,12 @@ depend:
 # DO NOT DELETE THIS LINE
 Context.o: Context.cpp Gmsh.h Message.h ../DataStr/Malloc.h \
   ../DataStr/List.h ../DataStr/Tree.h ../DataStr/avl.h ../DataStr/Tools.h \
-  ../Numeric/Numeric.h ../Geo/Geo.h ../Mesh/Mesh.h ../Mesh/Vertex.h \
-  ../Mesh/Element.h ../Mesh/Simplex.h ../Mesh/Face.h ../Mesh/Edge.h \
-  ../Geo/ExtrudeParams.h ../Mesh/DiscreteSurface.h \
-  ../Common/VertexArray.h ../Common/SmoothNormals.h ../Mesh/Metric.h \
-  ../Mesh/Matrix.h ../Graphics/Draw.h ../Common/Views.h \
-  ../Common/ColorTable.h ../Common/GmshMatrix.h ../Common/AdaptiveViews.h \
-  Context.h Options.h DefaultOptions.h Trackball.h
+  ../Numeric/Numeric.h Context.h DefaultOptions.h Options.h \
+  ../Mesh/Mesh.h ../Mesh/Vertex.h ../Mesh/Element.h ../Mesh/Simplex.h \
+  ../Mesh/Face.h ../Mesh/Edge.h ../Geo/ExtrudeParams.h \
+  ../Mesh/DiscreteSurface.h ../Common/VertexArray.h \
+  ../Common/SmoothNormals.h ../Mesh/Metric.h ../Mesh/Matrix.h Views.h \
+  ColorTable.h GmshMatrix.h AdaptiveViews.h Trackball.h
 AdaptiveViews.o: AdaptiveViews.cpp AdaptiveViews.h ../DataStr/List.h \
   GmshMatrix.h ../Plugin/Plugin.h ../Common/Options.h ../Common/Message.h \
   ../Common/Views.h ../Common/ColorTable.h ../Common/VertexArray.h \
diff --git a/Common/Options.cpp b/Common/Options.cpp
index 866a7c7b82..12e17092a0 100644
--- a/Common/Options.cpp
+++ b/Common/Options.cpp
@@ -1,4 +1,4 @@
-// $Id: Options.cpp,v 1.228 2005-03-11 05:47:54 geuzaine Exp $
+// $Id: Options.cpp,v 1.229 2005-03-11 08:56:37 geuzaine Exp $
 //
 // Copyright (C) 1997-2005 C. Geuzaine, J.-F. Remacle
 //
@@ -2149,6 +2149,20 @@ double opt_general_clip_position1(OPT_ARGS_NUM)
   return CTX.clip_position[1];
 }
 
+double opt_general_manip_position0(OPT_ARGS_NUM)
+{
+  if(action & GMSH_SET)
+    CTX.manip_position[0] = (int)val;
+  return CTX.manip_position[0];
+}
+
+double opt_general_manip_position1(OPT_ARGS_NUM)
+{
+  if(action & GMSH_SET)
+    CTX.manip_position[1] = (int)val;
+  return CTX.manip_position[1];
+}
+
 double opt_general_visibility_mode(OPT_ARGS_NUM)
 {
   if(action & GMSH_SET)
@@ -2253,6 +2267,10 @@ double opt_general_quaternion0(OPT_ARGS_NUM)
 {
   if(action & GMSH_SET)
     CTX.quaternion[0] = val;
+#if defined(HAVE_FLTK)
+  if(WID && (action & GMSH_GUI))
+    WID->update_manip_window();
+#endif
   return CTX.quaternion[0];
 }
 
@@ -2260,6 +2278,10 @@ double opt_general_quaternion1(OPT_ARGS_NUM)
 {
   if(action & GMSH_SET)
     CTX.quaternion[1] = val;
+#if defined(HAVE_FLTK)
+  if(WID && (action & GMSH_GUI))
+    WID->update_manip_window();
+#endif
   return CTX.quaternion[1];
 }
 
@@ -2267,6 +2289,10 @@ double opt_general_quaternion2(OPT_ARGS_NUM)
 {
   if(action & GMSH_SET)
     CTX.quaternion[2] = val;
+#if defined(HAVE_FLTK)
+  if(WID && (action & GMSH_GUI))
+    WID->update_manip_window();
+#endif
   return CTX.quaternion[2];
 }
 
@@ -2274,6 +2300,10 @@ double opt_general_quaternion3(OPT_ARGS_NUM)
 {
   if(action & GMSH_SET)
     CTX.quaternion[3] = val;
+#if defined(HAVE_FLTK)
+  if(WID && (action & GMSH_GUI))
+    WID->update_manip_window();
+#endif
   return CTX.quaternion[3];
 }
 
@@ -2281,6 +2311,10 @@ double opt_general_translation0(OPT_ARGS_NUM)
 {
   if(action & GMSH_SET)
     CTX.t[0] = val;
+#if defined(HAVE_FLTK)
+  if(WID && (action & GMSH_GUI))
+    WID->update_manip_window();
+#endif
   return CTX.t[0];
 }
 
@@ -2288,6 +2322,10 @@ double opt_general_translation1(OPT_ARGS_NUM)
 {
   if(action & GMSH_SET)
     CTX.t[1] = val;
+#if defined(HAVE_FLTK)
+  if(WID && (action & GMSH_GUI))
+    WID->update_manip_window();
+#endif
   return CTX.t[1];
 }
 
@@ -2295,6 +2333,10 @@ double opt_general_translation2(OPT_ARGS_NUM)
 {
   if(action & GMSH_SET)
     CTX.t[2] = val;
+#if defined(HAVE_FLTK)
+  if(WID && (action & GMSH_GUI))
+    WID->update_manip_window();
+#endif
   return CTX.t[2];
 }
 
@@ -2302,6 +2344,10 @@ double opt_general_scale0(OPT_ARGS_NUM)
 {
   if(action & GMSH_SET)
     CTX.s[0] = val ? val : 1.0;
+#if defined(HAVE_FLTK)
+  if(WID && (action & GMSH_GUI))
+    WID->update_manip_window();
+#endif
   return CTX.s[0];
 }
 
@@ -2309,6 +2355,10 @@ double opt_general_scale1(OPT_ARGS_NUM)
 {
   if(action & GMSH_SET)
     CTX.s[1] = val ? val : 1.0;
+#if defined(HAVE_FLTK)
+  if(WID && (action & GMSH_GUI))
+    WID->update_manip_window();
+#endif
   return CTX.s[1];
 }
 
@@ -2316,6 +2366,10 @@ double opt_general_scale2(OPT_ARGS_NUM)
 {
   if(action & GMSH_SET)
     CTX.s[2] = val ? val : 1.0;
+#if defined(HAVE_FLTK)
+  if(WID && (action & GMSH_GUI))
+    WID->update_manip_window();
+#endif
   return CTX.s[2];
 }
 
diff --git a/Common/Options.h b/Common/Options.h
index 897fb36e71..507ac7b367 100644
--- a/Common/Options.h
+++ b/Common/Options.h
@@ -234,6 +234,8 @@ double opt_general_visibility_position0(OPT_ARGS_NUM);
 double opt_general_visibility_position1(OPT_ARGS_NUM);
 double opt_general_clip_position0(OPT_ARGS_NUM);
 double opt_general_clip_position1(OPT_ARGS_NUM);
+double opt_general_manip_position0(OPT_ARGS_NUM);
+double opt_general_manip_position1(OPT_ARGS_NUM);
 double opt_general_visibility_mode(OPT_ARGS_NUM);
 double opt_general_session_save(OPT_ARGS_NUM);
 double opt_general_options_save(OPT_ARGS_NUM);
diff --git a/Fltk/Callbacks.cpp b/Fltk/Callbacks.cpp
index 78a6759fc2..7682ff6991 100644
--- a/Fltk/Callbacks.cpp
+++ b/Fltk/Callbacks.cpp
@@ -1,4 +1,4 @@
-// $Id: Callbacks.cpp,v 1.340 2005-03-11 05:47:54 geuzaine Exp $
+// $Id: Callbacks.cpp,v 1.341 2005-03-11 08:56:37 geuzaine Exp $
 //
 // Copyright (C) 1997-2005 C. Geuzaine, J.-F. Remacle
 //
@@ -183,42 +183,36 @@ void window_cb(CALLBACK_ARGS)
 
 void status_xyz1p_cb(CALLBACK_ARGS)
 {
-  extern void set_r(int i, double val);
-  extern void set_t(int i, double val);
-  extern void set_s(int i, double val);
-
   switch ((long)data) {
   case 0:
     if(CTX.useTrackball)
       CTX.setQuaternion(0., -1. / sqrt(2.), 0., 1. / sqrt(2.));
-    set_r(0, 0.);
-    set_r(1, 90.);
-    set_r(2, 0.);
+    CTX.r[0] = 0.;
+    CTX.r[1] = 90.;
+    CTX.r[2] = 0.;
     Draw();
     break;
   case 1:
     if(CTX.useTrackball)
       CTX.setQuaternion(1. / sqrt(2.), 0., 0., 1. / sqrt(2.));
-    set_r(0, -90.);
-    set_r(1, 0.);
-    set_r(2, 0.);
+    CTX.r[0] = -90.;
+    CTX.r[1] = 0.;
+    CTX.r[2] = 0.;
     Draw();
     break;
+  case 6:
+    CTX.t[0] = CTX.t[1] = CTX.t[2] = 0.;
+    CTX.s[0] = CTX.s[1] = CTX.s[2] = 1.;
+    // fall-through
   case 2:
     if(CTX.useTrackball)
       CTX.setQuaternion(0., 0., 0., 1.);
-    set_r(0, 0.);
-    set_r(1, 0.);
-    set_r(2, 0.);
+    CTX.r[0] = CTX.r[1] = CTX.r[2] = 0.;
     Draw();
     break;
   case 3:
-    set_t(0, 0.);
-    set_t(1, 0.);
-    set_t(2, 0.);
-    set_s(0, 1.);
-    set_s(1, 1.);
-    set_s(2, 1.);
+    CTX.t[0] = CTX.t[1] = CTX.t[2] = 0.;
+    CTX.s[0] = CTX.s[1] = CTX.s[2] = 1.;
     Draw();
     break;
   case 4:
@@ -231,6 +225,7 @@ void status_xyz1p_cb(CALLBACK_ARGS)
     WID->create_message_window();
     break;
   }
+  WID->update_manip_window();
 }
 
 static int stop_anim, view_in_cycle = -1;
@@ -1248,6 +1243,28 @@ void clip_reset_cb(CALLBACK_ARGS)
   Draw();
 }
 
+// Manipulator menu
+
+void manip_cb(CALLBACK_ARGS)
+{
+  WID->create_manip_window();
+}
+
+void manip_update_cb(CALLBACK_ARGS)
+{
+  CTX.r[0] = WID->manip_value[0]->value();
+  CTX.r[1] = WID->manip_value[1]->value();
+  CTX.r[2] = WID->manip_value[2]->value();
+  CTX.t[0] = WID->manip_value[3]->value();
+  CTX.t[1] = WID->manip_value[4]->value();
+  CTX.t[2] = WID->manip_value[5]->value();
+  CTX.s[0] = WID->manip_value[6]->value();
+  CTX.s[1] = WID->manip_value[7]->value();
+  CTX.s[2] = WID->manip_value[8]->value();
+  CTX.setQuaternionFromEulerAngles();
+  Draw();
+}
+
 // Help Menu (if you change the following, please also change the
 // texinfo documentation in doc/texinfo/shortcuts.texi)
 
@@ -1297,6 +1314,7 @@ void help_short_cb(CALLBACK_ARGS)
   Msg(DIRECT, "  Ctrl+s        Save file as");
   Msg(DIRECT, " ");
   Msg(DIRECT, "  Shift+Ctrl+c  Show clipping plane window");
+  Msg(DIRECT, "  Shift+Ctrl+m  Show manipulator window"); 
   Msg(DIRECT, "  Shift+Ctrl+n  Show option window"); 
   Msg(DIRECT, "  Shift+Ctrl+o  Merge file(s)"); 
   Msg(DIRECT, "  Shift+Ctrl+s  Save mesh in default format");
diff --git a/Fltk/Callbacks.h b/Fltk/Callbacks.h
index cf635fe910..01985c9ef8 100644
--- a/Fltk/Callbacks.h
+++ b/Fltk/Callbacks.h
@@ -166,6 +166,11 @@ void clip_invert_cb(CALLBACK_ARGS);
 void clip_num_cb(CALLBACK_ARGS);
 void clip_reset_cb(CALLBACK_ARGS);
 
+// Manipulator Menu
+
+void manip_cb(CALLBACK_ARGS);
+void manip_update_cb(CALLBACK_ARGS);
+
 // Help Menu
 
 void help_short_cb(CALLBACK_ARGS);
diff --git a/Fltk/GUI.cpp b/Fltk/GUI.cpp
index ffe7f6c390..9e388e9347 100644
--- a/Fltk/GUI.cpp
+++ b/Fltk/GUI.cpp
@@ -1,4 +1,4 @@
-// $Id: GUI.cpp,v 1.421 2005-03-11 05:47:55 geuzaine Exp $
+// $Id: GUI.cpp,v 1.422 2005-03-11 08:56:38 geuzaine Exp $
 //
 // Copyright (C) 1997-2005 C. Geuzaine, J.-F. Remacle
 //
@@ -111,7 +111,8 @@ Fl_Menu_Item m_menubar_table[] = {
   {"&Tools", 0, 0, 0, FL_SUBMENU},
     {"&Options...",      FL_CTRL+FL_SHIFT+'n', (Fl_Callback *)options_cb, 0},
     {"&Visibility",      FL_CTRL+FL_SHIFT+'v', (Fl_Callback *)visibility_cb, 0},
-    {"&Clipping Planes", FL_CTRL+FL_SHIFT+'c', (Fl_Callback *)clip_cb, 0, FL_MENU_DIVIDER},
+    {"&Clipping Planes", FL_CTRL+FL_SHIFT+'c', (Fl_Callback *)clip_cb, 0},
+    {"&Manipulator",     FL_CTRL+FL_SHIFT+'m', (Fl_Callback *)manip_cb, 0, FL_MENU_DIVIDER},
     {"S&tatistics",      FL_CTRL+'i', (Fl_Callback *)statistics_cb, 0},
     {"M&essage Console", FL_CTRL+'l', (Fl_Callback *)message_cb, 0},
     {0},
@@ -143,7 +144,8 @@ Fl_Menu_Item m_sys_menubar_table[] = {
   {"Tools",0,0,0,FL_SUBMENU},
     {"Options...",      FL_CTRL+FL_SHIFT+'n', (Fl_Callback *)options_cb, 0},
     {"Visibility",      FL_CTRL+FL_SHIFT+'v', (Fl_Callback *)visibility_cb, 0},
-    {"Clipping Planes", FL_CTRL+FL_SHIFT+'c', (Fl_Callback *)clip_cb, 0, FL_MENU_DIVIDER},
+    {"Clipping Planes", FL_CTRL+FL_SHIFT+'c', (Fl_Callback *)clip_cb, 0},
+    {"Manipulator",     FL_CTRL+FL_SHIFT+'m', (Fl_Callback *)manip_cb, 0, FL_MENU_DIVIDER},
     {"Statistics",      FL_CTRL+'i', (Fl_Callback *)statistics_cb, 0},
     {"Message Console", FL_CTRL+'l', (Fl_Callback *)message_cb, 0},
     {0},
@@ -804,6 +806,7 @@ GUI::GUI(int argc, char **argv)
   msg_window = NULL;
   vis_window = NULL;
   clip_window = NULL;
+  manip_window = NULL;
   about_window = NULL;
   context_geometry_window = NULL;
   context_mesh_window = NULL;
@@ -871,6 +874,7 @@ GUI::GUI(int argc, char **argv)
   create_statistics_window();
   create_visibility_window();
   create_clip_window();
+  create_manip_window();
   create_about_window();
   create_geometry_context_window(0);
   create_mesh_context_window(0);
@@ -3744,6 +3748,95 @@ void GUI::create_clip_window()
   clip_window->end();
 }
 
+// create the manipulator
+
+void GUI::update_manip_window(int force)
+{
+  if(force || manip_window->shown()){
+    double val1 = CTX.lc;
+    for(int i = 0; i < 3; i++){
+      manip_value[i]->value(CTX.r[i]);
+      manip_value[i]->minimum(-360.);
+      manip_value[i]->maximum(360.);
+      manip_value[i]->step(1.);
+
+      manip_value[i+3]->value(CTX.t[i]);
+      manip_value[i+3]->minimum(-val1);
+      manip_value[i+3]->maximum(val1);
+      manip_value[i+3]->step(val1/200.);
+
+      manip_value[i+6]->value(CTX.s[i]);
+      manip_value[i+6]->minimum(0.01);
+      manip_value[i+6]->maximum(100.);
+      manip_value[i+6]->step(0.01);
+    }
+  }
+}
+
+void GUI::create_manip_window()
+{
+  if(manip_window) {
+    update_manip_window(1);
+    manip_window->show();
+    return;
+  }
+
+  int width = 4 * BB + 2 * WB;
+  int height = 5 * BH + 3 * WB;
+
+  manip_window = new Dialog_Window(width, height, "Manipulator");
+  manip_window->box(GMSH_WINDOW_BOX);
+
+  Fl_Box *top[3], *left[3];
+  top[0] = new Fl_Box(WB + 1 * BB, 1 * WB + 0 * BH, BB, BH, "X");
+  top[1] = new Fl_Box(WB + 2 * BB, 1 * WB + 0 * BH, BB, BH, "Y");
+  top[2] = new Fl_Box(WB + 3 * BB, 1 * WB + 0 * BH, BB, BH, "Z");
+  left[0] = new Fl_Box(WB + 0 * BB, 1 * WB + 1 * BH, BB, BH, "Rotation");
+  left[1] = new Fl_Box(WB + 0 * BB, 1 * WB + 2 * BH, BB, BH, "Translation");
+  left[2] = new Fl_Box(WB + 0 * BB, 1 * WB + 3 * BH, BB, BH, "Scale");
+  for(int i = 0; i < 3; i++){  
+    top[i]->align(FL_ALIGN_INSIDE|FL_ALIGN_CENTER);
+    left[i]->align(FL_ALIGN_INSIDE|FL_ALIGN_CENTER);
+  }
+
+  manip_value[0] = new Fl_Value_Input(WB + 1 * BB, 1 * WB + 1 * BH, BB, BH);
+  manip_value[1] = new Fl_Value_Input(WB + 2 * BB, 1 * WB + 1 * BH, BB, BH);
+  manip_value[2] = new Fl_Value_Input(WB + 3 * BB, 1 * WB + 1 * BH, BB, BH);
+  manip_value[3] = new Fl_Value_Input(WB + 1 * BB, 1 * WB + 2 * BH, BB, BH);
+  manip_value[4] = new Fl_Value_Input(WB + 2 * BB, 1 * WB + 2 * BH, BB, BH);
+  manip_value[5] = new Fl_Value_Input(WB + 3 * BB, 1 * WB + 2 * BH, BB, BH);
+  manip_value[6] = new Fl_Value_Input(WB + 1 * BB, 1 * WB + 3 * BH, BB, BH);
+  manip_value[7] = new Fl_Value_Input(WB + 2 * BB, 1 * WB + 3 * BH, BB, BH);
+  manip_value[8] = new Fl_Value_Input(WB + 3 * BB, 1 * WB + 3 * BH, BB, BH);
+
+  for(int i = 0; i < 9; i++){
+    if(i < 3){
+      manip_value[i]->minimum(0.);
+      manip_value[i]->maximum(360.);
+      manip_value[i]->step(1.);
+    }
+    else if(i > 5){
+      manip_value[i]->minimum(0.1);
+      manip_value[i]->maximum(100.);
+      manip_value[i]->step(0.1);
+    }
+    manip_value[i]->align(FL_ALIGN_RIGHT);
+    manip_value[i]->callback(manip_update_cb);
+  }
+
+  {
+    Fl_Button *o = new Fl_Button(width - 2 * BB - 2 * WB, height - BH - WB, BB, BH, "Reset");
+    o->callback(status_xyz1p_cb, (void *)6);
+  }
+  {
+    Fl_Return_Button *o = new Fl_Return_Button(width - BB - WB, height - BH - WB, BB, BH, "Cancel");
+    o->callback(cancel_cb, (void *)manip_window);
+  }
+
+  manip_window->position(CTX.manip_position[0], CTX.manip_position[1]);
+  manip_window->end();
+}
+
 // Create the about window
 
 void GUI::create_about_window()
diff --git a/Fltk/GUI.h b/Fltk/GUI.h
index 6dd0834788..12c094fbb5 100644
--- a/Fltk/GUI.h
+++ b/Fltk/GUI.h
@@ -229,6 +229,10 @@ public:
   Fl_Multi_Browser *clip_browser ;
   Fl_Value_Input   *clip_value[4];
 
+  // manipulator window
+  Fl_Window        *manip_window ;
+  Fl_Value_Input   *manip_value[9] ;
+
   // about window
   Fl_Window        *about_window ;
 
@@ -264,6 +268,7 @@ public:
   void create_view_options_window(int numview);
   void create_visibility_window();
   void create_clip_window();
+  void create_manip_window();
   void create_statistics_window();
   void create_message_window();
   void create_about_window();
@@ -297,6 +302,7 @@ public:
   void reset_visibility();
   void reset_option_browser();
   void reset_clip_browser();
+  void update_manip_window(int force=0);
   void reset_external_view_list();
   int  selection, try_selection, quit_selection, end_selection, undo_selection;
 
diff --git a/Fltk/Message.cpp b/Fltk/Message.cpp
index 79c67fa072..352405214c 100644
--- a/Fltk/Message.cpp
+++ b/Fltk/Message.cpp
@@ -1,4 +1,4 @@
-// $Id: Message.cpp,v 1.63 2005-02-16 05:17:54 geuzaine Exp $
+// $Id: Message.cpp,v 1.64 2005-03-11 08:56:38 geuzaine Exp $
 //
 // Copyright (C) 1997-2005 C. Geuzaine, J.-F. Remacle
 //
@@ -243,6 +243,8 @@ void Exit(int level)
       CTX.vis_position[1] = WID->vis_window->y();
       CTX.clip_position[0] = WID->clip_window->x();
       CTX.clip_position[1] = WID->clip_window->y();
+      CTX.manip_position[0] = WID->manip_window->x();
+      CTX.manip_position[1] = WID->manip_window->y();
       CTX.ctx_position[0] = WID->context_geometry_window->x();
       CTX.ctx_position[1] = WID->context_geometry_window->y();
       CTX.solver_position[0] = WID->solver[0].window->x();
diff --git a/Fltk/Opengl.cpp b/Fltk/Opengl.cpp
index 58fb869c70..9b4b5a8ba8 100644
--- a/Fltk/Opengl.cpp
+++ b/Fltk/Opengl.cpp
@@ -1,4 +1,4 @@
-// $Id: Opengl.cpp,v 1.50 2005-03-11 05:47:55 geuzaine Exp $
+// $Id: Opengl.cpp,v 1.51 2005-03-11 08:56:38 geuzaine Exp $
 //
 // Copyright (C) 1997-2005 C. Geuzaine, J.-F. Remacle
 //
@@ -37,9 +37,6 @@ void Process_SelectionBuffer(int x, int y, int *n, GLuint * ii, GLuint * jj);
 void Filter_SelectionBuffer(int n, GLuint * typ, GLuint * ient,
                             Vertex ** thev, Curve ** thec, Surface ** thes,
                             Mesh * m);
-void myZoom(GLdouble X1, GLdouble X2, GLdouble Y1, GLdouble Y2, GLdouble Xc1,
-            GLdouble Xc2, GLdouble Yc1, GLdouble Yc2);
-
 // Draw specialization
 
 void InitOpengl(void)
@@ -200,31 +197,6 @@ void Draw_OnScreenMessages()
   }
 }
 
-// Euler angles set_XXX
-
-void set_r(int i, double val)
-{
-  if(!CTX.useTrackball) {
-    if(!CTX.rlock[i]) {
-      CTX.r[i] = val;
-    }
-  }
-}
-
-void set_t(int i, double val)
-{
-  if(!CTX.tlock[i]) {
-    CTX.t[i] = val;
-  }
-}
-
-void set_s(int i, double val)
-{
-  if(!CTX.slock[i]) {
-    CTX.s[i] = val;
-  }
-}
-
 // Select entity routines
 
 int check_type(int type, Vertex * v, Curve * c, Surface * s)
diff --git a/Fltk/Opengl_Window.cpp b/Fltk/Opengl_Window.cpp
index 27c9e5d8bb..a4ae8aa352 100644
--- a/Fltk/Opengl_Window.cpp
+++ b/Fltk/Opengl_Window.cpp
@@ -1,4 +1,4 @@
-// $Id: Opengl_Window.cpp,v 1.46 2005-03-09 02:18:40 geuzaine Exp $
+// $Id: Opengl_Window.cpp,v 1.47 2005-03-11 08:56:38 geuzaine Exp $
 //
 // Copyright (C) 1997-2005 C. Geuzaine, J.-F. Remacle
 //
@@ -39,8 +39,6 @@ void Process_SelectionBuffer(int x, int y, int *n, GLuint * ii, GLuint * jj);
 void Filter_SelectionBuffer(int n, GLuint * typ, GLuint * ient,
                             Vertex ** thev, Curve ** thec, Surface ** thes,
                             Mesh * m);
-void myZoom(GLdouble X1, GLdouble X2, GLdouble Y1, GLdouble Y2, GLdouble Xc1,
-            GLdouble Xc2, GLdouble Yc1, GLdouble Yc2);
 int check_type(int type, Vertex * v, Curve * c, Surface * s);
 
 void Opengl_Window::draw()
@@ -113,6 +111,27 @@ void Opengl_Window::draw()
   locked = 0;
 }
 
+// FIXME: this is notoriously wrong :-)
+
+void myZoom(GLdouble X1, GLdouble X2, GLdouble Y1, GLdouble Y2,
+            GLdouble Xc1, GLdouble Xc2, GLdouble Yc1, GLdouble Yc2)
+{
+  GLdouble xscale1 = CTX.s[0];
+  GLdouble yscale1 = CTX.s[1];
+  CTX.s[0] *= (CTX.vxmax - CTX.vxmin) / (X2 - X1);
+  CTX.s[1] *= (CTX.vymax - CTX.vymin) / (Y1 - Y2);
+  CTX.s[2] = MIN(CTX.s[0], CTX.s[1]); // bof...
+  CTX.t[0] = CTX.t[0] * (xscale1 / CTX.s[0]) - 
+    ((Xc1 + Xc2) / 2.) * (1. - (xscale1 / CTX.s[0]));
+  CTX.t[1] = CTX.t[1] * (yscale1 / CTX.s[1]) - 
+    ((Yc1 + Yc2) / 2.) * (1. - (yscale1 / CTX.s[1]));
+  
+  WID->update_manip_window();
+  InitPosition();
+  Draw();
+}
+
+
 // The event model in FLTK is pretty different from other toolkits:
 // the events are passed to the widget handle of the widget that has
 // the focus. If this handle returns 1, then the event is considered
@@ -167,8 +186,8 @@ int Opengl_Window::handle(int event)
     }
     else if(ibut == 2 || (ibut == 1 && Fl::event_state(FL_SHIFT))) {
       if(Fl::event_state(FL_CTRL) && !ZoomClick) {
-        set_s(1, CTX.s[0]);
-        set_s(2, CTX.s[0]);
+        CTX.s[1] = CTX.s[0];
+        CTX.s[2] = CTX.s[0];
         redraw();
       }
       else {
@@ -179,23 +198,17 @@ int Opengl_Window::handle(int event)
       if(Fl::event_state(FL_CTRL) && !ZoomClick) {
         if(CTX.useTrackball)
           CTX.setQuaternion(0., 0., 0., 1.);
-        else {
-          set_r(0, 0.);
-          set_r(1, 0.);
-          set_r(2, 0.);
-        }
-        set_t(0, 0.);
-        set_t(1, 0.);
-        set_t(2, 0.);
-        set_s(0, 1.);
-        set_s(1, 1.);
-        set_s(2, 1.);
+        else
+          CTX.r[0] = CTX.r[1] = CTX.r[2] = 0.;
+	CTX.t[0] = CTX.t[1] = CTX.t[2] = 0.;
+	CTX.s[0] = CTX.s[1] = CTX.s[2] = 1.;
         redraw();
       }
       else {
         ZoomClick = false;
       }
     }
+    WID->update_manip_window();
     return 1;
 
   case FL_RELEASE:
@@ -237,25 +250,22 @@ int Opengl_Window::handle(int event)
                             (2.0 * Fl::event_x() - w()) / w(),
                             (h() - 2.0 * Fl::event_y()) / h());
         else {
-          set_r(1, CTX.r[1] + ((abs(xmov) > abs(ymov)) ?
-			       180 * (double)xmov / (double)w() : 0));
-          set_r(0, CTX.r[0] + ((abs(xmov) > abs(ymov)) ? 
-			       0 : 180 * (double)ymov / (double)h()));
+          CTX.r[1] += ((abs(xmov) > abs(ymov)) ? 180 * (double)xmov / (double)w() : 0);
+	  CTX.r[0] += ((abs(xmov) > abs(ymov)) ? 0 : 180 * (double)ymov / (double)h());
         }
       }
       else if(ibut == 2 || (ibut == 1 && Fl::event_state(FL_SHIFT))) {
 	if(!CTX.useTrackball)
-          set_r(2, CTX.r[2] + (abs(ymov) > abs(xmov) ? 
-			       0 : -180 * (double)xmov / (double)w()));
+          CTX.r[2] += (abs(ymov) > abs(xmov) ? 0 : -180 * (double)xmov / (double)w());
 	double zoomfact = (ymov > 0) ? 
 	  (double)(CTX.zoom_factor * abs(ymov) + h()) / (double)h() : 
 	  (double)(h()) / (double)(CTX.zoom_factor * abs(ymov) + h());
-	set_s(0, CTX.s[0] * (abs(ymov) > abs(xmov) ? zoomfact : 1.));
-        set_s(1, CTX.s[0]);
-        set_s(2, CTX.s[0]);
+	CTX.s[0] *= (abs(ymov) > abs(xmov) ? zoomfact : 1.);
+        CTX.s[1] = CTX.s[0];
+        CTX.s[2] = CTX.s[0];
         if(abs(ymov) > abs(xmov)) {
-          set_t(0, xt1 * (xscale1 / CTX.s[0]) - xc1 * (1. - (xscale1 / CTX.s[0])));
-          set_t(1, yt1 * (yscale1 / CTX.s[1]) - yc1 * (1. - (yscale1 / CTX.s[1])));
+          CTX.t[0] = xt1 * (xscale1 / CTX.s[0]) - xc1 * (1. - (xscale1 / CTX.s[0]));
+          CTX.t[1] = yt1 * (yscale1 / CTX.s[1]) - yc1 * (1. - (yscale1 / CTX.s[1]));
         }
       }
       else {
@@ -263,9 +273,9 @@ int Opengl_Window::handle(int event)
           / CTX.s[0];
         yc = (CTX.vymax - ((double)ypos / (double)h()) * (CTX.vymax - CTX.vymin))
           / CTX.s[1];
-        set_t(0, xc - xc1);
-        set_t(1, yc - yc1);
-        set_t(2, 0.);
+        CTX.t[0] = xc - xc1;
+        CTX.t[1] = yc - yc1;
+        CTX.t[2] = 0.;
       }
 
       if(CTX.fast_redraw) {
@@ -278,6 +288,7 @@ int Opengl_Window::handle(int event)
 
     xpos += xmov;
     ypos += ymov;
+    WID->update_manip_window();
     return 1;
 
   case FL_MOVE:
diff --git a/Geo/MinMax.cpp b/Geo/MinMax.cpp
index 22aedbb114..4f6c9d62d8 100644
--- a/Geo/MinMax.cpp
+++ b/Geo/MinMax.cpp
@@ -1,4 +1,4 @@
-// $Id: MinMax.cpp,v 1.16 2005-01-01 19:35:28 geuzaine Exp $
+// $Id: MinMax.cpp,v 1.17 2005-03-11 08:56:38 geuzaine Exp $
 //
 // Copyright (C) 1997-2005 C. Geuzaine, J.-F. Remacle
 //
@@ -57,7 +57,7 @@ void CalculateMinMax(Tree_T * t, double *bbox)
       CTX.min[0] = CTX.min[1] = CTX.min[2] = -1.;
       CTX.max[0] = CTX.max[1] = CTX.max[2] = 1.;
       CTX.range[0] = CTX.range[1] = CTX.range[2] = 0.;
-      CTX.lc = CTX.lc_middle = 1.;
+      CTX.lc = 1.;
       return;
     }
     else {
@@ -95,56 +95,40 @@ void CalculateMinMax(Tree_T * t, double *bbox)
     CTX.max[0] += 1.;
     CTX.max[1] += 1.;
     CTX.lc = 1.;
-    CTX.lc_middle = 0.;
   }
   else if(CTX.range[0] == 0. && CTX.range[1] == 0.) {
-    CTX.lc = CTX.lc_middle = CTX.range[2];
+    CTX.lc = CTX.range[2];
     CTX.min[0] -= CTX.lc;
     CTX.min[1] -= CTX.lc;
     CTX.max[0] += CTX.lc;
     CTX.max[1] += CTX.lc;
   }
   else if(CTX.range[0] == 0. && CTX.range[2] == 0.) {
-    CTX.lc = CTX.lc_middle = CTX.range[1];
+    CTX.lc = CTX.range[1];
     CTX.min[0] -= CTX.lc;
     CTX.max[0] += CTX.lc;
   }
   else if(CTX.range[1] == 0. && CTX.range[2] == 0.) {
-    CTX.lc = CTX.lc_middle = CTX.range[0];
+    CTX.lc = CTX.range[0];
     CTX.min[1] -= CTX.lc;
     CTX.max[1] += CTX.lc;
   }
   else if(CTX.range[0] == 0.) {
     CTX.lc = sqrt(DSQR(CTX.range[1]) + DSQR(CTX.range[2]));
-    CTX.lc_middle = DMIN(CTX.range[1], CTX.range[2]);
     CTX.min[0] -= CTX.lc;
     CTX.max[0] += CTX.lc;
   }
   else if(CTX.range[1] == 0.) {
     CTX.lc = sqrt(DSQR(CTX.range[0]) + DSQR(CTX.range[2]));
-    CTX.lc_middle = DMIN(CTX.range[0], CTX.range[2]);
     CTX.min[1] -= CTX.lc;
     CTX.max[1] += CTX.lc;
   }
   else if(CTX.range[2] == 0.) {
     CTX.lc = sqrt(DSQR(CTX.range[0]) + DSQR(CTX.range[1]));
-    CTX.lc_middle = DMIN(CTX.range[0], CTX.range[1]);
   }
   else {
     CTX.lc =
       sqrt(DSQR(CTX.range[0]) + DSQR(CTX.range[1]) + DSQR(CTX.range[2]));
-    if((CTX.range[1] <= CTX.range[0] && CTX.range[0] <= CTX.range[2])
-       || (CTX.range[2] <= CTX.range[0] && CTX.range[0] <= CTX.range[1]))
-      CTX.lc_middle = CTX.range[0];
-    else if((CTX.range[0] <= CTX.range[1] && CTX.range[1] <= CTX.range[2]) ||
-            (CTX.range[2] <= CTX.range[1] && CTX.range[1] <= CTX.range[0]))
-      CTX.lc_middle = CTX.range[1];
-    else
-      CTX.lc_middle = CTX.range[2];
   }
 
-  // CTX.lc_order : CTX.lc == f * 10^CTX.lc_order with -1<f<1 
-
-  frac = frexp(CTX.lc, &exp);
-  CTX.lc_order = (int)floor(log10(ldexp(frac, exp)));
 }
diff --git a/Graphics/Draw.cpp b/Graphics/Draw.cpp
index 43e94d931c..48d1efee27 100644
--- a/Graphics/Draw.cpp
+++ b/Graphics/Draw.cpp
@@ -1,4 +1,4 @@
-// $Id: Draw.cpp,v 1.74 2005-03-11 05:47:55 geuzaine Exp $
+// $Id: Draw.cpp,v 1.75 2005-03-11 08:56:38 geuzaine Exp $
 //
 // Copyright (C) 1997-2005 C. Geuzaine, J.-F. Remacle
 //
@@ -266,7 +266,7 @@ void InitPosition(void)
 		 CTX.rotation_center[1],
 		 CTX.rotation_center[2]);
   
-  CTX.buildRotmatrix();
+  CTX.buildRotationMatrix();
   glMultMatrixd(CTX.rot);
 
   if(CTX.rotation_center_cg)
@@ -348,24 +348,6 @@ void Filter_SelectionBuffer(int n, GLuint * typ, GLuint * ient,
   }
 }
 
-// FIXME: this is notoriously wrong :-)
-
-void myZoom(GLdouble X1, GLdouble X2, GLdouble Y1, GLdouble Y2,
-            GLdouble Xc1, GLdouble Xc2, GLdouble Yc1, GLdouble Yc2)
-{
-  GLdouble xscale1 = CTX.s[0];
-  GLdouble yscale1 = CTX.s[1];
-  set_s(0, CTX.s[0] * (CTX.vxmax - CTX.vxmin) / (X2 - X1));
-  set_s(1, CTX.s[1] * (CTX.vymax - CTX.vymin) / (Y1 - Y2));
-  set_s(2, MIN(CTX.s[0], CTX.s[1])); // bof...
-  set_t(0, CTX.t[0] * (xscale1 / CTX.s[0]) - 
-	((Xc1 + Xc2) / 2.) * (1. - (xscale1 / CTX.s[0])));
-  set_t(1, CTX.t[1] * (yscale1 / CTX.s[1]) - 
-	((Yc1 + Yc2) / 2.) * (1. - (yscale1 / CTX.s[1])));
-  InitPosition();
-  Draw();
-}
-
 // Takes a cursor position in window coordinates and returns the line
 // (given by a point and a unit direction vector), in real space, that
 // corresponds to that cursor position
diff --git a/Graphics/Draw.h b/Graphics/Draw.h
index c444654d99..3daecb2a55 100644
--- a/Graphics/Draw.h
+++ b/Graphics/Draw.h
@@ -38,10 +38,6 @@ void Orthogonalize(int x, int y);
 void ClearOpengl(void);
 void SetOpenglContext(void);
 
-void set_r(int i, double val);
-void set_t(int i, double val);
-void set_s(int i, double val);
-
 void unproject(double x, double y, double p[3], double d[3]);
 void Viewport2World(double win[3], double xyz[3]);
 void World2Viewport(double xyz[3], double win[3]);
diff --git a/doc/VERSIONS b/doc/VERSIONS
index 4d1bce9c62..d4e9d83fad 100644
--- a/doc/VERSIONS
+++ b/doc/VERSIONS
@@ -1,12 +1,12 @@
-$Id: VERSIONS,v 1.316 2005-03-11 05:47:56 geuzaine Exp $
+$Id: VERSIONS,v 1.317 2005-03-11 08:56:38 geuzaine Exp $
 
 New since 1.59: added support for discrete curves; new Window menu on
 Mac OS X; generalized all octree-based plugins (CutGrid, StreamLines,
 Probe, etc.) to handle all element types (and not only scalar and
 vector triangles+tetrahedra); generalized Plugin(Evaluate) and
 Plugin(Extract); enhanced clipping plane interface; new grid options
-for 3D post-processing views; various small enhancements and bug
-fixes.
+for 3D post-processing views; new manipulator dialog; various small
+enhancements and bug fixes.
 
 New in 1.59: added support for discrete (triangulated) surfaces,
 either in STL format or with the new "Discrete Surface" command; added
diff --git a/doc/texinfo/shortcuts.texi b/doc/texinfo/shortcuts.texi
index c280ec2100..7279d9b5b8 100644
--- a/doc/texinfo/shortcuts.texi
+++ b/doc/texinfo/shortcuts.texi
@@ -73,6 +73,8 @@ Save file
 
 @item Shift+Ctrl+c
 Show clipping plane window
+@item Shift+Ctrl+m
+Show manipulator window 
 @item Shift+Ctrl+n
 Show option window 
 @item Shift+Ctrl+o
-- 
GitLab