#ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #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), _("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(view->priv->error_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(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(_("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(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