diff options
Diffstat (limited to 'core/src/UI/UI_DatabaseView.cpp')
-rw-r--r-- | core/src/UI/UI_DatabaseView.cpp | 378 |
1 files changed, 249 insertions, 129 deletions
diff --git a/core/src/UI/UI_DatabaseView.cpp b/core/src/UI/UI_DatabaseView.cpp index 4009e5b..96009fb 100644 --- a/core/src/UI/UI_DatabaseView.cpp +++ b/core/src/UI/UI_DatabaseView.cpp @@ -5,14 +5,12 @@ #include "UI/Localization.hpp" #include "UI/States.hpp" #include "Utils/ScopeGuard.hpp" -#include "cplt_fwd.hpp" +#include "Utils/Time.hpp" #include <IconsFontAwesome.h> #include <SQLiteCpp/Statement.h> #include <imgui.h> -#include <chrono> #include <cstdint> -#include <ctime> #include <memory> #include <vector> @@ -35,8 +33,14 @@ public: std::string DeliveryTime; }; -class SalesViewTab { -private: +class GenericTableView { +protected: + // Translation entries for implementer to fill out + const char* mEditDialogTitle; + + SQLite::Statement* mGetRowCountStatement; + SQLite::Statement* mGetRowsStatement; + Project* mProject; /// Current active filter object, or \c nullptr. @@ -49,11 +53,10 @@ private: /// \see mFirstCachedRowId int64_t mLastCachedRowId; - /// A cached, contiguous (row id of each entry is monotonically increasing, but not necessarily starts at 0) list ready-to-be-presented entries. May be incomplete. - std::vector<SaleEntry> mEntries; - - /// A vector of row ids of entries (in \c mEntries) that are visible under the current filter. To use these indices, the elements should be mapped to \c mEntries - /// index by offsetting by \c mFirstCachedRowId. + /// A vector of row ids of entries (in \c mEntries) that are visible under the current filter. To use these indices, the elements should be mapped to + /// index of the list of entries by adding \c mFirstCachedRowId. + /// The list of entries is a cached, contiguous (row id of each entry is monotonically increasing, but not necessarily starts at 0) list + /// of ready-to-be-presented entries, held by the implementer. std::vector<int> mActiveEntries; /// Number of rows in the table. @@ -66,16 +69,26 @@ private: int mSelectedEntryRowId; public: + static int CalcPageForRowId(int64_t rowId) { + return rowId / kMaxEntriesPerPage; + } + + /// Calculate range [begin, end) of index for the list of entries that are currently visible that the path-th page would show. + /// i.e. when there is a filter, look into \c mActiveEntryIndices; when there is no filter, use directly. + static std::pair<int64_t, int64_t> CalcRangeForPage(int page) { + int begin = page * kMaxEntriesPerPage; + return { begin, begin + kMaxEntriesPerPage }; + } + Project* GetProject() const { return mProject; } - void OnProjectChanged(Project* newProject) { + virtual void OnProjectChanged(Project* newProject) { mProject = newProject; - auto& stmt = newProject->GetTransactionsModel().GetSales().GetRowCountStatement; - if (stmt.executeStep()) { - mRowCount = stmt.getColumn(0).getInt(); + if (mGetRowCountStatement->executeStep()) { + mRowCount = mGetRowCountStatement->getColumn(0).getInt(); } else { // TODO report error } @@ -83,7 +96,7 @@ public: mFirstCachedRowId = 0; mLastCachedRowId = 0; - mEntries.clear(); + ClearEntries(); mActiveEntries.clear(); UpdateLastPage(); @@ -96,7 +109,7 @@ public: return mActiveFilter.get(); } - void OnFilterChanged() { + virtual void OnFilterChanged() { auto& stmt = mProject->GetTransactionsModel().GetSales().FilterRowsStatement; DEFER { stmt.reset(); @@ -141,18 +154,16 @@ public: ImGui::SameLine(); if (ImGui::Button(ls->Edit.Get(), mSelectedEntryRowId == -1)) { - ImGui::OpenPopup(ls->EditSaleEntryDialogTitle.Get()); + ImGui::OpenPopup(mEditDialogTitle); } - if (ImGui::BeginPopupModal(ls->EditSaleEntryDialogTitle.Get(), &dummy, ImGuiWindowFlags_AlwaysAutoResize)) { - // TODO + if (ImGui::BeginPopupModal(mEditDialogTitle, &dummy, ImGuiWindowFlags_AlwaysAutoResize)) { + EditEntry(mSelectedEntryRowId); ImGui::EndPopup(); } - if (ImGui::BeginTable("##SalesTable", 3)) { + if (ImGui::BeginTable("", GetTableColumnCount())) { - ImGui::TableSetupColumn(ls->DatabaseCustomerColumn.Get()); - ImGui::TableSetupColumn(ls->DatabaseDeadlineColumn.Get()); - ImGui::TableSetupColumn(ls->DatabaseDeliveryTimeColumn.Get()); + SetupTableColumns(); ImGui::TableHeadersRow(); auto [begin, end] = CalcRangeForPage(mCurrentPage); @@ -160,12 +171,12 @@ public: end = std::min(end, (int64_t)mActiveEntries.size() - 1); for (int i = begin; i < end; ++i) { int rowId = mActiveEntries[i]; - DrawEntry(GetEntry(rowId), rowId); + DisplayEntry(rowId); } } else { end = std::min(end, mLastCachedRowId); for (int rowId = begin; rowId < end; ++rowId) { - DrawEntry(GetEntry(rowId), rowId); + DisplayEntry(rowId); } } @@ -173,49 +184,27 @@ public: } } - void DrawEntry(const SaleEntry& entry, int rowId) { - auto ls = LocaleStrings::Instance.get(); - - ImGui::PushID(rowId); - ImGui::TableNextRow(); - - ImGui::TableNextColumn(); - if (ImGui::Selectable(entry.Customer.c_str(), mSelectedEntryRowId == rowId, ImGuiSelectableFlags_SpanAllColumns)) { - mSelectedEntryRowId = rowId; - } - - ImGui::TableNextColumn(); - ImGui::Text("%s", entry.Deadline.c_str()); - - ImGui::TableNextColumn(); - if (entry.DeliveryTime.empty()) { - ImGui::Text("%s", ls->NotDeliveredMessage.Get()); - } else { - ImGui::Text("%s", entry.DeliveryTime.c_str()); - } - - ImGui::PopID(); + void SetPage(int page) { + mCurrentPage = page; + EnsureCacheCoversPage(page); } - const SaleEntry& GetEntry(int64_t rowId) const { - return mEntries[RowIdToIndex(rowId)]; + int RowIdToIndex(int64_t rowId) const { + return rowId - mFirstCachedRowId; } - SaleEntry& GetEntry(int64_t rowId) { - return mEntries[RowIdToIndex(rowId)]; + int64_t IndexToRowId(int index) const { + return index + mFirstCachedRowId; } -private: - void SetPage(int page) { - mCurrentPage = page; - EnsureCacheCoversPage(page); - } +protected: + virtual int GetTableColumnCount() const = 0; + virtual void SetupTableColumns() = 0; - void UpdateLastPage() { - mLastPage = mActiveEntries.empty() - ? CalcPageForRowId(mRowCount) - : CalcPageForRowId(mActiveEntries.back()); - } + virtual void DisplayEntry(int rowId) = 0; + virtual void EditEntry(int rowId) = 0; + + virtual void ClearEntries() = 0; void EnsureCacheCoversPage(int page) { auto [begin, end] = CalcRangeForPage(page); @@ -241,19 +230,17 @@ private: } if (!doRebuild) return; - auto front = LoadRange(newFirst, mFirstCachedRowId); - auto back = LoadRange(mLastCachedRowId + 1, newLast + 1); - - mFirstCachedRowId -= front.size(); - mLastCachedRowId += back.size(); - - // TODO don't reallocate buffer/move elements around all the time. Maybe use a linked list of buckets? - mEntries.insert(mEntries.begin(), std::make_move_iterator(front.begin()), std::make_move_iterator(front.end())); - mEntries.insert(mEntries.end(), std::make_move_iterator(back.begin()), std::make_move_iterator(back.end())); + EnsureCacheCoversImpl(newFirst, newLast); } - std::vector<SaleEntry> LoadRange(int64_t begin, int64_t end) { - std::vector<SaleEntry> result; + /// To be implemented by child classes, presumable calling LoadRange() to get the front and back new contents. + /// \param newFirst The first rowid the new cache should cover + /// \param newLast The last rowid the new cache should cover + virtual void EnsureCacheCoversImpl(int newFirst, int newLast) = 0; + + template <class TEntry, class TCollector> + std::vector<TEntry> LoadRange(int64_t begin, int64_t end, TCollector&& collector) { + std::vector<TEntry> result; size_t size = end - begin; if (size == 0) { @@ -262,87 +249,220 @@ private: result.reserve(size); - auto& stmt = mProject->GetTransactionsModel().GetSales().GetRowsStatement; DEFER { - stmt.reset(); + mGetRowsStatement->reset(); }; - stmt.bind(1, begin); - stmt.bind(2, end); + mGetRowsStatement->bind(1, begin); + mGetRowsStatement->bind(2, end); - auto StringifyEpochTime = [](int64_t epoch) -> std::string { - if (epoch == 0) { - return ""; - } + collector(result); + return result; + } - namespace chrono = std::chrono; - using Clock = chrono::system_clock; + void UpdateLastPage() { + mLastPage = mActiveEntries.empty() + ? CalcPageForRowId(mRowCount) + : CalcPageForRowId(mActiveEntries.back()); + } +}; + +class SalesTableView : public GenericTableView { +private: + /// A cached, contiguous (row id of each entry is monotonically increasing, but not necessarily starts at 0) list ready-to-be-presented entries. May be incomplete. + std::vector<SaleEntry> mEntries; + +public: + SalesTableView() { + auto ls = LocaleStrings::Instance.get(); + mEditDialogTitle = ls->EditSaleEntryDialogTitle.Get(); + } - chrono::milliseconds d{ epoch }; - chrono::time_point<chrono::system_clock> tp{ d }; - auto t = chrono::system_clock::to_time_t(tp); + virtual void OnProjectChanged(Project* newProject) override { + auto& sales = newProject->GetTransactionsModel().GetSales(); + mGetRowCountStatement = &sales.GetRowCountStatement; + mGetRowsStatement = &sales.GetRowsStatement; - char data[32]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" // C++ doesn't have std::localtime_s - std::strftime(data, sizeof(data), "%Y-%m-%d %H:%M:%S", std::localtime(&t)); -#pragma clang diagnostic pop + GenericTableView::OnProjectChanged(newProject); + } - return std::string(data); - }; +protected: + virtual int GetTableColumnCount() const override { + return 3; + } - int customerCol = stmt.getColumnIndex("Customer"); - int deadlineCol = stmt.getColumnIndex("Deadline"); - int deliveryTimeCol = stmt.getColumnIndex("DeliveryTime"); + virtual void SetupTableColumns() override { + auto ls = LocaleStrings::Instance.get(); + ImGui::TableSetupColumn(ls->DatabaseCustomerColumn.Get()); + ImGui::TableSetupColumn(ls->DatabaseDeadlineColumn.Get()); + ImGui::TableSetupColumn(ls->DatabaseDeliveryTimeColumn.Get()); + } - while (stmt.executeStep()) { - auto customer = stmt.getColumn(customerCol).getInt(); - auto deadline = stmt.getColumn(deadlineCol).getInt64(); - auto deliveryTime = stmt.getColumn(deliveryTimeCol).getInt64(); - result.push_back(SaleEntry{ - .Customer = mProject->Customers.Find(customer)->GetName(), - .Deadline = StringifyEpochTime(deadline), - .DeliveryTime = StringifyEpochTime(deliveryTime), - }); + virtual void DisplayEntry(int rowId) override { + auto& entry = GetEntry(rowId); + auto ls = LocaleStrings::Instance.get(); + + ImGui::PushID(rowId); + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + if (ImGui::Selectable(entry.Customer.c_str(), mSelectedEntryRowId == rowId, ImGuiSelectableFlags_SpanAllColumns)) { + mSelectedEntryRowId = rowId; } - return result; + + ImGui::TableNextColumn(); + ImGui::TextUnformatted(entry.Deadline.c_str()); + + ImGui::TableNextColumn(); + if (entry.DeliveryTime.empty()) { + ImGui::TextUnformatted(ls->NotDelievered.Get()); + } else { + ImGui::TextUnformatted(entry.DeliveryTime.c_str()); + } + + ImGui::PopID(); } - int RowIdToIndex(int64_t rowId) const { - return rowId - mFirstCachedRowId; + virtual void EditEntry(int rowId) override { + // TODO } - int64_t IndexToRowId(int index) const { - return index + mFirstCachedRowId; + virtual void ClearEntries() override { + mEntries.clear(); } - static int CalcPageForRowId(int64_t rowId) { - return rowId / kMaxEntriesPerPage; + virtual void EnsureCacheCoversImpl(int newFirst, int newLast) override { + auto CollectRows = [&](std::vector<SaleEntry>& result) { + auto& stmt = *mGetRowsStatement; + int customerCol = stmt.getColumnIndex("Customer"); + int deadlineCol = stmt.getColumnIndex("Deadline"); + int deliveryTimeCol = stmt.getColumnIndex("DeliveryTime"); + + while (stmt.executeStep()) { + auto customer = stmt.getColumn(customerCol).getInt(); + auto deadline = stmt.getColumn(deadlineCol).getInt64(); + auto deliveryTime = stmt.getColumn(deliveryTimeCol).getInt64(); + result.push_back(SaleEntry{ + .Customer = mProject->Customers.Find(customer)->GetName(), + .Deadline = StringifyTimeStamp(deadline), + .DeliveryTime = StringifyTimeStamp(deliveryTime), + }); + } + }; + + auto front = LoadRange<SaleEntry>(newFirst, mFirstCachedRowId, CollectRows); + auto back = LoadRange<SaleEntry>(mLastCachedRowId + 1, newLast + 1, CollectRows); + + mFirstCachedRowId -= front.size(); + mLastCachedRowId += back.size(); + + mEntries.insert(mEntries.begin(), std::make_move_iterator(front.begin()), std::make_move_iterator(front.end())); + mEntries.insert(mEntries.end(), std::make_move_iterator(back.begin()), std::make_move_iterator(back.end())); } - /// Calculate range [begin, end) of index for the list of entries that are currently visible that the path-th page would show. - /// i.e. when there is a filter, look into \c mActiveEntryIndices; when there is no filter, use directly. - static std::pair<int64_t, int64_t> CalcRangeForPage(int page) { - int begin = page * kMaxEntriesPerPage; - return { begin, begin + kMaxEntriesPerPage }; + SaleEntry& GetEntry(int rowId) { + return mEntries[RowIdToIndex(rowId)]; } }; -class PurchasesViewTab { +class PurchasesTableView : public GenericTableView { private: std::vector<PurchaseEntry> mEntries; - int mCurrentPage = 0; public: - void OnProjectChanged(Project* newProject) { - // TODO + PurchasesTableView() { + auto ls = LocaleStrings::Instance.get(); + mEditDialogTitle = ls->EditPurchaseEntryDialogTitle.Get(); } - void Draw() { + virtual void OnProjectChanged(Project* newProject) override { + auto& purchases = newProject->GetTransactionsModel().GetPurchases(); + mGetRowCountStatement = &purchases.GetRowCountStatement; + mGetRowsStatement = &purchases.GetRowsStatement; + + GenericTableView::OnProjectChanged(newProject); + } + +protected: + virtual int GetTableColumnCount() const override { + return 3; + } + + virtual void SetupTableColumns() override { auto ls = LocaleStrings::Instance.get(); - if (ImGui::BeginTable("##PurcahsesTable", 4)) { - ImGui::EndTable(); + ImGui::TableSetupColumn(ls->DatabaseFactoryColumn.Get()); + ImGui::TableSetupColumn(ls->DatabaseOrderTimeColumn.Get()); + ImGui::TableSetupColumn(ls->DatabaseDeliveryTimeColumn.Get()); + } + + virtual void DisplayEntry(int rowId) override { + auto& entry = GetEntry(rowId); + auto ls = LocaleStrings::Instance.get(); + + ImGui::PushID(rowId); + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + if (ImGui::Selectable(entry.Factory.c_str(), mSelectedEntryRowId == rowId, ImGuiSelectableFlags_SpanAllColumns)) { + mSelectedEntryRowId = rowId; + } + + ImGui::TableNextColumn(); + if (entry.OrderTime.empty()) { + ImGui::TextUnformatted(ls->NotDelievered.Get()); + } else { + ImGui::TextUnformatted(entry.OrderTime.c_str()); } + + ImGui::TableNextColumn(); + if (entry.DeliveryTime.empty()) { + ImGui::TextUnformatted(ls->NotDelievered.Get()); + } else { + ImGui::TextUnformatted(entry.DeliveryTime.c_str()); + } + + ImGui::PopID(); + } + + virtual void EditEntry(int rowId) override { + // TODO + } + + virtual void ClearEntries() override { + mEntries.clear(); + } + + virtual void EnsureCacheCoversImpl(int newFirst, int newLast) override { + auto CollectRows = [&](std::vector<PurchaseEntry>& result) { + auto& stmt = *mGetRowsStatement; + int factoryCol = stmt.getColumnIndex("Factory"); + int orderTimeCol = stmt.getColumnIndex("OrderTime"); + int deliveryTimeCol = stmt.getColumnIndex("DeliveryTime"); + + while (stmt.executeStep()) { + auto factory = stmt.getColumn(factoryCol).getInt(); + auto orderTime = stmt.getColumn(orderTimeCol).getInt64(); + auto deliveryTime = stmt.getColumn(deliveryTimeCol).getInt64(); + result.push_back(PurchaseEntry{ + .Factory = mProject->Factories.Find(factory)->GetName(), + .OrderTime = StringifyTimeStamp(orderTime), + .DeliveryTime = StringifyTimeStamp(deliveryTime), + }); + } + }; + + auto front = LoadRange<PurchaseEntry>(newFirst, mFirstCachedRowId, CollectRows); + auto back = LoadRange<PurchaseEntry>(mLastCachedRowId + 1, newLast + 1, CollectRows); + + mFirstCachedRowId -= front.size(); + mLastCachedRowId += back.size(); + + mEntries.insert(mEntries.begin(), std::make_move_iterator(front.begin()), std::make_move_iterator(front.end())); + mEntries.insert(mEntries.end(), std::make_move_iterator(back.begin()), std::make_move_iterator(back.end())); + } + + PurchaseEntry& GetEntry(int rowId) { + return mEntries[RowIdToIndex(rowId)]; } }; } // namespace @@ -352,22 +472,22 @@ void UI::DatabaseViewTab() { auto& uis = UIState::GetInstance(); static Project* currentProject = nullptr; - static SalesViewTab salesView; - static PurchasesViewTab purchasesView; + static SalesTableView sales; + static PurchasesTableView purchases; if (currentProject != uis.CurrentProject.get()) { currentProject = uis.CurrentProject.get(); - salesView.OnProjectChanged(currentProject); - purchasesView.OnProjectChanged(currentProject); + sales.OnProjectChanged(currentProject); + purchases.OnProjectChanged(currentProject); } if (ImGui::BeginTabBar("##DatabaseViewTabs")) { if (ImGui::BeginTabItem(ls->SalesViewTab.Get())) { - salesView.Draw(); + sales.Draw(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem(ls->PurchasesViewTab.Get())) { - purchasesView.Draw(); + purchases.Draw(); ImGui::EndTabItem(); } ImGui::EndTabBar(); |