diff options
| author | mbkma <[email protected]> | 2026-03-14 15:32:03 +0100 |
|---|---|---|
| committer | mbkma <[email protected]> | 2026-03-14 15:32:03 +0100 |
| commit | 5129f54111334e69d2cf13599a216b60923c1946 (patch) | |
| tree | 1e4c61b91e90a421ab24fd3a9bdafe0c53b7be13 | |
| parent | bcf3bd6ea32d71e57eabe9af44a9f8bc958c6ce5 (diff) | |
| download | eom-crop.tar.bz2 eom-crop.tar.xz | |
add crop featurecrop
| -rw-r--r-- | data/eom-ui.xml | 2 | ||||
| -rw-r--r-- | src/eom-image.c | 58 | ||||
| -rw-r--r-- | src/eom-image.h | 6 | ||||
| -rw-r--r-- | src/eom-scroll-view.c | 398 | ||||
| -rw-r--r-- | src/eom-scroll-view.h | 7 | ||||
| -rw-r--r-- | src/eom-window.c | 77 |
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) }, |
