use relative path in sidebar
[geanyprj.git] / sidebar.c
bloba7c867afeec870fc766f48376fdd17b3ab08967b
1 /*
2 * geanyprj - Alternative project support for geany light IDE.
4 * Copyright 2008 Yura Siamashka <yurand2@gmail.com>
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.
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.
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/>.
21 #include <sys/time.h>
22 #include <gdk/gdkkeysyms.h>
24 #include "geany.h"
25 #include "support.h"
26 #include "prefs.h"
27 #include "plugindata.h"
28 #include "document.h"
29 #include "filetypes.h"
30 #include "keybindings.h"
31 #include "utils.h"
32 #include "pluginmacros.h"
34 #include "project.h"
36 #include "geanyprj.h"
38 extern GeanyData *geany_data;
40 static GtkWidget *file_view_vbox;
41 static GtkWidget *file_view;
42 static GtkListStore *file_store;
44 enum
46 FILEVIEW_COLUMN_NAME = 0,
47 FILEVIEW_N_COLUMNS,
50 static struct
52 GtkWidget *new_project;
53 GtkWidget *delete_project;
55 GtkWidget *add_file;
56 GtkWidget *remove_files;
58 GtkWidget *preferences;
60 GtkWidget *find_in_files;
61 } popup_items;
64 /* Returns: the full filename in locale encoding. */
65 static gchar *
66 get_tree_path_filename(GtkTreePath * treepath)
68 GtkTreeModel *model = GTK_TREE_MODEL(file_store);
69 GtkTreeIter iter;
70 gchar *name;
72 gtk_tree_model_get_iter(model, &iter, treepath);
73 gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_NAME, &name, -1);
74 setptr(name, p_utils->get_locale_from_utf8(name));
75 return name;
78 /* We use documents->open_files() as it's more efficient. */
79 static void
80 open_selected_files(GList * list)
82 GSList *files = NULL;
83 GList *item;
85 for (item = list; item != NULL; item = g_list_next(item))
87 GtkTreePath *treepath = item->data;
88 gchar *fname = get_tree_path_filename(treepath);
89 setptr(fname, get_full_path(g_current_project->path, fname));
90 files = g_slist_append(files, fname);
92 p_document->open_files(files, FALSE, NULL, NULL);
93 g_slist_foreach(files, (GFunc) g_free, NULL); // free filenames
94 g_slist_free(files);
97 static void
98 on_open_clicked(GtkMenuItem * menuitem, gpointer user_data)
100 GtkTreeSelection *treesel;
101 GtkTreeModel *model;
102 GList *list;
103 gboolean dir_found;
105 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view));
107 list = gtk_tree_selection_get_selected_rows(treesel, &model);
108 open_selected_files(list);
109 g_list_foreach(list, (GFunc) gtk_tree_path_free, NULL);
110 g_list_free(list);
113 static gboolean
114 on_button_press(GtkWidget * widget, GdkEventButton * event, gpointer user_data)
116 if (event->button == 1 && event->type == GDK_2BUTTON_PRESS)
117 on_open_clicked(NULL, NULL);
118 return FALSE;
121 static GtkWidget *
122 make_toolbar()
124 GtkWidget *toolbar;
126 toolbar = gtk_toolbar_new();
127 gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar), GTK_ICON_SIZE_MENU);
128 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
130 return toolbar;
133 static void
134 remove_selected_files(GList * list)
136 GList *item;
137 for (item = list; item != NULL; item = g_list_next(item))
139 GtkTreePath *treepath = item->data;
140 gchar *fname = get_tree_path_filename(treepath);
141 xproject_remove_file(fname);
142 g_free(fname);
146 static void
147 on_remove_files(GtkMenuItem * menuitem, gpointer user_data)
149 GtkTreeSelection *treesel;
150 GtkTreeModel *model;
151 GList *list;
152 gboolean dir_found;
154 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view));
156 list = gtk_tree_selection_get_selected_rows(treesel, &model);
157 remove_selected_files(list);
158 g_list_foreach(list, (GFunc) gtk_tree_path_free, NULL);
159 g_list_free(list);
162 static gboolean
163 on_key_press(GtkWidget * widget, GdkEventKey * event, gpointer data)
165 if (event->keyval == GDK_Return
166 || event->keyval == GDK_ISO_Enter
167 || event->keyval == GDK_KP_Enter || event->keyval == GDK_space)
168 on_open_clicked(NULL, NULL);
169 return FALSE;
172 static GtkWidget *
173 create_popup_menu()
175 GtkWidget *item, *menu, *image;
177 menu = gtk_menu_new();
179 image = gtk_image_new_from_stock(GTK_STOCK_NEW, GTK_ICON_SIZE_MENU);
180 gtk_widget_show(image);
181 item = gtk_image_menu_item_new_with_mnemonic(_("New 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_new_project), NULL);
186 popup_items.new_project = item;
188 image = gtk_image_new_from_stock(GTK_STOCK_DELETE, GTK_ICON_SIZE_MENU);
189 gtk_widget_show(image);
190 item = gtk_image_menu_item_new_with_mnemonic(_("Delete Project"));
191 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
192 gtk_widget_show(item);
193 gtk_container_add(GTK_CONTAINER(menu), item);
194 g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_delete_project), NULL);
195 popup_items.delete_project = item;
197 item = gtk_separator_menu_item_new();
198 gtk_widget_show(item);
199 gtk_container_add(GTK_CONTAINER(menu), item);
201 image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
202 gtk_widget_show(image);
203 item = gtk_image_menu_item_new_with_mnemonic(_("Add 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_add_file), NULL);
208 popup_items.add_file = item;
210 image = gtk_image_new_from_stock(GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU);
211 gtk_widget_show(image);
212 item = gtk_image_menu_item_new_with_mnemonic(_("Remove File"));
213 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
214 gtk_widget_show(item);
215 gtk_container_add(GTK_CONTAINER(menu), item);
216 g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_remove_files), NULL);
217 popup_items.remove_files = item;
219 item = gtk_separator_menu_item_new();
220 gtk_widget_show(item);
221 gtk_container_add(GTK_CONTAINER(menu), item);
223 image = gtk_image_new_from_stock(GTK_STOCK_PREFERENCES, GTK_ICON_SIZE_MENU);
224 gtk_widget_show(image);
225 item = gtk_image_menu_item_new_with_mnemonic(_("Preferences"));
226 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
227 gtk_widget_show(item);
228 gtk_container_add(GTK_CONTAINER(menu), item);
229 g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_preferences), NULL);
230 popup_items.preferences = item;
232 item = gtk_separator_menu_item_new();
233 gtk_widget_show(item);
234 gtk_container_add(GTK_CONTAINER(menu), item);
236 image = gtk_image_new_from_stock(GTK_STOCK_FIND, GTK_ICON_SIZE_MENU);
237 gtk_widget_show(image);
238 item = gtk_image_menu_item_new_with_mnemonic(_("Find in Project"));
239 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
240 gtk_widget_show(item);
241 gtk_container_add(GTK_CONTAINER(menu), item);
242 g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_find_in_project), NULL);
243 popup_items.find_in_files = item;
245 item = gtk_separator_menu_item_new();
246 gtk_widget_show(item);
247 gtk_container_add(GTK_CONTAINER(menu), item);
249 item = gtk_image_menu_item_new_with_mnemonic(_("H_ide Sidebar"));
250 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
251 gtk_image_new_from_stock("gtk-close", GTK_ICON_SIZE_MENU));
252 gtk_widget_show(item);
253 gtk_container_add(GTK_CONTAINER(menu), item);
254 g_signal_connect_swapped((gpointer) item, "activate",
255 G_CALLBACK(p_keybindings->send_command),
256 GINT_TO_POINTER(GEANY_KEYS_MENU_SIDEBAR));
258 return menu;
261 static void
262 update_popup_menu(GtkWidget * popup_menu)
264 gint idx;
265 gboolean cur_file_exists;
266 gboolean badd_file;
268 idx = p_document->get_cur_idx();
270 cur_file_exists = DOC_IDX_VALID(idx) &&
271 doc_list[idx].file_name != NULL && g_path_is_absolute(doc_list[idx].file_name);
273 badd_file = (g_current_project ? TRUE : FALSE) &&
274 cur_file_exists &&
275 !g_hash_table_lookup(g_current_project->tags, doc_list[idx].file_name);
277 GtkTreeSelection *treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view));
278 gboolean have_sel = (gtk_tree_selection_count_selected_rows(treesel) > 0);
280 gtk_widget_set_sensitive(popup_items.new_project, TRUE);
281 gtk_widget_set_sensitive(popup_items.delete_project, g_current_project ? TRUE : FALSE);
283 gtk_widget_set_sensitive(popup_items.add_file, badd_file);
284 gtk_widget_set_sensitive(popup_items.remove_files, have_sel);
286 gtk_widget_set_sensitive(popup_items.preferences, g_current_project ? TRUE : FALSE);
288 gtk_widget_set_sensitive(popup_items.find_in_files, g_current_project ? TRUE : FALSE);
291 // delay updating popup menu until the selection has been set
292 static gboolean
293 on_button_release(GtkWidget * widget, GdkEventButton * event, gpointer user_data)
295 if (event->button == 3)
297 static GtkWidget *popup_menu = NULL;
299 if (popup_menu == NULL)
300 popup_menu = create_popup_menu();
302 update_popup_menu(popup_menu);
304 gtk_menu_popup(GTK_MENU(popup_menu), NULL, NULL, NULL, NULL,
305 event->button, event->time);
307 return FALSE;
310 static void
311 prepare_file_view()
313 GtkCellRenderer *text_renderer;
314 GtkTreeViewColumn *column;
315 GtkTreeSelection *select;
316 PangoFontDescription *pfd;
318 file_store = gtk_list_store_new(FILEVIEW_N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
320 gtk_tree_view_set_model(GTK_TREE_VIEW(file_view), GTK_TREE_MODEL(file_store));
322 text_renderer = gtk_cell_renderer_text_new();
323 column = gtk_tree_view_column_new();
324 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
325 gtk_tree_view_column_set_attributes(column, text_renderer, "text", FILEVIEW_COLUMN_NAME,
326 NULL);
327 gtk_tree_view_append_column(GTK_TREE_VIEW(file_view), column);
328 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(file_view), FALSE);
330 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(file_view), TRUE);
331 gtk_tree_view_set_search_column(GTK_TREE_VIEW(file_view), FILEVIEW_COLUMN_NAME);
333 pfd = pango_font_description_from_string(prefs->tagbar_font);
334 gtk_widget_modify_font(file_view, pfd);
335 pango_font_description_free(pfd);
337 // selection handling
338 select = gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view));
339 gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
341 g_signal_connect(G_OBJECT(file_view), "button-release-event",
342 G_CALLBACK(on_button_release), NULL);
343 g_signal_connect(G_OBJECT(file_view), "button-press-event",
344 G_CALLBACK(on_button_press), NULL);
346 g_signal_connect(G_OBJECT(file_view), "key-press-event", G_CALLBACK(on_key_press), NULL);
349 void
350 sidebar_clear()
352 gtk_list_store_clear(file_store);
355 gint mycmp(const gchar *a, const gchar *b)
357 const gchar *p1 = a;
358 const gchar *p2 = b;
360 gint cnt1 = 0;
361 gint cnt2 = 0;
363 while(*p1)
365 if (*p1 == G_DIR_SEPARATOR_S[0])
366 cnt1++;
367 p1++;
370 while(*p2)
372 if (*p2 == G_DIR_SEPARATOR_S[0])
373 cnt2++;
374 p2++;
377 if (cnt1 != cnt2)
378 return cnt2 - cnt1;
380 p1 = a;
381 p2 = b;
383 while(*p1 && *p2)
385 if (*p1 != *p2)
387 if (*p1 == G_DIR_SEPARATOR_S[0])
388 return -1;
389 else if (*p2 == G_DIR_SEPARATOR_S[0])
390 return 1;
391 return *p1 - *p2;
393 p1++;
394 p2++;
396 if (*p1 == 0 && *p2 == 0)
397 return 0;
398 else if (*p1)
399 return 1;
400 return -1;
403 static void
404 add_item(gpointer name, gpointer value, gpointer user_data)
406 gchar *item;
407 GSList **lst = (GSList **) user_data;
409 item = get_relative_path(g_current_project->path, name);
410 *lst = g_slist_prepend(*lst, item);
413 // recreate the tree model from current_dir.
414 void
415 sidebar_refresh()
417 GtkTreeIter iter;
418 GSList *lst = NULL;
419 GSList *tmp;
421 sidebar_clear();
423 if (!g_current_project)
424 return;
426 g_hash_table_foreach(g_current_project->tags, add_item, &lst);
427 lst = g_slist_sort(lst, (GCompareFunc) strcmp);
428 for (tmp = lst; tmp != NULL; tmp = g_slist_next(tmp))
430 gtk_list_store_append(file_store, &iter);
431 gtk_list_store_set(file_store, &iter, FILEVIEW_COLUMN_NAME, tmp->data, -1);
433 g_slist_foreach(lst, (GFunc) g_free, NULL);
434 g_slist_free(lst);
437 void
438 create_sidebar()
440 GtkWidget *scrollwin, *toolbar;
442 file_view_vbox = gtk_vbox_new(FALSE, 0);
443 toolbar = make_toolbar();
444 gtk_box_pack_start(GTK_BOX(file_view_vbox), toolbar, FALSE, FALSE, 0);
446 file_view = gtk_tree_view_new();
447 prepare_file_view();
449 scrollwin = gtk_scrolled_window_new(NULL, NULL);
450 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
451 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
452 gtk_container_add(GTK_CONTAINER(scrollwin), file_view);
453 gtk_container_add(GTK_CONTAINER(file_view_vbox), scrollwin);
455 gtk_widget_show_all(file_view_vbox);
456 gtk_notebook_append_page(GTK_NOTEBOOK(app->treeview_notebook), file_view_vbox,
457 gtk_label_new(_("Project")));
460 void
461 destroy_sidebar()
463 gtk_widget_destroy(file_view_vbox);