CustusX  2023.01.05-dev+develop.0da12
An IGT application
cxLayoutEditorWidget.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 #include "cxLayoutEditorWidget.h"
12 #include <iostream>
13 #include <QtWidgets>
14 
15 #include "cxTypeConversions.h"
16 #include "cxUtilHelpers.h"
17 #include "cxViewService.h"
18 #include "cxBoolProperty.h"
19 #include "cxHelperWidgets.h"
20 
21 namespace cx
22 {
23 
24 
26  QWidget(parent),
27  mViewService(viewService)
28 {
29  mTopLayout = new QVBoxLayout(this);
30  QHBoxLayout* nameLayout = new QHBoxLayout;
31  mTopLayout->addLayout(nameLayout);
32  mRCLayout = new QHBoxLayout;
33  mTopLayout->addLayout(mRCLayout);
34  mLayout = new QGridLayout;
35  mLayout->setMargin(0);
36  mLayout->setSpacing(2);
37  mTopLayout->addLayout(mLayout);
38 
39  mNameEdit = new QLineEdit;
40  connect(mNameEdit, SIGNAL(editingFinished()), this, SLOT(nameChanged()));
41  nameLayout->addWidget(new QLabel("Name"));
42  nameLayout->addWidget(mNameEdit);
43 
44 
45  // create the row/column bar
46  mRowsEdit = new QSpinBox;
47  mRowsEdit->setRange(1,LayoutData::MaxGridSize);
48  mColsEdit = new QSpinBox;
49  mColsEdit->setRange(1,LayoutData::MaxGridSize);
50  connect(mRowsEdit, SIGNAL(valueChanged(int)), this, SLOT(rowsColumnsChangedSlot()));
51  connect(mColsEdit, SIGNAL(valueChanged(int)), this, SLOT(rowsColumnsChangedSlot()));
52  mRCLayout->addWidget(new QLabel("Rows"));
53  mRCLayout->addWidget(mRowsEdit);
54  mRCLayout->addWidget(new QLabel("Columns"));
55  mRCLayout->addWidget(mColsEdit);
56  mRCLayout->addStretch();
57 
58  for (int i=ptNOPLANE+1; i<ptCOUNT; ++i)
59  {
60  PLANE_TYPE type = static_cast<PLANE_TYPE>(i);
61  mPlaneNames[type] = qstring_cast(type);
62  mViewNames.push_back(ViewNamesType(type,View::VIEW_2D, qstring_cast(type)));
63  }
64  mViewNames.push_back(ViewNamesType(ptNOPLANE,View::VIEW_3D, "3D"));
65  mViewNames.push_back(ViewNamesType(ptNOPLANE,View::VIEW_REAL_TIME, "RT"));
66 // mPlaneNames[ptNOPLANE] = "3D";
67 // mPlaneNames[static_cast<PLANE_TYPE>(-1)] = "RT";
68 
69  mSelection = LayoutRegion(-1,-1);
70  initCache();
71 
72  mOffScreenRendering = BoolProperty::initialize("Offscreeen Render", "",
73  "Render the layout to memory only.\n"
74  "This will cause the displayed area to be black,\n"
75  "but the application can access the rendering programatically.",
76  false);
77  mTopLayout->addWidget(sscCreateDataWidget(this, mOffScreenRendering));
78  connect(mOffScreenRendering.get(), &BoolProperty::changed, this, &LayoutEditorWidget::onOffScreenRenderingChanged);
79 
80 
81  this->updateGrid();
82 }
83 
85 {
86  mViewData = data;
87  this->updateGrid();
88 }
89 
91 {
92  return mViewData;
93 }
94 
95 void LayoutEditorWidget::onOffScreenRenderingChanged()
96 {
97  mViewData.setOffScreenRendering(mOffScreenRendering->getValue());
98 }
99 
100 void LayoutEditorWidget::nameChanged()
101 {
102  mViewData.setName(mNameEdit->text());
103 }
104 
105 void LayoutEditorWidget::contextMenuSlot(const QPoint& point)
106 {
107  //QWidget* sender = dynamic_cast<QWidget*>(this->sender());
108  QPoint pointGlobal = this->mapToGlobal(point);
109  QMenu menu(this);
110 
111  LayoutViewData viewData = this->getViewData(point);
112 
113  QAction* mergeAction = new QAction("merge view", &menu);
114  mergeAction->setEnabled(this->getSelectedViews().size()>1);
115  connect(mergeAction, SIGNAL(triggered()), this, SLOT(mergeActionSlot()));
116  menu.addAction(mergeAction);
117 
118  QAction* splitAction = new QAction("split view", &menu);
119  splitAction->setEnabled(mSelection.span.row!=1 || mSelection.span.col!=1);
120  connect(splitAction, SIGNAL(triggered()), this, SLOT(splitActionSlot()));
121  menu.addAction(splitAction);
122 
123  menu.addSeparator();
124 
125  // actions for view group
126 // int viewGroupCount = static_cast<int>(viewService()->getViewGroups().size());
127  int viewGroupCount = mViewService->groupCount();
128  QActionGroup* groupActions = new QActionGroup(this);
129  for (int i=0; i<viewGroupCount; ++i)
130  {
131  QAction* action = new QAction(QString("Group %1").arg(i), groupActions);
132  action->setData(QVariant(i));
133  action->setCheckable(true);
134  connect(action, SIGNAL(triggered()), this, SLOT(groupActionSlot()));
135  action->setChecked(viewData.mGroup==i);
136 // menu.addAction(action);
137  }
138 
139  //menu.addMenu("View Group")->addActions(groupActions->actions());
140  menu.addActions(groupActions->actions());
141  menu.addSeparator();
142 
143  // actions for view type
144  QActionGroup* typeActions = new QActionGroup(this);
145 // for (std::map<PLANE_TYPE, QString>::iterator iter=mPlaneNames.begin(); iter!=mPlaneNames.end(); ++iter)
146  for (unsigned i=0; i<mViewNames.size(); ++i)
147  {
148  ViewNamesType current = mViewNames[i];
149 // PLANE_TYPE type = static_cast<PLANE_TYPE>(i);
150 // PLANE_TYPE type = iter->first;
151 // QString name = iter->second;
152 
153  QAction* action = new QAction(QString("%1").arg(current.mName), typeActions);
154 // action->setData(QVariant(t));
155  action->setCheckable(true);
156  connect(action, SIGNAL(triggered()), this, SLOT(typeActionSlot()));
157  action->setChecked(viewData.mPlane==current.mPlane && viewData.mType==current.mView);
158  }
159 // for (int i=ptNOPLANE; i<ptCOUNT; ++i)
160 // {
161 // PLANE_TYPE type = static_cast<PLANE_TYPE>(i);
162 //
163 // QAction* action = new QAction(QString("%1").arg(mPlaneNames[type]), typeActions);
164 // action->setData(QVariant(i));
165 // action->setCheckable(true);
166 // connect(action, SIGNAL(triggered()), this, SLOT(typeActionSlot()));
167 // action->setChecked(viewData.mPlane==type);
168 // //menu.addAction(action);
169 // }
170 
171  //menu.addMenu("View Plane Type")->addActions(typeActions->actions());
172  menu.addActions(typeActions->actions());
173 
174  menu.exec(pointGlobal);
175 }
176 
177 void LayoutEditorWidget::splitActionSlot()
178 {
179  mViewData.split(mSelection);
180  this->updateGrid();
181 }
182 
183 void LayoutEditorWidget::mergeActionSlot()
184 {
185  mViewData.merge(mSelection);
186  this->updateGrid();
187 }
188 
189 void LayoutEditorWidget::groupActionSlot()
190 {
191  QAction* sender = dynamic_cast<QAction*>(this->sender());
192  if (!sender)
193  return;
194  int group = sender->data().toInt();
195 
196  std::set<LayoutData::iterator> selection = this->getSelectedViews();
197  for (std::set<LayoutData::iterator>::iterator iter=selection.begin(); iter!=selection.end(); ++iter)
198  (*iter)->mGroup = group;
199 
200  this->updateGrid();
201 }
202 
203 void LayoutEditorWidget::typeActionSlot()
204 {
205  QAction* sender = dynamic_cast<QAction*>(this->sender());
206  if (!sender)
207  return;
208 // PLANE_TYPE type = static_cast<PLANE_TYPE>(sender->data().toInt());
209  ViewNamesType type;
210  for (unsigned i=0; i<mViewNames.size(); ++i)
211  if (mViewNames[i].mName == sender->text())
212  type=mViewNames[i];
213 
214  std::set<LayoutData::iterator> selection = this->getSelectedViews();
215  for (std::set<LayoutData::iterator>::iterator iter=selection.begin(); iter!=selection.end(); ++iter)
216  {
217  (*iter)->mPlane = type.mPlane;
218  (*iter)->mType = type.mView;
219  }
220 
221  this->updateGrid();
222 }
223 
224 void LayoutEditorWidget::mouseMoveEvent(QMouseEvent* event)
225 {
226  this->updateSelection(event->pos());
227 }
228 
229 void LayoutEditorWidget::updateSelection(QPoint pos)
230 {
231  LayoutViewData start = this->getViewData(mClickPos);
232  LayoutViewData stop = this->getViewData(pos);
233  mSelection = merge(start.mRegion, stop.mRegion);
234  this->colorRegion(mSelection, "dimgrey", "lightgrey");
235 }
236 
237 /* Return a set of unique iterators into the layout data,
238  * representing the selected region.
239  */
240 std::set<LayoutData::iterator> LayoutEditorWidget::getSelectedViews()
241 {
242  std::set<LayoutData::iterator> retval;
243  for (int r=mSelection.pos.row; r<mSelection.pos.row+mSelection.span.row; ++r)
244  for (int c=mSelection.pos.col; c<mSelection.pos.col+mSelection.span.col; ++c)
245  retval.insert(mViewData.find(LayoutPosition(r,c)));
246  return retval;
247 }
248 
249 void LayoutEditorWidget::mousePressEvent(QMouseEvent* event)
250 {
251  mClickPos = event->pos();
252 
253  if (event->button()==Qt::RightButton)
254  {
255  // reselect if click is outside old selection
256  if (!mSelection.contains(this->getViewData(mClickPos).mRegion.pos))
257  this->updateSelection(event->pos());
258 
259  //std::cout << "mouse press context" << std::endl;
260  this->contextMenuSlot(event->pos());
261  }
262  else
263  {
264  //std::cout << "mouse press clean" << std::endl;
265  this->updateSelection(event->pos());
266  }
267 }
268 
269 void LayoutEditorWidget::colorRegion(LayoutRegion region, QString selectColor, QString backColor)
270 {
271  for (LayoutData::iterator iter=mViewData.begin(); iter!=mViewData.end(); ++iter)
272  {
273  LayoutPosition pos = iter->mRegion.pos;
274  QString color;
275 
276  if (region.contains(pos))
277  color = selectColor;
278  else
279  color = backColor;
280 
281  mViewDataCache[pos.row][pos.col].mFrame->setStyleSheet(QString("QFrame { background-color: %1 }").arg(color));
282  }
283 }
284 
288 LayoutViewData LayoutEditorWidget::getViewData(QPoint pt)
289 {
290  for (LayoutData::iterator iter=mViewData.begin(); iter!=mViewData.end(); ++iter)
291  {
292  LayoutPosition pos = iter->mRegion.pos;
293  if (!mViewDataCache[pos.row][pos.col].mFrame->geometry().contains(pt))
294  continue;
295 
296  return *iter;
297  }
298 
299  return LayoutViewData();
300 }
301 
305 void LayoutEditorWidget::rowsColumnsChangedSlot()
306 {
307  mViewData.resize(mRowsEdit->value(), mColsEdit->value());
308  this->setSaneGroupIDs();
309  this->updateGrid();
310 }
311 
312 QString LayoutEditorWidget::getViewName(LayoutViewData data) const
313 {
314  for (unsigned i=0; i<mViewNames.size(); ++i)
315  {
316  if (mViewNames[i].mPlane==data.mPlane && mViewNames[i].mView==data.mType)
317  return mViewNames[i].mName;
318  }
319  return "NA";
320 }
321 
325 void LayoutEditorWidget::setSaneGroupIDs()
326 {
327  for (LayoutData::iterator iter=mViewData.begin(); iter!=mViewData.end(); ++iter)
328  {
329  if (iter->mGroup<0)
330  iter->mGroup = 0;
331  }
332 }
333 
338 void LayoutEditorWidget::updateGrid()
339 {
340  //std::cout << "pre update:" << streamXml2String(mViewData) << std::endl;
341 
342  this->clearDisplay();
343 
344  //std::cout << "update start" << std::endl;
345  for (LayoutData::iterator iter=mViewData.begin(); iter!=mViewData.end(); ++iter)
346  {
347  LayoutRegion region = iter->mRegion;
348  GridElement gridData = mViewDataCache[region.pos.row][region.pos.col];
349 
350  // add and show frame in correct position
351  mLayout->addWidget(gridData.mFrame, region.pos.row, region.pos.col, region.span.row, region.span.col);
352  gridData.mFrame->show();
353 
354  // set view text
355  QString name = this->getViewName(*iter);
356  if (iter->mGroup<0 && name.isEmpty())
357  gridData.mLabel->setText("NA");
358  else
359  gridData.mLabel->setText(QString("%1/%2").arg(iter->mGroup).arg(name));
360  }
361 
362  mNameEdit->setText(mViewData.getName());
363  mOffScreenRendering->setValue(mViewData.getOffScreenRendering());
364 // mRowsEdit->setText(qstring_cast(mViewData.size().row));
365 // mColsEdit->setText(qstring_cast(mViewData.size().col));
366 
367  mRowsEdit->blockSignals(true);
368  mRowsEdit->setValue(mViewData.size().row);
369  mRowsEdit->blockSignals(false);
370 
371  mColsEdit->blockSignals(true);
372  mColsEdit->setValue(mViewData.size().col);
373  mColsEdit->blockSignals(false);
374 
375 // this->colorRegion(LayoutRegion(-1,-1,1,1), "lightgrey", "lightgrey");
376  this->colorRegion(mSelection, "dimgrey", "lightgrey");
377 
378  //this->updateGeometry();
379  //QSize msize = mLayout->minimumSize();
380 // QSize rsize = mGridWidget->size();
381  //std::cout << "minsize: " << msize.width() << "," << msize.height() << std::endl;
382 // std::cout << "real size: " << rsize.width() << "," << rsize.height() << std::endl;
383 
384 // QString color("QFrame { background-color: red }");
385 // mViewData[0][0].mFrame->setStyleSheet(color);
386 
387 // mGridWidget->resize(msize);
388 // this->resize(400,700);
389 // QTimer::singleShot(0, this, SLOT(setNiceSize()));
390 
391  // std::cout << "post update:" << streamXml2String(mViewData) << std::endl;
392 
393 }
394 
395 //void LayoutEditorWidget::setNiceSize()
396 //{
397 // //this->resize(400,700);
398 //}
399 
400 void LayoutEditorWidget::clearDisplay()
401 {
402  for (unsigned r = 0; r < mViewDataCache.size(); ++r)
403  {
404  for (unsigned c = 0; c < mViewDataCache[r].size(); ++c)
405  {
406  mViewDataCache[r][c].mFrame->hide();
407  }
408  }
409 }
410 
411 void LayoutEditorWidget::initCache()
412 {
413  int maxRows = LayoutData::MaxGridSize;
414  int maxCols = LayoutData::MaxGridSize;
415  mViewDataCache.resize(maxRows);
416 
417  for (int r = 0; r < maxRows; ++r)
418  {
419  mViewDataCache[r].resize(maxCols);
420 
421  for (int c = 0; c < maxCols; ++c)
422  {
423  QFrame* frame = new QFrame(this);
424  //frame->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
425  frame->setFrameStyle(QFrame::Panel | QFrame::Sunken);
426  frame->setLineWidth(3);
427  frame->setLayout(new QVBoxLayout);
428  QLabel* label = new QLabel("NA", frame);
429  frame->layout()->addWidget(label);
430 
431  mViewDataCache[r][c].mFrame = frame;
432  mViewDataCache[r][c].mLabel = label;
433  }
434  }
435 }
436 
437 
438 } // namespace cx
QString qstring_cast(const T &val)
int mGroup
what group to connect to. -1 means not set.
Definition: cxLayoutData.h:63
static BoolPropertyPtr initialize(const QString &uid, QString name, QString help, bool value, QDomNode root=QDomNode())
ViewDataContainer::iterator iterator
Definition: cxLayoutData.h:80
bool contains(LayoutPosition p) const
Definition: cxLayoutData.h:48
boost::shared_ptr< class ViewService > ViewServicePtr
LayoutPosition span
size of region
Definition: cxLayoutData.h:46
bool merge(LayoutRegion region)
iterator begin()
Definition: cxLayoutData.h:97
void setName(const QString &name)
Definition: cxLayoutData.h:92
PLANE_TYPE mPlane
ptNOPLANE means 3D
Definition: cxLayoutData.h:64
LayoutRegion mRegion
Definition: cxLayoutData.h:66
LayoutData getLayoutData() const
iterator find(LayoutPosition pos)
void split(iterator iter)
void resize(int rows, int cols)
LayoutPosition size() const
Definition: cxLayoutData.h:108
void changed()
emit when the underlying data value is changed: The user interface will be updated.
ptNOPLANE
a initial plane, if no yet set
Definition: cxDefinitions.h:39
QString getName() const
Definition: cxLayoutData.h:89
static const int MaxGridSize
Definition: cxLayoutData.h:82
void setLayoutData(const LayoutData &data)
LayoutPosition pos
start position of region
Definition: cxLayoutData.h:45
QWidget * sscCreateDataWidget(QWidget *parent, PropertyPtr data, QGridLayout *gridLayout, int row)
Create a widget capable of displaying the input data.
iterator end()
Definition: cxLayoutData.h:98
View::Type mType
Definition: cxLayoutData.h:65
void setOffScreenRendering(bool val)
Definition: cxLayoutData.h:91
LayoutRegion merge(LayoutRegion a, LayoutRegion b)
LayoutEditorWidget(cx::ViewServicePtr viewService, QWidget *parent)
bool getOffScreenRendering() const
Definition: cxLayoutData.h:90
Namespace for all CustusX production code.