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