32 Commits

Author SHA1 Message Date
G.K.MacGregor
86e2856d50 Tag version 0.6.2-alpha 2023-08-15 19:50:56 +01:00
G.K.MacGregor
116b02d68f Refresh page when C5 or C6 bits are toggled 2023-08-15 18:41:26 +01:00
G.K.MacGregor
c9a0d0bf84 Change default page timing from 8 to 20 seconds 2023-08-15 18:36:33 +01:00
G.K.MacGregor
f36143e10f Really show correct G0 and G2 character set in widgets
Amendment to 7b59c78. This should prevent spurious NOS character sets from
appearing in the widgets, and should also prevent the G0 and G2 character
sets from getting mixed up in the widgets when those triplets are within
object definitions.

The G0 and G2 character set in the widgets may still be incorrect in corner
cases of objects invoked within areas of the page where the character set
has been changed either with the ESC/Switch spacing attribute or with the
"modified G0/G2 character set designation" triplet.
2023-08-15 18:11:40 +01:00
G.K.MacGregor
6185ca7110 Make full screen+row colours and CLUT remapping undoable 2023-08-13 21:47:22 +01:00
G.K.MacGregor
3729b293f0 Fix pasting 0x7f on unmapped plain text character 2023-08-13 14:38:56 +01:00
G.K.MacGregor
b71c178840 Fix crash if Termination or Define Object was only triplet 2023-08-09 22:20:20 +01:00
G.K.MacGregor
ba2d1241d0 Fix flashing rows with mixed 1Hz and 2Hz rates 2023-08-06 16:49:44 +01:00
G.K.MacGregor
07e0d757bb Forgot to remove "no mod'd G0 and G2 designation" from README 2023-06-24 23:55:55 +01:00
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 2034 additions and 1372 deletions

View File

@@ -28,9 +28,7 @@ Optionally, type `make install` afterwards to place the executable into /usr/loc
The following X/26 enhancement triplets are not rendered by the editor, although the list is fully aware of them.
- Invocation of Objects from POP and GPOP pages.
- DRCS characters.
- Modified G0 and G2 character set designation using X/26 triplets with mode 01000.
- Full screen and full row colours set by Active Objects.
- Level 3.5 font style: bold, italic and proportional spacing.
- Proportional font spacing on Level 3.5
## Using the X/26 triplet editor
The X/26 triplet editor sorts all the triplet modes available into categories, which are:

1684
decode.cpp

File diff suppressed because it is too large Load Diff

413
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.
*
@@ -20,239 +20,54 @@
#ifndef DECODE_H
#define DECODE_H
#include <QList>
#include <QMap>
#include <QMultiMap>
#include <QPair>
#include <vector>
#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
{
Q_OBJECT
public:
enum CharacterFragment { NormalSize, DoubleHeightTopHalf, DoubleHeightBottomHalf, DoubleWidthLeftHalf, DoubleWidthRightHalf, DoubleSizeTopLeftQuarter, DoubleSizeTopRightQuarter, DoubleSizeBottomLeftQuarter, DoubleSizeBottomRightQuarter };
enum RowHeight { NormalHeight, TopHalf, BottomHalf };
TeletextPageDecode();
~TeletextPageDecode();
bool refresh(int r, int c) const { return m_refresh[r][c]; }
void setRefresh(int, int, bool);
void decodePage();
void decodeRow(int r);
LevelOnePage *teletextPage() const { return m_levelOnePage; };
void setTeletextPage(LevelOnePage *);
void updateSidePanels();
void buildEnhanceMap(TextLayer *, int=0);
unsigned char cellCharacterCode(int r, int c) { return cellAtCharacterOrigin(r, c).character.code; };
int cellCharacterSet(int r, int c) { return cellAtCharacterOrigin(r, c).character.set; };
int cellCharacterDiacritical(int r, int c) { return cellAtCharacterOrigin(r, c).character.diacritical; };
int cellForegroundCLUT(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.foreColour; };
int cellBackgroundCLUT(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.backColour; };
unsigned char cellCharacterCode(int r, int c) const { return m_cell[r][c].character.code; };
int cellCharacterSet(int r, int c) const { return m_cell[r][c].character.set; };
int cellCharacterDiacritical(int r, int c) const { return m_cell[r][c].character.diacritical; };
int cellG0CharacterSet(int r, int c) const { return m_cell[r][c].g0Set; };
int cellG2CharacterSet(int r, int c) const { return m_cell[r][c].g2Set; };
int cellForegroundCLUT(int r, int c) const { return m_cell[r][c].attribute.foregroundCLUT; };
int cellBackgroundCLUT(int r, int c) const { return m_cell[r][c].attribute.backgroundCLUT; };
QColor cellForegroundQColor(int, int);
QColor cellBackgroundQColor(int, int);
QColor cellFlashForegroundQColor(int, int);
int cellFlashMode(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.flash.mode; };
int cellFlashRatePhase(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.flash.ratePhase; };
int cellFlash2HzPhaseNumber(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.flash.phaseNumber; };
CharacterFragment cellCharacterFragment(int, int) const;
bool cellBoxed(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.display.boxingWindow; };
bool cellConceal(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.display.conceal; };
bool cellUnderlined(int r, int c) { return cellAtCharacterOrigin(r, c).attribute.display.underlineSeparated && cellAtCharacterOrigin(r, c).character.set < 24; };
int cellFlashMode(int r, int c) const { return m_cell[r][c].attribute.flash.mode; };
int cellFlashRatePhase(int r, int c) const { return m_cell[r][c].attribute.flash.ratePhase; };
int cellFlash2HzPhaseNumber(int r, int c) const { return m_cell[r][c].attribute.flash.phase2HzShown; };
CharacterFragment cellCharacterFragment(int r, int c) const { return m_cell[r][c].fragment; };
bool cellBoxed(int r, int c) const { return m_cell[r][c].attribute.display.boxingWindow; };
bool cellConceal(int r, int c) const { return m_cell[r][c].attribute.display.conceal; };
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; };
int level1CharSet(int r, int c) const { return m_cell[r][c].level1CharSet; };
bool level1MosaicAttribute(int r, int c) const { return m_cellLevel1Mosaic[r][c]; };
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 fullRowQColor(int r) const { return m_fullRowQColor[r]; };
@@ -270,29 +85,111 @@ signals:
protected:
inline void setFullScreenColour(int);
inline void setFullRowColour(int, int);
textCell& cellAtCharacterOrigin(int, int);
int m_finalFullScreenColour, m_level;
QColor m_finalFullScreenQColor;
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_backgroundRemap[8] = { 0, 8, 16, 8, 16, 8, 16, 24 };
private:
class Invocation;
enum ColourPart { Foreground, Background, FlashForeground };
QColor cellQColor(int, int, ColourPart);
textCell m_cell[25][72];
bool m_refresh[25][72];
LevelOnePage* m_levelOnePage;
int m_fullRowColour[25];
QColor m_fullRowQColor[25];
struct textCharacter {
unsigned char code=0x20;
int set=0;
int diacritical=0;
};
static const QMap<int, int> g0CharacterMap {
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;
int g0Set=0;
int g2Set=7;
};
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 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 },
@@ -302,5 +199,81 @@ static const QMap<int, int> g0CharacterMap {
{ 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 &);
bool m_refresh[25][72];
textCell m_cell[25][72];
bool m_cellLevel1Mosaic[25][40];
int m_cellLevel1CharSet[25][40];
LevelOnePage* m_levelOnePage;
int m_fullRowColour[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;
RowHeight m_rowHeight[25];
};
#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.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Gavin MacGregor
* Copyright (C) 2020-2023 Gavin MacGregor
*
* This file is part of QTeletextMaker.
*
@@ -108,6 +108,7 @@ signals:
void selectionMoved();
void colourChanged(int);
void contentsChange(int);
void pageOptionsChanged();
void aboutToChangeSubPage();
void subPageSelected();
void refreshNeeded();

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Gavin MacGregor
* Copyright (C) 2020-2023 Gavin MacGregor
*
* 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!
// 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
static constexpr quint32 mosaicNativeScanCodes[9] = {
0x18, 0x19, 0x26, 0x27, 0x34, 0x35, 0x1b, 0x29, 0x36
static constexpr quint32 mosaicNativeScanCodes[10] = {
0x18, 0x19, 0x26, 0x27, 0x34, 0x35, 0x1b, 0x29, 0x36, 0x28
};
#elif defined(Q_OS_WIN)
static constexpr quint32 mosaicNativeScanCodes[9] = {
0x10, 0x11, 0x1e, 0x1f, 0x2c, 0x2d, 0x13, 0x21, 0x2e
static constexpr quint32 mosaicNativeScanCodes[10] = {
0x10, 0x11, 0x1e, 0x1f, 0x2c, 0x2d, 0x13, 0x21, 0x2e, 0x20
};
#else
#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.
*
@@ -106,6 +106,8 @@ ToggleMosaicBitCommand::ToggleMosaicBitCommand(TeletextDocument *teletextDocumen
m_oldCharacter = teletextDocument->currentSubPage()->character(m_row, m_column);
if (bitToToggle == 0x20 || bitToToggle == 0x7f)
m_newCharacter = bitToToggle;
else if (bitToToggle == 0x66)
m_newCharacter = (m_row & 1) ? 0x66 : 0x39;
else
m_newCharacter = m_oldCharacter ^ bitToToggle;
@@ -413,8 +415,18 @@ PasteCommand::PasteCommand(TeletextDocument *teletextDocument, int pageCharSet,
if (m_selectionActive) {
m_selectionCornerRow = m_teletextDocument->selectionCornerRow();
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;
// Try to get something from the clipboard
@@ -422,6 +434,7 @@ PasteCommand::PasteCommand(TeletextDocument *teletextDocument, int pageCharSet,
nativeData = mimeData->data("application/x-teletext");
if (nativeData.size() > 2) {
// Native clipboard data: we put it there ourselves
m_plainText = false;
m_clipboardDataHeight = nativeData.at(0);
m_clipboardDataWidth = nativeData.at(1);
@@ -429,32 +442,126 @@ 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)
for (int r=0; r<m_clipboardDataHeight; r++)
m_pastingCharacters.append(nativeData.mid(2 + r * m_clipboardDataWidth, m_clipboardDataWidth));
else
else {
// Invalidate
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()) {
// 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"));
// "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_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++) {
m_pastingCharacters.append(QByteArray());
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;
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
convertedChar = keymapping[pageCharSet].value(charToConvert);
else {
// Either a Latin character or non-Latin character that can't be converted
// See if it's a Latin character
convertedChar = charToConvert.toLatin1();
if (convertedChar == 0)
if (convertedChar <= 0)
// Couldn't convert - make it a block character so it doesn't need to be inserted-between later on
convertedChar = 0x7f;
}
@@ -463,26 +570,19 @@ PasteCommand::PasteCommand(TeletextDocument *teletextDocument, int pageCharSet,
}
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++)
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)
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
for (int r=m_pasteTopRow; r<=m_pasteBottomRow; r++) {
QByteArray rowArray;
@@ -517,11 +617,21 @@ void PasteCommand::redo()
for (int c=m_pasteLeftColumn; c<=m_pasteRightColumn; c++)
// Guard against size of pasted block going beyond last line or column
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 (arrayC == m_clipboardDataWidth)
// if it wasn't plain text
if (arrayC == m_clipboardDataWidth) {
if (!m_plainText)
arrayC = 0;
else
break;
}
}
if (r < 25)
@@ -529,8 +639,13 @@ void PasteCommand::redo()
arrayR++;
// If paste area is taller than clipboard data, repeat the pattern
if (arrayR == m_clipboardDataHeight)
// if it wasn't plain text
if (arrayR == m_clipboardDataHeight) {
if (!m_plainText)
arrayR = 0;
else
break;
}
}
if (m_selectionActive) {
@@ -556,8 +671,11 @@ void PasteCommand::undo()
arrayC = 0;
for (int c=m_pasteLeftColumn; c<=m_pasteRightColumn; c++)
// Guard against size of pasted block going beyond last line or column
if (r < 25 && c < 40)
m_teletextDocument->currentSubPage()->setCharacter(r, c, m_deletedCharacters[arrayR].at(arrayC++));
if (r < 25 && c < 40) {
m_teletextDocument->currentSubPage()->setCharacter(r, c, m_deletedCharacters[arrayR].at(arrayC));
arrayC++;
}
if (r < 25)
emit m_teletextDocument->contentsChange(r);
@@ -614,6 +732,161 @@ void DeleteSubPageCommand::undo()
}
SetFullScreenColourCommand::SetFullScreenColourCommand(TeletextDocument *teletextDocument, int newColour, QUndoCommand *parent) : LevelOneCommand(teletextDocument, parent)
{
m_oldColour = teletextDocument->currentSubPage()->defaultScreenColour();
m_newColour = newColour;
setText(QObject::tr("full screen colour"));
}
void SetFullScreenColourCommand::redo()
{
m_teletextDocument->selectSubPageIndex(m_subPageIndex);
m_teletextDocument->currentSubPage()->setDefaultScreenColour(m_newColour);
emit m_teletextDocument->refreshNeeded();
emit m_teletextDocument->pageOptionsChanged();
}
void SetFullScreenColourCommand::undo()
{
m_teletextDocument->selectSubPageIndex(m_subPageIndex);
m_teletextDocument->currentSubPage()->setDefaultScreenColour(m_oldColour);
emit m_teletextDocument->refreshNeeded();
emit m_teletextDocument->pageOptionsChanged();
}
bool SetFullScreenColourCommand::mergeWith(const QUndoCommand *command)
{
const SetFullScreenColourCommand *newerCommand = static_cast<const SetFullScreenColourCommand *>(command);
if (m_subPageIndex != newerCommand->m_subPageIndex)
return false;
m_newColour = newerCommand->m_newColour;
return true;
}
SetFullRowColourCommand::SetFullRowColourCommand(TeletextDocument *teletextDocument, int newColour, QUndoCommand *parent) : LevelOneCommand(teletextDocument, parent)
{
m_oldColour = teletextDocument->currentSubPage()->defaultRowColour();
m_newColour = newColour;
setText(QObject::tr("full row colour"));
}
void SetFullRowColourCommand::redo()
{
m_teletextDocument->selectSubPageIndex(m_subPageIndex);
m_teletextDocument->currentSubPage()->setDefaultRowColour(m_newColour);
emit m_teletextDocument->refreshNeeded();
emit m_teletextDocument->pageOptionsChanged();
}
void SetFullRowColourCommand::undo()
{
m_teletextDocument->selectSubPageIndex(m_subPageIndex);
m_teletextDocument->currentSubPage()->setDefaultRowColour(m_oldColour);
emit m_teletextDocument->refreshNeeded();
emit m_teletextDocument->pageOptionsChanged();
}
bool SetFullRowColourCommand::mergeWith(const QUndoCommand *command)
{
const SetFullRowColourCommand *newerCommand = static_cast<const SetFullRowColourCommand *>(command);
if (m_subPageIndex != newerCommand->m_subPageIndex)
return false;
m_newColour = newerCommand->m_newColour;
return true;
}
SetCLUTRemapCommand::SetCLUTRemapCommand(TeletextDocument *teletextDocument, int newMap, QUndoCommand *parent) : LevelOneCommand(teletextDocument, parent)
{
m_oldMap = teletextDocument->currentSubPage()->colourTableRemap();
m_newMap = newMap;
setText(QObject::tr("CLUT remapping"));
}
void SetCLUTRemapCommand::redo()
{
m_teletextDocument->selectSubPageIndex(m_subPageIndex);
m_teletextDocument->currentSubPage()->setColourTableRemap(m_newMap);
emit m_teletextDocument->refreshNeeded();
emit m_teletextDocument->pageOptionsChanged();
}
void SetCLUTRemapCommand::undo()
{
m_teletextDocument->selectSubPageIndex(m_subPageIndex);
m_teletextDocument->currentSubPage()->setColourTableRemap(m_oldMap);
emit m_teletextDocument->refreshNeeded();
emit m_teletextDocument->pageOptionsChanged();
}
bool SetCLUTRemapCommand::mergeWith(const QUndoCommand *command)
{
const SetCLUTRemapCommand *newerCommand = static_cast<const SetCLUTRemapCommand *>(command);
if (m_subPageIndex != newerCommand->m_subPageIndex)
return false;
m_newMap = newerCommand->m_newMap;
return true;
}
SetBlackBackgroundSubstCommand::SetBlackBackgroundSubstCommand(TeletextDocument *teletextDocument, bool newSub, QUndoCommand *parent) : LevelOneCommand(teletextDocument, parent)
{
m_oldSub = teletextDocument->currentSubPage()->blackBackgroundSubst();
m_newSub = newSub;
setText(QObject::tr("black background substitution"));
}
void SetBlackBackgroundSubstCommand::redo()
{
m_teletextDocument->selectSubPageIndex(m_subPageIndex);
m_teletextDocument->currentSubPage()->setBlackBackgroundSubst(m_newSub);
emit m_teletextDocument->refreshNeeded();
emit m_teletextDocument->pageOptionsChanged();
}
void SetBlackBackgroundSubstCommand::undo()
{
m_teletextDocument->selectSubPageIndex(m_subPageIndex);
m_teletextDocument->currentSubPage()->setBlackBackgroundSubst(m_oldSub);
emit m_teletextDocument->refreshNeeded();
emit m_teletextDocument->pageOptionsChanged();
}
bool SetBlackBackgroundSubstCommand::mergeWith(const QUndoCommand *command)
{
const SetBlackBackgroundSubstCommand *newerCommand = static_cast<const SetBlackBackgroundSubstCommand *>(command);
if (m_subPageIndex != newerCommand->m_subPageIndex)
return false;
setObsolete(true);
return true;
}
SetColourCommand::SetColourCommand(TeletextDocument *teletextDocument, int colourIndex, int newColour, QUndoCommand *parent) : LevelOneCommand(teletextDocument, parent)
{
m_colourIndex = colourIndex;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Gavin MacGregor
* Copyright (C) 2020-2023 Gavin MacGregor
*
* This file is part of QTeletextMaker.
*
@@ -179,10 +179,74 @@ private:
int m_pasteTopRow, m_pasteBottomRow, m_pasteLeftColumn, m_pasteRightColumn;
int m_clipboardDataHeight, m_clipboardDataWidth;
int m_selectionCornerRow, m_selectionCornerColumn;
bool m_selectionActive;
bool m_selectionActive, m_plainText;
};
#endif // !QT_NO_CLIPBOARD
class SetFullScreenColourCommand : public LevelOneCommand
{
public:
enum { Id = 105 };
SetFullScreenColourCommand(TeletextDocument *, int, QUndoCommand *parent = 0);
void redo() override;
void undo() override;
bool mergeWith(const QUndoCommand *) override;
int id() const override { return Id; }
private:
int m_oldColour, m_newColour;
};
class SetFullRowColourCommand : public LevelOneCommand
{
public:
enum { Id = 106 };
SetFullRowColourCommand(TeletextDocument *, int, QUndoCommand *parent = 0);
void redo() override;
void undo() override;
bool mergeWith(const QUndoCommand *) override;
int id() const override { return Id; }
private:
int m_oldColour, m_newColour;
};
class SetCLUTRemapCommand : public LevelOneCommand
{
public:
enum { Id = 107 };
SetCLUTRemapCommand(TeletextDocument *, int, QUndoCommand *parent = 0);
void redo() override;
void undo() override;
bool mergeWith(const QUndoCommand *) override;
int id() const override { return Id; }
private:
int m_oldMap, m_newMap;
};
class SetBlackBackgroundSubstCommand : public LevelOneCommand
{
public:
enum { Id = 108 };
SetBlackBackgroundSubstCommand(TeletextDocument *, bool, QUndoCommand *parent = 0);
void redo() override;
void undo() override;
bool mergeWith(const QUndoCommand *) override;
int id() const override { return Id; }
private:
int m_oldSub, m_newSub;
};
class SetColourCommand : public LevelOneCommand
{
public:

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Gavin MacGregor
* Copyright (C) 2020-2023 Gavin MacGregor
*
* This file is part of QTeletextMaker.
*
@@ -64,7 +64,7 @@ void LevelOnePage::clearPage()
m_fastTextLink[i] = { 0x0ff, 0x3f7f };
/* m_subPageNumber = 0x0000; */
m_cycleValue = 8;
m_cycleValue = 20;
m_cycleType = CTseconds;
m_defaultCharSet = 0;
m_defaultNOS = 0;
@@ -396,7 +396,7 @@ bool LevelOnePage::isPaletteDefault(int colour) 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])
return false;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Gavin MacGregor
* Copyright (C) 2020-2023 Gavin MacGregor
*
* 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.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Gavin MacGregor
* Copyright (C) 2020-2023 Gavin MacGregor
*
* 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.
*
@@ -30,7 +30,7 @@ int main(int argc, char *argv[])
QApplication::setApplicationDisplayName(QApplication::applicationName());
QApplication::setOrganizationName("gkmac.co.uk");
QApplication::setOrganizationDomain("gkmac.co.uk");
QApplication::setApplicationVersion("0.5.5-alpha");
QApplication::setApplicationVersion("0.6.2-alpha");
QCommandLineParser parser;
parser.setApplicationDescription(QApplication::applicationName());
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.
*
@@ -96,7 +96,10 @@ void TeletextWidget::subPageSelected()
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();
}
@@ -181,8 +184,11 @@ void TeletextWidget::setShowControlCodes(bool showControlCodes)
void TeletextWidget::setControlBit(int bitNumber, bool active)
{
m_levelOnePage->setControlBit(bitNumber, active);
if (bitNumber == 1 || bitNumber == 2)
if (bitNumber == 1 || bitNumber == 2) {
m_pageDecode.decodePage();
m_pageRender.renderPage(true);
update();
}
}
void TeletextWidget::setDefaultCharSet(int newDefaultCharSet)
@@ -195,33 +201,6 @@ void TeletextWidget::setDefaultNOS(int newDefaultNOS)
m_levelOnePage->setDefaultNOS(newDefaultNOS);
}
void TeletextWidget::setDefaultScreenColour(int newColour)
{
m_levelOnePage->setDefaultScreenColour(newColour);
m_pageDecode.decodePage();
}
void TeletextWidget::setDefaultRowColour(int newColour)
{
m_levelOnePage->setDefaultRowColour(newColour);
m_pageDecode.decodePage();
update();
}
void TeletextWidget::setColourTableRemap(int newMap)
{
m_levelOnePage->setColourTableRemap(newMap);
m_pageDecode.decodePage();
update();
}
void TeletextWidget::setBlackBackgroundSubst(bool substOn)
{
m_levelOnePage->setBlackBackgroundSubst(substOn);
m_pageDecode.decodePage();
update();
}
void TeletextWidget::setSidePanelWidths(int newLeftSidePanelColumns, int newRightSidePanelColumns)
{
m_levelOnePage->setLeftSidePanelDisplayed(newLeftSidePanelColumns != 0);
@@ -259,7 +238,7 @@ void TeletextWidget::keyPressEvent(QKeyEvent *event)
mappedKeyPress = 0x7f;
if (m_pageDecode.level1MosaicAttribute(m_teletextDocument->cursorRow(), m_teletextDocument->cursorColumn()) && (mappedKeyPress < 0x40 || mappedKeyPress > 0x5f)) {
// We're on a mosaic and a blast-through character was NOT pressed
if (event->key() >= Qt::Key_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()) {
case Qt::Key_7:
toggleCharacterBit(0x01); // Top left
@@ -288,6 +267,9 @@ void TeletextWidget::keyPressEvent(QKeyEvent *event)
case Qt::Key_3:
toggleCharacterBit(0x20); // Clear all
break;
case Qt::Key_0:
toggleCharacterBit(0x66); // Dither
break;
}
return;
}
@@ -328,6 +310,9 @@ void TeletextWidget::keyPressEvent(QKeyEvent *event)
case mosaicNativeScanCodes[8]:
toggleCharacterBit(0x20); // Clear all
break;
case mosaicNativeScanCodes[9]:
toggleCharacterBit(0x66); // Dither
break;
}
return;
} else
@@ -688,18 +673,13 @@ void LevelOneScene::toggleGrid(bool gridOn)
void LevelOneScene::hideGUIElements(bool hidden)
{
if (hidden) {
m_mainGridItemGroup->setVisible(false);
m_cursorRectItem->setVisible(false);
m_selectionRectItem->setVisible(false);
for (int i=0; i<32; i++)
if (m_sidePanelGridNeeded[i])
m_sidePanelGridItemGroup[i]->setVisible(false);
} else {
if (static_cast<TeletextWidget *>(m_levelOneProxyWidget->widget())->document()->selectionActive())
m_selectionRectItem->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.
*
@@ -74,10 +74,6 @@ public slots:
void setControlBit(int, bool);
void setDefaultCharSet(int);
void setDefaultNOS(int);
void setDefaultScreenColour(int);
void setDefaultRowColour(int);
void setColourTableRemap(int);
void setBlackBackgroundSubst(bool);
void setSidePanelWidths(int, int);
void setSidePanelAtL35Only(bool);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Gavin MacGregor
* Copyright (C) 2020-2023 Gavin MacGregor
*
* This file is part of QTeletextMaker.
*
@@ -186,9 +186,6 @@ void MainWindow::exportPNG()
// Prepare widget image for extraction
m_textWidget->pauseFlash(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
bool reMix = m_textWidget->pageRender()->mix();
if (reMix) {
@@ -206,8 +203,6 @@ void MainWindow::exportPNG()
// Now we've extracted the image we can put the GUI things back
m_textScene->hideGUIElements(false);
if (reshowControlCodes)
m_textWidget->setShowControlCodes(true);
if (reMix) {
m_textWidget->setMix(true);
m_textScene->setMix(true);
@@ -246,7 +241,7 @@ void MainWindow::about()
QMessageBox::about(this, tr("About"), QString("<b>%1</b><br>"
"An open source Level 2.5 teletext page editor.<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>"
"<a href=\"https://github.com/gkthemac/qteletextmaker\">https://github.com/gkthemac/qteletextmaker</a>").arg(QApplication::applicationDisplayName()).arg(QApplication::applicationVersion()));
}
@@ -290,6 +285,7 @@ void MainWindow::init()
connect(m_textWidget->document()->undoStack(), &QUndoStack::cleanChanged, this, [=]() { setWindowModified(!m_textWidget->document()->undoStack()->isClean()); } );
connect(m_textWidget->document(), &TeletextDocument::aboutToChangeSubPage, m_x26DockWidget, &X26DockWidget::unloadX26List);
connect(m_textWidget->document(), &TeletextDocument::subPageSelected, this, &MainWindow::updatePageWidgets);
connect(m_textWidget->document(), &TeletextDocument::pageOptionsChanged, this, &MainWindow::updatePageWidgets);
connect(m_textWidget, &TeletextWidget::sizeChanged, this, &MainWindow::setSceneDimensions);
connect(m_textWidget->pageDecode(), &TeletextPageDecode::fullScreenColourChanged, m_textScene, &LevelOneScene::setFullScreenColour);
connect(m_textWidget->pageDecode(), &TeletextPageDecode::fullRowColourChanged, m_textScene, &LevelOneScene::setFullRowColour);
@@ -376,9 +372,16 @@ void MainWindow::createActions()
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..."));
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"));
@@ -834,10 +837,10 @@ void MainWindow::createStatusBar()
statusBar()->addPermanentWidget(m_levelRadioButton[i]);
}
m_levelRadioButton[0]->toggle();
connect(m_levelRadioButton[0], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setLevel(0); m_textWidget->update(); });
connect(m_levelRadioButton[1], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setLevel(1); m_textWidget->update(); });
connect(m_levelRadioButton[2], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setLevel(2); m_textWidget->update(); });
connect(m_levelRadioButton[3], &QAbstractButton::clicked, [=]() { m_textWidget->pageDecode()->setLevel(3); 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(); m_paletteDockWidget->setLevel3p5Accepted(false);});
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(); m_paletteDockWidget->setLevel3p5Accepted(true);});
statusBar()->showMessage(tr("Ready"));
}
@@ -939,14 +942,19 @@ void MainWindow::loadFile(const QString &fileName)
QApplication::setOverrideCursor(Qt::WaitCursor);
if (fileInfo.suffix() == "t42")
if (fileInfo.suffix() == "t42") {
importT42(&file, m_textWidget->document());
else
m_exportAutoFileName = fileName;
} else {
loadTTI(&file, m_textWidget->document());
m_exportAutoFileName.clear();
}
levelSeen = m_textWidget->document()->levelRequired();
m_levelRadioButton[levelSeen]->toggle();
m_textWidget->pageDecode()->setLevel(levelSeen);
if (levelSeen == 3)
m_paletteDockWidget->setLevel3p5Accepted(true);
updatePageWidgets();
QApplication::restoreOverrideCursor();
@@ -1026,6 +1034,18 @@ void MainWindow::updateRecentFileActions()
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()
{
if (const QAction *action = qobject_cast<const QAction *>(sender()))
@@ -1056,16 +1076,30 @@ bool MainWindow::saveFile(const QString &fileName)
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 exportFileName = m_curFile;
QString exportFileName;
if (fromAuto)
exportFileName = m_exportAutoFileName;
else {
exportFileName = m_curFile;
changeSuffixFromTTI(exportFileName, "t42");
exportFileName = QFileDialog::getSaveFileName(this, tr("Export t42"), exportFileName, "t42 stream (*.t42)");
if (exportFileName.isEmpty())
return;
}
QApplication::setOverrideCursor(Qt::WaitCursor);
QSaveFile file(exportFileName);
@@ -1077,8 +1111,15 @@ void MainWindow::exportT42()
errorMessage = tr("Cannot open file %1 for writing:\n%2.").arg(QDir::toNativeSeparators(exportFileName), file.errorString());
QApplication::restoreOverrideCursor();
if (!errorMessage.isEmpty())
if (!errorMessage.isEmpty()) {
QMessageBox::warning(this, QApplication::applicationDisplayName(), errorMessage);
return;
}
MainWindow::prependToRecentFiles(exportFileName);
m_exportAutoFileName = exportFileName;
statusBar()->showMessage(tr("File exported"), 2000);
}
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.
*
@@ -60,12 +60,14 @@ private slots:
bool save();
bool saveAs();
void reload();
void exportT42();
void exportAuto();
void exportT42(bool);
void exportZXNet();
void exportEditTF();
void exportPNG();
void exportM29();
void updateRecentFileActions();
void updateExportAutoAction();
void openRecentFile();
void about();
void updatePageWidgets();
@@ -121,6 +123,7 @@ private:
QAction *m_recentFileActs[m_MaxRecentFiles];
QAction *m_recentFileSeparator;
QAction *m_recentFileSubMenuAct;
QAction *m_exportAutoAct;
QAction *m_deleteSubPageAction;
QAction *m_borderActs[3];
QAction *m_aspectRatioActs[4];
@@ -131,7 +134,7 @@ private:
QPushButton *m_insertModePushButton;
QRadioButton *m_levelRadioButton[4];
QString m_curFile;
QString m_curFile, m_exportAutoFileName;
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.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Gavin MacGregor
* Copyright (C) 2020-2023 Gavin MacGregor
*
* 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.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Gavin MacGregor
* Copyright (C) 2020-2023 Gavin MacGregor
*
* 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.
*
@@ -26,6 +26,8 @@
#include "pageenhancementsdockwidget.h"
#include "levelonecommands.h"
PageEnhancementsDockWidget::PageEnhancementsDockWidget(TeletextWidget *parent): QDockWidget(parent)
{
QVBoxLayout *pageEnhancementsLayout = new QVBoxLayout;
@@ -48,9 +50,9 @@ PageEnhancementsDockWidget::PageEnhancementsDockWidget(TeletextWidget *parent):
m_defaultRowColourCombo = new QComboBox;
m_defaultRowColourCombo->setModel(m_parentMainWidget->document()->clutModel());
colourLayout->addWidget(m_defaultScreenColourCombo, 0, 1, 1, 1, Qt::AlignTop);
connect(m_defaultScreenColourCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index){ m_parentMainWidget->setDefaultScreenColour(index); });
connect(m_defaultScreenColourCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index){ m_parentMainWidget->document()->undoStack()->push(new SetFullScreenColourCommand(m_parentMainWidget->document(), index)); });
colourLayout->addWidget(m_defaultRowColourCombo, 1, 1, 1, 1, Qt::AlignTop);
connect(m_defaultRowColourCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index){ m_parentMainWidget->setDefaultRowColour(index); });
connect(m_defaultRowColourCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index){ m_parentMainWidget->document()->undoStack()->push(new SetFullRowColourCommand(m_parentMainWidget->document(), index)); });
// CLUT remapping
colourLayout->addWidget(new QLabel(tr("CLUT remapping")), 2, 0, 1, 1);
@@ -64,12 +66,12 @@ PageEnhancementsDockWidget::PageEnhancementsDockWidget(TeletextWidget *parent):
m_colourTableCombo->addItem("Fore 2 Back 2");
m_colourTableCombo->addItem("Fore 2 Back 3");
colourLayout->addWidget(m_colourTableCombo, 2, 1, 1, 1, Qt::AlignTop);
connect(m_colourTableCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index){ m_parentMainWidget->setColourTableRemap(index); });
connect(m_colourTableCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index){ m_parentMainWidget->document()->undoStack()->push(new SetCLUTRemapCommand(m_parentMainWidget->document(), index)); });
// Black background colour substitution
m_blackBackgroundSubstAct = new QCheckBox("Black background colour substitution");
colourLayout->addWidget(m_blackBackgroundSubstAct, 3, 0, 1, 2, Qt::AlignTop);
connect(m_blackBackgroundSubstAct, &QCheckBox::stateChanged, m_parentMainWidget, &TeletextWidget::setBlackBackgroundSubst);
connect(m_blackBackgroundSubstAct, &QCheckBox::stateChanged, [=](int state){ m_parentMainWidget->document()->undoStack()->push(new SetBlackBackgroundSubstCommand(m_parentMainWidget->document(), state)); });
// Add group box to the main layout
colourGroupBox->setLayout(colourLayout);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Gavin MacGregor
* Copyright (C) 2020-2023 Gavin MacGregor
*
* 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.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Gavin MacGregor
* Copyright (C) 2020-2023 Gavin MacGregor
*
* 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.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Gavin MacGregor
* Copyright (C) 2020-2023 Gavin MacGregor
*
* 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.
*
@@ -39,6 +39,9 @@ PaletteDockWidget::PaletteDockWidget(TeletextWidget *parent): QDockWidget(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->setWindowTitle("Palette");
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); });
}
}
updateLevel3p5Cursor();
m_showHexValuesCheckBox = new QCheckBox(tr("Show colour hex values"));
paletteGridLayout->addWidget(m_showHexValuesCheckBox, 5, 1, 1, 8);
connect(m_showHexValuesCheckBox, &QCheckBox::stateChanged, this, &PaletteDockWidget::updateAllColourButtons);
@@ -70,26 +76,34 @@ void PaletteDockWidget::updateColourButton(int colourIndex)
{
if (colourIndex == 8)
return;
QString colourString = QString("%1").arg(m_parentMainWidget->document()->currentSubPage()->CLUT(colourIndex), 3, 16, QChar('0'));
if (m_showHexValuesCheckBox->isChecked())
m_colourButton[colourIndex]->setText(colourString);
else
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 g = (m_parentMainWidget->document()->currentSubPage()->CLUT(colourIndex) >> 4) & 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';
QString qss = QString("background-color: #%1; color: #%2%2%2; border: none").arg(colourString).arg(blackOrWhite);
m_colourButton[colourIndex]->setStyleSheet(qss);
if (m_parentMainWidget->document()->currentSubPage()->isPaletteDefault(colourIndex))
// Default colour was set, disable Reset button if all colours in row are default as well
if (m_parentMainWidget->document()->currentSubPage()->isPaletteDefault(colourIndex)) {
// 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));
else
// Check if CLUTs 0 and 1 are all default if necessary
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()
@@ -98,6 +112,41 @@ void PaletteDockWidget::updateAllColourButtons()
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)
{
Q_UNUSED(event);
@@ -106,6 +155,9 @@ void PaletteDockWidget::showEvent(QShowEvent *event)
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");
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.
*
@@ -36,18 +36,22 @@ public:
public slots:
void updateColourButton(int);
void setLevel3p5Accepted(bool);
protected:
void showEvent(QShowEvent *);
private slots:
void selectColour(int);
void setLevel3p5Seen(bool);
void updateLevel3p5Cursor();
private:
void resetCLUT(int);
QPushButton *m_colourButton[32], *m_resetButton[4];
QCheckBox *m_showHexValuesCheckBox;
bool m_level3p5Accepted, m_level3p5Seen;
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.
*
@@ -55,10 +55,12 @@ TeletextPageRender::TeletextPageRender()
m_showControlCodes = false;
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++)
m_controlCodeCache[r][c] = 0x7f;
}
}
TeletextPageRender::~TeletextPageRender()
{
@@ -71,6 +73,39 @@ void TeletextPageRender::setDecoder(TeletextPageDecode *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)
{
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());
else if (characterCode == 0x7f && characterSet == 24)
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
drawFromFontBitmap(pixmapPainter, r, c, characterCode, characterSet, characterFragment);
@@ -180,46 +217,58 @@ inline void TeletextPageRender::drawCharacter(QPainter &pixmapPainter, int r, in
pixmapPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
}
inline void TeletextPageRender::drawBoldOrItalicCharacter(QPainter &pixmapPainter, int r, int c, unsigned char characterCode, int characterSet, TeletextPageDecode::CharacterFragment characterFragment)
{
QBitmap bitmap = QBitmap(12, 10);
QPainter bitmapPainter;
// TODO italic glyph-making is VERY slow!
if (m_decoder->cellItalic(r, c)) {
bitmap.clear();
bitmapPainter.begin(&bitmap);
bitmapPainter.setBackgroundMode(Qt::OpaqueMode);
bitmapPainter.drawPixmap(1, 0, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10, 11, 3);
bitmapPainter.drawPixmap(0, 3, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10+3, 12, 3);
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);
}
void TeletextPageRender::renderPage(bool force)
{
QPainter pixmapPainter[6];
int previousFlashBuffersHz = m_flashBuffersHz;
// If force-rendering (such as showing a new or different subpage) then
// we don't render flashing cells initially as it greatly speeds things up
// when rendering on top of an already flashing page.
// 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.
if (force) {
m_flashBuffersHz = 0;
previousFlashBuffersHz = 0;
emit flashChanged(0);
}
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++) {
renderRow(r, 0, force);
}
void TeletextPageRender::renderRow(int r, int ph, bool force)
{
QPainter pixmapPainter;
int flashingRow = 0;
bool rowRefreshed = false;
pixmapPainter.begin(m_pagePixmap[ph]);
pixmapPainter.setBackgroundMode(Qt::OpaqueMode);
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)) {
if (ph == 0 && 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)
@@ -229,10 +278,21 @@ void TeletextPageRender::renderPage(bool force)
}
}
if (ph == 0) {
if (m_decoder->cellFlashMode(r, c) != 0)
flashingRow = qMax(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;
@@ -243,163 +303,139 @@ void TeletextPageRender::renderPage(bool force)
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;
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 = (i < 3) ^ (m_decoder->cellFlashMode(r, c) == 2);
phaseOn = (ph < 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);
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[i].setBackground(m_decoder->cellBackgroundQColor(r, c));
pixmapPainter.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));
}
}
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[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);
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);
}
}
}
if (force && m_decoder->cellFlashMode(r, c) != 0)
m_decoder->setRefresh(r, c, true);
else
m_decoder->setRefresh(r, c, false);
}
}
pixmapPainter.end();
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()
{
int highestFlashHz;
if (!m_flash2HzCells.isEmpty())
highestFlashHz = 2;
else
highestFlashHz = !m_flash1HzCells.isEmpty();
if (highestFlashHz == m_flashBuffersHz)
if (ph != 0)
return;
if (highestFlashHz > m_flashBuffersHz) {
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 (highestFlashHz == 2) {
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 = highestFlashHz;
m_flashBuffersHz = rowFlashHz;
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.
*
@@ -21,7 +21,6 @@
#define RENDER_H
#include <QBitmap>
#include <QSet>
#include <QPixmap>
#include "decode.h"
@@ -67,14 +66,16 @@ protected:
QPixmap* m_pagePixmap[6];
unsigned char m_controlCodeCache[25][40];
bool m_reveal, m_mix, m_showControlCodes;
QSet<QPair<int, int>> m_flash1HzCells;
QSet<QPair<int, int>> m_flash2HzCells;
int m_flashBuffersHz;
int m_flashingRow[25];
private:
inline void drawFromBitmap(QPainter &, int, int, const QBitmap, 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);
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;
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Gavin MacGregor
* Copyright (C) 2020-2023 Gavin MacGregor
*
* 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.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Gavin MacGregor
* Copyright (C) 2020-2023 Gavin MacGregor
*
* This file is part of QTeletextMaker.
*
@@ -218,7 +218,7 @@ X26DockWidget::X26DockWidget(TeletextWidget *parent): QDockWidget(parent)
QMenu *reservedColumnSubMenu = pdcSubMenu->addMenu(tr("Reserved column"));
newModeMenuAction(reservedColumnSubMenu, 0x24);
newModeMenuAction(reservedColumnSubMenu, 0x25);
newModeMenuAction(reservedColumnSubMenu, 0x26);
newModeMenuAction(reservedColumnSubMenu, 0x2a);
}
m_cookedModePushButton->setMenu(m_cookedModeMenu);
@@ -749,16 +749,13 @@ void X26DockWidget::updateAllCookedTripletWidgets(const QModelIndex &index)
case 0x29: // G0 character
case 0x2b: // G3 character at Level 2.5
case 0x2f ... 0x3f: // G2 character, G0 character with diacritical
// TODO non-Latin G0 and G2 character sets
m_characterCodeComboBox->blockSignals(true);
if (modeExt == 0x22 || modeExt == 0x2b)
m_characterListModel.setCharacterSet(26);
else if (modeExt == 0x2f)
m_characterListModel.setCharacterSet(7);
else if (modeExt == 0x21)
if (modeExt == 0x21)
m_characterListModel.setCharacterSet(24);
else if (modeExt == 0x22 || modeExt == 0x2b)
m_characterListModel.setCharacterSet(26);
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->blockSignals(false);
m_tripletParameterStackedLayout->setCurrentIndex(2);
@@ -1036,9 +1033,11 @@ void X26DockWidget::insertTriplet(int modeExt, bool after)
{
QModelIndex index = m_x26View->currentIndex();
X26Triplet newTriplet(modeExt < 0x20 ? 41 : 0, modeExt & 0x1f, 0);
int row;
int newListRow;
if (index.isValid()) {
newListRow = index.row()+after;
// If we're inserting a column triplet next to another column triplet,
// duplicate the column number
// 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)
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
row = 0;
newListRow = 0;
// For character triplets, ensure Data is not reserved
if (modeExt == 0x21 || modeExt == 0x22 || modeExt == 0x29 || modeExt == 0x2b || modeExt >= 0x2f)
newTriplet.setData(0x20);
// For Address Row 0, set Address
if (modeExt == 0x07)
newTriplet.setAddress(63);
// For Termination Marker, set Address and Mode
if (modeExt == 0x1f) {
newTriplet.setAddress(63);
newTriplet.setData(7);
}
m_x26Model->insertRows(row, 1, QModelIndex(), newTriplet);
m_x26Model->insertRows(newListRow, 1, QModelIndex(), newTriplet);
}
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.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Gavin MacGregor
* Copyright (C) 2020-2023 Gavin MacGregor
*
* This file is part of QTeletextMaker.
*
@@ -266,7 +266,30 @@ QVariant X26Model::data(const QModelIndex &index, int role) const
return result;
}
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
if (triplet.data() & 0x02)
result.append("Boxing ");
@@ -314,7 +337,6 @@ QVariant X26Model::data(const QModelIndex &index, int role) const
result.chop(1);
result.append(QString(", %1 row(s)").arg(triplet.data() >> 4));
return result;
case 0x28: // Modified G0 and G2 character set
case 0x26: // PDC
return QString("0x%1").arg(triplet.data(), 2, 16, QChar('0'));
default: // Reserved
@@ -347,17 +369,15 @@ QVariant X26Model::data(const QModelIndex &index, int role) const
return m_fontBitmap.rawBitmap()->copy((triplet.data()-32)*12, 26*10, 12, 10);
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);
return m_fontBitmap.rawBitmap()->copy((triplet.data()-32)*12, m_parentMainWidget->pageDecode()->cellG2CharacterSet(triplet.activePositionRow(), triplet.activePositionColumn())*10, 12, 10);
break;
case 0x29: // G0 character
case 0x30 ... 0x3f: // G0 diacritical mark
// TODO non-Latin G0 character sets
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()->cellG0CharacterSet(triplet.activePositionRow(), triplet.activePositionColumn())*10, 12, 10);
break;
};
}
if (role == Qt::EditRole && index.column() == 2)
return triplet.modeExt();
@@ -471,9 +491,20 @@ QVariant X26Model::data(const QModelIndex &index, int role) const
return triplet.data() >> 4;
}
break;
case 0x2f: // G2 character
// Qt::UserRole+1 is character number, returned by default below
if (role == Qt::UserRole+2) // Character set
return m_parentMainWidget->pageDecode()->cellG2CharacterSet(triplet.activePositionRow(), triplet.activePositionColumn());
break;
case 0x29: // G0 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()->cellG0CharacterSet(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)
return triplet.data();
@@ -618,7 +649,7 @@ bool X26Model::setData(const QModelIndex &index, const QVariant &value, int role
if (triplet.data() >= 0x18)
m_parentMainWidget->document()->undoStack()->push(new EditTripletCommand(m_parentMainWidget->document(), this, index.row(), EditTripletCommand::ETdata, 0x00, 0, role));
break;
case 0x28: // Display attributes
case 0x2c: // Display attributes
case 0x2e: // Font style
// Clear reserved bit D3
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.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Gavin MacGregor
* Copyright (C) 2020-2023 Gavin MacGregor
*
* This file is part of QTeletextMaker.
*
@@ -74,6 +74,7 @@ void X26TripletList::updateInternalData()
ActivePosition activePosition;
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++) {
triplet = &m_list[i];
triplet->m_error = X26Triplet::NoError;
@@ -173,6 +174,21 @@ void X26TripletList::updateInternalData()
if (triplet->m_data >= 0x18)
triplet->m_reservedData = true;
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
if ((triplet->m_data & 0x3f) >= 48)
// Should really be an error?
@@ -182,6 +198,34 @@ void X26TripletList::updateInternalData()
triplet->m_activePositionRow = activePosition.row();
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)

View File

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