summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormbkma <[email protected]>2026-03-14 15:32:03 +0100
committermbkma <[email protected]>2026-03-14 15:32:03 +0100
commit5129f54111334e69d2cf13599a216b60923c1946 (patch)
tree1e4c61b91e90a421ab24fd3a9bdafe0c53b7be13
parentbcf3bd6ea32d71e57eabe9af44a9f8bc958c6ce5 (diff)
downloadeom-crop.tar.bz2
eom-crop.tar.xz
add crop featurecrop
-rw-r--r--data/eom-ui.xml2
-rw-r--r--src/eom-image.c58
-rw-r--r--src/eom-image.h6
-rw-r--r--src/eom-scroll-view.c398
-rw-r--r--src/eom-scroll-view.h7
-rw-r--r--src/eom-window.c77
6 files changed, 546 insertions, 2 deletions
diff --git a/data/eom-ui.xml b/data/eom-ui.xml
index 724e2cf..1469c1a 100644
--- a/data/eom-ui.xml
+++ b/data/eom-ui.xml
@@ -31,6 +31,8 @@
<menuitem action="EditRotate90"/>
<menuitem action="EditRotate270"/>
<separator/>
+ <menuitem action="EditCrop"/>
+ <separator/>
<menuitem action="EditMoveToTrash"/>
<separator/>
<menuitem action="EditToolbar"/>
diff --git a/src/eom-image.c b/src/eom-image.c
index e9741ef..6e6487f 100644
--- a/src/eom-image.c
+++ b/src/eom-image.c
@@ -1428,6 +1428,64 @@ eom_image_undo (EomImage *img)
priv->modified = (priv->undo_stack != NULL);
}
+void
+eom_image_crop (EomImage *img, gint x, gint y, gint width, gint height)
+{
+ EomImagePrivate *priv;
+ GdkPixbuf *subpixbuf;
+ GdkPixbuf *cropped;
+ gint orig_width;
+ gint orig_height;
+
+ g_return_if_fail (EOM_IS_IMAGE (img));
+
+ priv = img->priv;
+
+ g_return_if_fail (priv->image != NULL);
+ g_return_if_fail (x >= 0 && y >= 0 && width > 0 && height > 0);
+ g_return_if_fail (x + width <= priv->width && y + height <= priv->height);
+
+ orig_width = priv->width;
+ orig_height = priv->height;
+
+ subpixbuf = gdk_pixbuf_new_subpixbuf (priv->image, x, y, width, height);
+ cropped = gdk_pixbuf_copy (subpixbuf);
+ g_object_unref (subpixbuf);
+
+ g_object_unref (priv->image);
+ priv->image = cropped;
+ priv->width = width;
+ priv->height = height;
+ priv->modified = TRUE;
+
+ if (priv->thumbnail != NULL) {
+ gint thumb_w = gdk_pixbuf_get_width (priv->thumbnail);
+ gint thumb_h = gdk_pixbuf_get_height (priv->thumbnail);
+ gint tx = x * thumb_w / orig_width;
+ gint ty = y * thumb_h / orig_height;
+ gint tw = width * thumb_w / orig_width;
+ gint th = height * thumb_h / orig_height;
+
+ tw = CLAMP (tw, 1, thumb_w - tx);
+ th = CLAMP (th, 1, thumb_h - ty);
+
+ subpixbuf = gdk_pixbuf_new_subpixbuf (priv->thumbnail, tx, ty, tw, th);
+ cropped = gdk_pixbuf_copy (subpixbuf);
+ g_object_unref (subpixbuf);
+ g_object_unref (priv->thumbnail);
+ priv->thumbnail = cropped;
+ }
+
+ /* Crop cannot be undone via the transform mechanism, so clear the stack */
+ g_slist_free_full (priv->undo_stack, g_object_unref);
+ priv->undo_stack = NULL;
+
+ if (priv->trans != NULL) {
+ g_object_unref (priv->trans);
+ priv->trans = NULL;
+ }
+}
+
static GFile *
tmp_file_get (void)
{
diff --git a/src/eom-image.h b/src/eom-image.h
index cd77cc4..8904542 100644
--- a/src/eom-image.h
+++ b/src/eom-image.h
@@ -196,6 +196,12 @@ void eom_image_apply_display_profile (EomImage *img,
void eom_image_undo (EomImage *img);
+void eom_image_crop (EomImage *img,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+
GList *eom_image_get_supported_mime_types (void);
gboolean eom_image_is_supported_mime_type (const char *mime_type);
diff --git a/src/eom-scroll-view.c b/src/eom-scroll-view.c
index ee57c77..dac8c02 100644
--- a/src/eom-scroll-view.c
+++ b/src/eom-scroll-view.c
@@ -46,6 +46,7 @@ typedef enum {
/* Signal IDs */
enum {
SIGNAL_ZOOM_CHANGED,
+ SIGNAL_CROP_REQUESTED,
SIGNAL_LAST
};
@@ -54,9 +55,13 @@ static guint view_signals [SIGNAL_LAST] = { 0 };
typedef enum {
EOM_SCROLL_VIEW_CURSOR_NORMAL,
EOM_SCROLL_VIEW_CURSOR_HIDDEN,
- EOM_SCROLL_VIEW_CURSOR_DRAG
+ EOM_SCROLL_VIEW_CURSOR_DRAG,
+ EOM_SCROLL_VIEW_CURSOR_CROSSHAIR
} EomScrollViewCursor;
+#define CROP_HANDLE_SIZE 8
+#define CROP_HANDLE_NONE -1
+
/* Drag 'n Drop */
static GtkTargetEntry target_table[] = {
{ "text/uri-list", 0, 0},
@@ -130,6 +135,22 @@ struct _EomScrollViewPrivate {
int drag_ofs_x, drag_ofs_y;
guint dragging : 1;
+ /* crop mode */
+ gboolean crop_mode;
+ gboolean has_crop_rect;
+ gdouble crop_x1, crop_y1; /* normalized rect in image px (x1<=x2, y1<=y2) */
+ gdouble crop_x2, crop_y2;
+ gboolean crop_drawing; /* TRUE while drawing initial rect */
+ gint crop_active_handle; /* CROP_HANDLE_NONE or 0..7 */
+ gdouble crop_anchor_x, crop_anchor_y; /* image coords at draw start */
+ gdouble crop_drag_x1, crop_drag_y1; /* saved rect at handle drag start */
+ gdouble crop_drag_x2, crop_drag_y2;
+ gboolean crop_moving; /* TRUE while dragging rect to move it */
+ gdouble crop_move_anchor_x, crop_move_anchor_y; /* image coords at move start */
+ gdouble crop_move_orig_x1, crop_move_orig_y1; /* rect at move start */
+ gdouble crop_move_orig_x2, crop_move_orig_y2;
+ gdouble crop_press_wx, crop_press_wy; /* widget coords at press (for move vs. click) */
+
/* how to indicate transparency in images */
EomTransparencyStyle transp_style;
GdkRGBA transp_color;
@@ -158,6 +179,10 @@ static void view_on_drag_data_get_cb (GtkWidget *widget,
GdkDragContext*drag_context,
GtkSelectionData *data, guint info,
guint time, gpointer user_data);
+static void get_image_offsets (EomScrollView *view, gint *out_xofs, gint *out_yofs);
+static void widget_to_image (EomScrollView *view, gdouble wx, gdouble wy, gdouble *img_x, gdouble *img_y);
+static void image_to_widget (EomScrollView *view, gdouble img_x, gdouble img_y, gdouble *wx, gdouble *wy);
+static gint get_crop_handle_at (EomScrollView *view, gdouble wx, gdouble wy);
static gboolean _eom_gdk_rgba_equal0 (const GdkRGBA *a, const GdkRGBA *b);
@@ -376,6 +401,9 @@ eom_scroll_view_set_cursor (EomScrollView *view, EomScrollViewCursor new_cursor)
case EOM_SCROLL_VIEW_CURSOR_DRAG:
cursor = gdk_cursor_new_for_display (display, GDK_FLEUR);
break;
+ case EOM_SCROLL_VIEW_CURSOR_CROSSHAIR:
+ cursor = gdk_cursor_new_for_display (display, GDK_CROSSHAIR);
+ break;
}
if (cursor) {
@@ -902,6 +930,13 @@ display_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer data)
zoom = 1.0;
break;
+ case GDK_KEY_Escape:
+ if (priv->crop_mode) {
+ eom_scroll_view_set_crop_mode (view, FALSE);
+ return TRUE;
+ }
+ return FALSE;
+
default:
return FALSE;
}
@@ -941,6 +976,53 @@ eom_scroll_view_button_press_event (GtkWidget *widget, GdkEventButton *event, gp
if (priv->dragging)
return FALSE;
+ if (priv->crop_mode && event->button == 1) {
+ gdouble img_x, img_y;
+ gint handle;
+
+ widget_to_image (view, event->x, event->y, &img_x, &img_y);
+
+ if (priv->pixbuf != NULL) {
+ img_x = CLAMP (img_x, 0.0, (gdouble) gdk_pixbuf_get_width (priv->pixbuf));
+ img_y = CLAMP (img_y, 0.0, (gdouble) gdk_pixbuf_get_height (priv->pixbuf));
+ }
+
+ handle = get_crop_handle_at (view, event->x, event->y);
+
+ if (handle != CROP_HANDLE_NONE) {
+ priv->crop_active_handle = handle;
+ priv->crop_drag_x1 = priv->crop_x1;
+ priv->crop_drag_y1 = priv->crop_y1;
+ priv->crop_drag_x2 = priv->crop_x2;
+ priv->crop_drag_y2 = priv->crop_y2;
+ eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_DRAG);
+ } else if (priv->has_crop_rect &&
+ img_x >= priv->crop_x1 && img_x <= priv->crop_x2 &&
+ img_y >= priv->crop_y1 && img_y <= priv->crop_y2) {
+ priv->crop_moving = TRUE;
+ priv->crop_press_wx = event->x;
+ priv->crop_press_wy = event->y;
+ priv->crop_move_anchor_x = img_x;
+ priv->crop_move_anchor_y = img_y;
+ priv->crop_move_orig_x1 = priv->crop_x1;
+ priv->crop_move_orig_y1 = priv->crop_y1;
+ priv->crop_move_orig_x2 = priv->crop_x2;
+ priv->crop_move_orig_y2 = priv->crop_y2;
+ eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_DRAG);
+ } else {
+ priv->crop_drawing = TRUE;
+ priv->has_crop_rect = FALSE;
+ priv->crop_active_handle = CROP_HANDLE_NONE;
+ priv->crop_anchor_x = img_x;
+ priv->crop_anchor_y = img_y;
+ priv->crop_x1 = img_x;
+ priv->crop_y1 = img_y;
+ priv->crop_x2 = img_x;
+ priv->crop_y2 = img_y;
+ }
+ return TRUE;
+ }
+
switch (event->button) {
case 1:
case 2:
@@ -977,6 +1059,36 @@ eom_scroll_view_button_release_event (GtkWidget *widget, GdkEventButton *event,
view = EOM_SCROLL_VIEW (data);
priv = view->priv;
+ if (priv->crop_mode && event->button == 1) {
+ if (priv->crop_drawing) {
+ priv->crop_drawing = FALSE;
+ if (priv->crop_x2 > priv->crop_x1 + 0.5 ||
+ priv->crop_y2 > priv->crop_y1 + 0.5)
+ priv->has_crop_rect = TRUE;
+ gtk_widget_queue_draw (priv->display);
+ } else if (priv->crop_moving) {
+ gdouble moved = ABS (event->x - priv->crop_press_wx) +
+ ABS (event->y - priv->crop_press_wy);
+ priv->crop_moving = FALSE;
+ if (moved < 4.0) {
+ /* Treat as a click: apply the crop */
+ gint cw = (gint)(priv->crop_x2 - priv->crop_x1);
+ gint ch = (gint)(priv->crop_y2 - priv->crop_y1);
+ if (cw > 0 && ch > 0) {
+ g_signal_emit (view, view_signals[SIGNAL_CROP_REQUESTED], 0,
+ (gint) priv->crop_x1, (gint) priv->crop_y1, cw, ch);
+ }
+ } else {
+ eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_DRAG);
+ gtk_widget_queue_draw (priv->display);
+ }
+ } else if (priv->crop_active_handle != CROP_HANDLE_NONE) {
+ priv->crop_active_handle = CROP_HANDLE_NONE;
+ eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_CROSSHAIR);
+ }
+ return TRUE;
+ }
+
if (!priv->dragging)
return FALSE;
@@ -1080,6 +1192,85 @@ eom_scroll_view_motion_event (GtkWidget *widget, GdkEventMotion *event, gpointer
view = EOM_SCROLL_VIEW (data);
priv = view->priv;
+ if (priv->crop_mode) {
+ gdouble wx, wy, img_x, img_y;
+
+ if (event->is_hint) {
+ gdk_window_get_device_position (gtk_widget_get_window (priv->display),
+ event->device, &x, &y, &mods);
+ wx = x;
+ wy = y;
+ } else {
+ wx = event->x;
+ wy = event->y;
+ }
+
+ widget_to_image (view, wx, wy, &img_x, &img_y);
+
+ if (priv->pixbuf != NULL) {
+ img_x = CLAMP (img_x, 0.0, (gdouble) gdk_pixbuf_get_width (priv->pixbuf));
+ img_y = CLAMP (img_y, 0.0, (gdouble) gdk_pixbuf_get_height (priv->pixbuf));
+ }
+
+ if (priv->crop_drawing) {
+ priv->crop_x1 = MIN (priv->crop_anchor_x, img_x);
+ priv->crop_x2 = MAX (priv->crop_anchor_x, img_x);
+ priv->crop_y1 = MIN (priv->crop_anchor_y, img_y);
+ priv->crop_y2 = MAX (priv->crop_anchor_y, img_y);
+ gtk_widget_queue_draw (priv->display);
+ } else if (priv->crop_active_handle != CROP_HANDLE_NONE) {
+ gdouble nx1 = priv->crop_drag_x1;
+ gdouble ny1 = priv->crop_drag_y1;
+ gdouble nx2 = priv->crop_drag_x2;
+ gdouble ny2 = priv->crop_drag_y2;
+
+ switch (priv->crop_active_handle) {
+ case 0: nx1 = img_x; ny1 = img_y; break;
+ case 1: ny1 = img_y; break;
+ case 2: nx2 = img_x; ny1 = img_y; break;
+ case 3: nx1 = img_x; break;
+ case 4: nx2 = img_x; break;
+ case 5: nx1 = img_x; ny2 = img_y; break;
+ case 6: ny2 = img_y; break;
+ case 7: nx2 = img_x; ny2 = img_y; break;
+ }
+
+ priv->crop_x1 = MIN (nx1, nx2);
+ priv->crop_x2 = MAX (nx1, nx2);
+ priv->crop_y1 = MIN (ny1, ny2);
+ priv->crop_y2 = MAX (ny1, ny2);
+ gtk_widget_queue_draw (priv->display);
+ } else if (priv->crop_moving) {
+ gdouble dx = img_x - priv->crop_move_anchor_x;
+ gdouble dy = img_y - priv->crop_move_anchor_y;
+
+ if (priv->pixbuf != NULL) {
+ gdouble iw = (gdouble) gdk_pixbuf_get_width (priv->pixbuf);
+ gdouble ih = (gdouble) gdk_pixbuf_get_height (priv->pixbuf);
+ dx = CLAMP (dx, -priv->crop_move_orig_x1, iw - priv->crop_move_orig_x2);
+ dy = CLAMP (dy, -priv->crop_move_orig_y1, ih - priv->crop_move_orig_y2);
+ }
+ priv->crop_x1 = priv->crop_move_orig_x1 + dx;
+ priv->crop_y1 = priv->crop_move_orig_y1 + dy;
+ priv->crop_x2 = priv->crop_move_orig_x2 + dx;
+ priv->crop_y2 = priv->crop_move_orig_y2 + dy;
+ gtk_widget_queue_draw (priv->display);
+ } else {
+ /* Update cursor based on what is under the pointer */
+ gint handle = get_crop_handle_at (view, wx, wy);
+ if (handle != CROP_HANDLE_NONE) {
+ eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_DRAG);
+ } else if (priv->has_crop_rect &&
+ img_x >= priv->crop_x1 && img_x <= priv->crop_x2 &&
+ img_y >= priv->crop_y1 && img_y <= priv->crop_y2) {
+ eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_DRAG);
+ } else {
+ eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_CROSSHAIR);
+ }
+ }
+ return TRUE;
+ }
+
if (!priv->dragging)
return FALSE;
@@ -1216,6 +1407,158 @@ _set_hq_redraw_timeout (EomScrollView *view)
view->priv->hq_redraw_timeout_source = source;
}
+/* Crop-mode helper: compute image top-left position in widget coordinates */
+static void
+get_image_offsets (EomScrollView *view, gint *out_xofs, gint *out_yofs)
+{
+ EomScrollViewPrivate *priv = view->priv;
+ GtkAllocation allocation;
+ gint scaled_width, scaled_height;
+
+ compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height);
+ gtk_widget_get_allocation (priv->display, &allocation);
+
+ *out_xofs = (scaled_width <= allocation.width)
+ ? (allocation.width - scaled_width) / 2
+ : -priv->xofs;
+ *out_yofs = (scaled_height <= allocation.height)
+ ? (allocation.height - scaled_height) / 2
+ : -priv->yofs;
+}
+
+/* Convert widget coordinates to image pixel coordinates */
+static void
+widget_to_image (EomScrollView *view, gdouble wx, gdouble wy,
+ gdouble *img_x, gdouble *img_y)
+{
+ EomScrollViewPrivate *priv = view->priv;
+ gint xofs, yofs;
+
+ get_image_offsets (view, &xofs, &yofs);
+ *img_x = (wx - xofs) * priv->scale / priv->zoom;
+ *img_y = (wy - yofs) * priv->scale / priv->zoom;
+}
+
+/* Convert image pixel coordinates to widget coordinates */
+static void
+image_to_widget (EomScrollView *view, gdouble img_x, gdouble img_y,
+ gdouble *wx, gdouble *wy)
+{
+ EomScrollViewPrivate *priv = view->priv;
+ gint xofs, yofs;
+
+ get_image_offsets (view, &xofs, &yofs);
+ *wx = img_x * priv->zoom / priv->scale + xofs;
+ *wy = img_y * priv->zoom / priv->scale + yofs;
+}
+
+/*
+ * Return the index [0..7] of the crop handle at widget position (wx, wy),
+ * or CROP_HANDLE_NONE if none.
+ * Handle layout: 0=TL, 1=TC, 2=TR, 3=ML, 4=MR, 5=BL, 6=BC, 7=BR
+ */
+static gint
+get_crop_handle_at (EomScrollView *view, gdouble wx, gdouble wy)
+{
+ EomScrollViewPrivate *priv = view->priv;
+ gdouble wx1, wy1, wx2, wy2, mx, my;
+ gdouble hx[8], hy[8];
+ gint i;
+
+ if (!priv->has_crop_rect)
+ return CROP_HANDLE_NONE;
+
+ image_to_widget (view, priv->crop_x1, priv->crop_y1, &wx1, &wy1);
+ image_to_widget (view, priv->crop_x2, priv->crop_y2, &wx2, &wy2);
+ mx = (wx1 + wx2) / 2.0;
+ my = (wy1 + wy2) / 2.0;
+
+ hx[0] = wx1; hy[0] = wy1;
+ hx[1] = mx; hy[1] = wy1;
+ hx[2] = wx2; hy[2] = wy1;
+ hx[3] = wx1; hy[3] = my;
+ hx[4] = wx2; hy[4] = my;
+ hx[5] = wx1; hy[5] = wy2;
+ hx[6] = mx; hy[6] = wy2;
+ hx[7] = wx2; hy[7] = wy2;
+
+ for (i = 0; i < 8; i++) {
+ if (ABS (wx - hx[i]) <= CROP_HANDLE_SIZE &&
+ ABS (wy - hy[i]) <= CROP_HANDLE_SIZE)
+ return i;
+ }
+ return CROP_HANDLE_NONE;
+}
+
+/* Draw the crop selection overlay (dim, border, handles) */
+static void
+draw_crop_overlay (EomScrollView *view, cairo_t *cr)
+{
+ EomScrollViewPrivate *priv = view->priv;
+ GtkAllocation allocation;
+ gdouble wx1, wy1, wx2, wy2, mx, my;
+ gdouble hx[8], hy[8];
+ gint i;
+
+ if (!priv->has_crop_rect)
+ return;
+
+ gtk_widget_get_allocation (priv->display, &allocation);
+
+ image_to_widget (view, priv->crop_x1, priv->crop_y1, &wx1, &wy1);
+ image_to_widget (view, priv->crop_x2, priv->crop_y2, &wx2, &wy2);
+
+ /* Dim the area outside the selection using 4 rectangles */
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
+ if (wy1 > 0)
+ cairo_rectangle (cr, 0, 0, allocation.width, wy1);
+ if (wy2 < allocation.height)
+ cairo_rectangle (cr, 0, wy2, allocation.width, allocation.height - wy2);
+ if (wx1 > 0)
+ cairo_rectangle (cr, 0, wy1, wx1, wy2 - wy1);
+ if (wx2 < allocation.width)
+ cairo_rectangle (cr, wx2, wy1, allocation.width - wx2, wy2 - wy1);
+ cairo_fill (cr);
+
+ /* Selection border: black outer stroke */
+ cairo_rectangle (cr, wx1, wy1, wx2 - wx1, wy2 - wy1);
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_set_line_width (cr, 3.0);
+ cairo_stroke (cr);
+
+ /* Selection border: white inner stroke */
+ cairo_rectangle (cr, wx1, wy1, wx2 - wx1, wy2 - wy1);
+ cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
+ cairo_set_line_width (cr, 1.0);
+ cairo_stroke (cr);
+
+ /* Draw 8 resize handles */
+ mx = (wx1 + wx2) / 2.0;
+ my = (wy1 + wy2) / 2.0;
+ hx[0] = wx1; hy[0] = wy1;
+ hx[1] = mx; hy[1] = wy1;
+ hx[2] = wx2; hy[2] = wy1;
+ hx[3] = wx1; hy[3] = my;
+ hx[4] = wx2; hy[4] = my;
+ hx[5] = wx1; hy[5] = wy2;
+ hx[6] = mx; hy[6] = wy2;
+ hx[7] = wx2; hy[7] = wy2;
+
+ for (i = 0; i < 8; i++) {
+ gdouble hs = CROP_HANDLE_SIZE / 2.0;
+ /* Black border */
+ cairo_rectangle (cr, hx[i] - hs - 1, hy[i] - hs - 1,
+ CROP_HANDLE_SIZE + 2, CROP_HANDLE_SIZE + 2);
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_fill (cr);
+ /* White fill */
+ cairo_rectangle (cr, hx[i] - hs, hy[i] - hs,
+ CROP_HANDLE_SIZE, CROP_HANDLE_SIZE);
+ cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
+ cairo_fill (cr);
+ }
+}
+
static gboolean
display_draw (GtkWidget *widget, cairo_t *cr, gpointer data)
{
@@ -1289,6 +1632,7 @@ display_draw (GtkWidget *widget, cairo_t *cr, gpointer data)
* This is especially necessary for SVGs where there might
* be more image data available outside the image boundaries.
*/
+ cairo_save (cr);
cairo_rectangle (cr, xofs, yofs, scaled_width, scaled_height);
cairo_clip (cr);
@@ -1367,6 +1711,10 @@ display_draw (GtkWidget *widget, cairo_t *cr, gpointer data)
cairo_paint (cr);
}
+ cairo_restore (cr);
+
+ if (priv->crop_mode)
+ draw_crop_overlay (view, cr);
return TRUE;
}
@@ -1430,6 +1778,44 @@ eom_scroll_view_show_cursor (EomScrollView *view)
eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_NORMAL);
}
+void
+eom_scroll_view_set_crop_mode (EomScrollView *view, gboolean crop_mode)
+{
+ EomScrollViewPrivate *priv;
+
+ g_return_if_fail (EOM_IS_SCROLL_VIEW (view));
+
+ priv = view->priv;
+
+ if (priv->crop_mode == crop_mode)
+ return;
+
+ priv->crop_mode = crop_mode;
+
+ if (crop_mode) {
+ priv->has_crop_rect = FALSE;
+ priv->crop_drawing = FALSE;
+ priv->crop_active_handle = CROP_HANDLE_NONE;
+ priv->crop_moving = FALSE;
+ eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_CROSSHAIR);
+ } else {
+ priv->has_crop_rect = FALSE;
+ priv->crop_drawing = FALSE;
+ priv->crop_active_handle = CROP_HANDLE_NONE;
+ priv->crop_moving = FALSE;
+ eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_NORMAL);
+ }
+
+ gtk_widget_queue_draw (priv->display);
+}
+
+gboolean
+eom_scroll_view_get_crop_mode (EomScrollView *view)
+{
+ g_return_val_if_fail (EOM_IS_SCROLL_VIEW (view), FALSE);
+ return view->priv->crop_mode;
+}
+
/* general properties */
void
eom_scroll_view_set_zoom_upscale (EomScrollView *view, gboolean upscale)
@@ -2183,6 +2569,16 @@ eom_scroll_view_class_init (EomScrollViewClass *klass)
G_TYPE_NONE, 1,
G_TYPE_DOUBLE);
+ view_signals [SIGNAL_CROP_REQUESTED] =
+ g_signal_new ("crop-requested",
+ EOM_TYPE_SCROLL_VIEW,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EomScrollViewClass, crop_requested),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 4,
+ G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
+
widget_class->size_allocate = eom_scroll_view_size_allocate;
}
diff --git a/src/eom-scroll-view.h b/src/eom-scroll-view.h
index 2f10e3e..e98a5e6 100644
--- a/src/eom-scroll-view.h
+++ b/src/eom-scroll-view.h
@@ -25,7 +25,8 @@ struct _EomScrollView {
struct _EomScrollViewClass {
GtkGridClass parent_class;
- void (* zoom_changed) (EomScrollView *view, double zoom);
+ void (* zoom_changed) (EomScrollView *view, double zoom);
+ void (* crop_requested) (EomScrollView *view, gint x, gint y, gint width, gint height);
};
typedef enum {
@@ -67,6 +68,10 @@ gboolean eom_scroll_view_get_zoom_is_max (EomScrollView *view);
void eom_scroll_view_show_cursor (EomScrollView *view);
void eom_scroll_view_hide_cursor (EomScrollView *view);
+/* crop mode */
+void eom_scroll_view_set_crop_mode (EomScrollView *view, gboolean crop_mode);
+gboolean eom_scroll_view_get_crop_mode (EomScrollView *view);
+
G_END_DECLS
#endif /* _EOM_SCROLL_VIEW_H_ */
diff --git a/src/eom-window.c b/src/eom-window.c
index e24738b..04bf737 100644
--- a/src/eom-window.c
+++ b/src/eom-window.c
@@ -185,6 +185,8 @@ struct _EomWindowPrivate {
gboolean save_disabled;
gboolean needs_reload_confirmation;
+ gulong crop_signal_id;
+
GtkPageSetup *page_setup;
PeasExtensionSet *extensions;
@@ -3195,6 +3197,78 @@ eom_window_cmd_rotate_270 (GtkAction *action, gpointer user_data)
}
static void
+eom_window_crop_requested_cb (EomScrollView *view,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ gpointer user_data)
+{
+ EomWindow *window;
+ EomWindowPrivate *priv;
+ EomImage *image;
+
+ g_return_if_fail (EOM_IS_WINDOW (user_data));
+
+ window = EOM_WINDOW (user_data);
+ priv = window->priv;
+
+ /* Disconnect and exit crop mode */
+ if (priv->crop_signal_id) {
+ g_signal_handler_disconnect (priv->view, priv->crop_signal_id);
+ priv->crop_signal_id = 0;
+ }
+ eom_scroll_view_set_crop_mode (EOM_SCROLL_VIEW (priv->view), FALSE);
+
+ image = eom_window_get_image (window);
+ g_return_if_fail (EOM_IS_IMAGE (image));
+
+ if (width > 0 && height > 0) {
+ GtkAction *action_undo, *action_save;
+
+ eom_image_crop (image, x, y, width, height);
+ eom_image_modified (image);
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ action_undo = gtk_action_group_get_action (priv->actions_image, "EditUndo");
+ action_save = gtk_action_group_get_action (priv->actions_image, "ImageSave");
+ gtk_action_set_sensitive (action_undo, eom_image_is_modified (image));
+ if (!priv->save_disabled)
+ gtk_action_set_sensitive (action_save, eom_image_is_modified (image));
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+ }
+}
+
+static void
+eom_window_cmd_crop (GtkAction *action, gpointer user_data)
+{
+ EomWindow *window;
+ EomWindowPrivate *priv;
+
+ g_return_if_fail (EOM_IS_WINDOW (user_data));
+
+ window = EOM_WINDOW (user_data);
+ priv = window->priv;
+
+ g_return_if_fail (priv->image != NULL);
+
+ /* Clean up any stale signal connection first */
+ if (priv->crop_signal_id) {
+ g_signal_handler_disconnect (priv->view, priv->crop_signal_id);
+ priv->crop_signal_id = 0;
+ }
+
+ if (eom_scroll_view_get_crop_mode (EOM_SCROLL_VIEW (priv->view))) {
+ eom_scroll_view_set_crop_mode (EOM_SCROLL_VIEW (priv->view), FALSE);
+ } else {
+ eom_scroll_view_set_crop_mode (EOM_SCROLL_VIEW (priv->view), TRUE);
+ priv->crop_signal_id = g_signal_connect (priv->view, "crop-requested",
+ G_CALLBACK (eom_window_crop_requested_cb),
+ window);
+ }
+}
+
+static void
eom_window_cmd_wallpaper (GtkAction *action, gpointer user_data)
{
EomWindow *window;
@@ -3836,6 +3910,9 @@ static const GtkActionEntry action_entries_image[] = {
{ "EditRotate270", "object-rotate-left", N_("Rotate Counterc_lockwise"), "<ctrl><shift>r",
N_("Rotate the image 90 degrees to the left"),
G_CALLBACK (eom_window_cmd_rotate_270) },
+ { "EditCrop", "edit-cut", N_("_Crop…"), NULL,
+ N_("Crop the image to a selected region"),
+ G_CALLBACK (eom_window_cmd_crop) },
{ "ImageSetAsWallpaper", NULL, N_("Set as _Desktop Background"),
"<control>F8", N_("Set the selected image as the desktop background"),
G_CALLBACK (eom_window_cmd_wallpaper) },