23 Commits

Author SHA1 Message Date
G.K.MacGregor
88ea066481 Tag version 0.6.1-alpha 2023-06-24 18:05:05 +01:00
G.K.MacGregor
7b59c78cee Show correct G0 and G2 character set from page in widgets 2023-06-23 18:39:36 +01:00
G.K.MacGregor
c8f61d4d2c Fix default palette detection of CLUTs 1:7 and 3:7 2023-06-20 18:42:01 +01:00
G.K.MacGregor
bd894a523e Show language names with mod'd G0/G2 character set triplet 2023-06-15 18:55:47 +01:00
G.K.MacGregor
27ae092b5e Lock out CLUT 0-1 editing when Level is not 3.5 2023-06-14 18:21:23 +01:00
G.K.MacGregor
43dcccb00c Fix to reserved value in mode menu 2023-06-12 21:54:59 +01:00
G.K.MacGregor
0d0db2f8b6 Implement modified G0 and G2 character set designation triplet 2023-06-12 16:37:50 +01:00
G.K.MacGregor
61c52fe7cc Fix resetting reserved bit for display attributes 2023-05-21 18:32:25 +01:00
G.K.MacGregor
4b6ea4398d Implement Level 3.5 bold and italic font style
Proportional font attributes are tracked within the decoder, but the effect
is not rendered.
2023-05-21 15:33:21 +01:00
G.K.MacGregor
ed821bde45 Use proper composition mode when copying lines to flash buffers 2023-05-14 17:39:08 +01:00
G.K.MacGregor
955a1262e9 Clear invocation QMaps on empty triplet list
Fixes a bug where local enhancements hung around when switching to a subpage
without enhancements.
2023-05-09 23:35:29 +01:00
G.K.MacGregor
021fbfa60f Rewrite the decoder
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.
2023-05-07 19:25:06 +01:00
G.K.MacGregor
801efa570c Also track Active Position of Level 1.5 decoding
This is needed for the upcoming decoder write as decode.cpp will track the
Active Position from the internal data of x26triplets.

If decode.cpp is rewritten again to track the Active Position independently
of x26triplets this commit may be reverted, but then again tracking how the
Active Position moves on both levels 1.5 and 2.5+ may be useful in terms of
warning the user of differing results on different decoder levels.
2023-05-02 18:56:06 +01:00
G.K.MacGregor
0b50b064db Set address when inserting a Address Row 0 triplet 2023-04-12 21:18:22 +01:00
G.K.MacGregor
c5e3fd5668 Optimise rendering of pages with flashing characters
Previously every character on the page was drawn up to six times. Now the
page is drawn once, quickly copied up to five times and then only the
flashing characters are redrawn on the copies.
2023-03-19 17:38:52 +00:00
G.K.MacGregor
3125762133 Tag version 0.6-alpha 2023-03-05 14:38:45 +00:00
G.K.MacGregor
c0670c8281 Never mark a document cleanly saved after .t42 export
Amendment to 7493c8f so that a document even with only one subpage will not
be marked as cleanly saved. This should reinforce the fact that only TTI
files will be guaranteed to store everything.
2023-02-26 18:06:14 +00:00
G.K.MacGregor
7493c8f527 Add re-export option if .t42 file is loaded or exported
This allows a single keypress or menu click to repeatedly export a subpage
over the same .t42 file, like "Save" does for TTI files. If further teletext
page formats are added in the future this option should remember which
format was exported.

This option is deliberately different from "Save" as .t42 files are only
exported as the current subpage on view, and some metadata stored in TTI
files but not in .t42 files could be lost.

When exporting .t42 files the document is only marked as cleanly saved if it
consisted of a single subpage. Documents of more than one subpage will still
cause a confirmation dialog to be shown if unsaved, as the other subpages
will be lost unless the document is saved again as TTI.
2023-02-26 17:29:09 +00:00
G.K.MacGregor
9bd9f180c2 Don't hide control codes or grid when exporting PNG 2023-02-02 18:18:51 +00:00
G.K.MacGregor
c64be6a4c9 Update copyright notices to 2023 2022-12-31 21:19:15 +00:00
G.K.MacGregor
eb752835fd Auto-set row when inserting a Set Active Position triplet 2022-12-13 21:34:14 +00:00
G.K.MacGregor
72a2ef9660 Implement word wrapping when pasting plain text, wrt #6
When the clipboard has plain text data copied from a plain text or ASCII
text editor, pasting the text will perform word wrapping.

The text will originate from the cursor position and subsequent lines will
by default start from column 1 to provide room for alphanumeric colour
control codes in column 0.

If the cursor is at column 0 then all lines will start at column 0.

If a box selection is dragged before pasting the text will originate at
the top left of the selection box and be confined to the box. This can be
used if the second and subsequent lines need to start at a different
column, or if the pasted text must not overwrite an existing feature at
the right side of the page.

Note that cutting or copying text from within QTeletextMaker itself will
not be word wrapped on paste.
2022-11-20 17:36:53 +00:00
G.K.MacGregor
213eace512 Add dither to mosaic manipulating keypresses 2022-11-06 18:42:14 +00:00
39 changed files with 1798 additions and 1336 deletions

View File

@@ -29,8 +29,7 @@ The following X/26 enhancement triplets are not rendered by the editor, although
- Invocation of Objects from POP and GPOP pages. - Invocation of Objects from POP and GPOP pages.
- DRCS characters. - DRCS characters.
- Modified G0 and G2 character set designation using X/26 triplets with mode 01000. - Modified G0 and G2 character set designation using X/26 triplets with mode 01000.
- Full screen and full row colours set by Active Objects. - Proportional font spacing on Level 3.5
- Level 3.5 font style: bold, italic and proportional spacing.
## Using the X/26 triplet editor ## Using the X/26 triplet editor
The X/26 triplet editor sorts all the triplet modes available into categories, which are: The X/26 triplet editor sorts all the triplet modes available into categories, which are:

1674
decode.cpp

File diff suppressed because it is too large Load Diff

420
decode.h
View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -20,239 +20,52 @@
#ifndef DECODE_H #ifndef DECODE_H
#define DECODE_H #define DECODE_H
#include <QList>
#include <QMap> #include <QMap>
#include <QMultiMap> #include <QMultiMap>
#include <QPair>
#include <vector>
#include "levelonepage.h" #include "levelonepage.h"
struct textCharacter {
unsigned char code=0x20;
int set=0;
int diacritical=0;
};
inline bool operator!=(const textCharacter &lhs, const textCharacter &rhs)
{
return lhs.code != rhs.code ||
lhs.set != rhs.set ||
lhs.diacritical != rhs.diacritical;
}
struct displayAttributes {
bool doubleHeight=false;
bool doubleWidth=false;
bool boxingWindow=false;
bool conceal=false;
bool invert=false;
bool underlineSeparated=false;
bool forceContiguous=false;
};
inline bool operator!=(const displayAttributes &lhs, const displayAttributes &rhs)
{
return lhs.doubleHeight != rhs.doubleHeight ||
lhs.doubleWidth != rhs.doubleWidth ||
lhs.boxingWindow != rhs.boxingWindow ||
lhs.conceal != rhs.conceal ||
lhs.invert != rhs.invert ||
lhs.underlineSeparated != rhs.underlineSeparated ||
lhs.forceContiguous != rhs.forceContiguous;
}
struct textAttributes {
int foreColour=0x07;
int backColour=0x00;
struct flashFunctions {
int mode=0;
int ratePhase=0;
int phaseNumber=0;
} flash;
displayAttributes display;
/* font style */
};
inline bool operator!=(const textAttributes &lhs, const textAttributes &rhs)
{
return lhs.foreColour != rhs.foreColour ||
lhs.backColour != rhs.backColour ||
lhs.flash.mode != rhs.flash.mode ||
lhs.flash.ratePhase != rhs.flash.ratePhase ||
lhs.flash.phaseNumber != rhs.flash.phaseNumber ||
lhs.display != rhs.display;
}
struct textCell {
textCharacter character;
textAttributes attribute;
bool bottomHalf=false;
bool rightHalf=false;
bool level1Mosaic=false;
int level1CharSet=0;
};
inline bool operator!=(const textCell &lhs, const textCell &rhs)
{
return lhs.character != rhs.character ||
lhs.attribute != rhs.attribute ||
lhs.bottomHalf != rhs.bottomHalf ||
lhs.rightHalf != rhs.rightHalf ||
lhs.level1Mosaic != rhs.level1Mosaic ||
lhs.level1CharSet != rhs.level1CharSet;
}
struct applyAttributes {
bool applyForeColour=false;
bool applyBackColour=false;
bool applyFlash=false;
bool applyDisplayAttributes=false;
bool applyTextSizeOnly=false;
bool applyBoxingOnly=false;
bool applyConcealOnly=false;
bool applyContiguousOnly=false;
bool copyAboveAttributes=false;
textAttributes attribute;
};
class ActivePosition
{
public:
ActivePosition();
int row() const { return (m_row == -1) ? 0 : m_row; }
int column() const { return (m_column == -1) ? 0 : m_column; }
bool isDeployed() const { return m_row != -1; }
bool setRow(int);
bool setColumn(int);
// bool setRowAndColumn(int, int);
private:
int m_row, m_column;
};
class TextLayer
{
public:
// TextLayer(TeletextPage* thePage) : currentPage(thePage) { };
virtual ~TextLayer() = default;
void setTeletextPage(LevelOnePage *);
virtual textCharacter character(int, int) =0;
virtual void attributes(int, int, applyAttributes *) =0;
virtual int fullScreenColour() const =0;
virtual int fullRowColour(int) const =0;
virtual bool fullRowDownwards(int) const =0;
virtual int objectType() const =0;
virtual int originR() const { return 0; };
virtual int originC() const { return 0; };
void setFullScreenColour(int);
void setFullRowColour(int, int, bool);
// Key QPair is row and column, value QPair is triplet mode and data
QMultiMap<QPair<int, int>, QPair<int, int>> enhanceMap;
protected:
LevelOnePage* m_levelOnePage;
int m_layerFullScreenColour=-1;
int m_layerFullRowColour[25];
bool m_layerFullRowDownwards[25];
applyAttributes m_applyAttributes;
};
class EnhanceLayer: public TextLayer
{
public:
EnhanceLayer();
textCharacter character(int, int);
void attributes(int, int, applyAttributes *);
int fullScreenColour() const { return m_layerFullScreenColour; };
int fullRowColour(int r) const { return m_layerFullRowColour[r]; };
bool fullRowDownwards(int r) const { return m_layerFullRowDownwards[r]; };
int objectType() const { return m_objectType; };
int originR() const { return m_originR; };
int originC() const { return m_originC; };
void setObjectType(int);
void setOrigin(int, int);
protected:
int m_objectType=0;
int m_originR=0;
int m_originC=0;
int m_rowCached=-1;
int m_rightMostColumn[25];
};
class Level1Layer: public TextLayer
{
public:
enum RowHeight { Normal=-1, TopHalf, BottomHalf };
// Level1Layer(TeletextPage *thePage) : TextLayer(thePage) { };
Level1Layer();
textCharacter character(int, int);
void attributes(int, int, applyAttributes *);
int fullScreenColour() const { return -1; };
int fullRowColour(int) const { return -1; };
bool fullRowDownwards(int) const { return false; };
int objectType() const { return 0; }
RowHeight rowHeight(int r) const { return m_rowHeight[r]; };
private:
void updateRowCache(int);
struct level1CacheAttributes {
int foreColour=0x07;
int backColour=0x00;
unsigned char sizeCode=0x0c;
bool mosaics=false;
bool separated=false;
bool held=false;
bool escSwitch=false;
unsigned char holdChar=0x20;
bool holdSeparated=false;
};
level1CacheAttributes m_attributeCache[40];
int m_rowCached=-1;
bool m_rowHasDoubleHeightAttr[25];
RowHeight m_rowHeight[25];
};
class TeletextPageDecode : public QObject class TeletextPageDecode : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
enum CharacterFragment { NormalSize, DoubleHeightTopHalf, DoubleHeightBottomHalf, DoubleWidthLeftHalf, DoubleWidthRightHalf, DoubleSizeTopLeftQuarter, DoubleSizeTopRightQuarter, DoubleSizeBottomLeftQuarter, DoubleSizeBottomRightQuarter }; enum CharacterFragment { NormalSize, DoubleHeightTopHalf, DoubleHeightBottomHalf, DoubleWidthLeftHalf, DoubleWidthRightHalf, DoubleSizeTopLeftQuarter, DoubleSizeTopRightQuarter, DoubleSizeBottomLeftQuarter, DoubleSizeBottomRightQuarter };
enum RowHeight { NormalHeight, TopHalf, BottomHalf };
TeletextPageDecode(); TeletextPageDecode();
~TeletextPageDecode(); ~TeletextPageDecode();
bool refresh(int r, int c) const { return m_refresh[r][c]; } bool refresh(int r, int c) const { return m_refresh[r][c]; }
void setRefresh(int, int, bool); void setRefresh(int, int, bool);
void decodePage(); void decodePage();
void decodeRow(int r);
LevelOnePage *teletextPage() const { return m_levelOnePage; }; LevelOnePage *teletextPage() const { return m_levelOnePage; };
void setTeletextPage(LevelOnePage *); void setTeletextPage(LevelOnePage *);
void updateSidePanels(); void updateSidePanels();
void buildEnhanceMap(TextLayer *, int=0);
unsigned char cellCharacterCode(int r, int c) { return cellAtCharacterOrigin(r, c).character.code; }; unsigned char cellCharacterCode(int r, int c) const { return m_cell[r][c].character.code; };
int cellCharacterSet(int r, int c) { return cellAtCharacterOrigin(r, c).character.set; }; int cellCharacterSet(int r, int c) const { return m_cell[r][c].character.set; };
int cellCharacterDiacritical(int r, int c) { return cellAtCharacterOrigin(r, c).character.diacritical; }; int cellCharacterDiacritical(int r, int c) const { return m_cell[r][c].character.diacritical; };
int cellForegroundCLUT(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.foreColour; }; int cellForegroundCLUT(int r, int c) const { return m_cell[r][c].attribute.foregroundCLUT; };
int cellBackgroundCLUT(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.backColour; }; int cellBackgroundCLUT(int r, int c) const { return m_cell[r][c].attribute.backgroundCLUT; };
QColor cellForegroundQColor(int, int); QColor cellForegroundQColor(int, int);
QColor cellBackgroundQColor(int, int); QColor cellBackgroundQColor(int, int);
QColor cellFlashForegroundQColor(int, int); QColor cellFlashForegroundQColor(int, int);
int cellFlashMode(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.flash.mode; }; int cellFlashMode(int r, int c) const { return m_cell[r][c].attribute.flash.mode; };
int cellFlashRatePhase(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.flash.ratePhase; }; int cellFlashRatePhase(int r, int c) const { return m_cell[r][c].attribute.flash.ratePhase; };
int cellFlash2HzPhaseNumber(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.flash.phaseNumber; }; int cellFlash2HzPhaseNumber(int r, int c) const { return m_cell[r][c].attribute.flash.phase2HzShown; };
CharacterFragment cellCharacterFragment(int, int) const; CharacterFragment cellCharacterFragment(int r, int c) const { return m_cell[r][c].fragment; };
bool cellBoxed(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.display.boxingWindow; }; bool cellBoxed(int r, int c) const { return m_cell[r][c].attribute.display.boxingWindow; };
bool cellConceal(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.display.conceal; }; bool cellConceal(int r, int c) const { return m_cell[r][c].attribute.display.conceal; };
bool cellUnderlined(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.display.underlineSeparated && cellAtCharacterOrigin(r, c).character.set < 24; }; bool cellUnderlined(int r, int c) const { return cellCharacterSet(r, c) < 24 ? m_cell[r][c].attribute.display.underlineSeparated : false; };
bool cellBold(int r, int c) const { return m_cell[r][c].attribute.style.bold; };
bool cellItalic(int r, int c) const { return m_cell[r][c].attribute.style.italic; };
bool cellProportional(int r, int c) const { return m_cell[r][c].attribute.style.proportional; };
bool level1MosaicAttribute(int r, int c) const { return m_cell[r][c].level1Mosaic; }; bool level1MosaicAttribute(int r, int c) const { return m_cellLevel1Mosaic[r][c]; };
int level1CharSet(int r, int c) const { return m_cell[r][c].level1CharSet; }; int level1CharSet(int r, int c) const { return m_cellLevel1CharSet[r][c]; };
RowHeight rowHeight(int r) const { return m_rowHeight[r]; };
QColor fullScreenQColor() const { return m_finalFullScreenQColor; }; QColor fullScreenQColor() const { return m_finalFullScreenQColor; };
QColor fullRowQColor(int r) const { return m_fullRowQColor[r]; }; QColor fullRowQColor(int r) const { return m_fullRowQColor[r]; };
@@ -270,37 +83,196 @@ signals:
protected: protected:
inline void setFullScreenColour(int); inline void setFullScreenColour(int);
inline void setFullRowColour(int, int); inline void setFullRowColour(int, int);
textCell& cellAtCharacterOrigin(int, int);
int m_finalFullScreenColour, m_level; int m_finalFullScreenColour, m_level;
QColor m_finalFullScreenQColor; QColor m_finalFullScreenQColor;
int m_leftSidePanelColumns, m_rightSidePanelColumns; int m_leftSidePanelColumns, m_rightSidePanelColumns;
Level1Layer m_level1Layer;
std::vector<TextLayer *> m_textLayer;
const int m_foregroundRemap[8] = { 0, 0, 0, 8, 8, 16, 16, 16 }; const int m_foregroundRemap[8] = { 0, 0, 0, 8, 8, 16, 16, 16 };
const int m_backgroundRemap[8] = { 0, 8, 16, 8, 16, 8, 16, 24 }; const int m_backgroundRemap[8] = { 0, 8, 16, 8, 16, 8, 16, 24 };
private: private:
class Invocation;
enum ColourPart { Foreground, Background, FlashForeground }; enum ColourPart { Foreground, Background, FlashForeground };
QColor cellQColor(int, int, ColourPart); struct textCharacter {
unsigned char code=0x20;
int set=0;
int diacritical=0;
};
friend inline bool operator!=(const textCharacter &lhs, const textCharacter &rhs)
{
return lhs.code != rhs.code ||
lhs.set != rhs.set ||
lhs.diacritical != rhs.diacritical;
}
struct flashFunctions {
int mode=0;
int ratePhase=0;
int phase2HzShown=0;
};
struct displayAttributes {
bool doubleHeight=false;
bool doubleWidth=false;
bool boxingWindow=false;
bool conceal=false;
bool invert=false;
bool underlineSeparated=false;
};
struct fontStyle {
bool proportional=false;
bool bold=false;
bool italic=false;
};
friend inline bool operator!=(const displayAttributes &lhs, const displayAttributes &rhs)
{
return lhs.doubleHeight != rhs.doubleHeight ||
lhs.doubleWidth != rhs.doubleWidth ||
lhs.boxingWindow != rhs.boxingWindow ||
lhs.conceal != rhs.conceal ||
lhs.invert != rhs.invert ||
lhs.underlineSeparated != rhs.underlineSeparated;
}
struct textAttributes {
int foregroundCLUT=7;
int backgroundCLUT=0;
flashFunctions flash;
displayAttributes display;
fontStyle style;
};
friend inline bool operator!=(const textAttributes &lhs, const textAttributes &rhs)
{
return lhs.foregroundCLUT != rhs.foregroundCLUT ||
lhs.backgroundCLUT != rhs.backgroundCLUT ||
lhs.flash.mode != rhs.flash.mode ||
lhs.flash.ratePhase != rhs.flash.ratePhase ||
lhs.flash.phase2HzShown != rhs.flash.phase2HzShown ||
lhs.display != rhs.display ||
lhs.style.proportional != rhs.style.proportional ||
lhs.style.bold != rhs.style.bold ||
lhs.style.italic != rhs.style.italic;
}
struct textCell {
textCharacter character;
textAttributes attribute;
CharacterFragment fragment=NormalSize;
};
friend inline bool operator!=(const textCell &lhs, const textCell &rhs)
{
return lhs.character != rhs.character ||
lhs.attribute != rhs.attribute ||
lhs.fragment != rhs.fragment;
}
struct textPainter {
textAttributes attribute;
textCell result;
textCell rightHalfCell;
textCell bottomHalfCell[72];
int g0CharSet=0;
int g2CharSet=7;
int styleSpreadRows=0;
int setProportionalRows[72], clearProportionalRows[72];
int setBoldRows[72], clearBoldRows[72];
int setItalicRows[72], clearItalicRows[72];
};
const QMap<int, int> m_level1CharacterMap {
{ 0x00, 12 }, { 0x01, 15 }, { 0x02, 22 }, { 0x03, 16 }, { 0x04, 14 }, { 0x05, 19 }, { 0x06, 11 },
{ 0x08, 18 }, { 0x09, 15 }, { 0x0a, 22 }, { 0x0b, 16 }, { 0x0c, 14 }, { 0x0e, 11 },
{ 0x10, 12 }, { 0x11, 15 }, { 0x12, 22 }, { 0x13, 16 }, { 0x14, 14 }, { 0x15, 19 }, { 0x16, 23 },
{ 0x1d, 21 }, { 0x1f, 20 },
{ 0x20, 1 }, { 0x21, 15 }, { 0x22, 13 }, { 0x23, 17 }, { 0x24, 2 }, { 0x25, 3 }, { 0x26, 11 },
{ 0x36, 23 }, { 0x37, 4 },
{ 0x40, 12 }, { 0x44, 14 }, { 0x47, 5 },
{ 0x55, 6 }, { 0x57, 5 }
};
const QMap<int, int> m_g0CharacterMap {
{ 0x20, 1 }, { 0x24, 2 }, { 0x25, 3 },
{ 0x37, 4 },
{ 0x47, 5 },
{ 0x55, 6 }, { 0x57, 5 }
};
const QMap<int, int> m_g2CharacterMap {
{ 0x20, 8 }, { 0x24, 8 }, { 0x25, 8 },
{ 0x37, 9 },
{ 0x40, 10 }, { 0x44, 10 }, { 0x47, 10 },
{ 0x55, 10 }, { 0x57, 10 }
};
class Invocation
{
public:
Invocation();
X26TripletList *tripletList() const { return m_tripletList; };
void clear();
void setTripletList(X26TripletList *);
int startTripletNumber() const { return m_startTripletNumber; };
void setStartTripletNumber(int);
int endTripletNumber() const { return m_endTripletNumber; };
void setEndTripletNumber(int);
int originRow() const { return m_originRow; };
int originColumn() const { return m_originColumn; };
void setOrigin(int, int);
void buildMap(int);
QList<QPair<int, int>> charPositions() const { return m_characterMap.uniqueKeys(); };
QList<QPair<int, int>> attrPositions() const { return m_attributeMap.uniqueKeys(); };
QList<X26Triplet> charactersMappedAt(int r, int c) const { return m_characterMap.values(qMakePair(r, c)); };
QList<X26Triplet> attributesMappedAt(int r, int c) const { return m_attributeMap.values(qMakePair(r, c)); };
int rightMostColumn(int r) const { return m_rightMostColumn.value(r, -1); };
int fullScreenColour() const { return m_fullScreenCLUT; };
QList<X26Triplet> fullRowColoursMappedAt(int r) const { return m_fullRowCLUTMap.values(r); };
private:
X26TripletList *m_tripletList;
int m_startTripletNumber, m_endTripletNumber;
int m_originRow, m_originColumn;
// QPair is row and column
QMultiMap<QPair<int, int>, X26Triplet> m_characterMap;
QMultiMap<QPair<int, int>, X26Triplet> m_attributeMap;
QMap<int, int> m_rightMostColumn;
int m_fullScreenCLUT;
QMultiMap<int, X26Triplet> m_fullRowCLUTMap;
};
static int s_instances;
static textPainter s_blankPainter;
void decodeRow(int r);
QColor cellQColor(int, int, ColourPart);
textCell& cellAtCharacterOrigin(int, int);
void buildInvocationList(Invocation &, int);
textCharacter characterFromTriplets(const QList<X26Triplet>);
inline void rotateFlashMovement(flashFunctions &);
textCell m_cell[25][72];
bool m_refresh[25][72]; bool m_refresh[25][72];
textCell m_cell[25][72];
bool m_cellLevel1Mosaic[25][40];
int m_cellLevel1CharSet[25][40];
LevelOnePage* m_levelOnePage; LevelOnePage* m_levelOnePage;
int m_fullRowColour[25]; int m_fullRowColour[25];
QColor m_fullRowQColor[25]; QColor m_fullRowQColor[25];
}; QList<Invocation> m_invocations[3];
Invocation m_localEnhancements;
textPainter m_level1ActivePainter;
QList<textPainter> m_adapPassPainter[2];
int m_level1DefaultCharSet, m_level1SecondCharSet;
int m_defaultG0andG2, m_secondG0andG2;
static const QMap<int, int> g0CharacterMap { RowHeight m_rowHeight[25];
{ 0x00, 12 }, { 0x01, 15 }, { 0x02, 22 }, { 0x03, 16 }, { 0x04, 14 }, { 0x05, 19 }, { 0x06, 11 },
{ 0x08, 18 }, { 0x09, 15 }, { 0x0a, 22 }, { 0x0b, 16 }, { 0x0c, 14 }, { 0x0e, 11 },
{ 0x10, 12 }, { 0x11, 15 }, { 0x12, 22 }, { 0x13, 16 }, { 0x14, 14 }, { 0x15, 19 }, { 0x16, 23 },
{ 0x1d, 21 }, { 0x1f, 20 },
{ 0x20, 1 }, { 0x21, 15 }, { 0x22, 13 }, { 0x23, 17 }, { 0x24, 2 }, { 0x25, 3 }, { 0x26, 11 },
{ 0x36, 23 }, { 0x37, 4 },
{ 0x40, 12 }, { 0x44, 14 }, { 0x47, 5 },
{ 0x55, 6 }, { 0x57, 5 }
}; };
#endif #endif

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -528,15 +528,15 @@ static const QMap<QChar, char> keymapping[24] = {
// Native scan codes to toggle mosaic bits - different platforms have different scan codes! // Native scan codes to toggle mosaic bits - different platforms have different scan codes!
// Order is top left, top right, middle left, middle right, bottom left, bottom right, // Order is top left, top right, middle left, middle right, bottom left, bottom right,
// invert, set all, clear all // invert, set all, clear all, dither
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
static constexpr quint32 mosaicNativeScanCodes[9] = { static constexpr quint32 mosaicNativeScanCodes[10] = {
0x18, 0x19, 0x26, 0x27, 0x34, 0x35, 0x1b, 0x29, 0x36 0x18, 0x19, 0x26, 0x27, 0x34, 0x35, 0x1b, 0x29, 0x36, 0x28
}; };
#elif defined(Q_OS_WIN) #elif defined(Q_OS_WIN)
static constexpr quint32 mosaicNativeScanCodes[9] = { static constexpr quint32 mosaicNativeScanCodes[10] = {
0x10, 0x11, 0x1e, 0x1f, 0x2c, 0x2d, 0x13, 0x21, 0x2e 0x10, 0x11, 0x1e, 0x1f, 0x2c, 0x2d, 0x13, 0x21, 0x2e, 0x20
}; };
#else #else
#define QTTM_NONATIVESCANCODES #define QTTM_NONATIVESCANCODES

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -106,6 +106,8 @@ ToggleMosaicBitCommand::ToggleMosaicBitCommand(TeletextDocument *teletextDocumen
m_oldCharacter = teletextDocument->currentSubPage()->character(m_row, m_column); m_oldCharacter = teletextDocument->currentSubPage()->character(m_row, m_column);
if (bitToToggle == 0x20 || bitToToggle == 0x7f) if (bitToToggle == 0x20 || bitToToggle == 0x7f)
m_newCharacter = bitToToggle; m_newCharacter = bitToToggle;
else if (bitToToggle == 0x66)
m_newCharacter = (m_row & 1) ? 0x66 : 0x39;
else else
m_newCharacter = m_oldCharacter ^ bitToToggle; m_newCharacter = m_oldCharacter ^ bitToToggle;
@@ -413,8 +415,18 @@ PasteCommand::PasteCommand(TeletextDocument *teletextDocument, int pageCharSet,
if (m_selectionActive) { if (m_selectionActive) {
m_selectionCornerRow = m_teletextDocument->selectionCornerRow(); m_selectionCornerRow = m_teletextDocument->selectionCornerRow();
m_selectionCornerColumn = m_teletextDocument->selectionCornerColumn(); m_selectionCornerColumn = m_teletextDocument->selectionCornerColumn();
m_pasteTopRow = m_teletextDocument->selectionTopRow();
m_pasteBottomRow = m_teletextDocument->selectionBottomRow();
m_pasteLeftColumn = m_teletextDocument->selectionLeftColumn();
m_pasteRightColumn = m_teletextDocument->selectionRightColumn();
} else {
m_pasteTopRow = m_row;
m_pasteLeftColumn = m_column;
// m_pasteBottomRow and m_pasteRightColumn will be filled in later
// when the size of the clipboard data is known
} }
// Zero size here represents invalid or empty clipboard data
m_clipboardDataHeight = m_clipboardDataWidth = 0; m_clipboardDataHeight = m_clipboardDataWidth = 0;
// Try to get something from the clipboard // Try to get something from the clipboard
@@ -422,6 +434,7 @@ PasteCommand::PasteCommand(TeletextDocument *teletextDocument, int pageCharSet,
nativeData = mimeData->data("application/x-teletext"); nativeData = mimeData->data("application/x-teletext");
if (nativeData.size() > 2) { if (nativeData.size() > 2) {
// Native clipboard data: we put it there ourselves // Native clipboard data: we put it there ourselves
m_plainText = false;
m_clipboardDataHeight = nativeData.at(0); m_clipboardDataHeight = nativeData.at(0);
m_clipboardDataWidth = nativeData.at(1); m_clipboardDataWidth = nativeData.at(1);
@@ -429,25 +442,119 @@ PasteCommand::PasteCommand(TeletextDocument *teletextDocument, int pageCharSet,
if (m_clipboardDataHeight > 0 && m_clipboardDataWidth > 0 && m_clipboardDataHeight <= 25 && m_clipboardDataWidth <= 40 && nativeData.size() == m_clipboardDataHeight * m_clipboardDataWidth + 2) if (m_clipboardDataHeight > 0 && m_clipboardDataWidth > 0 && m_clipboardDataHeight <= 25 && m_clipboardDataWidth <= 40 && nativeData.size() == m_clipboardDataHeight * m_clipboardDataWidth + 2)
for (int r=0; r<m_clipboardDataHeight; r++) for (int r=0; r<m_clipboardDataHeight; r++)
m_pastingCharacters.append(nativeData.mid(2 + r * m_clipboardDataWidth, m_clipboardDataWidth)); m_pastingCharacters.append(nativeData.mid(2 + r * m_clipboardDataWidth, m_clipboardDataWidth));
else else {
// Invalidate // Invalidate
m_clipboardDataHeight = m_clipboardDataWidth = 0; m_clipboardDataHeight = m_clipboardDataWidth = 0;
return;
}
if (!m_selectionActive) {
m_pasteBottomRow = m_row + m_clipboardDataHeight - 1;
m_pasteRightColumn = m_column + m_clipboardDataWidth - 1;
}
} else if (mimeData->hasText()) { } else if (mimeData->hasText()) {
// Plain text // Plain text
m_plainText = true;
const int rightColumn = m_selectionActive ? m_pasteRightColumn : 39;
// Parse line-feeds in the clipboard data
QStringList plainTextData = mimeData->text().split(QRegExp("\n|\r\n|\r")); QStringList plainTextData = mimeData->text().split(QRegExp("\n|\r\n|\r"));
// "if" statement will be false if clipboard data is a single line of text
// that will fit from the cursor position
if (plainTextData.size() != 1 || m_pasteLeftColumn + plainTextData.at(0).size() - 1 > rightColumn) {
bool wrappingNeeded = false;
if (!m_selectionActive) {
// If selection is NOT active, use the full width of the page to paste.
// The second and subsequent lines will start at column 1, unless the
// cursor is explicitly on column 0.
if (m_pasteLeftColumn != 0)
m_pasteLeftColumn = 1;
// Check if first word in the first line will fit from the cursor position
bool firstWordFits = true;
const int firstSpace = plainTextData.at(0).indexOf(' ');
if (firstSpace == -1 && m_column + plainTextData.at(0).size() > 40)
firstWordFits = false; // Only one word in first line, and it won't fit
else if (m_column + firstSpace > 40)
firstWordFits = false; // First word in first line won't fit
// If the first word WILL fit at the cursor position, pad the first line
// to match the cursor position using null characters.
// In the QString null characters represent character cells in the
// pasting rectangle that won't overwrite what's on the page.
// If the first word WON'T fit, start pasting at the beginning of the next row.
if (firstWordFits)
plainTextData[0] = QString(m_column-m_pasteLeftColumn, QChar::Null) + plainTextData.at(0);
else if (m_pasteTopRow < 24)
m_pasteTopRow++;
else
return;
}
const int pasteWidth = rightColumn - m_pasteLeftColumn + 1;
// Find out if we need to word-wrap
for (int i=0; i<plainTextData.size(); i++)
if (plainTextData.at(i).size() > pasteWidth) {
wrappingNeeded = true;
break;
}
if (wrappingNeeded) {
QStringList wrappedText;
for (int i=0; i<plainTextData.size(); i++) {
// Split this line into individual words
QStringList lineWords = plainTextData.at(i).split(' ');
// If there's any words which are too long to fit,
// break them across multiple lines
for (int j=0; j<lineWords.size(); j++)
if (lineWords.at(j).size() > pasteWidth) {
lineWords.insert(j+1, lineWords.at(j).mid(pasteWidth));
lineWords[j].truncate(pasteWidth);
}
// Now reassemble the words into lines that will fit
QString currentLine = lineWords.at(0);
for (int j=1; j<lineWords.size(); j++)
if (currentLine.size() + 1 + lineWords.at(j).size() <= pasteWidth)
currentLine.append(' ' + lineWords.at(j));
else {
wrappedText.append(currentLine);
currentLine = lineWords.at(j);
}
wrappedText.append(currentLine);
}
plainTextData.swap(wrappedText);
}
}
m_clipboardDataHeight = plainTextData.size(); m_clipboardDataHeight = plainTextData.size();
m_clipboardDataWidth = 0; m_clipboardDataWidth = 0;
// Convert the unicode clipboard text into teletext bytes matching the current Level 1
// character set of this page
for (int r=0; r<m_clipboardDataHeight; r++) { for (int r=0; r<m_clipboardDataHeight; r++) {
m_pastingCharacters.append(QByteArray()); m_pastingCharacters.append(QByteArray());
for (int c=0; c<plainTextData.at(r).size(); c++) { for (int c=0; c<plainTextData.at(r).size(); c++) {
// Try to map the unicode character to the current Level 1 character set of this page
char convertedChar; char convertedChar;
const QChar charToConvert = plainTextData.at(r).at(c); const QChar charToConvert = plainTextData.at(r).at(c);
if (keymapping[pageCharSet].contains(charToConvert)) // Map a null character in the QString to 0xff (or -1)
// In the QByteArray 0xff bytes represent character cells in the pasting rectangle
// that won't overwrite what's on the page
if (charToConvert == QChar::Null)
convertedChar = -1;
else if (charToConvert >= 0x01 && charToConvert <= 0x1f)
convertedChar = ' ';
else if (keymapping[pageCharSet].contains(charToConvert))
// Remapped character or non-Latin character converted successfully // Remapped character or non-Latin character converted successfully
convertedChar = keymapping[pageCharSet].value(charToConvert); convertedChar = keymapping[pageCharSet].value(charToConvert);
else { else {
@@ -463,26 +570,19 @@ PasteCommand::PasteCommand(TeletextDocument *teletextDocument, int pageCharSet,
} }
m_clipboardDataWidth = qMax(m_pastingCharacters.at(r).size(), m_clipboardDataWidth); m_clipboardDataWidth = qMax(m_pastingCharacters.at(r).size(), m_clipboardDataWidth);
} }
// Pad short lines with spaces to make a box // Pad the end of short lines with spaces to make a box
for (int r=0; r<m_clipboardDataHeight; r++) for (int r=0; r<m_clipboardDataHeight; r++)
m_pastingCharacters[r] = m_pastingCharacters.at(r).leftJustified(m_clipboardDataWidth); m_pastingCharacters[r] = m_pastingCharacters.at(r).leftJustified(m_clipboardDataWidth);
if (!m_selectionActive) {
m_pasteBottomRow = m_pasteTopRow + m_clipboardDataHeight - 1;
m_pasteRightColumn = m_pasteLeftColumn + m_clipboardDataWidth - 1;
}
} }
if (m_clipboardDataWidth == 0 || m_clipboardDataHeight == 0) if (m_clipboardDataWidth == 0 || m_clipboardDataHeight == 0)
return; return;
if (m_selectionActive) {
m_pasteTopRow = m_teletextDocument->selectionTopRow();
m_pasteBottomRow = m_teletextDocument->selectionBottomRow();
m_pasteLeftColumn = m_teletextDocument->selectionLeftColumn();
m_pasteRightColumn = m_teletextDocument->selectionRightColumn();
} else {
m_pasteTopRow = m_row;
m_pasteBottomRow = m_row + m_clipboardDataHeight - 1;
m_pasteLeftColumn = m_column;
m_pasteRightColumn = m_column + m_clipboardDataWidth - 1;
}
// Store copy of the characters that we're about to overwrite // Store copy of the characters that we're about to overwrite
for (int r=m_pasteTopRow; r<=m_pasteBottomRow; r++) { for (int r=m_pasteTopRow; r<=m_pasteBottomRow; r++) {
QByteArray rowArray; QByteArray rowArray;
@@ -517,11 +617,21 @@ void PasteCommand::redo()
for (int c=m_pasteLeftColumn; c<=m_pasteRightColumn; c++) for (int c=m_pasteLeftColumn; c<=m_pasteRightColumn; c++)
// Guard against size of pasted block going beyond last line or column // Guard against size of pasted block going beyond last line or column
if (r < 25 && c < 40) { if (r < 25 && c < 40) {
m_teletextDocument->currentSubPage()->setCharacter(r, c, m_pastingCharacters[arrayR].at(arrayC++)); // Check for 0xff bytes using "-1"
// gcc complains about "comparision always true due to limited range"
if (m_pastingCharacters.at(arrayR).at(arrayC) != -1)
m_teletextDocument->currentSubPage()->setCharacter(r, c, m_pastingCharacters.at(arrayR).at(arrayC));
arrayC++;
// If paste area is wider than clipboard data, repeat the pattern // If paste area is wider than clipboard data, repeat the pattern
if (arrayC == m_clipboardDataWidth) // if it wasn't plain text
arrayC = 0; if (arrayC == m_clipboardDataWidth) {
if (!m_plainText)
arrayC = 0;
else
break;
}
} }
if (r < 25) if (r < 25)
@@ -529,8 +639,13 @@ void PasteCommand::redo()
arrayR++; arrayR++;
// If paste area is taller than clipboard data, repeat the pattern // If paste area is taller than clipboard data, repeat the pattern
if (arrayR == m_clipboardDataHeight) // if it wasn't plain text
arrayR = 0; if (arrayR == m_clipboardDataHeight) {
if (!m_plainText)
arrayR = 0;
else
break;
}
} }
if (m_selectionActive) { if (m_selectionActive) {
@@ -556,8 +671,11 @@ void PasteCommand::undo()
arrayC = 0; arrayC = 0;
for (int c=m_pasteLeftColumn; c<=m_pasteRightColumn; c++) for (int c=m_pasteLeftColumn; c<=m_pasteRightColumn; c++)
// Guard against size of pasted block going beyond last line or column // Guard against size of pasted block going beyond last line or column
if (r < 25 && c < 40) if (r < 25 && c < 40) {
m_teletextDocument->currentSubPage()->setCharacter(r, c, m_deletedCharacters[arrayR].at(arrayC++)); m_teletextDocument->currentSubPage()->setCharacter(r, c, m_deletedCharacters[arrayR].at(arrayC));
arrayC++;
}
if (r < 25) if (r < 25)
emit m_teletextDocument->contentsChange(r); emit m_teletextDocument->contentsChange(r);

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -179,7 +179,7 @@ private:
int m_pasteTopRow, m_pasteBottomRow, m_pasteLeftColumn, m_pasteRightColumn; int m_pasteTopRow, m_pasteBottomRow, m_pasteLeftColumn, m_pasteRightColumn;
int m_clipboardDataHeight, m_clipboardDataWidth; int m_clipboardDataHeight, m_clipboardDataWidth;
int m_selectionCornerRow, m_selectionCornerColumn; int m_selectionCornerRow, m_selectionCornerColumn;
bool m_selectionActive; bool m_selectionActive, m_plainText;
}; };
#endif // !QT_NO_CLIPBOARD #endif // !QT_NO_CLIPBOARD

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -297,7 +297,7 @@ bool LevelOnePage::packetExists(int packetNumber, int designationCode) const
return !isPaletteDefault(16, 31); return !isPaletteDefault(16, 31);
} }
if (designationCode == 4) if (designationCode == 4)
return !isPaletteDefault(0,15); return !isPaletteDefault(0, 15);
} }
return PageBase::packetExists(packetNumber, designationCode); return PageBase::packetExists(packetNumber, designationCode);
@@ -396,7 +396,7 @@ bool LevelOnePage::isPaletteDefault(int colour) const
bool LevelOnePage::isPaletteDefault(int fromColour, int toColour) const bool LevelOnePage::isPaletteDefault(int fromColour, int toColour) const
{ {
for (int i=fromColour; i<toColour; i++) for (int i=fromColour; i<=toColour; i++)
if (m_CLUT[i] != m_defaultCLUT[i]) if (m_CLUT[i] != m_defaultCLUT[i])
return false; return false;
@@ -412,7 +412,7 @@ int LevelOnePage::levelRequired() const
// TODO Check for X/28/1 for DCLUT for mode 1-3 DRCS characters - return 3 // TODO Check for X/28/1 for DCLUT for mode 1-3 DRCS characters - return 3
// Assume Level 2.5 if any X/28 page enhancements are present, otherwise assume Level 1 // Assume Level 2.5 if any X/28 page enhancements are present, otherwise assume Level 1
int levelSeen = (!isPaletteDefault(16,31) || m_leftSidePanelDisplayed || m_rightSidePanelDisplayed || m_defaultScreenColour !=0 || m_defaultRowColour !=0 || m_blackBackgroundSubst || m_colourTableRemap !=0 || m_defaultCharSet != 0 || m_secondCharSet != 0xf) ? 2 : 0; int levelSeen = (!isPaletteDefault(16, 31) || m_leftSidePanelDisplayed || m_rightSidePanelDisplayed || m_defaultScreenColour !=0 || m_defaultRowColour !=0 || m_blackBackgroundSubst || m_colourTableRemap !=0 || m_defaultCharSet != 0 || m_secondCharSet != 0xf) ? 2 : 0;
// If there's no X/26 triplets, exit here as Level 1 or 2.5 // If there's no X/26 triplets, exit here as Level 1 or 2.5
if (m_enhancements.isEmpty()) if (m_enhancements.isEmpty())

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -30,7 +30,7 @@ int main(int argc, char *argv[])
QApplication::setApplicationDisplayName(QApplication::applicationName()); QApplication::setApplicationDisplayName(QApplication::applicationName());
QApplication::setOrganizationName("gkmac.co.uk"); QApplication::setOrganizationName("gkmac.co.uk");
QApplication::setOrganizationDomain("gkmac.co.uk"); QApplication::setOrganizationDomain("gkmac.co.uk");
QApplication::setApplicationVersion("0.5.5-alpha"); QApplication::setApplicationVersion("0.6.1-alpha");
QCommandLineParser parser; QCommandLineParser parser;
parser.setApplicationDescription(QApplication::applicationName()); parser.setApplicationDescription(QApplication::applicationName());
parser.addHelpOption(); parser.addHelpOption();

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -96,7 +96,10 @@ void TeletextWidget::subPageSelected()
void TeletextWidget::refreshRow(int rowChanged) void TeletextWidget::refreshRow(int rowChanged)
{ {
m_pageDecode.decodeRow(rowChanged); Q_UNUSED(rowChanged);
// TODO trace signals where this is called so we can remove this
m_pageDecode.decodePage();
update(); update();
} }
@@ -181,8 +184,10 @@ void TeletextWidget::setShowControlCodes(bool showControlCodes)
void TeletextWidget::setControlBit(int bitNumber, bool active) void TeletextWidget::setControlBit(int bitNumber, bool active)
{ {
m_levelOnePage->setControlBit(bitNumber, active); m_levelOnePage->setControlBit(bitNumber, active);
if (bitNumber == 1 || bitNumber == 2) if (bitNumber == 1 || bitNumber == 2) {
m_pageDecode.decodePage(); m_pageDecode.decodePage();
m_pageRender.renderPage(true);
}
} }
void TeletextWidget::setDefaultCharSet(int newDefaultCharSet) void TeletextWidget::setDefaultCharSet(int newDefaultCharSet)
@@ -259,7 +264,7 @@ void TeletextWidget::keyPressEvent(QKeyEvent *event)
mappedKeyPress = 0x7f; mappedKeyPress = 0x7f;
if (m_pageDecode.level1MosaicAttribute(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn()) && (mappedKeyPress < 0x40 || mappedKeyPress > 0x5f)) { 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 // We're on a mosaic and a blast-through character was NOT pressed
if (event->key() >= Qt::Key_1 && event->key() <= Qt::Key_9 && event->modifiers() & Qt::KeypadModifier) { if (event->key() >= Qt::Key_0 && event->key() <= Qt::Key_9 && event->modifiers() & Qt::KeypadModifier) {
switch (event->key()) { switch (event->key()) {
case Qt::Key_7: case Qt::Key_7:
toggleCharacterBit(0x01); // Top left toggleCharacterBit(0x01); // Top left
@@ -288,6 +293,9 @@ void TeletextWidget::keyPressEvent(QKeyEvent *event)
case Qt::Key_3: case Qt::Key_3:
toggleCharacterBit(0x20); // Clear all toggleCharacterBit(0x20); // Clear all
break; break;
case Qt::Key_0:
toggleCharacterBit(0x66); // Dither
break;
} }
return; return;
} }
@@ -328,6 +336,9 @@ void TeletextWidget::keyPressEvent(QKeyEvent *event)
case mosaicNativeScanCodes[8]: case mosaicNativeScanCodes[8]:
toggleCharacterBit(0x20); // Clear all toggleCharacterBit(0x20); // Clear all
break; break;
case mosaicNativeScanCodes[9]:
toggleCharacterBit(0x66); // Dither
break;
} }
return; return;
} else } else
@@ -688,18 +699,13 @@ void LevelOneScene::toggleGrid(bool gridOn)
void LevelOneScene::hideGUIElements(bool hidden) void LevelOneScene::hideGUIElements(bool hidden)
{ {
if (hidden) { if (hidden) {
m_mainGridItemGroup->setVisible(false);
m_cursorRectItem->setVisible(false); m_cursorRectItem->setVisible(false);
m_selectionRectItem->setVisible(false); m_selectionRectItem->setVisible(false);
for (int i=0; i<32; i++)
if (m_sidePanelGridNeeded[i])
m_sidePanelGridItemGroup[i]->setVisible(false);
} else { } else {
if (static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->document()->selectionActive()) if (static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->document()->selectionActive())
m_selectionRectItem->setVisible(true); m_selectionRectItem->setVisible(true);
m_cursorRectItem->setVisible(true); m_cursorRectItem->setVisible(true);
toggleGrid(m_grid);
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -186,9 +186,6 @@ void MainWindow::exportPNG()
// Prepare widget image for extraction // Prepare widget image for extraction
m_textWidget->pauseFlash(true); m_textWidget->pauseFlash(true);
m_textScene->hideGUIElements(true); m_textScene->hideGUIElements(true);
bool reshowControlCodes = m_textWidget->showControlCodes();
if (reshowControlCodes)
m_textWidget->setShowControlCodes(false);
// Disable exporting in Mix mode as it corrupts the background // Disable exporting in Mix mode as it corrupts the background
bool reMix = m_textWidget->pageRender()->mix(); bool reMix = m_textWidget->pageRender()->mix();
if (reMix) { if (reMix) {
@@ -206,8 +203,6 @@ void MainWindow::exportPNG()
// Now we've extracted the image we can put the GUI things back // Now we've extracted the image we can put the GUI things back
m_textScene->hideGUIElements(false); m_textScene->hideGUIElements(false);
if (reshowControlCodes)
m_textWidget->setShowControlCodes(true);
if (reMix) { if (reMix) {
m_textWidget->setMix(true); m_textWidget->setMix(true);
m_textScene->setMix(true); m_textScene->setMix(true);
@@ -246,7 +241,7 @@ void MainWindow::about()
QMessageBox::about(this, tr("About"), QString("<b>%1</b><br>" QMessageBox::about(this, tr("About"), QString("<b>%1</b><br>"
"An open source Level 2.5 teletext page editor.<br>" "An open source Level 2.5 teletext page editor.<br>"
"<i>Version %2</i><br><br>" "<i>Version %2</i><br><br>"
"Copyright (C) 2020-2022 Gavin MacGregor<br><br>" "Copyright (C) 2020-2023 Gavin MacGregor<br><br>"
"Released under the GNU General Public License version 3<br>" "Released under the GNU General Public License version 3<br>"
"<a href=\"https://github.com/gkthemac/qteletextmaker\">https://github.com/gkthemac/qteletextmaker</a>").arg(QApplication::applicationDisplayName()).arg(QApplication::applicationVersion())); "<a href=\"https://github.com/gkthemac/qteletextmaker\">https://github.com/gkthemac/qteletextmaker</a>").arg(QApplication::applicationDisplayName()).arg(QApplication::applicationVersion()));
} }
@@ -376,9 +371,16 @@ void MainWindow::createActions()
setRecentFilesVisible(MainWindow::hasRecentFiles()); setRecentFilesVisible(MainWindow::hasRecentFiles());
m_exportAutoAct = fileMenu->addAction(tr("Export subpage..."));
m_exportAutoAct->setEnabled(false);
m_exportAutoAct->setShortcut(tr("Ctrl+E"));
m_exportAutoAct->setStatusTip("Export this subpage back to the imported file");
connect(fileMenu, &QMenu::aboutToShow, this, &MainWindow::updateExportAutoAction);
connect(m_exportAutoAct, &QAction::triggered, this, &MainWindow::exportAuto);
QAction *exportT42Act = fileMenu->addAction(tr("Export subpage as t42...")); QAction *exportT42Act = fileMenu->addAction(tr("Export subpage as t42..."));
exportT42Act->setStatusTip("Export this subpage as a t42 file"); exportT42Act->setStatusTip("Export this subpage as a t42 file");
connect(exportT42Act, &QAction::triggered, this, &MainWindow::exportT42); connect(exportT42Act, &QAction::triggered, this, [=]() { exportT42(false); });
QMenu *exportHashStringSubMenu = fileMenu->addMenu(tr("Export subpage to online editor")); QMenu *exportHashStringSubMenu = fileMenu->addMenu(tr("Export subpage to online editor"));
@@ -834,10 +836,10 @@ void MainWindow::createStatusBar()
statusBar()->addPermanentWidget(m_levelRadioButton[i]); statusBar()->addPermanentWidget(m_levelRadioButton[i]);
} }
m_levelRadioButton[0]->toggle(); m_levelRadioButton[0]->toggle();
connect(m_levelRadioButton[0], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setLevel(0); m_textWidget->update(); }); connect(m_levelRadioButton[0], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setLevel(0); m_textWidget->update(); m_paletteDockWidget->setLevel3p5Accepted(false); });
connect(m_levelRadioButton[1], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setLevel(1); m_textWidget->update(); }); connect(m_levelRadioButton[1], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setLevel(1); m_textWidget->update(); m_paletteDockWidget->setLevel3p5Accepted(false);});
connect(m_levelRadioButton[2], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setLevel(2); m_textWidget->update(); }); connect(m_levelRadioButton[2], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setLevel(2); m_textWidget->update(); m_paletteDockWidget->setLevel3p5Accepted(false);});
connect(m_levelRadioButton[3], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setLevel(3); m_textWidget->update(); }); connect(m_levelRadioButton[3], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setLevel(3); m_textWidget->update(); m_paletteDockWidget->setLevel3p5Accepted(true);});
statusBar()->showMessage(tr("Ready")); statusBar()->showMessage(tr("Ready"));
} }
@@ -939,14 +941,19 @@ void MainWindow::loadFile(const QString &fileName)
QApplication::setOverrideCursor(Qt::WaitCursor); QApplication::setOverrideCursor(Qt::WaitCursor);
if (fileInfo.suffix() == "t42") if (fileInfo.suffix() == "t42") {
importT42(&file, m_textWidget->document()); importT42(&file, m_textWidget->document());
else m_exportAutoFileName = fileName;
} else {
loadTTI(&file, m_textWidget->document()); loadTTI(&file, m_textWidget->document());
m_exportAutoFileName.clear();
}
levelSeen = m_textWidget->document()->levelRequired(); levelSeen = m_textWidget->document()->levelRequired();
m_levelRadioButton[levelSeen]->toggle(); m_levelRadioButton[levelSeen]->toggle();
m_textWidget->pageDecode()->setLevel(levelSeen); m_textWidget->pageDecode()->setLevel(levelSeen);
if (levelSeen == 3)
m_paletteDockWidget->setLevel3p5Accepted(true);
updatePageWidgets(); updatePageWidgets();
QApplication::restoreOverrideCursor(); QApplication::restoreOverrideCursor();
@@ -1026,6 +1033,18 @@ void MainWindow::updateRecentFileActions()
m_recentFileActs[i]->setVisible(false); m_recentFileActs[i]->setVisible(false);
} }
void MainWindow::updateExportAutoAction()
{
if (m_exportAutoFileName.isEmpty()) {
m_exportAutoAct->setText(tr("Export subpage..."));
m_exportAutoAct->setEnabled(false);
return;
}
m_exportAutoAct->setText(tr("Overwrite &%1").arg(MainWindow::strippedName(m_exportAutoFileName)));
m_exportAutoAct->setEnabled(true);
}
void MainWindow::openRecentFile() void MainWindow::openRecentFile()
{ {
if (const QAction *action = qobject_cast<const QAction *>(sender())) if (const QAction *action = qobject_cast<const QAction *>(sender()))
@@ -1056,16 +1075,30 @@ bool MainWindow::saveFile(const QString &fileName)
return true; return true;
} }
void MainWindow::exportT42() void MainWindow::exportAuto()
{
// Menu should be disabled if m_exportAutoFileName is empty, but just in case...
if (m_exportAutoFileName.isEmpty())
return;
exportT42(true);
}
void MainWindow::exportT42(bool fromAuto)
{ {
QString errorMessage; QString errorMessage;
QString exportFileName = m_curFile; QString exportFileName;
changeSuffixFromTTI(exportFileName, "t42"); if (fromAuto)
exportFileName = m_exportAutoFileName;
else {
exportFileName = m_curFile;
changeSuffixFromTTI(exportFileName, "t42");
exportFileName = QFileDialog::getSaveFileName(this, tr("Export t42"), exportFileName, "t42 stream (*.t42)"); exportFileName = QFileDialog::getSaveFileName(this, tr("Export t42"), exportFileName, "t42 stream (*.t42)");
if (exportFileName.isEmpty()) if (exportFileName.isEmpty())
return; return;
}
QApplication::setOverrideCursor(Qt::WaitCursor); QApplication::setOverrideCursor(Qt::WaitCursor);
QSaveFile file(exportFileName); QSaveFile file(exportFileName);
@@ -1077,8 +1110,15 @@ void MainWindow::exportT42()
errorMessage = tr("Cannot open file %1 for writing:\n%2.").arg(QDir::toNativeSeparators(exportFileName), file.errorString()); errorMessage = tr("Cannot open file %1 for writing:\n%2.").arg(QDir::toNativeSeparators(exportFileName), file.errorString());
QApplication::restoreOverrideCursor(); QApplication::restoreOverrideCursor();
if (!errorMessage.isEmpty()) if (!errorMessage.isEmpty()) {
QMessageBox::warning(this, QApplication::applicationDisplayName(), errorMessage); QMessageBox::warning(this, QApplication::applicationDisplayName(), errorMessage);
return;
}
MainWindow::prependToRecentFiles(exportFileName);
m_exportAutoFileName = exportFileName;
statusBar()->showMessage(tr("File exported"), 2000);
} }
void MainWindow::exportM29() void MainWindow::exportM29()

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -60,12 +60,14 @@ private slots:
bool save(); bool save();
bool saveAs(); bool saveAs();
void reload(); void reload();
void exportT42(); void exportAuto();
void exportT42(bool);
void exportZXNet(); void exportZXNet();
void exportEditTF(); void exportEditTF();
void exportPNG(); void exportPNG();
void exportM29(); void exportM29();
void updateRecentFileActions(); void updateRecentFileActions();
void updateExportAutoAction();
void openRecentFile(); void openRecentFile();
void about(); void about();
void updatePageWidgets(); void updatePageWidgets();
@@ -121,6 +123,7 @@ private:
QAction *m_recentFileActs[m_MaxRecentFiles]; QAction *m_recentFileActs[m_MaxRecentFiles];
QAction *m_recentFileSeparator; QAction *m_recentFileSeparator;
QAction *m_recentFileSubMenuAct; QAction *m_recentFileSubMenuAct;
QAction *m_exportAutoAct;
QAction *m_deleteSubPageAction; QAction *m_deleteSubPageAction;
QAction *m_borderActs[3]; QAction *m_borderActs[3];
QAction *m_aspectRatioActs[4]; QAction *m_aspectRatioActs[4];
@@ -131,7 +134,7 @@ private:
QPushButton *m_insertModePushButton; QPushButton *m_insertModePushButton;
QRadioButton *m_levelRadioButton[4]; QRadioButton *m_levelRadioButton[4];
QString m_curFile; QString m_curFile, m_exportAutoFileName;
bool m_isUntitled; bool m_isUntitled;
}; };

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -39,6 +39,9 @@ PaletteDockWidget::PaletteDockWidget(TeletextWidget *parent): QDockWidget(parent
m_parentMainWidget = parent; m_parentMainWidget = parent;
m_level3p5Accepted = false; // true when Level 3.5 radio button is checked in main window
m_level3p5Seen = false; // true when CLUT 0 or 1 is redefined
this->setObjectName("PaletteDockWidget"); this->setObjectName("PaletteDockWidget");
this->setWindowTitle("Palette"); this->setWindowTitle("Palette");
for (int c=0; c<=7; c++) for (int c=0; c<=7; c++)
@@ -57,6 +60,9 @@ PaletteDockWidget::PaletteDockWidget(TeletextWidget *parent): QDockWidget(parent
connect(m_colourButton[i], &QAbstractButton::clicked, [=]() { selectColour(i); }); connect(m_colourButton[i], &QAbstractButton::clicked, [=]() { selectColour(i); });
} }
} }
updateLevel3p5Cursor();
m_showHexValuesCheckBox = new QCheckBox(tr("Show colour hex values")); m_showHexValuesCheckBox = new QCheckBox(tr("Show colour hex values"));
paletteGridLayout->addWidget(m_showHexValuesCheckBox, 5, 1, 1, 8); paletteGridLayout->addWidget(m_showHexValuesCheckBox, 5, 1, 1, 8);
connect(m_showHexValuesCheckBox, &QCheckBox::stateChanged, this, &PaletteDockWidget::updateAllColourButtons); connect(m_showHexValuesCheckBox, &QCheckBox::stateChanged, this, &PaletteDockWidget::updateAllColourButtons);
@@ -70,26 +76,34 @@ void PaletteDockWidget::updateColourButton(int colourIndex)
{ {
if (colourIndex == 8) if (colourIndex == 8)
return; return;
QString colourString = QString("%1").arg(m_parentMainWidget->document()->currentSubPage()->CLUT(colourIndex), 3, 16, QChar('0')); QString colourString = QString("%1").arg(m_parentMainWidget->document()->currentSubPage()->CLUT(colourIndex), 3, 16, QChar('0'));
if (m_showHexValuesCheckBox->isChecked()) if (m_showHexValuesCheckBox->isChecked())
m_colourButton[colourIndex]->setText(colourString); m_colourButton[colourIndex]->setText(colourString);
else else
m_colourButton[colourIndex]->setText(nullptr); m_colourButton[colourIndex]->setText(nullptr);
// Set text itself to black or white so it can be seen over background colour - http://alienryderflex.com/hsp.html
int r = m_parentMainWidget->document()->currentSubPage()->CLUT(colourIndex) >> 8; int r = m_parentMainWidget->document()->currentSubPage()->CLUT(colourIndex) >> 8;
int g = (m_parentMainWidget->document()->currentSubPage()->CLUT(colourIndex) >> 4) & 0xf; int g = (m_parentMainWidget->document()->currentSubPage()->CLUT(colourIndex) >> 4) & 0xf;
int b = m_parentMainWidget->document()->currentSubPage()->CLUT(colourIndex) & 0xf; int b = m_parentMainWidget->document()->currentSubPage()->CLUT(colourIndex) & 0xf;
// Set text itself to black or white so it can be seen over background colour - http://alienryderflex.com/hsp.html
char blackOrWhite = (sqrt(r*r*0.299 + g*g*0.587 + b*b*0.114) > 7.647) ? '0' : 'f'; char blackOrWhite = (sqrt(r*r*0.299 + g*g*0.587 + b*b*0.114) > 7.647) ? '0' : 'f';
QString qss = QString("background-color: #%1; color: #%2%2%2; border: none").arg(colourString).arg(blackOrWhite); QString qss = QString("background-color: #%1; color: #%2%2%2; border: none").arg(colourString).arg(blackOrWhite);
m_colourButton[colourIndex]->setStyleSheet(qss); m_colourButton[colourIndex]->setStyleSheet(qss);
if (m_parentMainWidget->document()->currentSubPage()->isPaletteDefault(colourIndex)) if (m_parentMainWidget->document()->currentSubPage()->isPaletteDefault(colourIndex)) {
// Default colour was set, disable Reset button if all colours in row are default as well // Default colour was set, disable Reset button if all colours in a CLUT row are default as well
m_resetButton[colourIndex>>3]->setEnabled(!m_parentMainWidget->document()->currentSubPage()->isPaletteDefault(colourIndex & 0x18, colourIndex | 0x07)); m_resetButton[colourIndex >> 3]->setEnabled(!m_parentMainWidget->document()->currentSubPage()->isPaletteDefault(colourIndex & 0x18, colourIndex | 0x07));
else // Check if CLUTs 0 and 1 are all default if necessary
m_resetButton[colourIndex>>3]->setEnabled(true); if (colourIndex < 16 && m_parentMainWidget->document()->currentSubPage()->isPaletteDefault(0, 15))
setLevel3p5Seen(false);
} else {
// Custom colour was set, enable Reset button for that CLUT
m_resetButton[colourIndex >> 3]->setEnabled(true);
if (colourIndex < 16)
setLevel3p5Seen(true);
}
} }
void PaletteDockWidget::updateAllColourButtons() void PaletteDockWidget::updateAllColourButtons()
@@ -98,6 +112,41 @@ void PaletteDockWidget::updateAllColourButtons()
updateColourButton(i); updateColourButton(i);
} }
void PaletteDockWidget::setLevel3p5Accepted(bool b)
{
if (b == m_level3p5Accepted)
return;
m_level3p5Accepted = b;
updateLevel3p5Cursor();
}
void PaletteDockWidget::setLevel3p5Seen(bool b)
{
if (b == m_level3p5Seen)
return;
m_level3p5Seen = b;
updateLevel3p5Cursor();
}
void PaletteDockWidget::updateLevel3p5Cursor()
{
const Qt::CursorShape cursor = (m_level3p5Accepted || m_level3p5Seen) ? Qt::ArrowCursor : Qt::ForbiddenCursor;
if (m_colourButton[0]->cursor() == cursor)
return;
for (int i=0; i<16; i++) {
if (i == 8)
continue;
m_colourButton[i]->setCursor(cursor);
}
}
void PaletteDockWidget::showEvent(QShowEvent *event) void PaletteDockWidget::showEvent(QShowEvent *event)
{ {
Q_UNUSED(event); Q_UNUSED(event);
@@ -106,6 +155,9 @@ void PaletteDockWidget::showEvent(QShowEvent *event)
void PaletteDockWidget::selectColour(int colourIndex) void PaletteDockWidget::selectColour(int colourIndex)
{ {
if (colourIndex < 16 && !m_level3p5Accepted && !m_level3p5Seen)
return;
const QColor newColour = QColorDialog::getColor(m_parentMainWidget->document()->currentSubPage()->CLUTtoQColor(colourIndex), this, "Select Colour"); const QColor newColour = QColorDialog::getColor(m_parentMainWidget->document()->currentSubPage()->CLUTtoQColor(colourIndex), this, "Select Colour");
if (newColour.isValid()) if (newColour.isValid())

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -36,18 +36,22 @@ public:
public slots: public slots:
void updateColourButton(int); void updateColourButton(int);
void setLevel3p5Accepted(bool);
protected: protected:
void showEvent(QShowEvent *); void showEvent(QShowEvent *);
private slots: private slots:
void selectColour(int); void selectColour(int);
void setLevel3p5Seen(bool);
void updateLevel3p5Cursor();
private: private:
void resetCLUT(int); void resetCLUT(int);
QPushButton *m_colourButton[32], *m_resetButton[4]; QPushButton *m_colourButton[32], *m_resetButton[4];
QCheckBox *m_showHexValuesCheckBox; QCheckBox *m_showHexValuesCheckBox;
bool m_level3p5Accepted, m_level3p5Seen;
TeletextWidget *m_parentMainWidget; TeletextWidget *m_parentMainWidget;
}; };

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -55,9 +55,11 @@ TeletextPageRender::TeletextPageRender()
m_showControlCodes = false; m_showControlCodes = false;
m_flashBuffersHz = 0; m_flashBuffersHz = 0;
for (int r=0; r<25; r++) for (int r=0; r<25; r++) {
m_flashingRow[r] = 0;
for (int c=0; c<40; c++) for (int c=0; c<40; c++)
m_controlCodeCache[r][c] = 0x7f; m_controlCodeCache[r][c] = 0x7f;
}
} }
TeletextPageRender::~TeletextPageRender() TeletextPageRender::~TeletextPageRender()
@@ -71,6 +73,39 @@ void TeletextPageRender::setDecoder(TeletextPageDecode *decoder)
m_decoder = decoder; m_decoder = decoder;
} }
inline void TeletextPageRender::drawFromBitmap(QPainter &pixmapPainter, int r, int c, const QBitmap bitmap, TeletextPageDecode::CharacterFragment characterFragment)
{
switch (characterFragment) {
case TeletextPageDecode::NormalSize:
pixmapPainter.drawPixmap(c*12, r*10, bitmap);
break;
case TeletextPageDecode::DoubleHeightTopHalf:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, bitmap, 0, 0, 12, 5);
break;
case TeletextPageDecode::DoubleHeightBottomHalf:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, bitmap, 0, 5, 12, 5);
break;
case TeletextPageDecode::DoubleWidthLeftHalf:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, bitmap, 0, 0, 6, 10);
break;
case TeletextPageDecode::DoubleWidthRightHalf:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, bitmap, 6, 0, 6, 10);
break;
case TeletextPageDecode::DoubleSizeTopLeftQuarter:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, bitmap, 0, 0, 6, 5);
break;
case TeletextPageDecode::DoubleSizeTopRightQuarter:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, bitmap, 6, 0, 6, 5);
break;
case TeletextPageDecode::DoubleSizeBottomLeftQuarter:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, bitmap, 0, 5, 6, 5);
break;
case TeletextPageDecode::DoubleSizeBottomRightQuarter:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, bitmap, 6, 5, 6, 5);
break;
}
}
inline void TeletextPageRender::drawFromFontBitmap(QPainter &pixmapPainter, int r, int c, unsigned char characterCode, int characterSet, TeletextPageDecode::CharacterFragment characterFragment) inline void TeletextPageRender::drawFromFontBitmap(QPainter &pixmapPainter, int r, int c, unsigned char characterCode, int characterSet, TeletextPageDecode::CharacterFragment characterFragment)
{ {
switch (characterFragment) { switch (characterFragment) {
@@ -150,6 +185,8 @@ inline void TeletextPageRender::drawCharacter(QPainter &pixmapPainter, int r, in
pixmapPainter.fillRect(c*12, r*10, 12, 10, pixmapPainter.background().color()); pixmapPainter.fillRect(c*12, r*10, 12, 10, pixmapPainter.background().color());
else if (characterCode == 0x7f && characterSet == 24) else if (characterCode == 0x7f && characterSet == 24)
pixmapPainter.fillRect(c*12, r*10, 12, 10, pixmapPainter.pen().color()); pixmapPainter.fillRect(c*12, r*10, 12, 10, pixmapPainter.pen().color());
else if ((m_decoder->cellBold(r, c) || m_decoder->cellItalic(r, c)) && characterSet < 24)
drawBoldOrItalicCharacter(pixmapPainter, r, c, characterCode, characterSet, characterFragment);
else else
drawFromFontBitmap(pixmapPainter, r, c, characterCode, characterSet, characterFragment); drawFromFontBitmap(pixmapPainter, r, c, characterCode, characterSet, characterFragment);
@@ -180,226 +217,225 @@ inline void TeletextPageRender::drawCharacter(QPainter &pixmapPainter, int r, in
pixmapPainter.setCompositionMode(QPainter::CompositionMode_SourceOver); pixmapPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
} }
void TeletextPageRender::renderPage(bool force) inline void TeletextPageRender::drawBoldOrItalicCharacter(QPainter &pixmapPainter, int r, int c, unsigned char characterCode, int characterSet, TeletextPageDecode::CharacterFragment characterFragment)
{ {
QPainter pixmapPainter[6]; QBitmap bitmap = QBitmap(12, 10);
int previousFlashBuffersHz = m_flashBuffersHz; QPainter bitmapPainter;
// If force-rendering (such as showing a new or different subpage) then // TODO italic glyph-making is VERY slow!
// we don't render flashing cells initially as it greatly speeds things up if (m_decoder->cellItalic(r, c)) {
// when rendering on top of an already flashing page. bitmap.clear();
// At the end of this method, if flashing cells are present then this method
// recursively calls itself with force=false to render the flashing cells. bitmapPainter.begin(&bitmap);
if (force) { bitmapPainter.setBackgroundMode(Qt::OpaqueMode);
m_flashBuffersHz = 0; bitmapPainter.drawPixmap(1, 0, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10, 11, 3);
previousFlashBuffersHz = 0; bitmapPainter.drawPixmap(0, 3, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10+3, 12, 3);
emit flashChanged(0); bitmapPainter.drawPixmap(0, 6, *m_fontBitmap.rawBitmap(), (characterCode-32)*12+1, characterSet*10+6, 11, 4);
bitmapPainter.end();
} else
bitmap = m_fontBitmap.rawBitmap()->copy((characterCode-32)*12, characterSet*10, 12, 10);
if (m_decoder->cellBold(r, c)) {
QBitmap boldeningBitmap;
boldeningBitmap = bitmap.copy();
bitmapPainter.begin(&bitmap);
// No idea why we need this setPen workaround when character is made italic first?!
if (!m_decoder->cellItalic(r, c))
bitmapPainter.setPen(Qt::color0);
bitmapPainter.drawPixmap(1, 0, boldeningBitmap);
bitmapPainter.end();
} }
drawFromBitmap(pixmapPainter, r, c, bitmap, characterFragment);
pixmapPainter[0].begin(m_pagePixmap[0]);
pixmapPainter[0].setBackgroundMode(Qt::OpaqueMode);
if (m_flashBuffersHz != 0) {
pixmapPainter[3].begin(m_pagePixmap[3]);
pixmapPainter[3].setBackgroundMode(Qt::OpaqueMode);
if (m_flashBuffersHz == 2) {
pixmapPainter[1].begin(m_pagePixmap[1]);
pixmapPainter[1].setBackgroundMode(Qt::OpaqueMode);
pixmapPainter[2].begin(m_pagePixmap[2]);
pixmapPainter[2].setBackgroundMode(Qt::OpaqueMode);
pixmapPainter[4].begin(m_pagePixmap[4]);
pixmapPainter[4].setBackgroundMode(Qt::OpaqueMode);
pixmapPainter[5].begin(m_pagePixmap[5]);
pixmapPainter[5].setBackgroundMode(Qt::OpaqueMode);
}
}
for (int r=0; r<25; r++)
for (int c=0; c<72; c++) {
bool controlCodeChanged = false;
// Ensure that shown control codes are refreshed
if (m_showControlCodes && c < 40 && (m_controlCodeCache[r][c] != 0x7f || m_decoder->teletextPage()->character(r, c) < 0x20)) {
controlCodeChanged = m_controlCodeCache[r][c] != m_decoder->teletextPage()->character(r, c);
if (controlCodeChanged) {
if (m_decoder->teletextPage()->character(r, c) < 0x20)
m_controlCodeCache[r][c] = m_decoder->teletextPage()->character(r, c);
else
m_controlCodeCache[r][c] = 0x7f;
}
}
if (m_decoder->refresh(r, c) || force || controlCodeChanged) {
unsigned char characterCode;
int characterSet, characterDiacritical;
if (!m_reveal && m_decoder->cellConceal(r, c)) {
characterCode = 0x20;
characterSet = 0;
characterDiacritical = 0;
} else {
characterCode = m_decoder->cellCharacterCode(r, c);
characterSet = m_decoder->cellCharacterSet(r, c);
characterDiacritical = m_decoder->cellCharacterDiacritical(r, c);
}
// QSet::insert won't insert a duplicate value already in the set
// QSet::remove doesn't mind if we try to remove a value that's not there
if (m_flashBuffersHz == 0 && m_decoder->cellFlashMode(r, c) != 0) {
if (m_decoder->cellFlashRatePhase(r, c) == 0)
m_flash1HzCells.insert(qMakePair(r, c));
else
m_flash2HzCells.insert(qMakePair(r, c));
if (!force)
updateFlashBuffers();
} else if (m_decoder->cellFlashMode(r, c) == 0) {
m_flash1HzCells.remove(qMakePair(r, c));
m_flash2HzCells.remove(qMakePair(r, c));
if (!force)
updateFlashBuffers();
} else if (m_decoder->cellFlashRatePhase(r, c) == 0) {
m_flash1HzCells.insert(qMakePair(r, c));
m_flash2HzCells.remove(qMakePair(r, c));
if (!force)
updateFlashBuffers();
} else {
m_flash1HzCells.remove(qMakePair(r, c));
m_flash2HzCells.insert(qMakePair(r, c));
if (!force)
updateFlashBuffers();
}
// If flash rate has gone up, prepare painters for the other buffers
if (m_flashBuffersHz > previousFlashBuffersHz) {
if (previousFlashBuffersHz == 0) {
pixmapPainter[3].begin(m_pagePixmap[3]);
pixmapPainter[3].setBackgroundMode(Qt::OpaqueMode);
}
if (m_flashBuffersHz == 2) {
pixmapPainter[1].begin(m_pagePixmap[1]);
pixmapPainter[1].setBackgroundMode(Qt::OpaqueMode);
pixmapPainter[2].begin(m_pagePixmap[2]);
pixmapPainter[2].setBackgroundMode(Qt::OpaqueMode);
pixmapPainter[4].begin(m_pagePixmap[4]);
pixmapPainter[4].setBackgroundMode(Qt::OpaqueMode);
pixmapPainter[5].begin(m_pagePixmap[5]);
pixmapPainter[5].setBackgroundMode(Qt::OpaqueMode);
}
previousFlashBuffersHz = m_flashBuffersHz;
}
// If flash rate has gone down, end the painters so we don't crash
// if the pixmaps get copied due to the flash rate going up again
if (m_flashBuffersHz < previousFlashBuffersHz) {
if (previousFlashBuffersHz == 2) {
pixmapPainter[1].end();
pixmapPainter[2].end();
pixmapPainter[4].end();
pixmapPainter[5].end();
}
if (m_flashBuffersHz == 0)
pixmapPainter[3].end();
previousFlashBuffersHz = m_flashBuffersHz;
}
if (m_flashBuffersHz == 0) {
pixmapPainter[0].setPen(m_decoder->cellForegroundQColor(r, c));
if (!m_mix || m_decoder->cellBoxed(r, c))
pixmapPainter[0].setBackground(m_decoder->cellBackgroundQColor(r, c));
else
pixmapPainter[0].setBackground(Qt::transparent);
drawCharacter(pixmapPainter[0], r, c, characterCode, characterSet, characterDiacritical, m_decoder->cellCharacterFragment(r, c));
} else {
for (int i=0; i<6; i++) {
if (m_flashBuffersHz == 1 && (i == 1 || i == 2 || i == 4 || i == 5))
continue;
bool phaseOn;
if (m_decoder->cellFlashRatePhase(r, c) == 0)
phaseOn = (i < 3) ^ (m_decoder->cellFlashMode(r, c) == 2);
else
phaseOn = ((i == m_decoder->cellFlash2HzPhaseNumber(r, c)-1) || (i == m_decoder->cellFlash2HzPhaseNumber(r, c)+2)) ^ (m_decoder->cellFlashMode(r, c) == 2);
if (!m_mix || m_decoder->cellBoxed(r, c))
pixmapPainter[i].setBackground(m_decoder->cellBackgroundQColor(r, c));
else
pixmapPainter[i].setBackground(Qt::transparent);
if (m_decoder->cellFlashMode(r, c) == 3 && !phaseOn)
pixmapPainter[i].setPen(m_decoder->cellFlashForegroundQColor(r, c));
else
pixmapPainter[i].setPen(m_decoder->cellForegroundQColor(r, c));
if ((m_decoder->cellFlashMode(r, c) == 1 || m_decoder->cellFlashMode(r, c) == 2) && !phaseOn)
// Character 0x00 draws space without underline
drawCharacter(pixmapPainter[i], r, c, 0x00, 0, 0, m_decoder->cellCharacterFragment(r, c));
else
drawCharacter(pixmapPainter[i], r, c, characterCode, characterSet, characterDiacritical, m_decoder->cellCharacterFragment(r, c));
}
}
if (m_showControlCodes && c < 40 && m_decoder->teletextPage()->character(r, c) < 0x20) {
pixmapPainter[0].setBackground(QColor(0, 0, 0, 128));
pixmapPainter[0].setPen(QColor(255, 255, 255, 224));
pixmapPainter[0].drawPixmap(c*12, r*10, *m_fontBitmap.rawBitmap(), (m_decoder->teletextPage()->character(r, c)+32)*12, 250, 12, 10);
if (m_flashBuffersHz == 1) {
pixmapPainter[3].setBackground(QColor(0, 0, 0, 128));
pixmapPainter[3].setPen(QColor(255, 255, 255, 224));
pixmapPainter[3].drawPixmap(c*12, r*10, *m_fontBitmap.rawBitmap(), (m_decoder->teletextPage()->character(r, c)+32)*12, 250, 12, 10);
} else if (m_flashBuffersHz == 2)
for (int i=1; i<6; i++) {
pixmapPainter[i].setBackground(QColor(0, 0, 0, 128));
pixmapPainter[i].setPen(QColor(255, 255, 255, 224));
pixmapPainter[i].drawPixmap(c*12, r*10, *m_fontBitmap.rawBitmap(), (m_decoder->teletextPage()->character(r, c)+32)*12, 250, 12, 10);
}
}
if (force && m_decoder->cellFlashMode(r, c) != 0)
m_decoder->setRefresh(r, c, true);
else
m_decoder->setRefresh(r, c, false);
}
}
pixmapPainter[0].end();
if (m_flashBuffersHz != 0) {
pixmapPainter[3].end();
if (m_flashBuffersHz == 2) {
pixmapPainter[1].end();
pixmapPainter[2].end();
pixmapPainter[4].end();
pixmapPainter[5].end();
}
}
if (force && (!m_flash1HzCells.isEmpty() || !m_flash2HzCells.isEmpty()))
renderPage();
} }
void TeletextPageRender::updateFlashBuffers() void TeletextPageRender::renderPage(bool force)
{ {
int highestFlashHz; for (int r=0; r<25; r++)
renderRow(r, 0, force);
}
if (!m_flash2HzCells.isEmpty()) void TeletextPageRender::renderRow(int r, int ph, bool force)
highestFlashHz = 2; {
else QPainter pixmapPainter;
highestFlashHz = !m_flash1HzCells.isEmpty(); int flashingRow = 0;
bool rowRefreshed = false;
if (highestFlashHz == m_flashBuffersHz) pixmapPainter.begin(m_pagePixmap[ph]);
return; pixmapPainter.setBackgroundMode(Qt::OpaqueMode);
if (highestFlashHz > m_flashBuffersHz) { for (int c=0; c<72; c++) {
if (m_flashBuffersHz == 0) bool controlCodeChanged = false;
*m_pagePixmap[3] = m_pagePixmap[0]->copy();
if (highestFlashHz == 2) { // Ensure that shown control codes are refreshed
*m_pagePixmap[1] = m_pagePixmap[0]->copy(); if (ph == 0 && m_showControlCodes && c < 40 && (m_controlCodeCache[r][c] != 0x7f || m_decoder->teletextPage()->character(r, c) < 0x20)) {
*m_pagePixmap[2] = m_pagePixmap[0]->copy(); controlCodeChanged = m_controlCodeCache[r][c] != m_decoder->teletextPage()->character(r, c);
*m_pagePixmap[4] = m_pagePixmap[3]->copy(); if (controlCodeChanged) {
*m_pagePixmap[5] = m_pagePixmap[3]->copy(); if (m_decoder->teletextPage()->character(r, c) < 0x20)
m_controlCodeCache[r][c] = m_decoder->teletextPage()->character(r, c);
else
m_controlCodeCache[r][c] = 0x7f;
}
}
if (ph == 0) {
if (m_decoder->cellFlashMode(r, c) != 0)
flashingRow = (m_decoder->cellFlashRatePhase(r, c) == 0) ? 1 : 2;
} else
force = m_decoder->cellFlashMode(r, c) != 0;
// If drawing into a flash pixmap buffer, "force" is set on a flashing cell only
// and since the refresh and controlCodeChanged variables will be false at this point
// only flashing cells will be drawn
if (m_decoder->refresh(r, c) || force || controlCodeChanged) {
unsigned char characterCode;
int characterSet, characterDiacritical;
rowRefreshed = true;
if (!m_reveal && m_decoder->cellConceal(r, c)) {
characterCode = 0x20;
characterSet = 0;
characterDiacritical = 0;
} else {
characterCode = m_decoder->cellCharacterCode(r, c);
characterSet = m_decoder->cellCharacterSet(r, c);
characterDiacritical = m_decoder->cellCharacterDiacritical(r, c);
}
if (m_decoder->cellFlashMode(r, c) == 0)
pixmapPainter.setPen(m_decoder->cellForegroundQColor(r, c));
else {
// Flashing cell, decide if phase in this cycle is on or off
bool phaseOn;
if (m_decoder->cellFlashRatePhase(r, c) == 0)
phaseOn = (ph < 3) ^ (m_decoder->cellFlashMode(r, c) == 2);
else
phaseOn = ((ph == m_decoder->cellFlash2HzPhaseNumber(r, c)-1) || (ph == m_decoder->cellFlash2HzPhaseNumber(r, c)+2)) ^ (m_decoder->cellFlashMode(r, c) == 2);
// If flashing to adjacent CLUT select the appropriate foreground colour
if (m_decoder->cellFlashMode(r, c) == 3 && !phaseOn)
pixmapPainter.setPen(m_decoder->cellFlashForegroundQColor(r, c));
else
pixmapPainter.setPen(m_decoder->cellForegroundQColor(r, c));
// If flashing mode is Normal or Invert, draw a space instead of a character on phase
if ((m_decoder->cellFlashMode(r, c) == 1 || m_decoder->cellFlashMode(r, c) == 2) && !phaseOn) {
// Character 0x00 draws space without underline
characterCode = 0x00;
characterSet = 0;
characterDiacritical = 0;
}
}
if (!m_mix || m_decoder->cellBoxed(r, c))
pixmapPainter.setBackground(m_decoder->cellBackgroundQColor(r, c));
else
pixmapPainter.setBackground(Qt::transparent);
drawCharacter(pixmapPainter, r, c, characterCode, characterSet, characterDiacritical, m_decoder->cellCharacterFragment(r, c));
if (m_showControlCodes && c < 40 && m_decoder->teletextPage()->character(r, c) < 0x20) {
pixmapPainter.setBackground(QColor(0, 0, 0, 128));
pixmapPainter.setPen(QColor(255, 255, 255, 224));
pixmapPainter.drawPixmap(c*12, r*10, *m_fontBitmap.rawBitmap(), (m_decoder->teletextPage()->character(r, c)+32)*12, 250, 12, 10);
}
} }
} }
m_flashBuffersHz = highestFlashHz; pixmapPainter.end();
if (ph != 0)
return;
if (flashingRow == 3)
flashingRow = 2;
if (flashingRow != m_flashingRow[r])
setRowFlashStatus(r, flashingRow);
for (int c=0; c<72; c++)
m_decoder->setRefresh(r, c, false);
// If row had changes rendered and flashing is present anywhere on the screen,
// copy this rendered line into the other flash pixmap buffers and then re-render
// the flashing cells in those buffers
if (rowRefreshed && m_flashBuffersHz > 0) {
pixmapPainter.begin(m_pagePixmap[3]);
pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
pixmapPainter.drawPixmap(0, r*10, *m_pagePixmap[0], 0, r*10, 864, 10);
pixmapPainter.end();
renderRow(r, 3);
if (m_flashBuffersHz == 2) {
pixmapPainter.begin(m_pagePixmap[1]);
pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
pixmapPainter.drawPixmap(0, r*10, *m_pagePixmap[0], 0, r*10, 864, 10);
pixmapPainter.end();
pixmapPainter.begin(m_pagePixmap[2]);
pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
pixmapPainter.drawPixmap(0, r*10, *m_pagePixmap[0], 0, r*10, 864, 10);
pixmapPainter.end();
pixmapPainter.begin(m_pagePixmap[4]);
pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
pixmapPainter.drawPixmap(0, r*10, *m_pagePixmap[3], 0, r*10, 864, 10);
pixmapPainter.end();
pixmapPainter.begin(m_pagePixmap[5]);
pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
pixmapPainter.drawPixmap(0, r*10, *m_pagePixmap[3], 0, r*10, 864, 10);
pixmapPainter.end();
renderRow(r, 1);
renderRow(r, 2);
renderRow(r, 4);
renderRow(r, 5);
}
}
}
void TeletextPageRender::setRowFlashStatus(int r, int rowFlashHz)
{
m_flashingRow[r] = rowFlashHz;
if (rowFlashHz == m_flashBuffersHz)
return;
if (rowFlashHz < m_flashBuffersHz) {
// New flash Hz for this row is lower than the entire screen flash Hz
// Check the other rows if they still need flashing at the current flash Hz
// If not, reduce the screen flash Hz
int highestRowFlashHz = rowFlashHz;
for (int ri=0; ri<25; ri++)
if (m_flashingRow[ri] > highestRowFlashHz) {
highestRowFlashHz = m_flashingRow[ri];
if (highestRowFlashHz == 2)
break;
}
if (highestRowFlashHz > rowFlashHz)
rowFlashHz = highestRowFlashHz;
if (rowFlashHz == m_flashBuffersHz)
return;
m_flashBuffersHz = rowFlashHz;
emit flashChanged(m_flashBuffersHz);
return;
}
// If we get here, new flash Hz for this row is higher than the entire flash Hz
// so prepare the pixmap flash buffers
if (m_flashBuffersHz == 0)
*m_pagePixmap[3] = m_pagePixmap[0]->copy();
if (rowFlashHz == 2) {
*m_pagePixmap[1] = m_pagePixmap[0]->copy();
*m_pagePixmap[2] = m_pagePixmap[0]->copy();
*m_pagePixmap[4] = m_pagePixmap[3]->copy();
*m_pagePixmap[5] = m_pagePixmap[3]->copy();
}
m_flashBuffersHz = rowFlashHz;
emit flashChanged(m_flashBuffersHz); emit flashChanged(m_flashBuffersHz);
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -21,7 +21,6 @@
#define RENDER_H #define RENDER_H
#include <QBitmap> #include <QBitmap>
#include <QSet>
#include <QPixmap> #include <QPixmap>
#include "decode.h" #include "decode.h"
@@ -67,14 +66,16 @@ protected:
QPixmap* m_pagePixmap[6]; QPixmap* m_pagePixmap[6];
unsigned char m_controlCodeCache[25][40]; unsigned char m_controlCodeCache[25][40];
bool m_reveal, m_mix, m_showControlCodes; bool m_reveal, m_mix, m_showControlCodes;
QSet<QPair<int, int>> m_flash1HzCells;
QSet<QPair<int, int>> m_flash2HzCells;
int m_flashBuffersHz; int m_flashBuffersHz;
int m_flashingRow[25];
private: private:
inline void drawFromBitmap(QPainter &, int, int, const QBitmap, TeletextPageDecode::CharacterFragment);
inline void drawFromFontBitmap(QPainter &, int, int, unsigned char, int, TeletextPageDecode::CharacterFragment); inline void drawFromFontBitmap(QPainter &, int, int, unsigned char, int, TeletextPageDecode::CharacterFragment);
inline void drawCharacter(QPainter &, int, int, unsigned char, int, int, TeletextPageDecode::CharacterFragment); inline void drawCharacter(QPainter &, int, int, unsigned char, int, int, TeletextPageDecode::CharacterFragment);
void updateFlashBuffers(); inline void drawBoldOrItalicCharacter(QPainter &, int, int, unsigned char, int, TeletextPageDecode::CharacterFragment);
void renderRow(int, int, bool force=false);
void setRowFlashStatus(int, int);
TeletextPageDecode *m_decoder; TeletextPageDecode *m_decoder;
}; };

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -218,7 +218,7 @@ X26DockWidget::X26DockWidget(TeletextWidget *parent): QDockWidget(parent)
QMenu *reservedColumnSubMenu = pdcSubMenu->addMenu(tr("Reserved column")); QMenu *reservedColumnSubMenu = pdcSubMenu->addMenu(tr("Reserved column"));
newModeMenuAction(reservedColumnSubMenu, 0x24); newModeMenuAction(reservedColumnSubMenu, 0x24);
newModeMenuAction(reservedColumnSubMenu, 0x25); newModeMenuAction(reservedColumnSubMenu, 0x25);
newModeMenuAction(reservedColumnSubMenu, 0x26); newModeMenuAction(reservedColumnSubMenu, 0x2a);
} }
m_cookedModePushButton->setMenu(m_cookedModeMenu); m_cookedModePushButton->setMenu(m_cookedModeMenu);
@@ -749,16 +749,13 @@ void X26DockWidget::updateAllCookedTripletWidgets(const QModelIndex &index)
case 0x29: // G0 character case 0x29: // G0 character
case 0x2b: // G3 character at Level 2.5 case 0x2b: // G3 character at Level 2.5
case 0x2f ... 0x3f: // G2 character, G0 character with diacritical case 0x2f ... 0x3f: // G2 character, G0 character with diacritical
// TODO non-Latin G0 and G2 character sets
m_characterCodeComboBox->blockSignals(true); m_characterCodeComboBox->blockSignals(true);
if (modeExt == 0x22 || modeExt == 0x2b) if (modeExt == 0x21)
m_characterListModel.setCharacterSet(26);
else if (modeExt == 0x2f)
m_characterListModel.setCharacterSet(7);
else if (modeExt == 0x21)
m_characterListModel.setCharacterSet(24); m_characterListModel.setCharacterSet(24);
else if (modeExt == 0x22 || modeExt == 0x2b)
m_characterListModel.setCharacterSet(26);
else else
m_characterListModel.setCharacterSet(0); m_characterListModel.setCharacterSet(index.model()->data(index.model()->index(index.row(), 0), Qt::UserRole+2).toInt());
m_characterCodeComboBox->setCurrentIndex(index.model()->data(index.model()->index(index.row(), 0), Qt::UserRole+1).toInt()-32); m_characterCodeComboBox->setCurrentIndex(index.model()->data(index.model()->index(index.row(), 0), Qt::UserRole+1).toInt()-32);
m_characterCodeComboBox->blockSignals(false); m_characterCodeComboBox->blockSignals(false);
m_tripletParameterStackedLayout->setCurrentIndex(2); m_tripletParameterStackedLayout->setCurrentIndex(2);
@@ -1036,9 +1033,11 @@ void X26DockWidget::insertTriplet(int modeExt, bool after)
{ {
QModelIndex index = m_x26View->currentIndex(); QModelIndex index = m_x26View->currentIndex();
X26Triplet newTriplet(modeExt < 0x20 ? 41 : 0, modeExt & 0x1f, 0); X26Triplet newTriplet(modeExt < 0x20 ? 41 : 0, modeExt & 0x1f, 0);
int row; int newListRow;
if (index.isValid()) { if (index.isValid()) {
newListRow = index.row()+after;
// If we're inserting a column triplet next to another column triplet, // If we're inserting a column triplet next to another column triplet,
// duplicate the column number // duplicate the column number
// Avoid the PDC and reserved mode triplets // Avoid the PDC and reserved mode triplets
@@ -1048,20 +1047,40 @@ void X26DockWidget::insertTriplet(int modeExt, bool after)
if (existingTripletModeExt >= 0x20 && existingTripletModeExt != 0x24 && existingTripletModeExt != 0x25 && existingTripletModeExt != 0x26 && existingTripletModeExt != 0x2a) if (existingTripletModeExt >= 0x20 && existingTripletModeExt != 0x24 && existingTripletModeExt != 0x25 && existingTripletModeExt != 0x26 && existingTripletModeExt != 0x2a)
newTriplet.setAddress(index.model()->data(index.model()->index(index.row(), 0), Qt::UserRole).toInt()); newTriplet.setAddress(index.model()->data(index.model()->index(index.row(), 0), Qt::UserRole).toInt());
} }
row = index.row()+after; // If we're inserting a Set Active Position or Full Row Colour triplet,
// look for a previous row setting triplet and set this one to the row after
if (modeExt == 0x04 || modeExt == 0x01) {
for (int i=newListRow-1; i>=0; i--) {
const int scanTripletModeExt = index.model()->data(index.model()->index(i, 2), Qt::EditRole).toInt();
if (scanTripletModeExt == 0x04 || scanTripletModeExt == 0x01) {
const int scanActivePositionRow = index.model()->data(index.model()->index(i, 0), Qt::EditRole).toInt()+1;
if (scanActivePositionRow < 25)
newTriplet.setAddressRow(scanActivePositionRow);
else
newTriplet.setAddressRow(24);
break;
}
}
}
} else } else
row = 0; newListRow = 0;
// For character triplets, ensure Data is not reserved // For character triplets, ensure Data is not reserved
if (modeExt == 0x21 || modeExt == 0x22 || modeExt == 0x29 || modeExt == 0x2b || modeExt >= 0x2f) if (modeExt == 0x21 || modeExt == 0x22 || modeExt == 0x29 || modeExt == 0x2b || modeExt >= 0x2f)
newTriplet.setData(0x20); newTriplet.setData(0x20);
// For Address Row 0, set Address
if (modeExt == 0x07)
newTriplet.setAddress(63);
// For Termination Marker, set Address and Mode // For Termination Marker, set Address and Mode
if (modeExt == 0x1f) { if (modeExt == 0x1f) {
newTriplet.setAddress(63); newTriplet.setAddress(63);
newTriplet.setData(7); newTriplet.setData(7);
} }
m_x26Model->insertRows(row, 1, QModelIndex(), newTriplet); m_x26Model->insertRows(newListRow, 1, QModelIndex(), newTriplet);
} }
void X26DockWidget::insertTripletCopy() void X26DockWidget::insertTripletCopy()

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -266,7 +266,30 @@ QVariant X26Model::data(const QModelIndex &index, int role) const
return result; return result;
} }
break; break;
//TODO case 0x28: // G0 and G2 designation case 0x28: // Modified G0 and G2 character set
switch (triplet.data()) {
case 0x20:
return QString("0x20 Cyrillic 1 Serbian/Croatian");
case 0x24:
return QString("0x24 Cyrillic 2 Russian/Bulgarian");
case 0x25:
return QString("0x25 Cyrillic 3 Ukranian");
case 0x36:
return QString("0x36 Latin");
case 0x37:
return QString("0x37 Greek");
case 0x40:
case 0x44:
return QString("0x%1 G0 Latin, G2 Arabic").arg(triplet.data(), 2, 16);
case 0x47:
case 0x57:
return QString("0x%1 Arabic").arg(triplet.data(), 2, 16);
case 0x55:
return QString("0x55 G0 Hebrew, G2 Arabic");
}
if (triplet.data() < 0x27)
return QString("0x%1 Latin").arg(triplet.data(), 2, 16, QChar('0'));
break;
case 0x2c: // Display attributes case 0x2c: // Display attributes
if (triplet.data() & 0x02) if (triplet.data() & 0x02)
result.append("Boxing "); result.append("Boxing ");
@@ -314,7 +337,6 @@ QVariant X26Model::data(const QModelIndex &index, int role) const
result.chop(1); result.chop(1);
result.append(QString(", %1 row(s)").arg(triplet.data() >> 4)); result.append(QString(", %1 row(s)").arg(triplet.data() >> 4));
return result; return result;
case 0x28: // Modified G0 and G2 character set
case 0x26: // PDC case 0x26: // PDC
return QString("0x%1").arg(triplet.data(), 2, 16, QChar('0')); return QString("0x%1").arg(triplet.data(), 2, 16, QChar('0'));
default: // Reserved default: // Reserved
@@ -346,16 +368,11 @@ QVariant X26Model::data(const QModelIndex &index, int role) const
if (triplet.data() >= 0x20) if (triplet.data() >= 0x20)
return m_fontBitmap.rawBitmap()->copy((triplet.data()-32)*12, 26*10, 12, 10); return m_fontBitmap.rawBitmap()->copy((triplet.data()-32)*12, 26*10, 12, 10);
break; break;
case 0x2f: // G2 character
// TODO non-Latin G2 character sets
if (triplet.data() >= 0x20)
return m_fontBitmap.rawBitmap()->copy((triplet.data()-32)*12, 7*10, 12, 10);
break;
case 0x29: // G0 character case 0x29: // G0 character
case 0x2f: // G2 character
case 0x30 ... 0x3f: // G0 diacritical mark case 0x30 ... 0x3f: // G0 diacritical mark
// TODO non-Latin G0 character sets
if (triplet.data() >= 0x20) if (triplet.data() >= 0x20)
return m_fontBitmap.rawBitmap()->copy((triplet.data()-32)*12, 0, 12, 10); return m_fontBitmap.rawBitmap()->copy((triplet.data()-32)*12, m_parentMainWidget->pageDecode()->cellCharacterSet(triplet.activePositionRow(), triplet.activePositionColumn())*10, 12, 10);
break; break;
}; };
@@ -471,9 +488,16 @@ QVariant X26Model::data(const QModelIndex &index, int role) const
return triplet.data() >> 4; return triplet.data() >> 4;
} }
break; break;
case 0x29: // G0 character
case 0x2f: // G2 character
case 0x30 ... 0x3f: // G0 diacritical mark
// Qt::UserRole+1 is character number, returned by default below
if (role == Qt::UserRole+2) // Character set
return m_parentMainWidget->pageDecode()->cellCharacterSet(triplet.activePositionRow(), triplet.activePositionColumn());
break;
}; };
// Fpr other triplet modes, return the complete data value // For characters and other triplet modes, return the complete data value
if (role == Qt::UserRole+1) if (role == Qt::UserRole+1)
return triplet.data(); return triplet.data();
@@ -618,7 +642,7 @@ bool X26Model::setData(const QModelIndex &index, const QVariant &value, int role
if (triplet.data() >= 0x18) if (triplet.data() >= 0x18)
m_parentMainWidget->document()->undoStack()->push(new EditTripletCommand(m_parentMainWidget->document(), this, index.row(), EditTripletCommand::ETdata, 0x00, 0, role)); m_parentMainWidget->document()->undoStack()->push(new EditTripletCommand(m_parentMainWidget->document(), this, index.row(), EditTripletCommand::ETdata, 0x00, 0, role));
break; break;
case 0x28: // Display attributes case 0x2c: // Display attributes
case 0x2e: // Font style case 0x2e: // Font style
// Clear reserved bit D3 // Clear reserved bit D3
if (triplet.data() & 0x08) if (triplet.data() & 0x08)

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -74,6 +74,7 @@ void X26TripletList::updateInternalData()
ActivePosition activePosition; ActivePosition activePosition;
X26Triplet *triplet; X26Triplet *triplet;
// Check for errors, and fill in where the Active Position goes for Level 2.5 and above
for (int i=0; i < m_list.size(); i++) { for (int i=0; i < m_list.size(); i++) {
triplet = &m_list[i]; triplet = &m_list[i];
triplet->m_error = X26Triplet::NoError; triplet->m_error = X26Triplet::NoError;
@@ -173,6 +174,21 @@ void X26TripletList::updateInternalData()
if (triplet->m_data >= 0x18) if (triplet->m_data >= 0x18)
triplet->m_reservedData = true; triplet->m_reservedData = true;
break; break;
case 0x28: // Modified G0 and G2 character set
if (triplet->m_data > 0x26)
switch (triplet->m_data) {
case 0x36:
case 0x37:
case 0x40:
case 0x44:
case 0x47:
case 0x55:
case 0x57:
break;
default:
triplet->m_reservedData = true;
}
break;
case 0x2d: // DRCS character case 0x2d: // DRCS character
if ((triplet->m_data & 0x3f) >= 48) if ((triplet->m_data & 0x3f) >= 48)
// Should really be an error? // Should really be an error?
@@ -182,6 +198,34 @@ void X26TripletList::updateInternalData()
triplet->m_activePositionRow = activePosition.row(); triplet->m_activePositionRow = activePosition.row();
triplet->m_activePositionColumn = activePosition.column(); triplet->m_activePositionColumn = activePosition.column();
} }
// Now work out where the Active Position goes on a Level 1.5 decoder
activePosition.reset();
for (int i=0; i < m_list.size(); i++) {
triplet = &m_list[i];
if (triplet->modeExt() == 0x1f) // Termination marker
break;
switch (triplet->modeExt()) {
case 0x04: // Set Active Position;
activePosition.setRow(triplet->addressRow());
break;
case 0x07: // Address row 0
if (triplet->m_address == 63 && activePosition.setRow(0))
activePosition.setColumn(8);
break;
case 0x22: // G3 mosaic character at level 1.5
case 0x2f ... 0x3f: // G2 character or G0 diacritical mark
activePosition.setColumn(triplet->addressColumn());
break;
}
triplet->m_activePositionRow1p5 = activePosition.row();
triplet->m_activePositionColumn1p5 = activePosition.column();
}
} }
void X26TripletList::append(const X26Triplet &value) void X26TripletList::append(const X26Triplet &value)

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 Gavin MacGregor * Copyright (C) 2020-2023 Gavin MacGregor
* *
* This file is part of QTeletextMaker. * This file is part of QTeletextMaker.
* *
@@ -61,6 +61,8 @@ public:
int activePositionRow() const { return m_activePositionRow; } int activePositionRow() const { return m_activePositionRow; }
int activePositionColumn() const { return m_activePositionColumn; } int activePositionColumn() const { return m_activePositionColumn; }
int activePositionRow1p5() const { return m_activePositionRow1p5; }
int activePositionColumn1p5() const { return m_activePositionColumn1p5; }
X26TripletError error() const { return m_error; } X26TripletError error() const { return m_error; }
bool reservedMode() const { return m_reservedMode; } bool reservedMode() const { return m_reservedMode; }
bool reservedData() const { return m_reservedData; } bool reservedData() const { return m_reservedData; }
@@ -68,9 +70,13 @@ public:
friend class X26TripletList; friend class X26TripletList;
private: private:
// Only these variables are manipulated by the X26Triplet class
int m_address, m_mode, m_data; int m_address, m_mode, m_data;
// and the following are filled in by X26TripletList::updateInternalData()
int m_activePositionRow = -1; int m_activePositionRow = -1;
int m_activePositionColumn = -1; int m_activePositionColumn = -1;
int m_activePositionRow1p5 = -1;
int m_activePositionColumn1p5 = -1;
X26TripletError m_error = NoError; X26TripletError m_error = NoError;
bool m_reservedMode = false; bool m_reservedMode = false;
bool m_reservedData = false; bool m_reservedData = false;