A complete rewrite of decode.cpp and decode.h to make it easier to follow and maintain. Rather than a set of "layers" descended from C++ classes, the for-loop across the rows and columns explicitly lays out the order of processing the Level 1 characters and attributes, the X/26 enhancements that affect the page directly as well as those within each Invocation of an Object, followed by selecting which Invoked Object to place in the character cell or in the absence of an Object the underlying page fragment will be placed in the cell. The new decoder has the following improvements over the old... - Local Enhancement Data has priority over Active Objects. - Active Objects can set Full Screen and Full Row colours. - Incremental/decremental flash phases are tracked correctly within Objects. - X/26 characters overwriting the bottom half of Level 1 Double Height rows. - Interaction between the underlying page and Objects where characters of different sizes overlap.
764 lines
24 KiB
C++
764 lines
24 KiB
C++
/*
|
|
* Copyright (C) 2020-2023 Gavin MacGregor
|
|
*
|
|
* This file is part of QTeletextMaker.
|
|
*
|
|
* QTeletextMaker is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* QTeletextMaker is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with QTeletextMaker. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <QApplication>
|
|
#include <QBitmap>
|
|
#include <QClipboard>
|
|
#include <QFrame>
|
|
#include <QGraphicsItem>
|
|
#include <QGraphicsItemGroup>
|
|
#include <QGraphicsProxyWidget>
|
|
#include <QGraphicsScene>
|
|
#include <QGraphicsSceneEvent>
|
|
#include <QKeyEvent>
|
|
#include <QMenu>
|
|
#include <QMimeData>
|
|
#include <QPainter>
|
|
#include <QPair>
|
|
#include <QUndoCommand>
|
|
#include <QWidget>
|
|
#include <vector>
|
|
#include <iostream>
|
|
|
|
#include "mainwidget.h"
|
|
|
|
#include "decode.h"
|
|
#include "document.h"
|
|
#include "keymap.h"
|
|
#include "levelonecommands.h"
|
|
#include "levelonepage.h"
|
|
#include "render.h"
|
|
#include "x26triplets.h"
|
|
|
|
TeletextWidget::TeletextWidget(QFrame *parent) : QFrame(parent)
|
|
{
|
|
this->resize(QSize(480, 250));
|
|
this->setAttribute(Qt::WA_NoSystemBackground);
|
|
this->setAttribute(Qt::WA_InputMethodEnabled, true);
|
|
m_teletextDocument = new TeletextDocument();
|
|
m_levelOnePage = m_teletextDocument->currentSubPage();
|
|
m_pageDecode.setTeletextPage(m_levelOnePage);
|
|
m_pageRender.setDecoder(&m_pageDecode);
|
|
m_insertMode = false;
|
|
m_selectionInProgress = false;
|
|
setFocusPolicy(Qt::StrongFocus);
|
|
m_flashTiming = m_flashPhase = 0;
|
|
connect(&m_pageRender, &TeletextPageRender::flashChanged, this, &TeletextWidget::updateFlashTimer);
|
|
connect(&m_pageDecode, &TeletextPageDecode::sidePanelsChanged, this, &TeletextWidget::changeSize);
|
|
connect(m_teletextDocument, &TeletextDocument::subPageSelected, this, &TeletextWidget::subPageSelected);
|
|
connect(m_teletextDocument, &TeletextDocument::contentsChange, this, &TeletextWidget::refreshRow);
|
|
connect(m_teletextDocument, &TeletextDocument::refreshNeeded, this, &TeletextWidget::refreshPage);
|
|
connect(m_teletextDocument, &TeletextDocument::colourChanged, &m_pageRender, &TeletextPageRender::colourChanged);
|
|
}
|
|
|
|
TeletextWidget::~TeletextWidget()
|
|
{
|
|
if (m_flashTimer.isActive())
|
|
m_flashTimer.stop();
|
|
delete m_teletextDocument;
|
|
}
|
|
|
|
// Re-implemented so compose/dead keys work properly
|
|
void TeletextWidget::inputMethodEvent(QInputMethodEvent* event)
|
|
{
|
|
if (!event->commitString().isEmpty()) {
|
|
QKeyEvent keyEvent(QEvent::KeyPress, 0, Qt::NoModifier, event->commitString());
|
|
|
|
keyPressEvent(&keyEvent);
|
|
}
|
|
event->accept();
|
|
}
|
|
|
|
void TeletextWidget::subPageSelected()
|
|
{
|
|
m_levelOnePage = m_teletextDocument->currentSubPage();
|
|
m_pageDecode.setTeletextPage(m_levelOnePage);
|
|
m_pageDecode.decodePage();
|
|
m_pageRender.renderPage(true);
|
|
update();
|
|
}
|
|
|
|
void TeletextWidget::refreshRow(int rowChanged)
|
|
{
|
|
Q_UNUSED(rowChanged);
|
|
|
|
// TODO trace signals where this is called so we can remove this
|
|
m_pageDecode.decodePage();
|
|
update();
|
|
}
|
|
|
|
void TeletextWidget::refreshPage()
|
|
{
|
|
m_pageDecode.decodePage();
|
|
update();
|
|
}
|
|
|
|
void TeletextWidget::paintEvent(QPaintEvent *event)
|
|
{
|
|
Q_UNUSED(event);
|
|
QPainter widgetPainter(this);
|
|
|
|
m_pageRender.renderPage();
|
|
widgetPainter.drawPixmap(m_pageDecode.leftSidePanelColumns()*12, 0, *m_pageRender.pagePixmap(m_flashPhase), 0, 0, 480, 250);
|
|
if (m_pageDecode.leftSidePanelColumns())
|
|
widgetPainter.drawPixmap(0, 0, *m_pageRender.pagePixmap(m_flashPhase), 864-m_pageDecode.leftSidePanelColumns()*12, 0, m_pageDecode.leftSidePanelColumns()*12, 250);
|
|
if (m_pageDecode.rightSidePanelColumns())
|
|
widgetPainter.drawPixmap(480+m_pageDecode.leftSidePanelColumns()*12, 0, *m_pageRender.pagePixmap(m_flashPhase), 480, 0, m_pageDecode.rightSidePanelColumns()*12, 250);
|
|
}
|
|
|
|
void TeletextWidget::updateFlashTimer(int newFlashTimer)
|
|
{
|
|
m_flashTiming = newFlashTimer;
|
|
m_flashPhase = 0;
|
|
if (newFlashTimer == 0) {
|
|
m_flashTimer.stop();
|
|
update();
|
|
return;
|
|
}
|
|
m_flashTimer.start((newFlashTimer == 1) ? 500 : 167, this);
|
|
}
|
|
|
|
void TeletextWidget::timerEvent(QTimerEvent *event)
|
|
{
|
|
if (event->timerId() == m_flashTimer.timerId()) {
|
|
if (m_flashTiming == 1)
|
|
m_flashPhase += 3;
|
|
else
|
|
m_flashPhase++;
|
|
if (m_flashPhase == 6)
|
|
m_flashPhase = 0;
|
|
update();
|
|
} else
|
|
QWidget::timerEvent(event);
|
|
}
|
|
|
|
void TeletextWidget::pauseFlash(bool pauseNow)
|
|
{
|
|
if (pauseNow && m_flashTiming != 0) {
|
|
m_flashTimer.stop();
|
|
m_flashPhase = 0;
|
|
update();
|
|
} else if (m_flashTiming != 0)
|
|
m_flashTimer.start((m_flashTiming == 1) ? 500 : 167, this);
|
|
}
|
|
|
|
void TeletextWidget::setInsertMode(bool insertMode)
|
|
{
|
|
m_insertMode = insertMode;
|
|
}
|
|
|
|
void TeletextWidget::setReveal(bool reveal)
|
|
{
|
|
m_pageRender.setReveal(reveal);
|
|
update();
|
|
}
|
|
|
|
void TeletextWidget::setMix(bool mix)
|
|
{
|
|
m_pageRender.setMix(mix);
|
|
update();
|
|
}
|
|
|
|
void TeletextWidget::setShowControlCodes(bool showControlCodes)
|
|
{
|
|
m_pageRender.setShowControlCodes(showControlCodes);
|
|
update();
|
|
}
|
|
|
|
void TeletextWidget::setControlBit(int bitNumber, bool active)
|
|
{
|
|
m_levelOnePage->setControlBit(bitNumber, active);
|
|
if (bitNumber == 1 || bitNumber == 2) {
|
|
m_pageDecode.decodePage();
|
|
m_pageRender.renderPage(true);
|
|
}
|
|
}
|
|
|
|
void TeletextWidget::setDefaultCharSet(int newDefaultCharSet)
|
|
{
|
|
m_levelOnePage->setDefaultCharSet(newDefaultCharSet);
|
|
}
|
|
|
|
void TeletextWidget::setDefaultNOS(int newDefaultNOS)
|
|
{
|
|
m_levelOnePage->setDefaultNOS(newDefaultNOS);
|
|
}
|
|
|
|
void TeletextWidget::setDefaultScreenColour(int newColour)
|
|
{
|
|
m_levelOnePage->setDefaultScreenColour(newColour);
|
|
m_pageDecode.decodePage();
|
|
}
|
|
|
|
void TeletextWidget::setDefaultRowColour(int newColour)
|
|
{
|
|
m_levelOnePage->setDefaultRowColour(newColour);
|
|
m_pageDecode.decodePage();
|
|
update();
|
|
}
|
|
|
|
void TeletextWidget::setColourTableRemap(int newMap)
|
|
{
|
|
m_levelOnePage->setColourTableRemap(newMap);
|
|
m_pageDecode.decodePage();
|
|
update();
|
|
}
|
|
|
|
void TeletextWidget::setBlackBackgroundSubst(bool substOn)
|
|
{
|
|
m_levelOnePage->setBlackBackgroundSubst(substOn);
|
|
m_pageDecode.decodePage();
|
|
update();
|
|
}
|
|
|
|
void TeletextWidget::setSidePanelWidths(int newLeftSidePanelColumns, int newRightSidePanelColumns)
|
|
{
|
|
m_levelOnePage->setLeftSidePanelDisplayed(newLeftSidePanelColumns != 0);
|
|
m_levelOnePage->setRightSidePanelDisplayed(newRightSidePanelColumns != 0);
|
|
if (newLeftSidePanelColumns)
|
|
m_levelOnePage->setSidePanelColumns((newLeftSidePanelColumns == 16) ? 0 : newLeftSidePanelColumns);
|
|
else
|
|
m_levelOnePage->setSidePanelColumns((newRightSidePanelColumns == 0) ? 0 : 16-newRightSidePanelColumns);
|
|
m_pageDecode.updateSidePanels();
|
|
}
|
|
|
|
void TeletextWidget::setSidePanelAtL35Only(bool newSidePanelAtL35Only)
|
|
{
|
|
m_levelOnePage->setSidePanelStatusL25(!newSidePanelAtL35Only);
|
|
m_pageDecode.updateSidePanels();
|
|
}
|
|
|
|
void TeletextWidget::changeSize()
|
|
{
|
|
setFixedSize(QSize(480+(pageDecode()->leftSidePanelColumns()+pageDecode()->rightSidePanelColumns())*12, 250));
|
|
emit sizeChanged();
|
|
}
|
|
|
|
void TeletextWidget::keyPressEvent(QKeyEvent *event)
|
|
{
|
|
if (event->key() < 0x01000000) {
|
|
// A character-typing key was pressed
|
|
// Try to keymap it, if not keymapped then plain ASCII code (may be) returned
|
|
char mappedKeyPress = keymapping[m_pageDecode.level1CharSet(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn())].value(event->text().at(0), *qPrintable(event->text().at(0)));
|
|
if (mappedKeyPress >= 0x00 && mappedKeyPress <= 0x1f)
|
|
return;
|
|
// If outside ASCII map then the character can't be represented by current Level 1 character set
|
|
// Map it to block character so it doesn't need to be inserted-between later on
|
|
if (mappedKeyPress & 0x80)
|
|
mappedKeyPress = 0x7f;
|
|
if (m_pageDecode.level1MosaicAttribute(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn()) && (mappedKeyPress < 0x40 || mappedKeyPress > 0x5f)) {
|
|
// We're on a mosaic and a blast-through character was NOT pressed
|
|
if (event->key() >= Qt::Key_0 && event->key() <= Qt::Key_9 && event->modifiers() & Qt::KeypadModifier) {
|
|
switch (event->key()) {
|
|
case Qt::Key_7:
|
|
toggleCharacterBit(0x01); // Top left
|
|
break;
|
|
case Qt::Key_8:
|
|
toggleCharacterBit(0x02); // Top right
|
|
break;
|
|
case Qt::Key_4:
|
|
toggleCharacterBit(0x04); // Middle left
|
|
break;
|
|
case Qt::Key_5:
|
|
toggleCharacterBit(0x08); // Middle right
|
|
break;
|
|
case Qt::Key_1:
|
|
toggleCharacterBit(0x10); // Bottom left
|
|
break;
|
|
case Qt::Key_2:
|
|
toggleCharacterBit(0x40); // Bottom right
|
|
break;
|
|
case Qt::Key_9:
|
|
toggleCharacterBit(0x5f); // Invert
|
|
break;
|
|
case Qt::Key_6:
|
|
toggleCharacterBit(0x7f); // Set all
|
|
break;
|
|
case Qt::Key_3:
|
|
toggleCharacterBit(0x20); // Clear all
|
|
break;
|
|
case Qt::Key_0:
|
|
toggleCharacterBit(0x66); // Dither
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
if (event->key() == Qt::Key_Space) {
|
|
setCharacter(0x20);
|
|
return;
|
|
}
|
|
|
|
// This macro is defined in keymap.h if no native scan codes are defined
|
|
// for the platform we are compiling on
|
|
#ifndef QTTM_NONATIVESCANCODES
|
|
if (event->nativeScanCode() > 1) {
|
|
switch (event->nativeScanCode()) {
|
|
case mosaicNativeScanCodes[0]:
|
|
toggleCharacterBit(0x01); // Top left
|
|
break;
|
|
case mosaicNativeScanCodes[1]:
|
|
toggleCharacterBit(0x02); // Top right
|
|
break;
|
|
case mosaicNativeScanCodes[2]:
|
|
toggleCharacterBit(0x04); // Middle left
|
|
break;
|
|
case mosaicNativeScanCodes[3]:
|
|
toggleCharacterBit(0x08); // Middle right
|
|
break;
|
|
case mosaicNativeScanCodes[4]:
|
|
toggleCharacterBit(0x10); // Bottom left
|
|
break;
|
|
case mosaicNativeScanCodes[5]:
|
|
toggleCharacterBit(0x40); // Bottom right
|
|
break;
|
|
case mosaicNativeScanCodes[6]:
|
|
toggleCharacterBit(0x5f); // Invert
|
|
break;
|
|
case mosaicNativeScanCodes[7]:
|
|
toggleCharacterBit(0x7f); // Set all
|
|
break;
|
|
case mosaicNativeScanCodes[8]:
|
|
toggleCharacterBit(0x20); // Clear all
|
|
break;
|
|
case mosaicNativeScanCodes[9]:
|
|
toggleCharacterBit(0x66); // Dither
|
|
break;
|
|
}
|
|
return;
|
|
} else
|
|
qDebug("nativeScanCode not usable! Please use numeric keypad to toggle mosaic bits.");
|
|
#else
|
|
qDebug("nativeScanCode was not compiled in! Please use numeric keypad to toggle mosaic bits.");
|
|
#endif
|
|
|
|
// TODO some contingency plan if nativeScanCode didn't work?
|
|
return;
|
|
}
|
|
setCharacter(mappedKeyPress);
|
|
return;
|
|
}
|
|
switch (event->key()) {
|
|
case Qt::Key_Backspace:
|
|
m_teletextDocument->undoStack()->push(new BackspaceKeyCommand(m_teletextDocument, m_insertMode));
|
|
break;
|
|
case Qt::Key_Tab:
|
|
m_teletextDocument->undoStack()->push(new TypeCharacterCommand(m_teletextDocument, 0x20, true));
|
|
break;
|
|
case Qt::Key_Delete:
|
|
m_teletextDocument->undoStack()->push(new DeleteKeyCommand(m_teletextDocument));
|
|
break;
|
|
case Qt::Key_Insert:
|
|
emit insertKeyPressed();
|
|
break;
|
|
|
|
case Qt::Key_Up:
|
|
m_teletextDocument->cursorUp(event->modifiers() & Qt::ShiftModifier);
|
|
break;
|
|
case Qt::Key_Down:
|
|
m_teletextDocument->cursorDown(event->modifiers() & Qt::ShiftModifier);
|
|
break;
|
|
case Qt::Key_Left:
|
|
m_teletextDocument->cursorLeft(event->modifiers() & Qt::ShiftModifier);
|
|
break;
|
|
case Qt::Key_Right:
|
|
m_teletextDocument->cursorRight(event->modifiers() & Qt::ShiftModifier);
|
|
break;
|
|
case Qt::Key_Return:
|
|
case Qt::Key_Enter:
|
|
m_teletextDocument->cursorDown();
|
|
m_teletextDocument->moveCursor(m_teletextDocument->cursorRow(), 0);
|
|
break;
|
|
case Qt::Key_Home:
|
|
m_teletextDocument->moveCursor(m_teletextDocument->cursorRow(), 0, event->modifiers() & Qt::ShiftModifier);
|
|
break;
|
|
case Qt::Key_End:
|
|
m_teletextDocument->moveCursor(m_teletextDocument->cursorRow(), 39, event->modifiers() & Qt::ShiftModifier);
|
|
break;
|
|
|
|
case Qt::Key_PageUp:
|
|
m_teletextDocument->selectSubPageNext();
|
|
break;
|
|
case Qt::Key_PageDown:
|
|
m_teletextDocument->selectSubPagePrevious();
|
|
break;
|
|
case Qt::Key_F6:
|
|
m_pageDecode.decodePage();
|
|
m_pageRender.renderPage(true);
|
|
update();
|
|
break;
|
|
default:
|
|
QFrame::keyPressEvent(event);
|
|
}
|
|
}
|
|
|
|
void TeletextWidget::setCharacter(unsigned char newCharacter)
|
|
{
|
|
m_teletextDocument->undoStack()->push(new TypeCharacterCommand(m_teletextDocument, newCharacter, m_insertMode));
|
|
}
|
|
|
|
void TeletextWidget::toggleCharacterBit(unsigned char bitToToggle)
|
|
{
|
|
m_teletextDocument->undoStack()->push(new ToggleMosaicBitCommand(m_teletextDocument, bitToToggle));
|
|
}
|
|
|
|
void TeletextWidget::selectionToClipboard()
|
|
{
|
|
QByteArray nativeData;
|
|
QString plainTextData;
|
|
QClipboard *clipboard = QApplication::clipboard();
|
|
|
|
nativeData.resize(2 + m_teletextDocument->selectionWidth() * m_teletextDocument->selectionHeight());
|
|
nativeData[0] = m_teletextDocument->selectionHeight();
|
|
nativeData[1] = m_teletextDocument->selectionWidth();
|
|
|
|
plainTextData.reserve((m_teletextDocument->selectionWidth()+1) * m_teletextDocument->selectionHeight() - 1);
|
|
|
|
int i=2;
|
|
|
|
for (int r=m_teletextDocument->selectionTopRow(); r<=m_teletextDocument->selectionBottomRow(); r++) {
|
|
for (int c=m_teletextDocument->selectionLeftColumn(); c<=m_teletextDocument->selectionRightColumn(); c++) {
|
|
nativeData[i++] = m_teletextDocument->currentSubPage()->character(r, c);
|
|
|
|
if (m_teletextDocument->currentSubPage()->character(r, c) >= 0x20)
|
|
plainTextData.append(keymapping[m_pageDecode.level1CharSet(r, c)].key(m_teletextDocument->currentSubPage()->character(r, c), m_teletextDocument->currentSubPage()->character(r, c)));
|
|
else
|
|
plainTextData.append(' ');
|
|
}
|
|
|
|
plainTextData.append('\n');
|
|
}
|
|
|
|
QMimeData *mimeData = new QMimeData();
|
|
mimeData->setData("application/x-teletext", nativeData);
|
|
mimeData->setText(plainTextData);
|
|
clipboard->setMimeData(mimeData);
|
|
}
|
|
|
|
void TeletextWidget::cut()
|
|
{
|
|
if (!m_teletextDocument->selectionActive())
|
|
return;
|
|
|
|
selectionToClipboard();
|
|
m_teletextDocument->undoStack()->push(new CutCommand(m_teletextDocument));
|
|
}
|
|
|
|
void TeletextWidget::copy()
|
|
{
|
|
if (!m_teletextDocument->selectionActive())
|
|
return;
|
|
|
|
selectionToClipboard();
|
|
}
|
|
|
|
void TeletextWidget::paste()
|
|
{
|
|
m_teletextDocument->undoStack()->push(new PasteCommand(m_teletextDocument, m_pageDecode.level1CharSet(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn())));
|
|
}
|
|
|
|
QPair<int, int> TeletextWidget::mouseToRowAndColumn(const QPoint &mousePosition)
|
|
{
|
|
int row = mousePosition.y() / 10;
|
|
int column = mousePosition.x() / 12 - m_pageDecode.leftSidePanelColumns();
|
|
if (row < 1)
|
|
row = 1;
|
|
if (row > 24)
|
|
row = 24;
|
|
if (column < 0)
|
|
column = 0;
|
|
if (column > 39)
|
|
column = 39;
|
|
return qMakePair(row, column);
|
|
}
|
|
|
|
void TeletextWidget::mousePressEvent(QMouseEvent *event)
|
|
{
|
|
if (event->button() == Qt::LeftButton) {
|
|
m_teletextDocument->cancelSelection();
|
|
QPair<int, int> position = mouseToRowAndColumn(event->pos());
|
|
m_teletextDocument->moveCursor(position.first, position.second);
|
|
update();
|
|
}
|
|
}
|
|
|
|
void TeletextWidget::mouseMoveEvent(QMouseEvent *event)
|
|
{
|
|
if (event->buttons() & Qt::LeftButton) {
|
|
QPair<int, int> position = mouseToRowAndColumn(event->pos());
|
|
if (position.first != m_teletextDocument->cursorRow() || position.second != m_teletextDocument->cursorColumn()) {
|
|
if (!m_selectionInProgress) {
|
|
m_selectionInProgress = true;
|
|
m_teletextDocument->setSelectionCorner(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn());
|
|
}
|
|
m_teletextDocument->moveCursor(position.first, position.second, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TeletextWidget::mouseReleaseEvent(QMouseEvent *event)
|
|
{
|
|
if (event->button() == Qt::LeftButton)
|
|
m_selectionInProgress = false;
|
|
}
|
|
|
|
void TeletextWidget::focusInEvent(QFocusEvent *event)
|
|
{
|
|
QFrame::focusInEvent(event);
|
|
}
|
|
|
|
void TeletextWidget::focusOutEvent(QFocusEvent *event)
|
|
{
|
|
QFrame::focusOutEvent(event);
|
|
}
|
|
|
|
|
|
LevelOneScene::LevelOneScene(QWidget *levelOneWidget, QObject *parent) : QGraphicsScene(parent)
|
|
{
|
|
m_grid = false;
|
|
|
|
// These dimensions are scratch, setBorderDimensions will get called straight away to adjust them
|
|
setSceneRect(0, 0, 600, 288);
|
|
|
|
// Full screen colours
|
|
m_fullScreenTopRectItem = new QGraphicsRectItem(0, 0, 600, 19);
|
|
m_fullScreenTopRectItem->setPen(Qt::NoPen);
|
|
m_fullScreenTopRectItem->setBrush(QBrush(QColor(0, 0, 0)));
|
|
addItem(m_fullScreenTopRectItem);
|
|
m_fullScreenBottomRectItem = new QGraphicsRectItem(0, 269, 600, 19);
|
|
m_fullScreenBottomRectItem->setPen(Qt::NoPen);
|
|
m_fullScreenBottomRectItem->setBrush(QBrush(QColor(0, 0, 0)));
|
|
addItem(m_fullScreenBottomRectItem);
|
|
|
|
// Full row colours
|
|
for (int r=0; r<25; r++) {
|
|
m_fullRowLeftRectItem[r] = new QGraphicsRectItem(0, 19+r*10, 60, 10);
|
|
m_fullRowLeftRectItem[r]->setPen(Qt::NoPen);
|
|
m_fullRowLeftRectItem[r]->setBrush(QBrush(QColor(0, 0, 0)));
|
|
addItem(m_fullRowLeftRectItem[r]);
|
|
m_fullRowRightRectItem[r] = new QGraphicsRectItem(540, 19+r*10, 60, 10);
|
|
m_fullRowRightRectItem[r]->setPen(Qt::NoPen);
|
|
m_fullRowRightRectItem[r]->setBrush(QBrush(QColor(0, 0, 0)));
|
|
addItem(m_fullRowRightRectItem[r]);
|
|
}
|
|
|
|
// Main text widget
|
|
m_levelOneProxyWidget = addWidget(levelOneWidget);
|
|
m_levelOneProxyWidget->setPos(60, 19);
|
|
m_levelOneProxyWidget->setAutoFillBackground(false);
|
|
m_levelOneProxyWidget->setFocus();
|
|
|
|
// Selection
|
|
m_selectionRectItem = new QGraphicsRectItem(0, 0, 12, 10);
|
|
m_selectionRectItem->setVisible(false);
|
|
m_selectionRectItem->setPen(QPen(QColor(192, 192, 192), 1, Qt::DashLine));
|
|
m_selectionRectItem->setBrush(QBrush(QColor(255, 255, 255, 64)));
|
|
addItem(m_selectionRectItem);
|
|
|
|
// Cursor
|
|
m_cursorRectItem = new QGraphicsRectItem(0, 0, 12, 10);
|
|
m_cursorRectItem->setPen(Qt::NoPen);
|
|
m_cursorRectItem->setBrush(QBrush(QColor(128, 128, 128, 192)));
|
|
addItem(m_cursorRectItem);
|
|
|
|
// Optional grid overlay for text widget
|
|
m_mainGridItemGroup = new QGraphicsItemGroup;
|
|
m_mainGridItemGroup->setVisible(false);
|
|
addItem(m_mainGridItemGroup);
|
|
// Additional vertical pieces of grid for side panels
|
|
for (int i=0; i<32; i++) {
|
|
m_sidePanelGridNeeded[i] = false;
|
|
m_sidePanelGridItemGroup[i] = new QGraphicsItemGroup;
|
|
m_sidePanelGridItemGroup[i]->setVisible(false);
|
|
addItem(m_sidePanelGridItemGroup[i]);
|
|
}
|
|
for (int r=1; r<25; r++) {
|
|
for (int c=0; c<40; c++) {
|
|
QGraphicsRectItem *gridPiece = new QGraphicsRectItem(c*12, r*10, 12, 10);
|
|
gridPiece->setPen(QPen(QBrush(QColor(128, 128, 128, r<24 ? 192 : 128)), 0));
|
|
m_mainGridItemGroup->addToGroup(gridPiece);
|
|
}
|
|
|
|
if (r < 24)
|
|
for (int c=0; c<32; c++) {
|
|
QGraphicsRectItem *gridPiece = new QGraphicsRectItem(0, r*10, 12, 10);
|
|
gridPiece->setPen(QPen(QBrush(QColor(128, 128, 128, 64)), 0));
|
|
m_sidePanelGridItemGroup[c]->addToGroup(gridPiece);
|
|
}
|
|
}
|
|
|
|
installEventFilter(this);
|
|
}
|
|
|
|
void LevelOneScene::setBorderDimensions(int sceneWidth, int sceneHeight, int widgetWidth, int leftSidePanelColumns, int rightSidePanelColumns)
|
|
{
|
|
setSceneRect(0, 0, sceneWidth, sceneHeight);
|
|
|
|
// Assume text widget height is always 250
|
|
int topBottomBorders = (sceneHeight-250) / 2;
|
|
// Ideally we'd use m_levelOneProxyWidget->size() to discover the widget width ourselves
|
|
// but this causes a stubborn segfault, so we have to receive the widgetWidth as a parameter
|
|
int leftRightBorders = (sceneWidth-widgetWidth) / 2;
|
|
|
|
m_levelOneProxyWidget->setPos(leftRightBorders, topBottomBorders);
|
|
|
|
// Position grid to cover central 40 columns
|
|
m_mainGridItemGroup->setPos(leftRightBorders + leftSidePanelColumns*12, topBottomBorders);
|
|
|
|
updateCursor();
|
|
updateSelection();
|
|
|
|
// Grid for right side panel
|
|
for (int c=0; c<16; c++)
|
|
if (rightSidePanelColumns > c) {
|
|
m_sidePanelGridItemGroup[c]->setPos(leftRightBorders + leftSidePanelColumns*12 + 480 + c*12, topBottomBorders);
|
|
m_sidePanelGridItemGroup[c]->setVisible(m_grid);
|
|
m_sidePanelGridNeeded[c] = true;
|
|
} else {
|
|
m_sidePanelGridItemGroup[c]->setVisible(false);
|
|
m_sidePanelGridNeeded[c] = false;
|
|
}
|
|
// Grid for left side panel
|
|
for (int c=0; c<16; c++)
|
|
if (c < leftSidePanelColumns) {
|
|
m_sidePanelGridItemGroup[31-c]->setPos(leftRightBorders + (leftSidePanelColumns-c-1)*12, topBottomBorders);
|
|
m_sidePanelGridItemGroup[31-c]->setVisible(m_grid);
|
|
m_sidePanelGridNeeded[31-c] = true;
|
|
} else {
|
|
m_sidePanelGridItemGroup[31-c]->setVisible(false);
|
|
m_sidePanelGridNeeded[31-c] = false;
|
|
}
|
|
|
|
// Full screen colours
|
|
m_fullScreenTopRectItem->setRect(0, 0, sceneWidth, topBottomBorders);
|
|
m_fullScreenBottomRectItem->setRect(0, 250+topBottomBorders, sceneWidth, topBottomBorders);
|
|
// Full row colours
|
|
for (int r=0; r<25; r++) {
|
|
m_fullRowLeftRectItem[r]->setRect(0, topBottomBorders+r*10, leftRightBorders+1, 10);
|
|
m_fullRowRightRectItem[r]->setRect(sceneWidth-leftRightBorders-1, topBottomBorders+r*10, leftRightBorders+1, 10);
|
|
}
|
|
}
|
|
|
|
void LevelOneScene::updateCursor()
|
|
{
|
|
m_cursorRectItem->setPos(m_mainGridItemGroup->pos().x() + static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->document()->cursorColumn()*12, m_mainGridItemGroup->pos().y() + static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->document()->cursorRow()*10);
|
|
}
|
|
|
|
void LevelOneScene::updateSelection()
|
|
{
|
|
if (!static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->document()->selectionActive()) {
|
|
m_selectionRectItem->setVisible(false);
|
|
return;
|
|
}
|
|
|
|
m_selectionRectItem->setRect(m_mainGridItemGroup->pos().x() + static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->document()->selectionLeftColumn()*12, m_mainGridItemGroup->pos().y() + static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->document()->selectionTopRow()*10, static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->document()->selectionWidth()*12-1, static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->document()->selectionHeight()*10-1);
|
|
|
|
m_selectionRectItem->setVisible(true);
|
|
}
|
|
|
|
void LevelOneScene::setMix(bool mix)
|
|
{
|
|
if (mix) {
|
|
m_fullScreenTopRectItem->setBrush(Qt::transparent);
|
|
m_fullScreenBottomRectItem->setBrush(Qt::transparent);
|
|
for (int r=0; r<25; r++) {
|
|
m_fullRowLeftRectItem[r]->setBrush(Qt::transparent);
|
|
m_fullRowRightRectItem[r]->setBrush(Qt::transparent);
|
|
}
|
|
} else {
|
|
setFullScreenColour(static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->pageDecode()->fullScreenQColor());
|
|
for (int r=0; r<25; r++)
|
|
setFullRowColour(r, static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->pageDecode()->fullRowQColor(r));
|
|
}
|
|
}
|
|
|
|
void LevelOneScene::toggleGrid(bool gridOn)
|
|
{
|
|
m_grid = gridOn;
|
|
m_mainGridItemGroup->setVisible(gridOn);
|
|
for (int i=0; i<32; i++)
|
|
if (m_sidePanelGridNeeded[i])
|
|
m_sidePanelGridItemGroup[i]->setVisible(gridOn);
|
|
}
|
|
|
|
void LevelOneScene::hideGUIElements(bool hidden)
|
|
{
|
|
if (hidden) {
|
|
m_cursorRectItem->setVisible(false);
|
|
m_selectionRectItem->setVisible(false);
|
|
} else {
|
|
if (static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->document()->selectionActive())
|
|
m_selectionRectItem->setVisible(true);
|
|
|
|
m_cursorRectItem->setVisible(true);
|
|
}
|
|
}
|
|
|
|
// Implements Ctrl+mousewheel zoom
|
|
bool LevelOneScene::eventFilter(QObject *object, QEvent *event)
|
|
{
|
|
Q_UNUSED(object);
|
|
|
|
if (event->type() == QEvent::GraphicsSceneWheel && static_cast<QGraphicsSceneWheelEvent *>(event)->modifiers() == Qt::ControlModifier) {
|
|
if (static_cast<QGraphicsSceneWheelEvent *>(event)->delta() > 0)
|
|
emit mouseZoomIn();
|
|
else if (static_cast<QGraphicsSceneWheelEvent *>(event)->delta() < 0)
|
|
emit mouseZoomOut();
|
|
|
|
event->accept();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Clicking outside the main text widget but still within the scene would
|
|
// cause keyboard focus loss.
|
|
// So on every keypress within the scene, wrench the focus back to the widget
|
|
// if necessary.
|
|
void LevelOneScene::keyPressEvent(QKeyEvent *keyEvent)
|
|
{
|
|
if (focusItem() != m_levelOneProxyWidget)
|
|
setFocusItem(m_levelOneProxyWidget);
|
|
|
|
QGraphicsScene::keyPressEvent(keyEvent);
|
|
}
|
|
|
|
void LevelOneScene::keyReleaseEvent(QKeyEvent *keyEvent)
|
|
{
|
|
if (focusItem() != m_levelOneProxyWidget)
|
|
setFocusItem(m_levelOneProxyWidget);
|
|
|
|
QGraphicsScene::keyReleaseEvent(keyEvent);
|
|
}
|
|
|
|
void LevelOneScene::setFullScreenColour(const QColor &newColor)
|
|
{
|
|
if (!static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->pageRender()->mix()) {
|
|
m_fullScreenTopRectItem->setBrush(newColor);
|
|
m_fullScreenBottomRectItem->setBrush(newColor);
|
|
}
|
|
}
|
|
|
|
void LevelOneScene::setFullRowColour(int row, const QColor &newColor)
|
|
{
|
|
if (!static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->pageRender()->mix()) {
|
|
m_fullRowLeftRectItem[row]->setBrush(newColor);
|
|
m_fullRowRightRectItem[row]->setBrush(newColor);
|
|
}
|
|
}
|