diff --git a/.gitignore b/.gitignore index 97283cc..04fe903 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ /qtads.pro.user* /qrc_resources.cpp /.qmake.stash +.cache +compile_commands.json diff --git a/src/confdialog.cc b/src/confdialog.cc index 196b8ac..e2cd02d 100644 --- a/src/confdialog.cc +++ b/src/confdialog.cc @@ -2,6 +2,7 @@ #include "confdialog.h" #include "globals.h" +#include "qstringconverter_base.h" #include "settings.h" #include "sysframe.h" #include "syswingroup.h" @@ -10,7 +11,9 @@ #include #include #include -#include +#include +#include +#include #include #include @@ -40,27 +43,20 @@ ConfDialog::ConfDialog(CHtmlSysWinGroupQt* const parent) ui->linkClickedColorButton->setFixedSize(macSize); #endif - const auto aliases = QTextCodec::availableCodecs(); - std::vector 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, + }; 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))) + == -1) + { + ui->encodingComboBox->addItem( + QString::fromLatin1(QStringConverter::nameForEncoding(codec))); } } diff --git a/src/kcolorbutton.cc b/src/kcolorbutton.cc index 88e4a18..cf403eb 100644 --- a/src/kcolorbutton.cc +++ b/src/kcolorbutton.cc @@ -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); } } @@ -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) @@ -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); } } diff --git a/src/main.cc b/src/main.cc index c364fd9..9440a28 100644 --- a/src/main.cc +++ b/src/main.cc @@ -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) { + 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; + } } } @@ -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; diff --git a/src/missing.cc b/src/missing.cc index 947726f..9e24020 100644 --- a/src/missing.cc +++ b/src/missing.cc @@ -1,7 +1,9 @@ // This is copyrighted software. More information is at the end of this file. #include #include -#include +#include +#include +#include #include #include #include diff --git a/src/osqt.cc b/src/osqt.cc index 164715b..9484986 100644 --- a/src/osqt.cc +++ b/src/osqt.cc @@ -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 @@ -35,9 +36,11 @@ #include #include #include +#include +#include +#include #include #include -#include #include #include #include @@ -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; @@ -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. @@ -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( @@ -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: @@ -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); } } diff --git a/src/sysframe.cc b/src/sysframe.cc index 13f2512..7e646ad 100644 --- a/src/sysframe.cc +++ b/src/sysframe.cc @@ -8,10 +8,14 @@ #include #include #include -#include +#include +#include +#include +#include #include #include "gameinfodialog.h" +#include "qstringconverter_base.h" #include "qtadshostifc.h" #include "qtadssound.h" #include "syswinaboutbox.h" @@ -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()) { @@ -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. @@ -392,8 +398,10 @@ static auto find_font_match(const std::vector& 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; } @@ -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()) { @@ -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) { @@ -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()); } } @@ -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'; @@ -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; diff --git a/src/sysframe.h b/src/sysframe.h index 21ecf9d..26f92d8 100644 --- a/src/sysframe.h +++ b/src/sysframe.h @@ -75,6 +75,10 @@ class CHtmlSysFrameQt: public QApplication, public CHtmlSysFrame int fPendingWaitForKeystrokeCmd = 0; + // Are we running in embed mode (i.e. do we need to print out our window ID + // so another application can capture it?) + bool fEmbedMode; + // Run the game file contained in fNextGame. void fRunGame(); @@ -108,7 +112,7 @@ class CHtmlSysFrameQt: public QApplication, public CHtmlSysFrame public slots: // Replacement for main(). We need this so that we can start the Tads VM // after the QApplication main event loop has started. - void entryPoint(QString gameFileName); + void entryPoint(QString gameFileName, bool embed = false); public: CHtmlSysFrameQt( @@ -183,6 +187,11 @@ public slots: return fNonStopMode; } + void setEmbedMode(bool fmode) + { + fEmbedMode = fmode; + } + // Recalculate and adjust the sizes of all HTML banners. void adjustBannerSizes(); diff --git a/src/syswin.cc b/src/syswin.cc index cfc7a8f..581f8c6 100644 --- a/src/syswin.cc +++ b/src/syswin.cc @@ -539,7 +539,7 @@ auto CHtmlSysWinQt::measure_text(CHtmlSysFont* font, const textchar_t* str, size // subsequent text should be drawn. This is really what our caller needs // to know, otherwise letters will start jumping left and right when // selecting text or moving the text cursor. - return {tmpMetr.width(QString::fromUtf8(str, len)), tmpMetr.height()}; + return {tmpMetr.horizontalAdvance(QString::fromUtf8(str, len)), tmpMetr.height()}; } auto CHtmlSysWinQt::get_max_chars_in_width( @@ -614,7 +614,7 @@ void CHtmlSysWinQt::draw_text_space(int hilite, long x, long y, CHtmlSysFont* fo // Construct a string of spaces that's at least 'width' pixels wide. QString str(' '); const QFontMetrics& metr = painter.fontMetrics(); - while (metr.width(str) < wid) { + while (metr.horizontalAdvance(str) < wid) { str.append(' '); } diff --git a/src/syswin.h b/src/syswin.h index 583ff0e..10bc5fb 100644 --- a/src/syswin.h +++ b/src/syswin.h @@ -1,8 +1,8 @@ // This is copyrighted software. More information is at the end of this file. #pragma once #include -#include #include +#include #include "config.h" #include "globals.h" @@ -154,7 +154,7 @@ class CHtmlSysWinQt: public QScrollArea, public CHtmlSysWin auto get_pix_per_inch() -> long override { - return QApplication::desktop()->logicalDpiX(); + return QApplication::primaryScreen()->logicalDotsPerInchX(); } auto measure_text(class CHtmlSysFont* font, const textchar_t* str, size_t len, int* ascent) diff --git a/src/syswininput.cc b/src/syswininput.cc index 7fba0a7..784ab5e 100644 --- a/src/syswininput.cc +++ b/src/syswininput.cc @@ -5,7 +5,9 @@ #include #include #include -#include +#include +#include +#include #include #include #include @@ -215,7 +217,8 @@ void CHtmlSysWinInputQt::keyPressEvent(QKeyEvent* e) fTadsBuffer.start_of_line(false); fCastDispWidget->clearSelection(); } else if ( - e->matches(QKeySequence::MoveToEndOfLine) or e->matches(QKeySequence::MoveToEndOfBlock)) { + e->matches(QKeySequence::MoveToEndOfLine) or e->matches(QKeySequence::MoveToEndOfBlock)) + { fTadsBuffer.end_of_line(false); fCastDispWidget->clearSelection(); } else if (e->matches(QKeySequence::InsertParagraphSeparator)) { @@ -285,7 +288,8 @@ void CHtmlSysWinInputQt::keyPressEvent(QKeyEvent* e) } fTadsBuffer.start_of_line(true); } else if ( - e->matches(QKeySequence::SelectEndOfLine) or e->matches(QKeySequence::SelectEndOfBlock)) { + e->matches(QKeySequence::SelectEndOfLine) or e->matches(QKeySequence::SelectEndOfBlock)) + { if (not fTadsBuffer.has_sel_range()) { fCastDispWidget->clearSelection(); } @@ -326,7 +330,7 @@ void CHtmlSysWinInputQt::inputMethodEvent(QInputMethodEvent* e) if (fInputMode == SingleKeyInput) { fLastKeyEvent = static_cast(0); - fLastKeyText = 0; + fLastKeyText = '\0'; // If the keypress doesn't correspond to exactly one character, ignore // it. if (e->commitString().size() != 1) { @@ -349,7 +353,7 @@ void CHtmlSysWinInputQt::singleKeyPressEvent(QKeyEvent* event) Q_ASSERT(fInputMode == SingleKeyInput); fLastKeyEvent = static_cast(0); - fLastKeyText = 0; + fLastKeyText = '\0'; switch (event->key()) { case 0: @@ -484,11 +488,11 @@ void CHtmlSysWinInputQt::getInput( if (qFrame->tads3()) { strncpy(buf, fTadsBuffer.getbuf(), len); } else { - QTextCodec* codec = QTextCodec::codecForName(qFrame->settings().tads2Encoding); + QStringDecoder fromUnicode{ + QStringConverter::encodingForName(qFrame->settings().tads2Encoding).value()}; strncpy( buf, - codec->fromUnicode(QString::fromUtf8(fTadsBuffer.getbuf(), fTadsBuffer.getlen())) - .constData(), + fromUnicode(QByteArray::fromRawData(fTadsBuffer.getbuf(), fTadsBuffer.getlen())).data, len); } buf[len] = '\0'; @@ -681,7 +685,7 @@ auto CHtmlSysWinInputQt::getKeypress(unsigned long timeout, bool useTimeout, boo default: // If we got here, something went wrong. Just report a // space. - qWarning() << Q_FUNC_INFO << "unrecognized key event in switch:" << hex + qWarning() << Q_FUNC_INFO << "unrecognized key event in switch:" << Qt::hex << fLastKeyEvent; return ' '; }