/* * Copyright (c) 2025 Aldrik Ramaekers * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "ui.hpp" #include "strops.hpp" #include "memops.hpp" #include "config.hpp" #include "locales.hpp" #include "countries.hpp" #include "administration.hpp" #include "administration_writer.hpp" namespace ui { ImFont* fontBold; ImFont* fontBig; ImFont* fontSmall; } namespace ImGui { bool CheckboxX(const char* label, bool* v, bool disabled, bool show_loading_indicator_while_disabled) { bool result = false; if (disabled) ImGui::BeginDisabled(); if (!disabled || !show_loading_indicator_while_disabled) { result = ImGui::Checkbox(label, v); } else { bool tmp = false; result = ImGui::Checkbox(label, &tmp); if (result) *v = !(*v); float radius = 10.0f; ImVec2 p_min = ImGui::GetItemRectMin(); ImVec2 p_max = ImGui::GetItemRectMax(); ImVec2 center = ImVec2((p_min.x + p_max.x) * 0.5f - radius, (p_min.y + p_max.y) * 0.5f - radius); ImGui::SetCursorScreenPos(ImVec2(center.x, center.y)); const ImVec4 col = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); const ImVec4 bg = ImGui::GetStyleColorVec4(ImGuiCol_Button); ImGui::LoadingIndicatorCircle(radius, bg, col, 6, 4.0f); } if (disabled) ImGui::EndDisabled(); return result; } static ImVec2 CalcButtonSize(const char* label) { ImGuiStyle& style = ImGui::GetStyle(); ImVec2 text_size = ImGui::CalcTextSize(label, nullptr, true); ImVec2 button_size = ImVec2( text_size.x + style.FramePadding.x * 2.0f, text_size.y + style.FramePadding.y * 2.0f ); return button_size; } static void DrawSuccessMark(int bWidth = 0, bool isButton = true) { ImGui::PushStyleColor(ImGuiCol_Text, config::colors::COLOR_SUCCESS); if (isButton) ImGui::Button("√", ImVec2(bWidth, 0)); else ImGui::Text("√"); ImGui::PopStyleColor(); } static void DrawFailureMark(int bWidth = 0, bool isButton = true) { ImGui::PushStyleColor(ImGuiCol_Text, config::colors::COLOR_ERROR); if (isButton) ImGui::Button("X", ImVec2(bWidth, 0)); else ImGui::Text("√"); ImGui::PopStyleColor(); } bool Button(const char* label, bool block_while_writing_to_disk, bool show_loading_indicator_while_blocked) { ImGui::PushID(label); ImGuiID id = ImGui::GetID(label); static ImGuiID active_id = -1; static bool show_status = false; static double status_time; if (block_while_writing_to_disk) { if (administration_writer::is_writing()) { // Only show indicator on activated button. if (show_loading_indicator_while_blocked && active_id == id) { ImGui::BeginDisabled(); bool result = ImGui::Button("", CalcButtonSize(label)); ImVec2 oldPos = ImGui::GetCursorScreenPos(); float radius = 10.0f; ImVec2 p_min = ImGui::GetItemRectMin(); ImVec2 p_max = ImGui::GetItemRectMax(); ImVec2 center = ImVec2((p_min.x + p_max.x) * 0.5f - radius, (p_min.y + p_max.y) * 0.5f - radius); ImGui::SetCursorScreenPos(ImVec2(center.x, center.y)); const ImVec4 col = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); const ImVec4 bg = ImGui::GetStyleColorVec4(ImGuiCol_Button); ImGui::LoadingIndicatorCircle(radius, bg, col, 6, 4.0f); ImGui::EndDisabled(); ImGui::SetCursorScreenPos(oldPos); ImGui::SameLine(); ImGui::Dummy(ImVec2(0, 0)); ImGui::PopID(); return result; } else { ImGui::BeginDisabled(); bool result = ImGui::Button(label); ImGui::EndDisabled(); ImGui::PopID(); return result; } } else { bool result = false; if (active_id == id && !show_status && show_loading_indicator_while_blocked) { show_status = true; status_time = ImGui::GetTime(); } if (active_id == id && show_status) { ImGui::BeginDisabled(); ImGuiStyle& style = ImGui::GetStyle(); ImVec2 framePadding = style.FramePadding; ImVec2 textSize = ImGui::CalcTextSize(label, 0, true); float padding = framePadding.x; // Adjust based on your design float buttonWidth = textSize.x + 2 * padding; if (administration_writer::last_write_result()) DrawSuccessMark(buttonWidth); else DrawFailureMark(buttonWidth); ImGui::EndDisabled(); ImGui::PopID(); if (ImGui::GetTime() - status_time > 0.5) { show_status = false; active_id = -1; } return result; } else { bool result = ImGui::Button(label); if (result) { active_id = id; show_status = false; } ImGui::PopID(); return result; } } } else { ImGui::PopID(); return ImGui::Button(label); } } void InputTextWithError(const char* text, char* buffer, size_t buf_size, bool has_error) { if (has_error) { ImGui::PushStyleColor(ImGuiCol_Border, config::colors::COLOR_ERROR_OUTLINE); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5f); } ImGui::InputText(text, buffer, buf_size); if (has_error) { ImGui::PopStyleVar(); ImGui::PopStyleColor(); } } void InputTextWithErrorHint(const char* hint, char* buffer, size_t buf_size, bool has_error) { float widthAvailable = ImGui::GetContentRegionAvail().x; ImGui::SetNextItemWidth(widthAvailable*0.5f); if (has_error) { ImGui::PushStyleColor(ImGuiCol_Border, config::colors::COLOR_ERROR_OUTLINE); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5f); } ImGui::InputTextWithHint(hint, hint, buffer, buf_size); if (has_error) { ImGui::PopStyleVar(); ImGui::PopStyleColor(); ImGui::SameLine(); ImGui::SetCursorPosX(ImGui::GetCursorPosX() - ImGui::CalcTextSize("*").x); ImGui::TextColored(ImVec4(1, 0, 0, 1), "*"); } } bool FileSelect(const char* text, char* buffer) { bool result = false; float widthAvailable = ImGui::GetContentRegionAvail().x; ImGui::SetNextItemWidth(widthAvailable*0.5f); if (ImGui::Button(text)) { const char *filterPatterns[] = { "*.pdf" }; const char *file = tinyfd_openFileDialog( locale::get("ui.fileselect.title"), // dialog title NULL, // default path 1, // number of filter patterns filterPatterns, // filter patterns array NULL, // single filter description (can be NULL) 0); // allowMultiple (0 = single) if (file) { strops::copy(buffer, file, MAX_LEN_PATH); buffer[MAX_LEN_PATH-1] = '\0'; result = true; } } if (buffer[0] != '\0') { ImGui::SameLine(); if (ImGui::Button(locale::get("ui.clear"))) { buffer[0] = '\0'; result = true; } ImGui::SameLine(); ImGui::TextWrapped("%s: %s", locale::get("ui.selected"), buffer); } return result; } void CountryDropdown(char* buffer, size_t buf_size, bool activated_only) { const char* selected_country = 0; float widthAvailable = ImGui::GetContentRegionAvail().x; ImGui::SetNextItemWidth(widthAvailable*0.5f); const char* countries[300]; bool enabled_countries[300] = {0}; for (int x = 0; x < country::get_count(); x++) { char locale_str[20]; strops::format(locale_str, 20, "country.%s", country::get_code_by_index(x)); countries[x] = locale::get(locale_str); if (activated_only) { enabled_countries[x] = country::is_enabled(country::get_code_by_index(x)); } else { enabled_countries[x] = 1; } } for (int i = 0; i < country::get_count(); i++) { if (strops::equals(country::get_code_by_index(i), buffer)) { selected_country = countries[i]; break; } } bool has_a_selection = selected_country != 0; int selected_country_index = -1; if (!has_a_selection) { ImGui::PushStyleColor(ImGuiCol_Border, config::colors::COLOR_ERROR_OUTLINE); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5f); } if (ImGui::BeginCombo(locale::get("contact.form.country"), selected_country)) { for (int n = 0; n < country::get_count(); n++) { if (enabled_countries[n]) { bool is_selected = (selected_country == countries[n]); if (ImGui::Selectable(countries[n], is_selected)) { selected_country = countries[n]; selected_country_index = n; } } } ImGui::EndCombo(); } if (!has_a_selection) { ImGui::PopStyleVar(); ImGui::PopStyleColor(); ImGui::SameLine(); ImGui::SetCursorPosX(ImGui::GetCursorPosX() - ImGui::CalcTextSize("*").x); ImGui::TextColored(ImVec4(1, 0, 0, 1), "*"); } if (selected_country_index != -1) { strops::copy(buffer, country::get_code_by_index(selected_country_index), buf_size); } } void ContactTypeDropdown(contact_type* type) { float widthAvailable = ImGui::GetContentRegionAvail().x; ImGui::SetNextItemWidth(widthAvailable*0.5f); const char* customer_types[2] = { locale::get("contact.form.type.business"), locale::get("contact.form.type.consumer") }; int currentItem = static_cast(*type); if (ImGui::Combo(locale::get("contact.form.type"), ¤tItem, customer_types, IM_ARRAYSIZE(customer_types))) { *type = static_cast(currentItem); } } typedef struct { const char* id; bool is_open; } open_autocomplete_ref; int InputTextWithAutocomplete(const char* hint, char* buffer, size_t buf_size, char* suggestions[], int suggestion_count, bool has_error) { int result = -1; const u32 max_ref_count = 10; static open_autocomplete_ref open_refs[max_ref_count] = {0}; open_autocomplete_ref* found_ref = 0; { for (u32 i = 0; i < max_ref_count; i++) { if (open_refs[i].id == buffer) { found_ref = &open_refs[i]; break; } } if (!found_ref) { for (u32 i = 0; i < max_ref_count; i++) { if (open_refs[i].id == 0) { found_ref = &open_refs[i]; found_ref->id = buffer; found_ref->is_open = false; break; } } } if (!found_ref) assert(0); } bool is_open = found_ref->is_open; if (has_error) { ImGui::PushStyleColor(ImGuiCol_Border, config::colors::COLOR_ERROR_OUTLINE); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5f); } ImGui::InputTextWithHint(hint, hint, buffer, buf_size); if (has_error) { ImGui::PopStyleVar(); ImGui::PopStyleColor(); ImGui::SameLine(); ImGui::SetCursorPosX(ImGui::GetCursorPosX() - ImGui::CalcTextSize("*").x); ImGui::TextColored(ImVec4(1, 0, 0, 1), "*"); } if (buffer[0] == '\0' && is_open) is_open = false; if (suggestion_count == 0 && is_open) is_open = false; bool is_active = ImGui::IsItemActive(); if (is_active && buffer[0] != '\0' && suggestion_count > 0) { is_open = true; } if (is_open) { ImGui::BeginChild("autocomplete_popup", ImVec2(0, 10.0f + suggestion_count*23.0f), true); { ImVec2 win_pos = ImGui::GetWindowPos(); ImVec2 win_size = ImGui::GetWindowSize(); ImVec2 mouse_pos = ImGui::GetMousePos(); bool mouse_clicked_outside = !is_active && ImGui::IsMouseClicked(0) && (mouse_pos.x < win_pos.x || mouse_pos.x > win_pos.x + win_size.x || mouse_pos.y < win_pos.y || mouse_pos.y > win_pos.y + win_size.y); if (mouse_clicked_outside) is_open = false; for (int i = 0; i < suggestion_count; ++i) { ImGui::PushID(i); if (ImGui::Selectable(suggestions[i])) { // Copy selected suggestion to buffer strops::copy(buffer, suggestions[i], buf_size); buffer[buf_size - 1] = '\0'; result = i; is_open = false; } ImGui::PopID(); } ImGui::EndChild(); } } found_ref->is_open = is_open; return result; } static void _ContactFormNameAutocomplete(contact* buffer, bool has_error) { float widthAvailable = ImGui::GetContentRegionAvail().x; ImGui::SetNextItemWidth(widthAvailable*0.5f); contact autocomplete_list[5]; int autocomplete_count = administration::contact_get_autocompletions(autocomplete_list, 5, buffer->name); char* autocomplete_strings[5]; for (int i = 0; i < autocomplete_count; i++) { autocomplete_strings[i] = (char*)memops::alloc(200); strops::format(autocomplete_strings[i], 200, "%s (%s %s)", autocomplete_list[i].name, autocomplete_list[i].address.address1, autocomplete_list[i].address.address2); } int autocomplete_index = ImGui::InputTextWithAutocomplete(locale::get("contact.form.fullname"), buffer->name, IM_ARRAYSIZE(buffer->name), (char**)autocomplete_strings, autocomplete_count, has_error); if (autocomplete_index != -1) { memops::copy(buffer, &autocomplete_list[autocomplete_index], sizeof(contact)); } for (int i = 0; i < autocomplete_count; i++) { memops::unalloc(autocomplete_strings[i]); } } void ContactForm(contact* buffer, bool viewing_only, bool with_autocomplete, bool active_countries_only) { a_err last_err = administration::contact_is_valid(*buffer); ImGui::PushID(buffer); ImGui::Spacing(); ImGui::BeginDisabled(); if (!viewing_only) ImGui::EndDisabled(); if (with_autocomplete) _ContactFormNameAutocomplete(buffer, last_err & A_ERR_MISSING_NAME); else ImGui::InputTextWithErrorHint(locale::get("contact.form.fullname"), buffer->name, IM_ARRAYSIZE(buffer->name), last_err & A_ERR_MISSING_NAME); ImGui::AddressForm(&buffer->address, last_err, active_countries_only); ImGui::ContactTypeDropdown(&buffer->type); // Fields only required for businesses. if (buffer->type == contact_type::CONTACT_BUSINESS) { ImGui::InputTextWithErrorHint(locale::get("contact.form.taxnumber"), buffer->taxid, IM_ARRAYSIZE(buffer->taxid), last_err & A_ERR_MISSING_TAXID); ImGui::InputTextWithErrorHint(locale::get("contact.form.businessnumber"), buffer->businessid, IM_ARRAYSIZE(buffer->businessid), last_err & A_ERR_MISSING_BUSINESSID); } ImGui::InputTextWithErrorHint(locale::get("contact.form.email"), buffer->email, IM_ARRAYSIZE(buffer->email), last_err & A_ERR_MISSING_EMAIL); ImGui::InputTextWithErrorHint(locale::get("contact.form.phonenumber"), buffer->phone_number, IM_ARRAYSIZE(buffer->phone_number), 0); ImGui::InputTextWithErrorHint(locale::get("contact.form.bankaccount"), buffer->bank_account, IM_ARRAYSIZE(buffer->bank_account), 0); if (viewing_only) ImGui::EndDisabled(); ImGui::PopID(); } void CostCenterDropdown(char* costcenter_id) { u32 costcenter_count = administration::cost_center_count(); cost_center* buffer = (cost_center*) memops::alloc(sizeof(cost_center) * costcenter_count); cost_center* selected_costcenter = NULL; costcenter_count = administration::cost_center_get_all(buffer); // Select cost center by given id. if (!strops::empty(costcenter_id)) { for (u32 i = 0; i < costcenter_count; i++) { if (strops::equals(buffer[i].id, costcenter_id)) { selected_costcenter = &buffer[i]; break; } } } int selected_costcenter_index = -1; if (ImGui::BeginCombo(locale::get("invoice.form.costcenter"), selected_costcenter == NULL ? NULL : locale::get(selected_costcenter->description))) { for (u32 n = 0; n < costcenter_count; n++) { bool is_selected = selected_costcenter && strops::equals(selected_costcenter->id, buffer[n].id); if (ImGui::Selectable(locale::get(buffer[n].description), is_selected)) { selected_costcenter_index = n; } } ImGui::EndCombo(); } if (selected_costcenter_index != -1) { strops::copy(costcenter_id, buffer[selected_costcenter_index].id, MAX_LEN_ID); } memops::unalloc(buffer); } void ProjectDropdown(char* project_id) { u32 project_count = administration::project_count(); project* buffer = (project*) memops::alloc(sizeof(project) * project_count); project* selected_project = NULL; project_count = administration::project_get_all(buffer); // Select project by given id. if (!strops::empty(project_id)) { for (u32 i = 0; i < project_count; i++) { if (strops::equals(buffer[i].id, project_id)) { selected_project = &buffer[i]; break; } } } int selected_project_index = -1; if (ImGui::BeginCombo(locale::get("invoice.form.project"), selected_project == NULL ? NULL : selected_project->description)) { for (u32 n = 0; n < project_count; n++) { bool is_selected = selected_project && strops::equals(selected_project->id, buffer[n].id); if (ImGui::Selectable(buffer[n].description, is_selected)) { selected_project_index = n; } } ImGui::EndCombo(); } if (selected_project_index != -1) { strops::copy(project_id, buffer[selected_project_index].id, MAX_LEN_ID); } memops::unalloc(buffer); } void TaxRateDropdown(char* tax_internal_code, bool outgoing, bool has_error) { u32 tax_rate_count = administration::tax_rate_count(); tax_rate* buffer = (tax_rate*) memops::alloc(sizeof(tax_rate) * tax_rate_count); tax_rate* selected_tax_rate = NULL; tax_rate_count = administration::tax_rate_get_all(buffer, outgoing ? tax_rate_type::TAX_RATE_OUTGOING_INVOICE : tax_rate_type::TAX_RATE_INCOMMING_INVOICE); // Select tax rate by given id. if (!strops::empty(tax_internal_code)) { for (u32 i = 0; i < tax_rate_count; i++) { if (strops::equals(buffer[i].internal_code, tax_internal_code)) { selected_tax_rate = &buffer[i]; break; } } } int selected_tax_rate_index = -1; char rate_str_buf[MAX_LEN_LONG_DESC] = {0}; if (selected_tax_rate) { char category_code_desc[MAX_LEN_LONG_DESC]; strops::format(category_code_desc, MAX_LEN_LONG_DESC, "taxrate.code.%s", selected_tax_rate->internal_code); strops::format(rate_str_buf, MAX_LEN_LONG_DESC, "%s", locale::get(category_code_desc)); } if (has_error) { ImGui::PushStyleColor(ImGuiCol_Border, config::colors::COLOR_ERROR_OUTLINE); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5f); } if (ImGui::BeginCombo("##Tax Bracket", rate_str_buf)) { for (u32 n = 0; n < tax_rate_count; n++) { bool is_selected = selected_tax_rate && strops::equals(selected_tax_rate->internal_code, buffer[n].internal_code); char category_code_desc[MAX_LEN_LONG_DESC]; strops::format(category_code_desc, MAX_LEN_LONG_DESC, "taxrate.code.%s", buffer[n].internal_code); strops::format(rate_str_buf, MAX_LEN_LONG_DESC, "%s", locale::get(category_code_desc)); if (ImGui::Selectable(rate_str_buf, is_selected)) { selected_tax_rate_index = n; } } ImGui::EndCombo(); } if (has_error) { ImGui::PopStyleVar(); ImGui::PopStyleColor(); } if (selected_tax_rate_index != -1) { strops::copy(tax_internal_code, buffer[selected_tax_rate_index].internal_code, MAX_LEN_SHORT_DESC); } memops::unalloc(buffer); } bool CurrencyDropdown(char* currency) { int currentCurrency = 0; bool result = false; // Top 15 most traded currencies + all EU official currencies const char* currencies[] = { // Top 15 "EUR", "USD", "JPY", "GBP", "AUD", "CAD", "CHF", "CNY", "HKD", "NZD", "SEK", "KRW", "SGD", "NOK", "MXN", // Additional EU currencies "BGN", // Bulgarian Lev "CZK", // Czech Koruna "DKK", // Danish Krone "HUF", // Hungarian Forint "PLN", // Polish Zloty "RON", // Romanian Leu // "HRK", // Croatian Kuna (legacy, replaced by EUR in 2023) }; int currency_count = sizeof(currencies) / sizeof(char*); if (!strops::empty(currency)) { for (int i = 0; i < currency_count; i++) { if (strops::equals(currencies[i], currency)) { currentCurrency = i; break; } } } ImGui::SetNextItemWidth(100.0f); if (ImGui::BeginCombo("##currency", currencies[currentCurrency])) { for (int n = 0; n < IM_ARRAYSIZE(currencies); n++) { bool isSelected = (currentCurrency == n); if (ImGui::Selectable(currencies[n], isSelected)) { result = true; strops::copy(currency, currencies[n], MAX_LEN_CURRENCY); } if (isSelected) ImGui::SetItemDefaultFocus(); } ImGui::EndCombo(); } return result; } void ToggleDropdown(bool *buffer, const char* option1, const char* option2) { const char* items[] = { option1, option2 }; char ID[MAX_LEN_LONG_DESC]; strops::format(ID, MAX_LEN_LONG_DESC, "Mode##%p", buffer); if (ImGui::BeginCombo(ID, items[*buffer])) { for (int n = 0; n < 2; n++) { bool is_selected = (n == (int)*buffer); if (ImGui::Selectable(items[n], is_selected)) { *buffer = n; } if (is_selected) { ImGui::SetItemDefaultFocus(); } } ImGui::EndCombo(); } } bool WarningIcon(float radius) { ImGui::SameLine(); ImVec2 cursor_pos = ImGui::GetCursorScreenPos(); ImVec2 center = ImVec2(cursor_pos.x + radius, cursor_pos.y + radius + 4); ImGui::PushID((int)center.y); ImDrawList* draw_list = ImGui::GetWindowDrawList(); // Draw red circle draw_list->AddCircleFilled(center, radius, IM_COL32(255, 0, 0, 255)); // Draw exclamation mark float line_height = radius * 0.9f; float line_thickness = radius * 0.3f; float dot_radius = radius * 0.2f; ImVec2 line_start(center.x - line_thickness/3.0f, center.y - line_height * 0.7f); ImVec2 line_end(center.x - line_thickness/3.0f, center.y + line_height * 0.1f); // Vertical bar draw_list->AddLine(line_start, line_end, IM_COL32(255, 255, 255, 255), line_thickness); // Bottom dot draw_list->AddCircleFilled(ImVec2(center.x - dot_radius/3.0f, center.y + line_height * 0.55f), dot_radius, IM_COL32(255, 255, 255, 255)); ImGui::SetCursorScreenPos(ImVec2(center.x - radius, center.y - radius)); ImGui::InvisibleButton("##warning_icon", ImVec2(radius * 2, radius * 2)); ImGui::PopID(); if (ImGui::IsItemHovered()) { return true; } return false; } void DeliveryInfoForm(delivery_info* buffer, bool viewing_only) { a_err last_err = administration::addressee_is_valid(*buffer); ImGui::PushID(buffer); ImGui::Spacing(); ImGui::BeginDisabled(); if (!viewing_only) ImGui::EndDisabled(); ImGui::InputTextWithErrorHint(locale::get("contact.form.fullname"), buffer->name, IM_ARRAYSIZE(buffer->name), last_err & A_ERR_MISSING_NAME); ImGui::AddressForm(&buffer->address, last_err); if (viewing_only) ImGui::EndDisabled(); ImGui::PopID(); } void AddressForm(address* buffer, a_err last_err, bool active_countries_only) { ImGui::InputTextWithErrorHint(locale::get("contact.form.address1"), buffer->address1, IM_ARRAYSIZE(buffer->address1), last_err & A_ERR_MISSING_ADDRESS1); ImGui::InputTextWithErrorHint(locale::get("contact.form.address2"), buffer->address2, IM_ARRAYSIZE(buffer->address2), 0); ImGui::InputTextWithErrorHint(locale::get("contact.form.city"), buffer->city, IM_ARRAYSIZE(buffer->city), last_err & A_ERR_MISSING_CITY); ImGui::InputTextWithErrorHint(locale::get("contact.form.postal"), buffer->postal, IM_ARRAYSIZE(buffer->postal), last_err & A_ERR_MISSING_POSTAL); ImGui::InputTextWithErrorHint(locale::get("contact.form.region"), buffer->region, IM_ARRAYSIZE(buffer->region), 0); ImGui::CountryDropdown(buffer->country_code, IM_ARRAYSIZE(buffer->country_code), active_countries_only); } bool BeginExportDropdown(const char* title, const char* text, exporter::export_request* active_request) { static bool is_new_request = false; static bool show_status_change = false; static time_t status_changed_at = 0; static e_err last_err; if (active_request && active_request->status == exporter::status::EXPORT_DONE && is_new_request) { is_new_request = false; show_status_change = true; last_err = active_request->error; status_changed_at = time(NULL); } if (active_request && active_request->status != exporter::status::EXPORT_DONE) { is_new_request = true; ImGui::BeginDisabled(); ImGui::BeginCombo("##sendStatus", NULL, 1 << 20); { if (ImGui::BeginComboPreview()) { ImGui::Text("Sending"); // @Localize ImGui::SameLine(); ImGui::LoadingIndicatorCircleSmall(); ImGui::EndComboPreview(); } } ImGui::EndDisabled(); } else if (show_status_change) { ImGui::BeginDisabled(); ImGui::BeginCombo("##sendStatus", NULL, 1 << 20); { if (ImGui::BeginComboPreview()) { if (last_err == E_ERR_SUCCESS) { DrawSuccessMark(0, false); ImGui::Text("Success"); // @Localize } else { DrawFailureMark(0, false); ImGui::Text("Failed"); // @Localize } ImGui::EndComboPreview(); } } ImGui::EndDisabled(); if (difftime(time(NULL), status_changed_at) > 0.5f) { show_status_change = false; } } else { return ImGui::BeginCombo(title, text); } return false; } bool InvalidCompanyInfoWarning() { if (!administration::company_info_is_valid()) { ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 102, 204, 255)); // blue ImGui::Text(locale::get("ui.invoiceRequirementP1")); ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { ui::set_state(ui::main_state::UI_SETTINGS); } } ImGui::SameLine(); ImGui::Text(locale::get("ui.invoiceRequirementP2")); return true; } return false; } }