1 /*
2 * Copyright © 2009-2018 Siyan Panayotov <contact@siyanpanayotov.com>
3 *
4 * This file is part of Viewnior.
5 *
6 * Viewnior is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Viewnior is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Viewnior. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "vnr-crop.h"
21 #include "vnr-tools.h"
22 #include "uni-utils.h"
23 #include "uni-image-view.h"
24
25 #define CROP_UI_PATH PACKAGE_DATA_DIR"/viewnior/vnr-crop-dialog.ui"
26
27 G_DEFINE_TYPE (VnrCrop, vnr_crop, G_TYPE_OBJECT);
28
29 static void spin_x_cb (GtkSpinButton *spinbutton, VnrCrop *crop);
30 static void spin_width_cb (GtkSpinButton *spinbutton, VnrCrop *crop);
31 static void spin_y_cb (GtkSpinButton *spinbutton, VnrCrop *crop);
32 static void spin_height_cb (GtkSpinButton *spinbutton, VnrCrop *crop);
33
34 static gboolean drawable_expose_cb (GtkWidget *widget,
35 GdkEventExpose *event, VnrCrop *crop);
36 static gboolean drawable_button_press_cb (GtkWidget *widget,
37 GdkEventButton *event, VnrCrop *crop);
38 static gboolean drawable_button_release_cb (GtkWidget *widget,
39 GdkEventButton *event, VnrCrop *crop);
40 static gboolean drawable_motion_cb (GtkWidget *widget,
41 GdkEventMotion *event, VnrCrop *crop);
42
43 /*************************************************************/
44 /***** Private actions ***************************************/
45 /*************************************************************/
46 static void
vnr_crop_clear_rectangle(VnrCrop * crop)47 vnr_crop_clear_rectangle(VnrCrop *crop)
48 {
49 if(crop->do_redraw)
50 gdk_draw_rectangle (GDK_DRAWABLE(gtk_widget_get_window(crop->image)), crop->gc, FALSE,
51 crop->sub_x, crop->sub_y,
52 crop->sub_width, crop->sub_height);
53 }
54
55 static void
vnr_crop_draw_rectangle(VnrCrop * crop)56 vnr_crop_draw_rectangle(VnrCrop *crop)
57 {
58 if(crop->do_redraw)
59 gdk_draw_rectangle (GDK_DRAWABLE(gtk_widget_get_window(crop->image)), crop->gc, FALSE,
60 crop->sub_x, crop->sub_y,
61 crop->sub_width, crop->sub_height);
62 }
63
64 static void
vnr_crop_check_sub_y(VnrCrop * crop)65 vnr_crop_check_sub_y(VnrCrop *crop)
66 {
67 if(gtk_spin_button_get_value(crop->spin_height)
68 + gtk_spin_button_get_value(crop->spin_y)
69 == crop->vnr_win->current_image_height)
70 {
71 crop->sub_y = (int)crop->height - (int)crop->sub_height;
72 }
73 }
74
75 static void
vnr_crop_check_sub_x(VnrCrop * crop)76 vnr_crop_check_sub_x(VnrCrop *crop)
77 {
78 if(gtk_spin_button_get_value(crop->spin_width)
79 + gtk_spin_button_get_value(crop->spin_x)
80 == crop->vnr_win->current_image_width)
81 {
82 crop->sub_x = (int)crop->width - (int)crop->sub_width;
83 }
84 }
85
86 static void
vnr_crop_update_spin_button_values(VnrCrop * crop)87 vnr_crop_update_spin_button_values (VnrCrop *crop)
88 {
89 gtk_spin_button_set_value (crop->spin_height, crop->sub_height / crop->zoom);
90 gtk_spin_button_set_value (crop->spin_width, crop->sub_width / crop->zoom);
91
92 gtk_spin_button_set_value (crop->spin_x, crop->sub_x / crop->zoom);
93 gtk_spin_button_set_value (crop->spin_y, crop->sub_y / crop->zoom);
94 }
95
96 static GtkWidget *
vnr_crop_build_dialog(VnrCrop * crop)97 vnr_crop_build_dialog (VnrCrop *crop)
98 {
99 GtkBuilder *builder;
100 GtkWidget *window;
101 GdkPixbuf *original;
102 GdkPixbuf *preview;
103 GError *error = NULL;
104
105 builder = gtk_builder_new ();
106 gtk_builder_add_from_file (builder, CROP_UI_PATH, &error);
107
108 if (error != NULL)
109 {
110 g_warning ("%s\n", error->message);
111 g_object_unref(builder);
112 return NULL;
113 }
114
115 window = GTK_WIDGET (gtk_builder_get_object (builder, "crop-dialog"));
116 gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(crop->vnr_win));
117
118 original = uni_image_view_get_pixbuf(UNI_IMAGE_VIEW(crop->vnr_win->view));
119
120 gdouble width, height;
121
122 width = crop->vnr_win->current_image_width;
123 height = crop->vnr_win->current_image_height;
124
125 vnr_tools_fit_to_size_double(&height, &width, 400,400);
126 crop->width = width;
127 crop->height = height;
128 crop->zoom = ( width/crop->vnr_win->current_image_width
129 + height/crop->vnr_win->current_image_height )/2;
130
131 preview = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (original),
132 gdk_pixbuf_get_has_alpha (original),
133 gdk_pixbuf_get_bits_per_sample (original),
134 width, height);
135
136 uni_pixbuf_scale_blend(original, preview, 0, 0, width, height, 0, 0,
137 crop->zoom, GDK_INTERP_BILINEAR, 0, 0);
138 crop->preview_pixbuf = preview;
139
140 crop->image = GTK_WIDGET (gtk_builder_get_object (builder, "main-image"));
141 gtk_widget_set_size_request(crop->image, width, height);
142
143 crop->spin_x = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "spin-x"));
144 gtk_spin_button_set_range (crop->spin_x, 0, crop->vnr_win->current_image_width - 1);
145 gtk_spin_button_set_increments (crop->spin_x, 1, 10);
146
147 crop->spin_y = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "spin-y"));
148 gtk_spin_button_set_range (crop->spin_y, 0, crop->vnr_win->current_image_height - 1);
149 gtk_spin_button_set_increments (crop->spin_y, 1, 10);
150
151 crop->spin_width = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "spin-width"));
152 gtk_spin_button_set_range (crop->spin_width, 1, crop->vnr_win->current_image_width);
153 gtk_spin_button_set_increments (crop->spin_width, 1, 10);
154 gtk_spin_button_set_value (crop->spin_width, crop->vnr_win->current_image_width);
155
156 crop->spin_height = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "spin-height"));
157 gtk_spin_button_set_range (crop->spin_height, 1, crop->vnr_win->current_image_height);
158 gtk_spin_button_set_increments (crop->spin_height, 1, 10);
159 gtk_spin_button_set_value (crop->spin_height, crop->vnr_win->current_image_height);
160
161 gtk_widget_set_events (crop->image, GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK|GDK_BUTTON_MOTION_MASK);
162
163 g_signal_connect (crop->image, "expose-event",
164 G_CALLBACK (drawable_expose_cb), crop);
165 g_signal_connect (crop->image, "button-press-event",
166 G_CALLBACK (drawable_button_press_cb), crop);
167 g_signal_connect (crop->image, "button-release-event",
168 G_CALLBACK (drawable_button_release_cb), crop);
169 g_signal_connect (crop->image, "motion-notify-event",
170 G_CALLBACK (drawable_motion_cb), crop);
171
172 g_signal_connect (crop->spin_width, "value-changed",
173 G_CALLBACK (spin_width_cb), crop);
174 g_signal_connect (crop->spin_x, "value-changed",
175 G_CALLBACK (spin_x_cb), crop);
176 g_signal_connect (crop->spin_height, "value-changed",
177 G_CALLBACK (spin_height_cb), crop);
178 g_signal_connect (crop->spin_y, "value-changed",
179 G_CALLBACK (spin_y_cb), crop);
180
181 g_object_unref(builder);
182
183 return window;
184 }
185
186 /*************************************************************/
187 /***** Private signal handlers *******************************/
188 /*************************************************************/
189 static void
spin_x_cb(GtkSpinButton * spinbutton,VnrCrop * crop)190 spin_x_cb (GtkSpinButton *spinbutton, VnrCrop *crop)
191 {
192 if(crop->drawing_rectangle)
193 return;
194
195 vnr_crop_clear_rectangle (crop);
196
197 gboolean old_do_redraw = crop->do_redraw;
198 crop->do_redraw = FALSE;
199 gtk_spin_button_set_range (crop->spin_width, 1,
200 crop->vnr_win->current_image_width
201 - gtk_spin_button_get_value(spinbutton));
202 crop->do_redraw = old_do_redraw;
203
204 crop->sub_x = gtk_spin_button_get_value (spinbutton) * crop->zoom;
205
206 vnr_crop_check_sub_x(crop);
207
208 vnr_crop_draw_rectangle (crop);
209 }
210
211 static void
spin_width_cb(GtkSpinButton * spinbutton,VnrCrop * crop)212 spin_width_cb (GtkSpinButton *spinbutton, VnrCrop *crop)
213 {
214 if(crop->drawing_rectangle)
215 return;
216
217 vnr_crop_clear_rectangle (crop);
218
219 crop->sub_width = gtk_spin_button_get_value (spinbutton) * crop->zoom;
220
221 if(crop->sub_width <1)
222 crop->sub_width = 1;
223
224 vnr_crop_draw_rectangle (crop);
225 }
226
227 static void
spin_y_cb(GtkSpinButton * spinbutton,VnrCrop * crop)228 spin_y_cb (GtkSpinButton *spinbutton, VnrCrop *crop)
229 {
230 if(crop->drawing_rectangle)
231 return;
232
233 vnr_crop_clear_rectangle (crop);
234
235 gboolean old_do_redraw = crop->do_redraw;
236 crop->do_redraw = FALSE;
237 gtk_spin_button_set_range (crop->spin_height, 1,
238 crop->vnr_win->current_image_height
239 - gtk_spin_button_get_value(spinbutton));
240 crop->do_redraw = old_do_redraw;
241
242 crop->sub_y = gtk_spin_button_get_value(spinbutton) * crop->zoom;
243
244 vnr_crop_check_sub_y(crop);
245
246 vnr_crop_draw_rectangle (crop);
247 }
248
249 static void
spin_height_cb(GtkSpinButton * spinbutton,VnrCrop * crop)250 spin_height_cb (GtkSpinButton *spinbutton, VnrCrop *crop)
251 {
252 if(crop->drawing_rectangle)
253 return;
254
255 vnr_crop_clear_rectangle (crop);
256
257 crop->sub_height = gtk_spin_button_get_value(spinbutton) * crop->zoom;
258
259 if(crop->sub_height <1)
260 crop->sub_height = 1;
261
262 vnr_crop_draw_rectangle (crop);
263 }
264
265 static gboolean
drawable_expose_cb(GtkWidget * widget,GdkEventExpose * event,VnrCrop * crop)266 drawable_expose_cb (GtkWidget *widget, GdkEventExpose *event, VnrCrop *crop)
267 {
268 GdkWindow *window = gtk_widget_get_window(widget);
269 gdk_draw_pixbuf (GDK_DRAWABLE(window), NULL, crop->preview_pixbuf,
270 0, 0, 0, 0, -1, -1, GDK_RGB_DITHER_NORMAL, 0, 0);
271
272 crop->gc = gdk_gc_new(GDK_DRAWABLE(window));
273 gdk_gc_set_function (crop->gc, GDK_INVERT);
274 gdk_gc_set_line_attributes (crop->gc,
275 2,
276 GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER);
277
278 if(crop->sub_width == -1)
279 {
280 crop->sub_x = 0;
281 crop->sub_y = 0;
282 crop->sub_width = crop->width;
283 crop->sub_height = crop->height;
284 }
285 vnr_crop_clear_rectangle (crop);
286
287 return FALSE;
288 }
289
290 static gboolean
drawable_button_press_cb(GtkWidget * widget,GdkEventButton * event,VnrCrop * crop)291 drawable_button_press_cb (GtkWidget *widget, GdkEventButton *event, VnrCrop *crop)
292 {
293 if(event->button == 1)
294 {
295 crop->drawing_rectangle = TRUE;
296 crop->start_x = event->x;
297 crop->start_y = event->y;
298 }
299
300 return FALSE;
301 }
302
303 static gboolean
drawable_button_release_cb(GtkWidget * widget,GdkEventButton * event,VnrCrop * crop)304 drawable_button_release_cb (GtkWidget *widget, GdkEventButton *event, VnrCrop *crop)
305 {
306 if(event->button == 1)
307 {
308 crop->drawing_rectangle = FALSE;
309
310 gtk_spin_button_set_range(crop->spin_width, 1,
311 (crop->width - crop->sub_x) / crop->zoom);
312 gtk_spin_button_set_range(crop->spin_height, 1,
313 (crop->height - crop->sub_y) / crop->zoom);
314
315 vnr_crop_update_spin_button_values (crop);
316 }
317 return FALSE;
318 }
319
320 static gboolean
drawable_motion_cb(GtkWidget * widget,GdkEventMotion * event,VnrCrop * crop)321 drawable_motion_cb (GtkWidget *widget, GdkEventMotion *event, VnrCrop *crop)
322 {
323 if(!crop->drawing_rectangle)
324 return FALSE;
325
326 gdouble x, y;
327 x = event->x;
328 y = event->y;
329
330 x = CLAMP(x, 0, crop->width);
331 y = CLAMP(y, 0, crop->height);
332
333 vnr_crop_clear_rectangle (crop);
334
335 if(x > crop->start_x)
336 {
337 crop->sub_x = crop->start_x;
338 crop->sub_width = x - crop->start_x;
339 }
340 else if(x == crop->start_x)
341 {
342 crop->sub_x = x;
343 crop->sub_width = 1;
344 }
345 else
346 {
347 crop->sub_x = x;
348 crop->sub_width = crop->start_x - x;
349 }
350
351 if(y > crop->start_y)
352 {
353 crop->sub_y = crop->start_y;
354 crop->sub_height = y - crop->start_y;
355 }
356 else if(y == crop->start_y)
357 {
358 crop->sub_y = y;
359 crop->sub_height = 1;
360 }
361 else
362 {
363 crop->sub_y = y;
364 crop->sub_height = crop->start_y - y;
365 }
366
367 crop->drawing_rectangle = FALSE;
368 crop->do_redraw= FALSE;
369
370 vnr_crop_update_spin_button_values (crop);
371
372 crop->drawing_rectangle = TRUE;
373 crop->do_redraw= TRUE;
374
375 vnr_crop_draw_rectangle (crop);
376
377 return FALSE;
378 }
379
380 /*************************************************************/
381 /***** Stuff that deals with the type ************************/
382 /*************************************************************/
383 static void
vnr_crop_dispose(GObject * gobject)384 vnr_crop_dispose (GObject *gobject)
385 {
386 VnrCrop *self = VNR_CROP (gobject);
387
388 if (self->preview_pixbuf != NULL)
389 g_object_unref (self->preview_pixbuf);
390 if (self->gc != NULL)
391 g_object_unref (self->gc);
392
393 G_OBJECT_CLASS (vnr_crop_parent_class)->dispose (gobject);
394 }
395
396 static void
vnr_crop_class_init(VnrCropClass * klass)397 vnr_crop_class_init (VnrCropClass *klass)
398 {
399 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
400
401 gobject_class->dispose = vnr_crop_dispose;
402 }
403
404 GObject *
vnr_crop_new(VnrWindow * vnr_win)405 vnr_crop_new (VnrWindow *vnr_win)
406 {
407 VnrCrop *crop;
408
409 crop = g_object_new (VNR_TYPE_CROP, NULL);
410
411 crop->vnr_win = vnr_win;
412
413 return (GObject *) crop;
414 }
415
416 static void
vnr_crop_init(VnrCrop * crop)417 vnr_crop_init (VnrCrop *crop)
418 {
419 crop->drawing_rectangle = FALSE;
420 crop->do_redraw = TRUE;
421
422 crop->sub_x = -1;
423 crop->sub_y = -1;
424 crop->sub_height = -1;
425 crop->sub_width = -1;
426 crop->height = -1;
427 crop->width = -1;
428
429 crop->gc = NULL;
430 crop->image = NULL;
431 crop->spin_x = NULL;
432 crop->spin_y = NULL;
433 crop->spin_width = NULL;
434 crop->spin_height = NULL;
435 crop->preview_pixbuf = NULL;
436 }
437
438 /*************************************************************/
439 /***** Actions ***********************************************/
440 /*************************************************************/
441 gboolean
vnr_crop_run(VnrCrop * crop)442 vnr_crop_run (VnrCrop *crop)
443 {
444 GtkWidget *dialog;
445 gint crop_dialog_response;
446
447 dialog = vnr_crop_build_dialog(crop);
448
449 if(dialog == NULL)
450 return FALSE;
451
452 crop_dialog_response = gtk_dialog_run (GTK_DIALOG (dialog));
453
454 crop->area.x = gtk_spin_button_get_value_as_int (crop->spin_x);
455 crop->area.y = gtk_spin_button_get_value_as_int (crop->spin_y);
456 crop->area.width = gtk_spin_button_get_value_as_int (crop->spin_width);
457 crop->area.height = gtk_spin_button_get_value_as_int (crop->spin_height);
458
459 gtk_widget_destroy (dialog);
460
461 if(crop->area.x == 0 && crop->area.y == 0
462 && crop->area.width == crop->vnr_win->current_image_width
463 && crop->area.height == crop->vnr_win->current_image_height)
464 {
465 return FALSE;
466 }
467 else
468 {
469 return (crop_dialog_response == GTK_RESPONSE_ACCEPT);
470 }
471 }
472