From 538e804fc9beb83e711a210ffbb6badc15f285d5 Mon Sep 17 00:00:00 2001 From: rtk0c Date: Wed, 28 Apr 2021 21:25:55 -0700 Subject: Add table visualization for purchases --- core/CMakeLists.txt | 1 + core/locale/zh_CN.json | 4 +- core/src/Model/TransactionsModel.cpp | 8 +- core/src/Model/TransactionsModel.hpp | 4 + core/src/UI/Localization.hpp | 8 +- core/src/UI/UI_DatabaseView.cpp | 378 +++++++++++++++++++++++------------ core/src/UI/UI_Items.cpp | 6 +- core/src/UI/UI_MainWindow.cpp | 6 +- core/src/Utils/Time.cpp | 29 +++ core/src/Utils/Time.hpp | 7 + 10 files changed, 310 insertions(+), 141 deletions(-) create mode 100644 core/src/Utils/Time.cpp create mode 100644 core/src/Utils/Time.hpp diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 87e5e3c..fad1218 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -74,6 +74,7 @@ add_source_group(UTILS_MODULE_SOURCES src/Utils/I18n.cpp src/Utils/Sigslot.cpp src/Utils/StandardDirectories.cpp + src/Utils/Time.cpp ) function(add_executable_variant TARGET_NAME) diff --git a/core/locale/zh_CN.json b/core/locale/zh_CN.json index 1c1102d..97dba91 100644 --- a/core/locale/zh_CN.json +++ b/core/locale/zh_CN.json @@ -33,9 +33,11 @@ "Database.SalesView.TabName": "销售", "Database.SalesView.Edit.DialogTitle": "编辑销售记录", "Database.PurchasesView.TabName": "采购", - "Database.DeliveriesView.TabName": "运输", + "Database.PurchasesView.Edit.DialogTitle": "编辑采购记录", "Database.Column.Customer": "客户", + "Database.Column.Factory": "工厂", "Database.Column.Deadline": "交货期限", + "Database.Column.OrderTime": "下单时间", "Database.Column.DeliveryTime": "交货时间", "Database.Message.NotDelivered": "N/A", "Item.Add.DialogTitle": "新建物品项", diff --git a/core/src/Model/TransactionsModel.cpp b/core/src/Model/TransactionsModel.cpp index 968f0c0..914191d 100644 --- a/core/src/Model/TransactionsModel.cpp +++ b/core/src/Model/TransactionsModel.cpp @@ -18,10 +18,14 @@ SalesTable::SalesTable(TransactionModel& db) )""") { } -DeliveryTable::DeliveryTable(TransactionModel& db) { +PurchasesTable::PurchasesTable(TransactionModel& db) + // language=SQLite + : GetRowCountStatement(db.GetSQLite(), "SELECT Count(*) FROM Purchases") + // language=SQLite + , GetRowsStatement(db.GetSQLite(), "SELECT * FROM Purchases WHERE rowid >= ? AND rowid < ?") { } -PurchasesTable::PurchasesTable(TransactionModel& db) { +DeliveryTable::DeliveryTable(TransactionModel& db) { } static std::string GetDatabaseFilePath(const Project& project) { diff --git a/core/src/Model/TransactionsModel.hpp b/core/src/Model/TransactionsModel.hpp index 117c27a..7a71fca 100644 --- a/core/src/Model/TransactionsModel.hpp +++ b/core/src/Model/TransactionsModel.hpp @@ -17,6 +17,10 @@ public: }; class PurchasesTable { +public: + SQLite::Statement GetRowCountStatement; + SQLite::Statement GetRowsStatement; + public: PurchasesTable(TransactionModel& db); }; diff --git a/core/src/UI/Localization.hpp b/core/src/UI/Localization.hpp index 3f09fb2..777c161 100644 --- a/core/src/UI/Localization.hpp +++ b/core/src/UI/Localization.hpp @@ -61,13 +61,15 @@ public: BasicTranslation EditSaleEntryDialogTitle{ "Database.SalesView.Edit.DialogTitle"sv }; BasicTranslation PurchasesViewTab{ "Database.PurchasesView.TabName"sv }; - - BasicTranslation DeliveriesViewTab{ "Database.DeliveriesView.TabName"sv }; + BasicTranslation EditPurchaseEntryDialogTitle{ "Database.PurchasesView.Edit.DialogTitle"sv }; BasicTranslation DatabaseCustomerColumn{ "Database.Column.Customer"sv }; + BasicTranslation DatabaseFactoryColumn{ "Database.Column.Factory"sv }; BasicTranslation DatabaseDeadlineColumn{ "Database.Column.Deadline"sv }; + BasicTranslation DatabaseOrderTimeColumn{ "Database.Column.OrderTime"sv }; BasicTranslation DatabaseDeliveryTimeColumn{ "Database.Column.DeliveryTime"sv }; - BasicTranslation NotDeliveredMessage{ "Database.Message.NotDelivered"sv }; + + BasicTranslation NotDelievered{ "Database.Message.NotDelivered"sv }; /* Items tab */ 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 #include #include -#include #include -#include #include #include @@ -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 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 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 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 LoadRange(int64_t begin, int64_t end) { - std::vector 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 + std::vector LoadRange(int64_t begin, int64_t end, TCollector&& collector) { + std::vector 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 mEntries; + +public: + SalesTableView() { + auto ls = LocaleStrings::Instance.get(); + mEditDialogTitle = ls->EditSaleEntryDialogTitle.Get(); + } - chrono::milliseconds d{ epoch }; - chrono::time_point 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& 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(newFirst, mFirstCachedRowId, CollectRows); + auto back = LoadRange(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 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 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& 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(newFirst, mFirstCachedRowId, CollectRows); + auto back = LoadRange(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(); diff --git a/core/src/UI/UI_Items.cpp b/core/src/UI/UI_Items.cpp index 899db6e..624f942 100644 --- a/core/src/UI/UI_Items.cpp +++ b/core/src/UI/UI_Items.cpp @@ -122,12 +122,12 @@ void ItemListEntries(ItemList& list, int& selectedIdx) { if constexpr (kHasDescription) { ImGui::TableNextColumn(); - ImGui::Text("%s", entry.GetDescription().c_str()); + ImGui::TextUnformatted(entry.GetDescription().c_str()); } if constexpr (kHasEmail) { ImGui::TableNextColumn(); - ImGui::Text("%s", entry.GetEmail().c_str()); + ImGui::TextUnformatted(entry.GetEmail().c_str()); } if constexpr (kHasStock) { @@ -198,7 +198,7 @@ void ItemListEditor(ItemList& list) { list.Remove(selectedIdx); } if (ImGui::BeginPopupModal(ls->DeleteItemDialogTitle.Get(), &opened, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("%s", ls->DeleteItemDialogMessage.Get()); + ImGui::TextUnformatted(ls->DeleteItemDialogMessage.Get()); if (ImGui::Button(ls->DialogConfirm.Get())) { ImGui::CloseCurrentPopup(); diff --git a/core/src/UI/UI_MainWindow.cpp b/core/src/UI/UI_MainWindow.cpp index 001c2b8..4f09281 100644 --- a/core/src/UI/UI_MainWindow.cpp +++ b/core/src/UI/UI_MainWindow.cpp @@ -125,7 +125,7 @@ void ProjectTab_NoProject() { // Recent projects ImGui::Separator(); - ImGui::Text("%s", ls->RecentProjects.Get()); + ImGui::TextUnformatted(ls->RecentProjects.Get()); ImGui::SameLine(); if (ImGui::Button(ls->ClearRecentProjects.Get())) { @@ -137,11 +137,11 @@ void ProjectTab_NoProject() { size_t toRemoveIdx = rp.size(); if (rp.empty()) { - ImGui::Text("%s", ls->NoRecentProjectsMessage.Get()); + ImGui::TextUnformatted(ls->NoRecentProjectsMessage.Get()); } else { for (auto it = rp.rbegin(); it != rp.rend(); ++it) { auto& [path, recent] = *it; - ImGui::Text("%s", recent.c_str()); + ImGui::TextUnformatted(recent.c_str()); size_t idx = std::distance(it, rp.rend()) - 1; ImGui::PushID(idx); diff --git a/core/src/Utils/Time.cpp b/core/src/Utils/Time.cpp new file mode 100644 index 0000000..d8e0bd1 --- /dev/null +++ b/core/src/Utils/Time.cpp @@ -0,0 +1,29 @@ +#include "Time.hpp" + +#include + +std::string StringifyTimePoint(std::chrono::time_point tp) { + auto t = std::chrono::system_clock::to_time_t(tp); + + 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 + + return std::string(data); +} + +std::string StringifyTimeStamp(int64_t timeStamp) { + if (timeStamp == 0) { + return ""; + } + + namespace chrono = std::chrono; + using Clock = chrono::system_clock; + + chrono::milliseconds d{ timeStamp }; + chrono::time_point tp{ d }; + + return StringifyTimePoint(tp); +} diff --git a/core/src/Utils/Time.hpp b/core/src/Utils/Time.hpp new file mode 100644 index 0000000..1f5a048 --- /dev/null +++ b/core/src/Utils/Time.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include +#include + +std::string StringifyTimePoint(std::chrono::time_point tp); +std::string StringifyTimeStamp(int64_t timeStamp); -- cgit v1.2.3-70-g09d2