592 lines
16 KiB
C
592 lines
16 KiB
C
/*
|
|
* JoeyDev
|
|
* Copyright (C) 2018-2023 Scott Duensing <scott@kangaroopunch.com>
|
|
*
|
|
* 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; i<SECTION_COUNT; i++) {
|
|
gtk_tree_store_append(store, &iter, NULL);
|
|
gtk_tree_store_set(store, &iter, COL_FILENAME, _sectionData[i].name, -1);
|
|
}
|
|
|
|
gtk_tree_view_set_model(GTK_TREE_VIEW(self->treeProject), 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; section<SECTION_COUNT; section++) {
|
|
if (_sectionData[section].extension != NULL) {
|
|
filter = gtk_file_filter_new();
|
|
gtk_file_filter_set_name(filter, _sectionData[section].name);
|
|
gtk_file_filter_add_pattern(filter, _sectionData[section].extension);
|
|
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
|
|
}
|
|
}
|
|
|
|
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
|
|
temp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
|
|
addToTree(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;
|
|
|
|
// 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
|