From b9d04eb056c477f86142ff12aacd8c7ae8c2dcdd Mon Sep 17 00:00:00 2001 From: Matt Oliver Date: Wed, 3 Nov 2021 14:33:59 +1100 Subject: [PATCH] Add data provider and basic UI. --- .clang-format | 105 ++++++++++ .clang-tidy | 105 ++++++++++ .editorconfig | 12 ++ .gitattributes | 1 + .gitignore | 11 + CMakeLists.txt | 99 +++++++++ cmake/DeployQt.cmake | 74 +++++++ include/Application.h | 148 +++++++++++++ include/DataModel.h | 77 +++++++ include/DataProvider.h | 161 ++++++++++++++ include/Downloader.h | 57 +++++ source/Application.cpp | 145 +++++++++++++ source/DataModel.cpp | 44 ++++ source/DataProvider.cpp | 393 +++++++++++++++++++++++++++++++++++ source/Downloader.cpp | 80 +++++++ source/Page1.qml | 64 ++++++ source/main.cpp | 24 +++ source/main.qml | 44 ++++ source/qml.qrc | 7 + source/qtquickcontrols2.conf | 12 ++ 20 files changed, 1663 insertions(+) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 CMakeLists.txt create mode 100644 cmake/DeployQt.cmake create mode 100644 include/Application.h create mode 100644 include/DataModel.h create mode 100644 include/DataProvider.h create mode 100644 include/Downloader.h create mode 100644 source/Application.cpp create mode 100644 source/DataModel.cpp create mode 100644 source/DataProvider.cpp create mode 100644 source/Downloader.cpp create mode 100644 source/Page1.qml create mode 100644 source/main.cpp create mode 100644 source/main.qml create mode 100644 source/qml.qrc create mode 100644 source/qtquickcontrols2.conf diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..622d638 --- /dev/null +++ b/.clang-format @@ -0,0 +1,105 @@ +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: false +AlignEscapedNewlines: Left +AlignOperands: false +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: false + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterStruct: true + AfterUnion: true + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + #BeforeLambdaBody: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakAfterJavaFieldAnnotations: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: false +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: false +ColumnLimit: 120 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^"' + Priority: 1 + - Regex: '^<' + Priority: 2 +IncludeIsMainSourceRegex: '(.inl)$' +#IndentCaseBlocks: false +IndentCaseLabels: true +IndentGotoLabels: false +IndentPPDirectives: AfterHash +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto #Latest +TabWidth: 4 +UseCRLF: false +UseTab: Never +... + diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..cdad20e --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,105 @@ +Checks: 'bugprone-*,cert-*,clang-analyzer-*,cppcoreguidelines-*,fuchsia-*,google-*,hicpp-*,misc-*,modernize-*,perforamnce-*,readability-*,-modernize-use-trailing-return-type,-portability-simd-intrinsics,-readability-function-size,-llvmlibc-implementation-in-namespace,-fuchsia-default-arguments-declarations,-fuchsia-default-arguments-calls,-llvmlibc-callee-namespace,-google-runtime-references,-llvm-header-guard,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-cppcoreguidelines-owning-memory' +CheckOptions: + - key: readability-identifier-naming.AbstractClassCase + value: 'CamelCase' + - key: readability-identifier-naming.ClassCase + value: 'CamelCase' + - key: readability-identifier-naming.ClassConstantCase + value: 'camelBack' + - key: readability-identifier-naming.ClassMemberCase + value: 'camelBack' + - key: readability-identifier-naming.ClassMethodCase + value: 'camelBack' + - key: readability-identifier-naming.ConstantCase + value: 'camelBack' + - key: readability-identifier-naming.ConstantMemberCase + value: 'camelBack' + - key: readability-identifier-naming.ConstantParameterCase + value: 'camelBack' + - key: readability-identifier-naming.ConstantPointerParameterCase + value: 'camelBack' + - key: readability-identifier-naming.ConstexprFunctionCase + value: 'camelBack' + - key: readability-identifier-naming.ConstexprMethodCase + value: 'camelBack' + - key: readability-identifier-naming.ConstexprVariableCase + value: 'camelBack' + - key: readability-identifier-naming.EnumCase + value: 'CamelCase' + - key: readability-identifier-naming.EnumConstantCase + value: 'CamelCase' + - key: readability-identifier-naming.FunctionCase + value: 'camelBack' + - key: readability-identifier-naming.GlobalConstantCase + value: 'camelBack' + - key: readability-identifier-naming.GlobalConstantPointerCase + value: 'camelBack' + - key: readability-identifier-naming.GlobalFunctionCase + value: 'camelBack' + - key: readability-identifier-naming.GlobalPointerCase + value: 'camelBack' + - key: readability-identifier-naming.GlobalVariableCase + value: 'camelBack' + - key: readability-identifier-naming.InlineNamespaceCase + value: 'CamelCase' + - key: readability-identifier-naming.LocalConstantCase + value: 'camelBack' + - key: readability-identifier-naming.LocalConstantPointerCase + value: 'camelBack' + - key: readability-identifier-naming.LocalPointerCase + value: 'camelBack' + - key: readability-identifier-naming.LocalVariableCase + value: 'camelBack' + - key: readability-identifier-naming.MacroDefinitionCase + value: 'UPPER_CASE' + - key: readability-identifier-naming.MemberCase + value: 'camelBack' + - key: readability-identifier-naming.MethodCase + value: 'camelBack' + - key: readability-identifier-naming.NamespaceCase + value: 'CamelCase' + - key: readability-identifier-naming.ParameterCase + value: 'camelBack' + - key: readability-identifier-naming.ParameterPackCase + value: 'camelBack' + - key: readability-identifier-naming.PointerParameterCase + value: 'camelBack' + - key: readability-identifier-naming.PrivateMemberCase + value: 'camelBack' + - key: readability-identifier-naming.PrivateMethodCase + value: 'camelBack' + - key: readability-identifier-naming.ProtectedMemberCase + value: 'camelBack' + - key: readability-identifier-naming.ProtectedMethodCase + value: 'camelBack' + - key: readability-identifier-naming.PublicMemberCase + value: 'camelBack' + - key: readability-identifier-naming.PublicMethodCase + value: 'camelBack' + - key: readability-identifier-naming.ScopedEnumConstantCase + value: 'CamelCase' + - key: readability-identifier-naming.StaticConstantCase + value: 'camelBack' + - key: readability-identifier-naming.StaticVariableCase + value: 'camelBack' + - key: readability-identifier-naming.StructCase + value: 'CamelCase' + - key: readability-identifier-naming.TemplateParameterCase + value: 'CamelCase' + - key: readability-identifier-naming.TemplateTemplateParameterCase + value: 'CamelCase' + - key: readability-identifier-naming.TypeAliasCase + value: 'CamelCase' + - key: readability-identifier-naming.TypedefCase + value: 'CamelCase' + - key: readability-identifier-naming.TypeTemplateParameterCase + value: 'camelBack' + - key: readability-identifier-naming.UnionCase + value: 'CamelCase' + - key: readability-identifier-naming.ValueTemplateParameterCase + value: 'camelBack' + - key: readability-identifier-naming.VariableCase + value: 'camelBack' + - key: readability-identifier-naming.VirtualMethodCase + value: 'camelBack' +FormatStyle: 'file' \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6ba9c4f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ + +[*.{c,c++,cc,config,cp,cpp,cu,cxx,h,hh,hpp,hxx,inc,inl] +indent_style=space +indent_size=4 +tab_width=4 + +[*] + +# Standard properties +end_of_line=lf +insert_final_newline=true +max_line_length=120 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..07764a7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text eol=lf \ No newline at end of file diff --git a/.gitignore b/.gitignore index 259148f..3c36d5a 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,14 @@ *.exe *.out *.app + +# Build folders +.vs/ +build/ +install/ + +*.pdb +packages/ +*.onnx +_Resharper.Caches +*.DotSettings diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5af8938 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,99 @@ +cmake_minimum_required(VERSION 3.21) + +# Detect vcpkg ports +if(DEFINED ENV{VCPKG_ROOT} AND NOT DEFINED CMAKE_TOOLCHAIN_FILE) + file(TO_CMAKE_PATH "$ENV{VCPKG_ROOT}" ENV_VCPKG_ROOT) + set(CMAKE_TOOLCHAIN_FILE "${ENV_VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" + CACHE STRING "") +endif() + +# Import Qt6 dirctory if available +if(DEFINED ENV{Qt6_HOME}) + set(CMAKE_PREFIX_PATH $ENV{Qt6_HOME}) +endif() + +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +project(ShiftIntrinsicGuide + LANGUAGES CXX + VERSION 1.0.0 + DESCRIPTION "A GUI for viewing x86 intrinsics and associated performance data" +) + + +# Only do these if this is the main project, and not if it is included through add_subdirectory +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + # Default to a release build if desired configuration is not specified + if(NOT CMAKE_CONFIGURATION_TYPES) + if(NOT CMAKE_BUILD_TYPE) + message(STATUS "Setting build type to 'Release' as none was specified.") + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY VALUE "Release") + endif() + endif() + + # Set the install location to the source location if not currently specified + if("${CMAKE_INSTALL_PREFIX}" STREQUAL "") + message("Installing into source folder") + set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/install CACHE PATH "..." FORCE) + endif() + + # Use folder structure for arranging files within IDEs + set_property(GLOBAL PROPERTY USE_FOLDERS ON) +endif() + +find_package(Qt6 COMPONENTS GUI QML Network XML Concurrent REQUIRED) + +qt_add_executable(ShiftIntrinsicGuide MANUAL_FINALIZATION) + +# Add in the executable code +target_sources(ShiftIntrinsicGuide PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/include/Application.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/DataModel.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/Downloader.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/DataProvider.h" + "${CMAKE_CURRENT_SOURCE_DIR}/source/main.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/source/Application.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/source/DataModel.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/source/Downloader.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/source/DataProvider.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/source/qml.qrc" +) + +target_compile_features(ShiftIntrinsicGuide + INTERFACE cxx_std_17 +) + +target_include_directories(ShiftIntrinsicGuide + PRIVATE + "$" +) + +target_link_libraries(ShiftIntrinsicGuide PRIVATE + Qt6::Gui + Qt6::Qml + Qt6::Network + Qt6::Xml + Qt6::Concurrent +) + +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Header Files" REGULAR_EXPRESSION "*.hpp") +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source Files" REGULAR_EXPRESSION "*.cpp") +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source Files" REGULAR_EXPRESSION "*.inl") + +include(GNUInstallDirs) +install(TARGETS ShiftIntrinsicGuide + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +qt_import_qml_plugins(ShiftIntrinsicGuide) +qt_finalize_executable(ShiftIntrinsicGuide) + +# Include Qt deployment helper function +include(DeployQt) +deployqt(ShiftIntrinsicGuide "${CMAKE_CURRENT_SOURCE_DIR}/source/") diff --git a/cmake/DeployQt.cmake b/cmake/DeployQt.cmake new file mode 100644 index 0000000..7bad68f --- /dev/null +++ b/cmake/DeployQt.cmake @@ -0,0 +1,74 @@ +# The MIT License (MIT) +# +# Copyright (c) 2018 Nathan Osman +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +find_package(Qt6Core REQUIRED) + +# Retrieve the absolute path to qmake and then use that path to find +# the windeployqt and macdeployqt binaries +get_target_property(_qmake_executable Qt6::qmake IMPORTED_LOCATION) +get_filename_component(_qt_bin_dir "${_qmake_executable}" DIRECTORY) + +find_program(WINDEPLOYQT_EXECUTABLE windeployqt HINTS "${_qt_bin_dir}") +if(WIN32 AND NOT WINDEPLOYQT_EXECUTABLE) + message(FATAL_ERROR "windeployqt not found") +endif() + +find_program(MACDEPLOYQT_EXECUTABLE macdeployqt HINTS "${_qt_bin_dir}") +if(APPLE AND NOT MACDEPLOYQT_EXECUTABLE) + message(FATAL_ERROR "macdeployqt not found") +endif() + +# Add commands that copy the required Qt files to the same directory as the +# target after being built as well as including them in final installation +function(windeployqt target qmldir) + # Run windeployqt immediately after build + add_custom_command(TARGET ${target} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E + env PATH="${_qt_bin_dir}" "${WINDEPLOYQT_EXECUTABLE}" + --verbose 0 + --no-compiler-runtime + --qmldir ${qmldir} + \"$\" + COMMENT "Deploying Qt..." + ) +endfunction() + +# Add commands that copy the required Qt files to the application bundle +# represented by the target. +function(macdeployqt target qmldir) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND "${MACDEPLOYQT_EXECUTABLE}" + \"$/../..\" + -always-overwrite + COMMENT "Deploying Qt..." + ) +endfunction() + +function(deployqt target qmldir) + if(APPLE) + macdeployqt(${target} ${qmldir}) + elseif(WIN32) + windeployqt(${target} ${qmldir}) + endif() +endfunction() + +mark_as_advanced(WINDEPLOYQT_EXECUTABLE MACDEPLOYQT_EXECUTABLE) diff --git a/include/Application.h b/include/Application.h new file mode 100644 index 0000000..56c3b24 --- /dev/null +++ b/include/Application.h @@ -0,0 +1,148 @@ +#pragma once +/** + * Copyright Matthew Oliver + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DataModel.h" +#include "Downloader.h" + +#include +#include +#include + +class Application final : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool hasOKDialog READ getHasOKDialog NOTIFY hasOKDialogChanged); + Q_PROPERTY(QString OKDialogTitle READ getOKDialogTitle NOTIFY notifyOKDialogTitleChanged); + Q_PROPERTY(float progress READ getProgress WRITE setProgress NOTIFY notifyProgressChanged); + Q_PROPERTY(bool isLoaded READ getLoaded NOTIFY isLoadedChanged); + Q_PROPERTY(QString LoadingTitle READ getLoadingTitle NOTIFY notifyLoadingTitleChanged); + +public: + Application(const Application&) = delete; + + Application(Application&&) = delete; + + Application& operator=(const Application&) = delete; + + Application& operator=(Application&&) = delete; + + /** Default constructor. */ + explicit Application(QObject* parent = nullptr) noexcept; + + /** Destructor. */ + ~Application() noexcept override; + + /** + * Runs the GUI. + * @returns An int error code. + */ + int run() noexcept; + + /** + * Displays an dialog with an OK button. + * @param title The title for the dialog box. + * @param callback The callback function called when the dialog is closed. + */ + void addOKDialog(const QString& title, const std::function& callback); + + /** + * Gets whether a new OK dialog is required. + * @return True if there is, false otherwise. + */ + bool getHasOKDialog() const noexcept; + + /** Notify the GUI that the available OK dialog's has changed. */ + Q_SIGNAL void hasOKDialogChanged() const; + + /** + * Gets the title for a requested OK dialog. + * @return The dialog title. + */ + QString getOKDialogTitle() const noexcept; + + /** Notify the GUI that the available OK dialog title has changed. */ + Q_SIGNAL void notifyOKDialogTitleChanged() const; + + /** Callback, called when a OK dialog is accepted. */ + Q_SLOT void acceptOKCallback() noexcept; + + /** + * Gets the progress value. + * @returns The progress. + */ + float getProgress() const noexcept; + + /** + * Sets the progress value. + * @param newProgress The progress. + */ + void setProgress(float newProgress) noexcept; + + /** Notify the GUI that the progress has changed. */ + Q_SIGNAL void notifyProgressChanged() const; + + /** + * Gets whether the data is loaded. + * @return True if it is, false if it is not. + */ + bool getLoaded() const noexcept; + + /** + * Sets whether data is in loaded state. + * @param newLoaded (Optional) True if the engine is loaded. + */ + void setLoaded(bool newLoaded = true) noexcept; + + /** Notify the GUI that the loaded has changed. */ + Q_SIGNAL void isLoadedChanged() const; + + /** + * Gets the title for the loading text. + * @return The loading title. + */ + QString getLoadingTitle() const noexcept; + + /** + * Sets the loading title. + * @param title The new title. + */ + void setLoadingTitle(const QString& title); + + /** Notify the GUI that the available loading title has changed. */ + Q_SIGNAL void notifyLoadingTitleChanged() const; + +private: + /** Sets up the internal data models */ + void setupData(); + + QGuiApplication app; + QQmlApplicationEngine engine; + DataModel data; + + struct OKDialogData final + { + QString title; + std::function callback; + }; + + QQueue queueOK; + QMutex mutexOK; + std::atomic progress = 1.0F; + std::atomic_bool loaded = false; + QString loading = "Loading..."; + QFuture dataLoad; +}; diff --git a/include/DataModel.h b/include/DataModel.h new file mode 100644 index 0000000..91d7644 --- /dev/null +++ b/include/DataModel.h @@ -0,0 +1,77 @@ +#pragma once +/** + * Copyright Matthew Oliver + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +class QNetworkReply; + +class DataModel final : public QAbstractListModel +{ + Q_OBJECT; + +public: + DataModel(const DataModel& other) = delete; + + DataModel(DataModel&& other) noexcept = delete; + + DataModel& operator=(const DataModel& other) = delete; + + DataModel& operator=(DataModel&& other) noexcept = delete; + + /** + * Constructor. + * @param [in] parent (Optional) If non-null, the parent. + */ + explicit DataModel(QObject* parent = nullptr) noexcept; + + /** Destructor. */ + ~DataModel() override = default; + + /** + * Get the number of rows in the model. + * @note Used Automatically by Qt to get number of motors in the list. + * @param parameter1 (Optional) The first parameter. + * @return The number of rows. + */ + [[nodiscard]] int rowCount(const QModelIndex& parameter1) const noexcept override; + + /** + * Get data for specific value from a list element. + * @note Used Automatically by Qt to get the value of a data element. + * @param index Zero-based index of the list item in the model. + * @param role (Optional) The data element to retrieve. + * @return A QVariant. + */ + [[nodiscard]] QVariant data(const QModelIndex& index, int role) const noexcept override; + + /** + * Get the Role names for item in a list element. + * @note Used Automatically by Qt to get string names of each data value (used for labelling). + * @return The requested list of names. + */ + [[nodiscard]] QHash roleNames() const noexcept override; + + /** + * Get flags for the element at specified index. + * @note Used Automatically by Qt to get the available operations on each list element. + * @param index Zero-based index of the list item in the model. + * @return The flags. + */ + [[nodiscard]] Qt::ItemFlags flags(const QModelIndex& index) const noexcept override; + +private: +}; diff --git a/include/DataProvider.h b/include/DataProvider.h new file mode 100644 index 0000000..e8d1267 --- /dev/null +++ b/include/DataProvider.h @@ -0,0 +1,161 @@ +#pragma once +/** + * Copyright Matthew Oliver + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +class Application; + +class DataProvider +{ +public: + DataProvider(const DataProvider& other) = delete; + + DataProvider(DataProvider&& other) noexcept = delete; + + DataProvider& operator=(const DataProvider& other) = delete; + + DataProvider& operator=(DataProvider&& other) noexcept = delete; + + /** + * Constructor. + * @param [in] parent (Optional) If non-null, the parent. + */ + explicit DataProvider(Application* parent) noexcept; + + /** + * Gets the data. + */ + bool setup() noexcept; + + /** + * Sets the progress to the internal value offset by a progress value. + * @param value The progress value. + */ + void setProgress(float value) const noexcept; + + /** + * Increment the internal progress value. + * @param value The value to increment by. + */ + void addProgress(float value) noexcept; + +private: + /** + * Loads this model from stored data. + */ + [[nodiscard]] bool load() noexcept; + + /** + * Store the model to stored data. + */ + [[nodiscard]] bool store() noexcept; + + /** + * Creates this model by retrieving new data. + */ + [[nodiscard]] bool create() noexcept; + + /** + * Downloads to cache or loads from cache if already exists. + * @param fileName Filename of the cache file. + * @param name The name of the download/cache. + * @param [in,out] dataXML The data XML. + * @param url URL of the resource. + * @returns True if it succeeds, false if it fails. + */ + [[nodiscard]] bool downloadCache( + const QString& fileName, const QString& name, QDomDocument& dataXML, const QUrl& url); + + /** The uops measurements */ + struct Measurements + { + QString arch; /**< The processor architecture the measurement is for */ + uint32_t latency; /**< The measured latency */ + uint32_t latencyMem; /**< The measured maximum latency of memory operations */ + uint32_t throughput; /**< The measured throughput */ + uint32_t uops; /**< The instruction uops */ + QString ports; /**< The instruction ports */ + + friend QDataStream& operator<<(QDataStream& out, const struct Measurements& other) + { + out << other.arch << other.latency << other.latencyMem << other.throughput << other.uops << other.ports; + return out; + } + + friend QDataStream& operator>>(QDataStream& in, struct Measurements& other) + { + in >> other.arch >> other.latency >> other.latencyMem >> other.throughput >> other.uops >> other.ports; + return in; + } + }; + + /** An intrinsics data. */ + struct Instruction + { + QString name; /**< The intricics name */ + QString description; /**< The description */ + QString operation; /**< The pseudo code operation */ + QString header; /**< The instructions required include header */ + QString technology; /**< The required technology (e.g. AVX etc.) */ + QList types; /**< The types of data the intrinsic operates on (e.g. Integer/Float etc.) */ + QList categories; /**< The category of operation (e.g. Arithmetic etc.) */ + QString instruction; /**< The intrinsics assembly equivalent */ + QList measurements; /**< The list of measurements */ + + friend QDataStream& operator<<(QDataStream& out, const struct Instruction& other) + { + out << other.name << other.description << other.operation << other.header << other.technology << other.types + << other.categories << other.instruction << other.measurements; + return out; + } + + friend QDataStream& operator>>(QDataStream& in, struct Instruction& other) + { + in >> other.name >> other.description >> other.operation >> other.header >> other.technology >> + other.types >> other.categories >> other.instruction >> other.measurements; + return in; + } + }; + + struct Data + { + QList instructions; /**< The list of all known intrinsics */ + QList allTypes; /**< The list of all known intrinsic types */ + QList allCategories; /**< The list of all known intrinsic categories */ + QString version; /**< The intrinsic list version */ + QDate date; /**< The intrinsic list date */ + + friend QDataStream& operator<<(QDataStream& out, const struct Data& other) + { + out << other.instructions << other.allTypes << other.allCategories << other.version << other.date; + return out; + } + + friend QDataStream& operator>>(QDataStream& in, struct Data& other) + { + in >> other.instructions >> other.allTypes >> other.allCategories >> other.version >> other.date; + return in; + } + }; + + Data data; /**< The data */ + float progress = 0.0f; /**< Stored value indicating total progress of all loading operations */ + float progressModifier = 1.0f; /**< The progress modifier used to scale incoming progress values */ + Application* parentApp; +}; diff --git a/include/Downloader.h b/include/Downloader.h new file mode 100644 index 0000000..7764273 --- /dev/null +++ b/include/Downloader.h @@ -0,0 +1,57 @@ +#pragma once +/** + * Copyright Matthew Oliver + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +class Application; +class QNetworkReply; + +class Downloader final : public QObject +{ + Q_OBJECT; + +public: + /** + * Constructor + * @param setProgress (Optional) If non-null, the callback to signal download progress. + */ + explicit Downloader(std::function setProgress = nullptr) noexcept; + + /** Destructor */ + ~Downloader() noexcept override; + + /** + * Gets the url data. + * @param url URL of the resource. + * @param [out] retData Return used to pass back data from request. + * @returns True if it succeeds, false if it fails. + */ + bool get(const QUrl& url, QByteArray& retData) noexcept; + +private: + QNetworkAccessManager* manager = nullptr; + QEventLoop loop; + QTimer timer; + std::function callback; + + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); +}; diff --git a/source/Application.cpp b/source/Application.cpp new file mode 100644 index 0000000..f85c345 --- /dev/null +++ b/source/Application.cpp @@ -0,0 +1,145 @@ +/** + * Copyright Matthew Oliver + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Application.h" + +#include "DataProvider.h" + +#include + +int argc = 0; + +Application::Application(QObject* parent) noexcept + : QObject(parent) + , app(argc, nullptr) + , data(this) +{} + +Application::~Application() noexcept +{ + loaded = true; // Use loaded to shutdown any running data init + dataLoad.waitForFinished(); +} + +int Application::run() noexcept +{ + // Register the GUI data model + engine.rootContext()->setContextProperty("application", this); + engine.rootContext()->setContextProperty("dataModel", &data); + + // Load the UI with the default QML file + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + + if (!engine.rootObjects().isEmpty()) { + // return 1; + } + + // Queue up the model initialisation + QTimer::singleShot(1000, [&] { dataLoad = QtConcurrent::run([this] { setupData(); }); }); + + return app.exec(); +} + +void Application::addOKDialog(const QString& title, const std::function& callback) +{ + // Add the new dialog to the queue + QMutexLocker lock(&mutexOK); + queueOK.enqueue({title, callback}); + // Check if already a dialog being displayed + if (queueOK.size() == 1) { + emit notifyOKDialogTitleChanged(); + emit hasOKDialogChanged(); + } +} + +bool Application::getHasOKDialog() const noexcept +{ + return !queueOK.empty(); +} + +QString Application::getOKDialogTitle() const noexcept +{ + if (!queueOK.empty()) { + return queueOK.front().title; + } + return ""; +} + +void Application::acceptOKCallback() noexcept +{ + OKDialogData infoData; + { + QMutexLocker lock(&mutexOK); + // Call the callback function + infoData = queueOK.takeFirst(); + // Check if there are more dialogs to be displayed + if (!queueOK.empty()) { + // If there are any pending OK dialogs they need to be re-enabled + emit notifyOKDialogTitleChanged(); + emit hasOKDialogChanged(); + } + } + if (infoData.callback != nullptr) { + infoData.callback(); + } +} + +float Application::getProgress() const noexcept +{ + return progress.load(); +} + +void Application::setProgress(const float newProgress) noexcept +{ + progress = fmin(fabs(newProgress), 1.0F); + emit notifyProgressChanged(); +} + +bool Application::getLoaded() const noexcept +{ + return loaded.load(); +} + +void Application::setLoaded(const bool newLoaded) noexcept +{ + if (bool expected = !newLoaded; loaded.compare_exchange_strong(expected, newLoaded)) { + emit isLoadedChanged(); + } +} + +QString Application::getLoadingTitle() const noexcept +{ + return loading; +} + +void Application::setLoadingTitle(const QString& title) +{ + loading = title; + emit notifyLoadingTitleChanged(); +} + +void Application::setupData() +{ + if (DataProvider provider(this); provider.setup()) { + // TODO: setup data models + + // Update UI + setProgress(1.0f); + setLoaded(true); + } else { + addOKDialog("Failed to get intrinsic data", [] {}); + } +} diff --git a/source/DataModel.cpp b/source/DataModel.cpp new file mode 100644 index 0000000..a740b37 --- /dev/null +++ b/source/DataModel.cpp @@ -0,0 +1,44 @@ +/** + * Copyright Matthew Oliver + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DataModel.h" + +#include "Application.h" + +DataModel::DataModel(QObject* parent) noexcept + : QAbstractListModel(parent) +{} + +int DataModel::rowCount(const QModelIndex& parameter1) const noexcept +{ + return 0; +} + +QVariant DataModel::data(const QModelIndex& index, int role) const noexcept +{ + return QVariant(); +} + +QHash DataModel::roleNames() const noexcept +{ + static const QHash roles{}; + return roles; +} + +Qt::ItemFlags DataModel::flags(const QModelIndex& /*index*/) const noexcept +{ + return Qt::NoItemFlags; +} diff --git a/source/DataProvider.cpp b/source/DataProvider.cpp new file mode 100644 index 0000000..5243970 --- /dev/null +++ b/source/DataProvider.cpp @@ -0,0 +1,393 @@ +/** + * Copyright Matthew Oliver + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DataProvider.h" + +#include "Application.h" +#include "Downloader.h" + +DataProvider::DataProvider(Application* parent) noexcept + : parentApp(parent) +{} + +void DataProvider::setProgress(const float value) const noexcept +{ + const auto progress2 = progress + value * progressModifier; + parentApp->setProgress(progress2); +} + +void DataProvider::addProgress(const float value) noexcept +{ + progress += value * progressModifier; + parentApp->setProgress(progress); +} + +bool DataProvider::setup() noexcept +{ + bool fail = false; + // Load any existing cache from disk + if (!load()) { + // If no cache found then create new one + fail = !create(); + if (!fail) { + // Backup created cache to disk + (void)store(); + } + } + + if (!fail) { + // Remove loading progress from parent + parentApp->setProgress(1.0f); + } + return true; +} + +bool DataProvider::load() noexcept +{ + // Reset UI values to default + progressModifier = 1.0f; + progress = 0.0f; + parentApp->setLoadingTitle("Loading..."); + setProgress(0.0f); // Trigger parent update + + if (QFile fileCache("./dataCache"); fileCache.exists() && fileCache.open(QIODevice::ReadOnly)) { + // Load data from cache + parentApp->setLoadingTitle("Loading data from cache..."); + QDataStream in(&fileCache); + in.setVersion(QDataStream::Qt_6_2); + in >> data; + addProgress(1.0f); + return true; + } + return false; +} + +bool DataProvider::store() noexcept +{ + // Stream data model to disk + if (QFile fileCache("./dataCache"); fileCache.open(QIODevice::WriteOnly)) { + parentApp->setLoadingTitle("Writing data store to disk..."); + // Store data from cache + QDataStream out(&fileCache); + out.setVersion(QDataStream::Qt_6_2); + out << data; + addProgress(1.0f); + return true; + } + return false; +} + +bool DataProvider::create() noexcept +{ + // Reset UI values to default + progressModifier = 1.0f / 8.0f; + progress = 0.0f; + parentApp->setLoadingTitle("Creating..."); + setProgress(0.0f); // Trigger parent update + + QDomDocument dataXMLIntel, dataXMLUOps; + if (!downloadCache("./intrin.xml", "Intel Intrinsic Guide", dataXMLIntel, + QUrl("https://www.intel.com/content/dam/develop/public/us/en/include/intrinsics-guide/data-latest.xml"))) { + return false; + } + if (!downloadCache("./uops.xml", "uops.info", dataXMLUOps, QUrl("https://www.uops.info/instructions.xml"))) { + return false; + } + + parentApp->setLoadingTitle("Creating data store..."); + // Scan through intrinsic data for each function + QDomElement root = dataXMLIntel.documentElement(); + QDomElement root2 = dataXMLUOps.documentElement(); + data.version = root.attribute("version", "3.6.0"); + data.date = QDate::fromString(root.attribute("date", "06/30/2021"), "MM/dd/YYYY"); + + // Loop through each element and get information + for (auto i = root.firstChild(); !i.isNull(); i = i.nextSibling()) { + // Check if the child tag name is an 'intrinsic' + if (auto node = i.toElement(); !node.isNull() && node.tagName() == "intrinsic") { + // Get the intrinsic attributes + // tech: is Technologies selection where cpuids is subsection to tech (when not equal) + // types: is integer/floating point etc. + // name: is intrinsic name + // description: is text description of intrinsic operation + // operation: is pseudo code of intrinsic operation + // header: is the header file the intrinsic is declared in. + QString tech = node.attribute("tech", "Unknown"); + QString name = node.attribute("name"); + QList types, cpuids, categories; + QString description, operation, header, instruction, xed; + if (name.isEmpty()) { + qDebug() << "Intrinsic element in xml file did not have name attribute: " + node.toText().data(); + continue; + } + // Get each child nodes data + for (auto child = node.firstChild(); !child.isNull(); child = child.nextSibling()) { + if (auto childE = child.toElement(); !childE.isNull()) { + // Read Name and value + if (childE.tagName() == "type") { + // Can have multiple type nodes (e.g. Integer and Flag) + if (auto t = childE.firstChild().toText(); !t.isNull()) { + types.emplaceBack(t.data()); + } + } else if (childE.tagName() == "CPUID") { + // Can have multiple cpuid nodes (e.g. different AVX512 sets) + if (auto t = childE.firstChild().toText(); !t.isNull()) { + cpuids.emplaceBack(t.data()); + } + } else if (childE.tagName() == "category") { + if (auto t = childE.firstChild().toText(); !t.isNull()) { + categories.emplaceBack(t.data()); + } + //} else if (childE.tagName() == "return") { + // // Has attributes for type, varname, etype + //} else if (childE.tagName() == "parameter") { + // // Can have multiple parameters + // // Has same attributes as 'return' + } else if (childE.tagName() == "description") { + if (auto t = childE.firstChild().toText(); !t.isNull()) { + description = t.data(); + } + } else if (childE.tagName() == "operation") { + if (auto t = childE.firstChild().toText(); !t.isNull()) { + operation = t.data(); + } + } else if (childE.tagName() == "instruction") { + // Has attributes xed, form, name + if (!xed.isEmpty()) { + // Can have multiple xeds if intrinsic can map to different instructions + // qDebug() << "Intrinsic has multiple xeds: " + name + ", xed: " + xed; + } else { + // Always take the first instruction form + xed = childE.attribute("xed", ""); + } + if (!instruction.isEmpty()) { + instruction += ','; + } + instruction += childE.attribute("name", ""); + } else if (childE.tagName() == "header") { + if (auto t = childE.firstChild().toText(); !t.isNull()) { + header = t.data(); + } + } + } + } + if (types.isEmpty()) { + types.append("Other"); + } + // Skip SVML as its intel compiler only + if (tech == "SVML" || tech == "KNC") { + continue; + } + + // Get corresponding value from uops + QList measurements; + if (!xed.isEmpty()) { + // uops info stores instructions by extension name (cpuid) + bool found = false; + bool secondLoop = false; + QString cpuid; + for (auto& j : cpuids) { + cpuid = j; // Generally the last one is fine + } + // Perform required conversions between intrin and uops arch names + if (cpuid == "ADX") { + cpuid = "ADOX_ADCX"; + } else if (cpuid == "AVX2") { + // Allow searching through AVX subdomains + cpuid = "AVX"; + } else if (cpuid.startsWith("AVX512")) { + // Allow searching through AVX512 subdomains + cpuid = "AVX512"; + } + while (true) { + for (auto k = root2.firstChild(); !k.isNull() && !found; k = k.nextSibling()) { + if (auto elem = k.toElement(); elem.tagName() == "extension") { + // Check if the child tag name matches our requested cpuid + if (secondLoop || elem.attribute("name").startsWith(cpuid)) { + // Search for element containing required xed + for (auto child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { + if (auto childE = child.toElement(); !childE.isNull() && + childE.tagName() == "instruction" && childE.attribute("iform") == xed) { + found = true; + + for (auto child2 = child.firstChild(); !child2.isNull(); + child2 = child2.nextSibling()) { + if (auto child2E = child2.toElement(); + !child2E.isNull() && child2E.tagName() == "architecture") { + QString arch = child2E.attribute("name"); + + for (auto child3 = child2.firstChild(); !child3.isNull(); + child3 = child3.nextSibling()) { + if (auto child3E = child3.toElement(); + !child3E.isNull() && child3E.tagName() == "measurement") { + uint32_t uops = child3E.attribute("uops").toUInt(); + QString ports = child3E.attribute("ports"); + + uint32_t throughput = (child3E.hasAttribute("TP")) ? + child3E.attribute("TP").toUInt() : + child3E.attribute("TP_unrolled").toUInt(); + + // Calculate latency + uint32_t latency = 0; + uint32_t latencyMemory = 0; // Additional latency associated + // with using a memory address + for (auto child4 = child3.firstChild(); !child4.isNull(); + child4 = child4.nextSibling()) { + if (auto child4E = child4.toElement(); + !child4E.isNull() && child4E.tagName() == "latency") { + for (auto attr = 0; + attr < child4E.attributes().length(); ++attr) { + if (auto attrE = + child4E.attributes().item(attr).toElement(); + !attrE.isNull() && + attrE.tagName().startsWith("cycles")) { + if (attrE.tagName() == "cycles") { + latency = std::max( + latency, attrE.tagName().toUInt()); + } else { + latencyMemory = std::max(latencyMemory, + attrE.tagName().toUInt()); + } + } + } + } + } + + // Add to list + measurements.append( + {arch, latency, latencyMemory, throughput, uops, ports}); + found = true; + } + } + } + } + + break; + } + } + } + } + } + if (secondLoop) { + break; + } + // Go through the list a second time and this time skip checking the extension + secondLoop = true; + } + if (!found) { + // qDebug() << "Intrinsic uops data not found: " + name + ", xed: " + xed; + } + } else { + // qDebug() << "Intrinsic element in xml file did not have xed element: " + name; + } + + // Add information to list + data.instructions.append( + {name, description, operation, header, tech, types, categories, instruction, measurements}); + + // Add to list of known techs/types + for (auto& j : types) { + if (!data.allTypes.contains(j)) { + data.allTypes.append(j); + } + } + for (auto& j : categories) { + if (!data.allCategories.contains(j)) { + data.allCategories.append(j); + } + } + } + } + + addProgress(1.0f); + + // Sort the 'all' lists + data.allTypes.sort(); + data.allCategories.sort(); + + // TODO:**************** + // Delete any cache files from disk + + return true; +} + +bool DataProvider::downloadCache(const QString& fileName, const QString& name, QDomDocument& dataXML, const QUrl& url) +{ + // Check if cached xml file exists + if (QFile fileCache(fileName); fileCache.exists() && fileCache.open(QIODevice::ReadOnly)) { + parentApp->setLoadingTitle("Loading " + name + " from cache..."); + addProgress(2.0f); + QString errorMessage; + int errorLine, errorColumn; + if (!dataXML.setContent(&fileCache, &errorMessage, &errorLine, &errorColumn)) { + qCritical() << "Failed to parse XML: " << errorMessage << " (" << errorLine << ", " << errorColumn << ")"; + // Delete broken file cache + fileCache.remove(); + parentApp->addOKDialog("Failed to pass " + name + " data", [] {}); + return false; + } + } else { + parentApp->setLoadingTitle("Downloading " + name + "..."); + // Download intrinsic xml + Downloader dl([this](const float value) { this->setProgress(value); }); + QByteArray dlData; + if (!dl.get(url, dlData) || dlData.isEmpty()) { + parentApp->addOKDialog("Failed to download " + name + " data", [] {}); + return false; + } + + // Check if shutdown has been called + if (parentApp->getLoaded()) { + return false; + } + addProgress(1.0f); + + // Convert to xml + parentApp->setLoadingTitle("Converting " + name + "..."); + QString errorMessage; + int errorLine, errorColumn; + if (!dataXML.setContent(dlData, &errorMessage, &errorLine, &errorColumn)) { + qCritical() << "Failed to parse XML: " << errorMessage << " (" << errorLine << ", " << errorColumn << ")"; + + parentApp->addOKDialog("Failed to pass " + name + " data", [] {}); + return false; + } + addProgress(1.0f); + + // Check if shutdown has been called + if (parentApp->getLoaded()) { + return false; + } + + // Write out file to disk + parentApp->setLoadingTitle("Adding " + name + " to cache..."); + if (!fileCache.open(QIODevice::WriteOnly | QIODevice::Text)) { + qDebug() << "Failed to write XML cache: " + fileName; + } else { + QTextStream stream(&fileCache); + stream << dataXML.toString(); + fileCache.close(); + } + + // Check if shutdown has been called + if (parentApp->getLoaded()) { + return false; + } + } + + addProgress(1.0f); + return true; +} diff --git a/source/Downloader.cpp b/source/Downloader.cpp new file mode 100644 index 0000000..bac4d93 --- /dev/null +++ b/source/Downloader.cpp @@ -0,0 +1,80 @@ +/** + * Copyright Matthew Oliver + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Downloader.h" + +#include "Application.h" + +#include +#include +#include +#include + +Downloader::Downloader(std::function setProgress) noexcept + : QObject(nullptr) + , callback(std::move(setProgress)) +{ + manager = new QNetworkAccessManager(this); + connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + connect(manager, &QNetworkAccessManager::finished, &loop, &QEventLoop::quit); +} + +Downloader::~Downloader() noexcept +{ + delete manager; +} + +bool Downloader::get(const QUrl& url, QByteArray& retData) noexcept +{ + QNetworkRequest request(url); + request.setRawHeader("User-Agent", "Mozilla Firefox"); + + QNetworkReply* reply = manager->get(request); + connect(reply, &QNetworkReply::downloadProgress, this, &Downloader::downloadProgress); + timer.start(10000); // 10000ms wait for reply + loop.exec(); + + bool fail = false; + if (nullptr == reply) { + // Error. we probably timed out i.e SIGNAL(finished()) did not happen + // this handles above indicated case (1) + fail = true; + } else if (QNetworkReply::NoError != reply->error()) { + qCritical() << "Failed to download file: " << reply->errorString(); + fail = true; + } else { + retData = reply->readAll(); + } + + if (callback != nullptr) { + callback(1.0f); + } + if (reply) { + delete reply; + } + return !fail; +} + +void Downloader::downloadProgress(const qint64 bytesReceived, const qint64 bytesTotal) +{ + // Reset timer when progress is detected + timer.start(10000); + + if (callback != nullptr) { + const float progress = static_cast(bytesReceived) / static_cast(bytesTotal); + callback(progress); + } +} diff --git a/source/Page1.qml b/source/Page1.qml new file mode 100644 index 0000000..b664734 --- /dev/null +++ b/source/Page1.qml @@ -0,0 +1,64 @@ +/** + * Copyright Matthew Oliver + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: mainPage + width: 1920 + height: 1080 + + Pane { + anchors.centerIn: parent + visible: application.progress !== 1 + id: pane + width: 680 + height: 300 + + Label { + id: label + anchors.centerIn: parent + anchors.verticalCenterOffset: 140 + width: 500 + height: 50 + text: application.LoadingTitle + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + font.pointSize: 16 + } + + ProgressBar { + id: progressBar + anchors.centerIn: parent + width: parent.width - 60 + height: 80 + value: application.progress + } + } + + BusyIndicator { + id: busyIndicator + width: 60 + height: 64 + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: 84 + anchors.horizontalCenterOffset: 0 + running: !application.isLoaded + } +} diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..21ede5f --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,24 @@ +/** + * Copyright Matthew Oliver + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Application.h" + +int main() +{ + Application app; + + return app.run(); +} diff --git a/source/main.qml b/source/main.qml new file mode 100644 index 0000000..8eea6a6 --- /dev/null +++ b/source/main.qml @@ -0,0 +1,44 @@ +/** + * Copyright Matthew Oliver + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ApplicationWindow { + visible: true + width: 1920 + height: 1080 + title: "Shift Intrinsic Guide" + + Item { + anchors.centerIn: parent + width: infoDialog.width + height: infoDialog.height + Dialog { + id: infoDialog + title: application.OKDialogTitle + visible: application.hasOKDialog + + standardButtons: Dialog.Ok + + onAccepted: application.acceptOKCallback() + } + } + + Page1 { + } +} diff --git a/source/qml.qrc b/source/qml.qrc new file mode 100644 index 0000000..c076832 --- /dev/null +++ b/source/qml.qrc @@ -0,0 +1,7 @@ + + + main.qml + qtquickcontrols2.conf + Page1.qml + + diff --git a/source/qtquickcontrols2.conf b/source/qtquickcontrols2.conf new file mode 100644 index 0000000..fc0f9bd --- /dev/null +++ b/source/qtquickcontrols2.conf @@ -0,0 +1,12 @@ +; This file can be edited to change the style of the application +; See Styling Qt Quick Controls 2 in the documentation for details: +; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html + +[Controls] +;Style=Material + +[Universal] +Theme=System + +[Material] +Theme=System