#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() { 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(); // } } }