Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'File->Open Files Recursively' #1163

Closed
wants to merge 12 commits into from
16 changes: 16 additions & 0 deletions data/geany.glade
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@
<property name="stock">gtk-open</property>
<property name="icon-size">1</property>
</object>
<object class="GtkImage" id="image_open_files_recursively1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-open</property>
<property name="icon-size">1</property>
</object>
<object class="GtkImage" id="image3192">
<property name="visible">True</property>
<property name="can_focus">False</property>
Expand Down Expand Up @@ -6368,6 +6374,16 @@
<signal name="activate" handler="on_open1_activate" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="menu_open_files_recursively1">
<property name="label" translatable="yes">Open Files Recursivel_y</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="image">image_open_files_recursively1</property>
<signal name="activate" handler="on_open_files_recursively1_activate" swapped="no"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menu_open_selected_file1">
<property name="visible">True</property>
Expand Down
2 changes: 2 additions & 0 deletions doc/geany.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3313,6 +3313,8 @@ New Ctrl-N (C) Creates a new file.

Open Ctrl-O (C) Opens a file.

Open files recursively Opens files recursively.

Open selected file Ctrl-Shift-O Opens the selected filename.

Re-open last closed tab Re-opens the last closed document tab.
Expand Down
9 changes: 8 additions & 1 deletion src/callbacks.c
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,14 @@ static void on_info1_activate(GtkMenuItem *menuitem, gpointer user_data)
/* open file */
void on_open1_activate(GtkMenuItem *menuitem, gpointer user_data)
{
dialogs_show_open_file();
dialogs_show_open_file(FALSE);
}


/* open files recursively */
void on_open_files_recursively1_activate(GtkMenuItem *menuitem, gpointer user_data)
{
dialogs_show_open_file(TRUE);
}


Expand Down
2 changes: 2 additions & 0 deletions src/callbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ void on_quit1_activate(GtkMenuItem *menuitem, gpointer user_data);

void on_open1_activate(GtkMenuItem *menuitem, gpointer user_data);

void on_open_files_recursively1_activate(GtkMenuItem *menuitem, gpointer user_data);

void on_save_all1_activate(GtkMenuItem *menuitem, gpointer user_data);

void on_close1_activate(GtkMenuItem *menuitem, gpointer user_data);
Expand Down
97 changes: 84 additions & 13 deletions src/dialogs.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@
enum
{
GEANY_RESPONSE_RENAME,
GEANY_RESPONSE_VIEW
GEANY_RESPONSE_VIEW,
GEANY_RESPONSE_OPEN_RECURSIVELY
};


Expand Down Expand Up @@ -124,11 +125,11 @@ static void file_chooser_set_filter_idx(GtkFileChooser *chooser, guint idx)
}


static gboolean open_file_dialog_handle_response(GtkWidget *dialog, gint response)
static gboolean open_file_dialog_handle_response(GtkWidget *dialog, gint response, gboolean recursive)
{
gboolean ret = TRUE;

if (response == GTK_RESPONSE_ACCEPT || response == GEANY_RESPONSE_VIEW)
if (response == GTK_RESPONSE_ACCEPT || response == GEANY_RESPONSE_VIEW || response == GEANY_RESPONSE_OPEN_RECURSIVELY)
{
GSList *filelist;
GeanyFiletype *ft = NULL;
Expand All @@ -151,6 +152,15 @@ static gboolean open_file_dialog_handle_response(GtkWidget *dialog, gint respons
charset = encodings[filesel_state.open.encoding_idx].charset;

filelist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));

if (filelist == NULL && recursive)
{
gchar *path = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog));

if (path)
filelist = g_slist_append(filelist, path);
}

if (filelist != NULL)
{
const gchar *first = filelist->data;
Expand All @@ -163,7 +173,50 @@ static gboolean open_file_dialog_handle_response(GtkWidget *dialog, gint respons
}
else
{
document_open_files(filelist, ro, ft, charset);
const gchar *only_dir = NULL;
gboolean opens_many = FALSE;

if (filelist->next)
opens_many = TRUE;
else if (g_file_test(filelist->data, G_FILE_TEST_IS_DIR))
{
opens_many = TRUE;
only_dir = filelist->data;
}

if (recursive && opens_many)
{
GtkFileFilter *filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
const gchar *filter_name = gtk_file_filter_get_name(filter);
GError *error;
gchar *message;

if (only_dir)
message = g_strdup_printf(_("Open files in \"%s\" using filter \"%s\"?"),
only_dir, filter_name);
else
message = g_strdup_printf(_("Open files using filter \"%s\"?"),
filter_name);

if (dialogs_show_question_full(dialog, GTK_STOCK_YES, GTK_STOCK_NO,
_("This may take a while depending on the files and the filter."),
"%s", message))
{
document_open_files_recursively(filelist, ro, ft, charset, filter, &error);

if (error)
{
dialogs_show_msgbox(GTK_MESSAGE_ERROR, "%s", error->message);
g_error_free(error);
}
}
else
ret = FALSE;

g_free(message);
}
else
document_open_files(filelist, ro, ft, charset);
}
g_slist_foreach(filelist, (GFunc) g_free, NULL); /* free filenames */
}
Expand Down Expand Up @@ -368,23 +421,40 @@ static GtkWidget *add_file_open_extra_widget(GtkWidget *dialog)
}


static GtkWidget *create_open_file_dialog(void)
static GtkWidget *create_open_file_dialog(gboolean recursive)
{
GtkWidget *dialog;
GtkWidget *viewbtn;
GSList *node;
const gchar *title, *open_button_text, *view_button_text;
gint open_response_id;

if (recursive)
{
title = _("Open Files Recursively");
open_button_text = _("_Open Recursively");
open_response_id = GEANY_RESPONSE_OPEN_RECURSIVELY;
view_button_text = _("_View Recursively");
}
else
{
title = _("Open File");
open_button_text = GTK_STOCK_OPEN;
open_response_id = GTK_RESPONSE_ACCEPT;
view_button_text = C_("Open dialog action", "_View");
}

dialog = gtk_file_chooser_dialog_new(_("Open File"), GTK_WINDOW(main_widgets.window),
dialog = gtk_file_chooser_dialog_new(title, GTK_WINDOW(main_widgets.window),
GTK_FILE_CHOOSER_ACTION_OPEN, NULL, NULL);
gtk_widget_set_name(dialog, "GeanyDialog");

viewbtn = gtk_dialog_add_button(GTK_DIALOG(dialog), C_("Open dialog action", "_View"), GEANY_RESPONSE_VIEW);
viewbtn = gtk_dialog_add_button(GTK_DIALOG(dialog), view_button_text, GEANY_RESPONSE_VIEW);
gtk_widget_set_tooltip_text(viewbtn,
_("Opens the file in read-only mode. If you choose more than one file to open, all files will be opened read-only."));

gtk_dialog_add_buttons(GTK_DIALOG(dialog),
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
open_button_text, open_response_id, NULL);
gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);

gtk_widget_set_size_request(dialog, -1, 460);
Expand Down Expand Up @@ -448,7 +518,7 @@ static void open_file_dialog_apply_settings(GtkWidget *dialog)


/* This shows the file selection dialog to open a file. */
void dialogs_show_open_file(void)
void dialogs_show_open_file(gboolean recursive)
{
gchar *initdir;

Expand All @@ -463,12 +533,12 @@ void dialogs_show_open_file(void)
SETPTR(initdir, utils_get_locale_from_utf8(initdir));

#ifdef G_OS_WIN32
if (interface_prefs.use_native_windows_dialogs)
if (interface_prefs.use_native_windows_dialogs && !recursive)
win32_show_document_open_dialog(GTK_WINDOW(main_widgets.window), _("Open File"), initdir);
else
#endif
{
GtkWidget *dialog = create_open_file_dialog();
GtkWidget *dialog = create_open_file_dialog(recursive);

open_file_dialog_apply_settings(dialog);

Expand All @@ -479,8 +549,9 @@ void dialogs_show_open_file(void)
gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog),
app->project->base_path, NULL);

while (!open_file_dialog_handle_response(dialog,
gtk_dialog_run(GTK_DIALOG(dialog))));
while (!open_file_dialog_handle_response(dialog, gtk_dialog_run(GTK_DIALOG(dialog)),
recursive))
;
gtk_widget_destroy(dialog);
}
g_free(initdir);
Expand Down
2 changes: 1 addition & 1 deletion src/dialogs.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ gchar *dialogs_show_input(const gchar *title, GtkWindow *parent,
typedef void (*GeanyInputCallback)(const gchar *text, gpointer data);


void dialogs_show_open_file(void);
void dialogs_show_open_file(gboolean recursive);

gboolean dialogs_show_unsaved_file(GeanyDocument *doc);

Expand Down
132 changes: 132 additions & 0 deletions src/document.c
Original file line number Diff line number Diff line change
Expand Up @@ -1565,6 +1565,138 @@ void document_open_files(const GSList *filenames, gboolean readonly, GeanyFilety
}


static GFileEnumerator *enumerate_children(const char *dir_path, GError **error)
{
GFile *file = g_file_new_for_path(dir_path);

const gchar *attributes = G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_NAME ","
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE;

GFileEnumerator *enumerator = g_file_enumerate_children(file, attributes,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently Geany only requires 2.32, but this is a 2.44 function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What function, g_file_enumerate_children? It does exist in that version.

G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, error);

g_object_unref(file);
return enumerator;
}


void document_open_files_recursively(const GSList *filenames, gboolean readonly, GeanyFiletype *ft,
const gchar *forced_enc, GtkFileFilter *filter, GError **error)
{
const GSList *item;
gchar *file_path, *dir_path;
const gchar *filename;
GFileEnumerator *enumerator;
GFileInfo *file_info;
GFileType file_type;
GError *my_error = NULL;

typedef struct EnumeratorData
{
GFileEnumerator *enumerator;
gchar *dir_path;
} EnumeratorData;

const guint RECURSION_LIMIT = 100;
const guint ENUM_STACK_SIZE = RECURSION_LIMIT - 1;
EnumeratorData enum_stack[ENUM_STACK_SIZE];
guint enum_stack_index = 0;

GtkFileFilterInfo filter_info;
filter_info.contains = GTK_FILE_FILTER_FILENAME|GTK_FILE_FILTER_DISPLAY_NAME|GTK_FILE_FILTER_MIME_TYPE;
filter_info.uri = NULL;

for (item = filenames; item != NULL; item = g_slist_next(item))
{
file_path = item->data;

if (g_file_test(file_path, G_FILE_TEST_IS_DIR))
{
dir_path = file_path;
enumerator = enumerate_children(dir_path, &my_error);

while (TRUE)
{
while (my_error == NULL)
{
file_info = g_file_enumerator_next_file(enumerator, NULL, &my_error);

if (file_info == NULL)
break;

file_type = g_file_info_get_file_type(file_info);

if (file_type != G_FILE_TYPE_SYMBOLIC_LINK)
{
filename = g_file_info_get_name(file_info);

if (filename == NULL)
my_error = g_error_new(0, 0, "Failed to get filename of a file");
else
{
if (file_type == G_FILE_TYPE_DIRECTORY)
{
if (enum_stack_index == ENUM_STACK_SIZE)
my_error = g_error_new(0, 0, "Recursion depth limit reached");
else
{
enum_stack[enum_stack_index].enumerator = enumerator;
enum_stack[enum_stack_index++].dir_path = dir_path;

dir_path = g_build_filename(dir_path, filename, NULL);
enumerator = enumerate_children(dir_path, &my_error);
}
}
else
{
file_path = g_build_filename(dir_path, filename, NULL);

if (file_path)
{
filter_info.filename = filename;
filter_info.display_name = g_file_info_get_display_name(file_info);
filter_info.mime_type = g_file_info_get_content_type(file_info);

if (gtk_file_filter_filter(filter, &filter_info))
document_open_file(file_path, readonly, ft, forced_enc);

g_free(file_path);
}
}
}
}

g_object_unref(file_info);
}

g_object_unref(enumerator);

if (enum_stack_index == 0)
break;

g_free(dir_path);
enumerator = enum_stack[--enum_stack_index].enumerator;
dir_path = enum_stack[enum_stack_index].dir_path;
}
}
else
document_open_file(file_path, readonly, ft, forced_enc);
}

if (my_error)
{
if (error)
*error = g_error_new(my_error->domain, my_error->code,
"Failed to open files recursively: %s", my_error->message);

g_error_free(my_error);
}
else
if (error)
*error = NULL;
}


static void on_keep_edit_history_on_reload_response(GtkWidget *bar, gint response_id, GeanyDocument *doc)
{
if (response_id == GTK_RESPONSE_NO)
Expand Down
3 changes: 3 additions & 0 deletions src/document.h
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,9 @@ void document_set_data(GeanyDocument *doc, const gchar *key, gpointer data);
void document_set_data_full(GeanyDocument *doc, const gchar *key,
gpointer data, GDestroyNotify free_func);

void document_open_files_recursively(const GSList *filenames, gboolean readonly, GeanyFiletype *ft,
const gchar *forced_enc, GtkFileFilter *filter, GError **error);

#endif /* GEANY_PRIVATE */

G_END_DECLS
Expand Down