1 /*
2  *  geanyprj - Alternative project support for geany light IDE.
3  *
4  *  Copyright 2008 Yura Siamashka <yurand2@gmail.com>
5  *
6  *  This program is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 
21 #include <sys/time.h>
22 #include <gdk/gdkkeysyms.h>
23 #include <string.h>
24 
25 #ifdef HAVE_CONFIG_H
26 	#include "config.h" /* for the gettext domain */
27 #endif
28 #include <geanyplugin.h>
29 
30 #include "geanyprj.h"
31 
32 
33 static GtkWidget *file_view_vbox;
34 static GtkWidget *file_view;
35 static GtkListStore *file_store;
36 
37 enum
38 {
39 	FILEVIEW_COLUMN_NAME = 0,
40 	FILEVIEW_N_COLUMNS,
41 };
42 
43 static struct
44 {
45 	GtkWidget *new_project;
46 	GtkWidget *delete_project;
47 
48 	GtkWidget *add_file;
49 	GtkWidget *remove_files;
50 
51 	GtkWidget *preferences;
52 
53 	GtkWidget *find_in_files;
54 } popup_items;
55 
56 
57 /* Returns: the full filename in locale encoding. */
get_tree_path_filename(GtkTreePath * treepath)58 static gchar *get_tree_path_filename(GtkTreePath *treepath)
59 {
60 	GtkTreeModel *model = GTK_TREE_MODEL(file_store);
61 	GtkTreeIter iter;
62 	gchar *name;
63 
64 	gtk_tree_model_get_iter(model, &iter, treepath);
65 	gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_NAME, &name, -1);
66 	setptr(name, utils_get_locale_from_utf8(name));
67 	setptr(name, get_full_path(g_current_project->path, name));
68 	return name;
69 }
70 
71 
72 /* We use documents->open_files() as it's more efficient. */
open_selected_files(GList * list)73 static void open_selected_files(GList *list)
74 {
75 	GSList *files = NULL;
76 	GList *item;
77 
78 	for (item = list; item != NULL; item = g_list_next(item))
79 	{
80 		GtkTreePath *treepath = item->data;
81 		gchar *fname = get_tree_path_filename(treepath);
82 		files = g_slist_append(files, fname);
83 	}
84 	document_open_files(files, FALSE, NULL, NULL);
85 	g_slist_foreach(files, (GFunc)g_free, NULL);	/* free filenames */
86 	g_slist_free(files);
87 }
88 
89 
on_open_clicked(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)90 static void on_open_clicked(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
91 {
92 	GtkTreeSelection *treesel;
93 	GtkTreeModel *model;
94 	GList *list;
95 
96 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view));
97 
98 	list = gtk_tree_selection_get_selected_rows(treesel, &model);
99 	open_selected_files(list);
100 	g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL);
101 	g_list_free(list);
102 }
103 
104 
on_button_press(G_GNUC_UNUSED GtkWidget * widget,GdkEventButton * event,G_GNUC_UNUSED gpointer user_data)105 static gboolean on_button_press(G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event,
106 								G_GNUC_UNUSED gpointer user_data)
107 {
108 	if (event->button == 1 && event->type == GDK_2BUTTON_PRESS)
109 		on_open_clicked(NULL, NULL);
110 	return FALSE;
111 }
112 
113 
make_toolbar(void)114 static GtkWidget *make_toolbar(void)
115 {
116 	GtkWidget *toolbar;
117 
118 	toolbar = gtk_toolbar_new();
119 	gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar), GTK_ICON_SIZE_MENU);
120 	gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
121 
122 	return toolbar;
123 }
124 
125 
remove_selected_files(GList * list)126 static void remove_selected_files(GList *list)
127 {
128 	GList *item;
129 	for (item = list; item != NULL; item = g_list_next(item))
130 	{
131 		GtkTreePath *treepath = item->data;
132 		gchar *fname = get_tree_path_filename(treepath);
133 		xproject_remove_file(fname);
134 		g_free(fname);
135 	}
136 }
137 
138 
on_remove_files(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)139 static void on_remove_files(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
140 {
141 	GtkTreeSelection *treesel;
142 	GtkTreeModel *model;
143 	GList *list;
144 
145 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view));
146 
147 	list = gtk_tree_selection_get_selected_rows(treesel, &model);
148 	remove_selected_files(list);
149 	g_list_foreach(list, (GFunc) gtk_tree_path_free, NULL);
150 	g_list_free(list);
151 }
152 
153 
on_key_press(G_GNUC_UNUSED GtkWidget * widget,GdkEventKey * event,G_GNUC_UNUSED gpointer data)154 static gboolean on_key_press(G_GNUC_UNUSED GtkWidget *widget, GdkEventKey *event, G_GNUC_UNUSED gpointer data)
155 {
156 	if (event->keyval == GDK_Return
157 	    || event->keyval == GDK_ISO_Enter
158 	    || event->keyval == GDK_KP_Enter || event->keyval == GDK_space)
159 		on_open_clicked(NULL, NULL);
160 	return FALSE;
161 }
162 
163 
create_popup_menu(void)164 static GtkWidget *create_popup_menu(void)
165 {
166 	GtkWidget *item, *menu, *image;
167 
168 	menu = gtk_menu_new();
169 
170 	image = gtk_image_new_from_stock(GTK_STOCK_NEW, GTK_ICON_SIZE_MENU);
171 	gtk_widget_show(image);
172 	item = gtk_image_menu_item_new_with_mnemonic(_("New Project"));
173 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
174 	gtk_widget_show(item);
175 	gtk_container_add(GTK_CONTAINER(menu), item);
176 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_new_project), NULL);
177 	popup_items.new_project = item;
178 
179 	image = gtk_image_new_from_stock(GTK_STOCK_DELETE, GTK_ICON_SIZE_MENU);
180 	gtk_widget_show(image);
181 	item = gtk_image_menu_item_new_with_mnemonic(_("Delete Project"));
182 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
183 	gtk_widget_show(item);
184 	gtk_container_add(GTK_CONTAINER(menu), item);
185 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_delete_project), NULL);
186 	popup_items.delete_project = item;
187 
188 	item = gtk_separator_menu_item_new();
189 	gtk_widget_show(item);
190 	gtk_container_add(GTK_CONTAINER(menu), item);
191 
192 	image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
193 	gtk_widget_show(image);
194 	item = gtk_image_menu_item_new_with_mnemonic(_("Add File"));
195 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
196 	gtk_widget_show(item);
197 	gtk_container_add(GTK_CONTAINER(menu), item);
198 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_add_file), NULL);
199 	popup_items.add_file = item;
200 
201 	image = gtk_image_new_from_stock(GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU);
202 	gtk_widget_show(image);
203 	item = gtk_image_menu_item_new_with_mnemonic(_("Remove File"));
204 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
205 	gtk_widget_show(item);
206 	gtk_container_add(GTK_CONTAINER(menu), item);
207 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_remove_files), NULL);
208 	popup_items.remove_files = item;
209 
210 	item = gtk_separator_menu_item_new();
211 	gtk_widget_show(item);
212 	gtk_container_add(GTK_CONTAINER(menu), item);
213 
214 	image = gtk_image_new_from_stock(GTK_STOCK_PREFERENCES, GTK_ICON_SIZE_MENU);
215 	gtk_widget_show(image);
216 	item = gtk_image_menu_item_new_with_mnemonic(_("Preferences"));
217 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
218 	gtk_widget_show(item);
219 	gtk_container_add(GTK_CONTAINER(menu), item);
220 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_preferences), NULL);
221 	popup_items.preferences = item;
222 
223 	item = gtk_separator_menu_item_new();
224 	gtk_widget_show(item);
225 	gtk_container_add(GTK_CONTAINER(menu), item);
226 
227 	image = gtk_image_new_from_stock(GTK_STOCK_FIND, GTK_ICON_SIZE_MENU);
228 	gtk_widget_show(image);
229 	item = gtk_image_menu_item_new_with_mnemonic(_("Find in Project"));
230 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
231 	gtk_widget_show(item);
232 	gtk_container_add(GTK_CONTAINER(menu), item);
233 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_find_in_project), NULL);
234 	popup_items.find_in_files = item;
235 
236 	item = gtk_separator_menu_item_new();
237 	gtk_widget_show(item);
238 	gtk_container_add(GTK_CONTAINER(menu), item);
239 
240 	item = gtk_image_menu_item_new_with_mnemonic(_("H_ide Sidebar"));
241 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
242 				      gtk_image_new_from_stock("gtk-close", GTK_ICON_SIZE_MENU));
243 	gtk_widget_show(item);
244 	gtk_container_add(GTK_CONTAINER(menu), item);
245 	g_signal_connect_swapped((gpointer) item, "activate",
246 				 G_CALLBACK(keybindings_send_command),
247 				 GINT_TO_POINTER(GEANY_KEYS_VIEW_SIDEBAR));
248 
249 	return menu;
250 }
251 
252 
update_popup_menu(G_GNUC_UNUSED GtkWidget * popup_menu)253 static void update_popup_menu(G_GNUC_UNUSED GtkWidget *popup_menu)
254 {
255 	gboolean cur_file_exists;
256 	gboolean badd_file;
257 	GeanyDocument *doc;
258 	GtkTreeSelection *treesel;
259 	gboolean bremove_file;
260 
261 	doc = document_get_current();
262 
263 	cur_file_exists = doc && doc->file_name != NULL && g_path_is_absolute(doc->file_name);
264 
265 	badd_file = (g_current_project ? TRUE : FALSE) &&
266 		!g_current_project->regenerate &&
267 		cur_file_exists && !g_hash_table_lookup(g_current_project->tags, doc->file_name);
268 
269 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view));
270 	bremove_file = (g_current_project ? TRUE : FALSE) &&
271 		!g_current_project->regenerate &&
272 		(gtk_tree_selection_count_selected_rows(treesel) > 0);
273 
274 	gtk_widget_set_sensitive(popup_items.new_project, TRUE);
275 	gtk_widget_set_sensitive(popup_items.delete_project, g_current_project ? TRUE : FALSE);
276 
277 	gtk_widget_set_sensitive(popup_items.add_file, badd_file);
278 	gtk_widget_set_sensitive(popup_items.remove_files, bremove_file);
279 
280 	gtk_widget_set_sensitive(popup_items.preferences, g_current_project ? TRUE : FALSE);
281 
282 	gtk_widget_set_sensitive(popup_items.find_in_files, g_current_project ? TRUE : FALSE);
283 }
284 
285 
286 /* delay updating popup menu until the selection has been set */
on_button_release(G_GNUC_UNUSED GtkWidget * widget,GdkEventButton * event,G_GNUC_UNUSED gpointer user_data)287 static gboolean on_button_release(G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event,
288 									G_GNUC_UNUSED gpointer user_data)
289 {
290 	if (event->button == 3)
291 	{
292 		static GtkWidget *popup_menu = NULL;
293 
294 		if (popup_menu == NULL)
295 			popup_menu = create_popup_menu();
296 
297 		update_popup_menu(popup_menu);
298 
299 		gtk_menu_popup(GTK_MENU(popup_menu), NULL, NULL, NULL, NULL,
300 			       event->button, event->time);
301 	}
302 	return FALSE;
303 }
304 
305 
prepare_file_view(void)306 static void prepare_file_view(void)
307 {
308 	GtkCellRenderer *text_renderer;
309 	GtkTreeViewColumn *column;
310 	GtkTreeSelection *selection;
311 	PangoFontDescription *pfd;
312 
313 	file_store = gtk_list_store_new(FILEVIEW_N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
314 
315 	gtk_tree_view_set_model(GTK_TREE_VIEW(file_view), GTK_TREE_MODEL(file_store));
316 
317 	text_renderer = gtk_cell_renderer_text_new();
318 	column = gtk_tree_view_column_new();
319 	gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
320 	gtk_tree_view_column_set_attributes(column, text_renderer, "text", FILEVIEW_COLUMN_NAME,
321 					    NULL);
322 	gtk_tree_view_append_column(GTK_TREE_VIEW(file_view), column);
323 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(file_view), FALSE);
324 
325 	gtk_tree_view_set_enable_search(GTK_TREE_VIEW(file_view), TRUE);
326 	gtk_tree_view_set_search_column(GTK_TREE_VIEW(file_view), FILEVIEW_COLUMN_NAME);
327 
328 	pfd = pango_font_description_from_string(geany_data->interface_prefs->tagbar_font);
329 	gtk_widget_modify_font(file_view, pfd);
330 	pango_font_description_free(pfd);
331 
332 	/* selection handling */
333 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view));
334 	gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
335 
336 	g_signal_connect(G_OBJECT(file_view), "button-release-event",
337 			 G_CALLBACK(on_button_release), NULL);
338 	g_signal_connect(G_OBJECT(file_view), "button-press-event",
339 			 G_CALLBACK(on_button_press), NULL);
340 
341 	g_signal_connect(G_OBJECT(file_view), "key-press-event", G_CALLBACK(on_key_press), NULL);
342 }
343 
344 
sidebar_clear(void)345 static void sidebar_clear(void)
346 {
347 	gtk_list_store_clear(file_store);
348 }
349 
350 #if 0
351 static gint mycmp(const gchar *a, const gchar *b)
352 {
353 	const gchar *p1 = a;
354 	const gchar *p2 = b;
355 
356 	gint cnt1 = 0;
357 	gint cnt2 = 0;
358 
359 	while (*p1)
360 	{
361 		if (*p1 == G_DIR_SEPARATOR_S[0])
362 			cnt1++;
363 		p1++;
364 	}
365 
366 	while (*p2)
367 	{
368 		if (*p2 == G_DIR_SEPARATOR_S[0])
369 			cnt2++;
370 		p2++;
371 	}
372 
373 	if (cnt1 != cnt2)
374 		return cnt2 - cnt1;
375 
376 	p1 = a;
377 	p2 = b;
378 
379 	while (*p1 && *p2)
380 	{
381 		if (*p1 != *p2)
382 		{
383 			if (*p1 == G_DIR_SEPARATOR_S[0])
384 				return -1;
385 			else if (*p2 == G_DIR_SEPARATOR_S[0])
386 				return 1;
387 			return *p1 - *p2;
388 		}
389 		p1++;
390 		p2++;
391 	}
392 	if (*p1 == 0 && *p2 == 0)
393 		return 0;
394 	else if (*p1)
395 		return 1;
396 	return -1;
397 }
398 #endif
399 
add_item(gpointer name,G_GNUC_UNUSED gpointer value,gpointer user_data)400 static void add_item(gpointer name, G_GNUC_UNUSED gpointer value, gpointer user_data)
401 {
402 	gchar *item;
403 	GSList **lst = (GSList **) user_data;
404 
405 	item = get_relative_path(g_current_project->path, name);
406 	*lst = g_slist_prepend(*lst, item);
407 }
408 
409 
410 /* recreate the tree model from current_dir */
sidebar_refresh(void)411 void sidebar_refresh(void)
412 {
413 	GtkTreeIter iter;
414 	GSList *lst = NULL;
415 	GSList *tmp;
416 
417 	if (! file_view_vbox)
418 		return;
419 
420 	sidebar_clear();
421 
422 	if (!g_current_project)
423 		return;
424 
425 	g_hash_table_foreach(g_current_project->tags, add_item, &lst);
426 	lst = g_slist_sort(lst, (GCompareFunc) strcmp);
427 	for (tmp = lst; tmp != NULL; tmp = g_slist_next(tmp))
428 	{
429 		gtk_list_store_append(file_store, &iter);
430 		gtk_list_store_set(file_store, &iter, FILEVIEW_COLUMN_NAME, tmp->data, -1);
431 	}
432 	g_slist_foreach(lst, (GFunc) g_free, NULL);
433 	g_slist_free(lst);
434 }
435 
436 
create_sidebar(void)437 void create_sidebar(void)
438 {
439 	GtkWidget *scrollwin, *toolbar;
440 
441 	file_view_vbox = gtk_vbox_new(FALSE, 0);
442 	toolbar = make_toolbar();
443 	gtk_box_pack_start(GTK_BOX(file_view_vbox), toolbar, FALSE, FALSE, 0);
444 
445 	file_view = gtk_tree_view_new();
446 	prepare_file_view();
447 
448 	scrollwin = gtk_scrolled_window_new(NULL, NULL);
449 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
450 				       GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
451 	gtk_container_add(GTK_CONTAINER(scrollwin), file_view);
452 	gtk_box_pack_start(GTK_BOX(file_view_vbox), scrollwin, TRUE, TRUE, 0);
453 
454 	gtk_widget_show_all(file_view_vbox);
455 	gtk_notebook_append_page(GTK_NOTEBOOK(geany->main_widgets->sidebar_notebook),
456 				 file_view_vbox, gtk_label_new(_("Project")));
457 }
458 
459 
destroy_sidebar(void)460 void destroy_sidebar(void)
461 {
462 	if (file_view_vbox)
463 		gtk_widget_destroy(file_view_vbox);
464 }
465