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 #include <sys/time.h>
21 #include <gdk/gdkkeysyms.h>
22 #include <glib/gstdio.h>
23 
24 #ifdef HAVE_CONFIG_H
25 	#include "config.h" /* for the gettext domain */
26 #endif
27 #include <geanyplugin.h>
28 
29 #include "geanyprj.h"
30 
31 static struct
32 {
33 	GtkWidget *tools_item;
34 
35 	GtkWidget *new_project;
36 	GtkWidget *delete_project;
37 
38 	GtkWidget *add_file;
39 
40 	GtkWidget *preferences;
41 
42 	GtkWidget *find_in_files;
43 } menu_items;
44 
45 /* simple struct to keep references to the elements of the properties dialog */
46 typedef struct _PropertyDialogElements
47 {
48 	GtkWidget *dialog;
49 	GtkWidget *name;
50 	GtkWidget *description;
51 	GtkWidget *file_name;
52 	GtkWidget *base_path;
53 	GtkWidget *make_in_base_path;
54 	GtkWidget *run_cmd;
55 	GtkWidget *regenerate;
56 	GtkWidget *type;
57 	GtkWidget *patterns;
58 } PropertyDialogElements;
59 
60 
build_properties_dialog(gboolean properties)61 static PropertyDialogElements *build_properties_dialog(gboolean properties)
62 {
63 	GtkWidget *vbox;
64 	GtkWidget *table;
65 	GtkWidget *image;
66 	GtkWidget *button;
67 	GtkWidget *bbox;
68 	GtkWidget *label;
69 	PropertyDialogElements *e;
70 	gchar *dir = NULL;
71 	gchar *basename = NULL;
72 	gint i;
73 	GeanyDocument *doc;
74 
75 	doc = document_get_current();
76 
77 	if (doc && doc->file_name != NULL && g_path_is_absolute(doc->file_name))
78 	{
79 		dir = g_path_get_dirname(doc->file_name);
80 	}
81 	else
82 	{
83 		dir = g_strdup("");
84 	}
85 	basename = g_path_get_basename(dir);
86 
87 	e = g_new0(PropertyDialogElements, 1);
88 
89 	if (properties)
90 	{
91 		e->dialog =
92 			gtk_dialog_new_with_buttons(_("Project Preferences"),
93 						    GTK_WINDOW(geany->main_widgets->window),
94 						    GTK_DIALOG_DESTROY_WITH_PARENT,
95 						    GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
96 						    GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
97 	}
98 	else
99 	{
100 		e->dialog =
101 			gtk_dialog_new_with_buttons(_("New Project"),
102 						    GTK_WINDOW(geany->main_widgets->window),
103 						    GTK_DIALOG_DESTROY_WITH_PARENT,
104 						    GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
105 
106 		gtk_widget_set_name(e->dialog, "GeanyDialogProject");
107 		bbox = gtk_hbox_new(FALSE, 0);
108 		button = gtk_button_new();
109 		image = gtk_image_new_from_stock("gtk-new", GTK_ICON_SIZE_BUTTON);
110 		label = gtk_label_new_with_mnemonic(_("C_reate"));
111 		gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 3);
112 		gtk_box_pack_start(GTK_BOX(bbox), label, FALSE, FALSE, 3);
113 		gtk_container_add(GTK_CONTAINER(button), bbox);
114 		gtk_dialog_add_action_widget(GTK_DIALOG(e->dialog), button, GTK_RESPONSE_OK);
115 	}
116 
117 	vbox = ui_dialog_vbox_new(GTK_DIALOG(e->dialog));
118 
119 	table = gtk_table_new(5, 2, FALSE);
120 	gtk_table_set_row_spacings(GTK_TABLE(table), 5);
121 	gtk_table_set_col_spacings(GTK_TABLE(table), 10);
122 
123 	label = gtk_label_new(_("Name:"));
124 	gtk_misc_set_alignment(GTK_MISC(label), 1, 0);
125 
126 	e->name = gtk_entry_new();
127 	gtk_entry_set_max_length(GTK_ENTRY(e->name), MAX_NAME_LEN);
128 	gtk_entry_set_text(GTK_ENTRY(e->name), basename);
129 
130 	ui_table_add_row(GTK_TABLE(table), 0, label, e->name, NULL);
131 
132 	label = gtk_label_new(_("Location:"));
133 	gtk_misc_set_alignment(GTK_MISC(label), 1, 0);
134 	e->file_name = gtk_entry_new();
135 	gtk_entry_set_width_chars(GTK_ENTRY(e->file_name), 30);
136 
137 	if (properties)
138 	{
139 		gtk_widget_set_sensitive(e->file_name, FALSE);
140 		ui_table_add_row(GTK_TABLE(table), 1, label, e->file_name, NULL);
141 	}
142 	else
143 	{
144 		button = gtk_button_new();
145 		image = gtk_image_new_from_stock("gtk-open", GTK_ICON_SIZE_BUTTON);
146 		gtk_container_add(GTK_CONTAINER(button), image);
147 		bbox = ui_path_box_new(_("Choose Project Location"),
148 				       GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
149 				       GTK_ENTRY(e->file_name));
150 		gtk_entry_set_text(GTK_ENTRY(e->file_name), dir);
151 		ui_table_add_row(GTK_TABLE(table), 1, label, bbox, NULL);
152 	}
153 
154 
155 	label = gtk_label_new(_("Base path:"));
156 	gtk_misc_set_alignment(GTK_MISC(label), 1, 0);
157 
158 	e->base_path = gtk_entry_new();
159 	gtk_widget_set_tooltip_text(e->base_path,
160 			     _("Base directory of all files that make up the project. "
161 			       "This can be a new path, or an existing directory tree. "
162 			       "You can use paths relative to the project filename."));
163 	bbox = ui_path_box_new(_("Choose Project Base Path"),
164 			       GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_ENTRY(e->base_path));
165 	gtk_entry_set_text(GTK_ENTRY(e->base_path), dir);
166 
167 	ui_table_add_row(GTK_TABLE(table), 2, label, bbox, NULL);
168 
169 	label = gtk_label_new("");
170 	e->regenerate = gtk_check_button_new_with_label(_("Generate file list on load"));
171 	gtk_widget_set_tooltip_text(e->regenerate,
172 			     _("Automatically add files that match project type on project load "
173 			       "automatically. You can't manually add/remove files if "
174 			       "you checked this option, since your modification will be lost on "
175 			       "on next project load"));
176 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e->regenerate), TRUE);
177 	ui_table_add_row(GTK_TABLE(table), 3, label, e->regenerate, NULL);
178 
179 
180 	label = gtk_label_new(_("Type:"));
181 	gtk_misc_set_alignment(GTK_MISC(label), 1, 0);
182 
183 	e->type = gtk_combo_box_text_new();
184 	for (i = 0; i < NEW_PROJECT_TYPE_SIZE; i++)
185 		gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(e->type), project_type_string[i]);
186 	gtk_combo_box_set_active(GTK_COMBO_BOX(e->type), 0);
187 
188 	ui_table_add_row(GTK_TABLE(table), 4, label, e->type, NULL);
189 
190 	gtk_container_add(GTK_CONTAINER(vbox), table);
191 	g_free(dir);
192 	g_free(basename);
193 
194 	return e;
195 }
196 
197 
on_new_project(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)198 void on_new_project(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
199 {
200 	PropertyDialogElements *e;
201 	gint response;
202 
203 	e = build_properties_dialog(FALSE);
204 	gtk_widget_show_all(e->dialog);
205 
206       retry:
207 	response = gtk_dialog_run(GTK_DIALOG(e->dialog));
208 	if (response == GTK_RESPONSE_OK)
209 	{
210 		gchar *path;
211 		struct GeanyPrj *prj;
212 
213 		path = g_build_filename(gtk_entry_get_text(GTK_ENTRY(e->file_name)), ".geanyprj",
214 					NULL);
215 
216 		if (g_file_test(path, G_FILE_TEST_EXISTS))
217 		{
218 			ui_set_statusbar(TRUE, _("Project file \"%s\" already exists"), path);
219 			g_free(path);
220 			goto retry;
221 		}
222 		prj = geany_project_new();
223 
224 		geany_project_set_path(prj, path);
225 		geany_project_set_base_path(prj, gtk_entry_get_text(GTK_ENTRY(e->base_path)));
226 		geany_project_set_name(prj, gtk_entry_get_text(GTK_ENTRY(e->name)));
227 		geany_project_set_description(prj, "");
228 		geany_project_set_run_cmd(prj, "");
229 		geany_project_set_type_int(prj, gtk_combo_box_get_active(GTK_COMBO_BOX(e->type)));
230 		geany_project_set_regenerate(prj,
231 					     gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
232 									  (e->regenerate)));
233 
234 		geany_project_regenerate_file_list(prj);
235 
236 		geany_project_save(prj);
237 		geany_project_free(prj);
238 		document_open_file(path, FALSE, NULL, NULL);
239 	}
240 
241 	gtk_widget_destroy(e->dialog);
242 	g_free(e);
243 }
244 
245 
on_preferences(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)246 void on_preferences(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
247 {
248 	PropertyDialogElements *e;
249 	gint response;
250 	gchar *project_dir;
251 
252 	e = build_properties_dialog(TRUE);
253 
254 	project_dir = g_path_get_dirname(g_current_project->path);
255 	gtk_entry_set_text(GTK_ENTRY(e->file_name), project_dir);
256 	gtk_entry_set_text(GTK_ENTRY(e->name), g_current_project->name);
257 	gtk_entry_set_text(GTK_ENTRY(e->base_path), g_current_project->base_path);
258 	gtk_combo_box_set_active(GTK_COMBO_BOX(e->type), g_current_project->type);
259 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e->regenerate),
260 				     g_current_project->regenerate);
261 
262 	gtk_widget_show_all(e->dialog);
263 
264 	response = gtk_dialog_run(GTK_DIALOG(e->dialog));
265 	if (response == GTK_RESPONSE_OK)
266 	{
267 		geany_project_set_base_path(g_current_project,
268 					    gtk_entry_get_text(GTK_ENTRY(e->base_path)));
269 		geany_project_set_name(g_current_project, gtk_entry_get_text(GTK_ENTRY(e->name)));
270 		geany_project_set_description(g_current_project, "");
271 		geany_project_set_run_cmd(g_current_project, "");
272 		geany_project_set_type_int(g_current_project,
273 					   gtk_combo_box_get_active(GTK_COMBO_BOX(e->type)));
274 		geany_project_set_regenerate(g_current_project,
275 					     gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
276 									  (e->regenerate)));
277 		geany_project_save(g_current_project);
278 
279 		if (g_current_project->regenerate)
280 		{
281 			geany_project_regenerate_file_list(g_current_project);
282 		}
283 		sidebar_refresh();
284 	}
285 
286 	gtk_widget_destroy(e->dialog);
287 	g_free(e);
288 	g_free(project_dir);
289 }
290 
291 
on_delete_project(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)292 void on_delete_project(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
293 {
294 	gchar *path;
295 	if (!g_current_project)
296 		return;
297 
298 	if (dialogs_show_question("Do you really wish to delete current project:\n%s?",
299 				  g_current_project->name))
300 	{
301 		path = utils_get_locale_from_utf8(g_current_project->path);
302 		xproject_close(FALSE);
303 		g_unlink(path);
304 		g_free(path);
305 	}
306 }
307 
308 
on_add_file(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)309 void on_add_file(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
310 {
311 	GeanyDocument *doc;
312 
313 	doc = document_get_current();
314 	g_return_if_fail(doc && doc->file_name != NULL && g_path_is_absolute(doc->file_name));
315 
316 	if (!g_current_project)
317 		return;
318 
319 	xproject_add_file(doc->file_name);
320 }
321 
322 
on_find_in_project(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer user_data)323 void on_find_in_project(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data)
324 {
325 	gchar *dir;
326 	if (!g_current_project)
327 		return;
328 
329 	dir = g_path_get_dirname(g_current_project->path);
330 	search_show_find_in_files_dialog(dir);
331 	g_free(dir);
332 }
333 
334 
update_menu_items(void)335 static void update_menu_items(void)
336 {
337 	gboolean cur_file_exists;
338 	gboolean badd_file;
339 	GeanyDocument *doc;
340 
341 	doc = document_get_current();
342 	g_return_if_fail(doc != NULL && doc->file_name != NULL);
343 
344 	cur_file_exists = doc && doc->file_name != NULL && g_path_is_absolute(doc->file_name);
345 
346 	badd_file = (g_current_project ? TRUE : FALSE) &&
347 		!g_current_project->regenerate &&
348 		cur_file_exists && !g_hash_table_lookup(g_current_project->tags, doc->file_name);
349 
350 	gtk_widget_set_sensitive(menu_items.new_project, TRUE);
351 	gtk_widget_set_sensitive(menu_items.delete_project, g_current_project ? TRUE : FALSE);
352 
353 	gtk_widget_set_sensitive(menu_items.add_file, badd_file);
354 
355 	gtk_widget_set_sensitive(menu_items.preferences, g_current_project ? TRUE : FALSE);
356 
357 	gtk_widget_set_sensitive(menu_items.find_in_files, g_current_project ? TRUE : FALSE);
358 }
359 
360 
tools_menu_init(void)361 void tools_menu_init(void)
362 {
363 	GtkWidget *item, *image;
364 
365 	GtkWidget *menu_prj_menu = NULL;
366 
367 	menu_items.tools_item = gtk_image_menu_item_new_with_mnemonic(_("_Project"));
368 	gtk_container_add(GTK_CONTAINER(geany->main_widgets->tools_menu), menu_items.tools_item);
369 
370 	g_signal_connect((gpointer) menu_items.tools_item, "activate", G_CALLBACK(update_menu_items), NULL);
371 
372 	menu_prj_menu = gtk_menu_new();
373 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_items.tools_item), menu_prj_menu);
374 
375 	image = gtk_image_new_from_stock(GTK_STOCK_NEW, GTK_ICON_SIZE_MENU);
376 	item = gtk_image_menu_item_new_with_mnemonic(_("New Project"));
377 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
378 	gtk_widget_show(item);
379 	gtk_container_add(GTK_CONTAINER(menu_prj_menu), item);
380 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_new_project), NULL);
381 	menu_items.new_project = item;
382 
383 	image = gtk_image_new_from_stock(GTK_STOCK_DELETE, GTK_ICON_SIZE_MENU);
384 	gtk_widget_show(image);
385 	item = gtk_image_menu_item_new_with_mnemonic(_("Delete Project"));
386 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
387 	gtk_widget_show(item);
388 	gtk_container_add(GTK_CONTAINER(menu_prj_menu), item);
389 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_delete_project), NULL);
390 	menu_items.delete_project = item;
391 
392 	gtk_container_add(GTK_CONTAINER(menu_prj_menu), gtk_separator_menu_item_new());
393 
394 	image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
395 	gtk_widget_show(image);
396 	item = gtk_image_menu_item_new_with_mnemonic(_("Add File"));
397 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
398 	gtk_widget_show(item);
399 	gtk_container_add(GTK_CONTAINER(menu_prj_menu), item);
400 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_add_file), NULL);
401 	menu_items.add_file = item;
402 
403 	gtk_container_add(GTK_CONTAINER(menu_prj_menu), gtk_separator_menu_item_new());
404 
405 	image = gtk_image_new_from_stock(GTK_STOCK_PREFERENCES, GTK_ICON_SIZE_MENU);
406 	gtk_widget_show(image);
407 	item = gtk_image_menu_item_new_with_mnemonic(_("Preferences"));
408 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
409 	gtk_widget_show(item);
410 	gtk_container_add(GTK_CONTAINER(menu_prj_menu), item);
411 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_preferences), NULL);
412 	menu_items.preferences = item;
413 
414 	gtk_container_add(GTK_CONTAINER(menu_prj_menu), gtk_separator_menu_item_new());
415 
416 	image = gtk_image_new_from_stock(GTK_STOCK_FIND, GTK_ICON_SIZE_MENU);
417 	gtk_widget_show(image);
418 	item = gtk_image_menu_item_new_with_mnemonic(_("Find in Project"));
419 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
420 	gtk_widget_show(item);
421 	gtk_container_add(GTK_CONTAINER(menu_prj_menu), item);
422 	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_find_in_project), NULL);
423 	menu_items.find_in_files = item;
424 
425 	gtk_widget_show_all(menu_items.tools_item);
426 
427 	ui_add_document_sensitive(menu_items.tools_item);
428 }
429 
430 
tools_menu_uninit(void)431 void tools_menu_uninit(void)
432 {
433 	gtk_widget_destroy(menu_items.tools_item);
434 }
435