Add resize grips to GtkWindow

Allow any window to display a resize grip, in the south-east or
south-west corner, depending on text direction. This is implemented
as a shaped window that gets overlayed on top of whatever content
is there. We add api that allows widgets to avoid the resize grip,
if desired.

The ::has-resize-grip property controls if a window may display
a resize grip. It will only be displayed if the window is resizable
and not maximized.

The size and visual appearance of the resize grip is under theme
control, using the resize-grip-width/height style properties and
the paint_resize_grip style function.
This commit is contained in:
Matthias Clasen 2010-10-08 01:43:03 -04:00
parent 368f0d0843
commit f6347baf64
4 changed files with 531 additions and 11 deletions

View File

@ -5051,6 +5051,11 @@ gtk_window_get_opacity
gtk_window_set_opacity
gtk_window_get_mnemonics_visible
gtk_window_set_mnemonics_visible
gtk_window_set_has_resize_grip
gtk_window_get_has_resize_grip
gtk_window_get_resize_grip_is_visible
gtk_window_get_resize_grip_area
<SUBSECTION Standard>
GTK_WINDOW
GTK_IS_WINDOW

View File

@ -4379,6 +4379,7 @@ gtk_window_get_frame_dimensions
gtk_window_get_gravity
gtk_window_get_group
gtk_window_get_has_frame
gtk_window_get_has_resize_grip
gtk_window_get_icon
gtk_window_get_icon_list
gtk_window_get_icon_name
@ -4387,6 +4388,7 @@ gtk_window_get_mnemonics_visible
gtk_window_get_modal
gtk_window_get_position
gtk_window_get_resizable
gtk_window_get_resize_grip_area
gtk_window_get_role
gtk_window_get_screen
gtk_window_get_size
@ -4423,6 +4425,7 @@ gtk_window_remove_embedded_xid
gtk_window_remove_mnemonic
gtk_window_reshow_with_initial_size
gtk_window_resize
gtk_window_resize_grip_is_visible
gtk_window_set_accept_focus
gtk_window_set_auto_startup_notification
gtk_window_set_decorated
@ -4441,6 +4444,7 @@ gtk_window_set_frame_dimensions
gtk_window_set_geometry_hints
gtk_window_set_gravity
gtk_window_set_has_frame
gtk_window_set_has_resize_grip
gtk_window_set_icon
gtk_window_set_icon_from_file
gtk_window_set_icon_list

View File

@ -58,6 +58,16 @@
* @title: GtkWindow
* @short_description: Toplevel which can contain other widgets
*
* A GtkWindow is a toplevel window which can contain other widgets.
* Windows normally have decorations that are under the control
* of the windowing system and allow the user to manipulate the window
* (resize it, move it, close it,...).
*
* GTK+ also allows windows to have a resize grip (a small area in the lower
* right or left corner) which can be clicked to reszie the window. To
* control whether a window has a resize grip, use
* gtk_window_set_has_resize_grip().
*
* <refsect2 id="GtkWindow-BUILDER-UI">
* <title>GtkWindow as GtkBuildable</title>
* <para>
@ -104,6 +114,9 @@ struct _GtkWindowPrivate
gdouble opacity;
gboolean has_resize_grip;
GdkWindow *grip_window;
gchar *startup_id;
gchar *title;
gchar *wmclass_class;
@ -116,6 +129,11 @@ struct _GtkWindowPrivate
guint frame_top;
guint keys_changed_handler;
/* Don't use this value, it's only used for determining when
* to fire notify events on the "resize-grip-visible" property.
*/
gboolean resize_grip_visible;
guint16 configure_request_count;
/* The following flags are initially TRUE (before a window is mapped).
@ -200,6 +218,8 @@ enum {
PROP_GRAVITY,
PROP_TRANSIENT_FOR,
PROP_OPACITY,
PROP_HAS_RESIZE_GRIP,
PROP_RESIZE_GRIP_VISIBLE,
/* Readonly properties */
PROP_IS_ACTIVE,
@ -306,6 +326,8 @@ static gint gtk_window_key_press_event (GtkWidget *widget,
GdkEventKey *event);
static gint gtk_window_key_release_event (GtkWidget *widget,
GdkEventKey *event);
static gint gtk_window_button_press_event (GtkWidget *widget,
GdkEventButton *event);
static gint gtk_window_enter_notify_event (GtkWidget *widget,
GdkEventCrossing *event);
static gint gtk_window_leave_notify_event (GtkWidget *widget,
@ -316,11 +338,17 @@ static gint gtk_window_focus_out_event (GtkWidget *widget,
GdkEventFocus *event);
static gint gtk_window_client_event (GtkWidget *widget,
GdkEventClient *event);
static gboolean gtk_window_state_event (GtkWidget *widget,
GdkEventWindowState *event);
static void gtk_window_check_resize (GtkContainer *container);
static gint gtk_window_focus (GtkWidget *widget,
GtkDirectionType direction);
static void gtk_window_real_set_focus (GtkWindow *window,
GtkWidget *focus);
static void gtk_window_direction_changed (GtkWidget *widget,
GtkTextDirection prev_dir);
static void gtk_window_state_changed (GtkWidget *widget,
GtkStateType previous_state);
static void gtk_window_real_activate_default (GtkWindow *window);
static void gtk_window_real_activate_focus (GtkWindow *window);
@ -378,6 +406,9 @@ static GList *icon_list_from_theme (GtkWidget *widget,
const gchar *name);
static void gtk_window_realize_icon (GtkWindow *window);
static void gtk_window_unrealize_icon (GtkWindow *window);
static void resize_grip_create_window (GtkWindow *window);
static void resize_grip_destroy_window (GtkWindow *window);
static void update_grip_visibility (GtkWindow *window);
static void gtk_window_notify_keys_changed (GtkWindow *window);
static GtkKeyHash *gtk_window_get_key_hash (GtkWindow *window);
@ -541,12 +572,16 @@ gtk_window_class_init (GtkWindowClass *klass)
widget_class->enter_notify_event = gtk_window_enter_notify_event;
widget_class->leave_notify_event = gtk_window_leave_notify_event;
widget_class->focus_in_event = gtk_window_focus_in_event;
widget_class->button_press_event = gtk_window_button_press_event;
widget_class->focus_out_event = gtk_window_focus_out_event;
widget_class->client_event = gtk_window_client_event;
widget_class->focus = gtk_window_focus;
widget_class->draw = gtk_window_draw;
widget_class->get_preferred_width = gtk_window_get_preferred_width;
widget_class->get_preferred_height = gtk_window_get_preferred_height;
widget_class->window_state_event = gtk_window_state_event;
widget_class->direction_changed = gtk_window_direction_changed;
widget_class->state_changed = gtk_window_state_changed;
container_class->check_resize = gtk_window_check_resize;
@ -804,6 +839,41 @@ gtk_window_class_init (GtkWindowClass *klass)
TRUE,
GTK_PARAM_READWRITE));
/**
* GtkWindow:has-resize-grip
*
* Whether the window has a corner resize grip.
*
* Note that the resize grip is only shown if the window is
* actually resizable and not maximized. Use
* #GtkWindow:resize-grip-visible to find out if the resize
* grip is currently shown.
*
* Since: 3.0
*/
g_object_class_install_property (gobject_class,
PROP_HAS_RESIZE_GRIP,
g_param_spec_boolean ("has-resize-grip",
P_("Resize grip"),
P_("Specifies whether the window should have a resize grip"),
TRUE,
GTK_PARAM_READWRITE));
/**
* GtkWindow: resize-grip-visible:
*
* Whether a corner resize grip is currently shown.
*
* Since: 3.0
*/
g_object_class_install_property (gobject_class,
PROP_RESIZE_GRIP_VISIBLE,
g_param_spec_boolean ("resize-grip-visible",
P_("Resize grip is visible"),
P_("Specifies whether the window's resize grip is visible."),
FALSE,
GTK_PARAM_READABLE));
/**
* GtkWindow:gravity:
@ -857,6 +927,24 @@ gtk_window_class_init (GtkWindowClass *klass)
1.0,
GTK_PARAM_READWRITE));
/* Style properties.
*/
gtk_widget_class_install_style_property (widget_class,
g_param_spec_int ("resize-grip-width",
P_("Width of resize grip"),
P_("Width of resize grip"),
0, G_MAXINT, 16, GTK_PARAM_READWRITE));
gtk_widget_class_install_style_property (widget_class,
g_param_spec_int ("resize-grip-height",
P_("Height of resize grip"),
P_("Height of resize grip"),
0, G_MAXINT, 16, GTK_PARAM_READWRITE));
/* Signals
*/
window_signals[SET_FOCUS] =
g_signal_new (I_("set-focus"),
G_TYPE_FROM_CLASS (gobject_class),
@ -1011,6 +1099,7 @@ gtk_window_init (GtkWindow *window)
priv->type_hint = GDK_WINDOW_TYPE_HINT_NORMAL;
priv->opacity = 1.0;
priv->startup_id = NULL;
priv->has_resize_grip = TRUE;
priv->mnemonics_visible = TRUE;
g_object_ref_sink (window);
@ -1047,8 +1136,7 @@ gtk_window_set_property (GObject *object,
gtk_window_set_startup_id (window, g_value_get_string (value));
break;
case PROP_RESIZABLE:
priv->resizable = g_value_get_boolean (value);
gtk_widget_queue_resize (GTK_WIDGET (window));
gtk_window_set_resizable (window, g_value_get_boolean (value));
break;
case PROP_MODAL:
gtk_window_set_modal (window, g_value_get_boolean (value));
@ -1118,6 +1206,9 @@ gtk_window_set_property (GObject *object,
case PROP_OPACITY:
gtk_window_set_opacity (window, g_value_get_double (value));
break;
case PROP_HAS_RESIZE_GRIP:
gtk_window_set_has_resize_grip (window, g_value_get_boolean (value));
break;
case PROP_MNEMONICS_VISIBLE:
gtk_window_set_mnemonics_visible (window, g_value_get_boolean (value));
break;
@ -1227,6 +1318,12 @@ gtk_window_get_property (GObject *object,
case PROP_OPACITY:
g_value_set_double (value, gtk_window_get_opacity (window));
break;
case PROP_HAS_RESIZE_GRIP:
g_value_set_boolean (value, priv->has_resize_grip);
break;
case PROP_RESIZE_GRIP_VISIBLE:
g_value_set_boolean (value, gtk_window_resize_grip_is_visible (window));
break;
case PROP_MNEMONICS_VISIBLE:
g_value_set_boolean (value, priv->mnemonics_visible);
break;
@ -4519,6 +4616,9 @@ gtk_window_map (GtkWidget *widget)
if (priv->frame)
gdk_window_show (priv->frame);
if (priv->grip_window)
gdk_window_show (priv->grip_window);
if (!disable_startup_notification)
{
/* Do we have a custom startup-notification id? */
@ -4793,6 +4893,9 @@ gtk_window_realize (GtkWidget *widget)
/* Icons */
gtk_window_realize_icon (window);
if (priv->has_resize_grip)
resize_grip_create_window (window);
}
static void
@ -4832,9 +4935,106 @@ gtk_window_unrealize (GtkWidget *widget)
/* Icons */
gtk_window_unrealize_icon (window);
if (priv->grip_window != NULL)
resize_grip_destroy_window (window);
GTK_WIDGET_CLASS (gtk_window_parent_class)->unrealize (widget);
}
static GdkWindowEdge
get_grip_edge (GtkWidget *widget)
{
return gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR ? GDK_WINDOW_EDGE_SOUTH_EAST : GDK_WINDOW_EDGE_SOUTH_WEST;
}
static void
set_grip_cursor (GtkWindow *window)
{
GtkWidget *widget = GTK_WIDGET (window);
GtkWindowPrivate *priv = window->priv;
GdkWindowEdge edge;
GdkDisplay *display;
GdkCursorType cursor_type;
GdkCursor *cursor;
if (priv->grip_window == NULL)
return;
if (gtk_widget_is_sensitive (widget))
{
edge = get_grip_edge (widget);
if (edge == GDK_WINDOW_EDGE_SOUTH_EAST)
cursor_type = GDK_BOTTOM_RIGHT_CORNER;
else
cursor_type = GDK_BOTTOM_LEFT_CORNER;
display = gtk_widget_get_display (widget);
cursor = gdk_cursor_new_for_display (display, cursor_type);
gdk_window_set_cursor (priv->grip_window, cursor);
gdk_cursor_unref (cursor);
}
else
gdk_window_set_cursor (priv->grip_window, NULL);
}
static void
set_grip_shape (GtkWindow *window)
{
GtkWindowPrivate *priv = window->priv;
cairo_region_t *region;
cairo_surface_t *surface;
cairo_t *cr;
double width, height;
if (priv->grip_window == NULL)
return;
width = gdk_window_get_width (priv->grip_window);
height = gdk_window_get_height (priv->grip_window);
surface = cairo_image_surface_create (CAIRO_FORMAT_A8, width, height);
cr = cairo_create (surface);
cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.0);
cairo_paint (cr);
cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
if (get_grip_edge (GTK_WIDGET (window)) == GDK_WINDOW_EDGE_SOUTH_EAST)
{
cairo_move_to (cr, width, 0.0);
cairo_line_to (cr, width, height);
cairo_line_to (cr, 0.0, height);
}
else
{
cairo_move_to (cr, 0.0, 0.0);
cairo_line_to (cr, width, height);
cairo_line_to (cr, 0.0, height);
}
cairo_close_path (cr);
cairo_fill (cr);
cairo_destroy (cr);
region = gdk_cairo_region_create_from_surface (surface);
cairo_surface_destroy (surface);
gdk_window_shape_combine_region (priv->grip_window, region, 0, 0);
}
static void
set_grip_position (GtkWindow *window)
{
GtkWindowPrivate *priv = window->priv;
GdkRectangle rect;
if (priv->grip_window == NULL)
return;
gtk_window_get_resize_grip_area (window, &rect);
gdk_window_raise (priv->grip_window);
gdk_window_move_resize (priv->grip_window,
rect.x, rect.y,
rect.width, rect.height);
}
static void
gtk_window_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
@ -4987,11 +5187,263 @@ gtk_window_configure_event (GtkWidget *widget,
allocation.height = event->height;
gtk_widget_set_allocation (widget, &allocation);
gdk_window_invalidate_rect (gtk_widget_get_window (widget), NULL, FALSE); // XXX - What was this for again?
_gtk_container_queue_resize (GTK_CONTAINER (widget));
return TRUE;
}
static gboolean
gtk_window_state_event (GtkWidget *widget,
GdkEventWindowState *event)
{
update_grip_visibility (GTK_WINDOW (widget));
return FALSE;
}
static void
gtk_window_direction_changed (GtkWidget *widget,
GtkTextDirection prev_dir)
{
GtkWindow *window = GTK_WINDOW (widget);
set_grip_cursor (window);
set_grip_position (window);
set_grip_shape (window);
}
static void
gtk_window_state_changed (GtkWidget *widget,
GtkStateType previous_state)
{
GtkWindow *window = GTK_WINDOW (widget);
set_grip_cursor (window);
update_grip_visibility (window);
}
static void
resize_grip_create_window (GtkWindow *window)
{
GtkWidget *widget;
GtkWindowPrivate *priv;
GdkWindowAttr attributes;
gint attributes_mask;
GdkRectangle rect;
priv = window->priv;
widget = GTK_WIDGET (window);
g_return_if_fail (gtk_widget_get_realized (widget));
g_return_if_fail (priv->grip_window == NULL);
gtk_window_get_resize_grip_area (window, &rect);
attributes.x = rect.x;
attributes.y = rect.y;
attributes.width = rect.width;
attributes.height = rect.height;
attributes.window_type = GDK_WINDOW_CHILD;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.event_mask = gtk_widget_get_events (widget) |
GDK_EXPOSURE_MASK |
GDK_BUTTON_PRESS_MASK;
attributes_mask = GDK_WA_X | GDK_WA_Y;
priv->grip_window = gdk_window_new (gtk_widget_get_window (widget),
&attributes,
attributes_mask);
gdk_window_set_user_data (priv->grip_window, widget);
gdk_window_raise (priv->grip_window);
set_grip_cursor (window);
set_grip_shape (window);
update_grip_visibility (window);
}
static void
resize_grip_destroy_window (GtkWindow *window)
{
GtkWindowPrivate *priv = window->priv;
gdk_window_set_user_data (priv->grip_window, NULL);
gdk_window_destroy (priv->grip_window);
priv->grip_window = NULL;
update_grip_visibility (window);
}
/**
* gtk_window_set_has_resize_grip:
* @window: a #GtkWindow
* @value: %TRUE to allow a resize grip
*
* Sets whether @window has a corner resize grip.
*
* Note that the resize grip is only shown if the window
* is actually resizable and not maximized. Use
* gtk_window_resize_grip_is_visible() to find out if the
* resize grip is currently shown.
*
* Since: 3.0
*/
void
gtk_window_set_has_resize_grip (GtkWindow *window,
gboolean value)
{
GtkWidget *widget = GTK_WIDGET (window);
GtkWindowPrivate *priv = window->priv;
value = value != FALSE;
if (value != priv->has_resize_grip)
{
priv->has_resize_grip = value;
gtk_widget_queue_draw (widget);
if (gtk_widget_get_realized (widget))
{
if (priv->has_resize_grip && priv->grip_window == NULL)
resize_grip_create_window (window);
else if (!priv->has_resize_grip && priv->grip_window != NULL)
resize_grip_destroy_window (window);
}
g_object_notify (G_OBJECT (window), "has-resize-grip");
}
}
static void
update_grip_visibility (GtkWindow *window)
{
GtkWindowPrivate *priv = window->priv;
gboolean val;
val = gtk_window_resize_grip_is_visible (window);
if (priv->grip_window != NULL)
{
if (val)
gdk_window_show (priv->grip_window);
else
gdk_window_hide (priv->grip_window);
}
if (priv->resize_grip_visible != val)
{
priv->resize_grip_visible = val;
g_object_notify (G_OBJECT (window), "resize-grip-visible");
}
}
/**
* gtk_window_resize_grip_is_visible:
* @window: a #GtkWindow
*
* Determines whether a resize grip is visible for the specified window.
*
* Returns %TRUE if a resize grip exists and is visible.
*
* Since: 3.0
*/
gboolean
gtk_window_resize_grip_is_visible (GtkWindow *window)
{
g_return_val_if_fail (GTK_IS_WINDOW (window), FALSE);
if (!window->priv->resizable)
return FALSE;
if (gtk_widget_get_realized (GTK_WIDGET (window)))
{
GdkWindowState state;
state = gdk_window_get_state (gtk_widget_get_window (GTK_WIDGET (window)));
if (state & GDK_WINDOW_STATE_MAXIMIZED || state & GDK_WINDOW_STATE_FULLSCREEN)
return FALSE;
}
return window->priv->has_resize_grip;
}
/**
* gtk_window_get_has_resize_grip:
* @window: a #GtkWindow
*
* Determines whether the window may has a resize grip.
*
* Returns: %TRUE if the window has a resize grip.
*
* Since: 3.0
*/
gboolean
gtk_window_get_has_resize_grip (GtkWindow *window)
{
g_return_val_if_fail (GTK_IS_WINDOW (window), FALSE);
return window->priv->has_resize_grip;
}
/**
* gtk_window_get_resize_grip_area:
* @window: a #GtkWindow
* @rect: a pointer to a #GdkRectangle which we should store the
* resize grip area.
*
* If a window has a resize grip, this will retrieve the grip
* position, width and height into the specified #GdkRectangle.
*
* Returns: %TRUE if the resize grip's area was retrieved.
*
* Since: 3.0
*/
gboolean
gtk_window_get_resize_grip_area (GtkWindow *window,
GdkRectangle *rect)
{
GtkWidget *widget = GTK_WIDGET (window);
GtkAllocation allocation;
GtkStyle *style;
gint grip_width;
gint grip_height;
g_return_val_if_fail (GTK_IS_WINDOW (window), FALSE);
if (!window->priv->has_resize_grip)
return FALSE;
gtk_widget_get_allocation (widget, &allocation);
style = gtk_widget_get_style (widget);
gtk_widget_style_get (widget,
"resize-grip-width", &grip_width,
"resize-grip-height", &grip_height,
NULL);
if (grip_width > allocation.width)
grip_width = allocation.width;
if (grip_height > allocation.height)
grip_height = allocation.height;
rect->width = grip_width;
rect->height = grip_height;
rect->y = allocation.y + allocation.height - grip_height;
if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
rect->x = allocation.x + allocation.width - grip_width;
else
rect->x = allocation.x;
return TRUE;
}
/* the accel_key and accel_mods fields of the key have to be setup
* upon calling this function. it'll then return whether that key
* is at all used as accelerator, and if so will OR in the
@ -5130,6 +5582,23 @@ gtk_window_key_release_event (GtkWidget *widget,
return handled;
}
static gint
gtk_window_button_press_event (GtkWidget *widget,
GdkEventButton *event)
{
GtkWindowPrivate *priv = GTK_WINDOW (widget)->priv;
if (event->window == priv->grip_window)
gtk_window_begin_resize_drag (GTK_WINDOW (widget),
get_grip_edge (widget),
event->button,
event->x_root,
event->y_root,
event->time);
return FALSE;
}
static void
gtk_window_real_activate_default (GtkWindow *window)
{
@ -6245,6 +6714,13 @@ gtk_window_move_resize (GtkWindow *window)
/* gtk_window_configure_event() filled in widget->allocation */
gtk_widget_size_allocate (widget, &allocation);
if (priv->grip_window != NULL)
{
set_grip_position (window);
set_grip_cursor (window);
set_grip_shape (window);
}
gdk_window_process_updates (gdk_window, TRUE);
gdk_window_configure_finished (gdk_window);
@ -6631,6 +7107,9 @@ static gboolean
gtk_window_draw (GtkWidget *widget,
cairo_t *cr)
{
GtkWindowPrivate *priv = GTK_WINDOW (widget)->priv;
gboolean ret = FALSE;
if (!gtk_widget_get_app_paintable (widget))
gtk_paint_flat_box (gtk_widget_get_style (widget),
cr,
@ -6641,9 +7120,28 @@ gtk_window_draw (GtkWidget *widget,
gtk_widget_get_allocated_height (widget));
if (GTK_WIDGET_CLASS (gtk_window_parent_class)->draw)
return GTK_WIDGET_CLASS (gtk_window_parent_class)->draw (widget, cr);
ret = GTK_WIDGET_CLASS (gtk_window_parent_class)->draw (widget, cr);
return FALSE;
if (priv->has_resize_grip &&
gtk_cairo_should_draw_window (cr, priv->grip_window))
{
GdkRectangle rect;
cairo_save (cr);
gtk_cairo_transform_to_window (cr, widget, priv->grip_window);
gtk_window_get_resize_grip_area (GTK_WINDOW (widget), &rect);
gtk_paint_resize_grip (gtk_widget_get_style (widget),
cr,
gtk_widget_get_state (widget),
widget,
"statusbar",
get_grip_edge (widget),
0, 0,
rect.width, rect.height);
cairo_restore (cr);
}
return ret;
}
/**
@ -7284,6 +7782,9 @@ gtk_window_set_resizable (GtkWindow *window,
g_object_notify (G_OBJECT (window), "resizable");
if (priv->grip_window != NULL)
update_grip_visibility (window);
gtk_widget_queue_resize_no_redraw (GTK_WIDGET (window));
}

View File

@ -338,6 +338,16 @@ GtkWidget * gtk_window_group_get_current_device_grab (GtkWindowGroup *windo
GdkDevice *device);
/* Window grips
*/
void gtk_window_set_has_resize_grip (GtkWindow *window,
gboolean value);
gboolean gtk_window_get_has_resize_grip (GtkWindow *window);
gboolean gtk_window_resize_grip_is_visible (GtkWindow *window);
gboolean gtk_window_get_resize_grip_area (GtkWindow *window,
GdkRectangle *rect);
/* --- internal functions --- */
void _gtk_window_internal_set_focus (GtkWindow *window,
GtkWidget *focus);