From ce8660cc5bfc12e6e3f75d4cce22492783ca9066 Mon Sep 17 00:00:00 2001 From: rtk0c Date: Thu, 8 Apr 2021 08:49:10 -0700 Subject: Initial work on table visualizer --- core/src/UI/UI_DatabaseView.cpp | 274 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 273 insertions(+), 1 deletion(-) (limited to 'core/src/UI/UI_DatabaseView.cpp') diff --git a/core/src/UI/UI_DatabaseView.cpp b/core/src/UI/UI_DatabaseView.cpp index 234aeaa..2b74918 100644 --- a/core/src/UI/UI_DatabaseView.cpp +++ b/core/src/UI/UI_DatabaseView.cpp @@ -1,9 +1,281 @@ #include "UI.hpp" +#include "Model/Project.hpp" #include "UI/Localization.hpp" +#include "UI/States.hpp" +#include "Utils/ScopeGuard.hpp" +#include "cplt_fwd.hpp" +#include +#include #include +#include +#include +#include + +namespace { + +// TODO move to Settings +constexpr int kMaxEntriesPerPage = 20; + +class SaleEntry { +public: + std::string Customer; + std::string Deadline; + std::string DeliveryTime; +}; + +class SalesViewTab { +private: + Project* mProject; + + /// Current active filter object, or \c nullptr. + std::unique_ptr mActiveFilter; + + /// Inclusive. + /// \see mLastCachedRowId + int64_t mFirstCachedRowId; + /// Inclusive. + /// \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 mEntries; + + // TODO this is very impractical (running filter in client code instead of letting SQLite doing it) + // Maybe simply cache a list of indices produced by a sql query? + + /// A bitset of all active (should-be-displayed) entries based on current filter, updated whenever \c mActiveFilter is updated. + /// Should have the exact same size as \c entries. + std::vector mActiveEntries; + + /// A cached list of index to entries that should be displayed on the current page. + std::vector mCurrentPageEntries; + + /// The current page the user is on. + int mCurrentPage; + /// Last possible page for the current set table and filter (inclusive). + int mLastPage; + + /* UI states */ + + int mSelectedEntry; + +public: + void OnProjectChanged(Project* newProject) { + mProject = newProject; + + mEntries.clear(); + mCurrentPage = 0; + mLastPage = -1; + + mSelectedEntry = -1; + } + + void OnFilterChanged(std::unique_ptr filter) { + // TODO + } + + void Draw() { + bool dummy = true; + auto ls = LocaleStrings::Instance.get(); + + if (ImGui::Button(ICON_FA_ARROW_LEFT, mCurrentPage == 0)) { + SetPage(mCurrentPage - 1); + } + + ImGui::SameLine(); + // +1 to convert from 0-based indices to 1-based, for human legibility + ImGui::Text("%d/%d", mCurrentPage + 1, mLastPage + 1); + + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROW_RIGHT, mCurrentPage == mLastPage)) { + SetPage(mCurrentPage + 1); + } + + ImGui::SameLine(); + if (ImGui::Button(ls->Edit.Get(), mSelectedEntry == -1)) { + ImGui::OpenPopup(ls->EditSaleEntryDialogTitle.Get()); + } + if (ImGui::BeginPopupModal(ls->EditSaleEntryDialogTitle.Get(), &dummy, ImGuiWindowFlags_AlwaysAutoResize)) { + // TODO + ImGui::EndPopup(); + } + + if (ImGui::BeginTable("##SalesTable", 3)) { + + ImGui::TableSetupColumn(ls->DatabaseCustomerColumn.Get()); + ImGui::TableSetupColumn(ls->DatabaseDeadlineColumn.Get()); + ImGui::TableSetupColumn(ls->DatabaseDeliveryTimeColumn.Get()); + ImGui::TableHeadersRow(); + + for (int i : mCurrentPageEntries) { + auto& entry = mEntries[i]; + + ImGui::TableNextColumn(); + if (ImGui::Selectable(entry.Customer.c_str(), mSelectedEntry == i)) { + mSelectedEntry = i; + } + + ImGui::NextColumn(); + ImGui::Text("%s", entry.Deadline.c_str()); + + ImGui::NextColumn(); + if (entry.DeliveryTime.empty()) { + ImGui::Text("%s", ls->NotDeliveredMessage.Get()); + } else { + ImGui::Text("%s", entry.DeliveryTime.c_str()); + } + } + + ImGui::EndTable(); + } + } + +private: + void SetPage(int page) { + if (mCurrentPage != page) return; + + mCurrentPage = page; + EnsureCacheCoversPage(page); + + auto [begin, end] = CalcRangeForPage(page); + begin -= mFirstCachedRowId; + end -= mFirstCachedRowId; + + mCurrentPageEntries.clear(); + for (auto i = begin; i < end; ++i) { + if (mActiveEntries[i]) { + mCurrentPageEntries.push_back(i); + } + } + } + + void EnsureCacheCoversPage(int page) { + auto [begin, end] = CalcRangeForPage(page); + EnsureCacheCovers(begin, end - 1); + } + + void EnsureCacheCovers(int64_t firstRow, int64_t lastRow) { + if (firstRow > lastRow) { + std::swap(firstRow, lastRow); + } + + int newFirst = mFirstCachedRowId; + int newLast = mLastCachedRowId; + + bool doRebuild = false; + if (firstRow < mFirstCachedRowId) { + newFirst = CalcPageForRowId(firstRow) * kMaxEntriesPerPage; + doRebuild = true; + } + if (lastRow > mLastCachedRowId) { + newLast = CalcPageForRowId(lastRow) * kMaxEntriesPerPage; + doRebuild = true; + } + if (!doRebuild) return; + + auto front = LoadRange(newFirst, mFirstCachedRowId); + auto back = LoadRange(mLastCachedRowId + 1, newLast + 1); + + mEntries.insert(mEntries.begin(), front.begin(), front.end()); + mEntries.insert(mEntries.end(), back.begin(), back.end()); + // TODO update mActiveEntries + + mFirstCachedRowId = newFirst; + mLastCachedRowId = newLast; + } + + std::vector LoadRange(int64_t begin, int64_t end) { + std::vector result; + + size_t size = end - begin; + if (size == 0) { + return result; + } + + result.reserve(size); + + auto& stmt = mProject->GetDatabase().GetSales().GetRowsStatement; + DEFER { + stmt.reset(); + }; + + stmt.bind(1, begin); + stmt.bind(2, end); + + while (true) { + bool hasResult = stmt.executeStep(); + if (hasResult) { + result.push_back(stmt.getColumns()); + } else { + return result; + } + } + } + + static int CalcPageForRowId(int64_t rowId) { + return rowId / kMaxEntriesPerPage; + } + + /// Calculate range [begin, end) of row ids that the path-th page would show. + static std::pair CalcRangeForPage(int page) { + int begin = page * kMaxEntriesPerPage; + return { begin, begin + kMaxEntriesPerPage }; + } +}; + +class PurchaseEntry { +public: + std::string Factory; + std::string OrderTime; + std::string DeliveryTime; +}; + +class PurchasesViewTab { +private: + std::vector mEntries; + int mCurrentPage = 0; + +public: + void OnProjectChanged(Project* newProject) { + // TODO + } + + void Draw() { + auto ls = LocaleStrings::Instance.get(); + if (ImGui::BeginTable("##PurcahsesTable", 4)) { + ImGui::EndTable(); + } + } +}; +} // namespace void UI::DatabaseViewTab() { - // TODO + auto ls = LocaleStrings::Instance.get(); + auto& uis = UIState::GetInstance(); + + static Project* currentProject = nullptr; + static auto salesView = std::make_unique(); + static auto purchasesView = std::make_unique(); + + if (currentProject != uis.CurrentProject.get()) { + currentProject = uis.CurrentProject.get(); + salesView->OnProjectChanged(currentProject); + purchasesView->OnProjectChanged(currentProject); + } + + if (ImGui::BeginTabBar("##DatabaseViewTabs")) { + if (ImGui::BeginTabItem(ls->SalesViewTab.Get())) { + salesView->Draw(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem(ls->PurchasesViewTab.Get())) { + purchasesView->Draw(); + ImGui::EndTabItem(); + } + // if (ImGui::BeginTabItem(ls->DeliveriesTableTab.Get())) { + // ImGui::EndTabItem(); + // } + } } -- cgit v1.2.3-70-g09d2