1 /*
2  * Copyright 2010 Jiri Techet <techet@gmail.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include <sys/time.h>
20 #include <gdk/gdkkeysyms.h>
21 #include <string.h>
22 
23 #ifdef HAVE_CONFIG_H
24 	#include "config.h"
25 #endif
26 #include <geanyplugin.h>
27 #include <gtkcompat.h>
28 
29 #include "prjorg-utils.h"
30 #include "prjorg-project.h"
31 #include "prjorg-sidebar.h"
32 
33 extern GeanyPlugin *geany_plugin;
34 extern GeanyData *geany_data;
35 
36 enum
37 {
38 	FILEVIEW_COLUMN_ICON,
39 	FILEVIEW_COLUMN_NAME,
40 	FILEVIEW_COLUMN_COLOR,
41 	FILEVIEW_N_COLUMNS,
42 };
43 
44 typedef enum
45 {
46 	MATCH_FULL,
47 	MATCH_PREFIX,
48 	MATCH_PATTERN
49 } MatchType;
50 
51 typedef struct
52 {
53 	GeanyProject *project;
54 	GPtrArray *expanded_paths;
55 } ExpandData;
56 
57 
58 static GdkColor s_external_color;
59 static GtkWidget *s_toolbar = NULL;
60 static gboolean s_pending_reload = FALSE;
61 
62 static GtkWidget *s_file_view_vbox = NULL;
63 static GtkWidget *s_file_view = NULL;
64 static GtkTreeStore *s_file_store = NULL;
65 static gboolean s_follow_editor = TRUE;
66 
67 static struct
68 {
69 	GtkWidget *expand;
70 	GtkWidget *collapse;
71 	GtkWidget *follow;
72 	GtkWidget *add;
73 } s_project_toolbar = {NULL, NULL, NULL, NULL};
74 
75 
76 static struct
77 {
78 	GtkWidget *widget;
79 
80 	GtkWidget *dir_label;
81 	GtkWidget *combo;
82 	GtkWidget *case_sensitive;
83 	GtkWidget *full_path;
84 } s_fif_dialog = {NULL, NULL, NULL, NULL, NULL};
85 
86 
87 static struct
88 {
89 	GtkWidget *widget;
90 
91 	GtkWidget *dir_label;
92 	GtkWidget *combo;
93 	GtkWidget *combo_match;
94 	GtkWidget *case_sensitive;
95 	GtkWidget *declaration;
96 } s_ft_dialog = {NULL, NULL, NULL, NULL, NULL, NULL};
97 
98 
99 static struct
100 {
101 	GtkWidget *widget;
102 
103 	GtkWidget *find_in_directory;
104 	GtkWidget *find_file;
105 	GtkWidget *find_tag;
106 	GtkWidget *expand;
107 	GtkWidget *remove_external_dir;
108 
109 	GtkWidget *create_file;
110 	GtkWidget *create_dir;
111 	GtkWidget *rename;
112 	GtkWidget *delete;
113 } s_popup_menu;
114 
115 
show_dialog_find_file(gchar * utf8_path,gchar ** pattern,gboolean * case_sensitive,gboolean * is_full_path)116 static gint show_dialog_find_file(gchar *utf8_path, gchar **pattern, gboolean *case_sensitive, gboolean *is_full_path)
117 {
118 	gint res;
119 	GtkWidget *entry;
120 	gchar *selection;
121 	GtkSizeGroup *size_group;
122 
123 	if (!s_fif_dialog.widget)
124 	{
125 		GtkWidget *label, *vbox, *ebox;
126 
127 		s_fif_dialog.widget = gtk_dialog_new_with_buttons(
128 			_("Find File"), GTK_WINDOW(geany->main_widgets->window),
129 			GTK_DIALOG_DESTROY_WITH_PARENT,
130 			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
131 		gtk_dialog_add_button(GTK_DIALOG(s_fif_dialog.widget), "gtk-find", GTK_RESPONSE_ACCEPT);
132 		gtk_dialog_set_default_response(GTK_DIALOG(s_fif_dialog.widget), GTK_RESPONSE_ACCEPT);
133 
134 		vbox = ui_dialog_vbox_new(GTK_DIALOG(s_fif_dialog.widget));
135 		gtk_box_set_spacing(GTK_BOX(vbox), 6);
136 
137 		size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
138 
139 		label = gtk_label_new(_("Search for:"));
140 		gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
141 		gtk_size_group_add_widget(size_group, label);
142 		s_fif_dialog.combo = gtk_combo_box_text_new_with_entry();
143 		entry = gtk_bin_get_child(GTK_BIN(s_fif_dialog.combo));
144 		gtk_entry_set_width_chars(GTK_ENTRY(entry), 40);
145 		gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
146 		ui_entry_add_clear_icon(GTK_ENTRY(entry));
147 		gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
148 
149 		ebox = gtk_hbox_new(FALSE, 6);
150 		gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0);
151 		gtk_box_pack_start(GTK_BOX(ebox), s_fif_dialog.combo, TRUE, TRUE, 0);
152 
153 		gtk_box_pack_start(GTK_BOX(vbox), ebox, TRUE, FALSE, 0);
154 
155 		label = gtk_label_new(_("Search inside:"));
156 		gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
157 		gtk_size_group_add_widget(size_group, label);
158 		s_fif_dialog.dir_label = gtk_label_new("");
159 		gtk_misc_set_alignment(GTK_MISC(s_fif_dialog.dir_label), 0, 0.5);
160 
161 		ebox = gtk_hbox_new(FALSE, 6);
162 		gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0);
163 		gtk_box_pack_start(GTK_BOX(ebox), s_fif_dialog.dir_label, TRUE, TRUE, 0);
164 
165 		gtk_box_pack_start(GTK_BOX(vbox), ebox, TRUE, FALSE, 0);
166 
167 		s_fif_dialog.case_sensitive = gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
168 		gtk_button_set_focus_on_click(GTK_BUTTON(s_fif_dialog.case_sensitive), FALSE);
169 
170 		s_fif_dialog.full_path = gtk_check_button_new_with_mnemonic(_("Search in full path"));
171 		gtk_button_set_focus_on_click(GTK_BUTTON(s_fif_dialog.full_path), FALSE);
172 
173 		gtk_box_pack_start(GTK_BOX(vbox), s_fif_dialog.case_sensitive, TRUE, FALSE, 0);
174 		gtk_box_pack_start(GTK_BOX(vbox), s_fif_dialog.full_path, TRUE, FALSE, 0);
175 		gtk_widget_show_all(vbox);
176 	}
177 
178 	if (utf8_path)
179 		gtk_label_set_text(GTK_LABEL(s_fif_dialog.dir_label), utf8_path);
180 	else
181 		gtk_label_set_text(GTK_LABEL(s_fif_dialog.dir_label), _("project or external directory"));
182 	entry = gtk_bin_get_child(GTK_BIN(s_fif_dialog.combo));
183 	selection = get_selection();
184 	if (selection)
185 		gtk_entry_set_text(GTK_ENTRY(entry), selection);
186 	g_free(selection);
187 	gtk_widget_grab_focus(entry);
188 
189 	res = gtk_dialog_run(GTK_DIALOG(s_fif_dialog.widget));
190 
191 	if (res == GTK_RESPONSE_ACCEPT)
192 	{
193 		const gchar *str;
194 
195 		str = gtk_entry_get_text(GTK_ENTRY(entry));
196 		*pattern = g_strconcat("*", str, "*", NULL);
197 		*case_sensitive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s_fif_dialog.case_sensitive));
198 		*is_full_path = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s_fif_dialog.full_path));
199 		ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(s_fif_dialog.combo), str, 0);
200 	}
201 
202 	gtk_widget_hide(s_fif_dialog.widget);
203 
204 	return res;
205 }
206 
207 
topmost_selected(GtkTreeModel * model,GtkTreeIter * iter,gboolean first)208 static gboolean topmost_selected(GtkTreeModel *model, GtkTreeIter *iter, gboolean first)
209 {
210 	GtkTreePath *path, *first_path;
211 	gboolean ret, is_first;
212 
213 	first_path = gtk_tree_path_new_first();
214 	path = gtk_tree_model_get_path(model, iter);
215 
216 	is_first = gtk_tree_path_compare(first_path, path) == 0;
217 	ret = gtk_tree_path_get_depth(path) == 1 && ((is_first && first) || (!is_first && !first));
218 	gtk_tree_path_free(first_path);
219 	gtk_tree_path_free(path);
220 	return ret;
221 }
222 
223 
224 /* utf8 */
build_path(GtkTreeIter * iter)225 static gchar *build_path(GtkTreeIter *iter)
226 {
227 	GtkTreeIter node;
228 	GtkTreeIter parent;
229 	gchar *path = NULL;
230 	GtkTreeModel *model;
231 	gchar *name;
232 
233 	if (!iter)
234 		return get_project_base_path();
235 
236 	node = *iter;
237 	model = GTK_TREE_MODEL(s_file_store);
238 
239 	while (gtk_tree_model_iter_parent(model, &parent, &node))
240 	{
241 		gtk_tree_model_get(model, &node, FILEVIEW_COLUMN_NAME, &name, -1);
242 		if (path == NULL)
243 			path = g_strdup(name);
244 		else
245 			SETPTR(path, g_build_filename(name, path, NULL));
246 		g_free(name);
247 
248 		node = parent;
249 	}
250 
251 	if (topmost_selected(model, &node, TRUE))
252 	{
253 		gchar *utf8_base_path = get_project_base_path();
254 
255 		SETPTR(path, g_build_filename(utf8_base_path, path, NULL));
256 		g_free(utf8_base_path);
257 	}
258 	else
259 	{
260 		gtk_tree_model_get(model, &node, FILEVIEW_COLUMN_NAME, &name, -1);
261 		SETPTR(path, g_build_filename(name, path, NULL));
262 		g_free(name);
263 	}
264 	return path;
265 }
266 
267 
on_expand_all(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)268 static void on_expand_all(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer user_data)
269 {
270 	gtk_tree_view_expand_all(GTK_TREE_VIEW(s_file_view));
271 }
272 
273 
collapse(void)274 static void collapse(void)
275 {
276 	GtkTreeIter iter;
277 	GtkTreePath *tree_path;
278 	GtkTreeModel *model = GTK_TREE_MODEL(s_file_store);
279 
280 	gtk_tree_view_collapse_all(GTK_TREE_VIEW(s_file_view));
281 
282 	/* expand the project folder */
283 	gtk_tree_model_iter_children(model, &iter, NULL);
284 	tree_path = gtk_tree_model_get_path (model, &iter);
285 	gtk_tree_view_expand_to_path(GTK_TREE_VIEW(s_file_view), tree_path);
286 	gtk_tree_path_free(tree_path);
287 }
288 
289 
on_collapse_all(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)290 static void on_collapse_all(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer user_data)
291 {
292 	collapse();
293 }
294 
295 
on_follow_active(GtkToggleToolButton * button,G_GNUC_UNUSED gpointer user_data)296 static void on_follow_active(GtkToggleToolButton *button, G_GNUC_UNUSED gpointer user_data)
297 {
298 	s_follow_editor = gtk_toggle_tool_button_get_active(button);
299 	prjorg_sidebar_update(FALSE);
300 }
301 
302 
on_add_external(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)303 static void on_add_external(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer user_data)
304 {
305 	gchar *utf8_base_path = get_project_base_path();
306 	gchar *locale_path = utils_get_locale_from_utf8(utf8_base_path);
307 	GtkWidget *dialog;
308 
309 	dialog = gtk_file_chooser_dialog_new(_("Add External Directory"),
310 		GTK_WINDOW(geany->main_widgets->window), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
311 		_("_Cancel"), GTK_RESPONSE_CANCEL,
312 		_("Add"), GTK_RESPONSE_ACCEPT, NULL);
313 	gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), locale_path);
314 
315 	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
316 	{
317 		gchar *locale_filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
318 		gchar *utf8_filename = utils_get_utf8_from_locale(locale_filename);
319 
320 		prjorg_project_add_external_dir(utf8_filename);
321 		prjorg_sidebar_update(TRUE);
322 		project_write_config();
323 
324 		g_free(utf8_filename);
325 		g_free(locale_filename);
326 	}
327 
328 	gtk_widget_destroy(dialog);
329 
330 	g_free(utf8_base_path);
331 	g_free(locale_path);
332 }
333 
334 
on_remove_external_dir(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)335 static void on_remove_external_dir(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
336 {
337 	GtkTreeSelection *treesel;
338 	GtkTreeModel *model;
339 	GtkTreeIter iter, parent;
340 	gchar *name;
341 
342 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
343 	if (!gtk_tree_selection_get_selected(treesel, &model, &iter))
344 		return;
345 
346 	if (gtk_tree_model_iter_parent(model, &parent, &iter))
347 		return;
348 
349 	gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_NAME, &name, -1);
350 	prjorg_project_remove_external_dir(name);
351 	prjorg_sidebar_update(TRUE);
352 	project_write_config();
353 
354 	g_free(name);
355 }
356 
357 
358 // returned string must be freed
parent_dir_for_create()359 static gchar* parent_dir_for_create()
360 {
361 	GtkTreeSelection *treesel;
362 	GtkTreeModel *model;
363 	GtkTreeIter iter, parent;
364 	gchar *path = NULL;
365 
366 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
367 	if (gtk_tree_selection_get_selected(treesel, &model, &iter))
368 	{
369 		path = build_path(&iter);
370 		if (!g_file_test(path, G_FILE_TEST_IS_DIR))
371 		{
372 			g_free(path);
373 			path = NULL;
374 			if (gtk_tree_model_iter_parent(model, &parent, &iter))
375 				path = build_path(&parent);
376 		}
377 	}
378 	return path;
379 }
380 
381 
on_create_file(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)382 static void on_create_file(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
383 {
384 	gchar *dir, *name;
385 
386 	dir = parent_dir_for_create();
387 	if (dir == NULL)
388 		return;
389 
390 	name = dialogs_show_input(_("New File"), GTK_WINDOW(geany->main_widgets->window),
391 			_("File name:"), _("newfile.txt"));
392 
393 	if (name != NULL)
394 	{
395 		gchar *path = g_build_path(G_DIR_SEPARATOR_S, dir, name, NULL);
396 
397 		if (create_file(path))
398 		{
399 			open_file(path);
400 			//TODO: don't rescan the whole project, only change the affected file
401 			prjorg_project_rescan();
402 			prjorg_sidebar_update(TRUE);
403 		}
404 		else
405 			dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("Cannot create file '%s'."), path);
406 		g_free(path);
407 	}
408 	g_free(name);
409 	g_free(dir);
410 }
411 
412 
on_create_dir(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)413 static void on_create_dir(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
414 {
415 	gchar *dir, *name;
416 
417 	dir = parent_dir_for_create();
418 	if (dir == NULL)
419 		return;
420 
421 	name = dialogs_show_input(_("New Directory"), GTK_WINDOW(geany->main_widgets->window),
422 			_("Directory name:"), _("newdir"));
423 
424 	if (name != NULL)
425 	{
426 		gchar *path = g_build_path(G_DIR_SEPARATOR_S, dir, name, NULL);
427 
428 		if (create_dir(path))
429 		{
430 			//TODO: don't rescan the whole project, only change the affected directory
431 			prjorg_project_rescan();
432 			prjorg_sidebar_update(TRUE);
433 		}
434 		else
435 			dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("Cannot create directory '%s'."), path);
436 		g_free(path);
437 	}
438 	g_free(name);
439 	g_free(dir);
440 }
441 
442 
on_rename(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)443 static void on_rename(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
444 {
445 	GtkTreeSelection *treesel;
446 	GtkTreeModel *model;
447 	GtkTreeIter iter, parent;
448 	gchar *name, *dir;
449 
450 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
451 	if (!gtk_tree_selection_get_selected(treesel, &model, &iter))
452 		return;
453 	if (!gtk_tree_model_iter_parent(model, &parent, &iter))
454 		return;
455 	dir = build_path(&parent);
456 	if (dir == NULL)
457 		return;
458 
459 	gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_NAME, &name, -1);
460 	if (name != NULL)
461 	{
462 		gchar *newname = dialogs_show_input(_("Rename"), GTK_WINDOW(geany->main_widgets->window),
463 				_("New name:"), name);
464 
465 		if (newname != NULL)
466 		{
467 			gchar *oldpath = g_build_path(G_DIR_SEPARATOR_S, dir, name, NULL);
468 			gchar *newpath = g_build_path(G_DIR_SEPARATOR_S, dir, newname, NULL);
469 			if (rename_file_or_dir(oldpath, newpath))
470 			{
471 				//TODO: don't rescan the whole project, only change the affected file
472 				prjorg_project_rescan();
473 				prjorg_sidebar_update(TRUE);
474 			}
475 			else
476 				dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("Cannot rename '%s' to '%s'."),
477 						oldpath, newpath);
478 			g_free(oldpath);
479 			g_free(newpath);
480 		}
481 		g_free(newname);
482 	}
483 	g_free(dir);
484 	g_free(name);
485 }
486 
487 
on_delete(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)488 static void on_delete(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
489 {
490 	GtkTreeSelection *treesel;
491 	GtkTreeModel *model;
492 	GtkTreeIter iter;
493 	gchar *name;
494 
495 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
496 	if (!gtk_tree_selection_get_selected(treesel, &model, &iter))
497 		return;
498 
499 	gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_NAME, &name, -1);
500 
501 	if (dialogs_show_question(_("Are you sure you want to delete '%s'?"), name))
502 	{
503 		gchar *path = build_path(&iter);
504 
505 		if (remove_file_or_dir(path))
506 			close_file(path);
507 		else
508 			dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("Cannot delete file '%s'."), path);
509 
510 		g_free(path);
511 
512 		//TODO: don't rescan the whole project, only change the affected file
513 		prjorg_project_rescan();
514 		prjorg_sidebar_update(TRUE);
515 	}
516 
517 	g_free(name);
518 }
519 
520 
find_file_recursive(GtkTreeIter * iter,gboolean case_sensitive,gboolean full_path,GPatternSpec * pattern)521 static void find_file_recursive(GtkTreeIter *iter, gboolean case_sensitive, gboolean full_path, GPatternSpec *pattern)
522 {
523 	GtkTreeModel *model = GTK_TREE_MODEL(s_file_store);
524 	GtkTreeIter child;
525 	gboolean iterate;
526 
527 	iterate = gtk_tree_model_iter_children(model, &child, iter);
528 	if (iterate)
529 	{
530 		while (iterate)
531 		{
532 			find_file_recursive(&child, case_sensitive, full_path, pattern);
533 			iterate = gtk_tree_model_iter_next(model, &child);
534 		}
535 	}
536 	else
537 	{
538 		gchar *utf8_name;
539 
540 		if (iter == NULL)
541 			return;
542 
543 		if (full_path)
544 		{
545 			gchar *utf8_path, *utf8_base_path;
546 
547 			utf8_path = build_path(iter);
548 			utf8_base_path = get_project_base_path();
549 			utf8_name = get_relative_path(utf8_base_path, utf8_path);
550 			g_free(utf8_path);
551 			g_free(utf8_base_path);
552 		}
553 		else
554 			gtk_tree_model_get(GTK_TREE_MODEL(model), iter, FILEVIEW_COLUMN_NAME, &utf8_name, -1);
555 
556 		if (!case_sensitive)
557 			SETPTR(utf8_name, g_utf8_strdown(utf8_name, -1));
558 
559 		if (g_pattern_match_string(pattern, utf8_name))
560 		{
561 			gchar *utf8_base_path = get_project_base_path();
562 			gchar *utf8_path, *rel_path;
563 
564 			utf8_path = build_path(iter);
565 			rel_path = get_relative_path(utf8_base_path, utf8_path);
566 			msgwin_msg_add(COLOR_BLACK, -1, NULL, "%s", rel_path ? rel_path : utf8_path);
567 			g_free(utf8_path);
568 			g_free(rel_path);
569 			g_free(utf8_base_path);
570 		}
571 
572 		g_free(utf8_name);
573 	}
574 }
575 
576 
find_file(GtkTreeIter * iter)577 static void find_file(GtkTreeIter *iter)
578 {
579 	gchar *pattern_str = NULL;
580 	gboolean case_sensitive, is_full_path;
581 	gchar *utf8_path = build_path(iter);
582 
583 	if (show_dialog_find_file(iter ? utf8_path : NULL, &pattern_str, &case_sensitive, &is_full_path) == GTK_RESPONSE_ACCEPT)
584 	{
585 		gchar *utf8_base_path = get_project_base_path();
586 		gchar *locale_base_path = utils_get_locale_from_utf8(utf8_base_path);
587 		GPatternSpec *pattern;
588 
589 		if (!case_sensitive)
590 			SETPTR(pattern_str, g_utf8_strdown(pattern_str, -1));
591 
592 		pattern = g_pattern_spec_new(pattern_str);
593 
594 		msgwin_clear_tab(MSG_MESSAGE);
595 		msgwin_set_messages_dir(locale_base_path);
596 		find_file_recursive(iter, case_sensitive, is_full_path, pattern);
597 		msgwin_switch_tab(MSG_MESSAGE, TRUE);
598 		g_free(utf8_base_path);
599 		g_free(locale_base_path);
600 		g_pattern_spec_free(pattern);
601 	}
602 
603 	g_free(pattern_str);
604 	g_free(utf8_path);
605 }
606 
607 
create_dialog_find_tag(void)608 static void create_dialog_find_tag(void)
609 {
610 	GtkWidget *label, *vbox, *ebox, *entry;
611 	GtkSizeGroup *size_group;
612 
613 	if (s_ft_dialog.widget)
614 		return;
615 
616 	s_ft_dialog.widget = gtk_dialog_new_with_buttons(
617 		_("Find Symbol"), GTK_WINDOW(geany->main_widgets->window),
618 		GTK_DIALOG_DESTROY_WITH_PARENT,
619 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
620 	gtk_dialog_add_button(GTK_DIALOG(s_ft_dialog.widget), "gtk-find", GTK_RESPONSE_ACCEPT);
621 	gtk_dialog_set_default_response(GTK_DIALOG(s_ft_dialog.widget), GTK_RESPONSE_ACCEPT);
622 
623 	vbox = ui_dialog_vbox_new(GTK_DIALOG(s_ft_dialog.widget));
624 	gtk_box_set_spacing(GTK_BOX(vbox), 9);
625 
626 	size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
627 
628 	label = gtk_label_new(_("Search for:"));
629 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
630 	gtk_size_group_add_widget(size_group, label);
631 
632 	s_ft_dialog.combo = gtk_combo_box_text_new_with_entry();
633 	entry = gtk_bin_get_child(GTK_BIN(s_ft_dialog.combo));
634 
635 	ui_entry_add_clear_icon(GTK_ENTRY(entry));
636 	gtk_entry_set_width_chars(GTK_ENTRY(entry), 40);
637 	gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
638 	ui_entry_add_clear_icon(GTK_ENTRY(entry));
639 	gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
640 
641 	ebox = gtk_hbox_new(FALSE, 6);
642 	gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0);
643 	gtk_box_pack_start(GTK_BOX(ebox), s_ft_dialog.combo, TRUE, TRUE, 0);
644 	gtk_box_pack_start(GTK_BOX(vbox), ebox, TRUE, FALSE, 0);
645 
646 	label = gtk_label_new(_("Match type:"));
647 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
648 	gtk_size_group_add_widget(size_group, label);
649 
650 	s_ft_dialog.combo_match = gtk_combo_box_text_new();
651 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(s_ft_dialog.combo_match), _("exact"));
652 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(s_ft_dialog.combo_match), _("prefix"));
653 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(s_ft_dialog.combo_match), _("pattern"));
654 	gtk_combo_box_set_active(GTK_COMBO_BOX(s_ft_dialog.combo_match), 1);
655 	gtk_label_set_mnemonic_widget(GTK_LABEL(label), s_ft_dialog.combo_match);
656 
657 	ebox = gtk_hbox_new(FALSE, 6);
658 	gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0);
659 	gtk_box_pack_start(GTK_BOX(ebox), s_ft_dialog.combo_match, TRUE, TRUE, 0);
660 	gtk_box_pack_start(GTK_BOX(vbox), ebox, TRUE, FALSE, 0);
661 
662 	label = gtk_label_new(_("Search inside:"));
663 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
664 	gtk_size_group_add_widget(size_group, label);
665 	s_ft_dialog.dir_label = gtk_label_new("");
666 	gtk_misc_set_alignment(GTK_MISC(s_ft_dialog.dir_label), 0, 0.5);
667 
668 	ebox = gtk_hbox_new(FALSE, 6);
669 	gtk_box_pack_start(GTK_BOX(ebox), label, FALSE, FALSE, 0);
670 	gtk_box_pack_start(GTK_BOX(ebox), s_ft_dialog.dir_label, TRUE, TRUE, 0);
671 
672 	gtk_box_pack_start(GTK_BOX(vbox), ebox, TRUE, FALSE, 0);
673 
674 	s_ft_dialog.case_sensitive = gtk_check_button_new_with_mnemonic(_("C_ase sensitive"));
675 	gtk_button_set_focus_on_click(GTK_BUTTON(s_ft_dialog.case_sensitive), FALSE);
676 
677 	s_ft_dialog.declaration = gtk_check_button_new_with_mnemonic(_("_Declaration"));
678 	gtk_button_set_focus_on_click(GTK_BUTTON(s_ft_dialog.declaration), FALSE);
679 
680 	g_object_unref(G_OBJECT(size_group));   /* auto destroy the size group */
681 
682 	gtk_container_add(GTK_CONTAINER(vbox), s_ft_dialog.case_sensitive);
683 	gtk_container_add(GTK_CONTAINER(vbox), s_ft_dialog.declaration);
684 	gtk_widget_show_all(vbox);
685 }
686 
687 
tm_tag_type_name(const TMTag * tag)688 static const char *tm_tag_type_name(const TMTag *tag)
689 {
690 	g_return_val_if_fail(tag, NULL);
691 	switch(tag->type)
692 	{
693 		case tm_tag_class_t: return "class";
694 		case tm_tag_enum_t: return "enum";
695 		case tm_tag_enumerator_t: return "enumval";
696 		case tm_tag_field_t: return "field";
697 		case tm_tag_function_t: return "function";
698 		case tm_tag_interface_t: return "interface";
699 		case tm_tag_member_t: return "member";
700 		case tm_tag_method_t: return "method";
701 		case tm_tag_namespace_t: return "namespace";
702 		case tm_tag_package_t: return "package";
703 		case tm_tag_prototype_t: return "prototype";
704 		case tm_tag_struct_t: return "struct";
705 		case tm_tag_typedef_t: return "typedef";
706 		case tm_tag_union_t: return "union";
707 		case tm_tag_variable_t: return "variable";
708 		case tm_tag_externvar_t: return "extern";
709 		case tm_tag_macro_t: return "define";
710 		case tm_tag_macro_with_arg_t: return "macro";
711 		default: return NULL;
712 	}
713 	return NULL;
714 }
715 
716 
match(TMTag * tag,const gchar * name,gboolean declaration,gboolean case_sensitive,MatchType match_type,GPatternSpec * pspec,gchar * utf8_path)717 static gboolean match(TMTag *tag, const gchar *name, gboolean declaration, gboolean case_sensitive,
718 	MatchType match_type, GPatternSpec *pspec, gchar *utf8_path)
719 {
720 	const gint forward_types = tm_tag_prototype_t | tm_tag_externvar_t;
721 	gboolean matches = FALSE;
722 	gint type;
723 
724 	type = declaration ? forward_types : tm_tag_max_t - forward_types;
725 	matches = tag->type & type;
726 
727 	if (matches)
728 	{
729 		gchar *name_case;
730 
731 		if (case_sensitive)
732 			name_case = g_strdup(tag->name);
733 		else
734 			name_case = g_utf8_strdown(tag->name, -1);
735 
736 		switch (match_type)
737 		{
738 			case MATCH_FULL:
739 				matches = g_strcmp0(name_case, name) == 0;
740 				break;
741 			case MATCH_PATTERN:
742 				matches = g_pattern_match_string(pspec, name_case);
743 				break;
744 			case MATCH_PREFIX:
745 				matches = g_str_has_prefix(name_case, name);
746 				break;
747 		}
748 		g_free(name_case);
749 	}
750 
751 	if (matches && utf8_path)
752 	{
753 		gchar *utf8_file_name = utils_get_utf8_from_locale(tag->file->file_name);
754 		gchar *relpath;
755 
756 		relpath = get_relative_path(utf8_path, utf8_file_name);
757 		matches = relpath != NULL;
758 		g_free(relpath);
759 		g_free(utf8_file_name);
760 	}
761 
762 	return matches;
763 }
764 
765 
find_tags(const gchar * name,gboolean declaration,gboolean case_sensitive,MatchType match_type,gchar * utf8_path)766 static void find_tags(const gchar *name, gboolean declaration, gboolean case_sensitive, MatchType match_type, gchar *utf8_path)
767 {
768 	gchar *utf8_base_path = get_project_base_path();
769 	gchar *locale_base_path = utils_get_locale_from_utf8(utf8_base_path);
770 	GPtrArray *tags_array = geany_data->app->tm_workspace->tags_array;
771 	guint i;
772 	gchar *name_case;
773 	GPatternSpec *pspec;
774 
775 	if (case_sensitive)
776 		name_case = g_strdup(name);
777 	else
778 		name_case = g_utf8_strdown(name, -1);
779 
780 	pspec = g_pattern_spec_new(name_case);
781 
782 	msgwin_set_messages_dir(locale_base_path);
783 	msgwin_clear_tab(MSG_MESSAGE);
784 	for (i = 0; i < tags_array->len; i++) /* TODO: binary search */
785 	{
786 		TMTag *tag = tags_array->pdata[i];
787 
788 		if (match(tag, name_case, declaration, case_sensitive, match_type, pspec, utf8_path))
789 		{
790 			gchar *scopestr = tag->scope ? g_strconcat(tag->scope, "::", NULL) : g_strdup("");
791 			gchar *utf8_fname = utils_get_utf8_from_locale(tag->file->file_name);
792 			gchar *relpath;
793 
794 			relpath = get_relative_path(utf8_base_path, utf8_fname);
795 			msgwin_msg_add(COLOR_BLACK, -1, NULL, "%s:%lu:\n\t[%s]\t %s%s%s", relpath ? relpath : utf8_fname,
796 				tag->line, tm_tag_type_name(tag), scopestr, tag->name, tag->arglist ? tag->arglist : "");
797 			g_free(scopestr);
798 			g_free(relpath);
799 			g_free(utf8_fname);
800 		}
801 	}
802 	msgwin_switch_tab(MSG_MESSAGE, TRUE);
803 
804 	g_free(name_case);
805 	g_pattern_spec_free(pspec);
806 	g_free(utf8_base_path);
807 	g_free(locale_base_path);
808 }
809 
810 
find_tag(GtkTreeIter * iter)811 static void find_tag(GtkTreeIter *iter)
812 {
813 	gchar *selection;
814 	gchar *utf8_path;
815 	GtkWidget *entry;
816 
817 	create_dialog_find_tag();
818 
819 	entry = gtk_bin_get_child(GTK_BIN(s_ft_dialog.combo));
820 
821 	utf8_path = build_path(iter);
822 	if (iter)
823 		gtk_label_set_text(GTK_LABEL(s_ft_dialog.dir_label), utf8_path);
824 	else
825 		gtk_label_set_text(GTK_LABEL(s_ft_dialog.dir_label), _("project or external directory"));
826 
827 	selection = get_selection();
828 	if (selection)
829 		gtk_entry_set_text(GTK_ENTRY(entry), selection);
830 	g_free(selection);
831 
832 	gtk_widget_grab_focus(entry);
833 
834 	if (gtk_dialog_run(GTK_DIALOG(s_ft_dialog.widget)) == GTK_RESPONSE_ACCEPT)
835 	{
836 		const gchar *name;
837 		gboolean case_sensitive, declaration;
838 		MatchType match_type;
839 
840 		name = gtk_entry_get_text(GTK_ENTRY(entry));
841 		case_sensitive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s_ft_dialog.case_sensitive));
842 		declaration = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s_ft_dialog.declaration));
843 		match_type = gtk_combo_box_get_active(GTK_COMBO_BOX(s_ft_dialog.combo_match));
844 
845 		ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(s_ft_dialog.combo), name, 0);
846 
847 		find_tags(name, declaration, case_sensitive, match_type, iter ? utf8_path : NULL);
848 	}
849 
850 	g_free(utf8_path);
851 	gtk_widget_hide(s_ft_dialog.widget);
852 }
853 
854 
on_find_file(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)855 static void on_find_file(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
856 {
857 	GtkTreeSelection *treesel;
858 	GtkTreeModel *model;
859 	GtkTreeIter iter, parent;
860 
861 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
862 	if (!gtk_tree_selection_get_selected(treesel, &model, &iter))
863 		return;
864 
865 	if (!gtk_tree_model_iter_has_child(model, &iter))
866 	{
867 		if (gtk_tree_model_iter_parent(model, &parent, &iter))
868 			find_file(&parent);
869 		else
870 			find_file(NULL);
871 	}
872 	else
873 		find_file(&iter);
874 }
875 
876 
on_find_tag(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)877 static void on_find_tag(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
878 {
879 	GtkTreeSelection *treesel;
880 	GtkTreeModel *model;
881 	GtkTreeIter iter, parent;
882 
883 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
884 	if (!gtk_tree_selection_get_selected(treesel, &model, &iter))
885 		return;
886 
887 	if (!gtk_tree_model_iter_has_child(model, &iter))
888 	{
889 		if (gtk_tree_model_iter_parent(model, &parent, &iter))
890 			find_tag(&parent);
891 		else
892 			find_tag(NULL);
893 	}
894 	else
895 		find_tag(&iter);
896 }
897 
898 
on_reload_project(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)899 static void on_reload_project(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
900 {
901 	prjorg_project_rescan();
902 	prjorg_sidebar_update(TRUE);
903 }
904 
905 
on_open_clicked(void)906 static void on_open_clicked(void)
907 {
908 	GtkTreeSelection *treesel;
909 	GtkTreeModel *model;
910 	GtkTreeIter iter;
911 
912 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
913 
914 	if (gtk_tree_selection_get_selected(treesel, &model, &iter))
915 	{
916 		if (gtk_tree_model_iter_has_child(model, &iter))
917 		{
918 			GtkTreeView *tree_view;
919 			GtkTreePath *tree_path;
920 
921 			tree_view = GTK_TREE_VIEW(s_file_view);
922 			tree_path = gtk_tree_model_get_path (model, &iter);
923 
924 			if (gtk_tree_view_row_expanded(tree_view, tree_path))
925 				gtk_tree_view_collapse_row(tree_view, tree_path);
926 			else
927 				gtk_tree_view_expand_row(tree_view, tree_path, FALSE);
928 			gtk_tree_path_free(tree_path);
929 		}
930 		else
931 		{
932 			gchar *utf8_path;
933 			GIcon *icon;
934 
935 			gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_ICON, &icon, -1);
936 
937 			if (!icon)
938 			{
939 				/* help string doesn't have icon */
940 				return;
941 			}
942 
943 			utf8_path = build_path(&iter);
944 			open_file(utf8_path);
945 			g_free(utf8_path);
946 			g_object_unref(icon);
947 		}
948 	}
949 }
950 
951 
on_button_release(G_GNUC_UNUSED GtkWidget * widget,GdkEventButton * event,G_GNUC_UNUSED gpointer user_data)952 static gboolean on_button_release(G_GNUC_UNUSED GtkWidget * widget, GdkEventButton * event,
953 		G_GNUC_UNUSED gpointer user_data)
954 {
955 	if (event->button == 3)
956 	{
957 		GtkTreeSelection *treesel;
958 		GtkTreeModel *model;
959 		GtkTreeIter iter;
960 		gboolean delete_enabled = TRUE;
961 		gchar *path;
962 
963 		treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
964 
965 		if (!gtk_tree_selection_get_selected(treesel, &model, &iter))
966 			return FALSE;
967 
968 		gtk_widget_set_sensitive(s_popup_menu.expand, gtk_tree_model_iter_has_child(model, &iter));
969 		gtk_widget_set_sensitive(s_popup_menu.remove_external_dir, topmost_selected(model, &iter, FALSE));
970 
971 		path = build_path(&iter);
972 		SETPTR(path, utils_get_locale_from_utf8(path));
973 		if (g_file_test(path, G_FILE_TEST_IS_DIR))
974 		{
975 			GDir *dir = g_dir_open(path, 0, NULL);
976 
977 			delete_enabled = FALSE;
978 			if (dir)
979 			{
980 				delete_enabled = g_dir_read_name(dir) == NULL;
981 				g_dir_close(dir);
982 			}
983 		}
984 		g_free(path);
985 
986 		gtk_widget_set_sensitive(s_popup_menu.delete, delete_enabled);
987 
988 		gtk_menu_popup(GTK_MENU(s_popup_menu.widget), NULL, NULL, NULL, NULL,
989 						event->button, event->time);
990 		return TRUE;
991 	}
992 
993 	return FALSE;
994 }
995 
996 
on_button_press(G_GNUC_UNUSED GtkWidget * widget,GdkEventButton * event,G_GNUC_UNUSED gpointer user_data)997 static gboolean on_button_press(G_GNUC_UNUSED GtkWidget * widget, GdkEventButton * event,
998 		G_GNUC_UNUSED gpointer user_data)
999 {
1000 	if (event->button == 1 && event->type == GDK_2BUTTON_PRESS)
1001 	{
1002 		on_open_clicked();
1003 		return TRUE;
1004 	}
1005 
1006 	return FALSE;
1007 }
1008 
1009 
on_key_press(G_GNUC_UNUSED GtkWidget * widget,GdkEventKey * event,G_GNUC_UNUSED gpointer data)1010 static gboolean on_key_press(G_GNUC_UNUSED GtkWidget * widget, GdkEventKey * event, G_GNUC_UNUSED gpointer data)
1011 {
1012 	if (event->keyval == GDK_Return
1013 		|| event->keyval == GDK_ISO_Enter
1014 		|| event->keyval == GDK_KP_Enter
1015 		|| event->keyval == GDK_space)
1016 	{
1017 		on_open_clicked();
1018 		return TRUE;
1019 	}
1020 	return FALSE;
1021 }
1022 
1023 
expand_all(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)1024 static void expand_all(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
1025 {
1026 	GtkTreeSelection *treesel;
1027 	GtkTreeIter iter;
1028 	GtkTreeModel *model;
1029 
1030 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
1031 
1032 	if (gtk_tree_selection_get_selected(treesel, &model, &iter))
1033 	{
1034 		GtkTreePath *tree_path = gtk_tree_model_get_path (model, &iter);
1035 		gtk_tree_view_expand_row(GTK_TREE_VIEW(s_file_view), tree_path, TRUE);
1036 		gtk_tree_path_free(tree_path);
1037 	}
1038 }
1039 
1040 
on_find_in_files(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)1041 static void on_find_in_files(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
1042 {
1043 	GtkTreeSelection *treesel;
1044 	GtkTreeIter iter, parent;
1045 	GtkTreeModel *model;
1046 	gchar *utf8_path;
1047 
1048 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
1049 
1050 	if (!gtk_tree_selection_get_selected(treesel, &model, &iter))
1051 		return;
1052 
1053 	if (!gtk_tree_model_iter_has_child(model, &iter))
1054 	{
1055 		if (gtk_tree_model_iter_parent(model, &parent, &iter))
1056 			utf8_path = build_path(&parent);
1057 		else
1058 			utf8_path = build_path(NULL);
1059 	}
1060 	else
1061 		utf8_path = build_path(&iter);
1062 
1063 	search_show_find_in_files_dialog(utf8_path);
1064 	g_free(utf8_path);
1065 }
1066 
1067 
create_branch(gint level,GSList * leaf_list,GtkTreeIter * parent,GSList * header_patterns,GSList * source_patterns,gboolean project)1068 static void create_branch(gint level, GSList *leaf_list, GtkTreeIter *parent,
1069 	GSList *header_patterns, GSList *source_patterns, gboolean project)
1070 {
1071 	GSList *dir_list = NULL;
1072 	GSList *file_list = NULL;
1073 	GSList *elem = NULL;
1074 
1075 	foreach_slist (elem, leaf_list)
1076 	{
1077 		gchar **path_arr = elem->data;
1078 
1079 		if (path_arr[level+1] != NULL)
1080 			dir_list = g_slist_prepend(dir_list, path_arr);
1081 		else
1082 			file_list = g_slist_prepend(file_list, path_arr);
1083 	}
1084 
1085 	foreach_slist (elem, file_list)
1086 	{
1087 		GtkTreeIter iter;
1088 		gchar **path_arr = elem->data;
1089 		GIcon *icon = NULL;
1090 
1091 		if (g_strcmp0(PROJORG_DIR_ENTRY, path_arr[level]) == 0)
1092 			continue;
1093 
1094 		gchar *content_type = g_content_type_guess(path_arr[level], NULL, 0, NULL);
1095 
1096 		if (content_type)
1097 		{
1098 			icon = g_content_type_get_icon(content_type);
1099 			if (icon)
1100 			{
1101 				GtkIconInfo *icon_info;
1102 
1103 				icon_info = gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_default(), icon, 16, 0);
1104 				if (!icon_info)
1105 				{
1106 					g_object_unref(icon);
1107 					icon = NULL;
1108 				}
1109 				else
1110 					gtk_icon_info_free(icon_info);
1111 			}
1112 			g_free(content_type);
1113 		}
1114 
1115 		if (! icon)
1116 			icon = g_themed_icon_new("text-x-generic");
1117 
1118 		gtk_tree_store_insert_with_values(s_file_store, &iter, parent, 0,
1119 			FILEVIEW_COLUMN_ICON, icon,
1120 			FILEVIEW_COLUMN_NAME, path_arr[level],
1121 			FILEVIEW_COLUMN_COLOR, project ? NULL : &s_external_color, -1);
1122 
1123 		if (icon)
1124 			g_object_unref(icon);
1125 	}
1126 
1127 	if (dir_list)
1128 	{
1129 		GSList *tmp_list = NULL;
1130 		GtkTreeIter iter;
1131 		gchar **path_arr = dir_list->data;
1132 		gchar *last_dir_name;
1133 		GIcon *icon_dir = g_themed_icon_new("folder");
1134 
1135 		last_dir_name = path_arr[level];
1136 
1137 		foreach_slist (elem, dir_list)
1138 		{
1139 			gboolean dir_changed;
1140 
1141 			path_arr = (gchar **) elem->data;
1142 			dir_changed = g_strcmp0(last_dir_name, path_arr[level]) != 0;
1143 
1144 			if (dir_changed)
1145 			{
1146 				gtk_tree_store_insert_with_values(s_file_store, &iter, parent, 0,
1147 					FILEVIEW_COLUMN_ICON, icon_dir,
1148 					FILEVIEW_COLUMN_NAME, last_dir_name,
1149 					FILEVIEW_COLUMN_COLOR, project ? NULL : &s_external_color, -1);
1150 
1151 				create_branch(level+1, tmp_list, &iter, header_patterns, source_patterns, project);
1152 
1153 				g_slist_free(tmp_list);
1154 				tmp_list = NULL;
1155 				last_dir_name = path_arr[level];
1156 			}
1157 
1158 			tmp_list = g_slist_prepend(tmp_list, path_arr);
1159 		}
1160 
1161 		gtk_tree_store_insert_with_values(s_file_store, &iter, parent, 0,
1162 			FILEVIEW_COLUMN_ICON, icon_dir,
1163 			FILEVIEW_COLUMN_NAME, last_dir_name,
1164 			FILEVIEW_COLUMN_COLOR, project ? NULL : &s_external_color, -1);
1165 
1166 		create_branch(level+1, tmp_list, &iter, header_patterns, source_patterns, project);
1167 
1168 		g_slist_free(tmp_list);
1169 		g_slist_free(dir_list);
1170 		g_object_unref(icon_dir);
1171 	}
1172 
1173 	g_slist_free(file_list);
1174 }
1175 
1176 
set_intro_message(const gchar * msg)1177 static void set_intro_message(const gchar *msg)
1178 {
1179 	GtkTreeIter iter;
1180 
1181 	gtk_tree_store_insert_with_values(s_file_store, &iter, NULL, -1,
1182 		FILEVIEW_COLUMN_NAME, msg, -1);
1183 
1184 	gtk_widget_set_sensitive(s_project_toolbar.expand, FALSE);
1185 	gtk_widget_set_sensitive(s_project_toolbar.collapse, FALSE);
1186 	gtk_widget_set_sensitive(s_project_toolbar.follow, FALSE);
1187 	gtk_widget_set_sensitive(s_project_toolbar.add, FALSE);
1188 }
1189 
1190 
rev_strcmp(const char * str1,const char * str2)1191 static int rev_strcmp(const char *str1, const char *str2)
1192 {
1193 	return strcmp(str2, str1);
1194 }
1195 
1196 
load_project_root(PrjOrgRoot * root,GtkTreeIter * parent,GSList * header_patterns,GSList * source_patterns,gboolean project)1197 static void load_project_root(PrjOrgRoot *root, GtkTreeIter *parent, GSList *header_patterns, GSList *source_patterns, gboolean project)
1198 {
1199 	GSList *lst = NULL;
1200 	GSList *path_list = NULL;
1201 	GSList *elem = NULL;
1202 	GHashTableIter iter;
1203 	gpointer key, value;
1204 
1205 	g_hash_table_iter_init(&iter, root->file_table);
1206 	while (g_hash_table_iter_next(&iter, &key, &value))
1207 	{
1208 		gchar *path = get_relative_path(root->base_dir, key);
1209 		lst = g_slist_prepend(lst, path);
1210 	}
1211 	/* sort in reverse order so we can prepend nodes to the tree store -
1212 	 * the store behaves as a linked list and prepending is faster */
1213 	lst = g_slist_sort(lst, (GCompareFunc) rev_strcmp);
1214 
1215 	foreach_slist (elem, lst)
1216 	{
1217 		gchar **path_split;
1218 
1219 		path_split = g_strsplit_set(elem->data, "/\\", 0);
1220 		path_list = g_slist_prepend(path_list, path_split);
1221 	}
1222 
1223 	if (path_list != NULL)
1224 		create_branch(0, path_list, parent, header_patterns, source_patterns, project);
1225 
1226 	if (project)
1227 	{
1228 		if (path_list != NULL)
1229 		{
1230 			gtk_widget_set_sensitive(s_project_toolbar.expand, TRUE);
1231 			gtk_widget_set_sensitive(s_project_toolbar.collapse, TRUE);
1232 			gtk_widget_set_sensitive(s_project_toolbar.follow, TRUE);
1233 			gtk_widget_set_sensitive(s_project_toolbar.add, TRUE);
1234 		}
1235 		else
1236 			set_intro_message(_("Set file patterns under Project->Properties"));
1237 	}
1238 
1239 	g_slist_foreach(lst, (GFunc) g_free, NULL);
1240 	g_slist_free(lst);
1241 	g_slist_foreach(path_list, (GFunc) g_strfreev, NULL);
1242 	g_slist_free(path_list);
1243 }
1244 
1245 
load_project(void)1246 static void load_project(void)
1247 {
1248 	GSList *elem = NULL, *header_patterns, *source_patterns;
1249 	GtkTreeIter iter;
1250 	gboolean first = TRUE;
1251 	GIcon *icon_dir;
1252 
1253 	gtk_tree_store_clear(s_file_store);
1254 
1255 	if (!prj_org || !geany_data->app->project)
1256 		return;
1257 
1258 	icon_dir = g_themed_icon_new("folder");
1259 
1260 	header_patterns = get_precompiled_patterns(prj_org->header_patterns);
1261 	source_patterns = get_precompiled_patterns(prj_org->source_patterns);
1262 
1263 	/* reload on every refresh to update the color e.g. when the theme changes */
1264 	s_external_color = gtk_widget_get_style(s_toolbar)->bg[GTK_STATE_NORMAL];
1265 
1266 	foreach_slist (elem, prj_org->roots)
1267 	{
1268 		PrjOrgRoot *root = elem->data;
1269 		gchar *name;
1270 
1271 		if (first)
1272 			name = g_strconcat("<b>", geany_data->app->project->name, "</b>", NULL);
1273 		else
1274 			name = g_strdup(root->base_dir);
1275 
1276 		gtk_tree_store_insert_with_values(s_file_store, &iter, NULL, -1,
1277 			FILEVIEW_COLUMN_ICON, icon_dir,
1278 			FILEVIEW_COLUMN_NAME, name,
1279 			FILEVIEW_COLUMN_COLOR, first ? NULL : &s_external_color, -1);
1280 
1281 		load_project_root(root, &iter, header_patterns, source_patterns, first);
1282 
1283 		first = FALSE;
1284 		g_free(name);
1285 	}
1286 
1287 	collapse();
1288 
1289 	g_slist_foreach(header_patterns, (GFunc) g_pattern_spec_free, NULL);
1290 	g_slist_free(header_patterns);
1291 	g_slist_foreach(source_patterns, (GFunc) g_pattern_spec_free, NULL);
1292 	g_slist_free(source_patterns);
1293 	g_object_unref(icon_dir);
1294 }
1295 
1296 
find_in_tree(GtkTreeIter * parent,gchar ** path_split,gint level,GtkTreeIter * ret)1297 static gboolean find_in_tree(GtkTreeIter *parent, gchar **path_split, gint level, GtkTreeIter *ret)
1298 {
1299 	GtkTreeModel *model;
1300 	GtkTreeIter iter;
1301 	gboolean iterate;
1302 
1303 	model = GTK_TREE_MODEL(s_file_store);
1304 
1305 	iterate = gtk_tree_model_iter_children(model, &iter, parent);
1306 	while (iterate)
1307 	{
1308 		gchar *name;
1309 		gint cmpres;
1310 
1311 		gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_NAME, &name, -1);
1312 
1313 		cmpres = g_strcmp0(name, path_split[level]);
1314 		g_free(name);
1315 		if (cmpres == 0)
1316 		{
1317 			if (path_split[level+1] == NULL)
1318 			{
1319 				*ret = iter;
1320 				return TRUE;
1321 			}
1322 			else
1323 				return find_in_tree(&iter, path_split, level + 1, ret);
1324 		}
1325 
1326 		iterate = gtk_tree_model_iter_next(model, &iter);
1327 	}
1328 
1329 	return FALSE;
1330 }
1331 
1332 
expand_path(gchar * utf8_expanded_path,gboolean select)1333 static gboolean expand_path(gchar *utf8_expanded_path, gboolean select)
1334 {
1335 	GtkTreeIter root_iter, found_iter;
1336 	gchar *utf8_path = NULL;
1337 	gchar **path_split;
1338 	GSList *elem = NULL;
1339 	GtkTreeModel *model;
1340 
1341 	model = GTK_TREE_MODEL(s_file_store);
1342 	gtk_tree_model_iter_children(model, &root_iter, NULL);
1343 	foreach_slist (elem, prj_org->roots)
1344 	{
1345 		PrjOrgRoot *root = elem->data;
1346 
1347 		utf8_path = get_relative_path(root->base_dir, utf8_expanded_path);
1348 		if (utf8_path)
1349 			break;
1350 
1351 		g_free(utf8_path);
1352 		utf8_path = NULL;
1353 		if (!gtk_tree_model_iter_next(model, &root_iter))
1354 			break;
1355 	}
1356 
1357 	if (!utf8_path)
1358 		return FALSE;
1359 
1360 	path_split = g_strsplit_set(utf8_path, "/\\", 0);
1361 
1362 	if (find_in_tree(&root_iter, path_split, 0, &found_iter))
1363 	{
1364 		GtkTreePath *tree_path;
1365 		GtkTreeSelection *treesel;
1366 
1367 		tree_path = gtk_tree_model_get_path (model, &found_iter);
1368 		gtk_tree_view_expand_to_path(GTK_TREE_VIEW(s_file_view), tree_path);
1369 
1370 		if (select)
1371 		{
1372 			gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(s_file_view), tree_path,
1373 				NULL, FALSE, 0.0, 0.0);
1374 
1375 			treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
1376 			gtk_tree_selection_select_iter(treesel, &found_iter);
1377 			gtk_tree_path_free(tree_path);
1378 		}
1379 	}
1380 
1381 	g_free(utf8_path);
1382 	g_strfreev(path_split);
1383 
1384 	return FALSE;
1385 }
1386 
1387 
expand_on_idle(ExpandData * expand_data)1388 static gboolean expand_on_idle(ExpandData *expand_data)
1389 {
1390 	GeanyDocument *doc = document_get_current();
1391 
1392 	if (!prj_org)
1393 		return FALSE;
1394 
1395 	if (geany_data->app->project == expand_data->project &&
1396 		expand_data->expanded_paths)
1397 	{
1398 		gchar *item;
1399 		guint i;
1400 
1401 		foreach_ptr_array(item, i, expand_data->expanded_paths)
1402 			expand_path(item, FALSE);
1403 		g_ptr_array_free(expand_data->expanded_paths, TRUE);
1404 	}
1405 
1406 	g_free(expand_data);
1407 
1408 	if (!s_follow_editor || !doc || !doc->file_name)
1409 		return FALSE;
1410 
1411 	expand_path(doc->file_name, TRUE);
1412 
1413 	return FALSE;
1414 }
1415 
1416 
on_map_expanded(GtkTreeView * tree_view,GtkTreePath * tree_path,GPtrArray * path_arr)1417 static void on_map_expanded(GtkTreeView *tree_view, GtkTreePath *tree_path, GPtrArray *path_arr)
1418 {
1419 	GtkTreeIter iter;
1420 
1421 	if (gtk_tree_model_get_iter(GTK_TREE_MODEL(s_file_store), &iter, tree_path))
1422 	{
1423 		gchar *utf8_path = build_path(&iter);
1424 		gboolean replaced = FALSE;
1425 
1426 		if (path_arr->len > 0)
1427 		{
1428 			gchar *previous = path_arr->pdata[path_arr->len-1];
1429 			gchar *rel_path = get_relative_path(previous, utf8_path);
1430 
1431 			/* Filter-out redundant parent paths. gtk_tree_view_map_expanded_rows()
1432 			 * returns first parent paths and then nested paths so we can just
1433 			 * check if the last stored path is a parent of current path and
1434 			 * if so, replace it with curent path. */
1435 			if (rel_path)
1436 			{
1437 				g_free(previous);
1438 				path_arr->pdata[path_arr->len-1] = utf8_path;
1439 				replaced = TRUE;
1440 			}
1441 
1442 			g_free(rel_path);
1443 		}
1444 
1445 		if (!replaced)
1446 			g_ptr_array_add(path_arr, utf8_path);
1447 	}
1448 }
1449 
1450 
get_expanded_paths(void)1451 static GPtrArray *get_expanded_paths(void)
1452 {
1453 	GPtrArray *expanded_paths = g_ptr_array_new_with_free_func(g_free);
1454 
1455 	gtk_tree_view_map_expanded_rows(GTK_TREE_VIEW(s_file_view),
1456 		(GtkTreeViewMappingFunc)on_map_expanded, expanded_paths);
1457 
1458 	return expanded_paths;
1459 }
1460 
1461 
prjorg_sidebar_update(gboolean reload)1462 void prjorg_sidebar_update(gboolean reload)
1463 {
1464 	ExpandData *expand_data = g_new0(ExpandData, 1);
1465 
1466 	expand_data->project = geany_data->app->project;
1467 
1468 	if (reload)
1469 	{
1470 		expand_data->expanded_paths = get_expanded_paths();
1471 
1472 		load_project();
1473 		/* we get color information only after the sidebar is realized -
1474 		 * perform reload later if this is not the case yet */
1475 		if (!gtk_widget_get_realized(s_toolbar))
1476 			s_pending_reload = TRUE;
1477 	}
1478 
1479 	/* perform on idle - avoids unnecessary jumps on project load */
1480 	plugin_idle_add(geany_plugin, (GSourceFunc)expand_on_idle, expand_data);
1481 }
1482 
1483 
prjorg_sidebar_find_file_in_active(void)1484 void prjorg_sidebar_find_file_in_active(void)
1485 {
1486 	find_file(NULL);
1487 }
1488 
1489 
prjorg_sidebar_find_tag_in_active(void)1490 void prjorg_sidebar_find_tag_in_active(void)
1491 {
1492 	find_tag(NULL);
1493 }
1494 
1495 
prjorg_sidebar_focus_project_tab(void)1496 void prjorg_sidebar_focus_project_tab(void)
1497 {
1498 	GtkNotebook *notebook = GTK_NOTEBOOK(geany_data->main_widgets->sidebar_notebook);
1499 	gint pagenum = gtk_notebook_get_n_pages(notebook);
1500 	gint i;
1501 
1502 	for (i = 0; i < pagenum; i++)
1503 	{
1504 		GtkWidget *page = gtk_notebook_get_nth_page(notebook, i);
1505 		if (page == s_file_view_vbox)
1506 		{
1507 			gtk_notebook_set_current_page(notebook, i);
1508 			gtk_widget_grab_focus(s_file_view);
1509 			break;
1510 		}
1511 	}
1512 }
1513 
1514 
sidebar_realized_cb(GtkWidget * widget)1515 static void sidebar_realized_cb (GtkWidget *widget) {
1516 	if (s_pending_reload)
1517 		prjorg_sidebar_update(TRUE);
1518 }
1519 
1520 
prjorg_sidebar_init(void)1521 void prjorg_sidebar_init(void)
1522 {
1523 	GtkWidget *scrollwin, *item, *image;
1524 	GtkCellRenderer *renderer;
1525 	GtkTreeViewColumn *column;
1526 	GtkTreeSelection *sel;
1527 	PangoFontDescription *pfd;
1528 	GList *focus_chain = NULL;
1529 
1530 	s_file_view_vbox = gtk_vbox_new(FALSE, 0);
1531 
1532 	/**** toolbar ****/
1533 
1534 	s_toolbar = gtk_toolbar_new();
1535 	gtk_toolbar_set_icon_size(GTK_TOOLBAR(s_toolbar), GTK_ICON_SIZE_MENU);
1536 	gtk_toolbar_set_style(GTK_TOOLBAR(s_toolbar), GTK_TOOLBAR_ICONS);
1537 
1538 	g_signal_connect (s_toolbar, "realize", G_CALLBACK (sidebar_realized_cb), NULL);
1539 
1540 	image = gtk_image_new_from_icon_name("view-refresh", GTK_ICON_SIZE_SMALL_TOOLBAR);
1541 	item = GTK_WIDGET(gtk_tool_button_new(image, NULL));
1542 	gtk_widget_set_tooltip_text(item, _("Reload all"));
1543 	g_signal_connect(item, "clicked", G_CALLBACK(on_reload_project), NULL);
1544 	gtk_container_add(GTK_CONTAINER(s_toolbar), item);
1545 
1546 	item = GTK_WIDGET(gtk_separator_tool_item_new());
1547 	gtk_container_add(GTK_CONTAINER(s_toolbar), item);
1548 
1549 	image = gtk_image_new_from_icon_name("folder-new", GTK_ICON_SIZE_SMALL_TOOLBAR);
1550 	item = GTK_WIDGET(gtk_tool_button_new(image, NULL));
1551 	gtk_widget_set_tooltip_text(item, _("Add external directory"));
1552 	g_signal_connect(item, "clicked", G_CALLBACK(on_add_external), NULL);
1553 	gtk_container_add(GTK_CONTAINER(s_toolbar), item);
1554 	s_project_toolbar.add = item;
1555 
1556 	item = GTK_WIDGET(gtk_separator_tool_item_new());
1557 	gtk_container_add(GTK_CONTAINER(s_toolbar), item);
1558 
1559 	image = gtk_image_new_from_icon_name("go-down", GTK_ICON_SIZE_SMALL_TOOLBAR);
1560 	item = GTK_WIDGET(gtk_tool_button_new(image, NULL));
1561 	gtk_widget_set_tooltip_text(item, _("Expand all"));
1562 	g_signal_connect(item, "clicked", G_CALLBACK(on_expand_all), NULL);
1563 	gtk_container_add(GTK_CONTAINER(s_toolbar), item);
1564 	s_project_toolbar.expand = item;
1565 
1566 	image = gtk_image_new_from_icon_name("go-up", GTK_ICON_SIZE_SMALL_TOOLBAR);
1567 	item = GTK_WIDGET(gtk_tool_button_new(image, NULL));
1568 	gtk_widget_set_tooltip_text(item, _("Collapse to project root"));
1569 	g_signal_connect(item, "clicked", G_CALLBACK(on_collapse_all), NULL);
1570 	gtk_container_add(GTK_CONTAINER(s_toolbar), item);
1571 	s_project_toolbar.collapse = item;
1572 
1573 	item = GTK_WIDGET(gtk_separator_tool_item_new());
1574 	gtk_container_add(GTK_CONTAINER(s_toolbar), item);
1575 
1576 	item = GTK_WIDGET(gtk_toggle_tool_button_new());
1577 	gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), "go-jump");
1578 	gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(item), TRUE);
1579 	gtk_widget_set_tooltip_text(item, _("Follow active editor"));
1580 	g_signal_connect(item, "clicked", G_CALLBACK(on_follow_active), NULL);
1581 	gtk_container_add(GTK_CONTAINER(s_toolbar), item);
1582 	s_project_toolbar.follow = item;
1583 
1584 	gtk_box_pack_start(GTK_BOX(s_file_view_vbox), s_toolbar, FALSE, FALSE, 0);
1585 
1586 	/**** tree view ****/
1587 
1588 	s_file_view = gtk_tree_view_new();
1589 
1590 	s_file_store = gtk_tree_store_new(FILEVIEW_N_COLUMNS, G_TYPE_ICON, G_TYPE_STRING, GDK_TYPE_COLOR);
1591 	gtk_tree_view_set_model(GTK_TREE_VIEW(s_file_view), GTK_TREE_MODEL(s_file_store));
1592 
1593 	renderer = gtk_cell_renderer_pixbuf_new();
1594 	column = gtk_tree_view_column_new();
1595 	gtk_tree_view_column_pack_start(column, renderer, FALSE);
1596 	gtk_tree_view_column_add_attribute(column, renderer, "gicon", FILEVIEW_COLUMN_ICON);
1597 	gtk_tree_view_column_add_attribute(column, renderer, "cell-background-gdk", FILEVIEW_COLUMN_COLOR);
1598 
1599 	renderer = gtk_cell_renderer_text_new();
1600 	gtk_tree_view_column_pack_start(column, renderer, TRUE);
1601 	gtk_tree_view_column_add_attribute(column, renderer, "markup", FILEVIEW_COLUMN_NAME);
1602 	gtk_tree_view_column_add_attribute(column, renderer, "cell-background-gdk", FILEVIEW_COLUMN_COLOR);
1603 
1604 	gtk_tree_view_append_column(GTK_TREE_VIEW(s_file_view), column);
1605 
1606 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(s_file_view), FALSE);
1607 	gtk_tree_view_set_enable_search(GTK_TREE_VIEW(s_file_view), TRUE);
1608 	gtk_tree_view_set_search_column(GTK_TREE_VIEW(s_file_view), FILEVIEW_COLUMN_NAME);
1609 
1610 	pfd = pango_font_description_from_string(geany_data->interface_prefs->tagbar_font);
1611 	gtk_widget_modify_font(s_file_view, pfd);
1612 	pango_font_description_free(pfd);
1613 
1614 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s_file_view));
1615 	gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE);
1616 
1617 	g_signal_connect(G_OBJECT(s_file_view), "button-release-event",
1618 			G_CALLBACK(on_button_release), NULL);
1619 /*	row-activated grabs focus for the sidebar, use button-press-event instead */
1620 	g_signal_connect(G_OBJECT(s_file_view), "button-press-event",
1621 			G_CALLBACK(on_button_press), NULL);
1622 	g_signal_connect(G_OBJECT(s_file_view), "key-press-event",
1623 			G_CALLBACK(on_key_press), NULL);
1624 
1625 	set_intro_message(_("Open a project to start using the plugin"));
1626 	prjorg_sidebar_activate(FALSE);
1627 
1628 	/**** popup menu ****/
1629 
1630 	s_popup_menu.widget = gtk_menu_new();
1631 
1632 	item = menu_item_new("go-down", _("Expand All"));
1633 	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
1634 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(expand_all), NULL);
1635 	s_popup_menu.expand = item;
1636 
1637 	item = menu_item_new("edit-find", _("Find in Files..."));
1638 	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
1639 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_find_in_files), NULL);
1640 	s_popup_menu.find_in_directory = item;
1641 
1642 	item = menu_item_new("edit-find", _("Find File..."));
1643 	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
1644 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_find_file), NULL);
1645 	s_popup_menu.find_file = item;
1646 
1647 	item = menu_item_new("edit-find", _("Find Symbol..."));
1648 	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
1649 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_find_tag), NULL);
1650 	s_popup_menu.find_tag = item;
1651 
1652 	item = gtk_separator_menu_item_new();
1653 	gtk_widget_show(item);
1654 	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
1655 
1656 	item = menu_item_new("list-remove", _("Remove External Directory"));
1657 	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
1658 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_remove_external_dir), NULL);
1659 	s_popup_menu.remove_external_dir = item;
1660 
1661 	item = gtk_separator_menu_item_new();
1662 	gtk_widget_show(item);
1663 	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
1664 
1665 	item = menu_item_new("document-new", _("New File..."));
1666 	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
1667 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_create_file), NULL);
1668 	s_popup_menu.create_file = item;
1669 
1670 	item = menu_item_new("folder-new", _("New Directory..."));
1671 	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
1672 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_create_dir), NULL);
1673 	s_popup_menu.create_dir = item;
1674 
1675 	item = menu_item_new("document-save-as", _("Rename..."));
1676 	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
1677 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_rename), NULL);
1678 	s_popup_menu.rename = item;
1679 
1680 	item = menu_item_new("edit-delete", _("Delete"));
1681 	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
1682 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_delete), NULL);
1683 	s_popup_menu.delete = item;
1684 
1685 	item = gtk_separator_menu_item_new();
1686 	gtk_widget_show(item);
1687 	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
1688 
1689 	item = menu_item_new("window-close", _("H_ide Sidebar"));
1690 	gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
1691 	g_signal_connect_swapped((gpointer) item, "activate",
1692 				 G_CALLBACK(keybindings_send_command),
1693 				 GINT_TO_POINTER(GEANY_KEYS_VIEW_SIDEBAR));
1694 
1695 	/**** the rest ****/
1696 
1697 	focus_chain = g_list_prepend(focus_chain, s_file_view);
1698 	gtk_container_set_focus_chain(GTK_CONTAINER(s_file_view_vbox), focus_chain);
1699 	g_list_free(focus_chain);
1700 	scrollwin = gtk_scrolled_window_new(NULL, NULL);
1701 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
1702 					   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1703 	gtk_container_add(GTK_CONTAINER(scrollwin), s_file_view);
1704 	gtk_box_pack_start(GTK_BOX(s_file_view_vbox), scrollwin, TRUE, TRUE, 0);
1705 
1706 	gtk_widget_show_all(s_file_view_vbox);
1707 	gtk_notebook_append_page(GTK_NOTEBOOK(geany->main_widgets->sidebar_notebook),
1708 				 s_file_view_vbox, gtk_label_new(_("Project")));
1709 }
1710 
1711 
prjorg_sidebar_activate(gboolean activate)1712 void prjorg_sidebar_activate(gboolean activate)
1713 {
1714 	gtk_widget_set_sensitive(s_file_view_vbox, activate);
1715 }
1716 
1717 
prjorg_sidebar_cleanup(void)1718 void prjorg_sidebar_cleanup(void)
1719 {
1720 	gtk_widget_destroy(s_file_view_vbox);
1721 }
1722