/*
 *      build.c - this file is part of Geany, a fast and lightweight IDE
 *
 *      Copyright 2006 Enrico Troeger <enrico.troeger@uvena.de>
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $Id$
 */


#include "geany.h"
#include "build.h"

#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

#ifdef G_OS_UNIX
# include <sys/types.h>
# include <sys/wait.h>
#endif

#include "support.h"
#include "utils.h"
#include "ui_utils.h"
#include "dialogs.h"
#include "msgwindow.h"
#include "document.h"
#include "keybindings.h"


BuildInfo build_info = {NULL, GEANY_FILETYPES_ALL, NULL};


static gboolean build_iofunc(GIOChannel *ioc, GIOCondition cond, gpointer data);
static gboolean build_create_shellscript(const gint idx, const gchar *fname, const gchar *cmd);
static GPid build_spawn_cmd(gint idx, gchar **cmd);

static void
on_make_target_dialog_response         (GtkDialog *dialog,
                                        gint response,
                                        gpointer user_data);

static void
on_make_target_entry_activate          (GtkEntry        *entry,
                                        gpointer         user_data);



void build_finalize()
{
	g_free(build_info.dir);
	g_free(build_info.custom_target);
}


GPid build_compile_tex_file(gint idx, gint mode)
{
	gchar **argv;

	if (idx < 0 || doc_list[idx].file_name == NULL) return (GPid) 1;

	argv = g_new0(gchar*, 2);
	argv[0] = (mode == 0) ? g_strdup(doc_list[idx].file_type->programs->compiler) :
							g_strdup(doc_list[idx].file_type->programs->linker);
	argv[1] = NULL;

	return build_spawn_cmd(idx, argv);
}


GPid build_view_tex_file(gint idx, gint mode)
{
	gchar **argv;
	gchar  *executable = NULL;
	gchar  *view_file = NULL;
	gchar  *locale_filename = NULL;
	gchar  *cmd_string = NULL;
	gchar  *locale_cmd_string = NULL;
	GError *error = NULL;
	GPid 	child_pid;
	struct stat st;

	if (idx < 0 || doc_list[idx].file_name == NULL) return (GPid) 1;

	executable = utils_remove_ext_from_filename(doc_list[idx].file_name);
	view_file = g_strconcat(executable, (mode == 0) ? ".dvi" : ".pdf", NULL);

	// try convert in locale for stat()
	locale_filename = utils_get_locale_from_utf8(view_file);

	// check wether view_file exists
	if (stat(locale_filename, &st) != 0)
	{
		msgwin_status_add(_("Failed to view %s (make sure it is already compiled)"), view_file);
		g_free(executable);
		g_free(view_file);
		g_free(locale_filename);
		return (GPid) 1;
	}

	// replace %f and %e in the run_cmd string
	cmd_string = g_strdup((mode == 0) ?	g_strdup(doc_list[idx].file_type->programs->run_cmd) :
										g_strdup(doc_list[idx].file_type->programs->run_cmd2));
	cmd_string = utils_str_replace(cmd_string, "%f", view_file);
	cmd_string = utils_str_replace(cmd_string, "%e", executable);

	// try convert in locale
	locale_cmd_string = utils_get_locale_from_utf8(cmd_string);

#ifdef G_OS_WIN32
	argv = NULL;
	child_pid = (GPid) 0;

	if (! g_spawn_command_line_async(locale_cmd_string, &error))
#else
	argv = g_new0(gchar *, 4);
	argv[0] = g_strdup("/bin/sh");
	argv[1] = g_strdup("-c");
	argv[2] = locale_cmd_string;
	argv[3] = NULL;

	if (! g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
						NULL, NULL, &child_pid, NULL, NULL, NULL, &error))
#endif
	{
		geany_debug("g_spawn_async_with_pipes() failed: %s", error->message);
		msgwin_status_add(_("Process failed (%s)"), error->message);

		g_free(view_file);
		g_free(executable);
		g_free(locale_filename);
		g_free(cmd_string);
#ifdef G_OS_WIN32
		g_free(locale_cmd_string);
#endif
		g_strfreev(argv);
		g_error_free(error);
		error = NULL;
		return (GPid) 0;
	}

	g_free(view_file);
	g_free(executable);
	g_free(locale_filename);
	g_free(cmd_string);
	g_strfreev(argv);

	return child_pid;
}


static gchar *get_object_filename(gint idx)
{
	gchar *locale_filename, *short_file, *noext, *object_file;

	if (doc_list[idx].file_name == NULL) return NULL;

	locale_filename = utils_get_locale_from_utf8(doc_list[idx].file_name);

	short_file = g_path_get_basename(locale_filename);
	g_free(locale_filename);

	noext = utils_remove_ext_from_filename(short_file);
	g_free(short_file);

	object_file = g_strdup_printf("%s.o", noext);
	g_free(noext);
	
	return object_file;
}


GPid build_make_file(gint idx, gint build_opts)
{
	gchar **argv;

	if (idx < 0 || doc_list[idx].file_name == NULL) return (GPid) 1;

	argv = g_new0(gchar*, 3);
	argv[0] = g_strdup(app->tools_make_cmd);
	
	if (build_opts == GBO_MAKE_OBJECT)
	{
		argv[1] = get_object_filename(idx);
		argv[2] = NULL;
	}
	else if (build_opts == GBO_MAKE_CUSTOM && build_info.custom_target)
	{	//cust-target
		argv[1] = g_strdup(build_info.custom_target);
		argv[2] = NULL;
	}
	else	// GBO_MAKE_ALL
	{
		argv[1] = g_strdup("all");
		argv[2] = NULL;
	}

	return build_spawn_cmd(idx, argv);
}


GPid build_compile_file(gint idx)
{
	gchar **argv;

	if (idx < 0 || doc_list[idx].file_name == NULL) return (GPid) 1;

	argv = g_new0(gchar *, 2);
	argv[0] = g_strdup(doc_list[idx].file_type->programs->compiler);
	argv[1] = NULL;

	return build_spawn_cmd(idx, argv);
}


GPid build_link_file(gint idx)
{
	gchar **argv;
	gchar *executable = NULL;
	gchar *object_file, *locale_filename;
	struct stat st, st2;

	if (idx < 0 || doc_list[idx].file_name == NULL) return (GPid) 1;

	locale_filename = utils_get_locale_from_utf8(doc_list[idx].file_name);

	executable = utils_remove_ext_from_filename(locale_filename);
	object_file = g_strdup_printf("%s.o", executable);

	// check wether object file (file.o) exists
	if (stat(object_file, &st) == 0)
	{	// check wether src is newer than object file
		if (stat(locale_filename, &st2) == 0)
		{
			if (st2.st_mtime > st.st_mtime)
			{
				// set object_file to NULL, so the source file will be used for linking,
				// more precisely then we compile and link instead of just linking
				g_free(object_file);
				object_file = NULL;
			}
		}
		else
		{
			dialogs_show_error("Something very strange is occured, could not stat %s (%s)",
					doc_list[idx].file_name, strerror(errno));
		}
	}

	if (doc_list[idx].file_type->id == GEANY_FILETYPES_D)
	{	// the dmd compiler needs -of instead of -o and it accepts no whitespace after -of
		gchar *tmp = g_path_get_basename(executable);

		argv = g_new0(gchar *, 3);
		argv[0] = g_strdup(doc_list[idx].file_type->programs->linker);
		argv[1] = g_strconcat("-of", tmp, NULL);
		argv[2] = NULL;

		g_free(tmp);
	}
	else
	{
		argv = g_new0(gchar *, 4);
		argv[0] = g_strdup(doc_list[idx].file_type->programs->linker);
		argv[1] = g_strdup("-o");
		argv[2] = g_path_get_basename(executable);
		argv[3] = NULL;
	}

	g_free(executable);
	g_free(object_file);
	g_free(locale_filename);

	return build_spawn_cmd(idx, argv);
}


static GPid build_spawn_cmd(gint idx, gchar **cmd)
{
	GError  *error = NULL;
	gchar **argv;
	gchar	*working_dir;
	gchar	*utf8_working_dir;
	gchar	*cmd_string;
	gchar	*utf8_cmd_string;
	gchar	*locale_filename;
	gchar	*executable;
	gchar	*tmp;
	GPid     child_pid;
	gint     stdout_fd;
	gint     stderr_fd;

	g_return_val_if_fail(idx >= 0 && doc_list[idx].is_valid, (GPid) 1);

	document_clear_indicators(idx);

	cmd_string = g_strjoinv(" ", cmd);
	g_strfreev(cmd);

	locale_filename = utils_get_locale_from_utf8(doc_list[idx].file_name);

	executable = utils_remove_ext_from_filename(locale_filename);

	// replace %f and %e in the command string
	tmp = g_path_get_basename(locale_filename);
	cmd_string = utils_str_replace(cmd_string, "%f", tmp);
	g_free(tmp);
	tmp = g_path_get_basename(executable);
	cmd_string = utils_str_replace(cmd_string, "%e", tmp);
	g_free(tmp);
	g_free(executable);

	utf8_cmd_string = utils_get_utf8_from_locale(cmd_string);

	argv = g_new0(gchar *, 4);
	argv[0] = g_strdup("/bin/sh");
	argv[1] = g_strdup("-c");
	argv[2] = cmd_string;
	argv[3] = NULL;

	working_dir = g_path_get_dirname(locale_filename);
	utf8_working_dir = g_path_get_dirname(doc_list[idx].file_name);
	gtk_list_store_clear(msgwindow.store_compiler);
	msgwin_compiler_add(COLOR_BLUE, FALSE, _("%s (in directory: %s)"), utf8_cmd_string, utf8_working_dir);
	gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_COMPILER);

	// set the build info for the message window
	{
		filetype *ft = doc_list[idx].file_type;
		guint ft_id = (ft == NULL) ? filetypes[GEANY_FILETYPES_ALL]->id : ft->id;

		g_free(build_info.dir);
		build_info.dir = g_strdup(working_dir);
		build_info.file_type_id = ft_id;
	}

	if (! g_spawn_async_with_pipes(working_dir, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
						NULL, NULL, &child_pid, NULL, &stdout_fd, &stderr_fd, &error))
	{
		geany_debug("g_spawn_async_with_pipes() failed: %s", error->message);
		msgwin_status_add(_("Process failed (%s)"), error->message);
		g_strfreev(argv);
		g_error_free(error);
		g_free(working_dir);
		g_free(utf8_working_dir);
		g_free(utf8_cmd_string);
		g_free(locale_filename);
		error = NULL;
		return (GPid) 0;
	}

	// use GIOChannels to monitor stdout and stderr
	utils_set_up_io_channel(stdout_fd, G_IO_IN|G_IO_PRI|G_IO_ERR|G_IO_HUP|G_IO_NVAL,
		build_iofunc, GINT_TO_POINTER(0));
	utils_set_up_io_channel(stderr_fd, G_IO_IN|G_IO_PRI|G_IO_ERR|G_IO_HUP|G_IO_NVAL,
		build_iofunc, GINT_TO_POINTER(1));

	g_strfreev(argv);
	g_free(utf8_working_dir);
	g_free(utf8_cmd_string);
	g_free(working_dir);
	g_free(locale_filename);

	return child_pid;
}


GPid build_run_cmd(gint idx)
{
	GPid	 child_pid;
	GPid	 result_id;	// either child_pid or error id.
	GError	*error = NULL;
	gchar  **argv = NULL;
	gchar  **term_argv = NULL;
	gchar	*working_dir = NULL;
	gchar	*long_executable = NULL;
	gchar	*check_executable = NULL;
	gchar	*utf8_check_executable = NULL;
	gchar	*locale_filename = NULL;
	gchar	*locale_term_cmd = NULL;
	gchar	*cmd = NULL;
	gchar	*tmp = NULL;
	gchar	*executable = NULL;
	gchar	*script_name;
	guint    term_argv_len, i;
	struct stat st;

	if (idx < 0 || doc_list[idx].file_name == NULL) return (GPid) 1;

#ifdef G_OS_WIN32
	script_name = g_strdup("./geany_run_script.bat");
#else
	script_name = g_strdup("./geany_run_script.sh");
#endif

	locale_filename = utils_get_locale_from_utf8(doc_list[idx].file_name);

	long_executable = utils_remove_ext_from_filename(locale_filename);
#ifdef G_OS_WIN32
	long_executable = g_strconcat(long_executable, ".exe", NULL);
#endif

	// only check for existing executable, if executable is required by %e
	if (strstr(doc_list[idx].file_type->programs->run_cmd, "%e") != NULL)
	{
		// add .class extension for JAVA source files (only for stat)
		if (doc_list[idx].file_type->id == GEANY_FILETYPES_JAVA)
		{
#ifdef G_OS_WIN32
			// there is already the extension .exe, so first remove it and then add .class
			check_executable = utils_remove_ext_from_filename(long_executable);
			check_executable = g_strconcat(check_executable, ".class", NULL);
#else
			check_executable = g_strconcat(long_executable, ".class", NULL);
#endif
		}
		else
			check_executable = g_strdup(long_executable);

		// check whether executable exists
		if (stat(check_executable, &st) != 0)
		{
#ifndef G_OS_WIN32
			utf8_check_executable = g_strdup(check_executable);
#else
			utf8_check_executable = utils_remove_ext_from_filename(doc_list[idx].file_name);
			msgwin_status_add(_("Failed to execute %s (make sure it is already built)"),
														utf8_check_executable);
#endif
			result_id = (GPid) 1;
			goto free_strings;
		}
	}

	/* get the terminal path */
	locale_term_cmd = utils_get_locale_from_utf8(app->tools_term_cmd);
	// split the term_cmd, so arguments will work too
	term_argv = g_strsplit(locale_term_cmd, " ", -1);
	term_argv_len = g_strv_length(term_argv);

	// check that terminal exists (to prevent misleading error messages)
	if (term_argv[0] != NULL)
	{
		tmp = term_argv[0];
		// g_find_program_in_path checks tmp exists and is executable
		term_argv[0] = g_find_program_in_path(tmp);
		g_free(tmp);
	}
	if (term_argv[0] == NULL)
	{
		msgwin_status_add(
			_("Could not find terminal '%s' "
				"(check path for Terminal tool setting in Preferences)"), app->tools_term_cmd);
		result_id = (GPid) 1;
		goto free_strings;
	}

	executable = g_path_get_basename(long_executable);

	working_dir = g_path_get_dirname(locale_filename);
	if (chdir(working_dir) != 0)
	{
		gchar *utf8_working_dir = NULL;
		utf8_working_dir = utils_get_utf8_from_locale(working_dir);

		msgwin_status_add(_("Failed to change the working directory to %s"), working_dir);
		result_id = (GPid) 1;	// return 1, to prevent error handling of the caller
		g_free(utf8_working_dir);
		goto free_strings;
	}

	// replace %f and %e in the run_cmd string
	cmd = g_strdup(doc_list[idx].file_type->programs->run_cmd);
	tmp = g_path_get_basename(locale_filename);
	cmd = utils_str_replace(cmd, "%f", tmp);
	g_free(tmp);
	cmd = utils_str_replace(cmd, "%e", executable);

	// write a little shellscript to call the executable (similar to anjuta_launcher but "internal")
	// (script_name should be ok in UTF8 without converting in locale because it contains no umlauts)
	if (! build_create_shellscript(idx, script_name, cmd))
	{
		utf8_check_executable = utils_remove_ext_from_filename(doc_list[idx].file_name);
		msgwin_status_add(_("Failed to execute %s (start-script could not be created)"),
													utf8_check_executable);
		result_id = (GPid) 1;
		goto free_strings;
	}

	argv = g_new0(gchar *, term_argv_len + 3);
	for (i = 0; i < term_argv_len; i++)
	{
		argv[i] = g_strdup(term_argv[i]);
	}
#ifdef G_OS_WIN32
	// command line arguments for cmd.exe
	argv[term_argv_len   ]  = g_strdup("/Q /C");
	argv[term_argv_len + 1] = g_path_get_basename(script_name);
#else
	argv[term_argv_len   ]  = g_strdup("-e");
	argv[term_argv_len + 1] = g_strdup(script_name);
#endif
	argv[term_argv_len + 2] = NULL;

	if (! g_spawn_async_with_pipes(working_dir, argv, NULL, 0,
						NULL, NULL, &child_pid, NULL, NULL, NULL, &error))
	{
		geany_debug("g_spawn_async_with_pipes() failed: %s", error->message);
		msgwin_status_add(_("Process failed (%s)"), error->message);
		unlink(script_name);
		g_error_free(error);
		error = NULL;
		result_id = (GPid) 0;
		goto free_strings;
	}

	result_id = child_pid; // g_spawn was successful, result is child process id

	free_strings:
	/* free all non-NULL strings */
	g_strfreev(argv);
	g_strfreev(term_argv);
	g_free(working_dir);
	g_free(cmd);
	g_free(utf8_check_executable);
	g_free(locale_filename);
	g_free(locale_term_cmd);
	g_free(check_executable);
	g_free(long_executable);
	g_free(executable);
	g_free(script_name);

	return result_id;
}


static gboolean build_iofunc(GIOChannel *ioc, GIOCondition cond, gpointer data)
{
	if (cond & (G_IO_IN | G_IO_PRI))
	{
		//GIOStatus s;
		gchar *msg;

		while (g_io_channel_read_line(ioc, &msg, NULL, NULL, NULL) && msg)
		{
			//if (s != G_IO_STATUS_NORMAL && s != G_IO_STATUS_EOF) break;
			gint color;
			color = (GPOINTER_TO_INT(data)) ? COLOR_DARK_RED : COLOR_BLACK;
			g_strstrip(msg);

			if (app->pref_editor_use_indicators)
			{
				gchar *filename;
				gint line;
				msgwin_parse_compiler_error_line(msg, &filename, &line);
				if (line != -1 && filename != NULL)
				{
					gint idx = document_find_by_filename(filename, FALSE);
					document_set_indicator(idx, line - 1);	// will check valid idx
					color = COLOR_RED;	// error message parsed on the line
				}
				g_free(filename);
			}
			msgwin_compiler_add(color, FALSE, msg);

			g_free(msg);
		}
	}
	if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
		return FALSE;

	return TRUE;
}


static void build_exit_cb(GPid child_pid, gint status, gpointer user_data)
{
#ifdef G_OS_UNIX
	gboolean failure = FALSE;

	if (WIFEXITED(status))
	{
		if (WEXITSTATUS(status) != EXIT_SUCCESS)
			failure = TRUE;
	}
	else if (WIFSIGNALED(status))
	{
		// the terminating signal: WTERMSIG (status));
		failure = TRUE;
	}
	else
	{	// any other failure occured
		failure = TRUE;
	}


	if (failure)
	{
		msgwin_compiler_add(COLOR_DARK_RED, TRUE, _("Compilation failed."));
		if (! app->msgwindow_visible) msgwin_show();
	}
	else
	{
		gchar *msg = _("Compilation finished successfully.");
		msgwin_compiler_add(COLOR_BLUE, TRUE, msg);
		if (! app->msgwindow_visible) ui_set_statusbar(msg, FALSE);
	}

#endif
	utils_beep();
	gtk_widget_set_sensitive(app->compile_button, TRUE);
	g_spawn_close_pid(child_pid);
}


static gboolean build_create_shellscript(const gint idx, const gchar *fname, const gchar *cmd)
{
	FILE *fp;
	gchar *str;
#ifdef G_OS_WIN32
	gchar *tmp;
#endif

	fp = fopen(fname, "w");
	if (! fp) return FALSE;

#ifdef G_OS_WIN32
	tmp = g_path_get_basename(fname);
	str = g_strdup_printf("%s\n\npause\ndel %s\n", cmd, tmp);
	g_free(tmp);
#else
	str = g_strdup_printf(
		"#!/bin/sh\n\n%s\n\necho \"\n\n------------------\n(program exited with code: $?)\" \
		\n\necho \"Press return to continue\"\nread\nunlink $0\n", cmd);
#endif

	fputs(str, fp);
	g_free(str);

#ifndef G_OS_WIN32
	if (chmod(fname, 0700) != 0)
	{
		unlink(fname);
		return FALSE;
	}
#endif
	fclose(fp);

	return TRUE;
}


#define GEANY_ADD_WIDGET_ACCEL(gkey, menuitem) \
	if (keys[(gkey)]->key != 0) \
		gtk_widget_add_accelerator(menuitem, "activate", accel_group, \
			keys[(gkey)]->key, keys[(gkey)]->mods, GTK_ACCEL_VISIBLE)

static GtkWidget *create_build_menu_gen(gint idx)
{
	GtkWidget *menu, *item = NULL, *image, *separator;
	GtkAccelGroup *accel_group = gtk_accel_group_new();
	GtkTooltips *tooltips = GTK_TOOLTIPS(lookup_widget(app->window, "tooltips"));
	filetype *ft = doc_list[idx].file_type;

	menu = gtk_menu_new();

#ifndef G_OS_WIN32
	if (ft->menu_items->can_compile)
	{
		// compile the code
		item = gtk_image_menu_item_new_with_mnemonic(_("_Compile"));
		gtk_widget_show(item);
		gtk_container_add(GTK_CONTAINER(menu), item);
		gtk_tooltips_set_tip(tooltips, item, _("Compiles the current file"), NULL);
		GEANY_ADD_WIDGET_ACCEL(GEANY_KEYS_BUILD_COMPILE, item);
		image = gtk_image_new_from_stock("gtk-convert", GTK_ICON_SIZE_MENU);
		gtk_widget_show(image);
		gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
		g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_build_compile_activate), NULL);
		ft->menu_items->item_compile = item;
	}

	if (ft->menu_items->can_link)
	{	// build the code
		item = gtk_image_menu_item_new_with_mnemonic(_("_Build"));
		gtk_widget_show(item);
		gtk_container_add(GTK_CONTAINER(menu), item);
		gtk_tooltips_set_tip(tooltips, item,
					_("Builds the current file (generate an executable file)"), NULL);
		GEANY_ADD_WIDGET_ACCEL(GEANY_KEYS_BUILD_LINK, item);
		g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_build_build_activate), NULL);
		ft->menu_items->item_link = item;
	}

	if (item != NULL)
	{
		item = gtk_separator_menu_item_new();
		gtk_widget_show(item);
		gtk_container_add(GTK_CONTAINER(menu), item);
	}

	// build the code with make all
	item = gtk_image_menu_item_new_with_mnemonic(_("_Make all"));
	gtk_widget_show(item);
	gtk_container_add(GTK_CONTAINER(menu), item);
	gtk_tooltips_set_tip(tooltips, item, _("Builds the current file with the "
										   "make tool and the default target"), NULL);
	GEANY_ADD_WIDGET_ACCEL(GEANY_KEYS_BUILD_MAKE, item);
	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_build_make_activate),
		GINT_TO_POINTER(GBO_MAKE_ALL));
	ft->menu_items->item_make_all = item;

	// build the code with make custom
	item = gtk_image_menu_item_new_with_mnemonic(_("Make custom _target"));
	gtk_widget_show(item);
	GEANY_ADD_WIDGET_ACCEL(GEANY_KEYS_BUILD_MAKEOWNTARGET, item);
	gtk_container_add(GTK_CONTAINER(menu), item);
	gtk_tooltips_set_tip(tooltips, item, _("Builds the current file with the "
										   "make tool and the specified target"), NULL);
	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_build_make_activate),
		GINT_TO_POINTER(GBO_MAKE_CUSTOM));
	ft->menu_items->item_make_custom = item;

	// build the code with make object
	item = gtk_image_menu_item_new_with_mnemonic(_("Make _object"));
	gtk_widget_show(item);
	GEANY_ADD_WIDGET_ACCEL(GEANY_KEYS_BUILD_MAKEOBJECT, item);
	gtk_container_add(GTK_CONTAINER(menu), item);
	gtk_tooltips_set_tip(tooltips, item, _("Compiles the current file using the "
										   "make tool"), NULL);
	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_build_make_activate),
		GINT_TO_POINTER(GBO_MAKE_OBJECT));
	ft->menu_items->item_make_object = item;
#endif

	if (ft->menu_items->can_exec)
	{	// execute the code
		item = gtk_separator_menu_item_new();
		gtk_widget_show(item);
		gtk_container_add(GTK_CONTAINER(menu), item);

		item = gtk_image_menu_item_new_from_stock("gtk-execute", accel_group);
		gtk_widget_show(item);
		gtk_container_add(GTK_CONTAINER(menu), item);
		gtk_tooltips_set_tip(tooltips, item, _("Run or view the current file"), NULL);
		GEANY_ADD_WIDGET_ACCEL(GEANY_KEYS_BUILD_RUN, item);
		g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_build_execute_activate), NULL);
		ft->menu_items->item_exec = item;
	}

	// arguments
	if (ft->menu_items->can_compile || ft->menu_items->can_link || ft->menu_items->can_exec)
	{
		// separator
		separator = gtk_separator_menu_item_new();
		gtk_widget_show(separator);
		gtk_container_add(GTK_CONTAINER(menu), separator);
		gtk_widget_set_sensitive(separator, FALSE);

		item = gtk_image_menu_item_new_with_mnemonic(_("_Set Includes and Arguments"));
		gtk_widget_show(item);
		GEANY_ADD_WIDGET_ACCEL(GEANY_KEYS_BUILD_OPTIONS, item);
		gtk_container_add(GTK_CONTAINER(menu), item);
		gtk_tooltips_set_tip(tooltips, item,
					_("Sets the includes and library paths for the compiler and "
					  "the program arguments for execution"), NULL);
		image = gtk_image_new_from_stock("gtk-preferences", GTK_ICON_SIZE_MENU);
		gtk_widget_show(image);
		gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
		g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_build_arguments_activate), NULL);
	}

	return menu;
}


static GtkWidget *create_build_menu_tex()
{
	GtkWidget *menu, *item, *image, *separator;
	GtkAccelGroup *accel_group = gtk_accel_group_new();
	GtkTooltips *tooltips = GTK_TOOLTIPS(lookup_widget(app->window, "tooltips"));
	filetype *ft = filetypes[GEANY_FILETYPES_LATEX];

	menu = gtk_menu_new();

#ifndef G_OS_WIN32
	// DVI
	item = gtk_image_menu_item_new_with_mnemonic(_("LaTeX -> DVI"));
	gtk_widget_show(item);
	gtk_container_add(GTK_CONTAINER(menu), item);
	gtk_tooltips_set_tip(tooltips, item, _("Compiles the current file into a DVI file"), NULL);
	if (keys[GEANY_KEYS_BUILD_COMPILE]->key)
		gtk_widget_add_accelerator(item, "activate", accel_group, keys[GEANY_KEYS_BUILD_COMPILE]->key,
			keys[GEANY_KEYS_BUILD_COMPILE]->mods, GTK_ACCEL_VISIBLE);
	image = gtk_image_new_from_stock("gtk-convert", GTK_ICON_SIZE_MENU);
	gtk_widget_show(image);
	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_build_tex_activate), GINT_TO_POINTER(0));

	// PDF
	item = gtk_image_menu_item_new_with_mnemonic(_("LaTeX -> PDF"));
	gtk_widget_show(item);
	gtk_container_add(GTK_CONTAINER(menu), item);
	gtk_tooltips_set_tip(tooltips, item, _("Compiles the current file into a PDF file"), NULL);
	if (keys[GEANY_KEYS_BUILD_LINK]->key)
		gtk_widget_add_accelerator(item, "activate", accel_group, keys[GEANY_KEYS_BUILD_LINK]->key,
			keys[GEANY_KEYS_BUILD_LINK]->mods, GTK_ACCEL_VISIBLE);
	image = gtk_image_new_from_stock("gtk-convert", GTK_ICON_SIZE_MENU);
	gtk_widget_show(image);
	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_build_tex_activate), GINT_TO_POINTER(1));

	if (item != NULL)
	{
		item = gtk_separator_menu_item_new();
		gtk_widget_show(item);
		gtk_container_add(GTK_CONTAINER(menu), item);
	}

	// build the code with make all
	item = gtk_image_menu_item_new_with_mnemonic(_("_Make all"));
	gtk_widget_show(item);
	gtk_container_add(GTK_CONTAINER(menu), item);
	gtk_tooltips_set_tip(tooltips, item, _("Builds the current file with the "
										   "make tool and the default target"), NULL);
	GEANY_ADD_WIDGET_ACCEL(GEANY_KEYS_BUILD_MAKE, item);
	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_build_make_activate),
		GINT_TO_POINTER(GBO_MAKE_ALL));
	ft->menu_items->item_make_all = item;

	// build the code with make custom
	item = gtk_image_menu_item_new_with_mnemonic(_("Make custom _target"));
	gtk_widget_show(item);
	GEANY_ADD_WIDGET_ACCEL(GEANY_KEYS_BUILD_MAKEOWNTARGET, item);
	gtk_container_add(GTK_CONTAINER(menu), item);
	gtk_tooltips_set_tip(tooltips, item, _("Builds the current file with the "
										   "make tool and the specified target"), NULL);
	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_build_make_activate),
		GINT_TO_POINTER(GBO_MAKE_CUSTOM));
	ft->menu_items->item_make_custom = item;

	if (item != NULL)
	{
		item = gtk_separator_menu_item_new();
		gtk_widget_show(item);
		gtk_container_add(GTK_CONTAINER(menu), item);
	}
#endif

	// DVI view
	item = gtk_image_menu_item_new_with_mnemonic(_("View DVI file"));
	gtk_widget_show(item);
	gtk_container_add(GTK_CONTAINER(menu), item);
	if (keys[GEANY_KEYS_BUILD_RUN]->key)
		gtk_widget_add_accelerator(item, "activate", accel_group, keys[GEANY_KEYS_BUILD_RUN]->key,
			keys[GEANY_KEYS_BUILD_RUN]->mods, GTK_ACCEL_VISIBLE);
	gtk_tooltips_set_tip(tooltips, item, _("Compiles and view the current file"), NULL);
	image = gtk_image_new_from_stock("gtk-find", GTK_ICON_SIZE_MENU);
	gtk_widget_show(image);
	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_build_tex_activate), GINT_TO_POINTER(2));

	// PDF view
	item = gtk_image_menu_item_new_with_mnemonic(_("View PDF file"));
	gtk_widget_show(item);
	gtk_container_add(GTK_CONTAINER(menu), item);
	if (keys[GEANY_KEYS_BUILD_RUN2]->key)
		gtk_widget_add_accelerator(item, "activate", accel_group, keys[GEANY_KEYS_BUILD_RUN2]->key,
			keys[GEANY_KEYS_BUILD_RUN2]->mods, GTK_ACCEL_VISIBLE);
	gtk_tooltips_set_tip(tooltips, item, _("Compiles and view the current file"), NULL);
	image = gtk_image_new_from_stock("gtk-find", GTK_ICON_SIZE_MENU);
	gtk_widget_show(image);
	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_build_tex_activate), GINT_TO_POINTER(3));

	// separator
	separator = gtk_separator_menu_item_new();
	gtk_widget_show(separator);
	gtk_container_add(GTK_CONTAINER(menu), separator);
	gtk_widget_set_sensitive(separator, FALSE);

	// arguments
	item = gtk_image_menu_item_new_with_mnemonic(_("Set Arguments"));
	gtk_widget_show(item);
	if (keys[GEANY_KEYS_BUILD_OPTIONS]->key)
		gtk_widget_add_accelerator(item, "activate", accel_group, keys[GEANY_KEYS_BUILD_OPTIONS]->key,
			keys[GEANY_KEYS_BUILD_OPTIONS]->mods, GTK_ACCEL_VISIBLE);
	gtk_container_add(GTK_CONTAINER(menu), item);
	gtk_tooltips_set_tip(tooltips, item,
				_("Sets the program paths and arguments"), NULL);
	image = gtk_image_new_from_stock("gtk-preferences", GTK_ICON_SIZE_MENU);
	gtk_widget_show(image);
	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_build_tex_arguments_activate), NULL);

	gtk_window_add_accel_group(GTK_WINDOW(app->window), accel_group);

	return menu;
}


static gboolean is_c_header(const gchar *fname)
{
	gchar *ext = NULL;

	if (fname)
	{
		ext = strrchr(fname, '.');
	}
	return (ext == NULL) ? FALSE : (*(ext + 1) == 'h');	// match *.h*
}


void build_menu_update(gint idx)
{
	filetype *ft;
	gboolean have_path;

	if (idx == -1 || doc_list[idx].file_type == NULL)
	{
		gtk_widget_set_sensitive(lookup_widget(app->window, "menu_build1"), FALSE);
		gtk_menu_item_remove_submenu(GTK_MENU_ITEM(lookup_widget(app->window, "menu_build1")));
		gtk_widget_set_sensitive(app->compile_button, FALSE);
		gtk_widget_set_sensitive(app->run_button, FALSE);
		return;
	}
	else
		gtk_widget_set_sensitive(lookup_widget(app->window, "menu_build1"), TRUE);

	ft = doc_list[idx].file_type;

#ifdef G_OS_WIN32
	// disable compile and link under Windows until it is implemented
	ft->menu_items->can_compile = FALSE;
	ft->menu_items->can_link = FALSE;
#endif

	gtk_menu_item_remove_submenu(GTK_MENU_ITEM(lookup_widget(app->window, "menu_build1")));

	if (ft->menu_items->menu == NULL)
	{
		ft->menu_items->menu = (ft->id == GEANY_FILETYPES_LATEX) ?
			create_build_menu_tex() : create_build_menu_gen(idx);
		g_object_ref((gpointer)ft->menu_items->menu);	// to hold it after removing
	}
	gtk_menu_item_set_submenu(GTK_MENU_ITEM(lookup_widget(app->window, "menu_build1")),
						ft->menu_items->menu);

	have_path = (doc_list[idx].file_name != NULL);
	// update the Make items
	if (ft->menu_items->item_make_all != NULL)
		gtk_widget_set_sensitive(ft->menu_items->item_make_all, have_path);
	if (ft->menu_items->item_make_custom != NULL)
		gtk_widget_set_sensitive(ft->menu_items->item_make_custom, have_path);
	if (ft->menu_items->item_make_object != NULL)
		gtk_widget_set_sensitive(ft->menu_items->item_make_object, have_path);
	
	switch (ft->id)
	{
		case GEANY_FILETYPES_LATEX:
		{
			gtk_widget_set_sensitive(app->compile_button, have_path && ft->menu_items->can_compile);
			gtk_widget_set_sensitive(app->run_button, have_path && ft->menu_items->can_exec);
			break;
		}
		case GEANY_FILETYPES_C:	// intended fallthrough, C and C++ behave equal
		case GEANY_FILETYPES_CPP:
		{
			if (ft->menu_items->can_exec)
				gtk_widget_set_sensitive(ft->menu_items->item_exec, have_path);
			gtk_widget_set_sensitive(app->run_button, have_path && ft->menu_items->can_exec);

			// compile and link are disabled for header files
			have_path = have_path && ! is_c_header(doc_list[idx].file_name);
			gtk_widget_set_sensitive(app->compile_button, have_path && ft->menu_items->can_compile);
			if (ft->menu_items->can_compile)
				gtk_widget_set_sensitive(ft->menu_items->item_compile, have_path);
			if (ft->menu_items->can_link)
				gtk_widget_set_sensitive(ft->menu_items->item_link, have_path);
			break;
		}
		default:
		{
			gtk_widget_set_sensitive(app->compile_button, have_path && ft->menu_items->can_compile);
			gtk_widget_set_sensitive(app->run_button, have_path && ft->menu_items->can_exec);
			if (ft->menu_items->can_compile)
				gtk_widget_set_sensitive(ft->menu_items->item_compile, have_path);
			if (ft->menu_items->can_link)
				gtk_widget_set_sensitive(ft->menu_items->item_link, have_path);
			if (ft->menu_items->can_exec)
				gtk_widget_set_sensitive(ft->menu_items->item_exec, have_path);
		}
	}
}


void
on_build_compile_activate              (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
	gint idx = document_get_cur_idx();
	GPid child_pid = (GPid) 0;

	if (doc_list[idx].changed) document_save_file(idx, FALSE);

	if (doc_list[idx].file_type->id == GEANY_FILETYPES_LATEX)
		child_pid = build_compile_tex_file(idx, 0);
	else
		child_pid = build_compile_file(idx);

	if (child_pid != (GPid) 0)
	{
		gtk_widget_set_sensitive(app->compile_button, FALSE);
		g_child_watch_add(child_pid, build_exit_cb, NULL);
	}
}


void
on_build_tex_activate                  (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
	gint idx = document_get_cur_idx();
	GPid child_pid = (GPid) 0;

	if (doc_list[idx].changed) document_save_file(idx, FALSE);

	switch (GPOINTER_TO_INT(user_data))
	{
		case 0: child_pid = build_compile_tex_file(idx, 0); break;
		case 1: child_pid = build_compile_tex_file(idx, 1); break;
		case 2: child_pid = build_view_tex_file(idx, 0); break;
		case 3: child_pid = build_view_tex_file(idx, 1); break;
	}

	if (GPOINTER_TO_INT(user_data) <= 1 && child_pid != (GPid) 0)
	{
		gtk_widget_set_sensitive(app->compile_button, FALSE);
		g_child_watch_add(child_pid, build_exit_cb, NULL);
	}
}


void
on_build_build_activate                (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
	gint idx = document_get_cur_idx();
	GPid child_pid = (GPid) 0;

	if (doc_list[idx].changed) document_save_file(idx, FALSE);

	if (doc_list[idx].file_type->id == GEANY_FILETYPES_LATEX)
		child_pid = build_compile_tex_file(idx, 1);
	else
		child_pid = build_link_file(idx);

	if (child_pid != (GPid) 0)
	{
		gtk_widget_set_sensitive(app->compile_button, FALSE);
		g_child_watch_add(child_pid, build_exit_cb, NULL);
	}
}


void
on_build_make_activate                 (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
	gint idx = document_get_cur_idx();
	gint build_opts = GPOINTER_TO_INT(user_data);

	g_return_if_fail(DOC_IDX_VALID(idx) && doc_list[idx].file_name != NULL);

	switch (build_opts)
	{
		case GBO_MAKE_CUSTOM:
		{
			dialogs_show_input(_("Make custom target"),
				_("Enter custom options here, all entered text is passed to the make command."),
				build_info.custom_target,
				G_CALLBACK(on_make_target_dialog_response),
				G_CALLBACK(on_make_target_entry_activate));
			break;
		}

		case GBO_MAKE_OBJECT:
		// fall through
		case GBO_MAKE_ALL:
		{
			GPid child_pid;

			if (doc_list[idx].changed) document_save_file(idx, FALSE);

			child_pid = build_make_file(idx, build_opts);
			if (child_pid != (GPid) 0)
			{
				gtk_widget_set_sensitive(app->compile_button, FALSE);
				g_child_watch_add(child_pid, build_exit_cb, NULL);
			}
		}
	}
}


void
on_build_execute_activate              (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
	gint idx = document_get_cur_idx();

	if (doc_list[idx].file_type->id == GEANY_FILETYPES_LATEX && user_data != NULL)
	{
		if (build_view_tex_file(idx, GPOINTER_TO_INT(user_data)) == (GPid) 0)
		{
			msgwin_status_add(_("Failed to execute the view program"));
		}
	}
	else if (doc_list[idx].file_type->id == GEANY_FILETYPES_HTML)
	{
		gchar *uri = g_strconcat("file:///", g_path_skip_root(doc_list[idx].file_name), NULL);
		utils_start_browser(uri);
		g_free(uri);
	}
	else
	{
		// save the file only if the run command uses it
		if (doc_list[idx].changed &&
			strstr(doc_list[idx].file_type->programs->run_cmd, "%f") != NULL)
				document_save_file(idx, FALSE);
		if (build_run_cmd(idx) == (GPid) 0)
		{
#ifndef G_OS_WIN32 // on Windows there is no PID returned
			msgwin_status_add(_("Failed to execute the terminal program"));
#endif
		}
	}
	//gtk_widget_grab_focus(GTK_WIDGET(doc_list[idx].sci));
}


void
on_build_arguments_activate            (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
	dialogs_show_includes_arguments_gen();
}


void
on_build_tex_arguments_activate        (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
	dialogs_show_includes_arguments_tex();
}


static void
on_make_target_dialog_response         (GtkDialog *dialog,
                                        gint response,
                                        gpointer user_data)
{
	if (response == GTK_RESPONSE_ACCEPT)
	{
		gint idx = document_get_cur_idx();
		GPid child_pid;

		if (doc_list[idx].changed) document_save_file(idx, FALSE);

		g_free(build_info.custom_target);
		build_info.custom_target = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));

		child_pid = build_make_file(idx, GBO_MAKE_CUSTOM);
		if (child_pid != (GPid) 0)
		{
			gtk_widget_set_sensitive(app->compile_button, FALSE);
			g_child_watch_add(child_pid, build_exit_cb, NULL);
		}
	}
	gtk_widget_destroy(GTK_WIDGET(dialog));
}


static void
on_make_target_entry_activate          (GtkEntry        *entry,
                                        gpointer         user_data)
{
	on_make_target_dialog_response(GTK_DIALOG(user_data), GTK_RESPONSE_ACCEPT, entry);
}


