From 4ce1b027b0bd3e9ceff6f62e5b5698a31d63d416 Mon Sep 17 00:00:00 2001 From: "G.K.MacGregor" Date: Tue, 24 Nov 2020 19:01:25 +0000 Subject: [PATCH] Untangle saving TTI files into new file The saving code is an attempt to be page function and page coding agnostic so the same code can save (G)POP, (G)DRCS and MOT pages in the future. loadsave will be the home of page loading, URL exporting and also for importing and exporting of other teletext file formats. --- document.cpp | 28 -------- document.h | 2 +- levelonepage.cpp | 74 ++++++--------------- levelonepage.h | 1 - loadsave.cpp | 158 +++++++++++++++++++++++++++++++++++++++++++++ loadsave.h | 31 +++++++++ mainwindow.cpp | 25 ++++--- pagebase.cpp | 4 +- pagebase.h | 4 +- qteletextmaker.pro | 2 + 10 files changed, 230 insertions(+), 99 deletions(-) create mode 100644 loadsave.cpp create mode 100644 loadsave.h diff --git a/document.cpp b/document.cpp index eed8c20..f8f128a 100644 --- a/document.cpp +++ b/document.cpp @@ -147,34 +147,6 @@ void TeletextDocument::loadDocument(QFile *inFile) subPageSelected(); } -void TeletextDocument::saveDocument(QTextStream *outStream) -{ - if (!m_description.isEmpty()) - *outStream << "DE," << m_description << endl; - //TODO DS and SP commands - - int subPageNumber = m_subPages.size()>1; - - for (auto &subPage : m_subPages) { - subPage->savePage(outStream, m_pageNumber, subPageNumber++); - if ((subPage->fastTextLinkPageNumber(0) & 0x0ff) != 0x0ff) { - *outStream << "FL,"; - for (int i=0; i<6; i++) { - // Stored as page link with relative magazine number, convert to absolute page number for display - int absoluteLinkPageNumber = subPage->fastTextLinkPageNumber(i) ^ (m_pageNumber & 0x700); - // Fix magazine 0 to 8 - if ((absoluteLinkPageNumber & 0x700) == 0x000) - absoluteLinkPageNumber |= 0x800; - - *outStream << QString("%1").arg(absoluteLinkPageNumber, 3, 16, QChar('0')); - if (i<5) - *outStream << ','; - } - *outStream << endl; - } - } -} - void TeletextDocument::selectSubPageIndex(int newSubPageIndex, bool forceRefresh) { // forceRefresh overrides "beyond the last subpage" check, so inserting a subpage after the last one still shows - dangerous workaround? diff --git a/document.h b/document.h index 491e8c4..db78a86 100644 --- a/document.h +++ b/document.h @@ -48,8 +48,8 @@ public: // void setPacketCoding(PacketCodingEnum); void loadDocument(QFile *); - void saveDocument(QTextStream *); int numberOfSubPages() const { return m_subPages.size(); } + LevelOnePage* subPage(int p) const { return m_subPages[p]; } LevelOnePage* currentSubPage() const { return m_subPages[m_currentSubPageIndex]; } int currentSubPageIndex() const { return m_currentSubPageIndex; } void selectSubPageIndex(int, bool=false); diff --git a/levelonepage.cpp b/levelonepage.cpp index f15782a..8cc9704 100644 --- a/levelonepage.cpp +++ b/levelonepage.cpp @@ -42,7 +42,7 @@ LevelOnePage::LevelOnePage(const PageBase &other) localEnhance.reserve(208); clearPage(); - for (int i=PageBase::C4ErasePage; i<=PageBase::C11SerialMagazine; i++) + for (int i=PageBase::C4ErasePage; i<=PageBase::C14NOS; i++) setControlBit(i, other.controlBit(i)); for (int i=0; i<90; i++) if (other.packetNeededArrayIndex(i)) @@ -55,12 +55,12 @@ void LevelOnePage::clearPage() for (int r=0; r<25; r++) for (int c=0; c<40; c++) m_level1Page[r][c] = 0x20; - for (int i=0; i<8; i++) { + for (int i=C4ErasePage; i<=C14NOS; i++) setControlBit(i, false); + for (int i=0; i<8; i++) m_composeLink[i] = { (i<4) ? i : 0, false, i>=4, 0x0ff, 0x0000 }; - } for (int i=0; i<6; i++) - m_fastTextLink[i] = { 0x0ff, 0x37f7 }; + m_fastTextLink[i] = { 0x0ff, 0x3f7f }; /* m_subPageNumber = 0x0000; */ m_cycleValue = 8; @@ -256,7 +256,8 @@ bool LevelOnePage::setPacket(int packetNumber, int designationCode, QByteArray p int CLUToffset = (designationCode == 0) ? 16 : 0; m_defaultCharSet = ((packetContents.at(2) >> 4) & 0x3) | ((packetContents.at(3) << 2) & 0xc); - m_defaultNOS = (packetContents.at(2) >> 1) & 0x7; + // Don't set m_defaultNOS directly as we need to keep control bits in subclass in sync + setDefaultNOS((packetContents.at(2) >> 1) & 0x7); m_secondCharSet = ((packetContents.at(3) >> 5) & 0x1) | ((packetContents.at(4) << 1) & 0xe); m_secondNOS = (packetContents.at(3) >> 2) & 0x7; @@ -292,18 +293,13 @@ bool LevelOnePage::packetNeeded(int packetNumber, int designationCode) const if (packetNumber == 26) return ((localEnhance.size()+12) / 13) > designationCode; - // FIXME don't save this raw packet yet as TeletextDocument::savePage currently uses fastTextLinkPageNumber - // to put the FL commands into the .tti file - // When we separate out loading and saving into its own cpp file, that will then become responsible for - // converting this packet into an FL command itself - -/* if (packetNumber == 27 && designationCode == 0) { + if (packetNumber == 27 && designationCode == 0) { for (int i=0; i<6; i++) if ((m_fastTextLink[i].pageNumber & 0x0ff) != 0xff) return true; return false; - }*/ + } if (packetNumber == 27 && (designationCode == 4 || designationCode == 5)) { for (int i=0; i<(designationCode == 4 ? 6 : 2); i++) { @@ -363,7 +359,7 @@ void LevelOnePage::loadPagePacket(QByteArray &inLine) setPacket(lineNumber, inLine); } else { int designationCode = inLine.at(0) & 0x3f; - if (inLine.size() < 40) + if (inLine.size() < 40) { // OL is too short! if (lineNumber == 26) { // For a too-short enhancement triplets OL, first trim the line down to nearest whole triplet @@ -374,6 +370,7 @@ void LevelOnePage::loadPagePacket(QByteArray &inLine) } else // For other triplet OLs and Hamming 8/4 OLs, just pad with zero data inLine.leftJustified(40, '@'); + } for (int i=1; i<=39; i++) inLine[i] = inLine.at(i) & 0x3f; setPacket(lineNumber, designationCode, inLine); @@ -381,48 +378,6 @@ void LevelOnePage::loadPagePacket(QByteArray &inLine) } } -void LevelOnePage::savePage(QTextStream *outStream, int pageNumber, int subPageNumber) -{ - auto writePacketsWithDesignationCodes=[&](int packetNumber) - { - for (int i=0; i<=16; i++) - if (packetNeeded(packetNumber, i)) { - QByteArray outLine = packet(packetNumber, i); - - *outStream << QString("OL,%1,").arg(packetNumber); - outLine[0] = i | 0x40; - for (int c=1; c. + */ + +#include "loadsave.h" + +#include + +#include "document.h" +#include "levelonepage.h" +#include "pagebase.h" + +// Used by TTI and hashstring +int controlBitsToPS(PageBase *subPage) +{ + // C4 Erase page is stored in bit 14 + int pageStatus = 0x8000 | (subPage->controlBit(PageBase::C4ErasePage) << 14); + // C5 to C11 stored in order from bits 1 to 6 + for (int i=PageBase::C5Newsflash; i<=PageBase::C11SerialMagazine; i++) + pageStatus |= subPage->controlBit(i) << (i-1); + // Apparently the TTI format stores the NOS bits backwards + pageStatus |= subPage->controlBit(PageBase::C12NOS) << 9; + pageStatus |= subPage->controlBit(PageBase::C13NOS) << 8; + pageStatus |= subPage->controlBit(PageBase::C14NOS) << 7; + return pageStatus; +} + +void saveTTI(QSaveFile &file, const TeletextDocument &document) +{ + int p; + QTextStream outStream(&file); + + auto write7bitPacket=[&](int packetNumber) + { + if (document.subPage(p)->packetNeeded(packetNumber)) { + QByteArray outLine = document.subPage(p)->packet(packetNumber); + + outStream << QString("OL,%1,").arg(packetNumber); + for (int c=0; cpacketNeeded(packetNumber, designationCode)) { + QByteArray outLine = document.subPage(p)->packet(packetNumber, designationCode); + + outStream << QString("OL,%1,").arg(packetNumber); + // TTI stores raw values with bit 7 set, doesn't do Hamming encoding + outLine[0] = designationCode | 0x40; + for (int c=1; c 1; + + for (p=0; pcycleValue()).arg(document.subPage(p)->cycleType()==LevelOnePage::CTcycles ? 'C' : 'T') << Qt::endl; + else + // X/28/0 specifies page function and coding but the PF command + // should make it obvious to a human that this isn't a Level One Page + outStream << QString("PF,%1,%2").arg(document.pageFunction()).arg(document.packetCoding()) << Qt::endl; + + bool writeFLCommand = false; + if (document.pageFunction() == TeletextDocument::PFLevelOnePage && document.subPage(p)->packetNeeded(27,0)) { + // Subpage has FastText links - if any link to a specific subpage, we need to write X/27/0 as raw + // otherwise we write the links as a human-readable FL command later on + writeFLCommand = true; + // TODO uncomment this when we can edit FastText subpage links + /*for (int i=0; i<6; i++) + if (document.subPage(p)->fastTextLinkSubPageNumber(i) != 0x3f7f) { + writeFLCommand = false; + break; + }*/ + } + + // X27 then X28 always come first + for (int i=(writeFLCommand ? 1 : 0); i<16; i++) + writeHammingPacket(27, i); + for (int i=0; i<16; i++) + writeHammingPacket(28, i); + + if (document.packetCoding() == TeletextDocument::Coding7bit) { + // For 7 bit coding i.e. Level One Pages, X/26 are written before X/1 to X/25 + for (int i=0; i<16; i++) + writeHammingPacket(26, i); + for (int i=1; i<=24; i++) + write7bitPacket(i); + } else { + // For others (especially (G)POP pages) X/1 to X/25 are written before X/26 + for (int i=1; i<=25; i++) + writeHammingPacket(i); + for (int i=0; i<16; i++) + writeHammingPacket(26, i); + } + + if (writeFLCommand) { + outStream << "FL,"; + for (int i=0; i<6; i++) { + // Stored as page link with relative magazine number, convert to absolute page number for display + int absoluteLinkPageNumber = document.subPage(p)->fastTextLinkPageNumber(i) ^ (document.pageNumber() & 0x700); + // Fix magazine 0 to 8 + if ((absoluteLinkPageNumber & 0x700) == 0x000) + absoluteLinkPageNumber |= 0x800; + + outStream << QString("%1").arg(absoluteLinkPageNumber, 3, 16, QChar('0')); + if (i<5) + outStream << ','; + } + outStream << Qt::endl; + } + + subPageNumber++; + } +} diff --git a/loadsave.h b/loadsave.h new file mode 100644 index 0000000..3e1ba6b --- /dev/null +++ b/loadsave.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 Gavin MacGregor + * + * This file is part of QTeletextMaker. + * + * QTeletextMaker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QTeletextMaker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QTeletextMaker. If not, see . + */ + +#ifndef LOADSAVE_H +#define LOADSAVE_H + +#include + +#include "document.h" +#include "pagebase.h" + +int controlBitsToPS(PageBase *); +void saveTTI(QSaveFile &, const TeletextDocument &); + +#endif diff --git a/mainwindow.cpp b/mainwindow.cpp index d427330..43c1bfc 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,7 @@ #include "mainwindow.h" #include "levelonecommands.h" +#include "loadsave.h" #include "mainwidget.h" #include "pageenhancementsdockwidget.h" #include "pageoptionsdockwidget.h" @@ -762,18 +764,23 @@ void MainWindow::openRecentFile() bool MainWindow::saveFile(const QString &fileName) { - QFile file(fileName); - if (!file.open(QFile::WriteOnly | QFile::Text)) { - QMessageBox::warning(this, tr("QTeletextMaker"), tr("Cannot write file %1:\n%2.").arg(QDir::toNativeSeparators(fileName), file.errorString())); + QString errorMessage; + + QApplication::setOverrideCursor(Qt::WaitCursor); + QSaveFile file(fileName); + if (file.open(QFile::WriteOnly | QFile::Text)) { + saveTTI(file, *m_textWidget->document()); + if (!file.commit()) + errorMessage = tr("Cannot write file %1:\n%2.") .arg(QDir::toNativeSeparators(fileName), file.errorString()); + } else + errorMessage = tr("Cannot open file %1 for writing:\n%2.").arg(QDir::toNativeSeparators(fileName), file.errorString()); + QApplication::restoreOverrideCursor(); + + if (!errorMessage.isEmpty()) { + QMessageBox::warning(this, tr("QTeletextMaker"), errorMessage); return false; } - QTextStream out(&file); - out.setCodec("ISO-8859-1"); - QApplication::setOverrideCursor(Qt::WaitCursor); - m_textWidget->document()->saveDocument(&out); - QApplication::restoreOverrideCursor(); - setCurrentFile(fileName); statusBar()->showMessage(tr("File saved"), 2000); return true; diff --git a/pagebase.cpp b/pagebase.cpp index 1536dfc..bcd1ba3 100644 --- a/pagebase.cpp +++ b/pagebase.cpp @@ -26,13 +26,13 @@ PageBase::PageBase() // We use nullptrs to keep track of allocated packets, so initialise them this way for (int i=0; i<90; i++) m_packets[i] = nullptr; - for (int i=PageBase::C4ErasePage; i<=PageBase::C11SerialMagazine; i++) + for (int i=PageBase::C4ErasePage; i<=PageBase::C14NOS; i++) m_controlBits[i] = false; } PageBase::PageBase(const PageBase &other) { - for (int i=PageBase::C4ErasePage; i<=PageBase::C11SerialMagazine; i++) + for (int i=PageBase::C4ErasePage; i<=PageBase::C14NOS; i++) setControlBit(i, other.controlBit(i)); for (int i=0; i<90; i++) if (other.packetNeededArrayIndex(i)) diff --git a/pagebase.h b/pagebase.h index 25ebcd1..4b1a513 100644 --- a/pagebase.h +++ b/pagebase.h @@ -28,7 +28,7 @@ class PageBase //: public QObject //Q_OBJECT public: - enum ControlBitsEnum { C4ErasePage, C5Newsflash, C6Subtitle, C7SuppressHeader, C8Update, C9InterruptedSequence, C10InhibitDisplay, C11SerialMagazine }; + enum ControlBitsEnum { C4ErasePage, C5Newsflash, C6Subtitle, C7SuppressHeader, C8Update, C9InterruptedSequence, C10InhibitDisplay, C11SerialMagazine, C12NOS, C13NOS, C14NOS }; PageBase(); PageBase(const PageBase &); @@ -49,7 +49,7 @@ public: bool setControlBit(int, bool); private: - bool m_controlBits[8]; + bool m_controlBits[11]; QByteArray *m_packets[90]; // X/0 to X/25, plus 16 packets for X/26, another 16 for X/27, for X28 and for X/29 }; diff --git a/qteletextmaker.pro b/qteletextmaker.pro index caefd60..49dc390 100644 --- a/qteletextmaker.pro +++ b/qteletextmaker.pro @@ -4,6 +4,7 @@ requires(qtConfig(filedialog)) HEADERS = document.h \ levelonecommands.h \ levelonepage.h \ + loadsave.h \ mainwidget.h \ mainwindow.h \ pagebase.h \ @@ -17,6 +18,7 @@ HEADERS = document.h \ SOURCES = document.cpp \ levelonecommands.cpp \ levelonepage.cpp \ + loadsave.cpp \ main.cpp \ mainwidget.cpp \ mainwindow.cpp \