1 /* GTK - The GIMP Toolkit
2 * Copyright © 2012 Carlos Garnacho <carlosg@gnome.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 #include "gtkprivatetypebuiltins.h"
20 #include "gtktexthandleprivate.h"
21 #include "gtkmarshalers.h"
22 #include "gtkprivate.h"
27 typedef struct _GtkTextHandlePrivate GtkTextHandlePrivate;
28 typedef struct _HandleWindow HandleWindow;
45 GdkRectangle pointing_to;
51 struct _GtkTextHandlePrivate
53 HandleWindow windows[2];
55 GdkWindow *relative_to;
56 GtkStyleContext *style_context;
58 gulong draw_signal_id;
59 gulong event_signal_id;
60 gulong style_updated_id;
61 gulong composited_changed_id;
66 G_DEFINE_TYPE (GtkTextHandle, _gtk_text_handle, G_TYPE_OBJECT)
68 static guint signals[LAST_SIGNAL] = { 0 };
71 _gtk_text_handle_get_size (GtkTextHandle *handle,
75 GtkTextHandlePrivate *priv;
80 gtk_widget_style_get (priv->parent,
81 "text-handle-width", &w,
82 "text-handle-height", &h,
92 _gtk_text_handle_draw (GtkTextHandle *handle,
94 GtkTextHandlePosition pos)
96 GtkTextHandlePrivate *priv;
102 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
103 cairo_set_source_rgba (cr, 0, 0, 0, 0);
106 gtk_style_context_save (priv->style_context);
107 gtk_style_context_add_class (priv->style_context,
108 GTK_STYLE_CLASS_CURSOR_HANDLE);
110 if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_END)
112 gtk_style_context_add_class (priv->style_context,
113 GTK_STYLE_CLASS_BOTTOM);
115 if (priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR)
116 gtk_style_context_add_class (priv->style_context,
117 GTK_STYLE_CLASS_INSERTION_CURSOR);
120 gtk_style_context_add_class (priv->style_context,
121 GTK_STYLE_CLASS_TOP);
123 _gtk_text_handle_get_size (handle, &width, &height);
124 gtk_render_background (priv->style_context, cr, 0, 0, width, height);
126 gtk_style_context_restore (priv->style_context);
131 _gtk_text_handle_update_shape (GtkTextHandle *handle,
133 GtkTextHandlePosition pos)
135 GtkTextHandlePrivate *priv;
136 cairo_surface_t *surface;
137 cairo_region_t *region;
143 gdk_window_create_similar_surface (window,
144 CAIRO_CONTENT_COLOR_ALPHA,
145 gdk_window_get_width (window),
146 gdk_window_get_height (window));
148 cr = cairo_create (surface);
149 _gtk_text_handle_draw (handle, cr, pos);
152 region = gdk_cairo_region_create_from_surface (surface);
154 if (gtk_widget_is_composited (priv->parent))
155 gdk_window_shape_combine_region (window, NULL, 0, 0);
157 gdk_window_shape_combine_region (window, region, 0, 0);
159 gdk_window_input_shape_combine_region (window, region, 0, 0);
161 cairo_surface_destroy (surface);
162 cairo_region_destroy (region);
166 _gtk_text_handle_create_window (GtkTextHandle *handle,
167 GtkTextHandlePosition pos)
169 GtkTextHandlePrivate *priv;
170 GdkRGBA bg = { 0, 0, 0, 0 };
171 GdkWindowAttr attributes;
180 _gtk_text_handle_get_size (handle, &attributes.width, &attributes.height);
181 attributes.window_type = GDK_WINDOW_TEMP;
182 attributes.wclass = GDK_INPUT_OUTPUT;
183 attributes.event_mask = (GDK_EXPOSURE_MASK |
184 GDK_BUTTON_PRESS_MASK |
185 GDK_BUTTON_RELEASE_MASK |
186 GDK_BUTTON1_MOTION_MASK);
188 mask = GDK_WA_X | GDK_WA_Y;
190 visual = gdk_screen_get_rgba_visual (gtk_widget_get_screen (priv->parent));
194 attributes.visual = visual;
195 mask |= GDK_WA_VISUAL;
198 window = gdk_window_new (gtk_widget_get_root_window (priv->parent),
200 gtk_widget_register_window (priv->parent, window);
201 gdk_window_set_background_rgba (window, &bg);
203 _gtk_text_handle_update_shape (handle, window, pos);
209 gtk_text_handle_widget_draw (GtkWidget *widget,
211 GtkTextHandle *handle)
213 GtkTextHandlePrivate *priv;
214 GtkTextHandlePosition pos;
215 HandleWindow *handle_window;
222 if (gtk_cairo_should_draw_window (cr, priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window))
223 pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START;
224 else if (gtk_cairo_should_draw_window (cr, priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window))
225 pos = GTK_TEXT_HANDLE_POSITION_SELECTION_END;
229 handle_window = &priv->windows[pos];
230 if (gdk_window_is_visible (handle_window->window))
231 _gtk_text_handle_draw (handle, cr, pos);
237 gtk_text_handle_widget_event (GtkWidget *widget,
239 GtkTextHandle *handle)
241 GtkTextHandlePrivate *priv;
242 GtkTextHandlePosition pos;
246 if (event->any.window == priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window)
247 pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START;
248 else if (event->any.window == priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window)
249 pos = GTK_TEXT_HANDLE_POSITION_SELECTION_END;
253 if (event->type == GDK_BUTTON_PRESS)
255 priv->windows[pos].dx = event->button.x;
256 priv->windows[pos].dy = event->button.y;
257 priv->windows[pos].dragged = TRUE;
259 else if (event->type == GDK_BUTTON_RELEASE)
261 g_signal_emit (handle, signals[DRAG_FINISHED], 0, pos);
262 priv->windows[pos].dx = priv->windows[pos].dy = 0;
263 priv->windows[pos].dragged = FALSE;
265 else if (event->type == GDK_MOTION_NOTIFY && priv->windows[pos].dragged)
267 gint x, y, width, height;
269 _gtk_text_handle_get_size (handle, &width, &height);
270 gdk_window_get_origin (priv->relative_to, &x, &y);
272 x = event->motion.x_root - priv->windows[pos].dx + (width / 2) - x;
273 y = event->motion.y_root - priv->windows[pos].dy - y;
275 if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_START)
278 g_signal_emit (handle, signals[HANDLE_DRAGGED], 0, pos, x, y);
285 _gtk_text_handle_update_window (GtkTextHandle *handle,
286 GtkTextHandlePosition pos)
288 GtkTextHandlePrivate *priv;
289 HandleWindow *handle_window;
294 handle_window = &priv->windows[pos];
296 if (!handle_window->window)
299 /* Get current state and destroy */
300 visible = gdk_window_is_visible (handle_window->window);
306 _gtk_text_handle_get_size (handle, &width, NULL);
307 gdk_window_get_root_coords (handle_window->window,
308 width / 2, 0, &x, &y);
311 gtk_widget_unregister_window (priv->parent, handle_window->window);
312 gdk_window_destroy (handle_window->window);
314 /* Create new window and apply old state */
315 handle_window->window = _gtk_text_handle_create_window (handle, pos);
319 gdk_window_show (handle_window->window);
320 _gtk_text_handle_set_position (handle, pos,
321 &handle_window->pointing_to);
326 _gtk_text_handle_update_windows (GtkTextHandle *handle)
328 GtkTextHandlePrivate *priv = handle->priv;
330 gtk_style_context_invalidate (priv->style_context);
331 _gtk_text_handle_update_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START);
332 _gtk_text_handle_update_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END);
336 gtk_text_handle_constructed (GObject *object)
338 GtkTextHandlePrivate *priv;
340 priv = GTK_TEXT_HANDLE (object)->priv;
341 g_assert (priv->parent != NULL);
343 priv->draw_signal_id =
344 g_signal_connect (priv->parent, "draw",
345 G_CALLBACK (gtk_text_handle_widget_draw),
347 priv->event_signal_id =
348 g_signal_connect (priv->parent, "event",
349 G_CALLBACK (gtk_text_handle_widget_event),
351 priv->composited_changed_id =
352 g_signal_connect_swapped (priv->parent, "composited-changed",
353 G_CALLBACK (_gtk_text_handle_update_windows),
355 priv->style_updated_id =
356 g_signal_connect_swapped (priv->parent, "style-updated",
357 G_CALLBACK (_gtk_text_handle_update_windows),
362 gtk_text_handle_finalize (GObject *object)
364 GtkTextHandlePrivate *priv;
366 priv = GTK_TEXT_HANDLE (object)->priv;
368 if (priv->relative_to)
369 g_object_unref (priv->relative_to);
371 if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window)
373 gtk_widget_unregister_window (priv->parent,
374 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
375 gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
378 if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window)
380 gtk_widget_unregister_window (priv->parent,
381 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
382 gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
385 if (g_signal_handler_is_connected (priv->parent, priv->draw_signal_id))
386 g_signal_handler_disconnect (priv->parent, priv->draw_signal_id);
388 if (g_signal_handler_is_connected (priv->parent, priv->event_signal_id))
389 g_signal_handler_disconnect (priv->parent, priv->event_signal_id);
391 if (g_signal_handler_is_connected (priv->parent, priv->composited_changed_id))
392 g_signal_handler_disconnect (priv->parent, priv->composited_changed_id);
394 if (g_signal_handler_is_connected (priv->parent, priv->style_updated_id))
395 g_signal_handler_disconnect (priv->parent, priv->style_updated_id);
397 g_object_unref (priv->style_context);
399 G_OBJECT_CLASS (_gtk_text_handle_parent_class)->finalize (object);
403 gtk_text_handle_set_property (GObject *object,
408 GtkTextHandlePrivate *priv;
409 GtkTextHandle *handle;
411 handle = GTK_TEXT_HANDLE (object);
417 priv->parent = g_value_get_object (value);
419 case PROP_RELATIVE_TO:
420 _gtk_text_handle_set_relative_to (handle,
421 g_value_get_object (value));
424 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
429 gtk_text_handle_get_property (GObject *object,
434 GtkTextHandlePrivate *priv;
436 priv = GTK_TEXT_HANDLE (object)->priv;
441 g_value_set_object (value, priv->parent);
443 case PROP_RELATIVE_TO:
444 g_value_set_object (value, priv->relative_to);
447 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
452 _gtk_text_handle_class_init (GtkTextHandleClass *klass)
454 GObjectClass *object_class = G_OBJECT_CLASS (klass);
456 object_class->constructed = gtk_text_handle_constructed;
457 object_class->finalize = gtk_text_handle_finalize;
458 object_class->set_property = gtk_text_handle_set_property;
459 object_class->get_property = gtk_text_handle_get_property;
461 signals[HANDLE_DRAGGED] =
462 g_signal_new (I_("handle-dragged"),
463 G_OBJECT_CLASS_TYPE (object_class),
465 G_STRUCT_OFFSET (GtkTextHandleClass, handle_dragged),
467 _gtk_marshal_VOID__ENUM_INT_INT,
469 GTK_TYPE_TEXT_HANDLE_POSITION,
470 G_TYPE_INT, G_TYPE_INT);
471 signals[DRAG_FINISHED] =
472 g_signal_new (I_("drag-finished"),
473 G_OBJECT_CLASS_TYPE (object_class),
474 G_SIGNAL_RUN_LAST, 0,
476 g_cclosure_marshal_VOID__ENUM,
478 GTK_TYPE_TEXT_HANDLE_POSITION);
480 g_object_class_install_property (object_class,
482 g_param_spec_object ("parent",
486 GTK_PARAM_READWRITE |
487 G_PARAM_CONSTRUCT_ONLY));
488 g_object_class_install_property (object_class,
490 g_param_spec_object ("relative-to",
492 P_("Window the coordinates are based upon"),
494 GTK_PARAM_READWRITE));
496 g_type_class_add_private (object_class, sizeof (GtkTextHandlePrivate));
500 _gtk_text_handle_init (GtkTextHandle *handle)
502 GtkTextHandlePrivate *priv;
505 handle->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (handle,
506 GTK_TYPE_TEXT_HANDLE,
507 GtkTextHandlePrivate);
509 path = gtk_widget_path_new ();
510 gtk_widget_path_append_type (path, GTK_TYPE_TEXT_HANDLE);
512 priv->style_context = gtk_style_context_new ();
513 gtk_style_context_set_path (priv->style_context, path);
514 gtk_widget_path_free (path);
518 _gtk_text_handle_new (GtkWidget *parent)
520 return g_object_new (GTK_TYPE_TEXT_HANDLE,
526 _gtk_text_handle_set_relative_to (GtkTextHandle *handle,
529 GtkTextHandlePrivate *priv;
531 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
532 g_return_if_fail (!window || GDK_IS_WINDOW (window));
536 if (priv->relative_to)
538 gtk_widget_unregister_window (priv->parent,
539 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
540 gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
541 gtk_widget_unregister_window (priv->parent,
542 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
543 gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
544 g_object_unref (priv->relative_to);
549 priv->relative_to = g_object_ref (window);
550 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window =
551 _gtk_text_handle_create_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START);
552 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window =
553 _gtk_text_handle_create_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END);
554 priv->realized = TRUE;
558 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window = NULL;
559 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window = NULL;
560 priv->relative_to = NULL;
561 priv->realized = FALSE;
564 g_object_notify (G_OBJECT (handle), "relative-to");
568 _gtk_text_handle_set_mode (GtkTextHandle *handle,
569 GtkTextHandleMode mode)
571 GtkTextHandlePrivate *priv;
573 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
577 if (priv->mode == mode)
582 case GTK_TEXT_HANDLE_MODE_CURSOR:
583 /* Only display one handle */
584 gdk_window_show (priv->windows[GTK_TEXT_HANDLE_POSITION_CURSOR].window);
585 gdk_window_hide (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
587 case GTK_TEXT_HANDLE_MODE_SELECTION:
588 /* Display both handles */
589 gdk_window_show (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
590 gdk_window_show (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
592 case GTK_TEXT_HANDLE_MODE_NONE:
594 gdk_window_hide (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
595 gdk_window_hide (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
601 _gtk_text_handle_update_shape (handle,
602 priv->windows[GTK_TEXT_HANDLE_POSITION_CURSOR].window,
603 GTK_TEXT_HANDLE_POSITION_CURSOR);
607 _gtk_text_handle_get_mode (GtkTextHandle *handle)
609 GtkTextHandlePrivate *priv;
611 g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), GTK_TEXT_HANDLE_MODE_NONE);
618 _gtk_text_handle_set_position (GtkTextHandle *handle,
619 GtkTextHandlePosition pos,
622 GtkTextHandlePrivate *priv;
623 gint x, y, width, height;
624 HandleWindow *handle_window;
626 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
629 pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
630 GTK_TEXT_HANDLE_POSITION_SELECTION_START);
635 if (priv->mode == GTK_TEXT_HANDLE_MODE_NONE ||
636 (priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR &&
637 pos != GTK_TEXT_HANDLE_POSITION_CURSOR))
640 gdk_window_get_root_coords (priv->relative_to,
643 _gtk_text_handle_get_size (handle, &width, &height);
644 handle_window = &priv->windows[pos];
646 if (pos == GTK_TEXT_HANDLE_POSITION_CURSOR)
653 gdk_window_move (handle_window->window, x, y);
654 handle_window->pointing_to = *rect;
658 _gtk_text_handle_set_visible (GtkTextHandle *handle,
659 GtkTextHandlePosition pos,
662 GtkTextHandlePrivate *priv;
665 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
668 pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
669 GTK_TEXT_HANDLE_POSITION_SELECTION_START);
674 window = priv->windows[pos].window;
680 gdk_window_hide (window);
683 if (priv->mode == GTK_TEXT_HANDLE_MODE_NONE ||
684 (priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR &&
685 pos != GTK_TEXT_HANDLE_POSITION_CURSOR))
688 if (!gdk_window_is_visible (window))
689 gdk_window_show (window);
694 _gtk_text_handle_get_is_dragged (GtkTextHandle *handle,
695 GtkTextHandlePosition pos)
697 GtkTextHandlePrivate *priv;
699 g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), FALSE);
702 pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
703 GTK_TEXT_HANDLE_POSITION_SELECTION_START);
705 return priv->windows[pos].dragged;