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.
- package/CHANGELOG.md +2 -0
- package/CHECKLIST.md +30 -7
- package/binding.gyp +59 -0
- package/ejemplos/avanzados/README.md +2 -0
- package/ejemplos/avanzados/ejemplo_stringgrid_completo.js +95 -0
- package/ejemplos/avanzados/ejemplo_textview.js +65 -0
- package/ejemplos/intermedios/README.md +1 -0
- package/ejemplos/intermedios/ejemplo_entry.js +50 -0
- package/index.js +85 -0
- package/package.json +5 -1
- package/src/button.cpp +146 -0
- package/src/gtk3_node.cpp +162 -0
- package/widgets/box/README.md +42 -0
- package/widgets/box/box.cpp +77 -0
- package/widgets/box/box.h +10 -0
- package/widgets/entry/README.md +46 -0
- package/widgets/entry/entry.cpp +98 -0
- package/widgets/entry/entry.h +10 -0
- package/widgets/label/README.md +41 -0
- package/widgets/label/label.cpp +95 -0
- package/widgets/label/label.h +10 -0
- package/widgets/scroll/README.md +40 -0
- package/widgets/scroll/scroll.cpp +72 -0
- package/widgets/scroll/scroll.h +10 -0
- package/widgets/stringgrid/README.md +46 -0
- package/widgets/stringgrid/stringgrid.cpp +496 -0
- package/widgets/stringgrid/stringgrid.h +10 -0
- package/widgets/textview/README.md +48 -0
- package/widgets/textview/textview.cpp +209 -0
- package/widgets/textview/textview.h +10 -0
|
@@ -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,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,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.
|