/* * JoeyDev * Copyright (C) 2018-2023 Scott Duensing * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ #pragma clang diagnostic push #pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions" #pragma ide diagnostic ignored "cert-err34-c" // atoi warnings #include "common.h" #include "project.h" #include "cwalk.h" #include "utils.h" #include "http.h" #include "ssh.h" #include "messages.h" #include "compiler.h" #include "editor.h" #include "results.h" #define LOCAL_BUILD_RESULTS "build.tar.bz2" #define REMOTE_BUILD_RESULTS "build/build.tar.bz2" enum ProjectColumnsE { COL_FILENAME = 0, COL_COUNT }; typedef enum ProjectColumnsE ProjectColumnsT; // These have to match SectionDataT below. enum ProjectSectionTypeE { SECTION_HEADER = 0, SECTION_CODE, SECTION_BITMAP, SECTION_STENCIL, SECTION_VECTOR, SECTION_SOUND, SECTION_MUSIC, SECTION_RAW_DATA, SECTION_COOKED_DATA, SECTION_COUNT }; typedef enum ProjectSectionTypeE ProjectSectionTypeT; typedef struct SectionDataS { char *name; char *extension; } SectionDataT; // These have to match ProjectSectionTypeT above. static SectionDataT _sectionData[] = { { "Header", "*.h" }, { "Code", "*.c" }, { "Bitmap", "*.img" }, { "Stencil", "*.stn" }, { "Vector", "*.vic" }, { "Sound", "*.snd" }, { "Music", "*.mod" }, { "Raw Data", NULL }, { "Cooked Data", "*.dat" }, { NULL, NULL } }; static char **_sendList = NULL; #define BUILD_SETTINGS_RESPONSE_TEST 1 static void addToRecipeData(ProjectDataT *self, char *key, char *value); EVENT void buildTargetClicked(GtkButton *widget, gpointer userData); EVENT void btnEditRawClicked(GtkButton *widget, gpointer userData); EVENT void btnEditRecipeClicked(GtkButton *widget, gpointer userData); EVENT void btnNewRecipeClicked(GtkButton *widget, gpointer userData); static void clearRecipeData(ProjectDataT *self); EVENT void comboProjectTypeChanged(GtkComboBox *object, gpointer userData); static void cookFinished(CompilerContextT **context); static void decompressBuild(ArchiveT *archive); static void dialogCookOptions(char *filename, ProjectDataT *self); EVENT void fileCustomSet(GtkWidget *object, gpointer userData); static int findRecipeData(ProjectDataT *self, char *key); static gboolean isCustomJoeyLibProject(char *filename); static void loadConfig(ProjectDataT *self); static void loadProject(ProjectDataT *self); EVENT void menuProjectFileNew(GtkWidget *object, gpointer userData); EVENT void menuProjectFileOpen(GtkWidget *object, gpointer userData); EVENT void menuProjectFileSave(GtkWidget *object, gpointer userData); EVENT void menuProjectFileSaveAs(GtkWidget *object, gpointer userData); EVENT void menuProjectFileClose(GtkWidget *object, gpointer userData); EVENT void menuProjectProjectAdd(GtkWidget *object, gpointer userData); EVENT void menuProjectProjectRemove(GtkWidget *object, gpointer userData); EVENT void menuProjectProjectProperties(GtkWidget *object, gpointer userData); EVENT void menuProjectBuildSettings(GtkWidget *object, gpointer userData); EVENT void menuProjectBuildTargets(GtkWidget *object, gpointer userData); EVENT void menuProjectBuildLastResults(GtkWidget *object, gpointer userData); EVENT void menuProjectBuildCookRecipes(GtkWidget *object, gpointer userData); EVENT void menuProjectBuildBuild(GtkWidget *object, gpointer userData); EVENT void menuProjectHelpProject(GtkWidget *object, gpointer userData); static void receiveSFTP(SFTPT *sftp); static void saveConfig(ProjectDataT *self); static void sendSFTP(SFTPT *sftp); static TargetT **targetArrayCopy(TargetT **targets); static void targetArrayDelete(TargetT ***array); EVENT void treeProjectRowActivated(GtkTreeView *treeView, GtkTreePath *path, GtkTreeViewColumn *column, gpointer userData); static gboolean updateBuildOptions(ProjectDataT *self); static gboolean waitForBuild(gpointer userData); EVENT gboolean winProjectClose(GtkWidget *object, gpointer userData); static void winProjectDelete(gpointer userData); static void addToRecipeData(ProjectDataT *self, char *key, char *value) { int i; StringHashT *s; i = findRecipeData(self, key); if (i >= 0) arrdel(self->recipes, i); s = NEW(StringHashT); s->key = strdup(key); s->value = strdup(value); arrput(self->recipes, s); } EVENT void buildTargetClicked(GtkButton *widget, gpointer userData) { ArchT *arch = (ArchT *)userData; arch->selected = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); } EVENT void btnEditRawClicked(GtkButton *widget, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; char *filename = NULL; GtkWidget *lblRaw = utilFindChildWidget(self->tempWidget, "lblRaw"); (void)widget; gtk_dialog_response(GTK_DIALOG(self->tempWidget), GTK_RESPONSE_CANCEL); filename = utilCreateString("%s%s", self->windowData.path, gtk_label_get_text(GTK_LABEL(lblRaw))); winEditorCreate(filename); DEL(filename); } EVENT void btnEditRecipeClicked(GtkButton *widget, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; char *file = NULL; GtkWidget *fileRecipe = utilFindChildWidget(self->tempWidget, "fileRecipe"); (void)widget; gtk_dialog_response(GTK_DIALOG(self->tempWidget), GTK_RESPONSE_CANCEL); file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fileRecipe)); if (utilFileExists(file)) { winEditorCreate(file); } DEL(file); } EVENT void btnNewRecipeClicked(GtkButton *widget, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; char *newFile = NULL; char *fromTemp = NULL; char *toTemp = NULL; (void)widget; if (utilFileSaveOtherAs((WindowDataT *)userData, "*.c", "Recipe", &newFile)) { fromTemp = utilCreateString("%s%s", __resourcePath, "recipe.c"); utilFileCopy(fromTemp, newFile); DEL(fromTemp); fromTemp = utilCreateString("%s%s", __resourcePath, "gitignore"); toTemp = utilCreateString("%s%s", self->windowData.path, ".gitignore"); utilFileCopy(fromTemp, toTemp); DEL(toTemp); DEL(fromTemp); fromTemp = utilCreateString("%s%s", __resourcePath, "gitattributes"); toTemp = utilCreateString("%s%s", self->windowData.path, ".gitattributes"); utilFileCopy(fromTemp, toTemp); DEL(toTemp); DEL(fromTemp); gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(self->tempWidget), newFile); } } static void clearRecipeData(ProjectDataT *self) { // Clear recipe data. while (arrlen(self->recipes) > 0) { DEL(self->recipes[0]->key); DEL(self->recipes[0]->value); DEL(self->recipes[0]); arrdel(self->recipes, 0); } //ARRFREE(self->recipes); // This is making Memwatch mad. } EVENT void comboProjectTypeChanged(GtkComboBox *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; char *temp = NULL; temp = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(object)); if (strcmp(temp, "JoeyLib") == 0) { // JoeyLib project - Disable JoeyLib settings. gtk_widget_set_sensitive(self->tempWidget, FALSE); gtk_widget_set_sensitive(self->tempWidget2, FALSE); gtk_widget_set_sensitive(self->tempWidget3, FALSE); } else { // Application project - Enable JoeyLib settings. gtk_widget_set_sensitive(self->tempWidget, TRUE); gtk_widget_set_sensitive(self->tempWidget2, TRUE); gtk_widget_set_sensitive(self->tempWidget3, TRUE); } DEL(temp); } static void cookFinished(CompilerContextT **context) { CompilerContextT *ctx = *context; ProjectDataT *self = (ProjectDataT *)ctx->userData; char *raw; char *recipe; // Was there a compiler error? if (compilerHadError(context)) { compilerDeleteContext(context); return; } // If this is not first call, process context. if (self->tempInteger != -1) { if (ctx->programResult != 0) { message(MSG_ERROR, "Recipe %s returned %d", self->recipes[self->tempInteger]->value, ctx->programResult); } } compilerDeleteContext(context); // Is there something to cook? self->tempInteger++; if (self->tempInteger < arrlen(self->recipes)) { // Build path names. cwk_path_change_basename(self->windowData.filename, self->recipes[self->tempInteger]->key, __utilFilenameBuffer, sizeof(__utilFilenameBuffer)); raw = strdup(__utilFilenameBuffer); cwk_path_change_basename(self->windowData.filename, self->recipes[self->tempInteger]->value, __utilFilenameBuffer, sizeof(__utilFilenameBuffer)); recipe = strdup(__utilFilenameBuffer); // Run it! message(MSG_INFO, "Cooking %s", self->recipes[self->tempInteger]->key); ctx = compilerNewContext(cookFinished, self); compilerRunRecipe(ctx, recipe, raw, self->windowData.path); DEL(recipe); DEL(raw); } else { // Finished with recipe list. message(MSG_INFO, "Finished Cooking"); } } static void decompressBuild(ArchiveT *archive) { ProjectDataT *self = (ProjectDataT *)archive->userData; message(MSG_INFO, "Processing build results"); winBuildResultsCreate(self); } static void dialogCookOptions(char *filename, ProjectDataT *self) { GtkWidget *dialogCookSettings; GtkWidget *lblRaw; GtkWidget *fileRecipe; GtkWidget *btnNewRecipe; GtkWidget *btnCancel; GtkWidget *btnOkay; int original; int result; char *raw = NULL; char *temp = NULL; char *widgetNames[] = { "dialogCookSettings", "lblRaw", "fileRecipe", "btnNewRecipe", "btnCancel", "btnOkay", NULL }; GtkWidget **widgets[] = { &dialogCookSettings, &lblRaw, &fileRecipe, &btnNewRecipe, &btnCancel, &btnOkay }; utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/Cook.glade", widgetNames, widgets, self); raw = utilFileBasename(filename); gtk_label_set_text(GTK_LABEL(lblRaw), raw); self->tempWidget = dialogCookSettings; original = findRecipeData(self, raw); if (original >= 0) { cwk_path_change_basename(self->windowData.filename, self->recipes[original]->value, __utilFilenameBuffer, sizeof(__utilFilenameBuffer)); gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(fileRecipe), __utilFilenameBuffer); } result = gtk_dialog_run(GTK_DIALOG(dialogCookSettings)); if (result == GTK_RESPONSE_OK) { temp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fileRecipe)); if (temp != NULL) { // Convert to relative path. cwk_path_get_relative(self->windowData.path, temp, __utilFilenameBuffer, sizeof(__utilFilenameBuffer)); if (__utilFilenameBuffer[0] != 0) { DEL(temp); temp = strdup(__utilFilenameBuffer); } // Did the recipe change? if (!((original >= 0) && (strcmp(self->recipes[original]->value, temp) == 0))) { addToRecipeData(self, raw, temp); utilSetDirty((WindowDataT *)self, TRUE); } DEL(temp); } } DEL(raw); self->tempWidget = NULL; gtk_widget_destroy(dialogCookSettings); } EVENT void fileCustomSet(GtkWidget *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; char *temp = NULL; gboolean isJoeyLib = FALSE; temp = (char *)gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(object)); isJoeyLib = isCustomJoeyLibProject(temp); DEL(temp); if (isJoeyLib == FALSE) { utilMessageDialog(self->tempWidget4, "Not a Custom JoeyLib Project", "The selected project is not a custom JoeyLib library project."); // Deselect custom JoeyLib. gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->tempWidget), TRUE); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->tempWidget2), FALSE); gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(self->tempWidget3), ""); } else { // Select custom JoeyLib. gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->tempWidget), FALSE); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->tempWidget2), TRUE); } } static int findRecipeData(ProjectDataT *self, char *key) { int i; int result = -1; for (i=0; irecipes); i++) { if (strcmp(self->recipes[i]->key, key) == 0) { result = i; break; } } return result; } static gboolean isCustomJoeyLibProject(char *filename) { FILE *in = NULL; char *line = NULL; size_t len = 0; char *c = NULL; gboolean isJoeyLib = FALSE; // Is this project a custom JoeyLib project? in = fopen(filename, "rt"); if (in != NULL) { utilEnsureBufferSize((unsigned char **)&line, (int *)&len, 1024); // Not technically needed, but fixes a pointer warning from memmaker. while (utilGetLine(&line, &len, in) != -1) { if (strlen(line) > 0) line[strlen(line) - 1] = 0; c = utilGetToken(line, " ", "\"", "\""); utilDequote(c); // Is this a 'project' line? if (strcasecmp(c, "project") == 0) { c = utilGetToken(NULL, " ", "\"", "\""); utilDequote(c); if (strcmp(c, "JoeyLib") == 0) { isJoeyLib = TRUE; } break; } } fclose(in); DEL(line); } return isJoeyLib; } static void loadConfig(ProjectDataT *self) { FILE *in = NULL; char *line = NULL; size_t len = 0; size_t count = 0; self->configName = utilCreateString("%s%cjoeydev%cbuild.conf", g_get_user_config_dir(), UTIL_PATH_CHAR, UTIL_PATH_CHAR); if (utilFileExists(self->configName)) { in = fopen(self->configName, "rt"); if (in != NULL) { utilEnsureBufferSize((unsigned char **)&line, (int *)&len, 1024); // Not technically needed, but fixes a pointer warning from memmaker. while (utilGetLine(&line, &len, in) != -1) { if (strlen(line) > 0) line[strlen(line) - 1] = 0; switch (count) { case 0: // Version Number case 1: // Separator line break; case 2: // Host DEL(self->buildHost); self->buildHost = strdup(line); break; case 3: // HTTP Port self->buildHTTPPort = atoi(line); break; case 4: // SSH Port self->buildSSHPort = atoi(line); break; case 5: // User DEL(self->buildUser); self->buildUser = strdup(line); break; case 6: // Password DEL(self->buildPassword); self->buildPassword = utilDeobfuscateASCII(line); break; default: // Oops break; } count++; } fclose(in); DEL(line); } else { message(MSG_SEVERE, "Unknown error attempting to load build configuration!"); } } else { // No config file. Set defaults. DEL(self->buildHost); DEL(self->buildUser); DEL(self->buildPassword); self->buildHost = strdup("build.joeylib.com"); self->buildHTTPPort = 6502; self->buildSSHPort = 816; self->buildUser = strdup(g_get_user_name()); self->buildPassword = strdup("SuperSecret"); saveConfig(self); } } static void loadProject(ProjectDataT *self) { FILE *in = NULL; char *line = NULL; size_t len = 0; char *c = NULL; TargetT *t = NULL; int i; int j; char *raw = NULL; in = fopen(self->windowData.filename, "rt"); if (in != NULL) { // Set all our known archs to FALSE. for (i=0; itargets); i++) { for (j=0; jtargets[i]->archs); j++) { self->targets[i]->archs[j]->selected = FALSE; } } // Load project. utilEnsureBufferSize((unsigned char **)&line, (int *)&len, 1024); // Not technically needed, but fixes a pointer warning from memmaker. while (utilGetLine(&line, &len, in) != -1) { if (strlen(line) > 0) line[strlen(line) - 1] = 0; c = utilGetToken(line, " ", "\"", "\""); utilDequote(c); // Is this a 'source' line? if (strcasecmp(c, "source") == 0) { c = utilGetToken(NULL, " ", "\"", "\""); utilDequote(c); cwk_path_get_relative(self->windowData.path, c, __utilFilenameBuffer, sizeof(__utilFilenameBuffer)); if (__utilFilenameBuffer[0] == 0) { projectAddToTree(self, c); } else { projectAddToTree(self, __utilFilenameBuffer); } continue; } // Is this a 'target' line? if (strcasecmp(c, "target") == 0) { c = utilGetToken(NULL, " ", "\"", "\""); utilDequote(c); // See if we know about this target. for (i=0; i < arrlen(self->targets); i++) { if (strcasecmp(self->targets[i]->name, c) == 0) { t = self->targets[i]; // We know this target. Iterate over specified arches and turn them on if known. while (c != NULL) { c = utilGetToken(NULL, " ", "\"", "\""); if (c != NULL) { utilDequote(c); for (j=0; jarchs); j++) { if (strcasecmp(t->archs[j]->name, c) == 0) { t->archs[j]->selected = TRUE; break; } } } } break; } } continue; } // Is this a 'recipe' line? if (strcasecmp(c, "recipe") == 0) { c = utilGetToken(NULL, " ", "\"", "\""); utilDequote(c); raw = strdup(c); c = utilGetToken(NULL, " ", "\"", "\""); utilDequote(c); cwk_path_get_relative(self->windowData.path, c, __utilFilenameBuffer, sizeof(__utilFilenameBuffer)); if (__utilFilenameBuffer[0] == 0) { addToRecipeData(self, raw, c); } else { addToRecipeData(self, raw, __utilFilenameBuffer); } DEL(raw); continue; } // Is this a 'project' line? if (strcasecmp(c, "project") == 0) { c = utilGetToken(NULL, " ", "\"", "\""); utilDequote(c); DEL(self->projectType); self->projectType = strdup(c); c = utilGetToken(NULL, " ", "\"", "\""); utilDequote(c); DEL(self->projectName); self->projectName = strdup(c); continue; } // Is this a 'joeylib' line? if (strcasecmp(c, "joeylib") == 0) { c = utilGetToken(NULL, " ", "\"", "\""); utilDequote(c); DEL(self->projectJoeyLib); self->projectJoeyLib = strdup(c); continue; } } fclose(in); DEL(line); utilSetDirty((WindowDataT *)self, FALSE); // Do again - loading text marks us dirty. } else { message(MSG_SEVERE, "Unknown error attempting to load %s!", self->windowData.filename); } if (self->projectType == NULL) { self->projectType = strdup("Application"); } if (self->projectName == NULL) { self->projectName = strdup("None"); } } EVENT void menuProjectFileNew(GtkWidget *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; GtkTreeIter iter; ProjectSectionTypeT i; GtkTreeStore *store; int x; int y; (void)object; // Is the project dirty? if (self->windowData.isDirty) { if (!utilQuestionDialog(self->windowData.window, "New Project", "You have unsaved changes. Create new project anyway?")) { return; } } // Reset project tree. gtk_tree_view_set_model(GTK_TREE_VIEW(self->treeProject), NULL); store = gtk_tree_store_new(1, G_TYPE_STRING); for (i=0; itreeProject), GTK_TREE_MODEL(store)); g_object_unref(store); // Select every target and architecture. for (x=0; xtargets); x++) { for (y=0; ytargets[x]->archs); y++) { self->targets[x]->archs[y]->selected = TRUE; } } clearRecipeData(self); // Clear project properties. DEL(self->projectName); DEL(self->projectType); DEL(self->projectJoeyLib); self->projectType = strdup("Application"); self->projectName = strdup("None"); self->projectJoeyLib = strdup("Latest"); // Nuke filename & mark clean. DEL(self->windowData.filename); DEL(self->windowData.path); utilSetDirty(&self->windowData, FALSE); } EVENT void menuProjectFileOpen(GtkWidget *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; if (utilFileOpen((WindowDataT *)userData, "*.joe", "Project")) { menuProjectFileNew(object, userData); loadProject(self); } } EVENT void menuProjectFileSave(GtkWidget *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(self->treeProject)); FILE *out = NULL; GtkTreeIter iter; GtkTreeIter child; char *temp = NULL; TargetT *t = NULL; ArchT *a = NULL; gboolean archWritten = FALSE; int i; int j; // Do we need to save? if (self->windowData.isDirty == TRUE) { // Do we have a filename? If not, kick 'em to SaveAs. if (self->windowData.filename == NULL) { menuProjectFileSaveAs(object, userData); return; } out = fopen(self->windowData.filename, "wt"); if (out != NULL) { // Save! Write out header. fprintf(out, "%s\n", PROJECT_VERSION); fprintf(out, "------------------------------------------------------------------------------\n"); // Write out project properties. fprintf(out, "project \"%s\" \"%s\"\n", self->projectType, self->projectName); fprintf(out, "joeylib \"%s\"\n", self->projectJoeyLib); // Write out file list. gtk_tree_model_get_iter_first(model, &iter); do { if (gtk_tree_model_iter_children(model, &child, &iter)) { do { gtk_tree_model_get(model, &child, COL_FILENAME, &temp, -1); fprintf(out, "source \"%s\"\n", temp); DEL(temp); // This generates an unknown pointer warning in memwatch. Pretty sure it's bogus. } while (gtk_tree_model_iter_next(model, &child)); } } while (gtk_tree_model_iter_next(model, &iter)); // Write out desired targets. for (i=0; itargets); i++) { t = self->targets[i]; archWritten = FALSE; for (j=0; jarchs); j++) { a = t->archs[j]; if (a->selected == TRUE) { if (archWritten == FALSE) { fprintf(out, "target %s", t->name); archWritten = TRUE; } fprintf(out, " %s", a->name); } } if (archWritten == TRUE) fprintf(out, "\n"); } // Write out any data file recipes. for (i=0; irecipes); i++) { fprintf(out, "recipe \"%s\" \"%s\"\n", self->recipes[i]->key, self->recipes[i]->value); } // Close file. fclose(out); // We're clean now. utilSetDirty((WindowDataT *)self, FALSE); } else { message(MSG_SEVERE, "Unknown error attempting to save %s!", self->windowData.filename); } } } EVENT void menuProjectFileSaveAs(GtkWidget *object, gpointer userData) { (void)object; if (utilFileSaveAs((WindowDataT *)userData, "*.joe", "Project")) { menuProjectFileSave(object, (ProjectDataT *)userData); } } EVENT void menuProjectFileClose(GtkWidget *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; (void)object; gtk_window_close(GTK_WINDOW(self->windowData.window)); } EVENT void menuProjectProjectAdd(GtkWidget *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; GtkWidget *dialog; GtkFileFilter *filter; ProjectSectionTypeT section; char *temp = NULL; (void)object; dialog = gtk_file_chooser_dialog_new("Add File to Project", GTK_WINDOW(self->windowData.window), GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL ); if (self->windowData.filename != NULL) { gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),self->windowData.path); } for (section=0; sectionwindowData.path != NULL) { cwk_path_get_relative(self->windowData.path,temp, __utilFilenameBuffer, sizeof(__utilFilenameBuffer)); DEL(temp); temp = strdup(__utilFilenameBuffer); } projectAddToTree(self, temp); utilSetDirty((WindowDataT *)self, TRUE); DEL(temp); } gtk_widget_destroy(dialog); } EVENT void menuProjectProjectRemove(GtkWidget *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self->treeProject)); gboolean found = FALSE; GtkWidget *dialog; GtkTreeModel *model; GtkTreeIter iter; char *name = NULL; int i; (void)object; // Is anything selected? if (gtk_tree_selection_get_selected(selection, &model, &iter)) { // Are we on a child item? if (gtk_tree_store_iter_depth(GTK_TREE_STORE(model), &iter) > 0) { found = TRUE; } } if (found) { // Delete cook recipe if it exists. gtk_tree_model_get(model, &iter, COL_FILENAME, &name, -1); i = findRecipeData(self, name); if (i >= 0) arrdel(self->recipes, i); DEL(name); // Remove item from tree. gtk_tree_store_remove(GTK_TREE_STORE(model), &iter); utilSetDirty((WindowDataT *)self, TRUE); } else { // Provide help. dialog = gtk_message_dialog_new(GTK_WINDOW(self->windowData.window), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "Please select a file to remove."); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } } EVENT void menuProjectProjectProperties(GtkWidget *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; GtkWidget *dialogProperties; GtkWidget *comboType; GtkWidget *txtName; GtkWidget *radioLatest; GtkWidget *radioCustom; GtkWidget *fileCustom; (void)object; char *temp = NULL; char *widgetNames[] = { "dialogProjectProperties", "comboProjectType", "txtProjectName", "radioLatest", "radioCustom", "fileCustom", NULL }; GtkWidget **widgets[] = { &dialogProperties, &comboType, &txtName, &radioLatest, &radioCustom, &fileCustom }; GtkEntryBuffer *buffer; int result; utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/ProjectProperties.glade", widgetNames, widgets, self); // Save for callbacks. self->tempWidget = radioLatest; self->tempWidget2 = radioCustom; self->tempWidget3 = fileCustom; self->tempWidget4 = dialogProperties; // What kind of project is this? if (strcmp(self->projectType, "JoeyLib") == 0) { gtk_combo_box_set_active(GTK_COMBO_BOX(comboType), 1); } else { gtk_combo_box_set_active(GTK_COMBO_BOX(comboType), 0); // Which JoeyLib are we using? if (strcmp(self->projectJoeyLib, "Latest") == 0) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radioLatest), TRUE); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radioCustom), FALSE); gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(fileCustom), ""); } else { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radioLatest), FALSE); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radioCustom), TRUE); gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(fileCustom), self->projectJoeyLib); } } comboProjectTypeChanged(GTK_COMBO_BOX(comboType), self); buffer = gtk_entry_get_buffer(GTK_ENTRY(txtName)); gtk_entry_buffer_set_text(GTK_ENTRY_BUFFER(buffer), self->projectName, strlen(self->projectName)); result = gtk_dialog_run(GTK_DIALOG(dialogProperties)); if (result == GTK_RESPONSE_OK) { // Did the project name change? if (strcmp(self->projectName, gtk_entry_buffer_get_text(GTK_ENTRY_BUFFER(buffer))) != 0) { DEL(self->projectName); self->projectName = strdup(gtk_entry_buffer_get_text(GTK_ENTRY_BUFFER(buffer))); utilSetDirty((WindowDataT *)self, TRUE); } // Did the project type change? temp = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(comboType)); if (strcmp(self->projectType, temp) != 0) { DEL(self->projectType); self->projectType = strdup(temp); utilSetDirty((WindowDataT *)self, TRUE); } DEL(temp); // Is it an application project? if (strcmp(self->projectType, "JoeyLib") != 0) { if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radioLatest))) { temp = strdup("Latest"); } else { temp = (char *)gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fileCustom)); if (!isCustomJoeyLibProject(temp)) { DEL(temp); temp = strdup("Latest"); utilMessageDialog(dialogProperties, "Not a Custom JoeyLib Project", "The selected project is not a custom JoeyLib library project.\n" "Using the latest build instead."); } } // Did the desired JoeyLib change? if (strcmp(self->projectJoeyLib, temp) != 0) { DEL(self->projectJoeyLib); self->projectJoeyLib = strdup(temp); utilSetDirty((WindowDataT *)self, TRUE); } DEL(temp); } } gtk_widget_destroy(dialogProperties); } EVENT void menuProjectBuildLastResults(GtkWidget *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; (void)object; winBuildResultsCreate(self); } EVENT void menuProjectBuildSettings(GtkWidget *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; GtkWidget *dialog; GtkWidget *dialogServerSettings; GtkWidget *txtHost; GtkWidget *txtHTTPPort; GtkWidget *txtSSHPort; GtkWidget *txtUser; GtkWidget *txtPassword; GtkWidget *btnTest; GtkWidget *btnOkay; char *widgetNames[] = { "dialogServerSettings", "txtHost", "txtHTTPPort", "txtSSHPort", "txtUser", "txtPassword", "btnTest", "btnOkay", NULL }; GtkWidget **widgets[] = { &dialogServerSettings, &txtHost, &txtHTTPPort, &txtSSHPort, &txtUser, &txtPassword, &btnTest, &btnOkay }; char temp[6]; int result = 0; SFTPT *ssh = NULL; char *error = NULL; (void)object; utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/BuildServer.glade", widgetNames, widgets, self); while (1) { gtk_entry_set_text(GTK_ENTRY(txtHost), self->buildHost); snprintf(temp, 6, "%d", self->buildHTTPPort); gtk_entry_set_text(GTK_ENTRY(txtHTTPPort), temp); snprintf(temp, 6, "%d", self->buildSSHPort); gtk_entry_set_text(GTK_ENTRY(txtSSHPort), temp); gtk_entry_set_text(GTK_ENTRY(txtUser), self->buildUser); gtk_entry_set_text(GTK_ENTRY(txtPassword), self->buildPassword); result = gtk_dialog_run(GTK_DIALOG(dialogServerSettings)); if (result == BUILD_SETTINGS_RESPONSE_TEST || result == GTK_RESPONSE_OK) { // Save settings to disk. DEL(self->buildHost); DEL(self->buildUser); DEL(self->buildPassword); self->buildHost = strdup(gtk_entry_get_text(GTK_ENTRY(txtHost))); self->buildHTTPPort = atoi(gtk_entry_get_text(GTK_ENTRY(txtHTTPPort))); self->buildSSHPort = atoi(gtk_entry_get_text(GTK_ENTRY(txtSSHPort))); self->buildUser = strdup(gtk_entry_get_text(GTK_ENTRY(txtUser))); self->buildPassword = strdup(gtk_entry_get_text(GTK_ENTRY(txtPassword))); saveConfig(self); if (result == BUILD_SETTINGS_RESPONSE_TEST) { gtk_widget_set_sensitive(btnTest, FALSE); gtk_widget_set_sensitive(btnOkay, FALSE); // Run server connection tests - HTTP. if (updateBuildOptions(self) == FALSE) { error = utilCreateString("Unable to connect to HTTP port."); } // Run server connection tests - SSH. ssh = sshConnect(self->buildHost, self->buildSSHPort, self->buildUser, self->buildPassword); if (ssh) { sshDisconnect(&ssh); } else { DEL(error); error = utilCreateString("Unable to connect to SSH port."); } // Did the tests pass? if (error == NULL) { dialog = gtk_message_dialog_new(GTK_WINDOW(dialogServerSettings), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "Successful connection to build server!"); } else { dialog = gtk_message_dialog_new(GTK_WINDOW(dialogServerSettings), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", error); } gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); DEL(error); gtk_widget_set_sensitive(btnTest, TRUE); gtk_widget_set_sensitive(btnOkay, TRUE); } else { // Try to load the build options in case they didn't click TEST. updateBuildOptions(self); // Close dialog on OKAY but not TEST. break; } } } gtk_widget_destroy(dialogServerSettings); } EVENT void menuProjectBuildTargets(GtkWidget *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; GtkWidget *dialog; GtkWidget *contentArea; GtkWidget *grid; GtkWidget *widget; TargetT *t; ArchT *a; char *temp; int i; int j; int result; TargetT **backup; (void)object; backup = targetArrayCopy(self->targets); grid = gtk_grid_new(); gtk_grid_set_column_homogeneous(GTK_GRID(grid), FALSE); gtk_grid_set_row_homogeneous(GTK_GRID(grid), TRUE); gtk_grid_set_column_spacing(GTK_GRID(grid), 15); gtk_grid_set_row_spacing(GTK_GRID(grid), 5); for (i=0; itargets); i++) { t = self->targets[i]; temp = utilCreateString("%s:", t->longName); widget = gtk_label_new(temp); DEL(temp); gtk_label_set_line_wrap(GTK_LABEL(widget), FALSE); gtk_label_set_xalign(GTK_LABEL(widget), 1.0); gtk_grid_attach(GTK_GRID(grid), widget, 0, i, 1, 1); for (j=0; jarchs); j++) { a = t->archs[j]; widget = gtk_check_button_new_with_label(a->name); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), a->selected); g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(buildTargetClicked), a); gtk_grid_attach(GTK_GRID(grid), widget, j + 1, i, 1, 1); } } dialog = gtk_dialog_new_with_buttons( "Targets", GTK_WINDOW(self->windowData.window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, "_Cancel", GTK_RESPONSE_CANCEL, "_OK", GTK_RESPONSE_OK, NULL ); contentArea = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); gtk_container_add(GTK_CONTAINER(contentArea), grid); gtk_widget_show_all(dialog); result = gtk_dialog_run(GTK_DIALOG(dialog)); if (result != GTK_RESPONSE_OK) { // Undo any changes. targetArrayDelete(&self->targets); self->targets = targetArrayCopy(backup); } else { // Did their selections change? for (i=0; itargets); i++) { for (j=0; jtargets[i]->archs); j++) { if (backup[i]->archs[j]->selected != self->targets[i]->archs[j]->selected) { utilSetDirty((WindowDataT *)self, TRUE); break; // Try to speed it up just a bit. :-) } } } } targetArrayDelete(&backup); gtk_widget_destroy(dialog); } EVENT void menuProjectBuildCookRecipes(GtkWidget *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; CompilerContextT *ctx; // Are there recipes to cook? if (arrlen(self->recipes) > 0) { messageShow(); self->tempInteger = -1; // Tell cookFinished() we're just starting. ctx = compilerNewContext(cookFinished, self); cookFinished(&ctx); } } EVENT void menuProjectBuildBuild(GtkWidget *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(self->treeProject)); GtkTreeIter iter; GtkTreeIter child; SFTPT *ssh; char *temp; char *filename; char *pathString; char *buildStart; int section; int i; int j; gboolean archPrinted; FILE *out; //***TODO*** This needs to support building with custom JoeyLib libraries. messageShow(); ssh = sshConnect(self->buildHost, self->buildSSHPort, self->buildUser, self->buildPassword); if (!ssh) { message(MSG_ERROR, "Unable to connect to SSH port"); return; } // Do we have a build package to clean up? if (sshSFTPFileExists(ssh, REMOTE_BUILD_RESULTS)) { message(MSG_INFO, "Removing stale build results"); } // Generate build.start. buildStart = utilCreateString("%sbuild.start", self->windowData.path); out = fopen(buildStart, "wt"); if (!out) { message(MSG_SEVERE, "Unable to write temporary build.start file"); unlink(buildStart); // Just in case. message(MSG_INFO, "Build canceled"); DEL(buildStart); sshDisconnect(&ssh); return; } // Write what we're building. fprintf(out, "%s\n", self->projectType); // Write it's title. fprintf(out, "%s\n", self->projectName); // Write the JoeyLib to use. fprintf(out, "%s\n", self->projectJoeyLib); ///***TODO*** Paths to a custom JoeyLib mean nothing to JoeyBuild. // Write desired build targets. for (i=0; itargets); i++) { archPrinted = FALSE; for (j=0; jtargets[i]->archs); j++) { if (self->targets[i]->archs[j]->selected) { if (!archPrinted) { fprintf(out, "%s", self->targets[i]->name); archPrinted = TRUE; } fprintf(out, " %s", self->targets[i]->archs[j]->name); } } if (archPrinted) { fprintf(out, "\n"); } } fclose(out); // Collect file names to send. gtk_tree_model_get_iter_first(model, &iter); do { if (gtk_tree_model_iter_children(model, &child, &iter)) { do { gtk_tree_model_get(model, &child, COL_FILENAME, &filename, -1); pathString = gtk_tree_model_get_string_from_iter(model, &child); section = atoi(pathString); if (section != SECTION_RAW_DATA) { temp = utilCreateString("%s%s", self->windowData.path, filename); arrput(_sendList, temp); } //DEL(filename); // GTK says I need to free this. Memwatch says otherwise. } while (gtk_tree_model_iter_next(model, &child)); } } while (gtk_tree_model_iter_next(model, &iter)); // Add build.start to the end. arrput(_sendList, buildStart); // Prime the pump! We only set sshData and userData so sendSFTP can determine this is the first file. ssh->userData = self; sendSFTP(ssh); } EVENT void menuProjectHelpProject(GtkWidget *object, gpointer userData) { (void)object; (void)userData; gtk_show_uri_on_window(NULL, "https://skunkworks.kangaroopunch.com/skunkworks/joeydev/-/wikis/Project-Editor", GDK_CURRENT_TIME, NULL); } gboolean projectAddToTree(ProjectDataT *self, char *filename) { ProjectSectionTypeT section; char *temp = NULL; GtkTreeIter iter; GtkTreeIter child; GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(self->treeProject)); ProjectSectionTypeT foundAt = SECTION_RAW_DATA; // If it isn't a file we know, put it in "Raw Data". int fileLen; int extLen; // Is it long enough? if (strlen(filename) > 2) { // Is this already in the tree? This is a pretty brain-dead test. gtk_tree_model_get_iter_first(model, &iter); do { if (gtk_tree_model_iter_children(model, &child, &iter)) { do { gtk_tree_model_get(model, &child, COL_FILENAME, &temp, -1); if (strcmp(temp, filename) == 0) { //DEL(temp); // GTK says I need to free this. Memwatch says otherwise. return FALSE; // Item exists and was not added. } //DEL(temp); // GTK says I need to free this. Memwatch says otherwise. } while (gtk_tree_model_iter_next(model, &child)); } } while (gtk_tree_model_iter_next(model, &iter)); // Find proper section. for (section = 0; section < SECTION_COUNT; section++) { if (_sectionData[section].extension != NULL) { // Compare last two bytes of filename with extension - it's enough to differentiate them and allows for ".c" and ".h". fileLen = strlen(filename) - 1; extLen = strlen(_sectionData[section].extension) - 1; if (filename[fileLen - 1] == _sectionData[section].extension[extLen - 1] && filename[fileLen] == _sectionData[section].extension[extLen]) { foundAt = section; break; } } } temp = NULL; //DEL(temp); // GTK says I need to free this. Memwatch says otherwise. fileLen = 0; utilEnsureBufferSize((unsigned char **)&temp, &fileLen, 4); // fileLen is just a throwaway here. snprintf(temp, 4, "%d", foundAt); gtk_tree_model_get_iter_from_string(model, &iter, temp); gtk_tree_store_append(GTK_TREE_STORE(model), &child, &iter); gtk_tree_store_set(GTK_TREE_STORE(model), &child, COL_FILENAME, filename, -1); DEL(temp); gtk_tree_view_expand_all(GTK_TREE_VIEW(self->treeProject)); } return TRUE; // Item was added. } static void receiveSFTP(SFTPT *sftp) { ProjectDataT *self = (ProjectDataT *)sftp->userData; char *temp; if (sftp->finished) { if (sftp->success) { message(MSG_INFO, "Cleaning up remote build"); sshSFTPDelete(sftp, REMOTE_BUILD_RESULTS); temp = utilCreateString("%sresults", self->windowData.path); utilDeleteTree(temp); DEL(temp); message(MSG_INFO, "Unpacking build"); temp = utilCreateString("%s%s", self->windowData.path, LOCAL_BUILD_RESULTS); utilDecompress(temp, self->windowData.path, decompressBuild, self); DEL(temp); } else { message(MSG_ERROR, "Receiving %s from build server - FAILED", sftp->remoteName); message(MSG_INFO, "Build canceled"); } sshDisconnect(&sftp); } } static void saveConfig(ProjectDataT *self) { char *temp; FILE *out; // Save config. out = fopen(self->configName, "wt"); if (out != NULL) { fprintf(out, "%s\n", BUILD_SETTINGS_VERSION); fprintf(out, "------------------------------------------------------------------------------\n"); fprintf(out, "%s\n", self->buildHost); fprintf(out, "%d\n", self->buildHTTPPort); fprintf(out, "%d\n", self->buildSSHPort); fprintf(out, "%s\n", self->buildUser); temp = utilObfuscateASCII(self->buildPassword); fprintf(out, "%s\n", temp); DEL(temp); fclose(out); } else { message(MSG_SEVERE, "Unknown error attempting to save build configuration!"); } } EVENT void treeProjectRowActivated(GtkTreeView *treeView, GtkTreePath *path, GtkTreeViewColumn *column, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(self->treeProject)); GtkTreeIter iter; char *name = NULL; char *pathString = NULL; char *filename = NULL; int section; pathString = gtk_tree_path_to_string(path); if (strstr(pathString, ":") != NULL) { gtk_tree_model_get_iter_from_string(model, &iter, pathString); gtk_tree_model_get(model, &iter, COL_FILENAME, &name, -1); section = atoi(pathString); debug("Double click! [%d] [%s] [%s]\n", section, pathString, name); if (section == SECTION_CODE || section == SECTION_HEADER) { filename = utilCreateString("%s%s", self->windowData.path, name); // Launch code editor. winEditorCreate(filename); DEL(filename); } if (section == SECTION_RAW_DATA) { // Display cook dialog. dialogCookOptions(name, self); } DEL(name); } DEL(pathString); } static gboolean updateBuildOptions(ProjectDataT *self) { char *test = NULL; char *name = NULL; char *url = NULL; gboolean result = FALSE; FILE *in = NULL; char *line = NULL; size_t len = 0; char *c = NULL; TargetT *t = NULL; ArchT *a = NULL; TargetT **backup = NULL; int i; int j; int x; int y; // This updates the build options file without clobbering one if it already exists and we fail to get a new one. test = utilCreateString("%s%cjoeydev%cjoeydev.temp", g_get_user_config_dir(), UTIL_PATH_CHAR, UTIL_PATH_CHAR); name = utilCreateString("%s%cjoeydev%cjoeydev.info", g_get_user_config_dir(), UTIL_PATH_CHAR, UTIL_PATH_CHAR); url = utilCreateString("http://%s:%d/joeydev.info", self->buildHost, self->buildHTTPPort); httpDownload(httpWaitForDownloadCallback, test, url); if (httpWaitForDownload() == TRUE) { unlink(name); rename(test, name); result = TRUE; } //***TODO*** Grab the latest joey.h // Unload current target listing. backup = targetArrayCopy(self->targets); targetArrayDelete(&self->targets); // If there's a list of targets on the disk, load them. if (utilFileExists(name) == TRUE) { in = fopen(name, "rt"); if (in != NULL) { utilEnsureBufferSize((unsigned char **)&line, (int *)&len, 1024); // Not technically needed, but fixes a pointer warning from memmaker. while (utilGetLine(&line, &len, in) != -1) { if (strlen(line) > 0) line[strlen(line) - 1] = 0; c = utilGetToken(line, " ", "\"", "\""); utilDequote(c); // Is this a 'target' line? if (strcasecmp(c, "target") == 0) { t = NEW(TargetT); // Short name. c = utilGetToken(NULL, " ", "\"", "\""); utilDequote(c); t->name = strdup(c); // Long name. c = utilGetToken(NULL, " ", "\"", "\""); utilDequote(c); t->longName = strdup(c); // Architectures. do { c = utilGetToken(NULL, " ", "\"", "\""); if (c != NULL) { utilDequote(c); a = NEW(ArchT); a->name = strdup(c); a->selected = FALSE; arrput(t->archs, a); } } while (c != NULL); arrput(self->targets, t); } } fclose(in); DEL(line); } } // Merge any previously checked items into the new list. for (i=0; itargets); j++) { if (strcasecmp(backup[i]->name, self->targets[j]->name) == 0) { // This is our target. Iterate over archs and enable any we had enabled before. for (x=0; xarchs); x++) { for (y=0; xtargets[j]->archs); y++) { if (strcasecmp(backup[i]->archs[x]->name, self->targets[j]->archs[y]->name) == 0) { self->targets[j]->archs[y]->selected = backup[i]->archs[x]->selected; break; } } } break; } } } targetArrayDelete(&backup); DEL(url); DEL(name); DEL(test); return result; } static void sendSFTP(SFTPT *sftp) { ProjectDataT *self = (ProjectDataT *)sftp->userData; char *target; char *name; // If source and target are missing, this is the first file - start sending. // Or, did the current transfer succeed? If so, send next file. if ((sftp->localName == NULL && sftp->remoteName == NULL) || (sftp->finished && sftp->success)) { if (sftp->localName == NULL && sftp->remoteName == NULL) { message(MSG_INFO, "Building %s", self->windowData.filename); } // Are there more files to send? if (arrlen(_sendList) > 0) { // Send next entry in the file list. name = utilFileBasename(_sendList[0]); target = utilCreateString("build/%s", name); message(MSG_INFO, "Sending %s to build server", name); sshSFTPSend(sftp, _sendList[0], target, sendSFTP); DEL(target); DEL(name); DEL(_sendList[0]); arrdel(_sendList, 0); } else { // Finished! message(MSG_INFO, "Waiting for build to complete"); g_timeout_add_seconds(2, waitForBuild, sftp); } } // Do we have a transfer? Did the transfer fail? if ((!sftp) || (sftp->finished && !sftp->success)) { if (sftp) { message(MSG_ERROR, "Sending %s to build server - FAILED", sftp->remoteName); } message(MSG_INFO, "Build canceled"); sshDisconnect(&sftp); // Clear any remaining transfers. while (arrlen(_sendList) > 0) { DEL(_sendList[0]); arrdel(_sendList, 0); } ARRFREE(_sendList); } } static TargetT **targetArrayCopy(TargetT **targets) { int i; int j; TargetT *t = NULL; ArchT *a = NULL; TargetT **backup = NULL; for (i=0; iname = strdup(targets[i]->name); t->longName = strdup(targets[i]->longName); for (j=0; jarchs); j++) { a = NEW(ArchT); a->name = strdup(targets[i]->archs[j]->name); a->selected = targets[i]->archs[j]->selected; arrput(t->archs, a); } arrput(backup, t); } return backup; } static void targetArrayDelete(TargetT ***array) { TargetT **targets = (TargetT **)*array; TargetT *t = NULL; ArchT *a = NULL; while (arrlen(targets) > 0) { t = targets[0]; while (arrlen(t->archs) > 0) { a = t->archs[0]; DEL(a->name); DEL(a); arrdel(t->archs, 0); } //arrfree(t->archs); DEL(t->name); DEL(t->longName); DEL(t); arrdel(targets, 0); } //arrfree(targets); } static gboolean waitForBuild(gpointer userData) { SFTPT *sftp = (SFTPT *)userData; ProjectDataT *self = (ProjectDataT *)sftp->userData; char *temp; gboolean result; if (sshSFTPFileExists(sftp, REMOTE_BUILD_RESULTS)) { // Build complete! Next step! message(MSG_INFO, "Retrieving build results"); temp = utilCreateString("%s%s", self->windowData.path, LOCAL_BUILD_RESULTS); unlink(temp); result = sshSFTPReceive(sftp, REMOTE_BUILD_RESULTS, temp, receiveSFTP); DEL(temp); if (!result) { message(MSG_ERROR, "Receiving build from build server - FAILED"); message(MSG_INFO, "Build canceled"); sshDisconnect(&sftp); } return G_SOURCE_REMOVE; } // Keep waiting. //***TODO*** Add a timeout of some kind here and sshDisconnect. return G_SOURCE_CONTINUE; } EVENT gboolean winProjectClose(GtkWidget *object, gpointer userData) { // userData is not reliable due to menuVectorFileClose and util indirectly calling us. ProjectDataT *self = (ProjectDataT *)utilGetWindowData(object); (void)userData; if (self->windowData.isDirty == TRUE) { if (utilQuestionDialog(self->windowData.window, "Exit", "You have unsaved changes. Exit?")) { winProjectDelete(self); return FALSE; } return TRUE; } winProjectDelete(self); return FALSE; } void winProjectCreate(void) { ProjectDataT *self; char *widgetNames[] = { "winProject", "treeProject", NULL };static GtkWidget **widgets[] = { NULL, NULL }; GtkTreeViewColumn *col; GtkCellRenderer *renderer; // Set up instance data. self = NEW(ProjectDataT); self->windowData.closeWindow = winProjectClose; // Load widgets from XML. widgets[0] = &self->windowData.window; widgets[1] = &self->treeProject; utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/Project.glade", widgetNames, widgets, self); // Register window. utilWindowRegister(self); // Build tree. col = gtk_tree_view_column_new(); gtk_tree_view_append_column(GTK_TREE_VIEW(self->treeProject), col); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_column_pack_start(col, renderer, TRUE); gtk_tree_view_column_add_attribute(col, renderer, "text", COL_FILENAME); // Reset tree. menuProjectFileNew(self->windowData.window, self); // Load server settings. loadConfig(self); // Show window. gtk_widget_show_all(self->windowData.window); // Attempt to load build settings from build server. updateBuildOptions(self); //***DEBUG*** self->windowData.filename = strdup("/home/scott/joeyapps/warehouse/Warehouse.joe"); self->windowData.path = strdup("/home/scott/joeyapps/warehouse/"); loadProject(self); } static void winProjectDelete(gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; utilWindowUnRegister(userData); targetArrayDelete(&self->targets); clearRecipeData(self); DEL(self->projectName); DEL(self->projectType); DEL(self->buildHost); DEL(self->buildUser); DEL(self->buildPassword); DEL(self->configName); DEL(self); } #pragma clang diagnostic pop