/* GTK - The GIMP Toolkit * Copyright © 2012 Carlos Garnacho <carlosg@gnome.org> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. */ #include "config.h" #include "gtkprivatetypebuiltins.h" #include "gtktexthandleprivate.h" #include "gtkmarshalers.h" #include "gtkprivate.h" #include "gtkintl.h" #include <gtk/gtk.h> typedef struct _GtkTextHandlePrivate GtkTextHandlePrivate; typedef struct _HandleWindow HandleWindow; enum { HANDLE_DRAGGED, DRAG_FINISHED, LAST_SIGNAL }; enum { PROP_0, PROP_PARENT, PROP_RELATIVE_TO }; struct _HandleWindow { GdkWindow *window; GdkRectangle pointing_to; gint dx; gint dy; guint dragged : 1; }; struct _GtkTextHandlePrivate { HandleWindow windows[2]; GtkWidget *parent; GdkWindow *relative_to; GtkStyleContext *style_context; gulong draw_signal_id; gulong event_signal_id; gulong style_updated_id; gulong composited_changed_id; guint realized : 1; guint mode : 2; }; G_DEFINE_TYPE (GtkTextHandle, _gtk_text_handle, G_TYPE_OBJECT) static guint signals[LAST_SIGNAL] = { 0 }; static void _gtk_text_handle_get_size (GtkTextHandle *handle, gint *width, gint *height) { GtkTextHandlePrivate *priv; gint w, h; priv = handle->priv; gtk_widget_style_get (priv->parent, "text-handle-width", &w, "text-handle-height", &h, NULL); if (width) *width = w; if (height) *height = h; } static void _gtk_text_handle_draw (GtkTextHandle *handle, cairo_t *cr, GtkTextHandlePosition pos) { GtkTextHandlePrivate *priv; gint width, height; priv = handle->priv; cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba (cr, 0, 0, 0, 0); cairo_paint (cr); gtk_style_context_save (priv->style_context); gtk_style_context_add_class (priv->style_context, GTK_STYLE_CLASS_CURSOR_HANDLE); if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_END) { gtk_style_context_add_class (priv->style_context, GTK_STYLE_CLASS_BOTTOM); if (priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR) gtk_style_context_add_class (priv->style_context, GTK_STYLE_CLASS_INSERTION_CURSOR); } else gtk_style_context_add_class (priv->style_context, GTK_STYLE_CLASS_TOP); _gtk_text_handle_get_size (handle, &width, &height); gtk_render_background (priv->style_context, cr, 0, 0, width, height); gtk_style_context_restore (priv->style_context); cairo_restore (cr); } static void _gtk_text_handle_update_shape (GtkTextHandle *handle, GdkWindow *window, GtkTextHandlePosition pos) { GtkTextHandlePrivate *priv; cairo_surface_t *surface; cairo_region_t *region; cairo_t *cr; priv = handle->priv; surface = gdk_window_create_similar_surface (window, CAIRO_CONTENT_COLOR_ALPHA, gdk_window_get_width (window), gdk_window_get_height (window)); cr = cairo_create (surface); _gtk_text_handle_draw (handle, cr, pos); cairo_destroy (cr); region = gdk_cairo_region_create_from_surface (surface); if (gtk_widget_is_composited (priv->parent)) gdk_window_shape_combine_region (window, NULL, 0, 0); else gdk_window_shape_combine_region (window, region, 0, 0); gdk_window_input_shape_combine_region (window, region, 0, 0); cairo_surface_destroy (surface); cairo_region_destroy (region); } static GdkWindow * _gtk_text_handle_create_window (GtkTextHandle *handle, GtkTextHandlePosition pos) { GtkTextHandlePrivate *priv; GdkRGBA bg = { 0, 0, 0, 0 }; GdkWindowAttr attributes; GdkWindow *window; GdkVisual *visual; gint mask; priv = handle->priv; attributes.x = 0; attributes.y = 0; _gtk_text_handle_get_size (handle, &attributes.width, &attributes.height); attributes.window_type = GDK_WINDOW_TEMP; attributes.wclass = GDK_INPUT_OUTPUT; attributes.event_mask = (GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON1_MOTION_MASK); mask = GDK_WA_X | GDK_WA_Y; visual = gdk_screen_get_rgba_visual (gtk_widget_get_screen (priv->parent)); if (visual) { attributes.visual = visual; mask |= GDK_WA_VISUAL; } window = gdk_window_new (gtk_widget_get_root_window (priv->parent), &attributes, mask); gdk_window_set_user_data (window, priv->parent); gdk_window_set_background_rgba (window, &bg); _gtk_text_handle_update_shape (handle, window, pos); return window; } static gboolean gtk_text_handle_widget_draw (GtkWidget *widget, cairo_t *cr, GtkTextHandle *handle) { GtkTextHandlePrivate *priv; GtkTextHandlePosition pos; priv = handle->priv; if (!priv->realized) return FALSE; if (gtk_cairo_should_draw_window (cr, priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window)) pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START; else if (gtk_cairo_should_draw_window (cr, priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window)) pos = GTK_TEXT_HANDLE_POSITION_SELECTION_END; else return FALSE; _gtk_text_handle_draw (handle, cr, pos); return TRUE; } static gboolean gtk_text_handle_widget_event (GtkWidget *widget, GdkEvent *event, GtkTextHandle *handle) { GtkTextHandlePrivate *priv; GtkTextHandlePosition pos; priv = handle->priv; if (event->any.window == priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window) pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START; else if (event->any.window == priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window) pos = GTK_TEXT_HANDLE_POSITION_SELECTION_END; else return FALSE; if (event->type == GDK_BUTTON_PRESS) { priv->windows[pos].dx = event->button.x; priv->windows[pos].dy = event->button.y; priv->windows[pos].dragged = TRUE; } else if (event->type == GDK_BUTTON_RELEASE) { g_signal_emit (handle, signals[DRAG_FINISHED], 0, pos); priv->windows[pos].dx = priv->windows[pos].dy = 0; priv->windows[pos].dragged = FALSE; } else if (event->type == GDK_MOTION_NOTIFY && priv->windows[pos].dragged) { gint x, y, width, height; _gtk_text_handle_get_size (handle, &width, &height); gdk_window_get_origin (priv->relative_to, &x, &y); x = event->motion.x_root - priv->windows[pos].dx + (width / 2) - x; y = event->motion.y_root - priv->windows[pos].dy - y; if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_START) y += height; g_signal_emit (handle, signals[HANDLE_DRAGGED], 0, pos, x, y); } return TRUE; } static void _gtk_text_handle_update_window (GtkTextHandle *handle, GtkTextHandlePosition pos) { GtkTextHandlePrivate *priv; HandleWindow *handle_window; gboolean visible; gint x, y; priv = handle->priv; handle_window = &priv->windows[pos]; if (!handle_window->window) return; /* Get current state and destroy */ visible = gdk_window_is_visible (handle_window->window); if (visible) { gint width; _gtk_text_handle_get_size (handle, &width, NULL); gdk_window_get_root_coords (handle_window->window, width / 2, 0, &x, &y); } gdk_window_destroy (handle_window->window); /* Create new window and apply old state */ handle_window->window = _gtk_text_handle_create_window (handle, pos); if (visible) { gdk_window_show (handle_window->window); _gtk_text_handle_set_position (handle, pos, &handle_window->pointing_to); } } static void _gtk_text_handle_update_windows (GtkTextHandle *handle) { GtkTextHandlePrivate *priv = handle->priv; gtk_style_context_invalidate (priv->style_context); _gtk_text_handle_update_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START); _gtk_text_handle_update_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END); } static void gtk_text_handle_constructed (GObject *object) { GtkTextHandlePrivate *priv; priv = GTK_TEXT_HANDLE (object)->priv; g_assert (priv->parent != NULL); priv->draw_signal_id = g_signal_connect (priv->parent, "draw", G_CALLBACK (gtk_text_handle_widget_draw), object); priv->event_signal_id = g_signal_connect (priv->parent, "event", G_CALLBACK (gtk_text_handle_widget_event), object); priv->composited_changed_id = g_signal_connect_swapped (priv->parent, "composited-changed", G_CALLBACK (_gtk_text_handle_update_windows), object); priv->style_updated_id = g_signal_connect_swapped (priv->parent, "style-updated", G_CALLBACK (_gtk_text_handle_update_windows), object); } static void gtk_text_handle_finalize (GObject *object) { GtkTextHandlePrivate *priv; priv = GTK_TEXT_HANDLE (object)->priv; if (priv->relative_to) g_object_unref (priv->relative_to); if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window) gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window); if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window) gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window); if (g_signal_handler_is_connected (priv->parent, priv->draw_signal_id)) g_signal_handler_disconnect (priv->parent, priv->draw_signal_id); if (g_signal_handler_is_connected (priv->parent, priv->event_signal_id)) g_signal_handler_disconnect (priv->parent, priv->event_signal_id); if (g_signal_handler_is_connected (priv->parent, priv->composited_changed_id)) g_signal_handler_disconnect (priv->parent, priv->composited_changed_id); if (g_signal_handler_is_connected (priv->parent, priv->style_updated_id)) g_signal_handler_disconnect (priv->parent, priv->style_updated_id); g_object_unref (priv->style_context); G_OBJECT_CLASS (_gtk_text_handle_parent_class)->finalize (object); } static void gtk_text_handle_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkTextHandlePrivate *priv; GtkTextHandle *handle; handle = GTK_TEXT_HANDLE (object); priv = handle->priv; switch (prop_id) { case PROP_PARENT: priv->parent = g_value_get_object (value); break; case PROP_RELATIVE_TO: _gtk_text_handle_set_relative_to (handle, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void gtk_text_handle_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkTextHandlePrivate *priv; priv = GTK_TEXT_HANDLE (object)->priv; switch (prop_id) { case PROP_PARENT: g_value_set_object (value, priv->parent); break; case PROP_RELATIVE_TO: g_value_set_object (value, priv->relative_to); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void _gtk_text_handle_class_init (GtkTextHandleClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = gtk_text_handle_constructed; object_class->finalize = gtk_text_handle_finalize; object_class->set_property = gtk_text_handle_set_property; object_class->get_property = gtk_text_handle_get_property; signals[HANDLE_DRAGGED] = g_signal_new (I_("handle-dragged"), G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkTextHandleClass, handle_dragged), NULL, NULL, _gtk_marshal_VOID__ENUM_INT_INT, G_TYPE_NONE, 3, GTK_TYPE_TEXT_HANDLE_POSITION, G_TYPE_INT, G_TYPE_INT); signals[DRAG_FINISHED] = g_signal_new (I_("drag-finished"), G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__ENUM, G_TYPE_NONE, 1, GTK_TYPE_TEXT_HANDLE_POSITION); g_object_class_install_property (object_class, PROP_PARENT, g_param_spec_object ("parent", P_("Parent widget"), P_("Parent widget"), GTK_TYPE_WIDGET, GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_RELATIVE_TO, g_param_spec_object ("relative-to", P_("Window"), P_("Window the coordinates are based upon"), GDK_TYPE_WINDOW, GTK_PARAM_READWRITE)); g_type_class_add_private (object_class, sizeof (GtkTextHandlePrivate)); } static void _gtk_text_handle_init (GtkTextHandle *handle) { GtkTextHandlePrivate *priv; GtkWidgetPath *path; handle->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (handle, GTK_TYPE_TEXT_HANDLE, GtkTextHandlePrivate); path = gtk_widget_path_new (); gtk_widget_path_append_type (path, GTK_TYPE_TEXT_HANDLE); priv->style_context = gtk_style_context_new (); gtk_style_context_set_path (priv->style_context, path); gtk_widget_path_free (path); } GtkTextHandle * _gtk_text_handle_new (GtkWidget *parent) { return g_object_new (GTK_TYPE_TEXT_HANDLE, "parent", parent, NULL); } void _gtk_text_handle_set_relative_to (GtkTextHandle *handle, GdkWindow *window) { GtkTextHandlePrivate *priv; g_return_if_fail (GTK_IS_TEXT_HANDLE (handle)); g_return_if_fail (!window || GDK_IS_WINDOW (window)); priv = handle->priv; if (priv->relative_to) { gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window); gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window); g_object_unref (priv->relative_to); } if (window) { priv->relative_to = g_object_ref (window); priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window = _gtk_text_handle_create_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START); priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window = _gtk_text_handle_create_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END); priv->realized = TRUE; } else { priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window = NULL; priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window = NULL; priv->relative_to = NULL; priv->realized = FALSE; } g_object_notify (G_OBJECT (handle), "relative-to"); } void _gtk_text_handle_set_mode (GtkTextHandle *handle, GtkTextHandleMode mode) { GtkTextHandlePrivate *priv; g_return_if_fail (GTK_IS_TEXT_HANDLE (handle)); priv = handle->priv; if (priv->mode == mode) return; switch (mode) { case GTK_TEXT_HANDLE_MODE_CURSOR: /* Only display one handle */ gdk_window_show (priv->windows[GTK_TEXT_HANDLE_POSITION_CURSOR].window); gdk_window_hide (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window); break; case GTK_TEXT_HANDLE_MODE_SELECTION: /* Display both handles */ gdk_window_show (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window); gdk_window_show (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window); break; case GTK_TEXT_HANDLE_MODE_NONE: default: gdk_window_hide (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window); gdk_window_hide (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window); break; } priv->mode = mode; _gtk_text_handle_update_shape (handle, priv->windows[GTK_TEXT_HANDLE_POSITION_CURSOR].window, GTK_TEXT_HANDLE_POSITION_CURSOR); } GtkTextHandleMode _gtk_text_handle_get_mode (GtkTextHandle *handle) { GtkTextHandlePrivate *priv; g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), GTK_TEXT_HANDLE_MODE_NONE); priv = handle->priv; return priv->mode; } void _gtk_text_handle_set_position (GtkTextHandle *handle, GtkTextHandlePosition pos, GdkRectangle *rect) { GtkTextHandlePrivate *priv; gint x, y, width, height; HandleWindow *handle_window; g_return_if_fail (GTK_IS_TEXT_HANDLE (handle)); priv = handle->priv; pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR, GTK_TEXT_HANDLE_POSITION_SELECTION_START); if (!priv->realized) return; if (priv->mode == GTK_TEXT_HANDLE_MODE_NONE || (priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR && pos != GTK_TEXT_HANDLE_POSITION_CURSOR)) return; gdk_window_get_root_coords (priv->relative_to, rect->x, rect->y, &x, &y); _gtk_text_handle_get_size (handle, &width, &height); handle_window = &priv->windows[pos]; if (pos == GTK_TEXT_HANDLE_POSITION_CURSOR) y += rect->height; else y -= height; x -= width / 2; gdk_window_move (handle_window->window, x, y); handle_window->pointing_to = *rect; } void _gtk_text_handle_set_visible (GtkTextHandle *handle, GtkTextHandlePosition pos, gboolean visible) { GtkTextHandlePrivate *priv; GdkWindow *window; g_return_if_fail (GTK_IS_TEXT_HANDLE (handle)); priv = handle->priv; pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR, GTK_TEXT_HANDLE_POSITION_SELECTION_START); if (!priv->realized) return; window = priv->windows[pos].window; if (!window) return; if (!visible) gdk_window_hide (window); else { if (priv->mode == GTK_TEXT_HANDLE_MODE_NONE || (priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR && pos != GTK_TEXT_HANDLE_POSITION_CURSOR)) return; if (!gdk_window_is_visible (window)) gdk_window_show (window); } } gboolean _gtk_text_handle_get_is_dragged (GtkTextHandle *handle, GtkTextHandlePosition pos) { GtkTextHandlePrivate *priv; g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), FALSE); priv = handle->priv; pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR, GTK_TEXT_HANDLE_POSITION_SELECTION_START); return priv->windows[pos].dragged; }