fw4spl
SActivityWizard.cpp
1 /* ***** BEGIN LICENSE BLOCK *****
2  * FW4SPL - Copyright (C) IRCAD, 2016-2018.
3  * Distributed under the terms of the GNU Lesser General Public License (LGPL) as
4  * published by the Free Software Foundation.
5  * ****** END LICENSE BLOCK ****** */
6 
7 #include "uiMedDataQt/editor/SActivityWizard.hpp"
8 
9 #include <fwActivities/IValidator.hpp>
10 
11 #include <fwCom/Signal.hpp>
12 #include <fwCom/Signal.hxx>
13 #include <fwCom/Signals.hpp>
14 #include <fwCom/Slot.hpp>
15 #include <fwCom/Slots.hpp>
16 #include <fwCom/Slots.hxx>
17 
18 #include <fwData/Composite.hpp>
19 
20 #include <fwGui/dialog/InputDialog.hpp>
21 
22 #include <fwGuiQt/container/QtContainer.hpp>
23 
24 #include <fwMedData/Equipment.hpp>
25 #include <fwMedData/Patient.hpp>
26 #include <fwMedData/Series.hpp>
27 #include <fwMedData/Study.hpp>
28 
29 #include <fwMedDataTools/helper/SeriesDB.hpp>
30 
31 #include <fwRuntime/ConfigurationElement.hpp>
32 #include <fwRuntime/operations.hpp>
33 
34 #include <fwServices/macros.hpp>
35 
36 #include <fwTools/dateAndTime.hpp>
37 #include <fwTools/UUID.hpp>
38 
39 #include <QApplication>
40 #include <QHBoxLayout>
41 #include <QLabel>
42 #include <QMessageBox>
43 #include <QObject>
44 #include <QVBoxLayout>
45 
46 namespace uiMedDataQt
47 {
48 namespace editor
49 {
50 
51 //------------------------------------------------------------------------------
52 
54 
55 //------------------------------------------------------------------------------
56 
57 const ::fwCom::Slots::SlotKeyType SActivityWizard::s_CREATE_ACTIVITY_SLOT = "createActivity";
58 const ::fwCom::Slots::SlotKeyType SActivityWizard::s_UPDATE_ACTIVITY_SLOT = "updateActivity";
59 const ::fwCom::Slots::SlotKeyType SActivityWizard::s_UPDATE_ACTIVITY_SERIES_SLOT = "updateActivitySeries";
60 const ::fwCom::Signals::SignalKeyType SActivityWizard::s_ACTIVITY_CREATED_SIG = "activityCreated";
61 const ::fwCom::Signals::SignalKeyType SActivityWizard::s_ACTIVITY_UPDATED_SIG = "activityUpdated";
62 const ::fwCom::Signals::SignalKeyType SActivityWizard::s_CANCELED_SIG = "canceled";
63 
64 static const ::fwServices::IService::KeyType s_SERIESDB_INOUT = "seriesDB";
65 
66 //------------------------------------------------------------------------------
67 
69  m_mode(Mode::CREATE),
70  m_confirmUpdate(true),
71  m_isCancelable(true)
72 {
73  newSlot(s_CREATE_ACTIVITY_SLOT, &SActivityWizard::createActivity, this);
74  newSlot(s_UPDATE_ACTIVITY_SLOT, &SActivityWizard::updateActivity, this);
75  newSlot(s_UPDATE_ACTIVITY_SERIES_SLOT, &SActivityWizard::updateActivitySeries, this);
76 
77  m_sigActivityCreated = newSignal<ActivityCreatedSignalType>(s_ACTIVITY_CREATED_SIG);
78  m_sigActivityUpdated = newSignal<ActivityUpdatedSignalType>(s_ACTIVITY_UPDATED_SIG);
79  m_sigCanceled = newSignal< CanceledSignalType >(s_CANCELED_SIG);
80 }
81 
82 //------------------------------------------------------------------------------
83 
85 {
86 }
87 
88 //------------------------------------------------------------------------------
89 
91 {
93 
94  const auto config = this->getConfigTree();
95 
96  m_ioSelectorConfig = config.get("ioSelectorConfig", "");
97  SLM_ASSERT("ioSelector Configuration must not be empty", !m_ioSelectorConfig.empty());
98 
99  m_sdbIoSelectorConfig = config.get("sdbIoSelectorConfig", "");
100  if (m_sdbIoSelectorConfig.empty())
101  {
102  m_sdbIoSelectorConfig = m_ioSelectorConfig;
103  }
104 
105  m_confirmUpdate = config.get("confirm", m_confirmUpdate);
106  m_isCancelable = config.get("cancel", m_isCancelable);
107 
108  const auto iconsCfg = config.get_child("icons");
109  const auto iconCfg = iconsCfg.equal_range("icon");
110  for (auto itIcon = iconCfg.first; itIcon != iconCfg.second; ++itIcon)
111  {
112  const auto iconCfg = itIcon->second.get_child("<xmlattr>");
113 
114  const std::string type = iconCfg.get<std::string>("type");
115  SLM_ASSERT("'type' attribute must not be empty", !type.empty());
116  const std::string icon = iconCfg.get<std::string>("icon");
117  SLM_ASSERT("'icon' attribute must not be empty", !icon.empty());
118 
119  const auto file = ::fwRuntime::getResourceFilePath(icon);
120  m_objectIcons[type] = file.string();
121  }
122  OSLM_ASSERT("icons are empty", !m_objectIcons.empty());
123 }
124 
125 //------------------------------------------------------------------------------
126 
128 {
130 
131  fwGuiQt::container::QtContainer::sptr qtContainer = fwGuiQt::container::QtContainer::dynamicCast(getContainer());
132 
133  QWidget* const container = qtContainer->getQtContainer();
134 
135  QVBoxLayout* layout = new QVBoxLayout();
136 
137  m_title = new QLabel("");
138  m_title->setStyleSheet("QLabel { font: bold; color: blue; }");
139  m_title->setAlignment(Qt::AlignHCenter);
140  layout->addWidget(m_title);
141 
142  m_description = new QLabel("");
143  m_description->setStyleSheet("QLabel { font: italic; border: solid 1px;}");
144  m_description->setAlignment(Qt::AlignHCenter);
145  layout->addWidget(m_description);
146 
147  m_activityDataView = new widget::ActivityDataView();
148  m_activityDataView->setIOSelectorConfig(m_ioSelectorConfig);
149  m_activityDataView->setSDBIOSelectorConfig(m_sdbIoSelectorConfig);
150 
151  m_activityDataView->setObjectIconAssociation(m_objectIcons);
152 
153  layout->addWidget(m_activityDataView, 1);
154 
155  QHBoxLayout* buttonLayout = new QHBoxLayout();
156  layout->addLayout(buttonLayout);
157 
158  if (m_isCancelable)
159  {
160  m_cancelButton = new QPushButton("Cancel");
161  m_cancelButton->setToolTip("Cacnel the activity creation");
162  buttonLayout->addWidget(m_cancelButton);
163  }
164 
165  m_resetButton = new QPushButton("Clear");
166  m_resetButton->setToolTip("Clear the current selected data");
167  buttonLayout->addWidget(m_resetButton);
168 
169  m_okButton = new QPushButton("Apply");
170  m_okButton->setToolTip("Create or update the activity with the selected data");
171  buttonLayout->addWidget(m_okButton);
172 
173  container->setLayout(layout);
174 
175  QObject::connect(m_activityDataView.data(), &widget::ActivityDataView::currentChanged,
176  this, &SActivityWizard::onTabChanged);
177  QObject::connect(m_okButton.data(), &QPushButton::clicked, this, &SActivityWizard::onBuildActivity);
178  QObject::connect(m_resetButton.data(), &QPushButton::clicked, this, &SActivityWizard::onReset);
179  if (m_isCancelable)
180  {
181  QObject::connect(m_cancelButton.data(), &QPushButton::clicked, this, &SActivityWizard::onCancel);
182  }
183 }
184 
185 //------------------------------------------------------------------------------
186 
188 {
189  m_activityDataView->clear();
190 
191  QObject::disconnect(m_activityDataView.data(), &widget::ActivityDataView::currentChanged,
192  this, &SActivityWizard::onTabChanged);
193  QObject::disconnect(m_okButton.data(), &QPushButton::clicked, this, &SActivityWizard::onBuildActivity);
194  QObject::disconnect(m_resetButton.data(), &QPushButton::clicked, this, &SActivityWizard::onReset);
195  if (m_isCancelable)
196  {
197  QObject::disconnect(m_cancelButton.data(), &QPushButton::clicked, this, &SActivityWizard::onCancel);
198  }
199 
200  this->destroy();
201 }
202 
203 //------------------------------------------------------------------------------
204 
206 {
207  auto as = this->getInOut< ::fwMedData::ActivitySeries>("activitySeries");
208  if (as)
209  {
210  this->updateActivity(as);
211  }
212 
213  SLM_DEBUG_IF("activity series is not defined, it cannot be updated", !as);
214 }
215 
216 //------------------------------------------------------------------------------
217 
218 void SActivityWizard::createActivity(std::string activityID)
219 {
220  m_mode = Mode::CREATE;
222  info = ::fwActivities::registry::Activities::getDefault()->getInfo(activityID);
223 
224  // load activity bundle
225  std::shared_ptr< ::fwRuntime::Bundle > bundle = ::fwRuntime::findBundle(info.bundleId,
226  info.bundleVersion);
227  if (!bundle->isStarted())
228  {
229  bundle->start();
230  }
231 
232  m_actSeries = ::fwMedData::ActivitySeries::New();
233 
234  m_actSeries->setModality("OT");
235  m_actSeries->setInstanceUID("fwActivities." + ::fwTools::UUID::generateUUID() );
236 
237  ::boost::posix_time::ptime now = ::boost::posix_time::second_clock::local_time();
238  m_actSeries->setDate(::fwTools::getDate(now));
239  m_actSeries->setTime(::fwTools::getTime(now));
240 
241  m_actSeries->setActivityConfigId(info.id);
242 
243  m_title->setText(QString("<h1>%1</h1>").arg(QString::fromStdString(info.title)));
244  m_description->setText(QString::fromStdString(info.description));
245 
246  bool needConfig = false;
247 
248  // If we have requirements but they are not needed to start (maxOccurs = 0), we can skip the config as well
249  for(const auto& req : info.requirements)
250  {
251  if(req.maxOccurs > 0)
252  {
253  needConfig = true;
254  break;
255  }
256  }
257 
258  if (needConfig)
259  {
260  m_activityDataView->fillInformation(info);
261  if (m_activityDataView->count() > 1)
262  {
263  m_okButton->setText("Next");
264  }
265 
266  this->slot(s_SHOW_SLOT)->asyncRun();
267  }
268  else
269  {
270  // Create data automatically if they are not provided by the user
271  for(const auto& req : info.requirements)
272  {
273  SLM_ASSERT("minOccurs and maxOccurs should be 0", req.minOccurs == 0 && req.maxOccurs == 0);
274  ::fwData::Composite::sptr data = m_actSeries->getData();
275  (*data)[req.name] = ::fwData::factory::New(req.type);
276  }
277 
278  ::fwMedData::SeriesDB::sptr seriesDB = this->getInOut< ::fwMedData::SeriesDB >(s_SERIESDB_INOUT);
279  if (!seriesDB)
280  {
281  FW_DEPRECATED_KEY(s_SERIESDB_INOUT, "inout", "18.0");
282  seriesDB = this->getObject< ::fwMedData::SeriesDB >();
283  }
284 
285  ::fwMedDataTools::helper::SeriesDB helper(seriesDB);
286  helper.add(m_actSeries);
287  helper.notify();
288  m_sigActivityCreated->asyncEmit(m_actSeries);
289  }
290 }
291 
292 //------------------------------------------------------------------------------
293 
294 void SActivityWizard::updateActivity(::fwMedData::ActivitySeries::sptr activitySeries)
295 {
297  info = ::fwActivities::registry::Activities::getDefault()->getInfo(activitySeries->getActivityConfigId());
298 
299  // load activity bundle
300  std::shared_ptr< ::fwRuntime::Bundle > bundle = ::fwRuntime::findBundle(info.bundleId,
301  info.bundleVersion);
302  if (!bundle->isStarted())
303  {
304  bundle->start();
305  }
306 
307  m_title->setText(QString("<h1>%1</h1>").arg(QString::fromStdString(info.title)));
308  m_description->setText(QString::fromStdString(info.description));
309 
310  m_mode = Mode::UPDATE;
311  m_actSeries = activitySeries;
312 
313  bool needConfig = false;
314 
315  // If we have requirements but they are not needed to start (maxOccurs = 0), we can skip the config as well
316  for(const auto& req : info.requirements)
317  {
318  if(req.maxOccurs != 0)
319  {
320  needConfig = true;
321  break;
322  }
323  }
324 
325  if (needConfig)
326  {
327  m_activityDataView->fillInformation(m_actSeries);
328  if (m_activityDataView->count() > 1)
329  {
330  m_okButton->setText("Next");
331  }
332  }
333  else
334  {
335  // Start immediately without popping any configuration UI
336  ::fwData::Object::ModifiedSignalType::sptr sig;
338  sig->asyncEmit();
339  m_sigActivityUpdated->asyncEmit(m_actSeries);
340  }
341 }
342 
343 //------------------------------------------------------------------------------
344 
345 void SActivityWizard::updateActivitySeries(::fwMedData::Series::sptr series)
346 {
347  ::fwMedData::ActivitySeries::sptr activitySeries = ::fwMedData::ActivitySeries::dynamicCast(series);
348  if (activitySeries)
349  {
350  this->updateActivity(activitySeries);
351  }
352 }
353 
354 //------------------------------------------------------------------------------
355 
356 void SActivityWizard::onTabChanged(int index)
357 {
358  if (index == m_activityDataView->count() - 1)
359  {
360  m_okButton->setText("Apply");
361  }
362  else
363  {
364  m_okButton->setText("Next");
365  }
366 }
367 
368 //------------------------------------------------------------------------------
369 
370 void SActivityWizard::onReset()
371 {
372  if (m_actSeries)
373  {
375  info = ::fwActivities::registry::Activities::getDefault()->getInfo(m_actSeries->getActivityConfigId());
376  m_activityDataView->fillInformation(info);
377 
378  if (m_activityDataView->count() > 1)
379  {
380  m_okButton->setText("Next");
381  }
382  }
383 }
384 
385 //------------------------------------------------------------------------------
386 
387 void SActivityWizard::onCancel()
388 {
389  m_activityDataView->clear();
390  m_sigCanceled->asyncEmit();
391 }
392 
393 //------------------------------------------------------------------------------
394 
395 void SActivityWizard::onBuildActivity()
396 {
397  int index = m_activityDataView->currentIndex();
398  int lastTab = m_activityDataView->count() -1;
399 
400  std::string errorMsg;
401  // Check current data
402  if (m_activityDataView->checkData(size_t(index), errorMsg))
403  {
404  if (index != lastTab)
405  {
406  // enable and select the next tab
407  m_activityDataView->setTabEnabled(index+1, true);
408  m_activityDataView->setCurrentIndex(index+1);
409  }
410 
411  else // index == lastTab
412  {
413  // Create/update activity
414  if (m_mode == Mode::UPDATE && m_confirmUpdate)
415  {
416  QMessageBox::StandardButton button = QMessageBox::question(
417  qApp->activeWindow(),
418  "Update activity",
419  "You will override your activity. You could loose some data.\n"
420  "Would you duplicate your activity ?",
421  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
422  QMessageBox::No);
423 
424  if (button == QMessageBox::Cancel)
425  {
426  return;
427  }
428  else if (button == QMessageBox::Yes)
429  {
430  m_actSeries = ::fwData::Object::copy(m_actSeries);
431  m_mode = Mode::CREATE; // The new activity should be added in the seriesDB
432  }
433  }
434 
435  // check all data and create/update the activity
436  bool ok = m_activityDataView->checkAndComputeData(m_actSeries, errorMsg);
437  if (ok)
438  {
439  ::fwData::Composite::sptr data = m_actSeries->getData();
440 
441  // Copy the patient/study information of a series
442  ::fwMedData::Series::sptr series;
443  for(const auto& elt : (*data) )
444  {
445  series = ::fwMedData::Series::dynamicCast(elt.second);
446  if(series)
447  {
448  m_actSeries->setPatient( ::fwData::Object::copy(series->getPatient()) );
449  m_actSeries->setStudy( ::fwData::Object::copy(series->getStudy()) );
450  m_actSeries->setEquipment( ::fwData::Object::copy(series->getEquipment()) );
451  break;
452  }
453  }
454 
455  if (m_mode == Mode::CREATE)
456  {
457  // Add the new activity series in seriesDB
460  m_actSeries->getActivityConfigId());
461 
462  std::string description = ::fwGui::dialog::InputDialog::showInputDialog(
463  "Activity creation",
464  "Please, give a description of the activity.",
465  info.title);
466  if (description.empty())
467  {
468  return;
469  }
470  m_actSeries->setDescription(description);
471  ::fwMedData::SeriesDB::sptr seriesDB = this->getInOut< ::fwMedData::SeriesDB >(s_SERIESDB_INOUT);
472  if (!seriesDB)
473  {
474  FW_DEPRECATED_KEY(s_SERIESDB_INOUT, "inout", "18.0");
475  seriesDB = this->getObject< ::fwMedData::SeriesDB >();
476  }
477  ::fwMedDataTools::helper::SeriesDB helper(seriesDB);
478  helper.add(m_actSeries);
479  helper.notify();
480  m_sigActivityCreated->asyncEmit(m_actSeries);
481  }
482  else // m_mode == Mode::UPDATE
483  {
484  ::fwData::Object::ModifiedSignalType::sptr sig;
486  sig->asyncEmit();
487  m_sigActivityUpdated->asyncEmit(m_actSeries);
488  }
489  }
490  else
491  {
492  QString message = "This activity can not be created : \n";
493  message.append(QString::fromStdString(errorMsg));
494  QMessageBox::warning(qApp->activeWindow(), "Activity Creation", message);
495  SLM_ERROR(errorMsg);
496  }
497  }
498  }
499  else
500  {
501  QMessageBox::warning(qApp->activeWindow(), "Error", QString::fromStdString(errorMsg));
502  }
503 }
504 
505 //------------------------------------------------------------------------------
506 
507 } //namespace editor
508 } //namespace uiMedDataQt
virtual void starting() override
This method creates the editor gui.
#define FW_DEPRECATED_KEY(newKey, access, version)
Use this macro when deprecating a service key to warn the developer.
Definition: spyLog.hpp:366
The namespace uiMedDataQt contains editors for medical data.
virtual void stopping() override
This method destroys the editor gui.
virtual UIMEDDATAQT_API ~SActivityWizard() noexcept
Destructor. Do nothing.
Holds Activities configuration.
Definition: Activities.hpp:175
#define OSLM_ASSERT(message, cond)
work like &#39;assert&#39; from &#39;cassert&#39;, with in addition a message logged by spylog (with FATAL loglevel) ...
Definition: spyLog.hpp:310
#define SLM_DEBUG_IF(message, cond)
Definition: spyLog.hpp:243
Defines the service interface managing the editor service for object.
Definition: IEditor.hpp:25
static FWACTIVITIES_API Activities::sptr getDefault()
Return the default global instance of Activities.
Definition: Activities.cpp:226
FWGUI_API void destroy()
Stops sub-views and toobar services. Destroys view, sub-views and toolbar containers.
virtual void configuring() override
Initialize the editor.
This editor allows to select the data required by an activity in order to create the ActivitySeries...
std::string bundleVersion
Version of the bundle containing the activity.
Definition: Activities.hpp:198
Defines an helper to modify an fwMedData::SeriesDB and create in parallel the message to announce thi...
std::string bundleId
Identifier of the bundle containing the activity.
Definition: Activities.hpp:197
#define SLM_ERROR(message)
Definition: spyLog.hpp:272
#define SLM_ASSERT(message, cond)
work like &#39;assert&#39; from &#39;cassert&#39;, with in addition a message logged by spylog (with FATAL loglevel) ...
Definition: spyLog.hpp:308
static FWDATA_API::fwData::Object::sptr copy(const ::fwData::Object::csptr &source)
return a copy of the source. if source is a null pointer, return a null pointer.
static FWGUI_API std::string showInputDialog(const std::string &title, const std::string &message, const std::string &text="")
FWGUI_API void create()
Creates view, sub-views and toolbar containers. Manages sub-views and toobar services.
static FWDATA_APIconst::fwCom::Signals::SignalKeyType s_MODIFIED_SIG
Key in m_signals map of signal m_sigModified.
static FWGUI_APIconst::fwCom::Slots::SlotKeyType s_SHOW_SLOT
Slot to show the container.
FWTOOLS_API std::string getDate(const ::boost::posix_time::ptime &dateAndTime)
Convert a boost time to a string date.
FWTOOLS_API std::string getTime(const ::boost::posix_time::ptime &dateAndTime)
Convert a boost time to a string time.
virtual void updating() override
Update the activity if it is defined in the configuration, else does nothing.
This class displays a tab widget allowing to select the required data to create an activity...
UIMEDDATAQT_API SActivityWizard() noexcept
Constructor. Do nothing.
FWGUI_API void initialize()
Initialize managers.
virtual FWSERVICES_API void info(std::ostream &_sstream)
Write information in a stream.
Definition: IService.cpp:74
static FWTOOLS_API UUIDType generateUUID()
Return a new extended UUID;.
Definition: UUID.cpp:114
FWSERVICES_API ConfigType getConfigTree() const
Return the configuration, in an boost property tree.
Definition: IService.cpp:247