1 /*
2  * Copyright 2017 LarsGit223
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 /*
20  * Code for setup and control of the sidebar.
21  */
22 #include <sys/time.h>
23 #include <gdk/gdkkeysyms.h>
24 #include <string.h>
25 
26 #ifdef HAVE_CONFIG_H
27 	#include "config.h"
28 #endif
29 #include "wb_globals.h"
30 #include <gtkcompat.h>
31 #include <gp_gtkcompat.h>
32 
33 #include "sidebar.h"
34 #include "popup_menu.h"
35 #include "utils.h"
36 
37 enum
38 {
39 	FILEVIEW_COLUMN_ICON,
40 	FILEVIEW_COLUMN_NAME,
41 	FILEVIEW_COLUMN_DATA_ID,
42 	FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER,
43 	FILEVIEW_N_COLUMNS,
44 };
45 
46 typedef enum
47 {
48 	MATCH_FULL,
49 	MATCH_PREFIX,
50 	MATCH_PATTERN
51 } MatchType;
52 
53 typedef struct
54 {
55 	gboolean iter_valid;
56 	GtkTreeIter iter;
57 	gboolean parent_valid;
58 	GtkTreeIter parent;
59 }ITER_SEARCH_RESULT;
60 
61 typedef struct
62 {
63 	GeanyProject *project;
64 	GPtrArray *expanded_paths;
65 } ExpandData;
66 
67 typedef struct
68 {
69 	SIDEBAR_CONTEXT *context;
70 	GtkTreeModel *model;
71 	guint dataid;
72 	void (*func)(SIDEBAR_CONTEXT *, gpointer userdata);
73 	gpointer userdata;
74 }SB_CALLFOREACH_CONTEXT;
75 
76 typedef struct SIDEBAR
77 {
78     GtkWidget *file_view_vbox;
79     GtkWidget *file_view;
80     GtkTreeStore *file_store;
81     GtkWidget *file_view_label;
82 }SIDEBAR;
83 static SIDEBAR sidebar = {NULL, NULL, NULL, NULL};
84 
85 static gboolean sidebar_get_directory_iter(WB_PROJECT *prj, WB_PROJECT_DIR *dir, GtkTreeIter *iter);
86 static gboolean sidebar_get_filepath_iter (WB_PROJECT *prj, WB_PROJECT_DIR *root, const gchar *filepath, ITER_SEARCH_RESULT *result);
87 
88 /* Remove all child nodes below parent */
sidebar_remove_children(GtkTreeIter * parent)89 static void sidebar_remove_children(GtkTreeIter *parent)
90 {
91 	GtkTreeIter iter;
92 	GtkTreeModel *model;
93 
94 	model = gtk_tree_view_get_model(GTK_TREE_VIEW(sidebar.file_view));
95 	if (gtk_tree_model_iter_children (model, &iter, parent))
96 	{
97 		while (gtk_tree_store_remove (sidebar.file_store, &iter)) {}
98 	}
99 }
100 
101 
102 /* Create a branch for a sub-directory */
sidebar_create_branch(gint level,const gchar * abs_base_dir,GSList * leaf_list,GtkTreeIter * parent)103 static void sidebar_create_branch(gint level, const gchar *abs_base_dir, GSList *leaf_list, GtkTreeIter *parent)
104 {
105 	GSList *dir_list = NULL;
106 	GSList *file_list = NULL;
107 	GSList *elem = NULL;
108 	gchar **path_arr, *part, *full, *dirpath;
109 
110 	foreach_slist (elem, leaf_list)
111 	{
112 		if (elem->data == NULL)
113 		{
114 			continue;
115 		}
116 		path_arr = elem->data;
117 		if (path_arr[level] == NULL)
118 		{
119 			continue;
120 		}
121 
122 		if (path_arr[level+1] != NULL)
123 		{
124 			dir_list = g_slist_prepend(dir_list, path_arr);
125 		}
126 		else
127 		{
128 			// Extra check for empty directories
129 			part = g_build_filenamev(path_arr);
130 			dirpath = g_build_filename(abs_base_dir, part, NULL);
131 			if (dirpath != NULL && g_file_test (dirpath, G_FILE_TEST_IS_DIR))
132 			{
133 				dir_list = g_slist_prepend(dir_list, path_arr);
134 				g_free(dirpath);
135 			}
136 			else
137 			{
138 				file_list = g_slist_prepend(file_list, path_arr);
139 			}
140 		}
141 	}
142 
143 	foreach_slist (elem, file_list)
144 	{
145 		GtkTreeIter iter;
146 		GIcon *icon = NULL;
147 
148 		path_arr = elem->data;
149 		gchar *content_type = g_content_type_guess(path_arr[level], NULL, 0, NULL);
150 
151 		if (content_type)
152 		{
153 			icon = g_content_type_get_icon(content_type);
154 			if (icon)
155 			{
156 				GtkIconInfo *icon_info;
157 
158 				icon_info = gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_default(), icon, 16, 0);
159 				if (!icon_info)
160 				{
161 					g_object_unref(icon);
162 					icon = NULL;
163 				}
164 				else
165 					gtk_icon_info_free(icon_info);
166 			}
167 			g_free(content_type);
168 		}
169 
170 		/* Build full absolute file name to use it on row activate to
171 		   open the file. Will be assigned as data pointer, see below. */
172 		part = g_build_filenamev(path_arr);
173 		full = g_build_filename(abs_base_dir, part, NULL);
174 		g_free(part);
175 
176 		gtk_tree_store_insert_with_values(sidebar.file_store, &iter, parent, 0,
177 			FILEVIEW_COLUMN_ICON, icon,
178 			FILEVIEW_COLUMN_NAME, path_arr[level],
179 			FILEVIEW_COLUMN_DATA_ID, DATA_ID_FILE,
180 			FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, full,
181 			-1);
182 
183 		if (icon)
184 		{
185 			g_object_unref(icon);
186 		}
187 	}
188 
189 	if (dir_list)
190 	{
191 		GSList *tmp_list = NULL;
192 		GtkTreeIter iter;
193 		gchar *last_dir_name;
194 		GIcon *icon_dir = g_icon_new_for_string("folder", NULL);
195 
196 		path_arr = dir_list->data;
197 		last_dir_name = path_arr[level];
198 
199 		foreach_slist (elem, dir_list)
200 		{
201 			gboolean dir_changed;
202 
203 			part = g_build_filenamev(path_arr);
204 			full = g_build_filename(abs_base_dir, part, NULL);
205 			g_free(part);
206 
207 			path_arr = (gchar **) elem->data;
208 			dir_changed = g_strcmp0(last_dir_name, path_arr[level]) != 0;
209 
210 			if (dir_changed)
211 			{
212 				gtk_tree_store_insert_with_values(sidebar.file_store, &iter, parent, 0,
213 					FILEVIEW_COLUMN_ICON, icon_dir,
214 					FILEVIEW_COLUMN_NAME, last_dir_name,
215 					FILEVIEW_COLUMN_DATA_ID, DATA_ID_SUB_DIRECTORY,
216 					FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, g_strdup(full),
217 					-1);
218 
219 				sidebar_create_branch(level+1, abs_base_dir, tmp_list, &iter);
220 
221 				g_slist_free(tmp_list);
222 				tmp_list = NULL;
223 				last_dir_name = path_arr[level];
224 			}
225 			g_free(full);
226 
227 			tmp_list = g_slist_prepend(tmp_list, path_arr);
228 		}
229 
230 		part = g_build_filenamev(path_arr);
231 		full = g_build_filename(abs_base_dir, part, NULL);
232 		g_free(part);
233 
234 		gtk_tree_store_insert_with_values(sidebar.file_store, &iter, parent, 0,
235 			FILEVIEW_COLUMN_ICON, icon_dir,
236 			FILEVIEW_COLUMN_NAME, last_dir_name,
237 			FILEVIEW_COLUMN_DATA_ID, DATA_ID_SUB_DIRECTORY,
238 			FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, g_strdup(full),
239 			-1);
240 			g_free(full);
241 
242 		sidebar_create_branch(level+1, abs_base_dir, tmp_list, &iter);
243 
244 		g_slist_free(tmp_list);
245 		g_slist_free(dir_list);
246 		if (icon_dir != NULL)
247 		{
248 			g_object_unref(icon_dir);
249 		}
250 	}
251 
252 	g_slist_free(file_list);
253 }
254 
255 
256 /* Reverse strcmp */
rev_strcmp(const char * str1,const char * str2)257 static int rev_strcmp(const char *str1, const char *str2)
258 {
259 	return g_strcmp0(str2, str1);
260 }
261 
262 
263 /* Insert given directory/sub-directory into the sidebar file tree */
sidebar_insert_project_directory(WB_PROJECT * prj,WB_PROJECT_DIR * directory,GtkTreeIter * parent)264 static void sidebar_insert_project_directory(WB_PROJECT *prj, WB_PROJECT_DIR *directory, GtkTreeIter *parent)
265 {
266 	GSList *lst = NULL;
267 	GSList *path_list = NULL;
268 	GSList *elem = NULL;
269 	GHashTableIter iter;
270 	gpointer key, value;
271 	gchar *abs_base_dir;
272 
273 	g_hash_table_iter_init(&iter, wb_project_dir_get_file_table(directory));
274 	abs_base_dir = get_combined_path(wb_project_get_filename(prj), wb_project_dir_get_base_dir(directory));
275 
276 	while (g_hash_table_iter_next(&iter, &key, &value))
277 	{
278 		gchar *path = get_relative_path(abs_base_dir, key);
279 		if (path != NULL)
280 		{
281 			lst = g_slist_prepend(lst, path);
282 		}
283 	}
284 	/* sort in reverse order so we can prepend nodes to the tree store -
285 	 * the store behaves as a linked list and prepending is faster */
286 	lst = g_slist_sort(lst, (GCompareFunc) rev_strcmp);
287 
288 	foreach_slist (elem, lst)
289 	{
290 		gchar **path_split;
291 
292 		path_split = g_strsplit_set(elem->data, "/\\", 0);
293 		path_list = g_slist_prepend(path_list, path_split);
294 	}
295 
296 	if (path_list != NULL)
297 		sidebar_create_branch(0, abs_base_dir, path_list, parent);
298 
299 	g_slist_free_full(lst, g_free);
300 	g_slist_free_full(path_list, (GDestroyNotify) g_strfreev);
301 }
302 
303 
304 /* Insert all directories (WB_PROJECT_DIRs) into the sidebar file tree */
sidebar_insert_project_directories(WB_PROJECT * project,GtkTreeIter * parent,gint * position)305 static void sidebar_insert_project_directories (WB_PROJECT *project, GtkTreeIter *parent, gint *position)
306 {
307 	GtkTreeIter iter;
308 	GIcon *icon, *icon_dir, *icon_base;
309 	GSList *elem = NULL, *dirs;
310 
311 	if (project == NULL)
312 	{
313 		return;
314 	}
315 
316 	dirs = wb_project_get_directories(project);
317 	if (dirs != NULL)
318 	{
319 		const gchar *name;
320 
321 		icon_dir = g_icon_new_for_string("system-search", NULL);
322 		icon_base = g_icon_new_for_string("user-home", NULL);
323 
324 		foreach_slist (elem, dirs)
325 		{
326 			if (wb_project_dir_get_is_prj_base_dir(elem->data) == TRUE)
327 			{
328 				icon = icon_base;
329 				name = _("Base dir");
330 			}
331 			else
332 			{
333 				icon = icon_dir;
334 				name = wb_project_dir_get_name(elem->data);
335 			}
336 
337 			gtk_tree_store_insert_with_values(sidebar.file_store, &iter, parent, *position,
338 				FILEVIEW_COLUMN_ICON, icon,
339 				FILEVIEW_COLUMN_NAME, name,
340 				FILEVIEW_COLUMN_DATA_ID, DATA_ID_DIRECTORY,
341 				FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, elem->data,
342 				-1);
343 			(*position)++;
344 			sidebar_insert_project_directory(project, elem->data, &iter);
345 		}
346 
347 		if (icon_dir != NULL)
348 		{
349 			g_object_unref(icon_dir);
350 		}
351 		if (icon_base != NULL)
352 		{
353 			g_object_unref(icon_base);
354 		}
355 	}
356 	else
357 	{
358 		icon = g_icon_new_for_string("dialog-information", NULL);
359 
360 		gtk_tree_store_insert_with_values(sidebar.file_store, &iter, parent, *position,
361 			FILEVIEW_COLUMN_ICON, icon,
362 			FILEVIEW_COLUMN_NAME, _("No directories"),
363 			FILEVIEW_COLUMN_DATA_ID, DATA_ID_NO_DIRS,
364 			-1);
365 		(*position)++;
366 
367 		if (icon != NULL)
368 		{
369 			g_object_unref(icon);
370 		}
371 	}
372 }
373 
374 
375 /* Add a file to the sidebar. filepath can be a file or directory. */
sidebar_add_file(WB_PROJECT * prj,WB_PROJECT_DIR * root,const gchar * filepath)376 static void sidebar_add_file (WB_PROJECT *prj, WB_PROJECT_DIR *root, const gchar *filepath)
377 {
378 	GIcon *icon = NULL;
379 	gchar *name;
380 	guint dataid;
381 	ITER_SEARCH_RESULT search_result;
382 
383 	if (!sidebar_get_filepath_iter(prj, root, filepath, &search_result))
384 	{
385 		return;
386 	}
387 	else
388 	{
389 		if (search_result.iter_valid)
390 		{
391 			/* Error, file already exists in sidebar tree. */
392 			return;
393 		}
394 		else
395 		{
396 			/* This is the expected result as we want to add a new file node.
397 			   But parent should be valid. */
398 			if (!search_result.parent_valid)
399 			{
400 				return;
401 			}
402 		}
403 	}
404 
405 	/* Collect data */
406 	name = g_path_get_basename(filepath);
407 	if (g_file_test (filepath, G_FILE_TEST_IS_DIR))
408 	{
409 		dataid = DATA_ID_SUB_DIRECTORY;
410 		icon = g_icon_new_for_string("folder", NULL);
411 	}
412 	else
413 	{
414 		dataid = DATA_ID_FILE;
415 
416 		gchar *content_type = g_content_type_guess(filepath, NULL, 0, NULL);
417 		if (content_type)
418 		{
419 			icon = g_content_type_get_icon(content_type);
420 			if (icon)
421 			{
422 				GtkIconInfo *icon_info;
423 
424 				icon_info = gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_default(), icon, 16, 0);
425 				if (!icon_info)
426 				{
427 					g_object_unref(icon);
428 					icon = NULL;
429 				}
430 				else
431 					gtk_icon_info_free(icon_info);
432 			}
433 			g_free(content_type);
434 		}
435 
436 	}
437 
438 	/* Create new row/node in sidebar tree. */
439 	gtk_tree_store_insert_with_values(sidebar.file_store, &search_result.iter, &search_result.parent, -1,
440 		FILEVIEW_COLUMN_ICON, icon,
441 		FILEVIEW_COLUMN_NAME, name,
442 		FILEVIEW_COLUMN_DATA_ID, dataid,
443 		FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, g_strdup(filepath),
444 		-1);
445 
446 	if (icon)
447 	{
448 		g_object_unref(icon);
449 	}
450 }
451 
452 
453 /* Remove a file from the sidebar. filepath can be a file or directory. */
sidebar_remove_file(WB_PROJECT * prj,WB_PROJECT_DIR * root,const gchar * filepath)454 static void sidebar_remove_file (WB_PROJECT *prj, WB_PROJECT_DIR *root, const gchar *filepath)
455 {
456 	ITER_SEARCH_RESULT search_result;
457 
458 	if (!sidebar_get_filepath_iter(prj, root, filepath, &search_result))
459 	{
460 		return;
461 	}
462 	else
463 	{
464 		if (search_result.iter_valid)
465 		{
466 			/* File was found, remove the node. */
467 			gtk_tree_store_remove(sidebar.file_store, &search_result.iter);
468 		}
469 	}
470 }
471 
472 
473 /* Get the GtkTreeIter for project prj */
sidebar_get_project_iter(WB_PROJECT * prj,GtkTreeIter * iter)474 static gboolean sidebar_get_project_iter(WB_PROJECT *prj, GtkTreeIter *iter)
475 {
476 	GtkTreeModel *model;
477 
478 	model = gtk_tree_view_get_model(GTK_TREE_VIEW(sidebar.file_view));
479 	if (gtk_tree_model_get_iter_first (model, iter))
480 	{
481 		WB_PROJECT *current;
482 
483 		do
484 		{
485 			gtk_tree_model_get(model, iter, FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, &current, -1);
486 			if (current == prj)
487 			{
488 				return TRUE;
489 			}
490 		}while (gtk_tree_model_iter_next(model, iter));
491 	}
492 	return FALSE;
493 }
494 
495 
496 /* Get the GtkTreeIter for directory dir in project prj */
sidebar_get_directory_iter(WB_PROJECT * prj,WB_PROJECT_DIR * dir,GtkTreeIter * iter)497 static gboolean sidebar_get_directory_iter(WB_PROJECT *prj, WB_PROJECT_DIR *dir, GtkTreeIter *iter)
498 {
499 	GtkTreeIter parent;
500 	GtkTreeModel *model;
501 
502 	model = gtk_tree_view_get_model(GTK_TREE_VIEW(sidebar.file_view));
503 	if (sidebar_get_project_iter(prj, iter))
504 	{
505 		parent = *iter;
506 		if (gtk_tree_model_iter_children (model, iter, &parent))
507 		{
508 			WB_PROJECT_DIR *current;
509 
510 			do
511 			{
512 				gtk_tree_model_get(model, iter, FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, &current, -1);
513 				if (current == dir)
514 				{
515 					return TRUE;
516 				}
517 			}while (gtk_tree_model_iter_next(model, iter));
518 		}
519 	}
520 	return FALSE;
521 }
522 
523 
524 /* Get the GtkTreeIter for filepath (absolute) in directory root in project prj */
sidebar_get_filepath_iter(WB_PROJECT * prj,WB_PROJECT_DIR * root,const gchar * filepath,ITER_SEARCH_RESULT * search_result)525 static gboolean sidebar_get_filepath_iter (WB_PROJECT *prj, WB_PROJECT_DIR *root, const gchar *filepath, ITER_SEARCH_RESULT *search_result)
526 {
527 	guint len, index;
528 	gchar *part, **parts, *found = NULL, *last_found, *abs_base_dir;
529 	GtkTreeIter iter, parent;
530 	GtkTreeModel *model;
531 
532 	if (search_result == NULL)
533 	{
534 		return FALSE;
535 	}
536 	search_result->iter_valid = FALSE;
537 	search_result->parent_valid = FALSE;
538 
539 	abs_base_dir = get_combined_path(wb_project_get_filename(prj), wb_project_dir_get_base_dir(root));
540 	len = strlen(abs_base_dir);
541 	if (strncmp(abs_base_dir, filepath, len) != 0)
542 	{
543 		/* Error, return. */
544 		return FALSE;
545 	}
546 	if (filepath[len] == G_DIR_SEPARATOR)
547 	{
548 		len++;
549 	}
550 	part = g_strdup(&(filepath[len]));
551 	if (strlen(part) == 0)
552 	{
553 		return FALSE;
554 	}
555 	parts = g_strsplit(part, G_DIR_SEPARATOR_S, -1);
556 	if (parts[0] == NULL)
557 	{
558 		return FALSE;
559 	}
560 
561 	if (sidebar_get_directory_iter(prj, root, &iter))
562 	{
563 		index = 0;
564 		last_found = NULL;
565 		model = gtk_tree_view_get_model(GTK_TREE_VIEW(sidebar.file_view));
566 
567 		while (parts[index] != NULL)
568 		{
569 			parent = iter;
570 			if (gtk_tree_model_iter_children(model, &iter, &parent))
571 			{
572 				gchar *current;
573 
574 				found = NULL;
575 				do
576 				{
577 					gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_NAME, &current, -1);
578 
579 					if (g_strcmp0(current, parts[index]) == 0)
580 					{
581 						found = current;
582 						last_found = current;
583 						index++;
584 						//parent = iter;
585 						break;
586 					}
587 				}while (gtk_tree_model_iter_next(model, &iter));
588 
589 				/* Did we find the next match? */
590 				if (found == NULL)
591 				{
592 					/* No, abort. */
593 					break;
594 				}
595 			}
596 			else
597 			{
598 				break;
599 			}
600 		}
601 
602 		/* Did we find a full match? */
603 		if (parts[index] == NULL && found != NULL)
604 		{
605 			/* Yes. */
606 			search_result->iter_valid = TRUE;
607 			search_result->iter = iter;
608 
609 			search_result->parent_valid = TRUE;
610 			search_result->parent = parent;
611 		}
612 		else if (parts[index+1] == NULL &&
613 				 (last_found != NULL || parts[1] == NULL))
614 		{
615 			/* Not a full match but everything but the last part
616 			   of the filepath. At least return the parent. */
617 			search_result->parent_valid = TRUE;
618 			search_result->parent = parent;
619 		}
620 	}
621 
622 	g_free(part);
623 	g_free(abs_base_dir);
624 	g_strfreev(parts);
625 
626 	return TRUE;
627 }
628 
629 
630 /* Remove all rows from the side bar tree with the given data id */
sidebar_remove_nodes_with_data_id(guint toremove,GtkTreeIter * start)631 static void sidebar_remove_nodes_with_data_id(guint toremove, GtkTreeIter *start)
632 {
633 	GtkTreeIter iter;
634 	GtkTreeModel *model;
635 	gboolean has_next;
636 	guint dataid;
637 
638 	model = gtk_tree_view_get_model(GTK_TREE_VIEW(sidebar.file_view));
639 	if (start == NULL)
640 	{
641 		if (!gtk_tree_model_get_iter_first(model, &iter))
642 		{
643 			return;
644 		}
645 	}
646 	else
647 	{
648 		iter = *start;
649 	}
650 
651 	do
652 	{
653 		gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_DATA_ID, &dataid, -1);
654 		if (dataid == toremove)
655 		{
656 			has_next = gtk_tree_store_remove(sidebar.file_store, &iter);
657 		}
658 		else
659 		{
660 			has_next = gtk_tree_model_iter_next(model, &iter);
661 		}
662 	}
663 	while (has_next);
664 }
665 
666 
667 /* Insert all project bookmarks into the sidebar file tree */
sidebar_insert_project_bookmarks(WB_PROJECT * project,GtkTreeIter * parent,gint * position)668 static void sidebar_insert_project_bookmarks(WB_PROJECT *project, GtkTreeIter *parent, gint *position)
669 {
670 	GIcon *icon;
671 	guint index, max;
672 	GtkTreeIter iter;
673 
674 	if (project == NULL)
675 		return;
676 
677 	max = wb_project_get_bookmarks_count(project);
678 	if (max == 0)
679 		return;
680 
681 	icon = g_icon_new_for_string("user-bookmarks", NULL);
682 	for (index = 0 ; index < max ; index++)
683 	{
684 		gchar *file, *name;
685 
686 		file = wb_project_get_bookmark_at_index(project, index);
687 		name = get_any_relative_path(wb_project_get_filename(project), file);
688 		gtk_tree_store_insert_with_values(sidebar.file_store, &iter, parent, *position,
689 			FILEVIEW_COLUMN_ICON, icon,
690 			FILEVIEW_COLUMN_NAME, name,
691 			FILEVIEW_COLUMN_DATA_ID, DATA_ID_PRJ_BOOKMARK,
692 			FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, file,
693 			-1);
694 		(*position)++;
695 	}
696 	if (icon != NULL)
697 	{
698 		g_object_unref(icon);
699 	}
700 }
701 
702 
703 /* Update the sidebar for a project only */
sidebar_update_project(WB_PROJECT * project,gboolean title_only)704 static void sidebar_update_project(WB_PROJECT *project, gboolean title_only)
705 {
706 	GtkTreeIter iter;
707 
708 	if (wb_globals.opened_wb == NULL)
709 		return;
710 
711 	if (sidebar_get_project_iter(project, &iter))
712 	{
713 		/* Update the title */
714 		GString *name = g_string_new(wb_project_get_name(project));
715 		if (wb_project_is_modified(project))
716 		{
717 			g_string_append_c(name, '*');
718 		}
719 
720 		gtk_tree_store_set(sidebar.file_store, &iter,
721 			FILEVIEW_COLUMN_NAME, name->str,
722 			-1);
723 		g_string_free(name, TRUE);
724 
725 		/* Update children/content to? */
726 		if (!title_only)
727 		{
728 			gint position = 0;
729 			sidebar_remove_children(&iter);
730 			sidebar_insert_project_bookmarks(project, &iter, &position);
731 			sidebar_insert_project_directories(project, &iter, &position);
732 		}
733 	}
734 }
735 
736 
737 /* Insert all projects into the sidebar file tree */
sidebar_insert_all_projects(GtkTreeIter * iter,gint * position)738 static void sidebar_insert_all_projects(GtkTreeIter *iter, gint *position)
739 {
740 	GIcon *icon_good, *icon_bad, *icon;
741 	guint index, max;
742 
743 	if (wb_globals.opened_wb == NULL)
744 		return;
745 
746 	icon_good = g_icon_new_for_string("package-x-generic", NULL);
747 	icon_bad = g_icon_new_for_string("dialog-error", NULL);
748 
749 	max = workbench_get_project_count(wb_globals.opened_wb);
750 	for (index = 0 ; index < max ; index++)
751 	{
752 		gint child_position;
753 		WB_PROJECT *project;
754 
755 		project = workbench_get_project_at_index(wb_globals.opened_wb, index);
756 		if (workbench_get_project_status_at_index(wb_globals.opened_wb, index)
757 			==
758 			PROJECT_ENTRY_STATUS_OK)
759 		{
760 			icon = icon_good;
761 		}
762 		else
763 		{
764 			icon = icon_bad;
765 		}
766 
767 		GString *name = g_string_new(wb_project_get_name(project));
768 		if (wb_project_is_modified(project))
769 		{
770 			g_string_append_c(name, '*');
771 		}
772 
773 		gtk_tree_store_insert_with_values(sidebar.file_store, iter, NULL, *position,
774 			FILEVIEW_COLUMN_ICON, icon,
775 			FILEVIEW_COLUMN_NAME, name->str,
776 			FILEVIEW_COLUMN_DATA_ID, DATA_ID_PROJECT,
777 			FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, project,
778 			-1);
779 		g_string_free(name, TRUE);
780 
781 		child_position = 0;
782 		/* Not required here as we build a completely new tree
783 		   sidebar_remove_children(&iter); */
784 		sidebar_insert_project_bookmarks(project, iter, &child_position);
785 		sidebar_insert_project_directories(project, iter, &child_position);
786 	}
787 	gtk_tree_view_expand_all(GTK_TREE_VIEW(sidebar.file_view));
788 
789 	if (icon_good != NULL)
790 	{
791 		g_object_unref(icon_good);
792 	}
793 	if (icon_bad != NULL)
794 	{
795 		g_object_unref(icon_bad);
796 	}
797 }
798 
799 
800 /* Insert all workbench bookmarks into the sidebar file tree */
sidebar_insert_workbench_bookmarks(WORKBENCH * workbench,GtkTreeIter * iter,gint * position)801 static void sidebar_insert_workbench_bookmarks(WORKBENCH *workbench, GtkTreeIter *iter, gint *position)
802 {
803 	GIcon *icon;
804 	guint index, max;
805 
806 	if (workbench == NULL)
807 	{
808 		return;
809 	}
810 
811 	sidebar_remove_nodes_with_data_id(DATA_ID_WB_BOOKMARK, NULL);
812 
813 	max = workbench_get_bookmarks_count(workbench);
814 	if (max == 0)
815 	{
816 		return;
817 	}
818 
819 	icon = g_icon_new_for_string("user-bookmarks", NULL);
820 	for (index = 0 ; index < max ; index++)
821 	{
822 		gchar *file, *name;
823 
824 		file = workbench_get_bookmark_at_index(workbench, index);
825 		name = get_any_relative_path(workbench_get_filename(workbench), file);
826 		gtk_tree_store_insert_with_values(sidebar.file_store, iter, NULL, *position,
827 			FILEVIEW_COLUMN_ICON, icon,
828 			FILEVIEW_COLUMN_NAME, name,
829 			FILEVIEW_COLUMN_DATA_ID, DATA_ID_WB_BOOKMARK,
830 			FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, file,
831 			-1);
832 		(*position)++;
833 	}
834 
835 	gtk_tree_view_expand_all(GTK_TREE_VIEW(sidebar.file_view));
836 	if (icon != NULL)
837 	{
838 		g_object_unref(icon);
839 	}
840 }
841 
842 
843 /* Reset/Clear/empty the sidebar file tree */
sidebar_reset_tree_store(void)844 static void sidebar_reset_tree_store(void)
845 {
846 	gtk_tree_store_clear(sidebar.file_store);
847 }
848 
849 
850 /* Update the workbench part of the sidebar */
sidebar_update_workbench(GtkTreeIter * iter,gint * position)851 static void sidebar_update_workbench(GtkTreeIter *iter, gint *position)
852 {
853 	guint count;
854 
855 	if (wb_globals.opened_wb == NULL)
856 	{
857 		gtk_label_set_text (GTK_LABEL(sidebar.file_view_label), _("No workbench opened."));
858 		gtk_tree_store_clear(sidebar.file_store);
859 		sidebar_show_intro_message(_("Create or open a workbench\nusing the workbench menu."), FALSE);
860 		sidebar_deactivate();
861 	}
862 	else
863 	{
864 		gint length;
865 		gchar text[200];
866 
867 		gtk_tree_view_set_hover_expand(GTK_TREE_VIEW(sidebar.file_view),
868 			workbench_get_expand_on_hover(wb_globals.opened_wb));
869 
870 		gtk_tree_view_set_enable_tree_lines(GTK_TREE_VIEW(sidebar.file_view),
871 			workbench_get_enable_tree_lines(wb_globals.opened_wb));
872 
873 		count = workbench_get_project_count(wb_globals.opened_wb);
874 		length = g_snprintf(text, sizeof(text),
875 							g_dngettext(GETTEXT_PACKAGE, "%s: %u Project", "%s: %u Projects", count),
876 							workbench_get_name(wb_globals.opened_wb), count);
877 		if (length < (gint)(sizeof(text)-1) && workbench_is_modified(wb_globals.opened_wb))
878 		{
879 			text [length] = '*';
880 			text [length+1] = '\0';
881 		}
882 		gtk_label_set_text (GTK_LABEL(sidebar.file_view_label), text);
883 		if (count == 0)
884 		{
885 			gtk_tree_store_clear(sidebar.file_store);
886 			sidebar_show_intro_message(_("Add a project using the context menu\n"
887 				"or select \"Search projects\" from the menu."), TRUE);
888 		}
889 		else
890 		{
891 			/* Add/show workbench bookmarks if any */
892 			if (iter != NULL)
893 			{
894 				sidebar_insert_workbench_bookmarks(wb_globals.opened_wb, iter, position);
895 			}
896 		}
897 	}
898 }
899 
900 
901 /** Update the sidebar.
902  *
903  * Update the sidebar according to the given situation/event @a event
904  * and data in @a context.
905  *
906  * @param event   Reason for the sidebar update
907  * @param context Data, e.g. actually selected project...
908  *
909  **/
sidebar_update(SIDEBAR_EVENT event,SIDEBAR_CONTEXT * context)910 void sidebar_update (SIDEBAR_EVENT event, SIDEBAR_CONTEXT *context)
911 {
912 	GtkTreeIter iter;
913 	gint position = 0;
914 
915 	switch (event)
916 	{
917 		case SIDEBAR_CONTEXT_WB_CREATED:
918 		case SIDEBAR_CONTEXT_WB_OPENED:
919 		case SIDEBAR_CONTEXT_PROJECT_ADDED:
920 		case SIDEBAR_CONTEXT_PROJECT_REMOVED:
921 			sidebar_reset_tree_store();
922 			sidebar_update_workbench(&iter, &position);
923 			sidebar_insert_all_projects(&iter, &position);
924 
925 			if (event == SIDEBAR_CONTEXT_WB_CREATED ||
926 				event == SIDEBAR_CONTEXT_WB_OPENED)
927 			{
928 				gtk_tree_view_set_hover_expand(GTK_TREE_VIEW(sidebar.file_view),
929 					workbench_get_expand_on_hover(wb_globals.opened_wb));
930 			}
931 
932 			sidebar_activate();
933 		break;
934 		case SIDEBAR_CONTEXT_WB_SAVED:
935 		case SIDEBAR_CONTEXT_WB_SETTINGS_CHANGED:
936 		case SIDEBAR_CONTEXT_WB_CLOSED:
937 			sidebar_update_workbench(NULL, &position);
938 		break;
939 		case SIDEBAR_CONTEXT_PROJECT_SAVED:
940 			if (context != NULL && context->project != NULL)
941 			{
942 				sidebar_update_project(context->project, TRUE);
943 			}
944 		break;
945 		case SIDEBAR_CONTEXT_DIRECTORY_ADDED:
946 		case SIDEBAR_CONTEXT_DIRECTORY_REMOVED:
947 		case SIDEBAR_CONTEXT_DIRECTORY_RESCANNED:
948 		case SIDEBAR_CONTEXT_DIRECTORY_SETTINGS_CHANGED:
949 		case SIDEBAR_CONTEXT_PRJ_BOOKMARK_ADDED:
950 		case SIDEBAR_CONTEXT_PRJ_BOOKMARK_REMOVED:
951 			if (context != NULL && context->project != NULL)
952 			{
953 				sidebar_update_project(context->project, FALSE);
954 			}
955 		break;
956 		case SIDEBAR_CONTEXT_WB_BOOKMARK_ADDED:
957 		case SIDEBAR_CONTEXT_WB_BOOKMARK_REMOVED:
958 		{
959 			GtkTreeIter first;
960 			GtkTreeModel *model;
961 
962 			model = gtk_tree_view_get_model(GTK_TREE_VIEW(sidebar.file_view));
963 			if (gtk_tree_model_get_iter_first (model, &first))
964 			{
965 				sidebar_update_workbench(&first, &position);
966 			}
967 		}
968 		break;
969 		case SIDEBAR_CONTEXT_FILE_ADDED:
970 			sidebar_add_file(context->project, context->directory, context->file);
971 		break;
972 		case SIDEBAR_CONTEXT_FILE_REMOVED:
973 			sidebar_remove_file(context->project, context->directory, context->file);
974 		break;
975 	}
976 }
977 
978 
979 /* Callback function for clicking on a sidebar item */
sidebar_filew_view_on_row_activated(GtkTreeView * treeview,GtkTreePath * path,G_GNUC_UNUSED GtkTreeViewColumn * col,G_GNUC_UNUSED gpointer userdata)980 static void sidebar_filew_view_on_row_activated (GtkTreeView *treeview,
981 	GtkTreePath *path, G_GNUC_UNUSED GtkTreeViewColumn *col, G_GNUC_UNUSED gpointer userdata)
982 {
983 	gchar *info;
984 	GtkTreeModel *model;
985 	GtkTreeIter  iter;
986 
987 	model = gtk_tree_view_get_model(treeview);
988 
989 	if (gtk_tree_model_get_iter(model, &iter, path))
990 	{
991 		guint dataid;
992 		gchar *name;
993 		void  *address;
994 
995 		gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_NAME, &name, -1);
996 		gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_DATA_ID, &dataid, -1);
997 		gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, &address, -1);
998 
999 		switch (dataid)
1000 		{
1001 			case DATA_ID_PROJECT:
1002 				info = wb_project_get_info((WB_PROJECT *)address);
1003 				if (workbench_get_project_status_by_address(wb_globals.opened_wb, address)
1004 					==
1005 					PROJECT_ENTRY_STATUS_OK)
1006 				{
1007 					dialogs_show_msgbox(GTK_MESSAGE_INFO, "%s", info);
1008 				}
1009 				else
1010 				{
1011 					dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("%s\nProject file not found!"), info);
1012 				}
1013 				g_free(info);
1014 			break;
1015 			case DATA_ID_DIRECTORY:
1016 				info = wb_project_dir_get_info((WB_PROJECT_DIR *)address);
1017 				dialogs_show_msgbox(GTK_MESSAGE_INFO, "%s", info);
1018 			break;
1019 			case DATA_ID_WB_BOOKMARK:
1020 			case DATA_ID_PRJ_BOOKMARK:
1021 			case DATA_ID_FILE:
1022 				document_open_file((char *)address, FALSE, NULL, NULL);
1023 			break;
1024 			case DATA_ID_NO_DIRS:
1025 				dialogs_show_msgbox(GTK_MESSAGE_INFO, _("This project has no directories. Directories can be added to a project using the context menu."));
1026 			break;
1027 			default:
1028 			break;
1029 		}
1030 
1031 		g_free(name);
1032 	}
1033 }
1034 
1035 
1036 /* Callbac function for button release, used for the popup menu */
sidebar_file_view_on_button_release(G_GNUC_UNUSED GtkWidget * widget,GdkEventButton * event,G_GNUC_UNUSED gpointer user_data)1037 static gboolean sidebar_file_view_on_button_release(G_GNUC_UNUSED GtkWidget * widget, GdkEventButton * event,
1038 		G_GNUC_UNUSED gpointer user_data)
1039 {
1040 	if (event->button == 3)
1041 	{
1042 		SIDEBAR_CONTEXT context;
1043 		POPUP_CONTEXT popup_context = POPUP_CONTEXT_BACKGROUND;
1044 
1045 		if (sidebar_file_view_get_selected_context(&context))
1046 		{
1047 			if (context.file != NULL)
1048 			{
1049 				popup_context = POPUP_CONTEXT_FILE;
1050 			}
1051 			else if (context.subdir != NULL)
1052 			{
1053 				popup_context = POPUP_CONTEXT_SUB_DIRECTORY;
1054 			}
1055 			else if (context.directory != NULL)
1056 			{
1057 				popup_context = POPUP_CONTEXT_DIRECTORY;
1058 			}
1059 			else if (context.prj_bookmark != NULL)
1060 			{
1061 				popup_context = POPUP_CONTEXT_PRJ_BOOKMARK;
1062 			}
1063 			else if (context.project != NULL)
1064 			{
1065 				popup_context = POPUP_CONTEXT_PROJECT;
1066 			}
1067 			else if (context.wb_bookmark != NULL)
1068 			{
1069 				popup_context = POPUP_CONTEXT_WB_BOOKMARK;
1070 			}
1071 		}
1072 		popup_menu_show(popup_context, event);
1073 
1074 		return TRUE;
1075 	}
1076 
1077 	return FALSE;
1078 }
1079 
1080 
1081 /** Get the selected project and the path to it.
1082  *
1083  * Get the selected project and return a pointer to it. Also if @a path is not NULL then
1084  * the path to the row containing the project is returned in *path.
1085  *
1086  * @param path @nullable Location to return the path or @c NULL
1087  *
1088  **/
sidebar_file_view_get_selected_project(GtkTreePath ** path)1089 WB_PROJECT *sidebar_file_view_get_selected_project(GtkTreePath **path)
1090 {
1091 	gboolean has_parent;
1092 	guint dataid;
1093 	GtkTreeSelection *treesel;
1094 	GtkTreeModel *model;
1095 	GtkTreeIter current, parent;
1096 	WB_PROJECT *project;
1097 
1098 	if (path != NULL)
1099 	{
1100 		*path = NULL;
1101 	}
1102 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(sidebar.file_view));
1103 	if (gtk_tree_selection_get_selected(treesel, &model, &current))
1104 	{
1105 		do
1106 		{
1107 			gtk_tree_model_get(model, &current, FILEVIEW_COLUMN_DATA_ID, &dataid, -1);
1108 			gtk_tree_model_get(model, &current, FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, &project, -1);
1109 			if (dataid == DATA_ID_PROJECT && project != NULL)
1110 			{
1111 				if (path != NULL)
1112 				{
1113 					*path = gtk_tree_model_get_path(model, &current);
1114 				}
1115 				return project;
1116 			}
1117 			has_parent = gtk_tree_model_iter_parent(model,
1118 							&parent, &current);
1119 			current = parent;
1120 		}while (has_parent);
1121 	}
1122 	return NULL;
1123 }
1124 
1125 
1126 /* Search upwards from the selected row and get the iter of the
1127    closest parent with DATA_ID id. */
sidebar_file_view_get_selected_parent_iter(GtkTreeIter * iter,guint id)1128 static gboolean sidebar_file_view_get_selected_parent_iter(GtkTreeIter *iter, guint id)
1129 {
1130 	gboolean has_parent;
1131 	guint dataid;
1132 	GtkTreeSelection *treesel;
1133 	GtkTreeModel *model;
1134 	GtkTreeIter current, parent;
1135 
1136 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(sidebar.file_view));
1137 	if (gtk_tree_selection_get_selected(treesel, &model, &current))
1138 	{
1139 		do
1140 		{
1141 			gtk_tree_model_get(model, &current, FILEVIEW_COLUMN_DATA_ID, &dataid, -1);
1142 			if (dataid == id)
1143 			{
1144 				*iter = current;
1145 				return TRUE;
1146 			}
1147 			has_parent = gtk_tree_model_iter_parent(model,
1148 							&parent, &current);
1149 			current = parent;
1150 		}while (has_parent);
1151 	}
1152 	return FALSE;
1153 }
1154 
1155 
1156 /** Get the selected project directory and the path to it.
1157  *
1158  * Get the selected project directory and return a pointer to it. Also if @a path is not NULL then
1159  * the path to the row containing the project is returned in *path.
1160  *
1161  * @param path @nullable Location to return the path or @c NULL
1162  *
1163  **/
sidebar_file_view_get_selected_project_dir(GtkTreePath ** path)1164 static WB_PROJECT_DIR *sidebar_file_view_get_selected_project_dir(GtkTreePath **path)
1165 {
1166 	gboolean has_parent;
1167 	guint dataid;
1168 	GtkTreeSelection *treesel;
1169 	GtkTreeModel *model;
1170 	GtkTreeIter current, parent;
1171 	WB_PROJECT_DIR *directory;
1172 
1173 	if (path != NULL)
1174 	{
1175 		*path = NULL;
1176 	}
1177 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(sidebar.file_view));
1178 	if (gtk_tree_selection_get_selected(treesel, &model, &current))
1179 	{
1180 		do
1181 		{
1182 			gtk_tree_model_get(model, &current, FILEVIEW_COLUMN_DATA_ID, &dataid, -1);
1183 			gtk_tree_model_get(model, &current, FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, &directory, -1);
1184 			if (dataid == DATA_ID_DIRECTORY && directory != NULL)
1185 			{
1186 				if (path != NULL)
1187 				{
1188 					*path = gtk_tree_model_get_path(model, &current);
1189 				}
1190 				return directory;
1191 			}
1192 			has_parent = gtk_tree_model_iter_parent(model,
1193 							&parent, &current);
1194 			current = parent;
1195 		}while (has_parent);
1196 	}
1197 	return NULL;
1198 }
1199 
1200 
1201 /** Get data corresponding to the current selected tree view selection.
1202  *
1203  * The function collects all data corresponding to the current selection and stores it in
1204  * @a context. This is e.g. the selected file and the directory and project to which it
1205  * belongs.
1206  *
1207  * @param context The location to store the selection context data in.
1208  * @return TRUE if anything in the tree is selected, FALSE otherwise.
1209  *
1210  **/
sidebar_file_view_get_selected_context(SIDEBAR_CONTEXT * context)1211 gboolean sidebar_file_view_get_selected_context(SIDEBAR_CONTEXT *context)
1212 {
1213 	gboolean has_parent;
1214 	guint dataid;
1215 	GtkTreeSelection *treesel;
1216 	GtkTreeModel *model;
1217 	GtkTreeIter current, parent;
1218 	gpointer data;
1219 
1220 	if (context == NULL)
1221 	{
1222 		return FALSE;
1223 	}
1224 	memset (context, 0, sizeof(*context));
1225 
1226 	treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(sidebar.file_view));
1227 	if (gtk_tree_selection_get_selected(treesel, &model, &current))
1228 	{
1229 		/* Search through the parents upwards until we find the project node.
1230 		   Save everthing that's interesting in callers variables... */
1231 		do
1232 		{
1233 			gtk_tree_model_get(model, &current, FILEVIEW_COLUMN_DATA_ID, &dataid, -1);
1234 			gtk_tree_model_get(model, &current, FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, &data, -1);
1235 			if (data != NULL)
1236 			{
1237 				switch (dataid)
1238 				{
1239 					case DATA_ID_WB_BOOKMARK:
1240 						context->wb_bookmark = data;
1241 					break;
1242 					case DATA_ID_PROJECT:
1243 						context->project = data;
1244 					break;
1245 					case DATA_ID_PRJ_BOOKMARK:
1246 						context->prj_bookmark = data;
1247 					break;
1248 					case DATA_ID_DIRECTORY:
1249 						context->directory = data;
1250 					break;
1251 					case DATA_ID_NO_DIRS:
1252 						/* Has not got any data. */
1253 					break;
1254 					case DATA_ID_SUB_DIRECTORY:
1255 						if (context->subdir == NULL)
1256 						{
1257 							context->subdir = data;
1258 						}
1259 					break;
1260 					case DATA_ID_FILE:
1261 						context->file = data;
1262 					break;
1263 				}
1264 			}
1265 
1266 			has_parent = gtk_tree_model_iter_parent(model,
1267 							&parent, &current);
1268 			current = parent;
1269 		}while (has_parent);
1270 
1271 		return TRUE;
1272 	}
1273 	return FALSE;
1274 }
1275 
1276 /* Collect all filenames recursively starting from iter and add them to list */
sidebar_get_filelist_for_iter(GPtrArray * list,GtkTreeIter iter,gboolean dirnames)1277 static void sidebar_get_filelist_for_iter(GPtrArray *list, GtkTreeIter iter, gboolean dirnames)
1278 {
1279 	GtkTreeModel *model;
1280 	GtkTreeIter childs;
1281 	gboolean has_next;
1282 	guint dataid;
1283 	char *filename;
1284 
1285 	model = gtk_tree_view_get_model(GTK_TREE_VIEW(sidebar.file_view));
1286 	do
1287 	{
1288 		gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_DATA_ID, &dataid, -1);
1289 		switch (dataid)
1290 		{
1291 			case DATA_ID_FILE:
1292 				gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, &filename, -1);
1293 				g_ptr_array_add(list, g_strdup(filename));
1294 			break;
1295 			case DATA_ID_DIRECTORY:
1296 			case DATA_ID_SUB_DIRECTORY:
1297 				if (dirnames == TRUE)
1298 				{
1299 					gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, &filename, -1);
1300 					g_ptr_array_add(list, g_strdup(filename));
1301 				}
1302 				if (gtk_tree_model_iter_children(model, &childs, &iter) == TRUE)
1303 				{
1304 					sidebar_get_filelist_for_iter(list, childs, dirnames);
1305 				}
1306 			break;
1307 		}
1308 		has_next = gtk_tree_model_iter_next(model, &iter);
1309 	}
1310 	while (has_next);
1311 }
1312 
1313 
1314 /* Get the lkist of files belonging to the current selection for
1315    id (id = project, directory, sub-directory) */
sidebar_get_selected_filelist(guint id,gboolean dirnames)1316 static GPtrArray *sidebar_get_selected_filelist (guint id, gboolean dirnames)
1317 {
1318 	GtkTreeModel *model;
1319 	GPtrArray *list;
1320 	GtkTreeIter iter, childs;
1321 
1322 	if (sidebar_file_view_get_selected_parent_iter(&iter, id))
1323 	{
1324 		list = g_ptr_array_new_full(1, g_free);
1325 		model = gtk_tree_view_get_model(GTK_TREE_VIEW(sidebar.file_view));
1326 		if (gtk_tree_model_iter_children(model, &childs, &iter) == TRUE)
1327 		{
1328 			sidebar_get_filelist_for_iter(list, childs, dirnames);
1329 		}
1330 		return list;
1331 	}
1332 
1333 	return NULL;
1334 }
1335 
1336 
1337 /** Get the list of files corresponding to the selected project.
1338  *
1339  * @return GPtrArray containing file names or NULL.
1340  *
1341  **/
sidebar_get_selected_project_filelist(gboolean dirnames)1342 GPtrArray *sidebar_get_selected_project_filelist (gboolean dirnames)
1343 {
1344 	return sidebar_get_selected_filelist(DATA_ID_PROJECT, dirnames);
1345 }
1346 
1347 
1348 /** Get the list of files corresponding to the selected directory.
1349  *
1350  * @return GPtrArray containing file names or NULL.
1351  *
1352  **/
sidebar_get_selected_directory_filelist(gboolean dirnames)1353 GPtrArray *sidebar_get_selected_directory_filelist (gboolean dirnames)
1354 {
1355 	return sidebar_get_selected_filelist(DATA_ID_DIRECTORY, dirnames);
1356 }
1357 
1358 
1359 /** Get the list of files corresponding to the selected sub-directory.
1360  *
1361  * @return GPtrArray containing file names or NULL.
1362  *
1363  **/
sidebar_get_selected_subdir_filelist(gboolean dirnames)1364 GPtrArray *sidebar_get_selected_subdir_filelist (gboolean dirnames)
1365 {
1366 	return sidebar_get_selected_filelist(DATA_ID_SUB_DIRECTORY, dirnames);
1367 }
1368 
1369 /** Setup the sidebar.
1370  *
1371  **/
sidebar_init(void)1372 void sidebar_init(void)
1373 {
1374 	GtkWidget *scrollwin;
1375 	GtkCellRenderer *renderer;
1376 	GtkTreeViewColumn *column;
1377 	GtkTreeSelection *sel;
1378 	GList *focus_chain = NULL;
1379 
1380 	sidebar.file_view_vbox = gtk_vbox_new(FALSE, 0);
1381 	gtk_widget_set_name(sidebar.file_view_vbox, "workbench");
1382 
1383 	/**** label ****/
1384 	sidebar.file_view_label = gtk_label_new (_("No workbench opened."));
1385 	gtk_box_pack_start(GTK_BOX(sidebar.file_view_vbox), sidebar.file_view_label, FALSE, FALSE, 0);
1386 
1387 	/**** tree view ****/
1388 
1389 	sidebar.file_view = gtk_tree_view_new();
1390 	g_signal_connect(sidebar.file_view, "row-activated", (GCallback)sidebar_filew_view_on_row_activated, NULL);
1391 
1392 	sidebar.file_store = gtk_tree_store_new(FILEVIEW_N_COLUMNS, G_TYPE_ICON, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_POINTER);
1393 	gtk_tree_view_set_model(GTK_TREE_VIEW(sidebar.file_view), GTK_TREE_MODEL(sidebar.file_store));
1394 
1395 	renderer = gtk_cell_renderer_pixbuf_new();
1396 	column = gtk_tree_view_column_new();
1397 	gtk_tree_view_column_pack_start(column, renderer, FALSE);
1398 	gtk_tree_view_column_add_attribute(column, renderer, "gicon", FILEVIEW_COLUMN_ICON);
1399 
1400 	renderer = gtk_cell_renderer_text_new();
1401 	gtk_tree_view_column_pack_start(column, renderer, TRUE);
1402 	gtk_tree_view_column_add_attribute(column, renderer, "text", FILEVIEW_COLUMN_NAME);
1403 
1404 	gtk_tree_view_append_column(GTK_TREE_VIEW(sidebar.file_view), column);
1405 
1406 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(sidebar.file_view), FALSE);
1407 	gtk_tree_view_set_enable_search(GTK_TREE_VIEW(sidebar.file_view), TRUE);
1408 	gtk_tree_view_set_search_column(GTK_TREE_VIEW(sidebar.file_view), FILEVIEW_COLUMN_NAME);
1409 
1410 	ui_widget_modify_font_from_string(sidebar.file_view,
1411 		wb_globals.geany_plugin->geany_data->interface_prefs->tagbar_font);
1412 
1413 	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(sidebar.file_view));
1414 	gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE);
1415 
1416 	g_signal_connect(G_OBJECT(sidebar.file_view), "button-release-event",
1417 			G_CALLBACK(sidebar_file_view_on_button_release), NULL);
1418 
1419 	sidebar_deactivate();
1420 
1421 	/**** the rest ****/
1422 	focus_chain = g_list_prepend(focus_chain, sidebar.file_view);
1423 	gtk_container_set_focus_chain(GTK_CONTAINER(sidebar.file_view_vbox), focus_chain);
1424 	g_list_free(focus_chain);
1425 	scrollwin = gtk_scrolled_window_new(NULL, NULL);
1426 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
1427 					   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1428 	gtk_container_add(GTK_CONTAINER(scrollwin), sidebar.file_view);
1429 	gtk_box_pack_start(GTK_BOX(sidebar.file_view_vbox), scrollwin, TRUE, TRUE, 0);
1430 
1431 	gtk_widget_show_all(sidebar.file_view_vbox);
1432 	gtk_notebook_append_page(GTK_NOTEBOOK(wb_globals.geany_plugin->geany_data->main_widgets->sidebar_notebook),
1433 				 sidebar.file_view_vbox, gtk_label_new(_("Workbench")));
1434 }
1435 
1436 
1437 /** Display a text message in the sidebar.
1438  *
1439  * The function displays the text @a msg in the sidebar and eventually deactivates the sidebar
1440  * if @a activate is FALSE.
1441  *
1442  * @param msg      The text to display
1443  * @param activate If TRUE the sidebar will be activated, if not it will be deactivated
1444  *
1445  **/
sidebar_show_intro_message(const gchar * msg,gboolean activate)1446 void sidebar_show_intro_message(const gchar *msg, gboolean activate)
1447 {
1448 	GtkTreeIter iter;
1449 
1450 	gtk_tree_store_insert_with_values(sidebar.file_store, &iter, NULL, -1,
1451 		FILEVIEW_COLUMN_NAME, msg, -1);
1452 	if (activate)
1453 	{
1454 		sidebar_activate();
1455 	}
1456 	else
1457 	{
1458 		sidebar_deactivate();
1459 	}
1460 }
1461 
1462 
1463 /** Activate the sidebar.
1464  *
1465  **/
sidebar_activate(void)1466 void sidebar_activate(void)
1467 {
1468 	gtk_widget_set_sensitive(sidebar.file_view_vbox, TRUE);
1469 }
1470 
1471 
1472 /** Deactivate the sidebar.
1473  *
1474  **/
sidebar_deactivate(void)1475 void sidebar_deactivate(void)
1476 {
1477 	gtk_widget_set_sensitive(sidebar.file_view_vbox, FALSE);
1478 }
1479 
1480 
1481 /** Cleanup the sidebar.
1482  *
1483  **/
sidebar_cleanup(void)1484 void sidebar_cleanup(void)
1485 {
1486 	gtk_widget_destroy(sidebar.file_view_vbox);
1487 }
1488 
1489 
1490 /** Expand all rows in the sidebar tree.
1491  *
1492  **/
sidebar_expand_all(void)1493 void sidebar_expand_all(void)
1494 {
1495 	gtk_tree_view_expand_all(GTK_TREE_VIEW(sidebar.file_view));
1496 }
1497 
1498 
1499 /** Collapse all rows in the sidebar tree.
1500  *
1501  **/
sidebar_collapse_all(void)1502 void sidebar_collapse_all(void)
1503 {
1504 	gtk_tree_view_collapse_all(GTK_TREE_VIEW(sidebar.file_view));
1505 }
1506 
1507 
1508 /** Expand all rows in the sidebar tree for the selected project.
1509  *
1510  **/
sidebar_expand_selected_project(void)1511 void sidebar_expand_selected_project(void)
1512 {
1513 	GtkTreePath *path;
1514 
1515 	sidebar_file_view_get_selected_project(&path);
1516 	if (path != NULL)
1517 	{
1518 		gtk_tree_view_expand_row(GTK_TREE_VIEW(sidebar.file_view),
1519 			path, TRUE);
1520 		gtk_tree_path_free(path);
1521 	}
1522 }
1523 
1524 
1525 /** Collapse all rows in the sidebar tree for the selected project.
1526  *
1527  **/
sidebar_collapse_selected_project(void)1528 void sidebar_collapse_selected_project(void)
1529 {
1530 	GtkTreePath *path;
1531 
1532 	sidebar_file_view_get_selected_project(&path);
1533 	if (path != NULL)
1534 	{
1535 		gtk_tree_view_collapse_row(GTK_TREE_VIEW(sidebar.file_view),
1536 			path);
1537 		gtk_tree_path_free(path);
1538 	}
1539 }
1540 
1541 
1542 /** Toggle state of all rows in the sidebar tree for the selected project.
1543  *
1544  * If the project is expanded, then it will be collapsed and vice versa.
1545  *
1546  **/
sidebar_toggle_selected_project_expansion(void)1547 void sidebar_toggle_selected_project_expansion(void)
1548 {
1549 	GtkTreePath *path;
1550 
1551 	sidebar_file_view_get_selected_project(&path);
1552 	if (path != NULL)
1553 	{
1554 		if (gtk_tree_view_row_expanded
1555 		        (GTK_TREE_VIEW(sidebar.file_view),path))
1556 		{
1557 			gtk_tree_view_collapse_row(GTK_TREE_VIEW(sidebar.file_view),
1558 										path);
1559 		}
1560 		else
1561 		{
1562 			gtk_tree_view_expand_row (GTK_TREE_VIEW(sidebar.file_view),
1563 										path, TRUE);
1564 		}
1565 		gtk_tree_path_free(path);
1566 	}
1567 }
1568 
1569 
1570 /** Toggle state of all rows in the sidebar tree for the selected directory.
1571  *
1572  * If the directory is expanded, then it will be collapsed and vice versa.
1573  *
1574  **/
sidebar_toggle_selected_project_dir_expansion(void)1575 void sidebar_toggle_selected_project_dir_expansion (void)
1576 {
1577 	GtkTreePath *path;
1578 
1579 	sidebar_file_view_get_selected_project_dir(&path);
1580 	if (path != NULL)
1581 	{
1582 		if (gtk_tree_view_row_expanded
1583 			(GTK_TREE_VIEW(sidebar.file_view),path))
1584 		{
1585 			gtk_tree_view_collapse_row (GTK_TREE_VIEW(sidebar.file_view),
1586 										path);
1587 		}
1588 		else
1589 		{
1590 			gtk_tree_view_expand_row (GTK_TREE_VIEW(sidebar.file_view),
1591 										path, TRUE);
1592 		}
1593 		gtk_tree_path_free(path);
1594 	}
1595 }
1596 
1597 
1598 /* Helper function to set context according to given parameters. */
sidebar_set_context(SIDEBAR_CONTEXT * context,guint dataid,gpointer data)1599 static void sidebar_set_context (SIDEBAR_CONTEXT *context, guint dataid, gpointer data)
1600 {
1601 	if (data != NULL)
1602 	{
1603 		switch (dataid)
1604 		{
1605 			case DATA_ID_WB_BOOKMARK:
1606 				memset(context, 0, sizeof(*context));
1607 				context->wb_bookmark = data;
1608 			break;
1609 			case DATA_ID_PROJECT:
1610 				memset(context, 0, sizeof(*context));
1611 				context->project = data;
1612 			break;
1613 			case DATA_ID_PRJ_BOOKMARK:
1614 				context->prj_bookmark = data;
1615 				context->directory = NULL;
1616 				context->subdir = NULL;
1617 				context->file = NULL;
1618 			break;
1619 			case DATA_ID_DIRECTORY:
1620 				context->directory = data;
1621 				context->subdir = NULL;
1622 				context->file = NULL;
1623 			break;
1624 			case DATA_ID_NO_DIRS:
1625 				/* Has not got any data. */
1626 			break;
1627 			case DATA_ID_SUB_DIRECTORY:
1628 				context->subdir = data;
1629 				context->file = NULL;
1630 			break;
1631 			case DATA_ID_FILE:
1632 				context->file = data;
1633 			break;
1634 		}
1635 	}
1636 }
1637 
1638 
1639 /* Internal call foreach function. Traverses the whole sidebar. */
sidebar_call_foreach_int(SB_CALLFOREACH_CONTEXT * foreach_cntxt,GtkTreeIter * iter)1640 void sidebar_call_foreach_int(SB_CALLFOREACH_CONTEXT *foreach_cntxt,
1641 							  GtkTreeIter *iter)
1642 {
1643 	guint currentid;
1644 	gpointer current;
1645 	GtkTreeIter children;
1646 
1647 	do
1648 	{
1649 		gtk_tree_model_get(foreach_cntxt->model, iter, FILEVIEW_COLUMN_DATA_ID, &currentid, -1);
1650 		gtk_tree_model_get(foreach_cntxt->model, iter, FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, &current, -1);
1651 
1652 		sidebar_set_context(foreach_cntxt->context, currentid, current);
1653 		if (currentid == foreach_cntxt->dataid)
1654 		{
1655 			/* Found requested node. Call callback function. */
1656 			foreach_cntxt->func(foreach_cntxt->context, foreach_cntxt->userdata);
1657 		}
1658 
1659 		/* If the node has childs then check them to. */
1660 		if (gtk_tree_model_iter_children (foreach_cntxt->model, &children, iter))
1661 		{
1662 			sidebar_call_foreach_int(foreach_cntxt, &children);
1663 		}
1664 	}while (gtk_tree_model_iter_next(foreach_cntxt->model, iter));
1665 }
1666 
1667 
1668 /** Call a callback function for each matching node.
1669  *
1670  * This function traverses the complete sidebar tree and calls func each time
1671  * a node with dataid is found. The function is passed the sidebar context
1672  * for the current position in hte tree and the given userdata.
1673  *
1674  * @param dataid   Which nodes are of interest?
1675  * @param func     Callback function
1676  * @param userdata Pointer to user data. Is transparently passed through
1677  *                 on calling func.
1678  *
1679  **/
sidebar_call_foreach(guint dataid,void (* func)(SIDEBAR_CONTEXT *,gpointer userdata),gpointer userdata)1680 void sidebar_call_foreach(guint dataid,
1681 						  void (*func)(SIDEBAR_CONTEXT *, gpointer userdata),
1682 						  gpointer userdata)
1683 {
1684 	GtkTreeIter iter;
1685 	GtkTreeModel *model;
1686 	SIDEBAR_CONTEXT context;
1687 	SB_CALLFOREACH_CONTEXT foreach_cntxt;
1688 
1689 	model = gtk_tree_view_get_model(GTK_TREE_VIEW(sidebar.file_view));
1690 	if (gtk_tree_model_get_iter_first (model, &iter))
1691 	{
1692 		foreach_cntxt.context = &context;
1693 		foreach_cntxt.model = model;
1694 		foreach_cntxt.dataid = dataid;
1695 		foreach_cntxt.func = func;
1696 		foreach_cntxt.userdata = userdata;
1697 		sidebar_call_foreach_int(&foreach_cntxt, &iter);
1698 	}
1699 }
1700