/* $Id$ */ static char const _copyright[] = "Copyright © 2011-2020 Pierre Pronchery "; /* This file is part of DeforaOS Desktop Coder */ static char const _license[] = "This program is free software: you can redistribute it and/or modify\n" "it under the terms of the GNU General Public License as published by\n" "the Free Software Foundation, version 3 of the License.\n" "\n" "This program is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License for more details.\n" "\n" "You should have received a copy of the GNU General Public License\n" "along with this program. If not, see ."; /* TODO: * - add a "backend" type of plug-ins (asm, hexedit, make, project, UWff...) * - add a "plug-in" type of plug-ins (time tracker, ...) */ #include #include #include #include #include #include #include "callbacks.h" #include "coder.h" #include "../config.h" #define _(string) gettext(string) #define N_(string) (string) #ifndef PREFIX # define PREFIX "/usr/local" #endif #ifndef PROGNAME_HELPER # define PROGNAME_HELPER "helper" #endif /* Coder */ /* private */ /* types */ struct _Coder { Config * config; Project ** projects; size_t projects_cnt; Project * cur; /* widgets */ /* toolbar */ GtkWidget * tb_window; /* preferences */ GtkWidget * pr_window; GtkWidget * pr_editor_command; GtkWidget * pr_editor_terminal; /* files */ GtkWidget * fi_window; GtkWidget * fi_combo; GtkWidget * fi_view; /* about */ GtkWidget * ab_window; }; /* constants */ #define ICON_NAME "applications-development" static char const * _authors[] = { "Pierre Pronchery ", NULL }; /* menubar */ static const DesktopMenu _coder_menu_file[] = { { N_("_New file..."), G_CALLBACK(on_file_new), GTK_STOCK_NEW, GDK_CONTROL_MASK, GDK_KEY_N }, { N_("_Open file..."), G_CALLBACK(on_file_open), GTK_STOCK_OPEN, GDK_CONTROL_MASK, GDK_KEY_O }, { "", NULL , NULL, 0, 0 }, { N_("_Preferences..."), G_CALLBACK(on_file_preferences), GTK_STOCK_PREFERENCES, GDK_CONTROL_MASK, GDK_KEY_P }, { "", NULL, NULL, 0, 0 }, { N_("_Exit"), G_CALLBACK(on_file_exit), GTK_STOCK_QUIT, GDK_CONTROL_MASK, GDK_KEY_Q }, { NULL, NULL, NULL, 0, 0 } }; /* FIXME will certainly be dynamic */ static const DesktopMenu _coder_menu_project[] = { { N_("_New project..."), G_CALLBACK(on_project_new), GTK_STOCK_NEW, 0, 0 }, { N_("_Open project..."), G_CALLBACK(on_project_open), GTK_STOCK_OPEN, 0, 0 }, { N_("_Save project"), G_CALLBACK(on_project_save), GTK_STOCK_SAVE, GDK_CONTROL_MASK, GDK_KEY_S }, { N_("Save project _As..."), G_CALLBACK(on_project_save_as), GTK_STOCK_SAVE_AS, 0, 0 }, { "", NULL, NULL, 0, 0 }, { N_("_Properties..."), G_CALLBACK(on_project_properties), GTK_STOCK_PROPERTIES, GDK_MOD1_MASK, GDK_KEY_Return }, { NULL, NULL, NULL, 0, 0 } }; static const DesktopMenu _coder_menu_view[] = { { N_("_Files"), G_CALLBACK(on_view_files), NULL, 0, 0 }, { NULL, NULL, NULL, 0, 0 } }; static const DesktopMenu _coder_menu_tools[] = { { N_("_Debugger"), G_CALLBACK(on_tools_debugger), NULL, 0, 0 }, { N_("_PHP console"), G_CALLBACK(on_tools_php_console), NULL, 0, 0 }, { N_("_Simulator"), G_CALLBACK(on_tools_simulator), "stock_cell-phone", 0, 0 }, { N_("S_QL console"), G_CALLBACK(on_tools_sql_console), "stock_insert-table", 0, 0 }, { NULL, NULL, NULL, 0, 0 } }; static const DesktopMenu _coder_menu_help[] = { { N_("API _Reference"), G_CALLBACK(on_help_api_reference), "help-contents", 0, 0 }, { N_("_Contents"), G_CALLBACK(on_help_contents), "help-contents", 0, GDK_KEY_F1 }, { N_("_About"), G_CALLBACK(on_help_about), GTK_STOCK_ABOUT, 0, 0 }, { NULL, NULL, NULL, 0, 0 } }; static const DesktopMenubar _coder_menubar[] = { { N_("_File"), _coder_menu_file }, { N_("_Project"), _coder_menu_project }, { N_("_View"), _coder_menu_view }, { N_("_Tools"), _coder_menu_tools }, { N_("_Help"), _coder_menu_help }, { NULL, NULL } }; /* variables */ /* toolbar */ static DesktopToolbar _coder_toolbar[] = { { N_("Exit"), G_CALLBACK(on_file_exit), GTK_STOCK_QUIT, 0, 0, NULL }, { NULL, NULL, NULL, 0, 0, NULL } }; /* prototypes */ static Project * _coder_get_current_project(Coder * coder); /* public */ /* functions */ /* coder_new */ static void _new_config(Coder * g); Coder * coder_new(void) { Coder * coder; GtkAccelGroup * group; GtkWidget * vbox; GtkWidget * hbox; GtkWidget * widget; if((coder = malloc(sizeof(*coder))) == NULL) return NULL; coder->projects = NULL; coder->projects_cnt = 0; coder->cur = NULL; _new_config(coder); /* main window */ group = gtk_accel_group_new(); coder->tb_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_add_accel_group(GTK_WINDOW(coder->tb_window), group); #if GTK_CHECK_VERSION(2, 6, 0) gtk_window_set_icon_name(GTK_WINDOW(coder->tb_window), ICON_NAME); #endif gtk_window_set_title(GTK_WINDOW(coder->tb_window), _("Coder")); gtk_window_set_resizable(GTK_WINDOW(coder->tb_window), FALSE); g_signal_connect_swapped(coder->tb_window, "delete-event", G_CALLBACK( on_closex), coder); #if GTK_CHECK_VERSION(3, 0, 0) vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); #else vbox = gtk_vbox_new(FALSE, 0); #endif /* menubar */ widget = desktop_menubar_create(_coder_menubar, coder, group); gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, TRUE, 0); /* toolbar */ widget = desktop_toolbar_create(_coder_toolbar, coder, group); g_object_unref(group); gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, TRUE, 0); gtk_container_add(GTK_CONTAINER(coder->tb_window), vbox); /* files */ coder->fi_window = NULL; coder_show_files(coder, TRUE); /* about */ coder->ab_window = NULL; gtk_widget_show_all(coder->tb_window); return coder; } static char * _config_file(void); static void _new_config(Coder * coder) { char * filename; if((coder->config = config_new()) == NULL) { coder_error(coder, strerror(errno), 0); return; } config_load(coder->config, PREFIX "/etc/" PACKAGE ".conf"); if((filename = _config_file()) == NULL) return; config_load(coder->config, filename); free(filename); } static char * _config_file(void) { char const conffile[] = ".coder"; char const * homedir; size_t len; char * filename; if((homedir = getenv("HOME")) == NULL) return NULL; len = strlen(homedir) + 1 + strlen(conffile) + 1; if((filename = malloc(len)) == NULL) return NULL; snprintf(filename, len, "%s/%s", homedir, conffile); return filename; } /* coder_delete */ void coder_delete(Coder * coder) { char * filename; size_t i; if((filename = _config_file()) != NULL) { config_save(coder->config, filename); free(filename); } config_delete(coder->config); for(i = 0; i < coder->projects_cnt; i++) project_delete(coder->projects[i]); free(coder->projects); free(coder); } /* useful */ /* coder_about */ static gboolean _about_on_closex(gpointer data); void coder_about(Coder * coder) { if(coder->ab_window != NULL) { gtk_window_present(GTK_WINDOW(coder->ab_window)); return; } coder->ab_window = desktop_about_dialog_new(); gtk_window_set_transient_for(GTK_WINDOW(coder->ab_window), GTK_WINDOW( coder->tb_window)); desktop_about_dialog_set_authors(coder->ab_window, _authors); desktop_about_dialog_set_comments(coder->ab_window, _("Integrated Development Environment for the DeforaOS" " desktop")); desktop_about_dialog_set_copyright(coder->ab_window, _copyright); desktop_about_dialog_set_logo_icon_name(coder->ab_window, ICON_NAME); desktop_about_dialog_set_license(coder->ab_window, _license); desktop_about_dialog_set_name(coder->ab_window, PACKAGE); desktop_about_dialog_set_version(coder->ab_window, VERSION); desktop_about_dialog_set_website(coder->ab_window, "https://www.defora.org/"); g_signal_connect_swapped(coder->ab_window, "delete-event", G_CALLBACK( _about_on_closex), coder); gtk_widget_show(coder->ab_window); } static gboolean _about_on_closex(gpointer data) { Coder * coder = data; gtk_widget_hide(coder->ab_window); return TRUE; } /* coder_api_reference */ int coder_api_reference(Coder * coder) { char const * argv[] = { PROGNAME_HELPER, NULL }; const unsigned int flags = G_SPAWN_SEARCH_PATH; GError * error = NULL; if(g_spawn_async(NULL, argv, NULL, flags, NULL, NULL, NULL, &error) == FALSE) { coder_error(coder, error->message, 1); g_error_free(error); return -1; } return 0; } /* coder_error */ int coder_error(Coder * coder, char const * message, int ret) { GtkWidget * dialog; dialog = gtk_message_dialog_new(GTK_WINDOW(coder->tb_window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, #if GTK_CHECK_VERSION(2, 6, 0) "%s", _("Error")); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), #endif "%s", message); gtk_window_set_title(GTK_WINDOW(dialog), _("Error")); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); return ret; } /* coder_file_open */ void coder_file_open(Coder * coder, char const * filename) { /* FIXME really use the MIME sub-system */ char * argv[] = { NULL, NULL, NULL }; char const * p; GError * error = NULL; if((p = config_get(coder->config, "editor", "command")) == NULL) p = "editor"; /* XXX gather defaults in a common place */ if((argv[0] = strdup(p)) == NULL) return; /* XXX report error */ if(filename != NULL) argv[1] = strdup(filename); /* XXX check and report error */ if(g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error) != TRUE) { coder_error(coder, error->message, 1); g_error_free(error); } free(argv[1]); free(argv[0]); } /* coder_project_open */ int coder_project_open(Coder * coder, char const * filename) { Project * project; if((project = project_new()) == NULL) return -coder_error(coder, error_get(NULL), 1); if(project_load(project, filename) != 0 || coder_project_open_project(coder, project) != 0) { project_delete(project); return -coder_error(coder, error_get(NULL), 1); } coder->cur = project; #if GTK_CHECK_VERSION(2, 24, 0) gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(coder->fi_combo), project_get_package(project)); #else gtk_combo_box_append_text(GTK_COMBO_BOX(coder->fi_combo), project_get_package(project)); #endif /* FIXME doesn't always select the last project opened */ gtk_combo_box_set_active(GTK_COMBO_BOX(coder->fi_combo), gtk_combo_box_get_active(GTK_COMBO_BOX(coder->fi_combo)) + 1); return 0; } /* coder_project_open_dialog */ void coder_project_open_dialog(Coder * coder) { GtkWidget * dialog; GtkFileFilter * filter; gchar * filename = NULL; dialog = gtk_file_chooser_dialog_new(_("Open project..."), GTK_WINDOW(coder->tb_window), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); filter = gtk_file_filter_new(); gtk_file_filter_set_name(filter, _("Project files")); gtk_file_filter_add_pattern(filter, "project.conf"); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter); filter = gtk_file_filter_new(); gtk_file_filter_set_name(filter, _("All files")); gtk_file_filter_add_pattern(filter, "*"); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER( dialog)); gtk_widget_destroy(dialog); if(filename == NULL) return; coder_project_open(coder, filename); g_free(filename); } /* coder_project_open_project */ int coder_project_open_project(Coder * coder, Project * project) { Project ** p; if(project == NULL) return -error_set_code(1, "%s", strerror(EINVAL));; if((p = realloc(coder->projects, sizeof(*p) * (coder->projects_cnt + 1))) == NULL) return -error_set_code(1, "%s", strerror(errno)); coder->projects = p; coder->projects[coder->projects_cnt++] = project; return 0; } /* coder_project_properties */ void coder_project_properties(Coder * coder) { Project * project; if((project = _coder_get_current_project(coder)) == NULL) return; project_properties(project); } /* coder_project_save */ int coder_project_save(Coder * coder) { Project * project; if((project = _coder_get_current_project(coder)) == NULL) return -1; if(project_get_pathname(project) == NULL) return coder_project_save_dialog(coder); return project_save(project); } /* coder_project_save_as */ int coder_project_save_as(Coder * coder, char const * filename) { Project * project; if((project = _coder_get_current_project(coder)) == NULL) return -1; if(project_set_pathname(project, filename) != 0) return -1; return project_save(project); } /* coder_project_save_dialog */ int coder_project_save_dialog(Coder * coder) { int ret = -1; Project * project; GtkWidget * dialog; gchar * filename = NULL; if((project = _coder_get_current_project(coder)) == NULL) return -1; dialog = gtk_file_chooser_dialog_new(_("Save project as..."), NULL, GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); /* FIXME add options? (recursive save) */ if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER( dialog)); gtk_widget_destroy(dialog); if(filename != NULL) ret = coder_project_save_as(coder, filename); g_free(filename); return ret; } /* coder_show_files */ static void _show_files_window(Coder * coder); /* callbacks */ static gboolean _files_on_closex(gpointer data); void coder_show_files(Coder * coder, gboolean show) { if(coder->fi_window == NULL) _show_files_window(coder); if(show) gtk_window_present(GTK_WINDOW(coder->fi_window)); else gtk_widget_hide(coder->fi_window); } static void _show_files_window(Coder * coder) { GtkWidget * vbox; GtkWidget * hbox; coder->fi_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size(GTK_WINDOW(coder->fi_window), 150, 200); gtk_window_set_title(GTK_WINDOW(coder->fi_window), _("Files")); g_signal_connect_swapped(coder->fi_window, "delete-event", G_CALLBACK( _files_on_closex), coder); #if GTK_CHECK_VERSION(3, 0, 0) hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); #else hbox = gtk_hbox_new(FALSE, 0); vbox = gtk_vbox_new(FALSE, 0); #endif /* FIXME use gtk_container_set_border_width() instead */ gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 2); #if GTK_CHECK_VERSION(2, 24, 0) coder->fi_combo = gtk_combo_box_text_new(); #else coder->fi_combo = gtk_combo_box_new_text(); #endif gtk_box_pack_start(GTK_BOX(vbox), coder->fi_combo, FALSE, TRUE, 2); coder->fi_view = gtk_tree_view_new(); gtk_box_pack_start(GTK_BOX(vbox), coder->fi_view, TRUE, TRUE, 2); gtk_container_add(GTK_CONTAINER(coder->fi_window), hbox); gtk_widget_show_all(hbox); } /* callbacks */ static gboolean _files_on_closex(gpointer data) { Coder * coder = data; gtk_widget_hide(coder->fi_window); return TRUE; } /* coder_show_preferences */ static void _show_preferences_window(Coder * coder); static void _preferences_set(Coder * coder); /* callbacks */ static gboolean _on_preferences_closex(gpointer data); static void _on_preferences_apply(gpointer data); static void _on_preferences_cancel(gpointer data); static void _on_preferences_ok(gpointer data); void coder_show_preferences(Coder * coder, gboolean show) { if(coder->pr_window == NULL) _show_preferences_window(coder); if(show) gtk_window_present(GTK_WINDOW(coder->pr_window)); else gtk_widget_hide(coder->pr_window); } static void _show_preferences_window(Coder * coder) { GtkWidget * vbox; GtkWidget * nb; GtkWidget * nb_vbox; GtkWidget * hbox; GtkWidget * b_ok; GtkWidget * b_apply; GtkWidget * b_cancel; coder->pr_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); /* XXX dialog */ gtk_container_set_border_width(GTK_CONTAINER(coder->pr_window), 4); gtk_window_set_title(GTK_WINDOW(coder->pr_window), _("Preferences")); g_signal_connect_swapped(coder->pr_window, "delete-event", G_CALLBACK( _on_preferences_closex), coder); vbox = gtk_vbox_new(FALSE, 4); nb = gtk_notebook_new(); /* notebook page editor */ nb_vbox = gtk_vbox_new(FALSE, 4); gtk_container_set_border_width(GTK_CONTAINER(nb_vbox), 4); #if GTK_CHECK_VERSION(3, 0, 0) hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); #else hbox = gtk_hbox_new(FALSE, 4); #endif gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_("Editor:")), FALSE, TRUE, 0); coder->pr_editor_command = gtk_entry_new(); gtk_box_pack_start(GTK_BOX(hbox), coder->pr_editor_command, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(nb_vbox), hbox, FALSE, TRUE, 0); coder->pr_editor_terminal = gtk_check_button_new_with_mnemonic( _("Run in a _terminal")); gtk_box_pack_start(GTK_BOX(nb_vbox), coder->pr_editor_terminal, FALSE, TRUE, 0); gtk_notebook_append_page(GTK_NOTEBOOK(nb), nb_vbox, gtk_label_new( _("Editor"))); /* notebook page plug-ins */ nb_vbox = gtk_vbox_new(FALSE, 0); gtk_container_set_border_width(GTK_CONTAINER(nb_vbox), 4); gtk_notebook_append_page(GTK_NOTEBOOK(nb), nb_vbox, gtk_label_new( _("Plug-ins"))); gtk_box_pack_start(GTK_BOX(vbox), nb, TRUE, TRUE, 0); /* buttons */ #if GTK_CHECK_VERSION(3, 0, 0) hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); #else hbox = gtk_hbox_new(TRUE, 4); #endif b_ok = gtk_button_new_from_stock(GTK_STOCK_OK); g_signal_connect_swapped(b_ok, "clicked", G_CALLBACK( _on_preferences_ok), coder); gtk_box_pack_end(GTK_BOX(hbox), b_ok, FALSE, TRUE, 0); b_apply = gtk_button_new_from_stock(GTK_STOCK_APPLY); g_signal_connect_swapped(b_apply, "clicked", G_CALLBACK( _on_preferences_apply), coder); gtk_box_pack_end(GTK_BOX(hbox), b_apply, FALSE, TRUE, 0); b_cancel = gtk_button_new_from_stock(GTK_STOCK_CANCEL); g_signal_connect_swapped(b_cancel, "clicked", G_CALLBACK( _on_preferences_cancel), coder); gtk_box_pack_end(GTK_BOX(hbox), b_cancel, FALSE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); gtk_container_add(GTK_CONTAINER(coder->pr_window), vbox); _preferences_set(coder); gtk_widget_show_all(vbox); } static void _preferences_set(Coder * coder) { char const * p; if((p = config_get(coder->config, "editor", "command")) == NULL) p = "editor"; gtk_entry_set_text(GTK_ENTRY(coder->pr_editor_command), p); /* FIXME implement the rest */ } static void _on_preferences_apply(gpointer data) { Coder * coder = data; config_set(coder->config, "editor", "command", gtk_entry_get_text( GTK_ENTRY(coder->pr_editor_command))); /* FIXME implement the rest */ } static void _on_preferences_cancel(gpointer data) { Coder * coder = data; _preferences_set(coder); gtk_widget_hide(coder->pr_window); } static void _on_preferences_ok(gpointer data) { Coder * coder = data; _on_preferences_apply(coder); gtk_widget_hide(coder->pr_window); /* FIXME actually save preferences */ } /* callbacks */ static gboolean _on_preferences_closex(gpointer data) { Coder * coder = data; _on_preferences_cancel(coder); return TRUE; } /* private */ /* coder_get_current_project */ static Project * _coder_get_current_project(Coder * coder) { if(coder->cur == NULL) { /* FIXME should not happen (disable callback action) */ coder_error(coder, _("No project opened"), 1); return NULL; } return coder->cur; }