diff --git a/data/geany.glade b/data/geany.glade index 71327353c4..42585878e0 100644 --- a/data/geany.glade +++ b/data/geany.glade @@ -7976,6 +7976,15 @@ + + + Open _Folder... + True + False + True + + + True @@ -7984,6 +7993,15 @@ True + + + Save _As... + True + False + True + + + _Close diff --git a/doc/geany.txt b/doc/geany.txt index a156871ee3..d5c7e1ceab 100644 --- a/doc/geany.txt +++ b/doc/geany.txt @@ -274,6 +274,10 @@ Short option Long option Function *none* --ft-names Print a list of Geany's internal filetype names (useful for snippets configuration). +-k --project=path Open a project. The path may refer to a project file + (with a ``.geany`` extension) or a folder to use as the + base path of a project. See `Project management`_. + -g --generate-tags Generate a global tags file (see `Generating a global tags file`_). @@ -1801,7 +1805,12 @@ Startup path It must be an absolute path. Project files - Path to start in when opening project files. + Path to start in when saving new projects or opening project files. + The path may be relative. + + * When saving new projects, the project base path is used as the + reference path. (See `Open folder as project`_.) + * When opening projects, the GTK default fallback is used. Extra plugin path By default Geany looks in the system installation and the user @@ -2801,6 +2810,78 @@ When project session support is enabled, Geany will close the currently open files and open the session files associated with the project. +Open folder as project +^^^^^^^^^^^^^^^^^^^^^^ + +The Open folder command opens or creates projects associated with a folder. +This feature is useful when used in combination with plugins that show +the contents of the project base path. + +The command displays a standard folder chooser. +Geany first searches for, and attempts to open, an associated project. +For a folder with the path, ``/path/to/project/``, the first file found, +from the following list, is opened:: + + /path/to/project/project.geany + /path/to/project.geany + /path/to/project/project_file_path/project.geany + ~/.cache/geany/folders/project_[md5].geany + +If none of the files exists, a new project is created with the following +properties, continuing the example: + +* Project Name: project +* Project File: described below +* Project base path: /path/to/project/ + +The project file is saved according to settings in `Preferences`_. + +* If the option to "Store project file inside the project base directory" + is selected, the project file is stored inside the project folder with + the same name as the project. Namely, ``/path/to/project/project.geany`` + See `General Miscellaneous preferences`_ / `Projects`_. + +* If a relative ``project_file_path`` is set, the project is stored at + ``/path/to/project/project_file_path/project.geany``. + See `General Startup preferences`_ / `Paths`_. + + * Setting the path to ``.`` has the same effect as checking the + previously described option. + + * Setting the path to ``..`` causes the project file to be saved to + ``/path/to/project.geany``. + + * Missing folders are created. For instance, suppose the path is set + to ``.geany``, the folder ``/path/to/project/.geany`` is created, + and the project file saved to ``/path/to/project/.geany/project.geany``. + + * If the path is blank or absolute, the new project file is stored in + a subdirectory of the user cache folder. The filename contains a hash + to avoid file name collisions. On Linux, the path may look like:: + + ~/.cache/geany/folders/project_0a3b532362f47aa9e906117ed6ac9537.geany + + Absolute project_file_path is not used when opening folders + to avoid name collisions and clutter. + +The project may be customized further from Project properties, +and the Project / `Save As project`_ menu item may be used to save the +project file with a different name. + +Note: Project files created in the cache folder are intended to be transient. +They may be removed later by Geany or another process. Users are +strongly advised to move or resave projects to another location before +customizing them further. + + +Save As project +^^^^^^^^^^^^^^^ + +The Save As command resaves the current project in a different file. +The previous project file is not removed from the file system. +Settings from inactive plugins are not saved in the new file. + + Close project ^^^^^^^^^^^^^ diff --git a/src/build.c b/src/build.c index 4085fd7bb0..fac2c71a5a 100644 --- a/src/build.c +++ b/src/build.c @@ -1629,7 +1629,7 @@ static void on_set_build_commands_activate(GtkWidget *w, gpointer u) { /* For now, just show the project dialog */ if (app->project) - project_build_properties(); + project_properties_dialog("build"); else show_build_commands_dialog(); } diff --git a/src/callbacks.c b/src/callbacks.c index a5a9e3b3f3..73cdae745f 100644 --- a/src/callbacks.c +++ b/src/callbacks.c @@ -1340,13 +1340,25 @@ void on_previous_message1_activate(GtkMenuItem *menuitem, gpointer user_data) void on_project_new1_activate(GtkMenuItem *menuitem, gpointer user_data) { - project_new(); + project_new_dialog(); } void on_project_open1_activate(GtkMenuItem *menuitem, gpointer user_data) { - project_open(); + project_open_dialog(); +} + + +void on_project_open_folder1_activate(GtkMenuItem *menuitem, gpointer user_data) +{ + project_open_folder_dialog(); +} + + +void on_project_save_as1_activate(GtkMenuItem *menuitem, gpointer user_data) +{ + project_save_as_dialog(); } @@ -1358,7 +1370,7 @@ void on_project_close1_activate(GtkMenuItem *menuitem, gpointer user_data) void on_project_properties1_activate(GtkMenuItem *menuitem, gpointer user_data) { - project_properties(); + project_properties_dialog("project"); } diff --git a/src/libmain.c b/src/libmain.c index d316f440ac..7afc8ffa20 100644 --- a/src/libmain.c +++ b/src/libmain.c @@ -121,6 +121,7 @@ static GOptionEntry entries[] = { "config", 'c', 0, G_OPTION_ARG_FILENAME, &alternate_config, N_("Use alternate configuration directory DIR"), N_("DIR") }, { "ft-names", 0, 0, G_OPTION_ARG_NONE, &ft_names, N_("Print internal filetype names"), NULL }, { "generate-tags", 'g', 0, G_OPTION_ARG_NONE, &generate_tags, N_("Generate global tags file (see documentation)"), NULL }, + { "project", 'k', 0, G_OPTION_ARG_FILENAME, &cl_options.project_path, N_("Open a project .geany file, or open a directory as a project"), N_("PATH") }, { "no-preprocessing", 'P', 0, G_OPTION_ARG_NONE, &no_preprocessing, N_("Don't preprocess C/C++ files when generating tags file"), NULL }, #ifdef HAVE_SOCKET { "new-instance", 'i', 0, G_OPTION_ARG_NONE, &cl_options.new_instance, N_("Don't open files in a running instance, force opening a new instance"), NULL }, @@ -675,7 +676,7 @@ static gint create_config_dir(void) * in ~/.geany still exists and try to move it */ if (alternate_config == NULL) { - gchar *old_dir = g_build_filename(g_get_home_dir(), ".geany", NULL); + gchar *old_dir = g_build_filename(g_get_home_dir(), "." GEANY_PROJECT_EXT, NULL); /* move the old config dir if it exists */ if (g_file_test(old_dir, G_FILE_TEST_EXISTS)) { @@ -827,6 +828,7 @@ gboolean main_handle_filename(const gchar *locale_filename) if (filename == NULL) return FALSE; + /* resume normal file handling */ get_line_and_column_from_filename(filename, &line, &column); if (line >= 0) cl_options.goto_line = line; @@ -903,7 +905,7 @@ static void load_session_project_file(void) locale_filename = utils_get_locale_from_utf8(project_prefs.session_file); if (G_LIKELY(!EMPTY(locale_filename))) - project_load_file(locale_filename); + project_load_file(locale_filename, FALSE); g_free(locale_filename); g_free(project_prefs.session_file); /* no longer needed */ @@ -929,32 +931,36 @@ static void load_settings(void) } -void main_load_project_from_command_line(const gchar *locale_filename, gboolean use_session) -{ - gchar *pfile; - - pfile = utils_get_path_from_uri(locale_filename); - if (pfile != NULL) - { - if (use_session) - project_load_file_with_session(pfile); - else - project_load_file(pfile); - } - g_free(pfile); -} - - static void load_startup_files(gint argc, gchar **argv) { gboolean load_session = FALSE; - if (argc > 1 && g_str_has_suffix(argv[1], ".geany")) + if (cl_options.project_path) + { + gchar *filename = main_get_argv_filename(cl_options.project_path); + SETPTR(filename, utils_get_path_from_uri(filename)); + + if (g_file_test(filename, G_FILE_TEST_IS_DIR)) + { + geany_debug("Opening directory as project"); + project_open_folder(filename, FALSE); + load_session = project_prefs.project_session; + } + else if (g_file_test(filename, G_FILE_TEST_IS_REGULAR)) + { + geany_debug("Opening project file"); + project_load_file(filename, FALSE); + load_session = project_prefs.project_session; + } + g_free(filename); + } + else if (argc > 1 && g_str_has_suffix(argv[1], "." GEANY_PROJECT_EXT)) { gchar *filename = main_get_argv_filename(argv[1]); + SETPTR(filename, utils_get_path_from_uri(filename)); /* project file specified: load it, but decide the session later */ - main_load_project_from_command_line(filename, FALSE); + project_load_file(filename, FALSE); argc--, argv++; /* force session load if using project-based session files */ load_session = project_prefs.project_session; @@ -1070,8 +1076,9 @@ gint main_lib(gint argc, gchar **argv) socket_info.lock_socket = socket_init(argc, argv, socket_port); /* Quit if filenames were sent to first instance or the list of open * documents has been printed */ - if ((socket_info.lock_socket == -2 /* socket exists */ && argc > 1) || - cl_options.list_documents) + if ((socket_info.lock_socket == -2 /* socket exists */ + && (argc > 1 || cl_options.project_path)) + || cl_options.list_documents) { socket_finalize(); gdk_notify_startup_complete(); diff --git a/src/main.h b/src/main.h index 34f471acbd..0b942c6dc3 100644 --- a/src/main.h +++ b/src/main.h @@ -45,6 +45,7 @@ typedef struct gboolean ignore_global_tags; gboolean list_documents; gboolean readonly; + gchar *project_path; } CommandLineOptions; @@ -72,8 +73,6 @@ gboolean main_quit(void); gboolean main_handle_filename(const gchar *locale_filename); -void main_load_project_from_command_line(const gchar *locale_filename, gboolean use_session); - gint main_lib(gint argc, gchar **argv); #endif /* GEANY_PRIVATE */ diff --git a/src/osx.c b/src/osx.c index a1fcd4f69b..87c37efc95 100644 --- a/src/osx.c +++ b/src/osx.c @@ -44,7 +44,7 @@ static gboolean open_project_idle(gchar *locale_path) utf8_path = utils_get_utf8_from_locale(locale_path); if (app->project == NULL || (g_strcmp0(utf8_path, app->project->file_name) != 0 && project_ask_close())) - project_load_file_with_session(locale_path); + project_load_file(locale_path, TRUE); g_free(utf8_path); g_free(locale_path); return FALSE; @@ -65,7 +65,7 @@ static gboolean app_open_file_cb(GtkosxApplication *osx_app, gchar *path, gpoint g_free(cwd); } - if (g_str_has_suffix(path, ".geany")) + if (g_str_has_suffix(path, "." GEANY_PROJECT_EXT)) { g_idle_add((GSourceFunc)open_project_idle, locale_path); opened = TRUE; diff --git a/src/project.c b/src/project.c index 7a19990648..a89aaa16db 100644 --- a/src/project.c +++ b/src/project.c @@ -91,6 +91,9 @@ static void apply_editor_prefs(void); static void init_stash_prefs(void); static void destroy_project(gboolean open_default); +static GeanyProject *create_project(void); +static void update_ui(void); + #define SHOW_ERR(args) dialogs_show_msgbox(GTK_MESSAGE_ERROR, args) #define SHOW_ERR1(args, more) dialogs_show_msgbox(GTK_MESSAGE_ERROR, args, more) @@ -138,10 +141,379 @@ static gboolean handle_current_session(void) } +void project_open_folder_dialog(void) +{ + GtkWidget *dialog; + gchar *base_path = NULL; + + dialog = gtk_file_chooser_dialog_new(_("Choose Project Base Path"), + NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) + { + base_path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + } + + if (base_path && *base_path) + { + project_open_folder(base_path, TRUE); + g_free(base_path); + } + + gtk_widget_destroy(dialog); +} + + +/* resolve relative paths and check project file existence + * folder to look for file. Must not be empty. + * base_name of project file. Must not be empty. + * base_path of project. + * Return 0 on success with path in newly allocated string abs_path. Otherwise, NULL. */ +/* TODO: Make return values meaningful */ +static gint project_file_check_path(gchar const *folder, gchar const *base_name, gchar const *base_path, gchar **abs_path) +{ + g_return_val_if_fail(!EMPTY(folder), __LINE__); + g_return_val_if_fail(!EMPTY(base_name), __LINE__); + + if (utils_is_absolute_path(folder)) + { + *abs_path = g_build_filename(folder, base_name, NULL); + if (g_file_test(*abs_path, G_FILE_TEST_IS_REGULAR)) + { + GFile *gfile = g_file_new_for_path(*abs_path); + SETPTR(*abs_path, g_file_get_path(gfile)); + g_object_unref(gfile); + return 0; + } + g_free(*abs_path); + *abs_path = NULL; + return __LINE__; + } + else if (utils_is_absolute_path(base_path)) + { /* folder is relative and base_path is absolute */ + *abs_path = g_build_filename(base_path, folder, base_name, NULL); + if (g_file_test(*abs_path, G_FILE_TEST_IS_REGULAR)) + { + GFile *gfile = g_file_new_for_path(*abs_path); + SETPTR(*abs_path, g_file_get_path(gfile)); + g_object_unref(gfile); + return 0; + } + g_free(*abs_path); + *abs_path = NULL; + return __LINE__; + } + + return __LINE__; +} + + +/* open folder as project. + * open associated project if folder has one. otherwise, create new project. + * for `/path/to/project/`, associated projects are searched in order: + * /path/to/project/project.geany + * /path/to/project.geany + * /path/to/project/project_file_path/project.geany + * ~/.cache/geany/folders/project_[md5].geany + * folder_name must not be NULL or relative. + * return 0 on success. */ +/* TODO: Make return values meaningful */ +gint project_open_folder(gchar *folder_name, gboolean use_session) +{ + g_return_val_if_fail(!EMPTY(folder_name), __LINE__); + g_return_val_if_fail(utils_is_absolute_path(folder_name), __LINE__); + + gchar *prj_name, *prj_basepath; + gchar *prj_file_basename, *prj_file_path, *prj_cache_file; + gboolean prj_file_found = FALSE; + + prj_basepath = utils_get_real_path(folder_name); + g_return_val_if_fail(!EMPTY(prj_basepath), __LINE__); + + prj_name = g_path_get_basename(prj_basepath); + g_return_val_if_fail(!EMPTY(prj_name), __LINE__); + + prj_file_basename = g_strconcat(prj_name, "." GEANY_PROJECT_EXT, NULL); + + if (!prj_file_found) + { + prj_file_found = !project_file_check_path(".", prj_file_basename, prj_basepath, &prj_file_path); + } + if (!prj_file_found) + { + prj_file_found = !project_file_check_path("..", prj_file_basename, prj_basepath, &prj_file_path); + } + if (!prj_file_found && !EMPTY(local_prefs.project_file_path) + && !utils_is_absolute_path(local_prefs.project_file_path)) + { + prj_file_found = !project_file_check_path(local_prefs.project_file_path, prj_file_basename, prj_basepath, &prj_file_path); + } + + { /* build cache path */ + gchar *prj_cache_basename, *prj_cache_path; + GChecksum* md5calc; + + md5calc = g_checksum_new(G_CHECKSUM_MD5); + g_checksum_update(md5calc, (const guchar*)prj_basepath, -1); + prj_cache_basename = g_strconcat(prj_name, "_", g_checksum_get_string(md5calc), "." GEANY_PROJECT_EXT, NULL); + prj_cache_path = g_build_filename(g_get_user_cache_dir(), "geany", "folders", NULL); + utils_mkdir(prj_cache_path, TRUE); + prj_cache_file = g_build_filename(prj_cache_path, prj_cache_basename, NULL); + + if (!prj_file_found) + { /* check cache folder*/ + prj_file_found = !project_file_check_path(prj_cache_path, prj_cache_basename, prj_basepath, &prj_file_path); + } + + g_free(prj_cache_basename); + g_free(prj_cache_path); + g_checksum_free(md5calc); + } + + /* load project file if found */ + if (prj_file_found && !EMPTY(prj_file_path)) + { + if (app->project && !project_close(FALSE)) + g_return_val_if_fail(! "unable to close previous project", __LINE__); + + project_load_file(prj_file_path, use_session); + return 0; + } + + /* create new project */ + if (project_prefs.project_file_in_basedir + || utils_str_equal(local_prefs.project_file_path, ".") + || utils_str_equal(local_prefs.project_file_path, "./")) + { + prj_file_path = g_build_filename(prj_basepath, prj_file_basename, NULL); + GFile *gfile = g_file_new_for_path(prj_file_path); + SETPTR(prj_file_path, g_file_get_path(gfile)); + project_new(prj_name, prj_file_path, "."); + g_object_unref(gfile); + } + else if (!utils_is_absolute_path(local_prefs.project_file_path) + && !EMPTY(local_prefs.project_file_path)) + { + GFile *gfile; + + prj_file_path = g_build_filename(prj_basepath, local_prefs.project_file_path, NULL); + utils_mkdir(prj_file_path, TRUE); + + SETPTR(prj_file_path, g_build_filename(prj_file_path, prj_file_basename, NULL)); + gfile = g_file_new_for_path(prj_file_path); + SETPTR(prj_file_path, g_file_get_path(gfile)); + + project_new(prj_name, prj_file_path, prj_basepath); + g_object_unref(gfile); + } + else + { /* fallback to user cache */ + project_new(prj_name, prj_cache_file, prj_basepath); + } + + g_free(prj_name); + g_free(prj_basepath); + g_free(prj_file_basename); + g_free(prj_file_path); + g_free(prj_cache_file); + + if (!app || !app->project) + g_return_val_if_fail(! "project could not be created or opened", __LINE__); + + return 0; +} + + +/* save current project with new filename. + * open project if folder has one. + * otherwise, create new project. + * return 0 on success. */ +/* TODO: Make return values meaningful */ +void project_save_as_dialog(void) +{ + g_return_if_fail(app->project != NULL); + + gchar *filename = NULL; + +#ifdef G_OS_WIN32 + if (interface_prefs.use_native_windows_dialogs) + { + gchar *file = win32_show_project_open_dialog(main_widgets.window, _("Choose Project Filename"), app->project->file_name, TRUE, TRUE); + if (file != NULL) + { + filename = file; + } + } + else +#else + { + GtkWidget *dialog; + gchar *locale_dir, *name; + + /* initialise the dialog */ + dialog = gtk_file_chooser_dialog_new(_("Choose Project Filename"), NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); + gtk_widget_set_name(dialog, "GeanyDialogProject"); + gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE); + gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), TRUE); + gtk_window_set_type_hint(GTK_WINDOW(dialog), GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT); + + locale_dir = g_path_get_dirname(app->project->file_name); + name = g_path_get_basename(app->project->file_name); + + if (g_file_test(locale_dir, G_FILE_TEST_EXISTS) + && !g_str_has_prefix(locale_dir, g_get_user_cache_dir())) + { + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), locale_dir); + } + else if (utils_is_absolute_path(local_prefs.project_file_path)) + { + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), local_prefs.project_file_path); + } + else if (!EMPTY(local_prefs.project_file_path)) + { + gchar *project_file_path; + GFile *gfile; + + project_file_path = g_build_filename(app->project->base_path, local_prefs.project_file_path, NULL); + gfile = g_file_new_for_path(project_file_path); + SETPTR(project_file_path, g_file_get_path(gfile)); + + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), project_file_path); + g_free(project_file_path); + g_object_unref(gfile); + } + + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), name); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) + { + gchar *tmp_filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + filename = utils_get_utf8_from_locale(tmp_filename); + g_free(tmp_filename); + } + gtk_widget_destroy(dialog); + } +#endif + + if (!EMPTY(filename)) + { + g_free(app->project->file_name); + app->project->file_name = filename; + } + + /* save project session files, etc */ + if (!write_config()) + g_warning("Project file \"%s\" could not be written", app->project->file_name); +} + + +/* create a new project. + * name and file_name must not be NULL. + * file_name will not be overwritten if it exists. + * base_path will not be created if it does not exist. + * file_name or base_path may be relative to the other. If base_path is empty, it will be assumed to be "." + * return 0 on success. */ +/* TODO: Make return values meaningful */ +gint project_new(gchar const *name, gchar const *file_name, gchar const *base_path) +{ + gchar *locale_filename; + gsize name_len; + gint err_code = 0; + GeanyProject *p; + + g_return_val_if_fail(name != NULL, __LINE__); + g_return_val_if_fail(file_name != NULL, __LINE__); + + if (EMPTY(base_path)) + base_path = "."; + + name_len = strlen(name); + g_return_val_if_fail(name_len != 0, __LINE__); + g_return_val_if_fail(name_len < MAX_NAME_LEN, __LINE__); + + locale_filename = utils_get_locale_from_utf8(file_name); + { /* check whether the given directory actually exists */ + gchar *locale_path = utils_get_locale_from_utf8(base_path); + + if (! utils_is_absolute_path(locale_path)) + { /* relative base path, so add base dir of project file name */ + if (utils_is_absolute_path(locale_filename)) + { + gchar *dir = g_path_get_dirname(locale_filename); + SETPTR(locale_path, g_build_filename(dir, locale_path, NULL)); + g_free(dir); + } + else + { + g_free(locale_filename); + g_free(locale_path); + g_return_val_if_fail(! "both file_name and base path are relative", __LINE__); + } + } + + if (! g_file_test(locale_path, G_FILE_TEST_IS_DIR)) + { + g_free(locale_filename); + g_free(locale_path); + g_return_val_if_fail(! "base_path is not a directory or does not exist", __LINE__); + } + g_free(locale_path); + } + + if (!utils_is_absolute_path(locale_filename)) + { + SETPTR(locale_filename, g_build_filename(base_path, locale_filename, NULL)); + } + + /* finally test whether the given project file can be written */ + if ((err_code = utils_is_file_writable(locale_filename)) != 0 || + (err_code = g_file_test(locale_filename, G_FILE_TEST_IS_DIR) ? EISDIR : 0) != 0) + { + g_free(locale_filename); + g_return_val_if_fail(! "project file cannot be written", __LINE__); + } + else if (err_code = g_file_test(locale_filename, G_FILE_TEST_EXISTS)) + { + g_free(locale_filename); + g_return_val_if_fail(! "project file already exists", __LINE__); + } + g_free(locale_filename); + + if (app->project && !project_close(FALSE)) + g_return_val_if_fail(! "current project isn't closed", __LINE__); + + create_project(); + p = app->project; + + SETPTR(p->name, g_strdup(name)); + SETPTR(p->file_name, g_strdup(file_name)); + SETPTR(p->base_path, g_strdup(base_path)); + + update_ui(); + + if (!write_config()) + { + destroy_project(FALSE); + g_return_val_if_fail(! "write_config failed", __LINE__); + } + + ui_set_statusbar(TRUE, _("Project \"%s\" created."), app->project->name); + ui_add_recent_project_file(app->project->file_name); + return 0; +} + + /* TODO: this should be ported to Glade like the project preferences dialog, * then we can get rid of the PropertyDialogElements struct altogether as * widgets pointers can be accessed through ui_lookup_widget(). */ -void project_new(void) +void project_new_dialog(void) { GtkWidget *vbox; GtkWidget *table; @@ -271,22 +643,6 @@ static void run_new_dialog(PropertyDialogElements *e) } -gboolean project_load_file_with_session(const gchar *locale_file_name) -{ - if (project_load_file(locale_file_name)) - { - if (project_prefs.project_session) - { - configuration_open_files(); - document_new_file_if_non_open(); - ui_focus_current_document(); - } - return TRUE; - } - return FALSE; -} - - static void run_open_dialog(GtkDialog *dialog) { while (gtk_dialog_run(dialog) == GTK_RESPONSE_ACCEPT) @@ -295,7 +651,7 @@ static void run_open_dialog(GtkDialog *dialog) if (app->project && !project_close(FALSE)) {} /* try to load the config */ - else if (! project_load_file_with_session(filename)) + else if (! project_load_file(filename, TRUE)) { gchar *utf8_filename = utils_get_utf8_from_locale(filename); @@ -311,7 +667,7 @@ static void run_open_dialog(GtkDialog *dialog) } -void project_open(void) +void project_open_dialog(void) { const gchar *dir = local_prefs.project_file_path; @@ -323,7 +679,7 @@ void project_open(void) { if (app->project && !project_close(FALSE)) {} /* try to load the config */ - else if (! project_load_file_with_session(file)) + else if (! project_load_file(file, TRUE)) { SHOW_ERR1(_("Project file \"%s\" could not be loaded."), file); } @@ -564,7 +920,7 @@ static void create_properties_dialog(PropertyDialogElements *e) } -static void show_project_properties(gboolean show_build) +void project_properties_dialog(gchar const *page_name) { GeanyProject *p = app->project; GtkWidget *widget = NULL; @@ -615,7 +971,7 @@ static void show_project_properties(gboolean show_build) gtk_widget_show_all(e.dialog); /* note: notebook page must be shown before setting current page */ - if (show_build) + if (utils_str_equal(page_name, "build")) gtk_notebook_set_current_page(GTK_NOTEBOOK(e.notebook), e.build_page_num); else gtk_notebook_set_current_page(GTK_NOTEBOOK(e.notebook), 0); @@ -642,18 +998,6 @@ static void show_project_properties(gboolean show_build) } -void project_properties(void) -{ - show_project_properties(FALSE); -} - - -void project_build_properties(void) -{ - show_project_properties(TRUE); -} - - /* checks whether there is an already open project and asks the user if he wants to close it or * abort the current action. Returns FALSE when the current action(the caller) should be cancelled * and TRUE if we can go ahead */ @@ -696,6 +1040,7 @@ static GeanyProject *create_project(void) } + /* Verifies data for New & Properties dialogs. * Creates app->project if NULL. * Returns: FALSE if the user needs to change any data. */ @@ -742,11 +1087,20 @@ static gboolean update_config(const PropertyDialogElements *e, gboolean new_proj { /* check whether the given directory actually exists */ gchar *locale_path = utils_get_locale_from_utf8(base_path); - if (! g_path_is_absolute(locale_path)) + if (! utils_is_absolute_path(locale_path)) { /* relative base path, so add base dir of project file name */ - gchar *dir = g_path_get_dirname(locale_filename); - SETPTR(locale_path, g_build_filename(dir, locale_path, NULL)); - g_free(dir); + if (utils_is_absolute_path(file_name)) + { + gchar *dir = g_path_get_dirname(locale_filename); + SETPTR(locale_path, g_build_filename(dir, locale_path, NULL)); + g_free(dir); + } + else + { /* project filename and basepath are both relative */ + SHOW_ERR(_("The project filename and base path cannot both be relative paths.")); + gtk_widget_grab_focus(e->file_name); + return FALSE; + } } if (! g_file_test(locale_path, G_FILE_TEST_IS_DIR)) @@ -773,6 +1127,12 @@ static gboolean update_config(const PropertyDialogElements *e, gboolean new_proj } g_free(locale_path); } + + if (! utils_is_absolute_path(locale_filename)) + { + SETPTR(locale_filename, g_build_filename(base_path, file_name, NULL)); + } + /* finally test whether the given project file can be written */ if ((err_code = utils_is_file_writable(locale_filename)) != 0 || (err_code = g_file_test(locale_filename, G_FILE_TEST_IS_DIR) ? EISDIR : 0) != 0) @@ -1009,7 +1369,7 @@ static void on_radio_long_line_custom_toggled(GtkToggleButton *radio, GtkWidget } -gboolean project_load_file(const gchar *locale_file_name) +gboolean project_load_file(const gchar *locale_file_name, gboolean with_session) { g_return_val_if_fail(locale_file_name != NULL, FALSE); @@ -1021,6 +1381,13 @@ gboolean project_load_file(const gchar *locale_file_name) ui_add_recent_project_file(utf8_filename); g_free(utf8_filename); + + if (with_session && project_prefs.project_session) + { + configuration_open_files(); + document_new_file_if_non_open(); + ui_focus_current_document(); + } return TRUE; } else diff --git a/src/project.h b/src/project.h index 4fdacb65bb..e7b14c3b68 100644 --- a/src/project.h +++ b/src/project.h @@ -67,22 +67,26 @@ void project_init(void); void project_finalize(void); -void project_new(void); +void project_new_dialog(void); -void project_open(void); +gint project_new(gchar const *name, gchar const *file_name, gchar const *base_path); -gboolean project_close(gboolean open_default); +void project_open_dialog(void); + +void project_open_folder_dialog(void); -void project_properties(void); +gint project_open_folder(gchar *folder_name, gboolean use_session); -void project_build_properties(void); +void project_save_as_dialog(void); + +gboolean project_close(gboolean open_default); gboolean project_ask_close(void); +void project_properties_dialog(gchar const *page_name); -gboolean project_load_file(const gchar *locale_file_name); -gboolean project_load_file_with_session(const gchar *locale_file_name); +gboolean project_load_file(const gchar *locale_file_name, gboolean with_session); gchar *project_get_base_path(void); diff --git a/src/socket.c b/src/socket.c index 451cc926e5..1875f3a5f1 100644 --- a/src/socket.c +++ b/src/socket.c @@ -128,9 +128,42 @@ static void send_open_command(gint sock, gint argc, gchar **argv) { gint i; - g_return_if_fail(argc > 1); geany_debug("using running instance of Geany"); + if (cl_options.project_path) + { + gchar *filename = main_get_argv_filename(cl_options.project_path); + SETPTR(filename, utils_get_path_from_uri(filename)); + + if (g_file_test(filename, G_FILE_TEST_IS_DIR)) + { + socket_fd_write_all(sock, "folder\n", 7); + socket_fd_write_all(sock, filename, strlen(filename)); + socket_fd_write_all(sock, "\n.\n", 3); + } + else if (g_file_test(filename, G_FILE_TEST_IS_REGULAR)) + { + socket_fd_write_all(sock, "project\n", 8); + socket_fd_write_all(sock, filename, strlen(filename)); + socket_fd_write_all(sock, "\n.\n", 3); + } + g_free(filename); + } + else if (argc > 1 && g_str_has_suffix(argv[1], "." GEANY_PROJECT_EXT)) + { + gchar *filename = main_get_argv_filename(argv[1]); + SETPTR(filename, utils_get_path_from_uri(filename)); + + socket_fd_write_all(sock, "project\n", 8); + socket_fd_write_all(sock, filename, strlen(filename)); + socket_fd_write_all(sock, "\n.\n", 3); + argc--, argv++; + g_free(filename); + } + + if (argc < 2) + return; + if (cl_options.goto_line >= 0) { gchar *line = g_strdup_printf("%d\n", cl_options.goto_line); @@ -325,7 +358,7 @@ gint socket_init(gint argc, gchar **argv, G_GNUC_UNUSED gushort socket_port) /* remote command mode, here we have another running instance and want to use it */ /* now we send the command line args */ - if (argc > 1) + if (argc > 1 || cl_options.project_path) { #ifdef G_OS_WIN32 /* first we send a request to retrieve the window handle and focus the window */ @@ -629,12 +662,7 @@ static void handle_input_filename(const gchar *buf) locale_filename = utils_get_locale_from_utf8(utf8_filename); if (locale_filename) { - if (g_str_has_suffix(locale_filename, ".geany")) - { - if (project_ask_close()) - main_load_project_from_command_line(locale_filename, TRUE); - } - else + if (!g_file_test(locale_filename, G_FILE_TEST_IS_DIR)) main_handle_filename(locale_filename); } g_free(utf8_filename); @@ -642,6 +670,27 @@ static void handle_input_filename(const gchar *buf) } +static void handle_input_project(const gchar *buf) +{ + gchar *utf8_filename, *locale_filename; + + /* we never know how the input is encoded, so do the best auto detection we can */ + if (! g_utf8_validate(buf, -1, NULL)) + utf8_filename = encodings_convert_to_utf8(buf, -1, NULL); + else + utf8_filename = g_strdup(buf); + + locale_filename = utils_get_locale_from_utf8(utf8_filename); + if (locale_filename) + { + if (project_ask_close()) + project_load_file(locale_filename, TRUE); + } + g_free(utf8_filename); + g_free(locale_filename); +} + + static gchar *build_document_list(void) { GString *doc_list = g_string_new(NULL); @@ -701,6 +750,34 @@ gboolean socket_lock_input_cb(GIOChannel *source, GIOCondition condition, gpoint socket_fd_write_all(sock, "\3", 1); g_free(doc_list); } + else if (strncmp(buf, "project", 7) == 0) + { + while (socket_fd_gets(sock, buf, sizeof(buf)) != -1 && *buf != '.') + { + gsize buf_len = strlen(buf); + + /* remove trailing newline */ + if (buf_len > 0 && buf[buf_len - 1] == '\n') + buf[buf_len - 1] = '\0'; + + handle_input_project(buf); + } + popup = TRUE; + } + else if (strncmp(buf, "folder", 6) == 0) + { + while (socket_fd_gets(sock, buf, sizeof(buf)) != -1 && *buf != '.') + { + gsize buf_len = strlen(buf); + + /* remove trailing newline */ + if (buf_len > 0 && buf[buf_len - 1] == '\n') + buf[buf_len - 1] = '\0'; + + project_open_folder(buf, TRUE); + } + popup = TRUE; + } else if (strncmp(buf, "line", 4) == 0) { while (socket_fd_gets(sock, buf, sizeof(buf)) != -1 && *buf != '.') diff --git a/src/ui_utils.c b/src/ui_utils.c index f1768cf85a..9c4d743313 100644 --- a/src/ui_utils.c +++ b/src/ui_utils.c @@ -1191,7 +1191,7 @@ static void recent_project_activate_cb(GtkMenuItem *menuitem, G_GNUC_UNUSED gpoi gchar *locale_filename = utils_get_locale_from_utf8(utf8_filename); if (app->project && !project_close(FALSE)) {} - else if (project_load_file_with_session(locale_filename)) + else if (project_load_file(locale_filename, TRUE)) recent_file_loaded(utf8_filename, recent_get_recent_projects()); g_free(locale_filename);