Fraxinus  16.5.0-fx-rc1
An IGT application
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
cxTransform3DWidget.cpp
Go to the documentation of this file.
1 /*=========================================================================
2 This file is part of CustusX, an Image Guided Therapy Application.
3 
4 Copyright (c) 2008-2014, SINTEF Department of Medical Technology
5 All rights reserved.
6 
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9 
10 1. Redistributions of source code must retain the above copyright notice,
11  this list of conditions and the following disclaimer.
12 
13 2. Redistributions in binary form must reproduce the above copyright notice,
14  this list of conditions and the following disclaimer in the documentation
15  and/or other materials provided with the distribution.
16 
17 3. Neither the name of the copyright holder nor the names of its contributors
18  may be used to endorse or promote products derived from this software
19  without specific prior written permission.
20 
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 =========================================================================*/
32 
33 #include <cxTransform3DWidget.h>
34 
35 #include <QTextEdit>
36 #include <QLayout>
37 #include <QLabel>
38 #include <QFontMetrics>
39 
40 #include "cxTypeConversions.h"
41 
42 #include "cxDoubleProperty.h"
43 #include "cxDoubleWidgets.h"
44 #include <cmath>
45 #include "cxMousePadWidget.h"
46 
47 #include <QtWidgets>
48 
49 
50 #include "boost/bind.hpp"
51 #include "libQtSignalAdapters/Qt2Func.h"
52 #include "libQtSignalAdapters/ConnectionFactories.h"
53 
54 namespace cx
55 {
56 
57 class MatrixTextEdit : public QTextEdit
58 {
59 public:
60  MatrixTextEdit(QWidget* parent=NULL) : QTextEdit(parent) {}
61  QSize minimumSizeHint() const { return sizeHint(); }
62  QSize sizeHint() const
63  {
65  createTransformRotateZ(M_PI_4) *
67 
68  QString text = qstring_cast(M).split("\n")[0];
69  QRect rect = QFontMetrics(this->font()).boundingRect(text);
70  QSize s(rect.width()*1.0+5, 4*rect.height()*1.2+5);
71  return s;
72  }
73 };
74 
75 //template<class T>
76 //QAction* Transform3DWidget::createAction(QLayout* layout, QString iconName, QString text, QString tip, T slot)
77 //{
78 // QAction* action = new QAction(QIcon(iconName), text, this);
79 // action->setStatusTip(tip);
80 // action->setToolTip(tip);
81 // connect(action, SIGNAL(triggered()), this, slot);
82 // QToolButton* button = new QToolButton();
83 // button->setDefaultAction(action);
84 // layout->addWidget(button);
85 // return action;
86 //}
87 
88 
90  BaseWidget(parent, "Transform3DWidget", "Transform 3D")
91 {
92  this->setToolTip("Display and manipulate an affine (rotation+translation) matrix");
93  recursive = false;
94  mBlockChanges = false;
95  //layout
96  QVBoxLayout* toptopLayout = new QVBoxLayout(this);
97  toptopLayout->setMargin(4);
98  QHBoxLayout* mLayout = new QHBoxLayout;
99  mLayout->setMargin(0);
100  toptopLayout->addLayout(mLayout);
101 
102  mTextEdit = new MatrixTextEdit;
103  mTextEdit->setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::Maximum);
104  mTextEdit->setLineWrapMode(QTextEdit::NoWrap);
105  mTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
106  mTextEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
107  connect(mTextEdit, SIGNAL(textChanged()), this, SLOT(textEditChangedSlot()));
108 
109  mLayout->addWidget(mTextEdit, 1);
110 
111  QVBoxLayout* buttonLayout = new QVBoxLayout;
112  mLayout->addLayout(buttonLayout);
113  buttonLayout->setMargin(0);
114 
115  mEditAction = this->createAction(this,
116  QIcon(":/icons/open_icon_library/system-run-5.png"),
117  "Edit",
118  "Toggle Edit Matrix",
119  SLOT(toggleEditSlot()),
120  buttonLayout);
121  mEditAction->setCheckable(true);
122 
123  mInvertAction = this->createAction(this,
124  QIcon(":/icons/matrix_inverse.png"),
125  "Invert",
126  "Toggle Invert Matrix",
127  SLOT(toggleInvertSlot()),
128  buttonLayout);
129  mInvertAction->setCheckable(true);
130  mInvertAction->setChecked(false);
131  this->updateInvertAction();
132 
133 // mLayout->addStretch();
134 
135  aGroupBox = new QFrame(this);
136  QVBoxLayout* aLayout = new QVBoxLayout;
137  aGroupBox->setLayout(aLayout);
138  aGroupBox->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
139  aGroupBox->setLineWidth(3);
140  aLayout->setMargin(4);
141  toptopLayout->addWidget(aGroupBox);
142 
143  this->addAngleControls("xAngle", "X Angle", 0, aLayout);
144  this->addAngleControls("yAngle", "Y Angle", 1, aLayout);
145  this->addAngleControls("zAngle", "Z Angle", 2, aLayout);
146 
147  tGroupBox = new QFrame(this);
148  QVBoxLayout* tLayout = new QVBoxLayout;
149  tGroupBox->setLayout(tLayout);
150  tGroupBox->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
151  tGroupBox->setLineWidth(3);
152  tLayout->setMargin(4);
153  toptopLayout->addWidget(tGroupBox);
154 
155  this->addTranslationControls("xTranslation", "X", 0, tLayout);
156  this->addTranslationControls("yTranslation", "Y", 1, tLayout);
157  this->addTranslationControls("zTranslation", "Z", 2, tLayout);
158 
159  this->setMatrixInternal(Transform3D::Identity());
160 
161  toptopLayout->addStretch();
162 
163  this->setEditable(false);
164 }
165 
166 void Transform3DWidget::textEditChangedSlot()
167 {
168  bool ok = false;
169  Transform3D M = Transform3D::fromString(mTextEdit->toPlainText(), &ok);
170  // ignore setting if invalid matrix or no real change done (hopefully, this allows trivial editing without text reset)
171  if (!ok)
172  return;
173  if (similar(M, this->getMatrixInternal()))
174  return;
175 
176  this->setMatrixInternal(M);
177 }
178 
179 void Transform3DWidget::toggleEditSlot()
180 {
181  bool visible = tGroupBox->isVisible();
182  this->setEditable(!visible);
183 }
184 
185 
186 void Transform3DWidget::toggleInvertSlot()
187 {
188  // the interpretation of matrix is dependent on mInvertAction->isChecked()!
189  mDecomposition.reset(mDecomposition.getMatrix().inverse());
190  this->setModified();
191 // this->updateInvertAction();
192 }
193 
194 void Transform3DWidget::updateInvertAction()
195 {
196  if (mInvertAction->isChecked())
197  {
198  this->setActionText(mInvertAction, "Inverted Matrix", "The matrix is shown inverted");
199  }
200  else
201  {
202  this->setActionText(mInvertAction, "Noninverted Matrix", "The matrix is shown as is. Press to show inverted");
203  }
204 }
205 
206 void Transform3DWidget::setActionText(QAction* action, QString text, QString tip)
207 {
208  if (tip.isEmpty())
209  tip = text;
210  action->setText(text);
211  action->setStatusTip(tip);
212  action->setWhatsThis(tip);
213  action->setToolTip(tip);
214 }
215 
217 {
218  mTextEdit->setReadOnly(!edit);
219  aGroupBox->setVisible(edit);
220  tGroupBox->setVisible(edit);
221 }
222 
223 void Transform3DWidget::addAngleControls(QString uid, QString name, int index, QVBoxLayout* layout)
224 {
225  QHBoxLayout* hLayout = new QHBoxLayout;
226 
227  DoublePropertyPtr adapter = DoubleProperty::initialize(uid, name, "", 0, DoubleRange(-M_PI,M_PI,M_PI/180),1);
228  connect(adapter.get(), SIGNAL(changed()), this, SLOT(changedSlot()));
229  adapter->setInternal2Display(180/M_PI);
230  hLayout->addWidget(new SpinBoxGroupWidget(this, adapter));
231 
232  QSize mMinBarSize = QSize(20,20);
233  MousePadWidget* pad = new MousePadWidget(this, mMinBarSize);
234  pad->setFixedYPos(true);
235  hLayout->addWidget(pad);
236 
237  // use QtSignalAdapters library to work magic:
238  QtSignalAdapters::connect1<void(QPointF)>(pad, SIGNAL(mouseMoved(QPointF)),
239  boost::bind(&Transform3DWidget::rotateSlot, this, _1, index));
240 
241  layout->addLayout(hLayout);
242  mAngleAdapter[index] = adapter;
243 }
244 
245 void Transform3DWidget::addTranslationControls(QString uid, QString name, int index, QVBoxLayout* layout)
246 {
247  QHBoxLayout* hLayout = new QHBoxLayout;
248 
249  DoublePropertyPtr adapter = DoubleProperty::initialize(uid, name, "", 0, DoubleRange(-10000,10000,0.1),1);
250  connect(adapter.get(), SIGNAL(changed()), this, SLOT(changedSlot()));
251  adapter->setInternal2Display(1.0);
252  hLayout->addWidget(new SpinBoxGroupWidget(this, adapter));
253 
254  QSize mMinBarSize = QSize(20,20);
255  MousePadWidget* pad = new MousePadWidget(this, mMinBarSize);
256  pad->setFixedYPos(true);
257  hLayout->addWidget(pad);
258 
259  // use QtSignalAdapters library to work magic:
260  QtSignalAdapters::connect1<void(QPointF)>(pad, SIGNAL(mouseMoved(QPointF)),
261  boost::bind(&Transform3DWidget::translateSlot, this, _1, index));
262 
263  layout->addLayout(hLayout);
264  mTranslationAdapter[index] = adapter;
265 }
266 
267 void Transform3DWidget::rotateSlot(QPointF delta, int index)
268 {
269  double scale = M_PI_2;
270  double factor = scale * delta.x();
271  double current = mAngleAdapter[index]->getValue();
272  mAngleAdapter[index]->setValue(current + factor);
273 }
274 
275 void Transform3DWidget::translateSlot(QPointF delta, int index)
276 {
277  double scale = 20;
278  double factor = scale * delta.x();
279  double current = mTranslationAdapter[index]->getValue();
280  mTranslationAdapter[index]->setValue(current + factor);
281 }
282 
283 
285 {
286 }
287 
289 {
290  this->setMatrixInternal(this->convertToFromExternal(M));
291 }
292 
294 {
295  return this->convertToFromExternal(this->getMatrixInternal());
296 }
297 
298 Transform3D Transform3DWidget::convertToFromExternal(const Transform3D& M) const
299 {
300  if (mInvertAction->isChecked())
301  {
302  return M.inverse();
303  }
304  else
305  {
306  return M;
307  }
308 }
309 
310 void Transform3DWidget::setMatrixInternal(const Transform3D& M)
311 {
312  mDecomposition.reset(M);
313  this->setModified();
314  emit changed();
315 }
316 
317 Transform3D Transform3DWidget::getMatrixInternal() const
318 {
319  return mDecomposition.getMatrix();
320 }
321 
322 // http://en.wikipedia.org/wiki/Rotation_matrix
323 // http://en.wikipedia.org/wiki/Rotation_representation_(mathematics)#Conversion_formulae_between_representations
324 
325 void Transform3DWidget::changedSlot()
326 {
327  if (recursive || mBlockChanges)
328  return;
329  recursive = true;
330  Vector3D xyz(mAngleAdapter[0]->getValue(),mAngleAdapter[1]->getValue(),mAngleAdapter[2]->getValue());
331  mDecomposition.setAngles(xyz);
332 
333  Vector3D t(mTranslationAdapter[0]->getValue(),mTranslationAdapter[1]->getValue(),mTranslationAdapter[2]->getValue());
334  mDecomposition.setPosition(t);
335 
336  this->setModified();
337  emit changed();
338  recursive = false;
339 }
340 
341 namespace
342 {
346  double wrapAngle(double angle)
347  {
348  angle = fmod(angle, M_PI * 2);
349  if (angle > M_PI)
350  angle -= M_PI * 2;
351  if (angle < -M_PI)
352  angle += M_PI * 2;
353  return angle;
354  }
355 }
356 
358 {
359  QString M = qstring_cast(this->getMatrixInternal());
360 
361  mTextEdit->blockSignals(true);
362  int textPos = mTextEdit->textCursor().position();
363  mTextEdit->setText(M);
364  QTextCursor cursor = mTextEdit->textCursor();
365  cursor.setPosition(textPos);
366  mTextEdit->setTextCursor(cursor);
367  mTextEdit->blockSignals(false);
368 
369  Vector3D xyz = mDecomposition.getAngles();
370 
371  mBlockChanges = true;
372 
373  mAngleAdapter[0]->setValue(wrapAngle(xyz[0]));
374  mAngleAdapter[1]->setValue(wrapAngle(xyz[1]));
375  mAngleAdapter[2]->setValue(wrapAngle(xyz[2]));
376 
377  Vector3D t = mDecomposition.getPosition();
378  mTranslationAdapter[0]->setValue(t[0]);
379  mTranslationAdapter[1]->setValue(t[1]);
380  mTranslationAdapter[2]->setValue(t[2]);
381 
382  this->updateInvertAction();
383 
384  mBlockChanges = false;
385 }
386 }
QString qstring_cast(const T &val)
Transform3D Transform3D
Transform3D is a representation of an affine 3D transform.
Vector3D getPosition() const
Definition: cxFrame3D.cpp:108
Utility class for describing a bounded numeric range.
Definition: cxDoubleRange.h:53
A touchpad-friendly area for performing 1D/2D scroll operations.
QAction * createAction(QObject *parent, QIcon iconName, QString text, QString tip, T slot, QLayout *layout=NULL, QToolButton *button=new QToolButton())
Definition: cxBaseWidget.h:149
bool similar(const DoubleBoundingBox3D &a, const DoubleBoundingBox3D &b, double tol)
void setMatrix(const Transform3D &M)
MatrixTextEdit(QWidget *parent=NULL)
Vector3D getAngles() const
Definition: cxFrame3D.cpp:103
void reset(Transform3D m)
reinitialize with a fresh matrix.
Definition: cxFrame3D.cpp:56
Transform3D createTransformTranslate(const Vector3D &translation)
Transform3D getMatrix() const
Definition: cxFrame3D.cpp:113
void setFixedYPos(bool on)
void setPosition(Vector3D pos)
Definition: cxFrame3D.cpp:97
boost::shared_ptr< class DoubleProperty > DoublePropertyPtr
Eigen::Vector3d Vector3D
Vector3D is a representation of a point or vector in 3D.
Definition: cxVector3D.h:63
Interface for QWidget which handles widgets uniformly for the system.
Definition: cxBaseWidget.h:108
QSize minimumSizeHint() const
Composite widget for scalar data manipulation.
void setAngles(Vector3D xyz)
Definition: cxFrame3D.cpp:76
static DoublePropertyPtr initialize(const QString &uid, QString name, QString help, double value, DoubleRange range, int decimals, QDomNode root=QDomNode())
Transform3D getMatrix() const
Transform3D createTransformRotateZ(const double angle)
Transform3DWidget(QWidget *parent=NULL)
Transform3D createTransformRotateX(const double angle)
#define M_PI