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;