/* * 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" #include "common.h" #include "project.h" #include "utils.h" #include "ssh.h" enum ProjectColumnsE { COL_FILENAME = 0, COL_COUNT }; typedef enum ProjectColumnsE ProjectColumnsT; 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 ProjectDataS { WindowDataT windowData; GtkWidget *treeProject; char *configName; char *buildHost; int buildPort; char *buildUser; char *buildPassword; } ProjectDataT; typedef struct SectionDataS { char *name; char *extension; } SectionDataT; static SectionDataT _sectionData[] = { { "Header", "*.h" }, { "Code", "*.c" }, { "Bitmap", "*.img" }, { "Stencil", "*.stn" }, { "Vector", "*.vic" }, { "Sound", "*.snd" }, { "Music", "*.mod" }, { "Raw Data", "*.raw" }, { "Cooked Data", NULL }, { NULL, NULL } }; #define BUILD_SETTINGS_RESPONSE_TEST 1 static void addToTree(ProjectDataT *self, 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 menuProjectBuildBuild(GtkWidget *object, gpointer userData); EVENT void menuProjectHelpProject(GtkWidget *object, gpointer userData); static void saveConfig(ProjectDataT *self); EVENT gboolean winProjectClose(GtkWidget *object, gpointer userData); static void winProjectDelete(gpointer userData); static void addToTree(ProjectDataT *self, char *filename) { ProjectSectionTypeT section; char temp[4]; GtkTreeIter iterParent; GtkTreeIter iter; GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(self->treeProject)); int fileLen; int extLen; // Is it long enough? if (strlen(filename) > 2) { // 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]) { snprintf(temp, 4, "%d", section); gtk_tree_model_get_iter_from_string(model, &iterParent, temp); gtk_tree_store_append(GTK_TREE_STORE(model), &iter, &iterParent); gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_FILENAME, filename, -1); //***TODO*** Need to store extra RAW data somewhere. break; } } } } } 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) { while (getline(&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: // Port self->buildPort = atoi(line); break; case 4: // User DEL(self->buildUser); self->buildUser = strdup(line); break; case 5: // Password DEL(self->buildPassword); self->buildPassword = strdup(line); break; default: // Oops break; } count++; } fclose(in); DEL(line); } else { //***TODO*** Something bad happened. } } else { // No config file. Set defaults. DEL(self->buildHost); DEL(self->buildUser); DEL(self->buildPassword); self->buildHost = strdup("build.joeylib.com"); self->buildPort = 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; size_t count = 0; in = fopen(self->windowData.filename, "rt"); if (in != NULL) { while (getline(&line, &len, in) != -1) { if (strlen(line) > 0) line[strlen(line) - 1] = 0; switch (count) { case 0: // Version Number case 1: // Separator line break; default: // Project Data addToTree(self, line); break; } count++; } fclose(in); DEL(line); gtk_tree_view_expand_all(GTK_TREE_VIEW(self->treeProject)); utilSetDirty((WindowDataT *)self, FALSE); // Do again - loading text marks us dirty. } else { //***TODO*** Something bad happened. } } EVENT void menuProjectFileNew(GtkWidget *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; GtkTreeIter iter; ProjectSectionTypeT i; GtkTreeStore *store; (void)object; 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); } 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; // 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! fprintf(out, "%s\n", PROJECT_VERSION); fprintf(out, "------------------------------------------------------------------------------\n"); gtk_tree_model_get_iter_first(model, &iter); do { if (gtk_tree_model_iter_children(model, &child, &iter)) { do { //***TODO*** For RAW we need to store function name somewhere. gtk_tree_model_get(model, &child, COL_FILENAME, &temp, -1); fprintf(out, "%s\n", temp); DEL(temp); } while (gtk_tree_model_iter_next(model, &child)); } } while (gtk_tree_model_iter_next(model, &iter)); fclose(out); // We're clean now. utilSetDirty((WindowDataT *)self, FALSE); } else { //***TODO*** Something bad happened. } } } 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; (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 ); for (section=0; sectiontreeProject)); gboolean found = FALSE; GtkWidget *dialog; GtkTreeModel *model; GtkTreeIter iter; // 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) { 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) { } EVENT void menuProjectBuildSettings(GtkWidget *object, gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; GtkWidget *dialogServerSettings; GtkWidget *txtHost; GtkWidget *txtPort; GtkWidget *txtUser; GtkWidget *txtPassword; char *widgetNames[] = { "dialogServerSettings", "txtHost", "txtPort", "txtUser", "txtPassword", NULL }; GtkWidget **widgets[] = { &dialogServerSettings, &txtHost, &txtPort, &txtUser, &txtPassword }; char temp[6]; int result = 0; SSHT *ssh = NULL; char *output = NULL; (void)object; utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/BuildServer.glade", widgetNames, widgets, self); while (1) { snprintf(temp, 6, "%d", self->buildPort); gtk_entry_set_text(GTK_ENTRY(txtHost), self->buildHost); gtk_entry_set_text(GTK_ENTRY(txtPort), 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->buildPort = atoi(gtk_entry_get_text(GTK_ENTRY(txtPort))); 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) { // Run server connection test. ssh = sshConnect(self->buildHost, self->buildPort, self->buildUser, self->buildPassword); if (ssh) { debug("Connected!\n"); // echo ${JOEYLIB_SDKS} result = sshExecute(ssh, "ls -la", &output); debug("----------\n"); if ((result == 0) && (strlen(output) > 0)) { debug("%s", output); } else { debug("No output!\n"); } debug("----------\n"); DEL(output); sshDisconnect(&ssh); } } else { // Close dialog on OKAY but not TEST. break; } } } gtk_widget_destroy(dialogServerSettings); } EVENT void menuProjectBuildBuild(GtkWidget *object, gpointer userData) { } 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); } static void saveConfig(ProjectDataT *self) { char *temp; FILE *out; // Make sure we have a config folder. temp = utilCreateString("%s%cjoeydev", g_get_user_config_dir(), UTIL_PATH_CHAR); utilMkDirP(temp, 0777); DEL(temp); // 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->buildPort); fprintf(out, "%s\n", self->buildUser); fprintf(out, "%s\n", self->buildPassword); //***TODO*** This is hardly secure. fclose(out); } else { //***TODO*** Something bad happened. } } 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 }; 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); // 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); // Register window & show it. utilWindowRegister(self); gtk_widget_show_all(self->windowData.window); //***DEBUG*** self->windowData.filename = strdup("/home/scott/code/joeydev/cmake-build-debug/test.joe"); loadProject(self); } static void winProjectDelete(gpointer userData) { ProjectDataT *self = (ProjectDataT *)userData; utilWindowUnRegister(userData); DEL(self->buildHost); DEL(self->buildUser); DEL(self->buildPassword); DEL(self->configName); DEL(self); } #pragma clang diagnostic pop