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 \