File indexing completed on 2025-01-19 04:46:50
0001 /* 0002 SPDX-FileCopyrightText: 2017 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "itineraryrenderer.h" 0008 #include "itinerarykdeconnecthandler.h" 0009 #include "itinerarymemento.h" 0010 #include "itineraryurlhandler.h" 0011 0012 #include <MessageViewer/HtmlWriter> 0013 #include <MessageViewer/IconNameCache> 0014 #include <MessageViewer/MessagePartRendererManager> 0015 0016 #include <GrantleeTheme/GrantleeKi18nLocalizer> 0017 #include <GrantleeTheme/GrantleeThemeEngine> 0018 0019 #include <KItinerary/Flight> 0020 #include <KItinerary/Reservation> 0021 #include <KItinerary/Ticket> 0022 0023 #include <KColorScheme> 0024 #include <KTextTemplate/Engine> 0025 #include <KTextTemplate/MetaType> 0026 #include <KTextTemplate/Template> 0027 #include <Prison/Barcode> 0028 0029 #include <QGuiApplication> 0030 #include <QPalette> 0031 0032 using namespace KItinerary; 0033 0034 ItineraryRenderer::ItineraryRenderer() = default; 0035 0036 void ItineraryRenderer::setKDEConnectHandler(ItineraryKDEConnectHandler *kdeConnect) 0037 { 0038 m_kdeConnect = kdeConnect; 0039 } 0040 0041 bool ItineraryRenderer::render(const MimeTreeParser::MessagePartPtr &msgPart, 0042 MessageViewer::HtmlWriter *htmlWriter, 0043 MessageViewer::RenderContext *context) const 0044 { 0045 Q_UNUSED(context) 0046 const auto mpList = msgPart.dynamicCast<MimeTreeParser::MessagePartList>(); 0047 if (!msgPart->isRoot() || !mpList->hasSubParts()) { 0048 return false; 0049 } 0050 0051 const auto node = mpList->subParts().at(0)->content(); 0052 const auto nodeHelper = msgPart->nodeHelper(); 0053 if (!nodeHelper || !node) { 0054 return false; 0055 } 0056 0057 auto memento = dynamic_cast<ItineraryMemento *>(nodeHelper->bodyPartMemento(node->topLevel(), ItineraryMemento::identifier())); 0058 if (!memento || !memento->hasData()) { 0059 return false; 0060 } 0061 const auto extractedData = memento->data(); 0062 if (extractedData.isEmpty()) { // hasData() will not be correct for filtered structured data on the first pass through here... 0063 return false; 0064 } 0065 0066 const auto dir = nodeHelper->createTempDir(QStringLiteral("semantic")); 0067 auto c = MessageViewer::MessagePartRendererManager::self()->createContext(); 0068 0069 QVariantMap style; 0070 style.insert(QStringLiteral("expandIcon"), 0071 QString(QStringLiteral("file://") + MessageViewer::IconNameCache::instance()->iconPathFromLocal(QStringLiteral("quoteexpand.png")))); 0072 style.insert(QStringLiteral("collapseIcon"), 0073 QString(QStringLiteral("file://") + MessageViewer::IconNameCache::instance()->iconPathFromLocal(QStringLiteral("quotecollapse.png")))); 0074 style.insert(QStringLiteral("palette"), QGuiApplication::palette()); 0075 style.insert(QStringLiteral("viewScheme"), QVariant::fromValue(KColorScheme(QPalette::Normal, KColorScheme::View))); 0076 c.insert(QStringLiteral("style"), style); 0077 0078 const bool testMode = qEnvironmentVariableIsSet("BPF_ITINERARY_TESTMODE"); // ensure deterministic results for unit tests 0079 QVariantMap actionState; 0080 actionState.insert(QStringLiteral("canShowCalendar"), memento->startDate().isValid()); 0081 actionState.insert(QStringLiteral("canAddToCalendar"), memento->canAddToCalendar()); 0082 actionState.insert(QStringLiteral("hasItineraryApp"), ItineraryUrlHandler::hasItineraryApp() || testMode); 0083 if (!testMode) { 0084 const auto devices = m_kdeConnect->devices(); 0085 actionState.insert(QStringLiteral("canSendToDevice"), !devices.isEmpty()); 0086 if (devices.size() == 1) { 0087 actionState.insert(QStringLiteral("defaultDeviceName"), devices[0].name); 0088 actionState.insert(QStringLiteral("defaultDeviceId"), devices[0].deviceId); 0089 } 0090 } 0091 c.insert(QStringLiteral("actionState"), actionState); 0092 0093 // Grantlee can't do indexed map/array lookups, so we need to interleave this here already 0094 QVariantList elems; 0095 elems.reserve(extractedData.size()); 0096 int ticketTokenId = 0; 0097 for (int i = 0; i < extractedData.size(); ++i) { 0098 QVariantMap data; 0099 QVariantMap state; 0100 const auto d = extractedData.at(i); 0101 state.insert(QStringLiteral("expanded"), d.expanded); 0102 data.insert(QStringLiteral("state"), state); 0103 data.insert(QStringLiteral("groupId"), i); 0104 0105 QList<QVariant> reservations; 0106 reservations.reserve(d.reservations.count()); 0107 for (const auto &r : d.reservations) { 0108 QVariantMap m; 0109 m.insert(QStringLiteral("reservation"), r); 0110 0111 // generate ticket barcodes 0112 const auto ticket = JsonLd::convert<Reservation>(r).reservedTicket().value<Ticket>(); 0113 std::optional<Prison::Barcode> barcode; 0114 switch (ticket.ticketTokenType()) { 0115 case Ticket::AztecCode: 0116 barcode = Prison::Barcode::create(Prison::Aztec); 0117 break; 0118 case Ticket::QRCode: 0119 barcode = Prison::Barcode::create(Prison::QRCode); 0120 break; 0121 case Ticket::DataMatrix: 0122 barcode = Prison::Barcode::create(Prison::DataMatrix); 0123 break; 0124 case Ticket::Code128: 0125 barcode = Prison::Barcode::create(Prison::Code128); 0126 break; 0127 case Ticket::PDF417: 0128 barcode = Prison::Barcode::create(Prison::PDF417); 0129 break; 0130 default: 0131 break; 0132 } 0133 if (barcode) { 0134 const QVariant barcodeContent = ticket.ticketTokenData(); 0135 if (barcodeContent.userType() == QMetaType::QString) { 0136 barcode->setData(barcodeContent.toString()); 0137 } else { 0138 barcode->setData(barcodeContent.toByteArray()); 0139 } 0140 0141 const auto img = barcode->toImage(barcode->preferredSize(qGuiApp->devicePixelRatio())); 0142 const QString fileName = dir + QLatin1StringView("/ticketToken") + QString::number(ticketTokenId++) + QLatin1StringView(".png"); 0143 img.save(fileName); 0144 m.insert(QStringLiteral("ticketToken"), fileName); 0145 nodeHelper->addTempFile(fileName); 0146 } 0147 0148 reservations.push_back(m); 0149 } 0150 data.insert(QStringLiteral("reservations"), QVariant::fromValue(reservations)); 0151 elems.push_back(data); 0152 } 0153 c.insert(QStringLiteral("data"), elems); 0154 0155 auto t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("org.kde.messageviewer/itinerary/itinerary.html")); 0156 const_cast<KTextTemplate::Engine *>(t->engine())->addDefaultLibrary(QStringLiteral("kitinerary_ktexttemplate_extension")); 0157 dynamic_cast<GrantleeTheme::Engine *>(const_cast<KTextTemplate::Engine *>(t->engine())) 0158 ->localizer() 0159 ->setApplicationDomain(QByteArrayLiteral("messageviewer_semantic_plugin")); 0160 KTextTemplate::OutputStream s(htmlWriter->stream()); 0161 t->render(&s, &c); 0162 qobject_cast<GrantleeTheme::Engine *>(const_cast<KTextTemplate::Engine *>(t->engine())) 0163 ->localizer() 0164 ->setApplicationDomain(QByteArrayLiteral("libmessageviewer")); 0165 return false; // yes, false, we want the rest of the email rendered normally after this 0166 }