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 gdk_window_set_user_data (window, priv->parent);
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 gdk_window_destroy (handle_window->window);
309 /* Create new window and apply old state */
310 handle_window->window = _gtk_text_handle_create_window (handle, pos);
314 gdk_window_show (handle_window->window);
315 _gtk_text_handle_set_position (handle, pos,
316 &handle_window->pointing_to);
321 _gtk_text_handle_update_windows (GtkTextHandle *handle)
323 GtkTextHandlePrivate *priv = handle->priv;
325 gtk_style_context_invalidate (priv->style_context);
326 _gtk_text_handle_update_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START);
327 _gtk_text_handle_update_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END);
331 gtk_text_handle_constructed (GObject *object)
333 GtkTextHandlePrivate *priv;
335 priv = GTK_TEXT_HANDLE (object)->priv;
336 g_assert (priv->parent != NULL);
338 priv->draw_signal_id =
339 g_signal_connect (priv->parent, "draw",
340 G_CALLBACK (gtk_text_handle_widget_draw),
342 priv->event_signal_id =
343 g_signal_connect (priv->parent, "event",
344 G_CALLBACK (gtk_text_handle_widget_event),
346 priv->composited_changed_id =
347 g_signal_connect_swapped (priv->parent, "composited-changed",
348 G_CALLBACK (_gtk_text_handle_update_windows),
350 priv->style_updated_id =
351 g_signal_connect_swapped (priv->parent, "style-updated",
352 G_CALLBACK (_gtk_text_handle_update_windows),
357 gtk_text_handle_finalize (GObject *object)
359 GtkTextHandlePrivate *priv;
361 priv = GTK_TEXT_HANDLE (object)->priv;
363 if (priv->relative_to)
364 g_object_unref (priv->relative_to);
366 if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window)
367 gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
369 if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window)
370 gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
372 if (g_signal_handler_is_connected (priv->parent, priv->draw_signal_id))
373 g_signal_handler_disconnect (priv->parent, priv->draw_signal_id);
375 if (g_signal_handler_is_connected (priv->parent, priv->event_signal_id))
376 g_signal_handler_disconnect (priv->parent, priv->event_signal_id);
378 if (g_signal_handler_is_connected (priv->parent, priv->composited_changed_id))
379 g_signal_handler_disconnect (priv->parent, priv->composited_changed_id);
381 if (g_signal_handler_is_connected (priv->parent, priv->style_updated_id))
382 g_signal_handler_disconnect (priv->parent, priv->style_updated_id);
384 g_object_unref (priv->style_context);
386 G_OBJECT_CLASS (_gtk_text_handle_parent_class)->finalize (object);
390 gtk_text_handle_set_property (GObject *object,
395 GtkTextHandlePrivate *priv;
396 GtkTextHandle *handle;
398 handle = GTK_TEXT_HANDLE (object);
404 priv->parent = g_value_get_object (value);
406 case PROP_RELATIVE_TO:
407 _gtk_text_handle_set_relative_to (handle,
408 g_value_get_object (value));
411 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
416 gtk_text_handle_get_property (GObject *object,
421 GtkTextHandlePrivate *priv;
423 priv = GTK_TEXT_HANDLE (object)->priv;
428 g_value_set_object (value, priv->parent);
430 case PROP_RELATIVE_TO:
431 g_value_set_object (value, priv->relative_to);
434 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
439 _gtk_text_handle_class_init (GtkTextHandleClass *klass)
441 GObjectClass *object_class = G_OBJECT_CLASS (klass);
443 object_class->constructed = gtk_text_handle_constructed;
444 object_class->finalize = gtk_text_handle_finalize;
445 object_class->set_property = gtk_text_handle_set_property;
446 object_class->get_property = gtk_text_handle_get_property;
448 signals[HANDLE_DRAGGED] =
449 g_signal_new (I_("handle-dragged"),
450 G_OBJECT_CLASS_TYPE (object_class),
452 G_STRUCT_OFFSET (GtkTextHandleClass, handle_dragged),
454 _gtk_marshal_VOID__ENUM_INT_INT,
456 GTK_TYPE_TEXT_HANDLE_POSITION,
457 G_TYPE_INT, G_TYPE_INT);
458 signals[DRAG_FINISHED] =
459 g_signal_new (I_("drag-finished"),
460 G_OBJECT_CLASS_TYPE (object_class),
461 G_SIGNAL_RUN_LAST, 0,
463 g_cclosure_marshal_VOID__ENUM,
465 GTK_TYPE_TEXT_HANDLE_POSITION);
467 g_object_class_install_property (object_class,
469 g_param_spec_object ("parent",
473 GTK_PARAM_READWRITE |
474 G_PARAM_CONSTRUCT_ONLY));
475 g_object_class_install_property (object_class,
477 g_param_spec_object ("relative-to",
479 P_("Window the coordinates are based upon"),
481 GTK_PARAM_READWRITE));
483 g_type_class_add_private (object_class, sizeof (GtkTextHandlePrivate));
487 _gtk_text_handle_init (GtkTextHandle *handle)
489 GtkTextHandlePrivate *priv;
492 handle->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (handle,
493 GTK_TYPE_TEXT_HANDLE,
494 GtkTextHandlePrivate);
496 path = gtk_widget_path_new ();
497 gtk_widget_path_append_type (path, GTK_TYPE_TEXT_HANDLE);
499 priv->style_context = gtk_style_context_new ();
500 gtk_style_context_set_path (priv->style_context, path);
501 gtk_widget_path_free (path);
505 _gtk_text_handle_new (GtkWidget *parent)
507 return g_object_new (GTK_TYPE_TEXT_HANDLE,
513 _gtk_text_handle_set_relative_to (GtkTextHandle *handle,
516 GtkTextHandlePrivate *priv;
518 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
519 g_return_if_fail (!window || GDK_IS_WINDOW (window));
523 if (priv->relative_to)
525 gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
526 gdk_window_destroy (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
527 g_object_unref (priv->relative_to);
532 priv->relative_to = g_object_ref (window);
533 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window =
534 _gtk_text_handle_create_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START);
535 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window =
536 _gtk_text_handle_create_window (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END);
537 priv->realized = TRUE;
541 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window = NULL;
542 priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window = NULL;
543 priv->relative_to = NULL;
544 priv->realized = FALSE;
547 g_object_notify (G_OBJECT (handle), "relative-to");
551 _gtk_text_handle_set_mode (GtkTextHandle *handle,
552 GtkTextHandleMode mode)
554 GtkTextHandlePrivate *priv;
556 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
560 if (priv->mode == mode)
565 case GTK_TEXT_HANDLE_MODE_CURSOR:
566 /* Only display one handle */
567 gdk_window_show (priv->windows[GTK_TEXT_HANDLE_POSITION_CURSOR].window);
568 gdk_window_hide (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
570 case GTK_TEXT_HANDLE_MODE_SELECTION:
571 /* Display both handles */
572 gdk_window_show (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
573 gdk_window_show (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
575 case GTK_TEXT_HANDLE_MODE_NONE:
577 gdk_window_hide (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].window);
578 gdk_window_hide (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].window);
584 _gtk_text_handle_update_shape (handle,
585 priv->windows[GTK_TEXT_HANDLE_POSITION_CURSOR].window,
586 GTK_TEXT_HANDLE_POSITION_CURSOR);
590 _gtk_text_handle_get_mode (GtkTextHandle *handle)
592 GtkTextHandlePrivate *priv;
594 g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), GTK_TEXT_HANDLE_MODE_NONE);
601 _gtk_text_handle_set_position (GtkTextHandle *handle,
602 GtkTextHandlePosition pos,
605 GtkTextHandlePrivate *priv;
606 gint x, y, width, height;
607 HandleWindow *handle_window;
609 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
612 pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
613 GTK_TEXT_HANDLE_POSITION_SELECTION_START);
618 if (priv->mode == GTK_TEXT_HANDLE_MODE_NONE ||
619 (priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR &&
620 pos != GTK_TEXT_HANDLE_POSITION_CURSOR))
623 gdk_window_get_root_coords (priv->relative_to,
626 _gtk_text_handle_get_size (handle, &width, &height);
627 handle_window = &priv->windows[pos];
629 if (pos == GTK_TEXT_HANDLE_POSITION_CURSOR)
636 gdk_window_move (handle_window->window, x, y);
637 handle_window->pointing_to = *rect;
641 _gtk_text_handle_set_visible (GtkTextHandle *handle,
642 GtkTextHandlePosition pos,
645 GtkTextHandlePrivate *priv;
648 g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
651 pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
652 GTK_TEXT_HANDLE_POSITION_SELECTION_START);
657 window = priv->windows[pos].window;
663 gdk_window_hide (window);
666 if (priv->mode == GTK_TEXT_HANDLE_MODE_NONE ||
667 (priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR &&
668 pos != GTK_TEXT_HANDLE_POSITION_CURSOR))
671 if (!gdk_window_is_visible (window))
672 gdk_window_show (window);
677 _gtk_text_handle_get_is_dragged (GtkTextHandle *handle,
678 GtkTextHandlePosition pos)
680 GtkTextHandlePrivate *priv;
682 g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), FALSE);
685 pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
686 GTK_TEXT_HANDLE_POSITION_SELECTION_START);
688 return priv->windows[pos].dragged;