Port saving pages to packet handling framework

Any unhandled packets within pages are now loaded and saved intact
albeit with no way to edit them.

File handling is now two separate processes: parsing TTI files into
packets, and then interpreting the packets into how pages are
displayed. This will allow us to import other file formats, and to deal
with page functions other than Level 1 Teletext pages.
This commit is contained in:
G.K.MacGregor
2020-09-15 22:37:50 +01:00
parent c99525abd4
commit a1e0986e5c
4 changed files with 162 additions and 135 deletions

View File

@@ -61,6 +61,77 @@ void TeletextPage::clearPage()
// If clearPage() is called outside constructor, we need to implement localEnhance.clear();
}
QByteArray TeletextPage::packet(int packetNumber, int designationCode)
{
QByteArray result(40, 0x00);
if (packetNumber <= 24) {
for (int c=0; c<40; c++)
result[c] = m_level1Page[packetNumber][c];
return result;
}
if (packetNumber == 26) {
if (!packetNeeded(26, designationCode))
return result; // Blank result
int enhanceListPointer;
X26Triplet lastTriplet;
for (int i=0; i<13; i++) {
enhanceListPointer = designationCode*13+i;
if (enhanceListPointer < localEnhance.size()) {
result[i*3+1] = localEnhance.at(enhanceListPointer).address();
result[i*3+2] = localEnhance.at(enhanceListPointer).mode() | ((localEnhance.at(enhanceListPointer).data() & 1) << 5);
result[i*3+3] = localEnhance.at(enhanceListPointer).data() >> 1;
// If this is the last triplet, get a copy to repeat to the end of the packet
if (enhanceListPointer == localEnhance.size()-1) {
lastTriplet = localEnhance.at(enhanceListPointer);
// If the last triplet was NOT a Termination Marker, make up one
if (lastTriplet.mode() != 0x1f || lastTriplet.address() != 0x3f) {
lastTriplet.setAddress(0x3f);
lastTriplet.setMode(0x1f);
lastTriplet.setData(0x07);
}
}
} else {
// We've gone past the end of the triplet list, so repeat the Termination Marker to the end
result[i*3+1] = lastTriplet.address();
result[i*3+2] = lastTriplet.mode() | ((lastTriplet.data() & 1) << 5);
result[i*3+3] = lastTriplet.data() >> 1;
}
}
return result;
}
// TODO packet 27
if (packetNumber == 28 && (designationCode == 0 || designationCode == 4)) {
int CLUToffset = (designationCode == 0) ? 16 : 0;
result[1] = 0x00;
result[2] = ((m_defaultCharSet & 0x3) << 4) | (m_defaultNOS << 1);
result[3] = ((m_secondCharSet & 0x1) << 5) | (m_secondNOS << 2) | (m_defaultCharSet >> 2);
result[4] = (m_sidePanelStatusL25 << 5) | (m_rightSidePanelDisplayed << 4) | (m_leftSidePanelDisplayed << 3) | (m_secondCharSet >> 1);
result[5] = m_sidePanelColumns | ((m_CLUT[CLUToffset] & 0x300) >> 4);
for (int c=0; c<16; c++) {
result[c*2+6] = ((m_CLUT[CLUToffset+c] & 0x0f0) >> 2) | ((m_CLUT[CLUToffset+c] & 0xf00) >> 10);
result[c*2+7] = ((m_CLUT[CLUToffset+c+1] & 0x300) >> 4) | (m_CLUT[CLUToffset+c] & 0x00f);
}
result[37] = ((m_defaultScreenColour & 0x03) << 4) | (m_CLUT[CLUToffset+15] & 0x00f);
result[38] = ((m_defaultRowColour & 0x07) << 3) | (m_defaultScreenColour >> 2);
result[39] = (m_colourTableRemap << 3) | (m_blackBackgroundSubst << 2) | (m_defaultRowColour >> 3);
return result;
}
return PageBase::packet(packetNumber, designationCode);
}
bool TeletextPage::setPacket(int packetNumber, QByteArray packetContents)
{
if (packetNumber <= 24) {
@@ -99,6 +170,9 @@ bool TeletextPage::setPacket(int packetNumber, int designationCode, QByteArray p
return true;
}
// TODO packet 27
if (packetNumber == 28 && (designationCode == 0 || designationCode == 4)) {
int CLUToffset = (designationCode == 0) ? 16 : 0;
@@ -113,7 +187,7 @@ bool TeletextPage::setPacket(int packetNumber, int designationCode, QByteArray p
m_sidePanelColumns = packetContents.at(5) & 0xf;
for (int c=0; c<16; c++)
m_CLUT[CLUToffset+c] = ((packetContents.at(c*2+5) << 4) & 0x300) | ((packetContents.at(c*2+6) << 10) & 0xc00) | ((packetContents.at(c*2+6) << 2) & 0x0f0) | (packetContents.at(c*2+7) & 0xf);
m_CLUT[CLUToffset+c] = ((packetContents.at(c*2+5) << 4) & 0x300) | ((packetContents.at(c*2+6) << 10) & 0xc00) | ((packetContents.at(c*2+6) << 2) & 0x0f0) | (packetContents.at(c*2+7) & 0x00f);
m_defaultScreenColour = (packetContents.at(37) >> 4) | ((packetContents.at(38) << 2) & 0x1c);
m_defaultRowColour = ((packetContents.at(38)) >> 3) | ((packetContents.at(39) << 3) & 0x18);
@@ -123,7 +197,7 @@ bool TeletextPage::setPacket(int packetNumber, int designationCode, QByteArray p
return true;
}
qDebug("LevelOnePage unhandled packet X%d/%d", packetNumber, designationCode);
qDebug("LevelOnePage unhandled packet X/%d/%d", packetNumber, designationCode);
return PageBase::setPacket(packetNumber, designationCode, packetContents);
}
@@ -135,7 +209,12 @@ bool TeletextPage::packetNeeded(int packetNumber, int designationCode) const
return true;
return false;
}
// TODO packets 26 and 27
if (packetNumber == 26)
return ((localEnhance.size()+12) / 13) > designationCode;
// TODO packet 27
if (packetNumber == 28) {
if (designationCode == 0) {
if (m_leftSidePanelDisplayed || m_rightSidePanelDisplayed || m_defaultScreenColour !=0 || m_defaultRowColour !=0 || m_blackBackgroundSubst || m_colourTableRemap !=0 || m_defaultCharSet != 0 || m_secondCharSet != 0xf)
@@ -152,7 +231,7 @@ bool TeletextPage::packetNeeded(int packetNumber, int designationCode) const
return false;
}
}
return true;
return PageBase::packetNeeded(packetNumber, designationCode);
}
void TeletextPage::loadPagePacket(QByteArray &inLine)
@@ -169,8 +248,8 @@ void TeletextPage::loadPagePacket(QByteArray &inLine)
inLine.remove(0, secondCommaPosition+1);
if (lineNumber <= 25) {
for (int c=0; c<40; c++) {
// trimmed() helpfully removes CRLF line endings from the just-read line
// but it also (un)helpfully removes spaces at end of a line, so put them back
// trimmed() helpfully removes CRLF line endings from the just-read line for us
// But it also (un)helpfully removes spaces at the end of a 40 character line, so put them back
if (c >= inLine.size())
inLine.append(' ');
if (inLine.at(c) & 0x80)
@@ -192,77 +271,45 @@ void TeletextPage::loadPagePacket(QByteArray &inLine)
}
}
// This will be gradually be converted to just getting the raw packets from the Page class and writing out a TTI file.
void TeletextPage::savePage(QTextStream *outStream, int pageNumber, int subPageNumber)
{
// int pageStatus = 0x8000 | (controlBits[0] << 14) | ((defaultPageNOS & 1) << 9) | ((defaultPageNOS & 2) << 7) | ((defaultPageNOS & 4) << 5);
// for (int i=1; i<8; i++)
// pageStatus |= controlBits[i] << i;
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<outLine.size(); c++)
outLine[c] = outLine.at(c) | 0x40;
*outStream << outLine << endl;
}
};
*outStream << QString("PN,%1%2").arg(pageNumber, 3, 16, QChar('0')).arg(subPageNumber & 0xff, 2, 16, QChar('0')) << endl;
*outStream << QString("SC,%1").arg(subPageNumber, 4, 16, QChar('0')) << endl;
*outStream << QString("PS,%1").arg(0x8000 | controlBitsToPS(), 4, 16, QChar('0')) << endl;
*outStream << QString("CT,%1,%2").arg(m_cycleValue).arg(m_cycleType==CTcycles ? 'C' : 'T') << endl;
// TODO RE and maybe FLOF?
if (packetNeeded(28, 0))
*outStream << "OL,28," << x28toTTI(0) << endl;
if (packetNeeded(28, 4))
*outStream << "OL,28," << x28toTTI(4) << endl;
// BUG FL commands may clash with X/27/0 packets that specify links manually (e.g. with subcodes)
writePacketsWithDesignationCodes(27);
writePacketsWithDesignationCodes(28);
writePacketsWithDesignationCodes(26);
if (!localEnhance.isEmpty()) {
int tripletNumber = 0;
X26Triplet lastTriplet = localEnhance.at(localEnhance.size()-1);
int terminatorNeeded = true; // Becomes false if a termination marker (without a "...follow") is already at the end
if (lastTriplet.mode() == 0x1f && lastTriplet.address() == 0x3f)
if (lastTriplet.data() & 0x01)
terminatorNeeded = false;
else
// Last termination marker has "follow" set but nothing follows, so write another one afterwards
lastTriplet.setData(lastTriplet.data() | 0x01);
else {
// No termination marker there, so make up one
lastTriplet.setAddress(0x3f);
lastTriplet.setMode(0x1f);
lastTriplet.setData(0x07);
}
for (int d=0; d<16; d++) {
*outStream << "OL,26," << (char)(d | 0x40);
for (int t=0; t<13; t++) {
if (tripletNumber < localEnhance.size()) {
*outStream << (char)(0x40 | localEnhance.at(tripletNumber).address());
*outStream << (char)(0x40 | (localEnhance.at(tripletNumber).mode() | ((localEnhance.at(tripletNumber).data() & 1) << 5)));
*outStream << (char)(0x40 | (localEnhance.at(tripletNumber).data() >> 1));
} else {
*outStream << (char)(0x40 | lastTriplet.address());
*outStream << (char)(0x40 | (lastTriplet.mode() | ((lastTriplet.data() & 1) << 5)));
*outStream << (char)(0x40 | (lastTriplet.data() >> 1));
terminatorNeeded = false;
}
tripletNumber++;
}
*outStream << endl;
// If the last triplet of the last X26 row wasn't a termination marker,
// terminatorNeeded ensures we write an additional X26 row full of termination markers.
if (!terminatorNeeded && tripletNumber >= localEnhance.size())
break;
}
}
for (int r=1; r<25; r++)
if (packetNeeded(r)) {
QString rowString;
QByteArray outLine = packet(r);
rowString.append(QString("OL,%1,").arg(r));
for (int c=0; c<40; c++) {
unsigned char myChar = m_level1Page[r][c];
if (myChar < 32) {
rowString.append((char)0x1b);
rowString.append((char)(myChar+0x40));
} else
rowString.append((char)myChar);
*outStream << QString("OL,%1,").arg(r);
for (int c=0; c<outLine.size(); c++)
if (outLine.at(c) < 0x20) {
outLine[c] = outLine.at(c) | 0x40;
outLine.insert(c, 0x1b);
c++;
}
*outStream << rowString << endl;
*outStream << outLine << endl;
}
}
@@ -275,61 +322,6 @@ int TeletextPage::controlBitsToPS() const
return pageStatus;
}
QString TeletextPage::x28toTTI(int designationCode)
{
QString result;
int x28Triplets[13] = {0};
int offset;
switch (designationCode) {
case 0:
offset = 16;
break;
case 4:
offset = 0;
break;
}
result.append(designationCode + 0x40);
x28Triplets[0] = ((m_secondCharSet & 1) << 17) | (m_secondNOS << 14) | (m_defaultCharSet << 10) | (m_defaultNOS << 7);
x28Triplets[1] = (m_secondCharSet >> 1) | (m_leftSidePanelDisplayed << 3) | (m_rightSidePanelDisplayed << 4) | (m_sidePanelStatusL25 << 5) | (m_sidePanelColumns << 6);
for (int c=0; c<16; c++){
int r = (m_CLUT[offset+c] & 0xF00) >> 8;
int g = (m_CLUT[offset+c] & 0xF0) >> 4;
int b = m_CLUT[offset+c] & 0xF;
int rtr = ((c * 12) + 28) / 18;
int rsh = ((c * 12) + 28) % 18;
x28Triplets[rtr] |= (r << rsh);
if (rsh == 16)
x28Triplets[rtr+1] |= (r >> 2) & 3;
int gtr = ((c * 12) + 32) / 18;
int gsh = ((c * 12) + 32) % 18;
x28Triplets[gtr] |= (g << gsh);
if (gsh == 16)
x28Triplets[gtr+1] |= (g >> 2) & 3;
int btr = ((c * 12) + 36) / 18;
int bsh = ((c * 12) + 36) % 18;
x28Triplets[btr] |= (b << bsh);
if (bsh == 16)
x28Triplets[btr+1] |= (b >> 2) & 3;
}
x28Triplets[12] |= (m_defaultScreenColour << 4) | (m_defaultRowColour << 9) | (m_blackBackgroundSubst << 14) | (m_colourTableRemap << 15);
for (int i=0; i<13; i++) {
result.append(0x40 | (x28Triplets[i] & 0x3F));
result.append(0x40 | ((x28Triplets[i] & 0xFC0) >> 6));
result.append(0x40 | ((x28Triplets[i] & 0x3F000) >> 12));
}
return result;
}
QString TeletextPage::exportURLHash(QString pageHash)
{
int hashDigits[1167]={0};
@@ -349,9 +341,6 @@ QString TeletextPage::exportURLHash(QString pageHash)
for (int i=0; i<1167; i++)
pageHash.append(base64[hashDigits[i]]);
// CLUTChangedResult = CLUTChanged();
// if (leftSidePanel || rightSidePanel || defaultScreenColour !=0 || defaultRowColour !=0 || blackBackgroundSubst || colourTableRemap !=0 || CLUTChangedResult) {
if (packetNeeded(28,0) || packetNeeded(28,4)) {
QString x28StringBegin, x28StringEnd;

View File

@@ -42,6 +42,7 @@ public:
TeletextPage();
QByteArray packet(int, int=0);
bool setPacket(int, QByteArray);
bool setPacket(int, int, QByteArray);
bool packetNeeded(int, int=0) const;
@@ -91,7 +92,6 @@ public:
protected:
int controlBitsToPS() const;
QString x28toTTI(int);
private:
unsigned char m_level1Page[25][40];

View File

@@ -26,17 +26,37 @@ PageBase::PageBase()
m_pageFunction = PFLOP;
m_packetCoding = PC7bit;
// We use nullptrs to keep track of allocated packets, so initialise them this way
for (int i=0; i<89; i++)
for (int i=0; i<90; i++)
m_packets[i] = nullptr;
}
PageBase::~PageBase()
{
for (int i=0; i<89; i++)
for (int i=0; i<90; i++)
if (m_packets[i] != nullptr)
delete m_packets[i];
}
QByteArray PageBase::packet(int packetNumber, int designationCode) const
{
int packetArrayIndex = packetNumber;
if (packetNumber >= 26)
packetArrayIndex += (packetNumber - 26) * 16 + designationCode;
if (m_packets[packetArrayIndex] == nullptr)
return QByteArray(); // Blank result
return *m_packets[packetArrayIndex];
}
bool PageBase::packetNeeded(int packetNumber, int designationCode) const
{
int packetArrayIndex = packetNumber;
if (packetNumber >= 26)
packetArrayIndex += (packetNumber - 26) * 16 + designationCode;
return m_packets[packetArrayIndex] != nullptr;
}
bool PageBase::setPacket(int packetNumber, QByteArray packetContents)
{
return setPacket(packetNumber, 0, packetContents);
@@ -44,12 +64,27 @@ bool PageBase::setPacket(int packetNumber, QByteArray packetContents)
bool PageBase::setPacket(int packetNumber, int designationCode, QByteArray packetContents)
{
int packetArrayNumber = packetNumber;
int packetArrayIndex = packetNumber;
if (packetNumber >= 26)
packetArrayNumber += (packetNumber - 26) * 16;
if (m_packets[packetArrayNumber] == nullptr)
m_packets[packetArrayNumber] = new QByteArray(40, 0x00);
*m_packets[packetArrayNumber] = packetContents;
packetArrayIndex += (packetNumber - 26) * 16 + designationCode;
if (m_packets[packetArrayIndex] == nullptr)
m_packets[packetArrayIndex] = new QByteArray(40, 0x00);
*m_packets[packetArrayIndex] = packetContents;
return true;
}
bool PageBase::deletePacket(int packetNumber, int designationCode)
{
int packetArrayIndex = packetNumber;
if (packetNumber >= 26)
packetArrayIndex += (packetNumber - 26) * 16 + designationCode;
if (m_packets[packetArrayIndex] != nullptr) {
delete m_packets[packetArrayIndex];
m_packets[packetArrayIndex] = nullptr;
}
return true;
}

View File

@@ -35,18 +35,21 @@ public:
PageBase();
~PageBase();
QByteArray packet(int, int=0) const;
bool packetNeeded(int, int=0) const;
bool setPacket(int, QByteArray);
bool setPacket(int, int, QByteArray);
bool deletePacket(int, int=0);
PageFunctionEnum pageFunction() const { return m_pageFunction; }
bool setPageFunction(PageFunctionEnum);
PacketCodingEnum packetCoding(int=0, int=0) const;
bool setPacketCoding(PacketCodingEnum);
bool packetNeeded(int, int=0) const { return true; }
private:
PageFunctionEnum m_pageFunction;
PacketCodingEnum m_packetCoding;
QByteArray *m_packets[89]; // X/1 to X/25, plus 16 packets for X/26, another 16 for X/27, for X28 and for X/29
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
};
#endif