Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kpkpass for openSUSE:Factory checked in at 2026-08-24 12:03:45 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kpkpass (Old) and /work/SRC/openSUSE:Factory/.kpkpass.new.1258 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kpkpass" Mon Aug 24 12:03:45 2026 rev:96 rq:1372457 version:26.08.0 Changes: -------- --- /work/SRC/openSUSE:Factory/kpkpass/kpkpass.changes 2026-07-02 22:07:33.840955360 +0200 +++ /work/SRC/openSUSE:Factory/.kpkpass.new.1258/kpkpass.changes 2026-08-24 12:06:11.042327307 +0200 @@ -1,0 +2,40 @@ +Sun Aug 16 07:27:36 UTC 2026 - Christophe Marin <[email protected]> + +- Update to 26.08.0 + * New feature release + * For more details please see: + * https://kde.org/announcements/gear/26.08.0/ +- No code change since 26.07.90 + +------------------------------------------------------------------- +Sat Aug 1 11:39:17 UTC 2026 - Christophe Marin <[email protected]> + +- Update to 26.07.90 + * New feature release +- No code change since 26.07.80 + +------------------------------------------------------------------- +Sat Jul 18 11:11:59 UTC 2026 - Christophe Marin <[email protected]> + +- Update to 26.07.80 + * New feature release +- Changes since 26.04.3: + * Add additional API for semantic tag seat information + * Add basic support for semantic tag seat information + * Also print ZIP decoding error when we fail to open a pkpass + * Expose semantic tag API to QML as well + * Expose translation catalog lookup in public API + * Add method for checking whether a field value contains rich text markup + * Support date-only/time-only field formats + * Fix organization and description fields not being translated + * Install Qt metatypes file + * Support multi-row auxiliary fields + * Add properties for checking whether a specific color is set + * Support rgba() color values + * Find image assets only existing in a higher pixel ratio variant as well + * Add support for semantic tags and new pass styles + * CMAKE_CXX_SCAN_FOR_MODULES is already defined in ecm + * Move CMakePresets.json.license to REUSE.toml + * Use prek support + +------------------------------------------------------------------- Old: ---- kpkpass-26.04.3.tar.xz kpkpass-26.04.3.tar.xz.sig New: ---- kpkpass-26.08.0.tar.xz kpkpass-26.08.0.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kpkpass.spec ++++++ --- /var/tmp/diff_new_pack.AgKKAP/_old 2026-08-24 12:06:12.195368366 +0200 +++ /var/tmp/diff_new_pack.AgKKAP/_new 2026-08-24 12:06:12.197368438 +0200 @@ -16,12 +16,12 @@ # -%define kf6_version 6.19.0 +%define kf6_version 6.27.0 %define qt6_version 6.9.0 %bcond_without released Name: kpkpass -Version: 26.04.3 +Version: 26.08.0 Release: 0 Summary: Library to parse Passbook files License: LGPL-2.1-or-later @@ -105,4 +105,5 @@ %{_includedir}/KPim6/KPkPass/ %{_kf6_cmakedir}/KPim6PkPass/ %{_kf6_libdir}/libKPim6PkPass.so +%{_qt6_metatypesdir}/qt6kpim6pkpass_metatypes.json ++++++ kpkpass-26.04.3.tar.xz -> kpkpass-26.08.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/.pre-commit-config.yaml new/kpkpass-26.08.0/.pre-commit-config.yaml --- old/kpkpass-26.04.3/.pre-commit-config.yaml 2026-06-05 07:05:26.000000000 +0200 +++ new/kpkpass-26.08.0/.pre-commit-config.yaml 2026-08-02 07:45:33.000000000 +0200 @@ -22,7 +22,7 @@ - id: check-executables-have-shebangs - id: check-toml - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v21.1.8 + rev: v22.1.5 hooks: - id: clang-format # force using this project's configuration rather @@ -30,14 +30,14 @@ args: ["--style=file:.clang-format-pre-commit"] - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.15.0 + rev: v0.15.12 hooks: # Run the linter. - id: ruff # Run the formatter. - id: ruff-format - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 + rev: v2.4.2 hooks: - id: codespell exclude: (textemoticons/core/data/emoji.json|textgrammarcheck/grammalecte/autotests/data/result1.json|texttranslator/translator/plugins/bergamot/autotests/data/modellanguages/models.json|textgrammarcheck/languagetool/autotests/data/test2.json|textautocorrection/core/autotests/data/custom-fr-ref.xml|textgrammarcheck/common/autotests/grammarresultutiltest.cpp|textautocorrection/core/autotests/data/custom-fr.xml) @@ -46,11 +46,11 @@ hooks: - id: gersemi - repo: https://github.com/DavidAnson/markdownlint-cli2 - rev: v0.20.0 + rev: v0.22.1 hooks: - id: markdownlint-cli2 files: \.(md|mdown|markdown)$ - repo: https://github.com/scop/pre-commit-shfmt - rev: v3.12.0-2 + rev: v3.13.1-1 hooks: - id: shfmt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/CMakeLists.txt new/kpkpass-26.08.0/CMakeLists.txt --- old/kpkpass-26.04.3/CMakeLists.txt 2026-06-05 07:05:26.000000000 +0200 +++ new/kpkpass-26.08.0/CMakeLists.txt 2026-08-02 07:45:33.000000000 +0200 @@ -2,15 +2,14 @@ # SPDX-FileCopyrightText: 2018-2026 Laurent Montel <[email protected]> # SPDX-License-Identifier: BSD-3-Clause -cmake_minimum_required(VERSION 3.27) -set(PIM_VERSION "6.7.3") +cmake_minimum_required(VERSION 3.29) +set(PIM_VERSION "6.8.0") project(KPkPass VERSION ${PIM_VERSION}) set(KF_MIN_VERSION "6.13.0") set(QT_REQUIRED_VERSION "6.9.0") find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) -set(CMAKE_CXX_SCAN_FOR_MODULES OFF) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDEInstallDirs) @@ -35,6 +34,9 @@ if(GIT_SOURCE_TARBALL) set(PRE_COMMIT_INSTALLED FALSE) + include(KDEGitCommitHooks) + include(KDEClangFormat) + kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) if(${CMAKE_SOURCE_DIR} STREQUAL ${PROJECT_SOURCE_DIR}) if(EXISTS "${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit") file(READ "${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit" FILE_CONTENTS) @@ -49,35 +51,38 @@ endif() endif() if(NOT ${PRE_COMMIT_INSTALLED}) - find_program(PRE_COMMIT_EXECUTABLE pre-commit) - if(PRE_COMMIT_EXECUTABLE) + function(install_hook executable hook_name) message( STATUS - "This module has pre-commit support. pre-commit binary was found. Installing pre-commit hook" + "This module has ${hook_name} support. ${hook_name} binary was found. Installing ${hook_name} hook" ) execute_process( COMMAND - "${PRE_COMMIT_EXECUTABLE}" install -f + "${executable}" install -f ERROR_QUIET RESULT_VARIABLE _precommitresult WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" ) if(_precommitresult EQUAL 0) - message(STATUS "Pre-commit installed.") + message(STATUS "${hook_name} installed.") else() message( WARNING - "Error during installing pre-commit hook: ${PRE_COMMIT_EXECUTABLE} project source dir ${PROJECT_SOURCE_DIR}" + "Error during installing ${hook_name} hook: ${executable} project source dir ${PROJECT_SOURCE_DIR}" ) - include(KDEGitCommitHooks) - include(KDEClangFormat) - kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) endif() + endfunction() + + find_program(PREK_EXECUTABLE prek) + if(PREK_EXECUTABLE) + install_hook(${PREK_EXECUTABLE} "Prek") else() - message(WARNING "This module has pre-commit support. pre-commit binary was not found.") - include(KDEGitCommitHooks) - include(KDEClangFormat) - kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) + find_program(PRE_COMMIT_EXECUTABLE pre-commit) + if(PRE_COMMIT_EXECUTABLE) + install_hook(${PRE_COMMIT_EXECUTABLE} "pre-commit") + else() + message(WARNING "This module has pre-commit support. pre-commit binary was not found.") + endif() endif() endif() endif() @@ -119,7 +124,7 @@ # see e.g. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56556 or https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79328 string(APPEND CMAKE_CXX_FLAGS " -Wshadow") endif() -ecm_set_disabled_deprecation_versions(QT 6.11.0 KF 6.23.0) +ecm_set_disabled_deprecation_versions(QT 6.11.0 KF 6.28.0) option(USE_UNITY_CMAKE_SUPPORT "Use UNITY cmake support (speedup compile time)" OFF) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/CMakePresets.json.license new/kpkpass-26.08.0/CMakePresets.json.license --- old/kpkpass-26.04.3/CMakePresets.json.license 2026-06-05 07:05:26.000000000 +0200 +++ new/kpkpass-26.08.0/CMakePresets.json.license 1970-01-01 01:00:00.000000000 +0100 @@ -1,2 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2026 Laurent Montel <[email protected]> -# SPDX-License-Identifier: BSD-3-Clause diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/REUSE.toml new/kpkpass-26.08.0/REUSE.toml --- old/kpkpass-26.04.3/REUSE.toml 2026-06-05 07:05:26.000000000 +0200 +++ new/kpkpass-26.08.0/REUSE.toml 2026-08-02 07:45:33.000000000 +0200 @@ -8,3 +8,9 @@ precedence = "aggregate" SPDX-FileCopyrightText = "none" SPDX-License-Identifier = "CC0-1.0" + +[[annotations]] +path = ["CMakePresets.json"] +precedence = "aggregate" +SPDX-FileCopyrightText = "2021-2026 Laurent Montel <[email protected]>" +SPDX-License-Identifier = "BSD-3-Clause" Binary files old/kpkpass-26.04.3/autotests/data/apple-store-UA-sample-unsigned-scrubbed.pkpass and new/kpkpass-26.08.0/autotests/data/apple-store-UA-sample-unsigned-scrubbed.pkpass differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/autotests/data/apple-store-UA-sample-unsigned-scrubbed.pkpass.license new/kpkpass-26.08.0/autotests/data/apple-store-UA-sample-unsigned-scrubbed.pkpass.license --- old/kpkpass-26.04.3/autotests/data/apple-store-UA-sample-unsigned-scrubbed.pkpass.license 1970-01-01 01:00:00.000000000 +0100 +++ new/kpkpass-26.08.0/autotests/data/apple-store-UA-sample-unsigned-scrubbed.pkpass.license 2026-08-02 07:45:33.000000000 +0200 @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: none +SPDX-License-Identifier: CC0-1.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/autotests/fieldtest.cpp new/kpkpass-26.08.0/autotests/fieldtest.cpp --- old/kpkpass-26.04.3/autotests/fieldtest.cpp 2026-06-05 07:05:26.000000000 +0200 +++ new/kpkpass-26.08.0/autotests/fieldtest.cpp 2026-08-02 07:45:33.000000000 +0200 @@ -45,26 +45,77 @@ KPkPass::Field f(obj, pass.get()); QCOMPARE(f.value().userType(), QMetaType::QDateTime); QCOMPARE(f.value(), QDateTime({2021, 6, 27}, {14, 30}, QTimeZone::fromSecondsAheadOfUtc(7200))); - QCOMPARE(f.valueDisplayString(), QLatin1StringView("27/06/2021 14:30")); - - obj = QJsonDocument::fromJson(R"({"key":"valid-date","label":"Datum","dateStyle":"PKDateStyleShort","value":"2021-06-27T00:00:00+02:00"})").object(); - f = KPkPass::Field(obj, pass.get()); - QCOMPARE(f.value().userType(), QMetaType::QDateTime); - QCOMPARE(f.value().toDateTime().date(), QDate({2021, 6, 27})); - QCOMPARE(f.valueDisplayString(), QLatin1StringView("27/06/2021")); obj = QJsonDocument::fromJson(R"({"key":"valid-locations","label":"Ort","value":"Freibad Killesberg\n"})").object(); f = KPkPass::Field(obj, pass.get()); - QCOMPARE(f.value().userType(), QMetaType::QString); QCOMPARE(f.value(), QLatin1StringView("Freibad Killesberg\n")); QCOMPARE(f.valueDisplayString(), QLatin1StringView("Freibad Killesberg")); + QCOMPARE(f.row(), 0); obj = QJsonDocument::fromJson(R"({"key":"booking-number","label":"Buchungsnummer","value":1234567894})").object(); f = KPkPass::Field(obj, pass.get()); QCOMPARE(f.value().userType(), QMetaType::Double); QCOMPARE(f.valueDisplayString(), "1234567894"_L1); } + + static void testDateTimeFormat_data() + { + QTest::addColumn<QByteArray>("field"); + QTest::addColumn<QString>("output"); + + QTest::newRow("implicit-short-date-only") + << R"({"key":"valid-date","label":"Datum","dateStyle":"PKDateStyleShort","value":"2021-06-27T00:00:00+02:00"})"_ba << u"27/06/2021"_s; + QTest::newRow("explicit-short-date-only") + << R"({"key":"valid-date","label":"Datum","dateStyle":"PKDateStyleShort","value":"2021-06-27T12:35:00+02:00","timeStyle":"PKDateStyleNone"})"_ba + << u"27/06/2021"_s; + QTest::newRow("implicit-time-only") << R"({"key":"valid-date","label":"Datum","timeStyle":"PKDateStyleShort","value":"2021-06-27T12:34:00+02:00"})"_ba + << u"12:34"_s; + QTest::newRow("explicit-time-only") + << R"({"key":"valid-date","label":"Datum","dateStyle":"PKDateStyleNone","timeStyle":"PKDateStyleShort","value":"2021-06-27T12:34:00+02:00"})"_ba + << u"12:34"_s; + QTest::newRow("implicit-all") << R"({"key":"valid-date","label":"Datum","value":"2021-06-27T12:34:00+02:00"})"_ba << u"27/06/2021 12:34"_s; + QTest::newRow("explicit-long-all") + << R"({"key":"valid-date","label":"Datum","value":"2021-06-27T12:34:00+02:00","dateStyle":"PKDateStyleFull","timeStyle":"PKDateStyleLong"})"_ba + << u"dimanche 27 juin 2021 12:34:00 UTC+02:00"_s; + } + + static void testDateTimeFormat() + { + QFETCH(QByteArray, field); + QFETCH(QString, output); + + std::unique_ptr<KPkPass::Pass> pass(KPkPass::Pass::fromFile(QStringLiteral(SOURCE_DIR "/data/boardingpass-v1.pkpass"))); + QVERIFY(pass); + const auto obj = QJsonDocument::fromJson(field).object(); + const auto f = KPkPass::Field(obj, pass.get()); + QCOMPARE(f.value().userType(), QMetaType::QDateTime); + QCOMPARE(f.valueDisplayString(), output); + } + + static void testIsRichText_data() + { + QTest::addColumn<QString>("value"); + QTest::addColumn<bool>("output"); + + QTest::newRow("empty") << QString() << false; + QTest::newRow("plain") << u"Frankfurt (Main) Hbf"_s << false; + QTest::newRow("link") << u"<a href=\"https://maps.apple.com/?q=Frankfurt+%28Main%29+Hbf&ll=50.107149%2C8.663785\">Frankfurt (Main) Hbf</a>"_s << true; + QTest::newRow("format") << u"bla <b>bla</b> bla"_s << true; + QTest::newRow("entity") << u"Via: <1080>(L*EF*FD/SDL*WOB*H*GOE*KS)"_s << true; + } + + static void testIsRichText() + { + QFETCH(QString, value); + QFETCH(bool, output); + + std::unique_ptr<KPkPass::Pass> pass(KPkPass::Pass::fromFile(QStringLiteral(SOURCE_DIR "/data/boardingpass-v1.pkpass"))); + QVERIFY(pass); + const auto obj = QJsonObject{{"value"_L1, value}}; + const auto f = KPkPass::Field(obj, pass.get()); + QCOMPARE(f.isRichText(), output); + } }; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/autotests/pkpasstest.cpp new/kpkpass-26.08.0/autotests/pkpasstest.cpp --- old/kpkpass-26.04.3/autotests/pkpasstest.cpp 2026-06-05 07:05:26.000000000 +0200 +++ new/kpkpass-26.08.0/autotests/pkpasstest.cpp 2026-08-02 07:45:33.000000000 +0200 @@ -7,13 +7,17 @@ #include "barcode.h" #include "boardingpass.h" #include "location.h" +#include "seat.h" +#include <QJsonObject> #include <QLocale> #include <QTest> #include <QTimeZone> #include <cmath> +using namespace Qt::Literals; + class PkPassTest : public QObject { Q_OBJECT @@ -30,11 +34,15 @@ QCOMPARE(pass->type(), KPkPass::Pass::BoardingPass); QCOMPARE(pass->serialNumber(), QLatin1StringView("1234")); - QCOMPARE(pass->description(), QLatin1StringView("description")); + QCOMPARE(pass->description(), "KDE Bordkarte"_L1); + QCOMPARE(pass->lookupMessage(u"description"_s), "KDE Bordkarte"_L1); QCOMPARE(pass->organizationName(), QLatin1StringView("KDE")); QCOMPARE(pass->logoText(), QLatin1StringView("Boarding Pass")); + QVERIFY(pass->hasBackgroundColor()); QCOMPARE(pass->backgroundColor(), QColor(61, 174, 233)); + QVERIFY(pass->hasForegroundColor()); + QVERIFY(pass->hasLabelColor()); QCOMPARE(pass->relevantDate(), QDateTime(QDate(2017, 9, 17), QTime(0, 4, 0), QTimeZone::UTC)); QCOMPARE(pass->expirationDate(), QDateTime(QDate(2017, 9, 18), QTime(0, 0, 36), QTimeZone::UTC)); QCOMPARE(pass->isVoided(), false); @@ -55,10 +63,16 @@ QCOMPARE(field.value().toString(), QLatin1StringView("20:25")); QCOMPARE(field.textAlignment(), Qt::AlignLeft); + QCOMPARE(pass->auxiliaryFieldsRowCount(), 1); + QCOMPARE(pass->auxiliaryFields().size(), 4); + QCOMPARE(pass->auxiliaryFieldsInRow(0).size(), 4); + QCOMPARE(pass->auxiliaryFieldsInRow(1).size(), 0); + auto boardingPass = dynamic_cast<KPkPass::BoardingPass *>(pass.get()); QVERIFY(boardingPass); QCOMPARE(boardingPass->transitType(), KPkPass::BoardingPass::Air); + QVERIFY(pass->hasBarcode()); auto barcodes = pass->barcodes(); QCOMPARE(barcodes.size(), 1); auto bc = barcodes.at(0); @@ -88,10 +102,41 @@ img = pass->image(QStringLiteral("I don't exist")); QVERIFY(img.isNull()); + QVERIFY(pass->preferredStyleSchemes().isEmpty()); + QCOMPARE(pass->seats().size(), 0); + auto sourceFile = QFile(QStringLiteral(SOURCE_DIR "/data/boardingpass-v1.pkpass")); QVERIFY(sourceFile.open(QFile::ReadOnly)); QCOMPARE(pass->rawData(), sourceFile.readAll()); } + + static void testSemanticTags() + { + std::unique_ptr<KPkPass::Pass> pass(KPkPass::Pass::fromFile(u"" SOURCE_DIR "/data/apple-store-UA-sample-unsigned-scrubbed.pkpass"_s)); + QVERIFY(pass); + const auto semanticTags = pass->semanticTags(); + QVERIFY(!semanticTags.isEmpty()); + QCOMPARE(semanticTags.value("departureAirportName"_L1).toString(), "O'Hare International Airport"_L1); + + QCOMPARE(pass->rawValue("changeSeatURL"_L1).toString(), "https://www.united.com/en/us/fly/travel/trip-planning/seat-options-and-upgrades.html"_L1); + + QVERIFY(pass->hasBarcode()); + QCOMPARE(pass->barcodes().size(), 1); + QCOMPARE(pass->barcodes().front().format(), KPkPass::Barcode::Aztec); + + const auto styles = pass->preferredStyleSchemes(); + QCOMPARE(styles.size(), 2); + QCOMPARE(styles.front(), "semanticBoardingPass"_L1); + + const auto seats = pass->seats(); + QCOMPARE(seats.size(), 1); + const auto &seat = seats[0]; + QCOMPARE(seat.seatRow(), "52"_L1); + QCOMPARE(seat.hasSeatNumber(), true); + QCOMPARE(seat.seatNumber(), "C"_L1); + QCOMPARE(seat.hasSeatSection(), false); + QCOMPARE(seat.asAirplaneSeat(), "52C"_L1); + } }; QTEST_GUILESS_MAIN(PkPassTest) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/src/CMakeLists.txt new/kpkpass-26.08.0/src/CMakeLists.txt --- old/kpkpass-26.04.3/src/CMakeLists.txt 2026-06-05 07:05:26.000000000 +0200 +++ new/kpkpass-26.08.0/src/CMakeLists.txt 2026-08-02 07:45:33.000000000 +0200 @@ -6,7 +6,9 @@ add_library(KPim6PkPass) add_library(KPim6::PkPass ALIAS KPim6PkPass) -qt_extract_metatypes(KPim6PkPass) +qt_extract_metatypes(KPim6PkPass OUTPUT_FILES METATYPES_FILE) +get_filename_component(METATYPES_FILE_NAME ${METATYPES_FILE} NAME) +target_sources(KPim6PkPass INTERFACE $<INSTALL_INTERFACE:${KDE_INSTALL_QTMETATYPESDIR}/${METATYPES_FILE_NAME}>) target_sources( KPim6PkPass @@ -19,6 +21,7 @@ pass.h pass_p.h passes.cpp + seat.cpp location.h field.h boardingpass.h @@ -69,6 +72,7 @@ Location Pass Passes + Seat REQUIRED_HEADERS KPkPass_HEADERS ) @@ -80,6 +84,9 @@ ${CMAKE_BINARY_DIR}/src/kpkpass_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_PIM}/KPkPass ) +if(KDE_INSTALL_QTMETATYPESDIR) # since 6.27 + install(FILES ${METATYPES_FILE} DESTINATION ${KDE_INSTALL_QTMETATYPESDIR}) +endif() if(NOT ANDROID) if(SharedMimeInfo_VERSION_STRING VERSION_LESS 2.2) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/src/barcode.cpp new/kpkpass-26.08.0/src/barcode.cpp --- old/kpkpass-26.04.3/src/barcode.cpp 2026-06-05 07:05:26.000000000 +0200 +++ new/kpkpass-26.08.0/src/barcode.cpp 2026-08-02 07:45:33.000000000 +0200 @@ -10,6 +10,7 @@ #include <QJsonObject> +using namespace Qt::Literals; using namespace KPkPass; namespace KPkPass @@ -44,19 +45,27 @@ return {}; } +struct { + const char *name; + KPkPass::Barcode::Format format; +} static constexpr const barcode_formats[] = { + {"PKBarcodeFormatQR", Barcode::QR}, + {"PKBarcodeFormatPDF417", Barcode::PDF417}, + {"PKBarcodeFormatAztec", Barcode::Aztec}, + {"PKBarcodeFormatCode128", Barcode::Code128}, + {"PKBarcodeFormatCode39", Barcode::Code39}, + {"PKBarcodeFormatCodabar", Barcode::Codabar}, + {"PKBarcodeFormatEAN13", Barcode::EAN13}, + {"PKBarcodeFormatI2of5", Barcode::I2of5}, +}; + Barcode::Format KPkPass::Barcode::format() const { - const auto format = d->obj.value(QLatin1StringView("format")).toString(); - if (format == QLatin1StringView("PKBarcodeFormatQR")) { - return QR; - } else if (format == QLatin1StringView("PKBarcodeFormatPDF417")) { - return PDF417; - } else if (format == QLatin1StringView("PKBarcodeFormatAztec")) { - return Aztec; - } else if (format == QLatin1StringView("PKBarcodeFormatCode128")) { - return Code128; - } - return Invalid; + const auto format = d->obj.value("format"_L1); + const auto it = std::ranges::find_if(barcode_formats, [format](const auto &f) { + return QLatin1StringView(f.name) == format; + }); + return it != std::end(barcode_formats) ? (*it).format : Invalid; } QString Barcode::message() const diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/src/barcode.h new/kpkpass-26.08.0/src/barcode.h --- old/kpkpass-26.04.3/src/barcode.h 2026-06-05 07:05:26.000000000 +0200 +++ new/kpkpass-26.08.0/src/barcode.h 2026-08-02 07:45:33.000000000 +0200 @@ -41,7 +41,11 @@ QR, PDF417, Aztec, - Code128 + Code128, + Code39, + Codabar, + EAN13, + I2of5, }; Q_ENUM(Format) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/src/field.cpp new/kpkpass-26.08.0/src/field.cpp --- old/kpkpass-26.04.3/src/field.cpp 2026-06-05 07:05:26.000000000 +0200 +++ new/kpkpass-26.08.0/src/field.cpp 2026-08-02 07:45:33.000000000 +0200 @@ -80,6 +80,12 @@ return {}; } +constexpr inline const auto PKDateStyleNone = "PKDateStyleNone"_L1; +constexpr inline const auto PKDateStyleShort = "PKDateStyleShort"_L1; +constexpr inline const auto PKDateStyleMedium = "PKDateStyleMedium"_L1; +constexpr inline const auto PKDateStyleLong = "PKDateStyleLong"_L1; +constexpr inline const auto PKDateStyleFull = "PKDateStyleFull"_L1; + QString Field::valueDisplayString() const { const auto v = value(); @@ -88,15 +94,21 @@ // however, real-world data doesn't strictly follow that, so we have to guess a bit here... if (v.typeId() == QMetaType::QDateTime) { const auto dt = v.toDateTime(); + const auto dateStyle = d->obj.value("dateStyle"_L1); + const auto timeStyle = d->obj.value("timeStyle"_L1); + auto fmt = QLocale::ShortFormat; - const auto dtStyle = d->obj.value(QLatin1StringView("dateStyle")).toString(); - if (dtStyle == QLatin1StringView("PKDateStyleLong") || dtStyle == QLatin1StringView("PKDateStyleFull")) { + if (dateStyle == PKDateStyleLong || dateStyle == PKDateStyleFull) { fmt = QLocale::LongFormat; } - const auto timeStyle = d->obj.value(QLatin1StringView("timeStyle")).toString(); - if (timeStyle == QLatin1StringView("PKDateStyleNone") || (timeStyle.isEmpty() && !dtStyle.isEmpty() && dt.time() == QTime(0, 0))) { + // time only + if (timeStyle == PKDateStyleNone || (timeStyle.isUndefined() && !dateStyle.isUndefined())) { return QLocale().toString(dt.date(), fmt); } + // date only + if (dateStyle == PKDateStyleNone || (dateStyle.isUndefined() && !timeStyle.isUndefined())) { + return QLocale().toString(dt.time(), QLocale::ShortFormat); + } return QLocale().toString(dt, fmt); } @@ -144,4 +156,29 @@ return d->obj.value("currencyCode"_L1).toString(); } +int Field::row() const +{ + return d->obj.value("row"_L1).toInt(0); +} + +bool Field::isRichText() const +{ + const auto v = value(); + if (v.typeId() != QMetaType::QString) { + return false; + } + const auto text = v.toString(); + auto idx = text.indexOf('<'_L1); + if (idx >= 0 && idx < text.size() - 2) { + return text[idx + 1].isLetter() || text[idx + 1] == '/'_L1; + } + idx = text.indexOf('&'_L1); + for (auto i = idx; idx >= 0 && idx - i < 5; ++i) { + if (text[i] == ';'_L1) { + return true; + } + } + return false; +} + #include "moc_field.cpp" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/src/field.h new/kpkpass-26.08.0/src/field.h --- old/kpkpass-26.04.3/src/field.h 2026-06-05 07:05:26.000000000 +0200 +++ new/kpkpass-26.08.0/src/field.h 2026-08-02 07:45:33.000000000 +0200 @@ -39,6 +39,8 @@ Q_PROPERTY(QString changeMessage READ changeMessage CONSTANT) Q_PROPERTY(Qt::Alignment textAlignment READ textAlignment CONSTANT) Q_PROPERTY(QString currencyCode READ currencyCode CONSTANT) + Q_PROPERTY(int row READ row CONSTANT) + Q_PROPERTY(bool isRichText READ isRichText CONSTANT) public: Field(); @@ -71,6 +73,14 @@ /*! Currency code. */ [[nodiscard]] QString currencyCode() const; + /*! Row index of an (auxiliary) field. */ + [[nodiscard]] int row() const; + + /*! Returns whether the field value contains rich text markup. + * \since 26.08 + */ + [[nodiscard]] bool isRichText() const; + private: friend class PassPrivate; friend class FieldTest; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/src/pass.cpp new/kpkpass-26.08.0/src/pass.cpp --- old/kpkpass-26.04.3/src/pass.cpp 2026-06-05 07:05:26.000000000 +0200 +++ new/kpkpass-26.08.0/src/pass.cpp 2026-08-02 07:45:33.000000000 +0200 @@ -10,6 +10,7 @@ #include "location.h" #include "logging.h" #include "pass_p.h" +#include "seat.h" #include <KZip> @@ -26,6 +27,7 @@ #include <cctype> +using namespace Qt::Literals; using namespace KPkPass; static const char *const passTypes[] = {"boardingPass", "coupon", "eventTicket", "generic", "storeCard"}; @@ -165,13 +167,17 @@ return !messages.isEmpty(); } -QList<Field> PassPrivate::fields(QLatin1StringView fieldType, const Pass *q) const +QList<Field> PassPrivate::fields(QLatin1StringView fieldType, const Pass *q, int row) const { const auto a = passData().value(fieldType).toArray(); QList<Field> f; f.reserve(a.size()); for (const auto &v : a) { - f.push_back(Field{v.toObject(), q}); + Field fld{v.toObject(), q}; + if (row >= 0 && fld.row() != row) { + continue; + } + f.push_back(std::move(fld)); } return f; } @@ -180,12 +186,14 @@ { std::unique_ptr<KZip> zip(new KZip(device.get())); if (!zip->open(QIODevice::ReadOnly)) { + qCWarning(Log) << "Failed to open ZIP file" << zip->errorString(); return nullptr; } // extract pass.json auto file = zip->directory()->file(QStringLiteral("pass.json")); if (!file) { + qCWarning(Log) << "Cannot find pass.json file"; return nullptr; } std::unique_ptr<QIODevice> dev(file->createDevice()); @@ -256,12 +264,12 @@ QString Pass::description() const { - return d->passObj.value(QLatin1StringView("description")).toString(); + return d->message(d->passObj.value("description"_L1).toString()); } QString Pass::organizationName() const { - return d->passObj.value(QLatin1StringView("organizationName")).toString(); + return d->message(d->passObj.value("organizationName"_L1).toString()); } QString Pass::passTypeIdentifier() const @@ -306,25 +314,48 @@ return QDateTime::fromString(d->passObj.value(QLatin1StringView("relevantDate")).toString(), Qt::ISODate); } -static QColor parseColor(const QString &s) +static QColor parseColor(QStringView s) { - if (s.startsWith(QLatin1StringView("rgb("), Qt::CaseInsensitive)) { - const auto l = QStringView(s).mid(4, s.length() - 5).split(QLatin1Char(',')); - if (l.size() != 3) + if (s.startsWith("rgb("_L1, Qt::CaseInsensitive)) { + const auto l = s.mid(4, s.length() - 5).split(','_L1); + if (l.size() != 3) { return {}; + } return QColor(l[0].trimmed().toInt(), l[1].trimmed().toInt(), l[2].trimmed().toInt()); } + if (s.startsWith("rgba("_L1, Qt::CaseInsensitive)) { + const auto l = s.mid(5, s.length() - 6).split(','_L1); + if (l.size() != 4) { + return {}; + } + return QColor(l[0].trimmed().toInt(), l[1].trimmed().toInt(), l[2].trimmed().toInt(), l[3].trimmed().toDouble() * 255.0); + } return QColor(s); } +bool Pass::hasBackgroundColor() const +{ + return d->passObj.value("backgroundColor"_L1).isString(); +} + +bool Pass::hasForegroundColor() const +{ + return d->passObj.value("foregroundColor"_L1).isString(); +} + +bool Pass::hasLabelColor() const +{ + return d->passObj.value("labelColor"_L1).isString() || hasForegroundColor(); +} + QColor Pass::backgroundColor() const { - return parseColor(d->passObj.value(QLatin1StringView("backgroundColor")).toString()); + return parseColor(d->passObj.value("backgroundColor"_L1).toString()); } QColor Pass::foregroundColor() const { - return parseColor(d->passObj.value(QLatin1StringView("foregroundColor")).toString()); + return parseColor(d->passObj.value("foregroundColor"_L1).toString()); } QString Pass::groupingIdentifier() const @@ -334,7 +365,7 @@ QColor Pass::labelColor() const { - const auto c = parseColor(d->passObj.value(QLatin1StringView("labelColor")).toString()); + const auto c = parseColor(d->passObj.value("labelColor"_L1).toString()); if (c.isValid()) { return c; } @@ -351,8 +382,8 @@ const auto entries = d->zip->directory()->entries(); for (const auto &entry : entries) { if (entry.startsWith(baseName) - && (QStringView(entry).mid(baseName.size()).startsWith(QLatin1Char('@')) || QStringView(entry).mid(baseName.size()).startsWith(QLatin1Char('.'))) - && entry.endsWith(QLatin1StringView(".png"))) { + && (QStringView(entry).mid(baseName.size()).startsWith('@'_L1) || QStringView(entry).mid(baseName.size()).startsWith('.'_L1)) + && entry.endsWith(".png"_L1)) { return true; } } @@ -369,6 +400,16 @@ return hasImage(QStringLiteral("logo")); } +bool Pass::hasPrimaryLogo() const +{ + return hasImage(u"primaryLogo"_s); +} + +bool Pass::hasSecondaryLogo() const +{ + return hasImage(u"secondaryLogo"_s); +} + bool Pass::hasStrip() const { return hasImage(QStringLiteral("strip")); @@ -379,6 +420,11 @@ return hasImage(QStringLiteral("background")); } +bool Pass::hasArtwork() const +{ + return hasImage(u"artwork"_s); +} + bool Pass::hasFooter() const { return hasImage(QStringLiteral("footer")); @@ -402,17 +448,32 @@ break; } if (dpr > 1) { - file = d->zip->directory()->file(baseName + QLatin1Char('@') + QString::number(dpr) + QLatin1StringView("x.png")); + file = d->zip->directory()->file(baseName + '@'_L1 + QString::number(dpr) + "x.png"_L1); } else { - file = d->zip->directory()->file(baseName + QLatin1StringView(".png")); + file = d->zip->directory()->file(baseName + ".png"_L1); } - if (file) + if (file) { break; + } } if (!img.isNull()) { // cache hit return img; } + // no hit, check if there is any variant at all (happens in passes only containing eg. a 3x variant) + // (matches what hasImage does) + if (!file) { + const auto entries = d->zip->directory()->entries(); + for (const auto &entry : entries) { + if (entry.startsWith(baseName) + && (QStringView(entry).mid(baseName.size()).startsWith('@'_L1) || QStringView(entry).mid(baseName.size()).startsWith('.'_L1)) + && entry.endsWith(".png"_L1)) { + file = d->zip->directory()->file(entry); + break; + } + } + } + if (!file) { return {}; } @@ -437,6 +498,16 @@ return image(QStringLiteral("logo"), devicePixelRatio); } +QImage Pass::primaryLogo(unsigned int devicePixelRatio) const +{ + return image(u"primaryLogo"_s, devicePixelRatio); +} + +QImage Pass::secondaryLogo(unsigned int devicePixelRatio) const +{ + return image(u"secondaryLogo"_s, devicePixelRatio); +} + QImage Pass::strip(unsigned int devicePixelRatio) const { return image(QStringLiteral("strip"), devicePixelRatio); @@ -447,6 +518,11 @@ return image(QStringLiteral("background"), devicePixelRatio); } +QImage Pass::artwork(unsigned int devicePixelRatio) const +{ + return image(u"artwork"_s, devicePixelRatio); +} + QImage Pass::footer(unsigned int devicePixelRatio) const { return image(QStringLiteral("footer"), devicePixelRatio); @@ -462,6 +538,17 @@ return d->passObj.value(QLatin1StringView("authenticationToken")).toString(); } +QStringList Pass::preferredStyleSchemes() const +{ + const auto styleArray = d->passObj.value("preferredStyleSchemes"_L1).toArray(); + QStringList l; + l.reserve(styleArray.size()); + std::ranges::transform(styleArray, std::back_inserter(l), [](const auto &v) { + return v.toString(); + }); + return l; +} + QUrl Pass::webServiceUrl() const { return QUrl(d->passObj.value(QLatin1StringView("webServiceURL")).toString()); @@ -477,21 +564,28 @@ return url; } +bool Pass::hasBarcode() const +{ + return !d->passObj.value("barcodes"_L1).toArray().isEmpty() || !d->passObj.value("barcode"_L1).toObject().isEmpty(); +} + QList<Barcode> Pass::barcodes() const { QList<Barcode> codes; // barcodes array - const auto a = d->passObj.value(QLatin1StringView("barcodes")).toArray(); + const auto a = d->passObj.value("barcodes"_L1).toArray(); codes.reserve(a.size()); - for (const auto &bc : a) + for (const auto &bc : a) { codes.push_back(Barcode(bc.toObject(), this)); + } // just a single barcode if (codes.isEmpty()) { - const auto bc = d->passObj.value(QLatin1StringView("barcode")).toObject(); - if (!bc.isEmpty()) + const auto bc = d->passObj.value("barcode"_L1).toObject(); + if (!bc.isEmpty()) { codes.push_back(Barcode(bc, this)); + } } return codes; @@ -500,6 +594,17 @@ static const char *const fieldNames[] = {"auxiliaryFields", "backFields", "headerFields", "primaryFields", "secondaryFields"}; static const auto fieldNameCount = sizeof(fieldNames) / sizeof(fieldNames[0]); +int Pass::auxiliaryFieldsRowCount() const +{ + int maxRow = 0; + const auto auxFields = d->passData().value(QLatin1StringView(fieldNames[0])).toArray(); + for (const auto &v : auxFields) { + const auto auxField = v.toObject(); + maxRow = std::max(maxRow, auxField.value("row"_L1).toInt(0)); + } + return maxRow + 1; +} + QList<Field> Pass::auxiliaryFields() const { return d->fields(QLatin1StringView(fieldNames[0]), this); @@ -525,6 +630,11 @@ return d->fields(QLatin1StringView(fieldNames[4]), this); } +QList<Field> Pass::auxiliaryFieldsInRow(int row) const +{ + return d->fields(QLatin1StringView(fieldNames[0]), this, row); +} + Field Pass::field(const QString &key) const { for (unsigned int i = 0; i < fieldNameCount; ++i) { @@ -547,6 +657,41 @@ return fs; } +QList<Seat> Pass::seats() const +{ + const auto a = semanticTags().value("seats"_L1).toArray(); + QList<Seat> seats; + seats.reserve(a.size()); + std::ranges::transform(a, std::back_inserter(seats), [this](const QJsonValue &v) { + return Seat(v.toObject(), this); + }); + return seats; +} + +QJsonObject Pass::semanticTags() const +{ + return d->passObj.value("semantics"_L1).toObject(); +} + +QString Pass::lookupMessage(const QString &msg) const +{ + return d->message(msg); +} + +QJsonValue Pass::rawValue(const QString &key) const +{ + return d->passObj.value(key); +} + +QJsonValue Pass::rawValue(QStringView key) const +{ + return d->passObj.value(key); +} +QJsonValue Pass::rawValue(QLatin1StringView key) const +{ + return d->passObj.value(key); +} + Pass *Pass::fromData(const QByteArray &data, QObject *parent) { std::unique_ptr<QBuffer> buffer(new QBuffer); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/src/pass.h new/kpkpass-26.08.0/src/pass.h --- old/kpkpass-26.04.3/src/pass.h 2026-06-05 07:05:26.000000000 +0200 +++ new/kpkpass-26.08.0/src/pass.h 2026-08-02 07:45:33.000000000 +0200 @@ -26,6 +26,7 @@ class Barcode; class Location; class PassPrivate; +class Seat; /*! * \sa https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/PassKit_PG/index.html @@ -50,6 +51,10 @@ Q_PROPERTY(QDateTime relevantDate READ relevantDate CONSTANT) + Q_PROPERTY(bool hasBackgroundColor READ hasBackgroundColor CONSTANT) + Q_PROPERTY(bool hasForegroundColor READ hasForegroundColor CONSTANT) + Q_PROPERTY(bool hasLabelColor READ hasLabelColor CONSTANT) + Q_PROPERTY(QColor backgroundColor READ backgroundColor CONSTANT) Q_PROPERTY(QColor foregroundColor READ foregroundColor CONSTANT) Q_PROPERTY(QString groupingIdentifier READ groupingIdentifier CONSTANT) @@ -58,13 +63,20 @@ Q_PROPERTY(bool hasIcon READ hasIcon CONSTANT) Q_PROPERTY(bool hasLogo READ hasLogo CONSTANT) + Q_PROPERTY(bool hasPrimaryLogo READ hasPrimaryLogo CONSTANT) + Q_PROPERTY(bool hasSecondaryLogo READ hasSecondaryLogo CONSTANT) Q_PROPERTY(bool hasStrip READ hasStrip CONSTANT) Q_PROPERTY(bool hasBackground READ hasBackground CONSTANT) + Q_PROPERTY(bool hasArtwork READ hasArtwork CONSTANT) Q_PROPERTY(bool hasFooter READ hasFooter CONSTANT) Q_PROPERTY(bool hasThumbnail READ hasThumbnail CONSTANT) - // needs to be QVariantList just for QML (Grantlee would also work with QList<Field> + Q_PROPERTY(QStringList preferredStyleSchemes READ preferredStyleSchemes CONSTANT) + + Q_PROPERTY(bool hasBarcode READ hasBarcode CONSTANT) Q_PROPERTY(QList<KPkPass::Barcode> barcodes READ barcodes CONSTANT) + + Q_PROPERTY(int auxiliaryFieldsRowCount READ auxiliaryFieldsRowCount CONSTANT) Q_PROPERTY(QList<KPkPass::Field> auxiliaryFields READ auxiliaryFields CONSTANT) Q_PROPERTY(QList<KPkPass::Field> backFields READ backFields CONSTANT) Q_PROPERTY(QList<KPkPass::Field> headerFields READ headerFields CONSTANT) @@ -73,6 +85,10 @@ Q_PROPERTY(QList<KPkPass::Location> locations READ locations CONSTANT) Q_PROPERTY(QVariantMap field READ fieldsVariantMap CONSTANT) + Q_PROPERTY(QList<KPkPass::Seat> seats READ seats CONSTANT) + + Q_PROPERTY(QJsonObject semanticTags READ semanticTags CONSTANT) + public: ~Pass() override; @@ -106,7 +122,13 @@ // visual appearance keys /*! Returns all barcodes defined in the pass. */ + [[nodiscard]] bool hasBarcode() const; [[nodiscard]] QList<Barcode> barcodes() const; + + [[nodiscard]] bool hasBackgroundColor() const; + [[nodiscard]] bool hasForegroundColor() const; + [[nodiscard]] bool hasLabelColor() const; + [[nodiscard]] QColor backgroundColor() const; [[nodiscard]] QColor foregroundColor() const; [[nodiscard]] QString groupingIdentifier() const; @@ -117,13 +139,16 @@ * \a baseName The name of the asset, without the file type and high dpi extensions. * \since 5.20.41 */ - bool hasImage(const QString &baseName) const; - bool hasIcon() const; - bool hasLogo() const; - bool hasStrip() const; - bool hasBackground() const; - bool hasFooter() const; - bool hasThumbnail() const; + [[nodiscard]] bool hasImage(const QString &baseName) const; + [[nodiscard]] bool hasIcon() const; + [[nodiscard]] bool hasLogo() const; + [[nodiscard]] bool hasPrimaryLogo() const; + [[nodiscard]] bool hasSecondaryLogo() const; + [[nodiscard]] bool hasStrip() const; + [[nodiscard]] bool hasBackground() const; + [[nodiscard]] bool hasArtwork() const; + [[nodiscard]] bool hasFooter() const; + [[nodiscard]] bool hasThumbnail() const; /*! Returns an image asset of this pass. * \a baseName The name of the asset, without the file name extension. @@ -134,15 +159,32 @@ Q_INVOKABLE [[nodiscard]] QImage icon(unsigned int devicePixelRatio = 1) const; /*! Returns the pass logo. */ Q_INVOKABLE [[nodiscard]] QImage logo(unsigned int devicePixelRatio = 1) const; + /*! Returns the pass primary logo. + * \since 26.08 + */ + Q_INVOKABLE [[nodiscard]] QImage primaryLogo(unsigned int devicePixelRatio = 1) const; + /*! Returns the pass secondary logo. + * \since 26.08 + */ + Q_INVOKABLE [[nodiscard]] QImage secondaryLogo(unsigned int devicePixelRatio = 1) const; /*! Returns the strip image if present. */ Q_INVOKABLE [[nodiscard]] QImage strip(unsigned int devicePixelRatio = 1) const; /*! Returns the background image if present. */ Q_INVOKABLE [[nodiscard]] QImage background(unsigned int devicePixelRatio = 1) const; + /*! Returns the artwork background image if present. + * \since 26.08 + */ + Q_INVOKABLE [[nodiscard]] QImage artwork(unsigned int devicePixelRatio = 1) const; /*! Returns the footer image if present. */ Q_INVOKABLE [[nodiscard]] QImage footer(unsigned int devicePixelRatio = 1) const; /*! Returns the thumbnail image if present. */ Q_INVOKABLE [[nodiscard]] QImage thumbnail(unsigned int devicePixelRatio = 1) const; + /*! Preferred style schemes. + * \since 26.08 + */ + [[nodiscard]] QStringList preferredStyleSchemes() const; + // web service keys [[nodiscard]] QString authenticationToken() const; [[nodiscard]] QUrl webServiceUrl() const; @@ -151,16 +193,51 @@ */ [[nodiscard]] QUrl passUpdateUrl() const; - QList<Field> auxiliaryFields() const; - QList<Field> backFields() const; - QList<Field> headerFields() const; - QList<Field> primaryFields() const; - QList<Field> secondaryFields() const; + /*! Number of auxiliary field rows. + * \since 26.08 + */ + [[nodiscard]] int auxiliaryFieldsRowCount() const; + + [[nodiscard]] QList<Field> auxiliaryFields() const; + [[nodiscard]] QList<Field> backFields() const; + [[nodiscard]] QList<Field> headerFields() const; + [[nodiscard]] QList<Field> primaryFields() const; + [[nodiscard]] QList<Field> secondaryFields() const; + + /*! Returns only those auxiliary fields in \p row. + * \since 26.08 + */ + Q_INVOKABLE [[nodiscard]] QList<Field> auxiliaryFieldsInRow(int row) const; /*! Returns the field with key \a key. */ - Field field(const QString &key) const; + [[nodiscard]] Field field(const QString &key) const; /*! Returns all fields found in this pass. */ - QList<Field> fields() const; + [[nodiscard]] QList<Field> fields() const; + + /*! Returns seat information specified in semantic tag data. + * \since 26.08 + */ + [[nodiscard]] QList<Seat> seats() const; + + /*! Returns the semantic tags, if available. + * \since 26.08 + */ + [[nodiscard]] QJsonObject semanticTags() const; + + /*! Lookup a message in the passes translation catalog. + * This is mainly necessary for semantic tags or raw values of + * type localized string, other properties already do this internally. + * \since 26.08 + */ + Q_INVOKABLE [[nodiscard]] QString lookupMessage(const QString &msg) const; + + /*! Returns a raw entry from this pass' \c pass.json. + * Useful e.g. for semi-standardized additional flight information values. + * \since 26.08 + */ + Q_INVOKABLE [[nodiscard]] QJsonValue rawValue(const QString &key) const; + [[nodiscard]] QJsonValue rawValue(QStringView key) const; + [[nodiscard]] QJsonValue rawValue(QLatin1StringView key) const; /*! Create a appropriate sub-class based on the pkpass file type. */ static Pass *fromData(const QByteArray &data, QObject *parent = nullptr); @@ -172,13 +249,14 @@ * all the pass data. * \since 5.20.41 */ - QByteArray rawData() const; + [[nodiscard]] QByteArray rawData() const; protected: ///\\ond internal friend class Barcode; friend class Field; friend class PassPrivate; + friend class Seat; explicit Pass(Type passType, QObject *parent = nullptr); std::unique_ptr<PassPrivate> d; ///@endcond diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/src/pass_p.h new/kpkpass-26.08.0/src/pass_p.h --- old/kpkpass-26.04.3/src/pass_p.h 2026-06-05 07:05:26.000000000 +0200 +++ new/kpkpass-26.08.0/src/pass_p.h 2026-08-02 07:45:33.000000000 +0200 @@ -6,6 +6,8 @@ #pragma once +#include "pass.h" + #include <QHash> #include <QImage> #include <QJsonObject> @@ -40,14 +42,14 @@ { public: /** The pass data structure of the pass.json file. */ - QJsonObject passData() const; + [[nodiscard]] QJsonObject passData() const; /** Localized message for the given key. */ - QString message(const QString &key) const; + [[nodiscard]] QString message(const QString &key) const; void parse(); bool parseMessages(const QString &lang); - QList<Field> fields(QLatin1StringView fieldType, const Pass *q) const; + [[nodiscard]] QList<Field> fields(QLatin1StringView fieldType, const Pass *q, int row = -1) const; static Pass *fromData(std::unique_ptr<QIODevice> device, QObject *parent); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/src/quick/types.h new/kpkpass-26.08.0/src/quick/types.h --- old/kpkpass-26.04.3/src/quick/types.h 2026-06-05 07:05:26.000000000 +0200 +++ new/kpkpass-26.08.0/src/quick/types.h 2026-08-02 07:45:33.000000000 +0200 @@ -7,6 +7,7 @@ #include <barcode.h> #include <boardingpass.h> #include <field.h> +#include <seat.h> #define FOREIGN_ENUM_GADGET(Class) \ class Class##Derived : public KPkPass::Class \ @@ -48,3 +49,10 @@ QML_FOREIGN(KPkPass::Barcode) }; FOREIGN_ENUM_GADGET(Barcode) + +class SeatForeign +{ + Q_GADGET + QML_VALUE_TYPE(seat) + QML_FOREIGN(KPkPass::Seat) +}; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/src/seat.cpp new/kpkpass-26.08.0/src/seat.cpp --- old/kpkpass-26.04.3/src/seat.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/kpkpass-26.08.0/src/seat.cpp 2026-08-02 07:45:33.000000000 +0200 @@ -0,0 +1,98 @@ +/* + SPDX-FileCopyrightText: 2026 Volker Krause <[email protected]> + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "seat.h" +#include "pass.h" +#include "pass_p.h" + +#include <QJsonObject> + +using namespace Qt::Literals; +using namespace KPkPass; + +namespace KPkPass +{ +class SeatPrivate +{ +public: + const Pass *pass = nullptr; + QJsonObject obj; +}; +} + +Seat::Seat() + : d(new SeatPrivate) +{ +} + +Seat::Seat(const Seat &) = default; +Seat::Seat(Seat &&) noexcept = default; +Seat::~Seat() = default; +Seat &Seat::operator=(const Seat &) = default; +Seat &Seat::operator=(Seat &&) noexcept = default; + +Seat::Seat(const QJsonObject &obj, const Pass *pass) + : d(new SeatPrivate) +{ + d->pass = pass; + d->obj = obj; +} + +bool Seat::hasSeatAisle() const +{ + return !seatAisle().isEmpty(); +} + +bool Seat::hasSeatLevel() const +{ + return !seatLevel().isEmpty(); +} + +bool Seat::hasSeatNumber() const +{ + return !seatNumber().isEmpty(); +} + +bool Seat::hasSeatRow() const +{ + return !seatRow().isEmpty(); +} + +bool Seat::hasSeatSection() const +{ + return !seatSection().isEmpty(); +} + +QString Seat::seatAisle() const +{ + return d->pass->d->message(d->obj.value("seatAisle"_L1).toString()); +} + +QString Seat::seatLevel() const +{ + return d->pass->d->message(d->obj.value("seatLevel"_L1).toString()); +} + +QString Seat::seatNumber() const +{ + return d->pass->d->message(d->obj.value("seatNumber"_L1).toString()); +} + +QString Seat::seatRow() const +{ + return d->pass->d->message(d->obj.value("seatRow"_L1).toString()); +} + +QString Seat::seatSection() const +{ + return d->pass->d->message(d->obj.value("seatSection"_L1).toString()); +} + +QString Seat::asAirplaneSeat() const +{ + return seatRow() + seatNumber(); +} + +#include "moc_seat.cpp" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kpkpass-26.04.3/src/seat.h new/kpkpass-26.08.0/src/seat.h --- old/kpkpass-26.04.3/src/seat.h 1970-01-01 01:00:00.000000000 +0100 +++ new/kpkpass-26.08.0/src/seat.h 2026-08-02 07:45:33.000000000 +0200 @@ -0,0 +1,78 @@ +/* + SPDX-FileCopyrightText: 2026 Volker Krause <[email protected]> + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#ifndef KPKPASS_SEAT_H +#define KPKPASS_SEAT_H + +#include "kpkpass_export.h" + +#include <qobjectdefs.h> + +class QJsonObject; + +namespace KPkPass +{ + +class Pass; +class SeatPrivate; + +/*! + * \brief Seat information in semantic tag data in a pkpass. + * + * \sa https://developer.apple.com/documentation/walletpasses/semantictagtype/seat-data.dictionary + * \class KPkPass::Seat + * \inmodule KPkPass + * \inheaderfile KPkPass/Seat + * \since 26.08 + */ +class KPKPASS_EXPORT Seat +{ + Q_GADGET + + Q_PROPERTY(bool hasSeatAisle READ hasSeatAisle) + Q_PROPERTY(bool hasSeatLevel READ hasSeatLevel) + Q_PROPERTY(bool hasSeatNumber READ hasSeatNumber) + Q_PROPERTY(bool hasSeatRow READ hasSeatRow) + Q_PROPERTY(bool hasSeatSection READ hasSeatSection) + + Q_PROPERTY(QString seatAisle READ seatAisle) + Q_PROPERTY(QString seatLevel READ seatLevel) + Q_PROPERTY(QString seatNumber READ seatNumber) + Q_PROPERTY(QString seatRow READ seatRow) + Q_PROPERTY(QString seatSection READ seatSection) + +public: + explicit Seat(); + Seat(const Seat &); + Seat(Seat &&) noexcept; + ~Seat(); + Seat &operator=(const Seat &); + Seat &operator=(Seat &&) noexcept; + + [[nodiscard]] bool hasSeatAisle() const; + [[nodiscard]] bool hasSeatLevel() const; + [[nodiscard]] bool hasSeatNumber() const; + [[nodiscard]] bool hasSeatRow() const; + [[nodiscard]] bool hasSeatSection() const; + + [[nodiscard]] QString seatAisle() const; + [[nodiscard]] QString seatLevel() const; + [[nodiscard]] QString seatNumber() const; + [[nodiscard]] QString seatRow() const; + [[nodiscard]] QString seatSection() const; + + /*! Format seat information in the usual airplane seat format. */ + Q_INVOKABLE [[nodiscard]] QString asAirplaneSeat() const; + +private: + friend class Pass; + explicit Seat(const QJsonObject &obj, const Pass *pass); + + std::shared_ptr<SeatPrivate> d; +}; + +} + +#endif
