Files
ObsidianDragon/src/ui/windows/address_book_dialog.cpp
dan_s 96c27bb949 feat: Full UI internationalization, pool hashrate stats, and layout caching
- Replace all hardcoded English strings with TR() translation keys across
  every tab, dialog, and component (~20 UI files)
- Expand all 8 language files (de, es, fr, ja, ko, pt, ru, zh) with
  complete translations (~37k lines added)
- Improve i18n loader with exe-relative path fallback and English base
  fallback for missing keys
- Add pool-side hashrate polling via pool stats API in xmrig_manager
- Introduce Layout::beginFrame() per-frame caching and refresh balance
  layout config only on schema generation change
- Offload daemon output parsing to worker thread
- Add CJK subset fallback font for Chinese/Japanese/Korean glyphs
2026-03-11 00:40:50 -05:00

310 lines
12 KiB
C++

// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "address_book_dialog.h"
#include "../../app.h"
#include "../../data/address_book.h"
#include "../../util/i18n.h"
#include "../notifications.h"
#include "../schema/ui_schema.h"
#include "../material/draw_helpers.h"
#include "imgui.h"
#include <memory>
namespace dragonx {
namespace ui {
// Static member initialization
bool AddressBookDialog::s_open = false;
int AddressBookDialog::s_selected_index = -1;
bool AddressBookDialog::s_show_add_dialog = false;
bool AddressBookDialog::s_show_edit_dialog = false;
char AddressBookDialog::s_edit_label[128] = "";
char AddressBookDialog::s_edit_address[512] = "";
char AddressBookDialog::s_edit_notes[512] = "";
// Shared address book instance
static std::unique_ptr<data::AddressBook> s_address_book;
static data::AddressBook& getAddressBook() {
if (!s_address_book) {
s_address_book = std::make_unique<data::AddressBook>();
s_address_book->load();
}
return *s_address_book;
}
void AddressBookDialog::show()
{
s_open = true;
s_selected_index = -1;
s_show_add_dialog = false;
s_show_edit_dialog = false;
// Reload address book
getAddressBook().load();
}
bool AddressBookDialog::isOpen()
{
return s_open;
}
void AddressBookDialog::render(App* app)
{
(void)app; // May use for send-to feature later
if (!s_open) return;
auto& S = schema::UI();
auto win = S.window("dialogs.address-book");
auto addrTable = S.table("dialogs.address-book", "address-table");
auto addrFrontLbl = S.label("dialogs.address-book", "address-front-label");
auto addrBackLbl = S.label("dialogs.address-book", "address-back-label");
auto addrInput = S.input("dialogs.address-book", "address-input");
auto notesInput = S.input("dialogs.address-book", "notes-input");
auto actionBtn = S.button("dialogs.address-book", "action-button");
if (material::BeginOverlayDialog(TR("address_book_title"), &s_open, win.width, 0.94f)) {
auto& book = getAddressBook();
// Toolbar
if (material::StyledButton(TR("address_book_add_new"), ImVec2(0,0), S.resolveFont(actionBtn.font))) {
s_show_add_dialog = true;
s_edit_label[0] = '\0';
s_edit_address[0] = '\0';
s_edit_notes[0] = '\0';
}
ImGui::SameLine();
bool has_selection = s_selected_index >= 0 && s_selected_index < static_cast<int>(book.size());
if (!has_selection) ImGui::BeginDisabled();
if (material::StyledButton(TR("edit"), ImVec2(0,0), S.resolveFont(actionBtn.font))) {
if (has_selection) {
const auto& entry = book.entries()[s_selected_index];
strncpy(s_edit_label, entry.label.c_str(), sizeof(s_edit_label) - 1);
strncpy(s_edit_address, entry.address.c_str(), sizeof(s_edit_address) - 1);
strncpy(s_edit_notes, entry.notes.c_str(), sizeof(s_edit_notes) - 1);
s_show_edit_dialog = true;
}
}
ImGui::SameLine();
if (material::StyledButton(TR("delete"), ImVec2(0,0), S.resolveFont(actionBtn.font))) {
if (has_selection) {
book.removeEntry(s_selected_index);
s_selected_index = -1;
Notifications::instance().success(TR("address_book_deleted"));
}
}
ImGui::SameLine();
if (material::StyledButton(TR("copy_address"), ImVec2(0,0), S.resolveFont(actionBtn.font))) {
if (has_selection) {
ImGui::SetClipboardText(book.entries()[s_selected_index].address.c_str());
Notifications::instance().info(TR("address_copied"));
}
}
if (!has_selection) ImGui::EndDisabled();
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// Address list
if (ImGui::BeginTable("AddressBookTable", 3,
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY,
ImVec2(0, addrTable.bottomReserve > 0 ? -addrTable.bottomReserve : -35)))
{
float labelColW = (addrTable.columns.count("label") && addrTable.columns.at("label").width > 0) ? addrTable.columns.at("label").width : 150;
float notesColW = (addrTable.columns.count("notes") && addrTable.columns.at("notes").width > 0) ? addrTable.columns.at("notes").width : 150;
ImGui::TableSetupColumn(TR("label"), ImGuiTableColumnFlags_WidthFixed, labelColW);
ImGui::TableSetupColumn(TR("address_label"), ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn(TR("notes"), ImGuiTableColumnFlags_WidthFixed, notesColW);
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableHeadersRow();
if (book.empty()) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextDisabled("%s", TR("address_book_empty"));
} else {
for (size_t i = 0; i < book.size(); i++) {
const auto& entry = book.entries()[i];
ImGui::TableNextRow();
ImGui::PushID(static_cast<int>(i));
ImGui::TableNextColumn();
bool is_selected = (s_selected_index == static_cast<int>(i));
if (ImGui::Selectable(entry.label.c_str(), is_selected,
ImGuiSelectableFlags_SpanAllColumns)) {
s_selected_index = static_cast<int>(i);
}
// Double-click to edit
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
s_selected_index = static_cast<int>(i);
strncpy(s_edit_label, entry.label.c_str(), sizeof(s_edit_label) - 1);
strncpy(s_edit_address, entry.address.c_str(), sizeof(s_edit_address) - 1);
strncpy(s_edit_notes, entry.notes.c_str(), sizeof(s_edit_notes) - 1);
s_show_edit_dialog = true;
}
ImGui::TableNextColumn();
// Truncate long addresses
std::string addr_display = entry.address;
int addrTruncLen = (addrTable.columns.count("address") && addrTable.columns.at("address").truncate > 0) ? addrTable.columns.at("address").truncate : 40;
if (addr_display.length() > static_cast<size_t>(addrTruncLen)) {
addr_display = addr_display.substr(0, addrFrontLbl.truncate) + "..." +
addr_display.substr(addr_display.length() - addrBackLbl.truncate);
}
ImGui::TextDisabled("%s", addr_display.c_str());
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", entry.address.c_str());
}
ImGui::TableNextColumn();
ImGui::TextDisabled("%s", entry.notes.c_str());
ImGui::PopID();
}
}
ImGui::EndTable();
}
// Status line
ImGui::TextDisabled(TR("address_book_count"), book.size());
material::EndOverlayDialog();
}
// Add dialog
if (s_show_add_dialog) {
ImGui::OpenPopup("Add Address");
}
// Re-use center for sub-dialogs
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if (ImGui::BeginPopupModal("Add Address", &s_show_add_dialog, ImGuiWindowFlags_AlwaysAutoResize)) {
material::Type().text(material::TypeStyle::H6, TR("address_book_add"));
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
ImGui::Text("%s", TR("label"));
ImGui::SetNextItemWidth(addrInput.width);
ImGui::InputText("##AddLabel", s_edit_label, sizeof(s_edit_label));
ImGui::Spacing();
ImGui::Text("%s", TR("address_label"));
ImGui::SetNextItemWidth(addrInput.width);
ImGui::InputText("##AddAddress", s_edit_address, sizeof(s_edit_address));
ImGui::SameLine();
if (material::StyledButton(TR("paste"), ImVec2(0,0), S.resolveFont(actionBtn.font))) {
const char* clipboard = ImGui::GetClipboardText();
if (clipboard) {
strncpy(s_edit_address, clipboard, sizeof(s_edit_address) - 1);
}
}
ImGui::Spacing();
ImGui::Text("%s", TR("notes_optional"));
ImGui::SetNextItemWidth(addrInput.width);
ImGui::InputTextMultiline("##AddNotes", s_edit_notes, sizeof(s_edit_notes), ImVec2(addrInput.width, notesInput.height > 0 ? notesInput.height : 60));
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
bool can_add = strlen(s_edit_label) > 0 && strlen(s_edit_address) > 0;
if (!can_add) ImGui::BeginDisabled();
if (material::StyledButton(TR("add"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
data::AddressBookEntry entry(s_edit_label, s_edit_address, s_edit_notes);
if (getAddressBook().addEntry(entry)) {
Notifications::instance().success(TR("address_book_added"));
s_show_add_dialog = false;
} else {
Notifications::instance().error(TR("address_book_exists"));
}
}
if (!can_add) ImGui::EndDisabled();
ImGui::SameLine();
if (material::StyledButton(TR("cancel"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
s_show_add_dialog = false;
}
ImGui::EndPopup();
}
// Edit dialog
if (s_show_edit_dialog) {
ImGui::OpenPopup("Edit Address");
}
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if (ImGui::BeginPopupModal("Edit Address", &s_show_edit_dialog, ImGuiWindowFlags_AlwaysAutoResize)) {
material::Type().text(material::TypeStyle::H6, TR("address_book_edit"));
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
ImGui::Text("%s", TR("label"));
ImGui::SetNextItemWidth(addrInput.width);
ImGui::InputText("##EditLabel", s_edit_label, sizeof(s_edit_label));
ImGui::Spacing();
ImGui::Text("%s", TR("address_label"));
ImGui::SetNextItemWidth(addrInput.width);
ImGui::InputText("##EditAddress", s_edit_address, sizeof(s_edit_address));
ImGui::Spacing();
ImGui::Text("%s", TR("notes_optional"));
ImGui::SetNextItemWidth(addrInput.width);
ImGui::InputTextMultiline("##EditNotes", s_edit_notes, sizeof(s_edit_notes), ImVec2(addrInput.width, notesInput.height > 0 ? notesInput.height : 60));
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
bool can_save = strlen(s_edit_label) > 0 && strlen(s_edit_address) > 0;
if (!can_save) ImGui::BeginDisabled();
if (material::StyledButton(TR("save"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
data::AddressBookEntry entry(s_edit_label, s_edit_address, s_edit_notes);
if (getAddressBook().updateEntry(s_selected_index, entry)) {
Notifications::instance().success(TR("address_book_updated"));
s_show_edit_dialog = false;
} else {
Notifications::instance().error(TR("address_book_update_failed"));
}
}
if (!can_save) ImGui::EndDisabled();
ImGui::SameLine();
if (material::StyledButton(TR("cancel"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
s_show_edit_dialog = false;
}
ImGui::EndPopup();
}
}
} // namespace ui
} // namespace dragonx