36 Commits

Author SHA1 Message Date
G.K.MacGregor
3125762133 Tag version 0.6-alpha 2023-03-05 14:38:45 +00:00
G.K.MacGregor
c0670c8281 Never mark a document cleanly saved after .t42 export
Amendment to 7493c8f so that a document even with only one subpage will not
be marked as cleanly saved. This should reinforce the fact that only TTI
files will be guaranteed to store everything.
2023-02-26 18:06:14 +00:00
G.K.MacGregor
7493c8f527 Add re-export option if .t42 file is loaded or exported
This allows a single keypress or menu click to repeatedly export a subpage
over the same .t42 file, like "Save" does for TTI files. If further teletext
page formats are added in the future this option should remember which
format was exported.

This option is deliberately different from "Save" as .t42 files are only
exported as the current subpage on view, and some metadata stored in TTI
files but not in .t42 files could be lost.

When exporting .t42 files the document is only marked as cleanly saved if it
consisted of a single subpage. Documents of more than one subpage will still
cause a confirmation dialog to be shown if unsaved, as the other subpages
will be lost unless the document is saved again as TTI.
2023-02-26 17:29:09 +00:00
G.K.MacGregor
9bd9f180c2 Don't hide control codes or grid when exporting PNG 2023-02-02 18:18:51 +00:00
G.K.MacGregor
c64be6a4c9 Update copyright notices to 2023 2022-12-31 21:19:15 +00:00
G.K.MacGregor
eb752835fd Auto-set row when inserting a Set Active Position triplet 2022-12-13 21:34:14 +00:00
G.K.MacGregor
72a2ef9660 Implement word wrapping when pasting plain text, wrt #6
When the clipboard has plain text data copied from a plain text or ASCII
text editor, pasting the text will perform word wrapping.

The text will originate from the cursor position and subsequent lines will
by default start from column 1 to provide room for alphanumeric colour
control codes in column 0.

If the cursor is at column 0 then all lines will start at column 0.

If a box selection is dragged before pasting the text will originate at
the top left of the selection box and be confined to the box. This can be
used if the second and subsequent lines need to start at a different
column, or if the pasted text must not overwrite an existing feature at
the right side of the page.

Note that cutting or copying text from within QTeletextMaker itself will
not be word wrapped on paste.
2022-11-20 17:36:53 +00:00
G.K.MacGregor
213eace512 Add dither to mosaic manipulating keypresses 2022-11-06 18:42:14 +00:00
G.K.MacGregor
06e0b401ca Tag version 0.5.5-alpha 2022-10-23 15:24:43 +01:00
G.K.MacGregor
bc8780608c Force refresh when switching decode Levels
This should fix palettes not updating when switching in and out of
Level 3.5
2022-10-23 12:43:27 +01:00
G.K.MacGregor
536c231941 Fix detect-on-load of Level 3.5 objects and DRCS mode 2022-10-23 12:30:50 +01:00
G.K.MacGregor
4faed597c0 Disable triplet widgets when no triplet is selected
This fixes a crash that occured when the last triplet in the X/26 list
is deleted.
2022-10-04 21:22:02 +01:00
G.K.MacGregor
75816e7750 Fix reserved data detection in DRCS character triplet 2022-09-22 21:28:29 +01:00
G.K.MacGregor
8b655afb2d Highlight reserved mode and data in X/26 triplet list 2022-08-30 21:07:14 +01:00
G.K.MacGregor
abf649d2ab Add reload from disk
This is typically mapped to the F5 key, so the "secret force refresh"
key has been temporarily moved to F6.
2022-07-17 15:16:53 +01:00
G.K.MacGregor
a8f2152c92 Try fixing the "unknown keypress types a block character" 2022-06-20 18:30:53 +01:00
G.K.MacGregor
9d05126e8f Fix non-flashing when page is force-refreshed 2022-06-18 17:55:19 +01:00
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
40 changed files with 2824 additions and 1709 deletions

View File

@@ -14,13 +14,15 @@ 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 build and install the required dependencies 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. Depending on how qmake is installed, type `qmake && make -j3` or `qmake5 && make -j3` in a terminal to build, 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 should place the qteletextmaker executable in the same directory as the source, type `./qteletextmaker` in the terminal to launch. Some Qt installs may place the executable into a "release" directory.
Optionally, type `make install` afterwards to place the executable into /usr/local/bin.
## Current limitations ## Current limitations
The following X/26 enhancement triplets are not rendered by the editor, although the list is fully aware of them. The following X/26 enhancement triplets are not rendered by the editor, although the list is fully aware of them.

998
decode.cpp Normal file
View File

@@ -0,0 +1,998 @@
/*
* Copyright (C) 2020-2023 Gavin MacGregor
*
* This file is part of QTeletextMaker.
*
* QTeletextMaker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QTeletextMaker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QTeletextMaker. If not, see <https://www.gnu.org/licenses/>.
*/
#include <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;
for (int r=0; r<25; r++)
for (int c=0; c<72; c++)
m_refresh[r][c] = true;
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-2023 Gavin MacGregor
*
* This file is part of QTeletextMaker.
*
* QTeletextMaker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QTeletextMaker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QTeletextMaker. If not, see <https://www.gnu.org/licenses/>.
*/
#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-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -94,6 +94,25 @@ bool TeletextDocument::isEmpty() const
return true; return true;
} }
void TeletextDocument::clear()
{
LevelOnePage *blankSubPage = new LevelOnePage;
m_subPages.insert(m_subPages.begin(), blankSubPage);
emit aboutToChangeSubPage();
m_currentSubPageIndex = 0;
m_clutModel->setSubPage(m_subPages[0]);
emit subPageSelected();
cancelSelection();
m_undoStack->clear();
for (int i=m_subPages.size()-1; i>0; i--) {
delete(m_subPages[i]);
m_subPages.erase(m_subPages.begin()+i);
}
}
/* /*
void TeletextDocument::setPageFunction(PageFunctionEnum newPageFunction) void TeletextDocument::setPageFunction(PageFunctionEnum newPageFunction)
{ {

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -56,6 +56,7 @@ public:
~TeletextDocument(); ~TeletextDocument();
bool isEmpty() const; bool isEmpty() const;
void clear();
PageFunctionEnum pageFunction() const { return m_pageFunction; } PageFunctionEnum pageFunction() const { return m_pageFunction; }
// void setPageFunction(PageFunctionEnum); // void setPageFunction(PageFunctionEnum);

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -528,15 +528,15 @@ static const QMap<QChar, char> keymapping[24] = {
// Native scan codes to toggle mosaic bits - different platforms have different scan codes! // Native scan codes to toggle mosaic bits - different platforms have different scan codes!
// Order is top left, top right, middle left, middle right, bottom left, bottom right, // Order is top left, top right, middle left, middle right, bottom left, bottom right,
// invert, set all, clear all // invert, set all, clear all, dither
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
static constexpr quint32 mosaicNativeScanCodes[9] = { static constexpr quint32 mosaicNativeScanCodes[10] = {
0x18, 0x19, 0x26, 0x27, 0x34, 0x35, 0x1b, 0x29, 0x36 0x18, 0x19, 0x26, 0x27, 0x34, 0x35, 0x1b, 0x29, 0x36, 0x28
}; };
#elif defined(Q_OS_WIN) #elif defined(Q_OS_WIN)
static constexpr quint32 mosaicNativeScanCodes[9] = { static constexpr quint32 mosaicNativeScanCodes[10] = {
0x10, 0x11, 0x1e, 0x1f, 0x2c, 0x2d, 0x13, 0x21, 0x2e 0x10, 0x11, 0x1e, 0x1f, 0x2c, 0x2d, 0x13, 0x21, 0x2e, 0x20
}; };
#else #else
#define QTTM_NONATIVESCANCODES #define QTTM_NONATIVESCANCODES

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -106,6 +106,8 @@ ToggleMosaicBitCommand::ToggleMosaicBitCommand(TeletextDocument *teletextDocumen
m_oldCharacter = teletextDocument->currentSubPage()->character(m_row, m_column); m_oldCharacter = teletextDocument->currentSubPage()->character(m_row, m_column);
if (bitToToggle == 0x20 || bitToToggle == 0x7f) if (bitToToggle == 0x20 || bitToToggle == 0x7f)
m_newCharacter = bitToToggle; m_newCharacter = bitToToggle;
else if (bitToToggle == 0x66)
m_newCharacter = (m_row & 1) ? 0x66 : 0x39;
else else
m_newCharacter = m_oldCharacter ^ bitToToggle; m_newCharacter = m_oldCharacter ^ bitToToggle;
@@ -413,8 +415,18 @@ PasteCommand::PasteCommand(TeletextDocument *teletextDocument, int pageCharSet,
if (m_selectionActive) { if (m_selectionActive) {
m_selectionCornerRow = m_teletextDocument->selectionCornerRow(); m_selectionCornerRow = m_teletextDocument->selectionCornerRow();
m_selectionCornerColumn = m_teletextDocument->selectionCornerColumn(); m_selectionCornerColumn = m_teletextDocument->selectionCornerColumn();
m_pasteTopRow = m_teletextDocument->selectionTopRow();
m_pasteBottomRow = m_teletextDocument->selectionBottomRow();
m_pasteLeftColumn = m_teletextDocument->selectionLeftColumn();
m_pasteRightColumn = m_teletextDocument->selectionRightColumn();
} else {
m_pasteTopRow = m_row;
m_pasteLeftColumn = m_column;
// m_pasteBottomRow and m_pasteRightColumn will be filled in later
// when the size of the clipboard data is known
} }
// Zero size here represents invalid or empty clipboard data
m_clipboardDataHeight = m_clipboardDataWidth = 0; m_clipboardDataHeight = m_clipboardDataWidth = 0;
// Try to get something from the clipboard // Try to get something from the clipboard
@@ -422,6 +434,7 @@ PasteCommand::PasteCommand(TeletextDocument *teletextDocument, int pageCharSet,
nativeData = mimeData->data("application/x-teletext"); nativeData = mimeData->data("application/x-teletext");
if (nativeData.size() > 2) { if (nativeData.size() > 2) {
// Native clipboard data: we put it there ourselves // Native clipboard data: we put it there ourselves
m_plainText = false;
m_clipboardDataHeight = nativeData.at(0); m_clipboardDataHeight = nativeData.at(0);
m_clipboardDataWidth = nativeData.at(1); m_clipboardDataWidth = nativeData.at(1);
@@ -429,25 +442,119 @@ PasteCommand::PasteCommand(TeletextDocument *teletextDocument, int pageCharSet,
if (m_clipboardDataHeight > 0 && m_clipboardDataWidth > 0 && m_clipboardDataHeight <= 25 && m_clipboardDataWidth <= 40 && nativeData.size() == m_clipboardDataHeight * m_clipboardDataWidth + 2) if (m_clipboardDataHeight > 0 && m_clipboardDataWidth > 0 && m_clipboardDataHeight <= 25 && m_clipboardDataWidth <= 40 && nativeData.size() == m_clipboardDataHeight * m_clipboardDataWidth + 2)
for (int r=0; r<m_clipboardDataHeight; r++) for (int r=0; r<m_clipboardDataHeight; r++)
m_pastingCharacters.append(nativeData.mid(2 + r * m_clipboardDataWidth, m_clipboardDataWidth)); m_pastingCharacters.append(nativeData.mid(2 + r * m_clipboardDataWidth, m_clipboardDataWidth));
else else {
// Invalidate // Invalidate
m_clipboardDataHeight = m_clipboardDataWidth = 0; m_clipboardDataHeight = m_clipboardDataWidth = 0;
return;
}
if (!m_selectionActive) {
m_pasteBottomRow = m_row + m_clipboardDataHeight - 1;
m_pasteRightColumn = m_column + m_clipboardDataWidth - 1;
}
} else if (mimeData->hasText()) { } else if (mimeData->hasText()) {
// Plain text // Plain text
m_plainText = true;
const int rightColumn = m_selectionActive ? m_pasteRightColumn : 39;
// Parse line-feeds in the clipboard data
QStringList plainTextData = mimeData->text().split(QRegExp("\n|\r\n|\r")); QStringList plainTextData = mimeData->text().split(QRegExp("\n|\r\n|\r"));
// "if" statement will be false if clipboard data is a single line of text
// that will fit from the cursor position
if (plainTextData.size() != 1 || m_pasteLeftColumn + plainTextData.at(0).size() - 1 > rightColumn) {
bool wrappingNeeded = false;
if (!m_selectionActive) {
// If selection is NOT active, use the full width of the page to paste.
// The second and subsequent lines will start at column 1, unless the
// cursor is explicitly on column 0.
if (m_pasteLeftColumn != 0)
m_pasteLeftColumn = 1;
// Check if first word in the first line will fit from the cursor position
bool firstWordFits = true;
const int firstSpace = plainTextData.at(0).indexOf(' ');
if (firstSpace == -1 && m_column + plainTextData.at(0).size() > 40)
firstWordFits = false; // Only one word in first line, and it won't fit
else if (m_column + firstSpace > 40)
firstWordFits = false; // First word in first line won't fit
// If the first word WILL fit at the cursor position, pad the first line
// to match the cursor position using null characters.
// In the QString null characters represent character cells in the
// pasting rectangle that won't overwrite what's on the page.
// If the first word WON'T fit, start pasting at the beginning of the next row.
if (firstWordFits)
plainTextData[0] = QString(m_column-m_pasteLeftColumn, QChar::Null) + plainTextData.at(0);
else if (m_pasteTopRow < 24)
m_pasteTopRow++;
else
return;
}
const int pasteWidth = rightColumn - m_pasteLeftColumn + 1;
// Find out if we need to word-wrap
for (int i=0; i<plainTextData.size(); i++)
if (plainTextData.at(i).size() > pasteWidth) {
wrappingNeeded = true;
break;
}
if (wrappingNeeded) {
QStringList wrappedText;
for (int i=0; i<plainTextData.size(); i++) {
// Split this line into individual words
QStringList lineWords = plainTextData.at(i).split(' ');
// If there's any words which are too long to fit,
// break them across multiple lines
for (int j=0; j<lineWords.size(); j++)
if (lineWords.at(j).size() > pasteWidth) {
lineWords.insert(j+1, lineWords.at(j).mid(pasteWidth));
lineWords[j].truncate(pasteWidth);
}
// Now reassemble the words into lines that will fit
QString currentLine = lineWords.at(0);
for (int j=1; j<lineWords.size(); j++)
if (currentLine.size() + 1 + lineWords.at(j).size() <= pasteWidth)
currentLine.append(' ' + lineWords.at(j));
else {
wrappedText.append(currentLine);
currentLine = lineWords.at(j);
}
wrappedText.append(currentLine);
}
plainTextData.swap(wrappedText);
}
}
m_clipboardDataHeight = plainTextData.size(); m_clipboardDataHeight = plainTextData.size();
m_clipboardDataWidth = 0; m_clipboardDataWidth = 0;
// Convert the unicode clipboard text into teletext bytes matching the current Level 1
// character set of this page
for (int r=0; r<m_clipboardDataHeight; r++) { for (int r=0; r<m_clipboardDataHeight; r++) {
m_pastingCharacters.append(QByteArray()); m_pastingCharacters.append(QByteArray());
for (int c=0; c<plainTextData.at(r).size(); c++) { for (int c=0; c<plainTextData.at(r).size(); c++) {
// Try to map the unicode character to the current Level 1 character set of this page
char convertedChar; char convertedChar;
const QChar charToConvert = plainTextData.at(r).at(c); const QChar charToConvert = plainTextData.at(r).at(c);
if (keymapping[pageCharSet].contains(charToConvert)) // Map a null character in the QString to 0xff (or -1)
// In the QByteArray 0xff bytes represent character cells in the pasting rectangle
// that won't overwrite what's on the page
if (charToConvert == QChar::Null)
convertedChar = -1;
else if (charToConvert >= 0x01 && charToConvert <= 0x1f)
convertedChar = ' ';
else if (keymapping[pageCharSet].contains(charToConvert))
// Remapped character or non-Latin character converted successfully // Remapped character or non-Latin character converted successfully
convertedChar = keymapping[pageCharSet].value(charToConvert); convertedChar = keymapping[pageCharSet].value(charToConvert);
else { else {
@@ -463,26 +570,19 @@ PasteCommand::PasteCommand(TeletextDocument *teletextDocument, int pageCharSet,
} }
m_clipboardDataWidth = qMax(m_pastingCharacters.at(r).size(), m_clipboardDataWidth); m_clipboardDataWidth = qMax(m_pastingCharacters.at(r).size(), m_clipboardDataWidth);
} }
// Pad short lines with spaces to make a box // Pad the end of short lines with spaces to make a box
for (int r=0; r<m_clipboardDataHeight; r++) for (int r=0; r<m_clipboardDataHeight; r++)
m_pastingCharacters[r] = m_pastingCharacters.at(r).leftJustified(m_clipboardDataWidth); m_pastingCharacters[r] = m_pastingCharacters.at(r).leftJustified(m_clipboardDataWidth);
if (!m_selectionActive) {
m_pasteBottomRow = m_pasteTopRow + m_clipboardDataHeight - 1;
m_pasteRightColumn = m_pasteLeftColumn + m_clipboardDataWidth - 1;
}
} }
if (m_clipboardDataWidth == 0 || m_clipboardDataHeight == 0) if (m_clipboardDataWidth == 0 || m_clipboardDataHeight == 0)
return; return;
if (m_selectionActive) {
m_pasteTopRow = m_teletextDocument->selectionTopRow();
m_pasteBottomRow = m_teletextDocument->selectionBottomRow();
m_pasteLeftColumn = m_teletextDocument->selectionLeftColumn();
m_pasteRightColumn = m_teletextDocument->selectionRightColumn();
} else {
m_pasteTopRow = m_row;
m_pasteBottomRow = m_row + m_clipboardDataHeight - 1;
m_pasteLeftColumn = m_column;
m_pasteRightColumn = m_column + m_clipboardDataWidth - 1;
}
// Store copy of the characters that we're about to overwrite // Store copy of the characters that we're about to overwrite
for (int r=m_pasteTopRow; r<=m_pasteBottomRow; r++) { for (int r=m_pasteTopRow; r<=m_pasteBottomRow; r++) {
QByteArray rowArray; QByteArray rowArray;
@@ -517,11 +617,21 @@ void PasteCommand::redo()
for (int c=m_pasteLeftColumn; c<=m_pasteRightColumn; c++) for (int c=m_pasteLeftColumn; c<=m_pasteRightColumn; c++)
// Guard against size of pasted block going beyond last line or column // Guard against size of pasted block going beyond last line or column
if (r < 25 && c < 40) { if (r < 25 && c < 40) {
m_teletextDocument->currentSubPage()->setCharacter(r, c, m_pastingCharacters[arrayR].at(arrayC++)); // Check for 0xff bytes using "-1"
// gcc complains about "comparision always true due to limited range"
if (m_pastingCharacters.at(arrayR).at(arrayC) != -1)
m_teletextDocument->currentSubPage()->setCharacter(r, c, m_pastingCharacters.at(arrayR).at(arrayC));
arrayC++;
// If paste area is wider than clipboard data, repeat the pattern // If paste area is wider than clipboard data, repeat the pattern
if (arrayC == m_clipboardDataWidth) // if it wasn't plain text
arrayC = 0; if (arrayC == m_clipboardDataWidth) {
if (!m_plainText)
arrayC = 0;
else
break;
}
} }
if (r < 25) if (r < 25)
@@ -529,8 +639,13 @@ void PasteCommand::redo()
arrayR++; arrayR++;
// If paste area is taller than clipboard data, repeat the pattern // If paste area is taller than clipboard data, repeat the pattern
if (arrayR == m_clipboardDataHeight) // if it wasn't plain text
arrayR = 0; if (arrayR == m_clipboardDataHeight) {
if (!m_plainText)
arrayR = 0;
else
break;
}
} }
if (m_selectionActive) { if (m_selectionActive) {
@@ -556,8 +671,11 @@ void PasteCommand::undo()
arrayC = 0; arrayC = 0;
for (int c=m_pasteLeftColumn; c<=m_pasteRightColumn; c++) for (int c=m_pasteLeftColumn; c<=m_pasteRightColumn; c++)
// Guard against size of pasted block going beyond last line or column // Guard against size of pasted block going beyond last line or column
if (r < 25 && c < 40) if (r < 25 && c < 40) {
m_teletextDocument->currentSubPage()->setCharacter(r, c, m_deletedCharacters[arrayR].at(arrayC++)); m_teletextDocument->currentSubPage()->setCharacter(r, c, m_deletedCharacters[arrayR].at(arrayC));
arrayC++;
}
if (r < 25) if (r < 25)
emit m_teletextDocument->contentsChange(r); emit m_teletextDocument->contentsChange(r);

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -179,7 +179,7 @@ private:
int m_pasteTopRow, m_pasteBottomRow, m_pasteLeftColumn, m_pasteRightColumn; int m_pasteTopRow, m_pasteBottomRow, m_pasteLeftColumn, m_pasteRightColumn;
int m_clipboardDataHeight, m_clipboardDataWidth; int m_clipboardDataHeight, m_clipboardDataWidth;
int m_selectionCornerRow, m_selectionCornerColumn; int m_selectionCornerRow, m_selectionCornerColumn;
bool m_selectionActive; bool m_selectionActive, m_plainText;
}; };
#endif // !QT_NO_CLIPBOARD #endif // !QT_NO_CLIPBOARD

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -25,6 +25,8 @@
#include "levelonepage.h" #include "levelonepage.h"
#include "x26triplets.h"
LevelOnePage::LevelOnePage() LevelOnePage::LevelOnePage()
{ {
m_enhancements.reserve(maxEnhancements()); m_enhancements.reserve(maxEnhancements());
@@ -381,6 +383,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);
} }
@@ -400,17 +405,21 @@ bool LevelOnePage::isPaletteDefault(int fromColour, int toColour) const
int LevelOnePage::levelRequired() const int LevelOnePage::levelRequired() const
{ {
// X/28/4 present i.e. CLUTs 0 or 1 redefined - Level 3.5
if (!isPaletteDefault(0, 15)) if (!isPaletteDefault(0, 15))
return 3; return 3;
// TODO Check for X/28/1 for DCLUT for mode 1-3 DRCS characters - return 3 // TODO Check for X/28/1 for DCLUT for mode 1-3 DRCS characters - return 3
// Assume Level 2.5 if any X/28 page enhancements are present, otherwise assume Level 1
int levelSeen = (!isPaletteDefault(16,31) || m_leftSidePanelDisplayed || m_rightSidePanelDisplayed || m_defaultScreenColour !=0 || m_defaultRowColour !=0 || m_blackBackgroundSubst || m_colourTableRemap !=0 || m_defaultCharSet != 0 || m_secondCharSet != 0xf) ? 2 : 0; int levelSeen = (!isPaletteDefault(16,31) || m_leftSidePanelDisplayed || m_rightSidePanelDisplayed || m_defaultScreenColour !=0 || m_defaultRowColour !=0 || m_blackBackgroundSubst || m_colourTableRemap !=0 || m_defaultCharSet != 0 || m_secondCharSet != 0xf) ? 2 : 0;
// If there's no X/26 triplets, exit here as Level 1 or 2.5
if (m_enhancements.isEmpty()) if (m_enhancements.isEmpty())
return levelSeen; return levelSeen;
for (int i=0; i<m_enhancements.size(); i++) { for (int i=0; i<m_enhancements.size(); i++) {
// Font style - Level 3.5 only triplet
if (m_enhancements.at(i).modeExt() == 0x2e) // Font style if (m_enhancements.at(i).modeExt() == 0x2e) // Font style
return 3; return 3;
@@ -423,9 +432,10 @@ int LevelOnePage::levelRequired() const
case 0x22: // G3 character @ Level 1.5 case 0x22: // G3 character @ Level 1.5
case 0x2f: // G2 character case 0x2f: // G2 character
case 0x30 ... 0x3f: // G0 character with diacritical case 0x30 ... 0x3f: // G0 character with diacritical
levelSeen = qMax(levelSeen, 1); levelSeen = 1;
break; break;
} }
if (levelSeen < 2) if (levelSeen < 2)
switch (m_enhancements.at(i).modeExt()) { switch (m_enhancements.at(i).modeExt()) {
// Check for Level 2.5 triplets // Check for Level 2.5 triplets
@@ -433,25 +443,26 @@ 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
if ((m_enhancements.at(i).address() & 0x18) == 0x10)
return 3;
else
levelSeen = qMax(levelSeen, 2);
break;
case 0x18: // DRCS Mode case 0x18: // DRCS Mode
// Check if DRCS is required only at Level 3.5
if ((m_enhancements.at(i).data() & 0x30) == 0x20)
return 3;
else
levelSeen = qMax(levelSeen, 2);
break;
case 0x20: // Foreground colour case 0x20: // Foreground colour
case 0x21: // G1 character case 0x21: // G1 character
case 0x23: // Background colour case 0x23: // Background colour
case 0x27 ... 0x29: // Flash functions, G0 and G2 charset designation, G0 character @ Level 2.5 case 0x27 ... 0x29: // Flash functions, G0 and G2 charset designation, G0 character @ Level 2.5
case 0x2b ... 0x2d: // G3 character @ Level 2.5, display attributes, DRCS character case 0x2b ... 0x2d: // G3 character @ Level 2.5, display attributes, DRCS character
levelSeen = qMax(levelSeen, 2); levelSeen = 2;
break;
}
if (levelSeen == 2)
switch (m_enhancements.at(i).modeExt()) {
// Check for triplets with "required at Level 3.5 only" parameters
case 0x15 ... 0x17: // Object Definition
if ((m_enhancements.at(i).address() & 0x18) == 0x10)
return 3;
break;
case 0x18: // DRCS Mode
if ((m_enhancements.at(i).data() & 0x30) == 0x20)
return 3;
break; break;
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2023 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-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -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-2023 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-2023 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.2-alpha"); QApplication::setApplicationVersion("0.6-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-2023 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,16 +250,16 @@ 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().at(0)));
if (mappedKeyPress < 0x20) if (mappedKeyPress >= 0x00 && mappedKeyPress <= 0x1f)
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_0 && event->key() <= Qt::Key_9 && event->modifiers() & Qt::KeypadModifier) {
switch (event->key()) { switch (event->key()) {
case Qt::Key_7: case Qt::Key_7:
toggleCharacterBit(0x01); // Top left toggleCharacterBit(0x01); // Top left
@@ -283,6 +288,9 @@ void TeletextWidget::keyPressEvent(QKeyEvent *event)
case Qt::Key_3: case Qt::Key_3:
toggleCharacterBit(0x20); // Clear all toggleCharacterBit(0x20); // Clear all
break; break;
case Qt::Key_0:
toggleCharacterBit(0x66); // Dither
break;
} }
return; return;
} }
@@ -323,6 +331,9 @@ void TeletextWidget::keyPressEvent(QKeyEvent *event)
case mosaicNativeScanCodes[8]: case mosaicNativeScanCodes[8]:
toggleCharacterBit(0x20); // Clear all toggleCharacterBit(0x20); // Clear all
break; break;
case mosaicNativeScanCodes[9]:
toggleCharacterBit(0x66); // Dither
break;
} }
return; return;
} else } else
@@ -381,9 +392,9 @@ void TeletextWidget::keyPressEvent(QKeyEvent *event)
case Qt::Key_PageDown: case Qt::Key_PageDown:
m_teletextDocument->selectSubPagePrevious(); m_teletextDocument->selectSubPagePrevious();
break; break;
case Qt::Key_F5: case Qt::Key_F6:
m_pageRender.decodePage(); m_pageDecode.decodePage();
m_pageRender.renderPage(); m_pageRender.renderPage(true);
update(); update();
break; break;
default: default:
@@ -420,7 +431,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 +464,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 +666,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;
@@ -667,21 +694,17 @@ void LevelOneScene::toggleGrid(bool gridOn)
void LevelOneScene::hideGUIElements(bool hidden) void LevelOneScene::hideGUIElements(bool hidden)
{ {
if (hidden) { if (hidden) {
m_mainGridItemGroup->setVisible(false);
m_cursorRectItem->setVisible(false); m_cursorRectItem->setVisible(false);
m_selectionRectItem->setVisible(false); m_selectionRectItem->setVisible(false);
for (int i=0; i<32; i++)
if (m_sidePanelGridNeeded[i])
m_sidePanelGridItemGroup[i]->setVisible(false);
} else { } else {
if (static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->document()->selectionActive()) if (static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->document()->selectionActive())
m_selectionRectItem->setVisible(true); m_selectionRectItem->setVisible(true);
m_cursorRectItem->setVisible(true); m_cursorRectItem->setVisible(true);
toggleGrid(m_grid);
} }
} }
// 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 +721,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-2023 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-2023 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"
@@ -148,6 +149,34 @@ bool MainWindow::saveAs()
return saveFile(fileName); return saveFile(fileName);
} }
void MainWindow::reload()
{
if (m_isUntitled)
return;
if (!m_textWidget->document()->undoStack()->isClean()) {
const QMessageBox::StandardButton ret = QMessageBox::warning(this, QApplication::applicationDisplayName(), tr("The document \"%1\" has been modified.\nDo you want to discard your changes?").arg(QFileInfo(m_curFile).fileName()), QMessageBox::Discard | QMessageBox::Cancel);
if (ret != QMessageBox::Discard)
return;
} else {
const QMessageBox::StandardButton ret = QMessageBox::warning(this, QApplication::applicationDisplayName(), tr("Do you want to reload the document \"%1\" from disk?").arg(QFileInfo(m_curFile).fileName()), QMessageBox::Yes | QMessageBox::No);
if (ret != QMessageBox::Yes)
return;
}
int subPageIndex = m_textWidget->document()->currentSubPageIndex();
m_textWidget->document()->clear();
loadFile(m_curFile);
if (subPageIndex >= m_textWidget->document()->numberOfSubPages())
subPageIndex = m_textWidget->document()->numberOfSubPages()-1;
m_textWidget->document()->selectSubPageIndex(subPageIndex, true);
}
void MainWindow::exportPNG() void MainWindow::exportPNG()
{ {
QString exportFileName = QFileDialog::getSaveFileName(this, tr("Export PNG"), QString(), "PNG image (*.png)"); QString exportFileName = QFileDialog::getSaveFileName(this, tr("Export PNG"), QString(), "PNG image (*.png)");
@@ -157,28 +186,27 @@ 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();
if (reshowCodes)
m_textWidget->pageRender()->setShowCodes(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 (reMix) {
m_textWidget->pageRender()->setShowCodes(true); m_textWidget->setMix(true);
if (reMix) m_textScene->setMix(true);
m_textWidget->pageRender()->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 +241,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-2023 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 +262,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 +286,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);
@@ -321,6 +351,11 @@ void MainWindow::createActions()
saveAsAct->setShortcuts(QKeySequence::SaveAs); saveAsAct->setShortcuts(QKeySequence::SaveAs);
saveAsAct->setStatusTip(tr("Save the document under a new name")); saveAsAct->setStatusTip(tr("Save the document under a new name"));
const QIcon reloadIcon = QIcon::fromTheme("document-revert");
QAction *reloadAct = fileMenu->addAction(reloadIcon, tr("Reload"), this, &MainWindow::reload);
reloadAct->setShortcuts(QKeySequence::Refresh);
reloadAct->setStatusTip(tr("Reload the document from disk"));
fileMenu->addSeparator(); fileMenu->addSeparator();
QMenu *recentMenu = fileMenu->addMenu(tr("Recent")); QMenu *recentMenu = fileMenu->addMenu(tr("Recent"));
@@ -336,9 +371,16 @@ void MainWindow::createActions()
setRecentFilesVisible(MainWindow::hasRecentFiles()); setRecentFilesVisible(MainWindow::hasRecentFiles());
m_exportAutoAct = fileMenu->addAction(tr("Export subpage..."));
m_exportAutoAct->setEnabled(false);
m_exportAutoAct->setShortcut(tr("Ctrl+E"));
m_exportAutoAct->setStatusTip("Export this subpage back to the imported file");
connect(fileMenu, &QMenu::aboutToShow, this, &MainWindow::updateExportAutoAction);
connect(m_exportAutoAct, &QAction::triggered, this, &MainWindow::exportAuto);
QAction *exportT42Act = fileMenu->addAction(tr("Export subpage as t42...")); QAction *exportT42Act = fileMenu->addAction(tr("Export subpage as t42..."));
exportT42Act->setStatusTip("Export this subpage as a t42 file"); exportT42Act->setStatusTip("Export this subpage as a t42 file");
connect(exportT42Act, &QAction::triggered, this, &MainWindow::exportT42); connect(exportT42Act, &QAction::triggered, this, [=]() { exportT42(false); });
QMenu *exportHashStringSubMenu = fileMenu->addMenu(tr("Export subpage to online editor")); QMenu *exportHashStringSubMenu = fileMenu->addMenu(tr("Export subpage to online editor"));
@@ -456,13 +498,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 +513,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 +676,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 +712,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 +797,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 +836,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 +887,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);
} }
@@ -898,14 +941,17 @@ void MainWindow::loadFile(const QString &fileName)
QApplication::setOverrideCursor(Qt::WaitCursor); QApplication::setOverrideCursor(Qt::WaitCursor);
if (fileInfo.suffix() == "t42") if (fileInfo.suffix() == "t42") {
importT42(&file, m_textWidget->document()); importT42(&file, m_textWidget->document());
else m_exportAutoFileName = fileName;
} else {
loadTTI(&file, m_textWidget->document()); loadTTI(&file, m_textWidget->document());
m_exportAutoFileName.clear();
}
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();
@@ -985,6 +1031,18 @@ void MainWindow::updateRecentFileActions()
m_recentFileActs[i]->setVisible(false); m_recentFileActs[i]->setVisible(false);
} }
void MainWindow::updateExportAutoAction()
{
if (m_exportAutoFileName.isEmpty()) {
m_exportAutoAct->setText(tr("Export subpage..."));
m_exportAutoAct->setEnabled(false);
return;
}
m_exportAutoAct->setText(tr("Overwrite &%1").arg(MainWindow::strippedName(m_exportAutoFileName)));
m_exportAutoAct->setEnabled(true);
}
void MainWindow::openRecentFile() void MainWindow::openRecentFile()
{ {
if (const QAction *action = qobject_cast<const QAction *>(sender())) if (const QAction *action = qobject_cast<const QAction *>(sender()))
@@ -1015,16 +1073,30 @@ bool MainWindow::saveFile(const QString &fileName)
return true; return true;
} }
void MainWindow::exportT42() void MainWindow::exportAuto()
{
// Menu should be disabled if m_exportAutoFileName is empty, but just in case...
if (m_exportAutoFileName.isEmpty())
return;
exportT42(true);
}
void MainWindow::exportT42(bool fromAuto)
{ {
QString errorMessage; QString errorMessage;
QString exportFileName = m_curFile; QString exportFileName;
changeSuffixFromTTI(exportFileName, "t42"); if (fromAuto)
exportFileName = m_exportAutoFileName;
else {
exportFileName = m_curFile;
changeSuffixFromTTI(exportFileName, "t42");
exportFileName = QFileDialog::getSaveFileName(this, tr("Export t42"), exportFileName, "t42 stream (*.t42)"); exportFileName = QFileDialog::getSaveFileName(this, tr("Export t42"), exportFileName, "t42 stream (*.t42)");
if (exportFileName.isEmpty()) if (exportFileName.isEmpty())
return; return;
}
QApplication::setOverrideCursor(Qt::WaitCursor); QApplication::setOverrideCursor(Qt::WaitCursor);
QSaveFile file(exportFileName); QSaveFile file(exportFileName);
@@ -1036,8 +1108,15 @@ void MainWindow::exportT42()
errorMessage = tr("Cannot open file %1 for writing:\n%2.").arg(QDir::toNativeSeparators(exportFileName), file.errorString()); errorMessage = tr("Cannot open file %1 for writing:\n%2.").arg(QDir::toNativeSeparators(exportFileName), file.errorString());
QApplication::restoreOverrideCursor(); QApplication::restoreOverrideCursor();
if (!errorMessage.isEmpty()) if (!errorMessage.isEmpty()) {
QMessageBox::warning(this, QApplication::applicationDisplayName(), errorMessage); QMessageBox::warning(this, QApplication::applicationDisplayName(), errorMessage);
return;
}
MainWindow::prependToRecentFiles(exportFileName);
m_exportAutoFileName = exportFileName;
statusBar()->showMessage(tr("File exported"), 2000);
} }
void MainWindow::exportM29() void MainWindow::exportM29()
@@ -1129,7 +1208,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 +1231,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-2023 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"
@@ -58,12 +59,15 @@ private slots:
void open(); void open();
bool save(); bool save();
bool saveAs(); bool saveAs();
void exportT42(); void reload();
void exportAuto();
void exportT42(bool);
void exportZXNet(); void exportZXNet();
void exportEditTF(); void exportEditTF();
void exportPNG(); void exportPNG();
void exportM29(); void exportM29();
void updateRecentFileActions(); void updateRecentFileActions();
void updateExportAutoAction();
void openRecentFile(); void openRecentFile();
void about(); void about();
void updatePageWidgets(); void updatePageWidgets();
@@ -114,10 +118,12 @@ 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;
QAction *m_recentFileSubMenuAct; QAction *m_recentFileSubMenuAct;
QAction *m_exportAutoAct;
QAction *m_deleteSubPageAction; QAction *m_deleteSubPageAction;
QAction *m_borderActs[3]; QAction *m_borderActs[3];
QAction *m_aspectRatioActs[4]; QAction *m_aspectRatioActs[4];
@@ -128,7 +134,7 @@ private:
QPushButton *m_insertModePushButton; QPushButton *m_insertModePushButton;
QRadioButton *m_levelRadioButton[4]; QRadioButton *m_levelRadioButton[4];
QString m_curFile; QString m_curFile, m_exportAutoFileName;
bool m_isUntitled; bool m_isUntitled;
}; };

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2023 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-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -0,0 +1,222 @@
/*
* Copyright (C) 2020-2023 Gavin MacGregor
*
* This file is part of QTeletextMaker.
*
* QTeletextMaker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QTeletextMaker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QTeletextMaker. If not, see <https://www.gnu.org/licenses/>.
*/
#include <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-2023 Gavin MacGregor
*
* This file is part of QTeletextMaker.
*
* QTeletextMaker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QTeletextMaker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QTeletextMaker. If not, see <https://www.gnu.org/licenses/>.
*/
#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-2023 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-2023 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-2023 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-2023 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-2023 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-2023 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-2023 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-2023 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 \

1354
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-2023 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-2023 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-2023 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-2023 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,30 +604,41 @@ 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);
disableTripletWidgets();
x26Widget->setLayout(x26Layout); x26Widget->setLayout(x26Layout);
this->setWidget(x26Widget); this->setWidget(x26Widget);
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 +656,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,16 +672,52 @@ 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);
if (current.isValid()) {
updateAllRawTripletSpinBoxes(current);
updateAllCookedTripletWidgets(current);
} else
disableTripletWidgets();
}
void X26DockWidget::disableTripletWidgets()
{
m_rawTripletAddressSpinBox->setEnabled(false);
m_rawTripletDataSpinBox->setEnabled(false);
m_rawTripletModeSpinBox->setEnabled(false);
m_rawTripletAddressSpinBox->blockSignals(true);
m_rawTripletModeSpinBox->blockSignals(true);
m_rawTripletDataSpinBox->blockSignals(true);
m_rawTripletAddressSpinBox->setValue(0);
m_rawTripletModeSpinBox->setValue(0);
m_rawTripletDataSpinBox->setValue(0);
m_rawTripletAddressSpinBox->blockSignals(false);
m_rawTripletModeSpinBox->blockSignals(false);
m_rawTripletDataSpinBox->blockSignals(false);
m_cookedRowSpinBox->setEnabled(false);
m_cookedColumnSpinBox->setEnabled(false);
m_cookedRowSpinBox->blockSignals(true);
m_cookedColumnSpinBox->blockSignals(true);
m_cookedRowSpinBox->setValue(1);
m_cookedColumnSpinBox->setValue(0);
m_cookedRowSpinBox->blockSignals(false);
m_cookedColumnSpinBox->blockSignals(false);
m_cookedModePushButton->setEnabled(false);
m_cookedModePushButton->setText(QString());
m_tripletParameterStackedLayout->setCurrentIndex(0);
} }
void X26DockWidget::updateAllCookedTripletWidgets(const QModelIndex &index) void X26DockWidget::updateAllCookedTripletWidgets(const QModelIndex &index)
{ {
const int modeExt = index.model()->data(index.model()->index(index.row(), 2), Qt::EditRole).toInt(); const int modeExt = index.model()->data(index.model()->index(index.row(), 2), Qt::EditRole).toInt();
m_cookedModePushButton->setEnabled(true);
m_cookedModePushButton->setText(m_x26Model->modeTripletName(modeExt)); m_cookedModePushButton->setText(m_x26Model->modeTripletName(modeExt));
switch (modeExt) { switch (modeExt) {
@@ -955,14 +1032,66 @@ 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 newListRow;
if (index.isValid()) {
newListRow = index.row()+after;
// 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());
}
// If we're inserting a Set Active Position or Full Row Colour triplet,
// look for a previous row setting triplet and set this one to the row after
if (modeExt == 0x04 || modeExt == 0x01) {
for (int i=newListRow-1; i>=0; i--) {
const int scanTripletModeExt = index.model()->data(index.model()->index(i, 2), Qt::EditRole).toInt();
if (scanTripletModeExt == 0x04 || scanTripletModeExt == 0x01) {
const int scanActivePositionRow = index.model()->data(index.model()->index(i, 0), Qt::EditRole).toInt()+1;
if (scanActivePositionRow < 25)
newTriplet.setAddressRow(scanActivePositionRow);
else
newTriplet.setAddressRow(24);
break;
}
}
}
} else
newListRow = 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(newListRow, 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 +1107,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-2023 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,9 +114,12 @@ 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;
void disableTripletWidgets();
}; };
#endif #endif

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020, 2021 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -65,11 +65,19 @@ QVariant X26Model::data(const QModelIndex &index, int role) const
} }
// Error colours from KDE Plasma Breeze (light) theme // Error colours from KDE Plasma Breeze (light) theme
if (role == Qt::ForegroundRole && triplet.error() != X26Triplet::NoError && index.column() == m_tripletErrors[triplet.error()].columnHighlight) if (role == Qt::ForegroundRole) {
return QColor(252, 252, 252); if (triplet.error() != X26Triplet::NoError && index.column() == m_tripletErrors[triplet.error()].columnHighlight)
return QColor(252, 252, 252);
else if ((index.column() == 2 && triplet.reservedMode()) || (index.column() == 3 && triplet.reservedData()))
return QColor(35, 38, 39);
}
if (role == Qt::BackgroundRole && triplet.error() != X26Triplet::NoError && index.column() == m_tripletErrors[triplet.error()].columnHighlight) if (role == Qt::BackgroundRole) {
return QColor(218, 68, 63); if (triplet.error() != X26Triplet::NoError && index.column() == m_tripletErrors[triplet.error()].columnHighlight)
return QColor(218, 68, 63);
else if ((index.column() == 2 && triplet.reservedMode()) || (index.column() == 3 && triplet.reservedData()))
return QColor(246, 116, 0);
}
if (role == Qt::ToolTipRole && triplet.error() != X26Triplet::NoError) if (role == Qt::ToolTipRole && triplet.error() != X26Triplet::NoError)
return m_tripletErrors[triplet.error()].message; return m_tripletErrors[triplet.error()].message;
@@ -797,13 +805,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 +816,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-2023 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-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -69,43 +69,70 @@ 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;
triplet->m_reservedMode = false;
triplet->m_reservedData = false;
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
triplet->m_error = X26Triplet::ActivePositionMovedUp; triplet->m_error = X26Triplet::ActivePositionMovedUp;
if (triplet->m_data & 0x60)
triplet->m_reservedData = true;
break; break;
case 0x01: // Full row colour case 0x01: // Full row colour
if (!activePosition.setRow(triplet->addressRow())) if (!activePosition.setRow(triplet->addressRow()))
triplet->m_error = X26Triplet::ActivePositionMovedUp; triplet->m_error = X26Triplet::ActivePositionMovedUp;
if ((triplet->m_data & 0x60) != 0x00 && (triplet->m_data & 0x60) != 0x60)
triplet->m_reservedData = true;
break; break;
case 0x04: // Set Active Position; case 0x04: // Set Active Position;
if (!activePosition.setRow(triplet->addressRow())) if (!activePosition.setRow(triplet->addressRow()))
triplet->m_error = X26Triplet::ActivePositionMovedUp; triplet->m_error = X26Triplet::ActivePositionMovedUp;
else if (triplet->data() >= 40 || !activePosition.setColumn(triplet->data())) else if (triplet->data() >= 40)
// FIXME data column highlighted?
triplet->m_reservedData = true;
else if (!activePosition.setColumn(triplet->data()))
triplet->m_error = X26Triplet::ActivePositionMovedLeft; triplet->m_error = X26Triplet::ActivePositionMovedLeft;
break; break;
case 0x07: // Address row 0 case 0x07: // Address row 0
if (activePosition.isDeployed()) if (triplet->m_address != 63)
// FIXME data column highlighted?
triplet->m_reservedData = true;
else if (activePosition.isDeployed())
triplet->m_error = X26Triplet::ActivePositionMovedUp; triplet->m_error = X26Triplet::ActivePositionMovedUp;
else { else {
activePosition.setRow(0); activePosition.setRow(0);
activePosition.setColumn(8); activePosition.setColumn(8);
} }
if ((triplet->m_data & 0x60) != 0x00 && (triplet->m_data & 0x60) != 0x60)
triplet->m_reservedData = true;
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; break;
case 0x15 ... 0x17: // Define Object case 0x15 ... 0x17: // Define Object
activePosition.reset(); activePosition.reset();
@@ -113,11 +140,45 @@ void X26TripletList::updateInternalData(int r)
// otherwise the object won't appear // otherwise the object won't appear
triplet->setObjectLocalIndex(i); triplet->setObjectLocalIndex(i);
break; break;
case 0x18: // DRCS mode
if ((triplet->m_data & 0x30) == 0x00)
triplet->m_reservedData = true;
case 0x1f: // Termination marker
case 0x08 ... 0x0d: // PDC
break;
default:
triplet->m_reservedMode = true;
}; };
// Column triplet: make sure that PDC and reserved triplets don't affect the Active Position // Column triplet: all triplets modes except PDC and reserved move the Active Position
} else if (triplet->modeExt() != 0x24 && triplet->modeExt() != 0x25 && triplet->modeExt() != 0x26 && triplet->modeExt() != 0x2a) } else if (triplet->modeExt() == 0x24 || triplet->modeExt() == 0x25 || triplet->modeExt() == 0x2a)
if (!activePosition.setColumn(triplet->addressColumn())) triplet->m_reservedMode = true;
triplet->m_error = X26Triplet::ActivePositionMovedLeft; else if (triplet->modeExt() != 0x26 && !activePosition.setColumn(triplet->addressColumn()))
triplet->m_error = X26Triplet::ActivePositionMovedLeft;
else
switch (triplet->modeExt()) {
case 0x20: // Foreground colour
case 0x23: // Foreground colour
if (triplet->m_data & 0x60)
triplet->m_reservedData = true;
break;
case 0x21: // G1 mosaic character
case 0x22: // G3 mosaic character at level 1.5
case 0x29: // G0 character
case 0x2b: // G3 mosaic character at level >=2.5
case 0x2f ... 0x3f: // G2 character or G0 diacritical mark
if (triplet->m_data < 0x20)
triplet->m_reservedData = true;
break;
case 0x27: // Additional flash functions
if (triplet->m_data >= 0x18)
triplet->m_reservedData = true;
break;
case 0x2d: // DRCS character
if ((triplet->m_data & 0x3f) >= 48)
// Should really be an error?
triplet->m_reservedData = true;
}
triplet->m_activePositionRow = activePosition.row(); triplet->m_activePositionRow = activePosition.row();
triplet->m_activePositionColumn = activePosition.column(); triplet->m_activePositionColumn = activePosition.column();
} }
@@ -126,26 +187,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-2023 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(); }
@@ -58,6 +62,8 @@ public:
int activePositionRow() const { return m_activePositionRow; } int activePositionRow() const { return m_activePositionRow; }
int activePositionColumn() const { return m_activePositionColumn; } int activePositionColumn() const { return m_activePositionColumn; }
X26TripletError error() const { return m_error; } X26TripletError error() const { return m_error; }
bool reservedMode() const { return m_reservedMode; }
bool reservedData() const { return m_reservedData; }
friend class X26TripletList; friend class X26TripletList;
@@ -66,6 +72,8 @@ private:
int m_activePositionRow = -1; int m_activePositionRow = -1;
int m_activePositionColumn = -1; int m_activePositionColumn = -1;
X26TripletError m_error = NoError; X26TripletError m_error = NoError;
bool m_reservedMode = false;
bool m_reservedData = false;
}; };
class X26TripletList class X26TripletList
@@ -84,7 +92,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;