/*
 *      build.c - this file is part of Geany, a fast and lightweight IDE
 *
 *      Copyright 2005-2007 Enrico Tröger <enrico.troeger@uvena.de>
 *      Copyright 2006-2007 Nick Treleaven <nick.treleaven@btinternet.com>
 *
 *      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$
 */

/*
 * Build commands and menu items.
 */

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

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

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

#include "prefs.h"
#include "support.h"
#include "utils.h"
#include "ui_utils.h"
#include "dialogs.h"
#include "msgwindow.h"
#include "document.h"
#include "filetypes.h"
#include "keybindings.h"
#include "vte.h"
#include "project.h"
#include "editor.h"


BuildInfo build_info = {GBO_COMPILE, 0, NULL, GEANY_FILETYPES_ALL, NULL};

static gchar *current_dir_entered = NULL;

static struct
{
	GPid pid;
	gint file_type_id;
} run_info = {0, GEANY_FILETYPES_ALL};

#ifdef G_OS_WIN32
static const gchar RUN_SCRIPT_CMD[] = "./geany_run_script.bat";
#else
static const gchar RUN_SCRIPT_CMD[] = "./geany_run_script.sh";
#endif

enum
{
	LATEX_CMD_TO_DVI,
	LATEX_CMD_TO_PDF,
	LATEX_CMD_VIEW_DVI,
	LATEX_CMD_VIEW_PDF
};

static BuildMenuItems default_menu_items =
	{NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
static BuildMenuItems latex_menu_items =
	{NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};


static struct
{
	GtkWidget	*run_button;
	GtkWidget	*compile_button;
}
widgets;


static gboolean build_iofunc(GIOChannel *ioc, GIOCondition cond, gpointer data);
static gboolean build_create_shellscript(const gchar *fname, const gchar *cmd, gboolean autoclose);
static GPid build_spawn_cmd(gint idx, const gchar *cmd, const gchar *dir);
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);
static void set_stop_button(gboolean stop);
static void build_exit_cb(GPid child_pid, gint status, gpointer user_data);
static void run_exit_cb(GPid child_pid, gint status, gpointer user_data);
static void on_build_arguments_activate(GtkMenuItem *menuitem, gpointer user_data);

static void on_build_compile_activate(GtkMenuItem *menuitem, gpointer user_data);
static void on_build_tex_activate(GtkMenuItem *menuitem, gpointer user_data);
static void on_build_build_activate(GtkMenuItem *menuitem, gpointer user_data);
static void on_build_make_activate(GtkMenuItem *menuitem, gpointer user_data);
static void on_build_execute_activate(GtkMenuItem *menuitem, gpointer user_data);
static void on_build_next_error(GtkMenuItem *menuitem, gpointer user_data);


#ifndef G_OS_WIN32
static void kill_process(GPid *pid);
#endif


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

	if (default_menu_items.menu != NULL && GTK_IS_WIDGET(default_menu_items.menu))
		gtk_widget_destroy(default_menu_items.menu);
	if (latex_menu_items.menu != NULL && GTK_IS_WIDGET(latex_menu_items.menu))
		gtk_widget_destroy(latex_menu_items.menu);
}


static GPid build_compile_tex_file(gint idx, gint mode)
{
	const gchar *cmd = NULL;

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

	if (mode == LATEX_CMD_TO_DVI)
	{
		cmd = doc_list[idx].file_type->programs->compiler;
		build_info.type = GBO_COMPILE;
	}
	else
	{
		cmd = doc_list[idx].file_type->programs->linker;
		build_info.type = GBO_BUILD;
	}

	return build_spawn_cmd(idx, cmd, NULL);
}


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

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

	run_info.file_type_id = GEANY_FILETYPES_LATEX;

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

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

	// check wether view_file exists
	if (g_stat(locale_filename, &st) != 0)
	{
		msgwin_status_add(_("Failed to view %s (make sure it is already compiled)"), view_file);
		utils_free_pointers(executable, view_file, locale_filename, NULL);

		return (GPid) 1;
	}

	// replace %f and %e in the run_cmd string
	cmd_string = g_strdup((mode == LATEX_CMD_VIEW_DVI) ?
										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);

	/* get the terminal path */
	locale_term_cmd = utils_get_locale_from_utf8(prefs.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)
	{
		gchar *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)"), prefs.tools_term_cmd);

		utils_free_pointers(executable, view_file, locale_filename, cmd_string, locale_cmd_string,
										locale_term_cmd, NULL);
		g_strfreev(term_argv);
		return (GPid) 1;
	}

	// (RUN_SCRIPT_CMD should be ok in UTF8 without converting in locale because it contains no umlauts)
	if (! build_create_shellscript(RUN_SCRIPT_CMD, locale_cmd_string, TRUE))
	{
		msgwin_status_add(_("Failed to execute \"%s\" (start-script could not be created)"),
													executable);
		utils_free_pointers(executable, view_file, locale_filename, cmd_string, locale_cmd_string,
										locale_term_cmd, NULL);
		g_strfreev(term_argv);
		return (GPid) 1;
	}

	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(RUN_SCRIPT_CMD);
#else
	argv[term_argv_len   ]  = g_strdup("-e");
	argv[term_argv_len + 1] = g_strdup(RUN_SCRIPT_CMD);
#endif
	argv[term_argv_len + 2] = NULL;


	if (! g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
						NULL, NULL, &(run_info.pid), NULL, NULL, NULL, &error))
	{
		geany_debug("g_spawn_async_with_pipes() failed: %s", error->message);
		msgwin_status_add(_("Process failed (%s)"), error->message);

		utils_free_pointers(executable, view_file, locale_filename, cmd_string, locale_cmd_string,
										locale_term_cmd, NULL);
		g_strfreev(argv);
		g_strfreev(term_argv);
		g_error_free(error);
		error = NULL;
		return (GPid) 0;
	}

	if (run_info.pid > 0)
	{
		//setpgid(0, getppid());
		g_child_watch_add(run_info.pid, (GChildWatchFunc) run_exit_cb, NULL);
		build_menu_update(idx);
	}

	utils_free_pointers(executable, view_file, locale_filename, cmd_string, locale_cmd_string,
										locale_term_cmd, NULL);
	g_strfreev(argv);
	g_strfreev(term_argv);

	return run_info.pid;
}


// get curfile.o in locale encoding from document::file_name
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;
}


static GPid build_make_file(gint idx, gint build_opts)
{
	GString *cmdstr;
	const gchar *dir = NULL;
	GPid pid;

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

	cmdstr = g_string_new(prefs.tools_make_cmd);
	g_string_append_c(cmdstr, ' ');

	if (build_opts == GBO_MAKE_OBJECT)
	{
		gchar *tmp;

		build_info.type = build_opts;
		tmp = get_object_filename(idx);
		g_string_append(cmdstr, tmp);
		g_free(tmp);
	}
	else if (build_opts == GBO_MAKE_CUSTOM && build_info.custom_target)
	{
		build_info.type = GBO_MAKE_CUSTOM;
		g_string_append(cmdstr, build_info.custom_target);
		dir = project_get_make_dir();
	}
	else	// GBO_MAKE_ALL
	{
		build_info.type = GBO_MAKE_ALL;
		g_string_append(cmdstr, "all");
		dir = project_get_make_dir();
	}

	pid = build_spawn_cmd(idx, cmdstr->str, dir);	// if dir is NULL, idx filename is used
	g_string_free(cmdstr, TRUE);
	return pid;
}


static GPid build_compile_file(gint idx)
{
	const gchar *cmd;

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

	cmd = doc_list[idx].file_type->programs->compiler;

	build_info.type = GBO_COMPILE;
	return build_spawn_cmd(idx, cmd, NULL);
}


static GPid build_link_file(gint idx)
{
	GString *cmdstr;
	GPid pid;
	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);
	// check for filename extension and abort if filename doesn't have one
	if (utils_str_equal(locale_filename, executable))
	{
		msgwin_status_add(_("Command stopped because the current file has no extension."));
		utils_beep();
		utils_free_pointers(locale_filename, executable, NULL);
		return (GPid) 1;
	}

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

	// check wether object file (file.o) exists
	if (g_stat(object_file, &st) == 0)
	{	// check wether src is newer than object file
		if (g_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_msgbox(GTK_MESSAGE_ERROR,
					_("Something very strange is occurred, could not stat %s (%s)."),
					doc_list[idx].file_name, g_strerror(errno));
		}
	}

	cmdstr = g_string_new(doc_list[idx].file_type->programs->linker);
	g_string_append_c(cmdstr, ' ');

	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);

		g_string_append(cmdstr, "-of");
		g_string_append(cmdstr, tmp);
		g_free(tmp);
	}
	else
	{
		gchar *tmp = g_path_get_basename(executable);

		g_string_append(cmdstr, "-o");
		g_string_append_c(cmdstr, ' ');
		g_string_append(cmdstr, tmp);
		g_free(tmp);
	}

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

	build_info.type = GBO_BUILD;
	pid = build_spawn_cmd(idx, cmdstr->str, NULL);
	g_string_free(cmdstr, TRUE);
	return pid;
}


/* If linking, clear all error indicators in all documents.
 * Otherwise, just clear error indicators in document idx. */
static void clear_errors(gint idx)
{
	switch (build_info.type)
	{
		case GBO_COMPILE:
		case GBO_MAKE_OBJECT:
			document_clear_indicators(idx);
			break;

		case GBO_BUILD:
		case GBO_MAKE_ALL:
		case GBO_MAKE_CUSTOM:
		{
			guint i;

			for (i = 0; i < doc_array->len; i++)
			{
				if (doc_list[i].is_valid)
					document_clear_indicators(i);
			}
			break;
		}
	}
}


/* dir is the UTF-8 working directory to run cmd in. It can be NULL to use the
 * idx document directory */
static GPid build_spawn_cmd(gint idx, const gchar *cmd, const gchar *dir)
{
	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;
	gint     stdout_fd;
	gint     stderr_fd;

	g_return_val_if_fail(DOC_IDX_VALID(idx), (GPid) 1);

	clear_errors(idx);
	setptr(current_dir_entered, NULL);

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

	cmd_string = g_strdup(cmd);
	// 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);

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

	utf8_cmd_string = utils_get_utf8_from_locale(cmd_string);
	utf8_working_dir = (dir != NULL) ? g_strdup(dir) :
		g_path_get_dirname(doc_list[idx].file_name);
	working_dir = utils_get_locale_from_utf8(utf8_working_dir);

	gtk_list_store_clear(msgwindow.store_compiler);
	gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_COMPILER);
	msgwin_compiler_add_fmt(COLOR_BLUE, _("%s (in directory: %s)"), utf8_cmd_string, utf8_working_dir);
	g_free(utf8_working_dir);
	g_free(utf8_cmd_string);

	// set the build info for the message window
	g_free(build_info.dir);
	build_info.dir = g_strdup(working_dir);
	build_info.file_type_id = FILETYPE_ID(doc_list[idx].file_type);

	if (! g_spawn_async_with_pipes(working_dir, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
						NULL, NULL, &(build_info.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(locale_filename);
		error = NULL;
		return (GPid) 0;
	}

	if (build_info.pid > 0)
	{
		g_child_watch_add(build_info.pid, (GChildWatchFunc) build_exit_cb, NULL);
		build_menu_update(idx);
	}

	// 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,
		TRUE, 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,
		TRUE, build_iofunc, GINT_TO_POINTER(1));

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

	return build_info.pid;
}


/* Checks if the executable file corresponding to document idx exists.
 * Returns the name part of the filename, without extension.
 * Returns NULL if executable file doesn't exist. */
static gchar *get_build_executable(const gchar *locale_filename, gboolean check_exists,
		filetype_id ft_id)
{
	gchar *long_executable = NULL;
	struct stat st;

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

	if (check_exists)
	{
		gchar *check_executable = NULL;

		// add .class extension for JAVA source files (only for stat)
		if (ft_id == GEANY_FILETYPES_JAVA)
		{
#ifdef G_OS_WIN32
			gchar *tmp;
			// there is already the extension .exe, so first remove it and then add .class
			tmp = utils_remove_ext_from_filename(long_executable);
			check_executable = g_strconcat(tmp, ".class", NULL);
			g_free(tmp);
#else
			check_executable = g_strconcat(long_executable, ".class", NULL);
#endif
		}
		else
			check_executable = g_strdup(long_executable);

		// check for filename extension and abort if filename doesn't have one
		if (utils_str_equal(locale_filename, check_executable))
		{
			msgwin_status_add(_("Command stopped because the current file has no extension."));
			utils_beep();
			g_free(check_executable);
			return NULL;
		}

		// check whether executable exists
		if (g_stat(check_executable, &st) != 0)
		{
			gchar *utf8_check_executable = utils_get_utf8_from_locale(check_executable);

			msgwin_status_add(_("Failed to execute \"%s\" (make sure it is already built)"),
														utf8_check_executable);
			g_free(utf8_check_executable);
			g_free(check_executable);
			return NULL;
		}
	}

	// remove path
	setptr(long_executable, g_path_get_basename(long_executable));
	return long_executable;
}


// Returns: NULL if there was an error, or the working directory the script was created in.
static gchar *prepare_run_script(gint idx)
{
	gchar	*locale_filename = NULL;
	gboolean have_project;
	GeanyProject *project = app->project;
	filetype *ft = doc_list[idx].file_type;
	gboolean check_exists;
	gchar	*cmd = NULL;
	gchar	*executable = NULL;
	gchar	*working_dir = NULL;
	gboolean autoclose = FALSE;
	gboolean result = FALSE;
	gchar	*tmp;

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

	have_project = (project != NULL && NZV(project->run_cmd));
	cmd = (have_project) ?
		project->run_cmd :
		ft->programs->run_cmd;

	// only check for existing executable, if executable is required by %e
	check_exists = (strstr(cmd, "%e") != NULL);
	executable = get_build_executable(locale_filename, check_exists, FILETYPE_ID(ft));
	if (executable == NULL)
	{
		g_free(locale_filename);
		return NULL;
	}

	working_dir = (have_project) ?
		utils_get_locale_from_utf8(project->base_path) :
		g_path_get_dirname(locale_filename);

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

		msgwin_status_add(_("Failed to change the working directory to \"%s\""), utf8_working_dir);
		g_free(utf8_working_dir);
		g_free(working_dir);
		g_free(executable);
		g_free(locale_filename);
		return NULL;
	}

	// replace %f and %e in the run_cmd string
	cmd = g_strdup(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);

#ifdef HAVE_VTE
	if (vte_info.load_vte && vc != NULL && vc->run_in_vte)
		autoclose = TRUE; // don't wait for user input at the end of script when we are running in VTE
#endif

	// (RUN_SCRIPT_CMD should be ok in UTF8 without converting in locale because it contains no umlauts)
	result = build_create_shellscript(RUN_SCRIPT_CMD, cmd, autoclose);
	if (! result)
	{
		gchar *utf8_cmd = utils_get_utf8_from_locale(cmd);

		msgwin_status_add(_("Failed to execute \"%s\" (start-script could not be created)"),
			utf8_cmd);
		g_free(utf8_cmd);
	}

	g_free(cmd);
	g_free(executable);
	g_free(locale_filename);

	if (result)
		return working_dir;

	g_free(working_dir);
	return NULL;
}


static GPid build_run_cmd(gint idx)
{
	gchar	*working_dir;
	GError	*error = NULL;

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

	working_dir = prepare_run_script(idx);
	if (working_dir == NULL)
	{
		return (GPid) 1;
	}

	run_info.file_type_id = FILETYPE_ID(doc_list[idx].file_type);

#ifdef HAVE_VTE
	if (vte_info.load_vte && vc != NULL && vc->run_in_vte)
	{
		gchar *vte_cmd = g_strconcat(RUN_SCRIPT_CMD, "\n", NULL);
		// change into current directory if it is not done by default
		if (! vc->follow_path) vte_cwd(doc_list[idx].file_name, TRUE);
		vte_send_cmd(vte_cmd);

		// show the VTE
		gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_VTE);
		gtk_widget_grab_focus(vc->vte);
		msgwin_show_hide(TRUE);

		run_info.pid = 1;

		g_free(vte_cmd);
	}
	else
#endif
	{
		gchar	*locale_term_cmd = NULL;
		gchar  **term_argv = NULL;
		guint    term_argv_len, i;
		gchar  **argv = NULL;

		/* get the terminal path */
		locale_term_cmd = utils_get_locale_from_utf8(prefs.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)
		{
			gchar *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)"), prefs.tools_term_cmd);
			run_info.pid = (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(RUN_SCRIPT_CMD);
#else
		argv[term_argv_len   ]  = g_strdup("-e");
		argv[term_argv_len + 1] = g_strdup(RUN_SCRIPT_CMD);
#endif
		argv[term_argv_len + 2] = NULL;

		if (! g_spawn_async_with_pipes(working_dir, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
							NULL, NULL, &(run_info.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(RUN_SCRIPT_CMD);
			g_error_free(error);
			error = NULL;
			run_info.pid = (GPid) 0;
		}

		if (run_info.pid > 0)
		{
			g_child_watch_add(run_info.pid, (GChildWatchFunc) run_exit_cb, NULL);
			build_menu_update(idx);
		}
		free_strings:
		g_strfreev(argv);
		g_strfreev(term_argv);
		g_free(locale_term_cmd);
	}

	g_free(working_dir);
	return run_info.pid;
}


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;
			gchar *tmp;

			color = (GPOINTER_TO_INT(data)) ? COLOR_DARK_RED : COLOR_BLACK;
			g_strstrip(msg);

			if (build_parse_make_dir(msg, &tmp))
			{
				setptr(current_dir_entered, tmp);
			}

			if (editor_prefs.use_indicators)
			{
				gchar *filename;
				gint line;

				msgwin_parse_compiler_error_line(msg, current_dir_entered,
					&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, msg);

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

	return TRUE;
}


gboolean build_parse_make_dir(const gchar *string, gchar **prefix)
{
	const gchar *pos;

	*prefix = NULL;

	if (string == NULL)
		return FALSE;

	if ((pos = strstr(string, "Entering directory")) != NULL)
	{
		gsize len;
		gchar *input;

		// get the start of the path
		pos = strstr(string, "/");

		if (pos == NULL)
			return FALSE;

		input = g_strdup(pos);

		// kill the ' at the end of the path
		len = strlen(input);
		input[len - 1] = '\0';
		input = g_realloc(input, len);	// shorten by 1
		*prefix = input;

		return TRUE;
	}

	return FALSE;
}


#ifndef G_OS_WIN32
static void show_build_result_message(gboolean failure)
{
	gchar *msg;

	if (failure)
	{
		msg = _("Compilation failed.");
		msgwin_compiler_add(COLOR_DARK_RED, msg);
		// If msgwindow is hidden, user will want to display it to see the error
		if (! ui_prefs.msgwindow_visible)
		{
			gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_COMPILER);
			msgwin_show_hide(TRUE);
		}
		else
		if (gtk_notebook_get_current_page(GTK_NOTEBOOK(msgwindow.notebook)) != MSG_COMPILER)
			ui_set_statusbar("%s", msg);
	}
	else
	{
		msg = _("Compilation finished successfully.");
		msgwin_compiler_add(COLOR_BLUE, msg);
		if (! ui_prefs.msgwindow_visible ||
			gtk_notebook_get_current_page(GTK_NOTEBOOK(msgwindow.notebook)) != MSG_COMPILER)
				ui_set_statusbar("%s", msg);
	}
}
#endif


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;
	}
	show_build_result_message(failure);
#endif

	utils_beep();
	g_spawn_close_pid(child_pid);

	build_info.pid = 0;
	// enable build items again
	build_menu_update(-1);
}


static void run_exit_cb(GPid child_pid, gint status, gpointer user_data)
{
	g_spawn_close_pid(child_pid);

	run_info.pid = 0;
	// reset the stop button and menu item to the original meaning
	build_menu_update(-1);
}


// write a little shellscript to call the executable (similar to anjuta_launcher but "internal")
static gboolean build_create_shellscript(const gchar *fname, const gchar *cmd, gboolean autoclose)
{
	FILE *fp;
	gchar *str;
#ifdef G_OS_WIN32
	gchar *tmp;
#endif

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

#ifdef G_OS_WIN32
	tmp = g_path_get_basename(fname);
	str = g_strdup_printf("%s\n\n%s\ndel %s\n", cmd, (autoclose) ? "" : "pause", tmp);
	g_free(tmp);
#else
	str = g_strdup_printf(
		"#!/bin/sh\n\n%s\n\necho \"\n\n------------------\n(program exited with code: $?)\" \
		\n\n%s\nrm $0\n", cmd, (autoclose) ? "" :
		"\necho \"Press return to continue\"\n#to be more compatible with shells like dash\ndummy_var=\"\"\nread dummy_var");
#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 void create_build_menu_gen(BuildMenuItems *menu_items)
{
	GtkWidget *menu, *item = NULL, *image, *separator;
	GtkAccelGroup *accel_group = gtk_accel_group_new();
	GtkTooltips *tooltips = GTK_TOOLTIPS(lookup_widget(app->window, "tooltips"));

	menu = gtk_menu_new();

#ifndef G_OS_WIN32
	// 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);
	menu_items->item_compile = item;

	// 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);
	menu_items->item_link = item;

	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));
	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));
	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));
	menu_items->item_make_object = item;

	item = gtk_separator_menu_item_new();
	gtk_widget_show(item);
	gtk_container_add(GTK_CONTAINER(menu), item);

	// next error
	item = gtk_image_menu_item_new_with_mnemonic(_("_Next Error"));
	gtk_widget_show(item);
	GEANY_ADD_WIDGET_ACCEL(GEANY_KEYS_BUILD_NEXTERROR, item);
	gtk_container_add(GTK_CONTAINER(menu), item);
	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_build_next_error), NULL);
	menu_items->item_next_error = item;

	item = gtk_separator_menu_item_new();
	gtk_widget_show(item);
	gtk_container_add(GTK_CONTAINER(menu), item);
#endif

	// execute the code
	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);
	menu_items->item_exec = item;

	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 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);
	menu_items->item_set_args = item;

	menu_items->menu = menu;
	g_object_ref((gpointer)menu_items->menu);	// to hold it after removing
}


static void create_build_menu_tex(BuildMenuItems *menu_items)
{
	GtkWidget *menu, *item, *image, *separator;
	GtkAccelGroup *accel_group = gtk_accel_group_new();
	GtkTooltips *tooltips = GTK_TOOLTIPS(lookup_widget(app->window, "tooltips"));

	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);
	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_tex_activate), GINT_TO_POINTER(LATEX_CMD_TO_DVI));
	menu_items->item_compile = item;

	// 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);
	GEANY_ADD_WIDGET_ACCEL(GEANY_KEYS_BUILD_LINK, 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_tex_activate), GINT_TO_POINTER(LATEX_CMD_TO_PDF));
	menu_items->item_link = item;

	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));
	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));
	menu_items->item_make_custom = item;

	item = gtk_separator_menu_item_new();
	gtk_widget_show(item);
	gtk_container_add(GTK_CONTAINER(menu), item);

	// next error
	item = gtk_image_menu_item_new_with_mnemonic(_("_Next Error"));
	gtk_widget_show(item);
	GEANY_ADD_WIDGET_ACCEL(GEANY_KEYS_BUILD_NEXTERROR, item);
	gtk_container_add(GTK_CONTAINER(menu), item);
	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_build_next_error), NULL);
	menu_items->item_next_error = item;

	item = gtk_separator_menu_item_new();
	gtk_widget_show(item);
	gtk_container_add(GTK_CONTAINER(menu), item);
#endif

	// DVI view
#define LATEX_VIEW_DVI_LABEL _("View DVI File") // used later again
	item = gtk_image_menu_item_new_with_mnemonic(LATEX_VIEW_DVI_LABEL);
	gtk_widget_show(item);
	gtk_container_add(GTK_CONTAINER(menu), item);
	GEANY_ADD_WIDGET_ACCEL(GEANY_KEYS_BUILD_RUN, item);
	gtk_tooltips_set_tip(tooltips, item, _("Compile 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_execute_activate), GINT_TO_POINTER(LATEX_CMD_VIEW_DVI));
	menu_items->item_exec = item;

	// PDF view
	item = gtk_image_menu_item_new_with_mnemonic(_("View PDF File"));
	gtk_widget_show(item);
	gtk_container_add(GTK_CONTAINER(menu), item);
	GEANY_ADD_WIDGET_ACCEL(GEANY_KEYS_BUILD_RUN2, item);
	gtk_tooltips_set_tip(tooltips, item, _("Compile 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_execute_activate), GINT_TO_POINTER(LATEX_CMD_VIEW_PDF));
	menu_items->item_exec2 = item;

	// 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);
	GEANY_ADD_WIDGET_ACCEL(GEANY_KEYS_BUILD_OPTIONS, item);
	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_arguments_activate), filetypes[GEANY_FILETYPES_LATEX]);
	menu_items->item_set_args = item;

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

	menu_items->menu = menu;
	g_object_ref((gpointer)menu_items->menu);	// to hold it after removing
}


static void
on_includes_arguments_tex_dialog_response  (GtkDialog *dialog,
                                            gint response,
                                            gpointer user_data)
{
	filetype *ft = user_data;
	g_return_if_fail(ft != NULL);

	if (response == GTK_RESPONSE_ACCEPT)
	{
		const gchar *newstr;
		struct build_programs *programs = ft->programs;

		newstr = gtk_entry_get_text(
				GTK_ENTRY(lookup_widget(GTK_WIDGET(dialog), "tex_entry1")));
		if (! utils_str_equal(newstr, programs->compiler))
		{
			if (programs->compiler) g_free(programs->compiler);
			programs->compiler = g_strdup(newstr);
			programs->modified = TRUE;
		}
		newstr = gtk_entry_get_text(
				GTK_ENTRY(lookup_widget(GTK_WIDGET(dialog), "tex_entry2")));
		if (! utils_str_equal(newstr, programs->linker))
		{
			if (programs->linker) g_free(programs->linker);
			programs->linker = g_strdup(newstr);
			programs->modified = TRUE;
		}
		newstr = gtk_entry_get_text(
				GTK_ENTRY(lookup_widget(GTK_WIDGET(dialog), "tex_entry3")));
		if (! utils_str_equal(newstr, programs->run_cmd))
		{
			if (programs->run_cmd) g_free(programs->run_cmd);
			programs->run_cmd = g_strdup(newstr);
			programs->modified = TRUE;
		}
		newstr = gtk_entry_get_text(
				GTK_ENTRY(lookup_widget(GTK_WIDGET(dialog), "tex_entry4")));
		if (! utils_str_equal(newstr, programs->run_cmd2))
		{
			if (programs->run_cmd2) g_free(programs->run_cmd2);
			programs->run_cmd2 = g_strdup(newstr);
			programs->modified = TRUE;
		}
	}
}


static void show_includes_arguments_tex()
{
	GtkWidget *dialog, *label, *entries[4], *vbox, *table;
	gint idx = document_get_cur_idx();
	gint response;
	filetype *ft = NULL;

	if (DOC_IDX_VALID(idx)) ft = doc_list[idx].file_type;
	g_return_if_fail(ft != NULL);

	dialog = gtk_dialog_new_with_buttons(_("Set Arguments"), GTK_WINDOW(app->window),
										GTK_DIALOG_DESTROY_WITH_PARENT,
										GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
										GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
	vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog));
	gtk_widget_set_name(dialog, "GeanyDialog");

	label = gtk_label_new(_("Set programs and options for compiling and viewing (La)TeX files."));
	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
	gtk_container_add(GTK_CONTAINER(vbox), label);

	table = gtk_table_new(4, 2, FALSE);
	gtk_table_set_row_spacings(GTK_TABLE(table), 6);
	gtk_container_add(GTK_CONTAINER(vbox), table);

	// LaTeX -> DVI args
	if (ft->programs->compiler != NULL && ft->actions->can_compile)
	{
		label = gtk_label_new(_("DVI creation:"));
		gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
		gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1,
			GTK_FILL, GTK_FILL | GTK_EXPAND, 6, 0);

		entries[0] = gtk_entry_new();
		gtk_entry_set_width_chars(GTK_ENTRY(entries[0]), 30);
		if (ft->programs->compiler)
		{
			gtk_entry_set_text(GTK_ENTRY(entries[0]), ft->programs->compiler);
		}
		gtk_table_attach_defaults(GTK_TABLE(table), entries[0], 1, 2, 0, 1);
		g_object_set_data_full(G_OBJECT(dialog), "tex_entry1",
					gtk_widget_ref(entries[0]), (GDestroyNotify)gtk_widget_unref);
	}

	// LaTeX -> PDF args
	if (ft->programs->linker != NULL && ft->actions->can_link)
	{
		label = gtk_label_new(_("PDF creation:"));
		gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
		gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
			GTK_FILL, GTK_FILL | GTK_EXPAND, 6, 0);

		entries[1] = gtk_entry_new();
		gtk_entry_set_width_chars(GTK_ENTRY(entries[1]), 30);
		if (ft->programs->linker)
		{
			gtk_entry_set_text(GTK_ENTRY(entries[1]), ft->programs->linker);
		}
		gtk_table_attach_defaults(GTK_TABLE(table), entries[1], 1, 2, 1, 2);
		g_object_set_data_full(G_OBJECT(dialog), "tex_entry2",
					gtk_widget_ref(entries[1]), (GDestroyNotify)gtk_widget_unref);
	}

	// View LaTeX -> DVI args
	if (ft->programs->run_cmd != NULL)
	{
		label = gtk_label_new(_("DVI preview:"));
		gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
		gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3,
			GTK_FILL, GTK_FILL | GTK_EXPAND, 6, 0);

		entries[2] = gtk_entry_new();
		gtk_entry_set_width_chars(GTK_ENTRY(entries[2]), 30);
		if (ft->programs->run_cmd)
		{
			gtk_entry_set_text(GTK_ENTRY(entries[2]), ft->programs->run_cmd);
		}
		gtk_table_attach_defaults(GTK_TABLE(table), entries[2], 1, 2, 2, 3);
		g_object_set_data_full(G_OBJECT(dialog), "tex_entry3",
					gtk_widget_ref(entries[2]), (GDestroyNotify)gtk_widget_unref);
	}

	// View LaTeX -> PDF args
	if (ft->programs->run_cmd2 != NULL)
	{
		label = gtk_label_new(_("PDF preview:"));
		gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
		gtk_table_attach(GTK_TABLE(table), label, 0, 1, 3, 4,
			GTK_FILL, GTK_FILL | GTK_EXPAND, 6, 0);

		entries[3] = gtk_entry_new();
		gtk_entry_set_width_chars(GTK_ENTRY(entries[3]), 30);
		if (ft->programs->run_cmd2)
		{
			gtk_entry_set_text(GTK_ENTRY(entries[3]), ft->programs->run_cmd2);
		}
		gtk_table_attach_defaults(GTK_TABLE(table), entries[3], 1, 2, 3, 4);
		g_object_set_data_full(G_OBJECT(dialog), "tex_entry4",
					gtk_widget_ref(entries[3]), (GDestroyNotify)gtk_widget_unref);
	}

	label = gtk_label_new(_("%f will be replaced by the current filename, e.g. test_file.c\n"
							"%e will be replaced by the filename without extension, e.g. test_file"));
	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
	gtk_container_add(GTK_CONTAINER(vbox), label);

	gtk_widget_show_all(dialog);
	// run modally to prevent user changing idx filetype
	response = gtk_dialog_run(GTK_DIALOG(dialog));
	// call the callback manually
	on_includes_arguments_tex_dialog_response(GTK_DIALOG(dialog), response, ft);

	gtk_widget_destroy(dialog);
}


static void
on_includes_arguments_dialog_response  (GtkDialog *dialog,
                                        gint response,
                                        gpointer user_data)
{
	filetype *ft = user_data;

	g_return_if_fail(ft != NULL);

	if (response == GTK_RESPONSE_ACCEPT)
	{
		const gchar *newstr;
		struct build_programs *programs = ft->programs;

		if (ft->actions->can_compile)
		{
			newstr = gtk_entry_get_text(
					GTK_ENTRY(lookup_widget(GTK_WIDGET(dialog), "includes_entry1")));
			if (! utils_str_equal(newstr, programs->compiler))
			{
				if (programs->compiler) g_free(programs->compiler);
				programs->compiler = g_strdup(newstr);
				programs->modified = TRUE;
			}
		}
		if (ft->actions->can_link)
		{
			newstr = gtk_entry_get_text(
					GTK_ENTRY(lookup_widget(GTK_WIDGET(dialog), "includes_entry2")));
			if (! utils_str_equal(newstr, programs->linker))
			{
				if (programs->linker) g_free(programs->linker);
				programs->linker = g_strdup(newstr);
				programs->modified = TRUE;
			}
		}
		if (ft->actions->can_exec)
		{
			newstr = gtk_entry_get_text(
					GTK_ENTRY(lookup_widget(GTK_WIDGET(dialog), "includes_entry3")));
			if (! utils_str_equal(newstr, programs->run_cmd))
			{
				if (programs->run_cmd) g_free(programs->run_cmd);
				programs->run_cmd = g_strdup(newstr);
				programs->modified = TRUE;
			}
		}
	}
}


static void show_includes_arguments_gen()
{
	GtkWidget *dialog, *label, *entries[3], *vbox;
	GtkWidget *ft_table = NULL;
	gint row = 0;
	gint idx = document_get_cur_idx();
	gint response;
	filetype *ft = NULL;

	if (DOC_IDX_VALID(idx)) ft = doc_list[idx].file_type;
	g_return_if_fail(ft != NULL);

	dialog = gtk_dialog_new_with_buttons(_("Set Includes and Arguments"), GTK_WINDOW(app->window),
										GTK_DIALOG_DESTROY_WITH_PARENT,
										GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
										GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
	vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog));
	gtk_widget_set_name(dialog, "GeanyDialog");

	label = gtk_label_new(_("Set the commands for building and running programs."));
	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
	gtk_container_add(GTK_CONTAINER(vbox), label);

	if (ft->actions->can_compile || ft->actions->can_link || ft->actions->can_exec)
	{
		GtkWidget *align, *frame;
		// in-dialog heading for the "Set Includes and Arguments" dialog
		gchar *frame_title = g_strdup_printf(_("%s commands"), ft->title);

		frame = ui_frame_new_with_alignment(frame_title, &align);
		gtk_container_add(GTK_CONTAINER(vbox), frame);
		g_free(frame_title);

		ft_table = gtk_table_new(3, 2, FALSE);
		gtk_table_set_row_spacings(GTK_TABLE(ft_table), 6);
		gtk_container_add(GTK_CONTAINER(align), ft_table);
		row = 0;
	}

	// include-args
	if (ft->actions->can_compile)
	{
		label = gtk_label_new(_("Compile:"));
		gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
		gtk_table_attach(GTK_TABLE(ft_table), label, 0, 1, row, row + 1,
			GTK_FILL, GTK_FILL | GTK_EXPAND, 6, 0);

		entries[0] = gtk_entry_new();
		gtk_entry_set_width_chars(GTK_ENTRY(entries[0]), 30);
		if (ft->programs->compiler)
		{
			gtk_entry_set_text(GTK_ENTRY(entries[0]), ft->programs->compiler);
		}
		gtk_table_attach_defaults(GTK_TABLE(ft_table), entries[0], 1, 2, row, row + 1);
		row++;

		g_object_set_data_full(G_OBJECT(dialog), "includes_entry1",
					gtk_widget_ref(entries[0]), (GDestroyNotify)gtk_widget_unref);
	}

	// lib-args
	if (ft->actions->can_link)
	{
		label = gtk_label_new(_("Build:"));
		gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
		gtk_table_attach(GTK_TABLE(ft_table), label, 0, 1, row, row + 1,
			GTK_FILL, GTK_FILL | GTK_EXPAND, 6, 0);

		entries[1] = gtk_entry_new();
		gtk_entry_set_width_chars(GTK_ENTRY(entries[1]), 30);
		if (ft->programs->linker)
		{
			gtk_entry_set_text(GTK_ENTRY(entries[1]), ft->programs->linker);
		}
		gtk_table_attach_defaults(GTK_TABLE(ft_table), entries[1], 1, 2, row, row + 1);
		row++;

		g_object_set_data_full(G_OBJECT(dialog), "includes_entry2",
					gtk_widget_ref(entries[1]), (GDestroyNotify)gtk_widget_unref);
	}

	// program-args
	if (ft->actions->can_exec)
	{
		label = gtk_label_new(_("Execute:"));
		gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
		gtk_table_attach(GTK_TABLE(ft_table), label, 0, 1, row, row + 1,
			GTK_FILL, GTK_FILL | GTK_EXPAND, 6, 0);

		entries[2] = gtk_entry_new();
		gtk_entry_set_width_chars(GTK_ENTRY(entries[2]), 30);
		if (ft->programs->run_cmd)
		{
			gtk_entry_set_text(GTK_ENTRY(entries[2]), ft->programs->run_cmd);
		}
		gtk_table_attach_defaults(GTK_TABLE(ft_table), entries[2], 1, 2, row, row + 1);
		row++;

		g_object_set_data_full(G_OBJECT(dialog), "includes_entry3",
						gtk_widget_ref(entries[2]), (GDestroyNotify)gtk_widget_unref);

		// disable the run command if there is a valid project run command set
		if (app->project && NZV(app->project->run_cmd))
			gtk_widget_set_sensitive(entries[2], FALSE);
	}

	label = gtk_label_new(_("%f will be replaced by the current filename, e.g. test_file.c\n"
							"%e will be replaced by the filename without extension, e.g. test_file"));
	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
	gtk_container_add(GTK_CONTAINER(vbox), label);

	gtk_widget_show_all(dialog);
	// run modally to prevent user changing idx filetype
	response = gtk_dialog_run(GTK_DIALOG(dialog));
	// call the callback manually
	on_includes_arguments_dialog_response(GTK_DIALOG(dialog), response, ft);

	gtk_widget_destroy(dialog);
}


static void
on_build_arguments_activate            (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
	if (user_data && FILETYPE_ID((filetype*) user_data) == GEANY_FILETYPES_LATEX)
		show_includes_arguments_tex();
	else
		show_includes_arguments_gen();
}


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*
}


/* Call this whenever build menu items need to be enabled/disabled.
 * Uses current document (if there is one) when idx == -1 */
void build_menu_update(gint idx)
{
	filetype *ft;
	gboolean have_path, can_build, can_make, can_run, can_stop, can_set_args, have_errors;
	BuildMenuItems *menu_items;

	if (idx == -1)
		idx = document_get_cur_idx();
	if (idx == -1 ||
		(FILETYPE_ID(doc_list[idx].file_type) == GEANY_FILETYPES_ALL &&
			doc_list[idx].file_name == 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(widgets.compile_button, FALSE);
		gtk_widget_set_sensitive(widgets.run_button, FALSE);
		return;
	}
	else
		gtk_widget_set_sensitive(lookup_widget(app->window, "menu_build1"), TRUE);

	ft = doc_list[idx].file_type;
	g_return_if_fail(ft != NULL);

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

	menu_items = build_get_menu_items(ft->id);
	/* Note: don't remove the submenu first because it can now cause an X hang if
	 * the menu is already open when called from build_exit_cb(). */
	gtk_menu_item_set_submenu(GTK_MENU_ITEM(lookup_widget(app->window, "menu_build1")),
		menu_items->menu);

	have_path = (doc_list[idx].file_name != NULL);

	can_make = have_path && build_info.pid <= (GPid) 1;

	// disable compile and link for C/C++ header files
	if (ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP)
		can_build = can_make && ! is_c_header(doc_list[idx].file_name);
	else
		can_build = can_make;

	if (menu_items->item_compile)
		gtk_widget_set_sensitive(menu_items->item_compile, can_build && ft->actions->can_compile);
	if (menu_items->item_link)
		gtk_widget_set_sensitive(menu_items->item_link, can_build && ft->actions->can_link);
	if (menu_items->item_make_all)
		gtk_widget_set_sensitive(menu_items->item_make_all, can_make);
	if (menu_items->item_make_custom)
		gtk_widget_set_sensitive(menu_items->item_make_custom, can_make);
	if (menu_items->item_make_object)
		gtk_widget_set_sensitive(menu_items->item_make_object, can_make);

	if (app->project && NZV(app->project->run_cmd))
		can_run = have_path;	// for now run is disabled for all untitled files
	else
		can_run = have_path && ft->actions->can_exec;

	can_stop = run_info.pid > (GPid) 1;
	can_run &= ! can_stop;

	/* item_exec is enabled for both run and stop commands */
	if (menu_items->item_exec)
		gtk_widget_set_sensitive(menu_items->item_exec, can_run || can_stop);
	/* item_exec2 is disabled if there's a running process already */
	if (menu_items->item_exec2)
		gtk_widget_set_sensitive(menu_items->item_exec2, can_run);

	can_set_args =
		((ft->actions->can_compile ||
		ft->actions->can_link ||
		ft->actions->can_exec) &&
		FILETYPE_ID(ft) != GEANY_FILETYPES_ALL);
	if (menu_items->item_set_args)
		gtk_widget_set_sensitive(menu_items->item_set_args, can_set_args);

	gtk_widget_set_sensitive(widgets.compile_button, can_build && ft->actions->can_compile);
	gtk_widget_set_sensitive(widgets.run_button, can_run || can_stop);

	// show the stop command if a program is running, otherwise show run command
	set_stop_button(can_stop);

	// simply enable next error command if the compiler window has any items
	have_errors = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(msgwindow.store_compiler),
		NULL) > 0;
	if (menu_items->item_next_error)
		gtk_widget_set_sensitive(menu_items->item_next_error, have_errors);
}


// Call build_menu_update() instead of calling this directly.
static void set_stop_button(gboolean stop)
{
	GtkStockItem sitem;
	GtkWidget *menuitem =
		build_get_menu_items(run_info.file_type_id)->item_exec;

	if (stop && utils_str_equal(
		gtk_tool_button_get_stock_id(GTK_TOOL_BUTTON(widgets.run_button)), "gtk-stop")) return;
	if (! stop && utils_str_equal(
		gtk_tool_button_get_stock_id(GTK_TOOL_BUTTON(widgets.run_button)), "gtk-execute")) return;

	// use the run button also as stop button
	if (stop)
	{
		gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(widgets.run_button), "gtk-stop");

		if (menuitem != NULL)
		{
			gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
							gtk_image_new_from_stock("gtk-stop", GTK_ICON_SIZE_MENU));
			gtk_stock_lookup("gtk-stop", &sitem);
			gtk_label_set_text_with_mnemonic(GTK_LABEL(gtk_bin_get_child(GTK_BIN(menuitem))),
						sitem.label);
		}
	}
	else
	{
		gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(widgets.run_button), "gtk-execute");

		if (menuitem != NULL)
		{
			// LaTeX hacks ;-(
			if (run_info.file_type_id == GEANY_FILETYPES_LATEX)
			{
				gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
							gtk_image_new_from_stock("gtk-find", GTK_ICON_SIZE_MENU));
				gtk_label_set_text_with_mnemonic(GTK_LABEL(gtk_bin_get_child(GTK_BIN(menuitem))),
						LATEX_VIEW_DVI_LABEL);
			}
			else
			{
				gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
							gtk_image_new_from_stock("gtk-execute", GTK_ICON_SIZE_MENU));

				gtk_stock_lookup("gtk-execute", &sitem);
				gtk_label_set_text_with_mnemonic(GTK_LABEL(gtk_bin_get_child(GTK_BIN(menuitem))),
							sitem.label);
			}
		}
	}
}


/* Creates the relevant build menu if necessary.
 * If filetype_idx is -1, the current filetype is used, or GEANY_FILETYPES_ALL */
BuildMenuItems *build_get_menu_items(gint filetype_idx)
{
	BuildMenuItems *items;

	if (filetype_idx == -1)
	{
		gint idx = document_get_cur_idx();
		filetype *ft = NULL;

		if (DOC_IDX_VALID(idx))
			ft = doc_list[idx].file_type;
		filetype_idx = FILETYPE_ID(ft);
	}

	if (filetype_idx == GEANY_FILETYPES_LATEX)
	{
		items = &latex_menu_items;
		if (items->menu == NULL)
			create_build_menu_tex(items);
	}
	else
	{
		items = &default_menu_items;
		if (items->menu == NULL)
			create_build_menu_gen(items);
	}
	return items;
}


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

	if (! DOC_IDX_VALID(idx)) return;

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

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


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

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

	switch (GPOINTER_TO_INT(user_data))
	{
		case LATEX_CMD_TO_DVI:
		case LATEX_CMD_TO_PDF:
			build_compile_tex_file(idx, GPOINTER_TO_INT(user_data)); break;
		case LATEX_CMD_VIEW_DVI:
		case LATEX_CMD_VIEW_PDF:
			build_view_tex_file(idx, GPOINTER_TO_INT(user_data)); break;
	}
}


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

	if (! DOC_IDX_VALID(idx)) return;

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

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


static 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:
		{
			if (doc_list[idx].changed) document_save_file(idx, FALSE);

			build_make_file(idx, build_opts);
		}
	}
}


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

	// make the process "stopable"
	if (run_info.pid > (GPid) 1)
	{
		// on Windows there is no PID returned (resp. it is a handle), currently unsupported
#ifndef G_OS_WIN32
		kill_process(&run_info.pid);
#endif
		return;
	}

	if (doc_list[idx].file_type->id == GEANY_FILETYPES_LATEX)
	{	// run LaTeX file
		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)
	{	// run HTML file
		gchar *uri = g_strconcat("file:///", g_path_skip_root(doc_list[idx].file_name), NULL);
		utils_start_browser(uri);
		g_free(uri);
	}
	else
	{	// run everything 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)
		{
			msgwin_status_add(_("Failed to execute the terminal program"));
		}
	}
}


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();

		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)));

		build_make_file(idx, GBO_MAKE_CUSTOM);
	}
	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);
}


#ifndef G_OS_WIN32
static void kill_process(GPid *pid)
{
	/* SIGQUIT is not the best signal to use because it causes a core dump (this should not
	 * perforce necessary for just killing a process). But we must use a signal which we can
	 * ignore because the main process get it too, it is declared to ignore in main.c. */

	gint result;

	g_return_if_fail(*pid > 1);

	result = kill(*pid, SIGQUIT);

	if (result != 0)
		msgwin_status_add(_("Process could not be stopped (%s)."), g_strerror(errno));
	else
	{
		*pid = 0;
		build_menu_update(-1);
	}
}
#endif


static void
on_build_next_error                    (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
	if (ui_tree_view_find_next(GTK_TREE_VIEW(msgwindow.tree_compiler),
		msgwin_goto_compiler_file_line))
	{
		gtk_notebook_set_current_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_COMPILER);
	}
	else
		ui_set_statusbar(_("No more build errors."));
}


void build_init()
{
	widgets.compile_button = lookup_widget(app->window, "toolbutton13");
	widgets.run_button = lookup_widget(app->window, "toolbutton26");

#ifdef G_OS_WIN32
	// hide build support items, at least until they are available for Windows
	gtk_widget_hide(widgets.compile_button);
	{
		GtkWidget *compiler_tab;
		compiler_tab = gtk_notebook_get_tab_label(GTK_NOTEBOOK(msgwindow.notebook),
			gtk_notebook_get_nth_page(GTK_NOTEBOOK(msgwindow.notebook), MSG_COMPILER));
		gtk_widget_set_sensitive(compiler_tab, FALSE);
	}
#endif
}
