/* * mooui/mootext.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. */ #include "mooui/mootext.h" typedef struct _MooTextData MooTextData; struct _MooTextData { guint drag_scroll_timeout; GdkEventType drag_button; MooTextDragType drag_type; int drag_start_x; int drag_start_y; guint drag_moved : 1; guint double_click_selects_brackets : 1; guint double_click_selects_inside : 1; }; static const GQuark *get_quark (void) G_GNUC_CONST; static MooTextData *moo_text_data_new (void); static MooTextData *moo_text_data_clear (MooTextData *data); static void moo_text_data_free (MooTextData *data); static MooTextData *moo_text_get_data (MooText *obj); #define MOO_TEXT_DATA_QUARK (get_quark()[0]) #define GET_DATA(obj) (moo_text_get_data (obj)) typedef gboolean (*ExtendSelectionFunc) (MooText *obj, MooTextSelectionType type, GtkTextIter *insert, GtkTextIter *selection_bound); typedef void (*StartSelectionDndFunc) (MooText *obj, const GtkTextIter *iter, GdkEventMotion *event); static MooTextData *moo_text_data_new (void) { return moo_text_data_clear (g_new0 (MooTextData, 1)); } static MooTextData *moo_text_data_clear (MooTextData *data) { data->drag_moved = FALSE; data->drag_type = MOO_TEXT_DRAG_NONE; data->drag_start_x = -1; data->drag_start_y = -1; data->drag_button = GDK_BUTTON_RELEASE; return data; } static void moo_text_data_free (MooTextData *data) { g_free (data); } /**************************************************************************/ /* mouse functionality */ #define get_impl(func) MOO_TEXT_GET_IFACE(obj)->func static gboolean can_drag_selection (MooText *obj) { return MOO_TEXT_GET_IFACE(obj)->start_selection_dnd != NULL; } static void start_drag_scroll (MooText *obj); static void stop_drag_scroll (MooText *obj); static gboolean drag_scroll_timeout_func (MooText *obj); static const int SCROLL_TIMEOUT = 100; gboolean moo_text_button_press_event (GtkWidget *widget, GdkEventButton *event) { MooText *obj; GtkTextIter iter; MooTextData *data; int x, y; obj = MOO_TEXT (widget); data = moo_text_get_data (obj); if (!GTK_WIDGET_HAS_FOCUS (widget)) gtk_widget_grab_focus (widget); get_impl(window_to_buffer_coords) (obj, event->x, event->y, &x, &y); get_impl(get_iter_at_location) (obj, &iter, x, y); if (event->type == GDK_BUTTON_PRESS) { if (event->button == 1) { GtkTextIter sel_start, sel_end; data->drag_button = GDK_BUTTON_PRESS; data->drag_start_x = x; data->drag_start_y = y; /* if clicked in selected, start drag */ if (get_impl(get_selection_bounds) (obj, &sel_start, &sel_end)) { get_impl(iter_order) (&sel_start, &sel_end); if (get_impl(iter_in_range) (&iter, &sel_start, &sel_end) && can_drag_selection (obj)) { /* clicked inside of selection, * set up drag and return */ data->drag_type = MOO_TEXT_DRAG_DRAG; return TRUE; } } /* otherwise, clear selection, and position cursor at clicked point */ if (event->state & GDK_SHIFT_MASK) { get_impl(place_selection_end) (obj, &iter); } else { get_impl(select_range) (obj, &iter, &iter); } data->drag_type = MOO_TEXT_DRAG_SELECT; } else if (event->button == 2) { get_impl(middle_button_click) (obj, event); } else if (event->button == 3) { get_impl(right_button_click) (obj, event); } else { g_warning ("got button %d in button_press callback", event->button); } } else if (event->type == GDK_2BUTTON_PRESS && event->button == 1) { GtkTextIter bound; if (get_impl(get_selection_bounds) (obj, NULL, NULL)) { /* it may happen sometimes, if you click fast enough */ get_impl(select_range) (obj, &iter, &iter); } data->drag_button = GDK_2BUTTON_PRESS; data->drag_start_x = x; data->drag_start_y = y; data->drag_type = MOO_TEXT_DRAG_SELECT; bound = iter; if (get_impl(extend_selection) (obj, MOO_TEXT_SELECT_WORDS, &iter, &bound)) get_impl(select_range) (obj, &bound, &iter); } else if (event->type == GDK_3BUTTON_PRESS && event->button == 1) { GtkTextIter bound; data->drag_button = GDK_3BUTTON_PRESS; data->drag_start_x = x; data->drag_start_y = y; data->drag_type = MOO_TEXT_DRAG_SELECT; bound = iter; if (get_impl(extend_selection) (obj, MOO_TEXT_SELECT_LINES, &iter, &bound)) get_impl(select_range) (obj, &bound, &iter); } return TRUE; } gboolean moo_text_button_release_event (GtkWidget *widget, G_GNUC_UNUSED GdkEventButton *event) { MooText *obj = MOO_TEXT (widget); MooTextData *data = moo_text_get_data (obj); GtkTextIter iter; switch (data->drag_type) { case MOO_TEXT_DRAG_NONE: /* it may happen after right-click, or clicking outside * of widget or something like that * everything has been taken care of, so do nothing */ break; case MOO_TEXT_DRAG_SELECT: /* everything should be done already in button_press and * motion_notify handlers */ stop_drag_scroll (obj); break; case MOO_TEXT_DRAG_DRAG: /* if we were really dragging, drop it * otherwise, it was just a single click in selected text */ g_assert (!data->drag_moved); /* parent should handle drag */ /* TODO ??? */ get_impl(get_iter_at_location) (obj, &iter, data->drag_start_x, data->drag_start_y); get_impl(select_range) (obj, &iter, &iter); break; default: g_assert_not_reached (); } moo_text_data_clear (data); return TRUE; } #define outside(x,y,rect) \ ((x) < (rect).x || \ (y) < (rect).y || \ (x) >= (rect).x + (rect).width || \ (y) >= (rect).y + (rect).height) gboolean moo_text_motion_event (GtkWidget *widget, GdkEventMotion *event) { MooText *obj = MOO_TEXT (widget); MooTextData *data = moo_text_get_data (obj); int x, y, event_x, event_y; GtkTextIter iter; if (!data->drag_type) return FALSE; if (event->is_hint) { gdk_window_get_pointer (event->window, &event_x, &event_y, NULL); } else { event_x = (int)event->x; event_y = (int)event->y; } get_impl(window_to_buffer_coords) (obj, event_x, event_y, &x, &y); get_impl(get_iter_at_location) (obj, &iter, x, y); if (data->drag_type == MOO_TEXT_DRAG_SELECT) { GdkRectangle rect; GtkTextIter start; MooTextSelectionType t; data->drag_moved = TRUE; get_impl(get_visible_rect) (obj, &rect); if (outside (x, y, rect)) { start_drag_scroll (obj); return TRUE; } else { stop_drag_scroll (obj); } get_impl(get_iter_at_location) (obj, &start, data->drag_start_x, data->drag_start_y); switch (data->drag_button) { case GDK_BUTTON_PRESS: t = MOO_TEXT_SELECT_CHARS; break; case GDK_2BUTTON_PRESS: t = MOO_TEXT_SELECT_WORDS; break; default: t = MOO_TEXT_SELECT_LINES; } if (get_impl(extend_selection) (obj, t, &iter, &start)) get_impl(select_range) (obj, &start, &iter); else get_impl(select_range) (obj, &iter, &iter); } else { /* this piece is from gtktextview.c */ int x, y; gdk_window_get_pointer (get_impl(get_window) (obj), &x, &y, NULL); if (gtk_drag_check_threshold (widget, data->drag_start_x, data->drag_start_y, x, y)) { GtkTextIter iter; int buffer_x, buffer_y; get_impl(window_to_buffer_coords) (obj, data->drag_start_x, data->drag_start_y, &buffer_x, &buffer_y); get_impl(get_iter_at_location) (obj, &iter, buffer_x, buffer_y); data->drag_type = MOO_TEXT_DRAG_NONE; get_impl(start_selection_dnd) (obj, &iter, event); } } return TRUE; } static void start_drag_scroll (MooText *obj) { MooTextData *data = moo_text_get_data (obj); if (!data->drag_scroll_timeout) data->drag_scroll_timeout = g_timeout_add (SCROLL_TIMEOUT, (GSourceFunc)drag_scroll_timeout_func, obj); drag_scroll_timeout_func (obj); } static void stop_drag_scroll (MooText *obj) { MooTextData *data = moo_text_get_data (obj); if (data->drag_scroll_timeout) g_source_remove (data->drag_scroll_timeout); data->drag_scroll_timeout = 0; } static gboolean drag_scroll_timeout_func (MooText *obj) { int x, y, px, py; GtkTextIter iter; GtkTextIter start; MooTextSelectionType t; MooTextData *data = moo_text_get_data (obj); g_assert (data->drag_type == MOO_TEXT_DRAG_SELECT); gdk_window_get_pointer (get_impl(get_window) (obj), &px, &py, NULL); get_impl(window_to_buffer_coords) (obj, px, py, &x, &y); get_impl(get_iter_at_location) (obj, &iter, x, y); get_impl(get_iter_at_location) (obj, &start, data->drag_start_x, data->drag_start_y); switch (data->drag_button) { case GDK_BUTTON_PRESS: t = MOO_TEXT_SELECT_CHARS; break; case GDK_2BUTTON_PRESS: t = MOO_TEXT_SELECT_WORDS; break; default: t = MOO_TEXT_SELECT_LINES; } if (get_impl(extend_selection) (obj, t, &iter, &start)) get_impl(select_range) (obj, &start, &iter); else get_impl(select_range) (obj, &iter, &iter); get_impl(scroll_selection_end_onscreen) (obj); return TRUE; } /**************************************************************************/ /* GInterface stuff */ static void moo_text_iface_init (gpointer g_iface); GType moo_text_get_type (void) { static GType type = 0; if (!type) { static const GTypeInfo info = { sizeof (MooTextIface), /* class_size */ moo_text_iface_init, /* base_init */ NULL, /* base_finalize */ NULL, /* class_init */ 0, 0, 0, 0, 0, 0 }; type = g_type_register_static (G_TYPE_INTERFACE, "MooText", &info, (GTypeFlags)0); } return type; } static void moo_text_iface_init (G_GNUC_UNUSED gpointer g_iface) { } static const GQuark *get_quark (void) { static GQuark q[1] = {0}; if (!q[0]) { q[0] = g_quark_from_static_string ("moo_text_data"); } return q; } static MooTextData *moo_text_get_data (MooText *obj) { MooTextData *data; g_return_val_if_fail (MOO_IS_TEXT (obj), NULL); data = g_object_get_qdata (G_OBJECT (obj), MOO_TEXT_DATA_QUARK); if (!data) { data = moo_text_data_new (); g_object_set_qdata_full (G_OBJECT (obj), MOO_TEXT_DATA_QUARK, data, (GDestroyNotify) moo_text_data_free); } return data; } /**************************************************************************/ /* GInterface stuff */