diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ee48a80..1f7ec9d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,8 @@ project(QtGrpc ) find_package(Qt6 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS BuildInternals Core) -find_package(Qt6 ${PROJECT_VERSION} CONFIG OPTIONAL_COMPONENTS Network Gui Widgets) +find_package(Qt6 ${PROJECT_VERSION} CONFIG OPTIONAL_COMPONENTS Network Gui Widgets Quick + QuickControls2) # Try to find Qt6::qtprotobufgen and Qt6::qtgrpcgen targets from host tools # when cross-compiling. diff --git a/dependencies.yaml b/dependencies.yaml index 9ae29c09..5feeae18 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -2,3 +2,6 @@ dependencies: ../qtbase: ref: 360f69b74b5e28ea1cfb0ed1ead624d0323dfe09 required: true + ../qtdeclarative: + ref: 0737bb84e2bcf6acafe8e9892179234fd0926bdf + required: false diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8392432f..6f592b24 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -5,4 +5,7 @@ qt_examples_build_begin(EXTERNAL_BUILD) add_subdirectory(protobuf) +if(TARGET Qt6::Grpc) + add_subdirectory(grpc) +endif() qt_examples_build_end() diff --git a/examples/grpc/CMakeLists.txt b/examples/grpc/CMakeLists.txt new file mode 100644 index 00000000..729bf62e --- /dev/null +++ b/examples/grpc/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(TARGET Qt6::Quick + AND TARGET Qt6::QuickControls2 + AND TARGET Qt6::qtprotobufgen + AND TARGET Qt6::qtgrpcgen) + qt_internal_add_example(magic8ball) +endif() diff --git a/examples/grpc/doc/images/answer.webp b/examples/grpc/doc/images/answer.webp new file mode 100644 index 00000000..07b474a6 Binary files /dev/null and b/examples/grpc/doc/images/answer.webp differ diff --git a/examples/grpc/doc/src/magic8ball.qdoc b/examples/grpc/doc/src/magic8ball.qdoc new file mode 100644 index 00000000..fba1437b --- /dev/null +++ b/examples/grpc/doc/src/magic8ball.qdoc @@ -0,0 +1,55 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example magic8ball + \meta category {Networking} + \ingroup qtgrpc-examples + \title Magic 8 Ball + + \brief Creating a HTTP2 connection between a Qt GRPC client and + a C++ gRPC server. + + Magic 8 ball shows an answer it receives from a server: + \image answer.webp + + Magic 8 ball has the following components: + \list + \li \c magic8ball Qt GRPC client application that includes + the \l {qt_add_protobuf} and \l {qt_add_grpc} + CMake functions for message and service Qt code generation. + \li \c SimpleGrpcServer application that calls C++ gRPC plugin + for generating server code and implementing simple server + logic. + \endlist + + Both components use generated messages from the protobuf schema + described in the \c {exampleservice.proto} file: + \quotefromfile magic8ball/proto/exampleservice.proto + \skipto syntax = "proto3"; + \printuntil + + The client application binds on the \c localhost with port + \c 50051: + \quotefromfile magic8ball/clientservice.cpp + \skipto new QGrpcHttp2Channel + \printuntil m_client->attachChannel(channel); + + And sends a request to the server part: + \quotefromfile magic8ball/clientservice.cpp + \skipto void ClientService::sendRequest() + \printuntil } + + Click the \uicontrol {Ask question} button to send + the request to the SimpleGrpcServer application. + + The SimpleGrpcServer application chooses a random answer from + the list of answers and sends the data to the client's port. + \quotefromfile magic8ball/grpc_server_example/serverrunner.cpp + \skipto Status ExampleServiceServiceImpl::answerMethod + \printuntil } + + After receiving a response the client application shows the answer. + +*/ + diff --git a/examples/grpc/magic8ball/AnimatedAnswer.qml b/examples/grpc/magic8ball/AnimatedAnswer.qml new file mode 100644 index 00000000..150b6618 --- /dev/null +++ b/examples/grpc/magic8ball/AnimatedAnswer.qml @@ -0,0 +1,42 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls + +Item { + id: root + + signal closed() + + property alias closingAnimation: closeAnimator + property alias openingAnimation: openAnimator + property alias animationText: result.text + + MagicText { + id: result + anchors.centerIn: parent + + font.pointSize: text.length > 12 ? 14 : 16 + color: "#2E53B6" + + ScaleAnimator on scale { + id: openAnimator + target: result + from: 0 + to: 1 + duration: 2000 + running: false + } + + ScaleAnimator on scale { + id: closeAnimator + target: result + from: 1 + to: 0 + duration: 2000 + running: false + onStopped: root.closed() + } + } +} diff --git a/examples/grpc/magic8ball/CMakeLists.txt b/examples/grpc/magic8ball/CMakeLists.txt new file mode 100644 index 00000000..9d645d20 --- /dev/null +++ b/examples/grpc/magic8ball/CMakeLists.txt @@ -0,0 +1,61 @@ +cmake_minimum_required(VERSION 3.16) +project(Magic8Ball LANGUAGES CXX) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/grpc/magic8ball") + +find_package(Qt6 REQUIRED COMPONENTS Core Protobuf Grpc Quick) + +qt_standard_project_setup() + +add_subdirectory(grpc_server_example) + +qt_add_executable(magic8ball + main.cpp +) + +qt_add_protobuf(magic8ball + PROTO_FILES + proto/exampleservice.proto +) + +qt_add_grpc(magic8ball CLIENT + PROTO_FILES + proto/exampleservice.proto +) + +set_target_properties(magic8ball PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +qt_add_qml_module(magic8ball + URI qtgrpc.examples.magic8ball + VERSION 1.0 + AUTO_RESOURCE_PREFIX + SOURCES + clientservice.h + clientservice.cpp + QML_FILES + "WaitingAnimation.qml" + "AnimatedAnswer.qml" + "MagicText.qml" + "ProgressDot.qml" + "Main.qml" +) + +target_link_libraries(magic8ball PRIVATE + Qt6::Core + Qt6::Quick + Qt6::Protobuf + Qt6::Grpc +) + +install(TARGETS magic8ball + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/grpc/magic8ball/MagicText.qml b/examples/grpc/magic8ball/MagicText.qml new file mode 100644 index 00000000..561f89d3 --- /dev/null +++ b/examples/grpc/magic8ball/MagicText.qml @@ -0,0 +1,14 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick + +Text { + wrapMode: Text.WordWrap + + horizontalAlignment: Text.AlignHCenter + font.family: "Helvetica" + font.pointSize: 16 + color: "#264BAF" + style: Text.Sunken +} diff --git a/examples/grpc/magic8ball/Main.qml b/examples/grpc/magic8ball/Main.qml new file mode 100644 index 00000000..f40256ba --- /dev/null +++ b/examples/grpc/magic8ball/Main.qml @@ -0,0 +1,210 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Shapes + +import qtgrpc.examples.magic8ball + +ApplicationWindow { + id: root + width: 665 + height: width + + minimumWidth: width + minimumHeight: height + + visible: true + title: qsTr("Magic-8-ball Qt GRPC Example") + + property string textAnswer: "" + property string textError: "" + + MagicText { + anchors.top: parent.top + anchors.topMargin: 20 + anchors.horizontalCenter: parent.horizontalCenter + + width: parent.width * 0.9 + height: parent.height/3 + + color: "black" + + text: qsTr("For fortune-telling and seeking advice ask the ball" + + " a yes-no question and press the button.") + } + + Rectangle { + id: magic8ball + + anchors.centerIn: parent + + width: 433 + height: width + + color: "#000000" + radius: 300 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: "#4b4b4b" } + GradientStop { position: 0.33; color: "#212121" } + GradientStop { position: 1.0; color: "#000000" } + } + } + + Rectangle { + + width: 244 + height: width + + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + anchors.horizontalCenterOffset: 6 + radius: 300 + + color: "#bababa" + } + + Rectangle { + id: magic8ballCenter + + anchors.centerIn: parent + + width: 244 + height: width + + color: "black" + border.width: 0.5 + border.color: "#bababa" + radius: 300 + + Shape { + anchors.centerIn: parent + width: 200 + height: 200 + ShapePath { + strokeWidth: 4 + strokeColor: "#213f94" + capStyle: ShapePath.RoundCap + + fillGradient: RadialGradient { + centerX: 100 + centerY: 100 + focalX: centerX + focalY: centerY + centerRadius: 50 + focalRadius: 0 + + GradientStop { position: 0; color: "#1C2F60" } + GradientStop { position: 0.5; color: "#000547" } + GradientStop { position: 1; color: "#000324" } + } + + startX: 10 + startY: 40 + + PathLine { x: 100.5; y: 190 } + PathLine { x: 188; y: 40 } + PathLine { x: 10; y: 40 } + } + } + } + + WaitingAnimation { + id: waitingAnimation + anchors.centerIn: parent + + visible: false + } + + AnimatedAnswer { + id: answer + + anchors.centerIn: parent + visible: false + } + + Connections { + target: ClientService + function onMessageRecieved(value) { + root.textAnswer = value + } + + function onErrorRecieved(value) { + root.textError = value + } + } + + Rectangle { + id: button + + anchors.bottom: parent.bottom + anchors.bottomMargin: 30 + anchors.horizontalCenter: parent.horizontalCenter + + width: 200 + height: 50 + radius: 10 + + color: handler.pressed ? "#a5a5a5" : "#bebebe" + + MagicText { + id: btnText + anchors.centerIn: parent + + text: qsTr("Ask question") + color: "black" + } + + TapHandler { + id: handler + + onTapped: animationTimeout.start() + } + } + + Connections { + target: answer.closingAnimation + function onStopped() { + answer.animationText = "" + answer.visible = false + waitingAnimation.visible = true + waitingAnimation.runAnimation = true + } + } + + Connections { + target: waitingAnimation + function onRunAnimationChanged() { + if (!waitingAnimation.runAnimation) { + answer.visible = true + answer.openingAnimation.start() + } + } + } + + Timer { + id: animationTimeout + + interval: 3000 + repeat: false + running: false + onTriggered: ClientService.setMessage() + + onRunningChanged: { + if (running) { + answer.closingAnimation.start() + ClientService.sendRequest() + } else { + waitingAnimation.runAnimation = false + waitingAnimation.visible = false + answer.animationText = root.textError === "" ? root.textAnswer : root.textError + } + } + } + + footer: MagicText { + text: root.textError === "" ? "" : "Please, start server: ../magic8ball/SimpleGrpcServer" + } +} diff --git a/examples/grpc/magic8ball/ProgressDot.qml b/examples/grpc/magic8ball/ProgressDot.qml new file mode 100644 index 00000000..6f59f862 --- /dev/null +++ b/examples/grpc/magic8ball/ProgressDot.qml @@ -0,0 +1,12 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick + +Rectangle { + width: 11 + height: width + + color: "#264BAF" + radius: 100 +} diff --git a/examples/grpc/magic8ball/WaitingAnimation.qml b/examples/grpc/magic8ball/WaitingAnimation.qml new file mode 100644 index 00000000..50c3154b --- /dev/null +++ b/examples/grpc/magic8ball/WaitingAnimation.qml @@ -0,0 +1,54 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick + +Rectangle { + id: root + + property bool runAnimation: false + + anchors.centerIn: parent + width: 100 + height: width + + color: "transparent" + + Row { + id: scene + anchors.centerIn: parent + spacing: 12 + + Repeater { + model: 4 + ProgressDot {} + } + } + + ScaleAnimator on scale { + id: openning + target: root + from: 0.3 + to: 1 + duration: 1000 + running: runAnimation + onStopped: closing.start() + easing.amplitude: 6.0 + easing.period: 2.5 + } + + ScaleAnimator on scale { + id: closing + target: root + from: 1 + to: 0.3 + duration: 1000 + running: false + onStopped: { + if (runAnimation) + openning.start() + } + easing.amplitude: 6.0 + easing.period: 2.5 + } +} diff --git a/examples/grpc/magic8ball/clientservice.cpp b/examples/grpc/magic8ball/clientservice.cpp new file mode 100644 index 00000000..5673261a --- /dev/null +++ b/examples/grpc/magic8ball/clientservice.cpp @@ -0,0 +1,50 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "clientservice.h" + +#include +#include + +using namespace qtgrpc::examples; + +ClientService::ClientService(QObject *parent) : + QObject(parent), + m_client(new ExampleService::Client), + m_response(new AnswerResponse) +{ + connect(m_client.get(), &qtgrpc::examples::ExampleService::Client::errorOccurred, + this, &ClientService::errorOccurred); + + auto channel = std::shared_ptr(new QGrpcHttp2Channel( + QUrl("http://localhost:50051", + QUrl::StrictMode), + QGrpcInsecureChannelCredentials() + | QGrpcInsecureCallCredentials())); + + m_client->attachChannel(channel); +} + +void ClientService::errorOccurred() +{ + qWarning() << "Connection error occurred. Have you started server part:" + " ../magic8ball/SimpleGrpcServer?"; + + emit errorRecieved("No connection\nto\nserver"); +} + +void ClientService::sendRequest() +{ + // clean error on UI before new request + emit errorRecieved(""); + + qtgrpc::examples::AnswerRequest request; + request.setMessage("sleep"); + + m_client->answerMethod(request, m_response.get()); +} + +void ClientService::setMessage() +{ + emit messageRecieved(m_response.get()->message()); +} diff --git a/examples/grpc/magic8ball/clientservice.h b/examples/grpc/magic8ball/clientservice.h new file mode 100644 index 00000000..68e67264 --- /dev/null +++ b/examples/grpc/magic8ball/clientservice.h @@ -0,0 +1,36 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef CLIENT_SERVICE_H +#define CLIENT_SERVICE_H + +#include +#include +#include + +#include + +#include "exampleservice.qpb.h" +#include "exampleservice_client.grpc.qpb.h" + +class ClientService : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON +public: + explicit ClientService(QObject *parent = nullptr); + Q_INVOKABLE void sendRequest(); + Q_INVOKABLE void setMessage(); + void errorOccurred(); + +signals: + void messageRecieved(const QString &value); + void errorRecieved(const QString &value); + +private: + std::unique_ptr m_client; + std::unique_ptr m_response; +}; + +#endif // CLIENT_SERVICE_H diff --git a/examples/grpc/magic8ball/grpc_server_example/CMakeLists.txt b/examples/grpc/magic8ball/grpc_server_example/CMakeLists.txt new file mode 100644 index 00000000..aa7f917d --- /dev/null +++ b/examples/grpc/magic8ball/grpc_server_example/CMakeLists.txt @@ -0,0 +1,82 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Qt6::Grpc module is not used directly in this project. But this allows to find Qt6::Grpc's +# dependencies without setting extra cmake module paths. +find_package(Qt6 COMPONENTS Grpc) +find_package(WrapgRPCPlugin) +find_package(WrapgRPC) + +if(NOT TARGET WrapgRPC::WrapgRPCPlugin OR NOT TARGET WrapProtoc::WrapProtoc + OR NOT TARGET WrapgRPC::WrapLibgRPC) + message(WARNING "Dependencies of QtGrpc test server not found. Skipping.") + return() +endif() + +set(proto_files "${CMAKE_CURRENT_LIST_DIR}/../proto/exampleservice.proto") +set(out_dir ${CMAKE_CURRENT_BINARY_DIR}) + +set(generated_files + "${out_dir}/exampleservice.pb.h" "${out_dir}/exampleservice.pb.cc" + "${out_dir}/exampleservice.grpc.pb.h" "${out_dir}/exampleservice.grpc.pb.cc") + +add_custom_command( + OUTPUT ${generated_files} + COMMAND + $ + ARGS + --grpc_out "${out_dir}" + --cpp_out "${out_dir}" + -I "${CMAKE_CURRENT_LIST_DIR}/../proto/" + --plugin=protoc-gen-grpc=$ + "${proto_files}" + WORKING_DIRECTORY ${out_dir} + DEPENDS "${proto_files}" + COMMENT "Generating gRPC ${target} sources..." + COMMAND_EXPAND_LISTS + VERBATIM +) + +set_source_files_properties(${generated_files} PROPERTIES GENERATED TRUE) +add_library(ServerRunner_grpc_gen STATIC ${generated_files}) +target_include_directories(ServerRunner_grpc_gen + PRIVATE + ${out_dir} + WrapgRPC_INCLUDE_PATH +) + +target_link_libraries(ServerRunner_grpc_gen + PRIVATE + WrapProtobuf::WrapLibProtobuf + WrapgRPC::WrapLibgRPC +) + +add_library(MagicServerRunner + STATIC + serverrunner.cpp + serverrunner.h +) + +target_include_directories(MagicServerRunner PRIVATE ${out_dir}) + +target_link_libraries(MagicServerRunner + PRIVATE + ServerRunner_grpc_gen + WrapgRPC::WrapLibgRPC + Qt6::Core +) + +qt_add_executable(SimpleGrpcServer + main.cpp +) + +target_link_libraries(SimpleGrpcServer PRIVATE + Qt6::Core + MagicServerRunner +) + +install(TARGETS SimpleGrpcServer + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/grpc/magic8ball/grpc_server_example/main.cpp b/examples/grpc/magic8ball/grpc_server_example/main.cpp new file mode 100644 index 00000000..3cd5cd37 --- /dev/null +++ b/examples/grpc/magic8ball/grpc_server_example/main.cpp @@ -0,0 +1,15 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "serverrunner.h" + +#include +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + auto server = std::make_unique(); + server->run(); + return a.exec(); +} diff --git a/examples/grpc/magic8ball/grpc_server_example/serverrunner.cpp b/examples/grpc/magic8ball/grpc_server_example/serverrunner.cpp new file mode 100644 index 00000000..0588c835 --- /dev/null +++ b/examples/grpc/magic8ball/grpc_server_example/serverrunner.cpp @@ -0,0 +1,81 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "serverrunner.h" +#include "exampleservice.grpc.pb.h" + +#include +#include +#include + +#include +#include +#include +#include + +namespace { + +using grpc::Server; +using grpc::ServerBuilder; +using grpc::ServerContext; +using grpc::ServerWriter; +using grpc::Status; +using qtgrpc::examples::AnswerRequest; +using qtgrpc::examples::AnswerResponse; +using qtgrpc::examples::ExampleService; + +static const std::array answers = {"Yes", + "Yep", + "Most\nlikely", + "It is\ncertain", + "No", + "Nope", + "Try later", + "Are you\nsure?", + "Maybe", + "Very\ndoubtful"}; + +// Generates random index value. +static int generateRandomIndex() +{ + static std::uniform_int_distribution dist(0, answers.size() - 1); + return dist(*QRandomGenerator::global()); +} + +// Logic and data behind the server's behavior. +class ExampleServiceServiceImpl final : public qtgrpc::examples::ExampleService::Service +{ + grpc::Status answerMethod(grpc::ServerContext *, const AnswerRequest *request, + AnswerResponse *response) override; +}; +} + +Status ExampleServiceServiceImpl::answerMethod(grpc::ServerContext *, + const AnswerRequest *request, + AnswerResponse *response) +{ + if (request->message() == "sleep") + QThread::msleep(2000); + + response->set_message(std::string(answers[generateRandomIndex()])); + return Status(); +} + +void ExampleServer::run() +{ + std::string serverUri("127.0.0.1:50051"); + ExampleServiceServiceImpl service; + + grpc::ServerBuilder builder; + builder.AddListeningPort(serverUri, grpc::InsecureServerCredentials()); + builder.RegisterService(&service); + + std::unique_ptr server(builder.BuildAndStart()); + if (!server) { + qDebug() << "Creating grpc::Server failed."; + return; + } + + qDebug() << "Server listening on " << serverUri; + server->Wait(); +} diff --git a/examples/grpc/magic8ball/grpc_server_example/serverrunner.h b/examples/grpc/magic8ball/grpc_server_example/serverrunner.h new file mode 100644 index 00000000..50c00c80 --- /dev/null +++ b/examples/grpc/magic8ball/grpc_server_example/serverrunner.h @@ -0,0 +1,13 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef SERVER_RUNNER_H +#define SERVER_RUNNER_H + +class ExampleServer +{ +public: + void run(); +}; + +#endif // SERVER_RUNNER_H diff --git a/examples/grpc/magic8ball/main.cpp b/examples/grpc/magic8ball/main.cpp new file mode 100644 index 00000000..078995d1 --- /dev/null +++ b/examples/grpc/magic8ball/main.cpp @@ -0,0 +1,20 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "clientservice.h" + +#include +#include + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + QQmlApplicationEngine engine; + QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, + &app, [](){ + QCoreApplication::exit(-1); + }, Qt::QueuedConnection); + + engine.loadFromModule("qtgrpc.examples.magic8ball", "Main"); + return app.exec(); +} diff --git a/examples/grpc/magic8ball/proto/exampleservice.proto b/examples/grpc/magic8ball/proto/exampleservice.proto new file mode 100644 index 00000000..d406c033 --- /dev/null +++ b/examples/grpc/magic8ball/proto/exampleservice.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package qtgrpc.examples; + +message AnswerRequest { + string message = 1; +} + +message AnswerResponse { + string message = 1; +} + +service ExampleService { + rpc answerMethod(AnswerRequest) returns (AnswerResponse) {} +} diff --git a/src/grpc/doc/qtgrpc.qdocconf b/src/grpc/doc/qtgrpc.qdocconf index 83cc9211..2baac877 100644 --- a/src/grpc/doc/qtgrpc.qdocconf +++ b/src/grpc/doc/qtgrpc.qdocconf @@ -31,9 +31,12 @@ depends += qtdoc qtcore qtnetwork qtwidgets qtprotobuf qtcmake {headerdirs,sourcedirs} += .. +url.examples = "https://code.qt.io/cgit/qt/qtgrpc.git/tree/examples/\1?h=$QT_VER" + exampledirs += ../../../examples/grpc -imagedirs += images +imagedirs += images \ + ../../../examples/grpc/doc/images navigation.landingpage = "Qt GRPC" navigation.cppclassespage = "Qt GRPC C++ Classes" diff --git a/src/grpc/doc/src/qtgrpc-module.qdoc b/src/grpc/doc/src/qtgrpc-module.qdoc index cd921da9..bf4fb1c1 100644 --- a/src/grpc/doc/src/qtgrpc-module.qdoc +++ b/src/grpc/doc/src/qtgrpc-module.qdoc @@ -10,3 +10,14 @@ \brief The Qt GRPC module provides support for communicating with gRPC services. */ + +/*! + \group qtgrpc-examples + \ingroup all-examples + \keyword Qt GRPC Examples + \title Qt GRPC Examples + \brief A collection of examples for \l {Qt GRPC C++ Classes} + + These examples demonstrate how to generate code using the protobuf and gRPC schemas, + and use it in your projects. +*/