/* * mooterm/mooterm.c * * Copyright (C) 2004-2005 by Yevgen Muntyan * * 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 2 of the License, or * (at your option) any later version. * * See COPYING file that comes with this distribution. */ #define MOOTERM_COMPILATION #include "mooterm/mooterm-private.h" #include "mooutils/moocompat.h" #include "mooutils/moomarshals.h" // static GObject *moo_term_constructor (GType type, // guint n_construct_properties, // GObjectConstructParam *construct_properties); static void moo_term_finalize (GObject *object); static void moo_term_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void moo_term_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void moo_term_realize (GtkWidget *widget); static void moo_term_size_allocate (GtkWidget *widget, GtkAllocation *allocation); // static gboolean moo_term_popup_menu (GtkWidget *widget); // static gboolean moo_term_scroll (GtkWidget *widget, // GdkEventScroll *event); // static gboolean moo_term_motion_notify (GtkWidget *widget, // GdkEventMotion *event); static void moo_term_set_scroll_adjustments (GtkWidget *widget, GtkAdjustment *hadj, GtkAdjustment *vadj); static void update_adjustment (MooTerm *term); static void update_adjustment_value (MooTerm *term); static void adjustment_value_changed (MooTerm *term); static void queue_adjustment_changed (MooTerm *term); static void queue_adjustment_value_changed (MooTerm *term); static gboolean emit_adjustment_changed (MooTerm *term); static gboolean emit_adjustment_value_changed (MooTerm *term); static void scroll_abs (MooTerm *term, gulong line, gboolean update_adj); static void scroll_to_bottom (MooTerm *term, gboolean update_adj); static void scrollback_changed (MooTerm *term); static void width_changed (MooTerm *term); static void height_changed (MooTerm *term); enum { SET_SCROLL_ADJUSTMENTS, LAST_SIGNAL }; enum { PROP_0 = 0, PROP_BUFFER, LAST_PROP }; /* MOO_TYPE_TERM */ G_DEFINE_TYPE (MooTerm, moo_term, GTK_TYPE_WIDGET) static guint signals[LAST_SIGNAL]; static void moo_term_class_init (MooTermClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); // gobject_class->constructor = moo_term_constructor; gobject_class->set_property = moo_term_set_property; gobject_class->get_property = moo_term_get_property; gobject_class->finalize = moo_term_finalize; widget_class->realize = moo_term_realize; widget_class->size_allocate = moo_term_size_allocate; widget_class->expose_event = moo_term_expose_event; // widget_class->button_press_event = moo_term_button_press; // widget_class->button_release_event = moo_term_button_release; widget_class->key_press_event = moo_term_key_press; widget_class->key_release_event = moo_term_key_release; // widget_class->popup_menu = moo_term_popup_menu; // widget_class->scroll_event = moo_term_scroll; // widget_class->motion_notify_event = moo_term_motion_notify; klass->set_scroll_adjustments = moo_term_set_scroll_adjustments; signals[SET_SCROLL_ADJUSTMENTS] = g_signal_new ("set-scroll-adjustments", G_OBJECT_CLASS_TYPE (gobject_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (MooTermClass, set_scroll_adjustments), NULL, NULL, _moo_marshal_VOID__OBJECT_OBJECT, G_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT); widget_class->set_scroll_adjustments_signal = signals[SET_SCROLL_ADJUSTMENTS]; g_object_class_install_property (gobject_class, PROP_BUFFER, g_param_spec_object ("buffer", "buffer", "buffer", MOO_TYPE_TERM_BUFFER, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); } static void moo_term_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MooTerm *term = MOO_TERM (object); switch (prop_id) { case PROP_BUFFER: moo_term_set_buffer (term, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void moo_term_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MooTerm *term = MOO_TERM (object); switch (prop_id) { case PROP_BUFFER: g_value_set_object (value, moo_term_get_buffer (term)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void moo_term_init (MooTerm *term) { term->priv = g_new0 (MooTermPrivate, 1); term->priv->vt = moo_term_vt_new (); term->priv->selection = term_selection_new (); moo_term_set_buffer (term, NULL); } static void moo_term_finalize (GObject *object) { MooTerm *term = MOO_TERM (object); guint i, j; g_signal_handler_disconnect (term->priv->buffer, term->priv->scrollback_changed_id); g_signal_handler_disconnect (term->priv->buffer, term->priv->width_changed_id); g_signal_handler_disconnect (term->priv->buffer, term->priv->height_changed_id); g_signal_handler_disconnect (term->priv->buffer, term->priv->buf_content_changed_id); g_signal_handler_disconnect (term->priv->buffer, term->priv->cursor_moved_id); g_object_unref (term->priv->buffer); g_object_unref (term->priv->vt); term_selection_free (term->priv->selection); for (i = 0; i < CURSORS_NUM; ++i) if (term->priv->cursor[i]) gdk_cursor_unref (term->priv->cursor[i]); if (term->priv->im) g_object_unref (term->priv->im); if (term->priv->adjustment) g_object_unref (term->priv->adjustment); term_font_info_free (term->priv->font_info); term_pango_lines_free (term->priv->pango_lines); if (term->priv->dirty) gdk_region_destroy (term->priv->dirty); for (i = 0; i < MOO_TERM_COLOR_MAX; ++i) g_free (term->priv->color[i]); for (i = 0; i <= MOO_TERM_COLOR_MAX; ++i) for (j = 0; j <= MOO_TERM_COLOR_MAX; ++j) g_object_unref (term->priv->pair[i][j]); g_free (term->priv); G_OBJECT_CLASS (moo_term_parent_class)->finalize (object); } static void moo_term_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { int old_width, old_height; MooTerm *term = MOO_TERM (widget); old_width = widget->allocation.width; old_height = widget->allocation.height; widget->allocation = *allocation; if (GTK_WIDGET_REALIZED (widget)) { gdk_window_move_resize (widget->window, allocation->x, allocation->y, allocation->width, allocation->height); if (old_width != allocation->width || old_height != allocation->height) moo_term_size_changed (term); } } static void moo_term_realize (GtkWidget *widget) { MooTerm *term; GdkWindowAttr attributes; gint attributes_mask; GdkDisplay *display; GdkBitmap *empty_bitmap; GdkColor useless = {0, 0, 0, 0}; char invisible_cursor_bits[] = { 0x0 }; term = MOO_TERM (widget); empty_bitmap = gdk_bitmap_create_from_data (NULL, invisible_cursor_bits, 1, 1); term->priv->cursor[CURSOR_NONE] = gdk_cursor_new_from_pixmap (empty_bitmap, empty_bitmap, &useless, &useless, 0, 0); display = gtk_widget_get_display (widget); term->priv->cursor[CURSOR_TEXT] = gdk_cursor_new_for_display (display, GDK_XTERM); term->priv->cursor[CURSOR_POINTER] = NULL; attributes.window_type = GDK_WINDOW_CHILD; attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget); attributes.cursor = term->priv->cursor[CURSOR_TEXT]; attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_POINTER_MOTION_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK; attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP | GDK_WA_CURSOR; GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED | GTK_CAN_FOCUS | GTK_CAN_DEFAULT); widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask); gdk_window_set_user_data (widget->window, widget); widget->style = gtk_style_attach (widget->style, widget->window); // gdk_window_set_back_pixmap (widget->window, NULL, FALSE); // gtk_widget_set_double_buffered (widget, FALSE); moo_term_setup_palette (term); moo_term_init_font_stuff (term); moo_term_size_changed (term); gdk_window_set_background (widget->window, &(widget->style->white)); term->priv->im = gtk_im_multicontext_new (); gtk_im_context_set_client_window (term->priv->im, widget->window); gtk_im_context_set_use_preedit (term->priv->im, FALSE); g_signal_connect (term->priv->im, "commit", G_CALLBACK (moo_term_im_commit), term); gtk_im_context_focus_in (term->priv->im); } static void moo_term_set_scroll_adjustments (GtkWidget *widget, G_GNUC_UNUSED GtkAdjustment *hadj, GtkAdjustment *vadj) { moo_term_set_adjustment (MOO_TERM (widget), vadj); } void moo_term_set_adjustment (MooTerm *term, GtkAdjustment *vadj) { if (term->priv->adjustment == vadj) return; if (term->priv->adjustment) { g_signal_handlers_disconnect_by_func (term->priv->adjustment, (gpointer) adjustment_value_changed, term); g_object_unref (term->priv->adjustment); } term->priv->adjustment = vadj; if (term->priv->adjustment) { g_object_ref (term->priv->adjustment); gtk_object_sink (GTK_OBJECT (term->priv->adjustment)); update_adjustment (term); g_signal_connect_swapped (term->priv->adjustment, "value-changed", G_CALLBACK (adjustment_value_changed), term); } } static void scrollback_changed (MooTerm *term) { gulong scrollback = buf_screen_offset (term->priv->buffer); if (term->priv->scrolled && term->priv->_top_line > scrollback) scroll_to_bottom (term, TRUE); else update_adjustment (term); } static void width_changed (MooTerm *term) { term_selection_set_width (term, term_width (term)); term_selection_clear (term); term_pango_lines_invalidate_all (term); } static void height_changed (MooTerm *term) { if (term_top_line (term) > buf_screen_offset (term->priv->buffer)) scroll_to_bottom (term, TRUE); else update_adjustment (term); term_pango_lines_resize (term, buf_screen_height (term->priv->buffer)); term_selection_clear (term); moo_term_invalidate_all (term); } static void update_adjustment (MooTerm *term) { double upper, value, page_size; GtkAdjustment *adj = term->priv->adjustment; if (!adj) return; upper = buf_total_height (term->priv->buffer); value = term_top_line (term); page_size = term_height (term); if (adj->lower != 0 || adj->upper != upper || adj->value != value || adj->page_size != page_size || adj->page_increment != page_size || adj->step_increment != 1) { adj->lower = 0; adj->upper = upper; adj->value = value; adj->page_size = page_size; adj->page_increment = page_size; adj->step_increment = 1; queue_adjustment_changed (term); } } static void update_adjustment_value (MooTerm *term) { gulong value = term_top_line (term); if (!term->priv->adjustment) return; if ((gulong)term->priv->adjustment->value != value) { term->priv->adjustment->value = value; queue_adjustment_value_changed (term); } } static void adjustment_value_changed (MooTerm *term) { gulong val, real_val; g_assert (term->priv->adjustment != NULL); val = term->priv->adjustment->value; real_val = term_top_line (term); if (val == real_val) return; g_return_if_fail (val <= buf_screen_offset (term->priv->buffer)); scroll_abs (term, val, FALSE); } static void queue_adjustment_changed (MooTerm *term) { if (!term->priv->pending_adjustment_changed) { term->priv->pending_adjustment_changed = g_idle_add_full (ADJUSTMENT_PRIORITY, (GSourceFunc) emit_adjustment_changed, term, NULL); } } static void queue_adjustment_value_changed (MooTerm *term) { if (!term->priv->pending_adjustment_value_changed) { term->priv->pending_adjustment_value_changed = g_idle_add_full(ADJUSTMENT_VALUE_PRIORITY, (GSourceFunc) emit_adjustment_value_changed, term, NULL); } } static gboolean emit_adjustment_changed (MooTerm *term) { if (term->priv->pending_adjustment_changed) { term->priv->pending_adjustment_changed = 0; gtk_adjustment_changed (term->priv->adjustment); } return FALSE; } static gboolean emit_adjustment_value_changed (MooTerm *term) { if (term->priv->pending_adjustment_value_changed) { term->priv->pending_adjustment_value_changed = 0; gtk_adjustment_value_changed (term->priv->adjustment); } return FALSE; } static void scroll_abs (MooTerm *term, gulong line, gboolean update_adj) { if (term_top_line (term) == line) { if (update_adj) update_adjustment_value (term); return; } if (line >= buf_screen_offset (term->priv->buffer)) return scroll_to_bottom (term, update_adj); term->priv->_top_line = line; term->priv->scrolled = TRUE; term_pango_lines_invalidate_all (term); moo_term_invalidate_all (term); if (update_adj) update_adjustment_value (term); } static void scroll_to_bottom (MooTerm *term, gboolean update_adj) { if (!term->priv->scrolled) { if (update_adj) update_adjustment_value (term); return; } term->priv->scrolled = FALSE; term_pango_lines_invalidate_all (term); moo_term_invalidate_all (term); if (update_adj) update_adjustment_value (term); } void moo_term_set_buffer (MooTerm *term, MooTermBuffer *buffer) { g_return_if_fail (MOO_IS_TERM (term)); g_return_if_fail (!buffer || MOO_IS_TERM_BUFFER (buffer)); if (buffer && term->priv->buffer == buffer) return; if (term->priv->buffer) { g_signal_handler_disconnect (term->priv->buffer, term->priv->scrollback_changed_id); g_signal_handler_disconnect (term->priv->buffer, term->priv->width_changed_id); g_signal_handler_disconnect (term->priv->buffer, term->priv->height_changed_id); g_signal_handler_disconnect (term->priv->buffer, term->priv->buf_content_changed_id); g_signal_handler_disconnect (term->priv->buffer, term->priv->cursor_moved_id); g_object_unref (term->priv->buffer); } if (buffer) { term->priv->buffer = buffer; g_object_ref (term->priv->buffer); } else { term->priv->buffer = moo_term_buffer_new (0, 0); } moo_term_vt_set_buffer (term->priv->vt, term->priv->buffer); term->priv->scrollback_changed_id = g_signal_connect_swapped (term->priv->buffer, "notify::scrollback", G_CALLBACK (scrollback_changed), term); term->priv->width_changed_id = g_signal_connect_swapped (term->priv->buffer, "notify::screen-width", G_CALLBACK (width_changed), term); term->priv->height_changed_id = g_signal_connect_swapped (term->priv->buffer, "notify::screen-height", G_CALLBACK (height_changed), term); term->priv->buf_content_changed_id = g_signal_connect_swapped (term->priv->buffer, "changed", G_CALLBACK (moo_term_buf_content_changed), term); term->priv->cursor_moved_id = g_signal_connect_swapped (term->priv->buffer, "cursor-moved", G_CALLBACK (moo_term_cursor_moved), term); if (GTK_WIDGET_REALIZED (term)) { GtkWidget *widget = GTK_WIDGET (term); TermFontInfo *font_info = term->priv->font_info; gulong width = widget->allocation.width / font_info->width; gulong height = widget->allocation.height / font_info->height; term_pango_lines_invalidate_all (term); term->priv->scrolled = FALSE; moo_term_buffer_set_screen_size (term->priv->buffer, width, height); moo_term_vt_set_size (term->priv->vt, width, height); } } MooTermBuffer *moo_term_get_buffer (MooTerm *term) { g_return_val_if_fail (MOO_IS_TERM (term), NULL); return term->priv->buffer; } void moo_term_size_changed (MooTerm *term) { GtkWidget *widget = GTK_WIDGET (term); TermFontInfo *font_info = term->priv->font_info; gulong width, height; gulong old_width, old_height; width = widget->allocation.width / font_info->width; height = widget->allocation.height / font_info->height; old_width = term_width (term); old_height = term_height (term); if (width == old_width && height == old_height) return; moo_term_buffer_set_screen_size (term->priv->buffer, width, height); moo_term_vt_set_size (term->priv->vt, width, height); } gboolean moo_term_fork_command (MooTerm *term, const char *cmd, const char *working_dir, char **envp) { g_return_val_if_fail (MOO_IS_TERM (term), FALSE); return moo_term_vt_fork_command (term->priv->vt, cmd, working_dir, envp); } void moo_term_feed_child (MooTerm *term, const char *string, gssize len) { g_return_if_fail (MOO_IS_TERM (term)); moo_term_vt_write (term->priv->vt, string, len); } void moo_term_copy_clipboard (MooTerm *term) { g_warning ("%s: implement me", G_STRLOC); } void moo_term_paste_clipboard (MooTerm *term) { g_warning ("%s: implement me", G_STRLOC); } void moo_term_ctrl_c (MooTerm *term) { g_warning ("%s: implement me", G_STRLOC); } void moo_term_scroll_to_top (MooTerm *term) { g_warning ("%s: implement me", G_STRLOC); } void moo_term_scroll_to_bottom (MooTerm *term) { g_warning ("%s: implement me", G_STRLOC); } void moo_term_scroll_lines (MooTerm *term, int lines) { g_warning ("%s: implement me", G_STRLOC); } void moo_term_scroll_pages (MooTerm *term, int pages) { g_warning ("%s: implement me", G_STRLOC); }