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