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