Files
fn_registry/cpp/functions/viz/data_table_chips.cpp
T
egutierrez 7eb7b3d0c8 chore: snapshot WIP previo + flow 0008 + 7 sub-issues (0112-0119)
Snapshot de WIP acumulado de sesiones previas antes de merge wave 1
del flow 0008 (kanban_cpp + agent_runner_api + DoD schema).

Incluye:
- dev/flows/0008-kanban-cpp-and-agent-workflows.md
- dev/issues/0112-0119*.md (7 sub-issues)
- WIP previo en cmd/fn/doctor.go, registry/*, modules/, cpp/, etc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:17:08 +02:00

1158 lines
49 KiB
C++

// data_table_chips — barra de chips superior de la tabla TQL.
// Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c).
//
// Contiene: draw_joins_chips, draw_filter_chips, draw_breakout_chips,
// draw_aggregation_chips, draw_sort_chips, apply_header_sort_click,
// draw_edit_*_popup, draw_add_*_popup, draw_header_menu, draw_tql_bar.
#include "viz/data_table_chips.h"
#include "data_table/data_table_internal.h" // UiState, ui(), helpers inline
#include "core/tql_emit.h"
#include "core/tql_apply.h"
#include "core/tql_helpers.h" // parse_breakout_granularity, date_granularity_token
#include "viz/data_table_color_rules.h" // draw_color_rule_menu
#include "imgui.h"
#include "app_base.h" // fn::local_path
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <fstream>
#include <string>
#include <vector>
namespace data_table {
// ---------------------------------------------------------------------------
// Static helpers (solo usados en este TU)
// ---------------------------------------------------------------------------
// column_type_icon: returns a Tabler icon UTF-8 sequence for each ColumnType.
// (duplicado de data_table.cpp — lo dejamos static aqui para no depender del
// entrypoint; si se necesita compartir mover a data_table_internal.h inline.)
static const char* column_type_icon_chips(ColumnType t) {
switch (t) {
case ColumnType::Auto: return "\xef\xa4\x9d"; // TI_HELP_CIRCLE
case ColumnType::String: return "\xef\x95\xa7"; // TI_ABC
case ColumnType::Int: return "\xef\x95\x94"; // TI_123
case ColumnType::Float: return "\xef\xa8\xa6"; // TI_DECIMAL
case ColumnType::Bool: return "\xee\xae\xa6"; // TI_CHECKBOX
case ColumnType::Date: return "\xee\xa9\x93"; // TI_CALENDAR
case ColumnType::Json: return "\xee\xaf\x8c"; // TI_BRACES
}
return "?";
}
// agg_fn_label_chips: nombre del fn de agregacion.
static const char* agg_fn_label_chips(AggFn f) {
switch (f) {
case AggFn::Count: return "count";
case AggFn::Sum: return "sum";
case AggFn::Avg: return "avg";
case AggFn::Min: return "min";
case AggFn::Max: return "max";
case AggFn::Distinct: return "distinct";
case AggFn::Stddev: return "stddev";
case AggFn::Median: return "median";
case AggFn::P25: return "p25";
case AggFn::P75: return "p75";
case AggFn::P90: return "p90";
case AggFn::P99: return "p99";
case AggFn::Percentile: return "percentile";
}
return "?";
}
// date helpers (solo para draw_add_breakout_popup)
namespace {
static bool parse_ymd_chips(const std::string& s, int& y, int& m, int& d) {
if (s.size() < 10) return false;
for (int i : {0,1,2,3,5,6,8,9}) {
if (s[(size_t)i] < '0' || s[(size_t)i] > '9') return false;
}
if (s[4] != '-' || s[7] != '-') return false;
y = (s[0]-'0')*1000 + (s[1]-'0')*100 + (s[2]-'0')*10 + (s[3]-'0');
m = (s[5]-'0')*10 + (s[6]-'0');
d = (s[8]-'0')*10 + (s[9]-'0');
if (m < 1 || m > 12 || d < 1 || d > 31) return false;
return true;
}
static long ymd_to_days_chips(int y, int m, int d) {
if (m <= 2) { y -= 1; m += 12; }
long era = (y >= 0 ? y : y - 399) / 400;
unsigned yoe = (unsigned)(y - era * 400);
unsigned doy = (unsigned)((153 * (m - 3) + 2) / 5 + d - 1);
unsigned doe = yoe * 365 + yoe/4 - yoe/100 + doy;
return era * 146097 + (long)doe;
}
static DateGranularity auto_date_gran_chips(const std::string& min_ymd,
const std::string& max_ymd) {
int y1,m1,d1, y2,m2,d2;
if (!parse_ymd_chips(min_ymd, y1,m1,d1)) return DateGranularity::Day;
if (!parse_ymd_chips(max_ymd, y2,m2,d2)) return DateGranularity::Day;
long span = ymd_to_days_chips(y2,m2,d2) - ymd_to_days_chips(y1,m1,d1);
if (span < 0) span = -span;
if (span > 730) return DateGranularity::Year;
if (span > 60) return DateGranularity::Month;
if (span > 14) return DateGranularity::Week;
return DateGranularity::Day;
}
static void column_min_max_chips(const char* const* cells, int rows, int cols, int col_idx,
std::string& min_out, std::string& max_out) {
min_out.clear(); max_out.clear();
if (col_idx < 0 || col_idx >= cols) return;
bool first = true;
for (int r = 0; r < rows; ++r) {
const char* v = cells[r * cols + col_idx];
if (!v || !*v) continue;
std::string s(v);
if (first) { min_out = s; max_out = s; first = false; }
else { if (s < min_out) min_out = s; if (s > max_out) max_out = s; }
}
}
} // anon
// compose_breakout helper (local)
static std::string compose_breakout_chips(const std::string& col, DateGranularity g) {
if (g == DateGranularity::None) return col;
return col + ":" + date_granularity_token(g);
}
// ---------------------------------------------------------------------------
// draw_typed_ops
// ---------------------------------------------------------------------------
bool draw_typed_ops(ColumnType t, Op& out) {
auto ops = ops_for_type(t);
for (size_t i = 0; i < ops.size(); ++i) {
if (i % 5 != 0) ImGui::SameLine();
if (ImGui::SmallButton(op_label(ops[i]))) { out = ops[i]; return true; }
}
return false;
}
// ---------------------------------------------------------------------------
// type_supports_range
// ---------------------------------------------------------------------------
bool type_supports_range(ColumnType t) {
return t == ColumnType::Int || t == ColumnType::Float || t == ColumnType::Date;
}
// ---------------------------------------------------------------------------
// apply_header_sort_click
// ---------------------------------------------------------------------------
void apply_header_sort_click(Stage& stg, const std::string& col_name, bool shift) {
if (shift) {
stg.sorts.clear();
stg.sorts.push_back({col_name, false});
return;
}
int idx = -1;
for (size_t i = 0; i < stg.sorts.size(); ++i) {
if (stg.sorts[i].col == col_name) { idx = (int)i; break; }
}
if (idx < 0) {
stg.sorts.push_back({col_name, false});
} else {
if (!stg.sorts[idx].desc) stg.sorts[idx].desc = true;
else stg.sorts.erase(stg.sorts.begin() + idx);
}
}
// ---------------------------------------------------------------------------
// draw_sort_chips
// ---------------------------------------------------------------------------
void draw_sort_chips(Stage& stg) {
auto& U = ui();
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(220, 130, 50, 230));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(240, 155, 75, 245));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32(180, 100, 30, 240));
if (ImGui::SmallButton("+##addsort_btn")) ImGui::OpenPopup("##addsort");
ImGui::PopStyleColor(3);
ImGui::SameLine();
if (stg.sorts.empty()) {
ImGui::TextDisabled("Sort: ninguno.");
return;
}
int erase_idx = -1;
int drag_src = -1;
int drag_dst = -1;
for (size_t i = 0; i < stg.sorts.size(); ++i) {
const auto& sc = stg.sorts[i];
char buf[256];
std::snprintf(buf, sizeof(buf), "%zu. %s %s x##srt%zu",
i + 1, sc.col.c_str(), sc.desc ? "desc" : "asc", i);
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(220, 130, 50, 230));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(240, 155, 75, 245));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32(180, 100, 30, 240));
bool clicked = ImGui::SmallButton(buf);
ImGui::PopStyleColor(3);
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) {
int idx = (int)i;
ImGui::SetDragDropPayload("##sortreorder", &idx, sizeof(int));
ImGui::Text("Move sort #%zu", i + 1);
ImGui::EndDragDropSource();
}
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* p = ImGui::AcceptDragDropPayload("##sortreorder")) {
drag_src = *(const int*)p->Data;
drag_dst = (int)i;
}
ImGui::EndDragDropTarget();
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
U.edit_chip_kind = 4;
U.edit_chip_idx = (int)i;
U.edit_value = sc.col;
U.edit_sort_desc = sc.desc;
ImGui::OpenPopup("##edit_sort");
}
if (clicked) erase_idx = (int)i;
ImGui::SameLine();
}
ImGui::NewLine();
if (drag_src >= 0 && drag_dst >= 0 && drag_src != drag_dst &&
drag_src < (int)stg.sorts.size() && drag_dst < (int)stg.sorts.size())
{
SortClause moved = std::move(stg.sorts[drag_src]);
stg.sorts.erase(stg.sorts.begin() + drag_src);
int insert_at = drag_dst;
if (insert_at > (int)stg.sorts.size()) insert_at = (int)stg.sorts.size();
stg.sorts.insert(stg.sorts.begin() + insert_at, std::move(moved));
} else if (erase_idx >= 0 && erase_idx < (int)stg.sorts.size()) {
stg.sorts.erase(stg.sorts.begin() + erase_idx);
}
}
// ---------------------------------------------------------------------------
// draw_add_sort_popup
// ---------------------------------------------------------------------------
void draw_add_sort_popup(Stage& stg, const char* const* in_headers, int in_cols,
const std::vector<ColumnType>& in_types) {
auto& U = ui();
if (!ImGui::BeginPopup("##addsort")) return;
if (U.sort_picker_col < 0 || U.sort_picker_col >= in_cols) U.sort_picker_col = 0;
ImGui::SetNextItemWidth(220);
if (ImGui::BeginCombo("col##sortcol", in_headers[U.sort_picker_col])) {
for (int c = 0; c < in_cols; ++c) {
char it[160];
std::snprintf(it, sizeof(it), "%s %s",
column_type_icon_chips(in_types[c]), in_headers[c]);
bool sel = (U.sort_picker_col == c);
if (ImGui::Selectable(it, sel)) U.sort_picker_col = c;
}
ImGui::EndCombo();
}
ImGui::Checkbox("desc", &U.sort_picker_desc);
if (ImGui::Button("Add##srt")) {
SortClause sc;
sc.col = in_headers[U.sort_picker_col];
sc.desc = U.sort_picker_desc;
stg.sorts.push_back(sc);
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
// ---------------------------------------------------------------------------
// draw_edit_sort_popup
// ---------------------------------------------------------------------------
void draw_edit_sort_popup(Stage& stg, const char* const* headers, int n_cols) {
auto& U = ui();
if (!ImGui::BeginPopup("##edit_sort")) return;
if (U.edit_chip_idx >= 0 && U.edit_chip_idx < (int)stg.sorts.size()) {
ImGui::SetNextItemWidth(240);
if (ImGui::BeginCombo("col", U.edit_value.c_str())) {
for (int c = 0; c < n_cols; ++c) {
bool sel = (U.edit_value == headers[c]);
if (ImGui::Selectable(headers[c], sel)) U.edit_value = headers[c];
}
ImGui::EndCombo();
}
if (ImGui::RadioButton("asc", !U.edit_sort_desc)) U.edit_sort_desc = false;
ImGui::SameLine();
if (ImGui::RadioButton("desc", U.edit_sort_desc)) U.edit_sort_desc = true;
if (ImGui::Button("Save")) {
auto& sc = stg.sorts[U.edit_chip_idx];
sc.col = U.edit_value;
sc.desc = U.edit_sort_desc;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
// ---------------------------------------------------------------------------
// draw_filter_chips
// ---------------------------------------------------------------------------
void draw_filter_chips(Stage& stg, const char* const* eff_headers, int eff_cols,
const std::vector<ColumnType>& eff_types) {
auto& U = ui();
// Preset filter helpers needed locally
auto today_iso_chips = []() -> std::string {
std::time_t t = std::time(nullptr);
std::tm tm = *std::gmtime(&t);
char buf[16];
std::snprintf(buf, sizeof(buf), "%04d-%02d-%02d",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
return buf;
};
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(120, 60, 170, 220));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(150, 85, 200, 240));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32( 95, 45, 140, 240));
if (ImGui::SmallButton("+##addfilter_btn")) ImGui::OpenPopup("##addfilter");
ImGui::PopStyleColor(3);
ImGui::SameLine();
// Presets menu
if (ImGui::SmallButton("Presets##fpresets")) ImGui::OpenPopup("##presets_menu");
if (ImGui::BeginPopup("##presets_menu")) {
int first_date = -1, first_num = -1;
for (int c = 0; c < eff_cols && c < (int)eff_types.size(); ++c) {
if (first_date < 0 && eff_types[c] == ColumnType::Date) first_date = c;
if (first_num < 0 && (eff_types[c] == ColumnType::Int ||
eff_types[c] == ColumnType::Float)) first_num = c;
}
// build_preset_filters lambda
auto build_pf = [&](FilterPreset preset, int col) -> std::vector<Filter> {
std::vector<Filter> out;
std::string today = today_iso_chips();
auto last_n = [&](int n) {
int y, m, d;
if (!parse_ymd_chips(today, y, m, d)) return;
long days = ymd_to_days_chips(y, m, d) - n;
// reverse
long era = (days >= 0 ? days : days - 146096) / 146097;
unsigned doe = (unsigned)(days - era * 146097);
unsigned yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;
int yr = (int)yoe + (int)era * 400;
unsigned doy2 = doe - (365*yoe + yoe/4 - yoe/100);
unsigned mp = (5*doy2 + 2)/153;
unsigned day = doy2 - (153*mp + 2)/5 + 1;
unsigned mon = mp < 10 ? mp + 3 : mp - 9;
if (mon <= 2) yr += 1;
char buf[16];
std::snprintf(buf, sizeof(buf), "%04d-%02d-%02d", yr, (int)mon, (int)day);
Filter f; f.col = col; f.op = Op::Gte; f.value = buf;
out.push_back(f);
};
switch (preset) {
case FilterPreset::Last7d: last_n(7); break;
case FilterPreset::Last30d: last_n(30); break;
case FilterPreset::Last90d: last_n(90); break;
case FilterPreset::ExcludeNulls: {
Filter f; f.col = col; f.op = Op::Neq; f.value = "";
out.push_back(f); break;
}
case FilterPreset::NonZero: {
Filter f1; f1.col = col; f1.op = Op::Neq; f1.value = "";
Filter f2; f2.col = col; f2.op = Op::Neq; f2.value = "0";
out.push_back(f1); out.push_back(f2); break;
}
}
return out;
};
auto apply_preset = [&](FilterPreset p, int col) {
auto fs = build_pf(p, col);
for (auto& f : fs) stg.filters.push_back(f);
};
if (first_date >= 0) {
char l1[96], l2[96], l3[96];
std::snprintf(l1, sizeof(l1), "Last 7 days on \"%s\"", eff_headers[first_date]);
std::snprintf(l2, sizeof(l2), "Last 30 days on \"%s\"", eff_headers[first_date]);
std::snprintf(l3, sizeof(l3), "Last 90 days on \"%s\"", eff_headers[first_date]);
if (ImGui::MenuItem(l1)) apply_preset(FilterPreset::Last7d, first_date);
if (ImGui::MenuItem(l2)) apply_preset(FilterPreset::Last30d, first_date);
if (ImGui::MenuItem(l3)) apply_preset(FilterPreset::Last90d, first_date);
ImGui::Separator();
}
if (ImGui::BeginMenu("Exclude nulls in...")) {
for (int c = 0; c < eff_cols; ++c) {
if (ImGui::MenuItem(eff_headers[c])) apply_preset(FilterPreset::ExcludeNulls, c);
}
ImGui::EndMenu();
}
if (first_num >= 0) {
if (ImGui::BeginMenu("Non-zero in...")) {
for (int c = 0; c < eff_cols && c < (int)eff_types.size(); ++c) {
if (eff_types[c] == ColumnType::Int || eff_types[c] == ColumnType::Float) {
if (ImGui::MenuItem(eff_headers[c])) apply_preset(FilterPreset::NonZero, c);
}
}
ImGui::EndMenu();
}
}
ImGui::EndPopup();
}
ImGui::SameLine();
if (stg.filters.empty()) {
ImGui::TextDisabled("Sin filtros.");
return;
}
for (size_t i = 0; i < stg.filters.size(); ) {
const auto& f = stg.filters[i];
const char* hdr = (f.col >= 0 && f.col < eff_cols) ? eff_headers[f.col] : "?";
char buf[256];
std::snprintf(buf, sizeof(buf), "%s %s %s x##chip%zu",
hdr, op_label(f.op), f.value.c_str(), i);
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(120, 60, 170, 220));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(150, 85, 200, 240));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32( 95, 45, 140, 240));
bool clicked = ImGui::SmallButton(buf);
ImGui::PopStyleColor(3);
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
U.edit_chip_kind = 1;
U.edit_chip_idx = (int)i;
U.edit_col_idx = f.col;
U.edit_op = (int)f.op;
U.edit_value = f.value;
ImGui::OpenPopup("##edit_filter");
}
if (clicked) { stg.filters.erase(stg.filters.begin() + i); continue; }
ImGui::SameLine();
++i;
}
ImGui::NewLine();
}
// ---------------------------------------------------------------------------
// draw_add_filter_popup
// ---------------------------------------------------------------------------
void draw_add_filter_popup(Stage& stg, const char* const* eff_headers_arr, int eff_cols,
const std::vector<ColumnType>& eff_types) {
auto& U = ui();
if (!ImGui::BeginPopup("##addfilter")) return;
if (U.addf_col < 0 || U.addf_col >= eff_cols) U.addf_col = 0;
ImGui::SetNextItemWidth(220);
if (ImGui::BeginCombo("col", eff_headers_arr[U.addf_col])) {
for (int c = 0; c < eff_cols; ++c) {
char it[160];
std::snprintf(it, sizeof(it), "%s %s",
column_type_icon_chips(eff_types[c]), eff_headers_arr[c]);
bool sel = (U.addf_col == c);
if (ImGui::Selectable(it, sel)) U.addf_col = c;
}
ImGui::EndCombo();
}
ColumnType t = eff_types[U.addf_col];
ImGui::TextDisabled("type: %s %s", column_type_icon_chips(t), column_type_name(t));
bool can_range = type_supports_range(t);
if (can_range) ImGui::Checkbox("Range (min/max)", &U.addf_range);
else U.addf_range = false;
if (!U.addf_range) {
char buf[256] = {0};
std::snprintf(buf, sizeof(buf), "%s", U.addf_val.c_str());
ImGui::SetNextItemWidth(220);
if (ImGui::InputText("val", buf, sizeof(buf))) U.addf_val = buf;
Op picked;
if (draw_typed_ops(t, picked)) {
stg.filters.push_back({U.addf_col, picked, U.addf_val});
U.addf_val.clear();
ImGui::CloseCurrentPopup();
}
} else {
char lo[128] = {0}, hi[128] = {0};
std::snprintf(lo, sizeof(lo), "%s", U.addf_lo.c_str());
std::snprintf(hi, sizeof(hi), "%s", U.addf_hi.c_str());
ImGui::SetNextItemWidth(100);
if (ImGui::InputText("min", lo, sizeof(lo))) U.addf_lo = lo;
ImGui::SameLine();
ImGui::SetNextItemWidth(100);
if (ImGui::InputText("max", hi, sizeof(hi))) U.addf_hi = hi;
ImGui::SameLine();
if (ImGui::SmallButton("Add range")) {
if (!U.addf_lo.empty()) stg.filters.push_back({U.addf_col, Op::Gte, U.addf_lo});
if (!U.addf_hi.empty()) stg.filters.push_back({U.addf_col, Op::Lte, U.addf_hi});
U.addf_lo.clear(); U.addf_hi.clear();
ImGui::CloseCurrentPopup();
}
}
ImGui::EndPopup();
}
// ---------------------------------------------------------------------------
// draw_edit_filter_popup
// ---------------------------------------------------------------------------
void draw_edit_filter_popup(Stage& stg, const char* const* headers, int n_cols,
const std::vector<ColumnType>& types) {
auto& U = ui();
if (!ImGui::BeginPopup("##edit_filter")) return;
if (U.edit_chip_idx >= 0 && U.edit_chip_idx < (int)stg.filters.size()) {
auto& f = stg.filters[U.edit_chip_idx];
ImGui::SetNextItemWidth(200);
const char* cur = (U.edit_col_idx >= 0 && U.edit_col_idx < n_cols)
? headers[U.edit_col_idx] : "?";
if (ImGui::BeginCombo("col", cur)) {
for (int c = 0; c < n_cols; ++c) {
bool sel = (U.edit_col_idx == c);
if (ImGui::Selectable(headers[c], sel)) U.edit_col_idx = c;
}
ImGui::EndCombo();
}
ColumnType t = (U.edit_col_idx >= 0 && U.edit_col_idx < (int)types.size())
? types[U.edit_col_idx] : ColumnType::String;
auto ops = ops_for_type(t);
ImGui::SetNextItemWidth(140);
if (ImGui::BeginCombo("op", op_label((Op)U.edit_op))) {
for (auto o : ops) {
bool sel = ((int)o == U.edit_op);
if (ImGui::Selectable(op_label(o), sel)) U.edit_op = (int)o;
}
ImGui::EndCombo();
}
char vbuf[256] = {0};
std::snprintf(vbuf, sizeof(vbuf), "%s", U.edit_value.c_str());
ImGui::SetNextItemWidth(220);
if (ImGui::InputText("value", vbuf, sizeof(vbuf))) U.edit_value = vbuf;
if (ImGui::Button("Save")) {
f.col = U.edit_col_idx;
f.op = (Op)U.edit_op;
f.value = U.edit_value;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
// ---------------------------------------------------------------------------
// draw_breakout_chips
// ---------------------------------------------------------------------------
void draw_breakout_chips(Stage& stg, const char* const* in_headers, int in_cols,
const std::vector<ColumnType>& in_types) {
auto& U = ui();
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32( 60, 160, 170, 220));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32( 80, 190, 200, 240));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32( 40, 130, 140, 240));
if (ImGui::SmallButton("+##addbreakout_btn")) ImGui::OpenPopup("##addbreakout");
ImGui::PopStyleColor(3);
ImGui::SameLine();
if (stg.breakouts.empty()) {
ImGui::TextDisabled("Group by: ninguna col.");
return;
}
for (size_t i = 0; i < stg.breakouts.size(); ) {
std::string col_name;
DateGranularity g = parse_breakout_granularity(stg.breakouts[i], col_name);
int col_idx = -1;
for (int c = 0; c < in_cols; ++c) {
if (std::strcmp(in_headers[c], col_name.c_str()) == 0) { col_idx = c; break; }
}
bool is_date_col = (col_idx >= 0 && col_idx < (int)in_types.size()
&& in_types[col_idx] == ColumnType::Date);
char buf[256];
std::snprintf(buf, sizeof(buf), "%s x##bk%zu", stg.breakouts[i].c_str(), i);
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32( 60, 160, 170, 220));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32( 80, 190, 200, 240));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32( 40, 130, 140, 240));
bool clicked = ImGui::SmallButton(buf);
ImGui::PopStyleColor(3);
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
U.edit_chip_kind = 2;
U.edit_chip_idx = (int)i;
U.edit_col_idx = (col_idx >= 0) ? col_idx : 0;
ImGui::OpenPopup("##edit_breakout");
}
if (clicked) { stg.breakouts.erase(stg.breakouts.begin() + i); continue; }
if (is_date_col) {
ImGui::SameLine();
const char* preview = (g == DateGranularity::None)
? "(raw)" : date_granularity_token(g);
char combo_id[32];
std::snprintf(combo_id, sizeof(combo_id), "##gran%zu", i);
ImGui::SetNextItemWidth(72);
if (ImGui::BeginCombo(combo_id, preview)) {
DateGranularity opts[] = {
DateGranularity::None,
DateGranularity::Year,
DateGranularity::Month,
DateGranularity::Week,
DateGranularity::Day,
DateGranularity::Hour,
};
for (auto o : opts) {
const char* lbl = (o == DateGranularity::None)
? "(raw)" : date_granularity_token(o);
if (ImGui::Selectable(lbl, o == g)) {
stg.breakouts[i] = compose_breakout_chips(col_name, o);
}
}
ImGui::EndCombo();
}
}
ImGui::SameLine();
++i;
}
ImGui::NewLine();
}
// ---------------------------------------------------------------------------
// draw_add_breakout_popup
// ---------------------------------------------------------------------------
void draw_add_breakout_popup(Stage& stg, const char* const* in_headers, int in_cols,
const std::vector<ColumnType>& in_types,
const char* const* cur_cells, int cur_rows) {
auto& U = ui();
if (!ImGui::BeginPopup("##addbreakout")) return;
if (U.brk_picker_col < 0 || U.brk_picker_col >= in_cols) U.brk_picker_col = 0;
ImGui::SetNextItemWidth(220);
if (ImGui::BeginCombo("col##bkcol", in_headers[U.brk_picker_col])) {
for (int c = 0; c < in_cols; ++c) {
char it[160];
std::snprintf(it, sizeof(it), "%s %s",
column_type_icon_chips(in_types[c]), in_headers[c]);
bool sel = (U.brk_picker_col == c);
if (ImGui::Selectable(it, sel)) U.brk_picker_col = c;
}
ImGui::EndCombo();
}
if (ImGui::Button("Add##bk")) {
int c = U.brk_picker_col;
std::string col = in_headers[c];
if (c >= 0 && c < (int)in_types.size() && in_types[c] == ColumnType::Date) {
std::string lo, hi;
column_min_max_chips(cur_cells, cur_rows, in_cols, c, lo, hi);
DateGranularity g = auto_date_gran_chips(lo, hi);
stg.breakouts.emplace_back(compose_breakout_chips(col, g));
} else {
stg.breakouts.emplace_back(col);
}
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
// ---------------------------------------------------------------------------
// draw_edit_breakout_popup
// ---------------------------------------------------------------------------
void draw_edit_breakout_popup(Stage& stg, const char* const* headers, int n_cols) {
auto& U = ui();
if (!ImGui::BeginPopup("##edit_breakout")) return;
if (U.edit_chip_idx >= 0 && U.edit_chip_idx < (int)stg.breakouts.size()) {
ImGui::SetNextItemWidth(240);
const char* cur = (U.edit_col_idx >= 0 && U.edit_col_idx < n_cols)
? headers[U.edit_col_idx] : "?";
if (ImGui::BeginCombo("col", cur)) {
for (int c = 0; c < n_cols; ++c) {
bool sel = (U.edit_col_idx == c);
if (ImGui::Selectable(headers[c], sel)) U.edit_col_idx = c;
}
ImGui::EndCombo();
}
if (ImGui::Button("Save")) {
if (U.edit_col_idx >= 0 && U.edit_col_idx < n_cols)
stg.breakouts[U.edit_chip_idx] = headers[U.edit_col_idx];
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
// ---------------------------------------------------------------------------
// draw_aggregation_chips
// ---------------------------------------------------------------------------
void draw_aggregation_chips(Stage& stg, const char* const* in_headers, int in_cols) {
auto& U = ui();
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32( 40, 140, 60, 220));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32( 60, 170, 85, 240));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32( 30, 110, 45, 240));
if (ImGui::SmallButton("+##addagg_btn")) ImGui::OpenPopup("##addagg");
ImGui::PopStyleColor(3);
ImGui::SameLine();
if (stg.aggregations.empty()) {
ImGui::TextDisabled("Aggregations: ninguna.");
return;
}
for (size_t i = 0; i < stg.aggregations.size(); ) {
const auto& a = stg.aggregations[i];
char buf[256];
if (a.fn == AggFn::Count) {
std::snprintf(buf, sizeof(buf), "count x##ag%zu", i);
} else if (a.fn == AggFn::Percentile) {
std::snprintf(buf, sizeof(buf), "percentile(%s, %g) x##ag%zu",
a.col.c_str(), a.arg, i);
} else {
std::snprintf(buf, sizeof(buf), "%s(%s) x##ag%zu",
agg_fn_label_chips(a.fn), a.col.c_str(), i);
}
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32( 40, 140, 60, 220));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32( 60, 170, 85, 240));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32( 30, 110, 45, 240));
bool clicked = ImGui::SmallButton(buf);
ImGui::PopStyleColor(3);
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
U.edit_chip_kind = 3;
U.edit_chip_idx = (int)i;
U.edit_agg_fn = (int)a.fn;
U.edit_agg_arg = a.arg;
U.edit_col_idx = 0;
for (int c = 0; c < in_cols; ++c) {
if (std::strcmp(in_headers[c], a.col.c_str()) == 0) {
U.edit_col_idx = c; break;
}
}
ImGui::OpenPopup("##edit_agg");
}
if (clicked) { stg.aggregations.erase(stg.aggregations.begin() + i); continue; }
ImGui::SameLine();
++i;
}
(void)in_headers; (void)in_cols;
ImGui::NewLine();
}
// ---------------------------------------------------------------------------
// draw_add_aggregation_popup
// ---------------------------------------------------------------------------
void draw_add_aggregation_popup(Stage& stg, const char* const* in_headers, int in_cols,
const std::vector<ColumnType>& in_types) {
auto& U = ui();
if (!ImGui::BeginPopup("##addagg")) return;
AggFn cur_fn = (AggFn)U.agg_picker_fn;
ImGui::SetNextItemWidth(160);
if (ImGui::BeginCombo("fn##aggfn", agg_fn_label_chips(cur_fn))) {
AggFn all[] = {AggFn::Count, AggFn::Sum, AggFn::Avg, AggFn::Min, AggFn::Max,
AggFn::Distinct, AggFn::Stddev, AggFn::Median,
AggFn::P25, AggFn::P75, AggFn::P90, AggFn::P99,
AggFn::Percentile};
for (AggFn f : all) {
bool sel = (f == cur_fn);
if (ImGui::Selectable(agg_fn_label_chips(f), sel)) U.agg_picker_fn = (int)f;
}
ImGui::EndCombo();
}
if (cur_fn != AggFn::Count) {
if (U.agg_picker_col < 0 || U.agg_picker_col >= in_cols) U.agg_picker_col = 0;
ImGui::SetNextItemWidth(220);
if (ImGui::BeginCombo("col##aggcol", in_headers[U.agg_picker_col])) {
for (int c = 0; c < in_cols; ++c) {
char it[160];
std::snprintf(it, sizeof(it), "%s %s",
column_type_icon_chips(in_types[c]), in_headers[c]);
bool sel = (U.agg_picker_col == c);
if (ImGui::Selectable(it, sel)) U.agg_picker_col = c;
}
ImGui::EndCombo();
}
}
if (cur_fn == AggFn::Percentile) {
double v = U.agg_picker_arg;
ImGui::SetNextItemWidth(120);
if (ImGui::InputDouble("p (0..1)", &v, 0.05, 0.1, "%.2f")) {
if (v < 0) v = 0; if (v > 1) v = 1;
U.agg_picker_arg = v;
}
}
if (ImGui::Button("Add##ag")) {
Aggregation a;
a.fn = cur_fn;
a.col = (cur_fn == AggFn::Count) ? "" : std::string(in_headers[U.agg_picker_col]);
a.arg = (cur_fn == AggFn::Percentile) ? U.agg_picker_arg : 0.0;
stg.aggregations.push_back(a);
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
// ---------------------------------------------------------------------------
// draw_edit_agg_popup
// ---------------------------------------------------------------------------
void draw_edit_agg_popup(Stage& stg, const char* const* headers, int n_cols) {
auto& U = ui();
if (!ImGui::BeginPopup("##edit_agg")) return;
if (U.edit_chip_idx >= 0 && U.edit_chip_idx < (int)stg.aggregations.size()) {
const AggFn all_fns[] = {AggFn::Count, AggFn::Sum, AggFn::Avg, AggFn::Min, AggFn::Max,
AggFn::Distinct, AggFn::Stddev, AggFn::Median,
AggFn::P25, AggFn::P75, AggFn::P90, AggFn::P99,
AggFn::Percentile};
ImGui::SetNextItemWidth(160);
if (ImGui::BeginCombo("fn", agg_fn_label_chips((AggFn)U.edit_agg_fn))) {
for (auto f : all_fns) {
bool sel = ((int)f == U.edit_agg_fn);
if (ImGui::Selectable(agg_fn_label_chips(f), sel)) U.edit_agg_fn = (int)f;
}
ImGui::EndCombo();
}
if ((AggFn)U.edit_agg_fn != AggFn::Count) {
ImGui::SetNextItemWidth(200);
const char* cur = (U.edit_col_idx >= 0 && U.edit_col_idx < n_cols)
? headers[U.edit_col_idx] : "?";
if (ImGui::BeginCombo("col", cur)) {
for (int c = 0; c < n_cols; ++c) {
bool sel = (U.edit_col_idx == c);
if (ImGui::Selectable(headers[c], sel)) U.edit_col_idx = c;
}
ImGui::EndCombo();
}
}
if ((AggFn)U.edit_agg_fn == AggFn::Percentile) {
float v = (float)U.edit_agg_arg;
ImGui::SetNextItemWidth(140);
if (ImGui::InputFloat("p (0..1)", &v, 0.05f, 0.1f, "%.2f"))
U.edit_agg_arg = v;
}
if (ImGui::Button("Save")) {
auto& a = stg.aggregations[U.edit_chip_idx];
a.fn = (AggFn)U.edit_agg_fn;
if (a.fn != AggFn::Count && U.edit_col_idx >= 0 && U.edit_col_idx < n_cols)
a.col = headers[U.edit_col_idx];
else if (a.fn == AggFn::Count) a.col.clear();
a.arg = U.edit_agg_arg;
a.alias.clear();
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
// ---------------------------------------------------------------------------
// draw_joins_chips
// Firma del header: (State&, const std::vector<TableInput>&, const char* const*, int, const std::vector<ColumnType>&)
// ---------------------------------------------------------------------------
void draw_joins_chips(State& st, const std::vector<TableInput>& joinables,
const char* const* eff_headers, int eff_cols,
const std::vector<ColumnType>& /*eff_types*/) {
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32( 80, 130, 90, 220));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(100, 160, 110, 240));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32( 60, 110, 70, 220));
ImGui::TextDisabled("Joins:");
ImGui::SameLine();
int remove_idx = -1;
for (size_t i = 0; i < st.joins.size(); ++i) {
const auto& jn = st.joins[i];
char lbl[256];
std::string on_str;
for (size_t k = 0; k < jn.on.size(); ++k) {
if (k) on_str += ",";
on_str += jn.on[k].first + "=" + jn.on[k].second;
}
std::snprintf(lbl, sizeof(lbl), "%s <- %s on %s (%s)##join_%zu",
jn.alias.empty() ? "_" : jn.alias.c_str(),
jn.source.c_str(),
on_str.c_str(),
join_strategy_label(jn.strategy),
i);
ImGui::Button(lbl);
ImGui::SameLine();
char xlbl[32]; std::snprintf(xlbl, sizeof(xlbl), "x##rm_join_%zu", i);
if (ImGui::SmallButton(xlbl)) remove_idx = (int)i;
ImGui::SameLine();
}
if (remove_idx >= 0) st.joins.erase(st.joins.begin() + remove_idx);
if (ImGui::SmallButton("+##add_join")) {
ImGui::OpenPopup("##add_join_popup");
}
ImGui::PopStyleColor(3);
// Popup add-join. State variables are static (one join popup at a time).
static int pick_source_idx = 0;
static char pick_alias[64] = "";
static int pick_strategy = 0;
static int pick_left_col = 0;
static int pick_right_col = 0;
if (ImGui::BeginPopup("##add_join_popup")) {
ImGui::Text("Add join");
if (pick_source_idx >= (int)joinables.size()) pick_source_idx = 0;
ImGui::SetNextItemWidth(180);
if (ImGui::BeginCombo("source", joinables[pick_source_idx].name.c_str())) {
for (int k = 0; k < (int)joinables.size(); ++k) {
bool sel = (k == pick_source_idx);
if (ImGui::Selectable(joinables[k].name.c_str(), sel)) {
pick_source_idx = k;
pick_right_col = 0;
if (pick_alias[0] == 0)
std::snprintf(pick_alias, sizeof(pick_alias), "%s", joinables[k].name.c_str());
}
}
ImGui::EndCombo();
}
ImGui::SetNextItemWidth(180);
ImGui::InputText("alias", pick_alias, sizeof(pick_alias));
const char* strategies[] = {"left", "inner", "right", "full"};
ImGui::SetNextItemWidth(120);
ImGui::Combo("strategy", &pick_strategy, strategies, IM_ARRAYSIZE(strategies));
// left col combo (de eff_headers)
ImGui::SetNextItemWidth(180);
const char* lcur = (pick_left_col >= 0 && pick_left_col < eff_cols)
? eff_headers[pick_left_col] : "?";
if (ImGui::BeginCombo("left col", lcur)) {
for (int k = 0; k < eff_cols; ++k) {
bool sel = (k == pick_left_col);
if (ImGui::Selectable(eff_headers[k], sel)) pick_left_col = k;
}
ImGui::EndCombo();
}
// right col combo (de joinables[pick_source_idx].headers)
const TableInput& src = joinables[pick_source_idx];
const char* rcur = (pick_right_col >= 0 && pick_right_col < (int)src.headers.size())
? src.headers[pick_right_col].c_str() : "?";
ImGui::SetNextItemWidth(180);
if (ImGui::BeginCombo("right col", rcur)) {
for (int k = 0; k < (int)src.headers.size(); ++k) {
bool sel = (k == pick_right_col);
if (ImGui::Selectable(src.headers[k].c_str(), sel)) pick_right_col = k;
}
ImGui::EndCombo();
}
ImGui::Separator();
if (ImGui::SmallButton("Add") && pick_left_col < eff_cols &&
pick_right_col < (int)src.headers.size()) {
Join jn;
jn.alias = pick_alias;
jn.source = src.name;
jn.on.push_back({std::string(eff_headers[pick_left_col]),
src.headers[pick_right_col]});
jn.strategy = (JoinStrategy)pick_strategy;
st.joins.push_back(jn);
pick_alias[0] = 0;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::SmallButton("Cancel")) ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
ImGui::NewLine();
}
// ---------------------------------------------------------------------------
// draw_header_menu
// ---------------------------------------------------------------------------
void draw_header_menu(State& st, Stage& stg, int col,
const char* const* eff_headers_arr, int eff_cols,
const std::vector<ColumnType>& eff_types,
int orig_cols, bool is_raw_stage) {
auto& U = ui();
ColumnType t = eff_types[col];
if (ImGui::MenuItem("Sort ascending")) {
stg.sorts.clear();
stg.sorts.push_back({eff_headers_arr[col], false});
}
if (ImGui::MenuItem("Sort descending")) {
stg.sorts.clear();
stg.sorts.push_back({eff_headers_arr[col], true});
}
if (!stg.sorts.empty() && ImGui::MenuItem("Clear sort")) stg.sorts.clear();
ImGui::Separator();
auto& fbuf = U.filter_inputs[col];
fbuf.resize(256, '\0');
if (ImGui::BeginMenu("Filter...")) {
ImGui::SetNextItemWidth(220);
ImGui::InputText("##filterval", fbuf.data(), fbuf.size());
std::string val(fbuf.c_str());
auto ops = ops_for_type(t);
for (size_t i = 0; i < ops.size(); ++i) {
if (i % 5 != 0) ImGui::SameLine();
if (ImGui::SmallButton(op_label(ops[i]))) {
stg.filters.push_back({col, ops[i], val});
ImGui::CloseCurrentPopup();
}
}
ImGui::EndMenu();
}
if (is_raw_stage) {
if (ImGui::BeginMenu("Change type")) {
const ColumnType types[] = {
ColumnType::String, ColumnType::Int, ColumnType::Float,
ColumnType::Bool, ColumnType::Date, ColumnType::Json
};
for (auto nt : types) {
char lab[64];
std::snprintf(lab, sizeof(lab), "%s %s",
column_type_icon_chips(nt), column_type_name(nt));
if (ImGui::MenuItem(lab)) {
DerivedColumn d;
d.source_col = (col < orig_cols) ? col : stg.derived[col - orig_cols].source_col;
d.type = nt;
d.name = std::string(eff_headers_arr[col]) + "_" + column_type_name(nt);
stg.derived.push_back(d);
}
}
ImGui::EndMenu();
}
}
if (ImGui::BeginMenu("Conditional color")) {
draw_color_rule_menu(st, col, t, U.color_rules);
ImGui::EndMenu();
}
if (ImGui::MenuItem("Hide column")) st.col_visible[col] = false;
if (is_raw_stage && col >= orig_cols && ImGui::MenuItem("Remove derived column")) {
int k = col - orig_cols;
stg.derived.erase(stg.derived.begin() + k);
}
ImGui::Separator();
if (ImGui::BeginMenu("Columns")) {
for (int k = 0; k < eff_cols; ++k) {
bool v = st.col_visible[k];
char lab[160];
std::snprintf(lab, sizeof(lab), "%s %s",
column_type_icon_chips(eff_types[k]), eff_headers_arr[k]);
if (ImGui::Checkbox(lab, &v)) st.col_visible[k] = v;
}
if (ImGui::MenuItem("Show all")) {
for (int k = 0; k < eff_cols; ++k) st.col_visible[k] = true;
}
ImGui::EndMenu();
}
}
// ---------------------------------------------------------------------------
// draw_tql_bar
// Extrae el bloque TQL de render() (Show/Apply/Save/Load + modales).
// En el entrypoint data_table.cpp, reemplazar el bloque inline por llamadas
// a draw_tql_bar(U.tql_bar, st, orig_headers, orig_types, cells, row_count, orig_cols).
// ---------------------------------------------------------------------------
void draw_tql_bar(TqlBarState& tql_bar,
State& st,
const std::vector<std::string>& orig_headers,
const std::vector<ColumnType>& orig_types,
const char* const* cells,
int row_count, int orig_cols) {
if (ImGui::SmallButton("Show TQL")) {
tql_bar.show_text = tql::emit(st, orig_headers, orig_types);
tql_bar.show_open = true;
}
ImGui::SameLine();
if (ImGui::SmallButton("Apply TQL")) {
tql_bar.apply_open = true;
tql_bar.apply_error.clear();
}
ImGui::SameLine();
ImGui::SetNextItemWidth(160);
ImGui::InputText("##tql_file", tql_bar.file_path, sizeof(tql_bar.file_path));
ImGui::SameLine();
if (ImGui::SmallButton("Save .tql")) {
std::string text = tql::emit(st, orig_headers, orig_types);
const char* path = fn::local_path(tql_bar.file_path);
std::ofstream f(path);
if (f) { f << text; tql_bar.io_status = std::string("saved: ") + path; }
else { tql_bar.io_status = std::string("save FAILED: ") + path; }
}
ImGui::SameLine();
if (ImGui::SmallButton("Load .tql")) {
const char* path = fn::local_path(tql_bar.file_path);
std::ifstream f(path);
if (!f) { tql_bar.io_status = std::string("load FAILED: ") + path; }
else {
std::string text((std::istreambuf_iterator<char>(f)),
std::istreambuf_iterator<char>());
std::string err;
bool ok = tql::apply(text, st, orig_headers, orig_types,
cells, row_count, orig_cols, &err);
if (ok) tql_bar.io_status = std::string("loaded: ") + path +
(err.empty() ? "" : " (warn: " + err + ")");
else tql_bar.io_status = std::string("load parse error: ") + err;
}
}
if (!tql_bar.io_status.empty()) {
ImGui::SameLine();
ImGui::TextDisabled("%s", tql_bar.io_status.c_str());
}
// Modal "Show TQL"
if (tql_bar.show_open) {
ImGui::OpenPopup("##tql_show_modal");
tql_bar.show_open = false;
}
ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_Appearing);
if (ImGui::BeginPopupModal("##tql_show_modal", nullptr,
ImGuiWindowFlags_NoSavedSettings)) {
ImGui::TextUnformatted("TQL (read-only):");
ImGui::SameLine();
if (ImGui::SmallButton("Copy")) {
ImGui::SetClipboardText(tql_bar.show_text.c_str());
}
ImGui::InputTextMultiline("##tql_show_text",
const_cast<char*>(tql_bar.show_text.c_str()),
tql_bar.show_text.size() + 1,
ImVec2(-1, -40),
ImGuiInputTextFlags_ReadOnly);
if (ImGui::Button("Close")) ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
// Modal "Apply TQL"
if (tql_bar.apply_open) {
ImGui::OpenPopup("##tql_apply_modal");
tql_bar.apply_open = false;
// Pre-fill apply_text with current TQL
tql_bar.apply_text = tql::emit(st, orig_headers, orig_types);
}
ImGui::SetNextWindowSize(ImVec2(600, 420), ImGuiCond_Appearing);
if (ImGui::BeginPopupModal("##tql_apply_modal", nullptr,
ImGuiWindowFlags_NoSavedSettings)) {
ImGui::TextUnformatted("Paste / edit TQL and click Apply:");
tql_bar.apply_text.resize(4096, '\0');
ImGui::InputTextMultiline("##tql_apply_text",
tql_bar.apply_text.data(),
tql_bar.apply_text.size(),
ImVec2(-1, -80),
ImGuiInputTextFlags_None);
if (!tql_bar.apply_error.empty()) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 100, 100, 255));
ImGui::TextWrapped("Error: %s", tql_bar.apply_error.c_str());
ImGui::PopStyleColor();
}
if (ImGui::Button("Apply")) {
std::string err;
bool ok = tql::apply(std::string(tql_bar.apply_text.c_str()),
st, orig_headers, orig_types,
cells, row_count, orig_cols, &err);
if (ok) {
tql_bar.apply_error.clear();
ImGui::CloseCurrentPopup();
} else {
tql_bar.apply_error = err;
}
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
tql_bar.apply_error.clear();
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
} // namespace data_table