diff options
author | rtk0c <[email protected]> | 2021-04-08 08:49:10 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2021-04-08 08:49:10 -0700 |
commit | ce8660cc5bfc12e6e3f75d4cce22492783ca9066 (patch) | |
tree | f5c1088def48e60570a1225d6440f304b37b5b5c /core/src/UI | |
parent | 2f4b9db39239ed5150094a81743beea42a3eedc2 (diff) |
Initial work on table visualizer
Diffstat (limited to 'core/src/UI')
-rw-r--r-- | core/src/UI/Localization.hpp | 39 | ||||
-rw-r--r-- | core/src/UI/UI_DatabaseView.cpp | 274 | ||||
-rw-r--r-- | core/src/UI/UI_Items.cpp | 24 | ||||
-rw-r--r-- | core/src/UI/UI_MainWindow.cpp | 13 |
4 files changed, 326 insertions, 24 deletions
diff --git a/core/src/UI/Localization.hpp b/core/src/UI/Localization.hpp index 86b7afc..f476458 100644 --- a/core/src/UI/Localization.hpp +++ b/core/src/UI/Localization.hpp @@ -12,15 +12,23 @@ public: static std::unique_ptr<LocaleStrings> Instance; public: + /* Generic */ + BasicTranslation Error{ "Generic.Error"sv }; + BasicTranslation Add{ "Generic.Add"sv }; + BasicTranslation Edit{ "Generic.Edit"sv }; + BasicTranslation Delete{ "Generic.Delete"sv }; BasicTranslation DialogConfirm{ "Generic.Dialog.Confirm"sv }; BasicTranslation DialogCancel{ "Generic.Dialog.Cancel"sv }; - BasicTranslation TabSettings{ "MainWindow.Tab.Settings"sv }; - BasicTranslation TabProject{ "MainWindow.Tab.Project"sv }; - BasicTranslation TabDatabaseView{ "MainWindow.Tab.DatabaseView"sv }; - BasicTranslation TabItems{ "MainWindow.Tab.Items"sv }; - BasicTranslation TabExport{ "MainWindow.Tab.Exports"sv }; + /* Main window */ + + BasicTranslation SettingsTab{ "MainWindow.Tab.Settings"sv }; + BasicTranslation ProjectTab{ "MainWindow.Tab.Project"sv }; + BasicTranslation DatabaseViewTab{ "MainWindow.Tab.DatabaseView"sv }; + BasicTranslation ItemsTab{ "MainWindow.Tab.Items"sv }; + + /* Project tab */ BasicTranslation NewProject{ "Project.New"sv }; BasicTranslation NewProjectDialogTitle{ "Project.New.DialogTitle"sv }; @@ -46,11 +54,24 @@ public: BasicTranslation ActiveProjectName{ "ActiveProject.Info.Name"sv }; BasicTranslation ActiveProjectPath{ "ActiveProject.Info.Path"sv }; - BasicTranslation AddItem{ "Item.Add"sv }; + /* Database view tab */ + + BasicTranslation SalesViewTab{ "Database.SalesView.TabName"sv }; + BasicTranslation EditSaleEntryDialogTitle{ "Database.SalesView.Edit.DialogTitle"sv }; + + BasicTranslation PurchasesViewTab{ "Database.PurchasesView.TabName"sv }; + + BasicTranslation DeliveriesViewTab{ "Database.DeliveriesView.TabName"sv }; + + BasicTranslation DatabaseCustomerColumn{ "Database.Column.Customer"sv }; + BasicTranslation DatabaseDeadlineColumn{ "Database.Column.Deadline"sv }; + BasicTranslation DatabaseDeliveryTimeColumn{ "Database.Column.DeliveryTime"sv }; + BasicTranslation NotDeliveredMessage{ "Database.Message.NotDelivered"sv }; + + /* Items tab */ + BasicTranslation AddItemDialogTitle{ "Item.Add.DialogTitle"sv }; - BasicTranslation EditItem{ "Item.Edit"sv }; BasicTranslation EditItemDialogTitle{ "Item.Edit.DialogTitle"sv }; - BasicTranslation DeleteItem{ "Item.Delete"sv }; BasicTranslation DeleteItemDialogTitle{ "Item.Delete.DialogTitle"sv }; BasicTranslation DeleteItemDialogMessage{ "Item.Delete.DialogMessage"sv }; @@ -61,6 +82,8 @@ public: BasicTranslation ItemNameColumn{ "Item.Column.Name"sv }; BasicTranslation ItemDescriptionColumn{ "Item.Column.Description"sv }; BasicTranslation ItemEmailColumn{ "Item.Column.Email"sv }; + BasicTranslation ItemStockColumn{ "Item.Column.Stock"sv }; + BasicTranslation ItemPriceColumn{ "Item.Column.Price"sv }; BasicTranslation EmptyItemNameError{ "Item.EmptyNameError"sv }; BasicTranslation DuplicateItemNameError{ "Item.DuplicateNameError"sv }; 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 <IconsFontAwesome.h> +#include <SQLiteCpp/Statement.h> #include <imgui.h> +#include <cstdint> +#include <memory> +#include <vector> + +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<TableRowsFilter> 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<SaleEntry> 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<bool> mActiveEntries; + + /// A cached list of index to entries that should be displayed on the current page. + std::vector<int> 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<TableRowsFilter> 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<SaleEntry> LoadRange(int64_t begin, int64_t end) { + std::vector<SaleEntry> 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<SaleEntry, 3>()); + } 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<int64_t, int64_t> 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<PurchaseEntry> 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<SalesViewTab>(); + static auto purchasesView = std::make_unique<PurchasesViewTab>(); + + 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(); + // } + } } diff --git a/core/src/UI/UI_Items.cpp b/core/src/UI/UI_Items.cpp index 970d0df..a094f76 100644 --- a/core/src/UI/UI_Items.cpp +++ b/core/src/UI/UI_Items.cpp @@ -91,6 +91,8 @@ template <class T> void ItemListEntries(ItemList<T>& list, int& selectedIdx) { constexpr bool kHasDescription = requires(T t) { t.GetDescription(); }; 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; auto ls = LocaleStrings::Instance.get(); @@ -101,6 +103,8 @@ void ItemListEntries(ItemList<T>& list, int& selectedIdx) { ImGui::TableSetupColumn(ls->ItemNameColumn.Get()); if constexpr (kHasDescription) ImGui::TableSetupColumn(ls->ItemDescriptionColumn.Get()); if constexpr (kHasEmail) ImGui::TableSetupColumn(ls->ItemEmailColumn.Get()); + if constexpr (kHasStock) ImGui::TableSetupColumn(ls->ItemStockColumn.Get()); + if constexpr (kHasPrice) ImGui::TableSetupColumn(ls->ItemPriceColumn.Get()); ImGui::TableHeadersRow(); size_t idx = 0; @@ -111,24 +115,32 @@ void ItemListEntries(ItemList<T>& list, int& selectedIdx) { ImGui::TableNextRow(); - // Field: name ImGui::TableNextColumn(); if (ImGui::Selectable(entry.GetName().c_str(), selectedIdx == idx, ImGuiSelectableFlags_SpanAllColumns)) { selectedIdx = idx; } - // Field: description if constexpr (kHasDescription) { ImGui::TableNextColumn(); ImGui::Text("%s", entry.GetDescription().c_str()); } - // Field: email if constexpr (kHasEmail) { ImGui::TableNextColumn(); ImGui::Text("%s", entry.GetEmail().c_str()); } + if constexpr (kHasStock) { + ImGui::TableNextColumn(); + ImGui::Text("%d", entry.GetStock()); + } + + if constexpr (kHasPrice) { + ImGui::TableNextColumn(); + // TODO format in dollars + ImGui::Text("%d", entry.GetPrice()); + } + idx++; } ImGui::EndTable(); @@ -143,7 +155,7 @@ void ItemListEditor(ItemList<T>& list) { static int selectedIdx = -1; static T* editingItem = nullptr; - if (ImGui::Button(ls->AddItem.Get())) { + if (ImGui::Button(ls->Add.Get())) { ImGui::SetNextWindowCentered(); ImGui::OpenPopup(ls->AddItemDialogTitle.Get()); @@ -165,7 +177,7 @@ void ItemListEditor(ItemList<T>& list) { } ImGui::SameLine(); - if (ImGui::Button(ls->EditItem.Get(), selectedIdx == -1)) { + if (ImGui::Button(ls->Edit.Get(), selectedIdx == -1)) { ImGui::SetNextWindowCentered(); ImGui::OpenPopup(ls->EditItemDialogTitle.Get()); @@ -179,7 +191,7 @@ void ItemListEditor(ItemList<T>& list) { } ImGui::SameLine(); - if (ImGui::Button(ls->DeleteItem.Get(), selectedIdx == -1)) { + if (ImGui::Button(ls->Delete.Get(), selectedIdx == -1)) { ImGui::SetNextWindowCentered(); ImGui::OpenPopup(ls->DeleteItemDialogTitle.Get()); diff --git a/core/src/UI/UI_MainWindow.cpp b/core/src/UI/UI_MainWindow.cpp index a47efab..30505d6 100644 --- a/core/src/UI/UI_MainWindow.cpp +++ b/core/src/UI/UI_MainWindow.cpp @@ -195,12 +195,12 @@ void UI::MainWindow() { ImGui::SetNextWindowPos({ 0, 0 }); ImGui::Begin("##MainWindow", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); if (ImGui::BeginTabBar("##MainWindowTabs")) { - if (ImGui::BeginTabItem(ls->TabSettings.Get())) { + if (ImGui::BeginTabItem(ls->SettingsTab.Get())) { UI::SettingsTab(); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem(ls->TabProject.Get(), nullptr)) { + if (ImGui::BeginTabItem(ls->ProjectTab.Get(), nullptr)) { if (uis.CurrentProject) { ProjectTab_Normal(); } else { @@ -213,21 +213,16 @@ void UI::MainWindow() { goto endTab; } - if (ImGui::BeginTabItem(ls->TabDatabaseView.Get())) { + if (ImGui::BeginTabItem(ls->DatabaseViewTab.Get())) { UI::DatabaseViewTab(); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem(ls->TabItems.Get())) { + if (ImGui::BeginTabItem(ls->ItemsTab.Get())) { UI::ItemsTab(); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem(ls->TabExport.Get())) { - UI::ExportTab(); - ImGui::EndTabItem(); - } - endTab: ImGui::EndTabBar(); } |