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, ¤t, -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, ¤t, -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, ¤t, -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, ¤t))
1104 {
1105 do
1106 {
1107 gtk_tree_model_get(model, ¤t, FILEVIEW_COLUMN_DATA_ID, &dataid, -1);
1108 gtk_tree_model_get(model, ¤t, 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, ¤t);
1114 }
1115 return project;
1116 }
1117 has_parent = gtk_tree_model_iter_parent(model,
1118 &parent, ¤t);
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, ¤t))
1138 {
1139 do
1140 {
1141 gtk_tree_model_get(model, ¤t, 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, ¤t);
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, ¤t))
1179 {
1180 do
1181 {
1182 gtk_tree_model_get(model, ¤t, FILEVIEW_COLUMN_DATA_ID, &dataid, -1);
1183 gtk_tree_model_get(model, ¤t, 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, ¤t);
1189 }
1190 return directory;
1191 }
1192 has_parent = gtk_tree_model_iter_parent(model,
1193 &parent, ¤t);
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, ¤t))
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, ¤t, FILEVIEW_COLUMN_DATA_ID, &dataid, -1);
1234 gtk_tree_model_get(model, ¤t, 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, ¤t);
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, ¤tid, -1);
1650 gtk_tree_model_get(foreach_cntxt->model, iter, FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, ¤t, -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