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