From 80d8ae5a6fef6c9a34e81e240539cb655dd99851 Mon Sep 17 00:00:00 2001 From: rtk0c Date: Wed, 14 Apr 2021 15:09:13 -0700 Subject: Initial work on workflows --- core/CMakeLists.txt | 5 +- core/locale/zh_CN.json | 1 + core/src/Entrypoint/main.cpp | 2 +- core/src/Model/EvaluatedValue.cpp | 49 ++++++++++++++++++ core/src/Model/EvaluatedValue.hpp | 64 ++++++++++++++++++++++++ core/src/Model/Workflow.cpp | 98 ++++++++++++++++++++++++++++++++++++ core/src/Model/Workflow.hpp | 101 ++++++++++++++++++++++++++++++++++++++ core/src/Model/WorkflowSteps.cpp | 11 +++++ core/src/Model/WorkflowSteps.hpp | 44 +++++++++++++++++ core/src/Model/fwd.hpp | 20 ++++++++ core/src/UI/Localization.hpp | 1 + core/src/UI/UI.hpp | 2 +- core/src/UI/UI_DatabaseView.cpp | 18 +++++-- core/src/UI/UI_Export.cpp | 9 ---- core/src/UI/UI_Items.cpp | 2 +- core/src/UI/UI_MainWindow.cpp | 5 ++ core/src/UI/UI_Workflows.cpp | 9 ++++ core/src/Utils/Sigslot.hpp | 4 +- 18 files changed, 425 insertions(+), 20 deletions(-) create mode 100644 core/src/Model/EvaluatedValue.cpp create mode 100644 core/src/Model/EvaluatedValue.hpp create mode 100644 core/src/Model/Workflow.cpp create mode 100644 core/src/Model/Workflow.hpp create mode 100644 core/src/Model/WorkflowSteps.cpp create mode 100644 core/src/Model/WorkflowSteps.hpp delete mode 100644 core/src/UI/UI_Export.cpp create mode 100644 core/src/UI/UI_Workflows.cpp diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index e8abe5b..12361f3 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -33,18 +33,21 @@ set(ENTRYPOINT_MODULE_SOURCES ) add_source_group(MODEL_MODULE_SOURCES + src/Model/EvaluatedValue.cpp src/Model/Filter.cpp src/Model/GlobalStates.cpp src/Model/Items.cpp src/Model/Project.cpp src/Model/TransactionsModel.cpp + src/Model/Workflow.cpp + src/Model/WorkflowSteps.cpp ) add_source_group(UI_MODULE_SOURCES src/UI/Localization.cpp src/UI/States.cpp src/UI/UI_DatabaseView.cpp - src/UI/UI_Export.cpp + src/UI/UI_Workflows.cpp src/UI/UI_Items.cpp src/UI/UI_MainWindow.cpp src/UI/UI_Settings.cpp diff --git a/core/locale/zh_CN.json b/core/locale/zh_CN.json index 188ec4f..1c1102d 100644 --- a/core/locale/zh_CN.json +++ b/core/locale/zh_CN.json @@ -10,6 +10,7 @@ "MainWindow.Tab.Project": "\uf15b 项目", "MainWindow.Tab.DatabaseView": "\uf1c0 数据", "MainWindow.Tab.Items": "\uf466 物品", + "MainWindow.Tab.Workflows": "\uf5cb 工作流", "Project.New": "新建项目...", "Project.New.DialogTitle": "新建项目向导", "Project.New.Name": "项目名称", diff --git a/core/src/Entrypoint/main.cpp b/core/src/Entrypoint/main.cpp index 3016467..8f9832c 100644 --- a/core/src/Entrypoint/main.cpp +++ b/core/src/Entrypoint/main.cpp @@ -130,7 +130,7 @@ int main(int argc, char* argv[]) { // Configure default fonts { // Includes latin alphabet, although for some reason smaller than if rendered using 18 point NotoSans regular - io.Fonts->AddFontFromFileTTF("fonts/NotoSansSC-Regular.otf", 18, nullptr, io.Fonts->GetGlyphRangesChineseSimplifiedCommon()); + io.Fonts->AddFontFromFileTTF("fonts/NotoSansSC-Regular.otf", 18, nullptr, io.Fonts->GetGlyphRangesChineseFull()); ImWchar iconRanges[] = { ICON_MIN_FA, ICON_MAX_FA }; ImFontConfig config; diff --git a/core/src/Model/EvaluatedValue.cpp b/core/src/Model/EvaluatedValue.cpp new file mode 100644 index 0000000..c86f00b --- /dev/null +++ b/core/src/Model/EvaluatedValue.cpp @@ -0,0 +1,49 @@ +#include "EvaluatedValue.hpp" + +BaseValue::BaseValue(Type type) + : mType{ type } { +} + +BaseValue::Type BaseValue::GetType() const { + return mType; +} + +NumericValue::NumericValue() + : BaseValue(BaseValue::NumericType) { +} + +int64_t NumericValue::GetInt() const { + return static_cast(mValue); +} + +double NumericValue::GetValue() const { + return mValue; +} + +void NumericValue::SetValue(double value) { + mValue = value; +} + +TextValue::TextValue() + : BaseValue(BaseValue::TextType) { +} + +const std::string& TextValue::GetValue() const { + return mValue; +} + +void TextValue::SetValue(const std::string& value) { + mValue = value; +} + +DateTimeValue::DateTimeValue() + : BaseValue(BaseValue::DateTimeType) { +} + +const std::chrono::time_point& DateTimeValue::GetValue() const { + return mValue; +} + +void DateTimeValue::SetValue(const std::chrono::time_point& value) { + mValue = value; +} diff --git a/core/src/Model/EvaluatedValue.hpp b/core/src/Model/EvaluatedValue.hpp new file mode 100644 index 0000000..838fc39 --- /dev/null +++ b/core/src/Model/EvaluatedValue.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include + +class BaseValue { +public: + enum Type { + NumericType, + TextType, + DateTimeType, + + /// An unspecified type, otherwise known as "any" in some contexts. + InvalidType, + TypeCount = InvalidType, + }; + +private: + Type mType; + +public: + BaseValue(Type type); + virtual ~BaseValue() = default; + + BaseValue(const BaseValue&) = delete; + BaseValue& operator=(const BaseValue&) = delete; + BaseValue(BaseValue&&) = default; + BaseValue& operator=(BaseValue&&) = default; + + Type GetType() const; +}; + +class NumericValue : public BaseValue { +private: + double mValue; + +public: + NumericValue(); + + int64_t GetInt() const; + double GetValue() const; + void SetValue(double value); +}; + +class TextValue : public BaseValue { +private: + std::string mValue; + +public: + TextValue(); + const std::string& GetValue() const; + void SetValue(const std::string& value); +}; + +class DateTimeValue : public BaseValue { +private: + std::chrono::time_point mValue; + +public: + DateTimeValue(); + const std::chrono::time_point& GetValue() const; + void SetValue(const std::chrono::time_point& value); +}; diff --git a/core/src/Model/Workflow.cpp b/core/src/Model/Workflow.cpp new file mode 100644 index 0000000..96031c1 --- /dev/null +++ b/core/src/Model/Workflow.cpp @@ -0,0 +1,98 @@ +#include "Workflow.hpp" + +bool WorkflowStep::InputNode::IsConnected() const { + return ConnectedNodeIndex != -1; +} + +bool WorkflowStep::OutputNode::IsConnected() const { + return ConnectedNodeIndex != -1; +} + +size_t WorkflowStep::GetId() const { + return mId; +} + +void WorkflowStep::ConnectInput(int nodeId, size_t outputId, int outputNodeId) { + mWorkflow->Connect(mId, nodeId, outputId, outputNodeId); +} + +void WorkflowStep::DisconnectInput(int nodeId) { + mWorkflow->DisconnectByDestination(mId, nodeId); +} + +bool WorkflowStep::IsInputConnected(int nodeId) const { + return mInputs[nodeId].IsConnected(); +} + +void WorkflowStep::ConnectOutput(int nodeId, size_t inputId, int inputNodeId) { + mWorkflow->Connect(inputId, inputNodeId, mId, nodeId); +} + +void WorkflowStep::DisconnectOutput(int nodeId) { + mWorkflow->DisconnectBySource(mId, nodeId); +} + +bool WorkflowStep::IsOutputConnected(int nodeId) const { + return mOutputs[nodeId].IsConnected(); +} + +WorkflowConnection* Workflow::GetConnectionById(size_t id) { + return &mConnections[id]; +} + +WorkflowStep* Workflow::GetStepById(size_t id) { + return mSteps[id].get(); +} + +void WorkflowStep::OnIOAboutToChange() { + // TODO more robust solution that handles changes incrementally + for (size_t i = 0; i < mInputs.size(); ++i) { + DisconnectInput(i); + } + for (size_t i = 0; i < mOutputs.size(); ++i) { + DisconnectOutput(i); + } +} + +void WorkflowStep::OnIOChanged() { +} + +void Workflow::Connect(size_t source, int sourceNode, size_t destination, int destinationNode) { + auto& src = *mSteps[source]; + auto& o = src.mInputs[sourceNode]; + if (o.IsConnected()) { + DisconnectBySource(source, sourceNode); + } + + o.ConnectedStep = destination; + o.ConnectedNodeIndex = destinationNode; + + auto& dst = *mSteps[destination]; + auto& i = dst.mInputs[destinationNode]; + i.ConnectedStep = source; + i.ConnectedNodeIndex = sourceNode; +} + +void Workflow::DisconnectBySource(size_t source, int sourceNode) { + auto& src = *mSteps[source]; + auto& o = src.mOutputs[sourceNode]; + if (!o.IsConnected()) return; + + auto& i = mSteps[o.ConnectedStep]->mInputs[o.ConnectedNodeIndex]; + i.ConnectedStep = WorkflowStep::kInvalidId; + i.ConnectedNodeIndex = -1; + o.ConnectedStep = WorkflowStep::kInvalidId; + o.ConnectedNodeIndex = -1; +} + +void Workflow::DisconnectByDestination(size_t destination, int destinationNode) { + auto& dst = *mSteps[destination]; + auto& i = dst.mInputs[destinationNode]; + if (!i.IsConnected()) return; + + auto& o = mSteps[i.ConnectedStep]->mOutputs[i.ConnectedNodeIndex]; + i.ConnectedStep = WorkflowStep::kInvalidId; + i.ConnectedNodeIndex = -1; + o.ConnectedStep = WorkflowStep::kInvalidId; + o.ConnectedNodeIndex = -1; +} diff --git a/core/src/Model/Workflow.hpp b/core/src/Model/Workflow.hpp new file mode 100644 index 0000000..a90ef21 --- /dev/null +++ b/core/src/Model/Workflow.hpp @@ -0,0 +1,101 @@ +#pragma once + +#include "EvaluatedValue.hpp" + +#include +#include +#include +#include +#include + +class WorkflowConnection { +public: + size_t Source; + size_t Destination; +}; + +class WorkflowStep { +public: + static constexpr auto kInvalidId = std::numeric_limits::max(); + + // TODO do we even need this? + // enum Type { + // NumericAdditionType, + // NumericSubtractionType, + // NumericMultiplicationType, + // NumericDivisionType, + // NumericExpressionType, + // TextFormattingType, + // FormInputType, + // DatabaseRowsInputType, + // DocumentTemplateExpansionType, + // + // InvalidType, + // TypeCount = InvalidType, + // }; + +protected: + struct InputNode { + size_t ConnectedStep = kInvalidId; + int ConnectedNodeIndex = -1; + BaseValue::Type MatchingType = BaseValue::InvalidType; + + bool IsConnected() const; + }; + + struct OutputNode { + size_t ConnectedStep = kInvalidId; + int ConnectedNodeIndex = -1; + BaseValue::Type MatchingType = BaseValue::InvalidType; + + bool IsConnected() const; + }; + + friend class Workflow; + Workflow* mWorkflow; + size_t mId; + std::vector mInputs; + std::vector mOutputs; + +public: + virtual ~WorkflowStep() = default; + + WorkflowStep(const WorkflowStep&) = delete; + WorkflowStep& operator=(const WorkflowStep&) = delete; + WorkflowStep(WorkflowStep&&) = default; + WorkflowStep& operator=(WorkflowStep&&) = default; + + size_t GetId() const; + + void ConnectInput(int nodeId, size_t outputId, int outputNodeId); + void DisconnectInput(int nodeId); + bool IsInputConnected(int nodeId) const; + + void ConnectOutput(int nodeId, size_t inputId, int inputNodeId); + void DisconnectOutput(int nodeId); + bool IsOutputConnected(int nodeId) const; + +protected: + /// Child classes should call this whenever \c mInputs and \c mOutputs are about to be modified (append or remove) + /// after invocation of the constructor. + void OnIOAboutToChange(); + void OnIOChanged(); +}; + +class Workflow { +private: + std::vector mConnections; + std::vector> mSteps; + +public: + WorkflowConnection* GetConnectionById(size_t id); + WorkflowStep* GetStepById(size_t id); + + void Connect(size_t source, int sourceNode, size_t destination, int destinationNode); + void DisconnectBySource(size_t source, int sourceNode); + void DisconnectByDestination(size_t destination, int destinationNode); +}; + +class WorkflowEvaluationContext { + +}; diff --git a/core/src/Model/WorkflowSteps.cpp b/core/src/Model/WorkflowSteps.cpp new file mode 100644 index 0000000..815c5ca --- /dev/null +++ b/core/src/Model/WorkflowSteps.cpp @@ -0,0 +1,11 @@ +#include "WorkflowSteps.hpp" + +NumericOperationStep::NumericOperationStep(NumericOperationStep::Type type) + : mType{ type } { + mInputs.resize(2); + mInputs[0].MatchingType = BaseValue::NumericType; + mInputs[1].MatchingType = BaseValue::NumericType; + + mOutputs.resize(1); + mOutputs[0].MatchingType = BaseValue::NumericType; +} diff --git a/core/src/Model/WorkflowSteps.hpp b/core/src/Model/WorkflowSteps.hpp new file mode 100644 index 0000000..819ca63 --- /dev/null +++ b/core/src/Model/WorkflowSteps.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "Model/Workflow.hpp" + +#include + +class NumericOperationStep : public WorkflowStep { +public: + enum Type { + Addition, + Subtraction, + Multiplication, + Division, + + InvalidType, + TypeCount = InvalidType, + }; + +private: + Type mType; + +public: + NumericOperationStep(Type type); +}; + +class NumericExpressionStep : public WorkflowStep { +public: +}; + +class FormatTextStep : public WorkflowStep { +public: +}; + +class FormInputStep : public WorkflowStep { +public: +}; + +class DatabaseRowsInputStep : public WorkflowStep { +public: +}; + +class DocumentTemplateExpansionStep : public WorkflowStep { +public: +}; diff --git a/core/src/Model/fwd.hpp b/core/src/Model/fwd.hpp index 4bd508c..a2156aa 100644 --- a/core/src/Model/fwd.hpp +++ b/core/src/Model/fwd.hpp @@ -1,5 +1,11 @@ #pragma once +// EvaluatedValue.hpp +class BaseValue; +class NumericValue; +class TextValue; +class DateTimeValue; + // Filter.hpp class TableRowsFilter; @@ -23,3 +29,17 @@ class SalesTable; class PurchasesTable; class DeliveryTable; class TransactionModel; + +// Workflow.hpp +class WorkflowConnection; +class WorkflowStep; +class Workflow; +class WorkflowEvaluationContext; + +// WorkflowSteps.hpp +class NumericOperationStep; +class NumericExpressionStep; +class FormatTextStep; +class FormInputStep; +class DatabaseRowsInputStep; +class DocumentTemplateExpansionStep; diff --git a/core/src/UI/Localization.hpp b/core/src/UI/Localization.hpp index f476458..3f09fb2 100644 --- a/core/src/UI/Localization.hpp +++ b/core/src/UI/Localization.hpp @@ -27,6 +27,7 @@ public: BasicTranslation ProjectTab{ "MainWindow.Tab.Project"sv }; BasicTranslation DatabaseViewTab{ "MainWindow.Tab.DatabaseView"sv }; BasicTranslation ItemsTab{ "MainWindow.Tab.Items"sv }; + BasicTranslation WorkflowsTab{ "MainWindow.Tab.Workflows"sv }; /* Project tab */ diff --git a/core/src/UI/UI.hpp b/core/src/UI/UI.hpp index ab35321..9e97118 100644 --- a/core/src/UI/UI.hpp +++ b/core/src/UI/UI.hpp @@ -27,6 +27,6 @@ void MainWindow(); void SettingsTab(); void DatabaseViewTab(); void ItemsTab(); -void ExportTab(); +void WorkflowsTab(); } // namespace UI diff --git a/core/src/UI/UI_DatabaseView.cpp b/core/src/UI/UI_DatabaseView.cpp index 8791c5b..884ab51 100644 --- a/core/src/UI/UI_DatabaseView.cpp +++ b/core/src/UI/UI_DatabaseView.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -196,7 +197,7 @@ private: void UpdateLastPage() { mLastPage = mActiveEntries.empty() - ? 0 + ? 0 // TODO calc page : CalcPageForRowId(mActiveEntries.back()); } @@ -258,10 +259,17 @@ private: return ""; } - auto t = static_cast(epoch); - std::string str(29, '\0'); - std::strftime(str.data(), 21, "%Y-%m-%dT%H:%M:%S.", std::localtime(&t)); - return str; + namespace chrono = std::chrono; + using Clock = chrono::system_clock; + + chrono::milliseconds d{ epoch }; + chrono::time_point tp{ d }; + auto t = chrono::system_clock::to_time_t(tp); + + char data[32]; + std::strftime(data, sizeof(data), "%Y-%m-%d %H:%M:%S", std::localtime(&t)); + + return std::string(data); }; int customerCol = stmt.getColumnIndex("Customer"); diff --git a/core/src/UI/UI_Export.cpp b/core/src/UI/UI_Export.cpp deleted file mode 100644 index 06b49f5..0000000 --- a/core/src/UI/UI_Export.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "UI.hpp" - -#include "UI/Localization.hpp" - -#include - -void UI::ExportTab() { - // TODO -} diff --git a/core/src/UI/UI_Items.cpp b/core/src/UI/UI_Items.cpp index a094f76..56d6c2a 100644 --- a/core/src/UI/UI_Items.cpp +++ b/core/src/UI/UI_Items.cpp @@ -93,7 +93,7 @@ void ItemListEntries(ItemList& list, int& selectedIdx) { constexpr bool kHasEmail = requires(T t) { t.GetEmail(); }; constexpr bool kHasStock = requires(T t) { t.GetPrice(); }; constexpr bool kHasPrice = requires(T t) { t.GetPrice(); }; - constexpr int kColumns = 1 /* Name column */ + kHasDescription + kHasEmail; + constexpr int kColumns = 1 /* Name column */ + kHasDescription + kHasEmail + kHasStock + kHasPrice; auto ls = LocaleStrings::Instance.get(); auto& uis = UIState::GetInstance(); diff --git a/core/src/UI/UI_MainWindow.cpp b/core/src/UI/UI_MainWindow.cpp index 30505d6..f1d1e5e 100644 --- a/core/src/UI/UI_MainWindow.cpp +++ b/core/src/UI/UI_MainWindow.cpp @@ -223,6 +223,11 @@ void UI::MainWindow() { ImGui::EndTabItem(); } + if (ImGui::BeginTabItem()) { + UI::WorkflowsTab(); + ImGui::EndTabItem(); + } + endTab: ImGui::EndTabBar(); } diff --git a/core/src/UI/UI_Workflows.cpp b/core/src/UI/UI_Workflows.cpp new file mode 100644 index 0000000..2b0368e --- /dev/null +++ b/core/src/UI/UI_Workflows.cpp @@ -0,0 +1,9 @@ +#include "UI.hpp" + +#include "UI/Localization.hpp" + +#include + +void UI::WorkflowsTab() { + // TODO +} diff --git a/core/src/Utils/Sigslot.hpp b/core/src/Utils/Sigslot.hpp index 2751d9a..2a191b4 100644 --- a/core/src/Utils/Sigslot.hpp +++ b/core/src/Utils/Sigslot.hpp @@ -135,7 +135,7 @@ public: SlotGuard(SlotGuard&&) = default; SlotGuard& operator=(SlotGuard&&) = default; - /// Disconnect all connection associated with this SlotGuard. + /// DisconnectBySource all connection associated with this SlotGuard. void DisconnectAll(); private: @@ -144,7 +144,7 @@ private: /// Remove the connection data in this associated with slotId. This does not invoke /// the connections' stub's RemoveConnection function. void RemoveConnection(int slotId); - /// Disconnect all connections from the given stub associated with this SlotGuard. + /// DisconnectBySource all connections from the given stub associated with this SlotGuard. /// Implementation for SignalStub::RemoveConnectionsFor(SlotGuard&) void RemoveConnectionFor(SignalStub& stub); }; -- cgit v1.2.3-70-g09d2