Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@
/qtads.pro.user*
/qrc_resources.cpp
/.qmake.stash
.cache
compile_commands.json
38 changes: 17 additions & 21 deletions src/confdialog.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "confdialog.h"

#include "globals.h"
#include "qstringconverter_base.h"
#include "settings.h"
#include "sysframe.h"
#include "syswingroup.h"
Expand All @@ -10,7 +11,9 @@
#include <QCheckBox>
#include <QColorDialog>
#include <QPushButton>
#include <QTextCodec>
#include <QStringConverter>
#include <QStringDecoder>
#include <QStringEncoder>
#include <algorithm>
#include <vector>

Expand Down Expand Up @@ -40,27 +43,20 @@ ConfDialog::ConfDialog(CHtmlSysWinGroupQt* const parent)
ui->linkClickedColorButton->setFixedSize(macSize);
#endif

const auto aliases = QTextCodec::availableCodecs();
std::vector<QByteArray> codecs;
for (const auto& alias : aliases) {
auto codecName = QTextCodec::codecForName(alias)->name();
// Only allow some of the possible sets, otherwise we would get a big
// list with most of the encodings being irrelevant. The only Unicode
// encoding we allow is UTF-8, since it's a single-byte character set
// and therefore can be used by TADS 2 games (though I'm not aware of
// any that actually use UTF-8.)
if (codecName == "UTF-8" or codecName.startsWith("windows-") or codecName.startsWith("ISO-")
or codecName.startsWith("KOI8-") or codecName.startsWith("IBM")
or codecName.startsWith("EUC-") or codecName.startsWith("jisx020")
or codecName.startsWith("cp949"))
{
codecs.emplace_back(std::move(codecName));
}
}
std::sort(codecs.begin(), codecs.end());
const QStringConverter::Encoding codecs[9]{
QStringConverter::Encoding::System, QStringConverter::Encoding::Latin1,
QStringConverter::Encoding::Utf16, QStringConverter::Encoding::Utf16BE,
QStringConverter::Encoding::Utf16LE, QStringConverter::Encoding::Utf32,
QStringConverter::Encoding::Utf32BE, QStringConverter::Encoding::Utf32LE,
QStringConverter::Encoding::Utf8,
};
Comment on lines +46 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this list actually makes much sense. As the comment in the removed code said, in the original set only UTF-8 was included (despite even that not being common in practice) because at least TADS 2 has a one-byte-per-character format. So UTF16 is unlikely-to-impossible, and I don't think I've ever seen UTF32-encoded... anything. Anywhere. Ever.

On my (Fedora Linux) system, presumably thanks to the ICU library, QStringConverter::availableCodecs() produces a list of 233 codecs, pretty much a drop-in replacement for QTextCodec::availableCodecs(). Filtering that list by the same criteria as the old code leaves these 78:

['UTF-8',
 'ISO-8859-1',
 'ISO-8859-2',
 'ISO-8859-3',
 'ISO-8859-4',
 'ISO-8859-5',
 'ISO-8859-6',
 'ISO-8859-7',
 'ISO-8859-8',
 'ISO-8859-9',
 'ISO-8859-10',
 'ISO-8859-13',
 'ISO-8859-14',
 'ISO-8859-15',
 'EUC-JP',
 'EUC-KR',
 'windows-874-2000',
 'IBM437',
 'IBM775',
 'IBM850',
 'IBM852',
 'IBM855',
 'IBM857',
 'IBM00858',
 'IBM860',
 'IBM861',
 'IBM862',
 'IBM863',
 'IBM864',
 'IBM865',
 'IBM866',
 'IBM868',
 'IBM869',
 'KOI8-R',
 'KOI8-U',
 'windows-1250',
 'windows-1251',
 'windows-1252',
 'windows-1253',
 'windows-1254',
 'windows-1255',
 'windows-1256',
 'windows-1257',
 'windows-1258',
 'ISO-2022-JP',
 'ISO-2022-JP-1',
 'ISO-2022-JP-2',
 'ISO-2022-KR',
 'ISO-2022-CN',
 'ISO-2022-CN-EXT',
 'IBM037',
 'IBM273',
 'IBM277',
 'IBM278',
 'IBM280',
 'IBM284',
 'IBM285',
 'IBM290',
 'IBM297',
 'IBM420',
 'IBM424',
 'IBM500',
 'IBM-Thai',
 'IBM870',
 'IBM871',
 'IBM918',
 'IBM1026',
 'IBM1047',
 'IBM01140',
 'IBM01141',
 'IBM01142',
 'IBM01143',
 'IBM01144',
 'IBM01145',
 'IBM01146',
 'IBM01147',
 'IBM01148',
 'IBM01149']

That's probably excessive, but OTOH some legacy codecs like windows-1250, windows-1252, ISO-8859-15, etc. were extremely common in older files, and still come up fairly often even today.

for (const auto& codec : codecs) {
if (ui->encodingComboBox->findText(QString::fromLatin1(codec)) == -1) {
ui->encodingComboBox->addItem(QString::fromLatin1(codec));
if (ui->encodingComboBox->findText(
QString::fromLatin1(QStringConverter::nameForEncoding(codec)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to QStringConverter's docs, nameForEncoding() outputs a const char * string that's UTF-8 encoded, so fromLatin1() sounds like the wrong interpretation. It's probably best to just drop it, since QString will default to interpreting char * as UTF-8 anyway.

(fromLatin1() is used to override that default when dealing with strings that can be processed faster as 8-bit Latin1. But the names returned by nameForEncoding() aren't guaranteed to be 8-bit fixed-width.)

== -1)
{
ui->encodingComboBox->addItem(
QString::fromLatin1(QStringConverter::nameForEncoding(codec)));
}
}

Expand Down
12 changes: 5 additions & 7 deletions src/kcolorbutton.cc
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,9 @@ void KColorButton::paintEvent(QPaintEvent *)
if (hasFocus()) {
QRect focusRect = style->subElementRect(QStyle::SE_PushButtonFocusRect, &butOpt, this);
QStyleOptionFocusRect focusOpt;
focusOpt.init(this);
focusOpt.initFrom(this);
focusOpt.rect = focusRect;
focusOpt.backgroundColor = palette().background().color();
focusOpt.backgroundColor = palette().window().color();
style->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpt, &painter, this);
}
}
Expand All @@ -243,16 +243,14 @@ QSize KColorButton::sizeHint() const
{
QStyleOptionButton opt;
d->initStyleOption(&opt);
return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(40, 15), this).
expandedTo(QApplication::globalStrut());
return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(40, 15), this);
}

QSize KColorButton::minimumSizeHint() const
{
QStyleOptionButton opt;
d->initStyleOption(&opt);
return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(3, 3), this).
expandedTo(QApplication::globalStrut());
return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(3, 3), this);
}

void KColorButton::dragEnterEvent(QDragEnterEvent *event)
Expand Down Expand Up @@ -294,7 +292,7 @@ void KColorButton::mouseMoveEvent(QMouseEvent *e)
{
if ((e->buttons() & Qt::LeftButton) &&
(e->pos() - d->mPos).manhattanLength() > QApplication::startDragDistance()) {
_k_createDrag(color(), this)->start();
_k_createDrag(color(), this)->exec();
setDown(false);
}
}
Expand Down
61 changes: 48 additions & 13 deletions src/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,63 @@
auto main(int argc, char** argv) -> int
{
CHtmlResType::add_basic_types();
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
// No need to enable High Dpi scaling because it's always on
#elif QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
CHtmlSysFrameQt* app = new CHtmlSysFrameQt(
argc, argv, "QTads", QTADS_VERSION, "Nikos Chantziaras", {});
CHtmlSysFrameQt* app =
new CHtmlSysFrameQt(argc, argv, "QTads", QTADS_VERSION, "Nikos Chantziaras", {});
#if QT_VERSION <= QT_VERSION_CHECK(6, 0, 0)
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
QApplication::setDesktopFileName("nikos.chantziaras.qtads");
#endif

// Filename of the game to run.
QString gameFileName;
bool embed = false;

const QStringList& args = app->arguments();
if (args.size() == 2) {
if (QFile::exists(args.at(1))) {
gameFileName = args.at(1);
} else if (QFile::exists(args.at(1) + ".gam")) {
gameFileName = args.at(1) + ".gam";
} else if (QFile::exists(args.at(1) + ".t3")) {
gameFileName = args.at(1) + ".t3";
} else {
qWarning() << "File" << args.at(1) << "not found.";
if (args.size() >= 1) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Replace all of this tedious manual command-line parsing with,

#include <QCommandLineParser>

// ...

auto main(int argc, char** argv) -> int {

    QString gameFileName;
    bool embedOption = false;

    // ...

    QCommandLineParser parser;
    parser.setApplicationDescription("QTads: Interpreter for TADS 2 and 3 game files");
    parser.addHelpOption();
    parser.addVersionOption();
    parser.addPositionalArgument("FILE", "Game file to load");
    parser.addOptions({
                       {{"e", "embed"},
                        "Print the QTads window ID to stdout on startup (for embedding)"},
                       });

    parser.process(app);

    const QStringList args = parser.positionalArguments();

    embedOption = parser.isSet("embed");

    if (args.length() > 0) {
        gameFileName = checkFileName(args.at(0));
        if (gameFileName.isEmpty()) {
            qWarning() << "File" << args.at(0) << "not found.";
        }
    }

(And a helper checkFileName, something like:)

auto checkFileName(const QString& input) -> QString {
    QStringList fileExtensions;
    fileExtensions  << QLatin1String("")
                    << QStringLiteral(".gam")
                    << QStringLiteral(".t3");

    for (auto& ext : fileExtensions) {
        QString name = input + ext;
        if (QFile::exists(name)) {
            return name;
        }
    }
    return nullptr;
}

Qt will handle processing of -h / --help, -v / --version, failing with a message when given unrecognized arguments, and all of the other tedium involved in command-line parsing. When called with --help / --h it'll output something like this:

$ qtads --help
Usage: qtads [options] FILE
QTads: Interpreter for TADS 2 and 3 game files

Options:
  -h, --help     Displays help on commandline options.
  --help-all     Displays help including Qt specific options.
  -v, --version  Displays version information.
  -e, --embed    Print the QTads window ID to stdout on startup (for embedding)

Arguments:
  FILE           Game file to load

The Windows version will even show the help in a window, if QTads is launched from a script or desktop shortcut with a bad argument, instead of in a shell.

bool prevNonFlagArgument = false;

for (int i = 1; i < args.size(); ++i) {
const auto& arg = args.at(i);

if (!arg.startsWith("-")) {
if (prevNonFlagArgument) {
qWarning() << "It looks like you specified more than one non-flag command-line"
<< "argument at" << arg
<< "but QTADS can only accept one game file, so only the"
<< "first non flag argument will be used.";
} else if (gameFileName.isNull()) {
if (QFile::exists(arg)) {
gameFileName = arg;
} else if (QFile::exists(arg + ".gam")) {
gameFileName = arg + ".gam";
} else if (QFile::exists(arg + ".t3")) {
gameFileName = arg + ".t3";
} else {
qWarning() << "File" << arg << "not found.";
}
}

prevNonFlagArgument = true;
} else if (arg == "--help" || arg == "-h") {
qInfo() << "qtads [OPTIONS] [FILE]\n"
<< "\t--help\t\tThis help message\n"
<< "\t--embed\t\tPrint out the window id on startup so that qtads can be "
"embedded\n"
<< "\t-h\t\tSame as --help\n"
<< "\t-e\t\tSame as --embed";
} else if (arg == "--embed" || arg == "-e") {
embed = true;
} else {
qWarning() << "Unrecognized command line argument " << arg << ".";
return 1;
}
}
}

Expand All @@ -60,7 +94,8 @@ auto main(int argc, char** argv) -> int
}
#endif

QTimer::singleShot(0, app, [app, gameFileName] { app->entryPoint(gameFileName); });
QTimer::singleShot(
0, app, [app, embed, gameFileName] { app->entryPoint(gameFileName, embed); });
int ret = CHtmlSysFrameQt::exec();

delete app;
Expand Down
4 changes: 3 additions & 1 deletion src/missing.cc
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// This is copyrighted software. More information is at the end of this file.
#include <QDebug>
#include <QString>
#include <QTextCodec>
#include <QStringConverter>
#include <QStringDecoder>
#include <QStringEncoder>
#include <cctype>
#include <cstdlib>
#include <cstring>
Expand Down
40 changes: 29 additions & 11 deletions src/osqt.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/

// Make sure we get vasprintf() from cstdio, which in mingw is a GNU extension.
#include "qstringconverter_base.h"
#if defined(__MINGW32__) and not defined(_GNU_SOURCE)
#define _GNU_SOURCE
#define GNU_SOURCE_DEFINED
Expand All @@ -35,9 +36,11 @@
#include <QMessageBox>
#include <QPushButton>
#include <QStandardPaths>
#include <QStringConverter>
#include <QStringDecoder>
#include <QStringEncoder>
#include <QSysInfo>
#include <QTemporaryFile>
#include <QTextCodec>
#include <QTimer>
#include <algorithm>
#include <chrono>
Expand Down Expand Up @@ -615,8 +618,18 @@ auto os_strlwr(char* const s) -> char*
if (qFrame->tads3()) {
lower = QString::fromUtf8(s).toLower().toUtf8();
} else {
const auto* const codec = QTextCodec::codecForName(qFrame->settings().tads2Encoding);
lower = codec->fromUnicode(codec->toUnicode(s).toLower());
auto codec = QStringConverter::encodingForName(qFrame->settings().tads2Encoding);
if (codec.has_value()) {
QStringEncoder toUnicode{codec.value()};
const QByteArray encoded = toUnicode.encode(QString::fromLocal8Bit(s));
if (!toUnicode.hasError()) {
QStringDecoder fromUnicode{codec.value()};
auto decoded = fromUnicode.decode(encoded.toLower());
if (!fromUnicode.hasError()) {
lower = decoded.data;
}
}
}
}
std::memcpy(s, lower.constData(), lower.size() + 1);
return s;
Expand Down Expand Up @@ -678,7 +691,7 @@ void os_get_special_path(

case OS_GSP_T3_APP_DATA:
case OS_GSP_LOGFILE: {
const auto dirStr = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
const auto dirStr = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir dir(dirStr);
QByteArray result;
// Create the directory if it doesn't exist.
Expand Down Expand Up @@ -1304,9 +1317,10 @@ auto os_askfile(
filter += ";;" + QObject::tr("All Files") + " (*)";
}

const auto promptStr = qFrame->tads3()
? QString::fromUtf8(prompt)
: QTextCodec::codecForName(qFrame->settings().tads2Encoding)->toUnicode(prompt);
QStringEncoder toUnicode{
QStringConverter::encodingForName(qFrame->settings().tads2Encoding).value()};
const auto promptStr = qFrame->tads3() ? QString::fromUtf8(prompt)
: toUnicode(QString::fromLocal8Bit(prompt)).data;
const auto filename = prompt_type == OS_AFP_OPEN
? QFileDialog::getOpenFileName(qFrame->gameWindow(), promptStr, QDir::currentPath(), filter)
: QFileDialog::getSaveFileName(
Expand Down Expand Up @@ -1358,9 +1372,12 @@ auto os_input_dialog(
QMessageBox dialog(qWinGroup);

// We'll use that if we're running a T2 game.
const auto* const t2Codec = QTextCodec::codecForName(qFrame->settings().tads2Encoding);
QStringEncoder t2ToUnicode{
QStringConverter::encodingForName(qFrame->settings().tads2Encoding).value()};

dialog.setText(qFrame->tads3() ? QString::fromUtf8(prompt) : t2Codec->toUnicode(prompt));
dialog.setText(
qFrame->tads3() ? QString::fromUtf8(prompt)
: t2ToUnicode(QString::fromLocal8Bit(prompt)).data);

switch (icon_id) {
case OS_INDLG_ICON_NONE:
Expand Down Expand Up @@ -1405,8 +1422,9 @@ auto os_input_dialog(
} else {
for (int i = 0; i < button_count; ++i) {
Q_ASSERT(buttons[i] != nullptr);
const auto buttonText =
qFrame->tads3() ? QString::fromUtf8(buttons[i]) : t2Codec->toUnicode(buttons[i]);
const auto buttonText = qFrame->tads3()
? QString::fromUtf8(buttons[i])
: t2ToUnicode(QString::fromLocal8Bit(buttons[i])).data;
buttonList += dialog.addButton(buttonText, QMessageBox::AcceptRole);
}
}
Expand Down
33 changes: 21 additions & 12 deletions src/sysframe.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
#include <QMessageBox>
#include <QScreen>
#include <QStatusBar>
#include <QTextCodec>
#include <QStringConverter>
#include <QStringDecoder>
#include <QStringEncoder>
#include <Qt>
#include <cstdlib>

#include "gameinfodialog.h"
#include "qstringconverter_base.h"
#include "qtadshostifc.h"
#include "qtadssound.h"
#include "syswinaboutbox.h"
Expand Down Expand Up @@ -335,7 +339,7 @@ bool CHtmlSysFrameQt::event(QEvent* e)
}
#endif

void CHtmlSysFrameQt::entryPoint(QString gameFileName)
void CHtmlSysFrameQt::entryPoint(QString gameFileName, bool embed)
{
// Restore the application's size and position.
if (not fSettings.appGeometry.isEmpty()) {
Expand All @@ -344,6 +348,8 @@ void CHtmlSysFrameQt::entryPoint(QString gameFileName)
auto h = QApplication::primaryScreen()->availableSize().height() / 1.1;
fMainWin->resize(h, h);
}
if (embed)
qInfo() << "WinId: " << fMainWin->winId();
fMainWin->show();

// Do an online update check.
Expand Down Expand Up @@ -392,8 +398,10 @@ static auto find_font_match(const std::vector<QString>& font_names) -> QString
return system_font;
}
// Also try the font name without the "[foundry]" part.
auto clean_font_name = font_name.leftRef(font_name.lastIndexOf('[')).trimmed();
auto clean_system_font = system_font.leftRef(system_font.lastIndexOf('[')).trimmed();
QStringView font_view{font_name};
auto clean_font_name = font_view.left(font_name.lastIndexOf('[')).trimmed();
QStringView system_font_view{system_font};
auto clean_system_font = system_font_view.left(system_font.lastIndexOf('[')).trimmed();
if (clean_font_name.compare(clean_system_font, Qt::CaseInsensitive) == 0) {
return system_font;
}
Expand Down Expand Up @@ -457,7 +465,7 @@ auto CHtmlSysFrameQt::createFont(const CHtmlFontDesc* font_desc) -> CHtmlSysFont
// The face name field can contain multiple face names separated by
// commas. We split them into a list and try each one individualy.
const auto strList =
QString(QString::fromLatin1(newFontDesc.face)).split(',', QString::SkipEmptyParts);
QString(QString::fromLatin1(newFontDesc.face)).split(',', Qt::SkipEmptyParts);
for (int i = 0; i < strList.size(); ++i) {
auto s = strList.at(i).simplified().toLower();
if (s == QString::fromLatin1(HTMLFONT_TADS_SERIF).toLower()) {
Expand Down Expand Up @@ -591,8 +599,7 @@ auto CHtmlSysFrameQt::createFont(const CHtmlFontDesc* font_desc) -> CHtmlSysFont
// Workaround for QTBUG-76908 (wrong font variant is used and is out of sync with font metrics.)
new_font.setStyleName({});

new_font.setStyleStrategy(QFont::StyleStrategy(
QFont::PreferOutline | QFont::PreferQuality | QFont::ForceIntegerMetrics));
new_font.setStyleStrategy(QFont::StyleStrategy(QFont::PreferOutline | QFont::PreferQuality));
new_font.setUnderline(newFontDesc.underline);
new_font.setStrikeOut(newFontDesc.strikeout);
if (use_bold and weight < QFont::Bold) {
Expand Down Expand Up @@ -807,8 +814,9 @@ void CHtmlSysFrameQt::display_output(const textchar_t* buf, size_t len)
fBuffer.append(buf, len);
} else {
// TADS 2 does not use UTF-8; use the encoding from our settings.
QTextCodec* codec = QTextCodec::codecForName(fSettings.tads2Encoding);
fBuffer.append(codec->toUnicode(buf, len).toUtf8().constData());
QStringEncoder toUnicode{
QStringConverter::encodingForName(fSettings.tads2Encoding).value()};
fBuffer.append(toUnicode(QString::fromLocal8Bit(buf, len)).data.toLocal8Bit());
}
}

Expand Down Expand Up @@ -895,9 +903,10 @@ auto CHtmlSysFrameQt::get_input_event(unsigned long timeout, int use_timeout, os
info->href, fGameWin->pendingHrefEvent().toUtf8().constData(),
sizeof(info->href) - 1);
} else {
QTextCodec* codec = QTextCodec::codecForName(fSettings.tads2Encoding);
QStringDecoder fromUnicode{
QStringConverter::encodingForName(fSettings.tads2Encoding).value()};
strncpy(
info->href, codec->fromUnicode(fGameWin->pendingHrefEvent()).constData(),
info->href, fromUnicode(fGameWin->pendingHrefEvent().toUtf8()).data,
sizeof(info->href) - 1);
}
info->href[sizeof(info->href) - 1] = '\0';
Expand Down Expand Up @@ -1076,7 +1085,7 @@ void CHtmlSysFrameQt::remove_banner_window(CHtmlSysWin* win)

auto CHtmlSysFrameQt::get_exe_resource(
const textchar_t* /*resname*/, size_t /*resnamelen*/, textchar_t* /*fname_buf*/,
size_t /*fname_buf_len*/, unsigned long* /*seek_pos*/, unsigned long * /*siz*/) -> int
size_t /*fname_buf_len*/, unsigned long* /*seek_pos*/, unsigned long* /*siz*/) -> int
{
// qDebug() << Q_FUNC_INFO;
// qDebug() << "resname:" << resname << "fname_buf:" << fname_buf << "seek_pos:" << seek_pos;
Expand Down
Loading