01bc2aeb14
Change 3 of issue 0133 — rewire compute_visible_rows, filter eval, and sort comparators to read from the SnapshotCache when available. Hot paths rewired: - compute_visible_rows (overload with snap): filter eval uses compare_snap (fast i64/f64 numeric compare for Int/Float cols; id-compare for low-cardinality string Eq/Neq; raw cells fallback for Contains/StartsWith/EndsWith). - Sort comparators: direct i64/f64 array compare for Int/Float cols (goto sort_done skips string fallback); string sort uses uint32_t id compare with pool lookup only on mismatch. - Stage>0 filter/sort: same snapshot overload. Materialization paths (build_so, s0_backing, mat_backing, config popup) kept on raw cells — they copy into std::string anyway, no benefit from snapshot and snprintf-per-cell was 2M extra calls per frame. Bug fixes (required for correctness): 1. StringPool::intern() realloc safety: force reserve before emplace_back so string_view keys in the map never go dangling. 2. SnapshotCache::pool_size_built sentinel: detects when a new State is created with an empty pool but same cells pointer (begin_scenario pattern). Prevents str_ids from indexing into an empty pool (SIGSEGV). 3. Cardinality cap (2048 uniques / 25% sample): high-cardinality string cols (timestamps-as-strings, UUIDs, names) skip interning — str_ids stays empty and compare_snap falls back to raw cells. Prevents 30MB+ pool bloat that hurt cache for filter/sort on other cols. Bench delta vs baseline (100k rows, LIBGL_ALWAYS_SOFTWARE=1): linear_scroll: 16.0 -> 15.5 fps p50 (-3%, baseline already FAIL) filter_like: 59.7 -> 56.0 fps p50 (-6%, still PASS at 56fps) sort_numeric: 3.9 -> 9.0 fps p50 (+131%, snapshot i64 sort) color_rule: 15.2 -> 14.8 fps p50 (-3%, baseline already FAIL) Build: green for all 10 available Linux consumers (text_editor_smoke linker failure is preexisting, not caused by this change). API public intact. TableEvent.row indexing TableInput preserved. Pointer-identity invalidation preserved. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
431 lines
20 KiB
C++
431 lines
20 KiB
C++
#pragma once
|
|
// data_table_internal — contrato compartido entre las 6 sub-funciones del modulo.
|
|
// NO publico para apps: las apps incluyen `data_table/data_table.h` y nada mas.
|
|
// Este header lo incluyen SOLO los .cpp del modulo (data_table.cpp + sus 6 sub-funciones).
|
|
//
|
|
// Issue 0107c — split de data_table.cpp (4777 LOC) en 6 sub-funciones del registry.
|
|
// Issue 0133 — columnar snapshot + string interning (Changes 1+2).
|
|
//
|
|
// Provee:
|
|
// 1. `UiState` agregador (composicion de sub-states declarados en los .h de
|
|
// cada sub-funcion + estado compartido por >1 sub-funcion).
|
|
// 2. `ui()` accessor para singleton thread_local — mismo lifetime que el
|
|
// original UiState del playground.
|
|
// 3. Helpers compartidos como `inline` puros: `ops_for_type`, `op_label`,
|
|
// `effective_type`, `view_mode_label`, `join_strategy_label`, etc.
|
|
// 4. Forward refs de funciones internas que cruzan sub-funciones (ej. el
|
|
// draw_header_menu de chips llama draw_color_rule_menu de color_rules).
|
|
// 5. `ColumnSnapshot` / `SnapshotCache` — snapshot columnar interno (issue 0133).
|
|
//
|
|
// Politica:
|
|
// - Si un helper se usa SOLO dentro de UNA sub-funcion -> queda `static` en su .cpp.
|
|
// - Si se usa en >1 sub-funcion -> aqui como `inline`.
|
|
// - Si manipula estado -> miembro de UiState (o sub-state).
|
|
//
|
|
// API publica externa = data_table/data_table.h (intacta tras refactor).
|
|
// API interna del modulo = este header.
|
|
//
|
|
// ---------------------------------------------------------------------------
|
|
// DISEÑO: Snapshot columnar + String Interning (issue 0133)
|
|
// ---------------------------------------------------------------------------
|
|
//
|
|
// MOTIVACION
|
|
// El layout de datos de entrada es row-major: `cells[row * cols + col]`
|
|
// (punteros a C-strings en el `TableInput` del caller). Acceder a una
|
|
// columna entera (para filtrar, ordenar, colorear, calcular stats) requiere
|
|
// saltar `cols` posiciones en memoria por fila — mal para cache a 10M+ filas.
|
|
// El snapshot convierte a column-major una sola vez por frame y amortiza el
|
|
// coste entre filter / sort / color_rules / stats.
|
|
//
|
|
// SNAPSHOT COLUMNAR — `ColumnSnapshot` / `SnapshotCache`
|
|
// - `SnapshotCache` vive en `UiState` (singleton thread_local de este modulo).
|
|
// - Contiene un vector de `ColumnSnapshot`, uno por columna efectiva de la
|
|
// tabla DESPUES de joins pero ANTES de stages (= stage-0 input).
|
|
// - `SnapshotCache::last_cells_ptr` guarda el puntero de `cells` del ultimo
|
|
// rebuild. Si el puntero cambia (o es la primera llamada), se reconstruye
|
|
// el snapshot completo. Esto sigue exactamente el patron de `stats_last_cells`
|
|
// en `State` (data_table_types.h:399).
|
|
// - String columns (ColumnType::String / Auto sin numero) almacenan indices
|
|
// uint32_t al `StringPool` del `State` correspondiente.
|
|
// - Int columns almacenan int64_t parseados una sola vez via `parse_number`.
|
|
// - Float columns almacenan double parseados una sola vez via `parse_number`.
|
|
// - Si `parse_number` falla en una celda que deberia ser Int o Float, la celda
|
|
// se trata como 0 / 0.0. Este comportamiento es consistente con `compare()`
|
|
// y el sort actual que ya llaman `parse_number` per-compare.
|
|
//
|
|
// STRING INTERNING — `StringPool` en `State`
|
|
// - `StringPool` vive en `State` (NOT global, NOT singleton) para que cada
|
|
// instancia de tabla tenga su propio pool sin interferencia.
|
|
// - `intern(sv)` inserta la cadena si no esta y devuelve su indice uint32_t.
|
|
// Usa `unordered_map<string_view, uint32_t>` con la `string_view` apuntando
|
|
// al `strings[i]` del vector (el vector se reserva antes del rebuild para
|
|
// evitar reallocs que invaliden los string_views del mapa).
|
|
// - En datasets tipicos (60-70% de strings repetidos) la reduccion de RAM
|
|
// es de 60-70% en la columna interned vs copias planas.
|
|
//
|
|
// INVARIANTES
|
|
// 1. Pointer-identity: si `cells == last_cells_ptr`, el snapshot es valido
|
|
// para este frame. Cambio de puntero => rebuild completo.
|
|
// 2. row→snapshot_row: el indice de fila en el snapshot es IDENTICO al indice
|
|
// de fila del `TableInput` original (no hay reordenacion en el snapshot).
|
|
// `TableEvent.row` del caller sigue siendo indice en `TableInput`.
|
|
// 3. El snapshot es input de stage-0. Los stages sucesivos (compute_stage)
|
|
// operan sobre `StageOutput` materializado y NO consultan el snapshot
|
|
// directamente — el snapshot solo alimenta la ruta stage-0 de render().
|
|
// 4. `stats_last_cells` y snapshot son independientes: `stats_last_cells`
|
|
// ya existia antes de issue 0133 y permanece como sentinel propio del
|
|
// cache de stats. El snapshot tiene su propio `last_cells_ptr`.
|
|
// Ambos pueden diferir temporalmente si `stats_cache` invalida por
|
|
// filtro (hash de filtros) pero el snapshot sigue valido por puntero.
|
|
// 5. `StringPool` se limpia (clear()) en cada rebuild del snapshot para
|
|
// mantener coherencia: los indices del snapshot siempre corresponden
|
|
// al pool del mismo frame.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#include "core/data_table_types.h"
|
|
#include "core/auto_detect_type.h"
|
|
|
|
// Sub-states declarados en los .h de cada sub-funcion. Se incluyen aqui para
|
|
// que `UiState` los pueda componer por valor.
|
|
#include "viz/data_table_ai_panel.h" // AskAiState
|
|
#include "viz/data_table_chips.h" // TqlBarState
|
|
#include "viz/data_table_color_rules.h" // ColorRuleEditorState
|
|
|
|
#include "imgui.h"
|
|
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
namespace data_table {
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ColumnSnapshot — snapshot de una columna en memoria columnar.
|
|
// Creado una vez por frame al detectar cambio de puntero en `cells`.
|
|
// ---------------------------------------------------------------------------
|
|
struct ColumnSnapshot {
|
|
ColumnType type; // tipo efectivo inferido (post auto_detect)
|
|
std::vector<uint32_t> str_ids; // para String/Auto: indices al StringPool
|
|
std::vector<int64_t> i64; // para Int: valores parseados
|
|
std::vector<double> f64; // para Float: valores parseados
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SnapshotCache — vive en UiState (thread_local singleton).
|
|
// Un snapshot cubre TODAS las columnas efectivas de la tabla activa
|
|
// (post-join, pre-stages). Se invalida por pointer-identity de `cells`.
|
|
// ---------------------------------------------------------------------------
|
|
struct SnapshotCache {
|
|
const char* const* last_cells_ptr = nullptr; // sentinel de invalidacion por ptr
|
|
uint32_t pool_size_built = 0; // strings.size() cuando se construyo
|
|
std::vector<ColumnSnapshot> cols; // un entry por columna efectiva
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// UiState — singleton thread_local del modulo. Agrupa:
|
|
// (a) Sub-states declarados en headers de sub-funciones (AskAiState etc.).
|
|
// (b) Estado compartido por >1 sub-funcion que no encaja en ningun sub-state
|
|
// individual.
|
|
// El entrypoint thin (data_table.cpp) mantiene el UiState via `ui()`.
|
|
// Cada sub-funcion recibe `UiState&` o solo el sub-state que necesita.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
struct UiState {
|
|
// ----- Sub-states de sub-funciones (composicion) -----
|
|
AskAiState ask_ai; // data_table_ai_panel
|
|
TqlBarState tql_bar; // data_table_chips
|
|
ColorRuleEditorState color_rules; // data_table_color_rules
|
|
|
|
// ----- Cell popup (grid + chips, drill popup en cell ctx menu) -----
|
|
int pending_col = -1;
|
|
std::string pending_value;
|
|
bool open_cell_popup = false;
|
|
|
|
// ----- Header popup (chips draw_header_menu + color_rules + grid header) -----
|
|
int header_popup_col = -1;
|
|
std::unordered_map<int, std::string> filter_inputs;
|
|
// color_value_inputs + color_picker_vals viven en color_rules (ColorRuleEditorState).
|
|
|
|
// ----- Add-filter popup state (chips draw_add_filter_popup) -----
|
|
int addf_col = 0;
|
|
std::string addf_val;
|
|
bool addf_range = false;
|
|
std::string addf_lo;
|
|
std::string addf_hi;
|
|
|
|
// ----- Custom column modal (formula editor — vive en data_table.cpp entrypoint) -----
|
|
bool cf_open = false;
|
|
bool cf_editing = false;
|
|
int cf_edit_idx = -1;
|
|
int cf_target_stage = 0;
|
|
std::string cf_formula;
|
|
std::string cf_name;
|
|
ColumnType cf_type = ColumnType::String;
|
|
std::string cf_error;
|
|
bool cf_ac_open = false;
|
|
int cf_ac_start = -1;
|
|
int cf_ac_cursor = -1;
|
|
std::string cf_ac_filter;
|
|
bool cf_force_cursor = false;
|
|
int cf_target_cursor = -1;
|
|
|
|
// ----- Add-breakout / add-aggregation / edit chip popups (chips) -----
|
|
int brk_picker_col = 0;
|
|
int agg_picker_fn = (int)AggFn::Count;
|
|
int agg_picker_col = 0;
|
|
double agg_picker_arg = 0.95;
|
|
int edit_chip_kind = 0; // 0=none, 1=filter, 2=breakout, 3=agg, 4=sort
|
|
int edit_chip_idx = -1;
|
|
int edit_col_idx = 0;
|
|
int edit_op = (int)Op::Eq;
|
|
int edit_agg_fn = (int)AggFn::Count;
|
|
double edit_agg_arg = 0.5;
|
|
bool edit_sort_desc = false;
|
|
std::string edit_value;
|
|
int sort_picker_col = 0;
|
|
bool sort_picker_desc = false;
|
|
|
|
// ----- Snapshot del active stage output/input (viz_panels config popup) -----
|
|
std::vector<std::string> active_headers;
|
|
std::vector<ColumnType> active_types;
|
|
std::vector<std::string> input_headers_active;
|
|
std::vector<ColumnType> input_types_active;
|
|
|
|
// ----- Re-fit triggers para viz panels -----
|
|
ViewMode prev_viz_display = ViewMode::Table;
|
|
int prev_viz_stage = 0;
|
|
std::size_t prev_viz_cfg_h = 0;
|
|
|
|
// ----- Toggle Table <-> View (viz_panels) -----
|
|
ViewMode last_non_table_main = ViewMode::Bar;
|
|
|
|
// ----- Export path (chips export action) -----
|
|
std::string last_export_path;
|
|
|
|
// ----- Columnar snapshot (issue 0133, Change 1) -----
|
|
// Invalida cuando cells pointer cambia entre frames.
|
|
// Usado en render() stage-0 path para filter/sort/color_rules/stats.
|
|
SnapshotCache snapshot;
|
|
};
|
|
|
|
// Singleton accessor. Definido en data_table.cpp (entrypoint).
|
|
UiState& ui();
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ViewMode table — fuente de verdad para label/needs_agg/all_modes.
|
|
// Compartida por: data_table.cpp (re-fit trigger) y data_table_viz_panels.cpp.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
struct ViewModeInfo {
|
|
ViewMode m;
|
|
const char* token;
|
|
const char* label;
|
|
int min_cols;
|
|
bool needs_num;
|
|
bool needs_cat;
|
|
bool needs_agg;
|
|
};
|
|
|
|
static const ViewModeInfo kViewModes[] = {
|
|
{ ViewMode::Table, "table", "Table", 1, false, false, false },
|
|
{ ViewMode::Bar, "bar", "Bar (horizontal)", 2, true, true, true },
|
|
{ ViewMode::Column, "column", "Column (vertical)", 2, true, true, true },
|
|
{ ViewMode::GroupedBar, "grouped_bar", "Grouped bar", 2, true, true, true },
|
|
{ ViewMode::StackedBar, "stacked_bar", "Stacked bar", 2, true, true, true },
|
|
{ ViewMode::Line, "line", "Line", 1, true, false, false },
|
|
{ ViewMode::Area, "area", "Area", 1, true, false, false },
|
|
{ ViewMode::Stairs, "stairs", "Stairs", 1, true, false, false },
|
|
{ ViewMode::Scatter, "scatter", "Scatter", 2, true, false, false },
|
|
{ ViewMode::Bubble, "bubble", "Bubble", 3, true, false, false },
|
|
{ ViewMode::Histogram, "histogram", "Histogram", 1, true, false, false },
|
|
{ ViewMode::Histogram2D, "hist2d", "Histogram 2D", 2, true, false, false },
|
|
{ ViewMode::Heatmap, "heatmap", "Heatmap", 1, true, false, false },
|
|
{ ViewMode::BoxPlot, "boxplot", "Box plot", 2, true, true, false },
|
|
{ ViewMode::Stem, "stem", "Stem", 1, true, false, false },
|
|
{ ViewMode::ErrorBars, "errorbars", "Error bars", 2, true, false, false },
|
|
{ ViewMode::Pie, "pie", "Pie", 2, true, true, true },
|
|
{ ViewMode::Donut, "donut", "Donut", 2, true, true, true },
|
|
{ ViewMode::Funnel, "funnel", "Funnel", 2, true, true, true },
|
|
{ ViewMode::Waterfall, "waterfall", "Waterfall", 1, true, false, true },
|
|
{ ViewMode::KPI, "kpi", "KPI (single)", 1, true, false, true },
|
|
{ ViewMode::KPIGrid, "kpi_grid", "KPI grid", 1, true, false, true },
|
|
{ ViewMode::Candlestick, "candlestick", "Candlestick (OHLC)", 4, true, false, false },
|
|
{ ViewMode::Radar, "radar", "Radar", 2, true, true, false },
|
|
};
|
|
static const int kViewModesN = (int)(sizeof(kViewModes) / sizeof(kViewModes[0]));
|
|
|
|
inline const char* view_mode_label(ViewMode m) {
|
|
for (int i = 0; i < kViewModesN; ++i) if (kViewModes[i].m == m) return kViewModes[i].label;
|
|
return "Table";
|
|
}
|
|
|
|
inline bool view_mode_needs_aggregation(ViewMode m) {
|
|
for (int i = 0; i < kViewModesN; ++i) if (kViewModes[i].m == m) return kViewModes[i].needs_agg;
|
|
return false;
|
|
}
|
|
|
|
inline const ViewMode* all_view_modes(int* n_out) {
|
|
static ViewMode arr[64];
|
|
static bool init = false;
|
|
if (!init) {
|
|
for (int i = 0; i < kViewModesN; ++i) arr[i] = kViewModes[i].m;
|
|
init = true;
|
|
}
|
|
if (n_out) *n_out = kViewModesN;
|
|
return arr;
|
|
}
|
|
|
|
// filters_hash: FNV-1a hash de filtros activos para detectar cambios en maybe_recompute_stats.
|
|
inline size_t filters_hash(const std::vector<Filter>& f) {
|
|
size_t h = 0xcbf29ce484222325ULL;
|
|
for (const auto& x : f) {
|
|
h ^= (size_t)x.col; h *= 0x100000001b3ULL;
|
|
h ^= (size_t)x.op; h *= 0x100000001b3ULL;
|
|
for (char ch : x.value) { h ^= (size_t)(unsigned char)ch; h *= 0x100000001b3ULL; }
|
|
}
|
|
return h;
|
|
}
|
|
|
|
// ColInfo: nombre + tipo de columna. Usado por draw_viz_config_popup para
|
|
// construir las listas de seleccion X/Y/Cat.
|
|
struct ColInfo { std::string name; ColumnType type; };
|
|
|
|
// collect_active_col_info: devuelve snapshot de columnas activas del active_stage
|
|
// (pobladas por render() en UiState::active_headers / active_types).
|
|
// Compartida por: data_table.cpp (forward decl) y data_table_viz_panels.cpp.
|
|
inline std::vector<ColInfo> collect_active_col_info(const State& /*st*/) {
|
|
auto& U = ui();
|
|
std::vector<ColInfo> r;
|
|
int n = (int)std::min(U.active_headers.size(), U.active_types.size());
|
|
r.reserve(n);
|
|
for (int i = 0; i < n; ++i) r.push_back({U.active_headers[i], U.active_types[i]});
|
|
return r;
|
|
}
|
|
|
|
// auto_promote_aggregated: si user en stage 0 elige una viz que necesita
|
|
// agrupacion, crea stage 1 con breakout=primera cat + agg=sum(primera num) o count.
|
|
// Compartida por: data_table.cpp y data_table_viz_panels.cpp.
|
|
inline void auto_promote_aggregated(State& st) {
|
|
auto& U = ui();
|
|
if (st.active_stage != 0) return;
|
|
if (st.stages.size() != 1) return;
|
|
|
|
std::string cat_name;
|
|
std::string num_name;
|
|
for (size_t i = 0; i < U.active_headers.size() && i < U.active_types.size(); ++i) {
|
|
ColumnType t = U.active_types[i];
|
|
if (cat_name.empty() &&
|
|
(t == ColumnType::String || t == ColumnType::Date ||
|
|
t == ColumnType::Bool || t == ColumnType::Json)) {
|
|
cat_name = U.active_headers[i];
|
|
}
|
|
if (num_name.empty() &&
|
|
(t == ColumnType::Int || t == ColumnType::Float)) {
|
|
num_name = U.active_headers[i];
|
|
}
|
|
}
|
|
|
|
Stage s1;
|
|
if (!cat_name.empty()) s1.breakouts.push_back(cat_name);
|
|
Aggregation a;
|
|
if (!num_name.empty()) {
|
|
a.fn = AggFn::Sum;
|
|
a.col = num_name;
|
|
} else {
|
|
a.fn = AggFn::Count;
|
|
}
|
|
s1.aggregations.push_back(a);
|
|
st.stages.push_back(std::move(s1));
|
|
st.active_stage = (int)st.stages.size() - 1;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers compartidos (inline, puros). Usados por >1 sub-funcion.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// effective_type: si declared==Auto, llama auto_detect_type sobre el rango.
|
|
// Usado por: chips (filter popups), grid (cell render), viz_panels (stats).
|
|
inline ColumnType effective_type(ColumnType declared,
|
|
const char* const* cells, int rows, int cols, int col) {
|
|
if (declared != ColumnType::Auto) return declared;
|
|
return auto_detect_type(cells, rows, cols, col);
|
|
}
|
|
|
|
// ops_for_type: lista de operadores validos para un ColumnType.
|
|
// Usado por: chips (filter popups, edit popups), grid (cell ctx menu).
|
|
inline std::vector<Op> ops_for_type(ColumnType t) {
|
|
switch (t) {
|
|
case ColumnType::Int:
|
|
case ColumnType::Float:
|
|
case ColumnType::Date:
|
|
return {Op::Eq, Op::Neq, Op::Gt, Op::Gte, Op::Lt, Op::Lte};
|
|
case ColumnType::Bool:
|
|
return {Op::Eq, Op::Neq};
|
|
case ColumnType::Json:
|
|
return {Op::Eq, Op::Neq, Op::Contains, Op::NotContains};
|
|
case ColumnType::String:
|
|
return {Op::Eq, Op::Neq, Op::Contains, Op::NotContains, Op::StartsWith, Op::EndsWith};
|
|
case ColumnType::Auto:
|
|
default:
|
|
return {Op::Eq, Op::Neq, Op::Contains, Op::NotContains};
|
|
}
|
|
}
|
|
|
|
// op_label: nombre humano del operador para chips + cell ctx menu.
|
|
// Usado por: chips, grid.
|
|
// NOTE: `static inline` para evitar clash de simbolos con la definicion
|
|
// canonica de `op_label` en tql_helpers.cpp (issue 0107c follow-up). MinGW
|
|
// linker es estricto con inline-no-static vs out-of-line definition.
|
|
static inline const char* op_label(Op op) {
|
|
switch (op) {
|
|
case Op::Eq: return "=";
|
|
case Op::Neq: return "!=";
|
|
case Op::Gt: return ">";
|
|
case Op::Gte: return ">=";
|
|
case Op::Lt: return "<";
|
|
case Op::Lte: return "<=";
|
|
case Op::Contains: return "contains";
|
|
case Op::NotContains: return "!contains";
|
|
case Op::StartsWith: return "starts";
|
|
case Op::EndsWith: return "ends";
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
// join_strategy_label: nombre humano de la estrategia de join.
|
|
// Usado por: chips (joins chips), data_table.cpp (render setup).
|
|
inline const char* join_strategy_label(JoinStrategy s) {
|
|
switch (s) {
|
|
case JoinStrategy::Left: return "left-join";
|
|
case JoinStrategy::Inner: return "inner-join";
|
|
case JoinStrategy::Right: return "right-join";
|
|
case JoinStrategy::Full: return "full-join";
|
|
}
|
|
return "left-join";
|
|
}
|
|
|
|
// resolve_main_idx: encuentra el indice de tables[] que coincide con main_source.
|
|
// Usado por: data_table.cpp render entrypoint.
|
|
inline int resolve_main_idx(const std::vector<TableInput>& tables, const std::string& main_source) {
|
|
if (tables.empty()) return -1;
|
|
if (main_source.empty()) return 0;
|
|
for (std::size_t i = 0; i < tables.size(); ++i) {
|
|
if (tables[i].name == main_source) return (int)i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Forward refs de funciones internas que cruzan sub-funciones.
|
|
// Cada cual definida en SU .cpp; aqui solo declarada para que otros .cpp del
|
|
// modulo la puedan llamar sin doble include.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// draw_color_rule_menu: llamado desde draw_header_menu (submenu "Conditional color").
|
|
// Definido en data_table_color_rules.cpp.
|
|
// Retorna true si el usuario hizo click en "Apply".
|
|
bool draw_color_rule_menu(State& st, int col, ColumnType col_type,
|
|
ColorRuleEditorState& editor_st);
|
|
|
|
} // namespace data_table
|