MWRunFiles.cpp 29.7 KB
Newer Older
1
2
3
4
5
6
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
//     NScD Oak Ridge National Laboratory, European Spallation Source
//     & Institut Laue - Langevin
// SPDX - License - Identifier: GPL - 3.0 +
7
#include "MantidQtWidgets/Common/MWRunFiles.h"
8
#include "MantidQtWidgets/Common/DropEventHelper.h"
9
10

#include "MantidAPI/AlgorithmManager.h"
11
12
13
#include "MantidAPI/FileFinder.h"
#include "MantidAPI/FileProperty.h"
#include "MantidAPI/LiveListenerFactory.h"
LamarMoore's avatar
LamarMoore committed
14
15
16
17
#include "MantidAPI/MultipleFileProperty.h"
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/FacilityInfo.h"
#include "MantidKernel/VectorHelper.h"
18

LamarMoore's avatar
LamarMoore committed
19
20
21
#include <Poco/File.h>
#include <QDragEnterEvent>
#include <QDropEvent>
22
#include <QFileDialog>
Peterson, Peter's avatar
Peterson, Peter committed
23
24
#include <QFileInfo>
#include <QHash>
25
#include <QMimeData>
LamarMoore's avatar
LamarMoore committed
26
#include <QStringList>
27
#include <QUrl>
28
#include <QtConcurrentRun>
29
30

#include <boost/algorithm/string.hpp>
31
#include <boost/regex.hpp>
32
33
34

using namespace Mantid::Kernel;
using namespace Mantid::API;
35
using namespace MantidQt::API;
36
namespace DropEventHelper = MantidQt::MantidWidgets::DropEventHelper;
37

38
39
40
41
////////////////////////////////////////////////////////////////////
// MWRunFiles
////////////////////////////////////////////////////////////////////

Dan Nixon's avatar
Dan Nixon committed
42
43
44
45
46
MWRunFiles::MWRunFiles(QWidget *parent)
    : MantidWidget(parent), m_findRunFiles(true), m_isForDirectory(false),
      m_allowMultipleFiles(false), m_isOptional(false), m_multiEntry(false),
      m_buttonOpt(Text), m_fileProblem(""), m_entryNumProblem(""),
      m_algorithmProperty(""), m_fileExtensions(), m_extsAsSingleOption(true),
47
      m_liveButtonState(Hide), m_showValidator(true), m_foundFiles(),
48
      m_lastFoundFiles(), m_lastDir(), m_fileFilter(), m_pool() {
Dan Nixon's avatar
Dan Nixon committed
49

50
51
  m_uiForm.setupUi(this);

Dan Nixon's avatar
Dan Nixon committed
52
53
54
  connect(m_uiForm.fileEditor, SIGNAL(textChanged(const QString &)), this,
          SIGNAL(fileTextChanged(const QString &)));
  connect(m_uiForm.fileEditor, SIGNAL(editingFinished()), this,
Matthew Bowles's avatar
Matthew Bowles committed
55
          SIGNAL(fileEditingFinished()));
56
  connect(m_uiForm.browseBtn, SIGNAL(clicked()), this, SLOT(browseClicked()));
57
  connect(m_uiForm.browseIco, SIGNAL(clicked()), this, SLOT(browseClicked()));
58

59
  connect(this, SIGNAL(fileEditingFinished()), this, SLOT(findFiles()));
Dan Nixon's avatar
Dan Nixon committed
60
61
62
63
  connect(m_uiForm.entryNum, SIGNAL(textChanged(const QString &)), this,
          SLOT(checkEntry()));
  connect(m_uiForm.entryNum, SIGNAL(editingFinished()), this,
          SLOT(checkEntry()));
64

65
  m_uiForm.fileEditor->clear();
Dan Nixon's avatar
Dan Nixon committed
66
67

  if (m_multiEntry) {
68
    m_uiForm.entryNum->show();
69
    m_uiForm.numEntries->show();
Dan Nixon's avatar
Dan Nixon committed
70
  } else {
71
72
73
74
    m_uiForm.entryNum->hide();
    m_uiForm.numEntries->hide();
  }

75
  doButtonOpt(m_buttonOpt);
76

77
  liveButtonState(m_liveButtonState);
Dan Nixon's avatar
Dan Nixon committed
78
79
  connect(m_uiForm.liveButton, SIGNAL(toggled(bool)), this,
          SIGNAL(liveButtonPressed(bool)));
80

81
82
  setFocusPolicy(Qt::StrongFocus);
  setFocusProxy(m_uiForm.fileEditor);
83

Dan Nixon's avatar
Dan Nixon committed
84
85
  // When first used try to starting directory better than the directory
  // MantidPlot is installed in
86
  // First try default save directory
Dan Nixon's avatar
Dan Nixon committed
87
88
89
  m_lastDir = QString::fromStdString(
      Mantid::Kernel::ConfigService::Instance().getString(
          "defaultsave.directory"));
90
91

  // If that fails pick the first data search directory
Dan Nixon's avatar
Dan Nixon committed
92
93
94
95
  if (m_lastDir.isEmpty()) {
    QStringList dataDirs =
        QString::fromStdString(
            Mantid::Kernel::ConfigService::Instance().getString(
LamarMoore's avatar
LamarMoore committed
96
97
                "datasearch.directories"))
            .split(";", QString::SkipEmptyParts);
98

Dan Nixon's avatar
Dan Nixon committed
99
    if (!dataDirs.isEmpty())
100
101
      m_lastDir = dataDirs[0];
  }
102

Dan Nixon's avatar
Dan Nixon committed
103
  // this for accepts drops, but the underlying text input does not.
104
105
  this->setAcceptDrops(true);
  m_uiForm.fileEditor->setAcceptDrops(false);
106
107
108
}

/**
LamarMoore's avatar
LamarMoore committed
109
110
111
 * Returns if this widget is for run file searching or not
 * @returns True if this widget searches for run files, false otherwise
 */
Dan Nixon's avatar
Dan Nixon committed
112
bool MWRunFiles::isForRunFiles() const { return m_findRunFiles; }
113
114

/**
LamarMoore's avatar
LamarMoore committed
115
116
117
 * Sets whether this widget is for run file searching or not
 * @param mode :: True if this widget searches for run files, false otherwise
 */
Dan Nixon's avatar
Dan Nixon committed
118
void MWRunFiles::isForRunFiles(const bool mode) { m_findRunFiles = mode; }
119

120
121
122
123
/**
 * Returns if this widget is for selecting a directory or not.
 * @return True if selecting a directory
 */
Dan Nixon's avatar
Dan Nixon committed
124
bool MWRunFiles::isForDirectory() const { return m_isForDirectory; }
125
126
127
128
129

/**
 * Sets directory searching mode.
 * @param mode True to search for directories only
 */
Dan Nixon's avatar
Dan Nixon committed
130
void MWRunFiles::isForDirectory(const bool mode) {
Dan Nixon's avatar
Dan Nixon committed
131
  clear();
132
133
134
  m_isForDirectory = mode;
}

135
/**
LamarMoore's avatar
LamarMoore committed
136
137
138
 * Return the label text on the widget
 * @returns The current value of the text on the label
 */
Dan Nixon's avatar
Dan Nixon committed
139
QString MWRunFiles::getLabelText() const { return m_uiForm.textLabel->text(); }
140
141

/**
LamarMoore's avatar
LamarMoore committed
142
143
144
 * Set the text on the label
 * @param text :: A string giving the label to use for the text
 */
Dan Nixon's avatar
Dan Nixon committed
145
void MWRunFiles::setLabelText(const QString &text) {
146
  m_uiForm.textLabel->setText(text);
Dan Nixon's avatar
Dan Nixon committed
147
  m_uiForm.textLabel->setVisible(!text.isEmpty());
148
149
}

150
151
152
/** Set the minimum width on the label widget
 *  @param width The new minimum width of the widget
 */
Dan Nixon's avatar
Dan Nixon committed
153
void MWRunFiles::setLabelMinWidth(const int width) {
154
155
156
  m_uiForm.textLabel->setMinimumWidth(width);
}

157
/**
LamarMoore's avatar
LamarMoore committed
158
159
160
161
 * Return whether this widget allows multiple files to be specified within the
 * edit box
 * @returns True if multiple files can be specified, false otherwise
 */
Dan Nixon's avatar
Dan Nixon committed
162
bool MWRunFiles::allowMultipleFiles() const { return m_allowMultipleFiles; }
163
164

/**
LamarMoore's avatar
LamarMoore committed
165
166
167
168
 * Set whether this widget allows multiple files to be specifed or not
 * @param allow :: If true then the widget will accept multiple files else only
 * a single file may be specified
 */
Dan Nixon's avatar
Dan Nixon committed
169
void MWRunFiles::allowMultipleFiles(const bool allow) {
170
171
172
173
174
  m_allowMultipleFiles = allow;
  findFiles();
}

/**
LamarMoore's avatar
LamarMoore committed
175
176
 * Return whether empty input is allowed
 */
Dan Nixon's avatar
Dan Nixon committed
177
bool MWRunFiles::isOptional() const { return m_isOptional; }
178
179

/**
LamarMoore's avatar
LamarMoore committed
180
181
182
 * Sets if the text field is optional
 * @param optional :: Set the optional status of the text field
 */
Dan Nixon's avatar
Dan Nixon committed
183
void MWRunFiles::isOptional(const bool optional) {
184
185
186
187
  m_isOptional = optional;
  findFiles();
}

188
/**
LamarMoore's avatar
LamarMoore committed
189
190
191
 * Returns the preference for how the dialog control should be
 * @return the setting
 */
Dan Nixon's avatar
Dan Nixon committed
192
MWRunFiles::ButtonOpts MWRunFiles::doButtonOpt() const { return m_buttonOpt; }
193
194

/**
LamarMoore's avatar
LamarMoore committed
195
196
197
198
 * Set how the browse should appear
 * @param buttonOpt the preference for the control, if there will be one, to
 * activate the dialog box
 */
Dan Nixon's avatar
Dan Nixon committed
199
void MWRunFiles::doButtonOpt(const MWRunFiles::ButtonOpts buttonOpt) {
200
  m_buttonOpt = buttonOpt;
Dan Nixon's avatar
Dan Nixon committed
201
  if (buttonOpt == None) {
202
203
    m_uiForm.browseBtn->hide();
    m_uiForm.browseIco->hide();
Dan Nixon's avatar
Dan Nixon committed
204
  } else if (buttonOpt == Text) {
205
206
    m_uiForm.browseBtn->show();
    m_uiForm.browseIco->hide();
Dan Nixon's avatar
Dan Nixon committed
207
  } else if (buttonOpt == Icon) {
208
209
210
211
212
    m_uiForm.browseBtn->hide();
    m_uiForm.browseIco->show();
  }
}

213
/**
LamarMoore's avatar
LamarMoore committed
214
215
216
217
 * Whether to find the number of entries in the file or assume (the
 * normal situation) of one entry
 * @return true if the widget is to look for multiple entries
 */
Dan Nixon's avatar
Dan Nixon committed
218
bool MWRunFiles::doMultiEntry() const { return m_multiEntry; }
219
220

/**
LamarMoore's avatar
LamarMoore committed
221
222
223
 * Set to true to enable the period number box
 * @param multiEntry whether to show the multiperiod box
 */
Dan Nixon's avatar
Dan Nixon committed
224
void MWRunFiles::doMultiEntry(const bool multiEntry) {
225
  m_multiEntry = multiEntry;
Dan Nixon's avatar
Dan Nixon committed
226
  if (m_multiEntry) {
227
    m_uiForm.entryNum->show();
228
    m_uiForm.numEntries->show();
Dan Nixon's avatar
Dan Nixon committed
229
  } else {
230
    m_uiForm.entryNum->hide();
231
    m_uiForm.numEntries->hide();
232
  }
233
  refreshValidator();
234
235
}

236
/**
LamarMoore's avatar
LamarMoore committed
237
238
239
 * Returns the algorithm name
 * @returns The algorithm name
 */
Dan Nixon's avatar
Dan Nixon committed
240
QString MWRunFiles::getAlgorithmProperty() const { return m_algorithmProperty; }
241
242

/**
LamarMoore's avatar
LamarMoore committed
243
244
245
246
 * Sets an algorithm name that can be tied to this widget
 * @param text :: The name of the algorithm and property in the form
 * [AlgorithmName|PropertyName]
 */
Dan Nixon's avatar
Dan Nixon committed
247
void MWRunFiles::setAlgorithmProperty(const QString &text) {
248
249
250
  m_algorithmProperty = text;
}
/**
LamarMoore's avatar
LamarMoore committed
251
252
253
 * Returns the list of file extensions the widget will search for.
 * @return list of file extensions
 */
Dan Nixon's avatar
Dan Nixon committed
254
QStringList MWRunFiles::getFileExtensions() const { return m_fileExtensions; }
255
/**
LamarMoore's avatar
LamarMoore committed
256
257
258
259
 * Sets the list of file extensions the dialog will search for. Only taken
 * notice of if AlgorithmProperty not set.
 * @param extensions :: list of file extensions
 */
Dan Nixon's avatar
Dan Nixon committed
260
void MWRunFiles::setFileExtensions(const QStringList &extensions) {
261
262
263
264
  m_fileExtensions = extensions;
  m_fileFilter.clear();
}

265
/**
Dan Nixon's avatar
Dan Nixon committed
266
267
 * Returns whether the file dialog should display the exts as a single list or
 * as multiple items
268
269
 * @return boolean
 */
Dan Nixon's avatar
Dan Nixon committed
270
bool MWRunFiles::extsAsSingleOption() const { return m_extsAsSingleOption; }
271
272

/**
Dan Nixon's avatar
Dan Nixon committed
273
274
275
276
 * Sets whether the file dialog should display the exts as a single list or as
 * multiple items
 * @param value :: If true the file dialog wil contain a single entry will all
 * filters
277
 */
Dan Nixon's avatar
Dan Nixon committed
278
void MWRunFiles::extsAsSingleOption(const bool value) {
279
280
281
  m_extsAsSingleOption = value;
}

282
/// Returns whether the live button is being shown;
Dan Nixon's avatar
Dan Nixon committed
283
MWRunFiles::LiveButtonOpts MWRunFiles::liveButtonState() const {
284
285
286
  return m_liveButtonState;
}

Dan Nixon's avatar
Dan Nixon committed
287
void MWRunFiles::liveButtonState(const LiveButtonOpts option) {
288
  m_liveButtonState = option;
Dan Nixon's avatar
Dan Nixon committed
289
  if (m_liveButtonState == Hide) {
290
    m_uiForm.liveButton->hide();
291
  } else if (m_liveButtonState == Show) {
292
    m_uiForm.liveButton->show();
293
294
295
  }
}

Dan Nixon's avatar
Dan Nixon committed
296
void MWRunFiles::liveButtonSetChecked(const bool checked) {
297
298
299
  m_uiForm.liveButton->setChecked(checked);
}

Dan Nixon's avatar
Dan Nixon committed
300
bool MWRunFiles::liveButtonIsChecked() const {
301
302
303
  return m_uiForm.liveButton->isChecked();
}

304
/**
LamarMoore's avatar
LamarMoore committed
305
306
307
 * Is the input within the widget valid?
 * @returns True of the file names within the widget are valid, false otherwise
 */
308
bool MWRunFiles::isValid() const { return m_uiForm.valid->isHidden(); }
309

310
311
312
313
/**
 * Is the widget currently searching
 * @return True if a search is inprogress
 */
314
bool MWRunFiles::isSearching() const { return m_pool.isSearchRunning(); }
315

Dan Nixon's avatar
Dan Nixon committed
316
/**
LamarMoore's avatar
LamarMoore committed
317
318
319
 * Returns the names of the files found
 * @return an array of filenames entered in the box
 */
Dan Nixon's avatar
Dan Nixon committed
320
QStringList MWRunFiles::getFilenames() const { return m_foundFiles; }
321
322

/** Safer than using getRunFiles()[0] in the situation were there are no files
LamarMoore's avatar
LamarMoore committed
323
324
325
326
327
328
 *  @return an empty string is returned if no input files have been defined or
 * one of the files can't be found, otherwise it's the name of the first input
 * file
 *  @throw invalid_argument if one of the files couldn't be found it then no
 * filenames are returned
 */
Dan Nixon's avatar
Dan Nixon committed
329
330
QString MWRunFiles::getFirstFilename() const {
  if (m_foundFiles.isEmpty())
331
332
333
334
335
    return "";
  else
    return m_foundFiles[0];
}

336
/** Check if any text, valid or not, has been entered into the line edit
LamarMoore's avatar
LamarMoore committed
337
338
 *  @return true if no text has been entered
 */
Dan Nixon's avatar
Dan Nixon committed
339
bool MWRunFiles::isEmpty() const {
340
341
342
343
  return m_uiForm.fileEditor->text().isEmpty();
}

/** The verbatum, unexpanded text, that was entered into the box
LamarMoore's avatar
LamarMoore committed
344
345
 *  @return the contents shown in the Line Edit
 */
Dan Nixon's avatar
Dan Nixon committed
346
QString MWRunFiles::getText() const { return m_uiForm.fileEditor->text(); }
347

348
/** The number the user entered into the entryNum lineEdit
LamarMoore's avatar
LamarMoore committed
349
350
351
 * or NO_ENTRY_NUM on error. Checking if isValid is true should
 * eliminate the possiblity of getting NO_ENTRY_NUM
 */
Dan Nixon's avatar
Dan Nixon committed
352
353
int MWRunFiles::getEntryNum() const {
  if (m_uiForm.entryNum->text().isEmpty() || (!m_multiEntry)) {
354
355
    return ALL_ENTRIES;
  }
Dan Nixon's avatar
Dan Nixon committed
356
  if (isValid()) {
357
358
    bool isANumber;
    const int period = m_uiForm.entryNum->text().toInt(&isANumber);
Dan Nixon's avatar
Dan Nixon committed
359
    if (isANumber) {
360
361
362
363
364
365
      return period;
    }
  }
  return NO_ENTRY_NUM;
}

366
/** Set the entry displayed in the box to this value
LamarMoore's avatar
LamarMoore committed
367
368
 * @param num the period number to use
 */
Dan Nixon's avatar
Dan Nixon committed
369
void MWRunFiles::setEntryNum(const int num) {
370
371
372
  m_uiForm.entryNum->setText(QString::number(num));
}

373
/**
374
 * Retrieve user input from this widget. This expands the current
375
376
 * file text to include the found absolute paths so that no more
 * searching is required
377
 * NOTE: This knows nothing of periods yet
378
 * @returns A QVariant containing the text string for the algorithm property
379
 */
Dan Nixon's avatar
Dan Nixon committed
380
QVariant MWRunFiles::getUserInput() const {
381
  return QVariant(m_valueForProperty);
382
383
}

384
/**
385
386
387
388
 * "Silently" sets the value of the widget.  It does NOT emit a signal to say it
 * has changed, and as far as the file finding routine is concerned it has not
 * been modified and so it will NOT go searching for files.
 *
389
390
391
 * @param value A QString containing text to be entered into the widget
 */

Dan Nixon's avatar
Dan Nixon committed
392
void MWRunFiles::setText(const QString &value) {
393
394
395
  m_uiForm.fileEditor->setText(value);
}

396
/**
Dan Nixon's avatar
Dan Nixon committed
397
 * Sets a value on the widget through a common interface. The
398
 * QVariant is assumed to be text and to contain a file string. Note that this
399
400
401
402
403
 * is primarily here for use within the AlgorithmDialog.
 *
 * Emits fileEditingFinished(), and changes to state of the text box widget
 * to be "modified", so that the file finder will try and find the file(s).
 *
404
405
 * @param value A QVariant containing user text
 */
Dan Nixon's avatar
Dan Nixon committed
406
void MWRunFiles::setUserInput(const QVariant &value) {
407
408
409
  m_uiForm.fileEditor->setText(value.toString());
  m_uiForm.fileEditor->setModified(true);
  emit fileEditingFinished(); // Which is connected to slot findFiles()
410
411
412
}

/**
Dan Nixon's avatar
Dan Nixon committed
413
414
415
416
 * Flag a problem with the file the user entered, an empty string means no error
 * but
 * there may be an error with the entry box if enabled. Errors passed here are
 * shown first
417
 * @param message :: A message to include or "" for no error
418
 */
Dan Nixon's avatar
Dan Nixon committed
419
void MWRunFiles::setFileProblem(const QString &message) {
420
421
422
423
  m_fileProblem = message;
  refreshValidator();
}

424
/**
LamarMoore's avatar
LamarMoore committed
425
426
427
 * Return the error.
 * @returns A string explaining the error.
 */
Dan Nixon's avatar
Dan Nixon committed
428
QString MWRunFiles::getFileProblem() { return m_fileProblem; }
429

430
/**
LamarMoore's avatar
LamarMoore committed
431
432
433
 * Save settings to the given group
 * @param group :: The name of the group key to save to
 */
Dan Nixon's avatar
Dan Nixon committed
434
void MWRunFiles::saveSettings(const QString &group) {
435
436
437
438
439
440
441
  QSettings settings;
  settings.beginGroup(group);

  settings.setValue("last_directory", m_lastDir);

  settings.endGroup();
}
442

443
/** Writes the total number of periods in a file to the NumEntries
LamarMoore's avatar
LamarMoore committed
444
445
446
447
 *  Qlabel
 *  @param number the number to write, if this is < 1 a ? will be displayed in
 * it's place
 */
Dan Nixon's avatar
Dan Nixon committed
448
void MWRunFiles::setNumberOfEntries(const int number) {
449
  const QString total = number > 0 ? QString::number(number) : "?";
Dan Nixon's avatar
Dan Nixon committed
450
  { m_uiForm.numEntries->setText("/" + total); }
451
}
452

Dan Nixon's avatar
Dan Nixon committed
453
454
455
456
/** Inform the widget of a running instance of MonitorLiveData to be used in
 * stopLiveListener().
 *  Note that the type passed in is IAlgorithm and that no check is made that it
 * actually refers
457
458
459
 *  to an instance of MonitorLiveData.
 *  @param monitorLiveData The running algorithm
 */
Dan Nixon's avatar
Dan Nixon committed
460
void MWRunFiles::setLiveAlgorithm(const IAlgorithm_sptr &monitorLiveData) {
461
462
463
  m_monitorLiveData = monitorLiveData;
}

464
465
466
/**
 * Gets the instrument currently set by the override property.
 *
Dan Nixon's avatar
Dan Nixon committed
467
468
 * If no override is set then the instrument set by default instrument
 *configurtion
469
470
471
472
 * option will be used and this function returns an empty string.
 *
 * @return Name of instrument, empty if not set
 */
Dan Nixon's avatar
Dan Nixon committed
473
QString MWRunFiles::getInstrumentOverride() { return m_defaultInstrumentName; }
474
475
476
477

/**
 * Sets an instrument to fix the widget to.
 *
Dan Nixon's avatar
Dan Nixon committed
478
479
480
481
 * If an instrument name is geven then the widget will only look for files for
 *that
 * instrument, providing na empty string will remove this restriction and will
 *search
482
483
484
485
 * using the default instrument.
 *
 * @param instName Name of instrument, empty to disable override
 */
Dan Nixon's avatar
Dan Nixon committed
486
void MWRunFiles::setInstrumentOverride(const QString &instName) {
487
  m_defaultInstrumentName = instName;
488
  findFiles(true);
489
490
}

Dan Nixon's avatar
Dan Nixon committed
491
/**
LamarMoore's avatar
LamarMoore committed
492
493
494
495
496
497
498
499
 * Set the file text.  This is different to setText in that it emits findFiles,
 *as well
 * changing the state of the text box widget to "modified = true" which is a
 *prerequisite
 * of findFiles.
 *
 * @param text :: The text string to set
 */
Dan Nixon's avatar
Dan Nixon committed
500
void MWRunFiles::setFileTextWithSearch(const QString &text) {
501
502
503
504
  setFileTextWithoutSearch(text);
  findFiles();
}
/**
LamarMoore's avatar
LamarMoore committed
505
506
507
 * Set the file text but do not search
 * @param text :: The text string to set
 */
Dan Nixon's avatar
Dan Nixon committed
508
void MWRunFiles::setFileTextWithoutSearch(const QString &text) {
509
  m_uiForm.fileEditor->setText(text);
510
  m_uiForm.fileEditor->setModified(true);
511
}
512

Dan Nixon's avatar
Dan Nixon committed
513
514
515
/**
 * Clears the search string and found files from the widget.
 */
Dan Nixon's avatar
Dan Nixon committed
516
void MWRunFiles::clear() {
Dan Nixon's avatar
Dan Nixon committed
517
518
519
520
  m_foundFiles.clear();
  m_uiForm.fileEditor->setText("");
}

521
/**
LamarMoore's avatar
LamarMoore committed
522
523
 * Finds the files if the user has changed the parameter text.
 */
Matthew Bowles's avatar
Matthew Bowles committed
524
void MWRunFiles::findFiles() { findFiles(m_uiForm.fileEditor->isModified()); }
525
526

/**
LamarMoore's avatar
LamarMoore committed
527
528
 * Finds the files specified by the user in a background thread.
 */
529
530
void MWRunFiles::findFiles(bool isModified) {
  auto searchText = m_uiForm.fileEditor->text();
531
532
533
534
535
536
537
538

  if (m_isForDirectory) {
    m_foundFiles.clear();
    if (searchText.isEmpty()) {
      setFileProblem("A directory must be provided");
    } else {
      setFileProblem("");
      m_foundFiles.append(searchText);
539
    }
540
541
542
543
544
545
546
547
    return;
  }

  if (isModified) {
    // Reset modified flag.
    m_uiForm.fileEditor->setModified(false);
    searchText = findFilesGetSearchText(searchText);
    runFindFiles(searchText);
Matthew Bowles's avatar
Matthew Bowles committed
548
549
550
  } else {
    // Make sure errors are correctly set if we didn't run
    inspectThreadResult(m_cachedResults);
551
552
553
554
  }
}

/**
LamarMoore's avatar
LamarMoore committed
555
556
557
558
 * Gets the search text to find files with.
 * @param searchText :: text entered by user
 * @return search text to create search params with
 */
559
560
561
562
563
564
565
const QString MWRunFiles::findFilesGetSearchText(QString &searchText) {
  // If we have an override instrument then add it in appropriate places to
  // the search text
  if (!m_defaultInstrumentName.isEmpty()) {
    // Regex to match a selection of run numbers as defined here:
    // mantidproject.org/MultiFileLoading
    // Also allowing spaces between delimiters as this seems to work fine
Matthew Bowles's avatar
Matthew Bowles committed
566
    const std::string runNumberString = "([0-9]+)([:+-] ?[0-9]+)? ?(:[0-9]+)?";
567
    boost::regex runNumberExp(runNumberString, boost::regex::extended);
568
569
    // Regex to match a list of run numbers delimited by commas
    const std::string runListString =
Matthew Bowles's avatar
Matthew Bowles committed
570
        "(" + runNumberString + ")(, ?(" + runNumberString + "))*";
571
    boost::regex runNumberListExp(runListString, boost::regex::extended);
572
573

    // See if we can just prepend the instrument and be done
574
    if (boost::regex_match(searchText.toStdString(), runNumberExp)) {
575
576
577
      searchText = m_defaultInstrumentName + searchText;
    }
    // If it is a list we need to prepend the instrument to all run numbers
578
    else if (boost::regex_match(searchText.toStdString(), runNumberListExp)) {
579
580
581
      QStringList runNumbers = searchText.split(",", QString::SkipEmptyParts);
      QStringList newRunNumbers;

Tom Titcombe's avatar
Tom Titcombe committed
582
      for (auto &runNumber : runNumbers)
583
        newRunNumbers << m_defaultInstrumentName + runNumber.simplified();
Dan Nixon's avatar
Dan Nixon committed
584

585
586
587
588
589
590
591
      searchText = newRunNumbers.join(",");
    }
  }
  return searchText;
}

/**
LamarMoore's avatar
LamarMoore committed
592
593
594
 * creates background thread for find files
 * @param searchText :: text to create search parameters from
 */
595
void MWRunFiles::runFindFiles(const QString &searchText) {
596
  emit findingFiles();
597

598
599
600
  const auto parameters =
      createFindFilesSearchParameters(searchText.toStdString());
  m_pool.createWorker(this, parameters);
601
}
602

603
/** Calls cancel on a running instance of MonitorLiveData.
Dan Nixon's avatar
Dan Nixon committed
604
605
606
607
 *  Requires that a handle to the MonitorLiveData instance has been set via
 * setLiveListener()
 *  @return A handle to the cancelled algorithm (usable if the method is called
 * directly)
608
 */
Dan Nixon's avatar
Dan Nixon committed
609
IAlgorithm_const_sptr MWRunFiles::stopLiveAlgorithm() {
610
  IAlgorithm_const_sptr theAlgorithmBeingCancelled = m_monitorLiveData;
Dan Nixon's avatar
Dan Nixon committed
611
  if (m_monitorLiveData && m_monitorLiveData->isRunning()) {
612
613
614
615
616
617
    m_monitorLiveData->cancel();
    m_monitorLiveData.reset();
  }
  return theAlgorithmBeingCancelled;
}

618
619
620
621
/**
 * Called when the file finding thread finishes.  Inspects the result
 * of the thread, and emits fileFound() if it has been successful.
 */
622
void MWRunFiles::inspectThreadResult(const FindFilesSearchResults &results) {
623
624
625
626
627
628
629
630
631
  // Update caches before we might exit early
  m_cachedResults = results;
  m_valueForProperty = QString::fromStdString(results.valueForProperty);
  m_foundFiles.clear();
  m_lastFoundFiles.clear();

  // Early exit on failure
  if (!results.error.empty()) {
    setFileProblem(QString::fromStdString(results.error));
632
    emit fileInspectionFinished();
633
634
635
    return;
  }

636
637
  for (const auto &filename : results.filenames) {
    m_foundFiles.append(QString::fromStdString(filename));
638
  }
Dan Nixon's avatar
Dan Nixon committed
639
640
641
642
  if (m_foundFiles.isEmpty() && !isOptional()) {
    setFileProblem(
        "No files found. Check search paths and instrument selection.");
  } else if (m_foundFiles.count() > 1 && this->allowMultipleFiles() == false) {
643
    setFileProblem("Multiple files specified.");
Dan Nixon's avatar
Dan Nixon committed
644
  } else {
645
    setFileProblem("");
646
  }
647

648
649
  emit fileInspectionFinished();

650
  // Only emit the signal if file(s) were found
Dan Nixon's avatar
Dan Nixon committed
651
652
653
654
  if (!m_foundFiles.isEmpty())
    emit filesFound();
  if (m_lastFoundFiles != m_foundFiles)
    emit filesFoundChanged();
655
656
657
}

/**
LamarMoore's avatar
LamarMoore committed
658
659
660
 * Read settings from the given group
 * @param group :: The name of the group key to retrieve data from
 */
Dan Nixon's avatar
Dan Nixon committed
661
void MWRunFiles::readSettings(const QString &group) {
662
663
664
665
  QSettings settings;
  settings.beginGroup(group);
  m_lastDir = settings.value("last_directory", "").toString();

Dan Nixon's avatar
Dan Nixon committed
666
667
668
669
  if (m_lastDir == "") {
    QStringList datadirs =
        QString::fromStdString(
            Mantid::Kernel::ConfigService::Instance().getString(
LamarMoore's avatar
LamarMoore committed
670
671
                "datasearch.directories"))
            .split(";", QString::SkipEmptyParts);
Dan Nixon's avatar
Dan Nixon committed
672
673
    if (!datadirs.isEmpty())
      m_lastDir = datadirs[0];
674
675
676
677
678
679
  }

  settings.endGroup();
}

/**
LamarMoore's avatar
LamarMoore committed
680
681
682
 * Set a new file filter for the file dialog based on the given extensions
 * @returns A string containing the file filter
 */
Dan Nixon's avatar
Dan Nixon committed
683
QString MWRunFiles::createFileFilter() {
684
  QStringList fileExts;
Dan Nixon's avatar
Dan Nixon committed
685
686
  if (m_algorithmProperty.isEmpty()) {
    if (!m_fileExtensions.isEmpty()) {
687
      fileExts = m_fileExtensions;
Dan Nixon's avatar
Dan Nixon committed
688
689
690
    } else if (isForRunFiles()) {
      std::vector<std::string> exts =
          ConfigService::Instance().getFacility().extensions();
Tom Titcombe's avatar
Tom Titcombe committed
691
      for (auto &ext : exts) {
692
        fileExts.append(QString::fromStdString(ext));
693
      }
Dan Nixon's avatar
Dan Nixon committed
694
    } else {
695
    }
Dan Nixon's avatar
Dan Nixon committed
696
  } else {
697
    QStringList elements = m_algorithmProperty.split("|");
Dan Nixon's avatar
Dan Nixon committed
698
    if (elements.size() == 2) {
699
700
701
      fileExts = getFileExtensionsFromAlgorithm(elements[0], elements[1]);
    }
  }
702

703
  QString allFiles("All Files (*)");
Dan Nixon's avatar
Dan Nixon committed
704
705
706
707
  if (!fileExts.isEmpty()) {

    // The list may contain upper and lower cased versions, ensure these are on
    // the same line
708
    // I want this ordered
Dan Nixon's avatar
Dan Nixon committed
709
    QList<QPair<QString, QStringList>> finalIndex;
710
711
712
    QStringListIterator sitr(fileExts);
    QString ext = sitr.next();
    finalIndex.append(qMakePair(ext.toUpper(), QStringList(ext)));
Dan Nixon's avatar
Dan Nixon committed
713
    while (sitr.hasNext()) {
714
715
716
      ext = sitr.next();
      QString key = ext.toUpper();
      bool found(false);
717
      const int itemCount = finalIndex.count();
Dan Nixon's avatar
Dan Nixon committed
718
719
      for (int i = 0; i < itemCount; ++i) {
        if (key == finalIndex[i].first) {
720
721
722
723
724
          finalIndex[i].second.append(ext);
          found = true;
          break;
        }
      }
Dan Nixon's avatar
Dan Nixon committed
725
      if (!found) {
726
727
728
        finalIndex.append(qMakePair(key, QStringList(ext)));
      }
    }
729

Dan Nixon's avatar
Dan Nixon committed
730
731
    // The file filter consists of three parts, which we will combine to create
    // the
732
733
734
735
    // complete file filter:
    QString dataFiles("Data Files (");
    QString individualFiles("");

Dan Nixon's avatar
Dan Nixon committed
736
737
738
    if (extsAsSingleOption()) {
      QListIterator<QPair<QString, QStringList>> itr(finalIndex);
      while (itr.hasNext()) {
739
        const QStringList values = itr.next().second;
Dan Nixon's avatar
Dan Nixon committed
740

741
        individualFiles += "*" + values.join(" *") + ";;";
742
        dataFiles += "*" + values.join(" *") + " ";
743
      }
Dan Nixon's avatar
Dan Nixon committed
744
745
      // Don't remove final ;; from individualFiles as we are going to tack on
      // allFiles anyway
746
      dataFiles.chop(1); // Remove last space
747
      dataFiles += ");;";
Dan Nixon's avatar
Dan Nixon committed
748
749
750
    } else {
      QListIterator<QPair<QString, QStringList>> itr(finalIndex);
      while (itr.hasNext()) {
751
        const QStringList values = itr.next().second;
752
        dataFiles += "*" + values.join(" *") + ";;";
753
      }
754
    }
755
    return dataFiles + individualFiles + allFiles;
Dan Nixon's avatar
Dan Nixon committed
756
  } else {
757
    return allFiles;
758
  }
759
760
761
}

/**
LamarMoore's avatar
LamarMoore committed
762
763
764
765
766
 * Create a list of file extensions from the given algorithm
 * @param algName :: The name of the algorithm
 * @param propName :: The name of the property
 * @returns A list of file extensions
 */
Dan Nixon's avatar
Dan Nixon committed
767
768
769
770
771
772
QStringList
MWRunFiles::getFileExtensionsFromAlgorithm(const QString &algName,
                                           const QString &propName) {
  Mantid::API::IAlgorithm_sptr algorithm =
      Mantid::API::AlgorithmManager::Instance().createUnmanaged(
          algName.toStdString());
773
  QStringList fileExts;
Dan Nixon's avatar
Dan Nixon committed
774
775
  if (!algorithm)
    return fileExts;
776
777
  algorithm->initialize();
  Property *prop = algorithm->getProperty(propName.toStdString());
Dan Nixon's avatar
Dan Nixon committed
778
  FileProperty *fileProp = dynamic_cast<FileProperty *>(prop);
779
  auto *multiFileProp = dynamic_cast<MultipleFileProperty *>(prop);
780

781
  std::vector<std::string> allowed;
782
783
  QString preferredExt;

Dan Nixon's avatar
Dan Nixon committed
784
  if (fileProp) {
785
786
    allowed = fileProp->allowedValues();
    preferredExt = QString::fromStdString(fileProp->getDefaultExt());
Dan Nixon's avatar
Dan Nixon committed
787
  } else if (multiFileProp) {
788
789
    allowed = multiFileProp->allowedValues();
    preferredExt = QString::fromStdString(multiFileProp->getDefaultExt());
Dan Nixon's avatar
Dan Nixon committed
790
  } else {
791
792
793
    return fileExts;
  }

794
  std::vector<std::string>::const_iterator iend = allowed.end();
795
  int index(0);
Dan Nixon's avatar
Dan Nixon committed
796
797
798
  for (std::vector<std::string>::const_iterator it = allowed.begin();
       it != iend; ++it) {
    if (!it->empty()) {
799
800
      QString ext = QString::fromStdString(*it);
      fileExts.append(ext);
Dan Nixon's avatar
Dan Nixon committed
801
      if (ext == preferredExt) {
802
        fileExts.move(index, 0);
803
      }
804
      ++index;
805
806
    }
  }
807

808
809
810
811
  return fileExts;
}

/** Lauches a load file browser allowing a user to select multiple
LamarMoore's avatar
LamarMoore committed
812
813
814
 *  files
 *  @return the names of the selected files as a comma separated list
 */
Dan Nixon's avatar
Dan Nixon committed
815
QString MWRunFiles::openFileDialog() {
816
817
818
  QStringList filenames;
  QString dir = m_lastDir;

Dan Nixon's avatar
Dan Nixon committed
819
  if (m_fileFilter.isEmpty()) {
820
821
822
    m_fileFilter = createFileFilter();
  }

Dan Nixon's avatar
Dan Nixon committed
823
824
825
826
827
828
  if (m_isForDirectory) {
    QString file =
        QFileDialog::getExistingDirectory(this, "Select directory", dir);
    if (!file.isEmpty())
      filenames.append(file);
  } else if (m_allowMultipleFiles) {
Peterson, Peter's avatar
Peterson, Peter committed
829
830
831
    filenames = QFileDialog::getOpenFileNames(this, "Open file", dir,
                                              m_fileFilter, nullptr,
                                              QFileDialog::DontResolveSymlinks);
Dan Nixon's avatar
Dan Nixon committed
832
833
  } else {
    QString file =
Peterson, Peter's avatar
Peterson, Peter committed
834
835
        QFileDialog::getOpenFileName(this, "Open file", dir, m_fileFilter,
                                     nullptr, QFileDialog::DontResolveSymlinks);
Dan Nixon's avatar
Dan Nixon committed
836
837
    if (!file.isEmpty())
      filenames.append(file);
838
839
  }

Dan Nixon's avatar
Dan Nixon committed
840
  if (filenames.isEmpty()) {
841
842
843
844
845
846
    return "";
  }
  m_lastDir = QFileInfo(filenames.front()).absoluteDir().path();
  return filenames.join(", ");
}

Dan Nixon's avatar
Dan Nixon committed
847
/** flag a problem with the supplied entry number, an empty string means no
LamarMoore's avatar
LamarMoore committed
848
849
850
851
 * error.
 *  file errors take precedence of these errors
 *  @param message the message to display
 */
Dan Nixon's avatar
Dan Nixon committed
852
void MWRunFiles::setEntryNumProblem(const QString &message) {
853
  m_entryNumProblem = message;
854
  refreshValidator();
855
856
}

Dan Nixon's avatar
Dan Nixon committed
857
/** Checks the data m_fileProblem and m_entryNumProblem to see if the validator
LamarMoore's avatar
LamarMoore committed
858
859
860
 * label needs to be displayed. Validator always hidden if m_showValidator set
 * false.
 */
Dan Nixon's avatar
Dan Nixon committed
861
void MWRunFiles::refreshValidator() {
862
863
864
865
866
867
868
869
870
871
  if (m_showValidator) {
    if (!m_fileProblem.isEmpty()) {
      m_uiForm.valid->setToolTip(m_fileProblem);
      m_uiForm.valid->show();
    } else if (!m_entryNumProblem.isEmpty() && m_multiEntry) {
      m_uiForm.valid->setToolTip(m_entryNumProblem);
      m_uiForm.valid->show();
    } else {
      m_uiForm.valid->hide();
    }
Dan Nixon's avatar
Dan Nixon committed
872
  } else {
873
874
    m_uiForm.valid->hide();
  }
875
876
877
}

/** This slot opens a file browser
LamarMoore's avatar
LamarMoore committed
878
 */
Dan Nixon's avatar
Dan Nixon committed
879
void MWRunFiles::browseClicked() {
880
  QString uFile = openFileDialog();
Dan Nixon's avatar
Dan Nixon committed
881
882
  if (uFile.trimmed().isEmpty())
    return;
883

884
  m_uiForm.fileEditor->setText(uFile);
885
  m_uiForm.fileEditor->setModified(true);
886

887
888
  emit fileEditingFinished();
}
889

890
/** Currently just checks that entryNum contains an int > 0 and hence might be a
LamarMoore's avatar
LamarMoore committed
891
892
 *  valid entry number
 */
Dan Nixon's avatar
Dan Nixon committed
893
894
void MWRunFiles::checkEntry() {
  if (m_uiForm.entryNum->text().isEmpty()) {