CustusX  2023.01.05-dev+develop.0da12
An IGT application
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) SINTEF Department of Medical Technology.
5 All rights reserved.
6 
7 CustusX is released under a BSD 3-Clause license.
8 
9 See Lisence.txt (https://github.com/SINTEFMedtek/CustusX/blob/master/License.txt) for details.
10 =========================================================================*/
11 
12 #include <cxTransform3DWidget.h>
13 
14 #include <QTextEdit>
15 #include <QLayout>
16 #include <QLabel>
17 #include <QFontMetrics>
18 
19 #include "cxTypeConversions.h"
20 
21 #include "cxDoubleProperty.h"
22 #include "cxDoubleWidgets.h"
23 #include <cmath>
24 #include "cxMousePadWidget.h"
25 
26 #include <QtWidgets>
27 
28 
29 #include "boost/bind.hpp"
30 #include "libQtSignalAdapters/Qt2Func.h"
31 #include "libQtSignalAdapters/ConnectionFactories.h"
32 
33 namespace cx
34 {
35 
36 class MatrixTextEdit : public QTextEdit
37 {
38 public:
39  MatrixTextEdit(QWidget* parent=NULL) : QTextEdit(parent) {}
40  QSize minimumSizeHint() const { return sizeHint(); }
41  QSize sizeHint() const
42  {
44  createTransformRotateZ(M_PI_4) *
46 
47  QString text = qstring_cast(M).split("\n")[0];
48  QRect rect = QFontMetrics(this->font()).boundingRect(text);
49  QSize s(rect.width()*1.0+5, 4*rect.height()*1.2+5);
50  return s;
51  }
52 };
53 
54 //template<class T>
55 //QAction* Transform3DWidget::createAction(QLayout* layout, QString iconName, QString text, QString tip, T slot)
56 //{
57 // QAction* action = new QAction(QIcon(iconName), text, this);
58 // action->setStatusTip(tip);
59 // action->setToolTip(tip);
60 // connect(action, SIGNAL(triggered()), this, slot);
61 // QToolButton* button = new QToolButton();
62 // button->setDefaultAction(action);
63 // layout->addWidget(button);
64 // return action;
65 //}
66 
67 
69  BaseWidget(parent, "transform_3d_widget", "Transform 3D")
70 {
71  this->setToolTip("Display and manipulate an affine (rotation+translation) matrix");
72  recursive = false;
73  mBlockChanges = false;
74  //layout
75  QVBoxLayout* toptopLayout = new QVBoxLayout(this);
76  toptopLayout->setMargin(4);
77  QHBoxLayout* mLayout = new QHBoxLayout;
78  mLayout->setMargin(0);
79  toptopLayout->addLayout(mLayout);
80 
81  mTextEdit = new MatrixTextEdit;
82  mTextEdit->setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::Maximum);
83  mTextEdit->setLineWrapMode(QTextEdit::NoWrap);
84  mTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
85  mTextEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
86  connect(mTextEdit, SIGNAL(textChanged()), this, SLOT(textEditChangedSlot()));
87 
88  mLayout->addWidget(mTextEdit, 1);
89 
90  QVBoxLayout* buttonLayout = new QVBoxLayout;
91  mLayout->addLayout(buttonLayout);
92  buttonLayout->setMargin(0);
93 
94  mEditAction = this->createAction(this,
95  QIcon(":/icons/open_icon_library/system-run-5.png"),
96  "Edit",
97  "Toggle Edit Matrix",
98  SLOT(toggleEditSlot()),
99  buttonLayout);
100  mEditAction->setCheckable(true);
101 
102  mInvertAction = this->createAction(this,
103  QIcon(":/icons/matrix_inverse.png"),
104  "Invert",
105  "Toggle Invert Matrix",
106  SLOT(toggleInvertSlot()),
107  buttonLayout);
108  mInvertAction->setCheckable(true);
109  mInvertAction->setChecked(false);
110  this->updateInvertAction();
111 
112 // mLayout->addStretch();
113 
114  aGroupBox = new QFrame(this);
115  QVBoxLayout* aLayout = new QVBoxLayout;
116  aGroupBox->setLayout(aLayout);
117  aGroupBox->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
118  aGroupBox->setLineWidth(3);
119  aLayout->setMargin(4);
120  toptopLayout->addWidget(aGroupBox);
121 
122  this->addAngleControls("xAngle", "X Angle", 0, aLayout);
123  this->addAngleControls("yAngle", "Y Angle", 1, aLayout);
124  this->addAngleControls("zAngle", "Z Angle", 2, aLayout);
125 
126  tGroupBox = new QFrame(this);
127  QVBoxLayout* tLayout = new QVBoxLayout;
128  tGroupBox->setLayout(tLayout);
129  tGroupBox->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
130  tGroupBox->setLineWidth(3);
131  tLayout->setMargin(4);
132  toptopLayout->addWidget(tGroupBox);
133 
134  this->addTranslationControls("xTranslation", "X", 0, tLayout);
135  this->addTranslationControls("yTranslation", "Y", 1, tLayout);
136  this->addTranslationControls("zTranslation", "Z", 2, tLayout);
137 
138  this->setMatrixInternal(Transform3D::Identity());
139 
140  toptopLayout->addStretch();
141 
142  this->setEditable(false);
143 }
144 
145 void Transform3DWidget::textEditChangedSlot()
146 {
147  bool ok = false;
148  Transform3D M = Transform3D::fromString(mTextEdit->toPlainText(), &ok);
149  // ignore setting if invalid matrix or no real change done (hopefully, this allows trivial editing without text reset)
150  if (!ok)
151  return;
152  if (similar(M, this->getMatrixInternal()))
153  return;
154 
155  this->setMatrixInternal(M);
156 }
157 
158 void Transform3DWidget::toggleEditSlot()
159 {
160  bool visible = tGroupBox->isVisible();
161  this->setEditable(!visible);
162 }
163 
164 
165 void Transform3DWidget::toggleInvertSlot()
166 {
167  // the interpretation of matrix is dependent on mInvertAction->isChecked()!
168  mDecomposition.reset(mDecomposition.getMatrix().inverse());
169  this->setModified();
170 // this->updateInvertAction();
171 }
172 
173 void Transform3DWidget::updateInvertAction()
174 {
175  if (mInvertAction->isChecked())
176  {
177  this->setActionText(mInvertAction, "Inverted Matrix", "The matrix is shown inverted");
178  }
179  else
180  {
181  this->setActionText(mInvertAction, "Noninverted Matrix", "The matrix is shown as is. Press to show inverted");
182  }
183 }
184 
185 void Transform3DWidget::setActionText(QAction* action, QString text, QString tip)
186 {
187  if (tip.isEmpty())
188  tip = text;
189  action->setText(text);
190  action->setStatusTip(tip);
191  action->setWhatsThis(tip);
192  action->setToolTip(tip);
193 }
194 
196 {
197  mTextEdit->setReadOnly(!edit);
198  aGroupBox->setVisible(edit);
199  tGroupBox->setVisible(edit);
200 }
201 
202 void Transform3DWidget::addAngleControls(QString uid, QString name, int index, QVBoxLayout* layout)
203 {
204  QHBoxLayout* hLayout = new QHBoxLayout;
205 
206  DoublePropertyPtr adapter = DoubleProperty::initialize(uid, name, "", 0, DoubleRange(-M_PI,M_PI,M_PI/180),1);
207  connect(adapter.get(), SIGNAL(changed()), this, SLOT(changedSlot()));
208  adapter->setInternal2Display(180/M_PI);
209  hLayout->addWidget(new SpinBoxGroupWidget(this, adapter));
210 
211  QSize mMinBarSize = QSize(20,20);
212  MousePadWidget* pad = new MousePadWidget(this, mMinBarSize);
213  pad->setFixedYPos(true);
214  hLayout->addWidget(pad);
215 
216  // use QtSignalAdapters library to work magic:
217  QtSignalAdapters::connect1<void(QPointF)>(pad, SIGNAL(mouseMoved(QPointF)),
218  boost::bind(&Transform3DWidget::rotateSlot, this, _1, index));
219 
220  layout->addLayout(hLayout);
221  mAngleAdapter[index] = adapter;
222 }
223 
224 void Transform3DWidget::addTranslationControls(QString uid, QString name, int index, QVBoxLayout* layout)
225 {
226  QHBoxLayout* hLayout = new QHBoxLayout;
227 
228  DoublePropertyPtr adapter = DoubleProperty::initialize(uid, name, "", 0, DoubleRange(-10000,10000,0.1),1);
229  connect(adapter.get(), SIGNAL(changed()), this, SLOT(changedSlot()));
230  adapter->setInternal2Display(1.0);
231  hLayout->addWidget(new SpinBoxGroupWidget(this, adapter));
232 
233  QSize mMinBarSize = QSize(20,20);
234  MousePadWidget* pad = new MousePadWidget(this, mMinBarSize);
235  pad->setFixedYPos(true);
236  hLayout->addWidget(pad);
237 
238  // use QtSignalAdapters library to work magic:
239  QtSignalAdapters::connect1<void(QPointF)>(pad, SIGNAL(mouseMoved(QPointF)),
240  boost::bind(&Transform3DWidget::translateSlot, this, _1, index));
241 
242  layout->addLayout(hLayout);
243  mTranslationAdapter[index] = adapter;
244 }
245 
246 void Transform3DWidget::rotateSlot(QPointF delta, int index)
247 {
248  double scale = M_PI_2;
249  double factor = scale * delta.x();
250  double current = mAngleAdapter[index]->getValue();
251  mAngleAdapter[index]->setValue(current + factor);
252 }
253 
254 void Transform3DWidget::translateSlot(QPointF delta, int index)
255 {
256  double scale = 20;
257  double factor = scale * delta.x();
258  double current = mTranslationAdapter[index]->getValue();
259  mTranslationAdapter[index]->setValue(current + factor);
260 }
261 
262 
264 {
265 }
266 
268 {
269  this->setMatrixInternal(this->convertToFromExternal(M));
270 }
271 
273 {
274  return this->convertToFromExternal(this->getMatrixInternal());
275 }
276 
277 Transform3D Transform3DWidget::convertToFromExternal(const Transform3D& M) const
278 {
279  if (mInvertAction->isChecked())
280  {
281  return M.inverse();
282  }
283  else
284  {
285  return M;
286  }
287 }
288 
289 void Transform3DWidget::setMatrixInternal(const Transform3D& M)
290 {
291  mDecomposition.reset(M);
292  this->setModified();
293  emit changed();
294 }
295 
296 Transform3D Transform3DWidget::getMatrixInternal() const
297 {
298  return mDecomposition.getMatrix();
299 }
300 
301 // http://en.wikipedia.org/wiki/Rotation_matrix
302 // http://en.wikipedia.org/wiki/Rotation_representation_(mathematics)#Conversion_formulae_between_representations
303 
304 void Transform3DWidget::changedSlot()
305 {
306  if (recursive || mBlockChanges)
307  return;
308  recursive = true;
309  Vector3D xyz(mAngleAdapter[0]->getValue(),mAngleAdapter[1]->getValue(),mAngleAdapter[2]->getValue());
310  mDecomposition.setAngles(xyz);
311 
312  Vector3D t(mTranslationAdapter[0]->getValue(),mTranslationAdapter[1]->getValue(),mTranslationAdapter[2]->getValue());
313  mDecomposition.setPosition(t);
314 
315  this->setModified();
316  emit changed();
317  recursive = false;
318 }
319 
320 namespace
321 {
325  double wrapAngle(double angle)
326  {
327  angle = fmod(angle, M_PI * 2);
328  if (angle > M_PI)
329  angle -= M_PI * 2;
330  if (angle < -M_PI)
331  angle += M_PI * 2;
332  return angle;
333  }
334 }
335 
337 {
338  QString M = qstring_cast(this->getMatrixInternal());
339 
340  mTextEdit->blockSignals(true);
341  int textPos = mTextEdit->textCursor().position();
342  mTextEdit->setText(M);
343  QTextCursor cursor = mTextEdit->textCursor();
344  cursor.setPosition(textPos);
345  mTextEdit->setTextCursor(cursor);
346  mTextEdit->blockSignals(false);
347 
348  Vector3D xyz = mDecomposition.getAngles();
349 
350  mBlockChanges = true;
351 
352  mAngleAdapter[0]->setValue(wrapAngle(xyz[0]));
353  mAngleAdapter[1]->setValue(wrapAngle(xyz[1]));
354  mAngleAdapter[2]->setValue(wrapAngle(xyz[2]));
355 
356  Vector3D t = mDecomposition.getPosition();
357  mTranslationAdapter[0]->setValue(t[0]);
358  mTranslationAdapter[1]->setValue(t[1]);
359  mTranslationAdapter[2]->setValue(t[2]);
360 
361  this->updateInvertAction();
362 
363  mBlockChanges = false;
364 }
365 }
QString qstring_cast(const T &val)
Transform3D Transform3D
Transform3D is a representation of an affine 3D transform.
Vector3D getPosition() const
Definition: cxFrame3D.cpp:87
Utility class for describing a bounded numeric range.
Definition: cxDoubleRange.h:32
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:129
void setMatrix(const Transform3D &M)
MatrixTextEdit(QWidget *parent=NULL)
Vector3D getAngles() const
Definition: cxFrame3D.cpp:82
void reset(Transform3D m)
reinitialize with a fresh matrix.
Definition: cxFrame3D.cpp:35
Transform3D createTransformTranslate(const Vector3D &translation)
Transform3D getMatrix() const
Definition: cxFrame3D.cpp:92
void setFixedYPos(bool on)
void setPosition(Vector3D pos)
Definition: cxFrame3D.cpp:76
boost::shared_ptr< class DoubleProperty > DoublePropertyPtr
Eigen::Vector3d Vector3D
Vector3D is a representation of a point or vector in 3D.
Definition: cxVector3D.h:42
Interface for QWidget which handles widgets uniformly for the system.
Definition: cxBaseWidget.h:88
QSize minimumSizeHint() const
Composite widget for scalar data manipulation.
void setAngles(Vector3D xyz)
Definition: cxFrame3D.cpp:55
bool similar(const CameraInfo &lhs, const CameraInfo &rhs, double tol)
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
Namespace for all CustusX production code.