Port to CMake from qmake, and split decoder into subdirectory

Qt 5 has had to be dropped as CMakeLists.txt uses qt_* specific commands
which are implemented in Qt 6 onwards.

cmake --install is only working on Linux at the moment; it installs the
executable and the bundled example tti files into /usr/local/share/doc

The teletext decoder with its document storage has been moved to a
subdirectory with the intention of making it possible to use in other
projects, but some further separation of editing stuff will be needed.
This commit is contained in:
G.K.MacGregor
2024-08-11 15:32:45 +01:00
parent cf6c4855ce
commit 40fc1e38d8
59 changed files with 92 additions and 63 deletions

View File

@@ -0,0 +1,7 @@
file (GLOB SOURCES *.cpp)
add_library(qteletextdecoder ${SOURCES} teletextfonts.qrc)
target_include_directories(qteletextdecoder PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(qteletextdecoder Qt::Widgets)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,281 @@
/*
* Copyright (C) 2020-2024 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 <QList>
#include <QMap>
#include <QMultiMap>
#include "levelonepage.h"
class TeletextPageDecode : public QObject
{
Q_OBJECT
public:
enum CharacterFragment { NormalSize, DoubleHeightTopHalf, DoubleHeightBottomHalf, DoubleWidthLeftHalf, DoubleWidthRightHalf, DoubleSizeTopLeftQuarter, DoubleSizeTopRightQuarter, DoubleSizeBottomLeftQuarter, DoubleSizeBottomRightQuarter };
enum RowHeight { NormalHeight, TopHalf, BottomHalf };
TeletextPageDecode();
~TeletextPageDecode();
bool refresh(int r, int c) const { return m_refresh[r][c]; }
void setRefresh(int r, int c, bool refresh);
void decodePage();
LevelOnePage *teletextPage() const { return m_levelOnePage; };
void setTeletextPage(LevelOnePage *newCurrentPage);
void updateSidePanels();
unsigned char cellCharacterCode(int r, int c) const { return m_cell[r][c].character.code; };
int cellCharacterSet(int r, int c) const { return m_cell[r][c].character.set; };
int cellCharacterDiacritical(int r, int c) const { return m_cell[r][c].character.diacritical; };
int cellG0CharacterSet(int r, int c) const { return m_cell[r][c].g0Set; };
int cellG2CharacterSet(int r, int c) const { return m_cell[r][c].g2Set; };
int cellForegroundCLUT(int r, int c) const { return m_cell[r][c].attribute.foregroundCLUT; };
int cellBackgroundCLUT(int r, int c) const { return m_cell[r][c].attribute.backgroundCLUT; };
QColor cellForegroundQColor(int r, int c);
QColor cellBackgroundQColor(int r, int c);
QColor cellFlashForegroundQColor(int r, int c);
int cellFlashMode(int r, int c) const { return m_cell[r][c].attribute.flash.mode; };
int cellFlashRatePhase(int r, int c) const { return m_cell[r][c].attribute.flash.ratePhase; };
int cellFlash2HzPhaseNumber(int r, int c) const { return m_cell[r][c].attribute.flash.phase2HzShown; };
CharacterFragment cellCharacterFragment(int r, int c) const { return m_cell[r][c].fragment; };
bool cellBoxed(int r, int c) const { return m_cell[r][c].attribute.display.boxingWindow; };
bool cellConceal(int r, int c) const { return m_cell[r][c].attribute.display.conceal; };
bool cellUnderlined(int r, int c) const { return cellCharacterSet(r, c) < 24 ? m_cell[r][c].attribute.display.underlineSeparated : false; };
bool cellBold(int r, int c) const { return m_cell[r][c].attribute.style.bold; };
bool cellItalic(int r, int c) const { return m_cell[r][c].attribute.style.italic; };
bool cellProportional(int r, int c) const { return m_cell[r][c].attribute.style.proportional; };
bool level1MosaicAttr(int r, int c) const { return m_cellLevel1MosaicAttr[r][c]; };
bool level1MosaicChar(int r, int c) const { return m_cellLevel1MosaicChar[r][c]; };
int level1CharSet(int r, int c) const { return m_cellLevel1CharSet[r][c]; };
RowHeight rowHeight(int r) const { return m_rowHeight[r]; };
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 level);
signals:
void fullScreenColourChanged(QColor newColour);
void fullRowColourChanged(int r, QColor newColour);
void sidePanelsChanged();
protected:
inline void setFullScreenColour(int newColour);
inline void setFullRowColour(int row, int newColour);
int m_finalFullScreenColour, m_level;
QColor m_finalFullScreenQColor;
int m_leftSidePanelColumns, m_rightSidePanelColumns;
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:
class Invocation;
enum ColourPart { Foreground, Background, FlashForeground };
struct textCharacter {
unsigned char code=0x20;
int set=0;
int diacritical=0;
};
friend inline bool operator!=(const textCharacter &lhs, const textCharacter &rhs)
{
return lhs.code != rhs.code ||
lhs.set != rhs.set ||
lhs.diacritical != rhs.diacritical;
}
struct flashFunctions {
int mode=0;
int ratePhase=0;
int phase2HzShown=0;
};
struct displayAttributes {
bool doubleHeight=false;
bool doubleWidth=false;
bool boxingWindow=false;
bool conceal=false;
bool invert=false;
bool underlineSeparated=false;
};
struct fontStyle {
bool proportional=false;
bool bold=false;
bool italic=false;
};
friend 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;
}
struct textAttributes {
int foregroundCLUT=7;
int backgroundCLUT=0;
flashFunctions flash;
displayAttributes display;
fontStyle style;
};
friend inline bool operator!=(const textAttributes &lhs, const textAttributes &rhs)
{
return lhs.foregroundCLUT != rhs.foregroundCLUT ||
lhs.backgroundCLUT != rhs.backgroundCLUT ||
lhs.flash.mode != rhs.flash.mode ||
lhs.flash.ratePhase != rhs.flash.ratePhase ||
lhs.flash.phase2HzShown != rhs.flash.phase2HzShown ||
lhs.display != rhs.display ||
lhs.style.proportional != rhs.style.proportional ||
lhs.style.bold != rhs.style.bold ||
lhs.style.italic != rhs.style.italic;
}
struct textCell {
textCharacter character;
textAttributes attribute;
CharacterFragment fragment=NormalSize;
int g0Set=0;
int g2Set=7;
};
friend inline bool operator!=(const textCell &lhs, const textCell &rhs)
{
return lhs.character != rhs.character ||
lhs.attribute != rhs.attribute ||
lhs.fragment != rhs.fragment;
}
struct textPainter {
textAttributes attribute;
textCell result;
textCell rightHalfCell;
textCell bottomHalfCell[72];
int styleSpreadRows=0;
int setProportionalRows[72], clearProportionalRows[72];
int setBoldRows[72], clearBoldRows[72];
int setItalicRows[72], clearItalicRows[72];
};
const QMap<int, int> m_level1CharacterMap {
{ 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 }
};
const QMap<int, int> m_g0CharacterMap {
{ 0x20, 1 }, { 0x24, 2 }, { 0x25, 3 },
{ 0x37, 4 },
{ 0x47, 5 },
{ 0x55, 6 }, { 0x57, 5 }
};
const QMap<int, int> m_g2CharacterMap {
{ 0x20, 8 }, { 0x24, 8 }, { 0x25, 8 },
{ 0x37, 9 },
{ 0x40, 10 }, { 0x44, 10 }, { 0x47, 10 },
{ 0x55, 10 }, { 0x57, 10 }
};
class Invocation
{
public:
Invocation();
X26TripletList *tripletList() const { return m_tripletList; };
void clear();
void setTripletList(X26TripletList *tripletList);
int startTripletNumber() const { return m_startTripletNumber; };
void setStartTripletNumber(int n);
int endTripletNumber() const { return m_endTripletNumber; };
void setEndTripletNumber(int n);
int originRow() const { return m_originRow; };
int originColumn() const { return m_originColumn; };
void setOrigin(int row, int column);
void buildMap(int level);
QList<QPair<int, int>> charPositions() const { return m_characterMap.uniqueKeys(); };
QList<QPair<int, int>> attrPositions() const { return m_attributeMap.uniqueKeys(); };
QList<X26Triplet> charactersMappedAt(int r, int c) const { return m_characterMap.values(qMakePair(r, c)); };
QList<X26Triplet> attributesMappedAt(int r, int c) const { return m_attributeMap.values(qMakePair(r, c)); };
int rightMostColumn(int r) const { return m_rightMostColumn.value(r, -1); };
int fullScreenColour() const { return m_fullScreenCLUT; };
QList<X26Triplet> fullRowColoursMappedAt(int r) const { return m_fullRowCLUTMap.values(r); };
private:
X26TripletList *m_tripletList;
int m_startTripletNumber, m_endTripletNumber;
int m_originRow, m_originColumn;
// QPair is row and column
QMultiMap<QPair<int, int>, X26Triplet> m_characterMap;
QMultiMap<QPair<int, int>, X26Triplet> m_attributeMap;
QMap<int, int> m_rightMostColumn;
int m_fullScreenCLUT;
QMultiMap<int, X26Triplet> m_fullRowCLUTMap;
};
static int s_instances;
static textPainter s_blankPainter;
void decodeRow(int r);
QColor cellQColor(int r, int c, ColourPart colourPart);
textCell& cellAtCharacterOrigin(int r, int c);
void buildInvocationList(Invocation &invocation, int objectType);
textCharacter characterFromTriplets(const QList<X26Triplet> triplets);
inline void rotateFlashMovement(flashFunctions &flash);
bool m_refresh[25][72];
textCell m_cell[25][72];
bool m_cellLevel1MosaicAttr[25][40];
bool m_cellLevel1MosaicChar[25][40];
int m_cellLevel1CharSet[25][40];
LevelOnePage* m_levelOnePage;
int m_fullRowColour[25];
QColor m_fullRowQColor[25];
QList<Invocation> m_invocations[3];
Invocation m_localEnhancements;
textPainter m_level1ActivePainter;
QList<textPainter> m_adapPassPainter[2];
int m_level1DefaultCharSet, m_level1SecondCharSet;
int m_defaultG0andG2, m_secondG0andG2;
RowHeight m_rowHeight[25];
};
#endif

View File

@@ -0,0 +1,381 @@
/*
* Copyright (C) 2020-2024 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 <QAbstractListModel>
#include <vector>
#include "document.h"
#include "levelonepage.h"
ClutModel::ClutModel(QObject *parent): QAbstractListModel(parent)
{
m_subPage = nullptr;
}
int ClutModel::rowCount(const QModelIndex & /*parent*/) const
{
return 32;
}
QVariant ClutModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role == Qt::DisplayRole)
return QString("CLUT %1:%2").arg(index.row() >> 3).arg(index.row() & 0x07);
if (role == Qt::DecorationRole && m_subPage != nullptr)
return m_subPage->CLUTtoQColor(index.row());
return QVariant();
}
void ClutModel::setSubPage(LevelOnePage *subPage)
{
if (subPage != m_subPage) {
m_subPage = subPage;
emit dataChanged(createIndex(0, 0), createIndex(31, 0), QVector<int>(Qt::DecorationRole));
}
}
TeletextDocument::TeletextDocument()
{
m_pageNumber = 0x199;
m_description.clear();
m_pageFunction = PFLevelOnePage;
m_packetCoding = Coding7bit;
m_subPages.push_back(new LevelOnePage);
m_currentSubPageIndex = 0;
m_undoStack = new QUndoStack(this);
m_cursorRow = 1;
m_cursorColumn = 0;
m_selectionCornerRow = m_selectionCornerColumn = -1;
m_selectionSubPage = nullptr;
m_clutModel = new ClutModel;
m_clutModel->setSubPage(m_subPages[0]);
}
TeletextDocument::~TeletextDocument()
{
delete m_clutModel;
for (auto &subPage : m_subPages)
delete(subPage);
for (auto &recycleSubPage : m_recycleSubPages)
delete(recycleSubPage);
}
bool TeletextDocument::isEmpty() const
{
for (auto &subPage : m_subPages)
if (!subPage->isEmpty())
return false;
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)
{
m_pageFunction = newPageFunction;
}
void TeletextDocument::setPacketCoding(PacketCodingEnum newPacketEncoding)
{
m_packetCoding = newPacketEncoding;
}
*/
void TeletextDocument::selectSubPageIndex(int newSubPageIndex, bool forceRefresh)
{
// forceRefresh overrides "beyond the last subpage" check, so inserting a subpage after the last one still shows - dangerous workaround?
if (forceRefresh || (newSubPageIndex != m_currentSubPageIndex && newSubPageIndex < m_subPages.size())) {
emit aboutToChangeSubPage();
m_currentSubPageIndex = newSubPageIndex;
m_clutModel->setSubPage(m_subPages[m_currentSubPageIndex]);
emit subPageSelected();
emit selectionMoved();
return;
}
}
void TeletextDocument::selectSubPageNext()
{
if (m_currentSubPageIndex < m_subPages.size()-1) {
emit aboutToChangeSubPage();
m_currentSubPageIndex++;
m_clutModel->setSubPage(m_subPages[m_currentSubPageIndex]);
emit subPageSelected();
emit selectionMoved();
}
}
void TeletextDocument::selectSubPagePrevious()
{
if (m_currentSubPageIndex > 0) {
emit aboutToChangeSubPage();
m_currentSubPageIndex--;
m_clutModel->setSubPage(m_subPages[m_currentSubPageIndex]);
emit subPageSelected();
emit selectionMoved();
}
}
void TeletextDocument::insertSubPage(int beforeSubPageIndex, bool copySubPage)
{
LevelOnePage *insertedSubPage;
if (copySubPage)
insertedSubPage = new LevelOnePage(*m_subPages.at(beforeSubPageIndex));
else
insertedSubPage = new LevelOnePage;
if (beforeSubPageIndex == m_subPages.size())
m_subPages.push_back(insertedSubPage);
else
m_subPages.insert(m_subPages.begin()+beforeSubPageIndex, insertedSubPage);
}
void TeletextDocument::deleteSubPage(int subPageToDelete)
{
m_clutModel->setSubPage(nullptr);
delete(m_subPages[subPageToDelete]);
m_subPages.erase(m_subPages.begin()+subPageToDelete);
}
void TeletextDocument::deleteSubPageToRecycle(int subPageToRecycle)
{
m_recycleSubPages.push_back(m_subPages[subPageToRecycle]);
m_subPages.erase(m_subPages.begin()+subPageToRecycle);
}
void TeletextDocument::unDeleteSubPageFromRecycle(int subPage)
{
m_subPages.insert(m_subPages.begin()+subPage, m_recycleSubPages.back());
m_recycleSubPages.pop_back();
}
void TeletextDocument::setPageNumber(int pageNumber)
{
// If the magazine number was changed, we need to update the relative magazine numbers in FastText
// and page enhancement links
int oldMagazine = (m_pageNumber & 0xf00);
int newMagazine = (pageNumber & 0xf00);
// Fix magazine 0 to 8
if (oldMagazine == 0x800)
oldMagazine = 0x000;
if (newMagazine == 0x800)
newMagazine = 0x000;
int magazineFlip = oldMagazine ^ newMagazine;
m_pageNumber = pageNumber;
for (auto &subPage : m_subPages)
if (magazineFlip) {
for (int i=0; i<6; i++)
subPage->setFastTextLinkPageNumber(i, subPage->fastTextLinkPageNumber(i) ^ magazineFlip);
for (int i=0; i<8; i++)
subPage->setComposeLinkPageNumber(i, subPage->composeLinkPageNumber(i) ^ magazineFlip);
}
}
void TeletextDocument::setPageNumberFromString(QString pageNumberString)
{
bool pageNumberOk;
int pageNumberRead = pageNumberString.toInt(&pageNumberOk, 16);
if ((!pageNumberOk) || pageNumberRead < 0x100 || pageNumberRead > 0x8ff)
return;
setPageNumber(pageNumberRead);
}
void TeletextDocument::setDescription(QString newDescription)
{
m_description = newDescription;
}
void TeletextDocument::setFastTextLinkPageNumberOnAllSubPages(int linkNumber, int pageNumber)
{
for (auto &subPage : m_subPages)
subPage->setFastTextLinkPageNumber(linkNumber, pageNumber);
}
void TeletextDocument::cursorUp(bool shiftKey)
{
if (shiftKey && !selectionActive())
setSelectionCorner(m_cursorRow, m_cursorColumn);
if (--m_cursorRow == 0)
m_cursorRow = 24;
if (shiftKey)
emit selectionMoved();
else
cancelSelection();
emit cursorMoved();
}
void TeletextDocument::cursorDown(bool shiftKey)
{
if (shiftKey && !selectionActive())
setSelectionCorner(m_cursorRow, m_cursorColumn);
if (++m_cursorRow == 25)
m_cursorRow = 1;
if (shiftKey)
emit selectionMoved();
else
cancelSelection();
emit cursorMoved();
}
void TeletextDocument::cursorLeft(bool shiftKey)
{
if (shiftKey && !selectionActive())
setSelectionCorner(m_cursorRow, m_cursorColumn);
if (--m_cursorColumn == -1) {
m_cursorColumn = 39;
cursorUp(shiftKey);
}
if (shiftKey)
emit selectionMoved();
else
cancelSelection();
emit cursorMoved();
}
void TeletextDocument::cursorRight(bool shiftKey)
{
if (shiftKey && !selectionActive())
setSelectionCorner(m_cursorRow, m_cursorColumn);
if (++m_cursorColumn == 40) {
m_cursorColumn = 0;
cursorDown(shiftKey);
}
if (shiftKey)
emit selectionMoved();
else
cancelSelection();
emit cursorMoved();
}
void TeletextDocument::moveCursor(int cursorRow, int cursorColumn, bool selectionInProgress)
{
if (selectionInProgress && !selectionActive())
setSelectionCorner(m_cursorRow, m_cursorColumn);
if (cursorRow != -1)
m_cursorRow = cursorRow;
if (cursorColumn != -1)
m_cursorColumn = cursorColumn;
if (selectionInProgress)
emit selectionMoved();
else
cancelSelection();
emit cursorMoved();
}
void TeletextDocument::setSelectionCorner(int row, int column)
{
if (m_selectionCornerRow != row || m_selectionCornerColumn != column) {
m_selectionSubPage = currentSubPage();
m_selectionCornerRow = row;
m_selectionCornerColumn = column;
// emit selectionMoved();
}
}
void TeletextDocument::setSelection(int topRow, int leftColumn, int bottomRow, int rightColumn)
{
if (selectionTopRow() != topRow || selectionBottomRow() != bottomRow || selectionLeftColumn() != leftColumn || selectionRightColumn() != rightColumn) {
m_selectionSubPage = currentSubPage();
m_selectionCornerRow = topRow;
m_cursorRow = bottomRow;
m_selectionCornerColumn = leftColumn;
m_cursorColumn = rightColumn;
emit selectionMoved();
emit cursorMoved();
}
}
void TeletextDocument::cancelSelection()
{
if (m_selectionSubPage != nullptr) {
m_selectionSubPage = nullptr;
emit selectionMoved();
m_selectionCornerRow = m_selectionCornerColumn = -1;
}
}
int TeletextDocument::levelRequired() const
{
int levelSeen = 0;
for (auto &subPage : m_subPages) {
levelSeen = qMax(levelSeen, subPage->levelRequired());
if (levelSeen == 3)
break;
}
return levelSeen;
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright (C) 2020-2024 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 DOCUMENT_H
#define DOCUMENT_H
#include <QAbstractListModel>
#include <QObject>
#include <QUndoStack>
#include <vector>
#include "levelonepage.h"
class ClutModel : public QAbstractListModel
{
Q_OBJECT
public:
ClutModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void setSubPage(LevelOnePage *page);
private:
LevelOnePage *m_subPage;
};
class TeletextDocument : public QObject
{
Q_OBJECT
public:
// Available Page Functions according to 9.4.2.1 of the spec
enum PageFunctionEnum { PFLevelOnePage, PFDataBroadcasting, PFGlobalPOP, PFNormalPOP, PFGlobalDRCS, PFNormalDRCS, PFMOT, PFMIP, PFBasicTOPTable, PFAdditionalInformationTable, PFMultiPageTable, PFMultiPageExtensionTable, PFTriggerMessages };
// Available Page Codings of X/1 to X/25 according to 9.4.2.1 of the spec
enum PacketCodingEnum { Coding7bit, Coding8bit, Coding18bit, Coding4bit, Coding4bitThen7bit, CodingPerPacket };
TeletextDocument();
~TeletextDocument();
bool isEmpty() const;
void clear();
PageFunctionEnum pageFunction() const { return m_pageFunction; }
// void setPageFunction(PageFunctionEnum);
PacketCodingEnum packetCoding() const { return m_packetCoding; }
// void setPacketCoding(PacketCodingEnum);
int numberOfSubPages() const { return m_subPages.size(); }
LevelOnePage* subPage(int p) const { return m_subPages[p]; }
LevelOnePage* currentSubPage() const { return m_subPages[m_currentSubPageIndex]; }
int currentSubPageIndex() const { return m_currentSubPageIndex; }
void selectSubPageIndex(int newSubPageIndex, bool refresh=false);
void selectSubPageNext();
void selectSubPagePrevious();
void insertSubPage(int beforeSubPageIndex, bool copySubPage);
void deleteSubPage(int subPageToDelete);
void deleteSubPageToRecycle(int subPageToRecycle);
void unDeleteSubPageFromRecycle(int subPage);
int pageNumber() const { return m_pageNumber; }
void setPageNumber(int pageNumber);
void setPageNumberFromString(QString pageNumberString);
QString description() const { return m_description; }
void setDescription(QString newDescription);
void setFastTextLinkPageNumberOnAllSubPages(int linkNumber, int pageNumber);
QUndoStack *undoStack() const { return m_undoStack; }
ClutModel *clutModel() const { return m_clutModel; }
int cursorRow() const { return m_cursorRow; }
int cursorColumn() const { return m_cursorColumn; }
void cursorUp(bool shiftKey=false);
void cursorDown(bool shiftKey=false);
void cursorLeft(bool shiftKey=false);
void cursorRight(bool shiftKey=false);
void moveCursor(int cursorRow, int cursorColumn, bool selectionInProgress=false);
int selectionTopRow() const { return m_selectionCornerRow == -1 ? m_cursorRow : qMin(m_selectionCornerRow, m_cursorRow); }
int selectionBottomRow() const { return qMax(m_selectionCornerRow, m_cursorRow); }
int selectionLeftColumn() const { return m_selectionCornerColumn == -1 ? m_cursorColumn : qMin(m_selectionCornerColumn, m_cursorColumn); }
int selectionRightColumn() const { return qMax(m_selectionCornerColumn, m_cursorColumn); }
int selectionWidth() const { return m_selectionCornerColumn == -1 ? 1 : selectionRightColumn() - selectionLeftColumn() + 1; }
int selectionHeight() const { return m_selectionCornerRow == -1 ? 1 : selectionBottomRow() - selectionTopRow() + 1; }
bool selectionActive() const { return m_selectionSubPage == currentSubPage(); }
int selectionCornerRow() const { return m_selectionCornerRow == -1 ? m_cursorRow : m_selectionCornerRow; }
int selectionCornerColumn() const { return m_selectionCornerColumn == -1 ? m_cursorColumn : m_selectionCornerColumn; }
void setSelectionCorner(int row, int column);
void setSelection(int topRow, int leftColumn, int bottomRow, int rightColumn);
void cancelSelection();
int levelRequired() const;
signals:
void cursorMoved();
void selectionMoved();
void colourChanged(int i);
void pageOptionsChanged();
void aboutToChangeSubPage();
void subPageSelected();
void contentsChanged();
void tripletCommandHighlight(int tripletNumber);
private:
QString m_description;
int m_pageNumber, m_currentSubPageIndex;
PageFunctionEnum m_pageFunction;
PacketCodingEnum m_packetCoding;
std::vector<LevelOnePage *> m_subPages;
std::vector<LevelOnePage *> m_recycleSubPages;
QUndoStack *m_undoStack;
int m_cursorRow, m_cursorColumn, m_selectionCornerRow, m_selectionCornerColumn;
LevelOnePage *m_selectionSubPage;
ClutModel *m_clutModel;
};
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -0,0 +1,520 @@
/*
* Copyright (C) 2020-2024 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 <QByteArray>
#include <QColor>
#include <QList>
#include <QString>
#include <algorithm>
#include "levelonepage.h"
#include "x26triplets.h"
LevelOnePage::LevelOnePage()
{
m_enhancements.reserve(maxEnhancements());
clearPage();
}
LevelOnePage::LevelOnePage(const PageBase &other)
{
m_enhancements.reserve(maxEnhancements());
clearPage();
for (int i=0; i<26; i++)
if (other.packetExists(i))
setPacket(i, other.packet(i));
for (int i=26; i<30; i++)
for (int j=0; j<16; j++)
if (other.packetExists(i, j))
setPacket(i, j, other.packet(i));
for (int i=PageBase::C4ErasePage; i<=PageBase::C14NOS; i++)
setControlBit(i, other.controlBit(i));
}
// So far we only call clearPage() once, within the constructor
void LevelOnePage::clearPage()
{
for (int r=0; r<25; r++)
for (int c=0; c<40; c++)
m_level1Page[r][c] = 0x20;
for (int i=C4ErasePage; i<=C14NOS; i++)
setControlBit(i, false);
for (int i=0; i<8; i++)
m_composeLink[i] = { (i<4) ? i : 0, false, i>=4, 0x0ff, 0x0000 };
for (int i=0; i<6; i++)
m_fastTextLink[i] = { 0x0ff, 0x3f7f };
/* m_subPageNumber = 0x0000; */
m_cycleValue = 20;
m_cycleType = CTseconds;
m_defaultCharSet = 0;
m_defaultNOS = 0;
m_secondCharSet = 0xf;
m_secondNOS = 0x7;
m_defaultScreenColour = 0;
m_defaultRowColour = 0;
m_blackBackgroundSubst = false;
m_colourTableRemap = 0;
m_leftSidePanelDisplayed = m_rightSidePanelDisplayed = false;
m_sidePanelStatusL25 = true;
m_sidePanelColumns = 0;
std::copy(m_defaultCLUT, m_defaultCLUT+32, m_CLUT);
// If clearPage() is called outside constructor, we need to implement m_enhancements.clear();
}
bool LevelOnePage::isEmpty() const
{
if (!m_enhancements.isEmpty())
return false;
if (!isPaletteDefault(0, 31))
return false;
for (int r=0; r<25; r++)
for (int c=0; c<40; c++)
if (m_level1Page[r][c] != 0x20)
return false;
return true;
}
QByteArray LevelOnePage::packet(int packetNumber) const
{
QByteArray result(40, 0x00);
if (packetNumber <= 24) {
for (int c=0; c<40; c++)
result[c] = m_level1Page[packetNumber][c];
return result;
}
return PageBase::packet(packetNumber);
}
QByteArray LevelOnePage::packet(int packetNumber, int designationCode) const
{
QByteArray result(40, 0x00);
if (packetNumber == 26) {
if (!packetFromEnhancementListNeeded(designationCode))
return result; // Blank result
return packetFromEnhancementList(designationCode);
}
if (packetNumber == 27 && designationCode == 0) {
for (int i=0; i<6; i++) {
result[i*6+1] = m_fastTextLink[i].pageNumber & 0x00f;
result[i*6+2] = (m_fastTextLink[i].pageNumber & 0x0f0) >> 4;
result[i*6+3] = m_fastTextLink[i].subPageNumber & 0x000f;
result[i*6+4] = ((m_fastTextLink[i].subPageNumber & 0x0070) >> 4) | ((m_fastTextLink[i].pageNumber & 0x100) >> 5);
result[i*6+5] = (m_fastTextLink[i].subPageNumber & 0x0f00) >> 8;
result[i*6+6] = ((m_fastTextLink[i].subPageNumber & 0x3000) >> 12) | ((m_fastTextLink[i].pageNumber & 0x600) >> 7);
}
result[37] = 0xf;
result[38] = result[39] = 0;
return result;
}
if (packetNumber == 27 && (designationCode == 4 || designationCode == 5)) {
for (int i=0; i<(designationCode == 4 ? 6 : 2); i++) {
int pageLinkNumber = i+(designationCode == 4 ? 0 : 6);
result[i*6+1] = (m_composeLink[pageLinkNumber].level3p5 << 3) | (m_composeLink[pageLinkNumber].level2p5 << 2) | m_composeLink[pageLinkNumber].function;
result[i*6+2] = ((m_composeLink[pageLinkNumber].pageNumber & 0x100) >> 3) | 0x10 | (m_composeLink[pageLinkNumber].pageNumber & 0x00f);
result[i*6+3] = ((m_composeLink[pageLinkNumber].pageNumber & 0x0f0) >> 2) | ((m_composeLink[pageLinkNumber].pageNumber & 0x600) >> 9);
result[i*6+4] = ((m_composeLink[pageLinkNumber].subPageCodes & 0x000f) << 2);
result[i*6+5] = ((m_composeLink[pageLinkNumber].subPageCodes & 0x03f0) >> 4);
result[i*6+6] = ((m_composeLink[pageLinkNumber].subPageCodes & 0xfc00) >> 10);
}
return result;
}
if (packetNumber == 28 && (designationCode == 0 || designationCode == 4)) {
int CLUToffset = (designationCode == 0) ? 16 : 0;
result[1] = 0x00;
result[2] = ((m_defaultCharSet & 0x3) << 4) | (m_defaultNOS << 1);
result[3] = ((m_secondCharSet & 0x1) << 5) | (m_secondNOS << 2) | (m_defaultCharSet >> 2);
result[4] = (m_sidePanelStatusL25 << 5) | (m_rightSidePanelDisplayed << 4) | (m_leftSidePanelDisplayed << 3) | (m_secondCharSet >> 1);
result[5] = m_sidePanelColumns | ((m_CLUT[CLUToffset] & 0x300) >> 4);
for (int c=0; c<16; c++) {
result[c*2+6] = ((m_CLUT[CLUToffset+c] & 0x0f0) >> 2) | ((m_CLUT[CLUToffset+c] & 0xf00) >> 10);
result[c*2+7] = ((m_CLUT[CLUToffset+c+1] & 0x300) >> 4) | (m_CLUT[CLUToffset+c] & 0x00f);
}
result[37] = ((m_defaultScreenColour & 0x03) << 4) | (m_CLUT[CLUToffset+15] & 0x00f);
result[38] = ((m_defaultRowColour & 0x07) << 3) | (m_defaultScreenColour >> 2);
result[39] = (m_colourTableRemap << 3) | (m_blackBackgroundSubst << 2) | (m_defaultRowColour >> 3);
return result;
}
return PageBase::packet(packetNumber, designationCode);
}
bool LevelOnePage::setPacket(int packetNumber, QByteArray packetContents)
{
if (packetNumber <= 24) {
for (int c=0; c<40; c++)
m_level1Page[packetNumber][c] = packetContents.at(c);
return true;
}
qDebug("LevelOnePage unhandled setPacket X/%d", packetNumber);
return PageBase::setPacket(packetNumber, packetContents);
}
bool LevelOnePage::setPacket(int packetNumber, int designationCode, QByteArray packetContents)
{
if (packetNumber == 26) {
setEnhancementListFromPacket(designationCode, packetContents);
return true;
}
if (packetNumber == 27 && designationCode == 0) {
for (int i=0; i<6; i++) {
int relativeMagazine = (packetContents.at(i*6+4) >> 3) | ((packetContents.at(i*6+6) & 0xc) >> 1);
int pageNumber = (packetContents.at(i*6+2) << 4) | packetContents.at(i*6+1);
m_fastTextLink[i].pageNumber = (relativeMagazine << 8) | pageNumber;
m_fastTextLink[i].subPageNumber = packetContents.at(i*6+3) | ((packetContents.at(i*6+4) & 0x7) << 4) | (packetContents.at(i*6+5) << 8) | ((packetContents.at(i*6+6) & 0x3) << 12);
// TODO remove this warning when we can preserve FastText subpage links
if (m_fastTextLink[i].subPageNumber != 0x3f7f)
qDebug("FastText link %d has custom subPageNumber %x - will NOT be saved!", i, m_fastTextLink[i].subPageNumber);
}
return true;
}
if (packetNumber == 27 && (designationCode == 4 || designationCode == 5)) {
for (int i=0; i<(designationCode == 4 ? 6 : 2); i++) {
int pageLinkNumber = i+(designationCode == 4 ? 0 : 6);
int pageFunction = packetContents.at(i*6+1) & 0x03;
if (i >= 4)
m_composeLink[pageLinkNumber].function = pageFunction;
else if (i != pageFunction)
qDebug("X/27/4 link number %d fixed at function %d. Attempted to set to %d.", pageLinkNumber, pageLinkNumber, pageFunction);
m_composeLink[pageLinkNumber].level2p5 = packetContents.at(i*6+1) & 0x04;
m_composeLink[pageLinkNumber].level3p5 = packetContents.at(i*6+1) & 0x08;
m_composeLink[pageLinkNumber].pageNumber = ((packetContents.at(i*6+3) & 0x03) << 9) | ((packetContents.at(i*6+2) & 0x20) << 3) | ((packetContents.at(i*6+3) & 0x3c) << 2) | (packetContents.at(i*6+2) & 0x0f);
m_composeLink[pageLinkNumber].subPageCodes = (packetContents.at(i*6+4) >> 2) | (packetContents.at(i*6+5) << 4) | (packetContents.at(i*6+6) << 10);
}
return true;
}
if (packetNumber == 28 && (designationCode == 0 || designationCode == 4)) {
int CLUToffset = (designationCode == 0) ? 16 : 0;
m_defaultCharSet = ((packetContents.at(2) >> 4) & 0x3) | ((packetContents.at(3) << 2) & 0xc);
m_defaultNOS = (packetContents.at(2) >> 1) & 0x7;
m_secondCharSet = ((packetContents.at(3) >> 5) & 0x1) | ((packetContents.at(4) << 1) & 0xe);
m_secondNOS = (packetContents.at(3) >> 2) & 0x7;
m_leftSidePanelDisplayed = (packetContents.at(4) >> 3) & 1;
m_rightSidePanelDisplayed = (packetContents.at(4) >> 4) & 1;
m_sidePanelStatusL25 = (packetContents.at(4) >> 5) & 1;
m_sidePanelColumns = packetContents.at(5) & 0xf;
for (int c=0; c<16; c++)
m_CLUT[CLUToffset+c] = ((packetContents.at(c*2+5) << 4) & 0x300) | ((packetContents.at(c*2+6) << 10) & 0xc00) | ((packetContents.at(c*2+6) << 2) & 0x0f0) | (packetContents.at(c*2+7) & 0x00f);
m_defaultScreenColour = (packetContents.at(37) >> 4) | ((packetContents.at(38) << 2) & 0x1c);
m_defaultRowColour = ((packetContents.at(38)) >> 3) | ((packetContents.at(39) << 3) & 0x18);
m_blackBackgroundSubst = (packetContents.at(39) >> 2) & 1;
m_colourTableRemap = (packetContents.at(39) >> 3) & 7;
return true;
}
qDebug("LevelOnePage unhandled setPacket X/%d/%d", packetNumber, designationCode);
return PageBase::setPacket(packetNumber, designationCode, packetContents);
}
bool LevelOnePage::packetExists(int packetNumber) const
{
if (packetNumber <= 24) {
for (int c=0; c<40; c++)
if (m_level1Page[packetNumber][c] != 0x20)
return true;
return false;
}
return PageBase::packetExists(packetNumber);
}
bool LevelOnePage::packetExists(int packetNumber, int designationCode) const
{
if (packetNumber == 26)
return packetFromEnhancementListNeeded(designationCode);
if (packetNumber == 27 && designationCode == 0) {
for (int i=0; i<6; i++)
if ((m_fastTextLink[i].pageNumber & 0x0ff) != 0xff)
return true;
return false;
}
if (packetNumber == 27 && (designationCode == 4 || designationCode == 5)) {
for (int i=0; i<(designationCode == 4 ? 6 : 2); i++) {
int pageLinkNumber = i+(designationCode == 4 ? 0 : 6);
if ((m_composeLink[pageLinkNumber].pageNumber & 0x0ff) != 0x0ff)
return true;
}
return false;
}
if (packetNumber == 28) {
if (designationCode == 0) {
if (m_leftSidePanelDisplayed || m_rightSidePanelDisplayed || m_defaultScreenColour !=0 || m_defaultRowColour !=0 || m_blackBackgroundSubst || m_colourTableRemap !=0 || m_defaultCharSet != 0 || m_secondCharSet != 0xf)
return true;
return !isPaletteDefault(16, 31);
}
if (designationCode == 4)
return !isPaletteDefault(0, 15);
}
return PageBase::packetExists(packetNumber, designationCode);
}
bool LevelOnePage::controlBit(int bitNumber) const
{
switch (bitNumber) {
case C12NOS:
return (m_defaultNOS & 1) == 1;
case C13NOS:
return (m_defaultNOS & 2) == 2;
case C14NOS:
return (m_defaultNOS & 4) == 4;
default:
return PageBase::controlBit(bitNumber);
}
}
bool LevelOnePage::setControlBit(int bitNumber, bool active)
{
switch (bitNumber) {
case C12NOS:
m_defaultNOS &= 0x06;
if (active)
m_defaultNOS |= 0x01;
return true;
case C13NOS:
m_defaultNOS &= 0x05;
if (active)
m_defaultNOS |= 0x02;
return true;
case C14NOS:
m_defaultNOS &= 0x03;
if (active)
m_defaultNOS |= 0x04;
return true;
default:
return PageBase::setControlBit(bitNumber, active);
}
}
/* void LevelOnePage::setSubPageNumber(int newSubPageNumber) { m_subPageNumber = newSubPageNumber; } */
void LevelOnePage::setCycleValue(int newValue) { m_cycleValue = newValue; };
void LevelOnePage::setCycleType(CycleTypeEnum newType) { m_cycleType = newType; }
void LevelOnePage::setDefaultCharSet(int newDefaultCharSet) { m_defaultCharSet = newDefaultCharSet; }
void LevelOnePage::setDefaultNOS(int defaultNOS)
{
m_defaultNOS = defaultNOS;
}
void LevelOnePage::setSecondCharSet(int newSecondCharSet)
{
m_secondCharSet = newSecondCharSet;
if (m_secondCharSet == 0xf)
m_secondNOS = 0x7;
}
void LevelOnePage::setSecondNOS(int newSecondNOS) { m_secondNOS = newSecondNOS; }
void LevelOnePage::setCharacter(int row, int column, unsigned char newCharacter) { m_level1Page[row][column] = newCharacter; }
void LevelOnePage::setDefaultScreenColour(int newDefaultScreenColour) { m_defaultScreenColour = newDefaultScreenColour; }
void LevelOnePage::setDefaultRowColour(int newDefaultRowColour) { m_defaultRowColour = newDefaultRowColour; }
void LevelOnePage::setColourTableRemap(int newColourTableRemap) { m_colourTableRemap = newColourTableRemap; }
void LevelOnePage::setBlackBackgroundSubst(bool newBlackBackgroundSubst) { m_blackBackgroundSubst = newBlackBackgroundSubst; }
int LevelOnePage::CLUT(int index, int renderLevel) const
{
if (renderLevel == 2)
return index>=16 ? m_CLUT[index] : m_defaultCLUT[index];
else
return renderLevel==3 ? m_CLUT[index] : m_defaultCLUT[index];
}
void LevelOnePage::setCLUT(int index, int newColour)
{
if (index == 8)
return;
m_CLUT[index] = newColour;
}
QColor LevelOnePage::CLUTtoQColor(int index, int renderLevel) const
{
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);
}
bool LevelOnePage::isPaletteDefault(int colour) const
{
return m_CLUT[colour] == m_defaultCLUT[colour];
}
bool LevelOnePage::isPaletteDefault(int fromColour, int toColour) const
{
for (int i=fromColour; i<=toColour; i++)
if (m_CLUT[i] != m_defaultCLUT[i])
return false;
return true;
}
int LevelOnePage::levelRequired() const
{
// X/28/4 present i.e. CLUTs 0 or 1 redefined - Level 3.5
if (!isPaletteDefault(0, 15))
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;
// If there's no X/26 triplets, exit here as Level 1 or 2.5
if (m_enhancements.isEmpty())
return levelSeen;
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
return 3;
if (levelSeen == 0)
// Check for Level 1.5 triplets
switch (m_enhancements.at(i).modeExt()) {
case 0x04: // Set Active Position
case 0x07: // Address Row 0
case 0x1f: // Termination
case 0x22: // G3 character @ Level 1.5
case 0x2f: // G2 character
levelSeen = 1;
break;
default:
if (m_enhancements.at(i).modeExt() >= 0x30 && m_enhancements.at(i).modeExt() <= 0x3f)
// G0 character with diacritical
levelSeen = 1;
}
if (levelSeen < 2)
switch (m_enhancements.at(i).modeExt()) {
// Check for Level 2.5 triplets
case 0x00: // Full screen colour
case 0x01: // Full row colour
case 0x10: // Origin Modifier
case 0x11: // Invoke Active Object
case 0x12: // Invoke Adaptive Object
case 0x13: // Invoke Passive Object
case 0x15: // Define Active Object
case 0x16: // Define Adaptive Object
case 0x17: // Define Passive Object
case 0x18: // DRCS Mode
case 0x20: // Foreground colour
case 0x21: // G1 character
case 0x23: // Background colour
case 0x27: // Flash functions
case 0x28: // G0 and G2 charset designation
case 0x29: // G0 character @ Level 2.5
case 0x2b: // G3 character @ Level 2.5
case 0x2c: // Display attributes
case 0x2d: // DRCS character
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: // Define Active Object
case 0x16: // Define Adaptive Object
case 0x17: // Define Passive Object
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;
}
}
return levelSeen;
}
void LevelOnePage::setLeftSidePanelDisplayed(bool newLeftSidePanelDisplayed) { m_leftSidePanelDisplayed = newLeftSidePanelDisplayed; }
void LevelOnePage::setRightSidePanelDisplayed(bool newRightSidePanelDisplayed) { m_rightSidePanelDisplayed = newRightSidePanelDisplayed; }
void LevelOnePage::setSidePanelColumns(int newSidePanelColumns) { m_sidePanelColumns = newSidePanelColumns; }
void LevelOnePage::setSidePanelStatusL25(bool newSidePanelStatusL25) { m_sidePanelStatusL25 = newSidePanelStatusL25; }
void LevelOnePage::setFastTextLinkPageNumber(int linkNumber, int pageNumber)
{
m_fastTextLink[linkNumber].pageNumber = pageNumber;
}
void LevelOnePage::setComposeLinkFunction(int linkNumber, int newFunction)
{
m_composeLink[linkNumber].function = newFunction;
}
void LevelOnePage::setComposeLinkLevel2p5(int linkNumber, bool newRequired)
{
m_composeLink[linkNumber].level2p5 = newRequired;
}
void LevelOnePage::setComposeLinkLevel3p5(int linkNumber, bool newRequired)
{
m_composeLink[linkNumber].level3p5 = newRequired;
}
void LevelOnePage::setComposeLinkPageNumber(int linkNumber, int newPageNumber)
{
m_composeLink[linkNumber].pageNumber = newPageNumber;
}
void LevelOnePage::setComposeLinkSubPageCodes(int linkNumber, int newSubPageCodes)
{
m_composeLink[linkNumber].subPageCodes = newSubPageCodes;
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright (C) 2020-2024 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 LEVELONEPAGE_H
#define LEVELONEPAGE_H
#include <QByteArray>
#include <QColor>
#include <QObject>
#include <QString>
#include "pagex26base.h"
#include "x26triplets.h"
class LevelOnePage : public PageX26Base //: public QObject
{
//Q_OBJECT
public:
enum CycleTypeEnum { CTcycles, CTseconds };
LevelOnePage();
LevelOnePage(const PageBase &other);
bool isEmpty() const override;
QByteArray packet(int packetNumber) const override;
QByteArray packet(int packetNumber, int designationCode) const override;
bool packetExists(int packetNumber) const override;
bool packetExists(int packetNumber, int designationCode) const override;
bool setPacket(int packetNumber, QByteArray packetContents) override;
bool setPacket(int packetNumber, int designationCode, QByteArray packetContents) override;
bool controlBit(int bitNumber) const override;
bool setControlBit(int bitNumber, bool active) override;
void clearPage();
int maxEnhancements() const { return 208; };
/* void setSubPageNumber(int); */
int cycleValue() const { return m_cycleValue; };
void setCycleValue(int newValue);
CycleTypeEnum cycleType() const { return m_cycleType; };
void setCycleType(CycleTypeEnum newType);
int defaultCharSet() const { return m_defaultCharSet; }
void setDefaultCharSet(int newDefaultCharSet);
int defaultNOS() const { return m_defaultNOS; }
void setDefaultNOS(int defaultNOS);
int secondCharSet() const { return m_secondCharSet; }
void setSecondCharSet(int newSecondCharSet);
int secondNOS() const { return m_secondNOS; }
void setSecondNOS(int newSecondNOS);
unsigned char character(int row, int column) const { return m_level1Page[row][column]; }
void setCharacter(int row, int column, unsigned char newCharacter);
int defaultScreenColour() const { return m_defaultScreenColour; }
void setDefaultScreenColour(int newDefaultScreenColour);
int defaultRowColour() const { return m_defaultRowColour; }
void setDefaultRowColour(int newDefaultRowColour);
int colourTableRemap() const { return m_colourTableRemap; }
void setColourTableRemap(int newColourTableRemap);
bool blackBackgroundSubst() const { return m_blackBackgroundSubst; }
void setBlackBackgroundSubst(bool newBlackBackgroundSubst);
int CLUT(int index, int renderLevel=3) const;
void setCLUT(int index, int newColour);
QColor CLUTtoQColor(int index, int renderlevel=3) const;
bool isPaletteDefault(int colour) const;
bool isPaletteDefault(int fromColour, int toColour) const;
int levelRequired() const;
bool leftSidePanelDisplayed() const { return m_leftSidePanelDisplayed; }
void setLeftSidePanelDisplayed(bool newLeftSidePanelDisplayed);
bool rightSidePanelDisplayed() const { return m_rightSidePanelDisplayed; }
void setRightSidePanelDisplayed(bool newRightSidePanelDisplayed);
int sidePanelColumns() const { return m_sidePanelColumns; }
void setSidePanelColumns(int newSidePanelColumns);
bool sidePanelStatusL25() const { return m_sidePanelStatusL25; }
void setSidePanelStatusL25(bool newSidePanelStatusL25);
int fastTextLinkPageNumber(int linkNumber) const { return m_fastTextLink[linkNumber].pageNumber; }
void setFastTextLinkPageNumber(int linkNumber, int pageNumber);
int composeLinkFunction(int linkNumber) const { return m_composeLink[linkNumber].function; }
void setComposeLinkFunction(int linkNumber, int newFunction);
bool composeLinkLevel2p5(int linkNumber) const { return m_composeLink[linkNumber].level2p5; }
void setComposeLinkLevel2p5(int linkNumber, bool newRequired);
bool composeLinkLevel3p5(int linkNumber) const { return m_composeLink[linkNumber].level3p5; }
void setComposeLinkLevel3p5(int linkNumber, bool newRequired);
int composeLinkPageNumber(int linkNumber) const { return m_composeLink[linkNumber].pageNumber; }
void setComposeLinkPageNumber(int linkNumber, int newPageNumber);
int composeLinkSubPageCodes(int linkNumber) const { return m_composeLink[linkNumber].subPageCodes; }
void setComposeLinkSubPageCodes(int linkNumber, int newSubPageCodes);
private:
unsigned char m_level1Page[25][40];
/* int m_subPageNumber; */
int m_cycleValue;
CycleTypeEnum m_cycleType;
int m_defaultCharSet, m_defaultNOS, m_secondCharSet, m_secondNOS;
int m_defaultScreenColour, m_defaultRowColour, m_colourTableRemap, m_sidePanelColumns;
bool m_blackBackgroundSubst, m_leftSidePanelDisplayed, m_rightSidePanelDisplayed, m_sidePanelStatusL25;
int m_CLUT[32];
struct fastTextLink {
int pageNumber;
int subPageNumber;
} m_fastTextLink[6];
struct composeLink {
int function;
bool level2p5, level3p5;
int pageNumber, subPageCodes;
} m_composeLink[8];
const int m_defaultCLUT[32] = {
0x000, 0xf00, 0x0f0, 0xff0, 0x00f, 0xf0f, 0x0ff, 0xfff,
0x000, 0x700, 0x070, 0x770, 0x007, 0x707, 0x077, 0x777,
0xf05, 0xf70, 0x0f7, 0xffb, 0x0ca, 0x500, 0x652, 0xc77,
0x333, 0xf77, 0x7f7, 0xff7, 0x77f, 0xf7f, 0x7ff, 0xddd
};
};
#endif

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2020-2024 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 <QByteArray>
#include "pagebase.h"
PageBase::PageBase()
{
// We use nullptrs to keep track of allocated packets, so initialise them this way
for (int i=0; i<26; i++)
m_displayPackets[i] = nullptr;
for (int i=0; i<4; i++)
for (int j=0; j<16; j++)
m_designationPackets[i][j] = nullptr;
for (int i=PageBase::C4ErasePage; i<=PageBase::C14NOS; i++)
m_controlBits[i] = false;
}
PageBase::~PageBase()
{
for (int i=0; i<26; i++)
if (m_displayPackets[i] != nullptr)
delete m_displayPackets[i];
for (int i=0; i<4; i++)
for (int j=0; j<16; j++)
if (m_designationPackets[i][j] != nullptr)
delete m_designationPackets[i][j];
}
bool PageBase::isEmpty() const
{
for (int i=0; i<26; i++)
if (m_displayPackets[i] != nullptr)
return false;
for (int i=0; i<4; i++)
for (int j=0; j<16; j++)
if (m_designationPackets[i][j] != nullptr)
return false;
return true;
}
QByteArray PageBase::packet(int i) const
{
if (m_displayPackets[i] == nullptr)
return QByteArray(); // Blank result
return *m_displayPackets[i];
}
QByteArray PageBase::packet(int i, int j) const
{
if (m_designationPackets[i-26][j] == nullptr)
return QByteArray(); // Blank result
return *m_designationPackets[i-26][j];
}
bool PageBase::setPacket(int i, QByteArray packetContents)
{
if (m_displayPackets[i] == nullptr)
m_displayPackets[i] = new QByteArray(40, 0x00);
*m_displayPackets[i] = packetContents;
return true;
}
bool PageBase::setPacket(int i, int j, QByteArray packetContents)
{
if (m_designationPackets[i-26][j] == nullptr)
m_designationPackets[i-26][j] = new QByteArray(40, 0x00);
*m_designationPackets[i-26][j] = packetContents;
return true;
}
/*
bool PageBase::deletePacket(int i)
{
if (m_displayPackets[i] != nullptr) {
delete m_displayPackets[i];
m_displayPackets[i] = nullptr;
}
return true;
}
bool PageBase::deletePacket(int i)
{
if (m_designationPackets[i-26][j] != nullptr) {
delete m_designationPackets[i-26][j];
m_designationPackets[i-26][j] = nullptr;
}
return true;
}
*/
bool PageBase::setControlBit(int bitNumber, bool active)
{
m_controlBits[bitNumber] = active;
return true;
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2020-2024 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 PAGEBASE_H
#define PAGEBASE_H
#include <QByteArray>
// If we inherit from QObject then we can't copy construct, so "make a new subpage that's a copy of this one" wouldn't work
class PageBase //: public QObject
{
//Q_OBJECT
public:
enum ControlBitsEnum { C4ErasePage, C5Newsflash, C6Subtitle, C7SuppressHeader, C8Update, C9InterruptedSequence, C10InhibitDisplay, C11SerialMagazine, C12NOS, C13NOS, C14NOS };
PageBase();
virtual ~PageBase();
virtual bool isEmpty() const;
virtual QByteArray packet(int i) const;
virtual QByteArray packet(int i, int j) const;
virtual bool packetExists(int i) const { return m_displayPackets[i] != nullptr; }
virtual bool packetExists(int i, int j) const { return m_designationPackets[i-26][j] != nullptr; }
virtual bool setPacket(int i, QByteArray packetContents);
virtual bool setPacket(int i, int j, QByteArray packetContents);
// bool deletePacket(int);
// bool deletePacket(int, int);
virtual bool controlBit(int bitNumber) const { return m_controlBits[bitNumber]; }
virtual bool setControlBit(int bitNumber, bool active);
private:
bool m_controlBits[11];
QByteArray *m_displayPackets[26], *m_designationPackets[4][16];
};
#endif

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2020-2024 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 <QByteArray>
#include "pagex26base.h"
QByteArray PageX26Base::packetFromEnhancementList(int packetNumber) const
{
QByteArray result(40, 0x00);
int enhanceListPointer;
X26Triplet lastTriplet;
for (int i=0; i<13; i++) {
enhanceListPointer = packetNumber*13+i;
if (enhanceListPointer < m_enhancements.size()) {
result[i*3+1] = m_enhancements.at(enhanceListPointer).address();
result[i*3+2] = m_enhancements.at(enhanceListPointer).mode() | ((m_enhancements.at(enhanceListPointer).data() & 1) << 5);
result[i*3+3] = m_enhancements.at(enhanceListPointer).data() >> 1;
// If this is the last triplet, get a copy to repeat to the end of the packet
if (enhanceListPointer == m_enhancements.size()-1) {
lastTriplet = m_enhancements.at(enhanceListPointer);
// If the last triplet was NOT a Termination Marker, make up one
if (lastTriplet.mode() != 0x1f || lastTriplet.address() != 0x3f) {
lastTriplet.setAddress(0x3f);
lastTriplet.setMode(0x1f);
lastTriplet.setData(0x07);
}
}
} else {
// We've gone past the end of the triplet list, so repeat the Termination Marker to the end
result[i*3+1] = lastTriplet.address();
result[i*3+2] = lastTriplet.mode() | ((lastTriplet.data() & 1) << 5);
result[i*3+3] = lastTriplet.data() >> 1;
}
}
return result;
}
void PageX26Base::setEnhancementListFromPacket(int packetNumber, QByteArray packetContents)
{
// Preallocate entries in the m_enhancements list to hold our incoming triplets.
// We write "dummy" reserved 11110 Row Triplets in the allocated entries which then get overwritten by the packet contents.
// This is in case of missing packets so we can keep Local Object pointers valid.
while (m_enhancements.size() < (packetNumber+1)*13)
m_enhancements.append(m_paddingX26Triplet);
int enhanceListPointer;
X26Triplet newX26Triplet;
for (int i=0; i<13; i++) {
enhanceListPointer = packetNumber*13+i;
newX26Triplet.setAddress(packetContents.at(i*3+1) & 0x3f);
newX26Triplet.setMode(packetContents.at(i*3+2) & 0x1f);
newX26Triplet.setData(((packetContents.at(i*3+3) & 0x3f) << 1) | ((packetContents.at(i*3+2) & 0x20) >> 5));
m_enhancements.replace(enhanceListPointer, newX26Triplet);
}
if (newX26Triplet.mode() == 0x1f && newX26Triplet.address() == 0x3f && newX26Triplet.data() & 0x01)
// Last triplet was a Termination Marker (without ..follows) so clean up the repeated ones
while (m_enhancements.size()>1 && m_enhancements.at(m_enhancements.size()-2).mode() == 0x1f && m_enhancements.at(m_enhancements.size()-2).address() == 0x3f && m_enhancements.at(m_enhancements.size()-2).data() == newX26Triplet.data())
m_enhancements.removeLast();
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2020-2024 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 PAGEX26BASE_H
#define PAGEX26BASE_H
#include <QByteArray>
#include <QList>
#include "pagebase.h"
#include "x26triplets.h"
class PageX26Base : public PageBase //: public QObject
{
//Q_OBJECT
public:
X26TripletList *enhancements() { return &m_enhancements; };
virtual int maxEnhancements() const =0;
protected:
QByteArray packetFromEnhancementList(int packetNumber) const;
void setEnhancementListFromPacket(int packetNumber, QByteArray packetContents);
bool packetFromEnhancementListNeeded(int n) const { return ((m_enhancements.size()+12) / 13) > n; };
X26TripletList m_enhancements;
const X26Triplet m_paddingX26Triplet { 41, 0x1e, 0 };
};
#endif

View File

@@ -0,0 +1,467 @@
/*
* Copyright (C) 2020-2024 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 <QBitmap>
#include <QColor>
#include <QDir>
#include <QImage>
#include <QPainter>
#include <QPixmap>
#include "render.h"
#include "decode.h"
int TeletextFontBitmap::s_instances = 0;
QBitmap *TeletextFontBitmap::s_fontBitmap = nullptr;
QImage *TeletextFontBitmap::s_fontImage = nullptr;
TeletextFontBitmap::TeletextFontBitmap()
{
Q_INIT_RESOURCE(teletextfonts);
if (s_instances == 0) {
s_fontBitmap = new QBitmap(":/fontimages/teletextfont.png");
s_fontImage = new QImage(s_fontBitmap->toImage());
}
s_instances++;
}
TeletextFontBitmap::~TeletextFontBitmap()
{
s_instances--;
if (s_instances == 0) {
delete s_fontImage;
delete s_fontBitmap;
}
}
TeletextPageRender::TeletextPageRender()
{
for (int i=0; i<6; i++)
m_pageImage[i] = new QImage(864, 250, QImage::Format_ARGB32_Premultiplied);
m_reveal = false;
m_mix = false;
m_showControlCodes = false;
m_flashBuffersHz = 0;
for (int r=0; r<25; r++) {
m_flashingRow[r] = 0;
for (int c=0; c<40; c++)
m_controlCodeCache[r][c] = 0x7f;
}
}
TeletextPageRender::~TeletextPageRender()
{
for (int i=0; i<6; i++)
delete m_pageImage[i];
}
void TeletextPageRender::setDecoder(TeletextPageDecode *decoder)
{
m_decoder = decoder;
}
inline void TeletextPageRender::drawFromBitmap(QPainter &painter, int r, int c, const QImage image, TeletextPageDecode::CharacterFragment characterFragment)
{
switch (characterFragment) {
case TeletextPageDecode::NormalSize:
painter.drawImage(c*12, r*10, image);
break;
case TeletextPageDecode::DoubleHeightTopHalf:
painter.drawImage(QRect(c*12, r*10, 12, 10), image, QRect(0, 0, 12, 5));
break;
case TeletextPageDecode::DoubleHeightBottomHalf:
painter.drawImage(QRect(c*12, r*10, 12, 10), image, QRect(0, 5, 12, 5));
break;
case TeletextPageDecode::DoubleWidthLeftHalf:
painter.drawImage(QRect(c*12, r*10, 12, 10), image, QRect(0, 0, 6, 10));
break;
case TeletextPageDecode::DoubleWidthRightHalf:
painter.drawImage(QRect(c*12, r*10, 12, 10), image, QRect(6, 0, 6, 10));
break;
case TeletextPageDecode::DoubleSizeTopLeftQuarter:
painter.drawImage(QRect(c*12, r*10, 12, 10), image, QRect(0, 0, 6, 5));
break;
case TeletextPageDecode::DoubleSizeTopRightQuarter:
painter.drawImage(QRect(c*12, r*10, 12, 10), image, QRect(6, 0, 6, 5));
break;
case TeletextPageDecode::DoubleSizeBottomLeftQuarter:
painter.drawImage(QRect(c*12, r*10, 12, 10), image, QRect(0, 5, 6, 5));
break;
case TeletextPageDecode::DoubleSizeBottomRightQuarter:
painter.drawImage(QRect(c*12, r*10, 12, 10), image, QRect(6, 5, 6, 5));
break;
}
}
inline void TeletextPageRender::drawFromFontBitmap(QPainter &painter, int r, int c, unsigned char characterCode, int characterSet, TeletextPageDecode::CharacterFragment characterFragment)
{
switch (characterFragment) {
case TeletextPageDecode::NormalSize:
painter.drawImage(c*12, r*10, *m_fontBitmap.image(), (characterCode-32)*12, characterSet*10, 12, 10);
break;
case TeletextPageDecode::DoubleHeightTopHalf:
painter.drawImage(QRect(c*12, r*10, 12, 10), *m_fontBitmap.image(), QRect((characterCode-32)*12, characterSet*10, 12, 5));
break;
case TeletextPageDecode::DoubleHeightBottomHalf:
painter.drawImage(QRect(c*12, r*10, 12, 10), *m_fontBitmap.image(), QRect((characterCode-32)*12, characterSet*10+5, 12, 5));
break;
case TeletextPageDecode::DoubleWidthLeftHalf:
painter.drawImage(QRect(c*12, r*10, 12, 10), *m_fontBitmap.image(), QRect((characterCode-32)*12, characterSet*10, 6, 10));
break;
case TeletextPageDecode::DoubleWidthRightHalf:
painter.drawImage(QRect(c*12, r*10, 12, 10), *m_fontBitmap.image(), QRect((characterCode-32)*12+6, characterSet*10, 6, 10));
break;
case TeletextPageDecode::DoubleSizeTopLeftQuarter:
painter.drawImage(QRect(c*12, r*10, 12, 10), *m_fontBitmap.image(), QRect((characterCode-32)*12, characterSet*10, 6, 5));
break;
case TeletextPageDecode::DoubleSizeTopRightQuarter:
painter.drawImage(QRect(c*12, r*10, 12, 10), *m_fontBitmap.image(), QRect((characterCode-32)*12+6, characterSet*10, 6, 5));
break;
case TeletextPageDecode::DoubleSizeBottomLeftQuarter:
painter.drawImage(QRect(c*12, r*10, 12, 10), *m_fontBitmap.image(), QRect((characterCode-32)*12, characterSet*10+5, 6, 5));
break;
case TeletextPageDecode::DoubleSizeBottomRightQuarter:
painter.drawImage(QRect(c*12, r*10, 12, 10), *m_fontBitmap.image(), QRect((characterCode-32)*12+6, characterSet*10+5, 6, 5));
break;
}
}
inline void TeletextPageRender::drawCharacter(QPainter &painter, int r, int c, unsigned char characterCode, int characterSet, int characterDiacritical, TeletextPageDecode::CharacterFragment characterFragment)
{
const bool dontUnderline = characterCode == 0x00;
if (dontUnderline)
characterCode = 0x20;
if (characterCode == 0x20 && characterSet < 25 && characterDiacritical == 0)
painter.fillRect(c*12, r*10, 12, 10, m_backgroundQColor);
else if (characterCode == 0x7f && characterSet == 24)
painter.fillRect(c*12, r*10, 12, 10, m_foregroundQColor);
else if ((m_decoder->cellBold(r, c) || m_decoder->cellItalic(r, c)) && characterSet < 24)
drawBoldOrItalicCharacter(painter, r, c, characterCode, characterSet, characterFragment);
else {
m_fontBitmap.image()->setColorTable(QVector<QRgb>{m_backgroundQColor.rgba(), m_foregroundQColor.rgba()});
drawFromFontBitmap(painter, r, c, characterCode, characterSet, characterFragment);
}
if (m_decoder->cellUnderlined(r, c) && !dontUnderline) {
painter.setPen(m_foregroundQColor);
switch (characterFragment) {
case TeletextPageDecode::NormalSize:
case TeletextPageDecode::DoubleWidthLeftHalf:
case TeletextPageDecode::DoubleWidthRightHalf:
painter.drawLine(c*12, r*10+9, c*12+11, r*10+9);
break;
case TeletextPageDecode::DoubleHeightBottomHalf:
case TeletextPageDecode::DoubleSizeBottomLeftQuarter:
case TeletextPageDecode::DoubleSizeBottomRightQuarter:
painter.drawRect(c*12, r*10+8, 11, 1);
break;
default:
break;
}
}
if (characterDiacritical != 0) {
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
m_fontBitmap.image()->setColorTable(QVector<QRgb>{0x00000000, m_foregroundQColor.rgba()});
drawFromFontBitmap(painter, r, c, characterDiacritical+64, 7, characterFragment);
painter.setCompositionMode(QPainter::CompositionMode_Source);
}
}
inline void TeletextPageRender::drawBoldOrItalicCharacter(QPainter &painter, int r, int c, unsigned char characterCode, int characterSet, TeletextPageDecode::CharacterFragment characterFragment)
{
QImage styledImage = QImage(12, 10, QImage::Format_Mono);
QPainter styledPainter;
m_fontBitmap.image()->setColorTable(QVector<QRgb>{m_backgroundQColor.rgba(), m_foregroundQColor.rgba()});
styledImage.setColorTable(QVector<QRgb>{m_backgroundQColor.rgba(), m_foregroundQColor.rgba()});
if (m_decoder->cellItalic(r, c)) {
styledImage.fill(0);
styledPainter.begin(&styledImage);
styledPainter.setCompositionMode(QPainter::CompositionMode_Source);
styledPainter.drawImage(1, 0, *m_fontBitmap.image(), (characterCode-32)*12, characterSet*10, 11, 3);
styledPainter.drawImage(0, 3, *m_fontBitmap.image(), (characterCode-32)*12, characterSet*10+3, 12, 3);
styledPainter.drawImage(0, 6, *m_fontBitmap.image(), (characterCode-32)*12+1, characterSet*10+6, 11, 4);
styledPainter.end();
} else
styledImage = m_fontBitmap.image()->copy((characterCode-32)*12, characterSet*10, 12, 10);
if (m_decoder->cellBold(r, c)) {
QImage boldeningImage;
boldeningImage = styledImage.copy();
styledPainter.begin(&styledImage);
styledPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
boldeningImage.setColorTable(QVector<QRgb>{0x00000000, m_foregroundQColor.rgba()});
styledPainter.drawImage(1, 0, boldeningImage);
styledPainter.end();
}
drawFromBitmap(painter, r, c, styledImage, characterFragment);
}
void TeletextPageRender::renderPage(bool force)
{
for (int r=0; r<25; r++)
renderRow(r, 0, force);
}
void TeletextPageRender::renderRow(int r, int ph, bool force)
{
QPainter painter;
int flashingRow = 0;
bool rowRefreshed = false;
painter.begin(m_pageImage[ph]);
painter.setCompositionMode(QPainter::CompositionMode_Source);
for (int c=0; c<72; c++) {
bool controlCodeChanged = false;
// Ensure that shown control codes are refreshed
if (ph == 0 && m_showControlCodes && c < 40 && (m_controlCodeCache[r][c] != 0x7f || m_decoder->teletextPage()->character(r, c) < 0x20)) {
controlCodeChanged = m_controlCodeCache[r][c] != m_decoder->teletextPage()->character(r, c);
if (controlCodeChanged) {
if (m_decoder->teletextPage()->character(r, c) < 0x20)
m_controlCodeCache[r][c] = m_decoder->teletextPage()->character(r, c);
else
m_controlCodeCache[r][c] = 0x7f;
}
}
if (ph == 0) {
if (m_decoder->cellFlashMode(r, c) != 0)
flashingRow = qMax(flashingRow, (m_decoder->cellFlashRatePhase(r, c) == 0) ? 1 : 2);
} else
force = m_decoder->cellFlashMode(r, c) != 0;
// If drawing into a flash pixmap buffer, "force" is set on a flashing cell only
// and since the refresh and controlCodeChanged variables will be false at this point
// only flashing cells will be drawn
if (m_decoder->refresh(r, c) || force || controlCodeChanged) {
unsigned char characterCode;
int characterSet, characterDiacritical;
rowRefreshed = true;
if (!m_reveal && m_decoder->cellConceal(r, c)) {
characterCode = 0x20;
characterSet = 0;
characterDiacritical = 0;
} else {
characterCode = m_decoder->cellCharacterCode(r, c);
characterSet = m_decoder->cellCharacterSet(r, c);
characterDiacritical = m_decoder->cellCharacterDiacritical(r, c);
}
if (m_decoder->cellFlashMode(r, c) == 0)
m_foregroundQColor = m_decoder->cellForegroundQColor(r, c);
else {
// Flashing cell, decide if phase in this cycle is on or off
bool phaseOn;
if (m_decoder->cellFlashRatePhase(r, c) == 0)
phaseOn = (ph < 3) ^ (m_decoder->cellFlashMode(r, c) == 2);
else
phaseOn = ((ph == m_decoder->cellFlash2HzPhaseNumber(r, c)-1) || (ph == m_decoder->cellFlash2HzPhaseNumber(r, c)+2)) ^ (m_decoder->cellFlashMode(r, c) == 2);
// If flashing to adjacent CLUT select the appropriate foreground colour
if (m_decoder->cellFlashMode(r, c) == 3 && !phaseOn)
m_foregroundQColor = m_decoder->cellFlashForegroundQColor(r, c);
else
m_foregroundQColor = m_decoder->cellForegroundQColor(r, c);
// If flashing mode is Normal or Invert, draw a space instead of a character on phase
if ((m_decoder->cellFlashMode(r, c) == 1 || m_decoder->cellFlashMode(r, c) == 2) && !phaseOn) {
// Character 0x00 draws space without underline
characterCode = 0x00;
characterSet = 0;
characterDiacritical = 0;
}
}
if (!m_mix || m_decoder->cellBoxed(r, c))
m_backgroundQColor = m_decoder->cellBackgroundQColor(r, c);
else
m_backgroundQColor = Qt::transparent;
drawCharacter(painter, r, c, characterCode, characterSet, characterDiacritical, m_decoder->cellCharacterFragment(r, c));
if (m_showControlCodes && c < 40 && m_decoder->teletextPage()->character(r, c) < 0x20) {
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
m_fontBitmap.image()->setColorTable(QVector<QRgb>{0x7f000000, 0xe0ffffff});
painter.drawImage(c*12, r*10, *m_fontBitmap.image(), (m_decoder->teletextPage()->character(r, c)+32)*12, 250, 12, 10);
painter.setCompositionMode(QPainter::CompositionMode_Source);
}
}
}
painter.end();
if (ph != 0)
return;
if (flashingRow == 3)
flashingRow = 2;
if (flashingRow != m_flashingRow[r])
setRowFlashStatus(r, flashingRow);
for (int c=0; c<72; c++)
m_decoder->setRefresh(r, c, false);
// If row had changes rendered and flashing is present anywhere on the screen,
// copy this rendered line into the other flash pixmap buffers and then re-render
// the flashing cells in those buffers
if (rowRefreshed && m_flashBuffersHz > 0) {
painter.begin(m_pageImage[3]);
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.drawImage(0, r*10, *m_pageImage[0], 0, r*10, 864, 10);
painter.end();
renderRow(r, 3);
if (m_flashBuffersHz == 2) {
painter.begin(m_pageImage[1]);
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.drawImage(0, r*10, *m_pageImage[0], 0, r*10, 864, 10);
painter.end();
painter.begin(m_pageImage[2]);
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.drawImage(0, r*10, *m_pageImage[0], 0, r*10, 864, 10);
painter.end();
painter.begin(m_pageImage[4]);
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.drawImage(0, r*10, *m_pageImage[3], 0, r*10, 864, 10);
painter.end();
painter.begin(m_pageImage[5]);
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.drawImage(0, r*10, *m_pageImage[3], 0, r*10, 864, 10);
painter.end();
renderRow(r, 1);
renderRow(r, 2);
renderRow(r, 4);
renderRow(r, 5);
}
}
}
void TeletextPageRender::setRowFlashStatus(int r, int rowFlashHz)
{
m_flashingRow[r] = rowFlashHz;
if (rowFlashHz == m_flashBuffersHz)
return;
if (rowFlashHz < m_flashBuffersHz) {
// New flash Hz for this row is lower than the entire screen flash Hz
// Check the other rows if they still need flashing at the current flash Hz
// If not, reduce the screen flash Hz
int highestRowFlashHz = rowFlashHz;
for (int ri=0; ri<25; ri++)
if (m_flashingRow[ri] > highestRowFlashHz) {
highestRowFlashHz = m_flashingRow[ri];
if (highestRowFlashHz == 2)
break;
}
if (highestRowFlashHz > rowFlashHz)
rowFlashHz = highestRowFlashHz;
if (rowFlashHz == m_flashBuffersHz)
return;
m_flashBuffersHz = rowFlashHz;
emit flashChanged(m_flashBuffersHz);
return;
}
// If we get here, new flash Hz for this row is higher than the entire flash Hz
// so prepare the pixmap flash buffers
if (m_flashBuffersHz == 0)
*m_pageImage[3] = m_pageImage[0]->copy();
if (rowFlashHz == 2) {
*m_pageImage[1] = m_pageImage[0]->copy();
*m_pageImage[2] = m_pageImage[0]->copy();
*m_pageImage[4] = m_pageImage[3]->copy();
*m_pageImage[5] = m_pageImage[3]->copy();
}
m_flashBuffersHz = rowFlashHz;
emit flashChanged(m_flashBuffersHz);
}
void TeletextPageRender::colourChanged(int index)
{
for (int r=0; r<25; r++)
for (int c=0; c<72; c++) {
if (m_decoder->cellForegroundCLUT(r, c) == index || m_decoder->cellBackgroundCLUT(r, c) == index || m_decoder->cellForegroundCLUT(r, c) == 8 || m_decoder->cellBackgroundCLUT(r, c) == 8)
m_decoder->setRefresh(r, c, true);
if (m_decoder->cellFlashMode(r, c) == 3 && ((m_decoder->cellForegroundCLUT(r, c) ^ 8) == index || (m_decoder->cellForegroundCLUT(r, c) ^ 8) == 8))
m_decoder->setRefresh(r, c, true);
}
}
void TeletextPageRender::setReveal(bool reveal)
{
if (reveal == m_reveal)
return;
m_reveal = reveal;
for (int r=0; r<25; r++)
for (int c=0; c<72; c++)
if (m_decoder->cellConceal(r, c))
m_decoder->setRefresh(r, c, true);
}
void TeletextPageRender::setMix(bool mix)
{
if (mix == m_mix)
return;
m_mix = mix;
for (int r=0; r<25; r++)
for (int c=0; c<72; c++)
if (!m_decoder->cellBoxed(r, c))
m_decoder->setRefresh(r, c, true);
}
void TeletextPageRender::setShowControlCodes(bool showControlCodes)
{
if (showControlCodes == m_showControlCodes)
return;
m_showControlCodes = showControlCodes;
for (int r=0; r<25; r++)
for (int c=0; c<40; c++)
if (m_decoder->teletextPage()->character(r, c) < 0x20)
m_decoder->setRefresh(r, c, true);
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2020-2024 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 RENDER_H
#define RENDER_H
#include <QBitmap>
#include <QColor>
#include <QIcon>
#include <QImage>
#include <QPixmap>
#include "decode.h"
class TeletextFontBitmap
{
public:
TeletextFontBitmap();
~TeletextFontBitmap();
QImage *image() const { return s_fontImage; }
QPixmap charBitmap(int c, int s) const { return s_fontBitmap->copy((c-32)*12, s*10, 12, 10); }
QIcon charIcon(int c, int s) const { return QIcon(charBitmap(c, s)); }
private:
static int s_instances;
static QBitmap* s_fontBitmap;
static QImage* s_fontImage;
};
class TeletextPageRender : public QObject
{
Q_OBJECT
public:
TeletextPageRender();
~TeletextPageRender();
QImage* image(int i) const { return m_pageImage[i]; };
bool mix() const { return m_mix; };
void setDecoder(TeletextPageDecode *decoder);
void renderPage(bool force=false);
bool showControlCodes() const { return m_showControlCodes; };
public slots:
void colourChanged(int index);
void setReveal(bool reveal);
void setMix(bool mix);
void setShowControlCodes(bool showControlCodes);
signals:
void flashChanged(int newFlashHz);
protected:
TeletextFontBitmap m_fontBitmap;
QImage* m_pageImage[6];
unsigned char m_controlCodeCache[25][40];
bool m_reveal, m_mix, m_showControlCodes;
int m_flashBuffersHz;
int m_flashingRow[25];
private:
inline void drawFromBitmap(QPainter &, int, int, const QImage, TeletextPageDecode::CharacterFragment);
inline void drawFromFontBitmap(QPainter &painter, int r, int c, unsigned char characterCode, int characterSet, TeletextPageDecode::CharacterFragment characterFragment);
inline void drawCharacter(QPainter &painter, int r, int c, unsigned char characterCode, int characterSet, int characterDiacritical, TeletextPageDecode::CharacterFragment characterFragment);
inline void drawBoldOrItalicCharacter(QPainter &painter, int r, int c, unsigned char characterCode, int characterSet, TeletextPageDecode::CharacterFragment characterFragment);
void renderRow(int r, int ph, bool force=false);
void setRowFlashStatus(int r, int rowFlashHz);
QColor m_foregroundQColor, m_backgroundQColor;
TeletextPageDecode *m_decoder;
};
#endif

View File

@@ -0,0 +1,5 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>fontimages/teletextfont.png</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,316 @@
/*
* Copyright (C) 2020-2024 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 "x26triplets.h"
X26Triplet::X26Triplet(int address, int mode, int data)
{
m_address = address;
m_mode = mode;
m_data = data;
}
void X26Triplet::setAddress(int address)
{
m_address = address;
}
void X26Triplet::setMode(int mode)
{
m_mode = mode;
}
void X26Triplet::setData(int data)
{
m_data = data;
}
void X26Triplet::setAddressRow(int addressRow)
{
m_address = (addressRow == 24) ? 40 : addressRow+40;
}
void X26Triplet::setAddressColumn(int addressColumn)
{
m_address = addressColumn;
}
void X26Triplet::setObjectLocalDesignationCode(int i)
{
m_address = (m_address & 0x38) | (i >> 3);
m_data = (m_data & 0x0f) | ((i & 0x07) << 4);
}
void X26Triplet::setObjectLocalTripletNumber(int i)
{
m_data = (m_data & 0x70) | i;
}
void X26Triplet::setObjectLocalIndex(int i)
{
m_address = (m_address & 0x38) + (i >= 104); // Set bit 0 of address if triplet >= 8
m_data = (((i / 13) & 0x07) << 4) | (i % 13);
}
void X26TripletList::updateInternalData()
{
ActivePosition activePosition;
X26Triplet *triplet;
m_objects[0].clear();
m_objects[1].clear();
m_objects[2].clear();
// Check for errors, and fill in where the Active Position goes for Level 2.5 and above
for (int i=0; i < m_list.size(); i++) {
triplet = &m_list[i];
triplet->m_error = X26Triplet::NoError;
triplet->m_reservedMode = false;
triplet->m_reservedData = false;
if (triplet->isRowTriplet()) {
switch (triplet->modeExt()) {
case 0x00: // Full screen colour
if (activePosition.isDeployed())
// TODO more specific error needed
triplet->m_error = X26Triplet::ActivePositionMovedUp;
if (triplet->m_data & 0x60)
triplet->m_reservedData = true;
break;
case 0x01: // Full row colour
if (!activePosition.setRow(triplet->addressRow()))
triplet->m_error = X26Triplet::ActivePositionMovedUp;
if ((triplet->m_data & 0x60) != 0x00 && (triplet->m_data & 0x60) != 0x60)
triplet->m_reservedData = true;
break;
case 0x04: // Set Active Position;
if (!activePosition.setRow(triplet->addressRow()))
triplet->m_error = X26Triplet::ActivePositionMovedUp;
else if (triplet->data() >= 40)
// FIXME data column highlighted?
triplet->m_reservedData = true;
else if (!activePosition.setColumn(triplet->data()))
triplet->m_error = X26Triplet::ActivePositionMovedLeft;
break;
case 0x07: // Address row 0
if (triplet->m_address != 63)
// FIXME data column highlighted?
triplet->m_reservedData = true;
else if (activePosition.isDeployed())
triplet->m_error = X26Triplet::ActivePositionMovedUp;
else {
activePosition.setRow(0);
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: // Invoke Active Object
case 0x12: // Invoke Adaptive Object
case 0x13: // Invoke Passive Object
if (triplet->objectSource() == X26Triplet::LocalObject) {
if (triplet->objectLocalTripletNumber() > 12 ||
triplet->objectLocalIndex() > (m_list.size()-1) ||
m_list.at(triplet->objectLocalIndex()).modeExt() < 0x15 ||
m_list.at(triplet->objectLocalIndex()).modeExt() > 0x17)
triplet->m_error = X26Triplet::InvokePointerInvalid;
else if ((triplet->modeExt() | 0x04) != m_list.at(triplet->objectLocalIndex()).modeExt())
triplet->m_error = X26Triplet::InvokeTypeMismatch;
}
break;
case 0x15: // Define Active Object
case 0x16: // Define Adaptive Object
case 0x17: // Define Passive Object
m_objects[triplet->modeExt() - 0x15].append(i);
activePosition.reset();
// Make sure data field holds correct place of triplet
// otherwise the object won't appear
triplet->setObjectLocalIndex(i);
break;
case 0x18: // DRCS mode
if ((triplet->m_data & 0x30) == 0x00)
triplet->m_reservedData = true;
case 0x1f: // Termination marker
case 0x08: // PDC country of origin & programme source
case 0x09: // PDC month & day
case 0x0a: // PDC cursor row & announced start hour
case 0x0b: // PDC cursor row & announced finish hour
case 0x0c: // PDC cursor row & local time offset
case 0x0d: // PDC series ID & series code
break;
default:
triplet->m_reservedMode = true;
};
// Column triplet: all triplets modes except PDC and reserved move the Active Position
} else if (triplet->modeExt() == 0x24 || triplet->modeExt() == 0x25 || triplet->modeExt() == 0x2a)
triplet->m_reservedMode = true;
else if (triplet->modeExt() != 0x26 && !activePosition.setColumn(triplet->addressColumn()))
triplet->m_error = X26Triplet::ActivePositionMovedLeft;
else
switch (triplet->modeExt()) {
case 0x20: // Foreground colour
case 0x23: // Background colour
if (triplet->m_data & 0x60)
triplet->m_reservedData = true;
break;
case 0x27: // Additional flash functions
if (triplet->m_data >= 0x18)
triplet->m_reservedData = true;
break;
case 0x28: // Modified G0 and G2 character set
if (triplet->m_data > 0x26)
switch (triplet->m_data) {
case 0x36:
case 0x37:
case 0x40:
case 0x44:
case 0x47:
case 0x55:
case 0x57:
break;
default:
triplet->m_reservedData = true;
}
break;
case 0x2d: // DRCS character
if ((triplet->m_data & 0x3f) >= 48)
// Should really be an error?
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: // G2 character
if (triplet->m_data < 0x20)
triplet->m_reservedData = true;
break;
default:
if (triplet->modeExt() >= 0x30 && triplet->modeExt() <= 0x3f && triplet->m_data < 0x20)
// G0 diacritical mark
triplet->m_reservedData = true;
}
triplet->m_activePositionRow = activePosition.row();
triplet->m_activePositionColumn = activePosition.column();
}
// Now work out where the Active Position goes on a Level 1.5 decoder
activePosition.reset();
for (int i=0; i < m_list.size(); i++) {
triplet = &m_list[i];
if (triplet->modeExt() == 0x1f) // Termination marker
break;
switch (triplet->modeExt()) {
case 0x04: // Set Active Position;
activePosition.setRow(triplet->addressRow());
break;
case 0x07: // Address row 0
if (triplet->m_address == 63 && activePosition.setRow(0))
activePosition.setColumn(8);
break;
case 0x22: // G3 mosaic character at level 1.5
case 0x2f: // G2 character
activePosition.setColumn(triplet->addressColumn());
break;
default:
if (triplet->modeExt() >= 0x30 && triplet->modeExt() <= 0x3f)
// G0 diacritical mark
activePosition.setColumn(triplet->addressColumn());
}
triplet->m_activePositionRow1p5 = activePosition.row();
triplet->m_activePositionColumn1p5 = activePosition.column();
}
}
void X26TripletList::append(const X26Triplet &value)
{
m_list.append(value);
updateInternalData();
}
void X26TripletList::insert(int i, const X26Triplet &value)
{
m_list.insert(i, value);
updateInternalData();
}
void X26TripletList::removeAt(int i)
{
m_list.removeAt(i);
if (m_list.size() != 0 && i < m_list.size())
updateInternalData();
}
void X26TripletList::replace(int i, const X26Triplet &value)
{
m_list.replace(i, value);
updateInternalData();
}
X26TripletList::ActivePosition::ActivePosition()
{
m_row = m_column = -1;
}
void X26TripletList::ActivePosition::reset()
{
m_row = m_column = -1;
}
bool X26TripletList::ActivePosition::setRow(int row)
{
if (row < m_row)
return false;
if (row > m_row) {
m_row = row;
m_column = -1;
}
return true;
}
bool X26TripletList::ActivePosition::setColumn(int column)
{
if (column < m_column)
return false;
if (m_row == -1 and column >= 0)
m_row = 0;
m_column = column;
return true;
}
/*
bool X26TripletList::ActivePosition::setRowAndColumn(int newRow, int newColumn)
{
if (!setRow(newRow))
return false;
return setColumn(newColumn);
}
*/

View File

@@ -0,0 +1,126 @@
/*
* Copyright (C) 2020-2024 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 X26TRIPLETS_H
#define X26TRIPLETS_H
#include <QList>
class X26Triplet
{
public:
// 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(const X26Triplet &other);
// X26Triplet &operator=(const X26Triplet &other);
X26Triplet(int address, int mode, int data);
int address() const { return m_address; }
int mode() const { return m_mode; }
int modeExt() const { return (m_address >= 40) ? m_mode : (m_mode | 0x20); }
int data() const { return m_data; }
int addressRow() const { return (m_address == 40) ? 24 :m_address-40; }
int addressColumn() const { return (m_address); }
bool isRowTriplet() const { return (m_address >= 40); }
void setAddress(int address);
void setMode(int mode);
void setData(int data);
void setAddressRow(int addressRow);
void setAddressColumn(int addressColumn);
int objectSource() const { return (m_address & 0x18) >> 3; }
int objectLocalDesignationCode() const { return (((m_address & 0x01) << 3) | (m_data >> 4)); }
int objectLocalTripletNumber() const { return m_data & 0x0f; }
int objectLocalIndex() const { return objectLocalDesignationCode() * 13 + objectLocalTripletNumber(); }
void setObjectLocalDesignationCode(int i);
void setObjectLocalTripletNumber(int i);
void setObjectLocalIndex(int i);
int activePositionRow() const { return m_activePositionRow; }
int activePositionColumn() const { return m_activePositionColumn; }
int activePositionRow1p5() const { return m_activePositionRow1p5; }
int activePositionColumn1p5() const { return m_activePositionColumn1p5; }
X26TripletError error() const { return m_error; }
bool reservedMode() const { return m_reservedMode; }
bool reservedData() const { return m_reservedData; }
friend class X26TripletList;
private:
// Only these variables are manipulated by the X26Triplet class
int m_address, m_mode, m_data;
// and the following are filled in by X26TripletList::updateInternalData()
int m_activePositionRow = -1;
int m_activePositionColumn = -1;
int m_activePositionRow1p5 = -1;
int m_activePositionColumn1p5 = -1;
X26TripletError m_error = NoError;
bool m_reservedMode = false;
bool m_reservedData = false;
};
class X26TripletList
{
public:
void append(const X26Triplet &value);
void insert(int i, const X26Triplet &value);
void removeAt(int i);
void replace(int i, const X26Triplet &value);
void removeLast() { m_list.removeLast(); }
const X26Triplet &at(int i) const { return m_list.at(i); }
bool isEmpty() const { return m_list.isEmpty(); }
void reserve(int alloc) { m_list.reserve(alloc); }
int size() const { return m_list.size(); }
const QList<int> &objects(int t) const { return m_objects[t]; };
private:
void updateInternalData();
QList<X26Triplet> m_list;
QList<int> m_objects[3];
class ActivePosition
{
public:
ActivePosition();
void reset();
// int row() const { return (m_row == -1) ? 0 : m_row; }
// int column() const { return (m_column == -1) ? 0 : m_column; }
int row() const { return m_row; }
int column() const { return 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;
};
};
#endif