This repository has been archived on 2020-04-11. You can view files and clone it, but cannot push or open issues or pull requests.
gnoemoe/gnoemoe/widgets/gm-editor-view.c

701 lines
19 KiB
C

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <gtksourceview/gtksourceview.h>
#include <gtksourceview/gtksourcelanguage.h>
#include <gtksourceview/gtksourcelanguagesmanager.h>
#include <gtksourceview/gtksourcetag.h>
#include "gm-editor-view.h"
#include "gm-pixbuf.h"
#include "gm-support.h"
#include "gm-debug.h"
#include "gm-string.h"
#include "gm-options.h"
#include "gm-color-table.h"
#include "gm-app.h"
#include "gm-source-style-scheme.h"
#include "gm-searchable.h"
#ifdef HAVE_PARSER
#include "parser/gm-parser.h"
#endif
#define GM_EDITOR_VIEW_GET_PRIVATE(object)( \
G_TYPE_INSTANCE_GET_PRIVATE((object), \
GM_TYPE_EDITOR_VIEW, GmEditorViewPrivate))
struct _GmEditorViewPrivate {
GmWorld *world;
GmEditor *editor;
#ifdef HAVE_PARSER
GtkExpander *expander;
GtkWidget *message_area;
GtkWidget *error_area;
GtkWidget *error_label;
GtkWidget *warning_label;
GtkWidget *error_frame;
GtkWidget *warning_frame;
gboolean set_style;
guint timeout_handler;
guint hide_error_handler;
gboolean expanding;
gboolean was_expanded;
#endif
GtkSourceView *source_view;
};
static GtkSourceLanguage *language = NULL;
/* Signals */
enum {
MODIFIED_CHANGED,
NUM_SIGNALS
};
static guint gm_editor_view_signals[NUM_SIGNALS] = {0};
static void gm_editor_view_searchable_iface_init(
GmSearchableInterface *iface);
static GtkTextView *gm_editor_view_searchable_get_text_view(GmSearchable *sea);
void on_gm_editor_view_save_clicked(GtkToolButton *button,
GmEditorView *view);
void on_gm_editor_view_saveclose_clicked(GtkToolButton *button,
GmEditorView *view);
void on_gm_editor_view_close_clicked(GtkToolButton *button,
GmEditorView *view);
void on_gm_editor_view_execute_clicked(GtkToolButton *button,
GmEditorView *view);
void on_gm_editor_view_editor_saved(GmEditor *editor, GmEditorView *view);
void on_gm_editor_view_font_changed(GmColorTable *color_table,
gchar const *desc, GmEditorView *view);
void on_gm_editor_view_modified_changed(GtkTextBuffer *buffer,
GmEditorView *view);
#ifdef HAVE_PARSER
void on_gm_editor_view_changed(GtkTextBuffer *buffer,
GmEditorView *view);
void on_gm_editor_view_expander(GObject *object, GParamSpec *param_spec,
GmEditorView *view);
#endif
G_DEFINE_TYPE_EXTENDED(GmEditorView, gm_editor_view, GTK_TYPE_VBOX, 0, \
G_IMPLEMENT_INTERFACE(GM_TYPE_SEARCHABLE, \
gm_editor_view_searchable_iface_init))
static void
gm_editor_view_searchable_iface_init(GmSearchableInterface *iface) {
iface->get_text_view = gm_editor_view_searchable_get_text_view;
}
static GtkTextView *
gm_editor_view_searchable_get_text_view(GmSearchable *sea) {
GmEditorView *view = (GmEditorView *)(sea);
g_return_val_if_fail(GM_IS_EDITOR_VIEW(sea), NULL);
return GTK_TEXT_VIEW(view->priv->source_view);
}
static void
gm_editor_view_finalize(GObject *object) {
#ifdef HAVE_PARSER
GmEditorView *obj = GM_EDITOR_VIEW(object);
if (obj->priv->timeout_handler) {
g_source_remove(obj->priv->timeout_handler);
}
if (obj->priv->hide_error_handler) {
g_source_remove(obj->priv->hide_error_handler);
}
#endif
G_OBJECT_CLASS(gm_editor_view_parent_class)->finalize(object);
}
void
gm_editor_view_init_language() {
GtkSourceLanguagesManager *manager;
if (language == NULL) {
manager = gtk_source_languages_manager_new();
language = g_object_ref(gtk_source_languages_manager_get_language_from_mime_type(
manager, "text/x-moo"));
gtk_source_language_set_style_scheme(language,
gm_source_style_scheme_get_default());
g_object_unref(manager);
}
}
static void
gm_editor_view_class_init(GmEditorViewClass *klass) {
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->finalize = gm_editor_view_finalize;
gm_editor_view_signals[MODIFIED_CHANGED] =
g_signal_new("modified_changed",
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(GmEditorViewClass, modified_changed),
NULL, NULL,
g_cclosure_marshal_VOID__BOOLEAN,
G_TYPE_NONE,
1,
G_TYPE_BOOLEAN);
gm_editor_view_init_language();
g_type_class_add_private(object_class, sizeof(GmEditorViewPrivate));
}
GtkSourceView *
gm_editor_view_create_source_view(GmEditorView *view) {
GtkTextBuffer *buffer;
GtkWidget *source_view;
GtkTextIter iter;
gchar *line;
GList *lines;
PangoFontDescription *f;
GtkSourceTagStyle *st;
source_view = gtk_source_view_new();
buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(source_view));
if (language) {
gtk_source_buffer_set_language(GTK_SOURCE_BUFFER(buffer), language);
}
gtk_source_buffer_begin_not_undoable_action(GTK_SOURCE_BUFFER(buffer));
gtk_text_buffer_get_end_iter(buffer, &iter);
gm_register_schemed(source_view, gm_app_color_table(gm_app_instance()),
GM_SCHEMED_COLORS | GM_SCHEMED_FONT);
for (lines = gm_editor_lines(view->priv->editor); lines;
lines = lines->next) {
line = (gchar *) (lines->data);
gtk_text_buffer_insert(buffer, &iter, line, -1);
if (lines->next) {
gtk_text_buffer_insert(buffer, &iter, "\r\n", 2);
}
}
gtk_source_buffer_end_not_undoable_action(GTK_SOURCE_BUFFER(buffer));
gtk_text_buffer_set_modified(buffer, FALSE);
st = gtk_source_tag_style_new();
st->mask = GTK_SOURCE_TAG_STYLE_USE_BACKGROUND;
gdk_color_parse("#0000FF", &(st->background));
gtk_source_buffer_set_bracket_match_style(GTK_SOURCE_BUFFER(buffer), st);
f = pango_font_description_from_string(gm_color_table_font_description(
gm_app_color_table(gm_app_instance())));
if (f) {
gtk_widget_modify_font(source_view, f);
pango_font_description_free(f);
}
gtk_source_view_set_insert_spaces_instead_of_tabs(GTK_SOURCE_VIEW(
source_view), TRUE);
gtk_source_view_set_auto_indent(GTK_SOURCE_VIEW(source_view), TRUE);
gtk_source_view_set_show_line_numbers(GTK_SOURCE_VIEW(source_view), TRUE);
gtk_source_view_set_tabs_width(GTK_SOURCE_VIEW(source_view), 2);
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(source_view), GTK_WRAP_WORD_CHAR);
//gtk_text_view_set_left_margin(GTK_TEXT_VIEW(source_view), 6);
//gtk_text_view_set_right_margin(GTK_TEXT_VIEW(source_view), 6);
if (gm_editor_is_code(view->priv->editor)) {
gtk_source_buffer_set_highlight(GTK_SOURCE_BUFFER(buffer), TRUE);
#ifdef HAVE_PARSER
g_signal_connect(buffer, "changed",
G_CALLBACK(on_gm_editor_view_changed), view);
#endif
} else {
gtk_source_buffer_set_highlight(GTK_SOURCE_BUFFER(buffer), FALSE);
gtk_source_buffer_set_check_brackets(GTK_SOURCE_BUFFER(buffer), FALSE);
}
#ifdef HAVE_PARSER
gtk_text_buffer_create_tag(buffer, "gm_error", "underline",
PANGO_UNDERLINE_ERROR, NULL);
#endif
g_signal_connect(buffer, "modified_changed",
G_CALLBACK(on_gm_editor_view_modified_changed), view);
gtk_widget_show(source_view);
return GTK_SOURCE_VIEW(source_view);
}
#ifdef HAVE_PARSER
static gboolean
gm_editor_view_paint_message_area(GtkWidget *widget, GdkEventExpose *event,
GmEditorView *view) {
gtk_paint_flat_box(widget->style, widget->window, GTK_STATE_NORMAL,
GTK_SHADOW_OUT, NULL, widget, "tooltip",
widget->allocation.x + 1, widget->allocation.y + 1,
widget->allocation.width - 2, widget->allocation.height - 2);
return FALSE;
}
static void
gm_editor_view_message_area_style_set(GtkWidget *widget, GtkStyle *prev,
GmEditorView *view) {
GtkTooltips *tooltips;
GtkStyle *style;
if (view->priv->set_style) {
return;
}
widget = view->priv->message_area;
tooltips = gtk_tooltips_new();
g_object_ref(G_OBJECT(tooltips));
gtk_object_sink(GTK_OBJECT(tooltips));
gtk_tooltips_force_window(tooltips);
gtk_widget_ensure_style(tooltips->tip_window);
style = gtk_widget_get_style(tooltips->tip_window);
style->bg[GTK_STATE_PRELIGHT] = style->bg[GTK_STATE_NORMAL];
view->priv->set_style = TRUE;
gtk_widget_set_style(widget, style);
gtk_widget_set_style(GTK_WIDGET(view->priv->expander), style);
view->priv->set_style = FALSE;
g_object_unref(tooltips);
}
static void
gm_editor_view_create_message_area(GmEditorView *view) {
GtkWidget *message_area = gtk_vbox_new(FALSE, 0);
GtkWidget *align;
GtkWidget *lbl;
GtkWidget *expander;
gtk_container_set_border_width(GTK_CONTAINER(message_area), 10);
g_signal_connect(message_area, "expose_event",
G_CALLBACK(gm_editor_view_paint_message_area), view);
g_signal_connect(message_area, "style-set",
G_CALLBACK(gm_editor_view_message_area_style_set), view);
view->priv->message_area = message_area;
align = gtk_alignment_new(0.0, 1.0, 0.0, 0.0);
expander = gtk_expander_new(NULL);
g_signal_connect(expander, "notify::expanded",
G_CALLBACK(on_gm_editor_view_expander), view);
gtk_container_add(GTK_CONTAINER(align), expander);
gtk_box_pack_start(GTK_BOX(message_area), align, FALSE, FALSE, 0);
view->priv->expander = GTK_EXPANDER(expander);
g_signal_connect(expander, "style-set",
G_CALLBACK(gm_editor_view_message_area_style_set), view);
gtk_widget_show(expander);
gtk_widget_show(align);
view->priv->error_area = gtk_vbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(message_area), view->priv->error_area, TRUE,
TRUE, 0);
lbl = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(lbl), _("<b>Errors</b>"));
view->priv->error_frame = gtk_frame_new(NULL);
gtk_frame_set_shadow_type(GTK_FRAME(view->priv->error_frame),
GTK_SHADOW_NONE);
gtk_frame_set_label_widget(GTK_FRAME(view->priv->error_frame), lbl);
view->priv->error_label = gtk_label_new(NULL);
gtk_misc_set_alignment(GTK_MISC(view->priv->error_label), 0.0, 0.0);
align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 0);
gtk_container_add(GTK_CONTAINER(align), view->priv->error_label);
gtk_container_add(GTK_CONTAINER(view->priv->error_frame), align);
gtk_box_pack_start(GTK_BOX(view->priv->error_area),
view->priv->error_frame, TRUE, TRUE, 0);
lbl = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(lbl), _("<b>Warnings</b>"));
view->priv->warning_frame = gtk_frame_new(NULL);
gtk_frame_set_shadow_type(GTK_FRAME(view->priv->warning_frame),
GTK_SHADOW_NONE);
gtk_frame_set_label_widget(GTK_FRAME(view->priv->warning_frame), lbl);
view->priv->warning_label = gtk_label_new(NULL);
gtk_misc_set_alignment(GTK_MISC(view->priv->warning_label), 0.0, 0.0);
align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 0);
gtk_container_add(GTK_CONTAINER(align), view->priv->warning_label);
gtk_container_add(GTK_CONTAINER(view->priv->warning_frame), align);
gtk_box_pack_start(GTK_BOX(view->priv->error_area),
view->priv->warning_frame, TRUE, TRUE, 0);
}
#endif
static void
gm_editor_view_init(GmEditorView *obj) {
obj->priv = GM_EDITOR_VIEW_GET_PRIVATE(obj);
gtk_box_set_spacing(GTK_BOX(obj), 0);
gtk_box_set_homogeneous(GTK_BOX(obj), FALSE);
#ifdef HAVE_PARSER
gm_editor_view_create_message_area(obj);
gtk_box_pack_start(GTK_BOX(obj), obj->priv->message_area, FALSE, FALSE, 0);
#endif
}
GmEditorView *
gm_editor_view_new(GmWorld *world, GmEditor *editor) {
GmEditorView *obj = GM_EDITOR_VIEW(g_object_new(GM_TYPE_EDITOR_VIEW, NULL));
GtkWidget *srl;
obj->priv->editor = editor;
obj->priv->world = world;
obj->priv->source_view = gm_editor_view_create_source_view(obj);
srl = gtk_scrolled_window_new(NULL, NULL);
gtk_widget_show(srl);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(srl),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_container_add(GTK_CONTAINER(srl), GTK_WIDGET(obj->priv->source_view));
gtk_box_pack_end(GTK_BOX(obj), srl, TRUE, TRUE, 0);
g_signal_connect(gm_app_color_table(gm_app_instance()), "font_changed",
G_CALLBACK(on_gm_editor_view_font_changed), obj);
g_signal_connect(editor, "saved",
G_CALLBACK(on_gm_editor_view_editor_saved), obj);
return obj;
}
GmEditor *
gm_editor_view_editor(GmEditorView *view) {
return view->priv->editor;
}
GtkTextView *
gm_editor_view_text_view(GmEditorView *view) {
return GTK_TEXT_VIEW(view->priv->source_view);
}
void
gm_editor_view_save(GmEditorView *view) {
GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(
view->priv->source_view));
GtkTextIter start, end;
gchar *text;
gtk_text_buffer_get_bounds(buffer, &start, &end);
text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
gm_editor_set_lines_from_string(view->priv->editor, text);
g_free(text);
gm_editor_save(view->priv->editor);
}
#ifdef HAVE_PARSER
gboolean
gm_editor_view_no_errors(GmEditorView *view) {
gtk_widget_hide(view->priv->message_area);
return FALSE;
}
void
gm_editor_view_parse_error(GmEditorView *view, GmParserError *error,
GString **str) {
GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(
view->priv->source_view));
GtkTextIter start, end;
gchar *msg;
if (error->ch == 0) {
gtk_text_buffer_get_iter_at_line(buffer, &start,
error->line - 1);
msg = g_markup_printf_escaped(_("<u>Line %d</u>: %s\n"), error->line,
error->message);
} else {
// Underline line
gtk_text_buffer_get_iter_at_line(buffer, &start, error->line);
msg = g_markup_printf_escaped(_("<u>Line %d:%d</u>: %s\n"),
error->line + 1, error->ch + 1, error->message);
}
if (!*str) {
*str = g_string_new(msg);
} else {
*str = g_string_append(*str, msg);
}
g_free(msg);
end = start;
if (error->ch != 0) {
gtk_text_iter_forward_chars(&start, error->ch - 1);
}
gtk_text_iter_forward_to_line_end(&end);
gtk_text_buffer_apply_tag_by_name(buffer, "gm_error", &start, &end);
}
enum {
SYNTAX_OK = 0,
SYNTAX_ERRORS = 1 << 0,
SYNTAX_WARNINGS = 1 << 1
};
int
gm_editor_view_parse(GmEditorView *view) {
gchar *text;
GtkTextIter start, end;
GList *lines;
GmParserResult *result;
GList *errors;
GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(
view->priv->source_view));
gchar *msg;
GString *str = NULL, *lbl = NULL;
int ret = SYNTAX_OK;
if (view->priv->hide_error_handler) {
g_source_remove(view->priv->hide_error_handler);
}
gtk_text_buffer_get_bounds(buffer, &start, &end);
text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
lines = gm_string_split(text);
result = gm_parser_parse(lines);
gtk_text_buffer_remove_tag_by_name(buffer, "gm_error", &start, &end);
if (!result->isOk) {
if (result->errors) {
for (errors = result->errors; errors; errors = errors->next) {
gm_editor_view_parse_error(view, (GmParserError *)(errors->data),
&str);
}
str = g_string_truncate(str, str->len - 1);
gtk_label_set_markup(GTK_LABEL(view->priv->error_label), str->str);
gtk_widget_show_all(view->priv->error_frame);
g_string_free(str, TRUE);
str = NULL;
if (g_list_length(result->errors) == 1) {
msg = g_strdup(_("There is 1 error"));
} else {
msg = g_strdup_printf(_("There are %d errors"),
g_list_length(result->errors));
}
lbl = g_string_new(msg);
g_free(msg);
ret |= SYNTAX_ERRORS;
} else {
gtk_widget_hide(view->priv->error_frame);
}
if (result->warnings) {
for (errors = result->warnings; errors; errors = errors->next) {
gm_editor_view_parse_error(view, (GmParserError *)(errors->data),
&str);
}
str = g_string_truncate(str, str->len - 1);
gtk_label_set_markup(GTK_LABEL(view->priv->warning_label),
str->str);
gtk_widget_show_all(view->priv->warning_frame);
g_string_free(str, TRUE);
if (g_list_length(result->warnings) == 1) {
if (!lbl) {
msg = g_strdup(_("There is 1 warning"));
} else {
msg = g_strdup_printf(_(" and 1 warning"));
}
} else {
if (!lbl) {
msg = g_strdup_printf(_("There are %d warnings"),
g_list_length(result->warnings));
} else {
msg = g_strdup_printf(_(" and %d warnings"),
g_list_length(result->warnings));
}
}
if (!lbl) {
lbl = g_string_new(msg);
} else {
lbl = g_string_append(lbl, msg);
}
g_free(msg);
ret |= SYNTAX_WARNINGS;
} else {
gtk_widget_hide(view->priv->warning_frame);
}
gtk_widget_set_sensitive(GTK_WIDGET(view->priv->expander), TRUE);
gtk_expander_set_label(view->priv->expander, lbl->str);
g_string_free(lbl, TRUE);
gtk_widget_show(view->priv->message_area);
if (view->priv->was_expanded &&
!gtk_expander_get_expanded(view->priv->expander)) {
view->priv->expanding = TRUE;
gtk_expander_set_expanded(view->priv->expander, TRUE);
view->priv->expanding = FALSE;
}
gtk_widget_queue_draw(view->priv->message_area);
} else {
gtk_expander_set_label(view->priv->expander, _("There are no errors"));
view->priv->expanding = TRUE;
gtk_expander_set_expanded(view->priv->expander, FALSE);
view->priv->expanding = FALSE;
gtk_widget_set_sensitive(GTK_WIDGET(view->priv->expander), FALSE);
gtk_widget_hide(view->priv->error_area);
view->priv->hide_error_handler = g_timeout_add(3000,
(GSourceFunc)gm_editor_view_no_errors, view);
gtk_widget_queue_draw(view->priv->message_area);
}
gm_parser_result_free(result);
gm_g_list_free_simple(lines);
g_free(text);
return ret;
}
static gboolean
idle_grab_focus(gpointer user_data) {
g_message("Grabbing focus");
gtk_widget_grab_focus(GTK_WIDGET(user_data));
return FALSE;
}
void
gm_editor_view_check_syntax(GmEditorView *view) {
int ret = gm_editor_view_parse(view);
if (ret == SYNTAX_OK) {
gtk_expander_set_expanded(view->priv->expander, FALSE);
gtk_widget_hide(view->priv->message_area);
gm_info_dialog(_("No errors are found"),
GTK_WINDOW(gm_find_parent(GTK_WIDGET(view), GTK_TYPE_WINDOW)));
g_timeout_add(100, idle_grab_focus, view->priv->source_view);
} else {
gtk_expander_set_expanded(view->priv->expander, TRUE);
gtk_widget_show(view->priv->message_area);
gtk_widget_show(view->priv->error_area);
gtk_widget_queue_draw(view->priv->message_area);
}
}
#endif
/* Callbacks */
void
on_gm_editor_view_font_changed(GmColorTable *color_table, gchar const *desc,
GmEditorView *view) {
PangoFontDescription *f;
f = pango_font_description_from_string(gm_color_table_font_description(
gm_app_color_table(gm_app_instance())));
if (f) {
gtk_widget_modify_font(GTK_WIDGET(view->priv->source_view), f);
pango_font_description_free(f);
}
}
void
on_gm_editor_view_editor_saved(GmEditor *editor, GmEditorView *view) {
gtk_text_buffer_set_modified(gtk_text_view_get_buffer(GTK_TEXT_VIEW(
view->priv->source_view)), FALSE);
}
void
on_gm_editor_view_modified_changed(GtkTextBuffer *buffer,
GmEditorView *view) {
g_signal_emit(view, gm_editor_view_signals[MODIFIED_CHANGED], 0,
gtk_text_buffer_get_modified(buffer));
}
#ifdef HAVE_PARSER
gboolean
gm_editor_view_timeout(GmEditorView *view) {
gm_editor_view_parse(view);
view->priv->timeout_handler = 0;
return FALSE;
}
void
on_gm_editor_view_changed(GtkTextBuffer *buffer,
GmEditorView *view) {
if (view->priv->timeout_handler != 0) {
g_source_remove(view->priv->timeout_handler);
}
if (gm_options_get_int(gm_app_options(gm_app_instance()),
"auto_check_syntax")) {
view->priv->timeout_handler = g_timeout_add(1000,
(GSourceFunc)gm_editor_view_timeout, view);
}
}
void
on_gm_editor_view_expander(GObject *object, GParamSpec *param_spec,
GmEditorView *view) {
if (gtk_expander_get_expanded(view->priv->expander)) {
if (!view->priv->expanding) {
view->priv->was_expanded = TRUE;
}
gtk_widget_show(view->priv->error_area);
} else {
if (!view->priv->expanding) {
view->priv->was_expanded = FALSE;
}
gtk_widget_hide(view->priv->error_area);
}
gtk_widget_queue_draw(view->priv->message_area);
}
#endif