ObsidianDragon - DragonX ImGui Wallet
Full-node GUI wallet for DragonX cryptocurrency. Built with Dear ImGui, SDL3, and OpenGL3/DX11. Features: - Send/receive shielded and transparent transactions - Autoshield with merged transaction display - Built-in CPU mining (xmrig) - Peer management and network monitoring - Wallet encryption with PIN lock - QR code generation for receive addresses - Transaction history with pagination - Console for direct RPC commands - Cross-platform (Linux, Windows)
This commit is contained in:
316
src/ui/windows/address_book_dialog.cpp
Normal file
316
src/ui/windows/address_book_dialog.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
// 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 "../notifications.h"
|
||||
#include "../theme.h"
|
||||
#include "../effects/imgui_acrylic.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");
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::OpenPopup("Address Book");
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicPopupModal("Address Book", &s_open,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
auto& book = getAddressBook();
|
||||
|
||||
// Toolbar
|
||||
if (material::StyledButton("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("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("Delete", ImVec2(0,0), S.resolveFont(actionBtn.font))) {
|
||||
if (has_selection) {
|
||||
book.removeEntry(s_selected_index);
|
||||
s_selected_index = -1;
|
||||
Notifications::instance().success("Entry deleted");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (material::StyledButton("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("Address copied to clipboard");
|
||||
}
|
||||
}
|
||||
|
||||
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("Label", ImGuiTableColumnFlags_WidthFixed, labelColW);
|
||||
ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("Notes", ImGuiTableColumnFlags_WidthFixed, notesColW);
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
if (book.empty()) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextDisabled("No saved addresses. Click 'Add New' to add one.");
|
||||
} 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("%zu addresses saved", book.size());
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
|
||||
// Add dialog
|
||||
if (s_show_add_dialog) {
|
||||
ImGui::OpenPopup("Add Address");
|
||||
}
|
||||
|
||||
// Re-use center from above (already defined at start of render)
|
||||
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, "Add Address");
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
ImGui::Text("Label:");
|
||||
ImGui::SetNextItemWidth(addrInput.width);
|
||||
ImGui::InputText("##AddLabel", s_edit_label, sizeof(s_edit_label));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Address:");
|
||||
ImGui::SetNextItemWidth(addrInput.width);
|
||||
ImGui::InputText("##AddAddress", s_edit_address, sizeof(s_edit_address));
|
||||
ImGui::SameLine();
|
||||
if (material::StyledButton("Paste##Add", 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("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("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("Address added to book");
|
||||
s_show_add_dialog = false;
|
||||
} else {
|
||||
Notifications::instance().error("Address already exists in book");
|
||||
}
|
||||
}
|
||||
|
||||
if (!can_add) ImGui::EndDisabled();
|
||||
|
||||
ImGui::SameLine();
|
||||
if (material::StyledButton("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, "Edit Address");
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
ImGui::Text("Label:");
|
||||
ImGui::SetNextItemWidth(addrInput.width);
|
||||
ImGui::InputText("##EditLabel", s_edit_label, sizeof(s_edit_label));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Address:");
|
||||
ImGui::SetNextItemWidth(addrInput.width);
|
||||
ImGui::InputText("##EditAddress", s_edit_address, sizeof(s_edit_address));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("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("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("Address updated");
|
||||
s_show_edit_dialog = false;
|
||||
} else {
|
||||
Notifications::instance().error("Failed to update - address may be duplicate");
|
||||
}
|
||||
}
|
||||
|
||||
if (!can_save) ImGui::EndDisabled();
|
||||
|
||||
ImGui::SameLine();
|
||||
if (material::StyledButton("Cancel", ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
s_show_edit_dialog = false;
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
Reference in New Issue
Block a user