FunctionTreeView.cpp 75.9 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
7
// SPDX - License - Identifier: GPL - 3.0 +
#include "MantidQtWidgets/Common/FunctionTreeView.h"
Roman Tolchenov's avatar
Roman Tolchenov committed
8
#include "MantidQtWidgets/Common/FunctionBrowser/FunctionBrowserUtils.h"
9
10
11
12
13
14
15
16
17
18

#include "MantidAPI/CompositeFunction.h"
#include "MantidAPI/Expression.h"
#include "MantidAPI/FunctionFactory.h"
#include "MantidAPI/IConstraint.h"
#include "MantidAPI/MultiDomainFunction.h"
#include "MantidAPI/ParameterTie.h"

#include "MantidKernel/Logger.h"

Roman Tolchenov's avatar
Roman Tolchenov committed
19
#include "MantidQtWidgets/Common/EditLocalParameterDialog.h"
20
#include "MantidQtWidgets/Common/InterfaceManager.h"
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include "MantidQtWidgets/Common/QtPropertyBrowser/DoubleDialogEditor.h"
#include "MantidQtWidgets/Common/QtPropertyBrowser/FilenameDialogEditor.h"
#include "MantidQtWidgets/Common/QtPropertyBrowser/FormulaDialogEditor.h"
#include "MantidQtWidgets/Common/QtPropertyBrowser/WorkspaceEditorFactory.h"
#include "MantidQtWidgets/Common/SelectFunctionDialog.h"
#include "MantidQtWidgets/Common/UserFunctionDialog.h"

#include "MantidQtWidgets/Common/QtPropertyBrowser/CompositeEditorFactory.h"
#include "MantidQtWidgets/Common/QtPropertyBrowser/DoubleEditorFactory.h"
#include "MantidQtWidgets/Common/QtPropertyBrowser/qteditorfactory.h"
#include "MantidQtWidgets/Common/QtPropertyBrowser/qtpropertymanager.h"
#include "MantidQtWidgets/Common/QtPropertyBrowser/qttreepropertybrowser.h"

#include <QApplication>
#include <QClipboard>
#include <QFileInfo>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QInputDialog>
#include <QMenu>
#include <QMessageBox>
#include <QMetaMethod>
#include <QPushButton>
#include <QSettings>
#include <QSignalMapper>
#include <QTreeWidget>
#include <QVBoxLayout>

#include <algorithm>
#include <boost/lexical_cast.hpp>
51
#include <regex>
David Fairbrother's avatar
David Fairbrother committed
52
#include <utility>
53

54
55
using namespace Mantid::API;

56
57
58
namespace {
const char *globalOptionName = "Global";
Mantid::Kernel::Logger g_log("Function Browser");
59
const std::regex PREFIX_REGEX("(^[f][0-9](.*))");
Samuel Jones's avatar
Samuel Jones committed
60
inline bool variableIsPrefixed(const std::string &name) { return std::regex_match(name, PREFIX_REGEX); }
61
62

QString insertPrefix(const QString &param) {
Samuel Jones's avatar
Samuel Jones committed
63
  return param.left(param.indexOf(".") + 1) + "f0." + param.right(param.size() - param.indexOf(".") - 1);
64
65
}

66
QString addPrefix(const QString &param) { return "f0." + param; }
67

68
69
70
71
72
73
74
75
76
77
78
79
80
81
QString removeEmbeddedPrefix(const QString &param) {
  if (variableIsPrefixed(param.toStdString())) {
    const auto paramSplit = param.split(".");
    return paramSplit[0] + "." + paramSplit[paramSplit.size() - 1];
  } else {
    return param;
  }
}

QString removePrefix(const QString &param) {
  if (variableIsPrefixed(param.toStdString())) {
    const auto paramSplit = param.split(".");
    return paramSplit[paramSplit.size() - 1];
  } else {
Stephen's avatar
Stephen committed
82
83
84
    return param;
  }
}
85
86
87
88
89

bool containsOneOf(std::string const &str, std::string const &delimiters) {
  return !str.empty() && str.find_first_of(delimiters) != std::string::npos;
}

90
91
92
// These attributes require the function to be fully reconstructed, as a
// different number of properties will be required
const std::vector<QString> REQUIRESRECONSTRUCTIONATTRIBUTES = {QString("n")};
93
94
95
96
97
98
99
100
101
} // namespace

namespace MantidQt {
namespace MantidWidgets {

/**
 * Constructor
 * @param parent :: The parent widget.
 * @param multi  :: Option to use the browser for multi-dataset fitting.
Roman Tolchenov's avatar
Roman Tolchenov committed
102
103
 * @param categories :: Function categories to be included to the Add Function
 * dialog. An empty vector means include all available categories.
104
 */
105
106
107
FunctionTreeView::FunctionTreeView(QWidget *parent, bool multi, std::vector<std::string> categories)
    : IFunctionView(parent), m_multiDataset(multi), m_multiDomainFunctionPrefix(),
      m_allowedCategories(std::move(categories)), m_selectFunctionDialog(nullptr)
108
109
110
111
112
113
114
115

{
  // create m_browser
  createBrowser();
  createActions();

  QVBoxLayout *layout = new QVBoxLayout(this);
  layout->setContentsMargins(0, 0, 0, 0);
116
  layout->addWidget(m_browser);
117
118
119
120
121
}

/**
 * Destructor
 */
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
FunctionTreeView::~FunctionTreeView() {
  m_browser->unsetFactoryForManager(m_parameterManager);
  m_browser->unsetFactoryForManager(m_attributeStringManager);
  m_browser->unsetFactoryForManager(m_attributeDoubleManager);
  m_browser->unsetFactoryForManager(m_attributeIntManager);
  m_browser->unsetFactoryForManager(m_attributeBoolManager);
  m_browser->unsetFactoryForManager(m_indexManager);
  m_browser->unsetFactoryForManager(m_tieManager);
  m_browser->unsetFactoryForManager(m_constraintManager);
  m_browser->unsetFactoryForManager(m_filenameManager);
  m_browser->unsetFactoryForManager(m_formulaManager);
  m_browser->unsetFactoryForManager(m_workspaceManager);
  m_browser->unsetFactoryForManager(m_attributeSizeManager);
  m_browser->unsetFactoryForManager(m_attributeVectorDoubleManager);
}
137
138
139
140
141
142
143
144
145

/**
 * Create the Qt property browser and set up property managers.
 */
void FunctionTreeView::createBrowser() {
  QStringList options;
  if (m_multiDataset) {
    options << globalOptionName;
  }
146
  m_browser = new QtTreePropertyBrowser(this, options);
147
148
149
150
151
152
153
154
155
156
157

  /* Create property managers: they create, own properties, get and set values
   */
  m_functionManager = new QtGroupPropertyManager(this);
  m_parameterManager = new ParameterPropertyManager(this);
  m_attributeStringManager = new QtStringPropertyManager(this);
  m_attributeDoubleManager = new QtDoublePropertyManager(this);
  m_attributeIntManager = new QtIntPropertyManager(this);
  m_attributeBoolManager = new QtBoolPropertyManager(this);
  m_indexManager = new QtStringPropertyManager(this);
  m_tieManager = new QtStringPropertyManager(this);
158
  m_constraintManager = new QtDoublePropertyManager(this);
159
160
161
162
163
164
165
166
167
168
169
170
  m_filenameManager = new QtStringPropertyManager(this);
  m_formulaManager = new QtStringPropertyManager(this);
  m_workspaceManager = new QtStringPropertyManager(this);
  m_attributeVectorManager = new QtGroupPropertyManager(this);
  m_attributeSizeManager = new QtIntPropertyManager(this);
  m_attributeVectorDoubleManager = new QtDoublePropertyManager(this);

  // create editor factories
  QtSpinBoxFactory *spinBoxFactory = new QtSpinBoxFactory(this);
  DoubleEditorFactory *doubleEditorFactory = new DoubleEditorFactory(this);
  ParameterEditorFactory *paramEditorFactory = new ParameterEditorFactory(this);

Samuel Jones's avatar
Samuel Jones committed
171
  QtAbstractEditorFactory<ParameterPropertyManager> *parameterEditorFactory(nullptr);
172
  if (m_multiDataset) {
173
    m_doubleEditorFactory = new DoubleDialogEditorFactory(this);
Samuel Jones's avatar
Samuel Jones committed
174
    auto compositeFactory = new CompositeEditorFactory<ParameterPropertyManager>(this, m_doubleEditorFactory);
175
176
    compositeFactory->setSecondaryFactory(globalOptionName, paramEditorFactory);
    parameterEditorFactory = compositeFactory;
177
    connect(m_doubleEditorFactory, SIGNAL(buttonClicked(QtProperty *)), this,
178
            SLOT(parameterButtonClicked(QtProperty *)));
Samuel Jones's avatar
Samuel Jones committed
179
    connect(m_doubleEditorFactory, SIGNAL(closeEditor()), m_browser, SLOT(closeEditor()));
180
181
182
183
184
185
  } else {
    parameterEditorFactory = paramEditorFactory;
  }

  QtLineEditFactory *lineEditFactory = new QtLineEditFactory(this);
  QtCheckBoxFactory *checkBoxFactory = new QtCheckBoxFactory(this);
Samuel Jones's avatar
Samuel Jones committed
186
187
188
  FilenameDialogEditorFactory *filenameDialogEditorFactory = new FilenameDialogEditorFactory(this);
  FormulaDialogEditorFactory *formulaDialogEditFactory = new FormulaDialogEditorFactory(this);
  WorkspaceEditorFactory *workspaceEditorFactory = new WorkspaceEditorFactory(this);
189
190
191
192

  // assign factories to property managers
  m_browser->setFactoryForManager(m_parameterManager, parameterEditorFactory);
  m_browser->setFactoryForManager(m_attributeStringManager, lineEditFactory);
Samuel Jones's avatar
Samuel Jones committed
193
  m_browser->setFactoryForManager(m_attributeDoubleManager, doubleEditorFactory);
194
195
196
197
  m_browser->setFactoryForManager(m_attributeIntManager, spinBoxFactory);
  m_browser->setFactoryForManager(m_attributeBoolManager, checkBoxFactory);
  m_browser->setFactoryForManager(m_indexManager, lineEditFactory);
  m_browser->setFactoryForManager(m_tieManager, lineEditFactory);
198
  m_browser->setFactoryForManager(m_constraintManager, doubleEditorFactory);
Samuel Jones's avatar
Samuel Jones committed
199
  m_browser->setFactoryForManager(m_filenameManager, filenameDialogEditorFactory);
200
201
202
  m_browser->setFactoryForManager(m_formulaManager, formulaDialogEditFactory);
  m_browser->setFactoryForManager(m_workspaceManager, workspaceEditorFactory);
  m_browser->setFactoryForManager(m_attributeSizeManager, spinBoxFactory);
Samuel Jones's avatar
Samuel Jones committed
203
  m_browser->setFactoryForManager(m_attributeVectorDoubleManager, doubleEditorFactory);
204
205

  m_browser->setContextMenuPolicy(Qt::CustomContextMenu);
Samuel Jones's avatar
Samuel Jones committed
206
207
208
209
210
211
212
213
214
215
216
217
218
  connect(m_browser, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(popupMenu(const QPoint &)));
  connect(m_browser, SIGNAL(optionChanged(QtProperty *, const QString &, bool)), this,
          SLOT(globalChanged(QtProperty *, const QString &, bool)));

  connect(m_attributeStringManager, SIGNAL(propertyChanged(QtProperty *)), this, SLOT(attributeChanged(QtProperty *)));
  connect(m_attributeDoubleManager, SIGNAL(propertyChanged(QtProperty *)), this, SLOT(attributeChanged(QtProperty *)));
  connect(m_attributeIntManager, SIGNAL(propertyChanged(QtProperty *)), this, SLOT(attributeChanged(QtProperty *)));
  connect(m_attributeBoolManager, SIGNAL(propertyChanged(QtProperty *)), this, SLOT(attributeChanged(QtProperty *)));
  connect(m_formulaManager, SIGNAL(propertyChanged(QtProperty *)), this, SLOT(attributeChanged(QtProperty *)));
  connect(m_filenameManager, SIGNAL(propertyChanged(QtProperty *)), this, SLOT(attributeChanged(QtProperty *)));
  connect(m_workspaceManager, SIGNAL(propertyChanged(QtProperty *)), this, SLOT(attributeChanged(QtProperty *)));
  connect(m_attributeVectorDoubleManager, SIGNAL(propertyChanged(QtProperty *)), this,
          SLOT(attributeVectorDoubleChanged(QtProperty *)));
219
220
  connect(m_attributeSizeManager, SIGNAL(propertyChanged(QtProperty *)), this,
          SLOT(attributeVectorSizeChanged(QtProperty *)));
Samuel Jones's avatar
Samuel Jones committed
221
222
223
  connect(m_tieManager, SIGNAL(propertyChanged(QtProperty *)), this, SLOT(tieChanged(QtProperty *)));
  connect(m_constraintManager, SIGNAL(propertyChanged(QtProperty *)), this, SLOT(constraintChanged(QtProperty *)));
  connect(m_parameterManager, SIGNAL(valueChanged(QtProperty *, double)), SLOT(parameterPropertyChanged(QtProperty *)));
224

Samuel Jones's avatar
Samuel Jones committed
225
  connect(m_browser, SIGNAL(currentItemChanged(QtBrowserItem *)), SLOT(updateCurrentFunctionIndex()));
226
227
228
229
230
231
232
233
234

  m_browser->setFocusPolicy(Qt::StrongFocus);
}

/**
 * Create and connect actions
 */
void FunctionTreeView::createActions() {
  m_actionAddFunction = new QAction("Add function", this);
235
  m_actionAddFunction->setObjectName("add_function");
Samuel Jones's avatar
Samuel Jones committed
236
  connect(m_actionAddFunction, SIGNAL(triggered()), this, SLOT(addFunctionBegin()));
237
238

  m_actionRemoveFunction = new QAction("Remove function", this);
239
  m_actionRemoveFunction->setObjectName("remove_function");
Samuel Jones's avatar
Samuel Jones committed
240
  connect(m_actionRemoveFunction, SIGNAL(triggered()), this, SLOT(removeFunction()));
241
242

  m_actionFixParameter = new QAction("Fix", this);
Samuel Jones's avatar
Samuel Jones committed
243
  connect(m_actionFixParameter, SIGNAL(triggered()), this, SLOT(fixParameter()));
244
245
246
247
248
249
250

  m_actionRemoveTie = new QAction("Remove tie", this);
  connect(m_actionRemoveTie, SIGNAL(triggered()), this, SLOT(removeTie()));

  m_actionAddTie = new QAction("Add tie", this);
  connect(m_actionAddTie, SIGNAL(triggered()), this, SLOT(addTie()));

251
252
  m_actionFromClipboard = new QAction("Paste from clipboard", this);
  m_actionFromClipboard->setObjectName("paste_from_clipboard");
Samuel Jones's avatar
Samuel Jones committed
253
  connect(m_actionFromClipboard, SIGNAL(triggered()), this, SLOT(pasteFromClipboard()));
254
255

  m_actionToClipboard = new QAction("Copy to clipboard", this);
256
  m_actionToClipboard->setObjectName("copy_to_clipboard");
Samuel Jones's avatar
Samuel Jones committed
257
  connect(m_actionToClipboard, SIGNAL(triggered()), this, SLOT(copyToClipboard()));
258
259

  m_actionConstraints = new QAction("Custom", this);
Samuel Jones's avatar
Samuel Jones committed
260
  connect(m_actionConstraints, SIGNAL(triggered()), this, SLOT(addConstraints()));
261
262

  m_actionConstraints10 = new QAction("10%", this);
Samuel Jones's avatar
Samuel Jones committed
263
  connect(m_actionConstraints10, SIGNAL(triggered()), this, SLOT(addConstraints10()));
264
265

  m_actionConstraints50 = new QAction("50%", this);
Samuel Jones's avatar
Samuel Jones committed
266
  connect(m_actionConstraints50, SIGNAL(triggered()), this, SLOT(addConstraints50()));
267
268

  m_actionRemoveConstraints = new QAction("Remove constraints", this);
Samuel Jones's avatar
Samuel Jones committed
269
  connect(m_actionRemoveConstraints, SIGNAL(triggered()), this, SLOT(removeConstraints()));
270
271

  m_actionRemoveConstraint = new QAction("Remove", this);
Samuel Jones's avatar
Samuel Jones committed
272
  connect(m_actionRemoveConstraint, SIGNAL(triggered()), this, SLOT(removeConstraint()));
273

274
  m_actionFunctionHelp = new QAction("Help", this);
Samuel Jones's avatar
Samuel Jones committed
275
  connect(m_actionFunctionHelp, SIGNAL(triggered()), this, SIGNAL(functionHelpRequest()));
276

277
  setErrorsEnabled(false);
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
}

/**
 * Clear the contents
 */
void FunctionTreeView::clear() {
  m_browser->clear();
  m_properties.clear();
}

/**
 * Set the function in the browser
 * @param fun :: A function
 */
void FunctionTreeView::setFunction(Mantid::API::IFunction_sptr fun) {
  clear();
294
295
296
  if (fun) {
    addFunction(nullptr, fun);
  }
297
298
299
300
301
302
303
}

/**
 * Add a sub-property to a parent property
 * @param parent :: The parent property
 * @param subproperty :: New sub-property
 */
Samuel Jones's avatar
Samuel Jones committed
304
FunctionTreeView::AProperty FunctionTreeView::addProperty(QtProperty *parent, QtProperty *subproperty) {
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
  AProperty ap;
  ap.prop = subproperty;
  if (parent == nullptr) {
    ap.item = m_browser->addProperty(subproperty);
  } else {
    parent->addSubProperty(subproperty);
    auto items = m_browser->items(subproperty);
    if (items.isEmpty()) {
      throw std::runtime_error("Unexpected error in FunctionTreeView [1]");
    }
    ap.item = items[0];
  }
  ap.parent = parent;
  m_properties[subproperty] = ap;
  return ap;
}

/**
 * Remove and delete property
 * @param prop :: Property to remove.
 */
void FunctionTreeView::removeProperty(QtProperty *prop) {
  auto p = m_properties.find(prop);
  if (p == m_properties.end())
    return;
  AProperty ap = *p;

  // remove references to the children
  auto children = prop->subProperties();
  foreach (QtProperty *child, children) { removeProperty(child); }
  m_properties.erase(p);

  if (isFunction(prop)) {
    m_ties.remove(prop);
  }

  if (isTie(prop)) { //
    for (auto it = m_ties.begin(); it != m_ties.end(); ++it) {
      if (it.value().tieProp == prop) {
        m_ties.erase(it);
        break;
      }
    }
  }

  if (isConstraint(prop)) {
    for (auto it = m_constraints.begin(); it != m_constraints.end(); ++it) {
      auto &cp = it.value();
      if (cp.lower == prop) {
        if (!cp.upper) {
          m_constraints.erase(it);
        } else {
          cp.lower = nullptr;
        }
        break;
      } else if (cp.upper == prop) {
        if (!cp.lower) {
          m_constraints.erase(it);
        } else {
          cp.upper = nullptr;
        }
        break;
      }
    }
  }

  // remove property from Qt browser
  if (ap.parent) {
    ap.parent->removeSubProperty(prop);
  } else {
    m_browser->removeProperty(prop);
  }
  delete prop;
}

/**
 * Add a function property
 * @param parent :: Parent function property or NULL
 * @param funName :: Function name
 * @return :: A set AProperty struct
 */
Samuel Jones's avatar
Samuel Jones committed
386
FunctionTreeView::AProperty FunctionTreeView::addFunctionProperty(QtProperty *parent, const QString &funName) {
387
  // check that parent is a function property
Samuel Jones's avatar
Samuel Jones committed
388
  if (parent && dynamic_cast<QtAbstractPropertyManager *>(m_functionManager) != parent->propertyManager()) {
389
390
391
392
393
394
395
396
397
398
399
400
401
    throw std::runtime_error("Unexpected error in FunctionTreeView [2]");
  }
  QtProperty *prop = m_functionManager->addProperty(funName);
  return addProperty(parent, prop);
}

/**
 * Add a parameter property
 * @param parent :: Parent function property
 * @param paramName :: Parameter name
 * @param paramDesc :: Parameter description
 * @param paramValue :: Parameter value
 */
Samuel Jones's avatar
Samuel Jones committed
402
403
FunctionTreeView::AProperty FunctionTreeView::addParameterProperty(QtProperty *parent, const QString &paramName,
                                                                   const QString &paramDesc, double paramValue) {
404
  // check that parent is a function property
Samuel Jones's avatar
Samuel Jones committed
405
  if (!parent || dynamic_cast<QtAbstractPropertyManager *>(m_functionManager) != parent->propertyManager()) {
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
    throw std::runtime_error("Unexpected error in FunctionTreeView [3]");
  }
  QtProperty *prop = m_parameterManager->addProperty(paramName);
  m_parameterManager->blockSignals(true);
  m_parameterManager->setDecimals(prop, 6);
  m_parameterManager->setValue(prop, paramValue);
  m_parameterManager->setDescription(prop, paramDesc.toStdString());
  m_parameterManager->blockSignals(false);

  if (m_multiDataset) {
    prop->setOption(globalOptionName, false);
  }
  return addProperty(parent, prop);
}

/**
 * Set a function.
 * @param prop :: Property of the function or NULL
 * @param fun :: A function
 */
Samuel Jones's avatar
Samuel Jones committed
426
void FunctionTreeView::setFunction(QtProperty *prop, const Mantid::API::IFunction_sptr &fun) {
427
428
  auto children = prop->subProperties();
  foreach (QtProperty *child, children) { removeProperty(child); }
Roman Tolchenov's avatar
Roman Tolchenov committed
429
  // m_localParameterValues.clear();
430
431
  if (!m_multiDomainFunctionPrefix.isEmpty())
    addMultiDomainIndexProperty(prop);
432
  addAttributeAndParameterProperties(prop, fun);
433
434
435
436
437
}

/**
 * Add a function.
 * @param prop :: Property of the parent composite function or NULL
438
 * @param fun :: A function to add
439
 */
Samuel Jones's avatar
Samuel Jones committed
440
bool FunctionTreeView::addFunction(QtProperty *prop, const Mantid::API::IFunction_sptr &fun) {
441
442
  if (!fun)
    return false;
443
  if (!prop) {
Samuel Jones's avatar
Samuel Jones committed
444
    AProperty ap = addFunctionProperty(nullptr, QString::fromStdString(fun->name()));
445
446
447
448
    setFunction(ap.prop, fun);
  } else {
    Mantid::API::IFunction_sptr parentFun = getFunction(prop);
    if (!parentFun)
449
      return false;
Samuel Jones's avatar
Samuel Jones committed
450
    auto cf = std::dynamic_pointer_cast<Mantid::API::CompositeFunction>(parentFun);
451
    if (!cf) {
Samuel Jones's avatar
Samuel Jones committed
452
      throw std::logic_error("FunctionTreeView: CompositeFunction is expected for addFunction");
453
    }
454
455
456
    try {
      cf->addFunction(fun);
    } catch (const std::exception &e) {
Samuel Jones's avatar
Samuel Jones committed
457
      QMessageBox::warning(this, "Mantid - Warning", QString("Cannot Add function:\n\n%1").arg(e.what()));
458
459
      return false;
    }
460
461
462
    setFunction(prop, cf);
  }
  updateFunctionIndices();
463
  return true;
464
465
466
467
468
469
470
}

/**
 * Attribute visitor to create a QtProperty. Depending on the attribute type
 * the appropriate apply() method is used.
 */
class CreateAttributePropertyForFunctionTreeView
Samuel Jones's avatar
Samuel Jones committed
471
    : public Mantid::API::IFunction::ConstAttributeVisitor<FunctionTreeView::AProperty> {
472
public:
Samuel Jones's avatar
Samuel Jones committed
473
  CreateAttributePropertyForFunctionTreeView(FunctionTreeView *browser, QtProperty *parent, const QString &attName)
474
      : m_browser(browser), m_parent(parent), m_attName(attName) {
475
476
    // check that parent is a function property
    if (!m_parent ||
Samuel Jones's avatar
Samuel Jones committed
477
        dynamic_cast<QtAbstractPropertyManager *>(m_browser->m_functionManager) != m_parent->propertyManager()) {
478
479
480
481
482
483
484
485
      throw std::runtime_error("Unexpected error in FunctionTreeView [4]");
    }
  }

protected:
  /// Create string property
  FunctionTreeView::AProperty apply(const std::string &str) const override {
    QtProperty *prop = nullptr;
486
    if (m_attName.indexOf("FileName") != -1) {
487
      m_browser->m_filenameManager->blockSignals(true);
488
489
      prop = m_browser->m_filenameManager->addProperty(m_attName);
      m_browser->m_filenameManager->setValue(prop, QString::fromStdString(str));
490
      m_browser->m_filenameManager->blockSignals(false);
491
    } else if (m_attName.indexOf("Formula") != -1) {
492
      m_browser->m_formulaManager->blockSignals(true);
493
494
      prop = m_browser->m_formulaManager->addProperty(m_attName);
      m_browser->m_formulaManager->setValue(prop, QString::fromStdString(str));
495
      m_browser->m_formulaManager->blockSignals(false);
496
    } else if (m_attName.indexOf("Workspace") != -1) {
497
      m_browser->m_workspaceManager->blockSignals(true);
498
      prop = m_browser->m_workspaceManager->addProperty(m_attName);
Samuel Jones's avatar
Samuel Jones committed
499
      m_browser->m_workspaceManager->setValue(prop, QString::fromStdString(str));
500
      m_browser->m_workspaceManager->blockSignals(false);
501
    } else {
502
      m_browser->m_attributeStringManager->blockSignals(true);
503
      prop = m_browser->m_attributeStringManager->addProperty(m_attName);
Samuel Jones's avatar
Samuel Jones committed
504
      m_browser->m_attributeStringManager->setValue(prop, QString::fromStdString(str));
505
      m_browser->m_attributeStringManager->blockSignals(false);
506
507
508
509
510
    }
    return m_browser->addProperty(m_parent, prop);
  }
  /// Create double property
  FunctionTreeView::AProperty apply(const double &d) const override {
511
    m_browser->m_attributeDoubleManager->blockSignals(true);
Samuel Jones's avatar
Samuel Jones committed
512
    QtProperty *prop = m_browser->m_attributeDoubleManager->addProperty(m_attName);
513
    m_browser->m_attributeDoubleManager->setValue(prop, d);
514
    m_browser->m_attributeDoubleManager->blockSignals(false);
515
516
517
518
    return m_browser->addProperty(m_parent, prop);
  }
  /// Create int property
  FunctionTreeView::AProperty apply(const int &i) const override {
519
    m_browser->m_attributeIntManager->blockSignals(true);
520
521
    QtProperty *prop = m_browser->m_attributeIntManager->addProperty(m_attName);
    m_browser->m_attributeIntManager->setValue(prop, i);
522
    m_browser->m_attributeIntManager->blockSignals(false);
523
524
525
526
    return m_browser->addProperty(m_parent, prop);
  }
  /// Create bool property
  FunctionTreeView::AProperty apply(const bool &b) const override {
527
    m_browser->m_attributeBoolManager->blockSignals(true);
Samuel Jones's avatar
Samuel Jones committed
528
    QtProperty *prop = m_browser->m_attributeBoolManager->addProperty(m_attName);
529
    m_browser->m_attributeBoolManager->setValue(prop, b);
530
    m_browser->m_attributeBoolManager->blockSignals(false);
531
532
533
    return m_browser->addProperty(m_parent, prop);
  }
  /// Create vector property
Samuel Jones's avatar
Samuel Jones committed
534
535
  FunctionTreeView::AProperty apply(const std::vector<double> &v) const override {
    QtProperty *prop = m_browser->m_attributeVectorManager->addProperty(m_attName);
536
537
538
    FunctionTreeView::AProperty aprop = m_browser->addProperty(m_parent, prop);

    m_browser->m_attributeSizeManager->blockSignals(true);
Samuel Jones's avatar
Samuel Jones committed
539
540
    QtProperty *sizeProp = m_browser->m_attributeSizeManager->addProperty("Size");
    m_browser->m_attributeSizeManager->setValue(sizeProp, static_cast<int>(v.size()));
541
542
543
544
545
546
547
    m_browser->addProperty(prop, sizeProp);
    m_browser->m_attributeSizeManager->blockSignals(false);
    sizeProp->setEnabled(false);

    m_browser->m_attributeVectorDoubleManager->blockSignals(true);
    QString parName = "value[%1]";
    for (size_t i = 0; i < v.size(); ++i) {
Samuel Jones's avatar
Samuel Jones committed
548
      QtProperty *dprop = m_browser->m_attributeVectorDoubleManager->addProperty(parName.arg(i));
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
      m_browser->m_attributeVectorDoubleManager->setValue(dprop, v[i]);
      m_browser->addProperty(prop, dprop);
    }
    m_browser->m_attributeVectorDoubleManager->blockSignals(false);

    m_browser->m_browser->setExpanded(aprop.item, false);
    return aprop;
  }

private:
  FunctionTreeView *m_browser;
  QtProperty *m_parent;
  QString m_attName;
};

/**
 * Attribute visitor to set an attribute from a QtProperty. Depending on the
 * attribute type
 * the appropriate apply() method is used.
 */
Samuel Jones's avatar
Samuel Jones committed
569
class SetAttributeFromProperty : public Mantid::API::IFunction::AttributeVisitor<> {
570
public:
Samuel Jones's avatar
Samuel Jones committed
571
  SetAttributeFromProperty(FunctionTreeView *browser, QtProperty *prop) : m_browser(browser), m_prop(prop) {}
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587

protected:
  /// Set string attribute
  void apply(std::string &str) const override {
    QString attName = m_prop->propertyName();
    if (attName == "FileName") {
      str = m_browser->m_filenameManager->value(m_prop).toStdString();
    } else if (attName == "Formula") {
      str = m_browser->m_formulaManager->value(m_prop).toStdString();
    } else if (attName == "Workspace") {
      str = m_browser->m_workspaceManager->value(m_prop).toStdString();
    } else {
      str = m_browser->m_attributeStringManager->value(m_prop).toStdString();
    }
  }
  /// Set double attribute
Samuel Jones's avatar
Samuel Jones committed
588
  void apply(double &d) const override { d = m_browser->m_attributeDoubleManager->value(m_prop); }
589
  /// Set int attribute
Samuel Jones's avatar
Samuel Jones committed
590
  void apply(int &i) const override { i = m_browser->m_attributeIntManager->value(m_prop); }
591
  /// Set bool attribute
Samuel Jones's avatar
Samuel Jones committed
592
  void apply(bool &b) const override { b = m_browser->m_attributeBoolManager->value(m_prop); }
593
594
595
596
  /// Set vector attribute
  void apply(std::vector<double> &v) const override {
    QList<QtProperty *> members = m_prop->subProperties();
    if (members.empty())
Samuel Jones's avatar
Samuel Jones committed
597
      throw std::runtime_error("FunctionTreeView: empty vector attribute group.");
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
    int n = members.size() - 1;
    if (n == 0) {
      v.clear();
      return;
    }
    v.resize(n);
    for (int i = 0; i < n; ++i) {
      v[i] = m_browser->m_attributeVectorDoubleManager->value(members[i + 1]);
    }
  }

private:
  FunctionTreeView *m_browser;
  QtProperty *m_prop;
};

/**
 * Add a attribute property
 * @param parent :: Parent function property
 * @param attName :: Attribute name
 * @param att :: Attribute value
 */
Samuel Jones's avatar
Samuel Jones committed
620
621
FunctionTreeView::AProperty FunctionTreeView::addAttributeProperty(QtProperty *parent, const QString &attName,
                                                                   const Mantid::API::IFunction::Attribute &att) {
622
  CreateAttributePropertyForFunctionTreeView cap(this, parent, attName);
623
624
625
626
627
628
629
630
631
  return att.apply(cap);
}

/**
 * Add attribute and parameter properties to a function property. For a
 * composite function
 *  adds all member functions' properties
 * @param prop :: A function property
 * @param fun :: Shared pointer to a created function
632
633
634
 * @param parentComposite :: If relevant, the composite the function is part of.
 * @param parentIndex :: If relevant, the index of the function within its
 * composite.
635
 */
Samuel Jones's avatar
Samuel Jones committed
636
637
638
void FunctionTreeView::addAttributeAndParameterProperties(QtProperty *prop, const IFunction_sptr &fun,
                                                          const CompositeFunction_sptr &parentComposite,
                                                          const std::size_t &parentIndex) {
639
640
641
642
643
  // add the function index property
  addIndexProperty(prop);

  // add attribute properties
  auto attributeNames = fun->getAttributeNames();
644
645
646
647
648
  for (const auto &att : attributeNames) {
    if (!variableIsPrefixed(att)) {
      QString attName = QString::fromStdString(att);
      addAttributeProperty(prop, attName, fun->getAttribute(att));
    }
649
650
  }

651
  auto cf = std::dynamic_pointer_cast<Mantid::API::CompositeFunction>(fun);
652
653
  if (cf) {
    // if composite add members
654
    for (size_t i = 0; i < cf->nFunctions(); ++i) {
Samuel Jones's avatar
Samuel Jones committed
655
      AProperty ap = addFunctionProperty(prop, QString::fromStdString(cf->getFunction(i)->name()));
656
      addAttributeAndParameterProperties(ap.prop, cf->getFunction(i), cf, i);
657
658
659
660
661
662
663
664
    }
  } else { // if simple add parameters
    for (size_t i = 0; i < fun->nParams(); ++i) {
      QString name = QString::fromStdString(fun->parameterName(i));
      QString desc = QString::fromStdString(fun->parameterDescription(i));
      double value = fun->getParameter(i);
      AProperty ap = addParameterProperty(prop, name, desc, value);
      // if parameter has a tie
665
      if (!fun->isActive(i))
Samuel Jones's avatar
Samuel Jones committed
666
        addParameterTie(ap.prop, fun, name.toStdString(), i, parentComposite, parentIndex);
667
      else
Samuel Jones's avatar
Samuel Jones committed
668
        addGlobalParameterTie(ap.prop, name.toStdString(), parentComposite, parentIndex);
669
670

      if (const auto constraint = fun->getConstraint(i)) {
Samuel Jones's avatar
Samuel Jones committed
671
        addConstraintProperties(ap.prop, QString::fromStdString(constraint->asString()));
672
      }
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
    }
  }
}

/**
 * Add a tie to a function property. If the tie is stored within the wider
 * composite function, it will find this tie and apply it.
 * @param property :: A function property.
 * @param function :: Shared pointer to the function.
 * @param parameterName :: The name of the parameter to be tied.
 * @param parameterIndex :: The index of the parameter within its function.
 * @param parentComposite :: If relevant, the composite the function is part of.
 * @param parentIndex :: If relevant, the index of the function within its
 * composite.
 */
Samuel Jones's avatar
Samuel Jones committed
688
689
690
void FunctionTreeView::addParameterTie(QtProperty *property, const IFunction_sptr &function,
                                       const std::string &parameterName, const std::size_t &parameterIndex,
                                       const CompositeFunction_sptr &parentComposite, const std::size_t &parentIndex) {
691
692
693
694
695
  if (const auto tie = function->getTie(parameterIndex)) {
    addTieProperty(property, QString::fromStdString(tie->asString()));
  } else {
    auto tieAdded = false;
    if (parentComposite)
Samuel Jones's avatar
Samuel Jones committed
696
      tieAdded = addParameterTieInComposite(property, parameterName, parentComposite, parentIndex);
697
698

    if (!tieAdded)
Samuel Jones's avatar
Samuel Jones committed
699
      addTieProperty(property, QString::number(function->getParameter(parameterIndex)));
700
701
702
703
704
705
706
707
708
709
710
711
  }
}

/**
 * Add a tie to a function property. Used if a tie is stored within the wider
 * composite function.
 * @param property :: A function property.
 * @param parameterName :: The name of the parameter to be tied.
 * @param composite :: The composite function containing the tie.
 * @param index :: The index of the function within the composite function.
 * @returns true if a tie was found, and a tie property was added.
 */
Samuel Jones's avatar
Samuel Jones committed
712
713
bool FunctionTreeView::addParameterTieInComposite(QtProperty *property, const std::string &parameterName,
                                                  const CompositeFunction_sptr &composite, const std::size_t &index) {
714
715
716
717
718
719
720
  for (auto i = 0u; i < composite->nParams(); ++i) {
    const auto fullName = "f" + std::to_string(index) + "." + parameterName;
    if (fullName == composite->parameterName(i)) {
      if (const auto tie = composite->getTie(i)) {
        const auto tieStr = QString::fromStdString(tie->asString());
        addTieProperty(property, tieStr.mid(tieStr.indexOf('=') + 1));
        return true;
721
      }
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
      return false;
    }
  }
  return false;
}

/**
 * Add a global tie to a function property if one exists for the specified
 * parameter.
 * @param property :: A function property.
 * @param parameterName :: The name of the parameter to check for a global tie.
 * @param parentComposite :: The composite function the parameter is in. This is
 * a nullptr if the parameter is not in a composite function.
 * @param parentIndex :: The index of the parameter within its composite.
 */
Samuel Jones's avatar
Samuel Jones committed
737
738
739
void FunctionTreeView::addGlobalParameterTie(QtProperty *property, const std::string &parameterName,
                                             const CompositeFunction_sptr &parentComposite,
                                             const std::size_t &parentIndex) {
740
741
742
  if (m_multiDomainFunctionPrefix.isEmpty() || m_globalTies.empty())
    return;

Samuel Jones's avatar
Samuel Jones committed
743
  auto const fullName = getFullParameterName(parameterName, parentComposite ? static_cast<int>(parentIndex) : -1);
744
745
746
747
748

  for (auto const &globalTie : m_globalTies) {
    if (fullName == globalTie.m_parameter) {
      addTieProperty(property, QString::fromStdString(globalTie.m_tie), true);
      break;
749
750
751
752
    }
  }
}

753
754
755
756
757
758
759
760
761
762
763
764
765
/**
 * Adds a property showing the function index of a domain within a
 * MultiDomainFunction. It is used when we don't want to display an entire
 * MultiDomainFunction, just a specific function within it.
 * @param prop :: The property to add the function index on to.
 */
void FunctionTreeView::addMultiDomainIndexProperty(QtProperty *prop) {
  QtProperty *indexProperty = m_indexManager->addProperty("Index");
  indexProperty->setEnabled(false);
  m_indexManager->setValue(indexProperty, m_multiDomainFunctionPrefix);
  (void)addProperty(prop, indexProperty);
}

766
767
768
769
770
771
772
/**
 * Add property showing function's index in the composite function
 * @param prop :: A function property
 * @return :: AProperty struct for added property. If all fields are NULL -
 * property wasn't added
 *  because it is the top function
 */
Samuel Jones's avatar
Samuel Jones committed
773
FunctionTreeView::AProperty FunctionTreeView::addIndexProperty(QtProperty *prop) {
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
  AProperty ap;
  ap.item = nullptr;
  ap.parent = nullptr;
  ap.prop = nullptr;
  if (!prop)
    return ap;
  if (!isFunction(prop))
    return ap;
  if (!m_properties[prop].parent)
    return ap;

  QString index = "fff";
  QtProperty *ip = m_indexManager->addProperty("Index");
  ip->setEnabled(false);
  m_indexManager->setValue(ip, index);
  auto retval = addProperty(prop, ip);
  updateFunctionIndices();
  return retval;
}

/**
 * Update function index properties
 * @param prop :: A function property
 * @param index :: The parent function's index
 */
Samuel Jones's avatar
Samuel Jones committed
799
void FunctionTreeView::updateFunctionIndices(QtProperty *prop, const QString &index) {
800
801
802
803
804
805
806
807
808
809
810
811
812
  if (prop == nullptr) {
    auto top = m_browser->properties();
    if (top.isEmpty())
      return;
    prop = top[0];
  }
  auto children = prop->subProperties();
  size_t i = 0;
  foreach (QtProperty *child, children) {
    if (isFunction(child)) {
      updateFunctionIndices(child, index + "f" + QString::number(i) + ".");
      ++i;
    } else if (isIndex(child)) {
813
      m_indexManager->setValue(child, m_multiDomainFunctionPrefix + index);
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
    }
  }
}

/**
 * Get property of the overall function.
 */
FunctionTreeView::AProperty FunctionTreeView::getFunctionProperty() const {
  auto props = m_browser->properties();
  if (props.isEmpty()) {
    AProperty ap;
    ap.item = nullptr;
    ap.parent = nullptr;
    ap.prop = nullptr;
    return ap;
  }
  QtProperty *prop = props[0];
  return m_properties[prop];
}

/**
 * Check if property is a function group
 * @param prop :: Property to check
 */
bool FunctionTreeView::isFunction(QtProperty *prop) const {
Samuel Jones's avatar
Samuel Jones committed
839
  return prop && dynamic_cast<QtAbstractPropertyManager *>(m_functionManager) == prop->propertyManager();
840
841
842
843
844
845
846
}

/**
 * Check if property is any of the string attributes
 * @param prop :: Property to check
 */
bool FunctionTreeView::isStringAttribute(QtProperty *prop) const {
Samuel Jones's avatar
Samuel Jones committed
847
848
849
850
  return prop && (dynamic_cast<QtAbstractPropertyManager *>(m_attributeStringManager) == prop->propertyManager() ||
                  dynamic_cast<QtAbstractPropertyManager *>(m_formulaManager) == prop->propertyManager() ||
                  dynamic_cast<QtAbstractPropertyManager *>(m_filenameManager) == prop->propertyManager() ||
                  dynamic_cast<QtAbstractPropertyManager *>(m_workspaceManager) == prop->propertyManager());
851
852
853
854
855
856
857
}

/**
 * Check if property is a function attribute
 * @param prop :: Property to check
 */
bool FunctionTreeView::isDoubleAttribute(QtProperty *prop) const {
Samuel Jones's avatar
Samuel Jones committed
858
  return prop && dynamic_cast<QtAbstractPropertyManager *>(m_attributeDoubleManager) == prop->propertyManager();
859
860
861
862
863
864
865
}

/**
 * Check if property is a function attribute
 * @param prop :: Property to check
 */
bool FunctionTreeView::isIntAttribute(QtProperty *prop) const {
Samuel Jones's avatar
Samuel Jones committed
866
  return prop && dynamic_cast<QtAbstractPropertyManager *>(m_attributeIntManager) == prop->propertyManager();
867
868
869
870
871
872
873
}

/**
 * Check if property is a function bool attribute
 * @param prop :: Property to check
 */
bool FunctionTreeView::isBoolAttribute(QtProperty *prop) const {
Samuel Jones's avatar
Samuel Jones committed
874
  return prop && dynamic_cast<QtAbstractPropertyManager *>(m_attributeBoolManager) == prop->propertyManager();
875
876
877
878
879
880
881
}

/**
 * Check if property is a function vector attribute
 * @param prop :: Property to check
 */
bool FunctionTreeView::isVectorAttribute(QtProperty *prop) const {
Samuel Jones's avatar
Samuel Jones committed
882
  return prop && dynamic_cast<QtAbstractPropertyManager *>(m_attributeVectorManager) == prop->propertyManager();
883
884
885
886
887
888
889
}

/**
 * Check if property is a function attribute
 * @param prop :: Property to check
 */
bool FunctionTreeView::isAttribute(QtProperty *prop) const {
Samuel Jones's avatar
Samuel Jones committed
890
  return isStringAttribute(prop) || isDoubleAttribute(prop) || isIntAttribute(prop) || isBoolAttribute(prop) ||
891
892
893
894
895
896
897
898
         isVectorAttribute(prop);
}

/**
 * Check if property is a function parameter
 * @param prop :: Property to check
 */
bool FunctionTreeView::isParameter(QtProperty *prop) const {
Samuel Jones's avatar
Samuel Jones committed
899
  return prop && dynamic_cast<QtAbstractPropertyManager *>(m_parameterManager) == prop->propertyManager();
900
901
902
903
904
905
}

/**
 * Get parameter value as a string
 * @param prop :: A parameter property
 */
Samuel Jones's avatar
Samuel Jones committed
906
double FunctionTreeView::getParameter(QtProperty *prop) const { return m_parameterManager->value(prop); }
907
908
909
910
911
/**
 * Check if a property is an index
 * @param prop :: A property
 */
bool FunctionTreeView::isIndex(QtProperty *prop) const {
Samuel Jones's avatar
Samuel Jones committed
912
  return prop && dynamic_cast<QtAbstractPropertyManager *>(m_indexManager) == prop->propertyManager();
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
940
941
}

/**
 * Get the function index for a property
 * @param prop :: A property
 */
QString FunctionTreeView::getIndex(QtProperty *prop) const {
  if (!prop)
    return "";
  if (isFunction(prop)) {
    auto props = prop->subProperties();
    if (props.isEmpty())
      return "";
    for (auto it = props.begin(); it != props.end(); ++it) {
      if (isIndex(*it)) {
        return m_indexManager->value(*it);
      }
    }
    return "";
  }

  auto ap = m_properties[prop];
  return getIndex(ap.parent);
}

/**
 * Get name of the parameter for a property
 * @param prop :: A property
 */
942
QString FunctionTreeView::getParameterName(QtProperty *prop) const {
943
944
945
946
947
948
  if (isParameter(prop)) {
    return getIndex(prop) + prop->propertyName();
  } else {
    auto parent = getParentParameterProperty(prop);
    return getIndex(prop) + parent->propertyName();
  }
949
}
950
951
952
953
954
955
956
957
/**
 * Get name of the attribute for a property
 * @param prop :: A property
 */
QString FunctionTreeView::getAttributeName(QtProperty *prop) const {
  if (isAttribute(prop)) {
    return getIndex(prop) + prop->propertyName();
  }
Samuel Jones's avatar
Samuel Jones committed
958
  throw std::logic_error("QtProperty " + prop->propertyName().toStdString() + " is not an attribute property.");
959
}
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984

/**
 * Return function property for a function with given index.
 * @param index :: Function index to search, or empty string for top-level
 * function
 * @return Function property, or NULL if not found
 */
QtProperty *FunctionTreeView::getFunctionProperty(const QString &index) const {
  // Might not be the most efficient way to do it. m_functionManager might be
  // searched instead,
  // but it is not being kept up-to-date at the moment (is not cleared).
  foreach (auto property, m_properties.keys()) {
    if (isFunction(property) && getIndex(property) == index) {
      return property;
    }
  }

  // No function with such index
  return nullptr;
}

/**
 * Add a tie property
 * @param prop :: Parent parameter property
 * @param tie :: A tie string
985
 * @param globalTie :: true if the tie is a global tie.
986
 */
Samuel Jones's avatar
Samuel Jones committed
987
void FunctionTreeView::addTieProperty(QtProperty *prop, const QString &tie, bool globalTie) {
988
989
990
991
992
993
994
995
996
997
998
999
  if (!prop) {
    throw std::runtime_error("FunctionTreeView: null property pointer");
  }

  if (!isParameter(prop))
    return;

  QtProperty *funProp = getFunctionProperty().prop;

  // Create and add a QtProperty for the tie.
  m_tieManager->blockSignals(true);
  QtProperty *tieProp = m_tieManager->addProperty("Tie");
1000
  m_tieManager->setValue(tieProp, globalTie ? tie : getFullTie(tie));
For faster browsing, not all history is shown. View entire blame