#include #include #include "ui.hpp" #include "strops.hpp" #include "config.hpp" #include "locales.hpp" #include "administration.hpp" namespace ImGui { void FormInputTextWithErrorHint(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, 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), "*"); } } void FormCountryCombo(char* buffer, size_t buf_size) { const char* selected_country = 0; float widthAvailable = ImGui::GetContentRegionAvail().x; ImGui::SetNextItemWidth(widthAvailable*0.5f); const char* countries[30]; for (int x = 0; x < country_count; x++) { char locale_str[20]; snprintf(locale_str, 20, "country.%s", country_codes[x]); countries[x] = localize(locale_str); } for (int i = 0; i < country_count; i++) { if (strcmp(country_codes[i], buffer) == 0) { 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, COLOR_ERROR_OUTLINE); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5f); } if (ImGui::BeginCombo(localize("contact.form.country"), selected_country)) { for (int n = 0; n < country_count; 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_codes[selected_country_index], buf_size); } } void FormContactTypeCombo(contact_type* type) { float widthAvailable = ImGui::GetContentRegionAvail().x; ImGui::SetNextItemWidth(widthAvailable*0.5f); const char* customer_types[2] = { localize("contact.form.type.business"), localize("contact.form.type.consumer") }; int currentItem = static_cast(*type); if (ImGui::Combo(localize("contact.form.type"), ¤tItem, customer_types, IM_ARRAYSIZE(customer_types))) { *type = static_cast(currentItem); } } int TextInputWithAutocomplete(const char* hint, char* buffer, size_t buf_size, char* suggestions[], int suggestion_count, bool has_error) { int result = -1; static bool is_open = false; if (has_error) { ImGui::PushStyleColor(ImGuiCol_Border, 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(); } } return result; } void FormContactAutocomplete(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*)malloc(200); snprintf(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::TextInputWithAutocomplete(localize("contact.form.fullname"), buffer->name, IM_ARRAYSIZE(buffer->name), (char**)autocomplete_strings, autocomplete_count, has_error); if (autocomplete_index != -1) { memcpy(buffer, &autocomplete_list[autocomplete_index], sizeof(contact)); } for (int i = 0; i < autocomplete_count; i++) { free(autocomplete_strings[i]); } } void FormCostCenterCombo(char* costcenter_id) { u32 costcenter_count = administration_cost_center_count(); cost_center* buffer = (cost_center*) malloc(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 (strlen(costcenter_id) > 0) { for (u32 i = 0; i < costcenter_count; i++) { if (strcmp(buffer[i].id, costcenter_id) == 0) { selected_costcenter = &buffer[i]; break; } } } int selected_costcenter_index = -1; if (ImGui::BeginCombo(localize("invoice.form.costcenter"), selected_costcenter == NULL ? NULL : localize(selected_costcenter->description))) { for (u32 n = 0; n < costcenter_count; n++) { bool is_selected = selected_costcenter && strcmp(selected_costcenter->id, buffer[n].id) == 0; if (ImGui::Selectable(localize(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); } free(buffer); } void FormProjectCombo(char* project_id) { u32 project_count = administration_project_count(); project* buffer = (project*) malloc(sizeof(project) * project_count); project* selected_project = NULL; project_count = administration_project_get_all(buffer); // Select project by given id. if (strlen(project_id) > 0) { for (u32 i = 0; i < project_count; i++) { if (strcmp(buffer[i].id, project_id) == 0) { selected_project = &buffer[i]; break; } } } int selected_project_index = -1; if (ImGui::BeginCombo(localize("invoice.form.project"), selected_project == NULL ? NULL : selected_project->description)) { for (u32 n = 0; n < project_count; n++) { bool is_selected = selected_project && strcmp(selected_project->id, buffer[n].id) == 0; 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); } free(buffer); } void FormTaxRateCombo(char* tax_rate_id, char* orig_country, char* dest_country) { u32 tax_rate_count = administration_tax_rate_count(); tax_rate* buffer = (tax_rate*) malloc(sizeof(tax_rate) * tax_rate_count); tax_rate* selected_tax_rate = NULL; char* tax_country_codes[2] = {orig_country, dest_country}; tax_rate_count = administration_tax_rate_get_by_country(buffer, strcmp(orig_country, dest_country) == 0 ? 1 : 2, tax_country_codes); // Select tax rate by given id. if (strlen(tax_rate_id) > 0) { for (u32 i = 0; i < tax_rate_count; i++) { if (strcmp(buffer[i].id, tax_rate_id) == 0) { selected_tax_rate = &buffer[i]; break; } } } int selected_tax_rate_index = -1; char rate_str_buf[40]; rate_str_buf[0] = 0; if (selected_tax_rate) { if (strcmp(selected_tax_rate->country_code, "00") == 0) { char category_code_desc[MAX_LEN_LONG_DESC]; snprintf(category_code_desc, MAX_LEN_LONG_DESC, "taxcategory.%s", selected_tax_rate->category_code); snprintf(rate_str_buf, 40, "%s", localize(category_code_desc)); } else snprintf(rate_str_buf, 40, "%s/%.1f%%", selected_tax_rate->country_code, selected_tax_rate->rate); } if (ImGui::BeginCombo("##Tax Bracket", rate_str_buf)) { for (u32 n = 0; n < tax_rate_count; n++) { bool is_selected = selected_tax_rate && strcmp(selected_tax_rate->id, buffer[n].id) == 0; if (strcmp(buffer[n].country_code, "00") == 0) { char category_code_desc[MAX_LEN_LONG_DESC]; snprintf(category_code_desc, MAX_LEN_LONG_DESC, "taxcategory.%s", buffer[n].category_code); snprintf(rate_str_buf, 40, "%s", localize(category_code_desc)); } else snprintf(rate_str_buf, 40, "%s/%.1f%%", buffer[n].country_code, buffer[n].rate); if (ImGui::Selectable(rate_str_buf, is_selected)) { selected_tax_rate_index = n; } } ImGui::EndCombo(); } if (selected_tax_rate_index != -1) { strops_copy(tax_rate_id, buffer[selected_tax_rate_index].id, MAX_LEN_ID); } free(buffer); } bool FormCurrencyCombo(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 (strlen(currency) > 0) { for (int i = 0; i < currency_count; i++) { if (strcmp(currencies[i], currency) == 0) { 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 FormToggleCombo(bool *buffer, char* option1, char* option2) { const char* items[] = { option1, option2 }; if (ImGui::BeginCombo("Mode", 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(); } } }