summaryrefslogtreecommitdiff
path: root/core/src
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2021-04-28 21:25:55 -0700
committerrtk0c <[email protected]>2021-04-28 21:28:34 -0700
commit538e804fc9beb83e711a210ffbb6badc15f285d5 (patch)
tree6157a4065cfca3204f3406a4533095214ec3e04b /core/src
parent00fd95526677d670d002ca81069636f0f74b91f7 (diff)
Add table visualization for purchases
Diffstat (limited to 'core/src')
-rw-r--r--core/src/Model/TransactionsModel.cpp8
-rw-r--r--core/src/Model/TransactionsModel.hpp4
-rw-r--r--core/src/UI/Localization.hpp8
-rw-r--r--core/src/UI/UI_DatabaseView.cpp378
-rw-r--r--core/src/UI/UI_Items.cpp6
-rw-r--r--core/src/UI/UI_MainWindow.cpp6
-rw-r--r--core/src/Utils/Time.cpp29
-rw-r--r--core/src/Utils/Time.hpp7
8 files changed, 306 insertions, 140 deletions
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
@@ -18,6 +18,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 <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();
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<T>& 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<T>& 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 <ctime>
+
+std::string StringifyTimePoint(std::chrono::time_point<std::chrono::system_clock> 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<chrono::system_clock> 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 <string>
+#include <chrono>
+
+std::string StringifyTimePoint(std::chrono::time_point<std::chrono::system_clock> tp);
+std::string StringifyTimeStamp(int64_t timeStamp);