diff --git a/gnoemoe/widgets/gm-editor-view.c b/gnoemoe/widgets/gm-editor-view.c index c10fd3f..627ad5b 100644 --- a/gnoemoe/widgets/gm-editor-view.c +++ b/gnoemoe/widgets/gm-editor-view.c @@ -9,9 +9,12 @@ #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 "../parser/gm-parser.h" + #include "gm-searchable.h" #define GM_EDITOR_VIEW_GET_PRIVATE(object)( \ @@ -22,6 +25,18 @@ struct _GmEditorViewPrivate { GmWorld *world; GmEditor *editor; + GtkExpander *expander; + GtkWidget *message_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; GtkSourceView *source_view; }; @@ -47,12 +62,19 @@ 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); +void on_gm_editor_view_changed(GtkTextBuffer *buffer, + GmEditorView *view); +void on_gm_editor_view_expander(GObject *object, GParamSpec *param_spec, + GmEditorView *view); G_DEFINE_TYPE_EXTENDED(GmEditorView, gm_editor_view, GTK_TYPE_VBOX, 0, \ G_IMPLEMENT_INTERFACE(GM_TYPE_SEARCHABLE, \ @@ -74,7 +96,14 @@ gm_editor_view_searchable_get_text_view(GmSearchable *sea) { static void gm_editor_view_finalize(GObject *object) { - //GmEditorView *obj = GM_EDITOR_VIEW(object); + 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); + } G_OBJECT_CLASS(gm_editor_view_parent_class)->finalize(object); } @@ -111,7 +140,7 @@ gm_editor_view_class_init(GmEditorViewClass *klass) { } GtkSourceView * -gm_editor_create_source_view(GmEditorView *view) { +gm_editor_view_create_source_view(GmEditorView *view) { GtkTextBuffer *buffer; GtkWidget *source_view; GtkTextIter iter; @@ -128,6 +157,7 @@ gm_editor_create_source_view(GmEditorView *view) { 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); for (lines = gm_editor_lines(view->priv->editor); lines; @@ -140,6 +170,7 @@ gm_editor_create_source_view(GmEditorView *view) { } } + gtk_source_buffer_end_not_undoable_action(GTK_SOURCE_BUFFER(buffer)); gtk_text_buffer_set_modified(buffer, FALSE); st = gtk_source_tag_style_new(); @@ -169,19 +200,109 @@ gm_editor_create_source_view(GmEditorView *view) { gtk_source_buffer_set_highlight(GTK_SOURCE_BUFFER(buffer), FALSE); } + gtk_text_buffer_create_tag(buffer, "gm_error", "underline", + PANGO_UNDERLINE_ERROR, NULL); + g_signal_connect(buffer, "modified_changed", G_CALLBACK(on_gm_editor_view_modified_changed), view); + g_signal_connect(buffer, "changed", + G_CALLBACK(on_gm_editor_view_changed), view); + gtk_widget_show(source_view); return GTK_SOURCE_VIEW(source_view); } +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; + } + + 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); + + view->priv->set_style = TRUE; + gtk_widget_set_style(widget, 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, 6); + GtkWidget *align; + GtkWidget *lbl; + 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; + + lbl = gtk_label_new(NULL); + gtk_label_set_markup(GTK_LABEL(lbl), _("Errors")); + 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(message_area), view->priv->error_frame, TRUE, + TRUE, 0); + + lbl = gtk_label_new(NULL); + gtk_label_set_markup(GTK_LABEL(lbl), _("Warnings")); + 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(message_area), view->priv->warning_frame, TRUE, TRUE, 0); +} + static void gm_editor_view_init(GmEditorView *obj) { - GtkWidget *toolbar, *item; + GtkWidget *toolbar, *item, *expander, *tmp; obj->priv = GM_EDITOR_VIEW_GET_PRIVATE(obj); - gtk_box_set_spacing(GTK_BOX(obj), 6); + gtk_box_set_spacing(GTK_BOX(obj), 0); gtk_box_set_homogeneous(GTK_BOX(obj), FALSE); toolbar = gtk_toolbar_new(); @@ -202,8 +323,32 @@ gm_editor_view_init(GmEditorView *obj) { G_CALLBACK(on_gm_editor_view_close_clicked), obj); gtk_container_add(GTK_CONTAINER(toolbar), item); + item = GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_EXECUTE)); + g_signal_connect(item, "clicked", + G_CALLBACK(on_gm_editor_view_execute_clicked), obj); + gtk_container_add(GTK_CONTAINER(toolbar), item); + + item = GTK_WIDGET(gtk_separator_tool_item_new()); + gtk_container_add(GTK_CONTAINER(toolbar), item); + + item = GTK_WIDGET(gtk_tool_item_new()); + tmp = 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), obj); + + gtk_container_add(GTK_CONTAINER(tmp), expander); + gtk_container_add(GTK_CONTAINER(item), tmp); + gtk_container_add(GTK_CONTAINER(toolbar), item); + gtk_box_pack_start(GTK_BOX(obj), toolbar, FALSE, FALSE, 0); gtk_widget_show_all(toolbar); + + gtk_widget_hide(expander); + obj->priv->expander = GTK_EXPANDER(expander); + + gm_editor_view_create_message_area(obj); + gtk_box_pack_start(GTK_BOX(obj), obj->priv->message_area, FALSE, FALSE, 0); } GmEditorView * @@ -213,7 +358,7 @@ gm_editor_view_new(GmWorld *world, GmEditor *editor) { obj->priv->editor = editor; obj->priv->world = world; - obj->priv->source_view = gm_editor_create_source_view(obj); + obj->priv->source_view = gm_editor_view_create_source_view(obj); srl = gtk_scrolled_window_new(NULL, NULL); gtk_widget_show(srl); @@ -221,7 +366,7 @@ gm_editor_view_new(GmWorld *world, GmEditor *editor) { GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_container_add(GTK_CONTAINER(srl), GTK_WIDGET(obj->priv->source_view)); - gtk_box_pack_start(GTK_BOX(obj), srl, TRUE, TRUE, 0); + 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); @@ -255,6 +400,179 @@ gm_editor_view_save(GmEditorView *view) { gm_editor_save(view->priv->editor); } +gboolean +gm_editor_view_no_errors(GmEditorView *view) { + gtk_widget_hide(GTK_WIDGET(view->priv->expander)); + 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(_("Line %d: %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(_("Line %d:%d: %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(GTK_WIDGET(view->priv->expander)); + + 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; + } + } 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->message_area); + view->priv->hide_error_handler = g_timeout_add(3000, + (GSourceFunc)gm_editor_view_no_errors, view); + } + + gm_parser_result_free(result); + gm_g_list_free_simple(lines); + g_free(text); + + return ret; +} + /* Callbacks */ void @@ -296,9 +614,64 @@ on_gm_editor_view_close_clicked(GtkToolButton *button, gm_editor_close(view->priv->editor); } +void +on_gm_editor_view_execute_clicked(GtkToolButton *button, + 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"), NULL); + } else { + gtk_expander_set_expanded(view->priv->expander, TRUE); + gtk_widget_show_all(view->priv->message_area); + } +} + + 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)); } + +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(500, + (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->message_area); + } else { + if (!view->priv->expanding) { + view->priv->was_expanded = FALSE; + } + + gtk_widget_hide(view->priv->message_area); + } +}