CustusX  15.3.4-beta
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  recursive = false;
93  mBlockChanges = false;
94  //layout
95  QVBoxLayout* toptopLayout = new QVBoxLayout(this);
96  toptopLayout->setMargin(4);
97  QHBoxLayout* mLayout = new QHBoxLayout;
98  mLayout->setMargin(0);
99  toptopLayout->addLayout(mLayout);
100 
101  mTextEdit = new MatrixTextEdit;
102  mTextEdit->setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::Maximum);
103  mTextEdit->setLineWrapMode(QTextEdit::NoWrap);
104  mTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
105  mTextEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
106  connect(mTextEdit, SIGNAL(textChanged()), this, SLOT(textEditChangedSlot()));
107 
108  mLayout->addWidget(mTextEdit, 1);
109 
110  QVBoxLayout* buttonLayout = new QVBoxLayout;
111  mLayout->addLayout(buttonLayout);
112  buttonLayout->setMargin(0);
113 
114  mEditAction = this->createAction(this,
115  QIcon(":/icons/open_icon_library/system-run-5.png"),
116  "Edit",
117  "Toggle Edit Matrix",
118  SLOT(toggleEditSlot()),
119  buttonLayout);
120  mEditAction->setCheckable(true);
121 
122  mInvertAction = this->createAction(this,
123  QIcon(":/icons/matrix_inverse.png"),
124  "Invert",
125  "Toggle Invert Matrix",
126  SLOT(toggleInvertSlot()),
127  buttonLayout);
128  mInvertAction->setCheckable(true);
129  mInvertAction->setChecked(false);
130  this->updateInvertAction();
131 
132 // mLayout->addStretch();
133 
134  aGroupBox = new QFrame(this);
135  QVBoxLayout* aLayout = new QVBoxLayout;
136  aGroupBox->setLayout(aLayout);
137  aGroupBox->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
138  aGroupBox->setLineWidth(3);
139  aLayout->setMargin(4);
140  toptopLayout->addWidget(aGroupBox);
141 
142  this->addAngleControls("xAngle", "X Angle", 0, aLayout);
143  this->addAngleControls("yAngle", "Y Angle", 1, aLayout);
144  this->addAngleControls("zAngle", "Z Angle", 2, aLayout);
145 
146  tGroupBox = new QFrame(this);
147  QVBoxLayout* tLayout = new QVBoxLayout;
148  tGroupBox->setLayout(tLayout);
149  tGroupBox->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
150  tGroupBox->setLineWidth(3);
151  tLayout->setMargin(4);
152  toptopLayout->addWidget(tGroupBox);
153 
154  this->addTranslationControls("xTranslation", "X", 0, tLayout);
155  this->addTranslationControls("yTranslation", "Y", 1, tLayout);
156  this->addTranslationControls("zTranslation", "Z", 2, tLayout);
157 
158  this->setMatrixInternal(Transform3D::Identity());
159 
160  toptopLayout->addStretch();
161 
162  this->setEditable(false);
163 }
164 
166 {
167  return "<html>"
168  "<h3>Transform 3D</h3>"
169  "<p>Lets you display and manipulat an affine matrix, i.e. a rotation+translation matrix.</p>"
170  "<p><i></i></p>"
171  "</html>";
172 }
173 
174 void Transform3DWidget::textEditChangedSlot()
175 {
176  bool ok = false;
177  Transform3D M = Transform3D::fromString(mTextEdit->toPlainText(), &ok);
178  // ignore setting if invalid matrix or no real change done (hopefully, this allows trivial editing without text reset)
179  if (!ok)
180  return;
181  if (similar(M, this->getMatrixInternal()))
182  return;
183 
184  this->setMatrixInternal(M);
185 }
186 
187 void Transform3DWidget::toggleEditSlot()
188 {
189  bool visible = tGroupBox->isVisible();
190  this->setEditable(!visible);
191 }
192 
193 
194 void Transform3DWidget::toggleInvertSlot()
195 {
196  // the interpretation of matrix is dependent on mInvertAction->isChecked()!
197  mDecomposition.reset(mDecomposition.getMatrix().inverse());
198  this->updateValues();
199 // this->updateInvertAction();
200 }
201 
202 void Transform3DWidget::updateInvertAction()
203 {
204  if (mInvertAction->isChecked())
205  {
206  this->setActionText(mInvertAction, "Inverted Matrix", "The matrix is shown inverted");
207  }
208  else
209  {
210  this->setActionText(mInvertAction, "Noninverted Matrix", "The matrix is shown as is. Press to show inverted");
211  }
212 }
213 
214 void Transform3DWidget::setActionText(QAction* action, QString text, QString tip)
215 {
216  if (tip.isEmpty())
217  tip = text;
218  action->setText(text);
219  action->setStatusTip(tip);
220  action->setWhatsThis(tip);
221  action->setToolTip(tip);
222 }
223 
225 {
226  mTextEdit->setReadOnly(!edit);
227  aGroupBox->setVisible(edit);
228  tGroupBox->setVisible(edit);
229 }
230 
231 void Transform3DWidget::addAngleControls(QString uid, QString name, int index, QVBoxLayout* layout)
232 {
233  QHBoxLayout* hLayout = new QHBoxLayout;
234 
235  DoublePropertyPtr adapter = DoubleProperty::initialize(uid, name, "", 0, DoubleRange(-M_PI,M_PI,M_PI/180),1);
236  connect(adapter.get(), SIGNAL(changed()), this, SLOT(changedSlot()));
237  adapter->setInternal2Display(180/M_PI);
238  hLayout->addWidget(new SpinBoxGroupWidget(this, adapter));
239 
240  QSize mMinBarSize = QSize(20,20);
241  MousePadWidget* pad = new MousePadWidget(this, mMinBarSize);
242  pad->setFixedYPos(true);
243  hLayout->addWidget(pad);
244 
245  // use QtSignalAdapters library to work magic:
246  QtSignalAdapters::connect1<void(QPointF)>(pad, SIGNAL(mouseMoved(QPointF)),
247  boost::bind(&Transform3DWidget::rotateSlot, this, _1, index));
248 
249  layout->addLayout(hLayout);
250  mAngleAdapter[index] = adapter;
251 }
252 
253 void Transform3DWidget::addTranslationControls(QString uid, QString name, int index, QVBoxLayout* layout)
254 {
255  QHBoxLayout* hLayout = new QHBoxLayout;
256 
257  DoublePropertyPtr adapter = DoubleProperty::initialize(uid, name, "", 0, DoubleRange(-10000,10000,0.1),1);
258  connect(adapter.get(), SIGNAL(changed()), this, SLOT(changedSlot()));
259  adapter->setInternal2Display(1.0);
260  hLayout->addWidget(new SpinBoxGroupWidget(this, adapter));
261 
262  QSize mMinBarSize = QSize(20,20);
263  MousePadWidget* pad = new MousePadWidget(this, mMinBarSize);
264  pad->setFixedYPos(true);
265  hLayout->addWidget(pad);
266 
267  // use QtSignalAdapters library to work magic:
268  QtSignalAdapters::connect1<void(QPointF)>(pad, SIGNAL(mouseMoved(QPointF)),
269  boost::bind(&Transform3DWidget::translateSlot, this, _1, index));
270 
271  layout->addLayout(hLayout);
272  mTranslationAdapter[index] = adapter;
273 }
274 
275 void Transform3DWidget::rotateSlot(QPointF delta, int index)
276 {
277  double scale = M_PI_2;
278  double factor = scale * delta.x();
279  double current = mAngleAdapter[index]->getValue();
280  mAngleAdapter[index]->setValue(current + factor);
281 }
282 
283 void Transform3DWidget::translateSlot(QPointF delta, int index)
284 {
285  double scale = 20;
286  double factor = scale * delta.x();
287  double current = mTranslationAdapter[index]->getValue();
288  mTranslationAdapter[index]->setValue(current + factor);
289 }
290 
291 
293 {
294 }
295 
297 {
298  this->setMatrixInternal(this->convertToFromExternal(M));
299 }
300 
302 {
303  return this->convertToFromExternal(this->getMatrixInternal());
304 }
305 
306 Transform3D Transform3DWidget::convertToFromExternal(const Transform3D& M) const
307 {
308  if (mInvertAction->isChecked())
309  {
310  return M.inverse();
311  }
312  else
313  {
314  return M;
315  }
316 }
317 
318 void Transform3DWidget::setMatrixInternal(const Transform3D& M)
319 {
320  mDecomposition.reset(M);
321  this->updateValues();
322  emit changed();
323 }
324 
325 Transform3D Transform3DWidget::getMatrixInternal() const
326 {
327  return mDecomposition.getMatrix();
328 }
329 
330 // http://en.wikipedia.org/wiki/Rotation_matrix
331 // http://en.wikipedia.org/wiki/Rotation_representation_(mathematics)#Conversion_formulae_between_representations
332 
333 void Transform3DWidget::changedSlot()
334 {
335  if (recursive || mBlockChanges)
336  return;
337  recursive = true;
338  Vector3D xyz(mAngleAdapter[0]->getValue(),mAngleAdapter[1]->getValue(),mAngleAdapter[2]->getValue());
339  mDecomposition.setAngles(xyz);
340 
341  Vector3D t(mTranslationAdapter[0]->getValue(),mTranslationAdapter[1]->getValue(),mTranslationAdapter[2]->getValue());
342  mDecomposition.setPosition(t);
343 
344  this->updateValues();
345  emit changed();
346  recursive = false;
347 }
348 
349 namespace
350 {
354  double wrapAngle(double angle)
355  {
356  angle = fmod(angle, M_PI * 2);
357  if (angle > M_PI)
358  angle -= M_PI * 2;
359  if (angle < -M_PI)
360  angle += M_PI * 2;
361  return angle;
362  }
363 }
364 
365 void Transform3DWidget::updateValues()
366 {
367  QString M = qstring_cast(this->getMatrixInternal());
368 
369  mTextEdit->blockSignals(true);
370  int textPos = mTextEdit->textCursor().position();
371  mTextEdit->setText(M);
372  QTextCursor cursor = mTextEdit->textCursor();
373  cursor.setPosition(textPos);
374  mTextEdit->setTextCursor(cursor);
375  mTextEdit->blockSignals(false);
376 
377  Vector3D xyz = mDecomposition.getAngles();
378 
379  mBlockChanges = true;
380 
381  mAngleAdapter[0]->setValue(wrapAngle(xyz[0]));
382  mAngleAdapter[1]->setValue(wrapAngle(xyz[1]));
383  mAngleAdapter[2]->setValue(wrapAngle(xyz[2]));
384 
385  Vector3D t = mDecomposition.getPosition();
386  mTranslationAdapter[0]->setValue(t[0]);
387  mTranslationAdapter[1]->setValue(t[1]);
388  mTranslationAdapter[2]->setValue(t[2]);
389 
390  this->updateInvertAction();
391 
392  mBlockChanges = false;
393 }
394 }
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:130
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
virtual QString defaultWhatsThis() const
Returns a short description of what this widget will do for you.
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