From d3267483715650fb05c203bae2e2a4ec59b49a64 Mon Sep 17 00:00:00 2001 From: Gavin MacGregor Date: Sun, 20 Jul 2025 15:32:13 +0100 Subject: [PATCH] Add DCLUT editing --- src/qteletextdecoder/levelonepage.cpp | 51 +++++++++ src/qteletextdecoder/levelonepage.h | 1 + src/qteletextmaker/dclutdockwidget.cpp | 153 +++++++++++++++++++++++++ src/qteletextmaker/dclutdockwidget.h | 52 +++++++++ src/qteletextmaker/document.h | 1 + src/qteletextmaker/mainwindow.cpp | 7 ++ src/qteletextmaker/mainwindow.h | 2 + src/qteletextmaker/x28commands.cpp | 31 +++++ src/qteletextmaker/x28commands.h | 13 +++ 9 files changed, 311 insertions(+) create mode 100644 src/qteletextmaker/dclutdockwidget.cpp create mode 100644 src/qteletextmaker/dclutdockwidget.h diff --git a/src/qteletextdecoder/levelonepage.cpp b/src/qteletextdecoder/levelonepage.cpp index 385f15d..0239d0f 100644 --- a/src/qteletextdecoder/levelonepage.cpp +++ b/src/qteletextdecoder/levelonepage.cpp @@ -418,6 +418,57 @@ int LevelOnePage::dCLUT(bool globalDrcs, int mode, int index) const return 0; // Won't get here; used to suppress a compiler warning } +void LevelOnePage::setDCLUT(bool globalDrcs, int mode, int index, int colour) +{ + const QByteArray defaultPkt = QByteArray("\x01\x00\x00\x00\x20\x20\x18\x00\x02\x22\x01\x08\x08\x06\x24\x22\x39\x20\x12\x2a\x05\x2b\x39\x1e\x20\x20\x18\x10\x0a\x26\x03\x0a\x29\x16\x2c\x26\x3b\x01\x00\x00", 40); + + if (!packetExists(28, 1)) + setPacket(28, 1, defaultPkt); + + if (mode == 1) { + if (!globalDrcs) + index += 4; + } else if (mode == 2 || mode == 3) + index += globalDrcs ? 8 : 24; + else + return; + + QByteArray pkt = packet(28, 1); + + // Some tricky bit juggling to set 5 bits within parts of a 6-bit triplet + const int l = index/6*5 + 4; + + switch (index % 6) { + case 0: + pkt[l] = pkt.at(l) & 0x20 | colour; + break; + case 1: + pkt[l+1] = (pkt.at(l+1) & 0x30) | (colour >> 1); + pkt[l] = (pkt.at(l) & 0x1f) | ((colour << 5) & 0x3f); + break; + case 2: + pkt[l+2] = (pkt.at(l+2) & 0x38) | (colour >> 2); + pkt[l+1] = (pkt.at(l+1) & 0x0f) | ((colour << 4) & 0x3f); + break; + case 3: + pkt[l+3] = (pkt.at(l+3) & 0x3c) | (colour >> 3); + pkt[l+2] = (pkt.at(l+2) & 0x07) | ((colour << 3) & 0x3f); + break; + case 4: + pkt[l+4] = (pkt.at(l+4) & 0x3e) | (colour >> 4); + pkt[l+3] = (pkt.at(l+3) & 0x03) | ((colour << 2) & 0x3f); + break; + case 5: + pkt[l+4] = (pkt.at(l+4) & 0x01) | (colour << 1); + break; + } + + if (pkt == defaultPkt) + clearPacket(28, 1); + else + setPacket(28, 1, pkt); +} + int LevelOnePage::levelRequired() const { // X/28/4 present i.e. CLUTs 0 or 1 redefined - Level 3.5 diff --git a/src/qteletextdecoder/levelonepage.h b/src/qteletextdecoder/levelonepage.h index 486a26c..7649acc 100644 --- a/src/qteletextdecoder/levelonepage.h +++ b/src/qteletextdecoder/levelonepage.h @@ -86,6 +86,7 @@ public: bool isPaletteDefault(int colour) const; bool isPaletteDefault(int fromColour, int toColour) const; int dCLUT(bool globalDrcs, int mode, int index) const; + void setDCLUT(bool globalDrcs, int mode, int index, int colour); int levelRequired() const; bool leftSidePanelDisplayed() const { return m_leftSidePanelDisplayed; } void setLeftSidePanelDisplayed(bool newLeftSidePanelDisplayed); diff --git a/src/qteletextmaker/dclutdockwidget.cpp b/src/qteletextmaker/dclutdockwidget.cpp new file mode 100644 index 0000000..5eaceb9 --- /dev/null +++ b/src/qteletextmaker/dclutdockwidget.cpp @@ -0,0 +1,153 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "dclutdockwidget.h" + +#include "mainwidget.h" +#include "x26menus.h" +#include "x28commands.h" + +DClutDockWidget::DClutDockWidget(TeletextWidget *parent): QDockWidget(parent) +{ + QVBoxLayout *dClutLayout = new QVBoxLayout; + QWidget *dClutWidget = new QWidget; + + m_parentMainWidget = parent; + + this->setObjectName("DClutWidget"); + this->setWindowTitle("Level 3.5 DCLUTs"); + + QStackedWidget *stackedWidget = new QStackedWidget; + QGridLayout *pageLayout[4]; + + for (int p=0; p<4; p++) { + pageLayout[p] = new QGridLayout; + + for (int i=0; i<16; i++) { + m_dClutButton[p][i] = new QPushButton; + m_dClutButton[p][i]->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + pageLayout[p]->addWidget(m_dClutButton[p][i], i/4, i%4); + + m_dClutMenu[p][i] = new TripletCLUTQMenu(false, this); + m_dClutButton[p][i]->setMenu(m_dClutMenu[p][i]); + + for (int c=0; c<32; c++) + connect(static_cast(m_dClutMenu[p][i])->action(c), &QAction::triggered, [=]() { m_parentMainWidget->document()->undoStack()->push(new SetDCLUTCommand(m_parentMainWidget->document(), (p % 2 == 0), p/2 + 1, i, c)); }); + + connect(m_dClutMenu[p][i], &QMenu::aboutToShow, [=]() { updateDClutMenu(p, i); }); + + if (i == 3 && p < 2) + break; + } + + QWidget *pageWidget = new QWidget; + pageWidget->setLayout(pageLayout[p]); + stackedWidget->addWidget(pageWidget); + } + + QComboBox *dClutPageSelect = new QComboBox; + dClutPageSelect->addItem(tr("Global DRCS mode 1")); + dClutPageSelect->addItem(tr("Normal DRCS mode 1")); + dClutPageSelect->addItem(tr("Global DRCS modes 2 & 3")); + dClutPageSelect->addItem(tr("Normal DRCS modes 2 & 3")); + dClutLayout->addWidget(dClutPageSelect); + + dClutLayout->addWidget(stackedWidget); + + dClutWidget->setLayout(dClutLayout); + this->setWidget(dClutWidget); + + connect(dClutPageSelect, &QComboBox::activated, stackedWidget, &QStackedWidget::setCurrentIndex); + + connect(m_parentMainWidget->document(), &TeletextDocument::dClutChanged, this, &DClutDockWidget::dClutChanged); + connect(m_parentMainWidget->document(), &TeletextDocument::colourChanged, this, &DClutDockWidget::colourChanged); +} + +void DClutDockWidget::updateDClutMenu(int p, int i) +{ + for (int c=0; c<32; c++) + static_cast(m_dClutMenu[p][i])->setColour(c, m_parentMainWidget->document()->currentSubPage()->CLUTtoQColor(c)); +} + +void DClutDockWidget::updateColourButton(int p, int i) +{ + const int dIndex = m_parentMainWidget->document()->currentSubPage()->dCLUT((p % 2 == 0), p/2 + 1, i); + m_dClutButton[p][i]->setText(QString("%1:%2").arg(dIndex / 8).arg(dIndex % 8)); + const QString colourString = QString("%1").arg(m_parentMainWidget->document()->currentSubPage()->CLUT(dIndex), 3, 16, QChar('0')); + + if (dIndex != 8) { + // FIXME duplicated in palettedockwidget.cpp + const int r = m_parentMainWidget->document()->currentSubPage()->CLUT(dIndex) >> 8; + const int g = (m_parentMainWidget->document()->currentSubPage()->CLUT(dIndex) >> 4) & 0xf; + const int b = m_parentMainWidget->document()->currentSubPage()->CLUT(dIndex) & 0xf; + // Set text itself to black or white so it can be seen over background colour - http://alienryderflex.com/hsp.html + const char blackOrWhite = (sqrt(r*r*0.299 + g*g*0.587 + b*b*0.114) > 7.647) ? '0' : 'f'; + + m_dClutButton[p][i]->setStyleSheet(QString("background-color: #%1; color: #%2%2%2; border: none").arg(colourString).arg(blackOrWhite));; + } else + m_dClutButton[p][i]->setStyleSheet("border: none"); +} + +void DClutDockWidget::updateAllColourButtons() +{ + for (int p=0; p<4; p++) + for (int i=0; i<16; i++) { + updateColourButton(p, i); + + if (i == 3 && p < 2) + break; + } +} + +void DClutDockWidget::dClutChanged(bool g, int m, int i) +{ + updateColourButton(!g + m*2-2, i); + + if (m_parentMainWidget->pageDecode()->level() == 3) + for (int r=0; r<25; r++) + for (int c=0; c<72; c++) + if (m_parentMainWidget->pageDecode()->cellDrcsSource(r, c) != TeletextPageDecode::NoDRCS) + m_parentMainWidget->pageDecode()->setRefresh(r, c, true); + + emit m_parentMainWidget->document()->contentsChanged(); +} + +void DClutDockWidget::colourChanged(int c) +{ + const QString searchString = QString("%1:%2").arg(c / 8).arg(c % 8); + + for (int p=0; p<4; p++) + for (int i=0; i<16; i++) { + if (m_dClutButton[p][i]->text() == searchString) + updateColourButton(p, i); + + if (i == 3 && p < 2) + break; + } +} diff --git a/src/qteletextmaker/dclutdockwidget.h b/src/qteletextmaker/dclutdockwidget.h new file mode 100644 index 0000000..2e26aca --- /dev/null +++ b/src/qteletextmaker/dclutdockwidget.h @@ -0,0 +1,52 @@ +/* + * 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 DCLUTDOCKWIDGET_H +#define DCLUTDOCKWIDGET_H + +#include +#include +#include +#include +#include + +#include "mainwidget.h" + +class DClutDockWidget : public QDockWidget +{ + Q_OBJECT + +public: + DClutDockWidget(TeletextWidget *parent); + void updateAllColourButtons(); + +public slots: + void updateColourButton(int p, int i); + void dClutChanged(bool g, int m, int i); + void colourChanged(int c); + +private: + void updateDClutMenu(int p, int i); + + TeletextWidget *m_parentMainWidget; + QPushButton *m_dClutButton[4][16]; + QMenu *m_dClutMenu[4][16]; +}; + +#endif diff --git a/src/qteletextmaker/document.h b/src/qteletextmaker/document.h index 1977f55..6b685cb 100644 --- a/src/qteletextmaker/document.h +++ b/src/qteletextmaker/document.h @@ -102,6 +102,7 @@ signals: void cursorMoved(); void selectionMoved(); void colourChanged(int i); + void dClutChanged(bool g, int m, int i); void pageOptionsChanged(); void aboutToChangeSubPage(); void subPageSelected(); diff --git a/src/qteletextmaker/mainwindow.cpp b/src/qteletextmaker/mainwindow.cpp index 72aaa44..d57d893 100644 --- a/src/qteletextmaker/mainwindow.cpp +++ b/src/qteletextmaker/mainwindow.cpp @@ -44,6 +44,7 @@ #include "mainwindow.h" +#include "dclutdockwidget.h" #include "drcspage.h" #include "hashformats.h" #include "levelonecommands.h" @@ -363,6 +364,8 @@ void MainWindow::init() addDockWidget(Qt::RightDockWidgetArea, m_paletteDockWidget); m_pageComposeLinksDockWidget = new PageComposeLinksDockWidget(m_textWidget); addDockWidget(Qt::RightDockWidgetArea, m_pageComposeLinksDockWidget); + m_dClutDockWidget = new DClutDockWidget(m_textWidget); + addDockWidget(Qt::RightDockWidgetArea, m_dClutDockWidget); m_textScene = new LevelOneScene(m_textWidget, this); @@ -843,6 +846,7 @@ void MainWindow::createActions() toolsMenu->addAction(m_x26DockWidget->toggleViewAction()); toolsMenu->addAction(m_pageEnhancementsDockWidget->toggleViewAction()); toolsMenu->addAction(m_paletteDockWidget->toggleViewAction()); + toolsMenu->addAction(m_dClutDockWidget->toggleViewAction()); toolsMenu->addAction(m_pageComposeLinksDockWidget->toggleViewAction()); //FIXME is this main menubar separator to put help menu towards the right? @@ -1189,6 +1193,8 @@ void MainWindow::readSettings() m_x26DockWidget->setFloating(true); m_paletteDockWidget->hide(); m_paletteDockWidget->setFloating(true); + m_dClutDockWidget->hide(); + m_dClutDockWidget->setFloating(true); m_pageComposeLinksDockWidget->hide(); m_pageComposeLinksDockWidget->setFloating(true); } else @@ -1614,4 +1620,5 @@ void MainWindow::updatePageWidgets() m_x26DockWidget->loadX26List(); m_paletteDockWidget->updateAllColourButtons(); m_pageComposeLinksDockWidget->updateWidgets(); + m_dClutDockWidget->updateAllColourButtons(); } diff --git a/src/qteletextmaker/mainwindow.h b/src/qteletextmaker/mainwindow.h index ea0f9c3..3ec12e8 100644 --- a/src/qteletextmaker/mainwindow.h +++ b/src/qteletextmaker/mainwindow.h @@ -33,6 +33,7 @@ #include #include +#include "dclutdockwidget.h" #include "drcspage.h" #include "loadformats.h" #include "mainwidget.h" @@ -140,6 +141,7 @@ private: X26DockWidget *m_x26DockWidget; PaletteDockWidget *m_paletteDockWidget; PageComposeLinksDockWidget *m_pageComposeLinksDockWidget; + DClutDockWidget *m_dClutDockWidget; QAction *m_recentFileActs[m_MaxRecentFiles]; QAction *m_recentFileSeparator; diff --git a/src/qteletextmaker/x28commands.cpp b/src/qteletextmaker/x28commands.cpp index af94a84..a413d4a 100644 --- a/src/qteletextmaker/x28commands.cpp +++ b/src/qteletextmaker/x28commands.cpp @@ -241,3 +241,34 @@ void ResetCLUTCommand::undo() emit m_teletextDocument->contentsChanged(); } + + +SetDCLUTCommand::SetDCLUTCommand(TeletextDocument *teletextDocument, bool globalDrcs, int mode, int index, int colour, QUndoCommand *parent) : X28Command(teletextDocument, parent) +{ + m_globalDrcs = globalDrcs; + m_mode = mode; + m_index = index; + m_oldColour = teletextDocument->currentSubPage()->dCLUT(globalDrcs, mode, index); + m_newColour = colour; + + setText(QObject::tr("DCLUT")); +} + +void SetDCLUTCommand::redo() +{ + m_teletextDocument->selectSubPageIndex(m_subPageIndex); + m_teletextDocument->currentSubPage()->setDCLUT(m_globalDrcs, m_mode, m_index, m_newColour); + + emit m_teletextDocument->dClutChanged(m_globalDrcs, m_mode, m_index); + emit m_teletextDocument->contentsChanged(); +} + +void SetDCLUTCommand::undo() +{ + m_teletextDocument->selectSubPageIndex(m_subPageIndex); + m_teletextDocument->currentSubPage()->setDCLUT(m_globalDrcs, m_mode, m_index, m_oldColour); + + emit m_teletextDocument->dClutChanged(m_globalDrcs, m_mode, m_index); + // We don't emit contentsChanged() here, dClutChanged does that after + // marking DRCS character cells for refresh +} diff --git a/src/qteletextmaker/x28commands.h b/src/qteletextmaker/x28commands.h index d041206..962723e 100644 --- a/src/qteletextmaker/x28commands.h +++ b/src/qteletextmaker/x28commands.h @@ -123,4 +123,17 @@ private: int m_oldColourEntry[8]; }; +class SetDCLUTCommand : public X28Command +{ +public: + SetDCLUTCommand(TeletextDocument *teletextDocument, bool globalDrcs, int mode, int index, int colour, QUndoCommand *parent = 0); + + void redo() override; + void undo() override; + +private: + bool m_globalDrcs; + int m_mode, m_index, m_oldColour, m_newColour; +}; + #endif