From 3165d177ff3c036edad788ccf23acbc26dc9d94e Mon Sep 17 00:00:00 2001 From: gkatev Date: Wed, 17 Apr 2024 12:38:24 +0300 Subject: [PATCH 01/15] codenav/goto_file: Keep dialog open after denying creation of not-found file Previously, if the file wasn't found and you replied 'no' to the prompt for creating it, all dialogs closed. This can be annoying in situations where the name/path was entered wrong, and you have to start all over again after receiving the file not found error. This commit allows one to amend the mistake from right where they left off. It was deemed necessary to add GTK_DIALOG_MODAL flag to the dialog creation. Without it, after the repeat gtk_dialog_run call (through the goto), the UI got kinda bugged, with the focus (correctly) being in the dialog, but visually appearing as if it had shifted back to the editor pane. --- codenav/src/goto_file.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/codenav/src/goto_file.c b/codenav/src/goto_file.c index 1da44ec17..77658e69e 100644 --- a/codenav/src/goto_file.c +++ b/codenav/src/goto_file.c @@ -226,8 +226,8 @@ create_dialog(GtkWidget **dialog, GtkTreeModel *completion_model) GtkEntryCompletion *completion; *dialog = gtk_dialog_new_with_buttons(_("Go to File..."), GTK_WINDOW(geany->main_widgets->window), - GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL); + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL); gtk_dialog_set_default_response(GTK_DIALOG(*dialog), GTK_RESPONSE_ACCEPT); @@ -292,6 +292,8 @@ menu_item_activate(guint key_id) /* Create the user dialog and get response */ dialog_entry = create_dialog(&dialog, completion_list); + +_show_dialog: response = gtk_dialog_run(GTK_DIALOG(dialog)); /* Filename */ @@ -312,12 +314,20 @@ menu_item_activate(guint key_id) GTK_BUTTONS_OK_CANCEL, _("%s not found, create it?"), chosen_file); gtk_window_set_title(GTK_WINDOW(dialog_new), "Geany"); - if(gtk_dialog_run(GTK_DIALOG(dialog_new)) == GTK_RESPONSE_OK) + response = gtk_dialog_run(GTK_DIALOG(dialog_new)); + + if (response == GTK_RESPONSE_OK) { document_new_file(chosen_path, current_doc->file_type, NULL); document_set_text_changed(document_get_current(), TRUE); } + gtk_widget_destroy(dialog_new); + + /* File wasn't found and user denied creating it, + * go back to the initial "Go to file" dialog. */ + if (response != GTK_RESPONSE_OK) + goto _show_dialog; } else document_open_file(chosen_path, FALSE, NULL, NULL); From c5c0c95d4f0de57cc58848b88fa293c0beb8a3b7 Mon Sep 17 00:00:00 2001 From: gkatev Date: Sun, 12 May 2024 00:41:03 +0300 Subject: [PATCH 02/15] codenav/goto_file: Fix absolute paths --- codenav/src/goto_file.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/codenav/src/goto_file.c b/codenav/src/goto_file.c index 77658e69e..f352e4874 100644 --- a/codenav/src/goto_file.c +++ b/codenav/src/goto_file.c @@ -296,10 +296,14 @@ menu_item_activate(guint key_id) _show_dialog: response = gtk_dialog_run(GTK_DIALOG(dialog)); - /* Filename */ + /* Entered filename */ chosen_file = gtk_entry_get_text(GTK_ENTRY(dialog_entry)); - /* Path + Filename */ - chosen_path = g_build_filename(directory_ref, chosen_file, NULL); + + /* Filename as-is, if it's absolute, otherwise relative to the doc dir */ + if (g_path_is_absolute(chosen_file)) + chosen_path = g_strdup(chosen_file); + else + chosen_path = g_build_filename(directory_ref, chosen_file, NULL); if ( response == GTK_RESPONSE_ACCEPT ) { @@ -312,7 +316,7 @@ menu_item_activate(guint key_id) GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL, - _("%s not found, create it?"), chosen_file); + _("%s not found, create it?"), chosen_path); gtk_window_set_title(GTK_WINDOW(dialog_new), "Geany"); response = gtk_dialog_run(GTK_DIALOG(dialog_new)); @@ -327,7 +331,10 @@ menu_item_activate(guint key_id) /* File wasn't found and user denied creating it, * go back to the initial "Go to file" dialog. */ if (response != GTK_RESPONSE_OK) + { + g_free(chosen_path); goto _show_dialog; + } } else document_open_file(chosen_path, FALSE, NULL, NULL); @@ -336,5 +343,6 @@ menu_item_activate(guint key_id) /* Freeing memory */ gtk_widget_destroy(dialog); g_free(directory_ref); + g_free(chosen_path); g_object_unref (completion_list); } From 0e65b4b86644085ba8e87095177a2523e0e85297 Mon Sep 17 00:00:00 2001 From: gkatev Date: Sun, 12 May 2024 01:37:40 +0300 Subject: [PATCH 03/15] codenav/goto_file: Now works with no document open The user's home directory is used as a relative path reference. --- codenav/src/goto_file.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/codenav/src/goto_file.c b/codenav/src/goto_file.c index f352e4874..48900240a 100644 --- a/codenav/src/goto_file.c +++ b/codenav/src/goto_file.c @@ -283,11 +283,12 @@ menu_item_activate(guint key_id) log_func(); - if(current_doc == NULL || current_doc->file_name == NULL || current_doc->file_name[0] == '\0') - return; - + if(current_doc && !EMPTY(current_doc->file_name)) + directory_ref = g_path_get_dirname(current_doc->file_name); + else + directory_ref = g_strdup(g_get_home_dir()); + /* Build current directory listing */ - directory_ref = g_path_get_dirname(current_doc->file_name); completion_list = build_file_list(directory_ref, ""); /* Create the user dialog and get response */ @@ -322,7 +323,8 @@ menu_item_activate(guint key_id) if (response == GTK_RESPONSE_OK) { - document_new_file(chosen_path, current_doc->file_type, NULL); + document_new_file(chosen_path, (current_doc ? + current_doc->file_type : NULL), NULL); document_set_text_changed(document_get_current(), TRUE); } From 6f7ce66354604a21f7e236a576caba9765485788 Mon Sep 17 00:00:00 2001 From: gkatev Date: Tue, 11 Jun 2024 18:43:04 +0300 Subject: [PATCH 04/15] codenav/goto_file: Fix whitespace inconsistencies (mainly indentation) --- codenav/src/goto_file.c | 155 ++++++++++++++++++++-------------------- 1 file changed, 77 insertions(+), 78 deletions(-) diff --git a/codenav/src/goto_file.c b/codenav/src/goto_file.c index 48900240a..50c418329 100644 --- a/codenav/src/goto_file.c +++ b/codenav/src/goto_file.c @@ -109,31 +109,31 @@ build_file_list(const gchar* dirname, const gchar* prefix) GtkListStore *ret_list; GtkTreeIter iter; ret_list = gtk_list_store_new (1, G_TYPE_STRING); - + GSList* file_iterator; - GSList* files_list; /* used to free later the sub-elements*/ + GSList* files_list; /* used to later free the sub-elements */ gchar *file; gchar *pathfile; guint files_n; files_list = file_iterator = utils_get_file_list(dirname, &files_n, NULL); - + for( ; file_iterator; file_iterator = file_iterator->next) { - file = file_iterator->data; - + file = file_iterator->data; + pathfile = g_build_filename(dirname,file,NULL); - - /* Append the element to model list */ - gtk_list_store_append (ret_list, &iter); - gtk_list_store_set (ret_list, &iter, 0, - g_strconcat(prefix, file, NULL), -1); + + /* Append the element to model list */ + gtk_list_store_append (ret_list, &iter); + gtk_list_store_set (ret_list, &iter, 0, + g_strconcat(prefix, file, NULL), -1); g_free(pathfile); } - + g_slist_foreach(files_list, (GFunc) g_free, NULL); g_slist_free(files_list); - + return GTK_TREE_MODEL(ret_list); } @@ -148,64 +148,64 @@ build_file_list(const gchar* dirname, const gchar* prefix) static void directory_check(GtkEntry* entry, GtkEntryCompletion* completion) { - static GtkTreeModel *old_model = NULL; - GtkTreeModel* completion_list; - static gchar *curr_dir = NULL; - gchar *new_dir, *new_dir_path = NULL; - const gchar *text; - - text = gtk_entry_get_text(entry); - gint dir_sep = strrpos(text, G_DIR_SEPARATOR_S); - - /* No subdir separator found */ - if (dir_sep == -1) - { - if (old_model != NULL) - { /* Restore the no-sub-directory model */ - log_debug("Restoring old model!"); - - gtk_entry_completion_set_model (completion, old_model); - g_object_unref(old_model); - old_model = NULL; - - g_free(curr_dir); - curr_dir = NULL; - } - return; - } - - new_dir = g_strndup (text, dir_sep+1); - /* I've already inserted new model completion for sub-dir elements? */ - if ( g_strcmp0 (new_dir, curr_dir) == 0 ) - { - g_free(new_dir); - return; - } - if ( curr_dir != NULL ) - g_free(curr_dir); - - curr_dir = new_dir; - - /* Save the completion_mode for future restore. */ - if (old_model == NULL) - { - old_model = gtk_entry_completion_get_model(completion); - g_object_ref(old_model); - } - - log_debug("New completion list!"); - - if ( g_path_is_absolute(new_dir) ) - new_dir_path = g_strdup(new_dir); - else - new_dir_path = g_build_filename(directory_ref, new_dir, NULL); - - /* Build the new file list for completion */ - completion_list = build_file_list(new_dir_path, new_dir); - gtk_entry_completion_set_model (completion, completion_list); - g_object_unref(completion_list); - - g_free(new_dir_path); + static GtkTreeModel *old_model = NULL; + GtkTreeModel* completion_list; + static gchar *curr_dir = NULL; + gchar *new_dir, *new_dir_path = NULL; + const gchar *text; + + text = gtk_entry_get_text(entry); + gint dir_sep = strrpos(text, G_DIR_SEPARATOR_S); + + /* No subdir separator found */ + if (dir_sep == -1) + { + if (old_model != NULL) + { /* Restore the no-sub-directory model */ + log_debug("Restoring old model!"); + + gtk_entry_completion_set_model (completion, old_model); + g_object_unref(old_model); + old_model = NULL; + + g_free(curr_dir); + curr_dir = NULL; + } + return; + } + + new_dir = g_strndup (text, dir_sep+1); + /* I've already inserted new model completion for sub-dir elements? */ + if ( g_strcmp0 (new_dir, curr_dir) == 0 ) + { + g_free(new_dir); + return; + } + if ( curr_dir != NULL ) + g_free(curr_dir); + + curr_dir = new_dir; + + /* Save the completion_mode for future restore. */ + if (old_model == NULL) + { + old_model = gtk_entry_completion_get_model(completion); + g_object_ref(old_model); + } + + log_debug("New completion list!"); + + if ( g_path_is_absolute(new_dir) ) + new_dir_path = g_strdup(new_dir); + else + new_dir_path = g_build_filename(directory_ref, new_dir, NULL); + + /* Build the new file list for completion */ + completion_list = build_file_list(new_dir_path, new_dir); + gtk_entry_completion_set_model (completion, completion_list); + g_object_unref(completion_list); + + g_free(new_dir_path); } @@ -224,13 +224,13 @@ create_dialog(GtkWidget **dialog, GtkTreeModel *completion_model) GtkWidget *label; GtkWidget *vbox; GtkEntryCompletion *completion; - + *dialog = gtk_dialog_new_with_buttons(_("Go to File..."), GTK_WINDOW(geany->main_widgets->window), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL); - + gtk_dialog_set_default_response(GTK_DIALOG(*dialog), GTK_RESPONSE_ACCEPT); - + gtk_widget_set_name(*dialog, "GotoFile"); vbox = ui_dialog_vbox_new(GTK_DIALOG(*dialog)); @@ -244,20 +244,19 @@ create_dialog(GtkWidget **dialog, GtkTreeModel *completion_model) gtk_entry_set_max_length(GTK_ENTRY(entry), MAX_FILENAME_LENGTH); gtk_entry_set_width_chars(GTK_ENTRY(entry), 40); gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE); /* 'enter' key */ - + /* Completion definition */ completion = gtk_entry_completion_new(); gtk_entry_set_completion(GTK_ENTRY(entry), completion); gtk_entry_completion_set_model (completion, completion_model); - + /* Completion options */ gtk_entry_completion_set_inline_completion(completion, 1); gtk_entry_completion_set_text_column (completion, 0); /* Signals */ - g_signal_connect_after(GTK_ENTRY(entry), "changed", - G_CALLBACK(directory_check), completion); - + g_signal_connect_after(GTK_ENTRY(entry), "changed", + G_CALLBACK(directory_check), completion); gtk_widget_show_all(*dialog); return entry; From 7d71089a60932361e34d0e9a51723a5d89210df2 Mon Sep 17 00:00:00 2001 From: gkatev Date: Tue, 11 Jun 2024 18:54:30 +0300 Subject: [PATCH 05/15] codenav/goto_file: Completion enhancements (inspired by GtkFileChooser) 1. Can now use Tab key to 'accept' autocomplete's 'suggestion' 2. Add dir separator to end of items in completion list that are directories, to make traversing a path quicker, especially when used together with (1). 3. Show the completion list as soon as the goto_file dialog opens (at the same directory as the document). The code kind of reads like this is also what the original author intended. --- codenav/src/goto_file.c | 104 +++++++++++++++++++++++++++++++++++----- 1 file changed, 93 insertions(+), 11 deletions(-) diff --git a/codenav/src/goto_file.c b/codenav/src/goto_file.c index 50c418329..8c6bb1328 100644 --- a/codenav/src/goto_file.c +++ b/codenav/src/goto_file.c @@ -32,7 +32,8 @@ /******************* Global variables for the feature *****************/ static GtkWidget* menu_item = NULL; -gchar *directory_ref = NULL; +static gchar *directory_ref = NULL; +static gint entry_len_before_completion = -1; /********************** Prototypes *********************/ static void @@ -44,6 +45,12 @@ build_file_list(const gchar*, const gchar*); static void directory_check(GtkEntry*, GtkEntryCompletion*); +static gboolean +entry_inline_completion_event(GtkEntryCompletion *, gchar *, GtkEntry *); + +static gboolean +entry_key_event(GtkEntry *, GdkEventKey *, GtkEntryCompletion *); + static GtkWidget* create_dialog(GtkWidget**, GtkTreeModel*); @@ -113,7 +120,6 @@ build_file_list(const gchar* dirname, const gchar* prefix) GSList* file_iterator; GSList* files_list; /* used to later free the sub-elements */ gchar *file; - gchar *pathfile; guint files_n; files_list = file_iterator = utils_get_file_list(dirname, &files_n, NULL); @@ -122,20 +128,21 @@ build_file_list(const gchar* dirname, const gchar* prefix) { file = file_iterator->data; - pathfile = g_build_filename(dirname,file,NULL); + gchar *full_path = g_build_filename(dirname, file, NULL); + gboolean is_dir = g_file_test(full_path, G_FILE_TEST_IS_DIR); /* Append the element to model list */ gtk_list_store_append (ret_list, &iter); - gtk_list_store_set (ret_list, &iter, 0, - g_strconcat(prefix, file, NULL), -1); - g_free(pathfile); + gtk_list_store_set (ret_list, &iter, 0, g_strconcat(prefix, + file, (is_dir ? "/" : NULL), NULL), -1); + + g_free(full_path); } g_slist_foreach(files_list, (GFunc) g_free, NULL); g_slist_free(files_list); return GTK_TREE_MODEL(ret_list); - } /** @@ -152,9 +159,19 @@ directory_check(GtkEntry* entry, GtkEntryCompletion* completion) GtkTreeModel* completion_list; static gchar *curr_dir = NULL; gchar *new_dir, *new_dir_path = NULL; - const gchar *text; + gchar *text; + + /* We need to discern between text written by the user and text filled by the + * autocomplete, and to only use what the user wrote for the completion model. + * Otherwise, when a directory is 'suggested', the completion model will start + * traversing it, even though the user has not yet 'accepted' the suggestion. + * One way to tell apart the autocompletion is to check if there is a selection. + * However, the "changed" event triggers before the selection occurs, so no dice. + * Instead, we watch the completion event (insert-prefix) and back-up (in entry_ + * inline_completion_event) the user's text. */ + text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, entry_len_before_completion); + entry_len_before_completion = -1; - text = gtk_entry_get_text(entry); gint dir_sep = strrpos(text, G_DIR_SEPARATOR_S); /* No subdir separator found */ @@ -171,10 +188,13 @@ directory_check(GtkEntry* entry, GtkEntryCompletion* completion) g_free(curr_dir); curr_dir = NULL; } + g_free(text); return; } new_dir = g_strndup (text, dir_sep+1); + g_free(text); + /* I've already inserted new model completion for sub-dir elements? */ if ( g_strcmp0 (new_dir, curr_dir) == 0 ) { @@ -208,6 +228,59 @@ directory_check(GtkEntry* entry, GtkEntryCompletion* completion) g_free(new_dir_path); } +/** + * @brief GtkEntryCompletion insert-prefix callback + * @param GtkEntryCompletion *completion entry completion object + * @param gchar *prefix common prefix of all possible completions + * @param GtkEntry *entry attached data (path entry field object) + * @return gboolean whether the signal has been handled (i.e. run other handlers?) + * + */ +static gboolean +entry_inline_completion_event(GtkEntryCompletion *completion, gchar *prefix, GtkEntry *entry) +{ + entry_len_before_completion = gtk_entry_get_text_length(entry); + return FALSE; +} + +/** + * @brief Widget key callback (path entry field) + * @param GtkEntry *entry + * @param GdkEventKey *key key that triggered event + * @param GtkEntryCompletion *completion attached data (entry completion object) + * @return gboolean whether the signal has been handled (i.e. run other handlers?) + * + */ +static gboolean +entry_key_event(GtkEntry *entry, GdkEventKey *key, GtkEntryCompletion *completion) +{ + if (key->keyval == GDK_KEY_Tab) + { + gint end_pos; + + /* Give the user the ability to 'accept' the autocomplete's suggestion + * with the Tab key, like in shells (and in GtkFileChooser). If there's + * a suggestion, it will be selected, and we can simply move the cursor + * to accept it. */ + + if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), NULL, &end_pos)) + { + gtk_editable_set_position(GTK_EDITABLE(entry), end_pos); + + /* Moving the cursor does not trigger the "changed" event. + * Do it manually, to show completions for the new path. */ + g_signal_emit_by_name(entry, "changed"); + } + + /* Effectively reserve the Tab key for autocompletion purposes, + * so don't let any other handlers run. Do this even when there + * wasn't any actual suggestion, to make it safe for the user + * to 'spam' the key (mimics GtkFileChooser). */ + return TRUE; + } + + return FALSE; +} /** * @brief Create the dialog, return the entry object to get the @@ -253,10 +326,19 @@ create_dialog(GtkWidget **dialog, GtkTreeModel *completion_model) /* Completion options */ gtk_entry_completion_set_inline_completion(completion, 1); gtk_entry_completion_set_text_column (completion, 0); + gtk_entry_completion_set_minimum_key_length(completion, 0); + + /* Manually trigger the provided completion model */ + g_signal_emit_by_name(entry, "changed"); /* Signals */ - g_signal_connect_after(GTK_ENTRY(entry), "changed", - G_CALLBACK(directory_check), completion); + g_signal_connect_after(entry, "changed", + G_CALLBACK(directory_check), completion); + g_signal_connect(completion, "insert-prefix", + G_CALLBACK(entry_inline_completion_event), entry); + g_signal_connect(entry, "key-press-event", + G_CALLBACK(entry_key_event), completion); + gtk_widget_show_all(*dialog); return entry; From 544a5cbca590b9b717c0203c93373324346cce31 Mon Sep 17 00:00:00 2001 From: gkatev Date: Sat, 15 Jun 2024 15:57:31 +0300 Subject: [PATCH 06/15] codenav/goto_file: The Tab key now also triggers inline completion The pre-existing funcionality of using Tab to accept a completion suggestion is still supported. Furthermore, the Enter key may also be used now to accept a suggestion. --- codenav/src/goto_file.c | 61 ++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/codenav/src/goto_file.c b/codenav/src/goto_file.c index 8c6bb1328..e9fb66d84 100644 --- a/codenav/src/goto_file.c +++ b/codenav/src/goto_file.c @@ -239,8 +239,25 @@ directory_check(GtkEntry* entry, GtkEntryCompletion* completion) static gboolean entry_inline_completion_event(GtkEntryCompletion *completion, gchar *prefix, GtkEntry *entry) { - entry_len_before_completion = gtk_entry_get_text_length(entry); - return FALSE; + gint entry_len = gtk_entry_get_text_length(entry); + gint prefix_len = g_utf8_strlen(prefix, -1); + + /* We want to mark the length of the entry before the completion, but + * only if an inline completion is actually about to be suggested. If + * a suggestion is not created, the entry text won't be modified, the + * 'changed' event won't fire, and thus entry_len_before_completion's + * value will be stale, as it won't get cleared in directory_check. */ + if(prefix_len > entry_len) + { + entry_len_before_completion = entry_len; + return FALSE; + } + else + { + /* We know no completion will take place, no reason + * for the default sig handler to be called... */ + return TRUE; + } } /** @@ -254,30 +271,48 @@ entry_inline_completion_event(GtkEntryCompletion *completion, gchar *prefix, Gtk static gboolean entry_key_event(GtkEntry *entry, GdkEventKey *key, GtkEntryCompletion *completion) { - if (key->keyval == GDK_KEY_Tab) - { - gint end_pos; - - /* Give the user the ability to 'accept' the autocomplete's suggestion - * with the Tab key, like in shells (and in GtkFileChooser). If there's - * a suggestion, it will be selected, and we can simply move the cursor - * to accept it. */ + gint end_pos; - if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), NULL, &end_pos)) + if(key->keyval == GDK_KEY_Tab) + { + /* The user may 'accept' the autocomplete's suggestion with the Tab key, + * like in shells, and in GtkFileChoose. If there's a suggestion, it will + * be selected, and we may simply move the cursor to accept it. */ + if(gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), NULL, &end_pos)) { gtk_editable_set_position(GTK_EDITABLE(entry), end_pos); - /* Moving the cursor does not trigger the "changed" event. - * Do it manually, to show completions for the new path. */ + /* Moving the cursor does not trigger the "changed" event. Do it + * manually, to caclulate & show completions for the new path. */ g_signal_emit_by_name(entry, "changed"); } + /* The 2nd purpose of the Tab key is to trigger the procedure that + * calculates and suggests an inline completion. FYI doing this here + * is still useful even if the Tab key was used to accept a suggestion + * above, as it will trigger an attempt for inline completion in the + * new/next directory. */ + gtk_entry_completion_insert_prefix(completion); + /* Effectively reserve the Tab key for autocompletion purposes, * so don't let any other handlers run. Do this even when there * wasn't any actual suggestion, to make it safe for the user * to 'spam' the key (mimics GtkFileChooser). */ return TRUE; } + else if(key->keyval == GDK_KEY_Return) + { + if(gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), NULL, &end_pos)) + { + gtk_editable_set_position(GTK_EDITABLE(entry), end_pos); + g_signal_emit_by_name(entry, "changed"); + gtk_entry_completion_insert_prefix(completion); + + /* Unlike with the Tab key, only intercept Enter + * when there was a completion available. */ + return TRUE; + } + } return FALSE; } From 6566bc500c6109305f3f292188da2ddbbf45e162 Mon Sep 17 00:00:00 2001 From: gkatev Date: Sun, 16 Jun 2024 01:41:37 +0300 Subject: [PATCH 07/15] codenav/goto_file: Fix ref counting of entry completion object --- codenav/src/goto_file.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/codenav/src/goto_file.c b/codenav/src/goto_file.c index e9fb66d84..664871b6c 100644 --- a/codenav/src/goto_file.c +++ b/codenav/src/goto_file.c @@ -374,6 +374,10 @@ create_dialog(GtkWidget **dialog, GtkTreeModel *completion_model) g_signal_connect(entry, "key-press-event", G_CALLBACK(entry_key_event), completion); + /* The completion object is tracked in the entry. We may release our local + * reference, and it will be deallocated when the entry is destroyed. */ + g_object_unref(completion); + gtk_widget_show_all(*dialog); return entry; From cfe8b936165e155b4b19cde06227d6db08c60549 Mon Sep 17 00:00:00 2001 From: gkatev Date: Sun, 16 Jun 2024 01:44:08 +0300 Subject: [PATCH 08/15] codenav/goto_file: Simplify reference model handling The way the completion model to use when no directory separator is found is unecessarily convoluted. This change simplifies and improves it. Also fixes a bug that can cause junky behaviour when pasting a path as soon as the dialog opens, if that was also the last path in the dialog the last time it was opened. The root cause is that the completion model is set during init to the reference directory, but directory_check() does not create a new one for the pasted path, because curr_dir (which is static) has not been updated to reflect that the reference mode is now active. --- codenav/src/goto_file.c | 64 ++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/codenav/src/goto_file.c b/codenav/src/goto_file.c index 664871b6c..71f5e4bb8 100644 --- a/codenav/src/goto_file.c +++ b/codenav/src/goto_file.c @@ -32,7 +32,9 @@ /******************* Global variables for the feature *****************/ static GtkWidget* menu_item = NULL; -static gchar *directory_ref = NULL; +static GtkTreeModel *ref_model = NULL; +static gchar *ref_dir = NULL; +static gchar *curr_dir = NULL; static gint entry_len_before_completion = -1; /********************** Prototypes *********************/ @@ -155,10 +157,8 @@ build_file_list(const gchar* dirname, const gchar* prefix) static void directory_check(GtkEntry* entry, GtkEntryCompletion* completion) { - static GtkTreeModel *old_model = NULL; GtkTreeModel* completion_list; - static gchar *curr_dir = NULL; - gchar *new_dir, *new_dir_path = NULL; + gchar *new_dir, *new_dir_path; gchar *text; /* We need to discern between text written by the user and text filled by the @@ -177,17 +177,15 @@ directory_check(GtkEntry* entry, GtkEntryCompletion* completion) /* No subdir separator found */ if (dir_sep == -1) { - if (old_model != NULL) - { /* Restore the no-sub-directory model */ - log_debug("Restoring old model!"); - - gtk_entry_completion_set_model (completion, old_model); - g_object_unref(old_model); - old_model = NULL; + // Restore the no-sub-directory model + if(g_strcmp0(ref_dir, curr_dir) != 0) + { + gtk_entry_completion_set_model(completion, ref_model); g_free(curr_dir); - curr_dir = NULL; + curr_dir = g_strdup(ref_dir); } + g_free(text); return; } @@ -195,36 +193,29 @@ directory_check(GtkEntry* entry, GtkEntryCompletion* completion) new_dir = g_strndup (text, dir_sep+1); g_free(text); - /* I've already inserted new model completion for sub-dir elements? */ + /* If this the same dir as the one of the active + * completion model, no further steps required. */ if ( g_strcmp0 (new_dir, curr_dir) == 0 ) { g_free(new_dir); return; } - if ( curr_dir != NULL ) - g_free(curr_dir); - - curr_dir = new_dir; - - /* Save the completion_mode for future restore. */ - if (old_model == NULL) - { - old_model = gtk_entry_completion_get_model(completion); - g_object_ref(old_model); - } log_debug("New completion list!"); if ( g_path_is_absolute(new_dir) ) new_dir_path = g_strdup(new_dir); else - new_dir_path = g_build_filename(directory_ref, new_dir, NULL); + new_dir_path = g_build_filename(ref_dir, new_dir, NULL); /* Build the new file list for completion */ completion_list = build_file_list(new_dir_path, new_dir); gtk_entry_completion_set_model (completion, completion_list); g_object_unref(completion_list); + g_free(curr_dir); + curr_dir = new_dir; + g_free(new_dir_path); } @@ -395,7 +386,6 @@ menu_item_activate(guint key_id) GtkWidget* dialog; GtkWidget* dialog_new = NULL; GtkWidget* dialog_entry; - GtkTreeModel* completion_list; GeanyDocument* current_doc = document_get_current(); gchar *chosen_path; const gchar *chosen_file; @@ -404,15 +394,16 @@ menu_item_activate(guint key_id) log_func(); if(current_doc && !EMPTY(current_doc->file_name)) - directory_ref = g_path_get_dirname(current_doc->file_name); + ref_dir = g_path_get_dirname(current_doc->file_name); else - directory_ref = g_strdup(g_get_home_dir()); + ref_dir = g_strdup(g_get_home_dir()); /* Build current directory listing */ - completion_list = build_file_list(directory_ref, ""); + ref_model = build_file_list(ref_dir, ""); + curr_dir = g_strdup(ref_dir); /* Create the user dialog and get response */ - dialog_entry = create_dialog(&dialog, completion_list); + dialog_entry = create_dialog(&dialog, ref_model); _show_dialog: response = gtk_dialog_run(GTK_DIALOG(dialog)); @@ -424,7 +415,7 @@ menu_item_activate(guint key_id) if (g_path_is_absolute(chosen_file)) chosen_path = g_strdup(chosen_file); else - chosen_path = g_build_filename(directory_ref, chosen_file, NULL); + chosen_path = g_build_filename(ref_dir, chosen_file, NULL); if ( response == GTK_RESPONSE_ACCEPT ) { @@ -463,8 +454,15 @@ menu_item_activate(guint key_id) } /* Freeing memory */ + gtk_widget_destroy(dialog); - g_free(directory_ref); + + g_object_unref(ref_model); g_free(chosen_path); - g_object_unref (completion_list); + g_free(ref_dir); + g_free(curr_dir); + + ref_model = NULL; + ref_dir = NULL; + curr_dir = NULL; } From 29630f1c5fc89198eec278c6d2b71b6246d4ace4 Mon Sep 17 00:00:00 2001 From: gkatev Date: Wed, 19 Jun 2024 19:39:01 +0300 Subject: [PATCH 09/15] codenav/goto_file: Rework directory_check; simpler, better, safer The new directory_check is (IMHO) simpler, easier to follow and maintain, and less prone to edge cases. As part of simplifications, do away with the "reference model". We do keep track of the "reference dir" and if need be simply calculate a mode for it as if any other dir. Bonus (admittedly rare) issue solved: not having to close and reopen the dialog if a file is created while it's opened... --- codenav/src/goto_file.c | 80 +++++++++++++---------------------------- 1 file changed, 25 insertions(+), 55 deletions(-) diff --git a/codenav/src/goto_file.c b/codenav/src/goto_file.c index 71f5e4bb8..00cd03198 100644 --- a/codenav/src/goto_file.c +++ b/codenav/src/goto_file.c @@ -32,7 +32,6 @@ /******************* Global variables for the feature *****************/ static GtkWidget* menu_item = NULL; -static GtkTreeModel *ref_model = NULL; static gchar *ref_dir = NULL; static gchar *curr_dir = NULL; static gint entry_len_before_completion = -1; @@ -54,7 +53,7 @@ static gboolean entry_key_event(GtkEntry *, GdkEventKey *, GtkEntryCompletion *); static GtkWidget* -create_dialog(GtkWidget**, GtkTreeModel*); +create_dialog(GtkWidget**); /********************** Functions for the feature *********************/ @@ -158,8 +157,7 @@ static void directory_check(GtkEntry* entry, GtkEntryCompletion* completion) { GtkTreeModel* completion_list; - gchar *new_dir, *new_dir_path; - gchar *text; + gchar *text, *new_dir, *new_dir_path; /* We need to discern between text written by the user and text filled by the * autocomplete, and to only use what the user wrote for the completion model. @@ -172,30 +170,14 @@ directory_check(GtkEntry* entry, GtkEntryCompletion* completion) text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, entry_len_before_completion); entry_len_before_completion = -1; + /* We are only interested in the directory part of the path that the + * user has given, to generate a file list for a completion model. */ gint dir_sep = strrpos(text, G_DIR_SEPARATOR_S); - - /* No subdir separator found */ - if (dir_sep == -1) - { - // Restore the no-sub-directory model - if(g_strcmp0(ref_dir, curr_dir) != 0) - { - gtk_entry_completion_set_model(completion, ref_model); - - g_free(curr_dir); - curr_dir = g_strdup(ref_dir); - } - - g_free(text); - return; - } - - new_dir = g_strndup (text, dir_sep+1); + new_dir = g_strndup(text, dir_sep + 1); g_free(text); - /* If this the same dir as the one of the active - * completion model, no further steps required. */ - if ( g_strcmp0 (new_dir, curr_dir) == 0 ) + /* We only create a new completion model when the directory changes */ + if(g_strcmp0(new_dir, curr_dir) == 0) { g_free(new_dir); return; @@ -203,19 +185,18 @@ directory_check(GtkEntry* entry, GtkEntryCompletion* completion) log_debug("New completion list!"); - if ( g_path_is_absolute(new_dir) ) - new_dir_path = g_strdup(new_dir); - else - new_dir_path = g_build_filename(ref_dir, new_dir, NULL); + g_free(curr_dir); + curr_dir = new_dir; + + /* Assemble full path to the entered directory */ + new_dir_path = (g_path_is_absolute(new_dir) ? g_strdup(new_dir) + : g_build_filename(ref_dir, new_dir, NULL)); /* Build the new file list for completion */ completion_list = build_file_list(new_dir_path, new_dir); gtk_entry_completion_set_model (completion, completion_list); g_object_unref(completion_list); - g_free(curr_dir); - curr_dir = new_dir; - g_free(new_dir_path); } @@ -312,12 +293,11 @@ entry_key_event(GtkEntry *entry, GdkEventKey *key, GtkEntryCompletion *completio * @brief Create the dialog, return the entry object to get the * response from user * @param GtkWidget **dialog entry object - * @param GtkTreeModel *completion_model completion object * @return GtkWidget* entry * */ static GtkWidget* -create_dialog(GtkWidget **dialog, GtkTreeModel *completion_model) +create_dialog(GtkWidget **dialog) { GtkWidget *entry; GtkWidget *label; @@ -347,16 +327,12 @@ create_dialog(GtkWidget **dialog, GtkTreeModel *completion_model) /* Completion definition */ completion = gtk_entry_completion_new(); gtk_entry_set_completion(GTK_ENTRY(entry), completion); - gtk_entry_completion_set_model (completion, completion_model); /* Completion options */ gtk_entry_completion_set_inline_completion(completion, 1); gtk_entry_completion_set_text_column (completion, 0); gtk_entry_completion_set_minimum_key_length(completion, 0); - /* Manually trigger the provided completion model */ - g_signal_emit_by_name(entry, "changed"); - /* Signals */ g_signal_connect_after(entry, "changed", G_CALLBACK(directory_check), completion); @@ -369,6 +345,11 @@ create_dialog(GtkWidget **dialog, GtkTreeModel *completion_model) * reference, and it will be deallocated when the entry is destroyed. */ g_object_unref(completion); + /* Manual trigger to invoke directory_check to put together + * a completion model (for the reference dirrectory), and to + * show the completion options as soon as the dialog opens. */ + g_signal_emit_by_name(entry, "changed"); + gtk_widget_show_all(*dialog); return entry; @@ -398,24 +379,15 @@ menu_item_activate(guint key_id) else ref_dir = g_strdup(g_get_home_dir()); - /* Build current directory listing */ - ref_model = build_file_list(ref_dir, ""); - curr_dir = g_strdup(ref_dir); - /* Create the user dialog and get response */ - dialog_entry = create_dialog(&dialog, ref_model); + dialog_entry = create_dialog(&dialog); _show_dialog: response = gtk_dialog_run(GTK_DIALOG(dialog)); - /* Entered filename */ chosen_file = gtk_entry_get_text(GTK_ENTRY(dialog_entry)); - - /* Filename as-is, if it's absolute, otherwise relative to the doc dir */ - if (g_path_is_absolute(chosen_file)) - chosen_path = g_strdup(chosen_file); - else - chosen_path = g_build_filename(ref_dir, chosen_file, NULL); + chosen_path = (g_path_is_absolute(chosen_file) ? g_strdup(chosen_file) + : g_build_filename(ref_dir, chosen_file, NULL)); if ( response == GTK_RESPONSE_ACCEPT ) { @@ -456,13 +428,11 @@ menu_item_activate(guint key_id) /* Freeing memory */ gtk_widget_destroy(dialog); - - g_object_unref(ref_model); g_free(chosen_path); - g_free(ref_dir); - g_free(curr_dir); - ref_model = NULL; + g_free(ref_dir); ref_dir = NULL; + + g_free(curr_dir); curr_dir = NULL; } From f1b8b9eef47fb6e979f585123e01115cc93664ca Mon Sep 17 00:00:00 2001 From: gkatev Date: Wed, 19 Jun 2024 20:02:56 +0300 Subject: [PATCH 10/15] codenav/goto_file: Show error message if the entered path is a directory --- codenav/src/goto_file.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/codenav/src/goto_file.c b/codenav/src/goto_file.c index 00cd03198..e3b682fbd 100644 --- a/codenav/src/goto_file.c +++ b/codenav/src/goto_file.c @@ -421,6 +421,22 @@ menu_item_activate(guint key_id) goto _show_dialog; } } + else if(g_file_test(chosen_path, G_FILE_TEST_IS_DIR)) + { + log_debug("File is a dir."); + + dialog_new = gtk_message_dialog_new(GTK_WINDOW(geany_data->main_widgets->window), + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + _("%s is a directory."), chosen_path); + gtk_window_set_title(GTK_WINDOW(dialog_new), "Geany"); + + gtk_dialog_run(GTK_DIALOG(dialog_new)); + gtk_widget_destroy(dialog_new); + + goto _show_dialog; + } else document_open_file(chosen_path, FALSE, NULL, NULL); } From dc62bfe98bf99b24187983718a7bd03871c9bb83 Mon Sep 17 00:00:00 2001 From: gkatev Date: Wed, 19 Jun 2024 20:27:50 +0300 Subject: [PATCH 11/15] codenav/goto_file: Add support for '~' in paths (= home dir) --- codenav/src/goto_file.c | 44 +++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/codenav/src/goto_file.c b/codenav/src/goto_file.c index e3b682fbd..4546928f8 100644 --- a/codenav/src/goto_file.c +++ b/codenav/src/goto_file.c @@ -104,6 +104,27 @@ goto_file_cleanup(void) gtk_widget_destroy(menu_item); } +/** + * @brief Replace any special characters present in path_name + * @param gchar** path_name + * @return void + * + */ +static void +path_name_replace_special(gchar **path_name) +{ + GString *str; + + if(*path_name[0] == '~') + { + str = g_string_new(*path_name); + utils_string_replace_first(str, "~", g_get_home_dir()); + + free(*path_name); + *path_name = g_string_free(str, FALSE); + } +} + /** * @brief Populate the file list with file list of directory * @param const char* dirname the directory where to find files @@ -157,7 +178,7 @@ static void directory_check(GtkEntry* entry, GtkEntryCompletion* completion) { GtkTreeModel* completion_list; - gchar *text, *new_dir, *new_dir_path; + gchar *text, *new_dir, *new_dir_exp, *new_dir_path; /* We need to discern between text written by the user and text filled by the * autocomplete, and to only use what the user wrote for the completion model. @@ -188,15 +209,20 @@ directory_check(GtkEntry* entry, GtkEntryCompletion* completion) g_free(curr_dir); curr_dir = new_dir; + /* Replace special chars here (e.g. '~') */ + new_dir_exp = g_strdup(new_dir); + path_name_replace_special(&new_dir_exp); + /* Assemble full path to the entered directory */ - new_dir_path = (g_path_is_absolute(new_dir) ? g_strdup(new_dir) - : g_build_filename(ref_dir, new_dir, NULL)); + new_dir_path = (g_path_is_absolute(new_dir_exp) ? g_strdup(new_dir_exp) + : g_build_filename(ref_dir, new_dir_exp, NULL)); /* Build the new file list for completion */ completion_list = build_file_list(new_dir_path, new_dir); gtk_entry_completion_set_model (completion, completion_list); g_object_unref(completion_list); + g_free(new_dir_exp); g_free(new_dir_path); } @@ -368,8 +394,8 @@ menu_item_activate(guint key_id) GtkWidget* dialog_new = NULL; GtkWidget* dialog_entry; GeanyDocument* current_doc = document_get_current(); - gchar *chosen_path; const gchar *chosen_file; + gchar *chosen_file_exp, *chosen_path; gint response; log_func(); @@ -386,8 +412,12 @@ menu_item_activate(guint key_id) response = gtk_dialog_run(GTK_DIALOG(dialog)); chosen_file = gtk_entry_get_text(GTK_ENTRY(dialog_entry)); - chosen_path = (g_path_is_absolute(chosen_file) ? g_strdup(chosen_file) - : g_build_filename(ref_dir, chosen_file, NULL)); + + chosen_file_exp = g_strdup(chosen_file); + path_name_replace_special(&chosen_file_exp); + + chosen_path = (g_path_is_absolute(chosen_file_exp) ? g_strdup(chosen_file_exp) + : g_build_filename(ref_dir, chosen_file_exp, NULL)); if ( response == GTK_RESPONSE_ACCEPT ) { @@ -444,6 +474,8 @@ menu_item_activate(guint key_id) /* Freeing memory */ gtk_widget_destroy(dialog); + + g_free(chosen_file_exp); g_free(chosen_path); g_free(ref_dir); From b79fda1ca154a6d219ad6a34881d8ebd39bfe458 Mon Sep 17 00:00:00 2001 From: gkatev Date: Sat, 22 Jun 2024 20:30:41 +0300 Subject: [PATCH 12/15] codenav/goto_file: Fix entry_key_event not getting called first Move the respective g_signal_connect before gtk_entry_set_completion(), which is where the GtkEntryCompletion's handler gets installed. --- codenav/src/goto_file.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/codenav/src/goto_file.c b/codenav/src/goto_file.c index 4546928f8..2a198a7ca 100644 --- a/codenav/src/goto_file.c +++ b/codenav/src/goto_file.c @@ -352,6 +352,12 @@ create_dialog(GtkWidget **dialog) /* Completion definition */ completion = gtk_entry_completion_new(); + + /* Installed here, so it'll get installed before the respective + * handler inside GtkEntryCompletion, and will thus be called first. */ + g_signal_connect(entry, "key-press-event", + G_CALLBACK(entry_key_event), completion); + gtk_entry_set_completion(GTK_ENTRY(entry), completion); /* Completion options */ @@ -364,8 +370,6 @@ create_dialog(GtkWidget **dialog) G_CALLBACK(directory_check), completion); g_signal_connect(completion, "insert-prefix", G_CALLBACK(entry_inline_completion_event), entry); - g_signal_connect(entry, "key-press-event", - G_CALLBACK(entry_key_event), completion); /* The completion object is tracked in the entry. We may release our local * reference, and it will be deallocated when the entry is destroyed. */ From 653c9ad65aaf73e78c05cc999f951a2db9c6a3f3 Mon Sep 17 00:00:00 2001 From: gkatev Date: Wed, 10 Jul 2024 23:31:51 +0300 Subject: [PATCH 13/15] codenav/goto_file: Always return FALSE from entry_inline_completion_event Does not seem our place to decide whether the default handler should not run, and it doesn't harm us any way if it does... --- codenav/src/goto_file.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/codenav/src/goto_file.c b/codenav/src/goto_file.c index 2a198a7ca..1ae20ec73 100644 --- a/codenav/src/goto_file.c +++ b/codenav/src/goto_file.c @@ -248,14 +248,9 @@ entry_inline_completion_event(GtkEntryCompletion *completion, gchar *prefix, Gtk if(prefix_len > entry_len) { entry_len_before_completion = entry_len; - return FALSE; - } - else - { - /* We know no completion will take place, no reason - * for the default sig handler to be called... */ - return TRUE; } + + return FALSE; } /** From 732e06f54aca6e1ca8ed0c5d13da16f34b2ea647 Mon Sep 17 00:00:00 2001 From: gkatev Date: Tue, 16 Jul 2024 21:36:12 +0300 Subject: [PATCH 14/15] codenav/goto_file: Tab keys shows the autocomplete list if not visible --- codenav/src/goto_file.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/codenav/src/goto_file.c b/codenav/src/goto_file.c index 1ae20ec73..352e7ca91 100644 --- a/codenav/src/goto_file.c +++ b/codenav/src/goto_file.c @@ -268,17 +268,17 @@ entry_key_event(GtkEntry *entry, GdkEventKey *key, GtkEntryCompletion *completio if(key->keyval == GDK_KEY_Tab) { - /* The user may 'accept' the autocomplete's suggestion with the Tab key, + /* The user can use the Tab key to 'accept' the autocomplete's suggestion, * like in shells, and in GtkFileChoose. If there's a suggestion, it will * be selected, and we may simply move the cursor to accept it. */ if(gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), NULL, &end_pos)) - { gtk_editable_set_position(GTK_EDITABLE(entry), end_pos); - /* Moving the cursor does not trigger the "changed" event. Do it - * manually, to caclulate & show completions for the new path. */ - g_signal_emit_by_name(entry, "changed"); - } + /* The Tab key may also be used to trigger the autocomplete list + * to show up. And this trigger right here after 'accepting' an + * autocomplete suggestion is also useful in order to calculate + * and show completions for the new path. */ + g_signal_emit_by_name(entry, "changed"); /* The 2nd purpose of the Tab key is to trigger the procedure that * calculates and suggests an inline completion. FYI doing this here @@ -360,6 +360,13 @@ create_dialog(GtkWidget **dialog) gtk_entry_completion_set_text_column (completion, 0); gtk_entry_completion_set_minimum_key_length(completion, 0); + /* Initialize a model for the completion, to avoid errors in code that + * might attempt to utilize it, before any 'changed' events are fired. */ + directory_check(GTK_ENTRY(entry), completion); + + /* Manual trigger to show the completion list as soon as the dialog opens */ + g_signal_emit_by_name(entry, "changed"); + /* Signals */ g_signal_connect_after(entry, "changed", G_CALLBACK(directory_check), completion); @@ -370,11 +377,6 @@ create_dialog(GtkWidget **dialog) * reference, and it will be deallocated when the entry is destroyed. */ g_object_unref(completion); - /* Manual trigger to invoke directory_check to put together - * a completion model (for the reference dirrectory), and to - * show the completion options as soon as the dialog opens. */ - g_signal_emit_by_name(entry, "changed"); - gtk_widget_show_all(*dialog); return entry; From 798c8041b980db12a6e73682cafd513a5a33e88d Mon Sep 17 00:00:00 2001 From: gkatev Date: Wed, 27 Nov 2024 14:01:42 +0200 Subject: [PATCH 15/15] codenav/goto_file: Revert 'show completion list as soon as dialog opens' --- codenav/src/goto_file.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/codenav/src/goto_file.c b/codenav/src/goto_file.c index 352e7ca91..f95b59b71 100644 --- a/codenav/src/goto_file.c +++ b/codenav/src/goto_file.c @@ -364,9 +364,6 @@ create_dialog(GtkWidget **dialog) * might attempt to utilize it, before any 'changed' events are fired. */ directory_check(GTK_ENTRY(entry), completion); - /* Manual trigger to show the completion list as soon as the dialog opens */ - g_signal_emit_by_name(entry, "changed"); - /* Signals */ g_signal_connect_after(entry, "changed", G_CALLBACK(directory_check), completion);