From e1ba67484ff3c2bb52cee7bb5b39168a8e8b37c0 Mon Sep 17 00:00:00 2001 From: Gavin MacGregor Date: Mon, 9 Jun 2025 18:57:16 +0100 Subject: [PATCH] Implement DRCS rendering External pages with DRCS definitions can be loaded using the options in the "DRCS pages" submenu within the "View" menu. Two DRCS pages can be loaded, one for Global DRCS definitions and the other for Normal DRCS definitions. Level 2.5 mode 0 PTUs are fully supported. Partial support for Level 3.5 mode 1, 2 and 3 PTUs. DCLUTs defined in X/28/1 on the main page are not yet implemented; the characters currently appear in the default DCLUTs described in D.1.6 and D.2.2 of the ETSI spec. --- src/qteletextdecoder/decode.cpp | 193 +++++++++++++++++++++++++++++- src/qteletextdecoder/decode.h | 38 +++++- src/qteletextdecoder/drcspage.cpp | 82 +++++++++++++ src/qteletextdecoder/drcspage.h | 40 +++++++ src/qteletextdecoder/render.cpp | 26 +++- src/qteletextdecoder/render.h | 3 +- src/qteletextmaker/mainwindow.cpp | 146 ++++++++++++++++++++++ src/qteletextmaker/mainwindow.h | 16 ++- 8 files changed, 536 insertions(+), 8 deletions(-) create mode 100644 src/qteletextdecoder/drcspage.cpp create mode 100644 src/qteletextdecoder/drcspage.h diff --git a/src/qteletextdecoder/decode.cpp b/src/qteletextdecoder/decode.cpp index 5f50d23..c374320 100644 --- a/src/qteletextdecoder/decode.cpp +++ b/src/qteletextdecoder/decode.cpp @@ -19,9 +19,16 @@ #include "decode.h" +#include +#include #include #include +#include "drcspage.h" +#include "levelonepage.h" +#include "pagebase.h" + + TeletextPageDecode::Invocation::Invocation() { m_tripletList = nullptr; @@ -113,6 +120,7 @@ void TeletextPageDecode::Invocation::buildMap(int level) if (targetRow == 0) m_fullRowCLUTMap.insert(targetRow, triplet); break; + case 0x18: // DRCS mode case 0x20: // Foreground colour case 0x23: // Background colour case 0x27: // Additional flash functions @@ -128,6 +136,7 @@ void TeletextPageDecode::Invocation::buildMap(int level) case 0x22: // G3 character at Level 1.5 case 0x29: // G0 character case 0x2b: // G3 character at Level 2.5 + case 0x2d: // DRCS character case 0x2f: // G2 character m_characterMap.insert(qMakePair(targetRow, targetColumn), triplet); m_rightMostColumn.insert(targetRow, targetColumn); @@ -184,6 +193,9 @@ TeletextPageDecode::TeletextPageDecode() m_fullRowQColor[r].setRgb(0, 0, 0); } m_leftSidePanelColumns = m_rightSidePanelColumns = 0; + + m_drcsPage[GlobalDRCSPage] = nullptr; + m_drcsPage[NormalDRCSPage] = nullptr; } TeletextPageDecode::~TeletextPageDecode() @@ -203,6 +215,28 @@ void TeletextPageDecode::setTeletextPage(LevelOnePage *newCurrentPage) updateSidePanels(); } +void TeletextPageDecode::setDRCSPage(DRCSPageType pageType, QList *pages) +{ + m_drcsPage[pageType] = pages; + + bool refreshRequired = false; + + for (int r=0; r<25; r++) + for (int c=0; c<72; c++) + if (m_cell[r][c].character.drcsSource != NoDRCS) { + m_refresh[r][c] = true; + refreshRequired = true; + } + + if (refreshRequired) + decodePage(); +} + +void TeletextPageDecode::clearDRCSPage(DRCSPageType pageType) +{ + setDRCSPage(pageType, nullptr); +} + void TeletextPageDecode::setLevel(int level) { if (level == m_level) @@ -218,6 +252,116 @@ void TeletextPageDecode::setLevel(int level) decodePage(); } +QImage TeletextPageDecode::drcsImage(DRCSSource pageType, int subTable, int chr, bool flashPhOn) +{ + if (pageType == NoDRCS) + return QImage(); + + // Check if page is loaded and if the subpage exists + const QList* drcsPage = m_drcsPage[pageType-1]; + if (drcsPage == nullptr || subTable >= drcsPage->size()) + return QImage(); + + // Level 2.5: only and always mode 0 (12x10x1) and doesn't use X/28/3 + // Level 3.5: if X/28/3 is absent, drcsMode below returns mode 0 + if (m_level == 2 || drcsPage->at(subTable).drcsMode(chr) == 0) { + uchar rawData[20]; + + if (!drcsPage->at(subTable).ptu(chr, rawData)) + return QImage(); + + QImage result = QImage(rawData, 12, 10, 2, QImage::Format_Mono); + return result.copy(); + } + + // Level 3.5: obey X/28/3 "subsequent PTU" and "no data" values, ignore reserved values + const int drcsMode = drcsPage->at(subTable).drcsMode(chr); + if (drcsMode > 3) + return QImage(); + + uchar rawData[120]; + + if (drcsMode != 3) { + // mode 1 (12x10x2) or mode 2 (12x10x4) + // Each complete bitplane stored sequentially across multiple PTUs + + uchar bitplaneArr[4][20] = { }; + + // Get the PTUs for each bitplane + drcsPage->at(subTable).ptu(chr, bitplaneArr[0]); + if (chr < 47) + drcsPage->at(subTable).ptu(chr+1, bitplaneArr[1]); + if (drcsMode == 2) { + if (chr < 46) + drcsPage->at(subTable).ptu(chr+2, bitplaneArr[2]); + if (chr < 45) + drcsPage->at(subTable).ptu(chr+3, bitplaneArr[3]); + } + + // Now assemble the bitplanes into byte-per-pixel data + for (int x=0; x<12; x++) + for (int y=0; y<10; y++) { + const int scanByte = y*2 + (x > 7); + const int scanBit = 7 - x%8; + + rawData[x + y*12] = bitplaneArr[0][scanByte] >> scanBit & 1; + rawData[x + y*12] |= (bitplaneArr[1][scanByte] >> scanBit & 1) << 1; + if (drcsMode == 2) { + rawData[x + y*12] |= (bitplaneArr[2][scanByte] >> scanBit & 1) << 2; + rawData[x + y*12] |= (bitplaneArr[3][scanByte] >> scanBit & 1) << 3; + } + } + } else { + // mode 3 (6x5x4) + // Interleaved: First row of six pixels is stored four times sequentially, one for + // each bitplane, then second row of pixels four times, and so on + const int pktNo = (chr+2)/2; + + if (!drcsPage->at(subTable).packetExists(pktNo)) + return QImage(); + + QByteArray pkt; + + if (chr % 2 == 0) + pkt = drcsPage->at(subTable).packet(pktNo).first(20); + else + pkt = drcsPage->at(subTable).packet(pktNo).last(20); + + for (int x=0; x<6; x++) + for (int y=0; y<5; y++) { + const int scanByte = y * 4; + const int scanBit = 5 - x; + uchar pixel; + + pixel = pkt.at(scanByte) >> scanBit & 1; + pixel |= (pkt.at(scanByte+1) >> scanBit & 1) << 1; + pixel |= (pkt.at(scanByte+2) >> scanBit & 1) << 2; + pixel |= (pkt.at(scanByte+3) >> scanBit & 1) << 3; + + rawData[x*2 + y*24 ] = pixel; + rawData[x*2+1 + y*24 ] = pixel; + rawData[x*2 + y*24+12] = pixel; + rawData[x*2+1 + y*24+12] = pixel; + } + } + + QImage result = QImage(rawData, 12, 10, 12, QImage::Format_Indexed8); + + // Now put in the colours + // TODO read colours from X/28/1, for now we put in the default colours + for (int i=0; i<16; i++) { + if (flashPhOn) + result.setColor(i, m_levelOnePage->CLUTtoQColor(i).rgb()); + else + result.setColor(i, m_levelOnePage->CLUTtoQColor(i ^ 8).rgb()); + + if (i == 3 && drcsMode == 1) + break; + } + + return result.copy(); +} + void TeletextPageDecode::updateSidePanels() { int oldLeftSidePanelColumns = m_leftSidePanelColumns; @@ -315,7 +459,8 @@ TeletextPageDecode::textCharacter TeletextPageDecode::characterFromTriplets(cons for (int a=triplets.size()-1; a>=0; a--) { const X26Triplet triplet = triplets.at(a); - if (triplet.data() < 0x20) + // Data values below 0x20 are reserved, except for DRCS character + if (triplet.data() < 0x20 && triplet.modeExt() != 0x2d) continue; const unsigned char charCode = triplet.data(); @@ -353,6 +498,9 @@ TeletextPageDecode::textCharacter TeletextPageDecode::characterFromTriplets(cons case 0x2b: // G3 character at Level 2.5 result = { charCode, 26, 0 }; break; + case 0x2d: // DRCS character + result.drcsSource = (charCode & 0x40) == 0x40 ? NormalDRCS : GlobalDRCS; + result.drcsChar = charCode & 0x3f; } } @@ -631,6 +779,7 @@ void TeletextPageDecode::decodeRow(int r) bool applyAdapt = false; + drcsMode *drcsModePtr; // Adaptive Invocation that is applying an attribute // If we're not tracking an Adaptive Invocation yet, start tracking this one // Otherwise check if this Invocation is the the same one as we are tracking @@ -645,6 +794,16 @@ void TeletextPageDecode::decodeRow(int r) } switch (triplet.modeExt()) { + case 0x18: // DRCS mode + drcsModePtr = (triplet.data() & 0x40) == 0x40 ? &painter->nDrcs : &painter->gDrcs; + if ((triplet.data() & 0x30) != 0x00) { + drcsModePtr->level2p5 = triplet.data() & 0x10; + drcsModePtr->level3p5 = triplet.data() & 0x20; + // "used" is never set to true on Level 3.5, to allow all 16 sub-tables + if (!drcsModePtr->used) + drcsModePtr->subTable = triplet.data() & 0x0f; + } + break; case 0x20: // Foreground colour if (applyAdapt) adapForeground = true; @@ -733,6 +892,7 @@ void TeletextPageDecode::decodeRow(int r) if (c < 40 && m_rowHeight[r] != BottomHalf) { m_level1ActivePainter.result.character.diacritical = 0; + m_level1ActivePainter.result.character.drcsSource = NoDRCS; if (m_levelOnePage->character(r, c) >= 0x20) { m_level1ActivePainter.result.character.code = m_levelOnePage->character(r, c); if (m_cellLevel1MosaicChar[r][c]) { @@ -773,7 +933,36 @@ void TeletextPageDecode::decodeRow(int r) for (int i=0; inDrcs : &painter->gDrcs; + + if ((m_level == 2 && drcsModePtr->level2p5) || (m_level == 3 && drcsModePtr->level3p5)) { + // "code" is zero if an X/26 character is NOT invoked in the same cell + if (result.code == 0x00) + result.code = 0x20; + result.drcsSubTable = drcsModePtr->subTable; + if (m_level < 3) + drcsModePtr->used = true; + } else + // DRCS character not required at the current level + result.drcsSource = NoDRCS; + } + + // If the DRCS character in question is not downloaded, scrap all that hard work + // looking it up. + // Ideally we'd leave it in case somebody wants to find which character was meant + // to be invoked, but things like underlying Level 1 characters still needing to + // appear when the DRCS characters are not (yet) downloaded are too complex to + // figure out with this too complex decoder. + if (result.drcsSource) { + const QList* drcsPage = m_drcsPage[result.drcsSource-1]; + if (drcsPage == nullptr || result.drcsSubTable >= drcsPage->size() || !drcsPage->at(result.drcsSubTable).ptu(result.drcsChar, nullptr)) { + result.drcsSource = NoDRCS; + result.code = 0x00; + } + } if (t == 0 && result.code == 0x00) continue; diff --git a/src/qteletextdecoder/decode.h b/src/qteletextdecoder/decode.h index 9db230f..5360d08 100644 --- a/src/qteletextdecoder/decode.h +++ b/src/qteletextdecoder/decode.h @@ -20,11 +20,14 @@ #ifndef DECODE_H #define DECODE_H +#include #include #include #include +#include "drcspage.h" #include "levelonepage.h" +#include "pagebase.h" class TeletextPageDecode : public QObject { @@ -32,6 +35,9 @@ class TeletextPageDecode : public QObject public: enum CharacterFragment { NormalSize, DoubleHeightTopHalf, DoubleHeightBottomHalf, DoubleWidthLeftHalf, DoubleWidthRightHalf, DoubleSizeTopLeftQuarter, DoubleSizeTopRightQuarter, DoubleSizeBottomLeftQuarter, DoubleSizeBottomRightQuarter }; + enum DRCSPageType { NormalDRCSPage, GlobalDRCSPage }; +// enum ObjectPageType { NormalPOPage = 2, GlobalPOPage }; + enum DRCSSource { NoDRCS, NormalDRCS, GlobalDRCS }; enum RowHeight { NormalHeight, TopHalf, BottomHalf }; TeletextPageDecode(); @@ -42,6 +48,9 @@ public: void decodePage(); LevelOnePage *teletextPage() const { return m_levelOnePage; }; void setTeletextPage(LevelOnePage *newCurrentPage); + QList *drcsPage(DRCSPageType pageType) const { return m_drcsPage[pageType]; }; + void setDRCSPage(DRCSPageType pageType, QList *pages); + void clearDRCSPage(DRCSPageType pageType); void updateSidePanels(); unsigned char cellCharacterCode(int r, int c) const { return m_cell[r][c].character.code; }; @@ -49,6 +58,13 @@ public: 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; }; + + DRCSSource cellDrcsSource(int r, int c) const { return m_cell[r][c].character.drcsSource; }; + int cellDrcsSubTable(int r, int c) const { return m_cell[r][c].character.drcsSubTable; }; + int cellDrcsCharacter(int r, int c) const { return m_cell[r][c].character.drcsChar; }; + + QImage drcsImage(DRCSSource pageType, int subTable, int chr, bool flashPhOn = true); + 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); @@ -103,13 +119,19 @@ private: unsigned char code=0x20; int set=0; int diacritical=0; + DRCSSource drcsSource=NoDRCS; + int drcsSubTable=0; + int drcsChar=0; }; friend inline bool operator!=(const textCharacter &lhs, const textCharacter &rhs) { - return lhs.code != rhs.code || - lhs.set != rhs.set || - lhs.diacritical != rhs.diacritical; + return lhs.code != rhs.code || + lhs.set != rhs.set || + lhs.diacritical != rhs.diacritical || + lhs.drcsSource != rhs.drcsSource || + lhs.drcsSubTable != rhs.drcsSubTable || + lhs.drcsChar != rhs.drcsChar; } struct flashFunctions { @@ -179,12 +201,21 @@ private: lhs.fragment != rhs.fragment; } + struct drcsMode { + bool level2p5=true; + bool level3p5=true; + bool used=false; + int subTable=0; + }; + struct textPainter { textAttributes attribute; textCell result; textCell rightHalfCell; textCell bottomHalfCell[72]; + drcsMode gDrcs, nDrcs; + int styleSpreadRows=0; int setProportionalRows[72], clearProportionalRows[72]; int setBoldRows[72], clearBoldRows[72]; @@ -267,6 +298,7 @@ private: bool m_cellLevel1MosaicChar[25][40]; int m_cellLevel1CharSet[25][40]; LevelOnePage* m_levelOnePage; + QList* m_drcsPage[2]; int m_fullRowColour[25]; QColor m_fullRowQColor[25]; QList m_invocations[3]; diff --git a/src/qteletextdecoder/drcspage.cpp b/src/qteletextdecoder/drcspage.cpp new file mode 100644 index 0000000..cabe518 --- /dev/null +++ b/src/qteletextdecoder/drcspage.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2020-2025 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 . + */ + +#include + +#include "drcspage.h" + +DRCSPage::DRCSPage(const PageBase &other) +{ + for (int y=0; y<26; y++) + if (other.packetExists(y)) + setPacket(y, other.packet(y)); + + for (int y=26; y<29; y++) + for (int d=0; d<16; d++) + if (other.packetExists(y, d)) + setPacket(y, d, other.packet(y, d)); + + for (int b=PageBase::C4ErasePage; b<=PageBase::C14NOS; b++) + setControlBit(b, other.controlBit(b)); +} + +int DRCSPage::drcsMode(int c) const +{ + if (!packetExists(28, 3)) + return 0; + + const QByteArray pkt = packet(28, 3); + + // Some tricky bit juggling to extract 4 bits from part of a 6-bit triplet + switch (c % 3) { + case 0: + return pkt.at(c/3*2 + 4) & 0xf; + case 1: + return ((pkt.at((c-1)/3*2 + 4) & 0x30) >> 4) | ((pkt.at((c-1)/3*2 + 5) & 0x3) << 2); + case 2: + return pkt.at(((c-2)/3*2 + 5) & 0x3f) >> 2; + } + + return 0; // Won't get here; used to suppress a compiler warning +} + +bool DRCSPage::ptu(int c, uchar *data) const +{ + const int pktNo = (c+2)/2; + + if (!packetExists(pktNo)) + return false; + + const int start = c%2 * 20; + + // FIXME should we check all 20 D-bytes for SPACE instead of just the first D-byte? + if (packet(pktNo).at(start) < 0x40) + return false; + + if (data != nullptr) { + const int end = start + 20; + + for (int i=start, j=0; i> 4); + data[j+1] = (packet(pktNo).at(i+1) & 0x0f) << 4; + } + } + + return true; +} diff --git a/src/qteletextdecoder/drcspage.h b/src/qteletextdecoder/drcspage.h new file mode 100644 index 0000000..738981d --- /dev/null +++ b/src/qteletextdecoder/drcspage.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020-2025 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 . + */ + +#ifndef DRCSPAGE_H +#define DRCSPAGE_H + +#include + +#include "pagebase.h" + +class DRCSPage : public PageBase +{ +public: + DRCSPage(const PageBase &other); + + // TODO PFNormalPOP as well? + PageFunctionEnum pageFunction() const { return PFGlobalPOP; } + PacketCodingEnum packetCoding() const override { return Coding7bit; } + + int drcsMode(int c) const; + bool ptu(int c, uchar *data) const; +}; + +#endif diff --git a/src/qteletextdecoder/render.cpp b/src/qteletextdecoder/render.cpp index 2ada5db..e2e43d9 100644 --- a/src/qteletextdecoder/render.cpp +++ b/src/qteletextdecoder/render.cpp @@ -191,6 +191,23 @@ inline void TeletextPageRender::drawCharacter(QPainter &painter, int r, int c, u } } +inline bool TeletextPageRender::drawDRCSCharacter(QPainter &painter, int r, int c, TeletextPageDecode::DRCSSource drcsSource, int drcsSubTable, int drcsChar, TeletextPageDecode::CharacterFragment characterFragment, bool flashPhOn) +{ + QImage drcsImage = m_decoder->drcsImage(drcsSource, drcsSubTable, drcsChar, flashPhOn); + + if (drcsImage.isNull()) + return false; + + if (drcsImage.format() == QImage::Format_Mono) + // mode 0 (12x10x1) returned here has no colours of its own + // so apply the foreground and background colours of the cell it appears in + drcsImage.setColorTable(QVector{m_backgroundQColor.rgba(), m_foregroundQColor.rgba()}); + + drawFromBitmap(painter, r, c, drcsImage, characterFragment); + + return true; +} + 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); @@ -306,7 +323,7 @@ void TeletextPageRender::renderRow(int r, int ph, bool force) drawCharacter(painter, r, c, 0x00, 0, 0, m_decoder->cellCharacterFragment(r, c)); else if (concealed) drawCharacter(painter, r, c, 0x20, 0, 0, m_decoder->cellCharacterFragment(r, c)); - else + else if (m_decoder->cellDrcsSource(r, c) == TeletextPageDecode::NoDRCS || !drawDRCSCharacter(painter, r, c, m_decoder->cellDrcsSource(r, c), m_decoder->cellDrcsSubTable(r, c), m_decoder->cellDrcsCharacter(r, c), m_decoder->cellCharacterFragment(r, c), flashPhOn)) drawCharacter(painter, r, c, m_decoder->cellCharacterCode(r, c), m_decoder->cellCharacterSet(r, c), m_decoder->cellCharacterDiacritical(r, c), m_decoder->cellCharacterFragment(r, c)); if (m_showControlCodes && c < 40 && m_decoder->teletextPage()->character(r, c) < 0x20) { @@ -422,6 +439,13 @@ void TeletextPageRender::colourChanged(int index) 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); } + + if (m_decoder->level() == 3) + // TODO don't refresh mode 0 DRCS + for (int r=0; r<25; r++) + for (int c=0; c<72; c++) + if (m_decoder->cellDrcsSource(r, c) != TeletextPageDecode::NoDRCS) + m_decoder->setRefresh(r, c, true); } void TeletextPageRender::setReveal(bool reveal) diff --git a/src/qteletextdecoder/render.h b/src/qteletextdecoder/render.h index 84b3601..6402d1a 100644 --- a/src/qteletextdecoder/render.h +++ b/src/qteletextdecoder/render.h @@ -79,9 +79,10 @@ protected: int m_flashingRow[25]; private: - inline void drawFromBitmap(QPainter &, int, int, const QImage, TeletextPageDecode::CharacterFragment); + inline void drawFromBitmap(QPainter &painter, int r, int c, const QImage image, TeletextPageDecode::CharacterFragment 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 bool drawDRCSCharacter(QPainter &painter, int r, int c, TeletextPageDecode::DRCSSource drcsSource, int drcsSubTable, int drcsChar, TeletextPageDecode::CharacterFragment characterFragment, bool flashPhOn = true); 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); diff --git a/src/qteletextmaker/mainwindow.cpp b/src/qteletextmaker/mainwindow.cpp index 9a9625c..1d5e103 100644 --- a/src/qteletextmaker/mainwindow.cpp +++ b/src/qteletextmaker/mainwindow.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,7 @@ #include "mainwindow.h" +#include "drcspage.h" #include "hashformats.h" #include "levelonecommands.h" #include "loadformats.h" @@ -378,6 +380,8 @@ void MainWindow::init() connect(m_textScene, &LevelOneScene::mouseZoomIn, this, &MainWindow::zoomIn); connect(m_textScene, &LevelOneScene::mouseZoomOut, this, &MainWindow::zoomOut); + connect(&m_fileWatcher, &QFileSystemWatcher::fileChanged, this, &MainWindow::updateWatchedFile); + QShortcut *blockShortCut = new QShortcut(QKeySequence(Qt::Key_Escape, Qt::Key_J), m_textView); connect(blockShortCut, &QShortcut::activated, [=]() { m_textWidget->setCharacter(0x7f); }); @@ -678,6 +682,35 @@ void MainWindow::createActions() zoomResetAct->setStatusTip(tr("Reset zoom level")); connect(zoomResetAct, &QAction::triggered, this, &MainWindow::zoomReset); + viewMenu->addSeparator(); + + QMenu *drcsSubMenu = viewMenu->addMenu(tr("DRCS pages")); + m_drcsSeparator[1] = drcsSubMenu->addSeparator(); + m_drcsSeparator[1]->setText("Global DRCS"); + QAction *gDrcsFileSelect = drcsSubMenu->addAction(tr("Load file...")); + gDrcsFileSelect->setStatusTip(tr("Load a file to use for Global DRCS definitions")); + connect(gDrcsFileSelect, &QAction::triggered, [=]() { loadDRCSFile(1); }); + m_drcsClear[1] = drcsSubMenu->addAction(tr("Clear")); + m_drcsClear[1]->setStatusTip(tr("Clear Global DRCS definitions")); + m_drcsClear[1]->setEnabled(false); + connect(m_drcsClear[1], &QAction::triggered, [=]() { clearDRCSFile(1); }); + + m_drcsSeparator[0] = drcsSubMenu->addSeparator(); + m_drcsSeparator[0]->setText("Normal DRCS"); + QAction *nDrcsFileSelect = drcsSubMenu->addAction(tr("Load file...")); + nDrcsFileSelect->setStatusTip(tr("Load a file to use for Normal DRCS definitions")); + connect(nDrcsFileSelect, &QAction::triggered, [=]() { loadDRCSFile(0); }); + m_drcsClear[0] = drcsSubMenu->addAction(tr("Clear")); + m_drcsClear[0]->setStatusTip(tr("Clear Normal DRCS definitions")); + m_drcsClear[0]->setEnabled(false); + connect(m_drcsClear[0], &QAction::triggered, [=]() { clearDRCSFile(0); }); + + drcsSubMenu->addSeparator(); + m_drcsSwap = drcsSubMenu->addAction(tr("Swap Global and Normal")); + m_drcsSwap->setStatusTip(tr("Swap the files used for Global and Normal DRCS definitions")); + m_drcsSwap->setEnabled(false); + connect(m_drcsSwap, &QAction::triggered, this, &MainWindow::swapDRCS); + QMenu *insertMenu = menuBar()->addMenu(tr("&Insert")); QMenu *alphaColourSubMenu = insertMenu->addMenu(tr("Alphanumeric colour")); @@ -900,6 +933,119 @@ void MainWindow::zoomReset() m_zoomSlider->setValue(2); } +void MainWindow::loadDRCSFile(int drcsType, QString fileName) +{ + const QString drcsTypeName = drcsType == 1 ? "Global DRCS" : "Normal DRCS"; + + const bool updatingWatched = !fileName.isEmpty(); + + if (!updatingWatched) + fileName = QFileDialog::getOpenFileName(this, tr("Select %1 file").arg(drcsTypeName), m_drcsFileName[drcsType], m_loadFormats.filters()); + + if (!fileName.isEmpty()) { + QFile file(fileName); + + LoadFormat *loadingFormat = m_loadFormats.findFormat(QFileInfo(fileName).suffix()); + if (loadingFormat == nullptr) { + if (updatingWatched) + clearDRCSFile(drcsType); + else + QMessageBox::warning(this, QApplication::applicationDisplayName(), tr("Cannot load file %1:\nUnknown file format or extension").arg(QDir::toNativeSeparators(fileName))); + + return; + } + + if (!file.open(QFile::ReadOnly)) { + if (updatingWatched) + clearDRCSFile(drcsType); + else + QMessageBox::warning(this, QApplication::applicationDisplayName(), tr("Cannot read file %1:\n%2.").arg(QDir::toNativeSeparators(fileName), file.errorString())); + + return; + } + + QList loadedPages; + + if (loadingFormat->load(&file, loadedPages, nullptr)) { + if (!m_drcsFileName[drcsType].isEmpty()) + m_fileWatcher.removePath(m_drcsFileName[drcsType]); + + m_textWidget->pageDecode()->clearDRCSPage((TeletextPageDecode::DRCSPageType)drcsType); + m_drcsPage[drcsType].clear(); + + for (int i=0; ipageDecode()->setDRCSPage((TeletextPageDecode::DRCSPageType)drcsType, &m_drcsPage[drcsType]); + m_textWidget->refreshPage(); + + m_fileWatcher.addPath(fileName); + m_drcsFileName[drcsType] = fileName; + m_drcsSeparator[drcsType]->setText(QString("%1: %2").arg(drcsTypeName).arg(QFileInfo(fileName).fileName())); + m_drcsClear[drcsType]->setEnabled(true); + m_drcsSwap->setEnabled(true); + } else { + if (updatingWatched) + clearDRCSFile(drcsType); + else + QMessageBox::warning(this, QApplication::applicationDisplayName(), tr("Cannot load file %1\n%2").arg(QDir::toNativeSeparators(fileName), loadingFormat->errorString())); + + return; + } + } +} + +void MainWindow::clearDRCSFile(int drcsType) +{ + m_fileWatcher.removePath(m_drcsFileName[drcsType]); + + m_textWidget->pageDecode()->clearDRCSPage((TeletextPageDecode::DRCSPageType)drcsType); + m_drcsPage[drcsType].clear(); + + m_textWidget->refreshPage(); + + m_drcsFileName[drcsType].clear(); + m_drcsSeparator[drcsType]->setText(drcsType == 1 ? "Global DRCS" : "Normal DRCS"); + m_drcsClear[drcsType]->setEnabled(false); + m_drcsSwap->setEnabled(m_drcsClear[0]->isEnabled() || m_drcsClear[1]->isEnabled()); +} + +void MainWindow::swapDRCS() +{ + m_drcsPage[0].swap(m_drcsPage[1]); + m_drcsFileName[0].swap(m_drcsFileName[1]); + + for (int i=0; i<2; i++) { + const QString drcsTypeName = i == 1 ? "Global DRCS" : "Normal DRCS"; + + if (m_drcsPage[i].isEmpty()) { + m_textWidget->pageDecode()->clearDRCSPage((TeletextPageDecode::DRCSPageType)i); + m_drcsSeparator[i]->setText(drcsTypeName); + } else { + m_textWidget->pageDecode()->setDRCSPage((TeletextPageDecode::DRCSPageType)i, &m_drcsPage[i]); + m_drcsSeparator[i]->setText(QString("%1: %2").arg(drcsTypeName).arg(QFileInfo(m_drcsFileName[i]).fileName())); + } + + m_drcsClear[i]->setEnabled(!m_drcsPage[i].isEmpty()); + } + + m_textWidget->refreshPage(); +} + +void MainWindow::updateWatchedFile(const QString &path) +{ + int drcsType; + + if (path == m_drcsFileName[1]) + drcsType = 1; + else if (path == m_drcsFileName[0]) + drcsType = 0; + else + return; + + loadDRCSFile(drcsType, path); +} + void MainWindow::toggleInsertMode() { m_textWidget->setInsertMode(!m_textWidget->insertMode()); diff --git a/src/qteletextmaker/mainwindow.h b/src/qteletextmaker/mainwindow.h index 03b227f..51d19a1 100644 --- a/src/qteletextmaker/mainwindow.h +++ b/src/qteletextmaker/mainwindow.h @@ -21,15 +21,18 @@ #define MAINWINDOW_H #include +#include #include #include #include #include #include +#include #include #include #include +#include "drcspage.h" #include "loadformats.h" #include "mainwidget.h" #include "pagecomposelinksdockwidget.h" @@ -89,6 +92,12 @@ private slots: void zoomSet(int viewZoom); void zoomReset(); + void loadDRCSFile(int drcsType, QString fileName = ""); + void clearDRCSFile(int drcsType); + void swapDRCS(); + + void updateWatchedFile(const QString &path); + void toggleInsertMode(); private: @@ -115,6 +124,10 @@ private: LevelOneScene *m_textScene; QGraphicsView *m_textView; + QList m_drcsPage[2]; + QString m_drcsFileName[2]; + QFileSystemWatcher m_fileWatcher; + int m_viewBorder, m_viewAspectRatio, m_viewZoom; bool m_viewSmoothTransform; PageOptionsDockWidget *m_pageOptionsDockWidget; @@ -128,10 +141,11 @@ private: QAction *m_recentFileSubMenuAct; QAction *m_exportAutoAct; QAction *m_deleteSubPageAction; + QAction *m_rowZeroAct; QAction *m_borderActs[3]; QAction *m_aspectRatioActs[4]; QAction *m_smoothTransformAction; - QAction *m_rowZeroAct; + QAction *m_drcsSeparator[2], *m_drcsClear[2], *m_drcsSwap; QLabel *m_subPageLabel, *m_cursorPositionLabel; QToolButton *m_previousSubPageButton, *m_nextSubPageButton;