/* * build.c - this file is part of Geany, a fast and lightweight IDE * * Copyright 2005-2012 Enrico Tröger * Copyright 2006-2012 Nick Treleaven * Copyright 2009 Lex Trotman * * 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. */ /* * Build commands and menu items. */ /* TODO: tidy code: * Use intermediate pointers for common subexpressions. * Replace defines with enums. * Other TODOs in code. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "app.h" #include "build.h" #include "dialogs.h" #include "document.h" #include "filetypesprivate.h" #include "geanymenubuttonaction.h" #include "geanyobject.h" #include "keybindingsprivate.h" #include "msgwindow.h" #include "prefs.h" #include "projectprivate.h" #include "sciwrappers.h" #include "spawn.h" #include "support.h" #include "toolbar.h" #include "ui_utils.h" #include "utils.h" #include "vte.h" #include "win32.h" #include "gtkcompat.h" #include #include #include #include #include #include /* Number of editor indicators to draw - limited as this can affect performance */ #define GEANY_BUILD_ERR_HIGHLIGHT_MAX 50 GeanyBuildInfo build_info = {GEANY_GBG_FT, 0, 0, NULL, GEANY_FILETYPES_NONE, NULL, 0}; static gchar *current_dir_entered = NULL; typedef struct RunInfo { GPid pid; gint file_type_id; } RunInfo; static RunInfo *run_info; #ifdef G_OS_WIN32 static const gchar RUN_SCRIPT_CMD[] = "geany_run_script_XXXXXX.bat"; #else static const gchar RUN_SCRIPT_CMD[] = "geany_run_script_XXXXXX.sh"; #endif /* pack group (<8) and command (<32) into a user_data pointer */ #define GRP_CMD_TO_POINTER(grp, cmd) GUINT_TO_POINTER((((grp)&7) << 5) | ((cmd)&0x1f)) #define GBO_TO_POINTER(gbo) (GRP_CMD_TO_POINTER(GBO_TO_GBG(gbo), GBO_TO_CMD(gbo))) #define GPOINTER_TO_CMD(gptr) (GPOINTER_TO_UINT(gptr)&0x1f) #define GPOINTER_TO_GRP(gptr) ((GPOINTER_TO_UINT(gptr)&0xe0) >> 5) static gpointer last_toolbutton_action = GBO_TO_POINTER(GEANY_GBO_BUILD); static BuildMenuItems menu_items = {NULL, {NULL, NULL, NULL, NULL}}; static struct { GtkAction *run_action; GtkAction *compile_action; GtkAction *build_action; GtkWidget *toolmenu; GtkWidget *toolitem_build; GtkWidget *toolitem_make_all; GtkWidget *toolitem_make_custom; GtkWidget *toolitem_make_object; GtkWidget *toolitem_set_args; } widgets; static guint build_groups_count[GEANY_GBG_COUNT] = { 3, 4, 2 }; static guint build_items_count = 9; static void build_exit_cb(GPid pid, gint status, gpointer user_data); static void build_iofunc(GString *string, GIOCondition condition, gpointer data); static gchar *build_create_shellscript(const gchar *working_dir, const gchar *cmd, gboolean autoclose, GError **error); static void build_spawn_cmd(GeanyDocument *doc, const gchar *cmd, const gchar *dir); static void set_stop_button(gboolean stop); static void run_exit_cb(GPid child_pid, gint status, gpointer user_data); static void on_set_build_commands_activate(GtkWidget *w, gpointer u); static void on_build_next_error(GtkWidget *menuitem, gpointer user_data); static void on_build_previous_error(GtkWidget *menuitem, gpointer user_data); static void kill_process(GPid *pid); static void show_build_result_message(gboolean failure); static void process_build_output_line(gchar *msg, gint color); static void show_build_commands_dialog(void); static void on_build_menu_item(GtkWidget *w, gpointer user_data); void build_finalize(void) { g_free(build_info.dir); g_free(build_info.custom_target); if (menu_items.menu != NULL && GTK_IS_WIDGET(menu_items.menu)) gtk_widget_destroy(menu_items.menu); } /* note: copied from keybindings.c, may be able to go away */ static void add_menu_accel(GeanyKeyGroup *group, guint kb_id, GtkAccelGroup *accel_group, GtkWidget *menuitem) { GeanyKeyBinding *kb = keybindings_get_item(group, kb_id); if (kb->key != 0) gtk_widget_add_accelerator(menuitem, "activate", accel_group, kb->key, kb->mods, GTK_ACCEL_VISIBLE); } /* convenience routines to access parts of GeanyBuildCommand */ static gchar *id_to_str(GeanyBuildCommand *bc, gint id) { switch (id) { case GEANY_BC_LABEL: return bc->label; case GEANY_BC_COMMAND: return bc->command; case GEANY_BC_WORKING_DIR: return bc->working_dir; } g_assert(0); return NULL; } static void set_command(GeanyBuildCommand *bc, gint id, gchar *str) { switch (id) { case GEANY_BC_LABEL: SETPTR(bc->label, str); break; case GEANY_BC_COMMAND: SETPTR(bc->command, str); break; case GEANY_BC_WORKING_DIR: SETPTR(bc->working_dir, str); break; default: g_assert(0); } } static const gchar *config_keys[GEANY_BC_CMDENTRIES_COUNT] = { "LB", /* label */ "CM", /* command */ "WD" /* working directory */ }; /*----------------------------------------------------- * * Execute commands and handle results * *-----------------------------------------------------*/ /* the various groups of commands not in the filetype struct */ static GeanyBuildCommand *ft_def = NULL; static GeanyBuildCommand *non_ft_proj = NULL; static GeanyBuildCommand *non_ft_pref = NULL; static GeanyBuildCommand *non_ft_def = NULL; static GeanyBuildCommand *exec_proj = NULL; static GeanyBuildCommand *exec_pref = NULL; static GeanyBuildCommand *exec_def = NULL; /* and the regexen not in the filetype structure */ static gchar *regex_pref = NULL; /* project non-fileregex string */ static gchar *regex_proj = NULL; /* control if build commands are printed by get_build_cmd, for debug purposes only*/ #ifndef PRINTBUILDCMDS #define PRINTBUILDCMDS FALSE #endif static gboolean printbuildcmds = PRINTBUILDCMDS; /* for debug only, print the commands structures in priority order */ static void printfcmds(void) { #if 0 GeanyBuildCommand **cl[GEANY_GBG_COUNT][GEANY_BCS_COUNT] = { /* GEANY_BCS_DEF, GEANY_BCS_FT, GEANY_BCS_HOME_FT, GEANY_BCS_PREF, * GEANY_BCS_FT_PROJ, GEANY_BCS_PROJ */ { &ft_def, NULL, NULL, NULL, NULL, NULL }, { &non_ft_def, NULL, NULL, &non_ft_pref, NULL, &non_ft_proj }, { &exec_def, NULL, NULL, &exec_pref, NULL, &exec_proj } }; GeanyFiletype *ft = NULL; GeanyDocument *doc; gint i, j, k, l, m; enum GeanyBuildCmdEntries n; gint cc[GEANY_BCS_COUNT]; gchar c; doc = document_get_current(); if (doc != NULL) ft = doc->file_type; if (ft != NULL) { printf("filetype %s\n",ft->name); cl[GEANY_GBG_FT][GEANY_BCS_FT] = &(ft->priv->filecmds); cl[GEANY_GBG_FT][GEANY_BCS_HOME_FT] = &(ft->priv->homefilecmds); cl[GEANY_GBG_FT][GEANY_BCS_PROJ] = &(ft->priv->projfilecmds); cl[GEANY_GBG_NON_FT][GEANY_BCS_FT] = &(ft->priv->ftdefcmds); cl[GEANY_GBG_EXEC][GEANY_BCS_FT] = &(ft->priv->execcmds); cl[GEANY_GBG_EXEC][GEANY_BCS_HOME_FT] = &(ft->priv->homeexeccmds); cl[GEANY_GBG_EXEC][GEANY_BCS_PROJ_FT] = &(ft->priv->projexeccmds); } for (i = 0; i < GEANY_BCS_COUNT; ++i) { m = 1; for (j = 0; j < GEANY_GBG_COUNT; ++j) { for (k = 0; k < build_groups_count[j]; ++k) if (cl[j][i] != NULL && *(cl[j][i]) != NULL && (*(cl[j][i]))[k].exists) { for (n = 0; n < GEANY_BC_CMDENTRIES_COUNT; n++) { if ((*(cl[j][i]))[k].entries[n] != NULL && (l = strlen((*(cl[j][i]))[k].entries[n])) > m) { m = l; } } } } cc[i] = m; } for (i = 0; i < GEANY_GBG_COUNT; ++i) { for (k = 0; k < build_groups_count[i]; ++k) { for (l = 0; l < 2; ++l) { c = ' '; for (j = 0; j < GEANY_BCS_COUNT; ++j) { if (cl[i][j] != NULL && *(cl[i][j]) != NULL && (*(cl[i][j]))[k].exists) { for (n = 0; n < GEANY_BC_CMDENTRIES_COUNT; n++) { if ((*(cl[i][j]))[k].entries[i] != NULL) printf("%c %*.*s",c,cc[j],cc[j],(*(cl[i][j]))[k].entries[i]); else printf("%c %*.*s",c,cc[j],cc[j]," "); } } else printf("%c %*.*s",c,cc[j],cc[j]," "); c = ','; } printf("\n"); } } printf("\n"); } #endif } /* macros to save typing and make the logic visible */ #define return_cmd_if(src, cmds)\ if (cmds != NULL && cmds[cmdindex].exists && below>src)\ { \ *fr=src; \ if (printbuildcmds) \ printf("cmd[%u,%u]=%u\n",cmdgrp,cmdindex,src); \ return &(cmds[cmdindex]); \ } #define return_ft_cmd_if(src, cmds)\ if (ft != NULL && ft->priv->cmds != NULL \ && ft->priv->cmds[cmdindex].exists && below>src)\ { \ *fr=src; \ if (printbuildcmds) \ printf("cmd[%u,%u]=%u\n",cmdgrp,cmdindex,src); \ return &(ft->priv->cmds[cmdindex]); \ } /* get the next lowest command taking priority into account */ static GeanyBuildCommand *get_next_build_cmd(GeanyDocument *doc, guint cmdgrp, guint cmdindex, guint below, guint *from) { /* Note: parameter below used in macros above */ GeanyFiletype *ft = NULL; guint sink, *fr = &sink; g_return_val_if_fail(doc == NULL || doc->is_valid, NULL); if (printbuildcmds) printfcmds(); if (cmdgrp >= GEANY_GBG_COUNT) return NULL; if (from != NULL) fr = from; if (doc == NULL) doc = document_get_current(); if (doc != NULL) ft = doc->file_type; switch (cmdgrp) { case GEANY_GBG_FT: /* order proj ft, home ft, ft, defft */ if (ft != NULL) { return_ft_cmd_if(GEANY_BCS_PROJ, projfilecmds); return_ft_cmd_if(GEANY_BCS_PREF, homefilecmds); return_ft_cmd_if(GEANY_BCS_FT, filecmds); } return_cmd_if(GEANY_BCS_DEF, ft_def); break; case GEANY_GBG_NON_FT: /* order proj, pref, def */ return_cmd_if(GEANY_BCS_PROJ, non_ft_proj); return_cmd_if(GEANY_BCS_PREF, non_ft_pref); return_ft_cmd_if(GEANY_BCS_FT, ftdefcmds); return_cmd_if(GEANY_BCS_DEF, non_ft_def); break; case GEANY_GBG_EXEC: /* order proj, proj ft, pref, home ft, ft, def */ return_cmd_if(GEANY_BCS_PROJ, exec_proj); return_ft_cmd_if(GEANY_BCS_PROJ_FT, projexeccmds); return_cmd_if(GEANY_BCS_PREF, exec_pref); return_ft_cmd_if(GEANY_BCS_FT, homeexeccmds); return_ft_cmd_if(GEANY_BCS_FT, execcmds); return_cmd_if(GEANY_BCS_DEF, exec_def); break; default: break; } return NULL; } /* shortcut to start looking at the top */ static GeanyBuildCommand *get_build_cmd(GeanyDocument *doc, guint grp, guint cmdindex, guint *from) { return get_next_build_cmd(doc, grp, cmdindex, GEANY_BCS_COUNT, from); } #define return_nonblank_regex(src, ptr)\ if (!EMPTY(ptr)) \ { *fr = (src); return &(ptr); } /* like get_build_cmd, but for regexen, used by filetypes */ gchar **build_get_regex(GeanyBuildGroup grp, GeanyFiletype *ft, guint *from) { guint sink, *fr = &sink; if (from != NULL) fr = from; if (grp == GEANY_GBG_FT) { if (ft == NULL) { GeanyDocument *doc = document_get_current(); if (doc != NULL) ft = doc->file_type; } if (ft == NULL) return NULL; return_nonblank_regex(GEANY_BCS_PROJ, ft->priv->projerror_regex_string); return_nonblank_regex(GEANY_BCS_HOME_FT, ft->priv->homeerror_regex_string); return_nonblank_regex(GEANY_BCS_FT, ft->error_regex_string); } else if (grp == GEANY_GBG_NON_FT) { return_nonblank_regex(GEANY_BCS_PROJ, regex_proj); return_nonblank_regex(GEANY_BCS_PREF, regex_pref); } return NULL; } static GeanyBuildCommand **get_build_group_pointer(const GeanyBuildSource src, const GeanyBuildGroup grp) { GeanyDocument *doc; GeanyFiletype *ft = NULL; switch (grp) { case GEANY_GBG_FT: if ((doc = document_get_current()) == NULL) return NULL; if ((ft = doc->file_type) == NULL) return NULL; switch (src) { case GEANY_BCS_DEF: return &(ft->priv->ftdefcmds); case GEANY_BCS_FT: return &(ft->priv->filecmds); case GEANY_BCS_HOME_FT: return &(ft->priv->homefilecmds); case GEANY_BCS_PREF: return &(ft->priv->homefilecmds); case GEANY_BCS_PROJ: return &(ft->priv->projfilecmds); default: return NULL; } break; case GEANY_GBG_NON_FT: switch (src) { case GEANY_BCS_DEF: return &(non_ft_def); case GEANY_BCS_PREF: return &(non_ft_pref); case GEANY_BCS_PROJ: return &(non_ft_proj); default: return NULL; } break; case GEANY_GBG_EXEC: if ((doc = document_get_current()) != NULL) ft = doc->file_type; switch (src) { case GEANY_BCS_DEF: return &(exec_def); case GEANY_BCS_FT: return ft ? &(ft->priv->execcmds): NULL; case GEANY_BCS_HOME_FT: return ft ? &(ft->priv->homeexeccmds): NULL; case GEANY_BCS_PROJ_FT: return ft ? &(ft->priv->projexeccmds): NULL; case GEANY_BCS_PREF: return &(exec_pref); case GEANY_BCS_PROJ: return &(exec_proj); default: return NULL; } break; default: return NULL; } } /* get pointer to the command group array */ static GeanyBuildCommand *get_build_group(const GeanyBuildSource src, const GeanyBuildGroup grp) { GeanyBuildCommand **g = get_build_group_pointer(src, grp); if (g == NULL) return NULL; return *g; } /** Remove the specified Build menu item. * * Makes the specified menu item configuration no longer exist. This * is different to setting fields to blank because the menu item * will be deleted from the configuration file on saving * (except the system filetypes settings @see Build Menu Configuration * section of the Manual). * * @param src the source of the menu item to remove. * @param grp the group of the command to remove. * @param cmd the index (from 0) of the command within the group. A negative * value will remove the whole group. * * If any parameter is out of range does nothing. * * Updates the menu. * **/ GEANY_API_SYMBOL void build_remove_menu_item(const GeanyBuildSource src, const GeanyBuildGroup grp, const gint cmd) { GeanyBuildCommand *bc; guint i; bc = get_build_group(src, grp); if (bc == NULL) return; if (cmd < 0) { for (i = 0; i < build_groups_count[grp]; ++i) bc[i].exists = FALSE; } else if ((guint) cmd < build_groups_count[grp]) bc[cmd].exists = FALSE; } /* * Get the @a GeanyBuildCommand structure for the specified Build menu item. * * Get the command for any menu item specified by @a src, @a grp and @a cmd even if it is * hidden by higher priority commands. * * @param src the source of the specified menu item. * @param grp the group of the specified menu item. * @param cmd the index of the command within the group. * * @return a pointer to the @a GeanyBuildCommand structure or @c NULL if it doesn't exist. * This is a pointer to an internal structure and must not be freed. * * @see build_menu_update **/ GeanyBuildCommand *build_get_menu_item(GeanyBuildSource src, GeanyBuildGroup grp, guint cmd) { GeanyBuildCommand *bc; g_return_val_if_fail(src < GEANY_BCS_COUNT, NULL); g_return_val_if_fail(grp < GEANY_GBG_COUNT, NULL); g_return_val_if_fail(cmd < build_groups_count[grp], NULL); bc = get_build_group(src, grp); if (bc == NULL) return NULL; return &(bc[cmd]); } /** Get the string for the menu item field. * * Get the current highest priority command specified by @a grp and @a cmd. This is the one * that the menu item will use if activated. * * @param grp the group of the specified menu item. * @param cmd the index of the command within the group. * @param fld the field to return * * @return @nullable a pointer to the constant string or @c NULL if it doesn't exist. * This is a pointer to an internal structure and must not be freed. * **/ GEANY_API_SYMBOL const gchar *build_get_current_menu_item(const GeanyBuildGroup grp, const guint cmd, const GeanyBuildCmdEntries fld) { GeanyBuildCommand *c; gchar *str = NULL; g_return_val_if_fail(grp < GEANY_GBG_COUNT, NULL); g_return_val_if_fail(fld < GEANY_BC_CMDENTRIES_COUNT, NULL); g_return_val_if_fail(cmd < build_groups_count[grp], NULL); c = get_build_cmd(NULL, grp, cmd, NULL); if (c == NULL) return NULL; switch (fld) { case GEANY_BC_COMMAND: str = c->command; break; case GEANY_BC_LABEL: str = c->label; break; case GEANY_BC_WORKING_DIR: str = c->working_dir; break; default: break; } return str; } /** Set the string for the menu item field. * * Set the specified field of the command specified by @a src, @a grp and @a cmd. * * @param src the source of the menu item * @param grp the group of the specified menu item. * @param cmd the index of the menu item within the group. * @param fld the field in the menu item command to set * @param val the value to set the field to, is copied * **/ GEANY_API_SYMBOL void build_set_menu_item(const GeanyBuildSource src, const GeanyBuildGroup grp, const guint cmd, const GeanyBuildCmdEntries fld, const gchar *val) { GeanyBuildCommand **g; g_return_if_fail(src < GEANY_BCS_COUNT); g_return_if_fail(grp < GEANY_GBG_COUNT); g_return_if_fail(fld < GEANY_BC_CMDENTRIES_COUNT); g_return_if_fail(cmd < build_groups_count[grp]); g = get_build_group_pointer(src, grp); if (g == NULL) return; if (*g == NULL ) { *g = g_new0(GeanyBuildCommand, build_groups_count[grp]); } switch (fld) { case GEANY_BC_COMMAND: SETPTR((*g)[cmd].command, g_strdup(val)); (*g)[cmd].exists = TRUE; break; case GEANY_BC_LABEL: SETPTR((*g)[cmd].label, g_strdup(val)); (*g)[cmd].exists = TRUE; break; case GEANY_BC_WORKING_DIR: SETPTR((*g)[cmd].working_dir, g_strdup(val)); (*g)[cmd].exists = TRUE; break; default: break; } build_menu_update(NULL); } /** Activate the menu item. * * Activate the menu item specified by @a grp and @a cmd. * * @param grp the group of the specified menu item. * @param cmd the index of the command within the group. * **/ GEANY_API_SYMBOL void build_activate_menu_item(const GeanyBuildGroup grp, const guint cmd) { on_build_menu_item(NULL, GRP_CMD_TO_POINTER(grp, cmd)); } /* Clear all error indicators in all documents. */ static void clear_all_errors(void) { guint i; foreach_document(i) { editor_indicator_clear_errors(documents[i]->editor); } } /* Replaces occurrences of %e and %p with the appropriate filenames and * %l with current line number. %d and %p replacements should be in UTF8 */ static gchar *build_replace_placeholder(const GeanyDocument *doc, const gchar *src) { GString *stack; gchar *replacement; gchar *executable = NULL; gint line_num; g_return_val_if_fail(doc == NULL || doc->is_valid, NULL); stack = g_string_new(src); if (doc != NULL && doc->file_name != NULL) { /* replace %f with the filename (including extension) */ replacement = g_path_get_basename(doc->file_name); utils_string_replace_all(stack, "%f", replacement); g_free(replacement); /* replace %d with the absolute path of the dir of the current file */ replacement = g_path_get_dirname(doc->file_name); utils_string_replace_all(stack, "%d", replacement); g_free(replacement); /* replace %e with the filename (excluding extension) */ executable = utils_remove_ext_from_filename(doc->file_name); replacement = g_path_get_basename(executable); utils_string_replace_all(stack, "%e", replacement); g_free(replacement); /* replace %l with the current 1-based line number */ line_num = sci_get_current_line(doc->editor->sci) + 1; replacement = g_strdup_printf("%i", line_num); utils_string_replace_all(stack, "%l", replacement); g_free(replacement); } /* replace %p with the current project's (absolute) base directory */ replacement = NULL; /* prevent double free if no replacement found */ if (app->project) { replacement = project_get_base_path(); } else if (strstr(stack->str, "%p")) { /* fall back to %d */ ui_set_statusbar(FALSE, _("failed to substitute %%p, no project active")); if (doc != NULL && doc->file_name != NULL) replacement = g_path_get_dirname(doc->file_name); } utils_string_replace_all(stack, "%p", replacement); g_free(replacement); g_free(executable); return g_string_free(stack, FALSE); /* don't forget to free src also if needed */ } /* dir is the UTF-8 working directory to run cmd in. It can be NULL to use the * idx document directory */ static void build_spawn_cmd(GeanyDocument *doc, const gchar *cmd, const gchar *dir) { GError *error = NULL; gchar *argv[] = { "/bin/sh", "-c", NULL, NULL }; gchar *working_dir; gchar *utf8_working_dir; gchar *cmd_string; g_return_if_fail(doc == NULL || doc->is_valid); if ((doc == NULL || EMPTY(doc->file_name)) && EMPTY(dir)) { geany_debug("Failed to run command with no working directory"); ui_set_statusbar(TRUE, _("Process failed, no working directory")); return; } clear_all_errors(); SETPTR(current_dir_entered, NULL); utf8_working_dir = !EMPTY(dir) ? g_strdup(dir) : g_path_get_dirname(doc->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(COLOR_BLUE, _("%s (in directory: %s)"), cmd, utf8_working_dir); g_free(utf8_working_dir); cmd_string = utils_get_locale_from_utf8(cmd); argv[2] = cmd_string; #ifdef G_OS_UNIX cmd = NULL; /* under Unix, use argv to start cmd via sh for compatibility */ #else argv[0] = NULL; /* under Windows, run cmd directly */ #endif /* 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 = (doc == NULL) ? GEANY_FILETYPES_NONE : doc->file_type->id; build_info.message_count = 0; if (!spawn_with_callbacks(working_dir, cmd, argv, NULL, 0, NULL, NULL, build_iofunc, GINT_TO_POINTER(0), 0, build_iofunc, GINT_TO_POINTER(1), 0, build_exit_cb, NULL, &build_info.pid, &error)) { geany_debug("build command spawning failed: %s", error->message); ui_set_statusbar(TRUE, _("Process failed (%s)"), error->message); g_error_free(error); } g_free(working_dir); g_free(cmd_string); } /* Returns: NULL if there was an error, or the command to be executed. If Geany is * set to use a run script, the returned value is a path to the script that runs * the command; otherwise the command itself is returned. working_dir is a pointer * to the working directory from which the command is executed. Both strings are * in the locale encoding. */ static gchar *prepare_run_cmd(GeanyDocument *doc, gchar **working_dir, guint cmdindex) { GeanyBuildCommand *cmd = NULL; const gchar *cmd_working_dir; gboolean autoclose = FALSE; gchar *cmd_string_utf8, *working_dir_utf8, *run_cmd, *cmd_string; GError *error = NULL; cmd = get_build_cmd(doc, GEANY_GBG_EXEC, cmdindex, NULL); cmd_string_utf8 = build_replace_placeholder(doc, cmd->command); cmd_working_dir = cmd->working_dir; if (EMPTY(cmd_working_dir)) cmd_working_dir = "%d"; working_dir_utf8 = build_replace_placeholder(doc, cmd_working_dir); *working_dir = utils_get_locale_from_utf8(working_dir_utf8); if (EMPTY(*working_dir) || ! g_file_test(*working_dir, G_FILE_TEST_EXISTS) || ! g_file_test(*working_dir, G_FILE_TEST_IS_DIR)) { ui_set_statusbar(TRUE, _("Invalid working directory \"%s\""), !EMPTY(working_dir_utf8) ? working_dir_utf8 : "" ); utils_free_pointers(3, cmd_string_utf8, working_dir_utf8, *working_dir, NULL); return NULL; } cmd_string = utils_get_locale_from_utf8(cmd_string_utf8); #ifdef HAVE_VTE if (vte_info.have_vte && vc->run_in_vte) { if (vc->skip_run_script) { utils_free_pointers(2, cmd_string_utf8, working_dir_utf8, NULL); return cmd_string; } else /* don't wait for user input at the end of script when we are running in VTE */ autoclose = TRUE; } #endif run_cmd = build_create_shellscript(*working_dir, cmd_string, autoclose, &error); if (!run_cmd) { ui_set_statusbar(TRUE, _("Failed to execute \"%s\" (start-script could not be created: %s)"), !EMPTY(cmd_string_utf8) ? cmd_string_utf8 : NULL, error->message); g_error_free(error); g_free(*working_dir); } utils_free_pointers(3, cmd_string_utf8, working_dir_utf8, cmd_string, NULL); return run_cmd; } static void build_run_cmd(GeanyDocument *doc, guint cmdindex) { gchar *working_dir; gchar *run_cmd = NULL; if (! DOC_VALID(doc) || doc->file_name == NULL) return; run_cmd = prepare_run_cmd(doc, &working_dir, cmdindex); if (run_cmd == NULL) return; run_info[cmdindex].file_type_id = doc->file_type->id; #ifdef HAVE_VTE if (vte_info.have_vte && vc->run_in_vte) { gchar *vte_cmd; /* VTE expects commands in UTF-8 */ SETPTR(run_cmd, utils_get_utf8_from_locale(run_cmd)); SETPTR(working_dir, utils_get_utf8_from_locale(working_dir)); if (vc->skip_run_script) vte_cmd = g_strconcat(run_cmd, "\n", NULL); else vte_cmd = g_strconcat("\n/bin/sh ", run_cmd, "\n", NULL); vte_cwd(working_dir, TRUE); if (! vte_send_cmd(vte_cmd)) { const gchar *msg = _("File not executed because the terminal may contain some input (press Ctrl+C or Enter to clear it)."); ui_set_statusbar(FALSE, "%s", msg); geany_debug("%s", msg); if (!vc->skip_run_script) g_unlink(run_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[cmdindex].pid = 1; g_free(vte_cmd); } else #endif { gchar *locale_term_cmd = utils_get_locale_from_utf8(tool_prefs.term_cmd); GError *error = NULL; utils_str_replace_all(&locale_term_cmd, "%c", run_cmd); if (spawn_async(working_dir, locale_term_cmd, NULL, NULL, &(run_info[cmdindex].pid), &error)) { g_child_watch_add(run_info[cmdindex].pid, (GChildWatchFunc) run_exit_cb, (gpointer) &(run_info[cmdindex])); build_menu_update(doc); } else { gchar *utf8_term_cmd = utils_get_utf8_from_locale(locale_term_cmd); ui_set_statusbar(TRUE, _("Cannot execute build command \"%s\": %s. " "Check the Terminal setting in Preferences"), utf8_term_cmd, error->message); g_free(utf8_term_cmd); g_error_free(error); g_unlink(run_cmd); run_info[cmdindex].pid = (GPid) 0; } } g_free(working_dir); g_free(run_cmd); } static void process_build_output_line(gchar *msg, gint color) { gchar *tmp; gchar *filename; gint line; g_strchomp(msg); if (EMPTY(msg)) return; if (build_parse_make_dir(msg, &tmp)) { SETPTR(current_dir_entered, tmp); } msgwin_parse_compiler_error_line(msg, current_dir_entered, &filename, &line); if (line != -1 && filename != NULL) { GeanyDocument *doc = document_find_by_filename(filename); /* limit number of indicators */ if (doc && editor_prefs.use_indicators && build_info.message_count < GEANY_BUILD_ERR_HIGHLIGHT_MAX) { if (line > 0) /* some compilers, like pdflatex report errors on line 0 */ line--; /* so only adjust the line number if it is greater than 0 */ editor_indicator_set_on_line(doc->editor, GEANY_INDICATOR_ERROR, line); } build_info.message_count++; color = COLOR_RED; /* error message parsed on the line */ } g_free(filename); msgwin_compiler_add_string(color, msg); } static void build_iofunc(GString *string, GIOCondition condition, gpointer data) { if (condition & (G_IO_IN | G_IO_PRI)) { process_build_output_line(string->str, (GPOINTER_TO_INT(data)) ? COLOR_DARK_RED : COLOR_BLACK); } } 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; } if (strstr(string, "Leaving directory") != NULL) { *prefix = NULL; return TRUE; } return FALSE; } static void show_build_result_message(gboolean failure) { gchar *msg; if (failure) { msg = _("Compilation failed."); msgwin_compiler_add_string(COLOR_BLUE, 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(FALSE, "%s", msg); } else { msg = _("Compilation finished successfully."); msgwin_compiler_add_string(COLOR_BLUE, msg); if (! ui_prefs.msgwindow_visible || gtk_notebook_get_current_page(GTK_NOTEBOOK(msgwindow.notebook)) != MSG_COMPILER) ui_set_statusbar(FALSE, "%s", msg); } } static void build_exit_cb(GPid child_pid, gint status, gpointer user_data) { show_build_result_message(!SPAWN_WIFEXITED(status) || SPAWN_WEXITSTATUS(status) != EXIT_SUCCESS); utils_beep(); build_info.pid = 0; /* enable build items again */ build_menu_update(NULL); ui_progress_bar_stop(); } static void run_exit_cb(GPid child_pid, gint status, gpointer user_data) { RunInfo *run_info_data = user_data; g_spawn_close_pid(child_pid); run_info_data->pid = 0; /* reset the stop button and menu item to the original meaning */ build_menu_update(NULL); } /* write a little shellscript to call the executable (similar to anjuta_launcher but "internal") * working_dir and cmd are both in the locale encoding * it returns the full file name (including path) of the created script in the locale encoding */ static gchar *build_create_shellscript(const gchar *working_dir, const gchar *cmd, gboolean autoclose, GError **error) { gint fd; gchar *str, *fname; gboolean success = TRUE; #ifdef G_OS_WIN32 gchar *expanded_cmd; #else gchar *escaped_dir; #endif fd = g_file_open_tmp (RUN_SCRIPT_CMD, &fname, error); if (fd < 0) return NULL; close(fd); #ifdef G_OS_WIN32 /* Expand environment variables like %blah%. */ expanded_cmd = win32_expand_environment_variables(cmd); str = g_strdup_printf("cd \"%s\"\n\n%s\n\n%s\ndel \"%%0\"\n\npause\n", working_dir, expanded_cmd, (autoclose) ? "" : "pause"); g_free(expanded_cmd); #else escaped_dir = g_shell_quote(working_dir); str = g_strdup_printf( "#!/bin/sh\n\nrm $0\n\ncd %s\n\n%s\n\necho \"\n\n------------------\n(program exited with code: $?)\" \ \n\n%s\n", escaped_dir, cmd, (autoclose) ? "" : "\necho \"Press return to continue\"\n#to be more compatible with shells like " "dash\ndummy_var=\"\"\nread dummy_var"); g_free(escaped_dir); #endif if (!g_file_set_contents(fname, str, -1, error)) success = FALSE; g_free(str); #ifdef __APPLE__ if (success && g_chmod(fname, 0777) != 0) { if (error) { gint errsv = errno; g_set_error(error, G_FILE_ERROR, g_file_error_from_errno(errsv), "Failed to make file executable: %s", g_strerror(errsv)); } success = FALSE; } #endif if (!success) { g_unlink(fname); g_free(fname); fname = NULL; } return fname; } typedef void Callback(GtkWidget *w, gpointer u); /* run the command catenating cmd_cat if present */ static void build_command(GeanyDocument *doc, GeanyBuildGroup grp, guint cmd, gchar *cmd_cat) { gchar *dir; gchar *full_command, *subs_command; GeanyBuildCommand *buildcmd = get_build_cmd(doc, grp, cmd, NULL); gchar *cmdstr; if (buildcmd == NULL) return; cmdstr = buildcmd->command; if (cmd_cat != NULL) { if (cmdstr != NULL) full_command = g_strconcat(cmdstr, cmd_cat, NULL); else full_command = g_strdup(cmd_cat); } else full_command = cmdstr; dir = build_replace_placeholder(doc, buildcmd->working_dir); subs_command = build_replace_placeholder(doc, full_command); build_info.grp = grp; build_info.cmd = cmd; build_spawn_cmd(doc, subs_command, dir); g_free(subs_command); g_free(dir); if (cmd_cat != NULL) g_free(full_command); build_menu_update(doc); if (build_info.pid) ui_progress_bar_start(NULL); } /*---------------------------------------------------------------- * * Create build menu and handle callbacks (&toolbar callbacks) * *----------------------------------------------------------------*/ static void on_make_custom_input_response(const gchar *input, gpointer data) { GeanyDocument *doc = document_get_current(); SETPTR(build_info.custom_target, g_strdup(input)); build_command(doc, GBO_TO_GBG(GEANY_GBO_CUSTOM), GBO_TO_CMD(GEANY_GBO_CUSTOM), build_info.custom_target); } static void on_build_menu_item(GtkWidget *w, gpointer user_data) { GeanyDocument *doc = document_get_current(); GeanyBuildCommand *bc; guint grp = GPOINTER_TO_GRP(user_data); guint cmd = GPOINTER_TO_CMD(user_data); if (doc && doc->changed) { if (!document_save_file(doc, FALSE)) return; } g_signal_emit_by_name(geany_object, "build-start"); if (grp == GEANY_GBG_NON_FT && cmd == GBO_TO_CMD(GEANY_GBO_CUSTOM)) { static GtkWidget *dialog = NULL; /* keep dialog for combo history */ if (! dialog) { dialog = dialogs_show_input_persistent(_("Custom Text"), GTK_WINDOW(main_widgets.window), _("Enter custom text here, all entered text is appended to the command."), build_info.custom_target, &on_make_custom_input_response, NULL); } else { gtk_widget_show(dialog); } return; } else if (grp == GEANY_GBG_EXEC) { if (run_info[cmd].pid > (GPid) 1) { kill_process(&run_info[cmd].pid); return; } bc = get_build_cmd(doc, grp, cmd, NULL); if (bc != NULL && strcmp(bc->command, "builtin") == 0) { gchar *uri; if (doc == NULL) return; uri = g_strconcat("file:///", g_path_skip_root(doc->file_name), NULL); utils_open_browser(uri); g_free(uri); } else build_run_cmd(doc, cmd); } else build_command(doc, grp, cmd, NULL); } /* group codes for menu items other than the known commands * value order is important, see the following table for use */ /* the rest in each group */ #define MENU_FT_REST (GEANY_GBG_COUNT + GEANY_GBG_FT) #define MENU_NON_FT_REST (GEANY_GBG_COUNT + GEANY_GBG_NON_FT) #define MENU_EXEC_REST (GEANY_GBG_COUNT + GEANY_GBG_EXEC) /* the separator */ #define MENU_SEPARATOR (2*GEANY_GBG_COUNT) /* the fixed items */ #define MENU_NEXT_ERROR (MENU_SEPARATOR + 1) #define MENU_PREV_ERROR (MENU_NEXT_ERROR + 1) #define MENU_COMMANDS (MENU_PREV_ERROR + 1) #define MENU_DONE (MENU_COMMANDS + 1) static struct BuildMenuItemSpec { const gchar *stock_id; const gint key_binding; const guint build_grp; const guint build_cmd; const gchar *fix_label; Callback *cb; } build_menu_specs[] = { {GTK_STOCK_CONVERT, GEANY_KEYS_BUILD_COMPILE, GBO_TO_GBG(GEANY_GBO_COMPILE), GBO_TO_CMD(GEANY_GBO_COMPILE), NULL, on_build_menu_item}, {GEANY_STOCK_BUILD, GEANY_KEYS_BUILD_LINK, GBO_TO_GBG(GEANY_GBO_BUILD), GBO_TO_CMD(GEANY_GBO_BUILD), NULL, on_build_menu_item}, {NULL, -1, MENU_FT_REST, GBO_TO_CMD(GEANY_GBO_BUILD) + 1, NULL, on_build_menu_item}, {NULL, -1, MENU_SEPARATOR, GBF_SEP_1, NULL, NULL}, {NULL, GEANY_KEYS_BUILD_MAKE, GBO_TO_GBG(GEANY_GBO_MAKE_ALL), GBO_TO_CMD(GEANY_GBO_MAKE_ALL), NULL, on_build_menu_item}, {NULL, GEANY_KEYS_BUILD_MAKEOWNTARGET, GBO_TO_GBG(GEANY_GBO_CUSTOM), GBO_TO_CMD(GEANY_GBO_CUSTOM), NULL, on_build_menu_item}, {NULL, GEANY_KEYS_BUILD_MAKEOBJECT, GBO_TO_GBG(GEANY_GBO_MAKE_OBJECT), GBO_TO_CMD(GEANY_GBO_MAKE_OBJECT), NULL, on_build_menu_item}, {NULL, -1, MENU_NON_FT_REST, GBO_TO_CMD(GEANY_GBO_MAKE_OBJECT) + 1, NULL, on_build_menu_item}, {NULL, -1, MENU_SEPARATOR, GBF_SEP_2, NULL, NULL}, {GTK_STOCK_GO_DOWN, GEANY_KEYS_BUILD_NEXTERROR, MENU_NEXT_ERROR, GBF_NEXT_ERROR, N_("_Next Error"), on_build_next_error}, {GTK_STOCK_GO_UP, GEANY_KEYS_BUILD_PREVIOUSERROR, MENU_PREV_ERROR, GBF_PREV_ERROR, N_("_Previous Error"), on_build_previous_error}, {NULL, -1, MENU_SEPARATOR, GBF_SEP_3, NULL, NULL}, {GTK_STOCK_EXECUTE, GEANY_KEYS_BUILD_RUN, GBO_TO_GBG(GEANY_GBO_EXEC), GBO_TO_CMD(GEANY_GBO_EXEC), NULL, on_build_menu_item}, {NULL, -1, MENU_EXEC_REST, GBO_TO_CMD(GEANY_GBO_EXEC) + 1, NULL, on_build_menu_item}, {NULL, -1, MENU_SEPARATOR, GBF_SEP_4, NULL, NULL}, {GTK_STOCK_PREFERENCES, GEANY_KEYS_BUILD_OPTIONS, MENU_COMMANDS, GBF_COMMANDS, N_("_Set Build Commands"), on_set_build_commands_activate}, {NULL, -1, MENU_DONE, 0, NULL, NULL} }; static void create_build_menu_item(GtkWidget *menu, GeanyKeyGroup *group, GtkAccelGroup *ag, struct BuildMenuItemSpec *bs, const gchar *lbl, guint grp, guint cmd) { GtkWidget *item = gtk_image_menu_item_new_with_mnemonic(lbl); if (bs->stock_id != NULL) { GtkWidget *image = gtk_image_new_from_stock(bs->stock_id, GTK_ICON_SIZE_MENU); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image); } gtk_widget_show(item); if (bs->key_binding >= 0) add_menu_accel(group, (guint) bs->key_binding, ag, item); gtk_container_add(GTK_CONTAINER(menu), item); if (bs->cb != NULL) { g_signal_connect(item, "activate", G_CALLBACK(bs->cb), GRP_CMD_TO_POINTER(grp,cmd)); } menu_items.menu_item[grp][cmd] = item; } static void create_build_menu(BuildMenuItems *build_menu_items) { GtkWidget *menu; GtkAccelGroup *accel_group = gtk_accel_group_new(); GeanyKeyGroup *keygroup = keybindings_get_core_group(GEANY_KEY_GROUP_BUILD); guint i, j; menu = gtk_menu_new(); build_menu_items->menu_item[GEANY_GBG_FT] = g_new0(GtkWidget*, build_groups_count[GEANY_GBG_FT]); build_menu_items->menu_item[GEANY_GBG_NON_FT] = g_new0(GtkWidget*, build_groups_count[GEANY_GBG_NON_FT]); build_menu_items->menu_item[GEANY_GBG_EXEC] = g_new0(GtkWidget*, build_groups_count[GEANY_GBG_EXEC]); build_menu_items->menu_item[GBG_FIXED] = g_new0(GtkWidget*, GBF_COUNT); for (i = 0; build_menu_specs[i].build_grp != MENU_DONE; ++i) { struct BuildMenuItemSpec *bs = &(build_menu_specs[i]); if (bs->build_grp == MENU_SEPARATOR) { GtkWidget *item = gtk_separator_menu_item_new(); gtk_widget_show(item); gtk_container_add(GTK_CONTAINER(menu), item); build_menu_items->menu_item[GBG_FIXED][bs->build_cmd] = item; } else if (bs->fix_label != NULL) { create_build_menu_item(menu, keygroup, accel_group, bs, _(bs->fix_label), GBG_FIXED, bs->build_cmd); } else if (bs->build_grp >= MENU_FT_REST && bs->build_grp <= MENU_SEPARATOR) { guint grp = bs->build_grp - GEANY_GBG_COUNT; for (j = bs->build_cmd; j < build_groups_count[grp]; ++j) { GeanyBuildCommand *bc = get_build_cmd(NULL, grp, j, NULL); const gchar *lbl = (bc == NULL) ? "" : bc->label; create_build_menu_item(menu, keygroup, accel_group, bs, lbl, grp, j); } } else { GeanyBuildCommand *bc = get_build_cmd(NULL, bs->build_grp, bs->build_cmd, NULL); const gchar *lbl = (bc == NULL) ? "" : bc->label; create_build_menu_item(menu, keygroup, accel_group, bs, lbl, bs->build_grp, bs->build_cmd); } } build_menu_items->menu = menu; gtk_widget_show(menu); gtk_menu_item_set_submenu(GTK_MENU_ITEM(ui_lookup_widget(main_widgets.window, "menu_build1")), menu); } /* portability to various GTK versions needs checking * conforms to description of gtk_accel_label as child of menu item * NB 2.16 adds set_label but not yet set_label_mnemonic */ static void geany_menu_item_set_label(GtkWidget *w, const gchar *label) { GtkWidget *c = gtk_bin_get_child(GTK_BIN(w)); gtk_label_set_text_with_mnemonic(GTK_LABEL(c), label); } /* * Update the build menu to reflect changes in configuration or status. * * Sets the labels and number of visible items to match the highest * priority configured commands. Also sets sensitivity if build commands are * running and switches executes to stop when commands are running. * * @param doc The current document, if available, to save looking it up. * If @c NULL it will be looked up. * * Call this after modifying any fields of a GeanyBuildCommand structure. * * @see Build Menu Configuration section of the Manual. * **/ void build_menu_update(GeanyDocument *doc) { guint i, cmdcount, cmd, grp; gboolean vis = FALSE; gboolean have_path, build_running, exec_running, have_errors, cmd_sensitivity; gboolean can_compile, can_build, can_make, run_sensitivity = FALSE, run_running = FALSE; GeanyBuildCommand *bc; g_return_if_fail(doc == NULL || doc->is_valid); if (menu_items.menu == NULL) create_build_menu(&menu_items); if (doc == NULL) doc = document_get_current(); have_path = doc != NULL && doc->file_name != NULL; build_running = build_info.pid > (GPid) 1; have_errors = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(msgwindow.store_compiler), NULL) > 0; for (i = 0; build_menu_specs[i].build_grp != MENU_DONE; ++i) { struct BuildMenuItemSpec *bs = &(build_menu_specs[i]); switch (bs->build_grp) { case MENU_SEPARATOR: if (vis == TRUE) { gtk_widget_show_all(menu_items.menu_item[GBG_FIXED][bs->build_cmd]); vis = FALSE; } else gtk_widget_hide(menu_items.menu_item[GBG_FIXED][bs->build_cmd]); break; case MENU_NEXT_ERROR: case MENU_PREV_ERROR: gtk_widget_set_sensitive(menu_items.menu_item[GBG_FIXED][bs->build_cmd], have_errors); vis |= TRUE; break; case MENU_COMMANDS: vis |= TRUE; break; default: /* all configurable commands */ if (bs->build_grp >= GEANY_GBG_COUNT) { grp = bs->build_grp - GEANY_GBG_COUNT; cmdcount = build_groups_count[grp]; } else { grp = bs->build_grp; cmdcount = bs->build_cmd + 1; } for (cmd = bs->build_cmd; cmd < cmdcount; ++cmd) { GtkWidget *menu_item = menu_items.menu_item[grp][cmd]; const gchar *label; bc = get_build_cmd(doc, grp, cmd, NULL); if (bc) label = bc->label; else label = NULL; if (grp < GEANY_GBG_EXEC) { cmd_sensitivity = (grp == GEANY_GBG_FT && bc != NULL && have_path && ! build_running) || (grp == GEANY_GBG_NON_FT && bc != NULL && ! build_running); gtk_widget_set_sensitive(menu_item, cmd_sensitivity); if (bc != NULL && !EMPTY(label)) { geany_menu_item_set_label(menu_item, label); gtk_widget_show_all(menu_item); vis |= TRUE; } else gtk_widget_hide(menu_item); } else { GtkWidget *image; exec_running = run_info[cmd].pid > (GPid) 1; cmd_sensitivity = (bc != NULL) || exec_running; gtk_widget_set_sensitive(menu_item, cmd_sensitivity); if (cmd == GBO_TO_CMD(GEANY_GBO_EXEC)) run_sensitivity = cmd_sensitivity; if (! exec_running) { image = gtk_image_new_from_stock(bs->stock_id, GTK_ICON_SIZE_MENU); } else { image = gtk_image_new_from_stock(GTK_STOCK_STOP, GTK_ICON_SIZE_MENU); } if (cmd == GBO_TO_CMD(GEANY_GBO_EXEC)) run_running = exec_running; gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), image); if (bc != NULL && !EMPTY(label)) { geany_menu_item_set_label(menu_item, label); gtk_widget_show_all(menu_item); vis |= TRUE; } else gtk_widget_hide(menu_item); } } } } run_sensitivity &= (doc != NULL); can_build = get_build_cmd(doc, GEANY_GBG_FT, GBO_TO_CMD(GEANY_GBO_BUILD), NULL) != NULL && have_path && ! build_running; if (widgets.toolitem_build != NULL) gtk_widget_set_sensitive(widgets.toolitem_build, can_build); can_make = FALSE; if (widgets.toolitem_make_all != NULL) gtk_widget_set_sensitive(widgets.toolitem_make_all, (can_make |= get_build_cmd(doc, GEANY_GBG_FT, GBO_TO_CMD(GEANY_GBO_MAKE_ALL), NULL) != NULL && ! build_running)); if (widgets.toolitem_make_custom != NULL) gtk_widget_set_sensitive(widgets.toolitem_make_custom, (can_make |= get_build_cmd(doc, GEANY_GBG_FT, GBO_TO_CMD(GEANY_GBO_CUSTOM), NULL) != NULL && ! build_running)); if (widgets.toolitem_make_object != NULL) gtk_widget_set_sensitive(widgets.toolitem_make_object, (can_make |= get_build_cmd(doc, GEANY_GBG_FT, GBO_TO_CMD(GEANY_GBO_MAKE_OBJECT), NULL) != NULL && ! build_running)); if (widgets.toolitem_set_args != NULL) gtk_widget_set_sensitive(widgets.toolitem_set_args, TRUE); can_compile = get_build_cmd(doc, GEANY_GBG_FT, GBO_TO_CMD(GEANY_GBO_COMPILE), NULL) != NULL && have_path && ! build_running; gtk_action_set_sensitive(widgets.compile_action, can_compile); gtk_action_set_sensitive(widgets.build_action, can_make); gtk_action_set_sensitive(widgets.run_action, run_sensitivity); /* show the stop command if a program is running from execute 0 , otherwise show run command */ set_stop_button(run_running); } /* Call build_menu_update() instead of calling this directly. */ static void set_stop_button(gboolean stop) { const gchar *button_stock_id = NULL; GtkToolButton *run_button; run_button = GTK_TOOL_BUTTON(toolbar_get_widget_by_name("Run")); if (run_button != NULL) button_stock_id = gtk_tool_button_get_stock_id(run_button); if (stop && utils_str_equal(button_stock_id, GTK_STOCK_STOP)) return; if (! stop && utils_str_equal(button_stock_id, GTK_STOCK_EXECUTE)) return; /* use the run button also as stop button */ if (stop) { if (run_button != NULL) gtk_tool_button_set_stock_id(run_button, GTK_STOCK_STOP); } else { if (run_button != NULL) gtk_tool_button_set_stock_id(run_button, GTK_STOCK_EXECUTE); } } static void on_set_build_commands_activate(GtkWidget *w, gpointer u) { /* For now, just show the project dialog */ if (app->project) project_build_properties(); else show_build_commands_dialog(); } static void on_toolbutton_build_activate(GtkWidget *menuitem, gpointer user_data) { last_toolbutton_action = user_data; g_object_set(widgets.build_action, "tooltip", _("Build the current file"), NULL); on_build_menu_item(menuitem, user_data); } static void on_toolbutton_make_activate(GtkWidget *menuitem, gpointer user_data) { gchar *msg; last_toolbutton_action = user_data; if (last_toolbutton_action == GBO_TO_POINTER(GEANY_GBO_MAKE_ALL)) msg = _("Build the current file with Make and the default target"); else if (last_toolbutton_action == GBO_TO_POINTER(GEANY_GBO_CUSTOM)) msg = _("Build the current file with Make and the specified target"); else if (last_toolbutton_action == GBO_TO_POINTER(GEANY_GBO_MAKE_OBJECT)) msg = _("Compile the current file with Make"); else msg = NULL; g_object_set(widgets.build_action, "tooltip", msg, NULL); on_build_menu_item(menuitem, user_data); } static void kill_process(GPid *pid) { GError *error = NULL; if (spawn_kill_process(*pid, &error)) { *pid = 0; build_menu_update(NULL); } else { ui_set_statusbar(TRUE, _("Process could not be stopped (%s)."), error->message); g_error_free(error); } } static void on_build_next_error(GtkWidget *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(FALSE, _("No more build errors.")); } static void on_build_previous_error(GtkWidget *menuitem, gpointer user_data) { if (ui_tree_view_find_previous(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(FALSE, _("No more build errors.")); } void build_toolbutton_build_clicked(GtkAction *action, gpointer unused) { if (last_toolbutton_action == GBO_TO_POINTER(GEANY_GBO_BUILD)) { on_build_menu_item(NULL, GBO_TO_POINTER(GEANY_GBO_BUILD)); } else { on_build_menu_item(NULL, last_toolbutton_action); } } /*------------------------------------------------------ * * Create and handle the build menu configuration dialog * *-------------------------------------------------------*/ typedef struct RowWidgets { GtkWidget *entries[GEANY_BC_CMDENTRIES_COUNT]; GeanyBuildSource src; GeanyBuildSource dst; GeanyBuildCommand *cmdsrc; guint grp; guint cmd; gboolean cleared; gboolean used_dst; } RowWidgets; #if GTK_CHECK_VERSION(3,0,0) typedef GdkRGBA InsensitiveColor; #else typedef GdkColor InsensitiveColor; #endif static InsensitiveColor insensitive_color; static void set_row_color(RowWidgets *r, InsensitiveColor *color) { enum GeanyBuildCmdEntries i; for (i = 0; i < GEANY_BC_CMDENTRIES_COUNT; i++) { if (i == GEANY_BC_LABEL) continue; #if GTK_CHECK_VERSION(3,0,0) gtk_widget_override_color(r->entries[i], GTK_STATE_FLAG_NORMAL, color); #else gtk_widget_modify_text(r->entries[i], GTK_STATE_NORMAL, color); #endif } } static void set_build_command_entry_text(GtkWidget *wid, const gchar *text) { if (GTK_IS_BUTTON(wid)) gtk_button_set_label(GTK_BUTTON(wid), text); else gtk_entry_set_text(GTK_ENTRY(wid), text); } static void on_clear_dialog_row(GtkWidget *unused, gpointer user_data) { RowWidgets *r = user_data; guint src; enum GeanyBuildCmdEntries i; GeanyBuildCommand *bc = get_next_build_cmd(NULL, r->grp, r->cmd, r->dst, &src); if (bc != NULL) { r->cmdsrc = bc; r->src = src; for (i = 0; i < GEANY_BC_CMDENTRIES_COUNT; i++) { set_build_command_entry_text(r->entries[i], id_to_str(bc,i) != NULL ? id_to_str(bc,i) : ""); } } else { r->cmdsrc = NULL; for (i = 0; i < GEANY_BC_CMDENTRIES_COUNT; i++) { set_build_command_entry_text(r->entries[i], ""); } } r->used_dst = FALSE; set_row_color(r, &insensitive_color); r->cleared = TRUE; } static void on_clear_dialog_regex_row(GtkEntry *regex, gpointer unused) { gtk_entry_set_text(regex,""); } static void on_label_button_clicked(GtkWidget *wid, gpointer user_data) { RowWidgets *r = user_data; GtkWidget *top_level = gtk_widget_get_toplevel(wid); const gchar *old = gtk_button_get_label(GTK_BUTTON(wid)); gchar *str; if (gtk_widget_is_toplevel(top_level) && GTK_IS_WINDOW(top_level)) str = dialogs_show_input(_("Set menu item label"), GTK_WINDOW(top_level), NULL, old); else str = dialogs_show_input(_("Set menu item label"), NULL, NULL, old); if (!str) return; gtk_button_set_label(GTK_BUTTON(wid), str); g_free(str); r->used_dst = TRUE; set_row_color(r, NULL); } static void on_entry_focus(GtkWidget *wid, GdkEventFocus *unused, gpointer user_data) { RowWidgets *r = user_data; r->used_dst = TRUE; set_row_color(r, NULL); } /* Column headings, array NULL-terminated */ static const gchar *colheads[] = { "#", N_("Label"), N_("Command"), N_("Working directory"), N_("Reset"), NULL }; /* column names */ #define DC_ITEM 0 #define DC_ENTRIES 1 #define DC_CLEAR 4 #define DC_N_COL 5 static const guint entry_x_padding = 3; static const guint entry_y_padding = 0; static RowWidgets *build_add_dialog_row(GeanyDocument *doc, GtkTable *table, guint row, GeanyBuildSource dst, guint grp, guint cmd, gboolean dir) { GtkWidget *label, *clear, *clearicon; RowWidgets *roww; GeanyBuildCommand *bc; guint src; enum GeanyBuildCmdEntries i; guint column = 0; gchar *text; g_return_val_if_fail(doc == NULL || doc->is_valid, NULL); text = g_strdup_printf("%d.", cmd + 1); label = gtk_label_new(text); g_free(text); #if GTK_CHECK_VERSION(3,0,0) { GtkStyleContext *ctx = gtk_widget_get_style_context(label); gtk_style_context_save(ctx); gtk_style_context_get_color(ctx, GTK_STATE_FLAG_INSENSITIVE, &insensitive_color); gtk_style_context_restore(ctx); } #else insensitive_color = gtk_widget_get_style(label)->text[GTK_STATE_INSENSITIVE]; #endif gtk_table_attach(table, label, column, column + 1, row, row + 1, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, entry_y_padding); roww = g_new0(RowWidgets, 1); roww->src = GEANY_BCS_COUNT; roww->grp = grp; roww->cmd = cmd; roww->dst = dst; for (i = 0; i < GEANY_BC_CMDENTRIES_COUNT; i++) { gint xflags = (i == GEANY_BC_COMMAND) ? GTK_FILL | GTK_EXPAND : GTK_FILL; column += 1; if (i == GEANY_BC_LABEL) { GtkWidget *wid = roww->entries[i] = gtk_button_new(); gtk_button_set_use_underline(GTK_BUTTON(wid), TRUE); gtk_widget_set_tooltip_text(wid, _("Click to set menu item label")); g_signal_connect(wid, "clicked", G_CALLBACK(on_label_button_clicked), roww); } else { roww->entries[i] = gtk_entry_new(); g_signal_connect(roww->entries[i], "focus-in-event", G_CALLBACK(on_entry_focus), roww); } gtk_table_attach(table, roww->entries[i], column, column + 1, row, row + 1, xflags, GTK_FILL | GTK_EXPAND, entry_x_padding, entry_y_padding); } column++; clearicon = gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU); clear = gtk_button_new(); gtk_button_set_image(GTK_BUTTON(clear), clearicon); g_signal_connect(clear, "clicked", G_CALLBACK(on_clear_dialog_row), roww); gtk_table_attach(table, clear, column, column + 1, row, row + 1, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, entry_y_padding); roww->cmdsrc = bc = get_build_cmd(doc, grp, cmd, &src); if (bc != NULL) roww->src = src; for (i = 0; i < GEANY_BC_CMDENTRIES_COUNT; i++) { const gchar *str = ""; if (bc != NULL ) { if ((str = id_to_str(bc, i)) == NULL) str = ""; else if (dst == src) roww->used_dst = TRUE; } set_build_command_entry_text(roww->entries[i], str); } if (bc != NULL && (dst > src)) set_row_color(roww, &insensitive_color); if (bc != NULL && (src > dst || (grp == GEANY_GBG_FT && (doc == NULL || doc->file_type == NULL)))) { for (i = 0; i < GEANY_BC_CMDENTRIES_COUNT; i++) gtk_widget_set_sensitive(roww->entries[i], FALSE); gtk_widget_set_sensitive(clear, FALSE); } return roww; } typedef struct BuildTableFields { RowWidgets **rows; GtkWidget *fileregex; GtkWidget *nonfileregex; gchar **fileregexstring; gchar **nonfileregexstring; } BuildTableFields; GtkWidget *build_commands_table(GeanyDocument *doc, GeanyBuildSource dst, BuildTableData *table_data, GeanyFiletype *ft) { GtkWidget *label, *sep, *clearicon, *clear; BuildTableFields *fields; GtkTable *table; const gchar **ch; gchar *txt; guint col, row, cmdindex; guint cmd; guint src; gboolean sensitivity; guint sep_padding = entry_y_padding + 3; table = GTK_TABLE(gtk_table_new(build_items_count + 12, 5, FALSE)); fields = g_new0(BuildTableFields, 1); fields->rows = g_new0(RowWidgets*, build_items_count); for (ch = colheads, col = 0; *ch != NULL; ch++, col++) { label = gtk_label_new(_(*ch)); gtk_table_attach(table, label, col, col + 1, 0, 1, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, entry_y_padding); } sep = gtk_hseparator_new(); gtk_table_attach(table, sep, 0, DC_N_COL, 1, 2, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, sep_padding); if (ft != NULL && ft->id != GEANY_FILETYPES_NONE) txt = g_strdup_printf(_("%s commands"), ft->name); else txt = g_strdup_printf(_("%s commands"), _("No filetype")); label = ui_label_new_bold(txt); g_free(txt); gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); gtk_table_attach(table, label, 0, DC_N_COL, 2, 3, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, entry_y_padding); for (row = 3, cmdindex = 0, cmd = 0; cmd < build_groups_count[GEANY_GBG_FT]; ++row, ++cmdindex, ++cmd) fields->rows[cmdindex] = build_add_dialog_row(doc, table, row, dst, GEANY_GBG_FT, cmd, FALSE); label = gtk_label_new(_("Error regular expression:")); gtk_table_attach(table, label, 0, DC_ENTRIES + 1, row, row + 1, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, entry_y_padding); fields->fileregex = gtk_entry_new(); fields->fileregexstring = build_get_regex(GEANY_GBG_FT, NULL, &src); sensitivity = (ft == NULL) ? FALSE : TRUE; if (fields->fileregexstring != NULL && *(fields->fileregexstring) != NULL) { gtk_entry_set_text(GTK_ENTRY(fields->fileregex), *(fields->fileregexstring)); if (src > dst) sensitivity = FALSE; } gtk_table_attach(table, fields->fileregex, DC_ENTRIES + 1, DC_CLEAR, row, row + 1, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, entry_y_padding); clearicon = gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU); clear = gtk_button_new(); gtk_button_set_image(GTK_BUTTON(clear), clearicon); g_signal_connect_swapped(clear, "clicked", G_CALLBACK(on_clear_dialog_regex_row), (fields->fileregex)); gtk_table_attach(table, clear, DC_CLEAR, DC_CLEAR + 1, row, row + 1, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, entry_y_padding); gtk_widget_set_sensitive(fields->fileregex, sensitivity); gtk_widget_set_sensitive(clear, sensitivity); ++row; sep = gtk_hseparator_new(); gtk_table_attach(table, sep, 0, DC_N_COL, row, row + 1, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, sep_padding); ++row; label = ui_label_new_bold(_("Independent commands")); gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); gtk_table_attach(table, label, 0, DC_N_COL, row, row + 1, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, entry_y_padding); for (++row, cmd = 0; cmd < build_groups_count[GEANY_GBG_NON_FT]; ++row, ++cmdindex, ++cmd) fields->rows[cmdindex] = build_add_dialog_row( doc, table, row, dst, GEANY_GBG_NON_FT, cmd, TRUE); label = gtk_label_new(_("Error regular expression:")); gtk_table_attach(table, label, 0, DC_ENTRIES + 1, row, row + 1, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, entry_y_padding); fields->nonfileregex = gtk_entry_new(); fields->nonfileregexstring = build_get_regex(GEANY_GBG_NON_FT, NULL, &src); sensitivity = TRUE; if (fields->nonfileregexstring != NULL && *(fields->nonfileregexstring) != NULL) { gtk_entry_set_text(GTK_ENTRY(fields->nonfileregex), *(fields->nonfileregexstring)); sensitivity = src > dst ? FALSE : TRUE; } gtk_table_attach(table, fields->nonfileregex, DC_ENTRIES + 1, DC_CLEAR, row, row + 1, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, entry_y_padding); clearicon = gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU); clear = gtk_button_new(); gtk_button_set_image(GTK_BUTTON(clear), clearicon); g_signal_connect_swapped(clear, "clicked", G_CALLBACK(on_clear_dialog_regex_row), (fields->nonfileregex)); gtk_table_attach(table, clear, DC_CLEAR, DC_CLEAR + 1, row, row + 1, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, entry_y_padding); gtk_widget_set_sensitive(fields->nonfileregex, sensitivity); gtk_widget_set_sensitive(clear, sensitivity); ++row; label = gtk_label_new(NULL); ui_label_set_markup(GTK_LABEL(label), "%s", _("Note: Item 2 opens a dialog and appends the response to the command.")); gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); gtk_table_attach(table, label, 0, DC_N_COL, row, row + 1, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, entry_y_padding); ++row; sep = gtk_hseparator_new(); gtk_table_attach(table, sep, 0, DC_N_COL, row, row + 1, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, sep_padding); ++row; label = ui_label_new_bold(_("Execute commands")); gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); gtk_table_attach(table, label, 0, DC_N_COL, row, row + 1, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, entry_y_padding); for (++row, cmd = 0; cmd < build_groups_count[GEANY_GBG_EXEC]; ++row, ++cmdindex, ++cmd) fields->rows[cmdindex] = build_add_dialog_row(doc, table, row, dst, GEANY_GBG_EXEC, cmd, TRUE); sep = gtk_hseparator_new(); gtk_table_attach(table, sep, 0, DC_N_COL, row, row + 1, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, sep_padding); ++row; label = gtk_label_new(NULL); ui_label_set_markup(GTK_LABEL(label), "%s", _("%d, %e, %f, %p, %l are substituted in command and directory fields, see manual for details.")); gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); gtk_table_attach(table, label, 0, DC_N_COL, row, row + 1, GTK_FILL, GTK_FILL | GTK_EXPAND, entry_x_padding, entry_y_padding); /*printf("%d extra rows in dialog\n", row-build_items_count);*/ ++row; *table_data = fields; return GTK_WIDGET(table); } void build_free_fields(BuildTableData table_data) { guint cmdindex; for (cmdindex = 0; cmdindex < build_items_count; ++cmdindex) g_free(table_data->rows[cmdindex]); g_free(table_data->rows); g_free(table_data); } /* string compare where null pointers match null or 0 length strings */ #if 0 static gint stcmp(const gchar *a, const gchar *b) { if (a == NULL && b == NULL) return 0; if (a == NULL && b != NULL) return strlen(b); if (a != NULL && b == NULL) return strlen(a); return strcmp(a, b); } #endif static const gchar *get_build_command_entry_text(GtkWidget *wid) { if (GTK_IS_BUTTON(wid)) return gtk_button_get_label(GTK_BUTTON(wid)); else return gtk_entry_get_text(GTK_ENTRY(wid)); } static gboolean read_row(BuildDestination *dst, BuildTableData table_data, guint drow, guint grp, guint cmd) { gchar *entries[GEANY_BC_CMDENTRIES_COUNT]; gboolean changed = FALSE; enum GeanyBuildCmdEntries i; for (i = 0; i < GEANY_BC_CMDENTRIES_COUNT; i++) { entries[i] = g_strdup(get_build_command_entry_text(table_data->rows[drow]->entries[i])); } if (table_data->rows[drow]->cleared) { if (dst->dst[grp] != NULL) { if (*(dst->dst[grp]) == NULL) *(dst->dst[grp]) = g_new0(GeanyBuildCommand, build_groups_count[grp]); (*(dst->dst[grp]))[cmd].exists = FALSE; (*(dst->dst[grp]))[cmd].changed = TRUE; changed = TRUE; } } if (table_data->rows[drow]->used_dst == TRUE) { if (dst->dst[grp] != NULL) { if (*(dst->dst[grp]) == NULL) *(dst->dst[grp]) = g_new0(GeanyBuildCommand, build_groups_count[grp]); for (i = 0; i < GEANY_BC_CMDENTRIES_COUNT; i++) set_command(&(*(dst->dst[grp]))[cmd], i, entries[i]); (*(dst->dst[grp]))[cmd].exists = TRUE; (*(dst->dst[grp]))[cmd].changed = TRUE; changed = TRUE; } } else { for (i = 0; i < GEANY_BC_CMDENTRIES_COUNT; i++) g_free(entries[i]); } return changed; } static gboolean read_regex(GtkWidget *regexentry, gchar **src, gchar **dst) { gboolean changed = FALSE; const gchar *reg = gtk_entry_get_text(GTK_ENTRY(regexentry)); if (((src == NULL /* originally there was no regex */ || *src == NULL) /* or it was NULL*/ && !EMPTY(reg)) /* and something was typed */ || (src != NULL /* originally there was a regex*/ && (*src == NULL /* and either it was NULL */ || strcmp(*src, reg) != 0))) /* or it has been changed */ { if (dst != NULL) { SETPTR(*dst, g_strdup(reg)); changed = TRUE; } } return changed; } static gboolean build_read_commands(BuildDestination *dst, BuildTableData table_data, gint response) { guint cmdindex, cmd; gboolean changed = FALSE; if (response == GTK_RESPONSE_ACCEPT) { for (cmdindex = 0, cmd = 0; cmd < build_groups_count[GEANY_GBG_FT]; ++cmdindex, ++cmd) changed |= read_row(dst, table_data, cmdindex, GEANY_GBG_FT, cmd); for (cmd = 0; cmd < build_groups_count[GEANY_GBG_NON_FT]; ++cmdindex, ++cmd) changed |= read_row(dst, table_data, cmdindex, GEANY_GBG_NON_FT, cmd); for (cmd = 0; cmd < build_groups_count[GEANY_GBG_EXEC]; ++cmdindex, ++cmd) changed |= read_row(dst, table_data, cmdindex, GEANY_GBG_EXEC, cmd); changed |= read_regex(table_data->fileregex, table_data->fileregexstring, dst->fileregexstr); changed |= read_regex(table_data->nonfileregex, table_data->nonfileregexstring, dst->nonfileregexstr); } return changed; } void build_read_project(GeanyFiletype *ft, BuildTableData build_properties) { BuildDestination menu_dst; if (ft != NULL) { menu_dst.dst[GEANY_GBG_FT] = &(ft->priv->projfilecmds); menu_dst.fileregexstr = &(ft->priv->projerror_regex_string); } else { menu_dst.dst[GEANY_GBG_FT] = NULL; menu_dst.fileregexstr = NULL; } menu_dst.dst[GEANY_GBG_NON_FT] = &non_ft_proj; menu_dst.dst[GEANY_GBG_EXEC] = &exec_proj; menu_dst.nonfileregexstr = ®ex_proj; build_read_commands(&menu_dst, build_properties, GTK_RESPONSE_ACCEPT); } static void show_build_commands_dialog(void) { GtkWidget *dialog, *table, *vbox; GeanyDocument *doc = document_get_current(); GeanyFiletype *ft = NULL; const gchar *title = _("Set Build Commands"); gint response; BuildTableData table_data; BuildDestination prefdsts; if (doc != NULL) ft = doc->file_type; dialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(main_widgets.window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL); table = build_commands_table(doc, GEANY_BCS_PREF, &table_data, ft); vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog)); gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 0); gtk_widget_show_all(dialog); /* run modally to prevent user changing idx filetype */ response = gtk_dialog_run(GTK_DIALOG(dialog)); prefdsts.dst[GEANY_GBG_NON_FT] = &non_ft_pref; if (ft != NULL) { prefdsts.dst[GEANY_GBG_FT] = &(ft->priv->homefilecmds); prefdsts.fileregexstr = &(ft->priv->homeerror_regex_string); prefdsts.dst[GEANY_GBG_EXEC] = &(ft->priv->homeexeccmds); } else { prefdsts.dst[GEANY_GBG_FT] = NULL; prefdsts.fileregexstr = NULL; prefdsts.dst[GEANY_GBG_EXEC] = NULL; } prefdsts.nonfileregexstr = ®ex_pref; if (build_read_commands(&prefdsts, table_data, response) && ft != NULL) filetypes_save_commands(ft); build_free_fields(table_data); build_menu_update(doc); gtk_widget_destroy(dialog); } /* Creates the relevant build menu if necessary. */ BuildMenuItems *build_get_menu_items(gint filetype_idx) { BuildMenuItems *items; items = &menu_items; if (items->menu == NULL) create_build_menu(items); return items; } /*---------------------------------------------------------- * * Load and store configuration * * ---------------------------------------------------------*/ static const gchar *build_grp_name = "build-menu"; /* config format for build-menu group is prefix_gg_nn_xx=value * where gg = FT, NF, EX for the command group * nn = 2 digit command number * xx = LB for label, CM for command and WD for working dir */ static const gchar *groups[GEANY_GBG_COUNT] = { "FT", "NF", "EX" }; static const gchar *fixedkey="xx_xx_xx"; #define set_key_grp(key, grp) (key[prefixlen + 0] = grp[0], key[prefixlen + 1] = grp[1]) #define set_key_cmd(key, cmd) (key[prefixlen + 3] = cmd[0], key[prefixlen + 4] = cmd[1]) #define set_key_fld(key, fld) (key[prefixlen + 6] = fld[0], key[prefixlen + 7] = fld[1]) static void build_load_menu_grp(GKeyFile *config, GeanyBuildCommand **dst, gint grp, gchar *prefix, gboolean loc) { guint cmd; gsize prefixlen; /* NOTE prefixlen used in macros above */ GeanyBuildCommand *dstcmd; gchar *key; static gchar cmdbuf[3] = " "; if (*dst == NULL) *dst = g_new0(GeanyBuildCommand, build_groups_count[grp]); dstcmd = *dst; prefixlen = prefix == NULL ? 0 : strlen(prefix); key = g_strconcat(prefix == NULL ? "" : prefix, fixedkey, NULL); for (cmd = 0; cmd < build_groups_count[grp]; ++cmd) { gchar *label; if (cmd >= 100) break; /* ensure no buffer overflow */ sprintf(cmdbuf, "%02u", cmd); set_key_grp(key, groups[grp]); set_key_cmd(key, cmdbuf); set_key_fld(key, "LB"); if (loc) label = g_key_file_get_locale_string(config, build_grp_name, key, NULL, NULL); else label = g_key_file_get_string(config, build_grp_name, key, NULL); if (label != NULL) { dstcmd[cmd].exists = TRUE; SETPTR(dstcmd[cmd].label, label); set_key_fld(key,"CM"); SETPTR(dstcmd[cmd].command, g_key_file_get_string(config, build_grp_name, key, NULL)); set_key_fld(key,"WD"); SETPTR(dstcmd[cmd].working_dir, g_key_file_get_string(config, build_grp_name, key, NULL)); } else dstcmd[cmd].exists = FALSE; } g_free(key); } /* for the specified source load new format build menu items or try to make some sense of * old format setings, not done perfectly but better than ignoring them */ void build_load_menu(GKeyFile *config, GeanyBuildSource src, gpointer p) { GeanyFiletype *ft; GeanyProject *pj; gchar **ftlist; gchar *value, *basedir, *makebasedir; gboolean bvalue = FALSE; if (g_key_file_has_group(config, build_grp_name)) { switch (src) { case GEANY_BCS_FT: ft = (GeanyFiletype*)p; if (ft == NULL) return; build_load_menu_grp(config, &(ft->priv->filecmds), GEANY_GBG_FT, NULL, TRUE); build_load_menu_grp(config, &(ft->priv->ftdefcmds), GEANY_GBG_NON_FT, NULL, TRUE); build_load_menu_grp(config, &(ft->priv->execcmds), GEANY_GBG_EXEC, NULL, TRUE); SETPTR(ft->error_regex_string, g_key_file_get_string(config, build_grp_name, "error_regex", NULL)); break; case GEANY_BCS_HOME_FT: ft = (GeanyFiletype*)p; if (ft == NULL) return; build_load_menu_grp(config, &(ft->priv->homefilecmds), GEANY_GBG_FT, NULL, FALSE); build_load_menu_grp(config, &(ft->priv->homeexeccmds), GEANY_GBG_EXEC, NULL, FALSE); SETPTR(ft->priv->homeerror_regex_string, g_key_file_get_string(config, build_grp_name, "error_regex", NULL)); break; case GEANY_BCS_PREF: build_load_menu_grp(config, &non_ft_pref, GEANY_GBG_NON_FT, NULL, FALSE); build_load_menu_grp(config, &exec_pref, GEANY_GBG_EXEC, NULL, FALSE); SETPTR(regex_pref, g_key_file_get_string(config, build_grp_name, "error_regex", NULL)); break; case GEANY_BCS_PROJ: build_load_menu_grp(config, &non_ft_proj, GEANY_GBG_NON_FT, NULL, FALSE); build_load_menu_grp(config, &exec_proj, GEANY_GBG_EXEC, NULL, FALSE); SETPTR(regex_proj, g_key_file_get_string(config, build_grp_name, "error_regex", NULL)); pj = (GeanyProject*)p; if (p == NULL) return; ftlist = g_key_file_get_string_list(config, build_grp_name, "filetypes", NULL, NULL); if (ftlist != NULL) { gchar **ftname; if (pj->priv->build_filetypes_list == NULL) pj->priv->build_filetypes_list = g_ptr_array_new(); g_ptr_array_set_size(pj->priv->build_filetypes_list, 0); for (ftname = ftlist; *ftname != NULL; ++ftname) { ft = filetypes_lookup_by_name(*ftname); if (ft != NULL) { gchar *regkey = g_strdup_printf("%serror_regex", *ftname); g_ptr_array_add(pj->priv->build_filetypes_list, ft); SETPTR(ft->priv->projerror_regex_string, g_key_file_get_string(config, build_grp_name, regkey, NULL)); g_free(regkey); build_load_menu_grp(config, &(ft->priv->projfilecmds), GEANY_GBG_FT, *ftname, FALSE); build_load_menu_grp(config, &(ft->priv->projexeccmds), GEANY_GBG_EXEC, *ftname, FALSE); } } g_free(ftlist); } break; default: /* defaults don't load from config, see build_init */ break; } } /* load old [build_settings] values if there is no value defined by [build-menu] */ /* set GeanyBuildCommand if it doesn't already exist and there is a command */ /* TODO: rewrite as function */ #define ASSIGNIF(type, id, string, value) \ do { \ gchar *ASSIGNF__value = (value); \ if (!EMPTY(ASSIGNF__value) && ! type[GBO_TO_CMD(id)].exists) { \ type[GBO_TO_CMD(id)].exists = TRUE; \ SETPTR(type[GBO_TO_CMD(id)].label, g_strdup(string)); \ SETPTR(type[GBO_TO_CMD(id)].command, ASSIGNF__value); \ SETPTR(type[GBO_TO_CMD(id)].working_dir, NULL); \ type[GBO_TO_CMD(id)].old = TRUE; \ } else \ g_free(ASSIGNF__value); \ } while (0) switch (src) { case GEANY_BCS_FT: ft = (GeanyFiletype*)p; value = g_key_file_get_string(config, "build_settings", "compiler", NULL); if (value != NULL) { if (ft->priv->filecmds == NULL) ft->priv->filecmds = g_new0(GeanyBuildCommand, build_groups_count[GEANY_GBG_FT]); ASSIGNIF(ft->priv->filecmds, GEANY_GBO_COMPILE, _("_Compile"), value); } value = g_key_file_get_string(config, "build_settings", "linker", NULL); if (value != NULL) { if (ft->priv->filecmds == NULL) ft->priv->filecmds = g_new0(GeanyBuildCommand, build_groups_count[GEANY_GBG_FT]); ASSIGNIF(ft->priv->filecmds, GEANY_GBO_BUILD, _("_Build"), value); } value = g_key_file_get_string(config, "build_settings", "run_cmd", NULL); if (value != NULL) { if (ft->priv->execcmds == NULL) ft->priv->execcmds = g_new0(GeanyBuildCommand, build_groups_count[GEANY_GBG_EXEC]); ASSIGNIF(ft->priv->execcmds, GEANY_GBO_EXEC, _("_Execute"), value); } if (ft->error_regex_string == NULL) ft->error_regex_string = g_key_file_get_string(config, "build_settings", "error_regex", NULL); break; case GEANY_BCS_PROJ: if (non_ft_pref == NULL) non_ft_pref = g_new0(GeanyBuildCommand, build_groups_count[GEANY_GBG_NON_FT]); basedir = project_get_base_path(); if (basedir == NULL) basedir = g_strdup("%d"); bvalue = g_key_file_get_boolean(config, "project", "make_in_base_path", NULL); if (bvalue) makebasedir = g_strdup(basedir); else makebasedir = g_strdup("%d"); if (non_ft_pref[GBO_TO_CMD(GEANY_GBO_MAKE_ALL)].old) SETPTR(non_ft_pref[GBO_TO_CMD(GEANY_GBO_MAKE_ALL)].working_dir, g_strdup(makebasedir)); if (non_ft_pref[GBO_TO_CMD(GEANY_GBO_CUSTOM)].old) SETPTR(non_ft_pref[GBO_TO_CMD(GEANY_GBO_CUSTOM)].working_dir, g_strdup(makebasedir)); if (non_ft_pref[GBO_TO_CMD(GEANY_GBO_MAKE_OBJECT)].old) SETPTR(non_ft_pref[GBO_TO_CMD(GEANY_GBO_MAKE_OBJECT)].working_dir, g_strdup("%d")); value = g_key_file_get_string(config, "project", "run_cmd", NULL); if (!EMPTY(value)) { if (exec_proj == NULL) exec_proj = g_new0(GeanyBuildCommand, build_groups_count[GEANY_GBG_EXEC]); if (! exec_proj[GBO_TO_CMD(GEANY_GBO_EXEC)].exists) { exec_proj[GBO_TO_CMD(GEANY_GBO_EXEC)].exists = TRUE; SETPTR(exec_proj[GBO_TO_CMD(GEANY_GBO_EXEC)].label, g_strdup(_("_Execute"))); SETPTR(exec_proj[GBO_TO_CMD(GEANY_GBO_EXEC)].command, value); SETPTR(exec_proj[GBO_TO_CMD(GEANY_GBO_EXEC)].working_dir, g_strdup(basedir)); exec_proj[GBO_TO_CMD(GEANY_GBO_EXEC)].old = TRUE; } } g_free(makebasedir); g_free(basedir); break; case GEANY_BCS_PREF: value = g_key_file_get_string(config, "tools", "make_cmd", NULL); if (value != NULL) { if (non_ft_pref == NULL) non_ft_pref = g_new0(GeanyBuildCommand, build_groups_count[GEANY_GBG_NON_FT]); ASSIGNIF(non_ft_pref, GEANY_GBO_CUSTOM, _("Make Custom _Target..."), g_strdup_printf("%s ", value)); ASSIGNIF(non_ft_pref, GEANY_GBO_MAKE_OBJECT, _("Make _Object"), g_strdup_printf("%s %%e.o",value)); ASSIGNIF(non_ft_pref, GEANY_GBO_MAKE_ALL, _("_Make"), value); } break; default: break; } } static guint build_save_menu_grp(GKeyFile *config, GeanyBuildCommand *src, gint grp, gchar *prefix) { guint cmd; gsize prefixlen; /* NOTE prefixlen used in macros above */ gchar *key; guint count = 0; enum GeanyBuildCmdEntries i; if (src == NULL) return 0; prefixlen = prefix == NULL ? 0 : strlen(prefix); key = g_strconcat(prefix == NULL ? "" : prefix, fixedkey, NULL); for (cmd = 0; cmd < build_groups_count[grp]; ++cmd) { if (src[cmd].exists) ++count; if (src[cmd].changed) { static gchar cmdbuf[4] = " "; if (cmd >= 100) break; /* ensure no buffer overflow */ sprintf(cmdbuf, "%02u", cmd); set_key_grp(key, groups[grp]); set_key_cmd(key, cmdbuf); if (src[cmd].exists) { for (i = 0; i < GEANY_BC_CMDENTRIES_COUNT; i++) { set_key_fld(key, config_keys[i]); g_key_file_set_string(config, build_grp_name, key, id_to_str(&src[cmd], i)); } } else { for (i = 0; i < GEANY_BC_CMDENTRIES_COUNT; i++) { set_key_fld(key, config_keys[i]); g_key_file_remove_key(config, build_grp_name, key, NULL); } } } } g_free(key); return count; } typedef struct ForEachData { GKeyFile *config; GPtrArray *ft_names; } ForEachData; static void foreach_project_filetype(gpointer data, gpointer user_data) { GeanyFiletype *ft = data; ForEachData *d = user_data; guint i = 0; gchar *regkey = g_strdup_printf("%serror_regex", ft->name); i += build_save_menu_grp(d->config, ft->priv->projfilecmds, GEANY_GBG_FT, ft->name); i += build_save_menu_grp(d->config, ft->priv->projexeccmds, GEANY_GBG_EXEC, ft->name); if (!EMPTY(ft->priv->projerror_regex_string)) { g_key_file_set_string(d->config, build_grp_name, regkey, ft->priv->projerror_regex_string); i++; } else g_key_file_remove_key(d->config, build_grp_name, regkey, NULL); g_free(regkey); if (i > 0) g_ptr_array_add(d->ft_names, ft->name); } /* TODO: untyped ptr is too ugly (also for build_load_menu) */ void build_save_menu(GKeyFile *config, gpointer ptr, GeanyBuildSource src) { GeanyFiletype *ft; GeanyProject *pj; ForEachData data; switch (src) { case GEANY_BCS_HOME_FT: ft = (GeanyFiletype*)ptr; if (ft == NULL) return; build_save_menu_grp(config, ft->priv->homefilecmds, GEANY_GBG_FT, NULL); build_save_menu_grp(config, ft->priv->homeexeccmds, GEANY_GBG_EXEC, NULL); if (!EMPTY(ft->priv->homeerror_regex_string)) g_key_file_set_string(config, build_grp_name, "error_regex", ft->priv->homeerror_regex_string); else g_key_file_remove_key(config, build_grp_name, "error_regex", NULL); break; case GEANY_BCS_PREF: build_save_menu_grp(config, non_ft_pref, GEANY_GBG_NON_FT, NULL); build_save_menu_grp(config, exec_pref, GEANY_GBG_EXEC, NULL); if (!EMPTY(regex_pref)) g_key_file_set_string(config, build_grp_name, "error_regex", regex_pref); else g_key_file_remove_key(config, build_grp_name, "error_regex", NULL); break; case GEANY_BCS_PROJ: pj = (GeanyProject*)ptr; build_save_menu_grp(config, non_ft_proj, GEANY_GBG_NON_FT, NULL); build_save_menu_grp(config, exec_proj, GEANY_GBG_EXEC, NULL); if (!EMPTY(regex_proj)) g_key_file_set_string(config, build_grp_name, "error_regex", regex_proj); else g_key_file_remove_key(config, build_grp_name, "error_regex", NULL); if (pj->priv->build_filetypes_list != NULL) { data.config = config; data.ft_names = g_ptr_array_new(); g_ptr_array_foreach(pj->priv->build_filetypes_list, foreach_project_filetype, (gpointer)(&data)); if (data.ft_names->pdata != NULL) g_key_file_set_string_list(config, build_grp_name, "filetypes", (const gchar**)(data.ft_names->pdata), data.ft_names->len); else g_key_file_remove_key(config, build_grp_name, "filetypes", NULL); g_ptr_array_free(data.ft_names, TRUE); } break; default: /* defaults and GEANY_BCS_FT can't save */ break; } } /* FIXME: count is int only because calling code doesn't handle checking its value itself */ void build_set_group_count(GeanyBuildGroup grp, gint count) { guint i, sum; g_return_if_fail(count >= 0); if ((guint) count > build_groups_count[grp]) build_groups_count[grp] = (guint) count; for (i = 0, sum = 0; i < GEANY_GBG_COUNT; ++i) sum += build_groups_count[i]; build_items_count = sum; } /** Get the count of commands for the group * * Get the number of commands in the group specified by @a grp. * * @param grp the group of the specified menu item. * * @return a count of the number of commands in the group * **/ GEANY_API_SYMBOL guint build_get_group_count(const GeanyBuildGroup grp) { g_return_val_if_fail(grp < GEANY_GBG_COUNT, 0); return build_groups_count[grp]; } static void on_project_close(void) { /* remove project regexen */ SETPTR(regex_proj, NULL); } static struct { const gchar *label; const gchar *command; const gchar *working_dir; GeanyBuildCommand **ptr; gint index; } default_cmds[] = { { N_("_Make"), "make", NULL, &non_ft_def, GBO_TO_CMD(GEANY_GBO_MAKE_ALL)}, { N_("Make Custom _Target..."), "make ", NULL, &non_ft_def, GBO_TO_CMD(GEANY_GBO_CUSTOM)}, { N_("Make _Object"), "make %e.o", NULL, &non_ft_def, GBO_TO_CMD(GEANY_GBO_MAKE_OBJECT)}, { N_("_Execute"), "./%e", NULL, &exec_def, GBO_TO_CMD(GEANY_GBO_EXEC)}, { NULL, NULL, NULL, NULL, 0 } }; void build_init(void) { GtkWidget *item; GtkWidget *toolmenu; gint cmdindex; g_signal_connect(geany_object, "project-close", on_project_close, NULL); ft_def = g_new0(GeanyBuildCommand, build_groups_count[GEANY_GBG_FT]); non_ft_def = g_new0(GeanyBuildCommand, build_groups_count[GEANY_GBG_NON_FT]); exec_def = g_new0(GeanyBuildCommand, build_groups_count[GEANY_GBG_EXEC]); run_info = g_new0(RunInfo, build_groups_count[GEANY_GBG_EXEC]); for (cmdindex = 0; default_cmds[cmdindex].command != NULL; ++cmdindex) { GeanyBuildCommand *cmd = &((*(default_cmds[cmdindex].ptr))[ default_cmds[cmdindex].index ]); cmd->exists = TRUE; cmd->label = g_strdup(_(default_cmds[cmdindex].label)); cmd->command = g_strdup(default_cmds[cmdindex].command); cmd->working_dir = g_strdup(default_cmds[cmdindex].working_dir); } /* create the toolbar Build item sub menu */ toolmenu = gtk_menu_new(); g_object_ref(toolmenu); /* build the code */ item = ui_image_menu_item_new(GEANY_STOCK_BUILD, _("_Build")); gtk_widget_show(item); gtk_container_add(GTK_CONTAINER(toolmenu), item); g_signal_connect(item, "activate", G_CALLBACK(on_toolbutton_build_activate), GBO_TO_POINTER(GEANY_GBO_BUILD)); widgets.toolitem_build = item; item = gtk_separator_menu_item_new(); gtk_widget_show(item); gtk_container_add(GTK_CONTAINER(toolmenu), 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(toolmenu), item); g_signal_connect(item, "activate", G_CALLBACK(on_toolbutton_make_activate), GBO_TO_POINTER(GEANY_GBO_MAKE_ALL)); widgets.toolitem_make_all = item; /* build the code with make custom */ item = gtk_image_menu_item_new_with_mnemonic(_("Make Custom _Target...")); gtk_widget_show(item); gtk_container_add(GTK_CONTAINER(toolmenu), item); g_signal_connect(item, "activate", G_CALLBACK(on_toolbutton_make_activate), GBO_TO_POINTER(GEANY_GBO_CUSTOM)); widgets.toolitem_make_custom = item; /* build the code with make object */ item = gtk_image_menu_item_new_with_mnemonic(_("Make _Object")); gtk_widget_show(item); gtk_container_add(GTK_CONTAINER(toolmenu), item); g_signal_connect(item, "activate", G_CALLBACK(on_toolbutton_make_activate), GBO_TO_POINTER(GEANY_GBO_MAKE_OBJECT)); widgets.toolitem_make_object = item; item = gtk_separator_menu_item_new(); gtk_widget_show(item); gtk_container_add(GTK_CONTAINER(toolmenu), item); /* arguments */ item = ui_image_menu_item_new(GTK_STOCK_PREFERENCES, _("_Set Build Commands")); gtk_widget_show(item); gtk_container_add(GTK_CONTAINER(toolmenu), item); g_signal_connect(item, "activate", G_CALLBACK(on_set_build_commands_activate), NULL); widgets.toolitem_set_args = item; /* get toolbar action pointers */ widgets.build_action = toolbar_get_action_by_name("Build"); widgets.compile_action = toolbar_get_action_by_name("Compile"); widgets.run_action = toolbar_get_action_by_name("Run"); widgets.toolmenu = toolmenu; /* set the submenu to the toolbar item */ geany_menu_button_action_set_menu(GEANY_MENU_BUTTON_ACTION(widgets.build_action), toolmenu); }