gem-graph-client/libide/editor/ide-editor-page-actions.c

643 lines
22 KiB
C
Raw Normal View History

/* ide-editor-page-actions.c
*
* Copyright 2017-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "ide-editor-page-actions"
#include "config.h"
#include <glib/gi18n.h>
#include <libide-code.h>
#include <libide-gui.h>
#include "ide-editor-surface.h"
#include "ide-editor-private.h"
#include "ide-editor-print-operation.h"
#include "ide-editor-settings-dialog.h"
typedef struct
{
IdeEditorPage *self;
guint line;
guint line_offset;
} ReloadState;
static void
reload_state_free (ReloadState *state)
{
g_clear_object (&state->self);
g_slice_free (ReloadState, state);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (ReloadState, reload_state_free)
static void
ide_editor_page_actions_reload_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeBufferManager *bufmgr = (IdeBufferManager *)object;
g_autoptr(ReloadState) state = user_data;
g_autoptr(IdeBuffer) buffer = NULL;
g_autoptr(GError) error = NULL;
g_assert (IDE_IS_BUFFER_MANAGER (bufmgr));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (state != NULL);
g_assert (IDE_IS_EDITOR_PAGE (state->self));
if (state->self->progress_bar != NULL)
dzl_gtk_widget_hide_with_fade (GTK_WIDGET (state->self->progress_bar));
if (!(buffer = ide_buffer_manager_load_file_finish (bufmgr, result, &error)))
{
g_warning ("%s", error->message);
ide_page_report_error (IDE_PAGE (state->self),
/* translators: %s is the error message */
_("Failed to load file: %s"), error->message);
ide_page_set_failed (IDE_PAGE (state->self), TRUE);
}
else
{
IdeSourceView *view;
GtkTextIter iter;
view = ide_editor_page_get_view (state->self);
gtk_text_buffer_get_iter_at_line_offset (GTK_TEXT_BUFFER (buffer),
&iter,
state->line,
state->line_offset);
gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer), &iter, &iter);
ide_source_view_scroll_to_iter (view,
&iter,
.25,
IDE_SOURCE_SCROLL_BOTH,
1.0,
0.5,
FALSE);
}
gtk_revealer_set_reveal_child (state->self->modified_revealer, FALSE);
}
static void
ide_editor_page_actions_reload (GSimpleAction *action,
GVariant *param,
gpointer user_data)
{
IdeEditorPage *self = user_data;
g_autoptr(IdeNotification) notif = NULL;
g_autoptr(IdeContext) context = NULL;
IdeBufferManager *bufmgr;
ReloadState *state;
GtkTextIter iter;
IdeBuffer *buffer;
GFile *file;
g_assert (IDE_IS_EDITOR_PAGE (self));
buffer = ide_editor_page_get_buffer (self);
context = ide_buffer_ref_context (buffer);
bufmgr = ide_buffer_manager_from_context (context);
file = ide_buffer_get_file (buffer);
gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->progress_bar), 0.0);
gtk_widget_show (GTK_WIDGET (self->progress_bar));
notif = ide_notification_new ();
ide_buffer_get_selection_bounds (buffer, &iter, NULL);
state = g_slice_new0 (ReloadState);
state->self = g_object_ref (self);
state->line = gtk_text_iter_get_line (&iter);
state->line_offset = gtk_text_iter_get_line_offset (&iter);
ide_buffer_manager_load_file_async (bufmgr,
file,
IDE_BUFFER_OPEN_FLAGS_FORCE_RELOAD,
notif,
NULL,
ide_editor_page_actions_reload_cb,
g_steal_pointer (&state));
g_object_bind_property (notif, "progress",
self->progress_bar, "fraction",
G_BINDING_SYNC_CREATE);
}
static void
handle_print_result (IdeEditorPage *self,
GtkPrintOperation *operation,
GtkPrintOperationResult result)
{
g_assert (IDE_IS_EDITOR_PAGE (self));
g_assert (GTK_IS_PRINT_OPERATION (operation));
if (result == GTK_PRINT_OPERATION_RESULT_ERROR)
{
g_autoptr(GError) error = NULL;
gtk_print_operation_get_error (operation, &error);
g_warning ("%s", error->message);
ide_page_report_error (IDE_PAGE (self),
/* translators: %s is the error message */
_("Print failed: %s"), error->message);
}
}
static void
print_done (GtkPrintOperation *operation,
GtkPrintOperationResult result,
gpointer user_data)
{
IdeEditorPage *self = user_data;
g_assert (GTK_IS_PRINT_OPERATION (operation));
g_assert (IDE_IS_EDITOR_PAGE (self));
handle_print_result (self, operation, result);
g_object_unref (operation);
g_object_unref (self);
}
static void
ide_editor_page_actions_print (GSimpleAction *action,
GVariant *param,
gpointer user_data)
{
g_autoptr(IdeEditorPrintOperation) operation = NULL;
IdeEditorPage *self = user_data;
IdeSourceView *source_view;
GtkWidget *toplevel;
GtkPrintOperationResult result;
g_assert (IDE_IS_EDITOR_PAGE (self));
toplevel = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_WINDOW);
source_view = ide_editor_page_get_view (self);
operation = ide_editor_print_operation_new (source_view);
/* keep a ref until "done" is emitted */
g_object_ref (operation);
g_signal_connect_after (g_object_ref (operation),
"done",
G_CALLBACK (print_done),
g_object_ref (self));
result = gtk_print_operation_run (GTK_PRINT_OPERATION (operation),
GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
GTK_WINDOW (toplevel),
NULL);
handle_print_result (self, GTK_PRINT_OPERATION (operation), result);
}
static void
ide_editor_page_actions_save_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeBuffer *buffer = (IdeBuffer *)object;
g_autoptr(IdeEditorPage) self = user_data;
g_autoptr(GError) error = NULL;
g_assert (IDE_IS_BUFFER (buffer));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_EDITOR_PAGE (self));
if (!ide_buffer_save_file_finish (buffer, result, &error))
{
g_warning ("%s", error->message);
ide_page_report_error (IDE_PAGE (self),
/* translators: %s is the error message */
_("Failed to save file: %s"), error->message);
ide_page_set_failed (IDE_PAGE (self), TRUE);
}
if (self->progress_bar != NULL)
dzl_gtk_widget_hide_with_fade (GTK_WIDGET (self->progress_bar));
}
static void
ide_editor_page_actions_save (GSimpleAction *action,
GVariant *variant,
gpointer user_data)
{
IdeEditorPage *self = user_data;
g_autoptr(IdeNotification) notif = NULL;
g_autoptr(IdeContext) context = NULL;
g_autoptr(GFile) local_file = NULL;
g_autoptr(GFile) workdir = NULL;
IdeBuffer *buffer;
GFile *file;
g_assert (G_IS_SIMPLE_ACTION (action));
g_assert (IDE_IS_EDITOR_PAGE (self));
buffer = ide_editor_page_get_buffer (self);
g_return_if_fail (IDE_IS_BUFFER (buffer));
context = ide_buffer_ref_context (buffer);
g_return_if_fail (IDE_IS_CONTEXT (context));
file = ide_buffer_get_file (buffer);
g_return_if_fail (G_IS_FILE (file));
workdir = ide_context_ref_workdir (context);
g_assert (G_IS_FILE (workdir));
if (ide_buffer_get_is_temporary (buffer))
{
GtkFileChooserNative *dialog;
GtkWidget *toplevel;
gint ret;
toplevel = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_WINDOW);
dialog = gtk_file_chooser_native_new (_("Save File"),
GTK_WINDOW (toplevel),
GTK_FILE_CHOOSER_ACTION_SAVE,
_("Save"), _("Cancel"));
g_object_set (dialog,
"do-overwrite-confirmation", TRUE,
"local-only", FALSE,
"modal", TRUE,
"select-multiple", FALSE,
"show-hidden", FALSE,
NULL);
gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (dialog), workdir, NULL);
ret = gtk_native_dialog_run (GTK_NATIVE_DIALOG (dialog));
if (ret == GTK_RESPONSE_ACCEPT)
file = local_file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (dialog));
if (local_file == NULL)
return;
}
ide_buffer_save_file_async (buffer,
file,
NULL,
&notif,
ide_editor_page_actions_save_cb,
g_object_ref (self));
g_object_bind_property (notif, "progress", self->progress_bar, "fraction",
G_BINDING_SYNC_CREATE);
gtk_widget_show (GTK_WIDGET (self->progress_bar));
}
static void
ide_editor_page_actions_save_as_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeBuffer *buffer = (IdeBuffer *)object;
g_autoptr(IdeEditorPage) self = user_data;
g_autoptr(GError) error = NULL;
g_assert (IDE_IS_BUFFER (buffer));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_EDITOR_PAGE (self));
if (!ide_buffer_save_file_finish (buffer, result, &error))
{
/* In this case, the editor page hasn't failed since this is for an
* alternate file (which maybe we just don't have access to on the
* network or something).
*
* But we do still need to notify the user of the error.
*/
g_warning ("%s", error->message);
ide_page_report_error (IDE_PAGE (self),
/* translators: %s is the underlying error message */
_("Failed to save file: %s"),
error->message);
}
dzl_gtk_widget_hide_with_fade (GTK_WIDGET (self->progress_bar));
}
static void
ide_editor_page_actions_save_as (GSimpleAction *action,
GVariant *param,
gpointer user_data)
{
IdeEditorPage *self = user_data;
GtkFileChooserNative *dialog;
IdeBuffer *buffer;
GtkWidget *toplevel;
GFile *file;
gint ret;
g_assert (IDE_IS_EDITOR_PAGE (self));
buffer = ide_editor_page_get_buffer (self);
file = ide_buffer_get_file (buffer);
/* Just redirect to the save flow if we have a temporary
* file currently. That way we can avoid splitting the
* flow to handle both cases here.
*/
if (ide_buffer_get_is_temporary (buffer))
{
ide_editor_page_actions_save (action, NULL, user_data);
return;
}
toplevel = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_WINDOW);
dialog = gtk_file_chooser_native_new (_("Save a Copy"),
GTK_WINDOW (toplevel),
GTK_FILE_CHOOSER_ACTION_SAVE,
_("Save"),
_("Cancel"));
gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (dialog), FALSE);
gtk_file_chooser_set_show_hidden (GTK_FILE_CHOOSER (dialog), FALSE);
if (file != NULL)
gtk_file_chooser_set_file (GTK_FILE_CHOOSER (dialog), file, NULL);
ret = gtk_native_dialog_run (GTK_NATIVE_DIALOG (dialog));
if (ret == GTK_RESPONSE_ACCEPT)
{
g_autoptr(GFile) save_as = NULL;
g_autoptr(IdeNotification) notif = NULL;
save_as = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
ide_buffer_save_file_async (buffer,
save_as,
NULL,
&notif,
ide_editor_page_actions_save_as_cb,
g_object_ref (self));
g_object_bind_property (notif, "progress", self->progress_bar, "fraction",
G_BINDING_SYNC_CREATE);
gtk_widget_show (GTK_WIDGET (self->progress_bar));
}
gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (dialog));
}
static void
ide_editor_page_actions_find (GSimpleAction *action,
GVariant *variant,
gpointer user_data)
{
IdeEditorPage *self = user_data;
GtkTextIter begin;
GtkTextIter end;
g_assert (G_IS_SIMPLE_ACTION (action));
g_assert (IDE_IS_EDITOR_PAGE (self));
if (gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (self->buffer), &begin, &end))
{
g_autofree gchar *word = gtk_text_iter_get_slice (&begin, &end);
ide_editor_search_set_search_text (self->search, word);
}
ide_editor_search_bar_set_replace_mode (self->search_bar, FALSE);
gtk_revealer_set_reveal_child (self->search_revealer, TRUE);
gtk_widget_grab_focus (GTK_WIDGET (self->search_bar));
}
static void
ide_editor_page_actions_find_replace (GSimpleAction *action,
GVariant *variant,
gpointer user_data)
{
IdeEditorPage *self = user_data;
GtkTextIter begin;
GtkTextIter end;
g_assert (G_IS_SIMPLE_ACTION (action));
g_assert (IDE_IS_EDITOR_PAGE (self));
if (gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (self->buffer), &begin, &end))
{
g_autofree gchar *word = gtk_text_iter_get_slice (&begin, &end);
ide_editor_search_set_search_text (self->search, word);
}
ide_editor_search_bar_set_replace_mode (self->search_bar, TRUE);
gtk_revealer_set_reveal_child (self->search_revealer, TRUE);
gtk_widget_grab_focus (GTK_WIDGET (self->search_bar));
}
static void
ide_editor_page_actions_hide_search (GSimpleAction *action,
GVariant *variant,
gpointer user_data)
{
IdeEditorPage *self = user_data;
g_assert (G_IS_SIMPLE_ACTION (action));
g_assert (IDE_IS_EDITOR_PAGE (self));
gtk_revealer_set_reveal_child (self->search_revealer, FALSE);
gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
}
static void
ide_editor_page_actions_notify_file_settings (IdeEditorPage *self,
GParamSpec *pspec,
IdeSourceView *source_view)
{
IdeFileSettings *file_settings;
GActionGroup *group;
g_assert (IDE_IS_EDITOR_PAGE (self));
g_assert (IDE_IS_SOURCE_VIEW (source_view));
group = gtk_widget_get_action_group (GTK_WIDGET (self), "file-settings");
g_assert (DZL_IS_PROPERTIES_GROUP (group));
file_settings = ide_source_view_get_file_settings (source_view);
g_assert (!file_settings || IDE_IS_FILE_SETTINGS (file_settings));
g_object_set (group, "object", file_settings, NULL);
}
static void
ide_editor_page_actions_move_next_error (GSimpleAction *action,
GVariant *variant,
gpointer user_data)
{
ide_editor_page_move_next_error (user_data);
}
static void
ide_editor_page_actions_move_previous_error (GSimpleAction *action,
GVariant *variant,
gpointer user_data)
{
ide_editor_page_move_previous_error (user_data);
}
static void
ide_editor_page_actions_activate_next_search_result (GSimpleAction *action,
GVariant *variant,
gpointer user_data)
{
IdeEditorPage *self = user_data;
GtkTextIter begin;
GtkTextIter end;
g_assert (IDE_IS_EDITOR_PAGE (self));
ide_editor_page_move_next_search_result (self);
gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (self->buffer), &begin, &end);
gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
gtk_text_buffer_select_range (GTK_TEXT_BUFFER (self->buffer), &begin, &end);
ide_source_view_scroll_to_insert (self->source_view);
}
static void
ide_editor_page_actions_move_next_search_result (GSimpleAction *action,
GVariant *variant,
gpointer user_data)
{
ide_editor_page_move_next_search_result (user_data);
}
static void
ide_editor_page_actions_move_previous_search_result (GSimpleAction *action,
GVariant *variant,
gpointer user_data)
{
ide_editor_page_move_previous_search_result (user_data);
}
static void
ide_editor_page_actions_properties (GSimpleAction *action,
GVariant *variant,
gpointer user_data)
{
IdeEditorPage *self = user_data;
IdeEditorSettingsDialog *dialog;
g_assert (IDE_IS_EDITOR_PAGE (self));
dialog = ide_editor_settings_dialog_new (self);
g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
ide_gtk_window_present (GTK_WINDOW (dialog));
}
static void
ide_editor_page_actions_toggle_map (GSimpleAction *action,
GVariant *variant,
gpointer user_data)
{
IdeEditorPage *self = user_data;
g_assert (G_IS_SIMPLE_ACTION (action));
g_assert (IDE_IS_EDITOR_PAGE (self));
ide_editor_page_set_show_map (self, !ide_editor_page_get_show_map (self));
}
static const GActionEntry editor_view_entries[] = {
{ "activate-next-search-result", ide_editor_page_actions_activate_next_search_result },
{ "find", ide_editor_page_actions_find },
{ "find-replace", ide_editor_page_actions_find_replace },
{ "hide-search", ide_editor_page_actions_hide_search },
{ "move-next-error", ide_editor_page_actions_move_next_error },
{ "move-next-search-result", ide_editor_page_actions_move_next_search_result },
{ "move-previous-error", ide_editor_page_actions_move_previous_error },
{ "move-previous-search-result", ide_editor_page_actions_move_previous_search_result },
{ "properties", ide_editor_page_actions_properties },
{ "print", ide_editor_page_actions_print },
{ "reload", ide_editor_page_actions_reload },
{ "save", ide_editor_page_actions_save },
{ "save-as", ide_editor_page_actions_save_as },
{ "toggle-map", ide_editor_page_actions_toggle_map },
};
void
_ide_editor_page_init_actions (IdeEditorPage *self)
{
g_autoptr(GSimpleActionGroup) group = NULL;
g_autoptr(DzlPropertiesGroup) sv_props = NULL;
g_autoptr(DzlPropertiesGroup) file_props = NULL;
IdeSourceView *source_view;
g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
source_view = ide_editor_page_get_view (self);
/* Setup our user-facing actions */
group = g_simple_action_group_new ();
g_action_map_add_action_entries (G_ACTION_MAP (group),
editor_view_entries,
G_N_ELEMENTS (editor_view_entries),
self);
gtk_widget_insert_action_group (GTK_WIDGET (self), "editor-page", G_ACTION_GROUP (group));
/* We want to access some settings properties as stateful GAction so they
* manipulated using regular Gtk widgets from the properties panel.
*/
sv_props = dzl_properties_group_new (G_OBJECT (source_view));
dzl_properties_group_add_all_properties (sv_props);
dzl_properties_group_add_property_full (sv_props,
"use-spaces",
"insert-spaces-instead-of-tabs",
DZL_PROPERTIES_FLAGS_STATEFUL_BOOLEANS);
gtk_widget_insert_action_group (GTK_WIDGET (self), "source-view", G_ACTION_GROUP (sv_props));
/*
* We want to bind our file-settings, used to tweak values in the
* source-view, to a GActionGroup that can be manipulated by the properties
* editor. Make sure we get notified of changes and sink the current state.
*/
file_props = dzl_properties_group_new_for_type (IDE_TYPE_FILE_SETTINGS);
dzl_properties_group_add_all_properties (file_props);
g_signal_connect_swapped (source_view,
"notify::file-settings",
G_CALLBACK (ide_editor_page_actions_notify_file_settings),
self);
gtk_widget_insert_action_group (GTK_WIDGET (self), "file-settings", G_ACTION_GROUP (file_props));
ide_editor_page_actions_notify_file_settings (self, NULL, source_view);
}
void
_ide_editor_page_update_actions (IdeEditorPage *self)
{
g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
}