[Xfce4-commits] <xfce4-screenshooter:master> First implementation of a nice rubber banding.
Jérôme Guelfucci
jeromeg at xfce.org
Fri Aug 14 00:26:27 CEST 2009
Updating branch refs/heads/master
to 015b99d80d9f78fa1f21af707bc2bafdb02b7620 (commit)
from f9ef49c19500975d94bac2a54def9204c0ecae7b (commit)
commit 015b99d80d9f78fa1f21af707bc2bafdb02b7620
Author: Jérôme Guelfucci <jeromeg at xfce.org>
Date: Wed Aug 12 16:05:28 2009 +0200
First implementation of a nice rubber banding.
When the screen is composited, use a nicer area selection mecanism. Draw
a fullscreen transparent window which is darker than the normal view.
When the user draws a rectangle with the mouse, show the "normal" view
in this rectangle: the area which will be grabbed is normal, the rest
of the screen is darker.
A "traditionnal" rubber banding ala Thunar or Xfdesktop could be nicer,
this has to be investigated.
ChangeLog | 13 ++
lib/screenshooter-capture.c | 334 ++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 342 insertions(+), 5 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index cd6523b..b1dd942 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2009-08-12 jeromeg
+
+First implementation of a nice rubber banding.
+
+When the screen is composited, use a nicer area selection mecanism. Draw
+a fullscreen transparent window which is darker than the normal view.
+When the user draws a rectangle with the mouse, show the "normal" view
+in this rectangle: the area which will be grabbed is normal, the rest
+of the screen is darker.
+
+A "traditionnal" rubber banding ala Thunar or Xfdesktop could be nicer,
+this has to be investigated.
+
2009-08-09 jeromeg
Pressing F1 now opens the help page.
diff --git a/lib/screenshooter-capture.c b/lib/screenshooter-capture.c
index 4da3a1f..9af6409 100644
--- a/lib/screenshooter-capture.c
+++ b/lib/screenshooter-capture.c
@@ -21,13 +21,46 @@
+/* Rubberband data */
+typedef struct
+{
+ gboolean left_pressed;
+ gboolean rubber_banding;
+ gint x;
+ gint y;
+ gint x_root;
+ gint y_root;
+ GdkRectangle rectangle;
+ GdkRectangle rectangle_root;
+} RubberBandData;
+
+
+
/* Prototypes */
-static GdkWindow *get_active_window (GdkScreen *screen, gboolean *needs_unref);
-static GdkPixbuf *get_window_screenshot (GdkWindow *window, gboolean show_mouse);
-static GdkPixbuf *get_rectangle_screenshot (void);
+static GdkWindow *get_active_window (GdkScreen *screen,
+ gboolean *needs_unref);
+static GdkPixbuf *get_window_screenshot (GdkWindow *window,
+ gboolean show_mouse);
+static GdkPixbuf *get_rectangle_screenshot (void);
+static gboolean cb_key_pressed (GtkWidget *widget,
+ GdkEventKey *event,
+ gboolean *cancelled);
+static gboolean cb_expose (GtkWidget *widget,
+ GdkEventExpose *event,
+ RubberBandData *rbdata);
+static gboolean cb_button_pressed (GtkWidget *widget,
+ GdkEventButton *event,
+ RubberBandData *rbdata);
+static gboolean cb_button_released (GtkWidget *widget,
+ GdkEventButton *event,
+ RubberBandData *rbdata);
+static gboolean cb_motion_notify (GtkWidget *widget,
+ GdkEventMotion *event,
+ RubberBandData *rbdata);
+static GdkPixbuf *get_rectangle_screenshot_composited (void);
@@ -202,6 +235,295 @@ static GdkPixbuf
+/* Callbacks for the rubber banding function */
+static gboolean cb_key_pressed (GtkWidget *widget,
+ GdkEventKey *event,
+ gboolean *cancelled)
+{
+ if (event->keyval == GDK_Escape)
+ {
+ gtk_widget_hide (widget);
+ *cancelled = TRUE;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+
+static gboolean cb_expose (GtkWidget *widget,
+ GdkEventExpose *event,
+ RubberBandData *rbdata)
+{
+ GdkRectangle *rects = NULL;
+ gint n_rects = 0, i;
+
+ TRACE ("Expose event received.");
+
+ gdk_region_get_rectangles(event->region, &rects, &n_rects);
+
+ if (rbdata->rubber_banding)
+ {
+ GdkRectangle intersect;
+ cairo_t *cr;
+
+ cr = gdk_cairo_create (GDK_DRAWABLE (widget->window));
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+
+ for (i = 0; i < n_rects; ++i)
+ {
+ /* Restore the transparent background */
+ cairo_set_source_rgba (cr, 0, 0, 0, 0.8);
+ gdk_cairo_rectangle (cr, &rects[i]);
+ cairo_fill (cr);
+
+ if (!gdk_rectangle_intersect (&rects[i],
+ &rbdata->rectangle,
+ &intersect))
+ {
+ continue;
+ }
+
+ /* Paint the rubber banding rectangles */
+ cairo_set_source_rgba (cr, 1.0f, 1.0f, 1.0f, 0.0f);
+ gdk_cairo_rectangle (cr, &intersect);
+ cairo_fill (cr);
+ }
+
+ cairo_destroy(cr);
+ }
+ else
+ {
+ cairo_t *cr;
+
+ /* Draw the transparent background */
+ cr = gdk_cairo_create (GDK_DRAWABLE (widget->window));
+ cairo_set_source_rgba (cr, 0, 0, 0, 0.8);
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+
+ for (i = 0; i < n_rects; ++i)
+ {
+ gdk_cairo_rectangle (cr, &rects[i]);
+ cairo_fill (cr);
+ }
+
+ cairo_destroy(cr);
+ }
+
+ g_free (rects);
+
+ return FALSE;
+}
+
+
+
+static gboolean cb_button_pressed (GtkWidget *widget,
+ GdkEventButton *event,
+ RubberBandData *rbdata)
+{
+ if (event->button == 1)
+ {
+ TRACE ("Left button pressed");
+
+ rbdata->left_pressed = TRUE;
+ rbdata->x = event->x;
+ rbdata->y = event->y;
+ rbdata->x_root = event->x_root;
+ rbdata->y_root = event->y_root;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+
+static gboolean cb_button_released (GtkWidget *widget,
+ GdkEventButton *event,
+ RubberBandData *rbdata)
+{
+ if (event->button == 1)
+ {
+ if (rbdata->rubber_banding)
+ {
+ gtk_dialog_response (GTK_DIALOG (widget), GTK_RESPONSE_NONE);
+ return TRUE;
+ }
+ else
+ rbdata->left_pressed = rbdata->rubber_banding = FALSE;
+ }
+
+ return FALSE;
+}
+
+
+
+static gboolean cb_motion_notify (GtkWidget *widget,
+ GdkEventMotion *event,
+ RubberBandData *rbdata)
+{
+ if (rbdata->left_pressed)
+ {
+ GdkRectangle *new_rect, *new_rect_root;
+ GdkRectangle old_rect, intersect;
+ GdkRegion *region;
+
+ TRACE ("Mouse is moving with left button pressed");
+
+ new_rect = &rbdata->rectangle;
+ new_rect_root = &rbdata->rectangle_root;
+
+ if (!rbdata->rubber_banding)
+ {
+ /* This is the start of a rubber banding */
+ rbdata->rubber_banding = TRUE;
+ old_rect.x = rbdata->x;
+ old_rect.y = rbdata->y;
+ old_rect.height = old_rect.width = 1;
+ }
+ else
+ {
+ /* Rubber banding has already started, update it */
+ old_rect.x = new_rect->x;
+ old_rect.y = new_rect->y;
+ old_rect.width = new_rect->width;
+ old_rect.height = new_rect->height;
+ }
+
+ /* Get the new rubber banding rectangle */
+ new_rect->x = MIN (rbdata->x , event->x);
+ new_rect->y = MIN (rbdata->y, event->y);
+ new_rect->width = ABS (rbdata->x - event->x) + 1;
+ new_rect->height = ABS (rbdata->y - event->y) +1;
+
+ new_rect_root->x = MIN (rbdata->x_root , event->x_root);
+ new_rect_root->y = MIN (rbdata->y_root, event->y_root);
+ new_rect_root->width = ABS (rbdata->x_root - event->x_root) + 1;
+ new_rect_root->height = ABS (rbdata->y_root - event->y_root) +1;
+
+ region = gdk_region_rectangle (&old_rect);
+ gdk_region_union_with_rect (region, new_rect);
+
+ /* Try to be smart: don't send the expose event for regions which
+ * have already been painted */
+ if (gdk_rectangle_intersect (&old_rect, new_rect, &intersect)
+ && intersect.width > 2 && intersect.height > 2)
+ {
+ GdkRegion *region_intersect;
+
+ intersect.x += 1;
+ intersect.width -= 2;
+ intersect.y += 1;
+ intersect.height -= 2;
+
+ region_intersect = gdk_region_rectangle(&intersect);
+ gdk_region_subtract(region, region_intersect);
+ gdk_region_destroy(region_intersect);
+ }
+
+ gdk_window_invalidate_region (widget->window, region, TRUE);
+ gdk_region_destroy (region);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static GdkPixbuf
+*get_rectangle_screenshot_composited (void)
+{
+ GtkWidget *window;
+ RubberBandData rbdata;
+ gboolean cancelled = FALSE;
+ GdkPixbuf *screenshot;
+ GdkWindow *root;
+ GdkCursor *xhair_cursor = gdk_cursor_new (GDK_CROSSHAIR);
+
+ /* Initialize the rubber band data */
+ rbdata.left_pressed = FALSE;
+ rbdata.rubber_banding = FALSE;
+ rbdata.x = rbdata.y = 0;
+
+ /* Create the fullscreen window on which the rubber banding
+ * will be drawn. */
+ window = gtk_dialog_new ();
+ gtk_window_set_decorated (GTK_WINDOW (window), FALSE);
+ gtk_window_set_deletable (GTK_WINDOW (window), FALSE);
+ gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
+ gtk_widget_set_app_paintable (window, TRUE);
+ gtk_widget_add_events (window,
+ GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
+ GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK |
+ GDK_KEY_PRESS_MASK);
+ gtk_widget_set_colormap (window,
+ gdk_screen_get_rgba_colormap (gdk_screen_get_default ()));
+
+ /* Connect to the interesting signals */
+ g_signal_connect (window, "key-press-event",
+ (GCallback) cb_key_pressed, &cancelled);
+ g_signal_connect (window, "expose-event",
+ (GCallback) cb_expose, &rbdata);
+ g_signal_connect (window, "button-press-event",
+ (GCallback) cb_button_pressed, &rbdata);
+ g_signal_connect (window, "button-release-event",
+ (GCallback) cb_button_released, &rbdata);
+ g_signal_connect (window, "motion-notify-event",
+ (GCallback) cb_motion_notify, &rbdata);
+
+ /* This window is not managed by the window manager, we have to set everything
+ * ourselves */
+ gtk_widget_realize (window);
+ gdk_window_set_cursor (window->window, xhair_cursor);
+ gdk_window_set_override_redirect (window->window, TRUE);
+ gtk_widget_set_size_request (window,
+ gdk_screen_get_width (gdk_screen_get_default ()),
+ gdk_screen_get_height (gdk_screen_get_default ()));
+ gdk_window_raise (window->window);
+ gtk_widget_show_now (window);
+ gtk_widget_grab_focus (window);
+ gdk_flush ();
+
+ /* Grab the mouse and the keyboard to prevent any interaction with other
+ * applications */
+ gdk_keyboard_grab (window->window, FALSE, GDK_CURRENT_TIME);
+ gdk_pointer_grab (window->window, TRUE, 0, NULL, NULL, GDK_CURRENT_TIME);
+
+ gtk_dialog_run (GTK_DIALOG (window));
+ gtk_widget_destroy (window);
+ gdk_cursor_unref (xhair_cursor);
+
+ if (cancelled)
+ return NULL;
+
+ screenshot = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ TRUE,
+ 8,
+ rbdata.rectangle.width,
+ rbdata.rectangle.height);
+
+ /* Grab the screenshot on the main window */
+ root = gdk_get_default_root_window ();
+ gdk_pixbuf_get_from_drawable (screenshot, root, NULL,
+ rbdata.rectangle_root.x,
+ rbdata.rectangle_root.y,
+ 0, 0,
+ rbdata.rectangle.width,
+ rbdata.rectangle.height);
+
+ /* Ungrab the mouse and the keyboard */
+ gdk_pointer_ungrab (GDK_CURRENT_TIME);
+ gdk_keyboard_ungrab (GDK_CURRENT_TIME);
+ gdk_flush ();
+
+ return screenshot;
+}
+
+
+
static GdkPixbuf
*get_rectangle_screenshot (void)
{
@@ -502,8 +824,10 @@ GdkPixbuf *screenshooter_take_screenshot (gint region, gint delay, gboolean show
else if (region == SELECT)
{
TRACE ("Let the user select the region to screenshot");
-
- screenshot = get_rectangle_screenshot ();
+ if (!gdk_screen_is_composited (screen))
+ screenshot = get_rectangle_screenshot ();
+ else
+ screenshot = get_rectangle_screenshot_composited ();
}
More information about the Xfce4-commits
mailing list