| 622 | } |
| 623 | |
| 624 | void DxClusterDialog::loadLogFiles(const QString& clusterLog, const QString& rbnLog, |
| 625 | const QString& wsjtxLog, const QString& potaLog, |
| 626 | const QString& freedvLog) |
| 627 | { |
| 628 | static const QRegularExpression rx( |
| 629 | R"(^DX\s+de\s+(\S+?):\s+(\d+\.?\d*)\s+(\S+)\s+(.*?)\s+(\d{4})Z)", |
| 630 | QRegularExpression::CaseInsensitiveOption); |
| 631 | |
| 632 | auto parseSpots = [&](const QStringList& lines, const QString& source) { |
| 633 | QVector<DxSpot> spots; |
| 634 | for (const auto& line : lines) { |
| 635 | auto match = rx.match(line); |
| 636 | if (match.hasMatch()) { |
| 637 | DxSpot spot; |
| 638 | spot.spotterCall = match.captured(1); |
| 639 | spot.freqMhz = match.captured(2).toDouble() / 1000.0; |
| 640 | spot.dxCall = match.captured(3); |
| 641 | spot.comment = match.captured(4).trimmed(); |
| 642 | QString timeStr = match.captured(5); |
| 643 | spot.utcTime = QTime(timeStr.left(2).toInt(), timeStr.mid(2, 2).toInt()); |
| 644 | if (spot.freqMhz > 0.0 && !spot.dxCall.isEmpty()) { |
| 645 | spot.source = source; |
| 646 | spots.append(spot); |
| 647 | } |
| 648 | } |
| 649 | } |
| 650 | return spots; |
| 651 | }; |
| 652 | |
| 653 | auto loadConsole = [](QPlainTextEdit* console, const QStringList& lines) { |
| 654 | if (!console || lines.isEmpty()) return; |
| 655 | console->setPlainText(lines.join('\n')); |
| 656 | auto* sb = console->verticalScrollBar(); |
| 657 | sb->setValue(sb->maximum()); |
| 658 | }; |
| 659 | |
| 660 | // Cluster log — parse spots + display in console |
| 661 | auto clusterLines = tailFile(clusterLog); |
| 662 | loadConsole(m_console, clusterLines); |
| 663 | auto clusterSpots = parseSpots(clusterLines, "Cluster"); |
| 664 | |
| 665 | // RBN log — parse spots + display in console |
| 666 | auto rbnLines = tailFile(rbnLog); |
| 667 | loadConsole(m_rbnConsole, rbnLines); |
| 668 | auto rbnSpots = parseSpots(rbnLines, "RBN"); |
| 669 | |
| 670 | // WSJT-X log — display only (no DX de format) |
| 671 | loadConsole(m_wsjtxConsole, tailFile(wsjtxLog)); |
| 672 | |
| 673 | // POTA log — display only |
| 674 | loadConsole(m_potaConsole, tailFile(potaLog)); |
| 675 | |
| 676 | // FreeDV log — display only |
| 677 | #ifdef HAVE_WEBSOCKETS |
| 678 | if (!freedvLog.isEmpty()) |
| 679 | loadConsole(m_freedvConsole, tailFile(freedvLog)); |
| 680 | #else |
| 681 | Q_UNUSED(freedvLog); |