A complete rewrite of decode.cpp and decode.h to make it easier to follow and maintain. Rather than a set of "layers" descended from C++ classes, the for-loop across the rows and columns explicitly lays out the order of processing the Level 1 characters and attributes, the X/26 enhancements that affect the page directly as well as those within each Invocation of an Object, followed by selecting which Invoked Object to place in the character cell or in the absence of an Object the underlying page fragment will be placed in the cell. The new decoder has the following improvements over the old... - Local Enhancement Data has priority over Active Objects. - Active Objects can set Full Screen and Full Row colours. - Incremental/decremental flash phases are tracked correctly within Objects. - X/26 characters overwriting the bottom half of Level 1 Double Height rows. - Interaction between the underlying page and Objects where characters of different sizes overlap.
1075 lines
37 KiB
C++
1075 lines
37 KiB
C++
/*
|
|
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "decode.h"
|
|
|
|
#include <QList>
|
|
#include <QMultiMap>
|
|
|
|
TeletextPageDecode::Invocation::Invocation()
|
|
{
|
|
m_tripletList = nullptr;
|
|
m_startTripletNumber = 0;
|
|
m_endTripletNumber = -1;
|
|
m_originRow = 0;
|
|
m_originColumn = 0;
|
|
m_fullScreenCLUT = -1;
|
|
}
|
|
|
|
void TeletextPageDecode::Invocation::setTripletList(X26TripletList *tripletList)
|
|
{
|
|
m_tripletList = tripletList;
|
|
}
|
|
|
|
void TeletextPageDecode::Invocation::setStartTripletNumber(int n)
|
|
{
|
|
m_startTripletNumber = n;
|
|
}
|
|
|
|
void TeletextPageDecode::Invocation::setEndTripletNumber(int n)
|
|
{
|
|
m_endTripletNumber = n;
|
|
}
|
|
|
|
void TeletextPageDecode::Invocation::setOrigin(int row, int column)
|
|
{
|
|
m_originRow = row;
|
|
m_originColumn = column;
|
|
}
|
|
|
|
void TeletextPageDecode::Invocation::buildMap(int level)
|
|
{
|
|
int endTripletNumber;
|
|
|
|
if (m_endTripletNumber == -1)
|
|
endTripletNumber = m_tripletList->size();
|
|
else
|
|
endTripletNumber = m_endTripletNumber;
|
|
|
|
m_characterMap.clear();
|
|
m_attributeMap.clear();
|
|
m_rightMostColumn.clear();
|
|
m_fullScreenCLUT = -1;
|
|
m_fullRowCLUTMap.clear();
|
|
|
|
for (int i=m_startTripletNumber; i<=endTripletNumber; i++) {
|
|
const X26Triplet triplet = m_tripletList->at(i);
|
|
|
|
if (triplet.error() != 0)
|
|
continue;
|
|
|
|
int targetRow, targetColumn;
|
|
|
|
if (level == 1) {
|
|
targetRow = m_originRow + triplet.activePositionRow1p5();
|
|
targetColumn = m_originColumn + triplet.activePositionColumn1p5();
|
|
} else {
|
|
targetRow = m_originRow + triplet.activePositionRow();
|
|
targetColumn = m_originColumn + triplet.activePositionColumn();
|
|
}
|
|
|
|
if (triplet.activePositionRow() == -1)
|
|
targetRow++;
|
|
if (triplet.activePositionColumn() == -1)
|
|
targetColumn++;
|
|
|
|
if (targetRow > 24 || targetColumn > 71)
|
|
continue;
|
|
|
|
switch (triplet.modeExt()) {
|
|
case 0x21: // G1 character
|
|
case 0x22: // G3 character at Level 1.5
|
|
case 0x29: // G0 character
|
|
case 0x2b: // G3 character at Level 2.5
|
|
case 0x2f: // G2 character
|
|
case 0x30 ... 0x3f: // G0 character with diacritical
|
|
m_characterMap.insert(qMakePair(targetRow, targetColumn), triplet);
|
|
// Store rightmost column in this row for Adaptive Object attribute tracking
|
|
// QMap stores one value per key, QMap::insert will replace the value if the key already exists
|
|
m_rightMostColumn.insert(targetRow, targetColumn);
|
|
break;
|
|
case 0x20: // Foreground colour
|
|
case 0x23: // Background colour
|
|
case 0x27: // Additional flash functions
|
|
case 0x2c: // Display attributes
|
|
m_attributeMap.insert(qMakePair(targetRow, targetColumn), triplet);
|
|
m_rightMostColumn.insert(targetRow, targetColumn);
|
|
break;
|
|
case 0x00: // Full screen colour
|
|
if ((triplet.data() & 0x60) != 0x00)
|
|
break;
|
|
m_fullScreenCLUT = triplet.data();
|
|
// Full Screen Colour triplet overrides both the X/28 Full Screen Colour AND Full Row Colour.
|
|
// For the latter, place a Full Row Colour "down to bottom" at the Active Position.
|
|
m_fullRowCLUTMap.insert(targetRow, X26Triplet(triplet.address(), triplet.mode(), triplet.data() | 0x60));
|
|
break;
|
|
case 0x01: // Full row colour
|
|
m_fullRowCLUTMap.insert(targetRow, triplet);
|
|
break;
|
|
case 0x07: // Address row 0
|
|
if (targetRow == 0)
|
|
m_fullRowCLUTMap.insert(targetRow, triplet);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int TeletextPageDecode::s_instances = 0;
|
|
|
|
TeletextPageDecode::textPainter TeletextPageDecode::s_blankPainter;
|
|
|
|
TeletextPageDecode::TeletextPageDecode()
|
|
{
|
|
if (s_instances == 0) {
|
|
for (int c=0; c<72; c++)
|
|
s_blankPainter.bottomHalfCell[c].character.code = 0x00;
|
|
s_blankPainter.rightHalfCell.character.code = 0x00;
|
|
}
|
|
s_instances++;
|
|
|
|
m_level = 0;
|
|
|
|
for (int r=0; r<25; r++) {
|
|
m_rowHeight[r] = NormalHeight;
|
|
for (int c=0; c<72; c++) {
|
|
if (c < 40) {
|
|
m_cellLevel1Mosaic[r][c] = false;
|
|
m_cellLevel1CharSet[r][c] = 0;
|
|
}
|
|
m_refresh[r][c] = true;
|
|
}
|
|
}
|
|
|
|
m_finalFullScreenColour = 0;
|
|
m_finalFullScreenQColor.setRgb(0, 0, 0);
|
|
for (int r=0; r<25; r++) {
|
|
m_fullRowColour[r] = 0;
|
|
m_fullRowQColor[r].setRgb(0, 0, 0);
|
|
}
|
|
m_leftSidePanelColumns = m_rightSidePanelColumns = 0;
|
|
}
|
|
|
|
TeletextPageDecode::~TeletextPageDecode()
|
|
{
|
|
s_instances--;
|
|
}
|
|
|
|
void TeletextPageDecode::setRefresh(int r, int c, bool refresh)
|
|
{
|
|
m_refresh[r][c] = refresh;
|
|
}
|
|
|
|
void TeletextPageDecode::setTeletextPage(LevelOnePage *newCurrentPage)
|
|
{
|
|
m_levelOnePage = newCurrentPage;
|
|
m_localEnhancements.setTripletList(m_levelOnePage->enhancements());
|
|
updateSidePanels();
|
|
}
|
|
|
|
void TeletextPageDecode::setLevel(int level)
|
|
{
|
|
if (level == m_level)
|
|
return;
|
|
|
|
m_level = level;
|
|
|
|
for (int r=0; r<25; r++)
|
|
for (int c=0; c<72; c++)
|
|
m_refresh[r][c] = true;
|
|
|
|
updateSidePanels();
|
|
decodePage();
|
|
}
|
|
|
|
void TeletextPageDecode::updateSidePanels()
|
|
{
|
|
int oldLeftSidePanelColumns = m_leftSidePanelColumns;
|
|
int oldRightSidePanelColumns = m_rightSidePanelColumns;
|
|
|
|
if (m_level >= (3-m_levelOnePage->sidePanelStatusL25()) && m_levelOnePage->leftSidePanelDisplayed())
|
|
m_leftSidePanelColumns = (m_levelOnePage->sidePanelColumns() == 0) ? 16 : m_levelOnePage->sidePanelColumns();
|
|
else
|
|
m_leftSidePanelColumns = 0;
|
|
|
|
if (m_level >= (3-m_levelOnePage->sidePanelStatusL25()) && m_levelOnePage->rightSidePanelDisplayed())
|
|
m_rightSidePanelColumns = 16-m_levelOnePage->sidePanelColumns();
|
|
else
|
|
m_rightSidePanelColumns = 0;
|
|
|
|
if (m_leftSidePanelColumns != oldLeftSidePanelColumns || m_rightSidePanelColumns != oldRightSidePanelColumns) {
|
|
emit sidePanelsChanged();
|
|
decodePage();
|
|
}
|
|
}
|
|
|
|
void TeletextPageDecode::buildInvocationList(Invocation &invocation, int objectType)
|
|
{
|
|
if (invocation.tripletList()->isEmpty())
|
|
return;
|
|
|
|
int i;
|
|
|
|
for (i=invocation.startTripletNumber(); i<invocation.tripletList()->size(); i++) {
|
|
const X26Triplet triplet = invocation.tripletList()->at(i);
|
|
|
|
if (triplet.modeExt() == 0x1f && triplet.address() == 63)
|
|
// Termination marker
|
|
break;
|
|
if (triplet.modeExt() >= 0x15 && triplet.modeExt() <= 0x17)
|
|
// Object Definition, also used as terminator
|
|
break;
|
|
if (m_level >= 2 && triplet.modeExt() >= 0x11 && triplet.modeExt() <= 0x13 && triplet.error() == 0) {
|
|
// Object Invocation
|
|
//TODO POP and GPOP objects
|
|
if (triplet.objectSource() != X26Triplet::LocalObject) {
|
|
qDebug("POP or GPOP");
|
|
continue;
|
|
}
|
|
|
|
// Check if (sub)Object type can be invoked by Object type we're within
|
|
if (triplet.modeExt() - 0x11 <= objectType)
|
|
continue;
|
|
|
|
// See if Object Definition is required at selected level
|
|
if (m_level == 2 && (invocation.tripletList()->at(triplet.objectLocalIndex()).address() & 0x08) == 0x00)
|
|
continue;
|
|
if (m_level == 3 && (invocation.tripletList()->at(triplet.objectLocalIndex()).address() & 0x10) == 0x00)
|
|
continue;
|
|
|
|
// Work out the absolute position where the Object is invoked
|
|
int originRow = invocation.originRow() + triplet.activePositionRow();
|
|
int originColumn = invocation.originColumn() + triplet.activePositionColumn();
|
|
// -1, -1 happens if Object is invoked before the Active Position is deployed
|
|
if (triplet.activePositionRow() == -1)
|
|
originRow++;
|
|
if (triplet.activePositionColumn() == -1)
|
|
originColumn++;
|
|
// Use Origin Modifier in previous triplet if there's one there
|
|
if (i > 0 && invocation.tripletList()->at(i-1).modeExt() == 0x10) {
|
|
originRow += invocation.tripletList()->at(i-1).address()-40;
|
|
originColumn += invocation.tripletList()->at(i-1).data();
|
|
}
|
|
|
|
// Add the Invocation to the list, and recurse
|
|
Invocation newInvocation;
|
|
const int newObjectType = triplet.modeExt() - 0x11;
|
|
|
|
newInvocation.setTripletList(invocation.tripletList());
|
|
newInvocation.setStartTripletNumber(triplet.objectLocalIndex()+1);
|
|
newInvocation.setOrigin(originRow, originColumn);
|
|
m_invocations[newObjectType].append(newInvocation);
|
|
buildInvocationList(m_invocations[newObjectType].last(), newObjectType);
|
|
}
|
|
}
|
|
|
|
invocation.setEndTripletNumber(i-1);
|
|
invocation.buildMap(m_level);
|
|
}
|
|
|
|
TeletextPageDecode::textCharacter TeletextPageDecode::characterFromTriplets(const QList<X26Triplet> triplets, int g0CharSet)
|
|
{
|
|
textCharacter result;
|
|
result.code = 0x00;
|
|
|
|
// QMultiMap::values returns a QList with the most recently inserted value sorted first,
|
|
// so do the loop backwards to iterate from least to most recent value
|
|
for (int a=triplets.size()-1; a>=0; a--) {
|
|
const X26Triplet triplet = triplets.at(a);
|
|
|
|
if (triplet.data() < 0x20)
|
|
continue;
|
|
|
|
const unsigned char charCode = triplet.data();
|
|
|
|
// Deal with Level 1.5 valid characters first
|
|
switch (triplet.modeExt()) {
|
|
case 0x22: // G3 character at Level 1.5
|
|
result = { charCode, 26, 0 };
|
|
break;
|
|
case 0x2f: // G2 character
|
|
result.code = charCode;
|
|
// Duplicated from decodePage
|
|
switch (m_level1DefaultCharSet) {
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
// Cyrillic G2
|
|
result.set = 8;
|
|
break;
|
|
case 4:
|
|
// Greek G2
|
|
result.set = 9;
|
|
break;
|
|
case 5:
|
|
// Arabic G2
|
|
result.set = 10;
|
|
break;
|
|
default:
|
|
// Latin G2
|
|
result.set = 7;
|
|
break;
|
|
}
|
|
result.diacritical = 0;
|
|
break;
|
|
case 0x30 ... 0x3f: // G0 character with diacritical
|
|
result = { charCode, g0CharSet, triplet.mode() & 0xf };
|
|
break;
|
|
}
|
|
|
|
if (m_level == 1)
|
|
continue;
|
|
|
|
// Now deal with Level 2.5 characters
|
|
switch (triplet.modeExt()) {
|
|
case 0x21: // G1 character
|
|
result.code = charCode;
|
|
if (triplet.data() & 0x20)
|
|
result.set = 24;
|
|
else
|
|
result.set = g0CharSet;
|
|
result.diacritical = 0;
|
|
break;
|
|
case 0x29: // G0 character
|
|
result = { charCode, g0CharSet, 0 };
|
|
break;
|
|
case 0x2b: // G3 character at Level 2.5
|
|
result = { charCode, 26, 0 };
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void TeletextPageDecode::decodePage()
|
|
{
|
|
m_invocations[0].clear();
|
|
m_invocations[1].clear();
|
|
m_invocations[2].clear();
|
|
|
|
buildInvocationList(m_localEnhancements, -1);
|
|
|
|
// Append Local Enhancement Data to end of Active Object QList
|
|
m_invocations[0].append(m_localEnhancements);
|
|
|
|
m_level1ActivePainter = s_blankPainter;
|
|
|
|
m_adapPassPainter[0].clear();
|
|
m_adapPassPainter[1].clear();
|
|
|
|
for (int t=1; t<3; t++)
|
|
for (int i=0; i<m_invocations[t].size(); i++)
|
|
m_adapPassPainter[t-1].append(s_blankPainter);
|
|
|
|
if (m_level >= 2) {
|
|
// Pick up default full screen/row colours from X/28
|
|
setFullScreenColour(m_levelOnePage->defaultScreenColour());
|
|
int downwardsRowCLUT = m_levelOnePage->defaultRowColour();
|
|
|
|
// Check for Full Screen Colour X/26 triplets in Local Enhancement Data and Active Objects
|
|
for (int i=0; i<m_invocations[0].size(); i++)
|
|
if (m_invocations[0].at(i).fullScreenColour() != -1)
|
|
setFullScreenColour(m_invocations[0].at(i).fullScreenColour());
|
|
|
|
// Now do the Full Row Colours
|
|
for (int r=0; r<25; r++) {
|
|
int thisFullRowColour = downwardsRowCLUT;
|
|
|
|
for (int i=0; i<m_invocations[0].size(); i++) {
|
|
const QList<X26Triplet> fullRowColoursHere = m_invocations[0].at(i).fullRowColoursMappedAt(r);
|
|
|
|
// QMultiMap::values returns QList with most recent value first...
|
|
for (int a=fullRowColoursHere.size()-1; a>=0; a--) {
|
|
thisFullRowColour = fullRowColoursHere.at(a).data() & 0x1f;
|
|
if ((fullRowColoursHere.at(a).data() & 0x60) == 0x60)
|
|
downwardsRowCLUT = thisFullRowColour;
|
|
}
|
|
}
|
|
|
|
setFullRowColour(r, thisFullRowColour);
|
|
}
|
|
} else {
|
|
setFullScreenColour(0);
|
|
for (int r=0; r<25; r++)
|
|
setFullRowColour(r, 0);
|
|
}
|
|
|
|
m_level1DefaultCharSet = m_g0CharacterMap.value(((m_levelOnePage->defaultCharSet() << 3) | m_levelOnePage->defaultNOS()), 0);
|
|
if (m_levelOnePage->secondCharSet() != 0xf)
|
|
m_level1SecondCharSet = m_g0CharacterMap.value(((m_levelOnePage->secondCharSet() << 3) | m_levelOnePage->secondNOS()), 0);
|
|
else
|
|
m_level1SecondCharSet = m_level1DefaultCharSet;
|
|
|
|
// This will be true if the Level 1 character set is non-Latin
|
|
if (m_level1DefaultCharSet <= 6)
|
|
m_x26DefaultG0CharSet = m_level1DefaultCharSet;
|
|
else
|
|
m_x26DefaultG0CharSet = 0;
|
|
|
|
switch (m_level1DefaultCharSet) {
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
// Cyrillic G2
|
|
m_x26DefaultG2CharSet = 8;
|
|
break;
|
|
case 4:
|
|
// Greek G2
|
|
m_x26DefaultG2CharSet = 9;
|
|
break;
|
|
case 5:
|
|
// Arabic G2
|
|
m_x26DefaultG2CharSet = 10;
|
|
break;
|
|
default:
|
|
// Latin G2
|
|
m_x26DefaultG2CharSet = 7;
|
|
break;
|
|
}
|
|
|
|
// Work out rows containing top and bottom halves of Level 1 double height characters
|
|
for (int r=1; r<24; r++) {
|
|
bool doubleHeightAttributeFound = false;
|
|
|
|
for (int c=0; c<40; c++)
|
|
if (m_levelOnePage->character(r, c) == 0x0d || m_levelOnePage->character(r, c) == 0x0f) {
|
|
doubleHeightAttributeFound = true;
|
|
break;
|
|
}
|
|
|
|
if (doubleHeightAttributeFound && r < 23) {
|
|
m_rowHeight[r] = TopHalf;
|
|
r++;
|
|
m_rowHeight[r] = BottomHalf;
|
|
} else
|
|
m_rowHeight[r] = NormalHeight;
|
|
}
|
|
|
|
for (int r=0; r<25; r++)
|
|
decodeRow(r);
|
|
}
|
|
|
|
void TeletextPageDecode::decodeRow(int r)
|
|
{
|
|
int level1ForegroundCLUT = 7;
|
|
bool level1Mosaics = false;
|
|
bool level1SeparatedMosaics = false;
|
|
bool level1HoldMosaics = false;
|
|
unsigned char level1HoldMosaicCharacter = 0x20;
|
|
bool level1HoldMosaicSeparated = false;
|
|
int level1CharSet = 0;
|
|
bool level1EscapeSwitch = false;
|
|
int x26G0CharSet = 0;
|
|
|
|
textPainter *painter;
|
|
|
|
// Used for tracking which Adaptive Invocation is applying which attribute type(s)
|
|
// A.7.1 and A.7.2 of the spec says Adaptive Objects can't overlap but can be interleaved
|
|
// if they don't have attributes, so we only need to track one
|
|
int adapInvokeAttrs = -1;
|
|
bool adapForeground = false;
|
|
bool adapBackground = false;
|
|
bool adapFlash = false;
|
|
bool adapDisplayAttrs = false;
|
|
|
|
for (int c=0; c<72; c++) {
|
|
textCell previousCellContents = m_cell[r][c];
|
|
|
|
// Start of row default conditions, also when crossing into and across side panels
|
|
if (c == 0 || c == 40 || c == 56) {
|
|
level1CharSet = m_level1DefaultCharSet;
|
|
x26G0CharSet = m_x26DefaultG0CharSet;
|
|
|
|
m_level1ActivePainter.attribute.flash.mode = 0;
|
|
m_level1ActivePainter.attribute.flash.ratePhase = 0;
|
|
m_level1ActivePainter.attribute.display.doubleHeight = false;
|
|
m_level1ActivePainter.attribute.display.doubleWidth = false;
|
|
m_level1ActivePainter.attribute.display.boxingWindow = false;
|
|
m_level1ActivePainter.attribute.display.conceal = false;
|
|
m_level1ActivePainter.attribute.display.invert = false;
|
|
m_level1ActivePainter.attribute.display.underlineSeparated = false;
|
|
|
|
if (m_level >= 2) {
|
|
m_level1ActivePainter.attribute.foregroundCLUT = 7 | m_foregroundRemap[m_levelOnePage->colourTableRemap()];
|
|
if (m_levelOnePage->blackBackgroundSubst() || c >= 40)
|
|
m_level1ActivePainter.attribute.backgroundCLUT = m_fullRowColour[r];
|
|
else
|
|
m_level1ActivePainter.attribute.backgroundCLUT = m_backgroundRemap[m_levelOnePage->colourTableRemap()];
|
|
} else {
|
|
m_level1ActivePainter.attribute.foregroundCLUT = 7;
|
|
m_level1ActivePainter.attribute.backgroundCLUT = 0;
|
|
}
|
|
}
|
|
|
|
// Level 1 set-at and "set-between" spacing attributes
|
|
if (c < 40 && m_rowHeight[r] != BottomHalf)
|
|
switch (m_levelOnePage->character(r, c)) {
|
|
case 0x09: // Steady
|
|
m_level1ActivePainter.attribute.flash.mode = 0;
|
|
m_level1ActivePainter.attribute.flash.ratePhase = 0;
|
|
break;
|
|
case 0x0a: // End box
|
|
// "Set-between" - requires two consecutive "end box" codes
|
|
if (c > 0 && m_levelOnePage->character(r, c-1) == 0x0a)
|
|
m_level1ActivePainter.attribute.display.boxingWindow = false;
|
|
break;
|
|
case 0x0b: // Start box
|
|
// "Set-between" - requires two consecutive "start box" codes
|
|
if (c > 0 && m_levelOnePage->character(r, c-1) == 0x0b)
|
|
m_level1ActivePainter.attribute.display.boxingWindow = true;
|
|
break;
|
|
case 0x0c: // Normal size
|
|
if (m_level1ActivePainter.attribute.display.doubleHeight || m_level1ActivePainter.attribute.display.doubleWidth) {
|
|
// Change of size resets hold mosaic character
|
|
level1HoldMosaicCharacter = 0x20;
|
|
level1HoldMosaicSeparated = false;
|
|
}
|
|
m_level1ActivePainter.attribute.display.doubleHeight = false;
|
|
m_level1ActivePainter.attribute.display.doubleWidth = false;
|
|
break;
|
|
case 0x18: // Conceal
|
|
m_level1ActivePainter.attribute.display.conceal = true;
|
|
break;
|
|
case 0x19: // Contiguous mosaics
|
|
// This spacing attribute cannot cancel an X/26 underlined/separated attribute
|
|
if (!m_level1ActivePainter.attribute.display.underlineSeparated)
|
|
level1SeparatedMosaics = false;
|
|
break;
|
|
case 0x1a: // Separated mosaics
|
|
level1SeparatedMosaics = true;
|
|
break;
|
|
case 0x1c: // Black background
|
|
if (m_level >= 2) {
|
|
if (m_levelOnePage->blackBackgroundSubst())
|
|
m_level1ActivePainter.attribute.backgroundCLUT = m_fullRowColour[r];
|
|
else
|
|
m_level1ActivePainter.attribute.backgroundCLUT = m_backgroundRemap[m_levelOnePage->colourTableRemap()];
|
|
} else
|
|
m_level1ActivePainter.attribute.backgroundCLUT = 0;
|
|
break;
|
|
case 0x1d: // New background
|
|
if (m_level >= 2)
|
|
m_level1ActivePainter.attribute.backgroundCLUT = level1ForegroundCLUT | m_backgroundRemap[m_levelOnePage->colourTableRemap()];
|
|
else
|
|
m_level1ActivePainter.attribute.backgroundCLUT = level1ForegroundCLUT;
|
|
break;
|
|
case 0x1e: // Hold mosaics
|
|
level1HoldMosaics = true;
|
|
break;
|
|
}
|
|
|
|
if (m_level < 2)
|
|
m_level1ActivePainter.result.attribute = m_level1ActivePainter.attribute;
|
|
else{
|
|
// Deal with incremental and decremental flash
|
|
rotateFlashMovement(m_level1ActivePainter.attribute.flash);
|
|
|
|
for (int t=0; t<2; t++)
|
|
for (int i=0; i<m_adapPassPainter[t].size(); i++)
|
|
rotateFlashMovement(m_adapPassPainter[t][i].attribute.flash);
|
|
|
|
// X/26 attributes
|
|
for (int t=0; t<3; t++)
|
|
for (int i=0; i<m_invocations[t].size(); i++) {
|
|
QList<X26Triplet> attributesHere = m_invocations[t].at(i).attributesMappedAt(r, c);
|
|
|
|
painter = (t == 0) ? &m_level1ActivePainter : &m_adapPassPainter[t-1][i];
|
|
|
|
// Adaptive Invocation painter: pick up the attributes we're NOT adapting from
|
|
// m_level1ActivePainter, which by now has taken into account all the attributes
|
|
// from the Level 1 page, Active Objects and the Local Enhancement Data
|
|
if (t == 1) {
|
|
if (!adapForeground)
|
|
painter->attribute.foregroundCLUT = m_level1ActivePainter.attribute.foregroundCLUT;
|
|
if (!adapBackground)
|
|
painter->attribute.backgroundCLUT = m_level1ActivePainter.attribute.backgroundCLUT;
|
|
if (!adapFlash)
|
|
painter->attribute.flash = m_level1ActivePainter.attribute.flash;
|
|
if (!adapDisplayAttrs)
|
|
painter->attribute.display = m_level1ActivePainter.attribute.display;
|
|
}
|
|
|
|
// QMultiMap::values returns QList with most recent value first...
|
|
for (int a=attributesHere.size()-1; a>=0; a--) {
|
|
const X26Triplet triplet = attributesHere.at(a);
|
|
|
|
bool applyAdapt = false;
|
|
|
|
// Adaptive Invocation that is applying an attribute
|
|
// If we're not tracking an Adaptive Invocation yet, start tracking this one
|
|
// Otherwise check if this Invocation is the the same one as we are tracking
|
|
if (t == 1) {
|
|
if (adapInvokeAttrs == -1) {
|
|
adapInvokeAttrs = i;
|
|
applyAdapt = true;
|
|
} else if (adapInvokeAttrs == i)
|
|
applyAdapt = true;
|
|
// else
|
|
// qDebug("Multiple adaptive object attributes");
|
|
}
|
|
|
|
switch (triplet.modeExt()) {
|
|
case 0x20: // Foreground colour
|
|
if (applyAdapt)
|
|
adapForeground = true;
|
|
painter->attribute.foregroundCLUT = triplet.data();
|
|
break;
|
|
case 0x23: // Background colour
|
|
if (applyAdapt)
|
|
adapBackground = true;
|
|
painter->attribute.backgroundCLUT = triplet.data();
|
|
break;
|
|
case 0x27: // Additional flash functions
|
|
if (applyAdapt)
|
|
adapFlash = true;
|
|
painter->attribute.flash.mode = triplet.data() & 0x03;
|
|
painter->attribute.flash.ratePhase = triplet.data() >> 2;
|
|
// For incremental/decremental 2Hz flash, start at phase 1
|
|
if (painter->attribute.flash.mode != 0 && painter->attribute.flash.ratePhase & 0x4)
|
|
painter->attribute.flash.phase2HzShown = 1;
|
|
else
|
|
painter->attribute.flash.phase2HzShown = painter->attribute.flash.ratePhase;
|
|
break;
|
|
case 0x2c: // Display attributes
|
|
if (applyAdapt)
|
|
adapDisplayAttrs = true;
|
|
painter->attribute.display.doubleHeight = triplet.data() & 0x01;
|
|
painter->attribute.display.boxingWindow = triplet.data() & 0x02;
|
|
painter->attribute.display.conceal = triplet.data() & 0x04;
|
|
painter->attribute.display.invert = triplet.data() & 0x10;
|
|
painter->attribute.display.underlineSeparated = triplet.data() & 0x20;
|
|
painter->attribute.display.doubleWidth = triplet.data() & 0x40;
|
|
// Cancelling separated mosaics with X/26 attribute
|
|
// also cancels the Level 1 separated mosaic attribute
|
|
if (t == 0 && !painter->attribute.display.underlineSeparated)
|
|
level1SeparatedMosaics = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
painter->result.attribute = painter->attribute;
|
|
}
|
|
}
|
|
|
|
// Level 1 character
|
|
if (c < 40 && m_rowHeight[r] != BottomHalf) {
|
|
m_level1ActivePainter.result.character.diacritical = 0;
|
|
if (m_levelOnePage->character(r, c) >= 0x20) {
|
|
m_level1ActivePainter.result.character.code = m_levelOnePage->character(r, c);
|
|
// Set to true on mosaic character - not on blast through alphanumerics
|
|
m_cellLevel1Mosaic[r][c] = level1Mosaics && (m_levelOnePage->character(r, c) & 0x20);
|
|
if (m_cellLevel1Mosaic[r][c]) {
|
|
m_level1ActivePainter.result.character.set = 24 + (level1SeparatedMosaics || m_level1ActivePainter.attribute.display.underlineSeparated);
|
|
level1HoldMosaicCharacter = m_levelOnePage->character(r, c);
|
|
level1HoldMosaicSeparated = level1SeparatedMosaics;
|
|
} else
|
|
m_level1ActivePainter.result.character.set = level1CharSet;
|
|
} else if (level1HoldMosaics) {
|
|
m_level1ActivePainter.result.character = { level1HoldMosaicCharacter, 24 + level1HoldMosaicSeparated, 0 };
|
|
} else
|
|
m_level1ActivePainter.result.character = { 0x20, 0, 0 };
|
|
} else
|
|
// In side panel or on bottom half of Level 1 double height row, no Level 1 characters here
|
|
m_level1ActivePainter.result.character = { 0x20, 0, 0 };
|
|
|
|
if (c < 40)
|
|
m_cellLevel1CharSet[r][c] = level1CharSet;
|
|
|
|
// X/26 characters
|
|
|
|
// Used to track if character was placed by X/26 data
|
|
// 0=Level 1 character, 1=Active Object or Local Enhancement Data, 2=Adaptive Object
|
|
int x26Character = 0;
|
|
|
|
if (m_level == 1 && !m_invocations[0].isEmpty()) {
|
|
// For Level 1.5 only do characters from Local Enhancements
|
|
// which is the last entry on the Active Objects QList
|
|
const textCharacter result = characterFromTriplets(m_invocations[0].constLast().charactersMappedAt(r, c), x26G0CharSet);
|
|
|
|
if (result.code != 0x00) {
|
|
m_level1ActivePainter.result.character = result;
|
|
x26Character = 1;
|
|
}
|
|
} else if (m_level >= 2)
|
|
for (int t=0; t<3; t++)
|
|
for (int i=0; i<m_invocations[t].size(); i++) {
|
|
painter = (t == 0) ? &m_level1ActivePainter : &m_adapPassPainter[t-1][i];
|
|
|
|
const textCharacter result = characterFromTriplets(m_invocations[t].at(i).charactersMappedAt(r, c), x26G0CharSet);
|
|
|
|
if (t == 0 && result.code == 0x00)
|
|
continue;
|
|
// For an Adaptive Invocation that is applying attribute(s) but not a character here
|
|
// pick up the character underneath so we can still place the attributes
|
|
if (t == 1 && adapInvokeAttrs == i && result.code == 0x00) {
|
|
painter->result.character = m_level1ActivePainter.result.character;
|
|
x26Character = 2;
|
|
continue;
|
|
}
|
|
|
|
painter->result.character = result;
|
|
if (painter->result.character.set == 24 && painter->attribute.display.underlineSeparated)
|
|
painter->result.character.set++;
|
|
|
|
if (t < 2 && result.code != 0x00)
|
|
x26Character = t + 1;
|
|
}
|
|
|
|
// Allow Active Objects or Local Enhancement Data to overlap bottom half of a Level 1 double height row
|
|
// where the character on the top half is normal size or double width
|
|
if (m_rowHeight[r] == BottomHalf && c < 40 && x26Character == 1 && m_level1ActivePainter.bottomHalfCell[c].fragment == NormalSize)
|
|
m_level1ActivePainter.bottomHalfCell[c].character.code = 0x00;
|
|
|
|
// Allow Adaptive Objects to always overlap bottom half of a Level 1 double height row
|
|
if (m_rowHeight[r] == BottomHalf && c < 40 && x26Character == 2)
|
|
m_level1ActivePainter.bottomHalfCell[c].character.code = 0x00;
|
|
|
|
// Work out which fragment of an enlarged character to display
|
|
for (int t=0; t<3; t++) {
|
|
for (int i=0; i<m_invocations[t].size(); i++) {
|
|
// This loop will only iterate once when t == 0 because m_level1ActivePainter is
|
|
// now the overall result of Level 1, Active Objects and Local Enhancement Data
|
|
painter = (t == 0) ? &m_level1ActivePainter : &m_adapPassPainter[t-1][i];
|
|
|
|
bool cellCovered = false;
|
|
|
|
// Deal with non-origin parts of enlarged characters if placed during previous iteration
|
|
if (painter->rightHalfCell.character.code != 0x00) {
|
|
painter->result = painter->rightHalfCell;
|
|
// Corner cases of right half of double-width characters overlapping
|
|
// a Level 1 double height row need this to avoid spurious characters below
|
|
if (painter->result.fragment == DoubleWidthRightHalf)
|
|
painter->bottomHalfCell[c].character.code = 0x00;
|
|
painter->rightHalfCell.character.code = 0x00;
|
|
cellCovered = true;
|
|
} else if (painter->bottomHalfCell[c].character.code != 0x00) {
|
|
painter->result = painter->bottomHalfCell[c];
|
|
painter->bottomHalfCell[c].character.code = 0x00;
|
|
cellCovered = true;
|
|
}
|
|
|
|
if (!cellCovered) {
|
|
// Cell is not covered by previous enlarged character
|
|
// Work out which fragments of enlarged characters are needed from size attributes,
|
|
// place origin of character here and other fragments into right half or bottom half
|
|
// painter buffer ready to be picked up on the next iteration
|
|
bool doubleHeight = painter->attribute.display.doubleHeight;
|
|
bool doubleWidth = painter->attribute.display.doubleWidth;
|
|
|
|
if (r == 0 || r > 22)
|
|
doubleHeight = false;
|
|
if (c == 39 || c == 39+m_rightSidePanelColumns || c == 71-m_leftSidePanelColumns || c == 71)
|
|
doubleWidth = false;
|
|
|
|
if (doubleHeight) {
|
|
if (doubleWidth) {
|
|
// Double size
|
|
painter->result.fragment = DoubleSizeTopLeftQuarter;
|
|
painter->bottomHalfCell[c] = painter->result;
|
|
painter->bottomHalfCell[c].fragment = DoubleSizeBottomLeftQuarter;
|
|
painter->rightHalfCell = painter->result;
|
|
painter->rightHalfCell.fragment = DoubleSizeTopRightQuarter;
|
|
painter->bottomHalfCell[c+1] = painter->result;
|
|
painter->bottomHalfCell[c+1].fragment = DoubleSizeBottomRightQuarter;
|
|
} else {
|
|
// Double height
|
|
painter->result.fragment = DoubleHeightTopHalf;
|
|
painter->bottomHalfCell[c] = painter->result;
|
|
painter->bottomHalfCell[c].fragment = DoubleHeightBottomHalf;
|
|
}
|
|
} else if (doubleWidth) {
|
|
// Double width
|
|
painter->result.fragment = DoubleWidthLeftHalf;
|
|
painter->rightHalfCell = painter->result;
|
|
painter->rightHalfCell.fragment = DoubleWidthRightHalf;
|
|
} else
|
|
// Normal size
|
|
painter->result.fragment = NormalSize;
|
|
|
|
// Now the enlargements and fragments are worked out, prevent Adaptive Objects that
|
|
// are NOT applying Display Attributes from trying to overlap wrong fragments of characters
|
|
// on the underlying page
|
|
if (t == 1 && !adapDisplayAttrs && painter->result.fragment != m_level1ActivePainter.result.fragment) {
|
|
painter->result.character.code = 0x00;
|
|
if (painter->result.fragment == DoubleWidthLeftHalf || painter->result.fragment == DoubleSizeTopLeftQuarter)
|
|
painter->rightHalfCell.character.code = 0x00;
|
|
if (painter->result.fragment == DoubleHeightTopHalf || painter->result.fragment == DoubleSizeTopLeftQuarter)
|
|
painter->bottomHalfCell[c].character.code = 0x00;
|
|
if (painter->result.fragment == DoubleSizeTopLeftQuarter && c < 71)
|
|
painter->bottomHalfCell[c+1].character.code = 0x00;
|
|
}
|
|
}
|
|
|
|
if (t == 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Top half of Level 1 double height row: normal or double width characters will cause a space
|
|
// with the same attributes to be on the bottom half
|
|
// Also occurs with bottom halves of X/26 double height characters overlapping a
|
|
// Level 1 top half row
|
|
if (m_rowHeight[r] == TopHalf && c < 40) {
|
|
if (m_level1ActivePainter.result.fragment != DoubleHeightTopHalf && m_level1ActivePainter.result.fragment != DoubleSizeTopLeftQuarter && m_level1ActivePainter.result.fragment != DoubleSizeTopRightQuarter) {
|
|
m_level1ActivePainter.bottomHalfCell[c] = m_level1ActivePainter.result;
|
|
m_level1ActivePainter.bottomHalfCell[c].character = { 0x20, 0, 0 };
|
|
m_level1ActivePainter.bottomHalfCell[c].fragment = NormalSize;
|
|
}
|
|
|
|
}
|
|
|
|
// Now we've finally worked out what characters and attributes are in place on
|
|
// the underlying page and Invoked Objects, work out which of those to actually render
|
|
if (m_level < 2)
|
|
m_cell[r][c] = m_level1ActivePainter.result;
|
|
else {
|
|
bool objectCell = false;
|
|
|
|
// Passive Objects highest priority, followed by Adaptive Objects
|
|
// Most recently invoked Object has priority
|
|
for (int t=1; t>=0; t--) {
|
|
for (int i=m_adapPassPainter[t].size()-1; i>=0; i--)
|
|
if (m_adapPassPainter[t][i].result.character.code != 0x00) {
|
|
m_cell[r][c] = m_adapPassPainter[t][i].result;
|
|
objectCell = true;
|
|
break;
|
|
}
|
|
if (objectCell)
|
|
break;
|
|
}
|
|
|
|
if (!objectCell)
|
|
// No Adaptive or Passive Object here: will either be Local Enhancement Data, Active Object
|
|
// or underlying Level 1 page
|
|
m_cell[r][c] = m_level1ActivePainter.result;
|
|
}
|
|
|
|
// Check for end of Adaptive Object row
|
|
if (adapInvokeAttrs != -1 && c == m_invocations[1].at(adapInvokeAttrs).rightMostColumn(r)) {
|
|
// Neutralise size attributes as they could interfere with double height stuff
|
|
// not sure if this is really necessary
|
|
m_adapPassPainter[0][adapInvokeAttrs].attribute.display.doubleHeight = false;
|
|
m_adapPassPainter[0][adapInvokeAttrs].attribute.display.doubleWidth = false;
|
|
adapInvokeAttrs = -1;
|
|
adapForeground = adapBackground = adapFlash = adapDisplayAttrs = false;
|
|
}
|
|
|
|
// Level 1 set-after spacing attributes
|
|
if (c < 40 && m_rowHeight[r] != BottomHalf)
|
|
switch (m_levelOnePage->character(r, c)) {
|
|
case 0x00 ... 0x07: // Alphanumeric and foreground colour
|
|
level1Mosaics = false;
|
|
level1ForegroundCLUT = m_levelOnePage->character(r, c);
|
|
if (m_level >= 2)
|
|
m_level1ActivePainter.attribute.foregroundCLUT = level1ForegroundCLUT | m_foregroundRemap[m_levelOnePage->colourTableRemap()];
|
|
else
|
|
m_level1ActivePainter.attribute.foregroundCLUT = level1ForegroundCLUT;
|
|
m_level1ActivePainter.attribute.display.conceal = false;
|
|
// Switch from mosaics to alpha resets hold mosaic character
|
|
level1HoldMosaicCharacter = 0x20;
|
|
level1HoldMosaicSeparated = false;
|
|
break;
|
|
case 0x10 ... 0x17: // Mosaic and foreground colour
|
|
level1Mosaics = true;
|
|
level1ForegroundCLUT = m_levelOnePage->character(r, c) & 0x07;
|
|
if (m_level >= 2)
|
|
m_level1ActivePainter.attribute.foregroundCLUT = level1ForegroundCLUT | m_foregroundRemap[m_levelOnePage->colourTableRemap()];
|
|
else
|
|
m_level1ActivePainter.attribute.foregroundCLUT = level1ForegroundCLUT;
|
|
m_level1ActivePainter.attribute.display.conceal = false;
|
|
break;
|
|
case 0x08: // Flashing
|
|
m_level1ActivePainter.attribute.flash.mode = 1;
|
|
m_level1ActivePainter.attribute.flash.ratePhase = 0;
|
|
break;
|
|
case 0x0d: // Double height
|
|
if (!m_level1ActivePainter.attribute.display.doubleHeight || m_level1ActivePainter.attribute.display.doubleWidth) {
|
|
// Change of size resets hold mosaic character
|
|
level1HoldMosaicCharacter = 0x20;
|
|
level1HoldMosaicSeparated = false;
|
|
}
|
|
m_level1ActivePainter.attribute.display.doubleHeight = true;
|
|
m_level1ActivePainter.attribute.display.doubleWidth = false;
|
|
break;
|
|
case 0x0e: // Double width
|
|
if (m_level1ActivePainter.attribute.display.doubleHeight || !m_level1ActivePainter.attribute.display.doubleWidth) {
|
|
// Change of size resets hold mosaic character
|
|
level1HoldMosaicCharacter = 0x20;
|
|
level1HoldMosaicSeparated = false;
|
|
}
|
|
m_level1ActivePainter.attribute.display.doubleHeight = false;
|
|
m_level1ActivePainter.attribute.display.doubleWidth = true;
|
|
break;
|
|
case 0x0f: // Double size
|
|
if (!m_level1ActivePainter.attribute.display.doubleHeight || !m_level1ActivePainter.attribute.display.doubleWidth) {
|
|
// Change of size resets hold mosaic character
|
|
level1HoldMosaicCharacter = 0x20;
|
|
level1HoldMosaicSeparated = false;
|
|
}
|
|
m_level1ActivePainter.attribute.display.doubleHeight = true;
|
|
m_level1ActivePainter.attribute.display.doubleWidth = true;
|
|
break;
|
|
case 0x1b: // ESC/switch
|
|
level1EscapeSwitch ^= true;
|
|
if (level1EscapeSwitch)
|
|
level1CharSet = m_level1SecondCharSet;
|
|
else
|
|
level1CharSet = m_level1DefaultCharSet;
|
|
break;
|
|
case 0x1f: // Release mosaics
|
|
level1HoldMosaics = false;
|
|
break;
|
|
}
|
|
|
|
if (m_cell[r][c] != previousCellContents)
|
|
setRefresh(r, c, true);
|
|
}
|
|
}
|
|
|
|
inline void TeletextPageDecode::rotateFlashMovement(flashFunctions &flash)
|
|
{
|
|
if (flash.ratePhase == 4) {
|
|
flash.phase2HzShown++;
|
|
if (flash.phase2HzShown == 4)
|
|
flash.phase2HzShown = 1;
|
|
} else if (flash.ratePhase == 5) {
|
|
flash.phase2HzShown--;
|
|
if (flash.phase2HzShown == 0)
|
|
flash.phase2HzShown = 3;
|
|
}
|
|
}
|
|
|
|
QColor TeletextPageDecode::cellQColor(int r, int c, ColourPart colourPart)
|
|
{
|
|
const bool newsFlashOrSubtitle = m_levelOnePage->controlBit(PageBase::C5Newsflash) || m_levelOnePage->controlBit(PageBase::C6Subtitle);
|
|
int resultCLUT = 0;
|
|
|
|
switch (colourPart) {
|
|
case Foreground:
|
|
if (!m_cell[r][c].attribute.display.invert)
|
|
resultCLUT = m_cell[r][c].attribute.foregroundCLUT;
|
|
else
|
|
resultCLUT = m_cell[r][c].attribute.backgroundCLUT;
|
|
break;
|
|
case Background:
|
|
if (!m_cell[r][c].attribute.display.invert)
|
|
resultCLUT = m_cell[r][c].attribute.backgroundCLUT;
|
|
else
|
|
resultCLUT = m_cell[r][c].attribute.foregroundCLUT;
|
|
break;
|
|
case FlashForeground:
|
|
if (!m_cell[r][c].attribute.display.invert)
|
|
resultCLUT = m_cell[r][c].attribute.foregroundCLUT ^ 8;
|
|
else
|
|
resultCLUT = m_cell[r][c].attribute.backgroundCLUT ^ 8;
|
|
break;
|
|
}
|
|
|
|
if (resultCLUT == 8) {
|
|
// Transparent CLUT - either Full Row Colour or Video
|
|
// Logic of table C.1 in spec implemented to find out which it is
|
|
if (m_cell[r][c].attribute.display.boxingWindow != newsFlashOrSubtitle)
|
|
return QColor(Qt::transparent);
|
|
|
|
int rowColour;
|
|
|
|
if (cellCharacterFragment(r, c) == TeletextPageDecode::DoubleHeightBottomHalf ||
|
|
cellCharacterFragment(r, c) == TeletextPageDecode::DoubleSizeBottomLeftQuarter ||
|
|
cellCharacterFragment(r, c) == TeletextPageDecode::DoubleSizeBottomRightQuarter)
|
|
rowColour = m_fullRowColour[r-1];
|
|
else
|
|
rowColour = m_fullRowColour[r];
|
|
|
|
if (rowColour == 8)
|
|
return QColor(Qt::transparent);
|
|
else
|
|
return m_levelOnePage->CLUTtoQColor(rowColour, m_level);
|
|
} else if (!m_cell[r][c].attribute.display.boxingWindow && newsFlashOrSubtitle)
|
|
return QColor(Qt::transparent);
|
|
|
|
return m_levelOnePage->CLUTtoQColor(resultCLUT, m_level);
|
|
}
|
|
|
|
QColor TeletextPageDecode::cellForegroundQColor(int r, int c)
|
|
{
|
|
return cellQColor(r, c, Foreground);
|
|
}
|
|
|
|
QColor TeletextPageDecode::cellBackgroundQColor(int r, int c)
|
|
{
|
|
return cellQColor(r, c, Background);
|
|
}
|
|
|
|
QColor TeletextPageDecode::cellFlashForegroundQColor(int r, int c)
|
|
{
|
|
return cellQColor(r, c, FlashForeground);
|
|
}
|
|
|
|
TeletextPageDecode::textCell& TeletextPageDecode::cellAtCharacterOrigin(int r, int c)
|
|
{
|
|
switch (cellCharacterFragment(r, c)) {
|
|
case TeletextPageDecode::DoubleHeightBottomHalf:
|
|
case TeletextPageDecode::DoubleSizeBottomLeftQuarter:
|
|
return m_cell[r-1][c];
|
|
case TeletextPageDecode::DoubleWidthRightHalf:
|
|
case TeletextPageDecode::DoubleSizeTopRightQuarter:
|
|
return m_cell[r][c-1];
|
|
case TeletextPageDecode::DoubleSizeBottomRightQuarter:
|
|
return m_cell[r-1][c-1];
|
|
default:
|
|
return m_cell[r][c];
|
|
}
|
|
}
|
|
|
|
inline void TeletextPageDecode::setFullScreenColour(int newColour)
|
|
{
|
|
if (newColour == 8 || m_levelOnePage->controlBit(PageBase::C5Newsflash) || m_levelOnePage->controlBit(PageBase::C6Subtitle)) {
|
|
m_finalFullScreenQColor = QColor(0, 0, 0, 0);
|
|
emit fullScreenColourChanged(QColor(0, 0, 0, 0));
|
|
return;
|
|
}
|
|
QColor newFullScreenQColor = m_levelOnePage->CLUTtoQColor(newColour, m_level);
|
|
m_finalFullScreenColour = newColour;
|
|
if (m_finalFullScreenQColor != newFullScreenQColor) {
|
|
m_finalFullScreenQColor = newFullScreenQColor;
|
|
emit fullScreenColourChanged(m_finalFullScreenQColor);
|
|
}
|
|
}
|
|
|
|
inline void TeletextPageDecode::setFullRowColour(int row, int newColour)
|
|
{
|
|
m_fullRowColour[row] = newColour;
|
|
|
|
if (newColour == 8 || m_levelOnePage->controlBit(PageBase::C5Newsflash) || m_levelOnePage->controlBit(PageBase::C6Subtitle)) {
|
|
m_fullRowQColor[row] = QColor(0, 0, 0, 0);
|
|
emit fullRowColourChanged(row, QColor(0, 0, 0, 0));
|
|
return;
|
|
}
|
|
QColor newFullRowQColor = m_levelOnePage->CLUTtoQColor(newColour, m_level);
|
|
if (m_fullRowQColor[row] != newFullRowQColor) {
|
|
for (int c=0; c<72; c++) {
|
|
if (m_cell[row][c].attribute.foregroundCLUT == 8 || m_cell[row][c].attribute.backgroundCLUT == 8)
|
|
setRefresh(row, c, true);
|
|
}
|
|
m_fullRowQColor[row] = newFullRowQColor;
|
|
emit fullRowColourChanged(row, m_fullRowQColor[row]);
|
|
}
|
|
}
|