gtk2/gtk/gtktexthandle.c
Alexander Larsson ebb84e8d19 TextHandle: Don't draw handles if not visible
When calling gtk_widget_draw() on the entry gtk_cairo_should_draw_window()
will return TRUE for all windows. This is used when rendering a widget to
somewhere other than the screen, and its now used for transparent widgets.
This caused the texthandle to always draw itself and terminate the draw
handler for the entry.

Instead we now only draw the markers when really visible, plus we return
FALSE to avoid stopping the entry drawing.

https://bugzilla.gnome.org/show_bug.cgi?id=687842
2013-02-07 11:11:38 +01:00

695 lines
20 KiB
C

/* 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);
gtk_widget_register_window (priv->parent, window);
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;
HandleWindow *handle_window;
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;
handle_window = &priv->windows[pos];
if (gdk_window_is_visible (handle_window->window))
_gtk_text_handle_draw (handle, cr, pos);
return FALSE;
}
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);
}
gtk_widget_unregister_window (priv->parent, handle_window->window);
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;
}