1 /***************************************************************************
2  *                                                                         *
3  *   Copyright : (C) 2003 The University of Toronto                        *
4  *   email     : netterfield@astro.utoronto.ca                             *
5  *                                                                         *
6  *   This program is free software; you can redistribute it and/or modify  *
7  *   it under the terms of the GNU General Public License as published by  *
8  *   the Free Software Foundation; either version 2 of the License, or     *
9  *   (at your option) any later version.                                   *
10  *                                                                         *
11  ***************************************************************************/
12 
13 #include "asciisource.h"
14 #include "asciidatainterfaces.h"
15 
16 #include "curve.h"
17 #include "colorsequence.h"
18 #include "objectstore.h"
19 
20 #include "math_kst.h"
21 
22 #include "kst_atof.h"
23 #include "measuretime.h"
24 #include "debug.h"
25 
26 #include <QFile>
27 #include <QFileInfo>
28 #include <QMessageBox>
29 #include <QThread>
30 #include <QtConcurrentRun>
31 #include <QFutureSynchronizer>
32 #include <QLabel>
33 #include <QApplication>
34 #include <QVBoxLayout>
35 #include <QProgressBar>
36 
37 
38 #include <ctype.h>
39 #include <stdlib.h>
40 
41 
42 using namespace Kst;
43 
44 const int BIG_READ=100000;
45 
46 //-------------------------------------------------------------------------------------------
47 struct ms : QThread
48 {
sleepms49   static void sleep(int t) { QThread::msleep(t); }
50 };
51 
52 
53 //-------------------------------------------------------------------------------------------
54 static const QString asciiTypeString = "ASCII file";
55 
56 
57 //-------------------------------------------------------------------------------------------
asciiTypeKey()58 const QString AsciiSource::asciiTypeKey()
59 {
60   return asciiTypeString;
61 }
62 
63 
64 //-------------------------------------------------------------------------------------------
AsciiSource(Kst::ObjectStore * store,QSettings * cfg,const QString & filename,const QString & type,const QDomElement & e)65 AsciiSource::AsciiSource(Kst::ObjectStore *store, QSettings *cfg, const QString& filename, const QString& type, const QDomElement& e) :
66   Kst::DataSource(store, cfg, filename, type),
67   _reader(_config),
68   _fileBuffer(),
69   _busy(false),
70   _read_count_max(-1),
71   _read_count(0),
72   _showFieldProgress(false),
73   is(new DataInterfaceAsciiString(*this)),
74   iv(new DataInterfaceAsciiVector(*this)),
75   _updatesDisabled(true)
76 {
77   setInterface(is);
78   setInterface(iv);
79 
80   reset();
81 
82   _source = asciiTypeString;
83   if (!type.isEmpty() && type != asciiTypeString) {
84     return;
85   }
86 
87   _config.readGroup(*cfg, filename);
88   if (!e.isNull()) {
89     _config.load(e);
90   }
91 
92   // TODO only works for local files
93   setUpdateType((UpdateCheckType)_config._updateType.value());
94 
95   _valid = true;
96   registerChange();
97   internalDataSourceUpdate();
98   _progressTimer.restart();
99 }
100 
101 
102 //-------------------------------------------------------------------------------------------
~AsciiSource()103 AsciiSource::~AsciiSource()
104 {
105 }
106 
107 
108 //-------------------------------------------------------------------------------------------
reset()109 void AsciiSource::reset()
110 {
111   // forget about cached data
112   _fileBuffer.clear();
113   _reader.clear();
114   _haveWarned = false;
115 
116   //_valid = false;
117   _fileSize = 0;
118   _lastFileSize = 0;
119   _haveHeader = false;
120   _fieldListComplete = false;
121 
122   _fieldList.clear();
123   _fieldLookup.clear();
124   _scalarList.clear();
125   _strings.clear();
126 
127   Object::reset();
128 
129   _strings = fileMetas();
130 
131   prepareRead(0);
132 }
133 
134 //-------------------------------------------------------------------------------------------
initRowIndex()135 bool AsciiSource::initRowIndex()
136 {
137   _reader.clear();
138   _fileSize = 0;
139 
140   if (_config._dataLine > 0) {
141     QFile file(_filename);
142     if (!AsciiFileBuffer::openFile(file)) {
143       return false;
144     }
145     qint64 header_row = 0;
146     qint64 left = _config._dataLine;
147     while (left > 0) {
148       QByteArray line = file.readLine();
149       if (line.isEmpty() || file.atEnd()) {
150         return false;
151       }
152       --left;
153       if (header_row != _config._fieldsLine && header_row != _config._unitsLine) {
154         _strings[QString("Header %1").arg(header_row, 2, 10, QChar('0'))] = QString::fromAscii(line).trimmed();
155       }
156       header_row++;
157     }
158     _reader.setRow0Begin(file.pos());
159   }
160 
161   return true;
162 }
163 
164 //-------------------------------------------------------------------------------------------
updateLists()165 void AsciiSource::updateLists() {
166   _fieldList = fieldListFor(_filename, _config);
167   QStringList units;
168   if (_config._readUnits) {
169     units += unitListFor(_filename, _config);
170     for (int index = 0; index < _fieldList.size(); ++index) {
171       if (index >= units.size()) {
172         break; // Missing units => the user's fault, but at least don't crash
173       }
174       _fieldUnits[_fieldList[index]] = units[index];
175     }
176   }
177   _fieldListComplete = _fieldList.count() > 1;
178 
179   _fieldLookup.clear();
180   for (int i = 0; i < _fieldList.size(); i++)
181       _fieldLookup[_fieldList[i]] = i;
182 
183   // Re-update the scalar list
184   _scalarList = scalarListFor(_filename, _config);
185 
186 }
187 
188 //-------------------------------------------------------------------------------------------
internalDataSourceUpdate()189 Kst::Object::UpdateType AsciiSource::internalDataSourceUpdate()
190 {
191   return internalDataSourceUpdate(true);
192 }
193 
194 
195 //-------------------------------------------------------------------------------------------
internalDataSourceUpdate(bool read_completely)196 Kst::Object::UpdateType AsciiSource::internalDataSourceUpdate(bool read_completely)
197 {
198   //MeasureTime t("AsciiSource::internalDataSourceUpdate: " + _filename);
199   if (_busy)
200     return NoChange;
201 
202   // forget about cached data
203   _fileBuffer.clear();
204 
205   if (!_haveHeader) {
206     _haveHeader = initRowIndex();
207     if (!_haveHeader) {
208       return NoChange;
209     }
210   }
211   updateLists();
212 
213   QFile file(_filename);
214   if (!AsciiFileBuffer::openFile(file)) {
215     // Qt: If the device is closed, the size returned will not reflect the actual size of the device.
216     return NoChange;
217   }
218 
219   if (_updatesDisabled) {
220     _fileSize = 0;
221   } else {
222     _fileSize = file.size();
223   }
224 
225   bool force_update = true;
226   if (_fileSize == file.size()) {
227     force_update = false;
228   }
229 
230   _fileCreationTime_t = QFileInfo(file).created().toTime_t();
231 
232   int col_count = _fieldList.size() - 1; // minus INDEX
233 
234   bool new_data = false;
235   // emit progress message if there are more than 100 MB to parse
236   if (_fileSize - _lastFileSize > 100 * 1024 * 1024 && read_completely) {
237     _showFieldProgress = true;
238     emitProgress(1, tr("Parsing '%1' ...").arg(_filename));
239     QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
240     QFuture<bool> future = QtConcurrent::run(&_reader, &AsciiDataReader::findAllDataRows, read_completely, &file, _fileSize, col_count);
241     _busy = true;
242     while (_busy) {
243       if (future.isFinished()) {
244         try {
245           new_data = future;
246         } catch ( const std::exception&) {
247           // TODO out of memory?
248         }
249         _busy = false;
250         emitProgress(50, tr("Finished parsing '%1'").arg(_filename));
251       } else {
252         ms::sleep(500);
253         emitProgress(1 + 99.0 * _reader.progressValue() / 100.0, tr("Parsing '%1': %2 rows").arg(_filename).arg(QString::number(_reader.progressRows())));
254         QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
255       }
256     }
257   } else {
258     _showFieldProgress = false;
259     new_data = _reader.findAllDataRows(read_completely, &file, _fileSize, col_count);
260   }
261 
262   _lastFileSize = _fileSize;
263 
264   return (!new_data && !force_update ? NoChange : Updated);
265 }
266 
267 
268 //-------------------------------------------------------------------------------------------
columnOfField(const QString & field) const269 int AsciiSource::columnOfField(const QString& field) const
270 {
271   if (_fieldLookup.contains(field)) {
272     return _fieldLookup[field];
273   }
274 
275   if (_fieldListComplete) {
276     return -1;
277   }
278 
279   bool ok = false;
280   int col = field.toInt(&ok);
281   if (ok) {
282     return col;
283   }
284 
285   return -1;
286 }
287 
288 
289 //-------------------------------------------------------------------------------------------
useSlidingWindow(qint64 bytesToRead) const290 bool AsciiSource::useSlidingWindow(qint64 bytesToRead)  const
291 {
292   return _config._limitFileBuffer && _config._limitFileBufferSize < bytesToRead;
293 }
294 
295 
296 //-------------------------------------------------------------------------------------------
readField(double * v,const QString & field,int s,int n)297 int AsciiSource::readField(double *v, const QString& field, int s, int n)
298 {
299   _actualField = field;
300   if (n>BIG_READ) {
301     updateFieldMessage(tr("Reading field: "));
302   }
303 
304   // TODO multi threading problem: could trigger a dead-lock
305   // FIXME: Debug::trace() here is a memory leak, which is serious for large quickly updating files.
306   //Debug::trace(QString("AsciiSource::readField() %1  s=%2  n=%3").arg(field.leftJustified(15)).arg(QString("%1").arg(s, 10)).arg(n));
307 
308   int read = tryReadField(v, field, s, n);
309 
310   if (isTime(field)) {
311     if (_config._indexInterpretation == AsciiSourceConfig::FixedRate ) {
312       double rate = _config._dataRate.value();
313       if (rate>0) {
314         rate = 1.0/rate;
315       } else {
316         rate = 1.0;
317       }
318 
319       for (int i=0; i<read; i++) {
320         v[i] *= rate;
321       }
322     }
323 
324     double dT = 0.0;
325     if (_config._offsetDateTime.value()) {
326       dT = (double)_config._dateTimeOffset.value().toTime_t();
327     } else if (_config._offsetRelative.value()) {
328       dT = _config._relativeOffset.value();
329     } else if (_config._offsetFileDate.value()) {
330       dT = _fileCreationTime_t;
331     }
332 
333     for (int i=0; i<read; i++) {
334       v[i] += dT;
335     }
336 
337   }
338 
339   QString msg("%1.\nTry without threads or use a different file buffer limit when using threads for reading.");
340   if (read == abs(n)) { // when reading only 1 sample, n could be -1 instead of 1 to signal 1 sample, not 1 frame.
341     return read;
342   } else if (read > 0) {
343     if (!_haveWarned)
344       QMessageBox::warning(0, "Error while reading ASCII file", msg.arg("The file was read only partially"));
345     _haveWarned = true;
346     return read;
347   } else if (read == 0) {
348     if (!_haveWarned) {
349       // TODO Why is nothing read? Only log once because of the danger of a dead-lock
350       Debug::warning("AsciiSource: 0 bytes read from file");
351     }
352     _haveWarned = true;
353   } else if (read == -3) {
354     if (!_haveWarned)
355       QMessageBox::warning(0, "Error while reading ASCII file", "The file could not be opened for reading");
356     _haveWarned = true;
357   }
358 
359   emitProgress(100, QString());
360   return 0;
361 }
362 
363 
364 //-------------------------------------------------------------------------------------------
useThreads() const365 bool AsciiSource::useThreads() const
366 {
367   // only use threads for files > 1 MB
368   return _config._useThreads && _fileSize > 1 * 1024 * 1024;
369 }
370 
371 //-------------------------------------------------------------------------------------------
prepareRead(int count)372 void AsciiSource::prepareRead(int count)
373 {
374   _read_count_max = count;
375   _read_count = 0;
376   _progress = 0;
377   _progressSteps = 0;
378 }
379 
380 //-------------------------------------------------------------------------------------------
readingDone()381 void AsciiSource::readingDone()
382 {
383   // clear
384   emit progress(100, "");
385 }
386 
387 //-------------------------------------------------------------------------------------------
tryReadField(double * v,const QString & field,int s,int n)388 int AsciiSource::tryReadField(double *v, const QString& field, int s, int n)
389 {
390   if (n < 0) {
391     n = 1; /* n < 0 means read one sample, not frame - irrelevent here */
392   }
393 
394   if (field == "INDEX") {
395     for (int i = 0; i < n; i++) {
396       v[i] = double(s + i);
397     }
398     if (n>BIG_READ) {
399       updateFieldMessage(tr("INDEX created"));
400     }
401     return n;
402   }
403 
404   int col = columnOfField(field);
405   if (col == -1) {
406     _read_count_max = -1;
407     return -2;
408   }
409 
410   // check if the already in buffer
411   const qint64 begin = _reader.beginOfRow(s);
412   const qint64 bytesToRead = _reader.beginOfRow(s + n) - begin;
413   if ((begin != _fileBuffer.begin()) || (bytesToRead != _fileBuffer.bytesRead())) {
414     QFile* file = new QFile(_filename);
415     if (!AsciiFileBuffer::openFile(*file)) {
416       delete file;
417       _read_count_max = -1;
418       return -3;
419     }
420 
421     // prepare file buffer
422 
423     _fileBuffer.setFile(file);
424 
425     int numThreads;
426     if (!useThreads()) {
427       numThreads = 1;
428     } else {
429       numThreads = QThread::idealThreadCount();
430       numThreads = (numThreads > 0) ? numThreads : 1;
431     }
432 
433     if (useSlidingWindow(bytesToRead)) {
434       if (useThreads()) {
435         _fileBuffer.useSlidingWindowWithChunks(_reader.rowIndex(), begin, bytesToRead, _config._limitFileBufferSize, numThreads);
436       } else {
437         _fileBuffer.useSlidingWindow(_reader.rowIndex(), begin, bytesToRead, _config._limitFileBufferSize);
438       }
439     } else {
440       _fileBuffer.useOneWindowWithChunks(_reader.rowIndex(), begin, bytesToRead, numThreads);
441     }
442 
443     if (_fileBuffer.bytesRead() == 0) {
444       _fileBuffer.clear();
445       _read_count_max = -1;
446       return 0;
447     }
448 
449     _reader.detectLineEndingType(*file);
450   }
451 
452   // now start reading
453   LexicalCast::NaNMode nanMode;
454   switch (_config._nanValue.value()) {
455   case 0: nanMode = LexicalCast::NullValue; break;
456   case 1: nanMode = LexicalCast::NaNValue; break;
457 #ifndef KST_NO_THREAD_LOCAL
458   case 2: nanMode = LexicalCast::PreviousValue; break;
459 #endif
460   default:nanMode = LexicalCast::NullValue; break;
461   }
462   LexicalCast::AutoReset useDot(_config._useDot, nanMode);
463 
464 
465   if (field == _config._indexVector && _config._indexInterpretation == AsciiSourceConfig::FormattedTime) {
466     LexicalCast::instance().setTimeFormat(_config._timeAsciiFormatString);
467   }
468 
469   QVector<QVector<AsciiFileData> >& slidingWindow = _fileBuffer.fileData();
470   int sampleRead = 0;
471 
472   _progressSteps = 0;
473   for (int i = 0; i < slidingWindow.size(); i++) {
474       _progressSteps += slidingWindow[i].size() * 2;
475   }
476   if (_read_count_max == -1) {
477     _progress = 0;
478   } else {
479     _progressSteps *= _read_count_max;
480   }
481 
482   for (int i = 0; i < slidingWindow.size(); i++) {
483 
484     int read;
485     if (useThreads())
486       read = parseWindowMultithreaded(slidingWindow[i], col, v, s, field);
487     else
488       read = parseWindowSinglethreaded(slidingWindow[i], col, v, s, field, sampleRead);
489 
490     // something went wrong abort reading
491     if (read == 0) {
492       break;
493     }
494 
495     sampleRead += read;
496   }
497 
498   if (useSlidingWindow(bytesToRead)) {
499     // only buffering the complete file makes sense
500     _fileBuffer.clear();
501   }
502 
503   if (n>BIG_READ) {
504     updateFieldMessage(tr("Finished reading: "));
505   }
506 
507   _read_count++;
508   if (_read_count_max == _read_count)
509     _read_count_max = -1;
510 
511   return sampleRead;
512 }
513 
514 
515 //-------------------------------------------------------------------------------------------
parseWindowSinglethreaded(QVector<AsciiFileData> & window,int col,double * v,int start,const QString & field,int sRead)516 int AsciiSource::parseWindowSinglethreaded(QVector<AsciiFileData>& window, int col, double* v, int start, const QString& field, int sRead)
517 {
518   int read = 0;
519   for (int i = 0; i < window.size(); i++) {
520     Q_ASSERT(sRead + start ==  window[i].rowBegin());
521     if (!window[i].read() || window[i].bytesRead() == 0)
522       return 0;
523     _progress++;
524     read += _reader.readFieldFromChunk(window[i], col, v, start, field);
525     _progress += window.size();
526   }
527   return read;
528 }
529 
530 
531 //-------------------------------------------------------------------------------------------
parseWindowMultithreaded(QVector<AsciiFileData> & window,int col,double * v,int start,const QString & field)532 int AsciiSource::parseWindowMultithreaded(QVector<AsciiFileData>& window, int col, double* v, int start, const QString& field)
533 {
534   updateFieldProgress(tr("reading ..."));
535   for (int i = 0; i < window.size(); i++) {
536     if (!window[i].read()) {
537       return 0;
538     }
539     _progress++;
540     updateFieldProgress(tr("reading ..."));
541   }
542 
543   updateFieldProgress(tr("parsing ..."));
544   QFutureSynchronizer<int> readFutures;
545   foreach (const AsciiFileData& chunk, window) {
546     QFuture<int> future = QtConcurrent::run(&_reader, &AsciiDataReader::readFieldFromChunk, chunk, col, v, start, field);
547     readFutures.addFuture(future);
548   }
549   readFutures.waitForFinished();
550   _progress += window.size();
551   updateFieldProgress(tr("parsing ..."));
552   int sampleRead = 0;
553   foreach (const QFuture<int> future, readFutures.futures()) {
554     sampleRead += future.result();
555   }
556   return sampleRead;
557 }
558 
559 //-------------------------------------------------------------------------------------------
emitProgress(int percent,const QString & message)560 void AsciiSource::emitProgress(int percent, const QString& message)
561 {
562   if (_progressTimer.elapsed() < 500) {
563     // don't flood the gui with progress messages
564     return;
565   }
566   emit progress(percent, message);
567   _progressTimer.restart();
568   QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
569 }
570 
571 //-------------------------------------------------------------------------------------------
updateFieldMessage(const QString & message)572 void AsciiSource::updateFieldMessage(const QString& message)
573 {
574    // hide progress bar
575    emitProgress(100, message + _actualField);
576 }
577 
578 //-------------------------------------------------------------------------------------------
updateFieldProgress(const QString & message)579 void AsciiSource::updateFieldProgress(const QString& message)
580 {
581   if (_read_count_max == 0) {
582     //emitProgress(-1, ""); // indicate "busy"  FIXME: commented out because it doesn't go away.
583   } else {
584     if (_progressSteps != 0 && _read_count_max != -1) {
585       emitProgress(50 + 50 * _progress / _progressSteps, _actualField + ": " + message);
586     }
587   }
588 }
589 
590 //-------------------------------------------------------------------------------------------
fileType() const591 QString AsciiSource::fileType() const
592 {
593   return asciiTypeString;
594 }
595 
596 //-------------------------------------------------------------------------------------------
setUpdateType(UpdateCheckType updateType)597 void AsciiSource::setUpdateType(UpdateCheckType updateType)
598 {
599     if (_config._updateType != updateType) {
600         //Q_ASSERT(AsciiSourceConfig().readGroup(*_cfg, _filename) == _config);
601         _config._updateType = updateType;
602         _config.saveGroup(*_cfg, _filename);
603     }
604     DataSource::setUpdateType(updateType);
605 }
606 
607 
608 //-------------------------------------------------------------------------------------------
isEmpty() const609 bool AsciiSource::isEmpty() const
610 {
611   return _reader.numberOfFrames() < 1;
612 }
613 
614 
615 //-------------------------------------------------------------------------------------------
scalarListFor(const QString & filename,AsciiSourceConfig)616 QStringList AsciiSource::scalarListFor(const QString& filename, AsciiSourceConfig)
617 {
618   QFile file(filename);
619   if (!AsciiFileBuffer::openFile(file)) {
620     return QStringList();
621   }
622   return QStringList() << "FRAMES";
623 }
624 
625 
626 //-------------------------------------------------------------------------------------------
stringListFor(const QString & filename,AsciiSourceConfig)627 QStringList AsciiSource::stringListFor(const QString& filename, AsciiSourceConfig)
628 {
629   QFile file(filename);
630   if (!AsciiFileBuffer::openFile(file)) {
631     return QStringList();
632   }
633   return QStringList() << "FILE";
634 }
635 
636 
637 //-------------------------------------------------------------------------------------------
splitHeaderLine(const QByteArray & line,const AsciiSourceConfig & cfg,QStringList * stringList)638 int AsciiSource::splitHeaderLine(const QByteArray& line, const AsciiSourceConfig& cfg, QStringList* stringList)
639 {
640   QStringList dummy;
641   QStringList& parts(stringList ? *stringList : dummy);
642   parts.clear();
643   const QRegExp regexColumnDelimiter(QString("[%1]").arg(QRegExp::escape(cfg._columnDelimiter.value())));
644 
645   if (cfg._columnType == AsciiSourceConfig::Custom && !cfg._columnDelimiter.value().isEmpty()) {
646     parts += QString(line).trimmed().split(regexColumnDelimiter, QString::SkipEmptyParts);
647   } else if (cfg._columnType == AsciiSourceConfig::Fixed) {
648     int cnt = line.length() / cfg._columnWidth;
649     for (int i = 0; i < cnt; ++i) {
650       QString sub = line.mid(i * cfg._columnWidth).left(cfg._columnWidth);
651       parts += sub.trimmed();
652     }
653   } else {
654     if (!stringList) {
655       //MeasureTime t("AsciiDataReader::countColumns()");
656       int columns = AsciiDataReader::splitColumns(line, AsciiCharacterTraits::IsWhiteSpace());
657 
658       // The following assert crashes (sometimes?) when kst is pointed at an
659       // executable.  So... rather than crashing, lets just bail.
660       if (columns != QString(line).trimmed().split(QRegExp("\\s"), QString::SkipEmptyParts).size()) {
661         return 0;
662       }
663       Q_ASSERT(columns == QString(line).trimmed().split(QRegExp("\\s"), QString::SkipEmptyParts).size());
664       return columns;
665     } else {
666       //MeasureTime t("AsciiDataReader::countColumns(parts)");
667       AsciiDataReader::splitColumns(line, AsciiCharacterTraits::IsWhiteSpace(), &parts);
668       Q_ASSERT(parts == QString(line).trimmed().split(QRegExp("\\s"), QString::SkipEmptyParts));
669     }
670   }
671   return parts.count();
672 }
673 
674 
675 //-------------------------------------------------------------------------------------------
fieldListFor(const QString & filename,AsciiSourceConfig cfg)676 QStringList AsciiSource::fieldListFor(const QString& filename, AsciiSourceConfig cfg)
677 {
678   QFile file(filename);
679   if (!AsciiFileBuffer::openFile(file)) {
680     return QStringList();
681   }
682 
683   QStringList fields;
684   fields += "INDEX";
685 
686   if (cfg._readFields) {
687     int fieldsLine = cfg._fieldsLine;
688     int currentLine = 0; // Explicit line counter, to make the code easier to understand
689     while (currentLine < cfg._dataLine) {
690       const QByteArray line = file.readLine();
691       int r = line.size();
692       if (currentLine == fieldsLine && r >= 0) {
693         QStringList parts;
694         AsciiSource::splitHeaderLine(line, cfg, &parts);
695         fields += parts;
696         break;
697       }
698       currentLine++;
699     }
700     QStringList trimmed;
701     foreach(const QString& str, fields) {
702       trimmed << str.trimmed();
703     }
704     return trimmed;
705   }
706 
707 
708   QRegExp regex;
709   if (cfg._columnType == AsciiSourceConfig::Custom && !cfg._columnDelimiter.value().isEmpty()) {
710     regex.setPattern(QString("^[%1]*[%2].*").arg(QRegExp::escape(cfg._columnDelimiter.value())).arg(cfg._delimiters));
711   } else {
712     regex.setPattern(QString("^\\s*[%1].*").arg(cfg._delimiters));
713   }
714 
715   bool done = false;
716   int skip = cfg._dataLine;
717   //FIXME This is a hack which should eventually be fixed by specifying
718   // the starting frame of the data when calling KstDataSource::fieldListForSource
719   // and KstDataSource::fieldList.  If the skip value is not specified, then
720   // we scan a few lines and take the maximum number of fields that we find.
721   int maxcnt;
722   if (skip > 0) {
723     maxcnt = -1;
724   } else {
725     maxcnt = 0;
726   }
727   int cnt;
728   int nextscan = 0;
729   int curscan = 0;
730   while (!file.atEnd() && !done && (nextscan < 200)) {
731     QByteArray line = file.readLine();
732     int r = line.size();
733     if (skip > 0) { //keep skipping until desired line
734       --skip;
735       if (r < 0) {
736         return fields;
737       }
738       continue;
739     }
740     if (maxcnt >= 0) { //original skip value == 0, so scan some lines
741       if (curscan >= nextscan) {
742         if (r > 1 && !regex.exactMatch(line)) {
743           cnt = splitHeaderLine(line, cfg);
744           if (cnt > maxcnt) {
745             maxcnt = cnt;
746           }
747         } else if (r < 0) {
748           return fields;
749         }
750         nextscan += nextscan + 1;
751       }
752       curscan++;
753       continue;
754     }
755     if (r > 1 && !regex.exactMatch(line)) { //at desired line, find count
756       maxcnt = splitHeaderLine(line, cfg);
757       done = true;
758     } else if (r < 0) {
759       return fields;
760     }
761   }
762 
763   for (int i = 1; i <= maxcnt; ++i) {
764     fields += tr("Column %1").arg(i).trimmed();
765   }
766 
767   return fields;
768 }
769 
770 
771 //-------------------------------------------------------------------------------------------
unitListFor(const QString & filename,AsciiSourceConfig cfg)772 QStringList AsciiSource::unitListFor(const QString& filename, AsciiSourceConfig cfg)
773 {
774   QFile file(filename);
775   if (!AsciiFileBuffer::openFile(file)) {
776     return QStringList();
777   }
778 
779   QStringList units;
780   units += ""; // To go with INDEX
781 
782   int unitsLine = cfg._unitsLine;
783   int currentLine = 0;
784   while (currentLine < cfg._dataLine) {
785     const QByteArray line = file.readLine();
786     int r = line.size();
787     if (currentLine == unitsLine && r >= 0) {
788       QStringList parts;
789       AsciiSource::splitHeaderLine(line, cfg, &parts);
790       units += parts;
791       break;
792     }
793     currentLine++;
794   }
795   QStringList trimmed;
796   foreach(const QString& str, units) {
797     trimmed << str.trimmed();
798   }
799   return trimmed;
800 }
801 
802 
803 //-------------------------------------------------------------------------------------------
save(QXmlStreamWriter & s)804 void AsciiSource::save(QXmlStreamWriter &s)
805 {
806   Kst::DataSource::save(s);
807   _config.save(s);
808 }
809 
810 
811 //-------------------------------------------------------------------------------------------
parseProperties(QXmlStreamAttributes & properties)812 void AsciiSource::parseProperties(QXmlStreamAttributes &properties)
813 {
814   _config.parseProperties(properties);
815   reset();
816   internalDataSourceUpdate();
817 }
818 
819 
820 //-------------------------------------------------------------------------------------------
supportsTimeConversions() const821 bool AsciiSource::supportsTimeConversions() const
822 {
823   return false; //fieldList().contains(_config._indexVector) && _config._indexInterpretation != AsciiSourceConfig::Unknown && _config._indexInterpretation != AsciiSourceConfig::INDEX;
824 }
825 
826 
827 //-------------------------------------------------------------------------------------------
sampleForTime(double ms,bool * ok)828 int AsciiSource::sampleForTime(double ms, bool *ok)
829 {
830   switch (_config._indexInterpretation) {
831   case AsciiSourceConfig::Seconds:
832     // FIXME: make sure "seconds" exists in _indexVector
833     if (ok) {
834       *ok = true;
835     }
836     return 0;
837   case AsciiSourceConfig::CTime:
838     // FIXME: make sure "seconds" exists in _indexVector (different than above?)
839     if (ok) {
840       *ok = true;
841     }
842     return 0;
843   default:
844     return Kst::DataSource::sampleForTime(ms, ok);
845   }
846 }
847 
848 
849 //-------------------------------------------------------------------------------------------
typeString() const850 const QString& AsciiSource::typeString() const
851 {
852   return asciiTypeString;
853 }
854 
855 
856 //-------------------------------------------------------------------------------------------
sampleForTime(const QDateTime & time,bool * ok)857 int AsciiSource::sampleForTime(const QDateTime& time, bool *ok)
858 {
859   switch (_config._indexInterpretation) {
860   case AsciiSourceConfig::Seconds:
861     // FIXME: make sure "time" exists in _indexVector
862     if (ok) {
863       *ok = true;
864     }
865     return time.toTime_t();
866   case AsciiSourceConfig::CTime:
867     // FIXME: make sure "time" exists in _indexVector (different than above?)
868     if (ok) {
869       *ok = true;
870     }
871     return time.toTime_t();
872   default:
873     return Kst::DataSource::sampleForTime(time, ok);
874   }
875 }
876 
877 //-------------------------------------------------------------------------------------------
isTime(const QString & field) const878 bool AsciiSource::isTime(const QString &field) const
879 {
880   return (_config._indexInterpretation.value() != AsciiSourceConfig::NoInterpretation) &&
881       (_config._indexInterpretation.value() != AsciiSourceConfig::Unknown) &&
882       (field == _config._indexVector);
883 }
884 
885 //-------------------------------------------------------------------------------------------
timeFormat() const886 QString AsciiSource::timeFormat() const
887 {
888   if (_config._indexInterpretation.value() != AsciiSourceConfig::FormattedTime) {
889     return QString("");
890   }
891   else {
892     return _config._timeAsciiFormatString;
893   }
894 }
895 
896 //-------------------------------------------------------------------------------------------
autoCurves(ObjectStore & objectStore)897 Kst::ObjectList<Kst::Object> AsciiSource::autoCurves(ObjectStore& objectStore)
898 {
899   // here we could do more sophisticated stuff when generating a list of curves
900   return ObjectList<Kst::Object>();
901 }
902 
903 
904 
905 // vim: ts=2 sw=2 et
906