annotate data/model/EditableDenseThreeDimensionalModel.cpp @ 1211:5a1198083d9a piper

Pull out model creation into the transformer thread run(), so that all communications with the plugin server happen on a single thread. Then make the model accessor wait for them to be created (which still happens right at the start of processing) before returning.
author Chris Cannam
date Mon, 17 Oct 2016 14:18:23 +0100
parents aa588c391d1a
children 2ff5e411151d
rev   line source
Chris@152 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@152 2
Chris@152 3 /*
Chris@152 4 Sonic Visualiser
Chris@152 5 An audio file viewer and annotation editor.
Chris@152 6 Centre for Digital Music, Queen Mary, University of London.
Chris@202 7 This file copyright 2006 Chris Cannam and QMUL.
Chris@152 8
Chris@152 9 This program is free software; you can redistribute it and/or
Chris@152 10 modify it under the terms of the GNU General Public License as
Chris@152 11 published by the Free Software Foundation; either version 2 of the
Chris@152 12 License, or (at your option) any later version. See the file
Chris@152 13 COPYING included with this distribution for more information.
Chris@152 14 */
Chris@152 15
Chris@152 16 #include "EditableDenseThreeDimensionalModel.h"
Chris@152 17
Chris@478 18 #include "base/LogRange.h"
Chris@478 19
Chris@152 20 #include <QTextStream>
Chris@387 21 #include <QStringList>
Chris@536 22 #include <QReadLocker>
Chris@536 23 #include <QWriteLocker>
Chris@387 24
Chris@181 25 #include <iostream>
Chris@181 26
Chris@256 27 #include <cmath>
Chris@534 28 #include <cassert>
Chris@256 29
Chris@1044 30 using std::vector;
Chris@1044 31
Chris@607 32 #include "system/System.h"
Chris@607 33
Chris@1040 34 EditableDenseThreeDimensionalModel::EditableDenseThreeDimensionalModel(sv_samplerate_t sampleRate,
Chris@929 35 int resolution,
Chris@929 36 int yBinCount,
Chris@535 37 CompressionType compression,
Chris@152 38 bool notifyOnAdd) :
Chris@611 39 m_startFrame(0),
Chris@152 40 m_sampleRate(sampleRate),
Chris@152 41 m_resolution(resolution),
Chris@152 42 m_yBinCount(yBinCount),
Chris@535 43 m_compression(compression),
Chris@152 44 m_minimum(0.0),
Chris@152 45 m_maximum(0.0),
Chris@256 46 m_haveExtents(false),
Chris@152 47 m_notifyOnAdd(notifyOnAdd),
Chris@152 48 m_sinceLastNotifyMin(-1),
Chris@152 49 m_sinceLastNotifyMax(-1),
Chris@152 50 m_completion(100)
Chris@152 51 {
Chris@152 52 }
Chris@152 53
Chris@152 54 bool
Chris@152 55 EditableDenseThreeDimensionalModel::isOK() const
Chris@152 56 {
Chris@152 57 return true;
Chris@152 58 }
Chris@152 59
Chris@1040 60 sv_samplerate_t
Chris@152 61 EditableDenseThreeDimensionalModel::getSampleRate() const
Chris@152 62 {
Chris@152 63 return m_sampleRate;
Chris@152 64 }
Chris@152 65
Chris@1038 66 sv_frame_t
Chris@152 67 EditableDenseThreeDimensionalModel::getStartFrame() const
Chris@152 68 {
Chris@611 69 return m_startFrame;
Chris@611 70 }
Chris@611 71
Chris@611 72 void
Chris@1038 73 EditableDenseThreeDimensionalModel::setStartFrame(sv_frame_t f)
Chris@611 74 {
Chris@611 75 m_startFrame = f;
Chris@152 76 }
Chris@152 77
Chris@1038 78 sv_frame_t
Chris@152 79 EditableDenseThreeDimensionalModel::getEndFrame() const
Chris@152 80 {
Chris@152 81 return m_resolution * m_data.size() + (m_resolution - 1);
Chris@152 82 }
Chris@152 83
Chris@929 84 int
Chris@152 85 EditableDenseThreeDimensionalModel::getResolution() const
Chris@152 86 {
Chris@152 87 return m_resolution;
Chris@152 88 }
Chris@152 89
Chris@152 90 void
Chris@929 91 EditableDenseThreeDimensionalModel::setResolution(int sz)
Chris@152 92 {
Chris@152 93 m_resolution = sz;
Chris@152 94 }
Chris@152 95
Chris@929 96 int
Chris@182 97 EditableDenseThreeDimensionalModel::getWidth() const
Chris@182 98 {
Chris@1154 99 return int(m_data.size());
Chris@182 100 }
Chris@182 101
Chris@929 102 int
Chris@182 103 EditableDenseThreeDimensionalModel::getHeight() const
Chris@152 104 {
Chris@152 105 return m_yBinCount;
Chris@152 106 }
Chris@152 107
Chris@152 108 void
Chris@929 109 EditableDenseThreeDimensionalModel::setHeight(int sz)
Chris@152 110 {
Chris@152 111 m_yBinCount = sz;
Chris@152 112 }
Chris@152 113
Chris@152 114 float
Chris@152 115 EditableDenseThreeDimensionalModel::getMinimumLevel() const
Chris@152 116 {
Chris@152 117 return m_minimum;
Chris@152 118 }
Chris@152 119
Chris@152 120 void
Chris@152 121 EditableDenseThreeDimensionalModel::setMinimumLevel(float level)
Chris@152 122 {
Chris@152 123 m_minimum = level;
Chris@152 124 }
Chris@152 125
Chris@152 126 float
Chris@152 127 EditableDenseThreeDimensionalModel::getMaximumLevel() const
Chris@152 128 {
Chris@152 129 return m_maximum;
Chris@152 130 }
Chris@152 131
Chris@152 132 void
Chris@152 133 EditableDenseThreeDimensionalModel::setMaximumLevel(float level)
Chris@152 134 {
Chris@152 135 m_maximum = level;
Chris@152 136 }
Chris@152 137
Chris@533 138 EditableDenseThreeDimensionalModel::Column
Chris@929 139 EditableDenseThreeDimensionalModel::getColumn(int index) const
Chris@152 140 {
Chris@536 141 QReadLocker locker(&m_lock);
Chris@1154 142 if (in_range_for(m_data, index)) return expandAndRetrieve(index);
Chris@1154 143 else return Column();
Chris@152 144 }
Chris@152 145
Chris@152 146 float
Chris@929 147 EditableDenseThreeDimensionalModel::getValueAt(int index, int n) const
Chris@152 148 {
Chris@534 149 Column c = getColumn(index);
Chris@1154 150 if (in_range_for(c, n)) return c.at(n);
Chris@534 151 return m_minimum;
Chris@534 152 }
Chris@152 153
Chris@535 154 //static int given = 0, stored = 0;
Chris@534 155
Chris@534 156 void
Chris@929 157 EditableDenseThreeDimensionalModel::truncateAndStore(int index,
Chris@534 158 const Column &values)
Chris@534 159 {
Chris@1154 160 assert(in_range_for(m_data, index));
Chris@534 161
Chris@843 162 //cout << "truncateAndStore(" << index << ", " << values.size() << ")" << endl;
Chris@534 163
Chris@535 164 // The default case is to store the entire column at m_data[index]
Chris@535 165 // and place 0 at m_trunc[index] to indicate that it has not been
Chris@535 166 // truncated. We only do clever stuff if one of the clever-stuff
Chris@535 167 // tests works out.
Chris@535 168
Chris@534 169 m_trunc[index] = 0;
Chris@535 170 if (index == 0 ||
Chris@535 171 m_compression == NoCompression ||
Chris@1154 172 int(values.size()) != m_yBinCount) {
Chris@535 173 // given += values.size();
Chris@535 174 // stored += values.size();
Chris@534 175 m_data[index] = values;
Chris@534 176 return;
Chris@152 177 }
Chris@152 178
Chris@535 179 // Maximum distance between a column and the one we refer to as
Chris@535 180 // the source of its truncated values. Limited by having to fit
Chris@535 181 // in a signed char, but in any case small values are usually
Chris@535 182 // better
Chris@535 183 static int maxdist = 6;
Chris@534 184
Chris@535 185 bool known = false; // do we know whether to truncate at top or bottom?
Chris@535 186 bool top = false; // if we do know, will we truncate at top?
Chris@534 187
Chris@535 188 // If the previous column is not truncated, then it is the only
Chris@535 189 // candidate for comparison. If it is truncated, then the column
Chris@535 190 // that it refers to is the only candidate. Either way, we only
Chris@535 191 // have one possible column to compare against here, and we are
Chris@535 192 // being careful to ensure it is not a truncated one (to avoid
Chris@535 193 // doing more work recursively when uncompressing).
Chris@534 194 int tdist = 1;
Chris@534 195 int ptrunc = m_trunc[index-1];
Chris@534 196 if (ptrunc < 0) {
Chris@534 197 top = false;
Chris@534 198 known = true;
Chris@534 199 tdist = -ptrunc + 1;
Chris@534 200 } else if (ptrunc > 0) {
Chris@534 201 top = true;
Chris@534 202 known = true;
Chris@534 203 tdist = ptrunc + 1;
Chris@534 204 }
Chris@534 205
Chris@534 206 Column p = expandAndRetrieve(index - tdist);
Chris@534 207 int h = m_yBinCount;
Chris@534 208
Chris@1154 209 if (int(p.size()) == h && tdist <= maxdist) {
Chris@534 210
Chris@534 211 int bcount = 0, tcount = 0;
Chris@534 212 if (!known || !top) {
Chris@535 213 // count how many identical values there are at the bottom
Chris@534 214 for (int i = 0; i < h; ++i) {
Chris@534 215 if (values.at(i) == p.at(i)) ++bcount;
Chris@534 216 else break;
Chris@534 217 }
Chris@534 218 }
Chris@534 219 if (!known || top) {
Chris@535 220 // count how many identical values there are at the top
Chris@534 221 for (int i = h; i > 0; --i) {
Chris@534 222 if (values.at(i-1) == p.at(i-1)) ++tcount;
Chris@534 223 else break;
Chris@534 224 }
Chris@534 225 }
Chris@534 226 if (!known) top = (tcount > bcount);
Chris@534 227
Chris@535 228 int limit = h / 4; // don't bother unless we have at least this many
Chris@534 229 if ((top ? tcount : bcount) > limit) {
Chris@534 230
Chris@534 231 if (!top) {
Chris@535 232 // create a new column with h - bcount values from bcount up
Chris@534 233 Column tcol(h - bcount);
Chris@535 234 // given += values.size();
Chris@535 235 // stored += h - bcount;
Chris@534 236 for (int i = bcount; i < h; ++i) {
Chris@534 237 tcol[i - bcount] = values.at(i);
Chris@534 238 }
Chris@534 239 m_data[index] = tcol;
Chris@1038 240 m_trunc[index] = (signed char)(-tdist);
Chris@534 241 return;
Chris@534 242 } else {
Chris@535 243 // create a new column with h - tcount values from 0 up
Chris@534 244 Column tcol(h - tcount);
Chris@535 245 // given += values.size();
Chris@535 246 // stored += h - tcount;
Chris@534 247 for (int i = 0; i < h - tcount; ++i) {
Chris@534 248 tcol[i] = values.at(i);
Chris@534 249 }
Chris@534 250 m_data[index] = tcol;
Chris@1038 251 m_trunc[index] = (signed char)(tdist);
Chris@534 252 return;
Chris@534 253 }
Chris@534 254 }
Chris@534 255 }
Chris@534 256
Chris@535 257 // given += values.size();
Chris@535 258 // stored += values.size();
Chris@843 259 // cout << "given: " << given << ", stored: " << stored << " ("
Chris@843 260 // << ((float(stored) / float(given)) * 100.f) << "%)" << endl;
Chris@534 261
Chris@535 262 // default case if nothing wacky worked out
Chris@534 263 m_data[index] = values;
Chris@534 264 return;
Chris@534 265 }
Chris@534 266
Chris@534 267 EditableDenseThreeDimensionalModel::Column
Chris@929 268 EditableDenseThreeDimensionalModel::expandAndRetrieve(int index) const
Chris@534 269 {
Chris@535 270 // See comment above m_trunc declaration in header
Chris@535 271
Chris@941 272 assert(index >= 0 && index < int(m_data.size()));
Chris@534 273 Column c = m_data.at(index);
Chris@534 274 if (index == 0) {
Chris@534 275 return c;
Chris@534 276 }
Chris@534 277 int trunc = (int)m_trunc[index];
Chris@534 278 if (trunc == 0) {
Chris@534 279 return c;
Chris@534 280 }
Chris@534 281 bool top = true;
Chris@534 282 int tdist = trunc;
Chris@534 283 if (trunc < 0) { top = false; tdist = -trunc; }
Chris@534 284 Column p = expandAndRetrieve(index - tdist);
Chris@1154 285 int psize = int(p.size()), csize = int(c.size());
Chris@941 286 if (psize != m_yBinCount) {
Chris@843 287 cerr << "WARNING: EditableDenseThreeDimensionalModel::expandAndRetrieve: Trying to expand from incorrectly sized column" << endl;
Chris@534 288 }
Chris@534 289 if (top) {
Chris@537 290 for (int i = csize; i < psize; ++i) {
Chris@534 291 c.push_back(p.at(i));
Chris@534 292 }
Chris@534 293 } else {
Chris@593 294 Column cc(psize);
Chris@593 295 for (int i = 0; i < psize - csize; ++i) {
Chris@593 296 cc[i] = p.at(i);
Chris@534 297 }
Chris@593 298 for (int i = 0; i < csize; ++i) {
Chris@593 299 cc[i + (psize - csize)] = c.at(i);
Chris@593 300 }
Chris@593 301 return cc;
Chris@534 302 }
Chris@534 303 return c;
Chris@152 304 }
Chris@152 305
Chris@152 306 void
Chris@929 307 EditableDenseThreeDimensionalModel::setColumn(int index,
Chris@182 308 const Column &values)
Chris@152 309 {
Chris@536 310 QWriteLocker locker(&m_lock);
Chris@152 311
Chris@1154 312 while (index >= int(m_data.size())) {
Chris@182 313 m_data.push_back(Column());
Chris@534 314 m_trunc.push_back(0);
Chris@152 315 }
Chris@152 316
Chris@152 317 bool allChange = false;
Chris@152 318
Chris@534 319 // if (values.size() > m_yBinCount) m_yBinCount = values.size();
Chris@439 320
Chris@1154 321 for (int i = 0; in_range_for(values, i); ++i) {
Chris@256 322 float value = values[i];
Chris@606 323 if (ISNAN(value) || ISINF(value)) {
Chris@256 324 continue;
Chris@256 325 }
Chris@256 326 if (!m_haveExtents || value < m_minimum) {
Chris@256 327 m_minimum = value;
Chris@152 328 allChange = true;
Chris@152 329 }
Chris@256 330 if (!m_haveExtents || value > m_maximum) {
Chris@256 331 m_maximum = value;
Chris@152 332 allChange = true;
Chris@152 333 }
Chris@256 334 m_haveExtents = true;
Chris@152 335 }
Chris@152 336
Chris@534 337 truncateAndStore(index, values);
Chris@534 338
Chris@593 339 // assert(values == expandAndRetrieve(index));
Chris@152 340
Chris@1110 341 sv_frame_t windowStart = index;
Chris@182 342 windowStart *= m_resolution;
Chris@182 343
Chris@152 344 if (m_notifyOnAdd) {
Chris@152 345 if (allChange) {
Chris@152 346 emit modelChanged();
Chris@152 347 } else {
Chris@931 348 emit modelChangedWithin(windowStart, windowStart + m_resolution);
Chris@152 349 }
Chris@152 350 } else {
Chris@152 351 if (allChange) {
Chris@152 352 m_sinceLastNotifyMin = -1;
Chris@152 353 m_sinceLastNotifyMax = -1;
Chris@152 354 emit modelChanged();
Chris@152 355 } else {
Chris@152 356 if (m_sinceLastNotifyMin == -1 ||
Chris@152 357 windowStart < m_sinceLastNotifyMin) {
Chris@152 358 m_sinceLastNotifyMin = windowStart;
Chris@152 359 }
Chris@152 360 if (m_sinceLastNotifyMax == -1 ||
Chris@152 361 windowStart > m_sinceLastNotifyMax) {
Chris@152 362 m_sinceLastNotifyMax = windowStart;
Chris@152 363 }
Chris@152 364 }
Chris@152 365 }
Chris@152 366 }
Chris@152 367
Chris@152 368 QString
Chris@929 369 EditableDenseThreeDimensionalModel::getBinName(int n) const
Chris@152 370 {
Chris@939 371 if (n >= 0 && (int)m_binNames.size() > n) return m_binNames[n];
Chris@152 372 else return "";
Chris@152 373 }
Chris@152 374
Chris@152 375 void
Chris@929 376 EditableDenseThreeDimensionalModel::setBinName(int n, QString name)
Chris@152 377 {
Chris@929 378 while ((int)m_binNames.size() <= n) m_binNames.push_back("");
Chris@152 379 m_binNames[n] = name;
Chris@152 380 emit modelChanged();
Chris@152 381 }
Chris@152 382
Chris@152 383 void
Chris@152 384 EditableDenseThreeDimensionalModel::setBinNames(std::vector<QString> names)
Chris@152 385 {
Chris@152 386 m_binNames = names;
Chris@152 387 emit modelChanged();
Chris@152 388 }
Chris@152 389
Chris@478 390 bool
Chris@886 391 EditableDenseThreeDimensionalModel::hasBinValues() const
Chris@886 392 {
Chris@886 393 return !m_binValues.empty();
Chris@886 394 }
Chris@886 395
Chris@886 396 float
Chris@929 397 EditableDenseThreeDimensionalModel::getBinValue(int n) const
Chris@886 398 {
Chris@929 399 if (n < (int)m_binValues.size()) return m_binValues[n];
Chris@886 400 else return 0.f;
Chris@886 401 }
Chris@886 402
Chris@886 403 void
Chris@886 404 EditableDenseThreeDimensionalModel::setBinValues(std::vector<float> values)
Chris@886 405 {
Chris@886 406 m_binValues = values;
Chris@886 407 }
Chris@886 408
Chris@886 409 QString
Chris@886 410 EditableDenseThreeDimensionalModel::getBinValueUnit() const
Chris@886 411 {
Chris@886 412 return m_binValueUnit;
Chris@886 413 }
Chris@886 414
Chris@886 415 void
Chris@886 416 EditableDenseThreeDimensionalModel::setBinValueUnit(QString unit)
Chris@886 417 {
Chris@886 418 m_binValueUnit = unit;
Chris@886 419 }
Chris@886 420
Chris@886 421 bool
Chris@478 422 EditableDenseThreeDimensionalModel::shouldUseLogValueScale() const
Chris@478 423 {
Chris@536 424 QReadLocker locker(&m_lock);
Chris@534 425
Chris@1044 426 vector<double> sample;
Chris@1044 427 vector<int> n;
Chris@478 428
Chris@478 429 for (int i = 0; i < 10; ++i) {
Chris@929 430 int index = i * 10;
Chris@1154 431 if (in_range_for(m_data, index)) {
Chris@533 432 const Column &c = m_data.at(index);
Chris@1154 433 while (c.size() > sample.size()) {
Chris@1044 434 sample.push_back(0.0);
Chris@478 435 n.push_back(0);
Chris@478 436 }
Chris@1154 437 for (int j = 0; in_range_for(c, j); ++j) {
Chris@533 438 sample[j] += c.at(j);
Chris@478 439 ++n[j];
Chris@478 440 }
Chris@478 441 }
Chris@478 442 }
Chris@478 443
Chris@478 444 if (sample.empty()) return false;
Chris@1044 445 for (decltype(sample)::size_type j = 0; j < sample.size(); ++j) {
Chris@1044 446 if (n[j]) sample[j] /= n[j];
Chris@478 447 }
Chris@478 448
Chris@1044 449 return LogRange::useLogScale(sample);
Chris@478 450 }
Chris@478 451
Chris@152 452 void
Chris@333 453 EditableDenseThreeDimensionalModel::setCompletion(int completion, bool update)
Chris@152 454 {
Chris@152 455 if (m_completion != completion) {
Chris@152 456 m_completion = completion;
Chris@152 457
Chris@152 458 if (completion == 100) {
Chris@152 459
Chris@152 460 m_notifyOnAdd = true; // henceforth
Chris@152 461 emit modelChanged();
Chris@152 462
Chris@152 463 } else if (!m_notifyOnAdd) {
Chris@152 464
Chris@333 465 if (update &&
Chris@333 466 m_sinceLastNotifyMin >= 0 &&
Chris@152 467 m_sinceLastNotifyMax >= 0) {
Chris@931 468 emit modelChangedWithin(m_sinceLastNotifyMin,
Chris@931 469 m_sinceLastNotifyMax + m_resolution);
Chris@152 470 m_sinceLastNotifyMin = m_sinceLastNotifyMax = -1;
Chris@152 471 } else {
Chris@152 472 emit completionChanged();
Chris@152 473 }
Chris@152 474 } else {
Chris@152 475 emit completionChanged();
Chris@152 476 }
Chris@152 477 }
Chris@152 478 }
Chris@152 479
Chris@318 480 QString
Chris@318 481 EditableDenseThreeDimensionalModel::toDelimitedDataString(QString delimiter) const
Chris@318 482 {
Chris@536 483 QReadLocker locker(&m_lock);
Chris@318 484 QString s;
Chris@1154 485 for (int i = 0; in_range_for(m_data, i); ++i) {
Chris@318 486 QStringList list;
Chris@1154 487 for (int j = 0; in_range_for(m_data.at(i), j); ++j) {
Chris@533 488 list << QString("%1").arg(m_data.at(i).at(j));
Chris@318 489 }
Chris@318 490 s += list.join(delimiter) + "\n";
Chris@318 491 }
Chris@318 492 return s;
Chris@318 493 }
Chris@318 494
Chris@838 495 QString
Chris@1038 496 EditableDenseThreeDimensionalModel::toDelimitedDataStringSubset(QString delimiter, sv_frame_t f0, sv_frame_t f1) const
Chris@838 497 {
Chris@838 498 QReadLocker locker(&m_lock);
Chris@838 499 QString s;
Chris@1154 500 for (int i = 0; in_range_for(m_data, i); ++i) {
Chris@1038 501 sv_frame_t fr = m_startFrame + i * m_resolution;
Chris@1038 502 if (fr >= f0 && fr < f1) {
Chris@838 503 QStringList list;
Chris@1154 504 for (int j = 0; in_range_for(m_data.at(i), j); ++j) {
Chris@838 505 list << QString("%1").arg(m_data.at(i).at(j));
Chris@838 506 }
Chris@838 507 s += list.join(delimiter) + "\n";
Chris@838 508 }
Chris@838 509 }
Chris@838 510 return s;
Chris@838 511 }
Chris@838 512
Chris@152 513 void
Chris@152 514 EditableDenseThreeDimensionalModel::toXml(QTextStream &out,
Chris@314 515 QString indent,
Chris@314 516 QString extraAttributes) const
Chris@152 517 {
Chris@536 518 QReadLocker locker(&m_lock);
Chris@534 519
Chris@152 520 // For historical reasons we read and write "resolution" as "windowSize"
Chris@152 521
Chris@690 522 SVDEBUG << "EditableDenseThreeDimensionalModel::toXml" << endl;
Chris@318 523
Chris@314 524 Model::toXml
Chris@314 525 (out, indent,
Chris@611 526 QString("type=\"dense\" dimensions=\"3\" windowSize=\"%1\" yBinCount=\"%2\" minimum=\"%3\" maximum=\"%4\" dataset=\"%5\" startFrame=\"%6\" %7")
Chris@152 527 .arg(m_resolution)
Chris@152 528 .arg(m_yBinCount)
Chris@152 529 .arg(m_minimum)
Chris@152 530 .arg(m_maximum)
Chris@152 531 .arg(getObjectExportId(&m_data))
Chris@611 532 .arg(m_startFrame)
Chris@152 533 .arg(extraAttributes));
Chris@152 534
Chris@152 535 out << indent;
Chris@152 536 out << QString("<dataset id=\"%1\" dimensions=\"3\" separator=\" \">\n")
Chris@152 537 .arg(getObjectExportId(&m_data));
Chris@152 538
Chris@929 539 for (int i = 0; i < (int)m_binNames.size(); ++i) {
Chris@152 540 if (m_binNames[i] != "") {
Chris@152 541 out << indent + " ";
Chris@152 542 out << QString("<bin number=\"%1\" name=\"%2\"/>\n")
Chris@152 543 .arg(i).arg(m_binNames[i]);
Chris@152 544 }
Chris@152 545 }
Chris@152 546
Chris@929 547 for (int i = 0; i < (int)m_data.size(); ++i) {
Chris@152 548 out << indent + " ";
Chris@152 549 out << QString("<row n=\"%1\">").arg(i);
Chris@929 550 for (int j = 0; j < (int)m_data.at(i).size(); ++j) {
Chris@152 551 if (j > 0) out << " ";
Chris@533 552 out << m_data.at(i).at(j);
Chris@152 553 }
Chris@152 554 out << QString("</row>\n");
Chris@318 555 out.flush();
Chris@152 556 }
Chris@152 557
Chris@152 558 out << indent + "</dataset>\n";
Chris@152 559 }
Chris@152 560
Chris@152 561