gtk3-node 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,72 @@
1
+ #include <napi.h>
2
+ #include <gtk/gtk.h>
3
+
4
+ // Estructura para almacenar datos del scroll
5
+ struct ScrollData {
6
+ GtkWidget* scroll;
7
+ Napi::Env env;
8
+
9
+ ScrollData(Napi::Env e) : scroll(nullptr), env(e) {}
10
+ };
11
+
12
+ // Constructor de scroll
13
+ Napi::Value CreateScroll(const Napi::CallbackInfo& info) {
14
+ Napi::Env env = info.Env();
15
+
16
+ GtkWidget* scroll = gtk_scrolled_window_new(NULL, NULL);
17
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
18
+ GTK_POLICY_AUTOMATIC,
19
+ GTK_POLICY_AUTOMATIC);
20
+
21
+ // Crear estructura de datos para el scroll
22
+ ScrollData* scroll_data = new ScrollData(env);
23
+ scroll_data->scroll = scroll;
24
+
25
+ // Guardar la estructura en los datos del widget
26
+ g_object_set_data_full(G_OBJECT(scroll), "scroll_data", scroll_data,
27
+ GDestroyNotify([](gpointer data) {
28
+ ScrollData* scroll_data = static_cast<ScrollData*>(data);
29
+ delete scroll_data;
30
+ }));
31
+
32
+ // Convertir el widget a External para pasarlo a JavaScript
33
+ Napi::Object obj = Napi::Object::New(env);
34
+ obj.Set("widget", Napi::External<GtkWidget>::New(env, scroll));
35
+
36
+ return obj;
37
+ }
38
+
39
+ // Función para agregar un widget al scroll
40
+ Napi::Value AddToScroll(const Napi::CallbackInfo& info) {
41
+ Napi::Env env = info.Env();
42
+
43
+ if (info.Length() < 2 || !info[0].IsObject() || !info[1].IsObject()) {
44
+ Napi::TypeError::New(env, "Se requieren dos objetos widget").ThrowAsJavaScriptException();
45
+ return Napi::Boolean::New(env, false);
46
+ }
47
+
48
+ Napi::Object containerObj = info[0].As<Napi::Object>();
49
+ Napi::Object widgetObj = info[1].As<Napi::Object>();
50
+
51
+ if (!containerObj.HasOwnProperty("widget") || !widgetObj.HasOwnProperty("widget")) {
52
+ Napi::TypeError::New(env, "Ambos objetos deben contener widgets válidos").ThrowAsJavaScriptException();
53
+ return Napi::Boolean::New(env, false);
54
+ }
55
+
56
+ Napi::External<GtkWidget> containerExt = containerObj.Get("widget").As<Napi::External<GtkWidget>>();
57
+ Napi::External<GtkWidget> widgetExt = widgetObj.Get("widget").As<Napi::External<GtkWidget>>();
58
+
59
+ GtkWidget* container = containerExt.Data();
60
+ GtkWidget* widget = widgetExt.Data();
61
+
62
+ gtk_container_add(GTK_CONTAINER(container), widget);
63
+
64
+ return Napi::Boolean::New(env, true);
65
+ }
66
+
67
+ Napi::Object InitScroll(Napi::Env env, Napi::Object exports) {
68
+ exports.Set("createScroll", Napi::Function::New(env, CreateScroll));
69
+ exports.Set("addToScroll", Napi::Function::New(env, AddToScroll));
70
+
71
+ return exports;
72
+ }
@@ -0,0 +1,10 @@
1
+ #ifndef SCROLL_H
2
+ #define SCROLL_H
3
+
4
+ #include <napi.h>
5
+ #include <gtk/gtk.h>
6
+
7
+ // Función para inicializar el widget Scroll
8
+ Napi::Object InitScroll(Napi::Env env, Napi::Object exports);
9
+
10
+ #endif // SCROLL_H
@@ -0,0 +1,46 @@
1
+ # Widget StringGrid
2
+
3
+ El widget StringGrid es parte de la colección de widgets para la extensión GTK3 para Node.js. Proporciona una tabla bidimensional para mostrar y editar datos en formato fila-columna.
4
+
5
+ ## Características
6
+
7
+ - Tabla bidimensional de filas y columnas
8
+ - Soporte para edición de celdas
9
+ - Compatible con el principio DRY
10
+ - Fácil integración con otros widgets
11
+
12
+ ## API
13
+
14
+ ### Constructor
15
+ - `new StringGrid(rows, cols)` - Crea una nueva tabla con el número especificado de filas y columnas
16
+
17
+ ### Métodos
18
+ - `setCellValue(row, col, value)` - Establece el valor de una celda
19
+ - `getCell(row, col)` - Obtiene el valor de una celda
20
+ - `setHeader(headers)` - Establece los encabezados de columna
21
+ - `addRow(data)` - Agrega una fila con los datos especificados
22
+
23
+ ## Ejemplo de Uso
24
+
25
+ ```javascript
26
+ const { StringGrid, Window, init, run } = require('../../index.js');
27
+
28
+ init();
29
+ const window = new Window('Ejemplo StringGrid', 600, 400);
30
+
31
+ const grid = new StringGrid(10, 5); // 10 filas, 5 columnas
32
+ grid.setHeader(['Nombre', 'Edad', 'Ciudad', 'País', 'Activo']);
33
+
34
+ // Agregar algunas filas de ejemplo
35
+ grid.addRow(['Juan Pérez', '30', 'Madrid', 'España', 'Sí']);
36
+ grid.addRow(['Ana García', '25', 'Barcelona', 'España', 'No']);
37
+
38
+ window.add(grid);
39
+ window.show();
40
+
41
+ run();
42
+ ```
43
+
44
+ ## Principio DRY
45
+
46
+ Este widget sigue el principio DRY (Don't Repeat Yourself) al reutilizar patrones comunes de implementación de widgets en la extensión GTK3 para Node.js.
@@ -0,0 +1,496 @@
1
+ #include <napi.h>
2
+ #include <gtk/gtk.h>
3
+
4
+ // Estructura para almacenar datos del stringgrid
5
+ struct StringGridData {
6
+ GtkWidget* treeview;
7
+ GtkListStore* store;
8
+ std::vector<std::string> headers;
9
+ Napi::Env env;
10
+ Napi::FunctionReference cell_edit_callback;
11
+
12
+ StringGridData(Napi::Env e) : treeview(nullptr), store(nullptr), env(e) {}
13
+ };
14
+
15
+ // Callback para cuando se edita una celda
16
+ static void cell_edited_callback(GtkCellRendererText *renderer, gchar *path_string, gchar *new_text, gpointer user_data) {
17
+ StringGridData* grid_data = static_cast<StringGridData*>(user_data);
18
+
19
+ // Convertir la cadena de ruta a un GtkTreePath
20
+ GtkTreePath* path = gtk_tree_path_new_from_string(path_string);
21
+
22
+ // Obtener el iterador para la fila
23
+ GtkTreeIter iter;
24
+ if (gtk_tree_model_get_iter(GTK_TREE_MODEL(grid_data->store), &iter, path)) {
25
+ // Determinar qué columna fue editada
26
+ GtkTreeViewColumn* column = gtk_tree_view_get_column(GTK_TREE_VIEW(grid_data->treeview), 0); // Necesitamos identificar la columna
27
+
28
+ // Buscar cuál columna es la que se está editando
29
+ gint n_columns = gtk_tree_view_get_n_columns(GTK_TREE_VIEW(grid_data->treeview));
30
+ for (gint i = 0; i < n_columns; i++) {
31
+ GtkTreeViewColumn* col = gtk_tree_view_get_column(GTK_TREE_VIEW(grid_data->treeview), i);
32
+ GList* renderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(col));
33
+
34
+ if (renderers) {
35
+ GtkCellRenderer* renderer_in_col = (GtkCellRenderer*)renderers->data;
36
+ if (renderer_in_col == (GtkCellRenderer*)renderer) {
37
+ // Actualizar el valor en la tienda
38
+ gtk_list_store_set(grid_data->store, &iter, i, new_text, -1);
39
+ break;
40
+ }
41
+ g_list_free(renderers);
42
+ }
43
+ }
44
+ }
45
+
46
+ gtk_tree_path_free(path);
47
+ }
48
+
49
+ // Constructor de stringgrid
50
+ Napi::Value CreateStringGrid(const Napi::CallbackInfo& info) {
51
+ Napi::Env env = info.Env();
52
+
53
+ int rows = 10; // Valor por defecto
54
+ int cols = 5; // Valor por defecto
55
+
56
+ if (info.Length() > 0 && info[0].IsNumber()) {
57
+ rows = info[0].As<Napi::Number>().Int32Value();
58
+ }
59
+ if (info.Length() > 1 && info[1].IsNumber()) {
60
+ cols = info[1].As<Napi::Number>().Int32Value();
61
+ }
62
+
63
+ // Crear modelo para el TreeView
64
+ GType* types = new GType[cols];
65
+ for (int i = 0; i < cols; i++) {
66
+ types[i] = G_TYPE_STRING;
67
+ }
68
+
69
+ GtkListStore* store = gtk_list_store_newv(cols, types);
70
+ GtkWidget* treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
71
+
72
+ // Crear columnas con edición
73
+ for (int i = 0; i < cols; i++) {
74
+ GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
75
+
76
+ // Permitir edición
77
+ g_object_set(renderer, "editable", TRUE, NULL);
78
+
79
+ // Conectar el callback de edición
80
+ g_signal_connect(renderer, "edited", G_CALLBACK(cell_edited_callback), NULL);
81
+
82
+ GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes(
83
+ ("Col " + std::to_string(i)).c_str(), renderer, "text", i, NULL);
84
+
85
+ gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
86
+ }
87
+
88
+ // Permitir selección
89
+ GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
90
+ gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
91
+
92
+ delete[] types;
93
+
94
+ // Crear estructura de datos para el stringgrid
95
+ StringGridData* grid_data = new StringGridData(env);
96
+ grid_data->treeview = treeview;
97
+ grid_data->store = store;
98
+
99
+ // Conectar el callback de edición a la estructura de datos
100
+ for (int i = 0; i < cols; i++) {
101
+ GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
102
+ g_object_set(renderer, "editable", TRUE, NULL);
103
+ g_signal_connect(renderer, "edited", G_CALLBACK(cell_edited_callback), grid_data);
104
+
105
+ // Actualizar el renderer en la columna existente
106
+ GtkTreeViewColumn* column = gtk_tree_view_get_column(GTK_TREE_VIEW(treeview), i);
107
+ gtk_tree_view_column_clear(column);
108
+ gtk_tree_view_column_pack_start(column, renderer, TRUE);
109
+ gtk_tree_view_column_add_attribute(column, renderer, "text", i);
110
+ }
111
+
112
+ // Guardar la estructura en los datos del widget
113
+ g_object_set_data_full(G_OBJECT(treeview), "stringgrid_data", grid_data,
114
+ GDestroyNotify([](gpointer data) {
115
+ StringGridData* grid_data = static_cast<StringGridData*>(data);
116
+ if (!grid_data->cell_edit_callback.IsEmpty()) {
117
+ grid_data->cell_edit_callback.Reset();
118
+ }
119
+ delete grid_data;
120
+ }));
121
+
122
+ // Convertir el widget a External para pasarlo a JavaScript
123
+ Napi::Object obj = Napi::Object::New(env);
124
+ obj.Set("widget", Napi::External<GtkWidget>::New(env, treeview));
125
+
126
+ return obj;
127
+ }
128
+
129
+ // Función para establecer el valor de una celda
130
+ Napi::Value SetGridCellValue(const Napi::CallbackInfo& info) {
131
+ Napi::Env env = info.Env();
132
+
133
+ if (info.Length() < 4 || !info[0].IsObject() || !info[1].IsNumber() ||
134
+ !info[2].IsNumber() || !info[3].IsString()) {
135
+ Napi::TypeError::New(env, "Se requiere un objeto widget, fila, columna y valor").ThrowAsJavaScriptException();
136
+ return Napi::Boolean::New(env, false);
137
+ }
138
+
139
+ Napi::Object widgetObj = info[0].As<Napi::Object>();
140
+ int row = info[1].As<Napi::Number>().Int32Value();
141
+ int col = info[2].As<Napi::Number>().Int32Value();
142
+ std::string value = info[3].As<Napi::String>().Utf8Value();
143
+
144
+ if (!widgetObj.HasOwnProperty("widget")) {
145
+ Napi::TypeError::New(env, "El objeto no contiene un widget válido").ThrowAsJavaScriptException();
146
+ return Napi::Boolean::New(env, false);
147
+ }
148
+
149
+ Napi::External<GtkWidget> widgetExt = widgetObj.Get("widget").As<Napi::External<GtkWidget>>();
150
+ GtkWidget* treeview = widgetExt.Data();
151
+
152
+ StringGridData* grid_data = static_cast<StringGridData*>(
153
+ g_object_get_data(G_OBJECT(treeview), "stringgrid_data"));
154
+
155
+ if (!grid_data || !grid_data->store) {
156
+ Napi::TypeError::New(env, "Datos del grid no encontrados").ThrowAsJavaScriptException();
157
+ return Napi::Boolean::New(env, false);
158
+ }
159
+
160
+ // Buscar la fila específica
161
+ GtkTreeIter iter;
162
+ gboolean valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(grid_data->store), &iter);
163
+ int current_row = 0;
164
+
165
+ while (valid && current_row < row) {
166
+ valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(grid_data->store), &iter);
167
+ current_row++;
168
+ }
169
+
170
+ if (valid && current_row == row) {
171
+ gtk_list_store_set(grid_data->store, &iter, col, value.c_str(), -1);
172
+ } else {
173
+ // Si la fila no existe, crearla
174
+ gtk_list_store_append(grid_data->store, &iter);
175
+ // Rellenar las columnas anteriores con cadenas vacías si es necesario
176
+ for (int i = 0; i < col; i++) {
177
+ gtk_list_store_set(grid_data->store, &iter, i, "", -1);
178
+ }
179
+ gtk_list_store_set(grid_data->store, &iter, col, value.c_str(), -1);
180
+ }
181
+
182
+ return Napi::Boolean::New(env, true);
183
+ }
184
+
185
+ // Función para obtener el valor de una celda
186
+ Napi::Value GetGridCell(const Napi::CallbackInfo& info) {
187
+ Napi::Env env = info.Env();
188
+
189
+ if (info.Length() < 3 || !info[0].IsObject() || !info[1].IsNumber() ||
190
+ !info[2].IsNumber()) {
191
+ Napi::TypeError::New(env, "Se requiere un objeto widget, fila y columna").ThrowAsJavaScriptException();
192
+ return Napi::String::New(env, "");
193
+ }
194
+
195
+ Napi::Object widgetObj = info[0].As<Napi::Object>();
196
+ int row = info[1].As<Napi::Number>().Int32Value();
197
+ int col = info[2].As<Napi::Number>().Int32Value();
198
+
199
+ if (!widgetObj.HasOwnProperty("widget")) {
200
+ Napi::TypeError::New(env, "El objeto no contiene un widget válido").ThrowAsJavaScriptException();
201
+ return Napi::String::New(env, "");
202
+ }
203
+
204
+ Napi::External<GtkWidget> widgetExt = widgetObj.Get("widget").As<Napi::External<GtkWidget>>();
205
+ GtkWidget* treeview = widgetExt.Data();
206
+
207
+ StringGridData* grid_data = static_cast<StringGridData*>(
208
+ g_object_get_data(G_OBJECT(treeview), "stringgrid_data"));
209
+
210
+ if (!grid_data || !grid_data->store) {
211
+ Napi::TypeError::New(env, "Datos del grid no encontrados").ThrowAsJavaScriptException();
212
+ return Napi::String::New(env, "");
213
+ }
214
+
215
+ // Buscar la fila específica
216
+ GtkTreeIter iter;
217
+ gboolean valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(grid_data->store), &iter);
218
+ int current_row = 0;
219
+
220
+ while (valid && current_row < row) {
221
+ valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(grid_data->store), &iter);
222
+ current_row++;
223
+ }
224
+
225
+ if (valid && current_row == row) {
226
+ GValue value = {0};
227
+ gtk_tree_model_get_value(GTK_TREE_MODEL(grid_data->store), &iter, col, &value);
228
+ const gchar* cell_value = g_value_get_string(&value);
229
+ g_value_unset(&value);
230
+ return Napi::String::New(env, cell_value ? cell_value : "");
231
+ }
232
+
233
+ return Napi::String::New(env, "");
234
+ }
235
+
236
+ // Función para establecer encabezados
237
+ Napi::Value SetGridHeader(const Napi::CallbackInfo& info) {
238
+ Napi::Env env = info.Env();
239
+
240
+ if (info.Length() < 2 || !info[0].IsObject() || !info[1].IsArray()) {
241
+ Napi::TypeError::New(env, "Se requiere un objeto widget y un array de encabezados").ThrowAsJavaScriptException();
242
+ return Napi::Boolean::New(env, false);
243
+ }
244
+
245
+ Napi::Object widgetObj = info[0].As<Napi::Object>();
246
+ Napi::Array headersArray = info[1].As<Napi::Array>();
247
+
248
+ if (!widgetObj.HasOwnProperty("widget")) {
249
+ Napi::TypeError::New(env, "El objeto no contiene un widget válido").ThrowAsJavaScriptException();
250
+ return Napi::Boolean::New(env, false);
251
+ }
252
+
253
+ Napi::External<GtkWidget> widgetExt = widgetObj.Get("widget").As<Napi::External<GtkWidget>>();
254
+ GtkWidget* treeview = widgetExt.Data();
255
+
256
+ StringGridData* grid_data = static_cast<StringGridData*>(
257
+ g_object_get_data(G_OBJECT(treeview), "stringgrid_data"));
258
+
259
+ if (!grid_data) {
260
+ Napi::TypeError::New(env, "Datos del grid no encontrados").ThrowAsJavaScriptException();
261
+ return Napi::Boolean::New(env, false);
262
+ }
263
+
264
+ // Actualizar los encabezados
265
+ guint n_columns = gtk_tree_view_get_n_columns(GTK_TREE_VIEW(treeview));
266
+ guint array_length = headersArray.Length();
267
+
268
+ for (guint i = 0; i < n_columns && i < array_length; i++) {
269
+ Napi::Value headerValue = headersArray.Get(i);
270
+ std::string header = headerValue.As<Napi::String>().Utf8Value();
271
+
272
+ GtkTreeViewColumn* column = gtk_tree_view_get_column(GTK_TREE_VIEW(treeview), i);
273
+ if (column) {
274
+ gtk_tree_view_column_set_title(column, header.c_str());
275
+ }
276
+ }
277
+
278
+ return Napi::Boolean::New(env, true);
279
+ }
280
+
281
+ // Función para agregar una fila
282
+ Napi::Value AddGridRow(const Napi::CallbackInfo& info) {
283
+ Napi::Env env = info.Env();
284
+
285
+ if (info.Length() < 2 || !info[0].IsObject() || !info[1].IsArray()) {
286
+ Napi::TypeError::New(env, "Se requiere un objeto widget y un array de datos").ThrowAsJavaScriptException();
287
+ return Napi::Boolean::New(env, false);
288
+ }
289
+
290
+ Napi::Object widgetObj = info[0].As<Napi::Object>();
291
+ Napi::Array dataArray = info[1].As<Napi::Array>();
292
+
293
+ if (!widgetObj.HasOwnProperty("widget")) {
294
+ Napi::TypeError::New(env, "El objeto no contiene un widget válido").ThrowAsJavaScriptException();
295
+ return Napi::Boolean::New(env, false);
296
+ }
297
+
298
+ Napi::External<GtkWidget> widgetExt = widgetObj.Get("widget").As<Napi::External<GtkWidget>>();
299
+ GtkWidget* treeview = widgetExt.Data();
300
+
301
+ StringGridData* grid_data = static_cast<StringGridData*>(
302
+ g_object_get_data(G_OBJECT(treeview), "stringgrid_data"));
303
+
304
+ if (!grid_data || !grid_data->store) {
305
+ Napi::TypeError::New(env, "Datos del grid no encontrados").ThrowAsJavaScriptException();
306
+ return Napi::Boolean::New(env, false);
307
+ }
308
+
309
+ // Crear una nueva fila
310
+ GtkTreeIter iter;
311
+ gtk_list_store_append(grid_data->store, &iter);
312
+
313
+ // Establecer los valores para cada columna
314
+ guint array_length = dataArray.Length();
315
+ guint n_cols = gtk_tree_model_get_n_columns(GTK_TREE_MODEL(grid_data->store));
316
+
317
+ for (guint i = 0; i < n_cols && i < array_length; i++) {
318
+ Napi::Value cellValue = dataArray.Get(i);
319
+ std::string cellStr = cellValue.As<Napi::String>().Utf8Value();
320
+ gtk_list_store_set(grid_data->store, &iter, i, cellStr.c_str(), -1);
321
+ }
322
+
323
+ return Napi::Boolean::New(env, true);
324
+ }
325
+
326
+ // Función para obtener la fila/columna seleccionada
327
+ Napi::Value GetSelectedCell(const Napi::CallbackInfo& info) {
328
+ Napi::Env env = info.Env();
329
+
330
+ if (info.Length() < 1 || !info[0].IsObject()) {
331
+ Napi::TypeError::New(env, "Se requiere un objeto widget").ThrowAsJavaScriptException();
332
+ return Napi::Object::New(env);
333
+ }
334
+
335
+ Napi::Object widgetObj = info[0].As<Napi::Object>();
336
+
337
+ if (!widgetObj.HasOwnProperty("widget")) {
338
+ Napi::TypeError::New(env, "El objeto no contiene un widget válido").ThrowAsJavaScriptException();
339
+ return Napi::Object::New(env);
340
+ }
341
+
342
+ Napi::External<GtkWidget> widgetExt = widgetObj.Get("widget").As<Napi::External<GtkWidget>>();
343
+ GtkWidget* treeview = widgetExt.Data();
344
+
345
+ StringGridData* grid_data = static_cast<StringGridData*>(
346
+ g_object_get_data(G_OBJECT(treeview), "stringgrid_data"));
347
+
348
+ if (!grid_data) {
349
+ Napi::TypeError::New(env, "Datos del grid no encontrados").ThrowAsJavaScriptException();
350
+ return Napi::Object::New(env);
351
+ }
352
+
353
+ // Obtener la selección
354
+ GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
355
+
356
+ if (gtk_tree_selection_get_selected(selection, NULL, NULL)) {
357
+ GtkTreeModel* model;
358
+ GtkTreeIter iter;
359
+
360
+ if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
361
+ // Para encontrar el índice de la fila, usaremos gtk_tree_model_get_path
362
+ GtkTreePath* path = gtk_tree_model_get_path(model, &iter);
363
+ gint* indices = gtk_tree_path_get_indices(path);
364
+ gint row_index = -1;
365
+
366
+ if (indices != NULL) {
367
+ row_index = indices[0]; // El primer índice es el número de fila
368
+ }
369
+
370
+ gtk_tree_path_free(path);
371
+
372
+ // Crear un objeto con la información de la selección
373
+ Napi::Object result = Napi::Object::New(env);
374
+ result.Set("row", Napi::Number::New(env, row_index));
375
+ result.Set("col", Napi::Number::New(env, -1)); // GTK TreeView no proporciona fácilmente la columna seleccionada
376
+
377
+ return result;
378
+ }
379
+ }
380
+
381
+ // Si no hay selección, retornar un objeto vacío
382
+ Napi::Object result = Napi::Object::New(env);
383
+ result.Set("row", Napi::Number::New(env, -1));
384
+ result.Set("col", Napi::Number::New(env, -1));
385
+
386
+ return result;
387
+ }
388
+
389
+ // Función para conectar un callback cuando se edita una celda
390
+ static void cell_edited_callback_with_context(GtkCellRendererText *renderer, gchar *path_string, gchar *new_text, gpointer user_data) {
391
+ StringGridData* grid_data = static_cast<StringGridData*>(user_data);
392
+
393
+ // Convertir la cadena de ruta a un GtkTreePath
394
+ GtkTreePath* path = gtk_tree_path_new_from_string(path_string);
395
+
396
+ // Obtener el iterador para la fila
397
+ GtkTreeIter iter;
398
+ if (gtk_tree_model_get_iter(GTK_TREE_MODEL(grid_data->store), &iter, path)) {
399
+ // Determinar qué columna fue editada
400
+ gint n_columns = gtk_tree_view_get_n_columns(GTK_TREE_VIEW(grid_data->treeview));
401
+ for (gint i = 0; i < n_columns; i++) {
402
+ GtkTreeViewColumn* col = gtk_tree_view_get_column(GTK_TREE_VIEW(grid_data->treeview), i);
403
+ GList* renderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(col));
404
+
405
+ if (renderers) {
406
+ GtkCellRenderer* renderer_in_col = (GtkCellRenderer*)renderers->data;
407
+ if (renderer_in_col == (GtkCellRenderer*)renderer) {
408
+ // Actualizar el valor en la tienda
409
+ gtk_list_store_set(grid_data->store, &iter, i, new_text, -1);
410
+
411
+ // Si hay un callback registrado, llamarlo
412
+ if (!grid_data->cell_edit_callback.IsEmpty()) {
413
+ Napi::HandleScope scope(grid_data->env);
414
+
415
+ // Crear objeto con información de la edición
416
+ Napi::Object editInfo = Napi::Object::New(grid_data->env);
417
+ editInfo.Set("row", Napi::Number::New(grid_data->env, gtk_tree_path_get_indices(path)[0]));
418
+ editInfo.Set("col", Napi::Number::New(grid_data->env, i));
419
+ editInfo.Set("newValue", Napi::String::New(grid_data->env, new_text));
420
+
421
+ grid_data->cell_edit_callback.Call({editInfo});
422
+ }
423
+ break;
424
+ }
425
+ g_list_free(renderers);
426
+ }
427
+ }
428
+ }
429
+
430
+ gtk_tree_path_free(path);
431
+ }
432
+
433
+ // Función para registrar un callback de edición de celda
434
+ Napi::Value RegisterCellEditCallback(const Napi::CallbackInfo& info) {
435
+ Napi::Env env = info.Env();
436
+
437
+ if (info.Length() < 2 || !info[0].IsObject() || !info[1].IsFunction()) {
438
+ Napi::TypeError::New(env, "Se requiere un objeto widget y una función callback").ThrowAsJavaScriptException();
439
+ return Napi::Boolean::New(env, false);
440
+ }
441
+
442
+ Napi::Object widgetObj = info[0].As<Napi::Object>();
443
+ Napi::Function callback = info[1].As<Napi::Function>();
444
+
445
+ if (!widgetObj.HasOwnProperty("widget")) {
446
+ Napi::TypeError::New(env, "El objeto no contiene un widget válido").ThrowAsJavaScriptException();
447
+ return Napi::Boolean::New(env, false);
448
+ }
449
+
450
+ Napi::External<GtkWidget> widgetExt = widgetObj.Get("widget").As<Napi::External<GtkWidget>>();
451
+ GtkWidget* treeview = widgetExt.Data();
452
+
453
+ StringGridData* grid_data = static_cast<StringGridData*>(
454
+ g_object_get_data(G_OBJECT(treeview), "stringgrid_data"));
455
+
456
+ if (!grid_data) {
457
+ Napi::TypeError::New(env, "Datos del grid no encontrados").ThrowAsJavaScriptException();
458
+ return Napi::Boolean::New(env, false);
459
+ }
460
+
461
+ // Limpiar callback anterior si existe
462
+ if (!grid_data->cell_edit_callback.IsEmpty()) {
463
+ grid_data->cell_edit_callback.Reset();
464
+ }
465
+
466
+ // Almacenar el nuevo callback
467
+ grid_data->cell_edit_callback = Napi::Persistent(callback);
468
+
469
+ // Conectar el callback de edición a todos los renderers
470
+ gint n_columns = gtk_tree_view_get_n_columns(GTK_TREE_VIEW(treeview));
471
+ for (gint i = 0; i < n_columns; i++) {
472
+ GtkTreeViewColumn* col = gtk_tree_view_get_column(GTK_TREE_VIEW(treeview), i);
473
+ GList* renderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(col));
474
+
475
+ if (renderers) {
476
+ GtkCellRenderer* renderer = (GtkCellRenderer*)renderers->data;
477
+ g_signal_handlers_disconnect_by_func(renderer, (gpointer)cell_edited_callback, NULL);
478
+ g_signal_connect(renderer, "edited", G_CALLBACK(cell_edited_callback_with_context), grid_data);
479
+ g_list_free(renderers);
480
+ }
481
+ }
482
+
483
+ return Napi::Boolean::New(env, true);
484
+ }
485
+
486
+ Napi::Object InitStringGrid(Napi::Env env, Napi::Object exports) {
487
+ exports.Set("createStringGrid", Napi::Function::New(env, CreateStringGrid));
488
+ exports.Set("setGridCellValue", Napi::Function::New(env, SetGridCellValue));
489
+ exports.Set("getGridCell", Napi::Function::New(env, GetGridCell));
490
+ exports.Set("setGridHeader", Napi::Function::New(env, SetGridHeader));
491
+ exports.Set("addGridRow", Napi::Function::New(env, AddGridRow));
492
+ exports.Set("getSelectedCell", Napi::Function::New(env, GetSelectedCell));
493
+ exports.Set("registerCellEditCallback", Napi::Function::New(env, RegisterCellEditCallback));
494
+
495
+ return exports;
496
+ }
@@ -0,0 +1,10 @@
1
+ #ifndef STRINGGRID_H
2
+ #define STRINGGRID_H
3
+
4
+ #include <napi.h>
5
+ #include <gtk/gtk.h>
6
+
7
+ // Función para inicializar el widget StringGrid
8
+ Napi::Object InitStringGrid(Napi::Env env, Napi::Object exports);
9
+
10
+ #endif // STRINGGRID_H
@@ -0,0 +1,48 @@
1
+ # Widget TextView
2
+
3
+ El widget TextView es parte de la colección de widgets para la extensión GTK3 para Node.js. Proporciona un área de texto multilinea para entrada y visualización de grandes cantidades de texto.
4
+
5
+ ## Características
6
+
7
+ - Área de texto multilinea para entrada de texto largo
8
+ - Soporta desplazamiento para contenido extenso
9
+ - Compatible con el principio DRY
10
+ - Fácil integración con otros widgets
11
+
12
+ ## API
13
+
14
+ ### Constructor
15
+ - `new TextView(textoInicial)` - Crea un nuevo área de texto con texto opcional inicial
16
+
17
+ ### Propiedades
18
+ - `text` - El texto actual en el área de texto (getter/setter)
19
+
20
+ ### Métodos
21
+ - `setText(texto)` - Establece el texto en el área de texto
22
+ - `getText()` - Obtiene el texto actual del área de texto
23
+ - `appendText(texto)` - Añade texto al final del contenido existente
24
+ - `insertText(posición, texto)` - Inserta texto en una posición específica
25
+
26
+ ## Ejemplo de Uso
27
+
28
+ ```javascript
29
+ const { TextView, Window, init, run } = require('../../index.js');
30
+
31
+ init();
32
+ const window = new Window('Ejemplo TextView', 400, 300);
33
+
34
+ const textView = new TextView('Texto inicial\\ncon múltiples\\nlíneas');
35
+ window.add(textView);
36
+ window.show();
37
+
38
+ // Leer el valor del textView
39
+ setTimeout(() => {
40
+ console.log('Contenido del TextView:', textView.text);
41
+ }, 2000);
42
+
43
+ run();
44
+ ```
45
+
46
+ ## Principio DRY
47
+
48
+ Este widget sigue el principio DRY (Don't Repeat Yourself) al reutilizar patrones comunes de implementación de widgets en la extensión GTK3 para Node.js.