1 /*
2 * goto_file.c - this file is part of "codenavigation", which is
3 * part of the "geany-plugins" project.
4 *
5 * Copyright 2009 Lionel Fuentes <funto66(at)gmail(dot)com>
6 * Copyright 2014 Federico Reghenzani <federico(dot)dev(at)reghe(dot)net>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include <geanyplugin.h>
26
27 #include "goto_file.h"
28 #include "utils.h"
29
30 #define MAX_FILENAME_LENGTH 255
31
32 /******************* Global variables for the feature *****************/
33
34 static GtkWidget* menu_item = NULL;
35 gchar *directory_ref = NULL;
36
37 /********************** Prototypes *********************/
38 static void
39 menu_item_activate(guint);
40
41 static GtkTreeModel*
42 build_file_list(const gchar*, const gchar*);
43
44 static void
45 directory_check(GtkEntry*, GtkEntryCompletion*);
46
47 static GtkWidget*
48 create_dialog(GtkWidget**, GtkTreeModel*);
49
50 /********************** Functions for the feature *********************/
51
52 /**
53 * @brief Initialization function called in plugin_init
54 * @param void
55 * @return void
56 *
57 */
58 void
goto_file_init(void)59 goto_file_init(void)
60 {
61 GtkWidget* edit_menu;
62
63 log_func();
64
65 edit_menu = ui_lookup_widget(geany->main_widgets->window, "edit1_menu");
66
67 /* Add the menu item, sensitive only when a document is opened */
68 menu_item = gtk_menu_item_new_with_mnemonic(_("Go to File..."));
69 gtk_widget_show(menu_item);
70 gtk_container_add(GTK_CONTAINER(edit_menu), menu_item);
71 ui_add_document_sensitive(menu_item);
72
73 /* Callback connection */
74 g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(menu_item_activate), NULL);
75
76 /* Initialize the key binding : */
77 keybindings_set_item( plugin_key_group,
78 KEY_ID_GOTO_FILE,
79 (GeanyKeyCallback)(&menu_item_activate),
80 GDK_g, GDK_MOD1_MASK | GDK_SHIFT_MASK,
81 "goto_file",
82 _("Go to File"), /* used in the Preferences dialog */
83 menu_item);
84 }
85
86 /**
87 * @brief Cleanup function called in plugin_cleanup
88 * @param void
89 * @return void
90 *
91 */
92 void
goto_file_cleanup(void)93 goto_file_cleanup(void)
94 {
95 log_func();
96 gtk_widget_destroy(menu_item);
97 }
98
99 /**
100 * @brief Populate the file list with file list of directory
101 * @param const char* dirname the directory where to find files
102 * @param const char* prefix file prefix (the path)
103 * @return GtkTreeModel*
104 *
105 */
106 static GtkTreeModel*
build_file_list(const gchar * dirname,const gchar * prefix)107 build_file_list(const gchar* dirname, const gchar* prefix)
108 {
109 GtkListStore *ret_list;
110 GtkTreeIter iter;
111 ret_list = gtk_list_store_new (1, G_TYPE_STRING);
112
113 GSList* file_iterator;
114 GSList* files_list; /* used to free later the sub-elements*/
115 gchar *file;
116 gchar *pathfile;
117 guint files_n;
118
119 files_list = file_iterator = utils_get_file_list(dirname, &files_n, NULL);
120
121 for( ; file_iterator; file_iterator = file_iterator->next)
122 {
123 file = file_iterator->data;
124
125 pathfile = g_build_filename(dirname,file,NULL);
126
127 /* Append the element to model list */
128 gtk_list_store_append (ret_list, &iter);
129 gtk_list_store_set (ret_list, &iter, 0,
130 g_strconcat(prefix, file, NULL), -1);
131 g_free(pathfile);
132 }
133
134 g_slist_foreach(files_list, (GFunc) g_free, NULL);
135 g_slist_free(files_list);
136
137 return GTK_TREE_MODEL(ret_list);
138
139 }
140
141 /**
142 * @brief Entry callback function for sub-directory search
143 * @param GtkEntry* entry entry object
144 * @param GtkEntryCompletion* completion completion object
145 * @return void
146 *
147 */
148 static void
directory_check(GtkEntry * entry,GtkEntryCompletion * completion)149 directory_check(GtkEntry* entry, GtkEntryCompletion* completion)
150 {
151 static GtkTreeModel *old_model = NULL;
152 GtkTreeModel* completion_list;
153 static gchar *curr_dir = NULL;
154 gchar *new_dir, *new_dir_path;
155 const gchar *text;
156
157 text = gtk_entry_get_text(entry);
158 gint dir_sep = strrpos(text, G_DIR_SEPARATOR_S);
159
160 /* No subdir separator found */
161 if (dir_sep == -1)
162 {
163 if (old_model != NULL)
164 { /* Restore the no-sub-directory model */
165 log_debug("Restoring old model!");
166 gtk_entry_completion_set_model (completion, old_model);
167 old_model = NULL;
168 g_free(curr_dir);
169 curr_dir = NULL;
170 }
171 return;
172 }
173
174 new_dir = g_strndup (text, dir_sep+1);
175 /* I've already inserted new model completion for sub-dir elements? */
176 if ( g_strcmp0 (new_dir, curr_dir) == 0 )
177 return;
178
179 if ( curr_dir != NULL )
180 g_free(curr_dir);
181
182 curr_dir = new_dir;
183
184 /* Save the completion_mode for future restore. */
185 if (old_model == NULL)
186 old_model = gtk_entry_completion_get_model(completion);
187
188 log_debug("New completion list!");
189
190 if ( g_path_is_absolute(new_dir) )
191 new_dir_path = new_dir;
192 else
193 new_dir_path = g_build_filename(directory_ref, new_dir, NULL);
194
195 /* Build the new file list for completion */
196 completion_list = build_file_list(new_dir_path, new_dir);
197 gtk_entry_completion_set_model (completion, completion_list);
198 g_object_unref(completion_list);
199 }
200
201
202 /**
203 * @brief Create the dialog, return the entry object to get the
204 * response from user
205 * @param GtkWidget **dialog entry object
206 * @param GtkTreeModel *completion_model completion object
207 * @return GtkWidget* entry
208 *
209 */
210 static GtkWidget*
create_dialog(GtkWidget ** dialog,GtkTreeModel * completion_model)211 create_dialog(GtkWidget **dialog, GtkTreeModel *completion_model)
212 {
213 GtkWidget *entry;
214 GtkWidget *label;
215 GtkWidget *vbox;
216 GtkEntryCompletion *completion;
217
218 *dialog = gtk_dialog_new_with_buttons(_("Go to File..."), GTK_WINDOW(geany->main_widgets->window),
219 GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
220 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
221
222 gtk_dialog_set_default_response(GTK_DIALOG(*dialog), GTK_RESPONSE_ACCEPT);
223
224 gtk_widget_set_name(*dialog, "GotoFile");
225 vbox = ui_dialog_vbox_new(GTK_DIALOG(*dialog));
226
227 label = gtk_label_new(_("Enter the file you want to open:"));
228 gtk_container_add(GTK_CONTAINER(vbox), label);
229
230 /* Entry definition */
231 entry = gtk_entry_new();
232 gtk_container_add(GTK_CONTAINER(vbox), entry);
233 gtk_entry_set_text(GTK_ENTRY(entry), "");
234 gtk_entry_set_max_length(GTK_ENTRY(entry), MAX_FILENAME_LENGTH);
235 gtk_entry_set_width_chars(GTK_ENTRY(entry), 40);
236 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE); /* 'enter' key */
237
238 /* Completion definition */
239 completion = gtk_entry_completion_new();
240 gtk_entry_set_completion(GTK_ENTRY(entry), completion);
241 gtk_entry_completion_set_model (completion, completion_model);
242
243 /* Completion options */
244 gtk_entry_completion_set_inline_completion(completion, 1);
245 gtk_entry_completion_set_text_column (completion, 0);
246
247 /* Signals */
248 g_signal_connect_after(GTK_ENTRY(entry), "changed",
249 G_CALLBACK(directory_check), completion);
250
251 gtk_widget_show_all(*dialog);
252
253 return entry;
254 }
255
256 /**
257 * @brief Callback when the menu item is clicked.
258 * @param guint key_id not used
259 * @return void
260 *
261 */
262 static void
menu_item_activate(guint key_id)263 menu_item_activate(guint key_id)
264 {
265 GtkWidget* dialog;
266 GtkWidget* dialog_new = NULL;
267 GtkWidget* dialog_entry;
268 GtkTreeModel* completion_list;
269 GeanyDocument* current_doc = document_get_current();
270 gchar *chosen_path;
271 const gchar *chosen_file;
272 gint response;
273
274 log_func();
275
276 if(current_doc == NULL || current_doc->file_name == NULL || current_doc->file_name[0] == '\0')
277 return;
278
279 /* Build current directory listing */
280 directory_ref = g_path_get_dirname(current_doc->file_name);
281 completion_list = build_file_list(directory_ref, "");
282
283 /* Create the user dialog and get response */
284 dialog_entry = create_dialog(&dialog, completion_list);
285 response = gtk_dialog_run(GTK_DIALOG(dialog));
286
287 /* Filename */
288 chosen_file = gtk_entry_get_text(GTK_ENTRY(dialog_entry));
289 /* Path + Filename */
290 chosen_path = g_build_filename(directory_ref, chosen_file, NULL);
291
292 if ( response == GTK_RESPONSE_ACCEPT )
293 {
294 log_debug("Trying to open: %s", chosen_path);
295 if ( ! g_file_test(chosen_path, G_FILE_TEST_EXISTS) )
296 {
297 log_debug("File not found.");
298
299 dialog_new = gtk_message_dialog_new(GTK_WINDOW(geany_data->main_widgets->window),
300 GTK_DIALOG_MODAL,
301 GTK_MESSAGE_QUESTION,
302 GTK_BUTTONS_OK_CANCEL,
303 _("%s not found, create it?"), chosen_file);
304 gtk_window_set_title(GTK_WINDOW(dialog_new), "Geany");
305 if(gtk_dialog_run(GTK_DIALOG(dialog_new)) == GTK_RESPONSE_OK)
306 {
307 document_new_file(chosen_path, current_doc->file_type, NULL);
308 document_set_text_changed(document_get_current(), TRUE);
309 }
310 gtk_widget_destroy(dialog_new);
311 }
312 else
313 document_open_file(chosen_path, FALSE, NULL, NULL);
314 }
315
316 /* Freeing memory */
317 gtk_widget_destroy(dialog);
318 g_free(directory_ref);
319 g_object_unref (completion_list);
320 }
321