I’m currently making a line-per-line text editor in C using GTK 4.8.3. It’s my first program ever using GTK 4 so I’m not comfortable with it.
To begin with, a text file is parsed into the following structures :
typedef struct Line {
char *text;
int id;
} Line;
typedef struct LineNode {
Line *line;
struct LineNode *next;
} LineNode;
typedef struct File {
char *filename;
Client *host_client; // not needed for this question
LineNode *lines;
int line_count;
ClientNode *clients; // not needed for this question
int client_count; // not needed for this question
} File;
The main part of my GUI is composed of a GtkListBox
in which each row contains a GtkTextView
that displays the text of the corresponding line. I would like to retrieve a pointer to the corresponding LineNode
when I select a row.
I tried using GtkListStore
and GtkMapListModel
to associate each row with a LineNode
but I couldn’t manage to make it work. The problem I encountered with GtkListStore
is that I couldn’t bind my store with my box since gtk_list_box_bind_model()
is only for GListModel
. Then, the issue I had with GtkMapListModel
is that I couldn’t manage to add data to the model.
I “bypassed” this issue by making using a function that gets a pointer to a LineNode
of a row by text. This “solution” is stupid because it doesn’t work as inteded when fetching an empty line or if the text is the same on multiple lines.
Here are the 2 most important functions :
void on_row_selected(GtkListBox *listbox, GtkListBoxRow *row, FileEditorWindow *win)
{
GtkLabel *lineSelectedLabel;
GtkWidget *textView;
File *file_struct = get_file_struct_from_filename(gtk_stack_get_visible_child_name(GTK_STACK(win->stack)));
lineSelectedLabel = GTK_LABEL(gtk_grid_get_child_at(GTK_GRID(win->grid), 0, 0));
if(row){
int index = gtk_list_box_row_get_index(row);
char *label_text = g_strdup_printf("Line selected: %d", index);
gtk_label_set_text(lineSelectedLabel, label_text);
g_free(label_text);
}
else{
lineSelectedLabel = GTK_LABEL(gtk_label_new("No line selected"));
}
// test : get LineNode from text
textView = gtk_widget_get_first_child(GTK_WIDGET(row));
// there's only one child in the row so we can safely cast it to a GtkTextView
if(GTK_IS_TEXT_VIEW(textView)) {
GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textView));
GtkTextIter start, end;
gtk_text_buffer_get_start_iter(buffer, &start);
gtk_text_buffer_get_end_iter(buffer, &end);
gchar *text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
LineNode *lineNode = get_line_node_from_text(file_struct, text); // calling the dumb function - NOT GOOD
if(lineNode) {
g_print("Line found: %sn", lineNode->line->text);
selected_line = lineNode->line;
}
else {
g_print("Line not foundn");
}
}
}
void file_editor_window_open(FileEditorWindow *win, GFile *file)
{
char *filepath;
GtkWidget *scrolled;
GtkTextBuffer *buffer;
char *contents;
gsize length;
GtkTextTag *tag;
GtkButton *add_btn, *edit_btn, *delete_btn;
// BUTTONS
add_btn = GTK_BUTTON(gtk_grid_get_child_at(GTK_GRID(win->grid), 1, 0));
edit_btn = GTK_BUTTON(gtk_grid_get_child_at(GTK_GRID(win->grid), 2, 0));
delete_btn = GTK_BUTTON(gtk_grid_get_child_at(GTK_GRID(win->grid), 3, 0));
gtk_widget_set_visible(GTK_WIDGET(add_btn), TRUE);
gtk_widget_set_visible(GTK_WIDGET(edit_btn), TRUE);
gtk_widget_set_visible(GTK_WIDGET(delete_btn), TRUE);
// g_signal_connect(add_btn, "clicked", G_CALLBACK(on_add_clicked), win);
// g_signal_connect(edit_btn, "clicked", G_CALLBACK(on_edit_clicked), win);
g_signal_connect(delete_btn, "clicked", G_CALLBACK(on_delete_clicked), win);
// FILE
filepath = g_file_get_path(file);
File *file_struct;
int i;
for(i = 0; i < MAX_FILES; i++) {
if(!open_files[i]) {
file_struct = open_files[i] = open_local_file(filepath);
break;
}
if(i == MAX_FILES - 1) {
// dialog error
GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(win), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "Too many files opened");
gtk_window_present(GTK_WINDOW(dialog));
g_free(filepath);
return;
}
}
// LISTBOX STUFF
scrolled = gtk_scrolled_window_new();
gtk_widget_set_hexpand(scrolled, TRUE);
gtk_widget_set_vexpand(scrolled, TRUE);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
GtkWidget *listbox = gtk_list_box_new();
gtk_list_box_set_selection_mode(GTK_LIST_BOX(listbox), GTK_SELECTION_SINGLE);
g_signal_connect(listbox, "row-selected", G_CALLBACK(on_row_selected), win);
listboxes[i] = GTK_LIST_BOX(listbox);
LineNode *curr = file_struct->lines;
while(curr) {
Line *line = curr->line;
buffer = gtk_text_buffer_new(NULL);
gtk_text_buffer_set_text(buffer, line->text, -1);
tag = gtk_text_buffer_create_tag(buffer, NULL, NULL);
g_settings_bind(win->settings, "font", tag, "font", G_SETTINGS_BIND_DEFAULT);
GtkWidget *view = gtk_text_view_new_with_buffer(buffer);
gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE);
GtkWidget *row = gtk_list_box_row_new();
gtk_widget_set_hexpand(view, TRUE);
gtk_widget_set_vexpand(view, FALSE);
gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), view);
gtk_list_box_append(GTK_LIST_BOX(listbox), row);
g_object_unref(buffer);
curr = curr->next;
}
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled), listbox);
gtk_stack_add_titled(GTK_STACK(win->stack), scrolled, file_struct->filename, file_struct->filename);
g_free(filepath);
}
I’ve browsed the documentation and multiple forum posts but I’m still so confused. That would be great if some of you could explain how GtkListBox
works in that case. Thank you very much!