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