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;
221 if (gtk_cairo_should_draw_window (cr, priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window))
222 pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START;
223 else if (gtk_cairo_should_draw_window (cr, priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window))
224 pos = GTK_TEXT_HANDLE_POSITION_SELECTION_END;
228 _gtk_text_handle_draw (handle, cr, pos);
233 gtk_text_handle_widget_event (GtkWidget *widget,
235 GtkTextHandle *handle)
237 GtkTextHandlePrivate *priv;
238 GtkTextHandlePosition pos;
242 if (event->any.window == priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window)
243 pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START;
244 else if (event->any.window == priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window)
245 pos = GTK_TEXT_HANDLE_POSITION_SELECTION_END;
249 if (event->type == GDK_BUTTON_PRESS)
251 priv->windows[pos].dx = event->button.x;
252 priv->windows[pos].dy = event->button.y;
253 priv->windows[pos].dragged = TRUE;
255 else if (event->type == GDK_BUTTON_RELEASE)
257 g_signal_emit (handle, signals[DRAG_FINISHED], 0, pos);
258 priv->windows[pos].dx = priv->windows[pos].dy = 0;
259 priv->windows[pos].dragged = FALSE;
261 else if (event->type == GDK_MOTION_NOTIFY && priv->windows[pos].dragged)
263 gint x, y, width, height;
265 _gtk_text_handle_get_size (handle, &width, &height);
266 gdk_window_get_origin (priv->relative_to, &x, &y);
268 x = event->motion.x_root - priv->windows[pos].dx + (width / 2) - x;
269 y = event->motion.y_root - priv->windows[pos].dy - y;
271 if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_START)
274 g_signal_emit (handle, signals[HANDLE_DRAGGED], 0, pos, x, y);
281 _gtk_text_handle_update_window (GtkTextHandle *handle,
282 GtkTextHandlePosition pos)
284 GtkTextHandlePrivate *priv;
285 HandleWindow *handle_window;
290 handle_window = &priv->windows[pos];
292 if (!handle_window->window)
295 /* Get current state and destroy */
296 visible = gdk_window_is_visible (handle_window->window);
302 _gtk_text_handle_get_size (handle, &width, NULL);
303 gdk_window_get_root_coords (handle_window->window,
304 width / 2, 0, &x, &y);
307 gtk_widget_unregister_window (priv->parent, handle_window->window);
308 gdk_window_destroy (handle_window->window);
310 /* Create new window and apply old state */
311 handle_window->window = _gtk_text_handle_create_window (handle, pos);
315 gdk_window_show (handle_window->window);
316 _gtk_text_handle_set_position (handle, pos,
317 &handle_window->pointing_to);
322 _gtk_text_handle_update_windows (GtkTextHandle *handle)
324 GtkTextHandlePrivate *priv = handle->priv;
326 gtk_style_context_invalidate (priv->style_context);
327 _gtk_text_handle_update_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START);
328 _gtk_text_handle_update_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END);
332 gtk_text_handle_constructed (GObject *object)
334 GtkTextHandlePrivate *priv;
336 priv = GTK_TEXT_HANDLE (object)->priv;
337 g_assert (priv->parent != NULL);
339 priv->draw_signal_id =
340 g_signal_connect (priv->parent, "draw",
341 G_CALLBACK (gtk_text_handle_widget_draw),
343 priv->event_signal_id =
344 g_signal_connect (priv->parent, "event",
345 G_CALLBACK (gtk_text_handle_widget_event),
347 priv->composited_changed_id =
348 g_signal_connect_swapped (priv->parent, "composited-changed",
349 G_CALLBACK (_gtk_text_handle_update_windows),
351 priv->style_updated_id =
352 g_signal_connect_swapped (priv->parent, "style-updated",
353 G_CALLBACK (_gtk_text_handle_update_windows),
358 gtk_text_handle_finalize (GObject *object)
360 GtkTextHandlePrivate *priv;
362 priv = GTK_TEXT_HANDLE (object)->priv;
364 if (priv->relative_to)
365 g_object_unref (priv->relative_to);
367 if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window)
368 gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
370 if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window)
371 gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
373 if (g_signal_handler_is_connected (priv->parent, priv->draw_signal_id))
374 g_signal_handler_disconnect (priv->parent, priv->draw_signal_id);
376 if (g_signal_handler_is_connected (priv->parent, priv->event_signal_id))
377 g_signal_handler_disconnect (priv->parent, priv->event_signal_id);
379 if (g_signal_handler_is_connected (priv->parent, priv->composited_changed_id))
380 g_signal_handler_disconnect (priv->parent, priv->composited_changed_id);
382 if (g_signal_handler_is_connected (priv->parent, priv->style_updated_id))
383 g_signal_handler_disconnect (priv->parent, priv->style_updated_id);
385 g_object_unref (priv->style_context);
387 G_OBJECT_CLASS (_gtk_text_handle_parent_class)->finalize (object);
391 gtk_text_handle_set_property (GObject *object,
396 GtkTextHandlePrivate *priv;
397 GtkTextHandle *handle;
399 handle = GTK_TEXT_HANDLE (object);
405 priv->parent = g_value_get_object (value);
407 case PROP_RELATIVE_TO:
408 _gtk_text_handle_set_relative_to (handle,
409 g_value_get_object (value));
412 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
417 gtk_text_handle_get_property (GObject *object,
422 GtkTextHandlePrivate *priv;
424 priv = GTK_TEXT_HANDLE (object)->priv;
429 g_value_set_object (value, priv->parent);
431 case PROP_RELATIVE_TO:
432 g_value_set_object (value, priv->relative_to);
435 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
440 _gtk_text_handle_class_init (GtkTextHandleClass *klass)
442 GObjectClass *object_class = G_OBJECT_CLASS (klass);
444 object_class->constructed = gtk_text_handle_constructed;
445 object_class->finalize = gtk_text_handle_finalize;
446 object_class->set_property = gtk_text_handle_set_property;
447 object_class->get_property = gtk_text_handle_get_property;
449 signals[HANDLE_DRAGGED] =
450 g_signal_new (I_("handle-dragged"),
451 G_OBJECT_CLASS_TYPE (object_class),
453 G_STRUCT_OFFSET (GtkTextHandleClass, handle_dragged),
455 _gtk_marshal_VOID__ENUM_INT_INT,
457 GTK_TYPE_TEXT_HANDLE_POSITION,
458 G_TYPE_INT, G_TYPE_INT);
459 signals[DRAG_FINISHED] =
460 g_signal_new (I_("drag-finished"),
461 G_OBJECT_CLASS_TYPE (object_class),
462 G_SIGNAL_RUN_LAST, 0,
464 g_cclosure_marshal_VOID__ENUM,
466 GTK_TYPE_TEXT_HANDLE_POSITION);
468 g_object_class_install_property (object_class,
470 g_param_spec_object ("parent",
474 GTK_PARAM_READWRITE |
475 G_PARAM_CONSTRUCT_ONLY));
476 g_object_class_install_property (object_class,
478 g_param_spec_object ("relative-to",
480 P_("Window the coordinates are based upon"),
482 GTK_PARAM_READWRITE));
484 g_type_class_add_private (object_class, sizeof (GtkTextHandlePrivate));
488 _gtk_text_handle_init (GtkTextHandle *handle)
490 GtkTextHandlePrivate *priv;
493 handle->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (handle,
494 GTK_TYPE_TEXT_HANDLE,
495 GtkTextHandlePrivate);
497 path = gtk_widget_path_new ();
498 gtk_widget_path_append_type (path, GTK_TYPE_TEXT_HANDLE);
500 priv->style_context = gtk_style_context_new ();
501 gtk_style_context_set_path (priv->style_context, path);
502 gtk_widget_path_free (path);
506 _gtk_text_handle_new (GtkWidget *parent)
508 return g_object_new (GTK_TYPE_TEXT_HANDLE,
514 _gtk_text_handle_set_relative_to (GtkTextHandle *handle,
517 GtkTextHandlePrivate *priv;
519 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
520 g_return_if_fail (!window || GDK_IS_WINDOW (window));
524 if (priv->relative_to)
526 gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
527 gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
528 g_object_unref (priv->relative_to);
533 priv->relative_to = g_object_ref (window);
534 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window =
535 _gtk_text_handle_create_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START);
536 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window =
537 _gtk_text_handle_create_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END);
538 priv->realized = TRUE;
542 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window = NULL;
543 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window = NULL;
544 priv->relative_to = NULL;
545 priv->realized = FALSE;
548 g_object_notify (G_OBJECT (handle), "relative-to");
552 _gtk_text_handle_set_mode (GtkTextHandle *handle,
553 GtkTextHandleMode mode)
555 GtkTextHandlePrivate *priv;
557 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
561 if (priv->mode == mode)
566 case GTK_TEXT_HANDLE_MODE_CURSOR:
567 /* Only display one handle */
568 gdk_window_show (priv->windows[GTK_TEXT_HANDLE_POSITION_CURSOR].window);
569 gdk_window_hide (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
571 case GTK_TEXT_HANDLE_MODE_SELECTION:
572 /* Display both handles */
573 gdk_window_show (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
574 gdk_window_show (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
576 case GTK_TEXT_HANDLE_MODE_NONE:
578 gdk_window_hide (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
579 gdk_window_hide (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
585 _gtk_text_handle_update_shape (handle,
586 priv->windows[GTK_TEXT_HANDLE_POSITION_CURSOR].window,
587 GTK_TEXT_HANDLE_POSITION_CURSOR);
591 _gtk_text_handle_get_mode (GtkTextHandle *handle)
593 GtkTextHandlePrivate *priv;
595 g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), GTK_TEXT_HANDLE_MODE_NONE);
602 _gtk_text_handle_set_position (GtkTextHandle *handle,
603 GtkTextHandlePosition pos,
606 GtkTextHandlePrivate *priv;
607 gint x, y, width, height;
608 HandleWindow *handle_window;
610 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
613 pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
614 GTK_TEXT_HANDLE_POSITION_SELECTION_START);
619 if (priv->mode == GTK_TEXT_HANDLE_MODE_NONE ||
620 (priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR &&
621 pos != GTK_TEXT_HANDLE_POSITION_CURSOR))
624 gdk_window_get_root_coords (priv->relative_to,
627 _gtk_text_handle_get_size (handle, &width, &height);
628 handle_window = &priv->windows[pos];
630 if (pos == GTK_TEXT_HANDLE_POSITION_CURSOR)
637 gdk_window_move (handle_window->window, x, y);
638 handle_window->pointing_to = *rect;
642 _gtk_text_handle_set_visible (GtkTextHandle *handle,
643 GtkTextHandlePosition pos,
646 GtkTextHandlePrivate *priv;
649 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
652 pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
653 GTK_TEXT_HANDLE_POSITION_SELECTION_START);
658 window = priv->windows[pos].window;
664 gdk_window_hide (window);
667 if (priv->mode == GTK_TEXT_HANDLE_MODE_NONE ||
668 (priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR &&
669 pos != GTK_TEXT_HANDLE_POSITION_CURSOR))
672 if (!gdk_window_is_visible (window))
673 gdk_window_show (window);
678 _gtk_text_handle_get_is_dragged (GtkTextHandle *handle,
679 GtkTextHandlePosition pos)
681 GtkTextHandlePrivate *priv;
683 g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), FALSE);
686 pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
687 GTK_TEXT_HANDLE_POSITION_SELECTION_START);
689 return priv->windows[pos].dragged;