// 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 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 s_address_book; static data::AddressBook& getAddressBook() { if (!s_address_book) { s_address_book = std::make_unique(); 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(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(i)); ImGui::TableNextColumn(); bool is_selected = (s_selected_index == static_cast(i)); if (ImGui::Selectable(entry.label.c_str(), is_selected, ImGuiSelectableFlags_SpanAllColumns)) { s_selected_index = static_cast(i); } // Double-click to edit if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { s_selected_index = static_cast(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(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