22 Commits

Author SHA1 Message Date
G.K.MacGregor
4d4bcc6151 Tag version 0.5.4-alpha 2022-05-30 19:44:55 +01:00
G.K.MacGregor
5b6fd56a37 Remove unused variable 2022-05-22 15:23:38 +01:00
G.K.MacGregor
bcc0d0d8e7 Keep keyboard focus if scene item other than proxy widget is clicked 2022-05-15 13:08:58 +01:00
G.K.MacGregor
ec4bdd6f7f Only check Local Object pointers 2022-05-15 11:22:19 +01:00
G.K.MacGregor
a8798260dc Rename Level method and variable 2022-05-02 22:24:04 +01:00
G.K.MacGregor
50582a95a4 Fix typo in comment 2022-05-02 22:22:58 +01:00
G.K.MacGregor
1302205911 Fix selecting X/26 triplets by keyboard 2022-05-01 16:49:23 +01:00
G.K.MacGregor
73c1b482e2 Force refresh in decoder constructor
Fixes broken initial rendering of pages with side panels.
2022-05-01 14:29:30 +01:00
G.K.MacGregor
d5487140cf Split compositional links from page enhancements 2022-05-01 12:55:08 +01:00
G.K.MacGregor
45bfa80340 Rename insert widgets 2022-04-28 21:37:44 +01:00
G.K.MacGregor
661a85066b Show Object related errors in X/26 triplet list 2022-04-05 22:11:01 +01:00
G.K.MacGregor
e16bb15310 Split decoder and render
decode.* will solely decode the teletext packets into a grid of characters and
colours and other attributes stored in an agnostic way, then render.* will read
this grid and render the characters onto the screen with the Qt specific
methods.
2022-04-02 22:38:38 +01:00
G.K.MacGregor
74ebc91ee6 Split Insert triplet button, and add mode menus
The "insert before" and "insert after" buttons will drop a menu to
select the new triplet mode, allowing triplets with different modes to
be inserted with one less mouse click.

The "insert copy" button still does the old behaviour of inserting a
copy of the currently selected triplet.
2022-03-06 12:00:53 +00:00
G.K.MacGregor
cda458b5bf Trim layout margins within triplet parameter stack 2022-02-14 23:00:47 +00:00
G.K.MacGregor
9d27ae24e7 Show selection size in status bar
Also remove the "subpage", "row" and "column" texts from the status
bar to better cope with tiny screens.
2022-02-13 18:50:50 +00:00
G.K.MacGregor
1eeeafb51e Show CLUT 1:0 as transparent in widgets 2022-02-08 17:31:34 +00:00
G.K.MacGregor
4aa77395c0 Tag version 0.5.3-alpha 2022-01-05 21:45:17 +00:00
G.K.MacGregor
ae1aef63f9 Fix incorrect C8 and C14 bits on t42 exporing 2022-01-05 20:57:26 +00:00
G.K.MacGregor
406ab6c6ed Update copyright notices to 2022 2021-12-31 21:40:36 +00:00
G.K.MacGregor
2da8da8c8e Tag version 0.5.2-alpha 2021-12-28 12:33:58 +00:00
G.K.MacGregor
c2057e979d Fix SC incorrectly saved as hexadecimal 2021-12-28 12:22:29 +00:00
G.K.MacGregor
f5402d216a Show filename when asking about unsaved changes 2021-12-14 22:06:18 +00:00
40 changed files with 2402 additions and 1638 deletions

View File

@@ -14,11 +14,11 @@ Features
- Configurable zoom. - Configurable zoom.
- View teletext pages in 4:3, 16:9 pillar-box and 16:9 stretch aspect ratios. - View teletext pages in 4:3, 16:9 pillar-box and 16:9 stretch aspect ratios.
Although designed on and developed for Linux, the Qt 5 libraries are cross platform so a Windows executable can be built. A Windows executable can be found within the "Releases" link, compiled on a Linux host using [MXE](https://github.com/mxe/mxe) based on [these instructions](https://blog.8bitbuddhism.com/2018/08/22/cross-compiling-windows-applications-with-mxe/). After MXE is installed `make qtbase` will be enough to build QTeletextMaker. Although designed on and developed for Linux, the Qt 5 libraries are cross platform so a Windows executable can be built. A Windows executable can be found within the "Releases" link, compiled on a Linux host using [MXE](https://github.com/mxe/mxe) based on [these instructions](https://blog.8bitbuddhism.com/2018/08/22/cross-compiling-windows-applications-with-mxe/). After MXE is installed `make qtbase` should be enough to build QTeletextMaker.
## Building ## Building
### Linux ### Linux
Install the QtCore, QtGui and QtWidgets libraries and build headers, along with the qmake tool. Then type `qmake && make -j3` in a terminal, you can replace -j3 with the number of processor cores used for the compile process. Install the QtCore, QtGui and QtWidgets libraries and build headers, along with the qmake tool. Then type `qmake5 && make -j3` in a terminal, you can replace -j3 with the number of processor cores used for the compile process.
The above will place the qteletextmaker executable in the same directory as the source, type `./qteletextmaker` in the terminal to launch. Optionally, type `make install` afterwards to place the executable into /usr/local/bin. The above will place the qteletextmaker executable in the same directory as the source, type `./qteletextmaker` in the terminal to launch. Optionally, type `make install` afterwards to place the executable into /usr/local/bin.

992
decode.cpp Normal file
View File

@@ -0,0 +1,992 @@
/*
* Copyright (C) 2020-2022 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 <QMultiMap>
//#include <QTime>
#include <QPair>
#include <algorithm>
#include <vector>
#include "decode.h"
TeletextPageDecode::TeletextPageDecode()
{
m_level = 0;
for (int r=0; r<25; r++)
for (int c=0; c<72; c++)
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;
m_textLayer.push_back(&m_level1Layer);
m_textLayer.push_back(new EnhanceLayer);
}
TeletextPageDecode::~TeletextPageDecode()
{
while (m_textLayer.size()>1) {
delete m_textLayer.back();
m_textLayer.pop_back();
}
}
void TeletextPageDecode::setRefresh(int r, int c, bool refresh)
{
m_refresh[r][c] = refresh;
}
void TeletextPageDecode::setTeletextPage(LevelOnePage *newCurrentPage)
{
m_levelOnePage = newCurrentPage;
m_level1Layer.setTeletextPage(newCurrentPage);
updateSidePanels();
}
void TeletextPageDecode::setLevel(int level)
{
if (level == m_level)
return;
m_level = level;
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::buildEnhanceMap(TextLayer *enhanceLayer, int tripletNumber)
{
bool terminatorFound=false;
ActivePosition activePosition;
const X26Triplet *x26Triplet;
int originModifierR=0;
int originModifierC=0;
do {
x26Triplet = &m_levelOnePage->enhancements()->at(tripletNumber);
if (x26Triplet->isRowTriplet())
// Row address group
switch (x26Triplet->mode()) {
case 0x00: // Full screen colour
if (m_level >= 2 && ((x26Triplet->data() & 0x60) == 0x00) && !activePosition.isDeployed())
enhanceLayer->setFullScreenColour(x26Triplet->data());
break;
case 0x01: // Full row colour
if (m_level >= 2 && activePosition.setRow(x26Triplet->addressRow()) && ((x26Triplet->data() & 0x60) == 0x00 || (x26Triplet->data() & 0x60) == 0x60))
enhanceLayer->setFullRowColour(activePosition.row(), x26Triplet->data() & 0x1f, (x26Triplet->data() & 0x60) == 0x60);
break;
case 0x04: // Set active position
if (activePosition.setRow(x26Triplet->addressRow()) && m_level >= 2 && x26Triplet->data() < 40)
activePosition.setColumn(x26Triplet->data());
break;
case 0x07: // Address row 0
if (x26Triplet->address() == 0x3f && !activePosition.isDeployed()) {
activePosition.setRow(0);
activePosition.setColumn(8);
if (m_level >= 2 && ((x26Triplet->data() & 0x60) == 0x00 || (x26Triplet->data() & 0x60) == 0x60))
enhanceLayer->setFullRowColour(0, x26Triplet->data() & 0x1f, (x26Triplet->data() & 0x60) == 0x60);
}
break;
case 0x10: // Origin modifier
if (m_level >= 2 && (tripletNumber+1) < m_levelOnePage->enhancements()->size() && m_levelOnePage->enhancements()->at(tripletNumber+1).mode() >= 0x11 && m_levelOnePage->enhancements()->at(tripletNumber+1).mode() <= 0x13 && x26Triplet->address() >= 40 && x26Triplet->data() < 72) {
originModifierR = x26Triplet->address()-40;
originModifierC = x26Triplet->data();
}
break;
case 0x11 ... 0x13: // Invoke Object
if (m_level >= 2) {
if ((x26Triplet->address() & 0x18) == 0x08) {
// Local Object
// Check if the pointer in the Invocation triplet is valid
// Can't point to triplets 13-15; only triplets 0-12 per packet
if ((x26Triplet->data() & 0x0f) > 12)
break;
int tripletPointer = ((x26Triplet->data() >> 4) | ((x26Triplet->address() & 1) << 3)) * 13 + (x26Triplet->data() & 0x0f);
// Can't point to triplet beyond the end of the Local Enhancement Data
if ((tripletPointer+1) >= m_levelOnePage->enhancements()->size())
break;
// Check if we're pointing to an actual Object Definition of the same type
if ((x26Triplet->mode() | 0x04) != m_levelOnePage->enhancements()->at(tripletPointer).mode())
break;
// The Object Definition can't declare it's at triplet 13-15; only triplets 0-12 per packet
if ((m_levelOnePage->enhancements()->at(tripletPointer).data() & 0x0f) > 12)
break;
// Check if the Object Definition triplet is where it declares it is
if ((((m_levelOnePage->enhancements()->at(tripletPointer).data() >> 4) | ((m_levelOnePage->enhancements()->at(tripletPointer).address() & 1) << 3)) * 13 + (m_levelOnePage->enhancements()->at(tripletPointer).data() & 0x0f)) != tripletPointer)
break;
// Check if (sub)Object type can be invoked by Object type we're within
if (enhanceLayer->objectType() >= (x26Triplet->mode() & 0x03))
break;
// Is the object required at the current presentation Level?
if (m_level == 2 && (m_levelOnePage->enhancements()->at(tripletPointer).address() & 0x08) == 0x00)
break;
if (m_level == 3 && (m_levelOnePage->enhancements()->at(tripletPointer).address() & 0x10) == 0x00)
break;
EnhanceLayer *newLayer = new EnhanceLayer;
m_textLayer.push_back(newLayer);
newLayer->setObjectType(x26Triplet->mode() & 0x03);
newLayer->setOrigin(enhanceLayer->originR() + activePosition.row() + originModifierR, enhanceLayer->originC() + activePosition.column() + originModifierC);
buildEnhanceMap(newLayer, tripletPointer+1);
} else
qDebug("POP or GPOP");
originModifierR = originModifierC = 0;
}
break;
case 0x15 ... 0x17: // Define Object, also used as terminator
terminatorFound = true;
break;
case 0x1f: // Terminator
if (x26Triplet->address() == 63)
terminatorFound = true;
break;
}
else {
// Column address group
bool columnTripletActioned = true;
switch (x26Triplet->mode()) {
// First we deal with column triplets that are also valid at Level 1.5
case 0x0b: // G3 mosaic character at Level 2.5
if (m_level <= 1)
break;
// fall-through
case 0x02: // G3 mosaic character at Level 1.5
case 0x0f: // G2 character
case 0x10 ... 0x1f: // Diacritical mark
if (activePosition.setColumn(x26Triplet->addressColumn()) && x26Triplet->data() >= 0x20)
enhanceLayer->enhanceMap.insert(qMakePair(activePosition.row(), activePosition.column()), qMakePair(x26Triplet->mode() | 0x20, x26Triplet->data()));
break;
// Make sure that PDC and reserved triplets don't affect the Active Position
case 0x04 ... 0x06: // 0x04 and 0x05 are reserved, 0x06 for PDC
case 0x0a: // Reserved
break;
default:
columnTripletActioned = false;
}
// All remaining possible column triplets at Level 2.5 affect the Active Position
if (m_level >= 2 && !columnTripletActioned && activePosition.setColumn(x26Triplet->addressColumn()))
enhanceLayer->enhanceMap.insert(qMakePair(activePosition.row(), activePosition.column()), qMakePair(x26Triplet->mode() | 0x20, x26Triplet->data()));
}
tripletNumber++;
} while (!terminatorFound && tripletNumber < m_levelOnePage->enhancements()->size());
}
void TeletextPageDecode::decodePage()
{
int currentFullRowColour, downwardsFullRowColour;
int renderedFullScreenColour;
struct {
bool operator() (TextLayer *i, TextLayer *j) { return (i->objectType() < j->objectType()); }
} compareLayer;
// QTime renderPageTime;
// renderPageTime.start();
updateSidePanels();
while (m_textLayer.size()>2) {
delete m_textLayer.back();
m_textLayer.pop_back();
}
renderedFullScreenColour = (m_level >= 2) ? m_levelOnePage->defaultScreenColour() : 0;
downwardsFullRowColour = (m_level >= 2) ? m_levelOnePage->defaultRowColour() : 0;
setFullScreenColour(renderedFullScreenColour);
for (int r=0; r<25; r++)
setFullRowColour(r, downwardsFullRowColour);
m_textLayer[1]->enhanceMap.clear();
if (m_level > 0 && !m_levelOnePage->enhancements()->isEmpty()) {
m_textLayer[1]->setFullScreenColour(-1);
for (int r=0; r<25; r++)
m_textLayer[1]->setFullRowColour(r, -1, false);
buildEnhanceMap(m_textLayer[1]);
if (m_textLayer.size() > 2)
std::stable_sort(m_textLayer.begin()+2, m_textLayer.end(), compareLayer);
if (m_level >= 2) {
if (m_textLayer[1]->fullScreenColour() != -1)
downwardsFullRowColour = m_textLayer[1]->fullScreenColour();
for (int r=0; r<25; r++) {
for (int l=0; l<2; l++) {
if (r == 0 && m_textLayer[l]->fullScreenColour() != - 1)
renderedFullScreenColour = m_textLayer[l]->fullScreenColour();
if (m_textLayer[l]->fullRowColour(r) == - 1)
currentFullRowColour = downwardsFullRowColour;
else {
currentFullRowColour = m_textLayer[l]->fullRowColour(r);
if (m_textLayer[l]->fullRowDownwards(r))
downwardsFullRowColour = currentFullRowColour;
}
}
setFullRowColour(r ,currentFullRowColour);
}
setFullScreenColour(renderedFullScreenColour);
}
}
for (int r=0; r<25; r++)
decodeRow(r);
// qDebug("Full page render: %d ms", renderPageTime.elapsed());
}
void TeletextPageDecode::decodeRow(int r)
{
int c;
int phaseNumberRender = 0;
bool decodeNextRow = false;
bool applyRightHalf = false;
bool previouslyDoubleHeight, previouslyBottomHalf, underlined;
bool doubleHeightFound = false;
textCharacter resultCharacter, layerCharacter;
applyAttributes layerApplyAttributes;
textAttributes underlyingAttributes, resultAttributes;
int level1CharSet;
for (c=0; c<72; c++) {
textCell oldTextCell = m_cell[r][c];
resultAttributes = underlyingAttributes;
for (int l=0; l<m_textLayer.size(); l++) {
layerCharacter = m_textLayer[l]->character(r, c);
if (layerCharacter.code != 0x00)
resultCharacter = layerCharacter;
if (l == 0) {
// m_cell[r][c].level1Mosaic = (resultCharacter.set == 24 || resultCharacter.set == 25) && m_levelOnePage->character(r, c) >= 0x20;
m_cell[r][c].level1Mosaic = (resultCharacter.set == 24 || resultCharacter.set == 25);
if (!m_cell[r][c].level1Mosaic)
level1CharSet = resultCharacter.set;
m_cell[r][c].level1CharSet = level1CharSet;
}
layerApplyAttributes = { false, false, false, false, false, false, false, false };
m_textLayer[l]->attributes(r, c, &layerApplyAttributes);
if (layerApplyAttributes.copyAboveAttributes) {
resultAttributes = m_cell[r-1][c].attribute;
layerApplyAttributes.copyAboveAttributes = false;
break;
}
if (layerApplyAttributes.applyForeColour) {
resultAttributes.foreColour = layerApplyAttributes.attribute.foreColour;
if (l == 0 && m_level >= 2)
resultAttributes.foreColour |= m_foregroundRemap[m_levelOnePage->colourTableRemap()];
}
if (layerApplyAttributes.applyBackColour) {
resultAttributes.backColour = layerApplyAttributes.attribute.backColour;
if (l == 0) {
if (m_level >= 2)
if (resultAttributes.backColour == 0x20)
resultAttributes.backColour = (c > 39 || m_levelOnePage->blackBackgroundSubst()) ? m_fullRowColour[r] : m_backgroundRemap[m_levelOnePage->colourTableRemap()];
else
resultAttributes.backColour |= m_backgroundRemap[m_levelOnePage->colourTableRemap()];
else
if (resultAttributes.backColour == 0x20)
resultAttributes.backColour = 0x00;
}
}
if (layerApplyAttributes.applyFlash) {
//BUG Adaptive Objects disrupt inc/dec flash
resultAttributes.flash = layerApplyAttributes.attribute.flash;
if (resultAttributes.flash.mode != 0)
phaseNumberRender = (resultAttributes.flash.ratePhase == 4 || resultAttributes.flash.ratePhase == 5) ? 1 : resultAttributes.flash.ratePhase;
}
if (layerApplyAttributes.applyDisplayAttributes)
resultAttributes.display = layerApplyAttributes.attribute.display;
else {
// Selecting contiguous mosaics wih a triplet will override an earlier Level 1 separated mosaics attribute until a further Level 1 contiguous mosaic attribute is encountered
resultAttributes.display.forceContiguous = (layerApplyAttributes.applyContiguousOnly) ? false : underlyingAttributes.display.forceContiguous;
if (layerApplyAttributes.applyTextSizeOnly) {
resultAttributes.display.doubleHeight = layerApplyAttributes.attribute.display.doubleHeight;
resultAttributes.display.doubleWidth = layerApplyAttributes.attribute.display.doubleWidth;
}
if (layerApplyAttributes.applyBoxingOnly)
resultAttributes.display.boxingWindow = layerApplyAttributes.attribute.display.boxingWindow;
if (layerApplyAttributes.applyConcealOnly || layerApplyAttributes.applyForeColour)
resultAttributes.display.conceal = layerApplyAttributes.attribute.display.conceal;
}
if (m_textLayer[l]->objectType() <= 1)
underlyingAttributes = resultAttributes;
if (m_level == 0)
break;
}
underlined = false;
if (resultAttributes.display.underlineSeparated) {
if (resultCharacter.set == 24)
resultCharacter.set = 25;
else
underlined = resultCharacter.set < 24;
}
if (resultAttributes.display.forceContiguous && resultCharacter.set == 25)
resultCharacter.set = 24;
resultAttributes.flash.phaseNumber = phaseNumberRender;
previouslyDoubleHeight = m_cell[r][c].attribute.display.doubleHeight;
previouslyBottomHalf = m_cell[r][c].bottomHalf;
m_cell[r][c].character = resultCharacter;
m_cell[r][c].attribute = resultAttributes;
if (m_cell[r][c] != oldTextCell) {
m_refresh[r][c] = true;
if (static_cast<Level1Layer *>(m_textLayer[0])->rowHeight(r) == Level1Layer::TopHalf) {
m_refresh[r+1][c] = true;
decodeNextRow = true;
}
if ((m_cell[r][c].attribute.display.doubleHeight || oldTextCell.attribute.display.doubleHeight) && r < 25)
m_refresh[r+1][c] = true;
if ((m_cell[r][c].attribute.display.doubleWidth || oldTextCell.attribute.display.doubleWidth) && c < 72)
m_refresh[r][c+1] = true;
if (((m_cell[r][c].attribute.display.doubleHeight && m_cell[r][c].attribute.display.doubleWidth) || (oldTextCell.attribute.display.doubleHeight && oldTextCell.attribute.display.doubleWidth)) && r < 25 && c < 72)
m_refresh[r+1][c+1] = true;
}
if (resultAttributes.flash.ratePhase == 4 && ++phaseNumberRender == 4)
phaseNumberRender = 1;
if (resultAttributes.flash.ratePhase == 5 && --phaseNumberRender == 0)
phaseNumberRender = 3;
if (r > 0)
m_cell[r][c].bottomHalf = m_cell[r-1][c].attribute.display.doubleHeight && !m_cell[r-1][c].bottomHalf;
if ((resultAttributes.display.doubleHeight != previouslyDoubleHeight) || (m_cell[r][c].bottomHalf != previouslyBottomHalf))
decodeNextRow = true;
m_cell[r][c].rightHalf = applyRightHalf;
if (resultAttributes.display.doubleHeight)
doubleHeightFound = true;
if (resultAttributes.display.doubleWidth || (m_cell[r][c].bottomHalf && c > 0 && m_cell[r-1][c-1].rightHalf))
applyRightHalf ^= true;
else
applyRightHalf = false;
}
if (decodeNextRow && r<24)
decodeRow(r+1);
}
textCell& TeletextPageDecode::cellAtCharacterOrigin(int r, int c)
{
/* if (m_cell[r][c].bottomHalf && r > 0) {
if (m_cell[r][c].rightHalf && c > 0)
// Double size
return m_cell[r-1][c-1];
else
// Double height
return m_cell[r-1][c];
} else {
if (m_cell[r][c].rightHalf && c > 0)
// Double width
return m_cell[r][c-1];
else
// Normal size
return m_cell[r][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];
}
}
QColor TeletextPageDecode::cellQColor(int r, int c, ColourPart colourPart)
{
const textCell& cell = cellAtCharacterOrigin(r, c);
const bool newsFlashOrSubtitle = m_levelOnePage->controlBit(PageBase::C5Newsflash) || m_levelOnePage->controlBit(PageBase::C6Subtitle);
int resultCLUT;
switch (colourPart) {
case Foreground:
if (!cell.attribute.display.invert)
resultCLUT = cell.attribute.foreColour;
else
resultCLUT = cell.attribute.backColour;
break;
case Background:
if (!cell.attribute.display.invert)
resultCLUT = cell.attribute.backColour;
else
resultCLUT = cell.attribute.foreColour;
break;
case FlashForeground:
if (!cell.attribute.display.invert)
resultCLUT = cell.attribute.foreColour ^ 8;
else
resultCLUT = cell.attribute.backColour ^ 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 (cell.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 (!cell.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::CharacterFragment TeletextPageDecode::cellCharacterFragment(int r, int c) const
{
if (m_cell[r][c].bottomHalf && r > 0) {
if (m_cell[r][c].rightHalf && c > 0)
return CharacterFragment::DoubleSizeBottomRightQuarter;
else if (m_cell[r-1][c].attribute.display.doubleWidth)
return CharacterFragment::DoubleSizeBottomLeftQuarter;
else
return CharacterFragment::DoubleHeightBottomHalf;
} else if (m_cell[r][c].rightHalf && c > 0) {
if (m_cell[r][c-1].attribute.display.doubleHeight)
return CharacterFragment::DoubleSizeTopRightQuarter;
else
return CharacterFragment::DoubleWidthRightHalf;
}
if (m_cell[r][c].attribute.display.doubleHeight) {
if (m_cell[r][c].attribute.display.doubleWidth)
return CharacterFragment::DoubleSizeTopLeftQuarter;
else
return CharacterFragment::DoubleHeightTopHalf;
} else if (m_cell[r][c].attribute.display.doubleWidth)
return CharacterFragment::DoubleWidthLeftHalf;
return CharacterFragment::NormalSize;
}
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.foreColour == 8 || m_cell[row][c].attribute.backColour == 8)
setRefresh(row, c, true);
}
m_fullRowQColor[row] = newFullRowQColor;
emit fullRowColourChanged(row, m_fullRowQColor[row]);
}
}
void TextLayer::setTeletextPage(LevelOnePage *newCurrentPage) { m_levelOnePage = newCurrentPage; }
void TextLayer::setFullScreenColour(int newColour) { m_layerFullScreenColour = newColour; }
void TextLayer::setFullRowColour(int r, int newColour, bool newDownwards)
{
m_layerFullRowColour[r] = newColour;
m_layerFullRowDownwards[r] = newDownwards;
}
void EnhanceLayer::setObjectType(int newObjectType) { m_objectType = newObjectType; }
void EnhanceLayer::setOrigin(int r, int c)
{
m_originR = r;
m_originC = c;
}
Level1Layer::Level1Layer()
{
for (int r=0; r<25; r++) {
m_rowHasDoubleHeightAttr[r] = false;
m_rowHeight[r] = Normal;
}
}
EnhanceLayer::EnhanceLayer()
{
for (int r=0; r<25; r++) {
m_layerFullRowColour[r] = -1;
m_layerFullRowDownwards[r] = false;
}
}
textCharacter EnhanceLayer::character(int r, int c)
{
r -= m_originR;
c -= m_originC;
if (r < 0 || c < 0)
return { 0, 0 };
// QPair.first is triplet mode, QPair.second is triplet data
QList<QPair<int, int>> enhancements = enhanceMap.values(qMakePair(r, c));
if (enhancements.size() > 0)
for (int i=0; i<enhancements.size(); i++)
switch (enhancements.at(i).first) {
case 0x2b: // G3 mosaic character at Level 2.5
case 0x22: // G3 mosaic character at Level 1.5
return { enhancements.at(i).second, 26 };
case 0x29: // G0 character at Level 2.5
return { enhancements.at(i).second, 0 };
case 0x2f: // G2 character
return { enhancements.at(i).second, 7 };
case 0x30 ... 0x3f: // Diacritical
// Deal with @ symbol replacing * symbol - clause 15.6.1 note 3 in spec
if (enhancements.at(i).first == 0x30 && enhancements.at(i).second == 0x2a)
return { 0x40, 0 };
else
return { enhancements.at(i).second, 0, enhancements.at(i).first & 0x0f };
case 0x21: // G1 character
if ((enhancements.at(i).second) >= 0x20)
return { enhancements.at(i).second, (enhancements.at(i).second & 0x20) ? 24 : 0 };
}
return { 0, 0 };
}
void EnhanceLayer::attributes(int r, int c, applyAttributes *layerApplyAttributes)
{
r -= m_originR;
c -= m_originC;
if (r < 0 || c < 0)
return;
if (m_objectType == 2) {
// Adaptive Object - find rightmost column addressed on this row if we haven't already
if (r != m_rowCached) {
m_rightMostColumn[r] = 0;
m_rowCached = r;
for (int cc=39; cc>0; cc--)
if (enhanceMap.contains(qMakePair(r, cc))) {
m_rightMostColumn[r] = cc;
break;
}
}
// On new row, default to attributes already on page
// At end of rightmost column, let go of all attributes
if (c == 0 || c == m_rightMostColumn[r]+1)
m_applyAttributes = { false, false, false, false, false, false, false, false };
else {
// Re-apply attributes that Object has defined previously on this row
if (m_applyAttributes.applyForeColour) {
layerApplyAttributes->applyForeColour = true;
layerApplyAttributes->attribute.foreColour = m_applyAttributes.attribute.foreColour;
}
if (m_applyAttributes.applyBackColour) {
layerApplyAttributes->applyBackColour = true;
layerApplyAttributes->attribute.backColour = m_applyAttributes.attribute.backColour;
}
//BUG Adaptive Objects disrupt inc/dec flash
if (m_applyAttributes.applyFlash) {
layerApplyAttributes->applyFlash = true;
layerApplyAttributes->attribute.flash.mode = m_applyAttributes.attribute.flash.mode;
layerApplyAttributes->attribute.flash.ratePhase = m_applyAttributes.attribute.flash.ratePhase;
}
if (m_applyAttributes.applyDisplayAttributes) {
layerApplyAttributes->applyDisplayAttributes = true;
layerApplyAttributes->attribute.display = m_applyAttributes.attribute.display;
}
}
}
if (m_objectType == 3) {
if (r == 0 && c == 0) {
// Passive Objects always start with all these default attributes
m_applyAttributes.applyForeColour = true;
m_applyAttributes.attribute.foreColour = 0x07;
m_applyAttributes.applyBackColour = true;
m_applyAttributes.attribute.backColour = 0x00;
m_applyAttributes.applyDisplayAttributes = true;
m_applyAttributes.applyFlash = true;
m_applyAttributes.attribute.flash.mode = 0;
m_applyAttributes.attribute.flash.ratePhase = 0;
m_applyAttributes.attribute.display.doubleHeight = false;
m_applyAttributes.attribute.display.doubleWidth = false;
m_applyAttributes.attribute.display.boxingWindow = false;
m_applyAttributes.attribute.display.conceal = false;
m_applyAttributes.attribute.display.invert = false;
m_applyAttributes.attribute.display.underlineSeparated = false;
m_applyAttributes.attribute.display.forceContiguous = false;
}
if (character(r+m_originR, c+m_originC).code == 0x00)
// Passive Object attributes only apply where it also defines a character
// In this case, wrench the pointer-parameter to alter only the attributes of the Object
layerApplyAttributes = &m_applyAttributes;
else
*layerApplyAttributes = m_applyAttributes;
}
// QPair.first is triplet mode, QPair.second is triplet data
QList<QPair<int, int>> enhancements = enhanceMap.values(qMakePair(r, c));
for (int i=0; i<enhancements.size(); i++)
switch (enhancements.at(i).first) {
case 0x20: // Foreground colour
if ((enhancements.at(i).second & 0x60) == 0) {
layerApplyAttributes->applyForeColour = true;
layerApplyAttributes->attribute.foreColour = enhancements.at(i).second;
}
break;
case 0x23: // Background colour
if ((enhancements.at(i).second & 0x60) == 0) {
layerApplyAttributes->applyBackColour = true;
layerApplyAttributes->attribute.backColour = enhancements.at(i).second;
}
break;
case 0x27: // Additional flash functions
if ((enhancements.at(i).second & 0x60) == 0 && (enhancements.at(i).second & 0x18) != 0x18) { // Avoid reserved rate/phase
layerApplyAttributes->applyFlash = true;
layerApplyAttributes->attribute.flash.mode = enhancements.at(i).second & 0x03;
layerApplyAttributes->attribute.flash.ratePhase = (enhancements.at(i).second >> 2) & 0x07;
}
break;
case 0x2c: // Display attributes
layerApplyAttributes->applyDisplayAttributes = true;
layerApplyAttributes->attribute.display.doubleHeight = enhancements.at(i).second & 0x01;
layerApplyAttributes->attribute.display.boxingWindow = enhancements.at(i).second & 0x02;
layerApplyAttributes->attribute.display.conceal = enhancements.at(i).second & 0x04;
layerApplyAttributes->attribute.display.invert = enhancements.at(i).second & 0x10;
layerApplyAttributes->attribute.display.underlineSeparated = enhancements.at(i).second & 0x20;
// Selecting contiguous mosaics wih a triplet will override an earlier Level 1 separated mosaics attribute
layerApplyAttributes->attribute.display.forceContiguous = !layerApplyAttributes->attribute.display.underlineSeparated;
layerApplyAttributes->attribute.display.doubleWidth = enhancements.at(i).second & 0x40;
break;
}
if (m_objectType >= 2)
m_applyAttributes = *layerApplyAttributes;
}
void Level1Layer::updateRowCache(int r)
{
level1CacheAttributes buildCacheAttributes;
bool doubleHeightAttrFound = false;
for (int c=0; c<40; c++) {
unsigned char charCode = m_levelOnePage->character(r, c);
// Set at spacing attributes
switch (charCode) {
case 0x0c: // Normal size
if (buildCacheAttributes.sizeCode != 0x0c) // Size CHANGE resets held mosaic to space
buildCacheAttributes.holdChar = 0x20;
buildCacheAttributes.sizeCode = 0x0c;
break;
case 0x19: // Contiguous mosaics
buildCacheAttributes.separated = false;
break;
case 0x1a: // Separated mosaics
buildCacheAttributes.separated = true;
break;
case 0x1c: // Black background
buildCacheAttributes.backColour = 0x00;
break;
case 0x1d: // New background
buildCacheAttributes.backColour = buildCacheAttributes.foreColour & 0x07;
break;
case 0x1e: // Hold mosaics
buildCacheAttributes.held = true;
break;
}
if (buildCacheAttributes.mosaics && (charCode & 0x20)) {
buildCacheAttributes.holdChar = charCode;
buildCacheAttributes.holdSeparated = buildCacheAttributes.separated;
}
m_attributeCache[c] = buildCacheAttributes;
// Set-after spacing attributes
switch (charCode) {
case 0x00 ... 0x07: // Alphanumeric + foreground colour
buildCacheAttributes.foreColour = charCode;
buildCacheAttributes.mosaics = false;
buildCacheAttributes.holdChar = 0x20; // Switch from mosaics to alpha resets held mosaic
buildCacheAttributes.holdSeparated = false;
break;
case 0x10 ... 0x17: // Mosaic + foreground colour
buildCacheAttributes.foreColour = charCode & 0x07;
buildCacheAttributes.mosaics = true;
break;
case 0x0d: // Double height
case 0x0f: // Double size
doubleHeightAttrFound = true;
// fall-through
case 0x0e: // Double width
if (buildCacheAttributes.sizeCode != charCode) // Size CHANGE resets held mosaic to space
buildCacheAttributes.holdChar = 0x20;
buildCacheAttributes.sizeCode = charCode;
break;
case 0x1b: // ESC/switch
buildCacheAttributes.escSwitch ^= true;
break;
case 0x1f: // Release mosaics
buildCacheAttributes.held = false;
break;
}
}
if (doubleHeightAttrFound != m_rowHasDoubleHeightAttr[r]) {
m_rowHasDoubleHeightAttr[r] = doubleHeightAttrFound;
for (int dr=r; dr<24; dr++)
if (m_rowHasDoubleHeightAttr[dr]) {
m_rowHeight[dr] = TopHalf;
m_rowHeight[++dr] = BottomHalf;
} else
m_rowHeight[dr] = Normal;
}
}
textCharacter Level1Layer::character(int r, int c)
{
textCharacter result;
if (r != m_rowCached)
updateRowCache(r);
if (c > 39 || m_rowHeight[r] == BottomHalf)
return { 0x20, 0 };
result.code = m_levelOnePage->character(r, c);
if (m_levelOnePage->secondCharSet() != 0xf && m_attributeCache[c].escSwitch)
result.set = g0CharacterMap.value(((m_levelOnePage->secondCharSet() << 3) | m_levelOnePage->secondNOS()), 0);
else
result.set = g0CharacterMap.value(((m_levelOnePage->defaultCharSet() << 3) | m_levelOnePage->defaultNOS()), 0);
if (result.code < 0x20) {
result.code = m_attributeCache[c].held ? m_attributeCache[c].holdChar : 0x20;
if (m_attributeCache[c].held && c > 0)
result.set = 24+m_attributeCache[c].holdSeparated;
// else
// result.set = m_attributeCache[c].mosaics*24;
} else if (m_attributeCache[c].mosaics && (result.code & 0x20))
result.set = 24+m_attributeCache[c].separated;
return result;
}
void Level1Layer::attributes(int r, int c, applyAttributes *layerApplyAttributes)
{
unsigned char characterCode;
if (m_rowHeight[r] == BottomHalf) {
layerApplyAttributes->copyAboveAttributes = true;
return;
}
if (r != m_rowCached)
updateRowCache(r);
if (c == 0 || c == 40 || c == 56) {
// Start of row default conditions, also when crossing into side panels
layerApplyAttributes->applyForeColour = true;
layerApplyAttributes->attribute.foreColour = 0x07;
layerApplyAttributes->applyBackColour = true;
layerApplyAttributes->attribute.backColour = 0x20;
layerApplyAttributes->applyDisplayAttributes = true;
layerApplyAttributes->applyFlash = true;
layerApplyAttributes->attribute.flash.mode = 0;
layerApplyAttributes->attribute.flash.ratePhase = 0;
layerApplyAttributes->attribute.display.doubleHeight = false;
layerApplyAttributes->attribute.display.doubleWidth = false;
layerApplyAttributes->attribute.display.boxingWindow = false;
layerApplyAttributes->attribute.display.conceal = false;
layerApplyAttributes->attribute.display.invert = false;
layerApplyAttributes->attribute.display.underlineSeparated = false;
layerApplyAttributes->attribute.display.forceContiguous = false;
//TODO fontstyle
}
if (c > 39)
return;
if (c > 0) {
// Set-after
characterCode = m_levelOnePage->character(r, c-1);
switch (characterCode) {
case 0x00 ... 0x07: // Alphanumeric + Foreground colour
case 0x10 ... 0x17: // Mosaic + Foreground colour
layerApplyAttributes->applyForeColour = true;
layerApplyAttributes->attribute.foreColour = characterCode & 0x07;
layerApplyAttributes->attribute.display.conceal = false;
break;
case 0x08: // Flashing
layerApplyAttributes->applyFlash = true;
layerApplyAttributes->attribute.flash.mode = 1;
layerApplyAttributes->attribute.flash.ratePhase = 0;
break;
case 0x0a: // End box
if (m_levelOnePage->character(r, c) == 0x0a) {
layerApplyAttributes->applyBoxingOnly = true;
layerApplyAttributes->attribute.display.boxingWindow = false;
}
break;
case 0x0b: // Start box
if (m_levelOnePage->character(r, c) == 0x0b) {
layerApplyAttributes->applyBoxingOnly = true;
layerApplyAttributes->attribute.display.boxingWindow = true;
}
break;
case 0x0d: // Double height
layerApplyAttributes->applyTextSizeOnly = true;
layerApplyAttributes->attribute.display.doubleHeight = true;
layerApplyAttributes->attribute.display.doubleWidth = false;
break;
case 0x0e: // Double width
layerApplyAttributes->applyTextSizeOnly = true;
layerApplyAttributes->attribute.display.doubleHeight = false;
layerApplyAttributes->attribute.display.doubleWidth = true;
break;
case 0x0f: // Double size
layerApplyAttributes->applyTextSizeOnly = true;
layerApplyAttributes->attribute.display.doubleHeight = true;
layerApplyAttributes->attribute.display.doubleWidth = true;
break;
}
}
// Set-at
characterCode = m_levelOnePage->character(r, c);
switch (characterCode) {
case 0x09: // Steady
layerApplyAttributes->applyFlash = true;
layerApplyAttributes->attribute.flash.mode = 0;
layerApplyAttributes->attribute.flash.ratePhase = 0;
break;
case 0x0c: // Normal size
layerApplyAttributes->applyTextSizeOnly = true;
layerApplyAttributes->attribute.display.doubleHeight = false;
layerApplyAttributes->attribute.display.doubleWidth = false;
break;
case 0x18: // Conceal
layerApplyAttributes->applyConcealOnly = true;
layerApplyAttributes->attribute.display.conceal = true;
break;
case 0x19: // Contiguous mosaics
layerApplyAttributes->applyContiguousOnly = true;
break;
case 0x1c: // Black background
layerApplyAttributes->applyBackColour = true;
layerApplyAttributes->attribute.backColour = 0x20;
break;
case 0x1d: // New background
layerApplyAttributes->applyBackColour = true;
layerApplyAttributes->attribute.backColour = m_attributeCache[c].backColour;
break;
}
}
ActivePosition::ActivePosition()
{
m_row = m_column = -1;
}
bool ActivePosition::setRow(int newRow)
{
if (newRow < m_row)
return false;
if (newRow > m_row) {
m_row = newRow;
m_column = -1;
}
return true;
}
bool ActivePosition::setColumn(int newColumn)
{
if (newColumn < m_column)
return false;
if (m_row == -1)
m_row = 0;
m_column = newColumn;
return true;
}
/*
bool ActivePosition::setRowAndColumn(int newRow, int newColumn)
{
if (!setRow(newRow))
return false;
return setColumn(newColumn);
}
*/

306
decode.h Normal file
View File

@@ -0,0 +1,306 @@
/*
* Copyright (C) 2020-2022 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/>.
*/
#ifndef DECODE_H
#define DECODE_H
#include <QMap>
#include <QMultiMap>
#include <QPair>
#include <vector>
#include "levelonepage.h"
struct textCharacter {
unsigned char code=0x20;
int set=0;
int diacritical=0;
};
inline bool operator!=(const textCharacter &lhs, const textCharacter &rhs)
{
return lhs.code != rhs.code ||
lhs.set != rhs.set ||
lhs.diacritical != rhs.diacritical;
}
struct displayAttributes {
bool doubleHeight=false;
bool doubleWidth=false;
bool boxingWindow=false;
bool conceal=false;
bool invert=false;
bool underlineSeparated=false;
bool forceContiguous=false;
};
inline bool operator!=(const displayAttributes &lhs, const displayAttributes &rhs)
{
return lhs.doubleHeight != rhs.doubleHeight ||
lhs.doubleWidth != rhs.doubleWidth ||
lhs.boxingWindow != rhs.boxingWindow ||
lhs.conceal != rhs.conceal ||
lhs.invert != rhs.invert ||
lhs.underlineSeparated != rhs.underlineSeparated ||
lhs.forceContiguous != rhs.forceContiguous;
}
struct textAttributes {
int foreColour=0x07;
int backColour=0x00;
struct flashFunctions {
int mode=0;
int ratePhase=0;
int phaseNumber=0;
} flash;
displayAttributes display;
/* font style */
};
inline bool operator!=(const textAttributes &lhs, const textAttributes &rhs)
{
return lhs.foreColour != rhs.foreColour ||
lhs.backColour != rhs.backColour ||
lhs.flash.mode != rhs.flash.mode ||
lhs.flash.ratePhase != rhs.flash.ratePhase ||
lhs.flash.phaseNumber != rhs.flash.phaseNumber ||
lhs.display != rhs.display;
}
struct textCell {
textCharacter character;
textAttributes attribute;
bool bottomHalf=false;
bool rightHalf=false;
bool level1Mosaic=false;
int level1CharSet=0;
};
inline bool operator!=(const textCell &lhs, const textCell &rhs)
{
return lhs.character != rhs.character ||
lhs.attribute != rhs.attribute ||
lhs.bottomHalf != rhs.bottomHalf ||
lhs.rightHalf != rhs.rightHalf ||
lhs.level1Mosaic != rhs.level1Mosaic ||
lhs.level1CharSet != rhs.level1CharSet;
}
struct applyAttributes {
bool applyForeColour=false;
bool applyBackColour=false;
bool applyFlash=false;
bool applyDisplayAttributes=false;
bool applyTextSizeOnly=false;
bool applyBoxingOnly=false;
bool applyConcealOnly=false;
bool applyContiguousOnly=false;
bool copyAboveAttributes=false;
textAttributes attribute;
};
class ActivePosition
{
public:
ActivePosition();
int row() const { return (m_row == -1) ? 0 : m_row; }
int column() const { return (m_column == -1) ? 0 : m_column; }
bool isDeployed() const { return m_row != -1; }
bool setRow(int);
bool setColumn(int);
// bool setRowAndColumn(int, int);
private:
int m_row, m_column;
};
class TextLayer
{
public:
// TextLayer(TeletextPage* thePage) : currentPage(thePage) { };
virtual ~TextLayer() = default;
void setTeletextPage(LevelOnePage *);
virtual textCharacter character(int, int) =0;
virtual void attributes(int, int, applyAttributes *) =0;
virtual int fullScreenColour() const =0;
virtual int fullRowColour(int) const =0;
virtual bool fullRowDownwards(int) const =0;
virtual int objectType() const =0;
virtual int originR() const { return 0; };
virtual int originC() const { return 0; };
void setFullScreenColour(int);
void setFullRowColour(int, int, bool);
// Key QPair is row and column, value QPair is triplet mode and data
QMultiMap<QPair<int, int>, QPair<int, int>> enhanceMap;
protected:
LevelOnePage* m_levelOnePage;
int m_layerFullScreenColour=-1;
int m_layerFullRowColour[25];
bool m_layerFullRowDownwards[25];
applyAttributes m_applyAttributes;
};
class EnhanceLayer: public TextLayer
{
public:
EnhanceLayer();
textCharacter character(int, int);
void attributes(int, int, applyAttributes *);
int fullScreenColour() const { return m_layerFullScreenColour; };
int fullRowColour(int r) const { return m_layerFullRowColour[r]; };
bool fullRowDownwards(int r) const { return m_layerFullRowDownwards[r]; };
int objectType() const { return m_objectType; };
int originR() const { return m_originR; };
int originC() const { return m_originC; };
void setObjectType(int);
void setOrigin(int, int);
protected:
int m_objectType=0;
int m_originR=0;
int m_originC=0;
int m_rowCached=-1;
int m_rightMostColumn[25];
};
class Level1Layer: public TextLayer
{
public:
enum RowHeight { Normal=-1, TopHalf, BottomHalf };
// Level1Layer(TeletextPage *thePage) : TextLayer(thePage) { };
Level1Layer();
textCharacter character(int, int);
void attributes(int, int, applyAttributes *);
int fullScreenColour() const { return -1; };
int fullRowColour(int) const { return -1; };
bool fullRowDownwards(int) const { return false; };
int objectType() const { return 0; }
RowHeight rowHeight(int r) const { return m_rowHeight[r]; };
private:
void updateRowCache(int);
struct level1CacheAttributes {
int foreColour=0x07;
int backColour=0x00;
unsigned char sizeCode=0x0c;
bool mosaics=false;
bool separated=false;
bool held=false;
bool escSwitch=false;
unsigned char holdChar=0x20;
bool holdSeparated=false;
};
level1CacheAttributes m_attributeCache[40];
int m_rowCached=-1;
bool m_rowHasDoubleHeightAttr[25];
RowHeight m_rowHeight[25];
};
class TeletextPageDecode : public QObject
{
Q_OBJECT
public:
enum CharacterFragment { NormalSize, DoubleHeightTopHalf, DoubleHeightBottomHalf, DoubleWidthLeftHalf, DoubleWidthRightHalf, DoubleSizeTopLeftQuarter, DoubleSizeTopRightQuarter, DoubleSizeBottomLeftQuarter, DoubleSizeBottomRightQuarter };
TeletextPageDecode();
~TeletextPageDecode();
bool refresh(int r, int c) const { return m_refresh[r][c]; }
void setRefresh(int, int, bool);
void decodePage();
void decodeRow(int r);
LevelOnePage *teletextPage() const { return m_levelOnePage; };
void setTeletextPage(LevelOnePage *);
void updateSidePanels();
void buildEnhanceMap(TextLayer *, int=0);
unsigned char cellCharacterCode(int r, int c) { return cellAtCharacterOrigin(r, c).character.code; };
int cellCharacterSet(int r, int c) { return cellAtCharacterOrigin(r, c).character.set; };
int cellCharacterDiacritical(int r, int c) { return cellAtCharacterOrigin(r, c).character.diacritical; };
int cellForegroundCLUT(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.foreColour; };
int cellBackgroundCLUT(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.backColour; };
QColor cellForegroundQColor(int, int);
QColor cellBackgroundQColor(int, int);
QColor cellFlashForegroundQColor(int, int);
int cellFlashMode(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.flash.mode; };
int cellFlashRatePhase(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.flash.ratePhase; };
int cellFlash2HzPhaseNumber(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.flash.phaseNumber; };
CharacterFragment cellCharacterFragment(int, int) const;
bool cellBoxed(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.display.boxingWindow; };
bool cellConceal(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.display.conceal; };
bool cellUnderlined(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.display.underlineSeparated && cellAtCharacterOrigin(r, c).character.set < 24; };
bool level1MosaicAttribute(int r, int c) const { return m_cell[r][c].level1Mosaic; };
int level1CharSet(int r, int c) const { return m_cell[r][c].level1CharSet; };
QColor fullScreenQColor() const { return m_finalFullScreenQColor; };
QColor fullRowQColor(int r) const { return m_fullRowQColor[r]; };
int leftSidePanelColumns() const { return m_leftSidePanelColumns; };
int rightSidePanelColumns() const { return m_rightSidePanelColumns; };
public slots:
void setLevel(int);
signals:
void fullScreenColourChanged(QColor);
void fullRowColourChanged(int, QColor);
void sidePanelsChanged();
protected:
inline void setFullScreenColour(int);
inline void setFullRowColour(int, int);
textCell& cellAtCharacterOrigin(int, int);
int m_finalFullScreenColour, m_level;
QColor m_finalFullScreenQColor;
int m_leftSidePanelColumns, m_rightSidePanelColumns;
Level1Layer m_level1Layer;
std::vector<TextLayer *> m_textLayer;
const int m_foregroundRemap[8] = { 0, 0, 0, 8, 8, 16, 16, 16 };
const int m_backgroundRemap[8] = { 0, 8, 16, 8, 16, 8, 16, 24 };
private:
enum ColourPart { Foreground, Background, FlashForeground };
QColor cellQColor(int, int, ColourPart);
textCell m_cell[25][72];
bool m_refresh[25][72];
LevelOnePage* m_levelOnePage;
int m_fullRowColour[25];
QColor m_fullRowQColor[25];
};
static const QMap<int, int> g0CharacterMap {
{ 0x00, 12 }, { 0x01, 15 }, { 0x02, 22 }, { 0x03, 16 }, { 0x04, 14 }, { 0x05, 19 }, { 0x06, 11 },
{ 0x08, 18 }, { 0x09, 15 }, { 0x0a, 22 }, { 0x0b, 16 }, { 0x0c, 14 }, { 0x0e, 11 },
{ 0x10, 12 }, { 0x11, 15 }, { 0x12, 22 }, { 0x13, 16 }, { 0x14, 14 }, { 0x15, 19 }, { 0x16, 23 },
{ 0x1d, 21 }, { 0x1f, 20 },
{ 0x20, 1 }, { 0x21, 15 }, { 0x22, 13 }, { 0x23, 17 }, { 0x24, 2 }, { 0x25, 3 }, { 0x26, 11 },
{ 0x36, 23 }, { 0x37, 4 },
{ 0x40, 12 }, { 0x44, 14 }, { 0x47, 5 },
{ 0x55, 6 }, { 0x57, 5 }
};
#endif

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -381,6 +381,9 @@ QColor LevelOnePage::CLUTtoQColor(int index, int renderLevel) const
{ {
int colour12Bit = CLUT(index, renderLevel); int colour12Bit = CLUT(index, renderLevel);
if (index == 8)
return QColor(Qt::transparent);
return QColor(((colour12Bit & 0xf00) >> 8) * 17, ((colour12Bit & 0x0f0) >> 4) * 17, (colour12Bit & 0x00f) * 17); return QColor(((colour12Bit & 0xf00) >> 8) * 17, ((colour12Bit & 0x0f0) >> 4) * 17, (colour12Bit & 0x00f) * 17);
} }
@@ -433,7 +436,7 @@ int LevelOnePage::levelRequired() const
case 0x01: // Full row colour case 0x01: // Full row colour
case 0x10 ... 0x13: // Origin Modifer and Object Invocation case 0x10 ... 0x13: // Origin Modifer and Object Invocation
case 0x15 ... 0x17: // Object Definition case 0x15 ... 0x17: // Object Definition
// Check if Object Defition is required only at Level 3.5 // Check if Object Definition is required only at Level 3.5
if ((m_enhancements.at(i).address() & 0x18) == 0x10) if ((m_enhancements.at(i).address() & 0x18) == 0x10)
return 3; return 3;
else else

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -437,7 +437,7 @@ void saveTTI(QSaveFile &file, const TeletextDocument &document)
for (p=0; p<document.numberOfSubPages(); p++) { for (p=0; p<document.numberOfSubPages(); p++) {
// Page number // Page number
outStream << QString("PN,%1%2").arg(document.pageNumber(), 3, 16, QChar('0')).arg(subPageNumber & 0xff, 2, 16, QChar('0')); outStream << QString("PN,%1%2").arg(document.pageNumber(), 3, 16, QChar('0')).arg(subPageNumber & 0xff, 2, 10, QChar('0'));
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
outStream << Qt::endl; outStream << Qt::endl;
#else #else
@@ -447,7 +447,7 @@ void saveTTI(QSaveFile &file, const TeletextDocument &document)
// Subpage // Subpage
// Magazine Organisation Table and Magazine Inventory Page don't have subpages // Magazine Organisation Table and Magazine Inventory Page don't have subpages
if (document.pageFunction() != TeletextDocument::PFMOT && document.pageFunction() != TeletextDocument::PFMIP) { if (document.pageFunction() != TeletextDocument::PFMOT && document.pageFunction() != TeletextDocument::PFMIP) {
outStream << QString("SC,%1").arg(subPageNumber, 4, 16, QChar('0')); outStream << QString("SC,%1").arg(subPageNumber, 4, 10, QChar('0'));
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
outStream << Qt::endl; outStream << Qt::endl;
#else #else
@@ -680,8 +680,8 @@ void exportT42File(QSaveFile &file, const TeletextDocument &document)
outLine[5] = subPage.controlBit(PageBase::C4ErasePage) << 3; outLine[5] = subPage.controlBit(PageBase::C4ErasePage) << 3;
outLine[6] = 0; // Subcode S3 - always export as 0 outLine[6] = 0; // Subcode S3 - always export as 0
outLine[7] = (subPage.controlBit(PageBase::C5Newsflash) << 2) | (subPage.controlBit(PageBase::C6Subtitle) << 3); outLine[7] = (subPage.controlBit(PageBase::C5Newsflash) << 2) | (subPage.controlBit(PageBase::C6Subtitle) << 3);
outLine[8] = subPage.controlBit(PageBase::C7SuppressHeader) | (subPage.controlBit(PageBase::C8Update) << 2) | (subPage.controlBit(PageBase::C9InterruptedSequence) << 2) | (subPage.controlBit(PageBase::C10InhibitDisplay) << 3); outLine[8] = subPage.controlBit(PageBase::C7SuppressHeader) | (subPage.controlBit(PageBase::C8Update) << 1) | (subPage.controlBit(PageBase::C9InterruptedSequence) << 2) | (subPage.controlBit(PageBase::C10InhibitDisplay) << 3);
outLine[9] = subPage.controlBit(PageBase::C11SerialMagazine) | (subPage.controlBit(PageBase::C14NOS) << 2) | (subPage.controlBit(PageBase::C13NOS) << 2) | (subPage.controlBit(PageBase::C12NOS) << 3); outLine[9] = subPage.controlBit(PageBase::C11SerialMagazine) | (subPage.controlBit(PageBase::C14NOS) << 1) | (subPage.controlBit(PageBase::C13NOS) << 2) | (subPage.controlBit(PageBase::C12NOS) << 3);
for (int i=0; i<10; i++) for (int i=0; i<10; i++)
outLine[i] = hamming_8_4_encode[(int)outLine.at(i)]; outLine[i] = hamming_8_4_encode[(int)outLine.at(i)];

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -30,7 +30,7 @@ int main(int argc, char *argv[])
QApplication::setApplicationDisplayName(QApplication::applicationName()); QApplication::setApplicationDisplayName(QApplication::applicationName());
QApplication::setOrganizationName("gkmac.co.uk"); QApplication::setOrganizationName("gkmac.co.uk");
QApplication::setOrganizationDomain("gkmac.co.uk"); QApplication::setOrganizationDomain("gkmac.co.uk");
QApplication::setApplicationVersion("0.5.1-alpha"); QApplication::setApplicationVersion("0.5.4-alpha");
QCommandLineParser parser; QCommandLineParser parser;
parser.setApplicationDescription(QApplication::applicationName()); parser.setApplicationDescription(QApplication::applicationName());
parser.addHelpOption(); parser.addHelpOption();

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -38,6 +38,7 @@
#include "mainwidget.h" #include "mainwidget.h"
#include "decode.h"
#include "document.h" #include "document.h"
#include "keymap.h" #include "keymap.h"
#include "levelonecommands.h" #include "levelonecommands.h"
@@ -52,16 +53,18 @@ TeletextWidget::TeletextWidget(QFrame *parent) : QFrame(parent)
this->setAttribute(Qt::WA_InputMethodEnabled, true); this->setAttribute(Qt::WA_InputMethodEnabled, true);
m_teletextDocument = new TeletextDocument(); m_teletextDocument = new TeletextDocument();
m_levelOnePage = m_teletextDocument->currentSubPage(); m_levelOnePage = m_teletextDocument->currentSubPage();
m_pageRender.setTeletextPage(m_levelOnePage); m_pageDecode.setTeletextPage(m_levelOnePage);
m_pageRender.setDecoder(&m_pageDecode);
m_insertMode = false; m_insertMode = false;
m_selectionInProgress = false; m_selectionInProgress = false;
setFocusPolicy(Qt::StrongFocus); setFocusPolicy(Qt::StrongFocus);
m_flashTiming = m_flashPhase = 0; m_flashTiming = m_flashPhase = 0;
connect(&m_pageRender, &TeletextPageRender::flashChanged, this, &TeletextWidget::updateFlashTimer); connect(&m_pageRender, &TeletextPageRender::flashChanged, this, &TeletextWidget::updateFlashTimer);
connect(&m_pageRender, &TeletextPageRender::sidePanelsChanged, this, &TeletextWidget::changeSize); connect(&m_pageDecode, &TeletextPageDecode::sidePanelsChanged, this, &TeletextWidget::changeSize);
connect(m_teletextDocument, &TeletextDocument::subPageSelected, this, &TeletextWidget::subPageSelected); connect(m_teletextDocument, &TeletextDocument::subPageSelected, this, &TeletextWidget::subPageSelected);
connect(m_teletextDocument, &TeletextDocument::contentsChange, this, &TeletextWidget::refreshRow); connect(m_teletextDocument, &TeletextDocument::contentsChange, this, &TeletextWidget::refreshRow);
connect(m_teletextDocument, &TeletextDocument::refreshNeeded, this, &TeletextWidget::refreshPage); connect(m_teletextDocument, &TeletextDocument::refreshNeeded, this, &TeletextWidget::refreshPage);
connect(m_teletextDocument, &TeletextDocument::colourChanged, &m_pageRender, &TeletextPageRender::colourChanged);
} }
TeletextWidget::~TeletextWidget() TeletextWidget::~TeletextWidget()
@@ -85,20 +88,21 @@ void TeletextWidget::inputMethodEvent(QInputMethodEvent* event)
void TeletextWidget::subPageSelected() void TeletextWidget::subPageSelected()
{ {
m_levelOnePage = m_teletextDocument->currentSubPage(); m_levelOnePage = m_teletextDocument->currentSubPage();
m_pageRender.setTeletextPage(m_levelOnePage); m_pageDecode.setTeletextPage(m_levelOnePage);
refreshPage(); m_pageDecode.decodePage();
m_pageRender.renderPage(true);
update();
} }
void TeletextWidget::refreshRow(int rowChanged) void TeletextWidget::refreshRow(int rowChanged)
{ {
m_pageRender.renderPage(rowChanged); m_pageDecode.decodeRow(rowChanged);
update(); update();
} }
void TeletextWidget::refreshPage() void TeletextWidget::refreshPage()
{ {
m_pageRender.decodePage(); m_pageDecode.decodePage();
m_pageRender.renderPage();
update(); update();
} }
@@ -107,11 +111,12 @@ void TeletextWidget::paintEvent(QPaintEvent *event)
Q_UNUSED(event); Q_UNUSED(event);
QPainter widgetPainter(this); QPainter widgetPainter(this);
widgetPainter.drawPixmap(m_pageRender.leftSidePanelColumns()*12, 0, *m_pageRender.pagePixmap(m_flashPhase), 0, 0, 480, 250); m_pageRender.renderPage();
if (m_pageRender.leftSidePanelColumns()) widgetPainter.drawPixmap(m_pageDecode.leftSidePanelColumns()*12, 0, *m_pageRender.pagePixmap(m_flashPhase), 0, 0, 480, 250);
widgetPainter.drawPixmap(0, 0, *m_pageRender.pagePixmap(m_flashPhase), 864-m_pageRender.leftSidePanelColumns()*12, 0, m_pageRender.leftSidePanelColumns()*12, 250); if (m_pageDecode.leftSidePanelColumns())
if (m_pageRender.rightSidePanelColumns()) widgetPainter.drawPixmap(0, 0, *m_pageRender.pagePixmap(m_flashPhase), 864-m_pageDecode.leftSidePanelColumns()*12, 0, m_pageDecode.leftSidePanelColumns()*12, 250);
widgetPainter.drawPixmap(480+m_pageRender.leftSidePanelColumns()*12, 0, *m_pageRender.pagePixmap(m_flashPhase), 480, 0, m_pageRender.rightSidePanelColumns()*12, 250); if (m_pageDecode.rightSidePanelColumns())
widgetPainter.drawPixmap(480+m_pageDecode.leftSidePanelColumns()*12, 0, *m_pageRender.pagePixmap(m_flashPhase), 480, 0, m_pageDecode.rightSidePanelColumns()*12, 250);
} }
void TeletextWidget::updateFlashTimer(int newFlashTimer) void TeletextWidget::updateFlashTimer(int newFlashTimer)
@@ -155,25 +160,29 @@ void TeletextWidget::setInsertMode(bool insertMode)
m_insertMode = insertMode; m_insertMode = insertMode;
} }
void TeletextWidget::toggleReveal(bool revealOn) void TeletextWidget::setReveal(bool reveal)
{ {
m_pageRender.setReveal(revealOn); m_pageRender.setReveal(reveal);
update(); update();
} }
void TeletextWidget::toggleMix(bool mixOn) void TeletextWidget::setMix(bool mix)
{ {
m_pageRender.setMix(mixOn); m_pageRender.setMix(mix);
update();
}
void TeletextWidget::setShowControlCodes(bool showControlCodes)
{
m_pageRender.setShowControlCodes(showControlCodes);
update(); update();
} }
void TeletextWidget::setControlBit(int bitNumber, bool active) void TeletextWidget::setControlBit(int bitNumber, bool active)
{ {
m_levelOnePage->setControlBit(bitNumber, active); m_levelOnePage->setControlBit(bitNumber, active);
if (bitNumber == 1 || bitNumber == 2) { if (bitNumber == 1 || bitNumber == 2)
m_pageRender.decodePage(); m_pageDecode.decodePage();
m_pageRender.renderPage();
}
} }
void TeletextWidget::setDefaultCharSet(int newDefaultCharSet) void TeletextWidget::setDefaultCharSet(int newDefaultCharSet)
@@ -189,31 +198,27 @@ void TeletextWidget::setDefaultNOS(int newDefaultNOS)
void TeletextWidget::setDefaultScreenColour(int newColour) void TeletextWidget::setDefaultScreenColour(int newColour)
{ {
m_levelOnePage->setDefaultScreenColour(newColour); m_levelOnePage->setDefaultScreenColour(newColour);
m_pageRender.decodePage(); m_pageDecode.decodePage();
m_pageRender.renderPage();
} }
void TeletextWidget::setDefaultRowColour(int newColour) void TeletextWidget::setDefaultRowColour(int newColour)
{ {
m_levelOnePage->setDefaultRowColour(newColour); m_levelOnePage->setDefaultRowColour(newColour);
m_pageRender.decodePage(); m_pageDecode.decodePage();
m_pageRender.renderPage();
update(); update();
} }
void TeletextWidget::setColourTableRemap(int newMap) void TeletextWidget::setColourTableRemap(int newMap)
{ {
m_levelOnePage->setColourTableRemap(newMap); m_levelOnePage->setColourTableRemap(newMap);
m_pageRender.decodePage(); m_pageDecode.decodePage();
m_pageRender.renderPage();
update(); update();
} }
void TeletextWidget::setBlackBackgroundSubst(bool substOn) void TeletextWidget::setBlackBackgroundSubst(bool substOn)
{ {
m_levelOnePage->setBlackBackgroundSubst(substOn); m_levelOnePage->setBlackBackgroundSubst(substOn);
m_pageRender.decodePage(); m_pageDecode.decodePage();
m_pageRender.renderPage();
update(); update();
} }
@@ -225,18 +230,18 @@ void TeletextWidget::setSidePanelWidths(int newLeftSidePanelColumns, int newRigh
m_levelOnePage->setSidePanelColumns((newLeftSidePanelColumns == 16) ? 0 : newLeftSidePanelColumns); m_levelOnePage->setSidePanelColumns((newLeftSidePanelColumns == 16) ? 0 : newLeftSidePanelColumns);
else else
m_levelOnePage->setSidePanelColumns((newRightSidePanelColumns == 0) ? 0 : 16-newRightSidePanelColumns); m_levelOnePage->setSidePanelColumns((newRightSidePanelColumns == 0) ? 0 : 16-newRightSidePanelColumns);
m_pageRender.updateSidePanels(); m_pageDecode.updateSidePanels();
} }
void TeletextWidget::setSidePanelAtL35Only(bool newSidePanelAtL35Only) void TeletextWidget::setSidePanelAtL35Only(bool newSidePanelAtL35Only)
{ {
m_levelOnePage->setSidePanelStatusL25(!newSidePanelAtL35Only); m_levelOnePage->setSidePanelStatusL25(!newSidePanelAtL35Only);
m_pageRender.updateSidePanels(); m_pageDecode.updateSidePanels();
} }
void TeletextWidget::changeSize() void TeletextWidget::changeSize()
{ {
setFixedSize(QSize(480+(pageRender()->leftSidePanelColumns()+pageRender()->rightSidePanelColumns())*12, 250)); setFixedSize(QSize(480+(pageDecode()->leftSidePanelColumns()+pageDecode()->rightSidePanelColumns())*12, 250));
emit sizeChanged(); emit sizeChanged();
} }
@@ -245,14 +250,14 @@ void TeletextWidget::keyPressEvent(QKeyEvent *event)
if (event->key() < 0x01000000) { if (event->key() < 0x01000000) {
// A character-typing key was pressed // A character-typing key was pressed
// Try to keymap it, if not keymapped then plain ASCII code (may be) returned // Try to keymap it, if not keymapped then plain ASCII code (may be) returned
char mappedKeyPress = keymapping[m_pageRender.level1CharSet(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn())].value(event->text().at(0), *qPrintable(event->text())); char mappedKeyPress = keymapping[m_pageDecode.level1CharSet(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn())].value(event->text().at(0), *qPrintable(event->text()));
if (mappedKeyPress < 0x20) if (mappedKeyPress < 0x20)
return; return;
// If outside ASCII map then the character can't be represented by current Level 1 character set // If outside ASCII map then the character can't be represented by current Level 1 character set
// Map it to block character so it doesn't need to be inserted-between later on // Map it to block character so it doesn't need to be inserted-between later on
if (mappedKeyPress & 0x80) if (mappedKeyPress & 0x80)
mappedKeyPress = 0x7f; mappedKeyPress = 0x7f;
if (m_pageRender.level1MosaicAttribute(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn()) && (mappedKeyPress < 0x40 || mappedKeyPress > 0x5f)) { if (m_pageDecode.level1MosaicAttribute(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn()) && (mappedKeyPress < 0x40 || mappedKeyPress > 0x5f)) {
// We're on a mosaic and a blast-through character was NOT pressed // We're on a mosaic and a blast-through character was NOT pressed
if (event->key() >= Qt::Key_1 && event->key() <= Qt::Key_9 && event->modifiers() & Qt::KeypadModifier) { if (event->key() >= Qt::Key_1 && event->key() <= Qt::Key_9 && event->modifiers() & Qt::KeypadModifier) {
switch (event->key()) { switch (event->key()) {
@@ -382,8 +387,8 @@ void TeletextWidget::keyPressEvent(QKeyEvent *event)
m_teletextDocument->selectSubPagePrevious(); m_teletextDocument->selectSubPagePrevious();
break; break;
case Qt::Key_F5: case Qt::Key_F5:
m_pageRender.decodePage(); m_pageDecode.decodePage();
m_pageRender.renderPage(); m_pageRender.renderPage(true);
update(); update();
break; break;
default: default:
@@ -420,7 +425,7 @@ void TeletextWidget::selectionToClipboard()
nativeData[i++] = m_teletextDocument->currentSubPage()->character(r, c); nativeData[i++] = m_teletextDocument->currentSubPage()->character(r, c);
if (m_teletextDocument->currentSubPage()->character(r, c) >= 0x20) if (m_teletextDocument->currentSubPage()->character(r, c) >= 0x20)
plainTextData.append(keymapping[m_pageRender.level1CharSet(r, c)].key(m_teletextDocument->currentSubPage()->character(r, c), m_teletextDocument->currentSubPage()->character(r, c))); plainTextData.append(keymapping[m_pageDecode.level1CharSet(r, c)].key(m_teletextDocument->currentSubPage()->character(r, c), m_teletextDocument->currentSubPage()->character(r, c)));
else else
plainTextData.append(' '); plainTextData.append(' ');
} }
@@ -453,13 +458,13 @@ void TeletextWidget::copy()
void TeletextWidget::paste() void TeletextWidget::paste()
{ {
m_teletextDocument->undoStack()->push(new PasteCommand(m_teletextDocument, m_pageRender.level1CharSet(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn()))); m_teletextDocument->undoStack()->push(new PasteCommand(m_teletextDocument, m_pageDecode.level1CharSet(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn())));
} }
QPair<int, int> TeletextWidget::mouseToRowAndColumn(const QPoint &mousePosition) QPair<int, int> TeletextWidget::mouseToRowAndColumn(const QPoint &mousePosition)
{ {
int row = mousePosition.y() / 10; int row = mousePosition.y() / 10;
int column = mousePosition.x() / 12 - m_pageRender.leftSidePanelColumns(); int column = mousePosition.x() / 12 - m_pageDecode.leftSidePanelColumns();
if (row < 1) if (row < 1)
row = 1; row = 1;
if (row > 24) if (row > 24)
@@ -655,6 +660,22 @@ void LevelOneScene::updateSelection()
m_selectionRectItem->setVisible(true); m_selectionRectItem->setVisible(true);
} }
void LevelOneScene::setMix(bool mix)
{
if (mix) {
m_fullScreenTopRectItem->setBrush(Qt::transparent);
m_fullScreenBottomRectItem->setBrush(Qt::transparent);
for (int r=0; r<25; r++) {
m_fullRowLeftRectItem[r]->setBrush(Qt::transparent);
m_fullRowRightRectItem[r]->setBrush(Qt::transparent);
}
} else {
setFullScreenColour(static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->pageDecode()->fullScreenQColor());
for (int r=0; r<25; r++)
setFullRowColour(r, static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->pageDecode()->fullRowQColor(r));
}
}
void LevelOneScene::toggleGrid(bool gridOn) void LevelOneScene::toggleGrid(bool gridOn)
{ {
m_grid = gridOn; m_grid = gridOn;
@@ -682,6 +703,7 @@ void LevelOneScene::hideGUIElements(bool hidden)
} }
} }
// Implements Ctrl+mousewheel zoom
bool LevelOneScene::eventFilter(QObject *object, QEvent *event) bool LevelOneScene::eventFilter(QObject *object, QEvent *event)
{ {
Q_UNUSED(object); Q_UNUSED(object);
@@ -698,14 +720,38 @@ bool LevelOneScene::eventFilter(QObject *object, QEvent *event)
return false; return false;
} }
// Clicking outside the main text widget but still within the scene would
// cause keyboard focus loss.
// So on every keypress within the scene, wrench the focus back to the widget
// if necessary.
void LevelOneScene::keyPressEvent(QKeyEvent *keyEvent)
{
if (focusItem() != m_levelOneProxyWidget)
setFocusItem(m_levelOneProxyWidget);
QGraphicsScene::keyPressEvent(keyEvent);
}
void LevelOneScene::keyReleaseEvent(QKeyEvent *keyEvent)
{
if (focusItem() != m_levelOneProxyWidget)
setFocusItem(m_levelOneProxyWidget);
QGraphicsScene::keyReleaseEvent(keyEvent);
}
void LevelOneScene::setFullScreenColour(const QColor &newColor) void LevelOneScene::setFullScreenColour(const QColor &newColor)
{ {
m_fullScreenTopRectItem->setBrush(QBrush(newColor)); if (!static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->pageRender()->mix()) {
m_fullScreenBottomRectItem->setBrush(QBrush(newColor)); m_fullScreenTopRectItem->setBrush(newColor);
m_fullScreenBottomRectItem->setBrush(newColor);
}
} }
void LevelOneScene::setFullRowColour(int row, const QColor &newColor) void LevelOneScene::setFullRowColour(int row, const QColor &newColor)
{ {
m_fullRowLeftRectItem[row]->setBrush(QBrush(newColor)); if (!static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->pageRender()->mix()) {
m_fullRowRightRectItem[row]->setBrush(QBrush(newColor)); m_fullRowLeftRectItem[row]->setBrush(newColor);
m_fullRowRightRectItem[row]->setBrush(newColor);
}
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -29,6 +29,7 @@
#include <QTextStream> #include <QTextStream>
#include <vector> #include <vector>
#include "decode.h"
#include "document.h" #include "document.h"
#include "levelonepage.h" #include "levelonepage.h"
#include "render.h" #include "render.h"
@@ -46,12 +47,14 @@ public:
void toggleCharacterBit(unsigned char); void toggleCharacterBit(unsigned char);
bool insertMode() const { return m_insertMode; }; bool insertMode() const { return m_insertMode; };
void setInsertMode(bool); void setInsertMode(bool);
bool showControlCodes() const { return m_pageRender.showControlCodes(); };
QSize sizeHint() { return QSize(480+(pageRender()->leftSidePanelColumns()+pageRender()->rightSidePanelColumns())*12, 250); } QSize sizeHint() { return QSize(480+(pageDecode()->leftSidePanelColumns()+pageDecode()->rightSidePanelColumns())*12, 250); }
void inputMethodEvent(QInputMethodEvent *); void inputMethodEvent(QInputMethodEvent *);
TeletextDocument* document() const { return m_teletextDocument; } TeletextDocument* document() const { return m_teletextDocument; }
TeletextPageDecode *pageDecode() { return &m_pageDecode; }
TeletextPageRender *pageRender() { return &m_pageRender; } TeletextPageRender *pageRender() { return &m_pageRender; }
signals: signals:
@@ -61,8 +64,9 @@ signals:
public slots: public slots:
void subPageSelected(); void subPageSelected();
void refreshPage(); void refreshPage();
void toggleReveal(bool); void setReveal(bool);
void toggleMix(bool); void setMix(bool);
void setShowControlCodes(bool);
void updateFlashTimer(int); void updateFlashTimer(int);
void pauseFlash(bool); void pauseFlash(bool);
void refreshRow(int); void refreshRow(int);
@@ -92,6 +96,7 @@ protected:
void focusInEvent(QFocusEvent *event) override; void focusInEvent(QFocusEvent *event) override;
void focusOutEvent(QFocusEvent *event) override; void focusOutEvent(QFocusEvent *event) override;
TeletextPageDecode m_pageDecode;
TeletextPageRender m_pageRender; TeletextPageRender m_pageRender;
private: private:
@@ -119,6 +124,7 @@ public:
public slots: public slots:
void updateCursor(); void updateCursor();
void updateSelection(); void updateSelection();
void setMix(bool);
void toggleGrid(bool); void toggleGrid(bool);
void hideGUIElements(bool); void hideGUIElements(bool);
void setFullScreenColour(const QColor &); void setFullScreenColour(const QColor &);
@@ -128,9 +134,12 @@ signals:
void mouseZoomIn(); void mouseZoomIn();
void mouseZoomOut(); void mouseZoomOut();
private: protected:
bool eventFilter(QObject *, QEvent *); bool eventFilter(QObject *, QEvent *);
void keyPressEvent(QKeyEvent *);
void keyReleaseEvent(QKeyEvent *);
private:
QGraphicsRectItem *m_fullScreenTopRectItem, *m_fullScreenBottomRectItem; QGraphicsRectItem *m_fullScreenTopRectItem, *m_fullScreenBottomRectItem;
QGraphicsRectItem *m_fullRowLeftRectItem[25], *m_fullRowRightRectItem[25]; QGraphicsRectItem *m_fullRowLeftRectItem[25], *m_fullRowRightRectItem[25];
QGraphicsProxyWidget *m_levelOneProxyWidget; QGraphicsProxyWidget *m_levelOneProxyWidget;

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -42,6 +42,7 @@
#include "levelonecommands.h" #include "levelonecommands.h"
#include "loadsave.h" #include "loadsave.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "pagecomposelinksdockwidget.h"
#include "pageenhancementsdockwidget.h" #include "pageenhancementsdockwidget.h"
#include "pageoptionsdockwidget.h" #include "pageoptionsdockwidget.h"
#include "palettedockwidget.h" #include "palettedockwidget.h"
@@ -157,28 +158,32 @@ void MainWindow::exportPNG()
// Prepare widget image for extraction // Prepare widget image for extraction
m_textWidget->pauseFlash(true); m_textWidget->pauseFlash(true);
m_textScene->hideGUIElements(true); m_textScene->hideGUIElements(true);
bool reshowCodes = m_textWidget->pageRender()->showCodes(); bool reshowControlCodes = m_textWidget->showControlCodes();
if (reshowCodes) if (reshowControlCodes)
m_textWidget->pageRender()->setShowCodes(false); m_textWidget->setShowControlCodes(false);
// Disable exporting in Mix mode as it corrupts the background // Disable exporting in Mix mode as it corrupts the background
bool reMix = m_textWidget->pageRender()->mix(); bool reMix = m_textWidget->pageRender()->mix();
if (reMix) if (reMix) {
m_textWidget->pageRender()->setMix(false); m_textWidget->setMix(false);
m_textScene->setMix(false);
}
// Extract the image from the scene // Extract the image from the scene
QImage interImage = QImage(m_textScene->sceneRect().size().toSize(), QImage::Format_RGB32); QImage interImage = QImage(m_textScene->sceneRect().size().toSize(), QImage::Format_RGB32);
// This ought to make the background transparent in Mix mode, but it doesn't // This ought to make the background transparent in Mix mode, but it doesn't
// if (m_textWidget->pageRender()->mix()) // if (m_textWidget->pageDecode()->mix())
// interImage.fill(QColor(0, 0, 0, 0)); // interImage.fill(QColor(0, 0, 0, 0));
QPainter interPainter(&interImage); QPainter interPainter(&interImage);
m_textScene->render(&interPainter); m_textScene->render(&interPainter);
// Now we've extracted the image we can put the GUI things back // Now we've extracted the image we can put the GUI things back
m_textScene->hideGUIElements(false); m_textScene->hideGUIElements(false);
if (reshowCodes) if (reshowControlCodes)
m_textWidget->pageRender()->setShowCodes(true); m_textWidget->setShowControlCodes(true);
if (reMix) if (reMix) {
m_textWidget->pageRender()->setMix(true); m_textWidget->setMix(true);
m_textScene->setMix(true);
}
m_textWidget->pauseFlash(false); m_textWidget->pauseFlash(false);
// Now scale the extracted image to the selected aspect ratio // Now scale the extracted image to the selected aspect ratio
@@ -213,7 +218,7 @@ void MainWindow::about()
QMessageBox::about(this, tr("About"), QString("<b>%1</b><br>" QMessageBox::about(this, tr("About"), QString("<b>%1</b><br>"
"An open source Level 2.5 teletext page editor.<br>" "An open source Level 2.5 teletext page editor.<br>"
"<i>Version %2</i><br><br>" "<i>Version %2</i><br><br>"
"Copyright (C) 2020, 2021 Gavin MacGregor<br><br>" "Copyright (C) 2020-2022 Gavin MacGregor<br><br>"
"Released under the GNU General Public License version 3<br>" "Released under the GNU General Public License version 3<br>"
"<a href=\"https://github.com/gkthemac/qteletextmaker\">https://github.com/gkthemac/qteletextmaker</a>").arg(QApplication::applicationDisplayName()).arg(QApplication::applicationVersion())); "<a href=\"https://github.com/gkthemac/qteletextmaker\">https://github.com/gkthemac/qteletextmaker</a>").arg(QApplication::applicationDisplayName()).arg(QApplication::applicationVersion()));
} }
@@ -234,6 +239,8 @@ void MainWindow::init()
addDockWidget(Qt::RightDockWidgetArea, m_x26DockWidget); addDockWidget(Qt::RightDockWidgetArea, m_x26DockWidget);
m_paletteDockWidget = new PaletteDockWidget(m_textWidget); m_paletteDockWidget = new PaletteDockWidget(m_textWidget);
addDockWidget(Qt::RightDockWidgetArea, m_paletteDockWidget); addDockWidget(Qt::RightDockWidgetArea, m_paletteDockWidget);
m_pageComposeLinksDockWidget = new PageComposeLinksDockWidget(m_textWidget);
addDockWidget(Qt::RightDockWidgetArea, m_pageComposeLinksDockWidget);
m_textScene = new LevelOneScene(m_textWidget, this); m_textScene = new LevelOneScene(m_textWidget, this);
@@ -256,8 +263,8 @@ void MainWindow::init()
connect(m_textWidget->document(), &TeletextDocument::aboutToChangeSubPage, m_x26DockWidget, &X26DockWidget::unloadX26List); connect(m_textWidget->document(), &TeletextDocument::aboutToChangeSubPage, m_x26DockWidget, &X26DockWidget::unloadX26List);
connect(m_textWidget->document(), &TeletextDocument::subPageSelected, this, &MainWindow::updatePageWidgets); connect(m_textWidget->document(), &TeletextDocument::subPageSelected, this, &MainWindow::updatePageWidgets);
connect(m_textWidget, &TeletextWidget::sizeChanged, this, &MainWindow::setSceneDimensions); connect(m_textWidget, &TeletextWidget::sizeChanged, this, &MainWindow::setSceneDimensions);
connect(m_textWidget->pageRender(), &TeletextPageRender::fullScreenColourChanged, m_textScene, &LevelOneScene::setFullScreenColour); connect(m_textWidget->pageDecode(), &TeletextPageDecode::fullScreenColourChanged, m_textScene, &LevelOneScene::setFullScreenColour);
connect(m_textWidget->pageRender(), &TeletextPageRender::fullRowColourChanged, m_textScene, &LevelOneScene::setFullRowColour); connect(m_textWidget->pageDecode(), &TeletextPageDecode::fullRowColourChanged, m_textScene, &LevelOneScene::setFullRowColour);
connect(m_textWidget, &TeletextWidget::insertKeyPressed, this, &MainWindow::toggleInsertMode); connect(m_textWidget, &TeletextWidget::insertKeyPressed, this, &MainWindow::toggleInsertMode);
connect(m_textScene, &LevelOneScene::mouseZoomIn, this, &MainWindow::zoomIn); connect(m_textScene, &LevelOneScene::mouseZoomIn, this, &MainWindow::zoomIn);
@@ -456,13 +463,14 @@ void MainWindow::createActions()
revealAct->setCheckable(true); revealAct->setCheckable(true);
revealAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R)); revealAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R));
revealAct->setStatusTip(tr("Toggle reveal")); revealAct->setStatusTip(tr("Toggle reveal"));
connect(revealAct, &QAction::toggled, m_textWidget, &TeletextWidget::toggleReveal); connect(revealAct, &QAction::toggled, m_textWidget, &TeletextWidget::setReveal);
QAction *mixAct = viewMenu->addAction(tr("&Mix")); QAction *mixAct = viewMenu->addAction(tr("&Mix"));
mixAct->setCheckable(true); mixAct->setCheckable(true);
mixAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M)); mixAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M));
mixAct->setStatusTip(tr("Toggle mix")); mixAct->setStatusTip(tr("Toggle mix"));
connect(mixAct, &QAction::toggled, m_textWidget, &TeletextWidget::toggleMix); connect(mixAct, &QAction::toggled, m_textWidget, &TeletextWidget::setMix);
connect(mixAct, &QAction::toggled, m_textScene, &LevelOneScene::setMix);
QAction *gridAct = viewMenu->addAction(tr("&Grid")); QAction *gridAct = viewMenu->addAction(tr("&Grid"));
gridAct->setCheckable(true); gridAct->setCheckable(true);
@@ -470,11 +478,11 @@ void MainWindow::createActions()
gridAct->setStatusTip(tr("Toggle the text grid")); gridAct->setStatusTip(tr("Toggle the text grid"));
connect(gridAct, &QAction::toggled, m_textScene, &LevelOneScene::toggleGrid); connect(gridAct, &QAction::toggled, m_textScene, &LevelOneScene::toggleGrid);
QAction *showCodesAct = viewMenu->addAction(tr("Show codes")); QAction *showControlCodesAct = viewMenu->addAction(tr("Show control codes"));
showCodesAct->setCheckable(true); showControlCodesAct->setCheckable(true);
showCodesAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_T)); showControlCodesAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_T));
showCodesAct->setStatusTip(tr("Toggle showing of control codes")); showControlCodesAct->setStatusTip(tr("Toggle showing of control codes"));
connect(showCodesAct, &QAction::toggled, m_textWidget->pageRender(), &TeletextPageRender::setShowCodes); connect(showControlCodesAct, &QAction::toggled, m_textWidget, &TeletextWidget::setShowControlCodes);
viewMenu->addSeparator(); viewMenu->addSeparator();
@@ -633,6 +641,7 @@ void MainWindow::createActions()
toolsMenu->addAction(m_x26DockWidget->toggleViewAction()); toolsMenu->addAction(m_x26DockWidget->toggleViewAction());
toolsMenu->addAction(m_pageEnhancementsDockWidget->toggleViewAction()); toolsMenu->addAction(m_pageEnhancementsDockWidget->toggleViewAction());
toolsMenu->addAction(m_paletteDockWidget->toggleViewAction()); toolsMenu->addAction(m_paletteDockWidget->toggleViewAction());
toolsMenu->addAction(m_pageComposeLinksDockWidget->toggleViewAction());
//FIXME is this main menubar separator to put help menu towards the right? //FIXME is this main menubar separator to put help menu towards the right?
menuBar()->addSeparator(); menuBar()->addSeparator();
@@ -668,7 +677,7 @@ void MainWindow::setSceneDimensions()
else else
newSceneWidth = m_textWidget->width() + leftRightBorders[m_viewBorder]*2; newSceneWidth = m_textWidget->width() + leftRightBorders[m_viewBorder]*2;
m_textScene->setBorderDimensions(newSceneWidth, 250+topBottomBorders[m_viewBorder]*2, m_textWidget->width(), m_textWidget->pageRender()->leftSidePanelColumns(), m_textWidget->pageRender()->rightSidePanelColumns()); m_textScene->setBorderDimensions(newSceneWidth, 250+topBottomBorders[m_viewBorder]*2, m_textWidget->width(), m_textWidget->pageDecode()->leftSidePanelColumns(), m_textWidget->pageDecode()->rightSidePanelColumns());
m_textView->setTransform(QTransform((1+(float)m_viewZoom/2)*aspectRatioHorizontalScaling[m_viewAspectRatio], 0, 0, 1+(float)m_viewZoom/2, 0, 0)); m_textView->setTransform(QTransform((1+(float)m_viewZoom/2)*aspectRatioHorizontalScaling[m_viewAspectRatio], 0, 0, 1+(float)m_viewZoom/2, 0, 0));
} }
@@ -753,30 +762,27 @@ void MainWindow::toggleInsertMode()
void MainWindow::createStatusBar() void MainWindow::createStatusBar()
{ {
QLabel *subPageLabel = new QLabel("Subpage");
statusBar()->insertWidget(0, subPageLabel);
m_previousSubPageButton = new QToolButton; m_previousSubPageButton = new QToolButton;
m_previousSubPageButton->setMinimumSize(subPageLabel->height(), subPageLabel->height());
m_previousSubPageButton->setMaximumSize(subPageLabel->height(), subPageLabel->height());
m_previousSubPageButton->setAutoRaise(true); m_previousSubPageButton->setAutoRaise(true);
m_previousSubPageButton->setArrowType(Qt::LeftArrow); m_previousSubPageButton->setArrowType(Qt::LeftArrow);
statusBar()->insertWidget(1, m_previousSubPageButton); statusBar()->insertWidget(0, m_previousSubPageButton);
connect(m_previousSubPageButton, &QToolButton::clicked, m_textWidget->document(), &TeletextDocument::selectSubPagePrevious); connect(m_previousSubPageButton, &QToolButton::clicked, m_textWidget->document(), &TeletextDocument::selectSubPagePrevious);
m_subPageLabel = new QLabel("1/1"); m_subPageLabel = new QLabel("1/1");
statusBar()->insertWidget(2, m_subPageLabel); statusBar()->insertWidget(1, m_subPageLabel);
m_nextSubPageButton = new QToolButton; m_nextSubPageButton = new QToolButton;
m_nextSubPageButton->setMinimumSize(subPageLabel->height(), subPageLabel->height()); m_previousSubPageButton->setMinimumSize(m_subPageLabel->height(), m_subPageLabel->height());
m_nextSubPageButton->setMaximumSize(subPageLabel->height(), subPageLabel->height()); m_previousSubPageButton->setMaximumSize(m_subPageLabel->height(), m_subPageLabel->height());
m_nextSubPageButton->setMinimumSize(m_subPageLabel->height(), m_subPageLabel->height());
m_nextSubPageButton->setMaximumSize(m_subPageLabel->height(), m_subPageLabel->height());
m_nextSubPageButton->setAutoRaise(true); m_nextSubPageButton->setAutoRaise(true);
m_nextSubPageButton->setArrowType(Qt::RightArrow); m_nextSubPageButton->setArrowType(Qt::RightArrow);
statusBar()->insertWidget(3, m_nextSubPageButton); statusBar()->insertWidget(2, m_nextSubPageButton);
connect(m_nextSubPageButton, &QToolButton::clicked, m_textWidget->document(), &TeletextDocument::selectSubPageNext); connect(m_nextSubPageButton, &QToolButton::clicked, m_textWidget->document(), &TeletextDocument::selectSubPageNext);
m_cursorPositionLabel = new QLabel("Row 1 Column 1"); m_cursorPositionLabel = new QLabel("1, 1");
statusBar()->insertWidget(4, m_cursorPositionLabel); statusBar()->insertWidget(3, m_cursorPositionLabel);
m_insertModePushButton = new QPushButton("OVERWRITE"); m_insertModePushButton = new QPushButton("OVERWRITE");
m_insertModePushButton->setFlat(true); m_insertModePushButton->setFlat(true);
@@ -795,10 +801,10 @@ void MainWindow::createStatusBar()
statusBar()->addPermanentWidget(m_levelRadioButton[i]); statusBar()->addPermanentWidget(m_levelRadioButton[i]);
} }
m_levelRadioButton[0]->toggle(); m_levelRadioButton[0]->toggle();
connect(m_levelRadioButton[0], &QAbstractButton::clicked, [=]() { m_textWidget->pageRender()->setRenderLevel(0); m_textWidget->update(); }); connect(m_levelRadioButton[0], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setLevel(0); m_textWidget->update(); });
connect(m_levelRadioButton[1], &QAbstractButton::clicked, [=]() { m_textWidget->pageRender()->setRenderLevel(1); m_textWidget->update(); }); connect(m_levelRadioButton[1], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setLevel(1); m_textWidget->update(); });
connect(m_levelRadioButton[2], &QAbstractButton::clicked, [=]() { m_textWidget->pageRender()->setRenderLevel(2); m_textWidget->update(); }); connect(m_levelRadioButton[2], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setLevel(2); m_textWidget->update(); });
connect(m_levelRadioButton[3], &QAbstractButton::clicked, [=]() { m_textWidget->pageRender()->setRenderLevel(3); m_textWidget->update(); }); connect(m_levelRadioButton[3], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setLevel(3); m_textWidget->update(); });
statusBar()->showMessage(tr("Ready")); statusBar()->showMessage(tr("Ready"));
} }
@@ -846,6 +852,8 @@ void MainWindow::readSettings()
m_x26DockWidget->setFloating(true); m_x26DockWidget->setFloating(true);
m_paletteDockWidget->hide(); m_paletteDockWidget->hide();
m_paletteDockWidget->setFloating(true); m_paletteDockWidget->setFloating(true);
m_pageComposeLinksDockWidget->hide();
m_pageComposeLinksDockWidget->setFloating(true);
} else } else
restoreState(windowState); restoreState(windowState);
} }
@@ -865,7 +873,7 @@ bool MainWindow::maybeSave()
{ {
if (m_textWidget->document()->undoStack()->isClean()) if (m_textWidget->document()->undoStack()->isClean())
return true; return true;
const QMessageBox::StandardButton ret = QMessageBox::warning(this, QApplication::applicationDisplayName(), tr("The document has been modified.\nDo you want to save your changes?"), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); const QMessageBox::StandardButton ret = QMessageBox::warning(this, QApplication::applicationDisplayName(), tr("The document \"%1\" has been modified.\nDo you want to save your changes or discard them?").arg(QFileInfo(m_curFile).fileName()), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
switch (ret) { switch (ret) {
case QMessageBox::Save: case QMessageBox::Save:
return save(); return save();
@@ -905,7 +913,7 @@ void MainWindow::loadFile(const QString &fileName)
levelSeen = m_textWidget->document()->levelRequired(); levelSeen = m_textWidget->document()->levelRequired();
m_levelRadioButton[levelSeen]->toggle(); m_levelRadioButton[levelSeen]->toggle();
m_textWidget->pageRender()->setRenderLevel(levelSeen); m_textWidget->pageDecode()->setLevel(levelSeen);
updatePageWidgets(); updatePageWidgets();
QApplication::restoreOverrideCursor(); QApplication::restoreOverrideCursor();
@@ -1129,7 +1137,20 @@ MainWindow *MainWindow::findMainWindow(const QString &fileName) const
void MainWindow::updateCursorPosition() void MainWindow::updateCursorPosition()
{ {
m_cursorPositionLabel->setText(QString("Row %1 Column %2").arg(m_textWidget->document()->cursorRow()).arg(m_textWidget->document()->cursorColumn())); QString result;
result.reserve(19);
if (m_textWidget->document()->cursorRow() == 0)
result = QString("Header, %1").arg(m_textWidget->document()->cursorColumn());
else if (m_textWidget->document()->cursorRow() == 24)
result = QString("FLOF, %1").arg(m_textWidget->document()->cursorColumn());
else
result = QString("%1, %2").arg(m_textWidget->document()->cursorRow()).arg(m_textWidget->document()->cursorColumn());
if (m_textWidget->document()->selectionActive())
result.append(QString(" (%1, %2)").arg(m_textWidget->document()->selectionHeight()).arg(m_textWidget->document()->selectionWidth()));
m_cursorPositionLabel->setText(result);
m_textScene->updateCursor(); m_textScene->updateCursor();
m_textView->ensureVisible(m_textScene->cursorRectItem(), 16, 24); m_textView->ensureVisible(m_textScene->cursorRectItem(), 16, 24);
} }
@@ -1139,9 +1160,11 @@ void MainWindow::updatePageWidgets()
m_subPageLabel->setText(QString("%1/%2").arg(m_textWidget->document()->currentSubPageIndex()+1).arg(m_textWidget->document()->numberOfSubPages())); m_subPageLabel->setText(QString("%1/%2").arg(m_textWidget->document()->currentSubPageIndex()+1).arg(m_textWidget->document()->numberOfSubPages()));
m_previousSubPageButton->setEnabled(!(m_textWidget->document()->currentSubPageIndex() == 0)); m_previousSubPageButton->setEnabled(!(m_textWidget->document()->currentSubPageIndex() == 0));
m_nextSubPageButton->setEnabled(!(m_textWidget->document()->currentSubPageIndex() == (m_textWidget->document()->numberOfSubPages()) - 1)); m_nextSubPageButton->setEnabled(!(m_textWidget->document()->currentSubPageIndex() == (m_textWidget->document()->numberOfSubPages()) - 1));
updateCursorPosition();
m_deleteSubPageAction->setEnabled(m_textWidget->document()->numberOfSubPages() > 1); m_deleteSubPageAction->setEnabled(m_textWidget->document()->numberOfSubPages() > 1);
m_pageOptionsDockWidget->updateWidgets(); m_pageOptionsDockWidget->updateWidgets();
m_pageEnhancementsDockWidget->updateWidgets(); m_pageEnhancementsDockWidget->updateWidgets();
m_x26DockWidget->loadX26List(); m_x26DockWidget->loadX26List();
m_paletteDockWidget->updateAllColourButtons(); m_paletteDockWidget->updateAllColourButtons();
m_pageComposeLinksDockWidget->updateWidgets();
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -31,6 +31,7 @@
#include <QToolButton> #include <QToolButton>
#include "mainwidget.h" #include "mainwidget.h"
#include "pagecomposelinksdockwidget.h"
#include "pageenhancementsdockwidget.h" #include "pageenhancementsdockwidget.h"
#include "pageoptionsdockwidget.h" #include "pageoptionsdockwidget.h"
#include "palettedockwidget.h" #include "palettedockwidget.h"
@@ -114,6 +115,7 @@ private:
PageEnhancementsDockWidget *m_pageEnhancementsDockWidget; PageEnhancementsDockWidget *m_pageEnhancementsDockWidget;
X26DockWidget *m_x26DockWidget; X26DockWidget *m_x26DockWidget;
PaletteDockWidget *m_paletteDockWidget; PaletteDockWidget *m_paletteDockWidget;
PageComposeLinksDockWidget *m_pageComposeLinksDockWidget;
QAction *m_recentFileActs[m_MaxRecentFiles]; QAction *m_recentFileActs[m_MaxRecentFiles];
QAction *m_recentFileSeparator; QAction *m_recentFileSeparator;

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -0,0 +1,222 @@
/*
* Copyright (C) 2020-2022 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 <QCheckBox>
#include <QComboBox>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMap>
#include <QPair>
#include <QRegExpValidator>
#include <QString>
#include "pagecomposelinksdockwidget.h"
PageComposeLinksDockWidget::PageComposeLinksDockWidget(TeletextWidget *parent): QDockWidget(parent)
{
QVBoxLayout *pageComposeLinksLayout = new QVBoxLayout;
QWidget *pageComposeLinksWidget = new QWidget;
m_parentMainWidget = parent;
this->setObjectName("PageComposeLinksDockWidget");
this->setWindowTitle("Compositional links");
QGridLayout *x27Layout = new QGridLayout;
QLabel *levelAllLabel = new QLabel(tr("Levels 2.5 and 3.5"));
levelAllLabel->setAlignment(Qt::AlignCenter);
x27Layout->addWidget(levelAllLabel, 0, 0, 1, 5);
// Link functions
x27Layout->addWidget(new QLabel(tr("GPOP")), 2, 0, 1, 1);
x27Layout->addWidget(new QLabel(tr("POP")), 3, 0, 1, 1);
x27Layout->addWidget(new QLabel(tr("GDRCS")), 4, 0, 1, 1);
x27Layout->addWidget(new QLabel(tr("DRCS")), 5, 0, 1, 1);
// Labels across the top
x27Layout->addWidget(new QLabel(tr("L2.5")), 1, 1, 1, 1);
x27Layout->addWidget(new QLabel(tr("L3.5")), 1, 2, 1, 1);
x27Layout->addWidget(new QLabel(tr("Page no")), 1, 3, 1, 1);
x27Layout->addWidget(new QLabel(tr("Sub pages required")), 1, 4, 1, 1);
QLabel *level3p5OnlyLabel = new QLabel(tr("Level 3.5 only"));
level3p5OnlyLabel->setAlignment(Qt::AlignCenter);
x27Layout->addWidget(level3p5OnlyLabel, 6, 0, 1, 5);
m_pageNumberValidator = new QRegExpValidator(QRegExp("[1-8][0-9A-Fa-f][0-9A-Fa-f]"), this);
for (int i=0; i<8; i++) {
if (i < 4) {
// Required at which Levels
m_composeLinkLevelCheckbox[i][0] = new QCheckBox(this);
x27Layout->addWidget(m_composeLinkLevelCheckbox[i][0], i+2, 1, 1, 1);
connect(m_composeLinkLevelCheckbox[i][0], &QCheckBox::stateChanged, [=](bool active) { m_parentMainWidget->document()->currentSubPage()->setComposeLinkLevel2p5(i, active); });
m_composeLinkLevelCheckbox[i][1] = new QCheckBox(this);
x27Layout->addWidget(m_composeLinkLevelCheckbox[i][1], i+2, 2, 1, 1);
connect(m_composeLinkLevelCheckbox[i][1], &QCheckBox::stateChanged, [=](bool active) { m_parentMainWidget->document()->currentSubPage()->setComposeLinkLevel3p5(i, active); });
} else {
// Selectable link functions for Level 3.5
m_composeLinkFunctionComboBox[i-4] = new QComboBox;
m_composeLinkFunctionComboBox[i-4]->addItem("GPOP");
m_composeLinkFunctionComboBox[i-4]->addItem("POP");
m_composeLinkFunctionComboBox[i-4]->addItem("GDRCS");
m_composeLinkFunctionComboBox[i-4]->addItem("DRCS");
x27Layout->addWidget(m_composeLinkFunctionComboBox[i-4], i+3, 0, 1, 1, Qt::AlignTop);
connect(m_composeLinkFunctionComboBox[i-4], QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index) { m_parentMainWidget->document()->currentSubPage()->setComposeLinkFunction(i, index); } );
}
// Page link
m_composeLinkPageNumberLineEdit[i] = new QLineEdit("100");
m_composeLinkPageNumberLineEdit[i]->setMaxLength(3);
m_composeLinkPageNumberLineEdit[i]->setInputMask(">DHH");
m_composeLinkPageNumberLineEdit[i]->setValidator(m_pageNumberValidator);
x27Layout->addWidget(m_composeLinkPageNumberLineEdit[i], i+(i<4 ? 2 : 3), 3, 1, 1);
connect(m_composeLinkPageNumberLineEdit[i], &QLineEdit::textEdited, [=](QString value) { setComposeLinkPageNumber(i, value); } );
// Sub page numbers required
m_composeLinkSubPageNumbersLineEdit[i] = new QLineEdit(this);
x27Layout->addWidget(m_composeLinkSubPageNumbersLineEdit[i], i+(i<4 ? 2 : 3), 4, 1, 1);
connect(m_composeLinkSubPageNumbersLineEdit[i], &QLineEdit::textEdited, [=](QString value) { setComposeLinkSubPageNumbers(i, value); } );
}
pageComposeLinksLayout->addLayout(x27Layout);
pageComposeLinksLayout->addStretch(1);
pageComposeLinksWidget->setLayout(pageComposeLinksLayout);
this->setWidget(pageComposeLinksWidget);
}
void PageComposeLinksDockWidget::setComposeLinkPageNumber(int linkNumber, const QString &newPageNumberString)
{
// The LineEdit should check if a valid hex number was entered, but just in case...
bool newPageNumberOk;
int newPageNumberRead = newPageNumberString.toInt(&newPageNumberOk, 16);
if ((!newPageNumberOk) || newPageNumberRead < 0x100 || newPageNumberRead > 0x8ff)
return;
// Stored as page link with relative magazine number, convert from absolute page number that was entered
newPageNumberRead ^= (m_parentMainWidget->document()->pageNumber() & 0x700);
m_parentMainWidget->document()->currentSubPage()->setComposeLinkPageNumber(linkNumber, newPageNumberRead);
}
void PageComposeLinksDockWidget::setComposeLinkSubPageNumbers(int linkNumber, const QString &newSubPagesString)
{
int newSubPageCodes = 0x0000;
if (!newSubPagesString.isEmpty()) {
// Turn user-entered comma separated subpages and subpage-ranges into bits
// First we do the "comma separated"
const QStringList items = newSubPagesString.split(QLatin1Char(','), QString::SkipEmptyParts);
// Now see if there's valid single numbers or ranges between the commas
for (const QString &item : items) {
if (item.isEmpty())
return;
if (item.contains(QLatin1Char('-'))) {
// Dash found, should be a range of subpages: check for two things either side of dash
const QStringList rangeItems = item.split('-');
if (rangeItems.count() != 2)
return;
// Now check if the two things are valid numbers 0-15, with first number less than second
bool ok1, ok2;
const int number1 = rangeItems[0].toInt(&ok1);
const int number2 = rangeItems[1].toInt(&ok2);
if (!ok1 || !ok2 || number1 < 0 || number2 < 0 || number1 > 15 || number2 > 15 || number2 < number1)
return;
// Yes, it is a valid range. Apply it to the result
for (int i=number1; i<=number2; i++)
newSubPageCodes |= (1 << i);
} else {
// A single thing (maybe extracted between commas): check if the thing is a number 0-15
bool ok;
const int number = item.toInt(&ok);
if (!ok || number < 0 || number > 15)
return;
// Yes, it is a valid number. Apply it to the result
newSubPageCodes |= (1 << number);
}
}
}
m_parentMainWidget->document()->currentSubPage()->setComposeLinkSubPageCodes(linkNumber, newSubPageCodes);
}
void PageComposeLinksDockWidget::updateWidgets()
{
for (int i=0; i<8; i++) {
if (i >= 4) {
m_composeLinkFunctionComboBox[i-4]->blockSignals(true);
m_composeLinkFunctionComboBox[i-4]->setCurrentIndex(m_parentMainWidget->document()->currentSubPage()->composeLinkFunction(i));
m_composeLinkFunctionComboBox[i-4]->blockSignals(false);
} else {
m_composeLinkLevelCheckbox[i][0]->blockSignals(true);
m_composeLinkLevelCheckbox[i][0]->setChecked(m_parentMainWidget->document()->currentSubPage()->composeLinkLevel2p5(i));
m_composeLinkLevelCheckbox[i][0]->blockSignals(false);
m_composeLinkLevelCheckbox[i][1]->blockSignals(true);
m_composeLinkLevelCheckbox[i][1]->setChecked(m_parentMainWidget->document()->currentSubPage()->composeLinkLevel3p5(i));
m_composeLinkLevelCheckbox[i][1]->blockSignals(false);
}
// Stored as page link with relative magazine number, convert to absolute page number for display
int absoluteLinkPageNumber = m_parentMainWidget->document()->currentSubPage()->composeLinkPageNumber(i) ^ (m_parentMainWidget->document()->pageNumber() & 0x700);
// Fix magazine 0 to 8
if ((absoluteLinkPageNumber & 0x700) == 0x000)
absoluteLinkPageNumber |= 0x800;
m_composeLinkPageNumberLineEdit[i]->blockSignals(true);
m_composeLinkPageNumberLineEdit[i]->setText(QString::number(absoluteLinkPageNumber, 16).toUpper());
m_composeLinkPageNumberLineEdit[i]->blockSignals(false);
// Turn subpage bits into user-friendly comma separated numbers and number-ranges
QString rangeString;
if (m_parentMainWidget->document()->currentSubPage()->composeLinkSubPageCodes(i) != 0x0000) {
// First build a list of consecutive ranges seen
// The "b-index" is based on https://codereview.stackexchange.com/a/90074
QMap<int, QPair<int, int>> ranges;
int index = 0;
for (int b=0; b<16; b++)
if ((m_parentMainWidget->document()->currentSubPage()->composeLinkSubPageCodes(i) >> b) & 0x01) {
if (!ranges.contains(b-index))
ranges.insert(b-index, qMakePair(b, b));
else
ranges[b-index].second = b;
index++;
}
// Now go through the list and add single numbers or ranges as appropriate
QPair<int, int> range;
foreach (range, ranges) {
// For second and subsequent entries only, append a comma first
if (!rangeString.isEmpty())
rangeString.append(',');
// Append the single number or the first number of the range
rangeString.append(QString("%1").arg(range.first));
// If that was part of a range and not a single orphaned number
if (range.first != range.second) {
// Ranges of 3 or more use a dash. A range of 2 can make do with a comma
rangeString.append((range.first+1 == range.second) ? ',' : '-');
// Append the second number of the range
rangeString.append(QString("%1").arg(range.second));
}
}
}
m_composeLinkSubPageNumbersLineEdit[i]->blockSignals(true);
m_composeLinkSubPageNumbersLineEdit[i]->setText(rangeString);
m_composeLinkSubPageNumbersLineEdit[i]->blockSignals(false);
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2020-2022 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/>.
*/
#ifndef PAGECOMPOSELINKSDOCKWIDGET_H
#define PAGECOMPOSELINKSDOCKWIDGET_H
#include <QCheckBox>
#include <QComboBox>
#include <QDockWidget>
#include <QLineEdit>
#include <QRegExpValidator>
#include <QString>
#include "mainwidget.h"
class PageComposeLinksDockWidget : public QDockWidget
{
Q_OBJECT
public:
PageComposeLinksDockWidget(TeletextWidget *parent);
void updateWidgets();
private:
void setComposeLinkPageNumber(int, const QString &);
void setComposeLinkSubPageNumbers(int, const QString &);
TeletextWidget *m_parentMainWidget;
QCheckBox *m_composeLinkLevelCheckbox[4][2]; // For links 0-3
QComboBox *m_composeLinkFunctionComboBox[4]; // For links 4-7; remember to subtract 4!
QLineEdit *m_composeLinkPageNumberLineEdit[8], *m_composeLinkSubPageNumbersLineEdit[8];
QRegExpValidator *m_pageNumberValidator;
};
#endif

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -22,12 +22,7 @@
#include <QGridLayout> #include <QGridLayout>
#include <QGroupBox> #include <QGroupBox>
#include <QLabel> #include <QLabel>
#include <QLineEdit>
#include <QMap>
#include <QPair>
#include <QRegExpValidator>
#include <QSpinBox> #include <QSpinBox>
#include <QString>
#include "pageenhancementsdockwidget.h" #include "pageenhancementsdockwidget.h"
@@ -39,25 +34,26 @@ PageEnhancementsDockWidget::PageEnhancementsDockWidget(TeletextWidget *parent):
m_parentMainWidget = parent; m_parentMainWidget = parent;
this->setObjectName("PageEnhancementsDockWidget"); this->setObjectName("PageEnhancementsDockWidget");
this->setWindowTitle("Page enhancements"); this->setWindowTitle("X/28 page enhancements");
QGroupBox *x28GroupBox = new QGroupBox(tr("X/28 enhancements")); // Colour group box and layout
QGridLayout *x28Layout = new QGridLayout; QGroupBox *colourGroupBox = new QGroupBox(tr("Colours"));
QGridLayout *colourLayout = new QGridLayout;
// Default screen and default row colours // Default screen and default row colours
x28Layout->addWidget(new QLabel(tr("Default screen colour")), 0, 0, 1, 1); colourLayout->addWidget(new QLabel(tr("Default screen colour")), 0, 0, 1, 1);
x28Layout->addWidget(new QLabel(tr("Default row colour")), 1, 0, 1, 1); colourLayout->addWidget(new QLabel(tr("Default row colour")), 1, 0, 1, 1);
m_defaultScreenColourCombo = new QComboBox; m_defaultScreenColourCombo = new QComboBox;
m_defaultScreenColourCombo->setModel(m_parentMainWidget->document()->clutModel()); m_defaultScreenColourCombo->setModel(m_parentMainWidget->document()->clutModel());
m_defaultRowColourCombo = new QComboBox; m_defaultRowColourCombo = new QComboBox;
m_defaultRowColourCombo->setModel(m_parentMainWidget->document()->clutModel()); m_defaultRowColourCombo->setModel(m_parentMainWidget->document()->clutModel());
x28Layout->addWidget(m_defaultScreenColourCombo, 0, 1, 1, 1, Qt::AlignTop); colourLayout->addWidget(m_defaultScreenColourCombo, 0, 1, 1, 1, Qt::AlignTop);
connect(m_defaultScreenColourCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index){ m_parentMainWidget->setDefaultScreenColour(index); }); connect(m_defaultScreenColourCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index){ m_parentMainWidget->setDefaultScreenColour(index); });
x28Layout->addWidget(m_defaultRowColourCombo, 1, 1, 1, 1, Qt::AlignTop); colourLayout->addWidget(m_defaultRowColourCombo, 1, 1, 1, 1, Qt::AlignTop);
connect(m_defaultRowColourCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index){ m_parentMainWidget->setDefaultRowColour(index); }); connect(m_defaultRowColourCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index){ m_parentMainWidget->setDefaultRowColour(index); });
// CLUT remapping // CLUT remapping
x28Layout->addWidget(new QLabel(tr("CLUT remapping")), 2, 0, 1, 1); colourLayout->addWidget(new QLabel(tr("CLUT remapping")), 2, 0, 1, 1);
m_colourTableCombo = new QComboBox; m_colourTableCombo = new QComboBox;
m_colourTableCombo->addItem("Fore 0 Back 0"); m_colourTableCombo->addItem("Fore 0 Back 0");
m_colourTableCombo->addItem("Fore 0 Back 1"); m_colourTableCombo->addItem("Fore 0 Back 1");
@@ -67,91 +63,44 @@ PageEnhancementsDockWidget::PageEnhancementsDockWidget(TeletextWidget *parent):
m_colourTableCombo->addItem("Fore 2 Back 1"); m_colourTableCombo->addItem("Fore 2 Back 1");
m_colourTableCombo->addItem("Fore 2 Back 2"); m_colourTableCombo->addItem("Fore 2 Back 2");
m_colourTableCombo->addItem("Fore 2 Back 3"); m_colourTableCombo->addItem("Fore 2 Back 3");
x28Layout->addWidget(m_colourTableCombo, 2, 1, 1, 1, Qt::AlignTop); colourLayout->addWidget(m_colourTableCombo, 2, 1, 1, 1, Qt::AlignTop);
connect(m_colourTableCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index){ m_parentMainWidget->setColourTableRemap(index); }); connect(m_colourTableCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index){ m_parentMainWidget->setColourTableRemap(index); });
// Black background colour substitution // Black background colour substitution
m_blackBackgroundSubstAct = new QCheckBox("Black background colour substitution"); m_blackBackgroundSubstAct = new QCheckBox("Black background colour substitution");
x28Layout->addWidget(m_blackBackgroundSubstAct, 3, 0, 1, 2, Qt::AlignTop); colourLayout->addWidget(m_blackBackgroundSubstAct, 3, 0, 1, 2, Qt::AlignTop);
connect(m_blackBackgroundSubstAct, &QCheckBox::stateChanged, m_parentMainWidget, &TeletextWidget::setBlackBackgroundSubst); connect(m_blackBackgroundSubstAct, &QCheckBox::stateChanged, m_parentMainWidget, &TeletextWidget::setBlackBackgroundSubst);
// Add group box to the main layout
colourGroupBox->setLayout(colourLayout);
pageEnhancementsLayout->addWidget(colourGroupBox);
// Side panels group box and layout
QGroupBox *sidePanelsGroupBox = new QGroupBox(tr("Side panels"));
QGridLayout *sidePanelsLayout = new QGridLayout;
// Side panel columns // Side panel columns
x28Layout->addWidget(new QLabel(tr("Left side panel columns")), 4, 0, 1, 1); sidePanelsLayout->addWidget(new QLabel(tr("Left side panel columns")), 0, 0, 1, 1);
m_leftSidePanelSpinBox = new QSpinBox(this); m_leftSidePanelSpinBox = new QSpinBox(this);
m_leftSidePanelSpinBox->setMaximum(16); m_leftSidePanelSpinBox->setMaximum(16);
x28Layout->addWidget(m_leftSidePanelSpinBox, 4, 1, 1, 1, Qt::AlignTop); sidePanelsLayout->addWidget(m_leftSidePanelSpinBox, 0, 1, 1, 1, Qt::AlignTop);
connect(m_leftSidePanelSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), [=](int index){ setLeftSidePanelWidth(index); }); connect(m_leftSidePanelSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), [=](int index){ setLeftSidePanelWidth(index); });
x28Layout->addWidget(new QLabel(tr("Right side panel columns")), 5, 0, 1, 1); sidePanelsLayout->addWidget(new QLabel(tr("Right side panel columns")), 1, 0, 1, 1);
m_rightSidePanelSpinBox = new QSpinBox(this); m_rightSidePanelSpinBox = new QSpinBox(this);
m_rightSidePanelSpinBox->setMaximum(16); m_rightSidePanelSpinBox->setMaximum(16);
x28Layout->addWidget(m_rightSidePanelSpinBox, 5, 1, 1, 1, Qt::AlignTop); sidePanelsLayout->addWidget(m_rightSidePanelSpinBox, 1, 1, 1, 1, Qt::AlignTop);
connect(m_rightSidePanelSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), [=](int index){ setRightSidePanelWidth(index); }); connect(m_rightSidePanelSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), [=](int index){ setRightSidePanelWidth(index); });
// Side panels status // Side panels status
m_sidePanelStatusAct = new QCheckBox("Side panels at level 3.5 only"); m_sidePanelStatusAct = new QCheckBox("Side panels at level 3.5 only");
x28Layout->addWidget(m_sidePanelStatusAct, 6, 0, 1, 2, Qt::AlignTop); sidePanelsLayout->addWidget(m_sidePanelStatusAct, 2, 0, 1, 2, Qt::AlignTop);
connect(m_sidePanelStatusAct, &QCheckBox::stateChanged, m_parentMainWidget, &TeletextWidget::setSidePanelAtL35Only); connect(m_sidePanelStatusAct, &QCheckBox::stateChanged, m_parentMainWidget, &TeletextWidget::setSidePanelAtL35Only);
x28GroupBox->setLayout(x28Layout); // Add group box to the main layout
pageEnhancementsLayout->addWidget(x28GroupBox); sidePanelsGroupBox->setLayout(sidePanelsLayout);
pageEnhancementsLayout->addWidget(sidePanelsGroupBox);
QGroupBox *x27GroupBox = new QGroupBox(tr("X/27 links - Level 2.5 and 3.5"));
QGridLayout *x27Layout = new QGridLayout;
// Link functions
x27Layout->addWidget(new QLabel(tr("GPOP")), 1, 0, 1, 1);
x27Layout->addWidget(new QLabel(tr("POP")), 2, 0, 1, 1);
x27Layout->addWidget(new QLabel(tr("GDRCS")), 3, 0, 1, 1);
x27Layout->addWidget(new QLabel(tr("DRCS")), 4, 0, 1, 1);
// Labels across the top
x27Layout->addWidget(new QLabel(tr("L2.5")), 0, 1, 1, 1);
x27Layout->addWidget(new QLabel(tr("L3.5")), 0, 2, 1, 1);
x27Layout->addWidget(new QLabel(tr("Page no")), 0, 3, 1, 1);
x27Layout->addWidget(new QLabel(tr("Sub pages required")), 0, 4, 1, 1);
QLabel *level3p5OnlyLabel = new QLabel(tr("Level 3.5 only"));
level3p5OnlyLabel->setAlignment(Qt::AlignCenter);
x27Layout->addWidget(level3p5OnlyLabel, 5, 0, 1, 5);
m_pageNumberValidator = new QRegExpValidator(QRegExp("[1-8][0-9A-Fa-f][0-9A-Fa-f]"), this);
for (int i=0; i<8; i++) {
if (i < 4) {
// Required at which Levels
m_composeLinkLevelCheckbox[i][0] = new QCheckBox(this);
x27Layout->addWidget(m_composeLinkLevelCheckbox[i][0], i+1, 1, 1, 1);
connect(m_composeLinkLevelCheckbox[i][0], &QCheckBox::stateChanged, [=](bool active) { m_parentMainWidget->document()->currentSubPage()->setComposeLinkLevel2p5(i, active); });
m_composeLinkLevelCheckbox[i][1] = new QCheckBox(this);
x27Layout->addWidget(m_composeLinkLevelCheckbox[i][1], i+1, 2, 1, 1);
connect(m_composeLinkLevelCheckbox[i][1], &QCheckBox::stateChanged, [=](bool active) { m_parentMainWidget->document()->currentSubPage()->setComposeLinkLevel3p5(i, active); });
} else {
// Selectable link functions for Level 3.5
m_composeLinkFunctionComboBox[i-4] = new QComboBox;
m_composeLinkFunctionComboBox[i-4]->addItem("GPOP");
m_composeLinkFunctionComboBox[i-4]->addItem("POP");
m_composeLinkFunctionComboBox[i-4]->addItem("GDRCS");
m_composeLinkFunctionComboBox[i-4]->addItem("DRCS");
x27Layout->addWidget(m_composeLinkFunctionComboBox[i-4], i+2, 0, 1, 1, Qt::AlignTop);
connect(m_composeLinkFunctionComboBox[i-4], QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index) { m_parentMainWidget->document()->currentSubPage()->setComposeLinkFunction(i, index); } );
}
// Page link
m_composeLinkPageNumberLineEdit[i] = new QLineEdit("100");
m_composeLinkPageNumberLineEdit[i]->setMaxLength(3);
m_composeLinkPageNumberLineEdit[i]->setInputMask(">DHH");
m_composeLinkPageNumberLineEdit[i]->setValidator(m_pageNumberValidator);
x27Layout->addWidget(m_composeLinkPageNumberLineEdit[i], i+(i<4 ? 1 : 2), 3, 1, 1);
connect(m_composeLinkPageNumberLineEdit[i], &QLineEdit::textEdited, [=](QString value) { setComposeLinkPageNumber(i, value); } );
// Sub page numbers required
m_composeLinkSubPageNumbersLineEdit[i] = new QLineEdit(this);
x27Layout->addWidget(m_composeLinkSubPageNumbersLineEdit[i], i+(i<4 ? 1 : 2), 4, 1, 1);
connect(m_composeLinkSubPageNumbersLineEdit[i], &QLineEdit::textEdited, [=](QString value) { setComposeLinkSubPageNumbers(i, value); } );
}
x27GroupBox->setLayout(x27Layout);
pageEnhancementsLayout->addWidget(x27GroupBox);
pageEnhancementsLayout->addStretch(1); pageEnhancementsLayout->addStretch(1);
@@ -181,60 +130,6 @@ void PageEnhancementsDockWidget::setRightSidePanelWidth(int newRightPanelSize)
m_parentMainWidget->setSidePanelWidths(m_leftSidePanelSpinBox->value(), newRightPanelSize); m_parentMainWidget->setSidePanelWidths(m_leftSidePanelSpinBox->value(), newRightPanelSize);
} }
void PageEnhancementsDockWidget::setComposeLinkPageNumber(int linkNumber, const QString &newPageNumberString)
{
// The LineEdit should check if a valid hex number was entered, but just in case...
bool newPageNumberOk;
int newPageNumberRead = newPageNumberString.toInt(&newPageNumberOk, 16);
if ((!newPageNumberOk) || newPageNumberRead < 0x100 || newPageNumberRead > 0x8ff)
return;
// Stored as page link with relative magazine number, convert from absolute page number that was entered
newPageNumberRead ^= (m_parentMainWidget->document()->pageNumber() & 0x700);
m_parentMainWidget->document()->currentSubPage()->setComposeLinkPageNumber(linkNumber, newPageNumberRead);
}
void PageEnhancementsDockWidget::setComposeLinkSubPageNumbers(int linkNumber, const QString &newSubPagesString)
{
int newSubPageCodes = 0x0000;
if (!newSubPagesString.isEmpty()) {
// Turn user-entered comma separated subpages and subpage-ranges into bits
// First we do the "comma separated"
const QStringList items = newSubPagesString.split(QLatin1Char(','), QString::SkipEmptyParts);
// Now see if there's valid single numbers or ranges between the commas
for (const QString &item : items) {
if (item.isEmpty())
return;
if (item.contains(QLatin1Char('-'))) {
// Dash found, should be a range of subpages: check for two things either side of dash
const QStringList rangeItems = item.split('-');
if (rangeItems.count() != 2)
return;
// Now check if the two things are valid numbers 0-15, with first number less than second
bool ok1, ok2;
const int number1 = rangeItems[0].toInt(&ok1);
const int number2 = rangeItems[1].toInt(&ok2);
if (!ok1 || !ok2 || number1 < 0 || number2 < 0 || number1 > 15 || number2 > 15 || number2 < number1)
return;
// Yes, it is a valid range. Apply it to the result
for (int i=number1; i<=number2; i++)
newSubPageCodes |= (1 << i);
} else {
// A single thing (maybe extracted between commas): check if the thing is a number 0-15
bool ok;
const int number = item.toInt(&ok);
if (!ok || number < 0 || number > 15)
return;
// Yes, it is a valid number. Apply it to the result
newSubPageCodes |= (1 << number);
}
}
}
m_parentMainWidget->document()->currentSubPage()->setComposeLinkSubPageCodes(linkNumber, newSubPageCodes);
}
void PageEnhancementsDockWidget::updateWidgets() void PageEnhancementsDockWidget::updateWidgets()
{ {
int leftSidePanelColumnsResult = 0; int leftSidePanelColumnsResult = 0;
@@ -268,64 +163,4 @@ void PageEnhancementsDockWidget::updateWidgets()
m_sidePanelStatusAct->blockSignals(true); m_sidePanelStatusAct->blockSignals(true);
m_sidePanelStatusAct->setChecked(!m_parentMainWidget->document()->currentSubPage()->sidePanelStatusL25()); m_sidePanelStatusAct->setChecked(!m_parentMainWidget->document()->currentSubPage()->sidePanelStatusL25());
m_sidePanelStatusAct->blockSignals(false); m_sidePanelStatusAct->blockSignals(false);
for (int i=0; i<8; i++) {
if (i >= 4) {
m_composeLinkFunctionComboBox[i-4]->blockSignals(true);
m_composeLinkFunctionComboBox[i-4]->setCurrentIndex(m_parentMainWidget->document()->currentSubPage()->composeLinkFunction(i));
m_composeLinkFunctionComboBox[i-4]->blockSignals(false);
} else {
m_composeLinkLevelCheckbox[i][0]->blockSignals(true);
m_composeLinkLevelCheckbox[i][0]->setChecked(m_parentMainWidget->document()->currentSubPage()->composeLinkLevel2p5(i));
m_composeLinkLevelCheckbox[i][0]->blockSignals(false);
m_composeLinkLevelCheckbox[i][1]->blockSignals(true);
m_composeLinkLevelCheckbox[i][1]->setChecked(m_parentMainWidget->document()->currentSubPage()->composeLinkLevel3p5(i));
m_composeLinkLevelCheckbox[i][1]->blockSignals(false);
}
// Stored as page link with relative magazine number, convert to absolute page number for display
int absoluteLinkPageNumber = m_parentMainWidget->document()->currentSubPage()->composeLinkPageNumber(i) ^ (m_parentMainWidget->document()->pageNumber() & 0x700);
// Fix magazine 0 to 8
if ((absoluteLinkPageNumber & 0x700) == 0x000)
absoluteLinkPageNumber |= 0x800;
m_composeLinkPageNumberLineEdit[i]->blockSignals(true);
m_composeLinkPageNumberLineEdit[i]->setText(QString::number(absoluteLinkPageNumber, 16).toUpper());
m_composeLinkPageNumberLineEdit[i]->blockSignals(false);
// Turn subpage bits into user-friendly comma separated numbers and number-ranges
QString rangeString;
if (m_parentMainWidget->document()->currentSubPage()->composeLinkSubPageCodes(i) != 0x0000) {
// First build a list of consecutive ranges seen
// The "b-index" is based on https://codereview.stackexchange.com/a/90074
QMap<int, QPair<int, int>> ranges;
int index = 0;
for (int b=0; b<16; b++)
if ((m_parentMainWidget->document()->currentSubPage()->composeLinkSubPageCodes(i) >> b) & 0x01) {
if (!ranges.contains(b-index))
ranges.insert(b-index, qMakePair(b, b));
else
ranges[b-index].second = b;
index++;
}
// Now go through the list and add single numbers or ranges as appropriate
QPair<int, int> range;
foreach (range, ranges) {
// For second and subsequent entries only, append a comma first
if (!rangeString.isEmpty())
rangeString.append(',');
// Append the single number or the first number of the range
rangeString.append(QString("%1").arg(range.first));
// If that was part of a range and not a single orphaned number
if (range.first != range.second) {
// Ranges of 3 or more use a dash. A range of 2 can make do with a comma
rangeString.append((range.first+1 == range.second) ? ',' : '-');
// Append the second number of the range
rangeString.append(QString("%1").arg(range.second));
}
}
}
m_composeLinkSubPageNumbersLineEdit[i]->blockSignals(true);
m_composeLinkSubPageNumbersLineEdit[i]->setText(rangeString);
m_composeLinkSubPageNumbersLineEdit[i]->blockSignals(false);
}
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -23,10 +23,7 @@
#include <QCheckBox> #include <QCheckBox>
#include <QComboBox> #include <QComboBox>
#include <QDockWidget> #include <QDockWidget>
#include <QLineEdit>
#include <QRegExpValidator>
#include <QSpinBox> #include <QSpinBox>
#include <QString>
#include "mainwidget.h" #include "mainwidget.h"
@@ -41,19 +38,11 @@ public:
private: private:
void setLeftSidePanelWidth(int); void setLeftSidePanelWidth(int);
void setRightSidePanelWidth(int); void setRightSidePanelWidth(int);
void setComposeLinkPageNumber(int, const QString &);
void setComposeLinkSubPageNumbers(int, const QString &);
TeletextWidget *m_parentMainWidget; TeletextWidget *m_parentMainWidget;
QComboBox *m_defaultScreenColourCombo, *m_defaultRowColourCombo, *m_colourTableCombo; QComboBox *m_defaultScreenColourCombo, *m_defaultRowColourCombo, *m_colourTableCombo;
QCheckBox *m_blackBackgroundSubstAct, *m_sidePanelStatusAct; QCheckBox *m_blackBackgroundSubstAct, *m_sidePanelStatusAct;
QSpinBox *m_leftSidePanelSpinBox, *m_rightSidePanelSpinBox; QSpinBox *m_leftSidePanelSpinBox, *m_rightSidePanelSpinBox;
QCheckBox *m_composeLinkLevelCheckbox[4][2]; // For links 0-3
QComboBox *m_composeLinkFunctionComboBox[4]; // For links 4-7; remember to subtract 4!
QLineEdit *m_composeLinkPageNumberLineEdit[8], *m_composeLinkSubPageNumbersLineEdit[8];
QRegExpValidator *m_pageNumberValidator;
}; };
#endif #endif

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,7 +1,8 @@
QT += widgets QT += widgets
requires(qtConfig(filedialog)) requires(qtConfig(filedialog))
HEADERS = document.h \ HEADERS = decode.h \
document.h \
hamming.h \ hamming.h \
keymap.h \ keymap.h \
levelonecommands.h \ levelonecommands.h \
@@ -11,6 +12,7 @@ HEADERS = document.h \
mainwindow.h \ mainwindow.h \
pagebase.h \ pagebase.h \
pagex26base.h \ pagex26base.h \
pagecomposelinksdockwidget.h \
pageenhancementsdockwidget.h \ pageenhancementsdockwidget.h \
pageoptionsdockwidget.h \ pageoptionsdockwidget.h \
palettedockwidget.h \ palettedockwidget.h \
@@ -19,7 +21,8 @@ HEADERS = document.h \
x26dockwidget.h \ x26dockwidget.h \
x26model.h \ x26model.h \
x26triplets.h x26triplets.h
SOURCES = document.cpp \ SOURCES = decode.cpp \
document.cpp \
levelonecommands.cpp \ levelonecommands.cpp \
levelonepage.cpp \ levelonepage.cpp \
loadsave.cpp \ loadsave.cpp \
@@ -28,6 +31,7 @@ SOURCES = document.cpp \
mainwindow.cpp \ mainwindow.cpp \
pagebase.cpp \ pagebase.cpp \
pagex26base.cpp \ pagex26base.cpp \
pagecomposelinksdockwidget.cpp \
pageenhancementsdockwidget.cpp \ pageenhancementsdockwidget.cpp \
pageoptionsdockwidget.cpp \ pageoptionsdockwidget.cpp \
palettedockwidget.cpp \ palettedockwidget.cpp \

1352
render.cpp

File diff suppressed because it is too large Load Diff

224
render.h
View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -21,169 +21,17 @@
#define RENDER_H #define RENDER_H
#include <QBitmap> #include <QBitmap>
#include <QMap> #include <QSet>
#include <QMultiMap> #include <QPixmap>
#include <QPair>
#include <vector>
#include "levelonepage.h" #include "decode.h"
struct textCharacter {
unsigned char code=0x20;
int set=0;
int diacritical=0;
};
struct displayAttributes {
bool doubleHeight=false;
bool doubleWidth=false;
bool boxingWindow=false;
bool conceal=false;
bool invert=false;
bool underlineSeparated=false;
bool forceContiguous=false;
};
struct textAttributes {
int foreColour=0x07;
int backColour=0x00;
struct flashFunctions {
int mode=0;
int ratePhase=0;
int phaseNumber=0;
} flash;
displayAttributes display;
/* font style */
};
struct textCell {
textCharacter character;
textAttributes attribute;
bool bottomHalf=false;
bool rightHalf=false;
bool level1Mosaic=false;
int level1CharSet=0;
};
struct applyAttributes {
bool applyForeColour=false;
bool applyBackColour=false;
bool applyFlash=false;
bool applyDisplayAttributes=false;
bool applyTextSizeOnly=false;
bool applyBoxingOnly=false;
bool applyConcealOnly=false;
bool applyContiguousOnly=false;
bool copyAboveAttributes=false;
textAttributes attribute;
};
class ActivePosition
{
public:
ActivePosition();
int row() const { return (m_row == -1) ? 0 : m_row; }
int column() const { return (m_column == -1) ? 0 : m_column; }
bool isDeployed() const { return m_row != -1; }
bool setRow(int);
bool setColumn(int);
// bool setRowAndColumn(int, int);
private:
int m_row, m_column;
};
class TextLayer
{
public:
// TextLayer(TeletextPage* thePage) : currentPage(thePage) { };
virtual ~TextLayer() = default;
void setTeletextPage(LevelOnePage *);
virtual textCharacter character(int, int) =0;
virtual void attributes(int, int, applyAttributes *) =0;
virtual int fullScreenColour() const =0;
virtual int fullRowColour(int) const =0;
virtual bool fullRowDownwards(int) const =0;
virtual int objectType() const =0;
virtual int originR() const { return 0; };
virtual int originC() const { return 0; };
void setFullScreenColour(int);
void setFullRowColour(int, int, bool);
// Key QPair is row and column, value QPair is triplet mode and data
QMultiMap<QPair<int, int>, QPair<int, int>> enhanceMap;
protected:
LevelOnePage* m_levelOnePage;
int m_layerFullScreenColour=-1;
int m_layerFullRowColour[25];
bool m_layerFullRowDownwards[25];
applyAttributes m_applyAttributes;
};
class EnhanceLayer: public TextLayer
{
public:
EnhanceLayer();
textCharacter character(int, int);
void attributes(int, int, applyAttributes *);
int fullScreenColour() const { return m_layerFullScreenColour; };
int fullRowColour(int r) const { return m_layerFullRowColour[r]; };
bool fullRowDownwards(int r) const { return m_layerFullRowDownwards[r]; };
int objectType() const { return m_objectType; };
int originR() const { return m_originR; };
int originC() const { return m_originC; };
void setObjectType(int);
void setOrigin(int, int);
protected:
int m_objectType=0;
int m_originR=0;
int m_originC=0;
int m_rowCached=-1;
int m_rightMostColumn[25];
};
class Level1Layer: public TextLayer
{
public:
// Level1Layer(TeletextPage *thePage) : TextLayer(thePage) { };
Level1Layer();
textCharacter character(int, int);
void attributes(int, int, applyAttributes *);
int fullScreenColour() const { return -1; };
int fullRowColour(int) const { return -1; };
bool fullRowDownwards(int) const { return false; };
int objectType() const { return 0; }
bool isRowBottomHalf(int r) const { return m_rowHeight[r]==RHbottomhalf; }
private:
void updateRowCache(int);
struct level1CacheAttributes {
int foreColour=0x07;
int backColour=0x00;
unsigned char sizeCode=0x0c;
bool mosaics=false;
bool separated=false;
bool held=false;
bool escSwitch=false;
unsigned char holdChar=0x20;
bool holdSeparated=false;
};
level1CacheAttributes m_attributeCache[40];
int m_rowCached=-1;
bool m_rowHasDoubleHeightAttr[25];
enum rowHeightEnum { RHnormal=-1, RHtophalf, RHbottomhalf } m_rowHeight[25];
};
class TeletextFontBitmap class TeletextFontBitmap
{ {
public: public:
TeletextFontBitmap(); TeletextFontBitmap();
~TeletextFontBitmap(); ~TeletextFontBitmap();
QBitmap *rawBitmap() const { return s_fontBitmap; } QBitmap *rawBitmap() const { return s_fontBitmap; }
private: private:
@@ -198,67 +46,37 @@ class TeletextPageRender : public QObject
public: public:
TeletextPageRender(); TeletextPageRender();
~TeletextPageRender(); ~TeletextPageRender();
void decodePage();
void renderPage();
void renderPage(int r);
bool mix() const { return m_mix; };
bool showCodes() const { return m_showCodes; };
void setTeletextPage(LevelOnePage *);
void updateSidePanels();
void buildEnhanceMap(TextLayer *, int=0);
QPixmap* pagePixmap(int i) const { return m_pagePixmap[i]; }; QPixmap* pagePixmap(int i) const { return m_pagePixmap[i]; };
bool level1MosaicAttribute(int r, int c) const { return m_cell[r][c].level1Mosaic; }; bool mix() const { return m_mix; };
int level1CharSet(int r, int c) const { return m_cell[r][c].level1CharSet; }; void setDecoder(TeletextPageDecode *);
int leftSidePanelColumns() const { return m_leftSidePanelColumns; }; void renderPage(bool force=false);
int rightSidePanelColumns() const { return m_rightSidePanelColumns; }; bool showControlCodes() const { return m_showControlCodes; };
public slots: public slots:
void colourChanged(int);
void setReveal(bool); void setReveal(bool);
void setMix(bool); void setMix(bool);
void setShowCodes(bool); void setShowControlCodes(bool);
void setRenderLevel(int);
signals: signals:
void fullScreenColourChanged(QColor);
void fullRowColourChanged(int, QColor);
void flashChanged(int); void flashChanged(int);
void sidePanelsChanged();
protected: protected:
void updateFlashRequired(int);
inline void setFullScreenColour(int);
inline void setFullRowColour(int, int);
TeletextFontBitmap m_fontBitmap; TeletextFontBitmap m_fontBitmap;
QPixmap* m_pagePixmap[6]; QPixmap* m_pagePixmap[6];
int m_finalFullScreenColour, m_renderLevel; unsigned char m_controlCodeCache[25][40];
QColor m_finalFullScreenQColor; bool m_reveal, m_mix, m_showControlCodes;
int m_leftSidePanelColumns, m_rightSidePanelColumns; QSet<QPair<int, int>> m_flash1HzCells;
bool m_reveal, m_mix, m_showCodes; QSet<QPair<int, int>> m_flash2HzCells;
Level1Layer m_level1Layer; int m_flashBuffersHz;
std::vector<TextLayer *> m_textLayer;
const int m_foregroundRemap[8] = { 0, 0, 0, 8, 8, 16, 16, 16 };
const int m_backgroundRemap[8] = { 0, 8, 16, 8, 16, 8, 16, 24 };
private: private:
textCell m_cell[25][72]; inline void drawFromFontBitmap(QPainter &, int, int, unsigned char, int, TeletextPageDecode::CharacterFragment);
LevelOnePage* m_levelOnePage; inline void drawCharacter(QPainter &, int, int, unsigned char, int, int, TeletextPageDecode::CharacterFragment);
int m_flashRequired; void updateFlashBuffers();
int m_fullRowColour[25];
QColor m_fullRowQColor[25];
int m_flashRow[25];
bool m_concealRow[25];
};
static const QMap<int, int> g0CharacterMap { TeletextPageDecode *m_decoder;
{ 0x00, 12 }, { 0x01, 15 }, { 0x02, 22 }, { 0x03, 16 }, { 0x04, 14 }, { 0x05, 19 }, { 0x06, 11 },
{ 0x08, 18 }, { 0x09, 15 }, { 0x0a, 22 }, { 0x0b, 16 }, { 0x0c, 14 }, { 0x0e, 11 },
{ 0x10, 12 }, { 0x11, 15 }, { 0x12, 22 }, { 0x13, 16 }, { 0x14, 14 }, { 0x15, 19 }, { 0x16, 23 },
{ 0x1d, 21 }, { 0x1f, 20 },
{ 0x20, 1 }, { 0x21, 15 }, { 0x22, 13 }, { 0x23, 17 }, { 0x24, 2 }, { 0x25, 3 }, { 0x26, 11 },
{ 0x36, 23 }, { 0x37, 4 },
{ 0x40, 12 }, { 0x44, 14 }, { 0x47, 5 },
{ 0x55, 6 }, { 0x57, 5 }
}; };
#endif #endif

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -72,8 +72,8 @@ void InsertTripletCommand::redo()
if (m_firstDo) if (m_firstDo)
m_firstDo = false; m_firstDo = false;
else
m_teletextDocument->emit tripletCommandHighlight(m_row+1); m_teletextDocument->emit tripletCommandHighlight(m_row);
} }
void InsertTripletCommand::undo() void InsertTripletCommand::undo()

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -133,73 +133,93 @@ X26DockWidget::X26DockWidget(TeletextWidget *parent): QDockWidget(parent)
m_cookedModePushButton = new QPushButton; m_cookedModePushButton = new QPushButton;
cookedTripletLayout->addWidget(m_cookedModePushButton); cookedTripletLayout->addWidget(m_cookedModePushButton);
auto newModeMenuAction=[&](QMenu *menu, int mode)
{
QAction *action = menu->addAction(m_x26Model->modeTripletName(mode));
connect(action, &QAction::triggered, [=]() { cookedModeMenuSelected(mode); });
};
// Cooked triplet menu // Cooked triplet menu
// We build the menus for both "Insert" buttons at the same time
m_cookedModeMenu = new QMenu(this); m_cookedModeMenu = new QMenu(this);
newModeMenuAction(m_cookedModeMenu, 0x04); m_insertBeforeMenu = new QMenu(this);
QMenu *rowTripletSubMenu = m_cookedModeMenu->addMenu(tr("Row triplet")); m_insertAfterMenu = new QMenu(this);
newModeMenuAction(rowTripletSubMenu, 0x00);
newModeMenuAction(rowTripletSubMenu, 0x01); for (int m=0; m<3; m++) {
newModeMenuAction(rowTripletSubMenu, 0x07); QMenu *menuToBuild;
newModeMenuAction(rowTripletSubMenu, 0x18);
QMenu *columnTripletSubMenu = m_cookedModeMenu->addMenu(tr("Column triplet")); if (m == 0)
newModeMenuAction(columnTripletSubMenu, 0x20); menuToBuild = m_cookedModeMenu;
newModeMenuAction(columnTripletSubMenu, 0x23); else if (m == 1)
newModeMenuAction(columnTripletSubMenu, 0x27); menuToBuild = m_insertBeforeMenu;
newModeMenuAction(columnTripletSubMenu, 0x2c); else // if (m == 2)
newModeMenuAction(columnTripletSubMenu, 0x2e); menuToBuild = m_insertAfterMenu;
newModeMenuAction(columnTripletSubMenu, 0x28);
columnTripletSubMenu->addSeparator(); auto newModeMenuAction=[&](QMenu *menu, int mode)
newModeMenuAction(columnTripletSubMenu, 0x29); {
newModeMenuAction(columnTripletSubMenu, 0x2f); QAction *action = menu->addAction(m_x26Model->modeTripletName(mode));
newModeMenuAction(columnTripletSubMenu, 0x21); if (m == 0)
newModeMenuAction(columnTripletSubMenu, 0x22); connect(action, &QAction::triggered, [=]() { cookedModeMenuSelected(mode); });
newModeMenuAction(columnTripletSubMenu, 0x2b); else if (m == 1)
newModeMenuAction(columnTripletSubMenu, 0x2d); connect(action, &QAction::triggered, [=]() { insertTriplet(mode, false); });
QMenu *diacriticalSubMenu = columnTripletSubMenu->addMenu(tr("G0 diacritical")); else // if (m == 2)
for (int i=0; i<16; i++) connect(action, &QAction::triggered, [=]() { insertTriplet(mode, true); });
newModeMenuAction(diacriticalSubMenu, 0x30 + i); };
QMenu *objectSubMenu = m_cookedModeMenu->addMenu(tr("Object"));
newModeMenuAction(objectSubMenu, 0x10); newModeMenuAction(menuToBuild, 0x04);
newModeMenuAction(objectSubMenu, 0x11); QMenu *rowTripletSubMenu = menuToBuild->addMenu(tr("Row triplet"));
newModeMenuAction(objectSubMenu, 0x12); newModeMenuAction(rowTripletSubMenu, 0x00);
newModeMenuAction(objectSubMenu, 0x13); newModeMenuAction(rowTripletSubMenu, 0x01);
newModeMenuAction(objectSubMenu, 0x15); newModeMenuAction(rowTripletSubMenu, 0x07);
newModeMenuAction(objectSubMenu, 0x16); newModeMenuAction(rowTripletSubMenu, 0x18);
newModeMenuAction(objectSubMenu, 0x17); QMenu *columnTripletSubMenu = menuToBuild->addMenu(tr("Column triplet"));
newModeMenuAction(m_cookedModeMenu, 0x1f); newModeMenuAction(columnTripletSubMenu, 0x20);
m_cookedModeMenu->addSeparator(); newModeMenuAction(columnTripletSubMenu, 0x23);
QMenu *pdcSubMenu = m_cookedModeMenu->addMenu(tr("PDC/reserved")); newModeMenuAction(columnTripletSubMenu, 0x27);
newModeMenuAction(pdcSubMenu, 0x08); newModeMenuAction(columnTripletSubMenu, 0x2c);
newModeMenuAction(pdcSubMenu, 0x09); newModeMenuAction(columnTripletSubMenu, 0x2e);
newModeMenuAction(pdcSubMenu, 0x0a); newModeMenuAction(columnTripletSubMenu, 0x28);
newModeMenuAction(pdcSubMenu, 0x0b); columnTripletSubMenu->addSeparator();
newModeMenuAction(pdcSubMenu, 0x0c); newModeMenuAction(columnTripletSubMenu, 0x29);
newModeMenuAction(pdcSubMenu, 0x0d); newModeMenuAction(columnTripletSubMenu, 0x2f);
newModeMenuAction(pdcSubMenu, 0x26); newModeMenuAction(columnTripletSubMenu, 0x21);
QMenu *reservedRowSubMenu = pdcSubMenu->addMenu(tr("Reserved row")); newModeMenuAction(columnTripletSubMenu, 0x22);
newModeMenuAction(reservedRowSubMenu, 0x02); newModeMenuAction(columnTripletSubMenu, 0x2b);
newModeMenuAction(reservedRowSubMenu, 0x03); newModeMenuAction(columnTripletSubMenu, 0x2d);
newModeMenuAction(reservedRowSubMenu, 0x05); QMenu *diacriticalSubMenu = columnTripletSubMenu->addMenu(tr("G0 diacritical"));
newModeMenuAction(reservedRowSubMenu, 0x06); for (int i=0; i<16; i++)
newModeMenuAction(reservedRowSubMenu, 0x0e); newModeMenuAction(diacriticalSubMenu, 0x30 + i);
newModeMenuAction(reservedRowSubMenu, 0x0f); QMenu *objectSubMenu = menuToBuild->addMenu(tr("Object"));
newModeMenuAction(reservedRowSubMenu, 0x14); newModeMenuAction(objectSubMenu, 0x10);
newModeMenuAction(reservedRowSubMenu, 0x19); newModeMenuAction(objectSubMenu, 0x11);
newModeMenuAction(reservedRowSubMenu, 0x1a); newModeMenuAction(objectSubMenu, 0x12);
newModeMenuAction(reservedRowSubMenu, 0x1b); newModeMenuAction(objectSubMenu, 0x13);
newModeMenuAction(reservedRowSubMenu, 0x1c); newModeMenuAction(objectSubMenu, 0x15);
newModeMenuAction(reservedRowSubMenu, 0x1d); newModeMenuAction(objectSubMenu, 0x16);
newModeMenuAction(reservedRowSubMenu, 0x1e); newModeMenuAction(objectSubMenu, 0x17);
QMenu *reservedColumnSubMenu = pdcSubMenu->addMenu(tr("Reserved column")); newModeMenuAction(menuToBuild, 0x1f);
newModeMenuAction(reservedColumnSubMenu, 0x24); menuToBuild->addSeparator();
newModeMenuAction(reservedColumnSubMenu, 0x25); QMenu *pdcSubMenu = menuToBuild->addMenu(tr("PDC/reserved"));
newModeMenuAction(reservedColumnSubMenu, 0x26); newModeMenuAction(pdcSubMenu, 0x08);
newModeMenuAction(pdcSubMenu, 0x09);
newModeMenuAction(pdcSubMenu, 0x0a);
newModeMenuAction(pdcSubMenu, 0x0b);
newModeMenuAction(pdcSubMenu, 0x0c);
newModeMenuAction(pdcSubMenu, 0x0d);
newModeMenuAction(pdcSubMenu, 0x26);
QMenu *reservedRowSubMenu = pdcSubMenu->addMenu(tr("Reserved row"));
newModeMenuAction(reservedRowSubMenu, 0x02);
newModeMenuAction(reservedRowSubMenu, 0x03);
newModeMenuAction(reservedRowSubMenu, 0x05);
newModeMenuAction(reservedRowSubMenu, 0x06);
newModeMenuAction(reservedRowSubMenu, 0x0e);
newModeMenuAction(reservedRowSubMenu, 0x0f);
newModeMenuAction(reservedRowSubMenu, 0x14);
newModeMenuAction(reservedRowSubMenu, 0x19);
newModeMenuAction(reservedRowSubMenu, 0x1a);
newModeMenuAction(reservedRowSubMenu, 0x1b);
newModeMenuAction(reservedRowSubMenu, 0x1c);
newModeMenuAction(reservedRowSubMenu, 0x1d);
newModeMenuAction(reservedRowSubMenu, 0x1e);
QMenu *reservedColumnSubMenu = pdcSubMenu->addMenu(tr("Reserved column"));
newModeMenuAction(reservedColumnSubMenu, 0x24);
newModeMenuAction(reservedColumnSubMenu, 0x25);
newModeMenuAction(reservedColumnSubMenu, 0x26);
}
m_cookedModePushButton->setMenu(m_cookedModeMenu); m_cookedModePushButton->setMenu(m_cookedModeMenu);
@@ -505,60 +525,70 @@ X26DockWidget::X26DockWidget(TeletextWidget *parent): QDockWidget(parent)
m_tripletParameterStackedLayout->addWidget(blankWidget); m_tripletParameterStackedLayout->addWidget(blankWidget);
// Index 1 // Index 1
colourAndRowLayout->setContentsMargins(0, 0, 0, 0);
QWidget *colourAndRowWidget = new QWidget; QWidget *colourAndRowWidget = new QWidget;
colourAndRowWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); colourAndRowWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
colourAndRowWidget->setLayout(colourAndRowLayout); colourAndRowWidget->setLayout(colourAndRowLayout);
m_tripletParameterStackedLayout->addWidget(colourAndRowWidget); m_tripletParameterStackedLayout->addWidget(colourAndRowWidget);
// Index 2 // Index 2
characterCodeLayout->setContentsMargins(0, 0, 0, 0);
QWidget *characterCodeWidget = new QWidget; QWidget *characterCodeWidget = new QWidget;
characterCodeWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); characterCodeWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
characterCodeWidget->setLayout(characterCodeLayout); characterCodeWidget->setLayout(characterCodeLayout);
m_tripletParameterStackedLayout->addWidget(characterCodeWidget); m_tripletParameterStackedLayout->addWidget(characterCodeWidget);
// Index 3 // Index 3
flashModeRateLayout->setContentsMargins(0, 0, 0, 0);
QWidget *flashModeRateWidget = new QWidget; QWidget *flashModeRateWidget = new QWidget;
flashModeRateWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); flashModeRateWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
flashModeRateWidget->setLayout(flashModeRateLayout); flashModeRateWidget->setLayout(flashModeRateLayout);
m_tripletParameterStackedLayout->addWidget(flashModeRateWidget); m_tripletParameterStackedLayout->addWidget(flashModeRateWidget);
// Index 4 // Index 4
displayAttributesLayout->setContentsMargins(0, 0, 0, 0);
QWidget *displayAttributesWidget = new QWidget; QWidget *displayAttributesWidget = new QWidget;
displayAttributesWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); displayAttributesWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
displayAttributesWidget->setLayout(displayAttributesLayout); displayAttributesWidget->setLayout(displayAttributesLayout);
m_tripletParameterStackedLayout->addWidget(displayAttributesWidget); m_tripletParameterStackedLayout->addWidget(displayAttributesWidget);
// Index 5 // Index 5
invokeObjectLayout->setContentsMargins(0, 0, 0, 0);
QWidget *invokeObjectWidget = new QWidget; QWidget *invokeObjectWidget = new QWidget;
invokeObjectWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); invokeObjectWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
invokeObjectWidget->setLayout(invokeObjectLayout); invokeObjectWidget->setLayout(invokeObjectLayout);
m_tripletParameterStackedLayout->addWidget(invokeObjectWidget); m_tripletParameterStackedLayout->addWidget(invokeObjectWidget);
// Index 6 // Index 6
DRCSModeLayout->setContentsMargins(0, 0, 0, 0);
QWidget *DRCSModeWidget = new QWidget; QWidget *DRCSModeWidget = new QWidget;
DRCSModeWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); DRCSModeWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
DRCSModeWidget->setLayout(DRCSModeLayout); DRCSModeWidget->setLayout(DRCSModeLayout);
m_tripletParameterStackedLayout->addWidget(DRCSModeWidget); m_tripletParameterStackedLayout->addWidget(DRCSModeWidget);
// Index 7 // Index 7
DRCSCharacterLayout->setContentsMargins(0, 0, 0, 0);
QWidget *DRCSCharacterWidget = new QWidget; QWidget *DRCSCharacterWidget = new QWidget;
DRCSCharacterWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); DRCSCharacterWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
DRCSCharacterWidget->setLayout(DRCSCharacterLayout); DRCSCharacterWidget->setLayout(DRCSCharacterLayout);
m_tripletParameterStackedLayout->addWidget(DRCSCharacterWidget); m_tripletParameterStackedLayout->addWidget(DRCSCharacterWidget);
// Index 8 // Index 8
fontStyleLayout->setContentsMargins(0, 0, 0, 0);
QWidget *fontStyleWidget = new QWidget; QWidget *fontStyleWidget = new QWidget;
fontStyleWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); fontStyleWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
fontStyleWidget->setLayout(fontStyleLayout); fontStyleWidget->setLayout(fontStyleLayout);
m_tripletParameterStackedLayout->addWidget(fontStyleWidget); m_tripletParameterStackedLayout->addWidget(fontStyleWidget);
// Index 9 // Index 9
reservedPDCLayout->setContentsMargins(0, 0, 0, 0);
QWidget *reservedPDCWidget = new QWidget; QWidget *reservedPDCWidget = new QWidget;
reservedPDCWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); reservedPDCWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
reservedPDCWidget->setLayout(reservedPDCLayout); reservedPDCWidget->setLayout(reservedPDCLayout);
m_tripletParameterStackedLayout->addWidget(reservedPDCWidget); m_tripletParameterStackedLayout->addWidget(reservedPDCWidget);
// Index 10 // Index 10
terminationMarkerLayout->setContentsMargins(0, 0, 0, 0);
QWidget *terminationMarkerWidget = new QWidget; QWidget *terminationMarkerWidget = new QWidget;
terminationMarkerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); terminationMarkerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
terminationMarkerWidget->setLayout(terminationMarkerLayout); terminationMarkerWidget->setLayout(terminationMarkerLayout);
@@ -574,12 +604,21 @@ X26DockWidget::X26DockWidget(TeletextWidget *parent): QDockWidget(parent)
// Insert and delete widgets // Insert and delete widgets
QHBoxLayout *insertDeleteLayout = new QHBoxLayout; QHBoxLayout *insertDeleteLayout = new QHBoxLayout;
m_insertPushButton = new QPushButton(tr("Insert triplet")); m_insertBeforePushButton = new QPushButton(tr("Insert before"));
insertDeleteLayout->addWidget(m_insertPushButton); insertDeleteLayout->addWidget(m_insertBeforePushButton);
m_deletePushButton = new QPushButton(tr("Delete triplet")); m_insertBeforePushButton->setMenu(m_insertBeforeMenu);
m_insertAfterPushButton = new QPushButton(tr("Insert after"));
insertDeleteLayout->addWidget(m_insertAfterPushButton);
m_insertAfterPushButton->setMenu(m_insertAfterMenu);
m_insertCopyPushButton = new QPushButton(tr("Insert copy"));
insertDeleteLayout->addWidget(m_insertCopyPushButton);
m_deletePushButton = new QPushButton(tr("Delete"));
insertDeleteLayout->addWidget(m_deletePushButton); insertDeleteLayout->addWidget(m_deletePushButton);
connect(m_insertPushButton, &QPushButton::clicked, this, &X26DockWidget::insertTriplet); connect(m_insertCopyPushButton, &QPushButton::clicked, this, &X26DockWidget::insertTripletCopy);
connect(m_deletePushButton, &QPushButton::clicked, this, &X26DockWidget::deleteTriplet); connect(m_deletePushButton, &QPushButton::clicked, this, &X26DockWidget::deleteTriplet);
x26Layout->addLayout(insertDeleteLayout); x26Layout->addLayout(insertDeleteLayout);
@@ -590,14 +629,14 @@ X26DockWidget::X26DockWidget(TeletextWidget *parent): QDockWidget(parent)
m_x26View->setContextMenuPolicy(Qt::CustomContextMenu); m_x26View->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_x26View, &QWidget::customContextMenuRequested, this, &X26DockWidget::customMenuRequested); connect(m_x26View, &QWidget::customContextMenuRequested, this, &X26DockWidget::customMenuRequested);
connect(m_x26View, &QAbstractItemView::clicked, this, &X26DockWidget::rowClicked); connect(m_x26View->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &X26DockWidget::rowSelected);
} }
void X26DockWidget::keyPressEvent(QKeyEvent *event) void X26DockWidget::keyPressEvent(QKeyEvent *event)
{ {
switch (event->key()) { switch (event->key()) {
case Qt::Key_Insert: case Qt::Key_Insert:
insertTriplet(); insertTripletCopy();
break; break;
case Qt::Key_Delete: case Qt::Key_Delete:
deleteTriplet(); deleteTriplet();
@@ -615,7 +654,7 @@ void X26DockWidget::selectX26ListRow(int row)
row = m_x26Model->rowCount() - 1; row = m_x26Model->rowCount() - 1;
m_x26View->selectRow(row); m_x26View->selectRow(row);
rowClicked(m_x26View->currentIndex()); rowSelected(m_x26View->currentIndex(), QModelIndex());
} }
void X26DockWidget::loadX26List() void X26DockWidget::loadX26List()
@@ -631,10 +670,12 @@ void X26DockWidget::unloadX26List()
m_rawTripletModeSpinBox->setEnabled(false); m_rawTripletModeSpinBox->setEnabled(false);
} }
void X26DockWidget::rowClicked(const QModelIndex &index) void X26DockWidget::rowSelected(const QModelIndex &current, const QModelIndex &previous)
{ {
updateAllRawTripletSpinBoxes(index); Q_UNUSED(previous);
updateAllCookedTripletWidgets(index);
updateAllRawTripletSpinBoxes(current);
updateAllCookedTripletWidgets(current);
} }
void X26DockWidget::updateAllCookedTripletWidgets(const QModelIndex &index) void X26DockWidget::updateAllCookedTripletWidgets(const QModelIndex &index)
@@ -955,14 +996,47 @@ void X26DockWidget::updateModelFromCookedWidget(const int value, const int role)
updateAllRawTripletSpinBoxes(m_x26View->currentIndex()); updateAllRawTripletSpinBoxes(m_x26View->currentIndex());
} }
void X26DockWidget::insertTriplet() void X26DockWidget::insertTriplet(int modeExt, bool after)
{
QModelIndex index = m_x26View->currentIndex();
X26Triplet newTriplet(modeExt < 0x20 ? 41 : 0, modeExt & 0x1f, 0);
int row;
if (index.isValid()) {
// If we're inserting a column triplet next to another column triplet,
// duplicate the column number
// Avoid the PDC and reserved mode triplets
if (modeExt >= 0x20 && modeExt != 0x24 && modeExt != 0x25 && modeExt != 0x26 && modeExt != 0x2a) {
const int existingTripletModeExt = index.model()->data(index.model()->index(index.row(), 2), Qt::EditRole).toInt();
if (existingTripletModeExt >= 0x20 && existingTripletModeExt != 0x24 && existingTripletModeExt != 0x25 && existingTripletModeExt != 0x26 && existingTripletModeExt != 0x2a)
newTriplet.setAddress(index.model()->data(index.model()->index(index.row(), 0), Qt::UserRole).toInt());
}
row = index.row()+after;
} else
row = 0;
// For character triplets, ensure Data is not reserved
if (modeExt == 0x21 || modeExt == 0x22 || modeExt == 0x29 || modeExt == 0x2b || modeExt >= 0x2f)
newTriplet.setData(0x20);
// For Termination Marker, set Address and Mode
if (modeExt == 0x1f) {
newTriplet.setAddress(63);
newTriplet.setData(7);
}
m_x26Model->insertRows(row, 1, QModelIndex(), newTriplet);
}
void X26DockWidget::insertTripletCopy()
{ {
QModelIndex index = m_x26View->currentIndex(); QModelIndex index = m_x26View->currentIndex();
if (index.isValid()) if (index.isValid())
m_x26Model->insertRow(index.row(), QModelIndex()); m_x26Model->insertRow(index.row(), QModelIndex());
else else
m_x26Model->insertFirstRow(); // No existing triplet to copy, so insert a Termination Marker
m_x26Model->insertRows(0, 1, QModelIndex(), X26Triplet(63, 31, 7));
} }
void X26DockWidget::deleteTriplet() void X26DockWidget::deleteTriplet()
@@ -978,9 +1052,9 @@ void X26DockWidget::customMenuRequested(QPoint pos)
QMenu *menu = new QMenu(this); QMenu *menu = new QMenu(this);
QAction *insertAct = new QAction("Insert triplet", this); QAction *insertAct = new QAction("Insert triplet copy", this);
menu->addAction(insertAct); menu->addAction(insertAct);
connect(insertAct, &QAction::triggered, this, &X26DockWidget::insertTriplet); connect(insertAct, &QAction::triggered, this, &X26DockWidget::insertTripletCopy);
if (index.isValid()) { if (index.isValid()) {
QAction *deleteAct = new QAction("Delete triplet", this); QAction *deleteAct = new QAction("Delete triplet", this);
menu->addAction(deleteAct); menu->addAction(deleteAct);

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -61,12 +61,13 @@ public:
X26DockWidget(TeletextWidget *parent); X26DockWidget(TeletextWidget *parent);
public slots: public slots:
void insertTriplet(); void insertTriplet(int, bool);
void insertTripletCopy();
void deleteTriplet(); void deleteTriplet();
void customMenuRequested(QPoint pos); void customMenuRequested(QPoint pos);
void loadX26List(); void loadX26List();
void unloadX26List(); void unloadX26List();
void rowClicked(const QModelIndex &); void rowSelected(const QModelIndex &, const QModelIndex &);
void updateAllRawTripletSpinBoxes(const QModelIndex &); void updateAllRawTripletSpinBoxes(const QModelIndex &);
void updateRawTripletDataSpinBox(const QModelIndex &); void updateRawTripletDataSpinBox(const QModelIndex &);
void updateAllCookedTripletWidgets(const QModelIndex &); void updateAllCookedTripletWidgets(const QModelIndex &);
@@ -87,7 +88,7 @@ private:
QTableView *m_x26View; QTableView *m_x26View;
X26Model *m_x26Model; X26Model *m_x26Model;
QSpinBox *m_cookedRowSpinBox, *m_cookedColumnSpinBox; QSpinBox *m_cookedRowSpinBox, *m_cookedColumnSpinBox;
QMenu *m_cookedModeMenu; QMenu *m_cookedModeMenu, *m_insertBeforeMenu, *m_insertAfterMenu;
QPushButton *m_cookedModePushButton; QPushButton *m_cookedModePushButton;
QSpinBox *m_rawTripletAddressSpinBox, *m_rawTripletModeSpinBox, *m_rawTripletDataSpinBox; QSpinBox *m_rawTripletAddressSpinBox, *m_rawTripletModeSpinBox, *m_rawTripletDataSpinBox;
QStackedLayout *m_rawOrCookedStackedLayout; QStackedLayout *m_rawOrCookedStackedLayout;
@@ -113,7 +114,7 @@ private:
QSpinBox *m_reservedPDCSpinBox; QSpinBox *m_reservedPDCSpinBox;
QComboBox *m_terminationMarkerPageTypeComboBox; QComboBox *m_terminationMarkerPageTypeComboBox;
QCheckBox *m_terminationMarkerMoreFollowsCheckBox; QCheckBox *m_terminationMarkerMoreFollowsCheckBox;
QPushButton *m_insertPushButton, *m_deletePushButton; QPushButton *m_insertBeforePushButton, *m_insertAfterPushButton, *m_insertCopyPushButton, *m_deletePushButton;
TeletextWidget *m_parentMainWidget; TeletextWidget *m_parentMainWidget;
}; };

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -797,13 +797,6 @@ QVariant X26Model::headerData(int section, Qt::Orientation orientation, int role
return QVariant(); return QVariant();
} }
bool X26Model::insertFirstRow()
{
m_parentMainWidget->document()->undoStack()->push(new InsertTripletCommand(m_parentMainWidget->document(), this, 0, 1, X26Triplet(63, 31, 7)));
return true;
}
bool X26Model::insertRows(int row, int count, const QModelIndex &parent) bool X26Model::insertRows(int row, int count, const QModelIndex &parent)
{ {
Q_UNUSED(parent); Q_UNUSED(parent);
@@ -815,6 +808,17 @@ bool X26Model::insertRows(int row, int count, const QModelIndex &parent)
return true; return true;
} }
bool X26Model::insertRows(int row, int count, const QModelIndex &parent, X26Triplet triplet)
{
Q_UNUSED(parent);
if (m_parentMainWidget->document()->currentSubPage()->enhancements()->size() + count > m_parentMainWidget->document()->currentSubPage()->maxEnhancements())
return false;
m_parentMainWidget->document()->undoStack()->push(new InsertTripletCommand(m_parentMainWidget->document(), this, row, count, triplet));
return true;
}
bool X26Model::removeRows(int row, int count, const QModelIndex &index) bool X26Model::removeRows(int row, int count, const QModelIndex &index)
{ {
Q_UNUSED(index); Q_UNUSED(index);

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -36,8 +36,8 @@ public:
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role); bool setData(const QModelIndex &index, const QVariant &value, int role);
QVariant headerData(int section, Qt::Orientation orientation, int role) const; QVariant headerData(int section, Qt::Orientation orientation, int role) const;
bool insertFirstRow();
bool insertRows(int position, int rows, const QModelIndex &parent); bool insertRows(int position, int rows, const QModelIndex &parent);
bool insertRows(int position, int rows, const QModelIndex &parent, X26Triplet triplet);
bool removeRows(int position, int rows, const QModelIndex &index); bool removeRows(int position, int rows, const QModelIndex &index);
// Qt::ItemFlags flags(const QModelIndex &index) const; // Qt::ItemFlags flags(const QModelIndex &index) const;
@@ -140,10 +140,14 @@ private:
int columnHighlight; int columnHighlight;
}; };
const tripletErrorShow m_tripletErrors[3] { // Needs to be in the same order as enum X26TripletError in x26triplets.h
const tripletErrorShow m_tripletErrors[6] {
{ "", 0 }, // No error { "", 0 }, // No error
{ "Active Position can't move up", 0 }, { "Active Position can't move up", 0 },
{ "Active Position can't move left within row", 1 } { "Active Position can't move left within row", 1 },
{ "Invocation not pointing to Object Definition", 3 },
{ "Invoked and Defined Object types don't match", 2 },
{ "Origin Modifier not followed by Object Invocation", 2 }
}; };
}; };

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -69,21 +69,16 @@ void X26Triplet::setObjectLocalIndex(int i)
} }
void X26TripletList::updateInternalData(int r) void X26TripletList::updateInternalData()
{ {
ActivePosition activePosition; ActivePosition activePosition;
X26Triplet *triplet; X26Triplet *triplet;
if (r != 0) { for (int i=0; i < m_list.size(); i++) {
activePosition.setRow(m_list[r-1].m_activePositionRow);
activePosition.setColumn(m_list[r-1].m_activePositionColumn);
}
for (int i=r; i < m_list.size(); i++) {
triplet = &m_list[i]; triplet = &m_list[i];
triplet->m_error = X26Triplet::NoError; triplet->m_error = X26Triplet::NoError;
if (triplet->isRowTriplet()) { if (triplet->isRowTriplet()) {
switch (m_list.at(i).modeExt()) { switch (triplet->modeExt()) {
case 0x00: // Full screen colour case 0x00: // Full screen colour
if (activePosition.isDeployed()) if (activePosition.isDeployed())
// TODO more specific error needed // TODO more specific error needed
@@ -107,6 +102,23 @@ void X26TripletList::updateInternalData(int r)
activePosition.setColumn(8); activePosition.setColumn(8);
} }
break; break;
case 0x10: // Origin Modifier
if (i == m_list.size()-1 ||
m_list.at(i+1).modeExt() < 0x11 ||
m_list.at(i+1).modeExt() > 0x13)
triplet->m_error = X26Triplet::OriginModifierAlone;
break;
case 0x11 ... 0x13: // Invoke Object
if (triplet->objectSource() == X26Triplet::LocalObject) {
if (triplet->objectLocalTripletNumber() > 12 ||
triplet->objectLocalIndex() > (m_list.size()-1) ||
m_list.at(triplet->objectLocalIndex()).modeExt() < 0x15 ||
m_list.at(triplet->objectLocalIndex()).modeExt() > 0x17)
triplet->m_error = X26Triplet::InvokePointerInvalid;
else if ((triplet->modeExt() | 0x04) != m_list.at(triplet->objectLocalIndex()).modeExt())
triplet->m_error = X26Triplet::InvokeTypeMismatch;
}
break;
case 0x15 ... 0x17: // Define Object case 0x15 ... 0x17: // Define Object
activePosition.reset(); activePosition.reset();
// Make sure data field holds correct place of triplet // Make sure data field holds correct place of triplet
@@ -126,26 +138,26 @@ void X26TripletList::updateInternalData(int r)
void X26TripletList::append(const X26Triplet &value) void X26TripletList::append(const X26Triplet &value)
{ {
m_list.append(value); m_list.append(value);
updateInternalData(m_list.size()-1); updateInternalData();
} }
void X26TripletList::insert(int i, const X26Triplet &value) void X26TripletList::insert(int i, const X26Triplet &value)
{ {
m_list.insert(i, value); m_list.insert(i, value);
updateInternalData(i); updateInternalData();
} }
void X26TripletList::removeAt(int i) void X26TripletList::removeAt(int i)
{ {
m_list.removeAt(i); m_list.removeAt(i);
if (m_list.size() != 0 && i < m_list.size()) if (m_list.size() != 0 && i < m_list.size())
updateInternalData(i); updateInternalData();
} }
void X26TripletList::replace(int i, const X26Triplet &value) void X26TripletList::replace(int i, const X26Triplet &value)
{ {
m_list.replace(i, value); m_list.replace(i, value);
updateInternalData(i); updateInternalData();
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2022 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -25,7 +25,9 @@
class X26Triplet class X26Triplet
{ {
public: public:
enum X26TripletError { NoError, ActivePositionMovedUp, ActivePositionMovedLeft }; // x26model.h has the Plain English descriptions of these errors
enum X26TripletError { NoError, ActivePositionMovedUp, ActivePositionMovedLeft, InvokePointerInvalid, InvokeTypeMismatch, OriginModifierAlone };
enum ObjectSource { InvalidObjectSource, LocalObject, POPObject, GPOPObject };
X26Triplet() {} X26Triplet() {}
// X26Triplet(const X26Triplet &other); // X26Triplet(const X26Triplet &other);
@@ -48,6 +50,8 @@ public:
void setAddressRow(int); void setAddressRow(int);
void setAddressColumn(int); void setAddressColumn(int);
int objectSource() const { return (m_address & 0x18) >> 3; }
int objectLocalDesignationCode() const { return (((m_address & 0x01) << 3) | (m_data >> 4)); } int objectLocalDesignationCode() const { return (((m_address & 0x01) << 3) | (m_data >> 4)); }
int objectLocalTripletNumber() const { return m_data & 0x0f; } int objectLocalTripletNumber() const { return m_data & 0x0f; }
int objectLocalIndex() const { return objectLocalDesignationCode() * 13 + objectLocalTripletNumber(); } int objectLocalIndex() const { return objectLocalDesignationCode() * 13 + objectLocalTripletNumber(); }
@@ -84,7 +88,7 @@ public:
int size() const { return m_list.size(); } int size() const { return m_list.size(); }
private: private:
void updateInternalData(int); void updateInternalData();
QList<X26Triplet> m_list; QList<X26Triplet> m_list;