InstrumentWidget.cpp 49.7 KB
Newer Older
1
2
3
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4
5
//   NScD Oak Ridge National Laboratory, European Spallation Source,
//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6
// SPDX - License - Identifier: GPL - 3.0 +
Roman Tolchenov's avatar
Roman Tolchenov committed
7
#include "MantidQtWidgets/InstrumentView/InstrumentWidget.h"
LamarMoore's avatar
LamarMoore committed
8
9
#include "MantidGeometry/Instrument/ComponentInfo.h"
#include "MantidGeometry/Instrument/DetectorInfo.h"
Roman Tolchenov's avatar
Roman Tolchenov committed
10
#include "MantidQtWidgets/Common/MantidDesktopServices.h"
11
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
LamarMoore's avatar
LamarMoore committed
12
#include "MantidQtWidgets/Common/TSVSerialiser.h"
13
#endif
Roman Tolchenov's avatar
Roman Tolchenov committed
14
15
16
17
18
19
#include "MantidQtWidgets/InstrumentView/DetXMLFile.h"
#include "MantidQtWidgets/InstrumentView/InstrumentActor.h"
#include "MantidQtWidgets/InstrumentView/InstrumentWidgetMaskTab.h"
#include "MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h"
#include "MantidQtWidgets/InstrumentView/InstrumentWidgetRenderTab.h"
#include "MantidQtWidgets/InstrumentView/InstrumentWidgetTreeTab.h"
20

21
#include "MantidAPI/Axis.h"
22
#include "MantidAPI/IMaskWorkspace.h"
23
#include "MantidAPI/IPeaksWorkspace.h"
24
#include "MantidAPI/MatrixWorkspace.h"
25
#include "MantidAPI/Workspace.h"
26
#include "MantidKernel/Unit.h"
Roman Tolchenov's avatar
Roman Tolchenov committed
27
28
29
30
31
32
#include "MantidQtWidgets/InstrumentView/PanelsSurface.h"
#include "MantidQtWidgets/InstrumentView/Projection3D.h"
#include "MantidQtWidgets/InstrumentView/SimpleWidget.h"
#include "MantidQtWidgets/InstrumentView/UnwrappedCylinder.h"
#include "MantidQtWidgets/InstrumentView/UnwrappedSphere.h"
#include "MantidQtWidgets/InstrumentView/XIntegrationControl.h"
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

#include <Poco/ActiveResult.h>

#include <QApplication>
#include <QCheckBox>
#include <QColorDialog>
#include <QComboBox>
#include <QDoubleValidator>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QFileDialog>
#include <QFileInfo>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QImageWriter>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QMessageBox>
54
#include <QMimeData>
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <QPushButton>
#include <QRadioButton>
#include <QSettings>
#include <QSplitter>
#include <QStackedLayout>
#include <QString>
#include <QTemporaryFile>
#include <QUrl>
#include <QVBoxLayout>
#include <QWidget>

#include <numeric>
#include <stdexcept>
David Fairbrother's avatar
David Fairbrother committed
68
#include <utility>
69
70
71
72
73

using namespace Mantid::API;
using namespace Mantid::Geometry;
using namespace MantidQt::API;

74
75
namespace MantidQt {
namespace MantidWidgets {
Giovanni Di Siena's avatar
Giovanni Di Siena committed
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
namespace {
/**
 * An object to correctly set the flag marking workspace replacement
 */
struct WorkspaceReplacementFlagHolder {
  /**
   * @param :: reference to the workspace replacement flag
   */
  explicit WorkspaceReplacementFlagHolder(bool &replacementFlag)
      : m_worskpaceReplacementFlag(replacementFlag) {
    m_worskpaceReplacementFlag = true;
  }
  ~WorkspaceReplacementFlagHolder() { m_worskpaceReplacementFlag = false; }

private:
  WorkspaceReplacementFlagHolder();
  bool &m_worskpaceReplacementFlag;
};
94

Giovanni Di Siena's avatar
Giovanni Di Siena committed
95
96
} // namespace

97
98
99
100
// Name of the QSettings group to store the InstrumentWindw settings
const char *InstrumentWidgetSettingsGroup = "Mantid/InstrumentWidget";

/**
LamarMoore's avatar
LamarMoore committed
101
102
103
 * Exception type thrown when an istrument has no sample and cannot be displayed
 * in the instrument view.
 */
104
105
106
107
108
109
110
111
class InstrumentHasNoSampleError : public std::runtime_error {
public:
  InstrumentHasNoSampleError()
      : std::runtime_error("Instrument has no sample.\nSource and sample need "
                           "to be set in the IDF.") {}
};

/**
LamarMoore's avatar
LamarMoore committed
112
113
 * Constructor.
 */
114
115
116
117
118
119
120
121
122
123
124
InstrumentWidget::InstrumentWidget(const QString &wsName, QWidget *parent,
                                   bool resetGeometry, bool autoscaling,
                                   double scaleMin, double scaleMax,
                                   bool setDefaultView)
    : QWidget(parent), WorkspaceObserver(), m_InstrumentDisplay(nullptr),
      m_simpleDisplay(nullptr), m_workspaceName(wsName),
      m_instrumentActor(nullptr), m_surfaceType(FULL3D),
      m_savedialog_dir(QString::fromStdString(
          Mantid::Kernel::ConfigService::Instance().getString(
              "defaultsave.directory"))),
      mViewChanged(false), m_blocked(false),
Anthony Lim's avatar
Anthony Lim committed
125
126
      m_instrumentDisplayContextMenuOn(false),
      m_stateOfTabs(std::vector<std::pair<std::string, bool>>{}),
Giovanni Di Siena's avatar
Giovanni Di Siena committed
127
      m_wsReplace(false), m_help(nullptr) {
128
  setFocusPolicy(Qt::StrongFocus);
129
  m_mainLayout = new QVBoxLayout(this);
130
  auto *controlPanelLayout = new QSplitter(Qt::Horizontal);
131
132
133
134
135
136
137
138
139
140

  // Add Tab control panel
  mControlsTab = new QTabWidget(this);
  controlPanelLayout->addWidget(mControlsTab);
  controlPanelLayout->setSizePolicy(QSizePolicy::Expanding,
                                    QSizePolicy::Expanding);

  // Create the display widget
  m_InstrumentDisplay = new MantidGLWidget(this);
  m_InstrumentDisplay->installEventFilter(this);
141
  m_InstrumentDisplay->setMinimumWidth(600);
142
143
144
145
146
147
148
149
150
151
152
153
154
155
  connect(this, SIGNAL(enableLighting(bool)), m_InstrumentDisplay,
          SLOT(enableLighting(bool)));

  // Create simple display widget
  m_simpleDisplay = new SimpleWidget(this);
  m_simpleDisplay->installEventFilter(this);

  QWidget *aWidget = new QWidget(this);
  m_instrumentDisplayLayout = new QStackedLayout(aWidget);
  m_instrumentDisplayLayout->addWidget(m_InstrumentDisplay);
  m_instrumentDisplayLayout->addWidget(m_simpleDisplay);

  controlPanelLayout->addWidget(aWidget);

156
  m_mainLayout->addWidget(controlPanelLayout);
157

158
159
160
  m_instrumentActor.reset(
      new InstrumentActor(m_workspaceName, autoscaling, scaleMin, scaleMax));

161
162
163
164
  m_xIntegration = new XIntegrationControl(this);
  m_mainLayout->addWidget(m_xIntegration);
  connect(m_xIntegration, SIGNAL(changed(double, double)), this,
          SLOT(setIntegrationRange(double, double)));
165
166

  // Set the mouse/keyboard operation info and help button
167
  auto *infoLayout = new QHBoxLayout();
168
169
  mInteractionInfo = new QLabel();
  infoLayout->addWidget(mInteractionInfo);
170
171
172
173
  m_help = new QPushButton("?");
  m_help->setMaximumWidth(25);
  connect(m_help, SIGNAL(clicked()), this, SLOT(helpClicked()));
  infoLayout->addWidget(m_help);
174
  infoLayout->setStretchFactor(mInteractionInfo, 1);
175
  infoLayout->setStretchFactor(m_help, 0);
176
  m_mainLayout->addLayout(infoLayout);
177
178
179
180
181
182
183
184
185
186
187
188
189
190
  QSettings settings;
  settings.beginGroup(InstrumentWidgetSettingsGroup);

  // Background colour
  setBackgroundColor(
      settings.value("BackgroundColor", QColor(0, 0, 0, 1.0)).value<QColor>());

  // Create the b=tabs
  createTabs(settings);

  settings.endGroup();

  // Init actions
  m_clearPeakOverlays = new QAction("Clear peaks", this);
191
  connect(m_clearPeakOverlays, SIGNAL(triggered()), this,
192
193
          SLOT(clearPeakOverlays()));

194
195
196
197
198
  // Clear alignment plane action
  m_clearAlignment = new QAction("Clear alignment plane", this);
  connect(m_clearAlignment, SIGNAL(triggered()), this,
          SLOT(clearAlignmentPlane()));

199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
  // confirmClose(app->confirmCloseInstrWindow);

  setAttribute(Qt::WA_DeleteOnClose);

  // Watch for the deletion of the associated workspace
  observePreDelete();
  observeAfterReplace();
  observeRename();
  observeADSClear();

  const int windowWidth = 800;
  const int tabsSize = windowWidth / 4;
  QList<int> sizes;
  sizes << tabsSize << windowWidth - tabsSize;
  controlPanelLayout->setSizes(sizes);
  controlPanelLayout->setStretchFactor(0, 0);
  controlPanelLayout->setStretchFactor(1, 1);

  resize(windowWidth, 650);

  tabChanged(0);

  connect(this, SIGNAL(needSetIntegrationRange(double, double)), this,
          SLOT(setIntegrationRange(double, double)), Qt::QueuedConnection);
  setAcceptDrops(true);

  setWindowTitle(QString("Instrument - ") + m_workspaceName);

227
228
229
  const bool resetActor(false);
  init(resetGeometry, autoscaling, scaleMin, scaleMax, setDefaultView,
       resetActor);
230
231
232
}

/**
LamarMoore's avatar
LamarMoore committed
233
234
 * Destructor
 */
235
236
237
238
239
240
InstrumentWidget::~InstrumentWidget() {
  if (m_instrumentActor) {
    saveSettings();
  }
}

241
242
void InstrumentWidget::hideHelp() { m_help->setVisible(false); }

243
244
245
246
247
248
249
250
251
252
QString InstrumentWidget::getWorkspaceName() const { return m_workspaceName; }

std::string InstrumentWidget::getWorkspaceNameStdString() const {
  return m_workspaceName.toStdString();
}

void InstrumentWidget::renameWorkspace(const std::string &workspace) {
  m_workspaceName = QString::fromStdString(workspace);
}

253
254
255
256
257
/**
 * Get the axis vector for the surface projection type.
 * @param surfaceType :: Surface type for this projection
 * @return a V3D for the axis being projected on
 */
258
259
Mantid::Kernel::V3D
InstrumentWidget::getSurfaceAxis(const int surfaceType) const {
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
  Mantid::Kernel::V3D axis;

  // define the axis
  if (surfaceType == SPHERICAL_Y || surfaceType == CYLINDRICAL_Y) {
    axis = Mantid::Kernel::V3D(0, 1, 0);
  } else if (surfaceType == SPHERICAL_Z || surfaceType == CYLINDRICAL_Z) {
    axis = Mantid::Kernel::V3D(0, 0, 1);
  } else if (surfaceType == SPHERICAL_X || surfaceType == CYLINDRICAL_X) {
    axis = Mantid::Kernel::V3D(1, 0, 0);
  } else // SIDE_BY_SIDE
  {
    axis = Mantid::Kernel::V3D(0, 0, 1);
  }

  return axis;
}

277
/**
LamarMoore's avatar
LamarMoore committed
278
279
280
281
282
283
284
285
286
287
288
 * Init the geometry and colour map outside constructor to prevent creating a
 * broken MdiSubwindow.
 * Must be called straight after constructor.
 * @param resetGeometry :: Set true for resetting the view's geometry: the
 * bounding box and rotation. Default is true.
 * @param autoscaling :: True to start with autoscaling option on.
 * @param scaleMin :: Minimum value of the colormap scale. Ignored if
 * autoscaling == true.
 * @param scaleMax :: Maximum value of the colormap scale. Ignored if
 * autoscaling == true.
 * @param setDefaultView :: Set the default surface type
289
 * @param resetActor :: If true reset the instrumentActor object
LamarMoore's avatar
LamarMoore committed
290
 */
291
292
void InstrumentWidget::init(bool resetGeometry, bool autoscaling,
                            double scaleMin, double scaleMax,
293
294
295
296
297
                            bool setDefaultView, bool resetActor) {
  if (resetActor) {
    m_instrumentActor.reset(
        new InstrumentActor(m_workspaceName, autoscaling, scaleMin, scaleMax));
  }
298

299
  updateIntegrationWidget(true);
300

301
302
303
304
  auto surface = getSurface();
  if (resetGeometry || !surface) {
    if (setDefaultView) {
      // set the view type to the instrument's default view
Lamar Moore's avatar
Lamar Moore committed
305
306
      QString defaultView =
          QString::fromStdString(m_instrumentActor->getDefaultView());
307
      if (defaultView == "3D" &&
Verena Reimund's avatar
Verena Reimund committed
308
          !Mantid::Kernel::ConfigService::Instance()
309
310
               .getValue<bool>("MantidOptions.InstrumentView.UseOpenGL")
               .get_value_or(true)) {
311
312
313
314
315
316
317
318
319
320
        // if OpenGL is switched off don't open the 3D view at start up
        defaultView = "CYLINDRICAL_Y";
      }
      setSurfaceType(defaultView);
    } else {
      setSurfaceType(m_surfaceType); // This call must come after the
                                     // InstrumentActor is created
    }
    setupColorMap();
  } else {
321
    surface->resetInstrumentActor(m_instrumentActor.get());
322
323
324
325
326
    updateInfoText();
  }
}

/**
LamarMoore's avatar
LamarMoore committed
327
328
329
 * Deletes instrument actor before re-initializing.
 * @param resetGeometry
 */
330
331
332
333
334
void InstrumentWidget::resetInstrument(bool resetGeometry) {
  init(resetGeometry, true, 0.0, 0.0, false);
  updateInstrumentDetectors();
}

335
336
337
338
339
340
void InstrumentWidget::resetSurface() {
  auto surface = getSurface();
  surface->updateDetectors();
  update();
}

341
/**
LamarMoore's avatar
LamarMoore committed
342
343
 * Select the tab to be displayed
 */
344
void InstrumentWidget::selectTab(int tab) {
345
  getSurface()->setCurrentTab(mControlsTab->tabText(tab));
346
347
348
349
  mControlsTab->setCurrentIndex(tab);
}

/**
LamarMoore's avatar
LamarMoore committed
350
351
352
 * Returns the named tab or the current tab if none supplied
 * @param title Optional title of a tab (default="")
 */
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
InstrumentWidgetTab *InstrumentWidget::getTab(const QString &title) const {
  QWidget *tab(nullptr);
  if (title.isEmpty())
    tab = mControlsTab->currentWidget();
  else {
    for (int i = 0; i < mControlsTab->count(); ++i) {
      if (mControlsTab->tabText(i) == title) {
        tab = mControlsTab->widget(i);
        break;
      }
    }
  }

  if (tab)
    return qobject_cast<InstrumentWidgetTab *>(tab);
  else
    return nullptr;
}

/**
LamarMoore's avatar
LamarMoore committed
373
374
375
 * @param tab An enumeration for the tab to select
 * @returns A pointer to the requested tab
 */
376
377
378
379
380
381
382
383
384
InstrumentWidgetTab *InstrumentWidget::getTab(const Tab tab) const {
  QWidget *widget = mControlsTab->widget(static_cast<int>(tab));
  if (widget)
    return qobject_cast<InstrumentWidgetTab *>(widget);
  else
    return nullptr;
}

/**
LamarMoore's avatar
LamarMoore committed
385
386
387
388
389
390
391
392
 * Opens Qt file dialog to select the filename.
 * The dialog opens in the directory used last for saving or the default user
 * directory.
 *
 * @param title :: The title of the dialog.
 * @param filters :: The filters
 * @param selectedFilter :: The selected filter.
 */
393
394
395
QString InstrumentWidget::getSaveFileName(const QString &title,
                                          const QString &filters,
                                          QString *selectedFilter) {
396
397
  QString filename = QFileDialog::getSaveFileName(this, title, m_savedialog_dir,
                                                  filters, selectedFilter);
398
399
400
401
402
403
404
405
406
407

  // If its empty, they cancelled the dialog
  if (!filename.isEmpty()) {
    // Save the directory used
    QFileInfo finfo(filename);
    m_savedialog_dir = finfo.dir().path();
  }
  return filename;
}

408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
/**
 * @brief InstrumentWidget::isIntegrable
 * Returns whether or not the workspace requires an integration bar.
 */
bool InstrumentWidget::isIntegrable() {
  try {
    size_t blockSize = m_instrumentActor->getWorkspace()->blocksize();

    return (blockSize > 1 ||
            m_instrumentActor->getWorkspace()->id() == "EventWorkspace");
  } catch (...) {
    return true;
  }
}

423
/**
LamarMoore's avatar
LamarMoore committed
424
425
 * Update the info text displayed at the bottom of the window.
 */
426
void InstrumentWidget::updateInfoText(const QString &text) {
Mathieu Tillet's avatar
Mathieu Tillet committed
427
  if (text.isEmpty()) {
428
429
430
431
432
    setInfoText(getSurfaceInfoText());
  } else {
    setInfoText(text);
  }
}
433
434
435
436
437
438
439
440
441
442
443
444

void InstrumentWidget::setSurfaceType(int type) {
  // we cannot do 3D without OpenGL
  if (type == FULL3D && !isGLEnabled()) {
    QMessageBox::warning(
        this, "Mantid - Warning",
        "OpenGL must be enabled to render the instrument in 3D.");
    return;
  }

  if (type < RENDERMODE_SIZE) {
    QApplication::setOverrideCursor(Qt::WaitCursor);
445
    auto surfaceType = SurfaceType(type);
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
    if (!m_instrumentActor)
      return;

    ProjectionSurface *surface = getSurface().get();
    int peakLabelPrecision = 6;
    bool showPeakRow = true;
    bool showPeakLabels = true;
    bool showPeakRelativeIntensity = true;
    if (surface) {
      peakLabelPrecision = surface->getPeakLabelPrecision();
      showPeakRow = surface->getShowPeakRowsFlag();
      showPeakLabels = surface->getShowPeakLabelsFlag();
    } else {
      QSettings settings;
      settings.beginGroup(InstrumentWidgetSettingsGroup);
      peakLabelPrecision = settings.value("PeakLabelPrecision", 2).toInt();
      showPeakRow = settings.value("ShowPeakRows", true).toBool();
      showPeakLabels = settings.value("ShowPeakLabels", true).toBool();

      // By default this is should be off for now.
      showPeakRelativeIntensity =
          settings.value("ShowPeakRelativeIntensities", false).toBool();
      settings.endGroup();
    }

    // Surface factory
    // If anything throws during surface creation, store error message here
    QString errorMessage;
    try {
475
      const auto &componentInfo = m_instrumentActor->componentInfo();
Lamar Moore's avatar
Lamar Moore committed
476
      if (!componentInfo.hasSample()) {
477
478
        throw InstrumentHasNoSampleError();
      }
Lamar Moore's avatar
Lamar Moore committed
479
      auto sample_pos = componentInfo.samplePosition();
480
      auto axis = getSurfaceAxis(surfaceType);
481

482
483
      m_maskTab->setDisabled(false);

484
485
      // create the surface
      if (surfaceType == FULL3D) {
486
        m_renderTab->forceLayers(false);
487
488
489
490

        if (m_instrumentActor->hasGridBank())
          m_maskTab->setDisabled(true);

491
        surface =
492
            new Projection3D(m_instrumentActor.get(), glWidgetDimensions());
493
      } else if (surfaceType <= CYLINDRICAL_Z) {
494
        m_renderTab->forceLayers(true);
Antti Soininen's avatar
Antti Soininen committed
495
496
        surface =
            new UnwrappedCylinder(m_instrumentActor.get(), sample_pos, axis);
497
      } else if (surfaceType <= SPHERICAL_Z) {
498
        m_renderTab->forceLayers(true);
Antti Soininen's avatar
Antti Soininen committed
499
500
        surface =
            new UnwrappedSphere(m_instrumentActor.get(), sample_pos, axis);
501
502
      } else // SIDE_BY_SIDE
      {
503
        m_renderTab->forceLayers(true);
504
        surface = new PanelsSurface(m_instrumentActor.get(), sample_pos, axis);
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
      }
    } catch (InstrumentHasNoSampleError &) {
      QApplication::restoreOverrideCursor();
      throw;
    } catch (std::exception &e) {
      errorMessage = e.what();
    } catch (...) {
      errorMessage = "Unknown exception thrown.";
    }
    if (!errorMessage.isNull()) {
      // if exception was thrown roll back to the current surface type.
      QApplication::restoreOverrideCursor();
      QMessageBox::critical(
          this, "MantidPlot - Error",
          "Surface cannot be created because of an exception:\n\n  " +
              errorMessage + "\n\nPlease select a different surface type.");
      // if suface change was initialized by the GUI this should ensure its
      // consistency
      emit surfaceTypeChanged(m_surfaceType);
      return;
    }
    // end Surface factory

    m_surfaceType = surfaceType;
    surface->setPeakLabelPrecision(peakLabelPrecision);
    surface->setShowPeakRowsFlag(showPeakRow);
    surface->setShowPeakLabelsFlag(showPeakLabels);
    surface->setShowPeakRelativeIntensityFlag(showPeakRelativeIntensity);
    // set new surface
    setSurface(surface);

    // init tabs with new surface
    foreach (InstrumentWidgetTab *tab, m_tabs) { tab->initSurface(); }

    connect(surface, SIGNAL(executeAlgorithm(Mantid::API::IAlgorithm_sptr)),
            this, SLOT(executeAlgorithm(Mantid::API::IAlgorithm_sptr)));
    connect(surface, SIGNAL(updateInfoText()), this, SLOT(updateInfoText()),
            Qt::QueuedConnection);
    QApplication::restoreOverrideCursor();
  }
  emit surfaceTypeChanged(type);
  updateInfoText();
  update();
}

/**
LamarMoore's avatar
LamarMoore committed
551
552
553
554
 * Set the surface type from a string.
 * @param typeStr :: Symbolic name of the surface type: same as the names in
 * SurfaceType enum. Caseless.
 */
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
void InstrumentWidget::setSurfaceType(const QString &typeStr) {
  int typeIndex = 0;
  QString upperCaseStr = typeStr.toUpper();
  if (upperCaseStr == "FULL3D" || upperCaseStr == "3D") {
    typeIndex = 0;
  } else if (upperCaseStr == "CYLINDRICAL_X") {
    typeIndex = 1;
  } else if (upperCaseStr == "CYLINDRICAL_Y") {
    typeIndex = 2;
  } else if (upperCaseStr == "CYLINDRICAL_Z") {
    typeIndex = 3;
  } else if (upperCaseStr == "SPHERICAL_X") {
    typeIndex = 4;
  } else if (upperCaseStr == "SPHERICAL_Y") {
    typeIndex = 5;
  } else if (upperCaseStr == "SPHERICAL_Z") {
    typeIndex = 6;
  } else if (upperCaseStr == "SIDE_BY_SIDE") {
    typeIndex = 7;
  }
  setSurfaceType(typeIndex);
}

578
/**
579
 * @brief InstrumentWidget::replaceWorkspace
580
581
582
 * Replace the workspace currently linked to the instrument viewer by a new one.
 * @param newWs the name of the new workspace
 * @param workspace the new workspace to show
583
 * @param newInstrumentWindowName the new title of the window
584
 */
585
void InstrumentWidget::replaceWorkspace(
586
    const std::string &newWs, const std::string &newInstrumentWindowName) {
587
  // change inside objects
588
589
  renameWorkspace(newWs);
  m_instrumentActor.reset(new InstrumentActor(QString::fromStdString(newWs)));
590

591
592
593
  // update the integration widget
  updateIntegrationWidget();

594
  // update the view and colormap
595
596
597
  auto surface = getSurface();
  surface->resetInstrumentActor(m_instrumentActor.get());
  setupColorMap();
598

599
600
601
  // reset the instrument position
  m_renderTab->resetView();

602
603
604
  // reset the plot and the info widget in the pick tab
  m_pickTab->clearWidgets();

605
606
607
  // change the title of the instrument window
  nativeParentWidget()->setWindowTitle(
      QString().fromStdString(newInstrumentWindowName));
608
609
}

610
/**
611
 * @brief InstrumentWidget::updateIntegrationWidget
612
 * Update the range of the integration widget, and show or hide it is needed
613
614
 * @param init : boolean set to true if the integration widget is still being
 * initialized
615
 */
616
void InstrumentWidget::updateIntegrationWidget(bool init) {
617
618
  m_xIntegration->setTotalRange(m_instrumentActor->minBinValue(),
                                m_instrumentActor->maxBinValue());
619
620
621
622
623
624

  if (!init) {
    m_xIntegration->setRange(m_instrumentActor->minBinValue(),
                             m_instrumentActor->maxBinValue());
  }

625
626
627
628
629
630
631
632
633
634
635
636
  m_xIntegration->setUnits(QString::fromStdString(
      m_instrumentActor->getWorkspace()->getAxis(0)->unit()->caption()));

  bool integrable = isIntegrable();

  if (integrable) {
    m_xIntegration->show();
  } else {
    m_xIntegration->hide();
  }
}

637
/**
LamarMoore's avatar
LamarMoore committed
638
639
 * Update the colormap on the render tab.
 */
640
641
642
void InstrumentWidget::setupColorMap() { emit colorMapChanged(); }

/**
LamarMoore's avatar
LamarMoore committed
643
644
 * Connected to QTabWidget::currentChanged signal
 */
645
646
647
648
void InstrumentWidget::tabChanged(int /*unused*/) {
  updateInfoText();
  auto surface = getSurface();
  if (surface) {
649
    surface->setCurrentTab(mControlsTab->tabText(getCurrentTab()));
650
651
  }
}
652
653

/**
LamarMoore's avatar
LamarMoore committed
654
655
 * Change color map button slot. This provides the file dialog box to select
 * colormap or sets it directly a string is provided
656
 * @param cmapNameOrPath Name of a color map or a file path
LamarMoore's avatar
LamarMoore committed
657
 */
658
void InstrumentWidget::changeColormap(const QString &cmapNameOrPath) {
659
660
  if (!m_instrumentActor)
    return;
661
  const auto currentCMap = m_instrumentActor->getCurrentColorMap();
662

663
664
665
666
667
668
  QString selection;
  if (cmapNameOrPath.isEmpty()) {
    // ask user
    selection = ColorMap::chooseColorMap(currentCMap, this);
    if (selection.isEmpty()) {
      // assume cancelled request
669
      return;
670
    }
671
  } else {
672
    selection = ColorMap::exists(cmapNameOrPath);
673
674
  }

675
  if (selection == currentCMap) {
676
    // selection matches current
677
    return;
678
679
  }
  m_instrumentActor->loadColorMap(selection);
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710

  if (this->isVisible()) {
    setupColorMap();
    updateInstrumentView();
  }
}

QString InstrumentWidget::confirmDetectorOperation(const QString &opName,
                                                   const QString &inputWS,
                                                   int ndets) {
  QString message("This operation will affect %1 detectors.\nSelect output "
                  "workspace option:");
  QMessageBox prompt(this);
  prompt.setWindowTitle("MantidPlot");
  prompt.setText(message.arg(QString::number(ndets)));
  QPushButton *replace = prompt.addButton("Replace", QMessageBox::ActionRole);
  QPushButton *create = prompt.addButton("New", QMessageBox::ActionRole);
  prompt.addButton("Cancel", QMessageBox::ActionRole);
  prompt.exec();
  QString outputWS;
  if (prompt.clickedButton() == replace) {
    outputWS = inputWS;
  } else if (prompt.clickedButton() == create) {
    outputWS = inputWS + "_" + opName;
  } else {
    outputWS = "";
  }
  return outputWS;
}

/**
LamarMoore's avatar
LamarMoore committed
711
712
 * Convert a list of integers to a comma separated string of numbers
 */
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
QString InstrumentWidget::asString(const std::vector<int> &numbers) const {
  QString num_str;
  std::vector<int>::const_iterator iend = numbers.end();
  for (std::vector<int>::const_iterator itr = numbers.begin(); itr < iend;
       ++itr) {
    num_str += QString::number(*itr) + ",";
  }
  // Remove trailing comma
  num_str.chop(1);
  return num_str;
}

/// Set a maximum and minimum for the colour map range
void InstrumentWidget::setColorMapRange(double minValue, double maxValue) {
  emit colorMapRangeChanged(minValue, maxValue);
  update();
}

/// Set the minimum value of the colour map
void InstrumentWidget::setColorMapMinValue(double minValue) {
  emit colorMapMinValueChanged(minValue);
  update();
}

/// Set the maximumu value of the colour map
void InstrumentWidget::setColorMapMaxValue(double maxValue) {
  emit colorMapMaxValueChanged(maxValue);
  update();
}

/**
LamarMoore's avatar
LamarMoore committed
744
745
 * This is the callback for the combo box that selects the view direction
 */
746
void InstrumentWidget::setViewDirection(const QString &input) {
747
  auto p3d = std::dynamic_pointer_cast<Projection3D>(getSurface());
748
749
750
751
752
753
754
755
  if (p3d) {
    p3d->setViewDirection(input);
  }
  updateInstrumentView();
  repaint();
}

/**
LamarMoore's avatar
LamarMoore committed
756
757
758
 *  For the scripting API. Selects a component in the tree and zooms to it.
 *  @param name The name of the component
 */
759
760
761
762
763
void InstrumentWidget::selectComponent(const QString &name) {
  emit requestSelectComponent(name);
}

/**
LamarMoore's avatar
LamarMoore committed
764
765
766
 * Set the scale type programmatically
 * @param type :: The scale choice
 */
767
768
void InstrumentWidget::setScaleType(ColorMap::ScaleType type) {
  emit scaleTypeChanged(static_cast<int>(type));
769
770
771
}

/**
LamarMoore's avatar
LamarMoore committed
772
773
774
 * Set the exponent for the Power scale type
 * @param nth_power :: The exponent choice
 */
775
776
777
778
779
void InstrumentWidget::setExponent(double nth_power) {
  emit nthPowerChanged(nth_power);
}

/**
LamarMoore's avatar
LamarMoore committed
780
781
782
 * This method opens a color dialog to pick the background color,
 * and then sets it.
 */
783
784
785
786
787
788
void InstrumentWidget::pickBackgroundColor() {
  QColor color = QColorDialog::getColor(Qt::green, this);
  setBackgroundColor(color);
}

/**
LamarMoore's avatar
LamarMoore committed
789
790
791
 * Saves the current image buffer as a png file.
 * @param filename Optional filename. Empty string raises a save dialog
 */
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
void InstrumentWidget::saveImage(QString filename) {
  QString defaultExt = ".png";
  QList<QByteArray> formats = QImageWriter::supportedImageFormats();
  if (filename.isEmpty()) {
    QListIterator<QByteArray> itr(formats);
    QString filter("");
    while (itr.hasNext()) {
      filter += "*." + itr.next();
      if (itr.hasNext()) {
        filter += ";;";
      }
    }
    QString selectedFilter = "*" + defaultExt;
    filename = getSaveFileName("Save image ...", filter, &selectedFilter);

    // If its empty, they cancelled the dialog
    if (filename.isEmpty())
      return;
  }

  QFileInfo finfo(filename);
  QString ext = finfo.completeSuffix();

  if (ext.isEmpty()) {
    filename += defaultExt;
  } else {
818
    if (!formats.contains(ext.toLatin1())) {
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
      QString msg("Unsupported file extension. Choose one of the following: ");
      QListIterator<QByteArray> itr(formats);
      while (itr.hasNext()) {
        msg += itr.next() + ", ";
      }
      msg.chop(2); // Remove last space and comma
      QMessageBox::warning(this, "MantidPlot", msg);
      return;
    }
  }

  if (isGLEnabled()) {
    m_InstrumentDisplay->saveToFile(filename);
  } else {
    m_simpleDisplay->saveToFile(filename);
  }
}

/**
LamarMoore's avatar
LamarMoore committed
838
839
 * Use the file dialog to select a filename to save grouping.
 */
840
QString InstrumentWidget::getSaveGroupingFilename() {
841
842
843
  QString filename =
      QFileDialog::getSaveFileName(this, "Save grouping file", m_savedialog_dir,
                                   "Grouping (*.xml);;All files (*)");
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863

  // If its empty, they cancelled the dialog
  if (!filename.isEmpty()) {
    // Save the directory used
    QFileInfo finfo(filename);
    m_savedialog_dir = finfo.dir().path();
  }

  return filename;
}

///**
// * Update the text display that informs the user of the current mode and
// details about it
// */
void InstrumentWidget::setInfoText(const QString &text) {
  mInteractionInfo->setText(text);
}

/**
LamarMoore's avatar
LamarMoore committed
864
865
 * Save properties of the window a persistent store
 */
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
void InstrumentWidget::saveSettings() {
  QSettings settings;
  settings.beginGroup(InstrumentWidgetSettingsGroup);
  if (m_InstrumentDisplay)
    settings.setValue("BackgroundColor",
                      m_InstrumentDisplay->currentBackgroundColor());
  auto surface = getSurface();
  if (surface) {
    // if surface is null istrument view wasn't created and there is nothing to
    // save
    settings.setValue("PeakLabelPrecision",
                      getSurface()->getPeakLabelPrecision());
    settings.setValue("ShowPeakRows", getSurface()->getShowPeakRowsFlag());
    settings.setValue("ShowPeakLabels", getSurface()->getShowPeakLabelsFlag());
    settings.setValue("ShowPeakRelativeIntensities",
                      getSurface()->getShowPeakRelativeIntensityFlag());
    foreach (InstrumentWidgetTab *tab, m_tabs) { tab->saveSettings(settings); }
  }
  settings.endGroup();
}

void InstrumentWidget::helpClicked() {
888
  MantidDesktopServices::openUrl(
889
890
891
892
      QUrl("http://www.mantidproject.org/MantidPlot:_Instrument_View"));
}

void InstrumentWidget::set3DAxesState(bool on) {
893
  auto p3d = std::dynamic_pointer_cast<Projection3D>(getSurface());
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
  if (p3d) {
    p3d->set3DAxesState(on);
    updateInstrumentView();
  }
}

void InstrumentWidget::finishHandle(const Mantid::API::IAlgorithm *alg) {
  UNUSED_ARG(alg);
  emit needSetIntegrationRange(m_instrumentActor->minBinValue(),
                               m_instrumentActor->maxBinValue());
  // m_instrumentActor->update();
  // m_InstrumentDisplay->refreshView();
}

void InstrumentWidget::changeScaleType(int type) {
  m_instrumentActor->changeScaleType(type);
  setupColorMap();
  updateInstrumentView();
}

void InstrumentWidget::changeNthPower(double nth_power) {
  m_instrumentActor->changeNthPower(nth_power);
  setupColorMap();
  updateInstrumentView();
}

void InstrumentWidget::changeColorMapMinValue(double minValue) {
  m_instrumentActor->setMinValue(minValue);
  setupColorMap();
  updateInstrumentView();
}

/// Set the maximumu value of the colour map
void InstrumentWidget::changeColorMapMaxValue(double maxValue) {
  m_instrumentActor->setMaxValue(maxValue);
  setupColorMap();
  updateInstrumentView();
}

void InstrumentWidget::changeColorMapRange(double minValue, double maxValue) {
  m_instrumentActor->setMinMaxRange(minValue, maxValue);
  setupColorMap();
  updateInstrumentView();
}

void InstrumentWidget::setWireframe(bool on) {
940
  auto p3d = std::dynamic_pointer_cast<Projection3D>(getSurface());
941
942
943
944
945
946
947
  if (p3d) {
    p3d->setWireframe(on);
  }
  updateInstrumentView();
}

/**
LamarMoore's avatar
LamarMoore committed
948
949
950
 * Set new integration range but don't update XIntegrationControl (because the
 * control calls this slot)
 */
951
952
953
954
955
956
957
958
void InstrumentWidget::setIntegrationRange(double xmin, double xmax) {
  m_instrumentActor->setIntegrationRange(xmin, xmax);
  setupColorMap();
  updateInstrumentDetectors();
  emit integrationRangeChanged(xmin, xmax);
}

/**
LamarMoore's avatar
LamarMoore committed
959
960
961
 * Set new integration range and update XIntegrationControl. To be called from
 * python.
 */
962
void InstrumentWidget::setBinRange(double xmin, double xmax) {
963
  m_xIntegration->setRange(xmin, xmax);
964
965
966
}

/**
LamarMoore's avatar
LamarMoore committed
967
968
969
970
 * Update the display to view a selected component. The selected component
 * is visible the rest of the instrument is hidden.
 * @param id :: The component id.
 */
Lamar Moore's avatar
Lamar Moore committed
971
void InstrumentWidget::componentSelected(size_t componentIndex) {
972
973
  auto surface = getSurface();
  if (surface) {
Lamar Moore's avatar
Lamar Moore committed
974
    surface->componentSelected(componentIndex);
975
976
977
978
    updateInstrumentView();
  }
}

Tom Titcombe's avatar
Tom Titcombe committed
979
980
void InstrumentWidget::executeAlgorithm(const QString & /*unused*/,
                                        const QString & /*unused*/) {
981
982
983
  // emit execMantidAlgorithm(alg_name, param_list, this);
}

David Fairbrother's avatar
David Fairbrother committed
984
985
void InstrumentWidget::executeAlgorithm(
    const Mantid::API::IAlgorithm_sptr &alg) {
986
987
988
989
990
991
992
993
994
995
  try {
    alg->executeAsync();
  } catch (Poco::NoThreadAvailableException &) {
    return;
  }

  return;
}

/**
LamarMoore's avatar
LamarMoore committed
996
997
998
999
1000
 * Set the type of the view (SurfaceType).
 * @param type :: String code for the type. One of:
 * FULL3D, CYLINDRICAL_X, CYLINDRICAL_Y, CYLINDRICAL_Z, SPHERICAL_X,
 * SPHERICAL_Y, SPHERICAL_Z
 */
For faster browsing, not all history is shown. View entire blame